diff --git a/config-vanilla.json b/config-vanilla.json index b8810686..d7c21458 100644 --- a/config-vanilla.json +++ b/config-vanilla.json @@ -14,9 +14,6 @@ "unlockAllSkins": false, "fullyStockedVendors": false, "skipClanKeyCrafting": false, - "spoofMasteryRank": -1, - "relicRewardItemCountMultiplier": 1, - "nightwaveStandingMultiplier": 1, "unfaithfulBugFixes": { "ignore1999LastRegionPlayed": false, "fixXtraCheeseTimer": false, @@ -31,7 +28,7 @@ "baroAlwaysAvailable": false, "baroFullyStocked": false, "varziaFullyStocked": false, - "wolfHunt": false, + "wolfHunt": null, "orphixVenom": false, "longShadow": false, "hallowedFlame": false, @@ -41,6 +38,11 @@ "naberusNightsOverride": null, "proxyRebellion": false, "proxyRebellionRewardsOverride": 0, + "voidCorruption2025Week1": false, + "voidCorruption2025Week2": false, + "voidCorruption2025Week3": false, + "voidCorruption2025Week4": false, + "qtccAlerts": false, "galleonOfGhouls": 0, "ghoulEmergenceOverride": null, "plagueStarOverride": null, diff --git a/package-lock.json b/package-lock.json index 84daa390..39d49f9c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "license": "GNU", "dependencies": { + "body-parser": "^2.2.0", "chokidar": "^4.0.3", "crc-32": "^1.2.2", "express": "^5", @@ -17,7 +18,7 @@ "morgan": "^1.10.0", "ncp": "^2.0.0", "undici": "^7.10.0", - "warframe-public-export-plus": "^0.5.89", + "warframe-public-export-plus": "^0.5.93", "warframe-riven-info": "^0.1.2", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0", @@ -37,6 +38,7 @@ "node": ">=20.18.1" }, "optionalDependencies": { + "@types/body-parser": "^1.19.6", "@types/express": "^5", "@types/morgan": "^1.9.9", "@types/websocket": "^1.0.10", @@ -5532,9 +5534,9 @@ } }, "node_modules/warframe-public-export-plus": { - "version": "0.5.89", - "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.89.tgz", - "integrity": "sha512-a6dM1MirzofSsuv3LlRQHFLSSIGKPVSN93dcXSDmA3njsWqOGjJJdWyXqcyxxYw8rEB8CNowSHst/MUmKvKlRg==" + "version": "0.5.93", + "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.93.tgz", + "integrity": "sha512-A8LSFJoyg7sU1n4L0zhLK1g0CREh8Fxvk7eXKoT8nMTroQg6YgEw02gK0MUi9U3rWTnlaGTsXZMp/tgC7HWUKw==" }, "node_modules/warframe-riven-info": { "version": "0.1.2", diff --git a/package.json b/package.json index fadc699f..4520ffdc 100644 --- a/package.json +++ b/package.json @@ -22,11 +22,12 @@ "lint:fix": "eslint --fix --ext .ts .", "prettier": "prettier --write .", "update-translations": "cd scripts && node update-translations.cjs", - "fix": "npm run update-translations && npm run lint:fix" + "fix": "npm run update-translations && npm run prettier" }, "license": "GNU", "type": "module", "dependencies": { + "body-parser": "^2.2.0", "chokidar": "^4.0.3", "crc-32": "^1.2.2", "express": "^5", @@ -35,13 +36,14 @@ "morgan": "^1.10.0", "ncp": "^2.0.0", "undici": "^7.10.0", - "warframe-public-export-plus": "^0.5.89", + "warframe-public-export-plus": "^0.5.93", "warframe-riven-info": "^0.1.2", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0", "ws": "^8.18.2" }, "optionalDependencies": { + "@types/body-parser": "^1.19.6", "@types/express": "^5", "@types/morgan": "^1.9.9", "@types/websocket": "^1.0.10", diff --git a/src/controllers/api/abortDojoComponentController.ts b/src/controllers/api/abortDojoComponentController.ts index 803c8baa..4ad7280e 100644 --- a/src/controllers/api/abortDojoComponentController.ts +++ b/src/controllers/api/abortDojoComponentController.ts @@ -31,12 +31,13 @@ export const abortDojoComponentController: RequestHandler = async (req, res) => if (request.DecoId) { removeDojoDeco(guild, request.ComponentId, request.DecoId); + await guild.save(); + res.json(await getDojoClient(guild, 0, request.ComponentId)); } else { await removeDojoRoom(guild, request.ComponentId); + await guild.save(); + res.json(await getDojoClient(guild, 0)); } - - await guild.save(); - res.json(await getDojoClient(guild, 0, request.ComponentId)); }; interface IAbortDojoComponentRequest { diff --git a/src/controllers/api/createGuildController.ts b/src/controllers/api/createGuildController.ts index 0db8799b..fb9474c6 100644 --- a/src/controllers/api/createGuildController.ts +++ b/src/controllers/api/createGuildController.ts @@ -5,11 +5,23 @@ import { Guild, GuildMember } from "../../models/guildModel.ts"; import { createUniqueClanName, getGuildClient, giveClanKey } from "../../services/guildService.ts"; import { getInventory } from "../../services/inventoryService.ts"; import type { IInventoryChanges } from "../../types/purchaseTypes.ts"; +import { sendWsBroadcastTo } from "../../services/wsService.ts"; export const createGuildController: RequestHandler = async (req, res) => { const account = await getAccountForRequest(req); const payload = getJSONfromString(String(req.body)); + const inventory = await getInventory(account._id.toString(), "GuildId LevelKeys Recipes"); + if (inventory.GuildId) { + const guild = await Guild.findById(inventory.GuildId); + if (guild) { + res.json({ + ...(await getGuildClient(guild, account)) + }); + return; + } + } + // Remove pending applications for this account await GuildMember.deleteMany({ accountId: account._id, status: 1 }); @@ -27,7 +39,6 @@ export const createGuildController: RequestHandler = async (req, res) => { rank: 0 }); - const inventory = await getInventory(account._id.toString(), "GuildId LevelKeys Recipes"); inventory.GuildId = guild._id; const inventoryChanges: IInventoryChanges = {}; giveClanKey(inventory, inventoryChanges); @@ -37,6 +48,7 @@ export const createGuildController: RequestHandler = async (req, res) => { ...(await getGuildClient(guild, account)), InventoryChanges: inventoryChanges }); + sendWsBroadcastTo(account._id.toString(), { update_inventory: true }); }; interface ICreateGuildRequest { diff --git a/src/controllers/api/feedPrinceController.ts b/src/controllers/api/feedPrinceController.ts new file mode 100644 index 00000000..8c6bde17 --- /dev/null +++ b/src/controllers/api/feedPrinceController.ts @@ -0,0 +1,62 @@ +import type { RequestHandler } from "express"; +import { getAccountIdForRequest } from "../../services/loginService.ts"; +import { addMiscItem, getInventory } from "../../services/inventoryService.ts"; +import { getJSONfromString } from "../../helpers/stringHelpers.ts"; +import { logger } from "../../utils/logger.ts"; +import type { IInventoryChanges } from "../../types/purchaseTypes.ts"; + +export const feedPrinceController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const inventory = await getInventory(accountId, "MiscItems NokkoColony NodeIntrosCompleted"); + const payload = getJSONfromString(String(req.body)); + + switch (payload.Mode) { + case "r": { + inventory.NokkoColony ??= { + FeedLevel: 0, + JournalEntries: [] + }; + const InventoryChanges: IInventoryChanges = {}; + inventory.NokkoColony.FeedLevel += payload.Amount; + if ( + (!inventory.NodeIntrosCompleted.includes("CompletedVision1") && inventory.NokkoColony.FeedLevel > 20) || + (!inventory.NodeIntrosCompleted.includes("CompletedVision2") && inventory.NokkoColony.FeedLevel > 60) + ) { + res.json({ + FeedSucceeded: false, + FeedLevel: inventory.NokkoColony.FeedLevel - payload.Amount, + InventoryChanges + } satisfies IFeedPrinceResponse); + } else { + addMiscItem( + inventory, + "/Lotus/Types/Items/MiscItems/MushroomFood", + payload.Amount * -1, + InventoryChanges + ); + await inventory.save(); + res.json({ + FeedSucceeded: true, + FeedLevel: inventory.NokkoColony.FeedLevel, + InventoryChanges + } satisfies IFeedPrinceResponse); + } + break; + } + + default: + logger.debug(`data provided to ${req.path}: ${String(req.body)}`); + throw new Error(`unknown feedPrince mode: ${payload.Mode}`); + } +}; + +interface IFeedPrinceRequest { + Mode: string; // r + Amount: number; +} + +interface IFeedPrinceResponse { + FeedSucceeded: boolean; + FeedLevel: number; + InventoryChanges: IInventoryChanges; +} diff --git a/src/controllers/api/focusController.ts b/src/controllers/api/focusController.ts index f838a9f4..89fccdb3 100644 --- a/src/controllers/api/focusController.ts +++ b/src/controllers/api/focusController.ts @@ -1,23 +1,91 @@ import type { RequestHandler } from "express"; -import { getAccountIdForRequest } from "../../services/loginService.ts"; +import { getAccountForRequest } from "../../services/loginService.ts"; import { getInventory, addMiscItems, addEquipment, occupySlot } from "../../services/inventoryService.ts"; import type { IMiscItem, TFocusPolarity, TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts"; import { InventorySlot } from "../../types/inventoryTypes/inventoryTypes.ts"; import { logger } from "../../utils/logger.ts"; import { ExportFocusUpgrades } from "warframe-public-export-plus"; import { Inventory } from "../../models/inventoryModels/inventoryModel.ts"; +import { version_compare } from "../../helpers/inventoryHelpers.ts"; export const focusController: RequestHandler = async (req, res) => { - const accountId = await getAccountIdForRequest(req); - switch (req.query.op) { + const account = await getAccountForRequest(req); + + let op = req.query.op as string; + const focus2 = account.BuildLabel && version_compare(account.BuildLabel, "2022.04.29.12.53") < 0; + if (focus2) { + // Focus 2.0 + switch (req.query.op) { + case Focus2Operation.InstallLens: + op = "InstallLens"; + break; + case Focus2Operation.UnlockWay: + op = "UnlockWay"; + break; + case Focus2Operation.UnlockUpgrade: + op = "UnlockUpgrade"; + break; + case Focus2Operation.IncreasePool: + op = "IncreasePool"; + break; + case Focus2Operation.LevelUpUpgrade: + op = "LevelUpUpgrade"; + break; + case Focus2Operation.ActivateWay: + op = "ActivateWay"; + break; + case Focus2Operation.UpdateUpgrade: + op = "UpdateUpgrade"; + break; + case Focus2Operation.SentTrainingAmplifier: + op = "SentTrainingAmplifier"; + break; + case Focus2Operation.UnbindUpgrade: + op = "UnbindUpgrade"; + break; + case Focus2Operation.ConvertShard: + op = "ConvertShard"; + break; + } + } else { + // Focus 3.0 + switch (req.query.op) { + case Focus3Operation.InstallLens: + op = "InstallLens"; + break; + case Focus3Operation.UnlockWay: + op = "UnlockWay"; + break; + case Focus3Operation.UnlockUpgrade: + op = "UnlockUpgrade"; + break; + case Focus3Operation.LevelUpUpgrade: + op = "LevelUpUpgrade"; + break; + case Focus3Operation.ActivateWay: + op = "ActivateWay"; + break; + case Focus3Operation.SentTrainingAmplifier: + op = "SentTrainingAmplifier"; + break; + case Focus3Operation.UnbindUpgrade: + op = "UnbindUpgrade"; + break; + case Focus3Operation.ConvertShard: + op = "ConvertShard"; + break; + } + } + + switch (op) { default: logger.error("Unhandled focus op type: " + String(req.query.op)); logger.debug(String(req.body)); res.end(); break; - case FocusOperation.InstallLens: { + case "InstallLens": { const request = JSON.parse(String(req.body)) as ILensInstallRequest; - const inventory = await getInventory(accountId); + const inventory = await getInventory(account._id.toString()); const item = inventory[request.Category].id(request.WeaponId); if (item) { item.FocusLens = request.LensType; @@ -35,10 +103,10 @@ export const focusController: RequestHandler = async (req, res) => { }); break; } - case FocusOperation.UnlockWay: { + case "UnlockWay": { const focusType = (JSON.parse(String(req.body)) as IWayRequest).FocusType; const focusPolarity = focusTypeToPolarity(focusType); - const inventory = await getInventory(accountId, "FocusAbility FocusUpgrades FocusXP"); + const inventory = await getInventory(account._id.toString(), "FocusAbility FocusUpgrades FocusXP"); const cost = inventory.FocusAbility ? 50_000 : 0; inventory.FocusAbility ??= focusType; inventory.FocusUpgrades.push({ ItemType: focusType }); @@ -52,12 +120,29 @@ export const focusController: RequestHandler = async (req, res) => { }); break; } - case FocusOperation.ActivateWay: { + case "IncreasePool": { + const request = JSON.parse(String(req.body)) as IIncreasePoolRequest; + const focusPolarity = focusTypeToPolarity(request.FocusType); + const inventory = await getInventory(account._id.toString(), "FocusXP FocusCapacity"); + let cost = 0; + for (let capacity = request.CurrentTotalCapacity; capacity != request.NewTotalCapacity; ++capacity) { + cost += increasePoolCost[capacity - 5]; + } + inventory.FocusXP![focusPolarity]! -= cost; + inventory.FocusCapacity = request.NewTotalCapacity; + await inventory.save(); + res.json({ + TotalCapacity: request.NewTotalCapacity, + FocusPointCosts: { [focusPolarity]: cost } + }); + break; + } + case "ActivateWay": { const focusType = (JSON.parse(String(req.body)) as IWayRequest).FocusType; await Inventory.updateOne( { - accountOwnerId: accountId + accountOwnerId: account._id.toString() }, { FocusAbility: focusType @@ -69,13 +154,20 @@ export const focusController: RequestHandler = async (req, res) => { }); break; } - case FocusOperation.UnlockUpgrade: { + case "UnlockUpgrade": { const request = JSON.parse(String(req.body)) as IUnlockUpgradeRequest; const focusPolarity = focusTypeToPolarity(request.FocusTypes[0]); - const inventory = await getInventory(accountId); + const inventory = await getInventory(account._id.toString()); let cost = 0; for (const focusType of request.FocusTypes) { - cost += ExportFocusUpgrades[focusType].baseFocusPointCost; + if (focusType in ExportFocusUpgrades) { + cost += ExportFocusUpgrades[focusType].baseFocusPointCost; + } else if (focusType == "/Lotus/Upgrades/Focus/Power/Residual/ChannelEfficiencyFocusUpgrade") { + // Zenurik's Inner Might (Focus 2.0) + cost += 50_000; + } else { + logger.warn(`unknown focus upgrade ${focusType}, will unlock it for free`); + } inventory.FocusUpgrades.push({ ItemType: focusType, Level: 0 }); } inventory.FocusXP![focusPolarity]! -= cost; @@ -86,15 +178,20 @@ export const focusController: RequestHandler = async (req, res) => { }); break; } - case FocusOperation.LevelUpUpgrade: { + case "LevelUpUpgrade": + case "UpdateUpgrade": { const request = JSON.parse(String(req.body)) as ILevelUpUpgradeRequest; const focusPolarity = focusTypeToPolarity(request.FocusInfos[0].ItemType); - const inventory = await getInventory(accountId); + const inventory = await getInventory(account._id.toString()); let cost = 0; for (const focusUpgrade of request.FocusInfos) { cost += focusUpgrade.FocusXpCost; const focusUpgradeDb = inventory.FocusUpgrades.find(entry => entry.ItemType == focusUpgrade.ItemType)!; - focusUpgradeDb.Level = focusUpgrade.Level; + if (op == "UpdateUpgrade") { + focusUpgradeDb.IsActive = focusUpgrade.IsActive; + } else { + focusUpgradeDb.Level = focusUpgrade.Level; + } } inventory.FocusXP![focusPolarity]! -= cost; await inventory.save(); @@ -104,9 +201,9 @@ export const focusController: RequestHandler = async (req, res) => { }); break; } - case FocusOperation.SentTrainingAmplifier: { + case "SentTrainingAmplifier": { const request = JSON.parse(String(req.body)) as ISentTrainingAmplifierRequest; - const inventory = await getInventory(accountId); + const inventory = await getInventory(account._id.toString()); const inventoryChanges = addEquipment(inventory, "OperatorAmps", request.StartingWeaponType, { ModularParts: [ "/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingGrip", @@ -119,10 +216,10 @@ export const focusController: RequestHandler = async (req, res) => { res.json(inventoryChanges.OperatorAmps![0]); break; } - case FocusOperation.UnbindUpgrade: { + case "UnbindUpgrade": { const request = JSON.parse(String(req.body)) as IUnbindUpgradeRequest; const focusPolarity = focusTypeToPolarity(request.FocusTypes[0]); - const inventory = await getInventory(accountId); + const inventory = await getInventory(account._id.toString()); inventory.FocusXP![focusPolarity]! -= 750_000 * request.FocusTypes.length; addMiscItems(inventory, [ { @@ -149,7 +246,7 @@ export const focusController: RequestHandler = async (req, res) => { }); break; } - case FocusOperation.ConvertShard: { + case "ConvertShard": { const request = JSON.parse(String(req.body)) as IConvertShardRequest; // Tally XP let xp = 0; @@ -167,7 +264,7 @@ export const focusController: RequestHandler = async (req, res) => { for (const shard of request.Shards) { shard.ItemCount *= -1; } - const inventory = await getInventory(accountId); + const inventory = await getInventory(account._id.toString()); const polarity = request.Polarity; inventory.FocusXP ??= {}; inventory.FocusXP[polarity] ??= 0; @@ -179,7 +276,8 @@ export const focusController: RequestHandler = async (req, res) => { } }; -enum FocusOperation { +// Focus 3.0 +enum Focus3Operation { InstallLens = "1", UnlockWay = "2", UnlockUpgrade = "3", @@ -190,6 +288,20 @@ enum FocusOperation { ConvertShard = "9" } +// Focus 2.0 +enum Focus2Operation { + InstallLens = "1", + UnlockWay = "2", + UnlockUpgrade = "3", + IncreasePool = "4", + LevelUpUpgrade = "5", + ActivateWay = "6", + UpdateUpgrade = "7", // used to change the IsActive state, same format as ILevelUpUpgradeRequest + SentTrainingAmplifier = "9", + UnbindUpgrade = "10", + ConvertShard = "11" +} + // For UnlockWay & ActivateWay interface IWayRequest { FocusType: string; @@ -199,6 +311,13 @@ interface IUnlockUpgradeRequest { FocusTypes: string[]; } +// Focus 2.0 +interface IIncreasePoolRequest { + FocusType: string; + CurrentTotalCapacity: number; + NewTotalCapacity: number; +} + interface ILevelUpUpgradeRequest { FocusInfos: { ItemType: string; @@ -206,6 +325,7 @@ interface ILevelUpUpgradeRequest { IsUniversal: boolean; Level: number; IsActiveAbility: boolean; + IsActive?: number; // Focus 2.0 }[]; } @@ -240,3 +360,19 @@ const shardValues = { "/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantItem": 25_000, "/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantTierTwoItem": 40_000 }; + +// Starting at a capacity of 5 (Source: https://wiki.warframe.com/w/Focus_2.0) +const increasePoolCost = [ + 2576, 3099, 3638, 4190, 4755, 5331, 5918, 6514, 7120, 7734, 8357, 8988, 9626, 10271, 10923, 11582, 12247, 12918, + 13595, 14277, 14965, 15659, 16357, 17061, 17769, 18482, 19200, 19922, 20649, 21380, 22115, 22854, 23597, 24344, + 25095, 25850, 26609, 27371, 28136, 28905, 29678, 30454, 31233, 32015, 32801, 33590, 34382, 35176, 35974, 36775, + 37579, 38386, 39195, 40008, 40823, 41641, 42461, 43284, 44110, 44938, 45769, 46603, 47439, 48277, 49118, 49961, + 50807, 51655, 52505, 53357, 54212, 55069, 55929, 56790, 57654, 58520, 59388, 60258, 61130, 62005, 62881, 63759, + 64640, 65522, 66407, 67293, 68182, 69072, 69964, 70858, 71754, 72652, 73552, 74453, 75357, 76262, 77169, 78078, + 78988, 79900, 80814, 81730, 82648, 83567, 84488, 85410, 86334, 87260, 88188, 89117, 90047, 90980, 91914, 92849, + 93786, 94725, 95665, 96607, 97550, 98495, 99441, 100389, 101338, 102289, 103241, 104195, 105150, 106107, 107065, + 108024, 108985, 109948, 110911, 111877, 112843, 113811, 114780, 115751, 116723, 117696, 118671, 119647, 120624, + 121603, 122583, 123564, 124547, 125531, 126516, 127503, 128490, 129479, 130470, 131461, 132454, 133448, 134443, + 135440, 136438, 137437, 138437, 139438, 140441, 141444, 142449, 143455, 144463, 145471, 146481, 147492, 148503, + 149517 +]; diff --git a/src/controllers/api/forceRemoveItemController.ts b/src/controllers/api/forceRemoveItemController.ts new file mode 100644 index 00000000..b55b58ae --- /dev/null +++ b/src/controllers/api/forceRemoveItemController.ts @@ -0,0 +1,27 @@ +import type { RequestHandler } from "express"; +import { getAccountIdForRequest } from "../../services/loginService.ts"; +import { getJSONfromString } from "../../helpers/stringHelpers.ts"; +import { getInventory } from "../../services/inventoryService.ts"; +import type { IInventoryChanges } from "../../types/purchaseTypes.ts"; + +export const forceRemoveItemController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const inventory = await getInventory(accountId, "MiscItems"); + const body = getJSONfromString(String(req.body)); + const inventoryChanges: IInventoryChanges = {}; + for (const item of body.items) { + const index = inventory.MiscItems.findIndex(x => x.ItemType == item); + if (index != -1) { + inventoryChanges.MiscItems ??= []; + inventoryChanges.MiscItems.push({ ItemType: item, ItemCount: inventory.MiscItems[index].ItemCount * -1 }); + + inventory.MiscItems.splice(index, 1); + } + } + await inventory.save(); + res.json({ InventoryChanges: inventoryChanges }); +}; + +interface IForceRemoveItemRequest { + items: string[]; +} diff --git a/src/controllers/api/inventoryController.ts b/src/controllers/api/inventoryController.ts index 76484e6f..a0b293e0 100644 --- a/src/controllers/api/inventoryController.ts +++ b/src/controllers/api/inventoryController.ts @@ -177,7 +177,7 @@ export const inventoryController: RequestHandler = async (request, response) => } } - cleanupInventory(inventory); + await cleanupInventory(inventory); inventory.NextRefill = new Date((today + 1) * 86400000); // tomorrow at 0 UTC //await inventory.save(); @@ -348,12 +348,12 @@ export const getInventoryResponse = async ( } } - if (typeof config.spoofMasteryRank === "number" && config.spoofMasteryRank >= 0) { - inventoryResponse.PlayerLevel = config.spoofMasteryRank; + if (inventory.spoofMasteryRank && inventory.spoofMasteryRank >= 0) { + inventoryResponse.PlayerLevel = inventory.spoofMasteryRank; if (!xpBasedLevelCapDisabled) { // This client has not been patched to accept any mastery rank, need to fake the XP. inventoryResponse.XPInfo = []; - let numFrames = getExpRequiredForMr(Math.min(config.spoofMasteryRank, 5030)) / 6000; + let numFrames = getExpRequiredForMr(Math.min(inventory.spoofMasteryRank, 5030)) / 6000; while (numFrames-- > 0) { inventoryResponse.XPInfo.push({ ItemType: "/Lotus/Powersuits/Mag/Mag", @@ -461,6 +461,9 @@ export const getInventoryResponse = async ( toLegacyOid(id); } } + if (inventoryResponse.GuildId) { + toLegacyOid(inventoryResponse.GuildId); + } } } } diff --git a/src/controllers/api/missionInventoryUpdateController.ts b/src/controllers/api/missionInventoryUpdateController.ts index 3633e02a..eb912e40 100644 --- a/src/controllers/api/missionInventoryUpdateController.ts +++ b/src/controllers/api/missionInventoryUpdateController.ts @@ -129,14 +129,22 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res) res.json(deltas); } else if (missionReport.RewardInfo) { logger.debug(`classic mission completion, sending everything`); - const inventoryResponse = await getInventoryResponse(inventory, true, account.BuildLabel); + const inventoryResponse = await getInventoryResponse( + inventory, + "xpBasedLevelCapDisabled" in req.query, + account.BuildLabel + ); res.json({ InventoryJson: JSON.stringify(inventoryResponse), ...deltas } satisfies IMissionInventoryUpdateResponse); } else { logger.debug(`no reward info, assuming this wasn't a mission completion and we should just sync inventory`); - const inventoryResponse = await getInventoryResponse(inventory, true, account.BuildLabel); + const inventoryResponse = await getInventoryResponse( + inventory, + "xpBasedLevelCapDisabled" in req.query, + account.BuildLabel + ); res.json({ InventoryJson: JSON.stringify(inventoryResponse) } satisfies IMissionInventoryUpdateResponseBackToDryDock); diff --git a/src/controllers/api/nemesisController.ts b/src/controllers/api/nemesisController.ts index cc5602ba..9f4af582 100644 --- a/src/controllers/api/nemesisController.ts +++ b/src/controllers/api/nemesisController.ts @@ -1,4 +1,4 @@ -import { fromDbOid, version_compare } from "../../helpers/inventoryHelpers.ts"; +import { fromDbOid, toMongoDate, version_compare } from "../../helpers/inventoryHelpers.ts"; import type { IKnifeResponse } from "../../helpers/nemesisHelpers.ts"; import { antivirusMods, @@ -310,6 +310,26 @@ export const nemesisController: RequestHandler = async (req, res) => { res.json({ target: inventory.toJSON().Nemesis }); + } else if ((req.query.mode as string) == "t") { + const inventory = await getInventory(account._id.toString(), "LastNemesisAllySpawnTime"); + //const body = getJSONfromString(String(req.body)); + const now = new Date(Math.trunc(Date.now() / 1000) * 1000); + inventory.LastNemesisAllySpawnTime = now; + await inventory.save(); + res.json({ + NewTime: toMongoDate(now) + } satisfies IUpdateAllySpawnTimeResponse); + } else if ((req.query.mode as string) == "d") { + const inventory = await getInventory(account._id.toString(), "NemesisHistory"); + const body = getJSONfromString(String(req.body)); + for (const fp of body.nemesisFingerprints) { + const index = inventory.NemesisHistory!.findIndex(x => x.fp == fp); + if (index != -1) { + inventory.NemesisHistory!.splice(index, 1); + } + } + await inventory.save(); + res.json(body); } else if ((req.query.mode as string) == "w") { const inventory = await getInventory(account._id.toString(), "Nemesis"); //const body = getJSONfromString(String(req.body)); @@ -447,3 +467,15 @@ const consumeModCharge = ( response.UpgradeNew.push(true); } }; + +interface IRelinquishAdversariesRequest { + nemesisFingerprints: (bigint | number)[]; +} + +// interface IUpdateAllySpawnTimeRequest { +// LastSpawnTime: IMongoDate; +// } + +interface IUpdateAllySpawnTimeResponse { + NewTime: IMongoDate; +} diff --git a/src/controllers/api/removeFromGuildController.ts b/src/controllers/api/removeFromGuildController.ts index 9535d4d7..7d4917d0 100644 --- a/src/controllers/api/removeFromGuildController.ts +++ b/src/controllers/api/removeFromGuildController.ts @@ -10,6 +10,7 @@ import { import { createMessage } from "../../services/inboxService.ts"; import { getInventory } from "../../services/inventoryService.ts"; import { getAccountForRequest, getSuffixedName } from "../../services/loginService.ts"; +import { sendWsBroadcastTo } from "../../services/wsService.ts"; import { GuildPermission } from "../../types/guildTypes.ts"; import type { RequestHandler } from "express"; @@ -85,6 +86,7 @@ export const removeFromGuildController: RequestHandler = async (req, res) => { ItemToRemove: "/Lotus/Types/Keys/DojoKey", RecipeToRemove: "/Lotus/Types/Keys/DojoKeyBlueprint" }); + sendWsBroadcastTo(payload.userId, { update_inventory: true }); }; interface IRemoveFromGuildRequest { diff --git a/src/controllers/api/researchMushroomController.ts b/src/controllers/api/researchMushroomController.ts new file mode 100644 index 00000000..727fe234 --- /dev/null +++ b/src/controllers/api/researchMushroomController.ts @@ -0,0 +1,117 @@ +import type { RequestHandler } from "express"; +import { getAccountIdForRequest } from "../../services/loginService.ts"; +import { addMiscItem, getInventory } from "../../services/inventoryService.ts"; +import { getJSONfromString } from "../../helpers/stringHelpers.ts"; +import { logger } from "../../utils/logger.ts"; +import type { IJournalEntry } from "../../types/inventoryTypes/inventoryTypes.ts"; +import type { IAffiliationMods } from "../../types/purchaseTypes.ts"; + +export const researchMushroomController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const inventory = await getInventory(accountId, "MiscItems NokkoColony Affiliations"); + const payload = getJSONfromString(String(req.body)); + switch (payload.Mode) { + case "r": { + const InventoryChanges = {}; + const AffiliationMods: IAffiliationMods[] = []; + + addMiscItem(inventory, payload.MushroomItem, payload.Amount * -1, InventoryChanges); + if (payload.Convert) { + addMiscItem(inventory, "/Lotus/Types/Items/MiscItems/MushroomFood", payload.Amount, InventoryChanges); + } + + inventory.NokkoColony ??= { + FeedLevel: 0, + JournalEntries: [] + }; + + let journalEntry = inventory.NokkoColony.JournalEntries.find(x => x.EntryType == payload.MushroomItem); + if (!journalEntry) { + journalEntry = { EntryType: payload.MushroomItem, Progress: 0 }; + inventory.NokkoColony.JournalEntries.push(journalEntry); + } + + let syndicate = inventory.Affiliations.find(x => x.Tag == "NightcapJournalSyndicate"); + if (!syndicate) { + syndicate = { Tag: "NightcapJournalSyndicate", Title: 0, Standing: 0 }; + inventory.Affiliations.push(syndicate); + } + const completedBefore = inventory.NokkoColony.JournalEntries.filter( + entry => getJournalRank(entry) === 3 + ).length; + const PrevRank = syndicateTitleThresholds.reduce( + (rank, threshold, i) => (completedBefore >= threshold ? i : rank), + 0 + ); + + if (getJournalRank(journalEntry) < 3) journalEntry.Progress += payload.Amount; + + const completedAfter = inventory.NokkoColony.JournalEntries.filter( + entry => getJournalRank(entry) === 3 + ).length; + const NewRank = syndicateTitleThresholds.reduce( + (rank, threshold, i) => (completedAfter >= threshold ? i : rank), + 0 + ); + + if (NewRank > (syndicate.Title ?? 0)) { + syndicate.Title = NewRank; + AffiliationMods.push({ Tag: "NightcapJournalSyndicate", Title: NewRank }); + } + + await inventory.save(); + res.json({ + PrevRank, + NewRank, + Progress: journalEntry.Progress, + InventoryChanges, + AffiliationMods + }); + break; + } + + default: + logger.debug(`data provided to ${req.path}: ${String(req.body)}`); + throw new Error(`unknown researchMushroom mode: ${payload.Mode}`); + } +}; + +interface IResearchMushroom { + Mode: string; // r + MushroomItem: string; + Amount: number; + Convert: boolean; +} + +const journalEntriesRank: Record = { + "/Lotus/Types/Items/MushroomJournal/PlainMushroomJournalItem": 1, + "/Lotus/Types/Items/MushroomJournal/GasMushroomJournalItem": 4, + "/Lotus/Types/Items/MushroomJournal/ToxinMushroomJournalItem": 3, + "/Lotus/Types/Items/MushroomJournal/ViralMushroomJournalItem": 4, + "/Lotus/Types/Items/MushroomJournal/MagneticMushroomJournalItem": 4, + "/Lotus/Types/Items/MushroomJournal/ElectricMushroomJournalItem": 3, + "/Lotus/Types/Items/MushroomJournal/TauMushroomJournalItem": 5, + "/Lotus/Types/Items/MushroomJournal/SlashMushroomJournalItem": 3, + "/Lotus/Types/Items/MushroomJournal/BlastMushroomJournalItem": 4, + "/Lotus/Types/Items/MushroomJournal/ImpactMushroomJournalItem": 3, + "/Lotus/Types/Items/MushroomJournal/ColdMushroomJournalItem": 3, + "/Lotus/Types/Items/MushroomJournal/CorrosiveMushroomJournalItem": 4, + "/Lotus/Types/Items/MushroomJournal/PunctureMushroomJournalItem": 3, + "/Lotus/Types/Items/MushroomJournal/HeatMushroomJournalItem": 3, + "/Lotus/Types/Items/MushroomJournal/RadiationMushroomJournalItem": 4, + "/Lotus/Types/Items/MushroomJournal/VoidMushroomJournalItem": 5 +}; + +const syndicateTitleThresholds = [0, 1, 2, 6, 12, 16]; + +const getJournalRank = (journalEntry: IJournalEntry): number => { + const k = journalEntriesRank[journalEntry.EntryType]; + if (!k) return 0; + + const thresholds = [k * 1, k * 3, k * 6]; + + if (journalEntry.Progress >= thresholds[2]) return 3; + if (journalEntry.Progress >= thresholds[1]) return 2; + if (journalEntry.Progress >= thresholds[0]) return 1; + return 0; +}; diff --git a/src/controllers/api/sellController.ts b/src/controllers/api/sellController.ts index cf74ca92..b32d2aa4 100644 --- a/src/controllers/api/sellController.ts +++ b/src/controllers/api/sellController.ts @@ -77,6 +77,9 @@ export const sellController: RequestHandler = async (req, res) => { requiredFields.add("CrewShipSalvagedWeaponSkins"); } } + if (payload.Items.WeaponSkins) { + requiredFields.add("WeaponSkins"); + } const inventory = await getInventory(accountId, Array.from(requiredFields).join(" ")); // Give currency @@ -302,6 +305,11 @@ export const sellController: RequestHandler = async (req, res) => { addFusionTreasures(inventory, [parseFusionTreasure(sellItem.String, sellItem.Count * -1)]); }); } + if (payload.Items.WeaponSkins) { + payload.Items.WeaponSkins.forEach(sellItem => { + inventory.WeaponSkins.pull({ _id: sellItem.String }); + }); + } await inventory.save(); res.json({ @@ -335,6 +343,7 @@ interface ISellRequest { CrewShipWeapons?: ISellItem[]; CrewShipWeaponSkins?: ISellItem[]; FusionTreasures?: ISellItem[]; + WeaponSkins?: ISellItem[]; // SNS specific field }; SellPrice: number; SellCurrency: diff --git a/src/controllers/api/startDojoRecipeController.ts b/src/controllers/api/startDojoRecipeController.ts index bfca8b08..dd5de407 100644 --- a/src/controllers/api/startDojoRecipeController.ts +++ b/src/controllers/api/startDojoRecipeController.ts @@ -13,6 +13,7 @@ import { Types } from "mongoose"; import { ExportDojoRecipes } from "warframe-public-export-plus"; import { getAccountForRequest } from "../../services/loginService.ts"; import { getInventory } from "../../services/inventoryService.ts"; +import { fromOid } from "../../helpers/inventoryHelpers.ts"; interface IStartDojoRecipeRequest { PlacedComponent: IDojoComponentClient; @@ -50,7 +51,7 @@ export const startDojoRecipeController: RequestHandler = async (req, res) => { _id: componentId, pf: request.PlacedComponent.pf, ppf: request.PlacedComponent.ppf, - pi: new Types.ObjectId(request.PlacedComponent.pi!.$oid), + pi: new Types.ObjectId(fromOid(request.PlacedComponent.pi!)), op: request.PlacedComponent.op, pp: request.PlacedComponent.pp, DecoCapacity: room?.decoCapacity diff --git a/src/controllers/api/unlockShipFeatureController.ts b/src/controllers/api/unlockShipFeatureController.ts index fb047d8d..e24a9c2c 100644 --- a/src/controllers/api/unlockShipFeatureController.ts +++ b/src/controllers/api/unlockShipFeatureController.ts @@ -1,11 +1,20 @@ import type { RequestHandler } from "express"; -import { updateShipFeature } from "../../services/personalRoomsService.ts"; -import type { IUnlockShipFeatureRequest } from "../../types/requestTypes.ts"; -import { parseString } from "../../helpers/general.ts"; +import { getAccountIdForRequest } from "../../services/loginService.ts"; +import { getInventory } from "../../services/inventoryService.ts"; +import { getJSONfromString } from "../../helpers/stringHelpers.ts"; +import { unlockShipFeature } from "../../services/personalRoomsService.ts"; export const unlockShipFeatureController: RequestHandler = async (req, res) => { - const accountId = parseString(req.query.accountId); - const shipFeatureRequest = JSON.parse((req.body as string).toString()) as IUnlockShipFeatureRequest; - await updateShipFeature(accountId, shipFeatureRequest.Feature); + const accountId = await getAccountIdForRequest(req); + const inventory = await getInventory(accountId, "MiscItems accountOwnerId"); + const request = getJSONfromString(String(req.body)); + + await unlockShipFeature(inventory, request.Feature); + await inventory.save(); res.send([]); }; +interface IUnlockShipFeatureRequest { + Feature: string; + KeyChain: string; + ChainStage: number; +} diff --git a/src/controllers/api/updateChallengeProgressController.ts b/src/controllers/api/updateChallengeProgressController.ts index 7d43e2f6..08ab55e1 100644 --- a/src/controllers/api/updateChallengeProgressController.ts +++ b/src/controllers/api/updateChallengeProgressController.ts @@ -14,7 +14,7 @@ export const updateChallengeProgressController: RequestHandler = async (req, res const inventory = await getInventory( account._id.toString(), - "ChallengesFixVersion ChallengeProgress SeasonChallengeHistory Affiliations CalendarProgress" + "ChallengesFixVersion ChallengeProgress SeasonChallengeHistory Affiliations CalendarProgress nightwaveStandingMultiplier" ); let affiliationMods: IAffiliationMods[] = []; if (challenges.ChallengeProgress) { diff --git a/src/controllers/custom/addVaultDecoRecipeController.ts b/src/controllers/custom/addVaultDecoRecipeController.ts deleted file mode 100644 index 0ea06594..00000000 --- a/src/controllers/custom/addVaultDecoRecipeController.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { getAccountIdForRequest } from "../../services/loginService.ts"; -import { getInventory } from "../../services/inventoryService.ts"; -import type { RequestHandler } from "express"; -import { getGuildForRequestEx, hasGuildPermission } from "../../services/guildService.ts"; -import { GuildPermission } from "../../types/guildTypes.ts"; -import type { ITypeCount } from "../../types/commonTypes.ts"; - -export const addVaultDecoRecipeController: RequestHandler = async (req, res) => { - const accountId = await getAccountIdForRequest(req); - const requests = req.body as ITypeCount[]; - const inventory = await getInventory(accountId, "GuildId"); - const guild = await getGuildForRequestEx(req, inventory); - if (!(await hasGuildPermission(guild, accountId, GuildPermission.Architect))) { - res.status(400).send("-1").end(); - return; - } - guild.VaultDecoRecipes ??= []; - for (const request of requests) { - const index = guild.VaultDecoRecipes.findIndex(x => x.ItemType === request.ItemType); - - if (index == -1) { - guild.VaultDecoRecipes.push({ - ItemType: request.ItemType, - ItemCount: request.ItemCount - }); - } else { - guild.VaultDecoRecipes[index].ItemCount += request.ItemCount; - - if (guild.VaultDecoRecipes[index].ItemCount < 1) { - guild.VaultDecoRecipes.splice(index, 1); - } - } - } - await guild.save(); - res.end(); -}; diff --git a/src/controllers/custom/addVaultTypeCountController.ts b/src/controllers/custom/addVaultTypeCountController.ts new file mode 100644 index 00000000..0d3acfde --- /dev/null +++ b/src/controllers/custom/addVaultTypeCountController.ts @@ -0,0 +1,43 @@ +import { getAccountIdForRequest } from "../../services/loginService.ts"; +import { getInventory } from "../../services/inventoryService.ts"; +import type { RequestHandler } from "express"; +import { getGuildForRequestEx, hasGuildPermission } from "../../services/guildService.ts"; +import { GuildPermission } from "../../types/guildTypes.ts"; +import type { ITypeCount } from "../../types/commonTypes.ts"; + +export const addVaultTypeCountController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const { vaultType, items } = req.body as { + vaultType: keyof typeof vaultConfig; + items: ITypeCount[]; + }; + const inventory = await getInventory(accountId, "GuildId"); + const guild = await getGuildForRequestEx(req, inventory); + if (!(await hasGuildPermission(guild, accountId, vaultConfig[vaultType]))) { + res.status(400).send("-1").end(); + return; + } + guild[vaultType] ??= []; + for (const item of items) { + const index = guild[vaultType].findIndex(x => x.ItemType === item.ItemType); + if (index === -1) { + guild[vaultType].push({ + ItemType: item.ItemType, + ItemCount: item.ItemCount + }); + } else { + guild[vaultType][index].ItemCount += item.ItemCount; + if (guild[vaultType][index].ItemCount < 1) { + guild[vaultType].splice(index, 1); + } + } + } + + await guild.save(); + res.end(); +}; +const vaultConfig = { + VaultShipDecorations: GuildPermission.Treasurer, + VaultMiscItems: GuildPermission.Treasurer, + VaultDecoRecipes: GuildPermission.Architect +} as const; diff --git a/src/controllers/custom/getItemListsController.ts b/src/controllers/custom/getItemListsController.ts index 2aa3f120..cea2c38f 100644 --- a/src/controllers/custom/getItemListsController.ts +++ b/src/controllers/custom/getItemListsController.ts @@ -38,6 +38,7 @@ interface ListedItem { parazon?: boolean; alwaysAvailable?: boolean; maxLevelCap?: number; + eligibleForVault?: boolean; } interface ItemLists { @@ -66,6 +67,7 @@ interface ItemLists { VaultDecoRecipes: ListedItem[]; FlavourItems: ListedItem[]; ShipDecorations: ListedItem[]; + WeaponSkins: ListedItem[]; //circuitGameModes: ListedItem[]; } @@ -107,7 +109,8 @@ const getItemListsController: RequestHandler = (req, response) => { TechProjects: [], VaultDecoRecipes: [], FlavourItems: [], - ShipDecorations: [] + ShipDecorations: [], + WeaponSkins: [] /*circuitGameModes: [ { uniqueName: "Survival", @@ -139,6 +142,12 @@ const getItemListsController: RequestHandler = (req, response) => { } ]*/ }; + const eligibleForVault = new Set([ + ...Object.values(ExportDojoRecipes.research).flatMap(r => r.ingredients.map(i => i.ItemType)), + ...Object.values(ExportDojoRecipes.fabrications).flatMap(f => f.ingredients.map(i => i.ItemType)), + ...Object.values(ExportDojoRecipes.rooms).flatMap(r => r.ingredients.map(i => i.ItemType)), + ...Object.values(ExportDojoRecipes.decos).flatMap(d => d.ingredients.map(i => i.ItemType)) + ]); for (const [uniqueName, item] of Object.entries(ExportWarframes)) { res[item.productCategory].push({ uniqueName, @@ -244,7 +253,8 @@ const getItemListsController: RequestHandler = (req, response) => { res.miscitems.push({ uniqueName: uniqueName, name: name, - subtype: "Resource" + subtype: "Resource", + ...(eligibleForVault.has(uniqueName) && { eligibleForVault: true }) }); } } @@ -298,10 +308,18 @@ const getItemListsController: RequestHandler = (req, response) => { }); } for (const [uniqueName, item] of Object.entries(ExportCustoms)) { - res.miscitems.push({ - uniqueName: uniqueName, - name: getString(item.name, lang) - }); + if ( + item.productCategory == "WeaponSkins" && + !uniqueName.startsWith("/Lotus/Types/Game/Lotus") && // Base Items + !uniqueName.endsWith("ProjectileSkin") && // UnrealTournament ProjectileSkins + !uniqueName.endsWith("Coat") // Frost Prime stuff + ) { + res.WeaponSkins.push({ + uniqueName: uniqueName, + name: getString(item.name, lang), + alwaysAvailable: item.alwaysAvailable + }); + } } for (const [uniqueName, upgrade] of Object.entries(ExportUpgrades)) { diff --git a/src/controllers/custom/manageQuestsController.ts b/src/controllers/custom/manageQuestsController.ts index 25c80cd0..34629e3b 100644 --- a/src/controllers/custom/manageQuestsController.ts +++ b/src/controllers/custom/manageQuestsController.ts @@ -136,7 +136,7 @@ export const manageQuestsController: RequestHandler = async (req, res) => { if (currentStage + 1 == questManifest.chainStages?.length) { logger.debug(`Trying to complete last stage with nextStage, calling completeQuest instead`); - await completeQuest(inventory, questKey.ItemType); + await completeQuest(inventory, questKey.ItemType, true); } else { if (run > 0) { questKey.Progress[currentStage + 1].c = run; diff --git a/src/controllers/custom/removeIsNewController.ts b/src/controllers/custom/removeIsNewController.ts new file mode 100644 index 00000000..e63a0d0a --- /dev/null +++ b/src/controllers/custom/removeIsNewController.ts @@ -0,0 +1,24 @@ +import { getInventory } from "../../services/inventoryService.ts"; +import { getAccountIdForRequest } from "../../services/loginService.ts"; +import type { RequestHandler } from "express"; +import { equipmentKeys } from "../../types/inventoryTypes/inventoryTypes.ts"; +import { broadcastInventoryUpdate } from "../../services/wsService.ts"; + +export const removeIsNewController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const filteredEquipmentKeys = equipmentKeys.filter(k => k !== "CrewShipWeapons" && k !== "CrewShipSalvagedWeapons"); + const inventory = await getInventory(accountId, [...filteredEquipmentKeys, "WeaponSkins"].join(" ")); + for (const key of filteredEquipmentKeys) { + if (key in inventory) { + for (const equipment of inventory[key]) { + if (equipment.IsNew) equipment.IsNew = false; + } + } + } + for (const equipment of inventory.WeaponSkins) { + if (equipment.IsNew) equipment.IsNew = false; + } + await inventory.save(); + res.end(); + broadcastInventoryUpdate(req); +}; diff --git a/src/controllers/custom/setAccountCheatController.ts b/src/controllers/custom/setAccountCheatController.ts index 586cebb6..efd7f6c8 100644 --- a/src/controllers/custom/setAccountCheatController.ts +++ b/src/controllers/custom/setAccountCheatController.ts @@ -1,6 +1,6 @@ import { getInventory } from "../../services/inventoryService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts"; -import { sendWsBroadcastTo } from "../../services/wsService.ts"; +import { sendWsBroadcastEx, sendWsBroadcastTo } from "../../services/wsService.ts"; import type { IAccountCheats } from "../../types/inventoryTypes/inventoryTypes.ts"; import type { RequestHandler } from "express"; import { logger } from "../../utils/logger.ts"; @@ -20,6 +20,8 @@ export const setAccountCheatController: RequestHandler = async (req, res) => { res.end(); if (["infiniteCredits", "infinitePlatinum", "infiniteEndo", "infiniteRegalAya"].indexOf(payload.key) != -1) { sendWsBroadcastTo(accountId, { update_inventory: true, sync_inventory: true }); + } else { + sendWsBroadcastEx({ update_inventory: true }, accountId, parseInt(String(req.query.wsid))); } }; diff --git a/src/controllers/custom/tunablesController.ts b/src/controllers/custom/tunablesController.ts index 87419fcc..7405075a 100644 --- a/src/controllers/custom/tunablesController.ts +++ b/src/controllers/custom/tunablesController.ts @@ -1,16 +1,9 @@ import type { RequestHandler } from "express"; +import type { ITunables } from "../../types/bootstrapperTypes.ts"; // This endpoint is specific to the OpenWF Bootstrapper: https://openwf.io/bootstrapper-manual -interface ITunables { - prohibit_skip_mission_start_timer?: boolean; - prohibit_fov_override?: boolean; - prohibit_freecam?: boolean; - prohibit_teleport?: boolean; - prohibit_scripts?: boolean; -} - -const tunablesController: RequestHandler = (_req, res) => { +export const tunablesController: RequestHandler = (_req, res) => { const tunables: ITunables = {}; //tunables.prohibit_skip_mission_start_timer = true; //tunables.prohibit_fov_override = true; @@ -19,5 +12,3 @@ const tunablesController: RequestHandler = (_req, res) => { //tunables.prohibit_scripts = true; res.json(tunables); }; - -export { tunablesController }; diff --git a/src/controllers/dynamic/getProfileViewingDataController.ts b/src/controllers/dynamic/getProfileViewingDataController.ts index 808c3242..40e57c58 100644 --- a/src/controllers/dynamic/getProfileViewingDataController.ts +++ b/src/controllers/dynamic/getProfileViewingDataController.ts @@ -20,13 +20,14 @@ import type { } from "../../types/inventoryTypes/inventoryTypes.ts"; import { LoadoutIndex } from "../../types/inventoryTypes/inventoryTypes.ts"; import type { RequestHandler } from "express"; -import { catBreadHash, getJSONfromString } from "../../helpers/stringHelpers.ts"; -import { ExportCustoms, ExportDojoRecipes } from "warframe-public-export-plus"; +import { getJSONfromString } from "../../helpers/stringHelpers.ts"; +import { ExportDojoRecipes } from "warframe-public-export-plus"; import type { IStatsClient } from "../../types/statTypes.ts"; import { toStoreItem } from "../../services/itemDataService.ts"; import type { FlattenMaps } from "mongoose"; import type { IEquipmentClient } from "../../types/equipmentTypes.ts"; import type { ILoadoutConfigClient } from "../../types/saveLoadoutTypes.ts"; +import { skinLookupTable } from "../../helpers/skinLookupTable.ts"; const getProfileViewingDataByPlayerIdImpl = async (playerId: string): Promise => { const account = await Account.findById(playerId, "DisplayName"); @@ -261,8 +262,6 @@ interface IXPComponentClient { locTags?: Record; } -let skinLookupTable: Record | undefined; - const resolveAndCollectSkins = ( inventory: TInventoryDatabaseDocument, skins: Set, @@ -274,12 +273,6 @@ const resolveAndCollectSkins = ( // Resolve oids to type names if (config.Skins[i].length == 24) { if (config.Skins[i].substring(0, 16) == "ca70ca70ca70ca70") { - if (!skinLookupTable) { - skinLookupTable = {}; - for (const key of Object.keys(ExportCustoms)) { - skinLookupTable[catBreadHash(key)] = key; - } - } config.Skins[i] = skinLookupTable[parseInt(config.Skins[i].substring(16), 16)]; } else { const skinItem = inventory.WeaponSkins.id(config.Skins[i]); diff --git a/src/controllers/pay/getSkuCatalogController.ts b/src/controllers/pay/getSkuCatalogController.ts index 8495c316..04af6690 100644 --- a/src/controllers/pay/getSkuCatalogController.ts +++ b/src/controllers/pay/getSkuCatalogController.ts @@ -1,5 +1,12 @@ import type { RequestHandler } from "express"; +import { getAccountForRequest } from "../../services/loginService.ts"; +import { version_compare } from "../../helpers/inventoryHelpers.ts"; -export const getSkuCatalogController: RequestHandler = (_req, res) => { - res.sendFile("static/fixed_responses/getSkuCatalog.json", { root: "./" }); +export const getSkuCatalogController: RequestHandler = async (req, res) => { + const account = await getAccountForRequest(req); + if (!account.BuildLabel || version_compare(account.BuildLabel, "2025.10.14.16.10") >= 0) { + res.sendFile("static/fixed_responses/getSkuCatalogU40.json", { root: "./" }); + } else { + res.sendFile("static/fixed_responses/getSkuCatalog.json", { root: "./" }); + } }; diff --git a/src/helpers/relicHelper.ts b/src/helpers/relicHelper.ts index 04b315f7..f2fdf942 100644 --- a/src/helpers/relicHelper.ts +++ b/src/helpers/relicHelper.ts @@ -8,7 +8,6 @@ import { logger } from "../utils/logger.ts"; import { addMiscItems, combineInventoryChanges } from "../services/inventoryService.ts"; import { handleStoreItemAcquisition } from "../services/purchaseService.ts"; import type { IInventoryChanges } from "../types/purchaseTypes.ts"; -import { config } from "../services/configService.ts"; export const crackRelic = async ( inventory: TInventoryDatabaseDocument, @@ -29,10 +28,10 @@ export const crackRelic = async ( ExportRewards[relic.rewardManifest][0] as { type: string; itemCount: number; rarity: TRarity }[], // rarity is nullable in PE+ typings, but always present for relics weights )!; - if (config.relicRewardItemCountMultiplier !== undefined && (config.relicRewardItemCountMultiplier ?? 1) != 1) { + if (inventory.relicRewardItemCountMultiplier && inventory.relicRewardItemCountMultiplier != 1) { reward = { ...reward, - itemCount: reward.itemCount * config.relicRewardItemCountMultiplier + itemCount: reward.itemCount * inventory.relicRewardItemCountMultiplier }; } logger.debug(`relic rolled`, reward); diff --git a/src/helpers/skinLookupTable.ts b/src/helpers/skinLookupTable.ts new file mode 100644 index 00000000..24e7496f --- /dev/null +++ b/src/helpers/skinLookupTable.ts @@ -0,0 +1,8 @@ +import { ExportCustoms } from "warframe-public-export-plus"; +import { catBreadHash } from "./stringHelpers.ts"; + +export const skinLookupTable: Record = {}; + +for (const key of Object.keys(ExportCustoms)) { + skinLookupTable[catBreadHash(key)] = key; +} diff --git a/src/models/inventoryModels/inventoryModel.ts b/src/models/inventoryModels/inventoryModel.ts index 58c57b93..909639cc 100644 --- a/src/models/inventoryModels/inventoryModel.ts +++ b/src/models/inventoryModels/inventoryModel.ts @@ -88,7 +88,9 @@ import type { IGoalProgressDatabase, IGoalProgressClient, IKubrowPetPrintClient, - IKubrowPetPrintDatabase + IKubrowPetPrintDatabase, + INokkoColony, + IJournalEntry } from "../../types/inventoryTypes/inventoryTypes.ts"; import { equipmentKeys } from "../../types/inventoryTypes/inventoryTypes.ts"; import type { IOid, ITypeCount } from "../../types/commonTypes.ts"; @@ -146,7 +148,8 @@ const focusUpgradeSchema = new Schema( { ItemType: String, Level: Number, - IsUniversal: Boolean + IsUniversal: Boolean, + IsActive: Number }, { _id: false } ); @@ -1423,6 +1426,22 @@ const hubNpcCustomizationSchema = new Schema( { _id: false } ); +const journalEntrySchema = new Schema( + { + EntryType: String, + Progress: Number + }, + { _id: false } +); + +const nokkoColonySchema = new Schema( + { + FeedLevel: Number, + JournalEntries: [journalEntrySchema] + }, + { _id: false } +); + const inventorySchema = new Schema( { accountOwnerId: Schema.Types.ObjectId, @@ -1468,6 +1487,9 @@ const inventorySchema = new Schema( nemesisHintProgressMultiplierGrineer: Number, nemesisHintProgressMultiplierCorpus: Number, nemesisExtraWeapon: Number, + spoofMasteryRank: { type: Number, default: -1 }, + relicRewardItemCountMultiplier: { type: Number, default: 1 }, + nightwaveStandingMultiplier: { type: Number, default: 1 }, SubscribedToEmails: { type: Number, default: 0 }, SubscribedToEmailsPersonalized: { type: Number, default: 0 }, @@ -1542,6 +1564,8 @@ const inventorySchema = new Schema( FocusAbility: String, //The treeways of the Focus school.(Active and passive Ability) FocusUpgrades: [focusUpgradeSchema], + //Focus 2.0 Pool + FocusCapacity: Number, //Achievement ChallengeProgress: [challengeProgressSchema], @@ -1743,7 +1767,7 @@ const inventorySchema = new Schema( NemesisAbandonedRewards: { type: [String], default: [] }, Nemesis: nemesisSchema, NemesisHistory: { type: [nemesisSchema], default: undefined }, - //LastNemesisAllySpawnTime: Schema.Types.Mixed, + LastNemesisAllySpawnTime: { type: Date, default: undefined }, //TradingRulesConfirmed,ShowFriendInvNotifications(Option->Social) Settings: settingsSchema, @@ -1834,7 +1858,9 @@ const inventorySchema = new Schema( ClaimedJunctionChallengeRewards: { type: [String], default: undefined }, - SpecialItemRewardAttenuation: { type: [rewardAttenutationSchema], default: undefined } + SpecialItemRewardAttenuation: { type: [rewardAttenutationSchema], default: undefined }, + + NokkoColony: { type: nokkoColonySchema, default: undefined } }, { timestamps: { createdAt: "Created", updatedAt: false } } ); @@ -1864,6 +1890,9 @@ inventorySchema.set("toJSON", { if (inventoryDatabase.BlessingCooldown) { inventoryResponse.BlessingCooldown = toMongoDate(inventoryDatabase.BlessingCooldown); } + if (inventoryDatabase.LastNemesisAllySpawnTime) { + inventoryResponse.LastNemesisAllySpawnTime = toMongoDate(inventoryDatabase.LastNemesisAllySpawnTime); + } if (inventoryDatabase.NextRefill) { inventoryResponse.NextRefill = toMongoDate(inventoryDatabase.NextRefill); } diff --git a/src/routes/api.ts b/src/routes/api.ts index a9c84b84..5502a0c8 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -50,9 +50,11 @@ import { dronesController } from "../controllers/api/dronesController.ts"; import { endlessXpController } from "../controllers/api/endlessXpController.ts"; import { entratiLabConquestModeController } from "../controllers/api/entratiLabConquestModeController.ts"; import { evolveWeaponController } from "../controllers/api/evolveWeaponController.ts"; +import { feedPrinceController } from "../controllers/api/feedPrinceController.ts"; import { findSessionsController } from "../controllers/api/findSessionsController.ts"; import { fishmongerController } from "../controllers/api/fishmongerController.ts"; import { focusController } from "../controllers/api/focusController.ts"; +import { forceRemoveItemController } from "../controllers/api/forceRemoveItemController.ts"; import { fusionTreasuresController } from "../controllers/api/fusionTreasuresController.ts"; import { gardeningController } from "../controllers/api/gardeningController.ts"; import { genericUpdateController } from "../controllers/api/genericUpdateController.ts"; @@ -66,8 +68,8 @@ import { getGuildEventScoreController } from "../controllers/api/getGuildEventSc import { getGuildLogController } from "../controllers/api/getGuildLogController.ts"; import { getIgnoredUsersController } from "../controllers/api/getIgnoredUsersController.ts"; import { getNewRewardSeedController } from "../controllers/api/getNewRewardSeedController.ts"; -import { getProfileViewingDataPostController } from "../controllers/dynamic/getProfileViewingDataController.ts"; import { getPastWeeklyChallengesController } from "../controllers/api/getPastWeeklyChallengesController.ts"; +import { getProfileViewingDataPostController } from "../controllers/dynamic/getProfileViewingDataController.ts"; import { getShipController } from "../controllers/api/getShipController.ts"; import { getVendorInfoController } from "../controllers/api/getVendorInfoController.ts"; import { getVoidProjectionRewardsController } from "../controllers/api/getVoidProjectionRewardsController.ts"; @@ -115,6 +117,7 @@ import { removeFromGuildController } from "../controllers/api/removeFromGuildCon import { removeIgnoredUserController } from "../controllers/api/removeIgnoredUserController.ts"; import { renamePetController } from "../controllers/api/renamePetController.ts"; import { rerollRandomModController } from "../controllers/api/rerollRandomModController.ts"; +import { researchMushroomController } from "../controllers/api/researchMushroomController.ts"; import { resetQuestProgressController } from "../controllers/api/resetQuestProgressController.ts"; import { retrievePetFromStasisController } from "../controllers/api/retrievePetFromStasisController.ts"; import { saveDialogueController } from "../controllers/api/saveDialogueController.ts"; @@ -270,9 +273,11 @@ apiRouter.post("/drones.php", dronesController); apiRouter.post("/endlessXp.php", endlessXpController); apiRouter.post("/entratiLabConquestMode.php", entratiLabConquestModeController); apiRouter.post("/evolveWeapon.php", evolveWeaponController); +apiRouter.post("/feedPrince.php", feedPrinceController); apiRouter.post("/findSessions.php", findSessionsController); apiRouter.post("/fishmonger.php", fishmongerController); apiRouter.post("/focus.php", focusController); +apiRouter.post("/forceRemoveItem.php", forceRemoveItemController); apiRouter.post("/fusionTreasures.php", fusionTreasuresController); apiRouter.post("/gardening.php", gardeningController); apiRouter.post("/genericUpdate.php", genericUpdateController); @@ -316,6 +321,7 @@ apiRouter.post("/removeFromGuild.php", removeFromGuildController); apiRouter.post("/removeIgnoredUser.php", removeIgnoredUserController); apiRouter.post("/renamePet.php", renamePetController); apiRouter.post("/rerollRandomMod.php", rerollRandomModController); +apiRouter.post("/researchMushroom.php", researchMushroomController); apiRouter.post("/retrievePetFromStasis.php", retrievePetFromStasisController); apiRouter.post("/saveDialogue.php", saveDialogueController); apiRouter.post("/saveLoadout.php", saveLoadoutController); diff --git a/src/routes/custom.ts b/src/routes/custom.ts index 628aff6b..05747f5a 100644 --- a/src/routes/custom.ts +++ b/src/routes/custom.ts @@ -22,6 +22,7 @@ import { unlockAllScansController } from "../controllers/custom/unlockAllScansCo import { unlockAllShipFeaturesController } from "../controllers/custom/unlockAllShipFeaturesController.ts"; import { unlockAllCapturaScenesController } from "../controllers/custom/unlockAllCapturaScenesController.ts"; import { removeCustomizationController } from "../controllers/custom/removeCustomizationController.ts"; +import { removeIsNewController } from "../controllers/custom/removeIsNewController.ts"; import { abilityOverrideController } from "../controllers/custom/abilityOverrideController.ts"; import { createAccountController } from "../controllers/custom/createAccountController.ts"; @@ -34,7 +35,7 @@ import { fundTechProjectController, removeTechProjectController } from "../controllers/custom/techProjectController.ts"; -import { addVaultDecoRecipeController } from "../controllers/custom/addVaultDecoRecipeController.ts"; +import { addVaultTypeCountController } from "../controllers/custom/addVaultTypeCountController.ts"; import { addXpController } from "../controllers/custom/addXpController.ts"; import { importController } from "../controllers/custom/importController.ts"; import { manageQuestsController } from "../controllers/custom/manageQuestsController.ts"; @@ -73,6 +74,7 @@ customRouter.get("/unlockAllScans", unlockAllScansController); customRouter.get("/unlockAllShipFeatures", unlockAllShipFeaturesController); customRouter.get("/unlockAllCapturaScenes", unlockAllCapturaScenesController); customRouter.get("/removeCustomization", removeCustomizationController); +customRouter.get("/removeIsNew", removeIsNewController); customRouter.post("/abilityOverride", abilityOverrideController); customRouter.post("/createAccount", createAccountController); @@ -81,7 +83,7 @@ customRouter.post("/addCurrency", addCurrencyController); customRouter.post("/addItems", addItemsController); customRouter.post("/addTechProject", addTechProjectController); customRouter.post("/removeTechProject", removeTechProjectController); -customRouter.post("/addVaultDecoRecipe", addVaultDecoRecipeController); +customRouter.post("/addVaultTypeCount", addVaultTypeCountController); customRouter.post("/fundTechProject", fundTechProjectController); customRouter.post("/completeTechProject", completeTechProjectsController); customRouter.post("/addXp", addXpController); diff --git a/src/services/configService.ts b/src/services/configService.ts index 0c34e967..db58354e 100644 --- a/src/services/configService.ts +++ b/src/services/configService.ts @@ -24,9 +24,6 @@ export interface IConfig { unlockAllSkins?: boolean; fullyStockedVendors?: boolean; skipClanKeyCrafting?: boolean; - spoofMasteryRank?: number; - relicRewardItemCountMultiplier?: number; - nightwaveStandingMultiplier?: number; unfaithfulBugFixes?: { ignore1999LastRegionPlayed?: boolean; fixXtraCheeseTimer?: boolean; @@ -41,7 +38,7 @@ export interface IConfig { baroAlwaysAvailable?: boolean; baroFullyStocked?: boolean; varziaFullyStocked?: boolean; - wolfHunt?: boolean; + wolfHunt?: number; orphixVenom?: boolean; longShadow?: boolean; hallowedFlame?: boolean; @@ -51,6 +48,11 @@ export interface IConfig { naberusNightsOverride?: boolean; proxyRebellion?: boolean; proxyRebellionRewardsOverride?: number; + voidCorruption2025Week1?: boolean; + voidCorruption2025Week2?: boolean; + voidCorruption2025Week3?: boolean; + voidCorruption2025Week4?: boolean; + qtccAlerts?: boolean; galleonOfGhouls?: number; ghoulEmergenceOverride?: boolean; plagueStarOverride?: boolean; @@ -78,6 +80,21 @@ export interface IConfig { } export const configRemovedOptionsKeys = [ + "unlockallShipFeatures", + "testQuestKey", + "lockTime", + "starDays", + "platformCDNs", + "completeAllQuests", + "worldSeed", + "unlockAllQuests", + "unlockAllMissions", + "version", + "matchmakingBuildId", + "buildLabel", + "infiniteResources", + "testMission", + "skipStoryModeChoice", "NRS", "myIrcAddresses", "skipAllDialogue", @@ -129,7 +146,10 @@ export const configRemovedOptionsKeys = [ "fastClanAscension", "unlockAllFlavourItems", "unlockAllShipDecorations", - "unlockAllDecoRecipes" + "unlockAllDecoRecipes", + "spoofMasteryRank", + "relicRewardItemCountMultiplier", + "nightwaveStandingMultiplier" ]; export const configPath = path.join(repoDir, args.configPath ?? "config.json"); diff --git a/src/services/friendService.ts b/src/services/friendService.ts index 445e4321..219535be 100644 --- a/src/services/friendService.ts +++ b/src/services/friendService.ts @@ -1,6 +1,5 @@ import type { IFriendInfo } from "../types/friendTypes.ts"; import { getInventory } from "./inventoryService.ts"; -import { config } from "./configService.ts"; import { Account } from "../models/loginModel.ts"; import type { Types } from "mongoose"; import { Friendship } from "../models/friendModel.ts"; @@ -13,8 +12,8 @@ export const addAccountDataToFriendInfo = async (info: IFriendInfo): Promise => { - const inventory = await getInventory(fromOid(info._id), "PlayerLevel ActiveAvatarImageType"); - info.PlayerLevel = config.spoofMasteryRank == -1 ? inventory.PlayerLevel : config.spoofMasteryRank; + const inventory = await getInventory(fromOid(info._id), "PlayerLevel ActiveAvatarImageType spoofMasteryRank"); + info.PlayerLevel = inventory.spoofMasteryRank == -1 ? inventory.PlayerLevel : inventory.spoofMasteryRank; info.ActiveAvatarImageType = inventory.ActiveAvatarImageType; }; diff --git a/src/services/guildService.ts b/src/services/guildService.ts index 8f609877..8c5f13a0 100644 --- a/src/services/guildService.ts +++ b/src/services/guildService.ts @@ -22,7 +22,7 @@ import type { ITechProjectDatabase } from "../types/guildTypes.ts"; import { GuildPermission } from "../types/guildTypes.ts"; -import { toMongoDate, toOid, toOid2 } from "../helpers/inventoryHelpers.ts"; +import { toMongoDate, toOid, toOid2, version_compare } from "../helpers/inventoryHelpers.ts"; import type { Types } from "mongoose"; import type { IDojoBuild, IDojoResearch } from "warframe-public-export-plus"; import { ExportDojoRecipes, ExportResources } from "warframe-public-export-plus"; @@ -68,9 +68,15 @@ export const getGuildClient = async ( let missingEntry = true; const dataFillInPromises: Promise[] = []; for (const guildMember of guildMembers) { + // Use 1-based indexing for clan ranks for versions before U24. In my testing, 2018.06.14.23.21 and below used 1-based indexing and 2019.04.04.21.31 and above used 0-based indexing. I didn't narrow it down further, but I think U24 is a good spot for them to have changed it. + let rankBase = 0; + if (account.BuildLabel && version_compare(account.BuildLabel, "2018.11.08.14.45") < 0) { + rankBase += 1; + } + const member: IGuildMemberClient = { _id: toOid2(guildMember.accountId, account.BuildLabel), - Rank: guildMember.rank, + Rank: guildMember.rank + rankBase, Status: guildMember.status, Note: guildMember.RequestMsg, RequestExpiry: guildMember.RequestExpiry ? toMongoDate(guildMember.RequestExpiry) : undefined diff --git a/src/services/importService.ts b/src/services/importService.ts index e137396b..c949a953 100644 --- a/src/services/importService.ts +++ b/src/services/importService.ts @@ -73,6 +73,7 @@ import type { ITailorShop, ITailorShopDatabase } from "../types/personalRoomsTypes.ts"; +import { fromMongoDate } from "../helpers/inventoryHelpers.ts"; const convertDate = (value: IMongoDate): Date => { return new Date(parseInt(value.$date.$numberLong)); @@ -326,7 +327,15 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial< "GiftsRemaining", "ChallengesFixVersion", "Founder", - "Guide" + "Guide", + "BountyScore", + "EntratiVaultCountLastPeriod", + "EntratiLabConquestUnlocked", + "EntratiLabConquestHardModeStatus", + "EntratiLabConquestCacheScoreMission", + "EchoesHexConquestUnlocked", + "EchoesHexConquestHardModeStatus", + "EchoesHexConquestCacheScoreMission" ] as const) { if (client[key] !== undefined) { db[key] = client[key]; @@ -354,12 +363,28 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial< "NodeIntrosCompleted", "DeathMarks", "Wishlist", - "NemesisAbandonedRewards" + "NemesisAbandonedRewards", + "EntratiLabConquestActiveFrameVariants", + "EchoesHexConquestActiveFrameVariants", + "EchoesHexConquestActiveStickers" ] as const) { if (client[key] !== undefined) { db[key] = client[key]; } } + // IMongoDate + for (const key of [ + "Created", + "TrainingDate", + "BlessingCooldown", + "LastNemesisAllySpawnTime", + "NextRefill", + "EntratiVaultCountResetDate" + ] as const) { + if (client[key] !== undefined) { + db[key] = fromMongoDate(client[key]); + } + } // IRewardAtten[] for (const key of ["SortieRewardAttenuation", "SpecialItemRewardAttenuation"] as const) { if (client[key] !== undefined) { diff --git a/src/services/inventoryService.ts b/src/services/inventoryService.ts index eeb411f7..66668b68 100644 --- a/src/services/inventoryService.ts +++ b/src/services/inventoryService.ts @@ -92,6 +92,7 @@ import type { } from "../types/equipmentTypes.ts"; import { EquipmentFeatures, Status } from "../types/equipmentTypes.ts"; import type { ITypeCount } from "../types/commonTypes.ts"; +import { skinLookupTable } from "../helpers/skinLookupTable.ts"; export const createInventory = async ( accountOwnerId: Types.ObjectId, @@ -116,6 +117,7 @@ export const createInventory = async ( inventory.PlayedParkourTutorial = true; await addStartingGear(inventory); await completeQuest(inventory, "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain"); + await completeQuest(inventory, "/Lotus/Types/Keys/ModQuest/ModQuestKeyChain"); const completedMissions = ["SolNode27", "SolNode89", "SolNode63", "SolNode85", "SolNode15", "SolNode79"]; @@ -133,16 +135,6 @@ export const createInventory = async ( } }; -//TODO: RawUpgrades might need to return a LastAdded -const awakeningRewards = [ - "/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem1", - "/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem2", - "/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem3", - "/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem4", - "/Lotus/Types/Restoratives/LisetAutoHack", - "/Lotus/Upgrades/Mods/Warframe/AvatarShieldMaxMod" -]; - export const addStartingGear = async ( inventory: TInventoryDatabaseDocument, startingGear?: TPartialStartingGear @@ -195,6 +187,14 @@ export const addStartingGear = async ( inventory.RegularCredits = 3000; inventoryChanges.RegularCredits = 3000; + const awakeningRewards = [ + "/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem1", + "/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem2", + "/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem3", + "/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem4", + "/Lotus/Types/Restoratives/LisetAutoHack" + ]; + for (const item of awakeningRewards) { const inventoryDelta = await addItem(inventory, item); combineInventoryChanges(inventoryChanges, inventoryDelta); @@ -388,7 +388,7 @@ export const addItem = async ( }; } else if (ExportResources[typeName].productCategory == "CrewShips") { return { - ...addCrewShip(inventory, typeName), + ...(await addCrewShip(inventory, typeName)), // fix to unlock railjack modding, item bellow supposed to be obtained from archwing quest // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition ...(!inventory.CrewShipHarnesses?.length @@ -1391,7 +1391,10 @@ export const addStanding = ( // TODO: AffiliationMods support (Nightwave). export const updateGeneric = async (data: IGenericUpdate, accountId: string): Promise => { - const inventory = await getInventory(accountId, "NodeIntrosCompleted MiscItems ShipDecorations"); + const inventory = await getInventory( + accountId, + "NodeIntrosCompleted MiscItems ShipDecorations FlavourItems WeaponSkins" + ); // Make it an array for easier parsing. if (typeof data.NodeIntrosCompleted === "string") { @@ -1400,6 +1403,12 @@ export const updateGeneric = async (data: IGenericUpdate, accountId: string): Pr const inventoryChanges: IInventoryChanges = {}; for (const node of data.NodeIntrosCompleted) { + if (inventory.NodeIntrosCompleted.indexOf(node) != -1) { + continue; + } + inventory.NodeIntrosCompleted.push(node); + logger.debug(`completed dialogue/cutscene for ${node}`); + if (node == "TC2025") { inventoryChanges.ShipDecorations = [ { @@ -1420,16 +1429,15 @@ export const updateGeneric = async (data: IGenericUpdate, accountId: string): Pr await addEmailItem(inventory, "/Lotus/Types/Items/EmailItems/BeatCaliberChicksEmailItem", inventoryChanges); } else if (node == "ClearedFiveLoops") { await addEmailItem(inventory, "/Lotus/Types/Items/EmailItems/ClearedFiveLoopsEmailItem", inventoryChanges); + } else if (node == "NokkoVisions_AllCompleted") { + addCustomization( + inventory, + "/Lotus/Types/StoreItems/AvatarImages/Warframes/NokkoBabySecretGlyph", + inventoryChanges + ); + addSkin(inventory, "/Lotus/Upgrades/Skins/Clan/NokkoBabySecretEmblemItem", inventoryChanges); } } - - // Combine the two arrays into one. - data.NodeIntrosCompleted = inventory.NodeIntrosCompleted.concat(data.NodeIntrosCompleted); - - // Remove duplicate entries. - const nodes = [...new Set(data.NodeIntrosCompleted)]; - - inventory.NodeIntrosCompleted = nodes; await inventory.save(); return { @@ -1489,7 +1497,13 @@ export const addSkin = ( inventoryChanges: IInventoryChanges = {} ): IInventoryChanges => { if (inventory.WeaponSkins.some(x => x.ItemType == typeName)) { - logger.debug(`refusing to add WeaponSkin ${typeName} because account already owns it`); + if (typeName == "/Lotus/Upgrades/Skins/Clan/BountyHunterBadgeItem") { + logger.debug(`account already owns stratos emblem, increasing bounty score instead`); + inventory.BountyScore ??= 0; + inventory.BountyScore += 1; + } else { + logger.debug(`refusing to add WeaponSkin ${typeName} because account already owns it`); + } } else { const index = inventory.WeaponSkins.push({ @@ -1537,17 +1551,31 @@ export const addCrewShipSalvagedWeaponSkin = ( return inventoryChanges; }; -const addCrewShip = ( +const addCrewShip = async ( inventory: TInventoryDatabaseDocument, typeName: string, inventoryChanges: IInventoryChanges = {} -): IInventoryChanges => { +): Promise => { if (inventory.CrewShips.length != 0) { logger.warn("refusing to add CrewShip because account already has one"); } else { const index = inventory.CrewShips.push({ ItemType: typeName }) - 1; inventoryChanges.CrewShips ??= []; inventoryChanges.CrewShips.push(inventory.CrewShips[index].toJSON()); + const railjackQuest = inventory.QuestKeys.find( + qk => qk.ItemType === "/Lotus/Types/Keys/RailJackBuildQuest/RailjackBuildQuestKeyChain" + ); + if (!railjackQuest || !railjackQuest.Completed) { + const questChanges = await completeQuest( + inventory, + "/Lotus/Types/Keys/RailJackBuildQuest/RailjackBuildQuestKeyChain", + false + ); + if (questChanges) { + inventoryChanges.QuestKeys ??= []; + inventoryChanges.QuestKeys.push(questChanges); + } + } } return inventoryChanges; }; @@ -2124,7 +2152,7 @@ export const addChallenges = async ( ]; } - const standingToAdd = meta.standing! * (config.nightwaveStandingMultiplier ?? 1); + const standingToAdd = meta.standing! * (inventory.nightwaveStandingMultiplier ?? 1); affiliation.Standing += standingToAdd; if (affiliationMods.length == 0) { affiliationMods.push({ Tag: nightwaveSyndicateTag }); @@ -2152,7 +2180,7 @@ export const addMissionComplete = (inventory: TInventoryDatabaseDocument, { Tag, if (itemIndex !== -1) { Missions[itemIndex].Completes += Completes; - if (Tier) { + if (Completes && Tier) { Missions[itemIndex].Tier = Tier; } } else { @@ -2254,7 +2282,7 @@ export const setupKahlSyndicate = (inventory: TInventoryDatabaseDocument): void }); }; -export const cleanupInventory = (inventory: TInventoryDatabaseDocument): void => { +export const cleanupInventory = async (inventory: TInventoryDatabaseDocument): Promise => { inventory.CurrentLoadOutIds = inventory.CurrentLoadOutIds.map(fromDbOid); let index = inventory.MiscItems.findIndex(x => x.ItemType == ""); @@ -2308,6 +2336,99 @@ export const cleanupInventory = (inventory: TInventoryDatabaseDocument): void => logger.debug(`removed ModularParts from ${numFixed} non-modular items`); } } + + { + const weaponMap = new Map(); + for (const skin of inventory.WeaponSkins) { + weaponMap.set(skin.ItemType, skin._id.toString()); + } + + const itemsToAdd = new Set(); + + for (const key of equipmentKeys) { + if (key in inventory) { + for (const equipment of inventory[key]) { + for (const config of equipment.Configs) { + if (config.Skins) collectSkins(config.Skins, weaponMap, itemsToAdd); + } + } + } + } + + for (const key of ["AdultOperatorLoadOuts", "OperatorLoadOuts", "KahlLoadOuts"] as const) { + if (key in inventory) { + for (const loadOut of inventory[key]) { + if (loadOut.Skins) collectSkins(loadOut.Skins, weaponMap, itemsToAdd); + } + } + } + + if (inventory.LotusCustomization?.Skins) + collectSkins(inventory.LotusCustomization.Skins, weaponMap, itemsToAdd); + + if (itemsToAdd.size > 0) { + logger.debug(`Adding ${itemsToAdd.size} items due to migration from unlockAllSkins cheat`); + const inventoryChanges = await addItems(inventory, Array.from(itemsToAdd)); + + if (inventoryChanges.WeaponSkins) { + for (const skin of inventoryChanges.WeaponSkins as IWeaponSkinClient[]) { + weaponMap.set(skin.ItemType, skin.ItemId.toString()); + } + } + + for (const key of equipmentKeys) { + if (key in inventory) { + for (const equipment of inventory[key]) { + for (const config of equipment.Configs) { + if (config.Skins) replaceSkinIds(config.Skins, weaponMap); + } + } + } + } + + for (const key of ["AdultOperatorLoadOuts", "OperatorLoadOuts", "KahlLoadOuts"] as const) { + if (key in inventory) { + for (const loadOut of inventory[key]) { + if (loadOut.Skins) replaceSkinIds(loadOut.Skins, weaponMap); + } + } + } + + if (inventory.LotusCustomization?.Skins) replaceSkinIds(inventory.LotusCustomization.Skins, weaponMap); + } + } +}; + +const collectSkins = (skins: string[], weaponMap: Map, itemsToAdd: Set): void => { + for (const skinId of skins) { + if (skinId.startsWith("ca70ca70ca70ca70")) { + const typeName = skinLookupTable[parseInt(skinId.slice(16), 16)]; + if (!weaponMap.has(typeName)) { + const { requirement } = ExportCustoms[typeName]; + if (typeof requirement == "string") { + itemsToAdd.add(requirement); + } else { + itemsToAdd.add(typeName); + } + } + } + } +}; + +const replaceSkinIds = (skins: string[], weaponMap: Map): void => { + for (let i = 0; i < skins.length; i++) { + const skinId = skins[i]; + if (skinId.startsWith("ca70ca70ca70ca70")) { + const typeName = skinLookupTable[parseInt(skinId.slice(16), 16)]; + const inventoryId = weaponMap.get(typeName); + if (inventoryId) { + skins[i] = inventoryId; + } else if (typeName in ExportCustoms) { + const { requirement } = ExportCustoms[typeName]; + skins[i] = typeof requirement == "string" ? typeName : ""; + } + } + } }; export const getDialogue = (inventory: TInventoryDatabaseDocument, dialogueName: string): IDialogueDatabase => { diff --git a/src/services/itemDataService.ts b/src/services/itemDataService.ts index 0b33a1a1..3fead3d3 100644 --- a/src/services/itemDataService.ts +++ b/src/services/itemDataService.ts @@ -81,7 +81,8 @@ export const getRecipe = (uniqueName: string): IRecipe | undefined => { ItemCount: 600 } ], - excludeFromMarket: true + excludeFromMarket: true, + tradable: false }; } diff --git a/src/services/loginService.ts b/src/services/loginService.ts index b9e200cf..f2947b6a 100644 --- a/src/services/loginService.ts +++ b/src/services/loginService.ts @@ -65,7 +65,7 @@ export const createPersonalRooms = async (accountId: Types.ObjectId, shipId: Typ activeShipId: shipId }); if (config.skipTutorial) { - // unlocked during Vor's Prize + // unlocked during Vor's Prize and The Teacher quests const defaultFeatures = [ "/Lotus/Types/Items/ShipFeatureItems/MercuryNavigationFeatureItem", "/Lotus/Types/Items/ShipFeatureItems/ArsenalFeatureItem", diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index dfb6c815..3c50c453 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -8,6 +8,7 @@ import { ExportAnimals, ExportEnemies, ExportFusionBundles, + ExportKeys, ExportRegions, ExportRelics, ExportRewards @@ -242,7 +243,7 @@ export const addMissionInventoryUpdates = async ( } } } - if (inventoryUpdates.MissionStatus == "GS_SUCCESS" && inventoryUpdates.RewardInfo.jobId) { + if (inventoryUpdates.RewardInfo.jobId) { // e.g. for Profit-Taker Phase 1: // JobTier: -6, // jobId: '/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyOne_-6_SolarisUnitedHub1_663a71c80000000000000025_EudicoHeists', @@ -250,7 +251,10 @@ export const addMissionInventoryUpdates = async ( // eslint-disable-next-line @typescript-eslint/no-unused-vars const [bounty, tier, hub, id, tag] = inventoryUpdates.RewardInfo.jobId.split("_"); - if (tag == "EudicoHeists") { + if ( + (tag == "EudicoHeists" && inventoryUpdates.MissionStatus == "GS_SUCCESS") || + (tag == "NokkoColony" && inventoryUpdates.RewardInfo.JobStage == 4) + ) { inventory.CompletedJobChains ??= []; let chain = inventory.CompletedJobChains.find(x => x.LocationTag == tag); if (!chain) { @@ -1128,7 +1132,8 @@ export const addMissionRewards = async ( RegularCredits: creditDrops, VoidTearParticipantsCurrWave: voidTearWave, StrippedItems: strippedItems, - AffiliationChanges: AffiliationMods + AffiliationChanges: AffiliationMods, + InvasionProgress: invasionProgress }: IMissionInventoryUpdateRequest, firstCompletion: boolean ): Promise => { @@ -1144,6 +1149,7 @@ export const addMissionRewards = async ( const MissionRewards: IMissionReward[] = getRandomMissionDrops( inventory, rewardInfo, + levelKeyName, missions, wagerTier, firstCompletion @@ -1154,8 +1160,42 @@ export const addMissionRewards = async ( let ConquestCompletedMissionsCount; let missionCompletionCredits = 0; + + if (rewardInfo.alertId) { + const alert = getWorldState().Alerts.find(x => x._id.$oid == rewardInfo.alertId); + if (!alert) { + logger.warn(`mission completed unknown alert`, { alertId: rewardInfo.alertId }); + } else { + if (inventory.CompletedAlerts.includes(alert._id.$oid)) { + logger.debug(`alert ${alert._id.$oid} already completed, skipping alert reward`); + } else { + inventory.CompletedAlerts.push(alert._id.$oid); + if (alert.MissionInfo.missionReward) { + missionCompletionCredits += addFixedLevelRewards( + alert.MissionInfo.missionReward, + MissionRewards, + rewardInfo + ); + } + } + } + } + //inventory change is what the client has not rewarded itself, also the client needs to know the credit changes for display + if (invasionProgress) { + for (const clientProgress of invasionProgress) { + const dbProgress = inventory.QualifyingInvasions.find(x => x.invasionId.equals(clientProgress._id.$oid)); + if (dbProgress) { + const run = + (clientProgress.AttackerScore > clientProgress.DefenderScore + ? dbProgress.AttackerScore + : dbProgress.DefenderScore) - 1; + missionCompletionCredits += 1000 * Math.min(run, 10); + } + } + } + if (rewardInfo.goalId) { const goal = getWorldState().Goals.find(x => x._id.$oid == rewardInfo.goalId); if (goal) { @@ -1477,7 +1517,7 @@ export const addMissionRewards = async ( syndicateEntry = Goals.find(m => m._id.$oid === syndicateMissionId); if (syndicateEntry) syndicateEntry.Tag = syndicateEntry.JobAffiliationTag!; } - if (syndicateEntry && syndicateEntry.Jobs) { + if (syndicateEntry && syndicateEntry.Jobs && !jobType.startsWith("/Lotus/Types/Gameplay/NokkoColony/Jobs")) { let currentJob = syndicateEntry.Jobs[rewardInfo.JobTier!]; if ( [ @@ -1667,8 +1707,8 @@ export const addCredits = async ( finalCredits.TotalCredits[1] += finalCredits.TotalCredits[1]; } if ((inventory.Boosters.find(x => x.ItemType == "/Lotus/Types/Boosters/CreditBlessing")?.ExpiryDate ?? 0) > now) { - inventory.RegularCredits += finalCredits.TotalCredits[1]; - finalCredits.TotalCredits[1] += finalCredits.TotalCredits[1]; + inventory.RegularCredits += finalCredits.TotalCredits[1] * 0.25; + finalCredits.TotalCredits[1] += finalCredits.TotalCredits[1] * 0.25; } return finalCredits; @@ -1735,6 +1775,7 @@ function getLevelCreditRewards(node: IRegion): number { function getRandomMissionDrops( inventory: TInventoryDatabaseDocument, RewardInfo: IRewardInfo, + levelKeyName: string | undefined, mission: IMission | undefined, tierOverride: number | undefined, firstCompletion: boolean @@ -1984,6 +2025,19 @@ function getRandomMissionDrops( xpAmounts: [1000] }; RewardInfo.Q = false; // Just in case + } else if (jobType.startsWith("/Lotus/Types/Gameplay/NokkoColony/Jobs/NokkoJob")) { + job = { + rewards: jobType + .replace("SteelPath", "Steel") + .replace( + "/Lotus/Types/Gameplay/NokkoColony/Jobs/NokkoJob", + "Lotus/Types/Game/MissionDecks/NokkoColonyRewards/NokkoColonyRewards" + ), + masteryReq: 0, + minEnemyLevel: 30, + maxEnemyLevel: 40, + xpAmounts: [0, 0, 0, 0, 0] + }; } else { const tierMap = { Two: "B", @@ -2027,14 +2081,22 @@ function getRandomMissionDrops( } else if (totalStage == 5 && curentStage == 4) { tableIndex = 2; } - - rotations = [tableIndex]; + if (jobType.startsWith("/Lotus/Types/Gameplay/NokkoColony/Jobs/NokkoJob")) { + if (RewardInfo.JobStage === job.xpAmounts.length - 1) { + rotations = [0, 1, 2]; + } else { + rewardManifests = []; + } + } else { + rotations = [tableIndex]; + } } else { rotations = [0]; } if ( RewardInfo.Q && (RewardInfo.JobStage === job.xpAmounts.length - 1 || jobType.endsWith("VaultBounty")) && + !jobType.startsWith("/Lotus/Types/Gameplay/NokkoColony/Jobs/NokkoJob") && !isEndlessJob ) { rotations.push(ExportRewards[job.rewards].length - 1); @@ -2172,7 +2234,7 @@ function getRandomMissionDrops( } } - if (region.cacheRewardManifest && RewardInfo.EnemyCachesFound) { + if (region.cacheRewardManifest && RewardInfo.EnemyCachesFound && !RewardInfo.goalId) { const deck = ExportRewards[region.cacheRewardManifest]; for (let rotation = 0; rotation != RewardInfo.EnemyCachesFound; ++rotation) { const drop = getRandomRewardByChance(deck[rotation]); @@ -2238,6 +2300,71 @@ function getRandomMissionDrops( } } + if (RewardInfo.EnemyCachesFound) { + if (RewardInfo.goalId) { + const goal = getWorldState().Goals.find(x => x._id.$oid == RewardInfo.goalId); + if (goal) { + let currentMissionKey: string | undefined; + if (RewardInfo.node == goal.Node) { + currentMissionKey = goal.MissionKeyName; + } else if (goal.ConcurrentNodes && goal.ConcurrentMissionKeyNames) { + for (let i = 0; i < goal.ConcurrentNodes.length; i++) { + if (RewardInfo.node == goal.ConcurrentNodes[i]) { + currentMissionKey = goal.ConcurrentMissionKeyNames[i]; + break; + } + } + } + if (currentMissionKey) { + const keyMeta = ExportKeys[currentMissionKey]; + if (keyMeta.cacheRewardManifest) { + const deck = ExportRewards[keyMeta.cacheRewardManifest]; + for (let rotation = 0; rotation != RewardInfo.EnemyCachesFound; ++rotation) { + const drop = getRandomRewardByChance(deck[rotation]); + if (drop) { + drops.push({ + StoreItem: drop.type, + ItemCount: drop.itemCount, + FromEnemyCache: true + }); + } + } + } + } + } + } else if (RewardInfo.alertId) { + const alert = getWorldState().Alerts.find(x => x._id.$oid == RewardInfo.alertId); + if (alert && alert.MissionInfo.enemyCacheOverride) { + const deck = ExportRewards[alert.MissionInfo.enemyCacheOverride]; + for (let rotation = 0; rotation != RewardInfo.EnemyCachesFound; ++rotation) { + const drop = getRandomRewardByChance(deck[rotation]); + if (drop) { + drops.push({ + StoreItem: drop.type, + ItemCount: drop.itemCount, + FromEnemyCache: true + }); + } + } + } + } else if (levelKeyName) { + const keyMeta = ExportKeys[levelKeyName]; + if (keyMeta.cacheRewardManifest) { + const deck = ExportRewards[keyMeta.cacheRewardManifest]; + for (let rotation = 0; rotation != RewardInfo.EnemyCachesFound; ++rotation) { + const drop = getRandomRewardByChance(deck[rotation]); + if (drop) { + drops.push({ + StoreItem: drop.type, + ItemCount: drop.itemCount, + FromEnemyCache: true + }); + } + } + } + } + } + if (inventory.missionsCanGiveAllRelics) { for (const drop of drops) { const itemType = fromStoreItem(drop.StoreItem); @@ -2458,6 +2585,18 @@ const goalMessagesByKey: Record => { - const personalRooms = await getPersonalRooms(accountId); +export const unlockShipFeature = async (inventory: TInventoryDatabaseDocument, shipFeature: string): Promise => { + const personalRooms = await getPersonalRooms(inventory.accountOwnerId.toString()); if (personalRooms.Ship.Features.includes(shipFeature)) { - throw new Error(`ship feature ${shipFeature} already unlocked`); + logger.warn(`ship feature ${shipFeature} already unlocked`); + } else { + personalRooms.Ship.Features.push(shipFeature); + await personalRooms.save(); } - - personalRooms.Ship.Features.push(shipFeature); - await personalRooms.save(); - - const inventory = await getInventory(accountId); - await addItem(inventory, shipFeature, -1); - await inventory.save(); + const miscItem = inventory.MiscItems.find(x => x.ItemType === shipFeature); + if (miscItem && miscItem.ItemCount > 0) await addItem(inventory, shipFeature, miscItem.ItemCount * -1); }; export const createGarden = (): IGardeningDatabase => { diff --git a/src/services/questService.ts b/src/services/questService.ts index 30866b6b..b99d1879 100644 --- a/src/services/questService.ts +++ b/src/services/questService.ts @@ -11,9 +11,10 @@ import { addFixedLevelRewards } from "./missionInventoryUpdateService.ts"; import type { IInventoryChanges } from "../types/purchaseTypes.ts"; import questCompletionItems from "../../static/fixed_responses/questCompletionRewards.json" with { type: "json" }; import type { ITypeCount } from "../types/commonTypes.ts"; +import { addString } from "../helpers/stringHelpers.ts"; export interface IUpdateQuestRequest { - QuestKeys: Omit[]; + QuestKeys: IQuestKeyClient[]; PS: string; questCompletion: boolean; PlayerShipEvents: unknown[]; @@ -36,6 +37,7 @@ export const updateQuestKey = async ( throw new Error(`quest key ${questKeyUpdate[0].ItemType} not found`); } + delete questKeyUpdate[0].CompletionDate; inventory.QuestKeys[questKeyIndex].overwrite(questKeyUpdate[0]); const inventoryChanges: IInventoryChanges = {}; @@ -115,7 +117,11 @@ export const addQuestKey = ( return inventory.QuestKeys[index - 1].toJSON(); }; -export const completeQuest = async (inventory: TInventoryDatabaseDocument, questKey: string): Promise => { +export const completeQuest = async ( + inventory: TInventoryDatabaseDocument, + questKey: string, + sendMessages: boolean = false +): Promise => { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition const chainStages = ExportKeys[questKey]?.chainStages; @@ -133,8 +139,8 @@ export const completeQuest = async (inventory: TInventoryDatabaseDocument, quest unlock: true, Progress: Array.from({ length: chainStageTotal }, () => ({ c: 0, - i: false, - m: false, + i: true, + m: true, b: [] })) }; @@ -161,7 +167,7 @@ export const completeQuest = async (inventory: TInventoryDatabaseDocument, quest const stage = existingQuestKey.Progress[i]; if (stage.c <= run) { stage.c = run; - await giveKeyChainStageTriggered(inventory, { KeyChain: questKey, ChainStage: i }); + await giveKeyChainStageTriggered(inventory, { KeyChain: questKey, ChainStage: i }, sendMessages); await giveKeyChainMissionReward(inventory, { KeyChain: questKey, ChainStage: i }); } } @@ -170,7 +176,14 @@ export const completeQuest = async (inventory: TInventoryDatabaseDocument, quest existingQuestKey.Completed = true; existingQuestKey.CompletionDate = new Date(); await handleQuestCompletion(inventory, questKey, undefined, run > 0); + + if (questKey == "/Lotus/Types/Keys/ModQuest/ModQuestKeyChain") { + // This would be set by the client during the equilogue, but since we're cheating through, we have to do it ourselves. + addString(inventory.NodeIntrosCompleted, "ModQuestTeshinAccess"); + } } + + return existingQuestKey.toJSON(); }; const getQuestCompletionItems = (questKey: string): ITypeCount[] | undefined => { @@ -327,7 +340,8 @@ export const giveKeyChainItem = async ( export const giveKeyChainMessage = async ( inventory: TInventoryDatabaseDocument, keyChainInfo: IKeyChainRequest, - questKey: IQuestKeyDatabase + questKey: IQuestKeyDatabase, + sendMessage: boolean = true ): Promise => { const keyChainMessage = getKeyChainMessage(keyChainInfo); @@ -336,7 +350,12 @@ export const giveKeyChainMessage = async ( keyChainMessage.countedAtt = []; } - await createMessage(inventory.accountOwnerId, [keyChainMessage]); + if (sendMessage) { + await createMessage(inventory.accountOwnerId, [keyChainMessage]); + } else { + if (keyChainMessage.countedAtt?.length) await addItems(inventory, keyChainMessage.countedAtt); + if (keyChainMessage.att?.length) await addItems(inventory, keyChainMessage.att); + } updateQuestStage(inventory, keyChainInfo, { m: true }); }; @@ -384,7 +403,8 @@ export const giveKeyChainMissionReward = async ( export const giveKeyChainStageTriggered = async ( inventory: TInventoryDatabaseDocument, - keyChainInfo: IKeyChainRequest + keyChainInfo: IKeyChainRequest, + sendMessage: boolean = true ): Promise => { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition const chainStages = ExportKeys[keyChainInfo.KeyChain]?.chainStages; @@ -396,7 +416,7 @@ export const giveKeyChainStageTriggered = async ( } if (chainStages[keyChainInfo.ChainStage].messageToSendWhenTriggered) { - await giveKeyChainMessage(inventory, keyChainInfo, questKey); + await giveKeyChainMessage(inventory, keyChainInfo, questKey, sendMessage); } } }; diff --git a/src/services/worldStateService.ts b/src/services/worldStateService.ts index 144698e9..b1f958c1 100644 --- a/src/services/worldStateService.ts +++ b/src/services/worldStateService.ts @@ -19,6 +19,7 @@ import type { ICalendarDay, ICalendarEvent, ICalendarSeason, + IAlert, IGoal, IInvasion, ILiteSortie, @@ -34,7 +35,8 @@ import type { IVoidTrader, IVoidTraderOffer, IWorldState, - TCircuitGameMode + TCircuitGameMode, + IFlashSale } from "../types/worldStateTypes.ts"; import { toMongoDate, toOid, version_compare } from "../helpers/inventoryHelpers.ts"; import { logger } from "../utils/logger.ts"; @@ -61,7 +63,7 @@ const sortieBosses = [ type TSortieBoss = (typeof sortieBosses)[number]; -const sortieBossToFaction: Record = { +const sortieBossToFaction: Record = { SORTIE_BOSS_HYENA: "FC_CORPUS", SORTIE_BOSS_KELA: "FC_GRINEER", SORTIE_BOSS_VOR: "FC_GRINEER", @@ -112,6 +114,89 @@ const sortieBossNode: Record, SORTIE_BOSS_VOR: "SolNode108" }; +const configAlerts: Record = { + voidCorruption2025Week1: { + _id: { $oid: "677d452e2f324ee7b90f8ccf" }, + Activation: { $date: { $numberLong: "1736524800000" } }, + Expiry: { $date: { $numberLong: "2000000000000" } }, + MissionInfo: { + location: "SolNode61", + missionType: "MT_SABOTAGE", + faction: "FC_CORPUS", + difficulty: 1, + missionReward: { + credits: 30000, + items: ["/Lotus/StoreItems/Upgrades/Mods/Pistol/DualStat/CorruptedFireRateDamagePistol"] + }, + levelOverride: "/Lotus/Levels/Proc/Corpus/CorpusShipCoreSabotage", + enemySpec: "/Lotus/Types/Game/EnemySpecs/CorpusShipEnemySpecs/CorpusShipSquadA", + extraEnemySpec: "/Lotus/Types/Game/EnemySpecs/GamemodeExtraEnemySpecs/CorpusSabotageTiersA", + minEnemyLevel: 10, + maxEnemyLevel: 15 + } + }, + voidCorruption2025Week2: { + _id: { $oid: "677d45811daeae9de40e8c0f" }, + Activation: { $date: { $numberLong: "1737129600000" } }, + Expiry: { $date: { $numberLong: "2000000000000" } }, + MissionInfo: { + location: "SettlementNode11", + missionType: "MT_DEFENSE", + faction: "FC_CORPUS", + difficulty: 1, + missionReward: { + credits: 30000, + items: ["/Lotus/StoreItems/Upgrades/Mods/Pistol/DualStat/CorruptedCritChanceFireRatePistol"] + }, + levelOverride: "/Lotus/Levels/Proc/Corpus/CorpusShipDefense", + enemySpec: "/Lotus/Types/Game/EnemySpecs/CorpusShipEnemySpecs/CorpusShipSquadDefenseB", + minEnemyLevel: 20, + maxEnemyLevel: 25, + maxRotations: 2 + } + }, + voidCorruption2025Week3: { + _id: { $oid: "677d45a494ad716c90006b9a" }, + Activation: { $date: { $numberLong: "1737734400000" } }, + Expiry: { $date: { $numberLong: "2000000000000" } }, + MissionInfo: { + location: "SolNode118", + missionType: "MT_ARTIFACT", + faction: "FC_CORPUS", + difficulty: 1, + missionReward: { + credits: 30000, + items: ["/Lotus/StoreItems/Upgrades/Mods/Pistol/DualStat/CorruptedCritDamagePistol"] + }, + levelOverride: "/Lotus/Levels/Proc/Corpus/CorpusShipDisruption", + enemySpec: "/Lotus/Types/Game/EnemySpecs/CorpusShipEnemySpecs/CorpusShipSurvivalA", + extraEnemySpec: "/Lotus/Types/Game/EnemySpecs/SpecialMissionSpecs/DisruptionCorpusShip", + customAdvancedSpawners: ["/Lotus/Types/Enemies/AdvancedSpawners/ErrantSpecterInvasion"], + minEnemyLevel: 30, + maxEnemyLevel: 35 + } + }, + voidCorruption2025Week4: { + _id: { $oid: "677d4700682d173abb0e19fe" }, + Activation: { $date: { $numberLong: "1738339200000" } }, + Expiry: { $date: { $numberLong: "2000000000000" } }, + MissionInfo: { + location: "SolNode4", + missionType: "MT_EXTERMINATION", + faction: "FC_CORPUS", + difficulty: 1, + missionReward: { + credits: 30000, + items: ["/Lotus/StoreItems/Upgrades/Mods/Pistol/DualStat/CorruptedDamageRecoilPistol"] + }, + levelOverride: "/Lotus/Levels/Proc/Corpus/CorpusShipExterminate", + enemySpec: "/Lotus/Types/Game/EnemySpecs/CorpusShipEnemySpecs/CorpusShipExterminateMixed", + minEnemyLevel: 40, + maxEnemyLevel: 45 + } + } +}; + const eidolonJobs: readonly string[] = [ "/Lotus/Types/Gameplay/Eidolon/Jobs/AssassinateBountyAss", "/Lotus/Types/Gameplay/Eidolon/Jobs/AssassinateBountyCap", @@ -291,6 +376,11 @@ export const getSortie = (day: number): ISortie => { const selectedNodes: ISortieMission[] = []; const missionTypes = new Set(); + if (enemyFaction == "FC_INFESTATION") { + // MT_RETRIEVAL may not be chosen for infested enemies (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2907) + missionTypes.add("MT_RETRIEVAL"); + } + for (let i = 0; i < 3; i++) { const randomIndex = rng.randomInt(0, nodes.length - 1); const node = nodes[randomIndex]; @@ -1392,6 +1482,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => { const weekStart = EPOCH + week * 604800000; const weekEnd = weekStart + 604800000; const date = new Date(timeMs); + const defenseWavesPerRotation = buildLabel && version_compare(buildLabel, "2025.03.18.16.07") < 0 ? 5 : 3; const worldState: IWorldState = { BuildLabel: typeof buildLabel == "string" ? buildLabel.split(" ").join("+") : buildConfig.buildLabel, @@ -1481,6 +1572,168 @@ export const getWorldState = (buildLabel?: string): IWorldState => { worldState.VoidTraders.push(vt); fullyStockBaro(vt); } + + if (config.worldState) { + for (const [key, alert] of Object.entries(configAlerts)) { + if (config.worldState[key as keyof typeof config.worldState]) { + if (alert.MissionInfo.missionType == "MT_DEFENSE") { + alert.MissionInfo.maxWaveNum = defenseWavesPerRotation * (alert.MissionInfo.maxRotations ?? 1); + alert.MissionInfo.maxRotations = undefined; + } + worldState.Alerts.push(alert); + } + } + } + + if (config.worldState?.qtccAlerts) { + const activationTimeStamp = "1759327200000"; + const expiryTimeStamp = "2000000000000"; + + worldState.Alerts.push( + { + _id: { + $oid: "68dc23c42e9d3acfa708ff3b" + }, + Activation: { $date: { $numberLong: activationTimeStamp } }, + Expiry: { $date: { $numberLong: expiryTimeStamp } }, + MissionInfo: { + location: "SolNode123", + missionType: "MT_SURVIVAL", + faction: "FC_CORPUS", + difficulty: 1, + missionReward: { + credits: 10000, + items: ["/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/Plushy2021QTCC"] + }, + levelOverride: "/Lotus/Levels/Proc/Corpus/CorpusShipSurvivalRaid", + enemySpec: "/Lotus/Types/Game/EnemySpecs/CorpusShipEnemySpecs/CorpusShipSurvivalA", + minEnemyLevel: 20, + maxEnemyLevel: 30, + descText: "/Lotus/Language/Alerts/TennoUnitedAlert", + maxWaveNum: 5 + }, + Tag: "LotusGift", + ForceUnlock: true + }, + { + _id: { + $oid: "68dc2466e298b4f04206687a" + }, + Activation: { $date: { $numberLong: activationTimeStamp } }, + Expiry: { $date: { $numberLong: expiryTimeStamp } }, + MissionInfo: { + location: "SolNode149", + missionType: "MT_DEFENSE", + faction: "FC_GRINEER", + difficulty: 1, + missionReward: { + credits: 10000, + items: ["/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/Plushy2022QTCC"] + }, + levelOverride: "/Lotus/Levels/Proc/Grineer/GrineerShipyardsDefense", + enemySpec: "/Lotus/Types/Game/EnemySpecs/GrineerShipyardsDefenseA", + minEnemyLevel: 20, + maxEnemyLevel: 30, + descText: "/Lotus/Language/Alerts/TennoUnitedAlert", + maxWaveNum: defenseWavesPerRotation * 1 + }, + Tag: "LotusGift", + ForceUnlock: true + }, + { + _id: { + $oid: "68dc26865e7cb56b820b4252" + }, + Activation: { $date: { $numberLong: activationTimeStamp } }, + Expiry: { $date: { $numberLong: expiryTimeStamp } }, + MissionInfo: { + location: "SolNode39", + missionType: "MT_EXCAVATE", + faction: "FC_GRINEER", + difficulty: 1, + missionReward: { + credits: 10000, + items: ["/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyVirminkQTCC"] + }, + levelOverride: "/Lotus/Levels/Proc/Grineer/GrineerForestExcavation", + enemySpec: "/Lotus/Types/Game/EnemySpecs/ForestGrineerExcavationA", + minEnemyLevel: 20, + maxEnemyLevel: 30, + descText: "/Lotus/Language/Alerts/TennoUnitedAlert", + maxWaveNum: 5 + }, + Tag: "LotusGift", + ForceUnlock: true + } + ); + + const storeItems: (Partial & { TypeName: string })[] = [ + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/ImageConquera2021D", RegularOverride: 1 }, + { TypeName: "/Lotus/Upgrades/Skins/Operator/Tattoos/TattooTennoI", RegularOverride: 1 }, + { TypeName: "/Lotus/Upgrades/Skins/Operator/Tattoos/TattooTennoH", RegularOverride: 1 }, + { TypeName: "/Lotus/Upgrades/Skins/Clan/QTCC2024EmblemItem", RegularOverride: 1 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/Conquera2024Display", RegularOverride: 1 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageConqueraGlyphVII", RegularOverride: 1 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageConqueraGlyphVI", RegularOverride: 1 }, + { TypeName: "/Lotus/Interface/Graphics/CustomUI/ConqueraStyle", RegularOverride: 1 }, + { TypeName: "/Lotus/Interface/Graphics/CustomUI/Backgrounds/ConqueraBackground", RegularOverride: 1 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/Conquera2021Deco", RegularOverride: 1 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/ImageConquera2022A", RegularOverride: 1 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/ImageConquera2021B", RegularOverride: 1 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/ImageConquera2021A", RegularOverride: 1 }, + { TypeName: "/Lotus/Upgrades/Skins/Scarves/TnCharityRibbonSyandana", RegularOverride: 1 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/ImageConquera2021C", RegularOverride: 1 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/Venus/Conquera2023CommunityDisplay", RegularOverride: 1 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageConqueraGlyphUpdated", RegularOverride: 1 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/ImageConquera", RegularOverride: 1 }, + { TypeName: "/Lotus/Upgrades/Skins/Sigils/QTCC2023ConqueraSigil", RegularOverride: 1 }, + { TypeName: "/Lotus/Upgrades/Skins/Sigils/ConqueraSigil", RegularOverride: 1 }, + { TypeName: "/Lotus/Upgrades/Skins/Effects/Conquera2022Ephemera", RegularOverride: 1 }, + { TypeName: "/Lotus/Upgrades/Skins/Effects/ConqueraEphemera", RegularOverride: 1 }, + { TypeName: "/Lotus/Upgrades/Skins/Armor/TnCharityRibbonArmor/ConqueraArmorL", RegularOverride: 1 }, + { TypeName: "/Lotus/Upgrades/Skins/Armor/TnCharityRibbonArmor/ConqueraArmorA", RegularOverride: 1 }, + { TypeName: "/Lotus/Upgrades/Skins/Armor/TnCharityRibbonArmor/ConqueraChestRibbon", RegularOverride: 1 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/Plushies/PlushyProtectorStalker", PremiumOverride: 35 } + ]; + + worldState.FlashSales.push( + ...storeItems.map(item => ({ + ...{ + StartDate: { $date: { $numberLong: activationTimeStamp } }, + EndDate: { $date: { $numberLong: expiryTimeStamp } }, + ProductExpiryOverride: { $date: { $numberLong: expiryTimeStamp } }, + ShowInMarket: item.ShowInMarket ?? true, + HideFromMarket: item.HideFromMarket ?? false, + SupporterPack: item.SupporterPack ?? false, + Discount: item.Discount ?? 0, + BogoBuy: item.BogoBuy ?? 0, + BogoGet: item.BogoGet ?? 0, + RegularOverride: item.RegularOverride ?? 0, + PremiumOverride: item.PremiumOverride ?? 0 + }, + ...item + })) + ); + + const seasonalItems = storeItems.map(item => item.TypeName); + + const seasonalCategory = worldState.InGameMarket.LandingPage.Categories.find( + c => c.CategoryName == "COMMUNITY" + ); + if (seasonalCategory) { + seasonalCategory.Items ??= []; + seasonalCategory.Items.push(...seasonalItems); + } else { + worldState.InGameMarket.LandingPage.Categories.push({ + CategoryName: "COMMUNITY", + Name: "/Lotus/Language/Store/CommunityCategoryTitle", + Icon: "community", + AddToMenu: true, + Items: seasonalItems + }); + } + } + const isFebruary = date.getUTCMonth() == 1; if (config.worldState?.starDaysOverride ?? isFebruary) { worldState.Goals.push({ @@ -1545,7 +1798,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => { Personal: true, Bounty: true, ClampNodeScores: true, - Node: "EventNode28", // Incompatible with Wolf Hunt (2025), Orphix Venom, Warframe Anniversary + Node: "EventNode28", // Incompatible with Wolf Hunt, Orphix Venom, Warframe Anniversary MissionKeyName: "/Lotus/Types/Keys/GalleonRobberyAlertB", Desc: "/Lotus/Language/Events/GalleonRobberyEventMissionTitle", Icon: "/Lotus/Interface/Icons/Player/GalleonRobberiesEvent.png", @@ -1846,82 +2099,39 @@ export const getWorldState = (buildLabel?: string): IWorldState => { NightLevel: "/Lotus/Levels/GrineerBeach/GrineerBeachEventNight.level" }); - const baseStoreItem = { - ShowInMarket: true, - HideFromMarket: false, - SupporterPack: false, - Discount: 0, - BogoBuy: 0, - BogoGet: 0, - StartDate: { $date: { $numberLong: activationTimeStamp } }, - EndDate: { $date: { $numberLong: expiryTimeStamp } }, - ProductExpiryOverride: { $date: { $numberLong: expiryTimeStamp } } - }; - - const storeItems = [ - { - TypeName: "/Lotus/Types/StoreItems/Packages/WaterFightNoggleBundle", - PremiumOverride: 240, - RegularOverride: 0 - }, - { - TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFBeastMasterBobbleHead", - PremiumOverride: 35, - RegularOverride: 0 - }, - { - TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFChargerBobbleHead", - PremiumOverride: 35, - RegularOverride: 0 - }, - { - TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFEngineerBobbleHead", - PremiumOverride: 35, - RegularOverride: 0 - }, - { - TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFGruntBobbleHead", - PremiumOverride: 35, - RegularOverride: 0 - }, - { - TypeName: "/Lotus/Types/StoreItems/AvatarImages/ImagePopsicleGrineerPurple", - PremiumOverride: 0, - RegularOverride: 1 - }, - { - TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFHealerBobbleHead", - PremiumOverride: 35, - RegularOverride: 0 - }, - { - TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFHeavyBobbleHead", - PremiumOverride: 35, - RegularOverride: 0 - }, - { - TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFHellionBobbleHead", - PremiumOverride: 35, - RegularOverride: 0 - }, - { - TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFSniperBobbleHead", - PremiumOverride: 35, - RegularOverride: 0 - }, - { - TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFTankBobbleHead", - PremiumOverride: 35, - RegularOverride: 0 - }, - { - TypeName: "/Lotus/Types/StoreItems/SuitCustomizations/ColourPickerRollers", - PremiumOverride: 75, - RegularOverride: 0 - } + const storeItems: (Partial & { TypeName: string })[] = [ + { TypeName: "/Lotus/Types/StoreItems/Packages/WaterFightNoggleBundle", PremiumOverride: 240 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFBeastMasterBobbleHead", PremiumOverride: 35 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFChargerBobbleHead", PremiumOverride: 35 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFEngineerBobbleHead", PremiumOverride: 35 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFGruntBobbleHead", PremiumOverride: 35 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/ImagePopsicleGrineerPurple", RegularOverride: 1 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFHealerBobbleHead", PremiumOverride: 35 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFHeavyBobbleHead", PremiumOverride: 35 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFHellionBobbleHead", PremiumOverride: 35 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFSniperBobbleHead", PremiumOverride: 35 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFTankBobbleHead", PremiumOverride: 35 }, + { TypeName: "/Lotus/Types/StoreItems/SuitCustomizations/ColourPickerRollers", PremiumOverride: 75 } ]; - worldState.FlashSales.push(...storeItems.map(item => ({ ...baseStoreItem, ...item }))); + worldState.FlashSales.push( + ...storeItems.map(item => ({ + ...{ + StartDate: { $date: { $numberLong: activationTimeStamp } }, + EndDate: { $date: { $numberLong: expiryTimeStamp } }, + ProductExpiryOverride: { $date: { $numberLong: expiryTimeStamp } }, + ShowInMarket: item.ShowInMarket ?? true, + HideFromMarket: item.HideFromMarket ?? false, + SupporterPack: item.SupporterPack ?? false, + Discount: item.Discount ?? 0, + BogoBuy: item.BogoBuy ?? 0, + BogoGet: item.BogoGet ?? 0, + RegularOverride: item.RegularOverride ?? 0, + PremiumOverride: item.PremiumOverride ?? 0 + }, + ...item + })) + ); const seasonalItems = storeItems.map(item => item.TypeName); @@ -1942,7 +2152,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => { } if (config.worldState?.anniversary != undefined) { - // Incompatible with: Use Tag from Warframe Anniversary for old Events, Wolf Hunt (2025), Galleon Of Ghouls, Hallowed Flame, Hallowed Nightmares, Dog Days, Proxy Rebellion, Long Shadow + // Incompatible with: Use Tag from Warframe Anniversary for old Events, Wolf Hunt, Galleon Of Ghouls, Hallowed Flame, Hallowed Nightmares, Dog Days, Proxy Rebellion, Long Shadow const goalsByWeek: Partial[][] = [ [ { @@ -2161,65 +2371,108 @@ export const getWorldState = (buildLabel?: string): IWorldState => { }); } - if (config.worldState?.wolfHunt) { - worldState.Goals.push({ - _id: { - $oid: "67ed7672798d6466172e3b9d" - }, - Activation: { - $date: { - $numberLong: "1743616800000" - } - }, - Expiry: { - $date: { - $numberLong: "2000000000000" - } - }, - Count: 0, - Goal: 3, - InterimGoals: [1, 2], - BonusGoal: 4, - Success: 0, - Personal: true, - Bounty: true, - ClampNodeScores: true, - Node: "EventNode29", - ConcurrentMissionKeyNames: [ - "/Lotus/Types/Keys/WolfTacAlertReduxB", - "/Lotus/Types/Keys/WolfTacAlertReduxC", - "/Lotus/Types/Keys/WolfTacAlertReduxD" - ], - ConcurrentNodeReqs: [1, 2, 3], - ConcurrentNodes: ["EventNode28", "EventNode39", "EventNode40"], // Incompatible with Galleon Of Ghouls, Orphix Venom, Warframe Anniversary - MissionKeyName: "/Lotus/Types/Keys/WolfTacAlertReduxA", - Faction: "FC_GRINEER", - Desc: "/Lotus/Language/Alerts/WolfAlert", - Icon: "/Lotus/Interface/Icons/Npcs/Seasonal/WolfStalker.png", - Tag: "WolfHuntRedux", - InterimRewards: [ - { - credits: 50000, - items: ["/Lotus/StoreItems/Types/Recipes/Weapons/WeaponParts/ThrowingHammerHandle"] + if (config.worldState?.wolfHunt != undefined) { + if (config.worldState.wolfHunt == 0) { + worldState.Goals.push({ + _id: { + $oid: "67ed7672798d6466172e3b9c" }, - { - credits: 50000, - items: ["/Lotus/StoreItems/Types/Recipes/Weapons/WeaponParts/ThrowingHammerHead"] + Activation: { + $date: { + $numberLong: "1743616800000" + } + }, + Expiry: { + $date: { + $numberLong: "2000000000000" + } + }, + Count: 0, + Goal: 1, + BonusGoal: 2, + Success: 0, + Personal: true, + Bounty: true, + ClampNodeScores: true, + Node: "EventNode29", + ConcurrentMissionKeyNames: ["/Lotus/Types/Keys/WolfTacAlertB"], + ConcurrentNodeReqs: [1], + ConcurrentNodes: ["EventNode28"], // Incompatible with Galleon Of Ghouls, Orphix Venom, Warframe Anniversary + MissionKeyName: "/Lotus/Types/Keys/WolfTacAlertA", + Faction: "FC_GRINEER", + Desc: "/Lotus/Language/Alerts/WolfAlert", + Icon: "/Lotus/Interface/Icons/Npcs/Seasonal/WolfStalker.png", + Tag: "WolfHuntRedux", // unfaithful + Reward: { + countedItems: [{ ItemType: "/Lotus/Types/Items/MiscItems/Alertium", ItemCount: 10 }] + }, + BonusReward: { + items: [ + "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawRifleRandomMod", + "/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem" + ] } - ], - Reward: { - credits: 50000, - items: ["/Lotus/StoreItems/Types/Recipes/Weapons/WeaponParts/ThrowingHammerMotor"] - }, - BonusReward: { - credits: 50000, - items: [ - "/Lotus/StoreItems/Types/Recipes/Weapons/ThrowingHammerBlueprint", - "/Lotus/StoreItems/Types/Items/MiscItems/OrokinCatalyst", - "/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem" - ] - } - }); + }); + } else if (config.worldState.wolfHunt == 1) { + worldState.Goals.push({ + _id: { + $oid: "67ed7672798d6466172e3b9d" + }, + Activation: { + $date: { + $numberLong: "1743616800000" + } + }, + Expiry: { + $date: { + $numberLong: "2000000000000" + } + }, + Count: 0, + Goal: 3, + InterimGoals: [1, 2], + BonusGoal: 4, + Success: 0, + Personal: true, + Bounty: true, + ClampNodeScores: true, + Node: "EventNode29", + ConcurrentMissionKeyNames: [ + "/Lotus/Types/Keys/WolfTacAlertReduxB", + "/Lotus/Types/Keys/WolfTacAlertReduxC", + "/Lotus/Types/Keys/WolfTacAlertReduxD" + ], + ConcurrentNodeReqs: [1, 2, 3], + ConcurrentNodes: ["EventNode28", "EventNode39", "EventNode40"], // Incompatible with Galleon Of Ghouls, Orphix Venom, Warframe Anniversary + MissionKeyName: "/Lotus/Types/Keys/WolfTacAlertReduxA", + Faction: "FC_GRINEER", + Desc: "/Lotus/Language/Alerts/WolfAlert", + Icon: "/Lotus/Interface/Icons/Npcs/Seasonal/WolfStalker.png", + Tag: "WolfHuntRedux", + InterimRewards: [ + { + credits: 50000, + items: ["/Lotus/StoreItems/Types/Recipes/Weapons/WeaponParts/ThrowingHammerHandle"] + }, + { + credits: 50000, + items: ["/Lotus/StoreItems/Types/Recipes/Weapons/WeaponParts/ThrowingHammerHead"] + } + ], + Reward: { + credits: 50000, + items: ["/Lotus/StoreItems/Types/Recipes/Weapons/WeaponParts/ThrowingHammerMotor"] + }, + BonusReward: { + credits: 50000, + items: [ + "/Lotus/StoreItems/Types/Recipes/Weapons/ThrowingHammerBlueprint", + "/Lotus/StoreItems/Types/Items/MiscItems/OrokinCatalyst", + "/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem" + ] + } + }); + } } const tagsForOlderGoals: string[] = [ @@ -2316,22 +2569,22 @@ export const getWorldState = (buildLabel?: string): IWorldState => { // 2016 [ { - items: ["/Lotus/StoreItems/Upgrades/Skins/Sigils/OrokinCatalyst"] - }, - { - items: ["/Lotus/StoreItems/Upgrades/Skins/Sigils/DotD2016Sigil"] + items: ["/Lotus/StoreItems/Types/Items/MiscItems/OrokinCatalyst"] }, { items: [ - "/Lotus/StoreItems/Types/Items/MiscItems/OrokinReactor", + "/Lotus/StoreItems/Upgrades/Skins/Sigils/DotD2016Sigil", "/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem" ] + }, + { + items: ["/Lotus/StoreItems/Types/Items/MiscItems/OrokinReactor"] } ], // 2015 [ { - items: ["/Lotus/StoreItems/Upgrades/Skins/Sigils/OrokinCatalyst"] + items: ["/Lotus/StoreItems/Types/Items/MiscItems/OrokinCatalyst"] }, { items: ["/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem"] @@ -2517,22 +2770,18 @@ export const getWorldState = (buildLabel?: string): IWorldState => { const isOctober = date.getUTCMonth() == 9; // October = month index 9 if (config.worldState?.naberusNightsOverride ?? isOctober) { + const activationTimeStamp = config.worldState?.naberusNightsOverride + ? "1727881200000" + : Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1).toString(); + + const expiryTimeStamp = config.worldState?.naberusNightsOverride + ? "2000000000000" + : Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 1).toString(); + worldState.Goals.push({ _id: { $oid: "66fd602de1778d583419e8e7" }, - Activation: { - $date: { - $numberLong: config.worldState?.naberusNightsOverride - ? "1727881200000" - : Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1).toString() - } - }, - Expiry: { - $date: { - $numberLong: config.worldState?.naberusNightsOverride - ? "2000000000000" - : Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 1).toString() - } - }, + Activation: { $date: { $numberLong: activationTimeStamp } }, + Expiry: { $date: { $numberLong: expiryTimeStamp } }, Count: 0, Goal: 0, Success: 0, @@ -2543,6 +2792,181 @@ export const getWorldState = (buildLabel?: string): IWorldState => { Tag: "DeimosHalloween", Node: "DeimosHub" }); + + const storeItems: (Partial & { TypeName: string })[] = [ + { TypeName: "/Lotus/Types/StoreItems/Packages/Halloween2023GlyphBundleA", PremiumOverride: 65 }, + { TypeName: "/Lotus/Types/StoreItems/Packages/Halloween2021GlyphBundle", PremiumOverride: 65 }, + { TypeName: "/Lotus/Types/StoreItems/Packages/Halloween2019GlyphBundleA", PremiumOverride: 65 }, + { TypeName: "/Lotus/Types/StoreItems/Packages/Halloween2019GlyphBundleB", PremiumOverride: 65 }, + { TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenGlyphBundle", PremiumOverride: 65 }, + { TypeName: "/Lotus/Types/StoreItems/Packages/Halloween2023ArmorBundle", PremiumOverride: 125 }, + { TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenCrpCircArmorPack", PremiumOverride: 100 }, + { TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenScarfBundleB", PremiumOverride: 80 }, + { TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenSkinPack", PremiumOverride: 175 }, + { TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenShipSkinBundle", PremiumOverride: 80 }, + { TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenSkinPackC", PremiumOverride: 175 }, + { TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenSkinPackII", PremiumOverride: 145 }, + { TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenScarfBundle", PremiumOverride: 130 }, + { TypeName: "/Lotus/Types/StoreItems/Packages/AcolyteNoggleBundle", PremiumOverride: 160 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/AcolyteAreaCasterBobbleHead", PremiumOverride: 35 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/AcolyteDuellistBobbleHead", PremiumOverride: 35 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/AcolyteControlBobbleHead", PremiumOverride: 35 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/AcolyteHeavyBobbleHead", PremiumOverride: 35 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/AcolyteRogueBobbleHead", PremiumOverride: 35 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/AcolyteStrikerBobbleHead", PremiumOverride: 35 }, + { TypeName: "/Lotus/Types/StoreItems/SuitCustomizations/ColourPickerHalloweenItemA", RegularOverride: 1 }, + { TypeName: "/Lotus/Upgrades/Skins/Armor/Halloween2014Wings/Halloween2014ArmArmor", PremiumOverride: 50 }, + { TypeName: "/Lotus/Upgrades/Skins/Festivities/PumpkinHead", RegularOverride: 1 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenRegorAxeShield", PremiumOverride: 20 }, + { + TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/Halloween2019CheshireKavat", + PremiumOverride: 20 + }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenAkvasto", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenAngstrum", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenBoltor", PremiumOverride: 20 }, + { + TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/Halloween2019GhostChibiWisp", + PremiumOverride: 20 + }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenBraton", PremiumOverride: 20 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageHalloween2016A", PremiumOverride: 20 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageHalloween2016C", PremiumOverride: 20 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageHalloween2016B", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenBuzlok", PremiumOverride: 20 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageHalloween2016D", PremiumOverride: 20 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/Halloween2019CreepyClem", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenDaikyu", PremiumOverride: 20 }, + { + TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageHalloween2021Dethcube", + PremiumOverride: 20 + }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenDragonNikana", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenDualZoren", PremiumOverride: 20 }, + { + TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/Halloween2019FrankenCorpus", + PremiumOverride: 20 + }, + { + TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageHalloween2021Grineer", + PremiumOverride: 20 + }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenGlaive", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenGalatine", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenGrakata", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenGorgon", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenGlaxion", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenTwinGremlins", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenGrinlok", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Scarves/HalloweenFireFlyScarf", PremiumOverride: 90 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenImperator", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenKronen", PremiumOverride: 20 }, + { + TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageHalloween2021Lotus", + PremiumOverride: 20 + }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenJatKittag", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Scarves/HalloweenKyropteraScarf", PremiumOverride: 50 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenKunai", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Liset/LisetSkinHalloween", PremiumOverride: 50 }, + { TypeName: "/Lotus/Upgrades/Skins/Liset/LisetInsectSkinHalloween", PremiumOverride: 50 }, + { + TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageChillingGlyphFour", + PremiumOverride: 20 + }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenMarelok", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenNikana", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenNukor", PremiumOverride: 20 }, + { + TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageHalloween2021Loid", + PremiumOverride: 20 + }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenOpticor", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenOrthos", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenParis", PremiumOverride: 20 }, + { + TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageChillingGlyphTwo", + PremiumOverride: 20 + }, + { TypeName: "/Lotus/Upgrades/Skins/Armor/CrpCircleArmour/HalloweenCrpCircC", PremiumOverride: 45 }, + { TypeName: "/Lotus/Upgrades/Skins/Armor/CrpCircleArmour/HalloweenCrpCircA", PremiumOverride: 50 }, + { TypeName: "/Lotus/Upgrades/Skins/Armor/CrpCircleArmour/HalloweenCrpCircL", PremiumOverride: 35 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenScindo", PremiumOverride: 20 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/Halloween2019GhoulGrave", PremiumOverride: 20 }, + { + TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageHalloween2021Pumpkin", + PremiumOverride: 20 + }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenSarpa", PremiumOverride: 20 }, + { + TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageChillingGlyphThree", + PremiumOverride: 20 + }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenSilvaAndAegis", PremiumOverride: 20 }, + { + TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageChillingGlyphOne", + PremiumOverride: 20 + }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenSoma", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenSkana", PremiumOverride: 20 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/Halloween2019SlimeLoki", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenSobek", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenSonicor", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenSimulor", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenTonkor", PremiumOverride: 20 }, + { + TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/Halloween2019TrickOrBalas", + PremiumOverride: 20 + }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenSpira", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenStradavar", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenTwinGrakatas", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenArchSword", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenLato", PremiumOverride: 20 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/Halloween2019Werefested", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Scarves/HalloweenErosionCape", PremiumOverride: 50 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenVasto", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenDarkSplitSword", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenDarkDagger", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Scarves/HalloweenGrnBannerScarf", PremiumOverride: 75 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenAmprex", PremiumOverride: 20 }, + { TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenSkinPackD", PremiumOverride: 180 } + ]; + + worldState.FlashSales.push( + ...storeItems.map(item => ({ + ...{ + StartDate: { $date: { $numberLong: activationTimeStamp } }, + EndDate: { $date: { $numberLong: expiryTimeStamp } }, + ProductExpiryOverride: { $date: { $numberLong: expiryTimeStamp } }, + ShowInMarket: item.ShowInMarket ?? true, + HideFromMarket: item.HideFromMarket ?? false, + SupporterPack: item.SupporterPack ?? false, + Discount: item.Discount ?? 0, + BogoBuy: item.BogoBuy ?? 0, + BogoGet: item.BogoGet ?? 0, + RegularOverride: item.RegularOverride ?? 0, + PremiumOverride: item.PremiumOverride ?? 0 + }, + ...item + })) + ); + + const seasonalItems = storeItems.map(item => item.TypeName); + const seasonalCategory = worldState.InGameMarket.LandingPage.Categories.find(c => c.CategoryName == "SEASONAL"); + + if (seasonalCategory) { + seasonalCategory.Items ??= []; + seasonalCategory.Items.push(...seasonalItems); + } else { + worldState.InGameMarket.LandingPage.Categories.push({ + CategoryName: "SEASONAL", + Name: "/Lotus/Language/Store/SeasonalCategoryTitle", + Icon: "seasonal", + AddToMenu: true, + Items: seasonalItems + }); + } } if (config.worldState?.bellyOfTheBeast) { @@ -2620,7 +3044,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => { Success: 0, Personal: true, Best: true, - Node: "EventNode28", // Incompatible with Galleon Of Ghouls, Wolf Hunt (2025) + Node: "EventNode28", // Incompatible with Galleon Of Ghouls, Wolf Hunt MissionKeyName: "/Lotus/Types/Keys/MechSurvivalGrineerGalleon", Faction: "FC_SENTIENT", Desc: "/Lotus/Language/Events/MechEventMissionTier2", @@ -3279,7 +3703,8 @@ export const getLiteSortie = (week: number): ILiteSortie => { value.missionType != "MT_ASSASSINATION" && value.missionType != "MT_JUNCTION" && value.missionType != "MT_LANDSCAPE" && - value.missionType != "MT_RAILJACK" + value.missionType != "MT_RAILJACK" && + key != "SolNode63" // This node uses GrineerForestTilesetCaves which only supports MT_CAPTURE, which is not valid for LiteSorties. ) { nodes.push(key); } diff --git a/src/services/wsService.ts b/src/services/wsService.ts index 0e35d0c2..6850e1a2 100644 --- a/src/services/wsService.ts +++ b/src/services/wsService.ts @@ -8,6 +8,7 @@ import type { IDatabaseAccountJson } from "../types/loginTypes.ts"; import type { HydratedDocument } from "mongoose"; import { logError, logger } from "../utils/logger.ts"; import type { Request } from "express"; +import type { ITunables } from "../types/bootstrapperTypes.ts"; let wsServer: WebSocketServer | undefined; let wssServer: WebSocketServer | undefined; @@ -89,8 +90,9 @@ interface IWsMsgToClient { logged_out?: boolean; have_game_ws?: boolean; - // to game + // to game/bootstrapper (https://openwf.io/bootstrapper-manual) sync_inventory?: boolean; + tunables?: ITunables; } const wsOnConnect = (ws: WebSocket, req: http.IncomingMessage): void => { diff --git a/src/types/bootstrapperTypes.ts b/src/types/bootstrapperTypes.ts new file mode 100644 index 00000000..f011b85a --- /dev/null +++ b/src/types/bootstrapperTypes.ts @@ -0,0 +1,9 @@ +// This is specific to the OpenWF Bootstrapper: https://openwf.io/bootstrapper-manual +export interface ITunables { + token?: string; + prohibit_skip_mission_start_timer?: boolean; + prohibit_fov_override?: boolean; + prohibit_freecam?: boolean; + prohibit_teleport?: boolean; + prohibit_scripts?: boolean; +} diff --git a/src/types/inventoryTypes/inventoryTypes.ts b/src/types/inventoryTypes/inventoryTypes.ts index 83d00c1d..cc44c061 100644 --- a/src/types/inventoryTypes/inventoryTypes.ts +++ b/src/types/inventoryTypes/inventoryTypes.ts @@ -61,6 +61,9 @@ export interface IAccountCheats { nemesisHintProgressMultiplierGrineer?: number; nemesisHintProgressMultiplierCorpus?: number; nemesisExtraWeapon?: number; + spoofMasteryRank?: number; + relicRewardItemCountMultiplier?: number; + nightwaveStandingMultiplier?: number; } export interface IInventoryDatabase @@ -92,6 +95,7 @@ export interface IInventoryDatabase | "NextRefill" | "Nemesis" | "NemesisHistory" + | "LastNemesisAllySpawnTime" | "EntratiVaultCountResetDate" | "BrandedSuits" | "LockedWeaponGroup" @@ -136,6 +140,7 @@ export interface IInventoryDatabase NextRefill?: Date; Nemesis?: INemesisDatabase; NemesisHistory?: INemesisBaseDatabase[]; + LastNemesisAllySpawnTime?: Date; EntratiVaultCountResetDate?: Date; BrandedSuits?: Types.ObjectId[]; LockedWeaponGroup?: ILockedWeaponGroupDatabase; @@ -338,6 +343,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu EmailItems: ITypeCount[]; CompletedSyndicates: string[]; FocusXP?: IFocusXP; + FocusCapacity?: number; Wishlist: string[]; Alignment?: IAlignment; CompletedSorties: string[]; @@ -360,7 +366,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu ThemeStyle: string; ThemeBackground: string; ThemeSounds: string; - BountyScore: number; + BountyScore?: number; //ChallengeInstanceStates: IChallengeInstanceState[]; LoginMilestoneRewards: string[]; RecentVendorPurchases?: IRecentVendorPurchaseClient[]; @@ -372,7 +378,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu //InvasionChainProgress: IInvasionChainProgress[]; Nemesis?: INemesisClient; NemesisHistory?: INemesisBaseClient[]; - //LastNemesisAllySpawnTime?: IMongoDate; + LastNemesisAllySpawnTime?: IMongoDate; Settings?: ISettings; PersonalTechProjects: IPersonalTechProjectClient[]; PlayerSkills: IPlayerSkills; @@ -430,6 +436,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu Ship?: IOrbiterClient; // U22 and below, response only ClaimedJunctionChallengeRewards?: string[]; // U39 SpecialItemRewardAttenuation?: IRewardAttenuation[]; // Baro's Void Surplus + NokkoColony?: INokkoColony; // Field Guide } export interface IAffiliation { @@ -636,6 +643,7 @@ export interface IFocusUpgrade { ItemType: string; Level?: number; IsUniversal?: boolean; + IsActive?: number; // Focus 2.0 } export interface IFocusXP { @@ -1213,3 +1221,13 @@ export interface IHubNpcCustomization { Pattern: string; Tag: string; } + +export interface IJournalEntry { + EntryType: string; + Progress: number; +} + +export interface INokkoColony { + FeedLevel: number; + JournalEntries: IJournalEntry[]; +} diff --git a/src/types/purchaseTypes.ts b/src/types/purchaseTypes.ts index e6d5ba1e..4b1542c2 100644 --- a/src/types/purchaseTypes.ts +++ b/src/types/purchaseTypes.ts @@ -9,7 +9,8 @@ import type { TEquipmentKey, ICrewMemberClient, IKubrowPetPrintClient, - IUpgradeClient + IUpgradeClient, + IQuestKeyClient } from "./inventoryTypes/inventoryTypes.ts"; export enum PurchaseSource { @@ -83,6 +84,7 @@ export type IInventoryChanges = { CrewMembers?: ICrewMemberClient[]; KubrowPetPrints?: IKubrowPetPrintClient[]; Upgrades?: IUpgradeClient[]; // TOVERIFY + QuestKeys?: IQuestKeyClient[]; } & Record< Exclude< string, diff --git a/src/types/requestTypes.ts b/src/types/requestTypes.ts index 0dac8d75..ed82d296 100644 --- a/src/types/requestTypes.ts +++ b/src/types/requestTypes.ts @@ -178,6 +178,7 @@ export interface IRewardInfo { goalManifest?: string; invasionId?: string; invasionAllyFaction?: "FC_GRINEER" | "FC_CORPUS"; + alertId?: string; sortieId?: string; sortieTag?: "Mission1" | "Mission2" | "Final"; sortiePrereqs?: string[]; @@ -233,11 +234,6 @@ export interface IUpgradeOperation { PolarizeValue: ArtifactPolarity; PolarityRemap: IPolarity[]; } -export interface IUnlockShipFeatureRequest { - Feature: string; - KeyChain: string; - ChainStage: number; -} export interface IVoidTearParticipantInfo { AccountId: string; diff --git a/src/types/worldStateTypes.ts b/src/types/worldStateTypes.ts index f39c4bf7..358e4e80 100644 --- a/src/types/worldStateTypes.ts +++ b/src/types/worldStateTypes.ts @@ -1,4 +1,4 @@ -import type { IMissionReward } from "warframe-public-export-plus"; +import type { IMissionReward, TFaction, TMissionType } from "warframe-public-export-plus"; import type { IMongoDate, IOid } from "./commonTypes.ts"; export interface IWorldState { @@ -7,7 +7,7 @@ export interface IWorldState { Time: number; InGameMarket: IInGameMarket; Goals: IGoal[]; - Alerts: []; + Alerts: IAlert[]; Sorties: ISortie[]; LiteSorties: ILiteSortie[]; SyndicateMissions: ISyndicateMissionInfo[]; @@ -35,6 +35,34 @@ export interface IWorldState { Tmp?: string; } +export interface IAlert { + _id: IOid; + Activation: IMongoDate; + Expiry: IMongoDate; + MissionInfo: IAlertMissionInfo; + Tag?: string; + ForceUnlock?: true; +} + +export interface IAlertMissionInfo { + location: string; + missionType: TMissionType; + faction: TFaction; + difficulty: number; + missionReward?: IMissionReward; + levelOverride?: string; + enemySpec?: string; + extraEnemySpec?: string; + customAdvancedSpawners?: string[]; + minEnemyLevel?: number; + maxEnemyLevel?: number; + maxWaveNum?: number; + descText?: string; + enemyCacheOverride?: string; + + maxRotations?: number; // SNS specific field +} + export interface IGoal { _id: IOid; Activation: IMongoDate; diff --git a/static/fixed_responses/allDialogue.json b/static/fixed_responses/allDialogue.json index 4b47303e..72bdc5b4 100644 --- a/static/fixed_responses/allDialogue.json +++ b/static/fixed_responses/allDialogue.json @@ -136,5 +136,7 @@ "ConquestSetupIntro", "EntratiLabConquestHardModeUnlocked", "/Lotus/Language/Npcs/KonzuPostNewWar", - "/Lotus/Language/SolarisVenus/EudicoPostNewWar" + "/Lotus/Language/SolarisVenus/EudicoPostNewWar", + "/Lotus/Language/NokkoColony/NokkoVendorName", + "NokkoVisions_FirstVisit" ] diff --git a/static/fixed_responses/getSkuCatalogU40.json b/static/fixed_responses/getSkuCatalogU40.json new file mode 100644 index 00000000..7a9d4d70 --- /dev/null +++ b/static/fixed_responses/getSkuCatalogU40.json @@ -0,0 +1,409 @@ +{ + "Skus": [ + { + "productId": 17, + "listPrice": { + "formatted": "4.99 USD", + "raw": "4.99", + "value": 4.99 + }, + "basePrice": { + "formatted": "4.99 USD", + "raw": "4.99", + "value": 4.99 + }, + "currencyCode": "USD", + "owned": false + }, + { + "productId": 18, + "listPrice": { + "formatted": "9.99 USD", + "raw": "9.99", + "value": 9.99 + }, + "basePrice": { + "formatted": "9.99 USD", + "raw": "9.99", + "value": 9.99 + }, + "currencyCode": "USD", + "owned": false + }, + { + "productId": 160, + "listPrice": { + "formatted": "19.99 USD", + "raw": "19.99", + "value": 19.99 + }, + "basePrice": { + "formatted": "19.99 USD", + "raw": "19.99", + "value": 19.99 + }, + "currencyCode": "USD", + "owned": false + }, + { + "productId": 257, + "listPrice": { + "formatted": "199.99 USD", + "raw": "199.99", + "value": 199.99 + }, + "basePrice": { + "formatted": "199.99 USD", + "raw": "199.99", + "value": 199.99 + }, + "currencyCode": "USD", + "owned": false + }, + { + "productId": 258, + "listPrice": { + "formatted": "99.99 USD", + "raw": "99.99", + "value": 99.99 + }, + "basePrice": { + "formatted": "99.99 USD", + "raw": "99.99", + "value": 99.99 + }, + "currencyCode": "USD", + "owned": false + }, + { + "productId": 259, + "listPrice": { + "formatted": "49.99 USD", + "raw": "49.99", + "value": 49.99 + }, + "basePrice": { + "formatted": "49.99 USD", + "raw": "49.99", + "value": 49.99 + }, + "currencyCode": "USD", + "owned": false + }, + { + "productId": 785, + "listPrice": { + "formatted": "19.99 USD", + "raw": "19.99", + "value": 19.99 + }, + "basePrice": { + "formatted": "19.99 USD", + "raw": "19.99", + "value": 19.99 + }, + "currencyCode": "USD", + "owned": false + }, + { + "productId": 786, + "listPrice": { + "formatted": "39.99 USD", + "raw": "39.99", + "value": 39.99 + }, + "basePrice": { + "formatted": "39.99 USD", + "raw": "39.99", + "value": 39.99 + }, + "currencyCode": "USD", + "owned": false + }, + { + "productId": 787, + "listPrice": { + "formatted": "79.99 USD", + "raw": "79.99", + "value": 79.99 + }, + "basePrice": { + "formatted": "79.99 USD", + "raw": "79.99", + "value": 79.99 + }, + "currencyCode": "USD", + "owned": false + }, + { + "productId": 979, + "listPrice": { + "formatted": "4.99 USD", + "raw": "4.99", + "value": 4.99 + }, + "basePrice": { + "formatted": "4.99 USD", + "raw": "4.99", + "value": 4.99 + }, + "currencyCode": "USD", + "owned": false + }, + { + "productId": 10037, + "listPrice": { + "formatted": "54.99 USD", + "raw": "54.99", + "value": 54.99 + }, + "basePrice": { + "formatted": "54.99 USD", + "raw": "54.99", + "value": 54.99 + }, + "currencyCode": "USD", + "owned": false + }, + { + "productId": 10054, + "listPrice": { + "formatted": "49.99 USD", + "raw": "49.99", + "value": 49.99 + }, + "basePrice": { + "formatted": "49.99 USD", + "raw": "49.99", + "value": 49.99 + }, + "currencyCode": "USD", + "owned": false + }, + { + "productId": 10061, + "listPrice": { + "formatted": "0.99 USD", + "raw": "0.99", + "value": 0.99 + }, + "basePrice": { + "formatted": "0.99 USD", + "raw": "0.99", + "value": 0.99 + }, + "currencyCode": "USD", + "owned": false + }, + { + "productId": 10076, + "listPrice": { + "formatted": "29.99 USD", + "raw": "29.99", + "value": 29.99 + }, + "basePrice": { + "formatted": "29.99 USD", + "raw": "29.99", + "value": 29.99 + }, + "currencyCode": "USD", + "owned": false + }, + { + "productId": 10078, + "listPrice": { + "formatted": "24.99 USD", + "raw": "24.99", + "value": 24.99 + }, + "basePrice": { + "formatted": "24.99 USD", + "raw": "24.99", + "value": 24.99 + }, + "currencyCode": "USD", + "owned": false + }, + { + "productId": 10080, + "listPrice": { + "formatted": "49.99 USD", + "raw": "49.99", + "value": 49.99 + }, + "basePrice": { + "formatted": "49.99 USD", + "raw": "49.99", + "value": 49.99 + }, + "currencyCode": "USD", + "owned": false + }, + { + "productId": 10081, + "listPrice": { + "formatted": "79.99 USD", + "raw": "79.99", + "value": 79.99 + }, + "basePrice": { + "formatted": "79.99 USD", + "raw": "79.99", + "value": 79.99 + }, + "currencyCode": "USD", + "owned": false + }, + { + "productId": 10082, + "listPrice": { + "formatted": "139.99 USD", + "raw": "139.99", + "value": 139.99 + }, + "basePrice": { + "formatted": "139.99 USD", + "raw": "139.99", + "value": 139.99 + }, + "currencyCode": "USD", + "owned": false + }, + { + "productId": 10084, + "listPrice": { + "formatted": "49.99 USD", + "raw": "49.99", + "value": 49.99 + }, + "basePrice": { + "formatted": "49.99 USD", + "raw": "49.99", + "value": 49.99 + }, + "currencyCode": "USD", + "owned": false + }, + { + "productId": 10085, + "listPrice": { + "formatted": "30.00 USD", + "raw": "30.00", + "value": 30 + }, + "basePrice": { + "formatted": "30.00 USD", + "raw": "30.00", + "value": 30 + }, + "currencyCode": "USD", + "owned": false + }, + { + "productId": 10086, + "listPrice": { + "formatted": "60.00 USD", + "raw": "60.00", + "value": 60 + }, + "basePrice": { + "formatted": "60.00 USD", + "raw": "60.00", + "value": 60 + }, + "currencyCode": "USD", + "owned": false + }, + { + "productId": 10087, + "listPrice": { + "formatted": "90.00 USD", + "raw": "90.00", + "value": 90 + }, + "basePrice": { + "formatted": "90.00 USD", + "raw": "90.00", + "value": 90 + }, + "currencyCode": "USD", + "owned": false + }, + { + "productId": 10088, + "listPrice": { + "formatted": "90.00 USD", + "raw": "90.00", + "value": 90 + }, + "basePrice": { + "formatted": "90.00 USD", + "raw": "90.00", + "value": 90 + }, + "currencyCode": "USD", + "owned": false + }, + { + "productId": 10089, + "listPrice": { + "formatted": "40.01 USD", + "raw": "40.01", + "value": 40.01 + }, + "basePrice": { + "formatted": "40.01 USD", + "raw": "40.01", + "value": 40.01 + }, + "currencyCode": "USD", + "owned": false + }, + { + "productId": 10090, + "listPrice": { + "formatted": "10.01 USD", + "raw": "10.01", + "value": 10.01 + }, + "basePrice": { + "formatted": "10.01 USD", + "raw": "10.01", + "value": 10.01 + }, + "currencyCode": "USD", + "owned": false + }, + { + "productId": 10094, + "listPrice": { + "formatted": "24.99 USD", + "raw": "24.99", + "value": 24.99 + }, + "basePrice": { + "formatted": "24.99 USD", + "raw": "24.99", + "value": 24.99 + }, + "currencyCode": "USD", + "owned": false + }, + { + "productId": 10095, + "listPrice": { + "formatted": "14.99 USD", + "raw": "14.99", + "value": 14.99 + }, + "basePrice": { + "formatted": "14.99 USD", + "raw": "14.99", + "value": 14.99 + }, + "currencyCode": "USD", + "owned": false + } + ] +} diff --git a/static/fixed_responses/questCompletionRewards.json b/static/fixed_responses/questCompletionRewards.json index 8a04e2aa..12cf850a 100644 --- a/static/fixed_responses/questCompletionRewards.json +++ b/static/fixed_responses/questCompletionRewards.json @@ -1,10 +1,4 @@ { - "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain": [ - { - "ItemType": "/Lotus/Types/NeutralCreatures/ErsatzHorse/ErsatzHorsePowerSuit", - "ItemCount": 1 - } - ], "/Lotus/Types/Keys/InfestedMicroplanetQuest/InfestedMicroplanetQuestKeyChain": [{ "ItemType": "/Lotus/Types/Recipes/WarframeRecipes/BrokenFrameBlueprint", "ItemCount": 1 }], "/Lotus/Types/Keys/OrokinMoonQuest/OrokinMoonQuestKeyChain": [ { diff --git a/static/webui/index.html b/static/webui/index.html index ba8e4b78..b14e004b 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -83,7 +83,7 @@

- +
@@ -110,7 +110,7 @@
- +
@@ -182,7 +182,7 @@
- +
@@ -198,13 +198,13 @@
- +
- - - + + +
@@ -221,13 +221,13 @@
- +
- - - + + +
@@ -242,13 +242,13 @@
- +
- - - + + +
@@ -265,7 +265,7 @@
- +
@@ -281,7 +281,7 @@
- +
@@ -299,7 +299,7 @@
- +
@@ -315,7 +315,7 @@
- +
@@ -333,7 +333,7 @@
- +
@@ -349,20 +349,20 @@
- +
- - - - + + + +
- - - - + + + +
@@ -379,16 +379,16 @@
- +
- - + +
- - + +
@@ -403,7 +403,7 @@
- +
@@ -421,13 +421,13 @@
- +
- - - + + +
@@ -442,10 +442,10 @@
- - - - + + + +
@@ -463,7 +463,7 @@
- +
@@ -479,7 +479,7 @@
- +
@@ -497,7 +497,7 @@
- +
@@ -514,7 +514,7 @@
- +
@@ -526,6 +526,24 @@
+
+
+
+
+
+
+ + + +
+
+ +
+
+
+
+
+
@@ -538,7 +556,9 @@ + +
@@ -590,7 +610,7 @@
- +
@@ -605,8 +625,8 @@
-
- + +
@@ -618,6 +638,42 @@
+
+
+
+
+
+
+ + + +
+
+ + +
+
+
+
+
+
+
+
+
+
+ + + +
+
+ + +
+
+
+
+
+
@@ -647,7 +703,8 @@
- + +
@@ -710,7 +767,7 @@
x - +
@@ -820,7 +877,7 @@
- + @@ -848,7 +905,7 @@
- +
@@ -1015,6 +1072,20 @@
+
+ +
+ + +
+ +
+ +
+ + +
+
@@ -1057,6 +1128,20 @@
+
+ +
+ + +
+ +
+ +
+ + +
+
@@ -1071,13 +1156,6 @@
-
- -
- - -
-
@@ -1105,27 +1183,6 @@ - - -
- - -
- - - -
- - -
- - - -
- - -
- @@ -1169,11 +1226,6 @@ -
- - - -
@@ -1189,6 +1241,19 @@
+
+ + +
+
+ + + +
@@ -1232,6 +1297,27 @@
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
@@ -1421,7 +1507,7 @@
- +
@@ -1503,6 +1589,8 @@ + + diff --git a/static/webui/script.js b/static/webui/script.js index c6400db7..a486bdd9 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -559,7 +559,7 @@ function fetchItemList() { }); } else if (type == "Syndicates") { items.forEach(item => { - if (item.uniqueName === "ConclaveSyndicate") { + if (["ConclaveSyndicate", "NightcapJournalSyndicate"].includes(item.uniqueName)) { return; } if (item.uniqueName.startsWith("RadioLegion")) { @@ -599,6 +599,46 @@ function fetchItemList() { } itemMap[item.uniqueName] = { ...item, type }; }); + } else if (type == "WeaponSkins") { + let beardNumber = 1; + let cutNumber = 13; + let adultHeadNumber = 1; + let headNumber = 1; + items.forEach(item => { + if (item.name == "") { + if (item.uniqueName.includes("/Beards/")) { + item.name = loc("code_drifterBeardName") + .split("|INDEX|") + .join(beardNumber.toString().padStart(3, "0")); + beardNumber++; + } else if (item.uniqueName.includes("/Hair/")) { + item.name = loc("code_cutName") + .split("|INDEX|") + .join(cutNumber.toString().padStart(3, "0")); + cutNumber++; + if (cutNumber == 19) cutNumber = 21; + } else if (item.uniqueName.includes("/Heads/Adult")) { + item.name = loc("code_drifterFaceName") + .split("|INDEX|") + .join(adultHeadNumber.toString().padStart(3, "0")); + adultHeadNumber++; + } else if (item.uniqueName.includes("/Heads/")) { + item.name = loc("code_operatorFaceName") + .split("|INDEX|") + .join(headNumber.toString().padStart(3, "0")); + headNumber++; + } else { + item.name = item.uniqueName; + } + } + if (!item.alwaysAvailable) { + const option = document.createElement("option"); + option.setAttribute("data-key", item.uniqueName); + option.value = item.name; + document.getElementById("datalist-" + type).appendChild(option); + } + itemMap[item.uniqueName] = { ...item, type }; + }); } else { const nameToItems = {}; items.forEach(item => { @@ -671,6 +711,10 @@ function fetchItemList() { option.value += " (" + item.subtype + ")"; } document.getElementById("datalist-" + type).appendChild(option); + if (item.eligibleForVault) { + const vaultOption = option.cloneNode(true); + document.getElementById("datalist-VaultMiscItems").appendChild(vaultOption); + } } else { //console.log(`Not adding ${item.uniqueName} to datalist for ${type} due to duplicate display name: ${item.name}`); } @@ -788,7 +832,7 @@ function updateInventory() { const td = document.createElement("td"); td.classList = "text-end text-nowrap"; - let maxXP = Math.pow(itemMap[item.ItemType].maxLevelCap ?? 30, 2) * 1000; + let maxXP = Math.pow(itemMap[item.ItemType]?.maxLevelCap ?? 30, 2) * 1000; if ( category != "Suits" && category != "SpaceSuits" && @@ -815,7 +859,7 @@ function updateInventory() { } } if ( - itemMap[item.ItemType].maxLevelCap > 30 && + itemMap[item.ItemType]?.maxLevelCap > 30 && (item.Polarized ?? 0) < (itemMap[item.ItemType].maxLevelCap - 30) / 2 ) { const a = document.createElement("a"); @@ -1103,6 +1147,44 @@ function updateInventory() { document.getElementById("FlavourItems-list").appendChild(tr); }); + document.getElementById("WeaponSkins-list").innerHTML = ""; + data.WeaponSkins.forEach(item => { + if (item.ItemId.$oid.startsWith("ca70ca70ca70ca70")) return; + const datalist = document.getElementById("datalist-WeaponSkins"); + const optionToRemove = datalist.querySelector(`option[data-key="${item.ItemType}"]`); + if (optionToRemove) { + datalist.removeChild(optionToRemove); + } + const tr = document.createElement("tr"); + { + const td = document.createElement("td"); + const name = itemMap[item.ItemType]?.name?.trim(); + td.textContent = name || item.ItemType; + tr.appendChild(td); + } + { + const td = document.createElement("td"); + td.classList = "text-end text-nowrap"; + { + const a = document.createElement("a"); + a.href = "#"; + a.onclick = function (event) { + event.preventDefault(); + document.getElementById("WeaponSkins-list").removeChild(tr); + reAddToItemList(itemMap, "WeaponSkins", item.ItemType); + disposeOfGear("WeaponSkins", item.ItemId.$oid); + }; + a.title = loc("code_remove"); + a.innerHTML = ``; + td.appendChild(a); + } + + tr.appendChild(td); + } + + document.getElementById("WeaponSkins-list").appendChild(tr); + }); + const datalistEvolutionProgress = document.querySelectorAll("#datalist-EvolutionProgress option"); const formEvolutionProgress = document.querySelector('form[onsubmit*="doAcquireEvolution()"]'); @@ -1202,7 +1284,7 @@ function updateInventory() { a.href = "#"; a.onclick = function (event) { event.preventDefault(); - doQuestUpdate("setInactive", item.ItemType); + debounce(doQuestUpdate, "setInactive", item.ItemType); }; a.title = loc("code_setInactive"); a.innerHTML = ``; @@ -1213,7 +1295,7 @@ function updateInventory() { a.href = "#"; a.onclick = function (event) { event.preventDefault(); - doQuestUpdate("resetKey", item.ItemType); + debounce(doQuestUpdate, "resetKey", item.ItemType); }; a.title = loc("code_reset"); a.innerHTML = ``; @@ -1224,7 +1306,7 @@ function updateInventory() { a.href = "#"; a.onclick = function (event) { event.preventDefault(); - doQuestUpdate("completeKey", item.ItemType); + debounce(doQuestUpdate, "completeKey", item.ItemType); }; a.title = loc("code_complete"); a.innerHTML = ``; @@ -1235,7 +1317,7 @@ function updateInventory() { a.href = "#"; a.onclick = function (event) { event.preventDefault(); - doQuestUpdate("prevStage", item.ItemType); + debounce(doQuestUpdate, "prevStage", item.ItemType); }; a.title = loc("code_prevStage"); a.innerHTML = ``; @@ -1250,7 +1332,7 @@ function updateInventory() { a.href = "#"; a.onclick = function (event) { event.preventDefault(); - doQuestUpdate("nextStage", item.ItemType); + debounce(doQuestUpdate, "nextStage", item.ItemType); }; a.title = loc("code_nextStage"); a.innerHTML = ``; @@ -1262,7 +1344,7 @@ function updateInventory() { a.onclick = function (event) { event.preventDefault(); reAddToItemList(itemMap, "QuestKeys", item.ItemType); - doQuestUpdate("deleteKey", item.ItemType); + debounce(doQuestUpdate, "deleteKey", item.ItemType); }; a.title = loc("code_remove"); a.innerHTML = ``; @@ -1724,6 +1806,8 @@ function updateInventory() { document.getElementById("VaultRegularCredits-owned").classList.remove("mb-0"); document.getElementById("vaultPremiumCredits-form").classList.remove("d-none"); document.getElementById("VaultPremiumCredits-owned").classList.remove("mb-0"); + document.getElementById("vaultMiscItems-form").classList.remove("d-none"); + document.getElementById("vaultShipDecorations-form").classList.remove("d-none"); } if (userGuildMember.Rank <= 1) { document.querySelectorAll("#guild-actions button").forEach(btn => { @@ -1771,7 +1855,7 @@ function updateInventory() { a.href = "#"; a.onclick = function (event) { event.preventDefault(); - fundGuildTechProject(item.ItemType); + debounce(fundGuildTechProject, item.ItemType); }; a.title = loc("code_fund"); a.innerHTML = ``; @@ -1788,7 +1872,7 @@ function updateInventory() { a.href = "#"; a.onclick = function (event) { event.preventDefault(); - completeGuildTechProject(item.ItemType); + debounce(completeGuildTechProject, item.ItemType); }; a.title = loc("code_complete"); a.innerHTML = ``; @@ -1801,7 +1885,7 @@ function updateInventory() { a.onclick = function (event) { event.preventDefault(); reAddToItemList(itemMap, "TechProjects", item.ItemType); - removeGuildTechProject(item.ItemType); + debounce(removeGuildTechProject, item.ItemType); }; a.title = loc("code_remove"); a.innerHTML = ``; @@ -1814,42 +1898,55 @@ function updateInventory() { document.getElementById("TechProjects-list").appendChild(tr); }); - document.getElementById("VaultDecoRecipes-list").innerHTML = ""; - guildData.VaultDecoRecipes ??= []; - guildData.VaultDecoRecipes.forEach(item => { - const datalist = document.getElementById("datalist-VaultDecoRecipes"); - const optionToRemove = datalist.querySelector(`option[data-key="${item.ItemType}"]`); - if (optionToRemove) { - datalist.removeChild(optionToRemove); - } - const tr = document.createElement("tr"); - tr.setAttribute("data-item-type", item.ItemType); - { - const td = document.createElement("td"); - td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType; - tr.appendChild(td); - } - { - const td = document.createElement("td"); - td.classList = "text-end text-nowrap"; - - if (userGuildMember && userGuildMember.Rank <= 1) { - const a = document.createElement("a"); - a.href = "#"; - a.onclick = function (event) { - event.preventDefault(); - reAddToItemList(itemMap, "VaultDecoRecipes", item.ItemType); - removeVaultDecoRecipe(item.ItemType); - }; - a.title = loc("code_remove"); - a.innerHTML = ``; - td.appendChild(a); + ["VaultDecoRecipes", "VaultMiscItems", "VaultShipDecorations"].forEach(vaultKey => { + document.getElementById(vaultKey + "-list").innerHTML = ""; + (guildData[vaultKey] ??= []).forEach(item => { + if (vaultKey == "VaultDecoRecipes") { + const datalist = document.getElementById("datalist-VaultDecoRecipes"); + const optionToRemove = datalist.querySelector( + `option[data-key="${item.ItemType}"]` + ); + if (optionToRemove) { + datalist.removeChild(optionToRemove); + } } - tr.appendChild(td); - } + const tr = document.createElement("tr"); + tr.setAttribute("data-item-type", item.ItemType); + { + const td = document.createElement("td"); + td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType; + if (item.ItemCount > 1) { + td.innerHTML += ` 🗍 ${parseInt(item.ItemCount)}`; + } + tr.appendChild(td); + } + { + const td = document.createElement("td"); + td.classList = "text-end text-nowrap"; + const canRemove = + vaultKey === "VaultDecoRecipes" + ? userGuildMember && userGuildMember.Rank <= 1 + : userGuildPermissions && userGuildPermissions & 64; + if (canRemove) { + const a = document.createElement("a"); + a.href = "#"; + a.title = loc("code_remove"); + a.onclick = e => { + e.preventDefault(); + if (vaultKey == "VaultDecoRecipes") { + reAddToItemList(itemMap, vaultKey, item.ItemType); + } + removeVaultItem(vaultKey, item.ItemType, item.ItemCount * -1); + }; + a.innerHTML = ``; + td.appendChild(a); + } + tr.appendChild(td); + } - document.getElementById("VaultDecoRecipes-list").appendChild(tr); + document.getElementById(vaultKey + "-list").appendChild(tr); + }); }); document.getElementById("Members-list").innerHTML = ""; @@ -2080,6 +2177,15 @@ function removeCustomization(uniqueName) { }); } +function removeIsNew() { + revalidateAuthz().then(() => { + const req = $.get("/custom/removeIsNew?" + window.authz); + req.done(() => { + updateInventory(); + }); + }); +} + function getRequiredParts(category, WeaponType) { switch (category) { case "Hoverboards": @@ -2268,41 +2374,52 @@ function addMissingEquipment(categories) { } } -function addVaultDecoRecipe() { - const uniqueName = getKey(document.getElementById("acquire-type-VaultDecoRecipes")); - if (!uniqueName) { - $("#acquire-type-VaultDecoRecipes").addClass("is-invalid").focus(); +function addVaultItem(vaultType) { + const ItemType = getKey(document.getElementById(`acquire-type-${vaultType}`)); + if (!ItemType) { + $(`#acquire-type-${vaultType}`).addClass("is-invalid").focus(); return; } - revalidateAuthz().then(() => { - const req = $.post({ - url: "/custom/addVaultDecoRecipe?" + window.authz + "&guildId=" + window.guildId, - contentType: "application/json", - data: JSON.stringify([ - { - ItemType: uniqueName, - ItemCount: 1 - } - ]) + const ItemCount = ["VaultMiscItems", "VaultShipDecorations"].includes(vaultType) + ? parseInt($(`#${vaultType}-count`).val()) + : 1; + if (ItemCount != 0) { + revalidateAuthz().then(() => { + const req = $.post({ + url: "/custom/addVaultTypeCount?" + window.authz + "&guildId=" + window.guildId, + contentType: "application/json", + data: JSON.stringify({ + vaultType, + items: [ + { + ItemType, + ItemCount + } + ] + }) + }); + req.done(() => { + document.getElementById(`acquire-type-${vaultType}`).value = ""; + updateInventory(); + }); }); - req.done(() => { - document.getElementById("acquire-type-VaultDecoRecipes").value = ""; - updateInventory(); - }); - }); + } } -function removeVaultDecoRecipe(uniqueName) { +function removeVaultItem(vaultType, ItemType, ItemCount) { revalidateAuthz().then(() => { const req = $.post({ - url: "/custom/addVaultDecoRecipe?" + window.authz + "&guildId=" + window.guildId, + url: "/custom/addVaultTypeCount?" + window.authz + "&guildId=" + window.guildId, contentType: "application/json", - data: JSON.stringify([ - { - ItemType: uniqueName, - ItemCount: -1 - } - ]) + data: JSON.stringify({ + vaultType, + items: [ + { + ItemType, + ItemCount + } + ] + }) }); req.done(() => { updateInventory(); @@ -2384,13 +2501,16 @@ function fundGuildTechProject(uniqueName) { }); } -function dispatchAddVaultDecoRecipesBatch(requests) { +function dispatchAddVaultItemsBatch(requests, vaultType) { return new Promise(resolve => { revalidateAuthz().then(() => { const req = $.post({ - url: "/custom/addVaultDecoRecipe?" + window.authz + "&guildId=" + window.guildId, + url: "/custom/addVaultItems?" + window.authz + "&guildId=" + window.guildId, contentType: "application/json", - data: JSON.stringify(requests) + data: JSON.stringify({ + vaultType, + items: requests + }) }); req.done(() => { updateInventory(); @@ -2400,20 +2520,23 @@ function dispatchAddVaultDecoRecipesBatch(requests) { }); } -function addMissingVaultDecoRecipes() { +function addMissingVaultItems(vaultType) { const requests = []; - document.querySelectorAll("#datalist-VaultDecoRecipes" + " option").forEach(elm => { - if (!document.querySelector("#VaultDecoRecipes-list [data-item-type='" + elm.getAttribute("data-key") + "']")) { - requests.push({ ItemType: elm.getAttribute("data-key"), ItemCount: 1 }); + document.querySelectorAll(`#datalist-${vaultType} option`).forEach(elm => { + const datalist = vaultType === "VaultShipDecorations" ? "ShipDecorations" : vaultType; + if (!document.querySelector(`#${datalist}-list [data-item-type='${elm.getAttribute("data-key")}']`)) { + let ItemCount = 1; + if (category == "VaultShipDecorations") ItemCount = 999999; + requests.push({ ItemType: elm.getAttribute("data-key"), ItemCount }); } }); if ( requests.length != 0 && - window.confirm(loc("code_addDecoRecipesConfirm").split("|COUNT|").join(requests.length)) + window.confirm(loc("code_addVaultItemsConfirm").split("|COUNT|").join(requests.length)) ) { - return dispatchAddVaultDecoRecipesBatch(requests); + return dispatchAddVaultItemsBatch(requests, vaultType); } } @@ -2844,8 +2967,8 @@ function removeCountItems(uniqueName, count) { function addItemByItemType() { const ItemType = document.getElementById("typeName-type").value; - // Must start with "/Lotus/", contain only A–Z letters, no "//", and not end with "/" - if (!ItemType || !/^\/Lotus\/(?:[A-Za-z]+(?:\/[A-Za-z]+)*)$/.test(ItemType)) { + // Must start with "/Lotus/", contain only letters A–Z, digits 0–9, no "//", and not end with "/" + if (!ItemType || !/^\/Lotus\/(?:[A-Za-z0-9]+(?:\/[A-Za-z0-9]+)*)$/.test(ItemType)) { $("#typeName-type").addClass("is-invalid").focus(); return; } @@ -3162,13 +3285,13 @@ function unlockFocusSchool(upgradeType) { return new Promise(resolve => { // Deselect current FocusAbility so we will be able to unlock the way for free $.post({ - url: "/api/focus.php?" + window.authz + "&op=5", + url: "/api/focus.php?" + window.authz + "&op=ActivateWay", contentType: "text/plain", data: JSON.stringify({ FocusType: null }) }).done(function () { // Unlock the way now $.post({ - url: "/api/focus.php?" + window.authz + "&op=2", + url: "/api/focus.php?" + window.authz + "&op=UnlockWay", contentType: "text/plain", data: JSON.stringify({ FocusType: upgradeType @@ -3195,13 +3318,16 @@ function doIntrinsicsUnlockAll() { document.querySelectorAll("#account-cheats input[type=checkbox]").forEach(elm => { elm.onchange = function () { revalidateAuthz().then(() => { + const value = elm.checked; $.post({ url: "/custom/setAccountCheat?" + window.authz, contentType: "application/json", data: JSON.stringify({ key: elm.id, - value: elm.checked + value: value }) + }).done(() => { + elm.checked = value; }); }); }; @@ -3237,6 +3363,8 @@ document.querySelectorAll("#account-cheats .input-group").forEach(grp => { key: input.id, value: parseInt(value) }) + }).done(() => { + btn.value = value; }); }); }; @@ -3362,6 +3490,12 @@ single.getRoute("#guild-route").on("beforeload", function () { document.getElementById("VaultDecoRecipes-list").innerHTML = ""; document.getElementById("vaultDecoRecipes-form").classList.add("d-none"); document.getElementById("acquire-type-VaultDecoRecipes").value = ""; + document.getElementById("VaultMiscItems-list").innerHTML = ""; + document.getElementById("vaultMiscItems-form").classList.add("d-none"); + document.getElementById("acquire-type-VaultMiscItems").value = ""; + document.getElementById("VaultShipDecorations-list").innerHTML = ""; + document.getElementById("vaultShipDecorations-form").classList.add("d-none"); + document.getElementById("acquire-type-VaultShipDecorations").value = ""; document.getElementById("Alliance-list").innerHTML = ""; document.getElementById("guildView-alliance").textContent = ""; document.getElementById("Members-list").innerHTML = ""; diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index b642b800..8f2f97a3 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -1,17 +1,17 @@ // German translation by Animan8000 dict = { - general_inventoryUpdateNote: `Hinweis: Um Änderungen im Spiel zu sehen, musst du dein Inventar neu synchronisieren, z. B. mit dem /sync Befehl des Bootstrappers, durch Besuch eines Dojo/Relais oder durch erneutes Einloggen.`, - general_inventoryUpdateNoteGameWs: `[UNTRANSLATED] Note: You may need to reopen any menu you are on for changes to be reflected.`, + general_inventoryUpdateNote: `Hinweis: Um Änderungen im Spiel zu sehen, musst du dein Inventar neu synchronisieren, z. B. mit dem /sync Befehl des Bootstrappers im Spielchat, durch Besuch eines Dojo/Relais oder durch erneutes Einloggen.`, + general_inventoryUpdateNoteGameWs: `Hinweis: Möglicherweise musst du ein Menü neu öffnen, damit die Änderungen sichtbar werden.`, general_addButton: `Hinzufügen`, general_setButton: `Festlegen`, - general_none: `Keines`, + general_none: `Nichts`, general_bulkActions: `Massenaktionen`, general_loading: `Lädt...`, code_loginFail: `Anmeldung fehlgeschlagen. Bitte überprüfe deine Angaben.`, code_regFail: `Registrierung fehlgeschlagen. Account existiert bereits?`, code_changeNameConfirm: `In welchen Namen möchtest du deinen Account umbenennen?`, - code_changeNameRetry: `[UNTRANSLATED] |NAME| is already taken.`, + code_changeNameRetry: `|NAME| ist bereits vergeben.`, code_deleteAccountConfirm: `Bist du sicher, dass du deinen Account |DISPLAYNAME| (|EMAIL|) löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.`, code_archgun: `Arch-Gewehr`, code_melee: `Nahkampf`, @@ -32,8 +32,8 @@ dict = { code_renamePrompt: `Neuen benutzerdefinierten Namen eingeben:`, code_remove: `Entfernen`, code_addItemsConfirm: `Bist du sicher, dass du |COUNT| Gegenstände zu deinem Account hinzufügen möchtest?`, - code_addTechProjectsConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| research to your clan?`, - code_addDecoRecipesConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| deco recipes to your clan?`, + code_addTechProjectsConfirm: `Bist du sicher, dass du |COUNT| Forschungen zu deinem Clan hinzufügen möchtest?`, + code_addVaultItemsConfirm: `Bist du sicher, dass du |COUNT| Gegenstände zu deinem Clan-Tresor hinzufügen möchtest?`, code_succRankUp: `Erfolgreich aufgestiegen.`, code_noEquipmentToRankUp: `Keine Ausstattung zum Rangaufstieg verfügbar.`, code_succAdded: `Erfolgreich hinzugefügt.`, @@ -45,10 +45,10 @@ dict = { code_rank: `Rang`, code_rankUp: `Rang erhöhen`, code_rankDown: `Rang verringern`, - code_unlockLevelCap: `[UNTRANSLATED] Unlock level cap`, + code_unlockLevelCap: `Level-Cap freischalten`, code_count: `Anzahl`, code_focusAllUnlocked: `Alle Fokus-Schulen sind bereits freigeschaltet.`, - code_focusUnlocked: `|COUNT| neue Fokus-Schulen freigeschaltet! Ein Inventar-Update wird benötigt, damit die Änderungen im Spiel sichtbar werden. Die Sternenkarte zu besuchen, sollte der einfachste Weg sein, dies auszulösen.`, + code_focusUnlocked: `|COUNT| neue Fokus-Schulen freigeschaltet! Ein Inventar-Update wird benötigt, damit die Änderungen im Spiel sichtbar werden.`, code_addModsConfirm: `Bist du sicher, dass du |COUNT| Mods zu deinem Account hinzufügen möchtest?`, code_succImport: `Erfolgreich importiert.`, code_succRelog: `Fertig. Bitte beachte, dass du dich neu einloggen musst, um Änderungen im Spiel zu sehen.`, @@ -65,18 +65,22 @@ dict = { code_completed: `Abgeschlossen`, code_active: `Aktiv`, code_pigment: `Pigment`, - code_controller: `[UNTRANSLATED] Controller cursor`, - code_mouseLine: `[UNTRANSLATED] Line cursor`, - code_mouse: `[UNTRANSLATED] Cursor`, + code_controller: `Controller-Cursor`, + code_mouseLine: `Linien-Cursor`, + code_mouse: `Cursor`, code_itemColorPalette: `|ITEM| Farbpalette`, code_mature: `Für den Kampf auswachsen lassen`, code_unmature: `Genetisches Altern zurücksetzen`, - code_fund: `[UNTRANSLATED] Fund`, - code_funded: `[UNTRANSLATED] Funded`, - code_replays: `[UNTRANSLATED] Replays`, + code_fund: `Spenden`, + code_funded: `Gespendet`, + code_replays: `Wiederholungen`, code_stalker: `Stalker`, + code_cutName: `Frisur: |INDEX|`, + code_drifterBeardName: `Drifter-Bart: |INDEX|`, + code_drifterFaceName: `Drifter-Gesicht: |INDEX|`, + code_operatorFaceName: `Operator-Gesicht: |INDEX|`, code_succChange: `Erfolgreich geändert.`, - code_requiredInvigorationUpgrade: `[UNTRANSLATED] You must select both an offensive & utility upgrade.`, + code_requiredInvigorationUpgrade: `Du musst sowohl ein Offensiv- als auch ein Support-Upgrade auswählen.`, login_description: `Melde dich mit deinem OpenWF-Account an (denselben Angaben wie im Spiel, wenn du dich mit diesem Server verbindest).`, login_emailLabel: `E-Mail-Adresse`, login_passwordLabel: `Passwort`, @@ -92,8 +96,8 @@ dict = { navbar_cheats: `Cheats`, navbar_import: `Importieren`, inventory_addItems: `Gegenstände hinzufügen`, - inventory_addItemByItemType: `[UNTRANSLATED] Raw`, - inventory_addItemByItemType_warning: `[UNTRANSLATED] Use this feature at your own risk. It may break your inventory, and you will need to remove items manually if something goes wrong.`, + inventory_addItemByItemType: `Roh`, + inventory_addItemByItemType_warning: `Verwende diese Funktion auf eigene Gefahr. Sie kann dein Inventar beschädigen und du musst Gegenstände manuell entfernen, falls etwas schiefgeht.`, inventory_suits: `Warframes`, inventory_longGuns: `Primärwaffen`, inventory_pistols: `Sekundärwaffen`, @@ -112,15 +116,17 @@ dict = { inventory_boosters: `Booster`, inventory_flavourItems: `Sammlerstücke`, inventory_shipDecorations: `Schiffsdekorationen`, + inventory_weaponSkins: `Skins`, inventory_bulkAddSuits: `Fehlende Warframes hinzufügen`, inventory_bulkAddWeapons: `Fehlende Waffen hinzufügen`, inventory_bulkAddSpaceSuits: `Fehlende Archwings hinzufügen`, inventory_bulkAddSpaceWeapons: `Fehlende Archwing-Waffen hinzufügen`, inventory_bulkAddSentinels: `Fehlende Wächter hinzufügen`, inventory_bulkAddSentinelWeapons: `Fehlende Wächter-Waffen hinzufügen`, - inventory_bulkAddFlavourItems: `[UNTRANSLATED] Add Missing Flavour Items`, - inventory_bulkAddShipDecorations: `[UNTRANSLATED] Add Missing Ship Decorations`, + inventory_bulkAddFlavourItems: `Fehlende Sammlerstücke hinzufügen`, + inventory_bulkAddShipDecorations: `Fehlende Schiffsdekorationen hinzufügen`, inventory_bulkAddEvolutionProgress: `Fehlende Incarnon-Entwicklungsfortschritte hinzufügen`, + inventory_bulkAddWeaponSkins: `Fehlende Skins hinzufügen`, inventory_bulkRankUpSuits: `Alle Warframes auf Max. Rang`, inventory_bulkRankUpWeapons: `Alle Waffen auf Max. Rang`, inventory_bulkRankUpSpaceSuits: `Alle Archwings auf Max. Rang`, @@ -129,6 +135,7 @@ dict = { inventory_bulkRankUpSentinelWeapons: `Alle Wächter-Waffen auf Max. Rang`, inventory_bulkRankUpEvolutionProgress: `Alle Incarnon-Entwicklungsfortschritte auf Max. Rang`, inventory_maxPlexus: `Plexus auf Max. Rang`, + inventory_removeIsNew: `[UNTRANSLATED] Remove New Equipment Exclamation Icon`, quests_list: `Quests`, quests_completeAll: `Alle Quests abschließen`, @@ -173,8 +180,8 @@ dict = { invigorations_utility_EnergyRegen: `+2 Energieregeneration pro Sekunde`, detailedView_invigorationOffensiveLabel: `Offensives Upgrade`, - detailedView_invigorationUtilityLabel: `[UNTRANSLATED] Utility Upgrade`, - detailedView_invigorationExpiryLabel: `[UNTRANSLATED] Invigoration Expiry (optional)`, + detailedView_invigorationUtilityLabel: `Support Upgrade`, + detailedView_invigorationExpiryLabel: `Kräftigungs-Ablaufdatum (optional)`, abilityOverride_label: `Fähigkeitsüberschreibung`, abilityOverride_onSlot: `auf Slot`, @@ -193,7 +200,7 @@ dict = { cheats_skipTutorial: `Tutorial überspringen`, cheats_skipAllDialogue: `Alle Dialoge überspringen`, cheats_unlockAllScans: `Alle Scans freischalten`, - cheats_unlockSuccRelog: `[UNTRANSLATED] Success. Please note that you'll need to relog for the client to refresh this.`, + cheats_unlockSuccRelog: `Erfolgreich. Bitte beachte, dass du dich neu einloggen musst, damit der Client dies aktualisiert.`, cheats_unlockAllMissions: `Alle Missionen freischalten`, cheats_unlockAllMissions_ok: `Erfolgreich. Bitte beachte, dass du ein Dojo/Relais besuchen oder dich neu einloggen musst, damit die Sternenkarte aktualisiert wird.`, cheats_infiniteCredits: `Unendlich Credits`, @@ -227,7 +234,7 @@ dict = { cheats_baroFullyStocked: `Baro hat volles Inventar`, cheats_syndicateMissionsRepeatable: `Syndikat-Missionen wiederholbar`, cheats_unlockAllProfitTakerStages: `Alle Profiteintreiber-Phasen freischalten`, - cheats_unlockSuccInventory: `[UNTRANSLATED] Success. Please note that you'll need to resync your inventory, e.g. using the bootstrapper's /sync command in game chat, visiting a dojo/relay, or relogging.`, + cheats_unlockSuccInventory: `Erfolgreich. Bitte beachte, dass du dein Inventar neu synchronisieren musst, z. B. mit dem /sync Befehl des Bootstrappers im Spielchat, durch Besuch eines Dojo/Relais oder durch erneutes Einloggen.`, cheats_instantFinishRivenChallenge: `Riven-Mod Herausforderung sofort abschließen`, cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`, cheats_noResourceExtractorDronesDamage: `Kein Schaden für Ressourcen-Extraktor-Drohnen`, @@ -256,47 +263,49 @@ dict = { cheats_changeSupportedSyndicate: `Unterstütztes Syndikat`, cheats_changeButton: `Ändern`, cheats_markAllAsRead: `Posteingang als gelesen markieren`, - cheats_finishInvasionsInOneMission: `[UNTRANSLATED] Finish Invasions in One Mission`, - cheats_nemesisHenchmenKillsMultiplierGrineer: `[UNTRANSLATED] Rage Progess Multiplier (Grineer)`, - cheats_nemesisHenchmenKillsMultiplierCorpus: `[UNTRANSLATED] Rage Progess Multiplier (Corpus)`, - cheats_nemesisAntivirusGainMultiplier: `[UNTRANSLATED] Antivirus Progress Multiplier`, - cheats_nemesisHintProgressMultiplierGrineer: `[UNTRANSLATED] Hint Progress Multiplier (Grineer)`, - cheats_nemesisHintProgressMultiplierCorpus: `[UNTRANSLATED] Hint Progress Multiplier (Corpus)`, - cheats_nemesisExtraWeapon: `[UNTRANSLATED] Extra Nemesis Weapon / Token On Vanquish (0 to disable)`, + cheats_finishInvasionsInOneMission: `Invasionen in einer Mission abschließen`, + cheats_nemesisHenchmenKillsMultiplierGrineer: `Wut-Fortschrittsmultiplikator (Grineer)`, + cheats_nemesisHenchmenKillsMultiplierCorpus: `Wut-Fortschrittsmultiplikator (Corpus)`, + cheats_nemesisAntivirusGainMultiplier: `Antivirus-Fortschrittsmultiplikator`, + cheats_nemesisHintProgressMultiplierGrineer: `Hinweis-Fortschrittsmultiplikator (Grineer)`, + cheats_nemesisHintProgressMultiplierCorpus: `Hinweis-Fortschrittsmultiplikator (Corpus)`, + cheats_nemesisExtraWeapon: `Zusätzliche Nemesis-Waffe/-Symbol bei Besiegung (0 zum deaktivieren)`, worldState: `Weltstatus`, worldState_creditBoost: `Event Booster: Credit`, worldState_affinityBoost: `Event Booster: Erfahrung`, worldState_resourceBoost: `Event Booster: Ressourcen`, worldState_tennoLiveRelay: `TennoLive Relais`, - worldState_baroTennoConRelay: `Baros TennoCon Relais`, + worldState_baroTennoConRelay: `Baros TennoCon-Relais`, worldState_starDays: `Sternen-Tage`, worldState_galleonOfGhouls: `Galeone der Ghule`, - worldState_anniversary: `[UNTRANSLATED] Warframe Anniversary`, - worldState_useAnniversaryTagForOldGoals: `[UNTRANSLATED] Use Tag from Warframe Anniversary for old Events`, + worldState_anniversary: `Warframe Jubiläum`, + worldState_useAnniversaryTagForOldGoals: `Verwende Tag vom Warframe Jubiläum für alte Events`, worldState_ghoulEmergence: `Ghul Ausrottung`, worldState_plagueStar: `Plagenstern`, worldState_dogDays: `Hitzefrei`, - worldState_dogDaysRewards: `[UNTRANSLATED] Dog Days Rewards`, - worldState_wolfHunt: `Wolfsjagd (2025)`, + worldState_dogDaysRewards: `Hitzefrei-Belohnungen`, + worldState_wolfHunt: `Wolfsjagd`, + worldState_voidCorruption: `Void-Korruption (|VAL|)`, worldState_orphixVenom: `Orphix Gift`, worldState_longShadow: `Lange Schatten`, worldState_hallowedFlame: `Geweihte Flamme`, worldState_hallowedNightmares: `Geweihte Albträume`, - worldState_hallowedNightmaresRewards: `[UNTRANSLATED] Hallowed Nightmares Rewards`, - worldState_naberusNights: `[UNTRANSLATED] Nights of Naberus`, + worldState_hallowedNightmaresRewards: `Geweihte Albträume-Belohnungen`, + worldState_naberusNights: `Naberus Nacht`, worldState_proxyRebellion: `Proxy-Rebellion`, - worldState_proxyRebellionRewards: `[UNTRANSLATED] Proxy Rebellion Rewards`, + worldState_proxyRebellionRewards: `Proxy-Rebellion-Belohnungen`, worldState_bellyOfTheBeast: `Das Innere der Bestie`, - worldState_bellyOfTheBeastProgressOverride: `[UNTRANSLATED] Belly of the Beast Progress`, + worldState_bellyOfTheBeastProgressOverride: `Das Innere der Bestie-Fortschritt`, worldState_eightClaw: `Acht Klauen`, - worldState_eightClawProgressOverride: `[UNTRANSLATED] Eight Claw Progress`, + worldState_eightClawProgressOverride: `Acht Klauen-Fortschritt`, worldState_thermiaFractures: `Thermische Risse`, - worldState_thermiaFracturesProgressOverride: `[UNTRANSLATED] Thermia Fractures Progress`, - worldState_from_year: `[UNTRANSLATED] from |VAL|`, - worldState_pre_year: `[UNTRANSLATED] pre |VAL|`, - worldState_week: `[UNTRANSLATED] Week |VAL|`, - worldState_incompatibleWith: `[UNTRANSLATED] Incompatible with:`, + worldState_thermiaFracturesProgressOverride: `Thermische Risse-Fortschritt`, + worldState_qtccAlerts: `Quest zum Kampf gegen Krebs-Alarmierungen`, + worldState_from_year: `Von |VAL|`, + worldState_pre_year: `Vor |VAL|`, + worldState_week: `Woche |VAL|`, + worldState_incompatibleWith: `Inkompatibel mit:`, enabled: `Aktiviert`, disabled: `Deaktiviert`, worldState_we1: `Wochenende 1`, @@ -340,7 +349,7 @@ dict = { worldState_varziaFullyStocked: `Varzia hat volles Inventar`, worldState_varziaOverride: `Varzia-Angebotsüberschreibung`, - import_importNote: `[UNTRANSLATED] You can provide a full or partial inventory.php or getShip.php response (client representation) here.`, + import_importNote: `Du kannst hier eine vollständige oder teilweise inventory.php oder getShip.php Antwort (Client-Darstellung) einfügen.`, import_importNote2: `Alle Felder, die vom Importer unterstützt werden, werden in deinem Account überschrieben.`, import_submit: `Absenden`, import_samples: `Beispiele:`, @@ -408,9 +417,11 @@ dict = { theme_dark: `Dunkles Design`, theme_light: `Helles Design`, - guildView_cheats: `[UNTRANSLATED] Clan Cheats`, + guildView_cheats: `Clan-Cheats`, guildView_techProjects: `Forschung`, - guildView_vaultDecoRecipes: `[UNTRANSLATED] Dojo Deco Recipes`, + guildView_vaultDecoRecipes: `Dojo-Deko-Baupläne`, + guildView_vaultMiscItems: `Ressourcen im Tresor`, + guildView_vaultShipDecorations: `Dekorationen im Tresor`, guildView_alliance: `Allianz`, guildView_members: `Mitglieder`, guildView_pending: `Ausstehend`, @@ -430,11 +441,12 @@ dict = { guildView_rank_soldier: `Soldat`, guildView_rank_utility: `Versorger`, guildView_rank_warlord: `Kriegsherr`, - guildView_currency_owned: `[UNTRANSLATED] |COUNT| in Vault.`, - guildView_bulkAddTechProjects: `[UNTRANSLATED] Add Missing Research`, - guildView_bulkAddVaultDecoRecipes: `[UNTRANSLATED] Add Missing Dojo Deco Recipes`, - guildView_bulkFundTechProjects: `[UNTRANSLATED] Fund All Research`, - guildView_bulkCompleteTechProjects: `[UNTRANSLATED] Complete All Research`, + guildView_currency_owned: `|COUNT| im Tresor.`, + guildView_bulkAddTechProjects: `Fehlende Forschungen hinzufügen`, + guildView_bulkAddVaultDecoRecipes: `Fehlende Dojo-Deko-Baupläne hinzufügen`, + guildView_bulkAddVaultShipDecorations: `Fehlende Dekorationen im Tresor hinzufügen`, + guildView_bulkFundTechProjects: `Alle Forschungen spenden`, + guildView_bulkCompleteTechProjects: `Alle Forschungen abschließen`, guildView_promote: `Befördern`, guildView_demote: `Degradieren`, diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index 57a32595..7a0af074 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -32,7 +32,7 @@ dict = { code_remove: `Remove`, code_addItemsConfirm: `Are you sure you want to add |COUNT| items to your account?`, code_addTechProjectsConfirm: `Are you sure you want to add |COUNT| research to your clan?`, - code_addDecoRecipesConfirm: `Are you sure you want to add |COUNT| deco recipes to your clan?`, + code_addVaultItemsConfirm: `Are you sure you want to add |COUNT| items to your clan vault?`, code_succRankUp: `Successfully ranked up.`, code_noEquipmentToRankUp: `No equipment to rank up.`, code_succAdded: `Successfully added.`, @@ -47,7 +47,7 @@ dict = { code_unlockLevelCap: `Unlock level cap`, code_count: `Count`, code_focusAllUnlocked: `All focus schools are already unlocked.`, - code_focusUnlocked: `Unlocked |COUNT| new focus schools! An inventory update will be needed for the changes to be reflected in-game. Visiting the navigation should be the easiest way to trigger that.`, + code_focusUnlocked: `Unlocked |COUNT| new focus schools! An inventory update will be needed for the changes to be reflected in-game.`, code_addModsConfirm: `Are you sure you want to add |COUNT| mods to your account?`, code_succImport: `Successfully imported.`, code_succRelog: `Done. Please note that you'll need to relog to see a difference in-game.`, @@ -74,6 +74,10 @@ dict = { code_funded: `Funded`, code_replays: `Replays`, code_stalker: `Stalker`, + code_cutName: `Cut |INDEX|`, + code_drifterBeardName: `Drifter Beard |INDEX|`, + code_drifterFaceName: `Drifter Visage |INDEX|`, + code_operatorFaceName: `Operator Visage |INDEX|`, code_succChange: `Successfully changed.`, code_requiredInvigorationUpgrade: `You must select both an offensive & utility upgrade.`, login_description: `Login using your OpenWF account credentials (same as in-game when connecting to this server).`, @@ -111,6 +115,7 @@ dict = { inventory_boosters: `Boosters`, inventory_flavourItems: `Flavour Items`, inventory_shipDecorations: `Ship Decorations`, + inventory_weaponSkins: `Skins`, inventory_bulkAddSuits: `Add Missing Warframes`, inventory_bulkAddWeapons: `Add Missing Weapons`, inventory_bulkAddSpaceSuits: `Add Missing Archwings`, @@ -120,6 +125,7 @@ dict = { inventory_bulkAddFlavourItems: `Add Missing Flavour Items`, inventory_bulkAddShipDecorations: `Add Missing Ship Decorations`, inventory_bulkAddEvolutionProgress: `Add Missing Incarnon Evolution Progress`, + inventory_bulkAddWeaponSkins: `Add Missing Skins`, inventory_bulkRankUpSuits: `Max Rank All Warframes`, inventory_bulkRankUpWeapons: `Max Rank All Weapons`, inventory_bulkRankUpSpaceSuits: `Max Rank All Archwings`, @@ -128,6 +134,7 @@ dict = { inventory_bulkRankUpSentinelWeapons: `Max Rank All Sentinel Weapons`, inventory_bulkRankUpEvolutionProgress: `Max Rank All Incarnon Evolution Progress`, inventory_maxPlexus: `Max Rank Plexus`, + inventory_removeIsNew: `Remove New Equipment Exclamation Icon`, quests_list: `Quests`, quests_completeAll: `Complete All Quests`, @@ -277,7 +284,8 @@ dict = { worldState_plagueStar: `Plague Star`, worldState_dogDays: `Dog Days`, worldState_dogDaysRewards: `Dog Days Rewards`, - worldState_wolfHunt: `Wolf Hunt (2025)`, + worldState_wolfHunt: `Wolf Hunt`, + worldState_voidCorruption: `Void Corruption (|VAL|)`, worldState_orphixVenom: `Orphix Venom`, worldState_longShadow: `Long Shadow`, worldState_hallowedFlame: `Hallowed Flame`, @@ -292,8 +300,9 @@ dict = { worldState_eightClawProgressOverride: `Eight Claw Progress`, worldState_thermiaFractures: `Thermia Fractures`, worldState_thermiaFracturesProgressOverride: `Thermia Fractures Progress`, - worldState_from_year: `from |VAL|`, - worldState_pre_year: `pre |VAL|`, + worldState_qtccAlerts: `Quest to Conquer Cancer Alerts`, + worldState_from_year: `From |VAL|`, + worldState_pre_year: `Pre-|VAL|`, worldState_week: `Week |VAL|`, worldState_incompatibleWith: `Incompatible with:`, enabled: `Enabled`, @@ -410,6 +419,8 @@ dict = { guildView_cheats: `Clan Cheats`, guildView_techProjects: `Research`, guildView_vaultDecoRecipes: `Dojo Deco Recipes`, + guildView_vaultMiscItems: `Resources in Vault`, + guildView_vaultShipDecorations: `Decorations in Vault`, guildView_alliance: `Alliance`, guildView_members: `Members`, guildView_pending: `Pending`, @@ -432,6 +443,7 @@ dict = { guildView_currency_owned: `|COUNT| in Vault.`, guildView_bulkAddTechProjects: `Add Missing Research`, guildView_bulkAddVaultDecoRecipes: `Add Missing Dojo Deco Recipes`, + guildView_bulkAddVaultShipDecorations: `Add Missing Decorations in Vault`, guildView_bulkFundTechProjects: `Fund All Research`, guildView_bulkCompleteTechProjects: `Complete All Research`, guildView_promote: `Promote`, diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index 888f16da..58eab8c8 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -33,7 +33,7 @@ dict = { code_remove: `Quitar`, code_addItemsConfirm: `¿Estás seguro de que deseas agregar |COUNT| objetos a tu cuenta?`, code_addTechProjectsConfirm: `¿Estás seguro de que quieres añadir |COUNT| proyectos de investigación a tu clan?`, - code_addDecoRecipesConfirm: `¿Estás seguro de que quieres añadir |COUNT| planos de decoración a tu clan?`, + code_addVaultItemsConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| items to your clan vault?`, code_succRankUp: `Ascenso exitoso.`, code_noEquipmentToRankUp: `No hay equipo para ascender.`, code_succAdded: `Agregado exitosamente.`, @@ -48,7 +48,7 @@ dict = { code_unlockLevelCap: `Desbloquear level cap`, code_count: `Cantidad`, code_focusAllUnlocked: `Todas las escuelas de enfoque ya están desbloqueadas.`, - code_focusUnlocked: `¡Desbloqueadas |COUNT| nuevas escuelas de enfoque! Se necesita una actualización del inventario para reflejar los cambios en el juego. Visitar la navegación debería ser la forma más sencilla de activarlo.`, + code_focusUnlocked: `¡Desbloqueadas |COUNT| nuevas escuelas de enfoque! Se necesita una actualización del inventario para reflejar los cambios en el juego.`, code_addModsConfirm: `¿Estás seguro de que deseas agregar |COUNT| modificadores a tu cuenta?`, code_succImport: `Importación exitosa.`, code_succRelog: `Hecho. Ten en cuenta que deberás volver a iniciar sesión para ver los cambios en el juego.`, @@ -75,6 +75,10 @@ dict = { code_funded: `Financiado`, code_replays: `Repeticiones`, code_stalker: `Stalker`, + code_cutName: `[UNTRANSLATED] Cut |INDEX|`, + code_drifterBeardName: `Barba del Viajero: |INDEX|`, + code_drifterFaceName: `Rostro del Viajero |INDEX|`, + code_operatorFaceName: `Rostro del operador |INDEX|`, code_succChange: `Cambiado correctamente`, code_requiredInvigorationUpgrade: `Debes seleccionar una mejora ofensiva y una mejora de utilidad.`, login_description: `Inicia sesión con las credenciales de tu cuenta OpenWF (las mismas que usas en el juego al conectarte a este servidor).`, @@ -112,6 +116,7 @@ dict = { inventory_boosters: `Potenciadores`, inventory_flavourItems: `Ítems estéticos`, inventory_shipDecorations: `Decoraciones de nave`, + inventory_weaponSkins: `Diseños`, inventory_bulkAddSuits: `Agregar Warframes faltantes`, inventory_bulkAddWeapons: `Agregar armas faltantes`, inventory_bulkAddSpaceSuits: `Agregar Archwings faltantes`, @@ -121,6 +126,7 @@ dict = { inventory_bulkAddFlavourItems: `Añadir items estéticos faltantes`, inventory_bulkAddShipDecorations: `Añadir decoraciones de Nave Faltantes`, inventory_bulkAddEvolutionProgress: `Completar el progreso de evolución Incarnon faltante`, + inventory_bulkAddWeaponSkins: `[UNTRANSLATED] Add Missing Skins`, inventory_bulkRankUpSuits: `Maximizar rango de todos los Warframes`, inventory_bulkRankUpWeapons: `Maximizar rango de todas las armas`, inventory_bulkRankUpSpaceSuits: `Maximizar rango de todos los Archwings`, @@ -129,6 +135,7 @@ dict = { inventory_bulkRankUpSentinelWeapons: `Maximizar rango de todas las armas de centinela`, inventory_bulkRankUpEvolutionProgress: `Maximizar todo el progreso de evolución Incarnon`, inventory_maxPlexus: `Rango máximo de Plexus`, + inventory_removeIsNew: `[UNTRANSLATED] Remove New Equipment Exclamation Icon`, quests_list: `Misiones`, quests_completeAll: `Completar todas las misiones`, @@ -278,7 +285,8 @@ dict = { worldState_plagueStar: `Estrella Infestada`, worldState_dogDays: `Canícula`, worldState_dogDaysRewards: `Recompensas de Canícula`, - worldState_wolfHunt: `Cacería del Lobo (2025)`, + worldState_wolfHunt: `Cacería del Lobo`, + worldState_voidCorruption: `Corrupción del Vacío (|VAL|)`, worldState_orphixVenom: `Veneno de Orphix`, worldState_longShadow: `Sombra Prolongada`, worldState_hallowedFlame: `Llama Sagrada`, @@ -293,8 +301,9 @@ dict = { worldState_eightClawProgressOverride: `Progreso de Octava Garra`, worldState_thermiaFractures: `Fracturas Thermia`, worldState_thermiaFracturesProgressOverride: `Progreso de Fracturas Thermia`, - worldState_from_year: `de |VAL|`, - worldState_pre_year: `antes de |VAL|`, + worldState_qtccAlerts: `[UNTRANSLATED] Quest to Conquer Cancer Alerts`, + worldState_from_year: `De |VAL|`, + worldState_pre_year: `Antes de |VAL|`, worldState_week: `Semana |VAL|`, worldState_incompatibleWith: `No compatible con:`, enabled: `Activado`, @@ -411,6 +420,8 @@ dict = { guildView_cheats: `Trucos de Clan`, guildView_techProjects: `Investigación`, guildView_vaultDecoRecipes: `Planos de Decoración de Dojo`, + guildView_vaultMiscItems: `[UNTRANSLATED] Resources in Vault`, + guildView_vaultShipDecorations: `[UNTRANSLATED] Decorations in Vault`, guildView_alliance: `Alianza`, guildView_members: `Miembros`, guildView_pending: `Pendiente`, @@ -433,6 +444,7 @@ dict = { guildView_currency_owned: `|COUNT| en la Bóveda.`, guildView_bulkAddTechProjects: `Añadir proyectos de Investigación Faltantes`, guildView_bulkAddVaultDecoRecipes: `Añadir planos de Decoración de Dojo Faltantes`, + guildView_bulkAddVaultShipDecorations: `[UNTRANSLATED] Add Missing Decorations in Vault`, guildView_bulkFundTechProjects: `Financiar toda la Investigación`, guildView_bulkCompleteTechProjects: `Completar toda la Investigación`, guildView_promote: `Promover`, diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index 66fbad3c..3fa19019 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -33,7 +33,7 @@ dict = { code_remove: `Retirer`, code_addItemsConfirm: `Ajouter |COUNT| items à l'inventaire ?`, code_addTechProjectsConfirm: `Ajouter |COUNT| recherches au clan ?`, - code_addDecoRecipesConfirm: `Ajouter |COUNT| décorations au clan ?`, + code_addVaultItemsConfirm: `Ajouter |COUNT| objets au coffre de clan ?`, code_succRankUp: `Montée de niveau effectuée.`, code_noEquipmentToRankUp: `Aucun équipement à monter de niveau.`, code_succAdded: `Ajouté.`, @@ -45,7 +45,7 @@ dict = { code_rank: `Rang`, code_rankUp: `Monter de rang`, code_rankDown: `Baisser de rang`, - code_unlockLevelCap: `[UNTRANSLATED] Unlock level cap`, + code_unlockLevelCap: `Débloquer le level cap (9999)`, code_count: `Quantité`, code_focusAllUnlocked: `Les écoles de Focus sont déjà déverrouillées.`, code_focusUnlocked: `|COUNT| écoles de Focus déverrouillées ! Synchronisation de l'inventaire nécessaire.`, @@ -65,18 +65,22 @@ dict = { code_completed: `Complétée`, code_active: `Active`, code_pigment: `Pigment`, - code_controller: `[UNTRANSLATED] Controller cursor`, - code_mouseLine: `[UNTRANSLATED] Line cursor`, - code_mouse: `[UNTRANSLATED] Cursor`, + code_controller: `Curseur manette`, + code_mouseLine: `Curseur linéaire`, + code_mouse: `Curseur`, code_itemColorPalette: `Palette de couleurs |ITEM|`, code_mature: `Maturer pour le combat`, code_unmature: `Régrésser l'âge génétique`, code_fund: `Financer`, code_funded: `Complété`, - code_replays: `[UNTRANSLATED] Replays`, + code_replays: `Rejouée`, code_stalker: `Stalker`, + code_cutName: `Coupe |INDEX|`, + code_drifterBeardName: `Barbe du Voyageur |INDEX|`, + code_drifterFaceName: `Visage du Voyageur |INDEX|`, + code_operatorFaceName: `Visage de l'Opérateur |INDEX|`, code_succChange: `Changement effectué.`, - code_requiredInvigorationUpgrade: `[UNTRANSLATED] You must select both an offensive & utility upgrade.`, + code_requiredInvigorationUpgrade: `Invigoration offensive et défensive requises.`, login_description: `Connexion avec les informations de connexion OpenWF.`, login_emailLabel: `Email`, login_passwordLabel: `Mot de passe`, @@ -110,17 +114,19 @@ dict = { inventory_kubrowPets: `Bêtes`, inventory_evolutionProgress: `Progrès de l'évolution Incarnon`, inventory_boosters: `Boosters`, - inventory_flavourItems: `[UNTRANSLATED] Flavour Items`, + inventory_flavourItems: `Collectables`, inventory_shipDecorations: `Décorations du vaisseau`, + inventory_weaponSkins: `Aspects`, inventory_bulkAddSuits: `Ajouter les Warframes manquantes`, inventory_bulkAddWeapons: `Ajouter les armes manquantes`, inventory_bulkAddSpaceSuits: `Ajouter les Archwings manquants`, inventory_bulkAddSpaceWeapons: `Ajouter les armes d'Archwing manquantes`, inventory_bulkAddSentinels: `Ajouter les Sentinelles manquantes`, inventory_bulkAddSentinelWeapons: `Ajouter les armes de Sentinelles manquantes`, - inventory_bulkAddFlavourItems: `[UNTRANSLATED] Add Missing Flavour Items`, - inventory_bulkAddShipDecorations: `[UNTRANSLATED] Add Missing Ship Decorations`, + inventory_bulkAddFlavourItems: `Ajouter les collectables manquants ?`, + inventory_bulkAddShipDecorations: `Ajouter les décorations de vaisseau manquantes`, inventory_bulkAddEvolutionProgress: `Ajouter les évolutions Incarnon manquantes`, + inventory_bulkAddWeaponSkins: `Ajouter les skins manquants`, inventory_bulkRankUpSuits: `Toutes les Warframes au rang max`, inventory_bulkRankUpWeapons: `Toutes les armes au rang max`, inventory_bulkRankUpSpaceSuits: `Tous les Archwings au rang max`, @@ -129,6 +135,7 @@ dict = { inventory_bulkRankUpSentinelWeapons: `Toutes les armes de Sentinelles au rang max`, inventory_bulkRankUpEvolutionProgress: `Toutes les évolutions Incarnon au rang max`, inventory_maxPlexus: `Plexus au rang max`, + inventory_removeIsNew: `[UNTRANSLATED] Remove New Equipment Exclamation Icon`, quests_list: `Quêtes`, quests_completeAll: `Compléter toutes les quêtes`, @@ -173,8 +180,8 @@ dict = { invigorations_utility_EnergyRegen: `+2 d'énergie régénérés/s`, detailedView_invigorationOffensiveLabel: `Amélioration offensive`, - detailedView_invigorationUtilityLabel: `[UNTRANSLATED] Utility Upgrade`, - detailedView_invigorationExpiryLabel: `[UNTRANSLATED] Invigoration Expiry (optional)`, + detailedView_invigorationUtilityLabel: `Amélioration défensive`, + detailedView_invigorationExpiryLabel: `Expiration de l'invigoration (optionnel)`, abilityOverride_label: `Remplacement de pouvoir`, abilityOverride_onSlot: `Sur l'emplacement`, @@ -257,12 +264,12 @@ dict = { cheats_changeButton: `Changer`, cheats_markAllAsRead: `Marquer la boîte de réception comme lue`, cheats_finishInvasionsInOneMission: `Compléter les invasions en une mission.`, - cheats_nemesisHenchmenKillsMultiplierGrineer: `[UNTRANSLATED] Rage Progess Multiplier (Grineer)`, - cheats_nemesisHenchmenKillsMultiplierCorpus: `[UNTRANSLATED] Rage Progess Multiplier (Corpus)`, - cheats_nemesisAntivirusGainMultiplier: `[UNTRANSLATED] Antivirus Progress Multiplier`, - cheats_nemesisHintProgressMultiplierGrineer: `[UNTRANSLATED] Hint Progress Multiplier (Grineer)`, - cheats_nemesisHintProgressMultiplierCorpus: `[UNTRANSLATED] Hint Progress Multiplier (Corpus)`, - cheats_nemesisExtraWeapon: `[UNTRANSLATED] Extra Nemesis Weapon / Token On Vanquish (0 to disable)`, + cheats_nemesisHenchmenKillsMultiplierGrineer: `Multiplicateur de rage (Grineer)`, + cheats_nemesisHenchmenKillsMultiplierCorpus: `Multiplicateur de rage (Corpus)`, + cheats_nemesisAntivirusGainMultiplier: `Multiplicateur de l'Antivirus`, + cheats_nemesisHintProgressMultiplierGrineer: `Multiplicateur d'indices (Grineer)`, + cheats_nemesisHintProgressMultiplierCorpus: `Multiplicateur d'indices (Corpus)`, + cheats_nemesisExtraWeapon: `Arme de Nemesis/jeton supplémentaire sur exécution (0 pour désactiver)`, worldState: `Carte Solaire`, worldState_creditBoost: `Booster de Crédit`, @@ -278,13 +285,14 @@ dict = { worldState_plagueStar: `Fléau Céleste`, worldState_dogDays: `Bataille d'Eau`, worldState_dogDaysRewards: `Récompenses de la Bataille d'Eau`, - worldState_wolfHunt: `Chasse au Loup (2025)`, + worldState_wolfHunt: `Chasse au Loup`, + worldState_voidCorruption: `Corruption du Néant (|VAL|)`, worldState_orphixVenom: `Venin Orphix`, worldState_longShadow: `La Propagation des Ombres`, worldState_hallowedFlame: `Flamme Hantée`, worldState_hallowedNightmares: `Cauchemars Hantés`, worldState_hallowedNightmaresRewards: `Récompenses Flamme Hantée Cauchemar`, - worldState_naberusNights: `[UNTRANSLATED] Nights of Naberus`, + worldState_naberusNights: `Les Nuits de Naberus`, worldState_proxyRebellion: `Rébellion Proxy`, worldState_proxyRebellionRewards: `Récompenses Rébellion Proxy`, worldState_bellyOfTheBeast: `Ventre de la Bête`, @@ -293,8 +301,9 @@ dict = { worldState_eightClawProgressOverride: `Progrès de la Huitième Griffe`, worldState_thermiaFractures: `Crevasses Thermia`, worldState_thermiaFracturesProgressOverride: `Progrès des Fractures Thermia`, - worldState_from_year: `de |VAL|`, - worldState_pre_year: `pre-|VAL|`, + worldState_qtccAlerts: `Alertes Quête pour Vaincre le Cancer`, + worldState_from_year: `De |VAL|`, + worldState_pre_year: `Pre-|VAL|`, worldState_week: `Semaine |VAL|`, worldState_incompatibleWith: `Incompatible avec :`, enabled: `Activé`, @@ -408,9 +417,11 @@ dict = { theme_dark: `Thème sombre`, theme_light: `Thème clair`, - guildView_cheats: `[UNTRANSLATED] Clan Cheats`, + guildView_cheats: `Clan`, guildView_techProjects: `Recherche`, guildView_vaultDecoRecipes: `Schémas de décorations de dojo`, + guildView_vaultMiscItems: `Ressources dans le coffre`, + guildView_vaultShipDecorations: `Décorations dans le coffre`, guildView_alliance: `Alliance`, guildView_members: `Members`, guildView_pending: `En Attente`, @@ -433,6 +444,7 @@ dict = { guildView_currency_owned: `|COUNT| dans le coffre.`, guildView_bulkAddTechProjects: `Ajouter les recherches manquantes`, guildView_bulkAddVaultDecoRecipes: `Ajouter les schémas de décorations de dojo manquantes`, + guildView_bulkAddVaultShipDecorations: `Ajouter dans le coffre les décorations manquantes`, guildView_bulkFundTechProjects: `Financer toutes les recherches`, guildView_bulkCompleteTechProjects: `Compléter toutes les recherches`, guildView_promote: `Promouvoir`, diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index 4de12099..bb80e3ed 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -33,7 +33,7 @@ dict = { code_remove: `Удалить`, code_addItemsConfirm: `Вы уверены, что хотите добавить |COUNT| предметов на ваш аккаунт?`, code_addTechProjectsConfirm: `Вы уверены, что хотите добавить |COUNT| исследований в свой клан?`, - code_addDecoRecipesConfirm: `Вы уверены, что хотите добавить |COUNT| рецептов декораций в свой клан?`, + code_addVaultItemsConfirm: `Вы уверены, что хотите добавить |COUNT| предметов в хранилище своего клана?`, code_succRankUp: `Ранг успешно повышен.`, code_noEquipmentToRankUp: `Нет снаряжения для повышения ранга.`, code_succAdded: `Успешно добавлено.`, @@ -48,7 +48,7 @@ dict = { code_unlockLevelCap: `Разблокировать ограничение уровня`, code_count: `Количество`, code_focusAllUnlocked: `Все школы Фокуса уже разблокированы.`, - code_focusUnlocked: `Разблокировано |COUNT| новых школ Фокуса! Для отображения изменений в игре потребуется обновление инвентаря. Посещение навигации — самый простой способ этого добиться.`, + code_focusUnlocked: `[UNTRANSLATED] Unlocked |COUNT| new focus schools! An inventory update will be needed for the changes to be reflected in-game.`, code_addModsConfirm: `Вы уверены, что хотите добавить |COUNT| модов на ваш аккаунт?`, code_succImport: `Успешно импортировано.`, code_succRelog: `Готово. Обратите внимание, что вам нужно будет перезайти, чтобы увидеть изменения в игре.`, @@ -75,6 +75,10 @@ dict = { code_funded: `Профинансировано`, code_replays: `Повторов`, code_stalker: `Сталкер`, + code_cutName: `Причёска: |INDEX|`, + code_drifterBeardName: `Борода скитальца: |INDEX|`, + code_drifterFaceName: `Внешность скитальца: |INDEX|`, + code_operatorFaceName: `Внешность оператора: |INDEX|`, code_succChange: `Успешно изменено.`, code_requiredInvigorationUpgrade: `Вы должны выбрать как атакующее, так и вспомогательное улучшение.`, login_description: `Войдите, используя учетные данные OpenWF (те же, что и в игре при подключении к этому серверу).`, @@ -112,6 +116,7 @@ dict = { inventory_boosters: `Бустеры`, inventory_flavourItems: `Уникальные предметы`, inventory_shipDecorations: `Украшения корабля`, + inventory_weaponSkins: `Скины`, inventory_bulkAddSuits: `Добавить отсутствующие Варфреймы`, inventory_bulkAddWeapons: `Добавить отсутствующее оружие`, inventory_bulkAddSpaceSuits: `Добавить отсутствующие Арчвинги`, @@ -121,6 +126,7 @@ dict = { inventory_bulkAddFlavourItems: `Добавить отсутствующие уникальные предметы`, inventory_bulkAddShipDecorations: `Добавить отсутствующие украшения корабля`, inventory_bulkAddEvolutionProgress: `Добавить отсутствующий прогресс эволюции Инкарнонов`, + inventory_bulkAddWeaponSkins: `Добавить отсутствующие скины`, inventory_bulkRankUpSuits: `Макс. ранг всех Варфреймов`, inventory_bulkRankUpWeapons: `Макс. ранг всего оружия`, inventory_bulkRankUpSpaceSuits: `Макс. ранг всех Арчвингов`, @@ -129,6 +135,7 @@ dict = { inventory_bulkRankUpSentinelWeapons: `Макс. ранг всего оружия Стражей`, inventory_bulkRankUpEvolutionProgress: `Макс. ранг всех эволюций Инкарнонов`, inventory_maxPlexus: `Макс. ранг Плексуса`, + inventory_removeIsNew: `Удалить значок восклицательного знака нового снаряжения`, quests_list: `Квесты`, quests_completeAll: `Завершить все квесты`, @@ -278,7 +285,8 @@ dict = { worldState_plagueStar: `Чумная звезда`, worldState_dogDays: `Знойные дни`, worldState_dogDaysRewards: `Награды Знойных дней`, - worldState_wolfHunt: `Волчья Охота (2025)`, + worldState_wolfHunt: `Волчья Охота`, + worldState_voidCorruption: `Искажение Бездны (|VAL|)`, worldState_orphixVenom: `Яд Орфикса`, worldState_longShadow: `Длинная Тень`, worldState_hallowedFlame: `Священное пламя`, @@ -293,8 +301,9 @@ dict = { worldState_eightClawProgressOverride: `Прогресс Восьми когтей`, worldState_thermiaFractures: `Разломы Термии`, worldState_thermiaFracturesProgressOverride: `Прогресс Разломов Термии`, - worldState_from_year: `из |VAL|`, - worldState_pre_year: `до |VAL|`, + worldState_qtccAlerts: `Тревоги Quest to Conquer Cancer`, + worldState_from_year: `Из |VAL|`, + worldState_pre_year: `До |VAL|`, worldState_week: `Неделя |VAL|`, worldState_incompatibleWith: `Несовместимо с:`, enabled: `Включено`, @@ -411,6 +420,8 @@ dict = { guildView_cheats: `Читы Клана`, guildView_techProjects: `Исследовения`, guildView_vaultDecoRecipes: `Рецепты декораций Додзё`, + guildView_vaultMiscItems: `Ресурсы в Хранилище`, + guildView_vaultShipDecorations: `Укращения в Хранилище`, guildView_alliance: `Альянс`, guildView_members: `Товарищи`, guildView_pending: `Ожидание`, @@ -433,6 +444,7 @@ dict = { guildView_currency_owned: `В хранилище |COUNT|.`, guildView_bulkAddTechProjects: `Добавить отсутствующие исследования`, guildView_bulkAddVaultDecoRecipes: `Добавить отсутствующие рецепты декораций Дoдзё`, + guildView_bulkAddVaultShipDecorations: `Добавить отсутствующие укращения в Хранилище`, guildView_bulkFundTechProjects: `Профинансировать все исследования`, guildView_bulkCompleteTechProjects: `Завершить все исследования`, guildView_promote: `Повысить`, diff --git a/static/webui/translations/uk.js b/static/webui/translations/uk.js index dcfdc95c..e36a9939 100644 --- a/static/webui/translations/uk.js +++ b/static/webui/translations/uk.js @@ -33,7 +33,7 @@ dict = { code_remove: `Видалити`, code_addItemsConfirm: `Ви впевнені, що хочете додати |COUNT| предметів на ваш обліковий запис?`, code_addTechProjectsConfirm: `Ви впевнені, що хочете додати |COUNT| досліджень до свого клану?`, - code_addDecoRecipesConfirm: `Ви впевнені, що хочете додати |COUNT| рецептів оздоблень до свого клану?`, + code_addVaultItemsConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| items to your clan vault?`, code_succRankUp: `Рівень успішно підвищено`, code_noEquipmentToRankUp: `Немає спорядження для підвищення рівня.`, code_succAdded: `Успішно додано.`, @@ -48,7 +48,7 @@ dict = { code_unlockLevelCap: `Розблокувати обмеження рівня`, code_count: `Кількість`, code_focusAllUnlocked: `Всі школи Фокусу вже розблоковані.`, - code_focusUnlocked: `Розблоковано |COUNT| нових шкіл Фокусу! Для відображення змін в грі знадобиться оновлення спорядження. Відвідування навігації — найпростіший спосіб цього досягти.`, + code_focusUnlocked: `Розблоковано |COUNT| нових шкіл Фокусу! Для відображення змін в грі знадобиться оновлення спорядження.`, code_addModsConfirm: `Ви впевнені, що хочете додати |COUNT| модифікаторів на ваш обліковий запис?`, code_succImport: `Успішно імпортовано.`, code_succRelog: `Готово. Зверніть увагу, що вам потрібно буде перезайти, щоб побачити зміни в грі.`, @@ -75,6 +75,10 @@ dict = { code_funded: `Профінансовано`, code_replays: `Повтори`, code_stalker: `Сталкер`, + code_cutName: `Зачіска: |INDEX|`, + code_drifterBeardName: `Борода мандрівника: |INDEX|`, + code_drifterFaceName: `Зовнішність мандрівника: |INDEX|`, + code_operatorFaceName: `Зовнішність оператора: |INDEX|`, code_succChange: `Успішно змінено.`, code_requiredInvigorationUpgrade: `Ви повинні вибрати як атакуюче, так і допоміжне вдосконалення.`, login_description: `Увійдіть, використовуючи облікові дані OpenWF (ті ж, що й у грі при підключенні до цього серверу).`, @@ -112,6 +116,7 @@ dict = { inventory_boosters: `Посилення`, inventory_flavourItems: `Унікальні предмети`, inventory_shipDecorations: `Прикраси судна`, + inventory_weaponSkins: `Вигляди`, inventory_bulkAddSuits: `Додати відсутні Ворфрейми`, inventory_bulkAddWeapons: `Додати відсутню зброю`, inventory_bulkAddSpaceSuits: `Додати відсутні Арквінґи`, @@ -121,6 +126,7 @@ dict = { inventory_bulkAddFlavourItems: `Додати відсутні унікальні предмети`, inventory_bulkAddShipDecorations: `Додати відсутні оздоби корабля`, inventory_bulkAddEvolutionProgress: `Додати відсутній прогрес еволюції Інкарнонів`, + inventory_bulkAddWeaponSkins: `[UNTRANSLATED] Add Missing Skins`, inventory_bulkRankUpSuits: `Макс. рівень всіх Ворфреймів`, inventory_bulkRankUpWeapons: `Макс. рівень всієї зброї`, inventory_bulkRankUpSpaceSuits: `Макс. рівень всіх Арквінґів`, @@ -129,6 +135,7 @@ dict = { inventory_bulkRankUpSentinelWeapons: `Макс. рівень всієї зброї Вартових`, inventory_bulkRankUpEvolutionProgress: `Макс. рівень всіх еволюцій Інкарнонів`, inventory_maxPlexus: `Макс. рівень Плексу`, + inventory_removeIsNew: `[UNTRANSLATED] Remove New Equipment Exclamation Icon`, quests_list: `Пригоди`, quests_completeAll: `Закінчити всі пригоди`, @@ -278,7 +285,8 @@ dict = { worldState_plagueStar: `Морова зірка`, worldState_dogDays: `Спекотні дні`, worldState_dogDaysRewards: `Нагороди Спекотних днів`, - worldState_wolfHunt: `Полювання на Вовка (2025)`, + worldState_wolfHunt: `Полювання на Вовка`, + worldState_voidCorruption: `Викривлення Порожнечі (|VAL|)`, worldState_orphixVenom: `Орфіксова отрута`, worldState_longShadow: `Довга тінь`, worldState_hallowedFlame: `Священне полум'я`, @@ -293,8 +301,9 @@ dict = { worldState_eightClawProgressOverride: `Прогрес Восьми кігтів`, worldState_thermiaFractures: `Розломи термії`, worldState_thermiaFracturesProgressOverride: `Прогрес Розломів термії`, - worldState_from_year: `з |VAL|`, - worldState_pre_year: `до |VAL|`, + worldState_qtccAlerts: `[UNTRANSLATED] Quest to Conquer Cancer Alerts`, + worldState_from_year: `З |VAL|`, + worldState_pre_year: `До |VAL|`, worldState_week: `Тиждень |VAL|`, worldState_incompatibleWith: `Несумісне з:`, enabled: `Увімкнено`, @@ -411,6 +420,8 @@ dict = { guildView_cheats: `Кланові чити`, guildView_techProjects: `Дослідження`, guildView_vaultDecoRecipes: `Рецепти оздоблень Доджьо`, + guildView_vaultMiscItems: `[UNTRANSLATED] Resources in Vault`, + guildView_vaultShipDecorations: `[UNTRANSLATED] Decorations in Vault`, guildView_alliance: `Альянс`, guildView_members: `Учасники`, guildView_pending: `Очікування`, @@ -433,6 +444,7 @@ dict = { guildView_currency_owned: `В сховищі |COUNT|.`, guildView_bulkAddTechProjects: `Додати відсутні дослідження`, guildView_bulkAddVaultDecoRecipes: `Додати відсутні рецепти оздоблень Доджьо`, + guildView_bulkAddVaultShipDecorations: `[UNTRANSLATED] Add Missing Decorations in Vault`, guildView_bulkFundTechProjects: `Фінансувати всі дослідження`, guildView_bulkCompleteTechProjects: `Завершити всі дослідження`, guildView_promote: `Підвищити звання`, diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index 97a24244..e856f8b9 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -33,7 +33,7 @@ dict = { code_remove: `移除`, code_addItemsConfirm: `确定要向您的账户添加 |COUNT| 件物品吗?`, code_addTechProjectsConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| research to your clan?`, - code_addDecoRecipesConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| deco recipes to your clan?`, + code_addVaultItemsConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| items to your clan vault?`, code_succRankUp: `等级已提升`, code_noEquipmentToRankUp: `没有可升级的装备`, code_succAdded: `添加成功`, @@ -48,7 +48,7 @@ dict = { code_unlockLevelCap: `[UNTRANSLATED] Unlock level cap`, code_count: `数量`, code_focusAllUnlocked: `所有专精学派均已解锁`, - code_focusUnlocked: `已解锁 |COUNT| 个新专精学派!需要游戏内仓库更新才能生效,您可以通过访问星图来触发仓库更新.`, + code_focusUnlocked: `已解锁 |COUNT| 个新专精学派!需要游戏内仓库更新才能生效.`, code_addModsConfirm: `确定要向您的账户添加 |COUNT| 张MOD吗?`, code_succImport: `导入成功`, code_succRelog: `完成.需要重新登录游戏才能看到变化.`, @@ -75,6 +75,10 @@ dict = { code_funded: `[UNTRANSLATED] Funded`, code_replays: `[UNTRANSLATED] Replays`, code_stalker: `追猎者`, + code_cutName: `[UNTRANSLATED] Cut |INDEX|`, + code_drifterBeardName: `漂泊者胡须 |INDEX|`, + code_drifterFaceName: `漂泊者面部 |INDEX|`, + code_operatorFaceName: `指挥官面部 |INDEX|`, code_succChange: `更改成功`, code_requiredInvigorationUpgrade: `[UNTRANSLATED] You must select both an offensive & utility upgrade.`, login_description: `使用您的 OpenWF 账户凭证登录(与游戏内连接本服务器时使用的昵称相同)`, @@ -112,6 +116,7 @@ dict = { inventory_boosters: `加成器`, inventory_flavourItems: `装饰物品`, inventory_shipDecorations: `飞船装饰`, + inventory_weaponSkins: `外观`, inventory_bulkAddSuits: `添加缺失战甲`, inventory_bulkAddWeapons: `添加缺失武器`, inventory_bulkAddSpaceSuits: `添加缺失载具`, @@ -121,6 +126,7 @@ dict = { inventory_bulkAddFlavourItems: `[UNTRANSLATED] Add Missing Flavour Items`, inventory_bulkAddShipDecorations: `[UNTRANSLATED] Add Missing Ship Decorations`, inventory_bulkAddEvolutionProgress: `添加缺失的灵化之源进度`, + inventory_bulkAddWeaponSkins: `[UNTRANSLATED] Add Missing Skins`, inventory_bulkRankUpSuits: `所有战甲升满级`, inventory_bulkRankUpWeapons: `所有武器升满级`, inventory_bulkRankUpSpaceSuits: `所有载具升满级`, @@ -129,6 +135,7 @@ dict = { inventory_bulkRankUpSentinelWeapons: `所有守护武器升满级`, inventory_bulkRankUpEvolutionProgress: `所有灵化之源进度最大等级`, inventory_maxPlexus: `最大深控等级`, + inventory_removeIsNew: `[UNTRANSLATED] Remove New Equipment Exclamation Icon`, quests_list: `系列任务`, quests_completeAll: `完成所有任务`, @@ -278,7 +285,8 @@ dict = { worldState_plagueStar: `瘟疫之星`, worldState_dogDays: `三伏天`, worldState_dogDaysRewards: `三伏天奖励设置`, - worldState_wolfHunt: `恶狼狩猎 (2025)`, + worldState_wolfHunt: `恶狼狩猎`, + worldState_voidCorruption: `虚空堕落 (|VAL|)`, worldState_orphixVenom: `奥影之毒`, worldState_longShadow: `暗夜长影`, worldState_hallowedFlame: `万圣之焰`, @@ -293,6 +301,7 @@ dict = { worldState_eightClawProgressOverride: `大帝金币收集进度(%)`, worldState_thermiaFractures: `热美亚裂缝`, worldState_thermiaFracturesProgressOverride: `[UNTRANSLATED] Thermia Fractures Progress`, + worldState_qtccAlerts: `[UNTRANSLATED] Quest to Conquer Cancer Alerts`, worldState_from_year: `|VAL|`, worldState_pre_year: `|VAL|之前`, worldState_week: `第|VAL|周`, @@ -411,6 +420,8 @@ dict = { guildView_cheats: `[UNTRANSLATED] Clan Cheats`, guildView_techProjects: `研究`, guildView_vaultDecoRecipes: `[UNTRANSLATED] Dojo Deco Recipes`, + guildView_vaultMiscItems: `[UNTRANSLATED] Resources in Vault`, + guildView_vaultShipDecorations: `[UNTRANSLATED] Decorations in Vault`, guildView_alliance: `联盟`, guildView_members: `成员`, guildView_pending: `待处理`, @@ -433,6 +444,7 @@ dict = { guildView_currency_owned: `[UNTRANSLATED] |COUNT| in Vault.`, guildView_bulkAddTechProjects: `[UNTRANSLATED] Add Missing Research`, guildView_bulkAddVaultDecoRecipes: `[UNTRANSLATED] Add Missing Dojo Deco Recipes`, + guildView_bulkAddVaultShipDecorations: `[UNTRANSLATED] Add Missing Decorations in Vault`, guildView_bulkFundTechProjects: `[UNTRANSLATED] Fund All Research`, guildView_bulkCompleteTechProjects: `[UNTRANSLATED] Complete All Research`, guildView_promote: `升级`,