Compare commits

...

42 Commits

Author SHA1 Message Date
317fb8324f chore: add a helper for skinLookupTable 2025-10-07 12:46:15 +02:00
05037a3ca2 move migration to cleanupInventory 2025-10-07 12:46:14 +02:00
c4434b87c1 give all missing skins 2025-10-07 12:46:14 +02:00
f665a8e585 typo 2025-10-07 12:46:14 +02:00
6b24a20ee1 Update saveLoadoutService.ts 2025-10-07 12:46:14 +02:00
8addb1e4f6 readd unlockAllSkins as account cheat 2025-10-07 12:46:14 +02:00
8549afd14e Update inventoryController.ts 2025-10-07 12:46:14 +02:00
a83d96289f add warning for other loadouts with skins 2025-10-07 12:46:14 +02:00
6fa80dab38 feat(webui): skins
Re #2361
2025-10-07 12:46:13 +02:00
f5c1b83598 fix: only commit 'Missions' on successful completion (#2866)
Fixes SP missions being marked as completed when failing/quitting.

Reviewed-on: OpenWF/SpaceNinjaServer#2866
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-06 22:57:18 -07:00
30f380f37e chore(webui): refresh when creating/deleting a clan in-game (#2864)
So the clan tab shows/hides instantly as expected.

Reviewed-on: OpenWF/SpaceNinjaServer#2864
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-06 22:57:08 -07:00
0f7a85db59 chore(webui): sync account cheats between different webui tabs (#2863)
Reviewed-on: OpenWF/SpaceNinjaServer#2863
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-06 22:56:51 -07:00
43bc12713a chore(webui): force account cheat element state after request is done (#2862)
There's a very slim chance we get an inventory response between sending the setAccountCheat request and receiving the response, in which case the element state would be ingruent.

Reviewed-on: OpenWF/SpaceNinjaServer#2862
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-06 22:56:35 -07:00
6022bf97b5 feat: nemesis mode d (#2860)
Reviewed-on: OpenWF/SpaceNinjaServer#2860
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-06 22:56:20 -07:00
159e151dc0 chore: check for xpBasedLevelCapDisabled in missionInventoryUpdate (#2859)
The bootstrapper provides this field since 0.8.2, so I think this field being absent is now more likely to mean that the patch is not in effect.

Reviewed-on: OpenWF/SpaceNinjaServer#2859
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-06 22:56:04 -07:00
56954260c8 chore(webui): debounce quest updates (#2858)
Closes #2855

Reviewed-on: OpenWF/SpaceNinjaServer#2858
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-06 22:55:46 -07:00
c535044af8 fix: use 1-based indexing for clan ranks for versions before U24 (#2857)
Reviewed-on: OpenWF/SpaceNinjaServer#2857
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-06 22:55:33 -07:00
f5146be129 fix: handle dojo room build request from old versions (#2854)
Reviewed-on: OpenWF/SpaceNinjaServer#2854
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-06 22:54:57 -07:00
d38ec06ed6 fix: disallow creating a clan from an account that's already in one (#2853)
Just a slight precaution to avoid snowballing problems.

Reviewed-on: OpenWF/SpaceNinjaServer#2853
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-06 22:54:35 -07:00
060f65900f fix: transform inventoryResponse.GuildId for older versions (#2852)
Reviewed-on: OpenWF/SpaceNinjaServer#2852
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-06 22:54:22 -07:00
66d3057d40 chore: fix typo 2025-10-06 08:33:44 +02:00
b14a5925df fix: setGuildMotd response for U29.3.1 (#2851)
Unsure which version introduced long descriptions exactly, but it surely wasn't this one. :^)

Reviewed-on: OpenWF/SpaceNinjaServer#2851
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-05 23:25:08 -07:00
9da47c406a fix: put CompletionTime of initial clan hall in the past (#2850)
For old versions, TimeRemaining of 0 would cause this to show in yellow, we need it to be negative.

Reviewed-on: OpenWF/SpaceNinjaServer#2850
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-05 23:24:59 -07:00
09065bdb4e chore: let webui know when client called updateQuest (#2849)
Reviewed-on: OpenWF/SpaceNinjaServer#2849
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-05 23:24:49 -07:00
8f04fc5fdf fix: default quest progress c to -1 (#2848)
Fixes #2846

Reviewed-on: OpenWF/SpaceNinjaServer#2848
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-05 23:24:38 -07:00
230ee5f638 fix: anniversary mission inbox messages showing unresolved |LOTUS_NAME| (#2845)
Reviewed-on: OpenWF/SpaceNinjaServer#2845
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-05 23:24:28 -07:00
21db6ce265 chore(webui): update uk & ru (#2844)
Reviewed-on: OpenWF/SpaceNinjaServer#2844
Co-authored-by: LoseFace <loseface@noreply.localhost>
Co-committed-by: LoseFace <loseface@noreply.localhost>
2025-10-05 23:24:14 -07:00
1ecf53c96b chore: don't add 'alwaysAvailable' skins with unlockAllSkins (#2843)
Reviewed-on: OpenWF/SpaceNinjaServer#2843
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-05 05:56:58 -07:00
e67ef63b77 fix: avoid using assassination node for an earlier sortie mission (#2838)
Closes #2837

Reviewed-on: OpenWF/SpaceNinjaServer#2838
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-04 04:18:21 -07:00
5772ebe746 feat(import): boosters (#2836)
Reviewed-on: OpenWF/SpaceNinjaServer#2836
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-03 06:46:07 -07:00
0136e4d152 chore(webui): clarify /sync command goes into chat (#2835)
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Reviewed-on: OpenWF/SpaceNinjaServer#2835
Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com>
Co-authored-by: LoseFace <loseface@noreply.localhost>
Co-committed-by: LoseFace <loseface@noreply.localhost>
2025-10-03 06:45:57 -07:00
8b3ee4b4f5 chore: allow sortie image randomisation for most tilesets (#2834)
This should reduce the impact while we investigate #2833

Reviewed-on: OpenWF/SpaceNinjaServer#2834
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-02 05:27:56 -07:00
6e8800f048 chore(webui): fix typos (#2832)
also updated author credits

Reviewed-on: OpenWF/SpaceNinjaServer#2832
Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com>
Co-authored-by: Animan8000 <animan8000@noreply.localhost>
Co-committed-by: Animan8000 <animan8000@noreply.localhost>
2025-10-01 01:23:08 -07:00
d65a667acd fix: ensure sorties show 'correct' image for corpus ice planet tileset (#2831)
Reviewed-on: OpenWF/SpaceNinjaServer#2831
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-09-30 00:00:13 -07:00
c6a3e86d2b fix(webui): invoke giveKeyChainStageTriggered for new stage (#2830)
Previously, this caused the old stage to just be reinitiated so we never went backwards.

Closes #2829

Reviewed-on: OpenWF/SpaceNinjaServer#2830
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-09-29 23:59:35 -07:00
a8e41c95e7 chore: move createNewEventMessages from inboxService to inboxController (#2828)
This function wasn't used anywhere else and caused a recursive include in inboxService.

Reviewed-on: OpenWF/SpaceNinjaServer#2828
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-09-29 23:59:26 -07:00
9426359370 feat: Nights of Naberus (#2817)
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Reviewed-on: OpenWF/SpaceNinjaServer#2817
Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com>
Co-authored-by: Gian <gianplu55@gmail.com>
Co-committed-by: Gian <gianplu55@gmail.com>
2025-09-29 23:59:17 -07:00
e5247700df fix: use safe navigation to check for replay in giveKeyChainMessage (#2826)
Reviewed-on: OpenWF/SpaceNinjaServer#2826
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-09-29 02:00:32 -07:00
1c3f1e2276 feat: DeleteAllReadNonCin (#2824)
Closes #2822

Reviewed-on: OpenWF/SpaceNinjaServer#2824
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-09-29 02:00:17 -07:00
7710e7c13f feat: inbox message for relics cracked during an unfinished mission (#2823)
Closes #2821

Reviewed-on: OpenWF/SpaceNinjaServer#2823
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-09-29 02:00:05 -07:00
a64c5ea3c1 chore(webui): remove administratorNames entry when deleting account (#2820)
Closes #2819

Reviewed-on: OpenWF/SpaceNinjaServer#2820
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-09-28 01:10:45 -07:00
17e1eb86dd fix(webui): don't send off 2 addXp requests at once (#2815)
One would likely fail due to Mongoose's array versioning

Closes #2811

Reviewed-on: OpenWF/SpaceNinjaServer#2815
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-09-27 03:23:53 -07:00
38 changed files with 659 additions and 302 deletions

View File

@ -35,5 +35,5 @@ SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [confi
- `RadioLegion2Syndicate` for The Emissary - `RadioLegion2Syndicate` for The Emissary
- `RadioLegionIntermissionSyndicate` for Intermission I - `RadioLegionIntermissionSyndicate` for Intermission I
- `RadioLegionSyndicate` for The Wolf of Saturn Six - `RadioLegionSyndicate` for The Wolf of Saturn Six
- `allTheFissures` can be set to `normal` or `hard` to enable all fissures either in normal or steel path, respectively. - `worldState.allTheFissures` can be set to `normal` or `hard` to enable all fissures either in normal or steel path, respectively.
- `worldState.circuitGameModes` can be set to an array of game modes which will override the otherwise-random pattern in The Circuit. Valid element values are `Survival`, `VoidFlood`, `Excavation`, `Defense`, `Exterminate`, `Assassination`, and `Alchemy`. - `worldState.circuitGameModes` can be set to an array of game modes which will override the otherwise-random pattern in The Circuit. Valid element values are `Survival`, `VoidFlood`, `Excavation`, `Defense`, `Exterminate`, `Assassination`, and `Alchemy`.

View File

@ -11,7 +11,6 @@
"administratorNames": [], "administratorNames": [],
"autoCreateAccount": true, "autoCreateAccount": true,
"skipTutorial": false, "skipTutorial": false,
"unlockAllSkins": false,
"fullyStockedVendors": false, "fullyStockedVendors": false,
"skipClanKeyCrafting": false, "skipClanKeyCrafting": false,
"spoofMasteryRank": -1, "spoofMasteryRank": -1,
@ -38,6 +37,7 @@
"anniversary": null, "anniversary": null,
"hallowedNightmares": false, "hallowedNightmares": false,
"hallowedNightmaresRewardsOverride": 0, "hallowedNightmaresRewardsOverride": 0,
"naberusNightsOverride": null,
"proxyRebellion": false, "proxyRebellion": false,
"proxyRebellionRewardsOverride": 0, "proxyRebellionRewardsOverride": 0,
"galleonOfGhouls": 0, "galleonOfGhouls": 0,

View File

@ -5,11 +5,23 @@ import { Guild, GuildMember } from "../../models/guildModel.ts";
import { createUniqueClanName, getGuildClient, giveClanKey } from "../../services/guildService.ts"; import { createUniqueClanName, getGuildClient, giveClanKey } from "../../services/guildService.ts";
import { getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "../../services/inventoryService.ts";
import type { IInventoryChanges } from "../../types/purchaseTypes.ts"; import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
import { sendWsBroadcastTo } from "../../services/wsService.ts";
export const createGuildController: RequestHandler = async (req, res) => { export const createGuildController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req); const account = await getAccountForRequest(req);
const payload = getJSONfromString<ICreateGuildRequest>(String(req.body)); const payload = getJSONfromString<ICreateGuildRequest>(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 // Remove pending applications for this account
await GuildMember.deleteMany({ accountId: account._id, status: 1 }); await GuildMember.deleteMany({ accountId: account._id, status: 1 });
@ -27,7 +39,6 @@ export const createGuildController: RequestHandler = async (req, res) => {
rank: 0 rank: 0
}); });
const inventory = await getInventory(account._id.toString(), "GuildId LevelKeys Recipes");
inventory.GuildId = guild._id; inventory.GuildId = guild._id;
const inventoryChanges: IInventoryChanges = {}; const inventoryChanges: IInventoryChanges = {};
giveClanKey(inventory, inventoryChanges); giveClanKey(inventory, inventoryChanges);
@ -37,6 +48,7 @@ export const createGuildController: RequestHandler = async (req, res) => {
...(await getGuildClient(guild, account)), ...(await getGuildClient(guild, account)),
InventoryChanges: inventoryChanges InventoryChanges: inventoryChanges
}); });
sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
}; };
interface ICreateGuildRequest { interface ICreateGuildRequest {

View File

@ -19,7 +19,7 @@ export const getGuildDojoController: RequestHandler = async (req, res) => {
_id: new Types.ObjectId(), _id: new Types.ObjectId(),
pf: "/Lotus/Levels/ClanDojo/DojoHall.level", pf: "/Lotus/Levels/ClanDojo/DojoHall.level",
ppf: "", ppf: "",
CompletionTime: new Date(Date.now()), CompletionTime: new Date(Date.now() - 1000),
DecoCapacity: 600 DecoCapacity: 600
}); });
await guild.save(); await guild.save();

View File

@ -1,12 +1,13 @@
import type { RequestHandler } from "express"; import type { Request, RequestHandler } from "express";
import { Inbox } from "../../models/inboxModel.ts"; import { Inbox } from "../../models/inboxModel.ts";
import { import {
createMessage, createMessage,
createNewEventMessages,
deleteAllMessagesRead, deleteAllMessagesRead,
deleteAllMessagesReadNonCin,
deleteMessageRead, deleteMessageRead,
getAllMessagesSorted, getAllMessagesSorted,
getMessage getMessage,
type IMessageCreationTemplate
} from "../../services/inboxService.ts"; } from "../../services/inboxService.ts";
import { getAccountForRequest, getAccountFromSuffixedName, getSuffixedName } from "../../services/loginService.ts"; import { getAccountForRequest, getAccountFromSuffixedName, getSuffixedName } from "../../services/loginService.ts";
import { import {
@ -21,6 +22,9 @@ import { ExportFlavour } from "warframe-public-export-plus";
import { handleStoreItemAcquisition } from "../../services/purchaseService.ts"; import { handleStoreItemAcquisition } from "../../services/purchaseService.ts";
import { fromStoreItem, isStoreItem } from "../../services/itemDataService.ts"; import { fromStoreItem, isStoreItem } from "../../services/itemDataService.ts";
import type { IOid } from "../../types/commonTypes.ts"; import type { IOid } from "../../types/commonTypes.ts";
import { unixTimesInMs } from "../../constants/timeConstants.ts";
import { config } from "../../services/configService.ts";
import { Types } from "mongoose";
export const inboxController: RequestHandler = async (req, res) => { export const inboxController: RequestHandler = async (req, res) => {
const { deleteId, lastMessage: latestClientMessageId, messageId } = req.query; const { deleteId, lastMessage: latestClientMessageId, messageId } = req.query;
@ -31,11 +35,11 @@ export const inboxController: RequestHandler = async (req, res) => {
if (deleteId) { if (deleteId) {
if (deleteId === "DeleteAllRead") { if (deleteId === "DeleteAllRead") {
await deleteAllMessagesRead(accountId); await deleteAllMessagesRead(accountId);
res.status(200).end(); } else if (deleteId === "DeleteAllReadNonCin") {
return; await deleteAllMessagesReadNonCin(accountId);
} else {
await deleteMessageRead(parseOid(deleteId as string));
} }
await deleteMessageRead(parseOid(deleteId as string));
res.status(200).end(); res.status(200).end();
} else if (messageId) { } else if (messageId) {
const message = await getMessage(parseOid(messageId as string)); const message = await getMessage(parseOid(messageId as string));
@ -134,6 +138,119 @@ export const inboxController: RequestHandler = async (req, res) => {
} }
}; };
const createNewEventMessages = async (req: Request): Promise<void> => {
const account = await getAccountForRequest(req);
const newEventMessages: IMessageCreationTemplate[] = [];
// Baro
const baroIndex = Math.trunc((Date.now() - 910800000) / (unixTimesInMs.day * 14));
const baroStart = baroIndex * (unixTimesInMs.day * 14) + 910800000;
const baroActualStart = baroStart + unixTimesInMs.day * (config.worldState?.baroAlwaysAvailable ? 0 : 12);
if (Date.now() >= baroActualStart && account.LatestEventMessageDate.getTime() < baroActualStart) {
newEventMessages.push({
sndr: "/Lotus/Language/G1Quests/VoidTraderName",
sub: "/Lotus/Language/CommunityMessages/VoidTraderAppearanceTitle",
msg: "/Lotus/Language/CommunityMessages/VoidTraderAppearanceMessage",
icon: "/Lotus/Interface/Icons/Npcs/BaroKiTeerPortrait.png",
startDate: new Date(baroActualStart),
endDate: new Date(baroStart + unixTimesInMs.day * 14),
CrossPlatform: true,
arg: [
{
Key: "NODE_NAME",
Tag: ["EarthHUB", "MercuryHUB", "SaturnHUB", "PlutoHUB"][baroIndex % 4]
}
],
date: new Date(baroActualStart)
});
}
// BUG: Deleting the inbox message manually means it'll just be automatically re-created. This is because we don't use startDate/endDate for these config-toggled events.
const promises = [];
if (config.worldState?.creditBoost) {
promises.push(
(async (): Promise<void> => {
if (!(await Inbox.exists({ ownerId: account._id, globaUpgradeId: "5b23106f283a555109666672" }))) {
newEventMessages.push({
globaUpgradeId: new Types.ObjectId("5b23106f283a555109666672"),
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sub: "/Lotus/Language/Items/EventDoubleCreditsName",
msg: "/Lotus/Language/Items/EventDoubleCreditsDesc",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
startDate: new Date(),
CrossPlatform: true
});
}
})()
);
}
if (config.worldState?.affinityBoost) {
promises.push(
(async (): Promise<void> => {
if (!(await Inbox.exists({ ownerId: account._id, globaUpgradeId: "5b23106f283a555109666673" }))) {
newEventMessages.push({
globaUpgradeId: new Types.ObjectId("5b23106f283a555109666673"),
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sub: "/Lotus/Language/Items/EventDoubleAffinityName",
msg: "/Lotus/Language/Items/EventDoubleAffinityDesc",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
startDate: new Date(),
CrossPlatform: true
});
}
})()
);
}
if (config.worldState?.resourceBoost) {
promises.push(
(async (): Promise<void> => {
if (!(await Inbox.exists({ ownerId: account._id, globaUpgradeId: "5b23106f283a555109666674" }))) {
newEventMessages.push({
globaUpgradeId: new Types.ObjectId("5b23106f283a555109666674"),
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sub: "/Lotus/Language/Items/EventDoubleResourceName",
msg: "/Lotus/Language/Items/EventDoubleResourceDesc",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
startDate: new Date(),
CrossPlatform: true
});
}
})()
);
}
if (config.worldState?.galleonOfGhouls) {
promises.push(
(async (): Promise<void> => {
if (!(await Inbox.exists({ ownerId: account._id, goalTag: "GalleonRobbery" }))) {
newEventMessages.push({
sndr: "/Lotus/Language/Bosses/BossCouncilorVayHek",
sub: "/Lotus/Language/Events/GalleonRobberyIntroMsgTitle",
msg: "/Lotus/Language/Events/GalleonRobberyIntroMsgDesc",
icon: "/Lotus/Interface/Icons/Npcs/VayHekPortrait.png",
transmission: "/Lotus/Sounds/Dialog/GalleonOfGhouls/DGhoulsWeekOneInbox0010VayHek",
att: ["/Lotus/Upgrades/Skins/Events/OgrisOldSchool"],
startDate: new Date(),
goalTag: "GalleonRobbery"
});
}
})()
);
}
await Promise.all(promises);
if (newEventMessages.length === 0) {
return;
}
await createMessage(account._id, newEventMessages);
const latestEventMessage = newEventMessages.reduce((prev, current) =>
prev.startDate! > current.startDate! ? prev : current
);
account.LatestEventMessageDate = new Date(latestEventMessage.startDate!);
await account.save();
};
// 33.6.0 has query arguments like lastMessage={"$oid":"68112baebf192e786d1502bb"} instead of lastMessage=68112baebf192e786d1502bb // 33.6.0 has query arguments like lastMessage={"$oid":"68112baebf192e786d1502bb"} instead of lastMessage=68112baebf192e786d1502bb
const parseOid = (oid: string): string => { const parseOid = (oid: string): string => {
if (oid[0] == "{") { if (oid[0] == "{") {

View File

@ -10,7 +10,6 @@ import { equipmentKeys } from "../../types/inventoryTypes/inventoryTypes.ts";
import type { IPolarity } from "../../types/inventoryTypes/commonInventoryTypes.ts"; import type { IPolarity } from "../../types/inventoryTypes/commonInventoryTypes.ts";
import { ArtifactPolarity } from "../../types/inventoryTypes/commonInventoryTypes.ts"; import { ArtifactPolarity } from "../../types/inventoryTypes/commonInventoryTypes.ts";
import type { ICountedItem } from "warframe-public-export-plus"; import type { ICountedItem } from "warframe-public-export-plus";
import { ExportCustoms } from "warframe-public-export-plus";
import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "../../services/infestedFoundryService.ts"; import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "../../services/infestedFoundryService.ts";
import { import {
addEmailItem, addEmailItem,
@ -23,7 +22,7 @@ import {
getCalendarProgress getCalendarProgress
} from "../../services/inventoryService.ts"; } from "../../services/inventoryService.ts";
import { logger } from "../../utils/logger.ts"; import { logger } from "../../utils/logger.ts";
import { addString, catBreadHash } from "../../helpers/stringHelpers.ts"; import { addString } from "../../helpers/stringHelpers.ts";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { getNemesisManifest } from "../../helpers/nemesisHelpers.ts"; import { getNemesisManifest } from "../../helpers/nemesisHelpers.ts";
import { getPersonalRooms } from "../../services/personalRoomsService.ts"; import { getPersonalRooms } from "../../services/personalRoomsService.ts";
@ -177,7 +176,7 @@ export const inventoryController: RequestHandler = async (request, response) =>
} }
} }
cleanupInventory(inventory); await cleanupInventory(inventory);
inventory.NextRefill = new Date((today + 1) * 86400000); // tomorrow at 0 UTC inventory.NextRefill = new Date((today + 1) * 86400000); // tomorrow at 0 UTC
//await inventory.save(); //await inventory.save();
@ -334,19 +333,6 @@ export const getInventoryResponse = async (
}); });
} }
if (config.unlockAllSkins) {
const missingWeaponSkins = new Set(Object.keys(ExportCustoms));
inventoryResponse.WeaponSkins.forEach(x => missingWeaponSkins.delete(x.ItemType));
for (const uniqueName of missingWeaponSkins) {
inventoryResponse.WeaponSkins.push({
ItemId: {
$oid: "ca70ca70ca70ca70" + catBreadHash(uniqueName).toString(16).padStart(8, "0")
},
ItemType: uniqueName
});
}
}
if (typeof config.spoofMasteryRank === "number" && config.spoofMasteryRank >= 0) { if (typeof config.spoofMasteryRank === "number" && config.spoofMasteryRank >= 0) {
inventoryResponse.PlayerLevel = config.spoofMasteryRank; inventoryResponse.PlayerLevel = config.spoofMasteryRank;
if (!xpBasedLevelCapDisabled) { if (!xpBasedLevelCapDisabled) {
@ -460,6 +446,9 @@ export const getInventoryResponse = async (
toLegacyOid(id); toLegacyOid(id);
} }
} }
if (inventoryResponse.GuildId) {
toLegacyOid(inventoryResponse.GuildId);
}
} }
} }
} }

View File

@ -9,6 +9,9 @@ import type { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "../../
import { logger } from "../../utils/logger.ts"; import { logger } from "../../utils/logger.ts";
import { version_compare } from "../../helpers/inventoryHelpers.ts"; import { version_compare } from "../../helpers/inventoryHelpers.ts";
import { handleNonceInvalidation } from "../../services/wsService.ts"; import { handleNonceInvalidation } from "../../services/wsService.ts";
import { getInventory } from "../../services/inventoryService.ts";
import { createMessage } from "../../services/inboxService.ts";
import { fromStoreItem } from "../../services/itemDataService.ts";
export const loginController: RequestHandler = async (request, response) => { export const loginController: RequestHandler = async (request, response) => {
const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object
@ -76,6 +79,24 @@ export const loginController: RequestHandler = async (request, response) => {
handleNonceInvalidation(account._id.toString()); handleNonceInvalidation(account._id.toString());
// If the client crashed during an endless fissure mission, discharge rewards to an inbox message. (https://www.reddit.com/r/Warframe/comments/5uwwjm/til_if_you_crash_during_a_fissure_you_keep_any/)
const inventory = await getInventory(account._id.toString(), "MissionRelicRewards");
if (inventory.MissionRelicRewards) {
await createMessage(account._id, [
{
sndr: "/Lotus/Language/Bosses/Ordis",
msg: "/Lotus/Language/Menu/VoidProjectionItemsMessage",
sub: "/Lotus/Language/Menu/VoidProjectionItemsSubject",
icon: "/Lotus/Interface/Icons/Npcs/Ordis.png",
countedAtt: inventory.MissionRelicRewards.map(x => ({ ...x, ItemType: fromStoreItem(x.ItemType) })),
attVisualOnly: true,
highPriority: true // TOVERIFY
}
]);
inventory.MissionRelicRewards = undefined;
await inventory.save();
}
response.json(createLoginResponse(myAddress, myUrlBase, account.toJSON(), buildLabel)); response.json(createLoginResponse(myAddress, myUrlBase, account.toJSON(), buildLabel));
}; };

View File

@ -129,14 +129,22 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
res.json(deltas); res.json(deltas);
} else if (missionReport.RewardInfo) { } else if (missionReport.RewardInfo) {
logger.debug(`classic mission completion, sending everything`); 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({ res.json({
InventoryJson: JSON.stringify(inventoryResponse), InventoryJson: JSON.stringify(inventoryResponse),
...deltas ...deltas
} satisfies IMissionInventoryUpdateResponse); } satisfies IMissionInventoryUpdateResponse);
} else { } else {
logger.debug(`no reward info, assuming this wasn't a mission completion and we should just sync inventory`); 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({ res.json({
InventoryJson: JSON.stringify(inventoryResponse) InventoryJson: JSON.stringify(inventoryResponse)
} satisfies IMissionInventoryUpdateResponseBackToDryDock); } satisfies IMissionInventoryUpdateResponseBackToDryDock);

View File

@ -310,6 +310,17 @@ export const nemesisController: RequestHandler = async (req, res) => {
res.json({ res.json({
target: inventory.toJSON().Nemesis target: inventory.toJSON().Nemesis
}); });
} else if ((req.query.mode as string) == "d") {
const inventory = await getInventory(account._id.toString(), "NemesisHistory");
const body = getJSONfromString<IRelinquishAdversariesRequest>(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") { } else if ((req.query.mode as string) == "w") {
const inventory = await getInventory(account._id.toString(), "Nemesis"); const inventory = await getInventory(account._id.toString(), "Nemesis");
//const body = getJSONfromString<INemesisWeakenRequest>(String(req.body)); //const body = getJSONfromString<INemesisWeakenRequest>(String(req.body));
@ -447,3 +458,7 @@ const consumeModCharge = (
response.UpgradeNew.push(true); response.UpgradeNew.push(true);
} }
}; };
interface IRelinquishAdversariesRequest {
nemesisFingerprints: (bigint | number)[];
}

View File

@ -10,6 +10,7 @@ import {
import { createMessage } from "../../services/inboxService.ts"; import { createMessage } from "../../services/inboxService.ts";
import { getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "../../services/inventoryService.ts";
import { getAccountForRequest, getSuffixedName } from "../../services/loginService.ts"; import { getAccountForRequest, getSuffixedName } from "../../services/loginService.ts";
import { sendWsBroadcastTo } from "../../services/wsService.ts";
import { GuildPermission } from "../../types/guildTypes.ts"; import { GuildPermission } from "../../types/guildTypes.ts";
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
@ -85,6 +86,7 @@ export const removeFromGuildController: RequestHandler = async (req, res) => {
ItemToRemove: "/Lotus/Types/Keys/DojoKey", ItemToRemove: "/Lotus/Types/Keys/DojoKey",
RecipeToRemove: "/Lotus/Types/Keys/DojoKeyBlueprint" RecipeToRemove: "/Lotus/Types/Keys/DojoKeyBlueprint"
}); });
sendWsBroadcastTo(payload.userId, { update_inventory: true });
}; };
interface IRemoveFromGuildRequest { interface IRemoveFromGuildRequest {

View File

@ -77,6 +77,9 @@ export const sellController: RequestHandler = async (req, res) => {
requiredFields.add("CrewShipSalvagedWeaponSkins"); requiredFields.add("CrewShipSalvagedWeaponSkins");
} }
} }
if (payload.Items.WeaponSkins) {
requiredFields.add("WeaponSkins");
}
const inventory = await getInventory(accountId, Array.from(requiredFields).join(" ")); const inventory = await getInventory(accountId, Array.from(requiredFields).join(" "));
// Give currency // Give currency
@ -302,6 +305,11 @@ export const sellController: RequestHandler = async (req, res) => {
addFusionTreasures(inventory, [parseFusionTreasure(sellItem.String, sellItem.Count * -1)]); 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(); await inventory.save();
res.json({ res.json({
@ -335,6 +343,7 @@ interface ISellRequest {
CrewShipWeapons?: ISellItem[]; CrewShipWeapons?: ISellItem[];
CrewShipWeaponSkins?: ISellItem[]; CrewShipWeaponSkins?: ISellItem[];
FusionTreasures?: ISellItem[]; FusionTreasures?: ISellItem[];
WeaponSkins?: ISellItem[];
}; };
SellPrice: number; SellPrice: number;
SellCurrency: SellCurrency:

View File

@ -57,7 +57,7 @@ export const setGuildMotdController: RequestHandler = async (req, res) => {
await guild.save(); await guild.save();
} }
if (!account.BuildLabel || version_compare(account.BuildLabel, "2020.03.24.20.24") > 0) { if (!account.BuildLabel || version_compare(account.BuildLabel, "2020.11.04.18.58") > 0) {
res.json({ IsLongMOTD, MOTD }); res.json({ IsLongMOTD, MOTD });
} else { } else {
res.send(MOTD).end(); res.send(MOTD).end();

View File

@ -13,6 +13,7 @@ import { Types } from "mongoose";
import { ExportDojoRecipes } from "warframe-public-export-plus"; import { ExportDojoRecipes } from "warframe-public-export-plus";
import { getAccountForRequest } from "../../services/loginService.ts"; import { getAccountForRequest } from "../../services/loginService.ts";
import { getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "../../services/inventoryService.ts";
import { fromOid } from "../../helpers/inventoryHelpers.ts";
interface IStartDojoRecipeRequest { interface IStartDojoRecipeRequest {
PlacedComponent: IDojoComponentClient; PlacedComponent: IDojoComponentClient;
@ -50,7 +51,7 @@ export const startDojoRecipeController: RequestHandler = async (req, res) => {
_id: componentId, _id: componentId,
pf: request.PlacedComponent.pf, pf: request.PlacedComponent.pf,
ppf: request.PlacedComponent.ppf, ppf: request.PlacedComponent.ppf,
pi: new Types.ObjectId(request.PlacedComponent.pi!.$oid), pi: new Types.ObjectId(fromOid(request.PlacedComponent.pi!)),
op: request.PlacedComponent.op, op: request.PlacedComponent.op,
pp: request.PlacedComponent.pp, pp: request.PlacedComponent.pp,
DecoCapacity: room?.decoCapacity DecoCapacity: room?.decoCapacity

View File

@ -5,6 +5,7 @@ import type { IUpdateQuestRequest } from "../../services/questService.ts";
import { updateQuestKey } from "../../services/questService.ts"; import { updateQuestKey } from "../../services/questService.ts";
import { getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "../../services/inventoryService.ts";
import type { IInventoryChanges } from "../../types/purchaseTypes.ts"; import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
import { sendWsBroadcastTo } from "../../services/wsService.ts";
export const updateQuestController: RequestHandler = async (req, res) => { export const updateQuestController: RequestHandler = async (req, res) => {
const accountId = parseString(req.query.accountId); const accountId = parseString(req.query.accountId);
@ -29,4 +30,5 @@ export const updateQuestController: RequestHandler = async (req, res) => {
await inventory.save(); await inventory.save();
res.send(updateQuestResponse); res.send(updateQuestResponse);
sendWsBroadcastTo(accountId, { update_inventory: true });
}; };

View File

@ -1,5 +1,5 @@
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountForRequest } from "../../services/loginService.ts";
import { Account, Ignore } from "../../models/loginModel.ts"; import { Account, Ignore } from "../../models/loginModel.ts";
import { Inbox } from "../../models/inboxModel.ts"; import { Inbox } from "../../models/inboxModel.ts";
import { Inventory } from "../../models/inventoryModels/inventoryModel.ts"; import { Inventory } from "../../models/inventoryModels/inventoryModel.ts";
@ -12,33 +12,44 @@ import { Leaderboard } from "../../models/leaderboardModel.ts";
import { deleteGuild } from "../../services/guildService.ts"; import { deleteGuild } from "../../services/guildService.ts";
import { Friendship } from "../../models/friendModel.ts"; import { Friendship } from "../../models/friendModel.ts";
import { sendWsBroadcastTo } from "../../services/wsService.ts"; import { sendWsBroadcastTo } from "../../services/wsService.ts";
import { config } from "../../services/configService.ts";
import { saveConfig } from "../../services/configWriterService.ts";
export const deleteAccountController: RequestHandler = async (req, res) => { export const deleteAccountController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const account = await getAccountForRequest(req);
// If this account is an admin, remove it from administratorNames
if (config.administratorNames) {
const adminIndex = config.administratorNames.indexOf(account.DisplayName);
if (adminIndex != -1) {
config.administratorNames.splice(adminIndex, 1);
await saveConfig();
}
}
// If account is the founding warlord of a guild, delete that guild as well. // If account is the founding warlord of a guild, delete that guild as well.
const guildMember = await GuildMember.findOne({ accountId, rank: 0, status: 0 }); const guildMember = await GuildMember.findOne({ accountId: account._id, rank: 0, status: 0 });
if (guildMember) { if (guildMember) {
await deleteGuild(guildMember.guildId); await deleteGuild(guildMember.guildId);
} }
await Promise.all([ await Promise.all([
Account.deleteOne({ _id: accountId }), Account.deleteOne({ _id: account._id }),
Friendship.deleteMany({ owner: accountId }), Friendship.deleteMany({ owner: account._id }),
Friendship.deleteMany({ friend: accountId }), Friendship.deleteMany({ friend: account._id }),
GuildMember.deleteMany({ accountId: accountId }), GuildMember.deleteMany({ accountId: account._id }),
Ignore.deleteMany({ ignorer: accountId }), Ignore.deleteMany({ ignorer: account._id }),
Ignore.deleteMany({ ignoree: accountId }), Ignore.deleteMany({ ignoree: account._id }),
Inbox.deleteMany({ ownerId: accountId }), Inbox.deleteMany({ ownerId: account._id }),
Inventory.deleteOne({ accountOwnerId: accountId }), Inventory.deleteOne({ accountOwnerId: account._id }),
Leaderboard.deleteMany({ ownerId: accountId }), Leaderboard.deleteMany({ ownerId: account._id }),
Loadout.deleteOne({ loadoutOwnerId: accountId }), Loadout.deleteOne({ loadoutOwnerId: account._id }),
PersonalRooms.deleteOne({ personalRoomsOwnerId: accountId }), PersonalRooms.deleteOne({ personalRoomsOwnerId: account._id }),
Ship.deleteMany({ ShipOwnerId: accountId }), Ship.deleteMany({ ShipOwnerId: account._id }),
Stats.deleteOne({ accountOwnerId: accountId }) Stats.deleteOne({ accountOwnerId: account._id })
]); ]);
sendWsBroadcastTo(accountId, { logged_out: true }); sendWsBroadcastTo(account._id.toString(), { logged_out: true });
res.end(); res.end();
}; };

View File

@ -66,6 +66,7 @@ interface ItemLists {
VaultDecoRecipes: ListedItem[]; VaultDecoRecipes: ListedItem[];
FlavourItems: ListedItem[]; FlavourItems: ListedItem[];
ShipDecorations: ListedItem[]; ShipDecorations: ListedItem[];
WeaponSkins: ListedItem[];
//circuitGameModes: ListedItem[]; //circuitGameModes: ListedItem[];
} }
@ -107,7 +108,8 @@ const getItemListsController: RequestHandler = (req, response) => {
TechProjects: [], TechProjects: [],
VaultDecoRecipes: [], VaultDecoRecipes: [],
FlavourItems: [], FlavourItems: [],
ShipDecorations: [] ShipDecorations: [],
WeaponSkins: []
/*circuitGameModes: [ /*circuitGameModes: [
{ {
uniqueName: "Survival", uniqueName: "Survival",
@ -298,10 +300,18 @@ const getItemListsController: RequestHandler = (req, response) => {
}); });
} }
for (const [uniqueName, item] of Object.entries(ExportCustoms)) { for (const [uniqueName, item] of Object.entries(ExportCustoms)) {
res.miscitems.push({ if (
uniqueName: uniqueName, item.productCategory == "WeaponSkins" &&
name: getString(item.name, lang) !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)) { for (const [uniqueName, upgrade] of Object.entries(ExportUpgrades)) {

View File

@ -115,7 +115,7 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
if (stage > 0) { if (stage > 0) {
await giveKeyChainStageTriggered(inventory, { await giveKeyChainStageTriggered(inventory, {
KeyChain: questKey.ItemType, KeyChain: questKey.ItemType,
ChainStage: stage ChainStage: stage - 1
}); });
} }
} }

View File

@ -1,6 +1,6 @@
import { getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "../../services/inventoryService.ts";
import { getAccountIdForRequest } from "../../services/loginService.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 { IAccountCheats } from "../../types/inventoryTypes/inventoryTypes.ts";
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
import { logger } from "../../utils/logger.ts"; import { logger } from "../../utils/logger.ts";
@ -20,6 +20,8 @@ export const setAccountCheatController: RequestHandler = async (req, res) => {
res.end(); res.end();
if (["infiniteCredits", "infinitePlatinum", "infiniteEndo", "infiniteRegalAya"].indexOf(payload.key) != -1) { if (["infiniteCredits", "infinitePlatinum", "infiniteEndo", "infiniteRegalAya"].indexOf(payload.key) != -1) {
sendWsBroadcastTo(accountId, { update_inventory: true, sync_inventory: true }); sendWsBroadcastTo(accountId, { update_inventory: true, sync_inventory: true });
} else {
sendWsBroadcastEx({ update_inventory: true }, accountId, parseInt(String(req.query.wsid)));
} }
}; };

View File

@ -20,13 +20,14 @@ import type {
} from "../../types/inventoryTypes/inventoryTypes.ts"; } from "../../types/inventoryTypes/inventoryTypes.ts";
import { LoadoutIndex } from "../../types/inventoryTypes/inventoryTypes.ts"; import { LoadoutIndex } from "../../types/inventoryTypes/inventoryTypes.ts";
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
import { catBreadHash, getJSONfromString } from "../../helpers/stringHelpers.ts"; import { getJSONfromString } from "../../helpers/stringHelpers.ts";
import { ExportCustoms, ExportDojoRecipes } from "warframe-public-export-plus"; import { ExportDojoRecipes } from "warframe-public-export-plus";
import type { IStatsClient } from "../../types/statTypes.ts"; import type { IStatsClient } from "../../types/statTypes.ts";
import { toStoreItem } from "../../services/itemDataService.ts"; import { toStoreItem } from "../../services/itemDataService.ts";
import type { FlattenMaps } from "mongoose"; import type { FlattenMaps } from "mongoose";
import type { IEquipmentClient } from "../../types/equipmentTypes.ts"; import type { IEquipmentClient } from "../../types/equipmentTypes.ts";
import type { ILoadoutConfigClient } from "../../types/saveLoadoutTypes.ts"; import type { ILoadoutConfigClient } from "../../types/saveLoadoutTypes.ts";
import { skinLookupTable } from "../../helpers/skinLookupTable.ts";
const getProfileViewingDataByPlayerIdImpl = async (playerId: string): Promise<IProfileViewingData | undefined> => { const getProfileViewingDataByPlayerIdImpl = async (playerId: string): Promise<IProfileViewingData | undefined> => {
const account = await Account.findById(playerId, "DisplayName"); const account = await Account.findById(playerId, "DisplayName");
@ -261,8 +262,6 @@ interface IXPComponentClient {
locTags?: Record<string, string>; locTags?: Record<string, string>;
} }
let skinLookupTable: Record<number, string> | undefined;
const resolveAndCollectSkins = ( const resolveAndCollectSkins = (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
skins: Set<string>, skins: Set<string>,
@ -274,12 +273,6 @@ const resolveAndCollectSkins = (
// Resolve oids to type names // Resolve oids to type names
if (config.Skins[i].length == 24) { if (config.Skins[i].length == 24) {
if (config.Skins[i].substring(0, 16) == "ca70ca70ca70ca70") { 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)]; config.Skins[i] = skinLookupTable[parseInt(config.Skins[i].substring(16), 16)];
} else { } else {
const skinItem = inventory.WeaponSkins.id(config.Skins[i]); const skinItem = inventory.WeaponSkins.id(config.Skins[i]);

View File

@ -0,0 +1,8 @@
import { ExportCustoms } from "warframe-public-export-plus";
import { catBreadHash } from "./stringHelpers.ts";
export const skinLookupTable: Record<number, string> = {};
for (const key of Object.keys(ExportCustoms)) {
skinLookupTable[catBreadHash(key)] = key;
}

View File

@ -21,7 +21,6 @@ export interface IConfig {
administratorNames?: string[]; administratorNames?: string[];
autoCreateAccount?: boolean; autoCreateAccount?: boolean;
skipTutorial?: boolean; skipTutorial?: boolean;
unlockAllSkins?: boolean;
fullyStockedVendors?: boolean; fullyStockedVendors?: boolean;
skipClanKeyCrafting?: boolean; skipClanKeyCrafting?: boolean;
spoofMasteryRank?: number; spoofMasteryRank?: number;
@ -48,6 +47,7 @@ export interface IConfig {
anniversary?: number; anniversary?: number;
hallowedNightmares?: boolean; hallowedNightmares?: boolean;
hallowedNightmaresRewardsOverride?: number; hallowedNightmaresRewardsOverride?: number;
naberusNightsOverride?: boolean;
proxyRebellion?: boolean; proxyRebellion?: boolean;
proxyRebellionRewardsOverride?: number; proxyRebellionRewardsOverride?: number;
galleonOfGhouls?: number; galleonOfGhouls?: number;
@ -128,7 +128,8 @@ export const configRemovedOptionsKeys = [
"fastClanAscension", "fastClanAscension",
"unlockAllFlavourItems", "unlockAllFlavourItems",
"unlockAllShipDecorations", "unlockAllShipDecorations",
"unlockAllDecoRecipes" "unlockAllDecoRecipes",
"unlockAllSkins"
]; ];
export const configPath = path.join(repoDir, args.configPath ?? "config.json"); export const configPath = path.join(repoDir, args.configPath ?? "config.json");

View File

@ -22,7 +22,7 @@ import type {
ITechProjectDatabase ITechProjectDatabase
} from "../types/guildTypes.ts"; } from "../types/guildTypes.ts";
import { GuildPermission } 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 { Types } from "mongoose";
import type { IDojoBuild, IDojoResearch } from "warframe-public-export-plus"; import type { IDojoBuild, IDojoResearch } from "warframe-public-export-plus";
import { ExportDojoRecipes, ExportResources } from "warframe-public-export-plus"; import { ExportDojoRecipes, ExportResources } from "warframe-public-export-plus";
@ -68,9 +68,15 @@ export const getGuildClient = async (
let missingEntry = true; let missingEntry = true;
const dataFillInPromises: Promise<void>[] = []; const dataFillInPromises: Promise<void>[] = [];
for (const guildMember of guildMembers) { 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 = { const member: IGuildMemberClient = {
_id: toOid2(guildMember.accountId, account.BuildLabel), _id: toOid2(guildMember.accountId, account.BuildLabel),
Rank: guildMember.rank, Rank: guildMember.rank + rankBase,
Status: guildMember.status, Status: guildMember.status,
Note: guildMember.RequestMsg, Note: guildMember.RequestMsg,
RequestExpiry: guildMember.RequestExpiry ? toMongoDate(guildMember.RequestExpiry) : undefined RequestExpiry: guildMember.RequestExpiry ? toMongoDate(guildMember.RequestExpiry) : undefined

View File

@ -6,6 +6,7 @@ import type {
} from "../types/inventoryTypes/commonInventoryTypes.ts"; } from "../types/inventoryTypes/commonInventoryTypes.ts";
import type { IMongoDate } from "../types/commonTypes.ts"; import type { IMongoDate } from "../types/commonTypes.ts";
import type { import type {
IBooster,
IDialogueClient, IDialogueClient,
IDialogueDatabase, IDialogueDatabase,
IDialogueHistoryClient, IDialogueHistoryClient,
@ -463,6 +464,9 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
if (client.Accolades !== undefined) { if (client.Accolades !== undefined) {
db.Accolades = client.Accolades; db.Accolades = client.Accolades;
} }
if (client.Boosters !== undefined) {
replaceArray<IBooster>(db.Boosters, client.Boosters);
}
}; };
export const importLoadOutConfig = (client: ILoadoutConfigClient): ILoadoutConfigDatabase => { export const importLoadOutConfig = (client: ILoadoutConfigClient): ILoadoutConfigDatabase => {

View File

@ -1,11 +1,6 @@
import type { IMessageDatabase } from "../models/inboxModel.ts"; import type { IMessageDatabase } from "../models/inboxModel.ts";
import { Inbox } from "../models/inboxModel.ts"; import { Inbox } from "../models/inboxModel.ts";
import { getAccountForRequest } from "./loginService.ts"; import type { HydratedDocument, Types } from "mongoose";
import type { HydratedDocument } from "mongoose";
import { Types } from "mongoose";
import type { Request } from "express";
import { unixTimesInMs } from "../constants/timeConstants.ts";
import { config } from "./configService.ts";
export const getAllMessagesSorted = async (accountId: string): Promise<HydratedDocument<IMessageDatabase>[]> => { export const getAllMessagesSorted = async (accountId: string): Promise<HydratedDocument<IMessageDatabase>[]> => {
const inbox = await Inbox.find({ ownerId: accountId }).sort({ date: -1 }); const inbox = await Inbox.find({ ownerId: accountId }).sort({ date: -1 });
@ -29,117 +24,8 @@ export const deleteAllMessagesRead = async (accountId: string): Promise<void> =>
await Inbox.deleteMany({ ownerId: accountId, r: true }); await Inbox.deleteMany({ ownerId: accountId, r: true });
}; };
export const createNewEventMessages = async (req: Request): Promise<void> => { export const deleteAllMessagesReadNonCin = async (accountId: string): Promise<void> => {
const account = await getAccountForRequest(req); await Inbox.deleteMany({ ownerId: accountId, r: true, cinematic: null });
const newEventMessages: IMessageCreationTemplate[] = [];
// Baro
const baroIndex = Math.trunc((Date.now() - 910800000) / (unixTimesInMs.day * 14));
const baroStart = baroIndex * (unixTimesInMs.day * 14) + 910800000;
const baroActualStart = baroStart + unixTimesInMs.day * (config.worldState?.baroAlwaysAvailable ? 0 : 12);
if (Date.now() >= baroActualStart && account.LatestEventMessageDate.getTime() < baroActualStart) {
newEventMessages.push({
sndr: "/Lotus/Language/G1Quests/VoidTraderName",
sub: "/Lotus/Language/CommunityMessages/VoidTraderAppearanceTitle",
msg: "/Lotus/Language/CommunityMessages/VoidTraderAppearanceMessage",
icon: "/Lotus/Interface/Icons/Npcs/BaroKiTeerPortrait.png",
startDate: new Date(baroActualStart),
endDate: new Date(baroStart + unixTimesInMs.day * 14),
CrossPlatform: true,
arg: [
{
Key: "NODE_NAME",
Tag: ["EarthHUB", "MercuryHUB", "SaturnHUB", "PlutoHUB"][baroIndex % 4]
}
],
date: new Date(baroActualStart)
});
}
// BUG: Deleting the inbox message manually means it'll just be automatically re-created. This is because we don't use startDate/endDate for these config-toggled events.
const promises = [];
if (config.worldState?.creditBoost) {
promises.push(
(async (): Promise<void> => {
if (!(await Inbox.exists({ ownerId: account._id, globaUpgradeId: "5b23106f283a555109666672" }))) {
newEventMessages.push({
globaUpgradeId: new Types.ObjectId("5b23106f283a555109666672"),
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sub: "/Lotus/Language/Items/EventDoubleCreditsName",
msg: "/Lotus/Language/Items/EventDoubleCreditsDesc",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
startDate: new Date(),
CrossPlatform: true
});
}
})()
);
}
if (config.worldState?.affinityBoost) {
promises.push(
(async (): Promise<void> => {
if (!(await Inbox.exists({ ownerId: account._id, globaUpgradeId: "5b23106f283a555109666673" }))) {
newEventMessages.push({
globaUpgradeId: new Types.ObjectId("5b23106f283a555109666673"),
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sub: "/Lotus/Language/Items/EventDoubleAffinityName",
msg: "/Lotus/Language/Items/EventDoubleAffinityDesc",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
startDate: new Date(),
CrossPlatform: true
});
}
})()
);
}
if (config.worldState?.resourceBoost) {
promises.push(
(async (): Promise<void> => {
if (!(await Inbox.exists({ ownerId: account._id, globaUpgradeId: "5b23106f283a555109666674" }))) {
newEventMessages.push({
globaUpgradeId: new Types.ObjectId("5b23106f283a555109666674"),
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sub: "/Lotus/Language/Items/EventDoubleResourceName",
msg: "/Lotus/Language/Items/EventDoubleResourceDesc",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
startDate: new Date(),
CrossPlatform: true
});
}
})()
);
}
if (config.worldState?.galleonOfGhouls) {
promises.push(
(async (): Promise<void> => {
if (!(await Inbox.exists({ ownerId: account._id, goalTag: "GalleonRobbery" }))) {
newEventMessages.push({
sndr: "/Lotus/Language/Bosses/BossCouncilorVayHek",
sub: "/Lotus/Language/Events/GalleonRobberyIntroMsgTitle",
msg: "/Lotus/Language/Events/GalleonRobberyIntroMsgDesc",
icon: "/Lotus/Interface/Icons/Npcs/VayHekPortrait.png",
transmission: "/Lotus/Sounds/Dialog/GalleonOfGhouls/DGhoulsWeekOneInbox0010VayHek",
att: ["/Lotus/Upgrades/Skins/Events/OgrisOldSchool"],
startDate: new Date(),
goalTag: "GalleonRobbery"
});
}
})()
);
}
await Promise.all(promises);
if (newEventMessages.length === 0) {
return;
}
await createMessage(account._id, newEventMessages);
const latestEventMessage = newEventMessages.reduce((prev, current) =>
prev.startDate! > current.startDate! ? prev : current
);
account.LatestEventMessageDate = new Date(latestEventMessage.startDate!);
await account.save();
}; };
export const createMessage = async ( export const createMessage = async (

View File

@ -92,6 +92,7 @@ import type {
} from "../types/equipmentTypes.ts"; } from "../types/equipmentTypes.ts";
import { EquipmentFeatures, Status } from "../types/equipmentTypes.ts"; import { EquipmentFeatures, Status } from "../types/equipmentTypes.ts";
import type { ITypeCount } from "../types/commonTypes.ts"; import type { ITypeCount } from "../types/commonTypes.ts";
import { skinLookupTable } from "../helpers/skinLookupTable.ts";
export const createInventory = async ( export const createInventory = async (
accountOwnerId: Types.ObjectId, accountOwnerId: Types.ObjectId,
@ -2254,7 +2255,7 @@ export const setupKahlSyndicate = (inventory: TInventoryDatabaseDocument): void
}); });
}; };
export const cleanupInventory = (inventory: TInventoryDatabaseDocument): void => { export const cleanupInventory = async (inventory: TInventoryDatabaseDocument): Promise<void> => {
inventory.CurrentLoadOutIds = inventory.CurrentLoadOutIds.map(fromDbOid); inventory.CurrentLoadOutIds = inventory.CurrentLoadOutIds.map(fromDbOid);
let index = inventory.MiscItems.findIndex(x => x.ItemType == ""); let index = inventory.MiscItems.findIndex(x => x.ItemType == "");
@ -2308,6 +2309,86 @@ export const cleanupInventory = (inventory: TInventoryDatabaseDocument): void =>
logger.debug(`removed ModularParts from ${numFixed} non-modular items`); logger.debug(`removed ModularParts from ${numFixed} non-modular items`);
} }
} }
{
const weaponMap = new Map<string, string>();
for (const skin of inventory.WeaponSkins) {
weaponMap.set(skin.ItemType, skin._id.toString());
}
const itemsToAdd = new Set<string>();
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<string, string>, itemsToAdd: Set<string>): void => {
for (const skinId of skins) {
if (skinId.startsWith("ca70ca70ca70ca70")) {
const typeName = skinLookupTable[parseInt(skinId.slice(16), 16)];
if (!weaponMap.has(typeName)) itemsToAdd.add(typeName);
}
}
};
const replaceSkinIds = (skins: string[], weaponMap: Map<string, string>): void => {
for (let i = 0; i < skins.length; i++) {
const skinId = skins[i];
if (skinId.startsWith("ca70ca70ca70ca70")) {
const inventoryId = weaponMap.get(skinLookupTable[parseInt(skinId.slice(16), 16)]);
if (inventoryId) skins[i] = inventoryId;
}
}
}; };
export const getDialogue = (inventory: TInventoryDatabaseDocument, dialogueName: string): IDialogueDatabase => { export const getDialogue = (inventory: TInventoryDatabaseDocument, dialogueName: string): IDialogueDatabase => {

View File

@ -309,9 +309,6 @@ export const addMissionInventoryUpdates = async (
} }
break; break;
} }
case "Missions":
addMissionComplete(inventory, value);
break;
case "LastRegionPlayed": case "LastRegionPlayed":
if (!(config.unfaithfulBugFixes?.ignore1999LastRegionPlayed && value === "1999MapName")) { if (!(config.unfaithfulBugFixes?.ignore1999LastRegionPlayed && value === "1999MapName")) {
inventory.LastRegionPlayed = value; inventory.LastRegionPlayed = value;
@ -1208,6 +1205,9 @@ export const addMissionRewards = async (
if (missions && missions.Tag in ExportRegions) { if (missions && missions.Tag in ExportRegions) {
const node = ExportRegions[missions.Tag]; const node = ExportRegions[missions.Tag];
// cannot add this with normal updates because { Tier: 1 } would mark the SP node as completed even on a failure
addMissionComplete(inventory, missions);
//node based credit rewards for mission completion //node based credit rewards for mission completion
if (isEligibleForCreditReward(rewardInfo, missions, node)) { if (isEligibleForCreditReward(rewardInfo, missions, node)) {
const levelCreditReward = getLevelCreditRewards(node); const levelCreditReward = getLevelCreditRewards(node);
@ -2483,95 +2483,95 @@ const goalMessagesByKey: Record<string, { sndr: string; msg: string; sub: string
icon: "/Lotus/Interface/Icons/Npcs/Seasonal/NoraNight.png" icon: "/Lotus/Interface/Icons/Npcs/Seasonal/NoraNight.png"
}, },
"/Lotus/Types/Keys/LanternEndlessEventKeyA": { "/Lotus/Types/Keys/LanternEndlessEventKeyA": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/GenericEventRewardMsgDesc", msg: "/Lotus/Language/G1Quests/GenericEventRewardMsgDesc",
sub: "/Lotus/Language/G1Quests/GenericTacAlertRewardMsgTitle", sub: "/Lotus/Language/G1Quests/GenericTacAlertRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png" icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
}, },
"/Lotus/Types/Keys/LanternEndlessEventKeyB": { "/Lotus/Types/Keys/LanternEndlessEventKeyB": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/GenericEventRewardMsgDesc", msg: "/Lotus/Language/G1Quests/GenericEventRewardMsgDesc",
sub: "/Lotus/Language/G1Quests/GenericTacAlertRewardMsgTitle", sub: "/Lotus/Language/G1Quests/GenericTacAlertRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png" icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
}, },
"/Lotus/Types/Keys/LanternEndlessEventKeyD": { "/Lotus/Types/Keys/LanternEndlessEventKeyD": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/GenericEventRewardMsgDesc", msg: "/Lotus/Language/G1Quests/GenericEventRewardMsgDesc",
sub: "/Lotus/Language/G1Quests/GenericTacAlertRewardMsgTitle", sub: "/Lotus/Language/G1Quests/GenericTacAlertRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png" icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
}, },
"/Lotus/Types/Keys/LanternEndlessEventKeyC": { "/Lotus/Types/Keys/LanternEndlessEventKeyC": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/GenericEventRewardMsgDesc", msg: "/Lotus/Language/G1Quests/GenericEventRewardMsgDesc",
sub: "/Lotus/Language/G1Quests/GenericTacAlertRewardMsgTitle", sub: "/Lotus/Language/G1Quests/GenericTacAlertRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png" icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
}, },
"/Lotus/Types/Keys/TacAlertKeyHalloween": { "/Lotus/Types/Keys/TacAlertKeyHalloween": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsBonusBody", msg: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsBonusBody",
sub: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsBonusTitle", sub: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsBonusTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png" icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
}, },
"/Lotus/Types/Keys/TacAlertKeyHalloweenBonus": { "/Lotus/Types/Keys/TacAlertKeyHalloweenBonus": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsBody", msg: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsBody",
sub: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsTitle", sub: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png" icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
}, },
"/Lotus/Types/Keys/TacAlertKeyHalloweenTimeAttack": { "/Lotus/Types/Keys/TacAlertKeyHalloweenTimeAttack": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsBody", msg: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsBody",
sub: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsTitle", sub: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png" icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
}, },
"/Lotus/Types/Keys/TacAlertKeyProxyRebellionOne": { "/Lotus/Types/Keys/TacAlertKeyProxyRebellionOne": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/RazorbackArmadaRewardBody", msg: "/Lotus/Language/G1Quests/RazorbackArmadaRewardBody",
sub: "/Lotus/Language/G1Quests/GenericTacAlertSmallRewardMsgTitle", sub: "/Lotus/Language/G1Quests/GenericTacAlertSmallRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png", icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["CREDIT_REWARD"] arg: ["CREDIT_REWARD"]
}, },
"/Lotus/Types/Keys/TacAlertKeyProxyRebellionTwo": { "/Lotus/Types/Keys/TacAlertKeyProxyRebellionTwo": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/RazorbackArmadaRewardBody", msg: "/Lotus/Language/G1Quests/RazorbackArmadaRewardBody",
sub: "/Lotus/Language/G1Quests/GenericTacAlertSmallRewardMsgTitle", sub: "/Lotus/Language/G1Quests/GenericTacAlertSmallRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png", icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["CREDIT_REWARD"] arg: ["CREDIT_REWARD"]
}, },
"/Lotus/Types/Keys/TacAlertKeyProxyRebellionThree": { "/Lotus/Types/Keys/TacAlertKeyProxyRebellionThree": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/RazorbackArmadaRewardBody", msg: "/Lotus/Language/G1Quests/RazorbackArmadaRewardBody",
sub: "/Lotus/Language/G1Quests/GenericTacAlertSmallRewardMsgTitle", sub: "/Lotus/Language/G1Quests/GenericTacAlertSmallRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png", icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["CREDIT_REWARD"] arg: ["CREDIT_REWARD"]
}, },
"/Lotus/Types/Keys/TacAlertKeyProxyRebellionFour": { "/Lotus/Types/Keys/TacAlertKeyProxyRebellionFour": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/GenericTacAlertBadgeRewardMsgDesc", msg: "/Lotus/Language/G1Quests/GenericTacAlertBadgeRewardMsgDesc",
sub: "/Lotus/Language/G1Quests/GenericTacAlertBadgeRewardMsgTitle", sub: "/Lotus/Language/G1Quests/GenericTacAlertBadgeRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png" icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png"
}, },
"/Lotus/Types/Keys/TacAlertKeyProjectNightwatchEasy": { "/Lotus/Types/Keys/TacAlertKeyProjectNightwatchEasy": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/ProjectNightwatchRewardMsgA", msg: "/Lotus/Language/G1Quests/ProjectNightwatchRewardMsgA",
sub: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionOneTitle", sub: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionOneTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png", icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["CREDIT_REWARD"] arg: ["CREDIT_REWARD"]
}, },
"/Lotus/Types/Keys/TacAlertKeyProjectNightwatch": { "/Lotus/Types/Keys/TacAlertKeyProjectNightwatch": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionRewardBody", msg: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionRewardBody",
sub: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionTwoTitle", sub: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionTwoTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png" icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png"
}, },
"/Lotus/Types/Keys/TacAlertKeyProjectNightwatchHard": { "/Lotus/Types/Keys/TacAlertKeyProjectNightwatchHard": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionRewardBody", msg: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionRewardBody",
sub: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionThreeTitle", sub: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionThreeTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png" icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png"
}, },
"/Lotus/Types/Keys/TacAlertKeyProjectNightwatchBonus": { "/Lotus/Types/Keys/TacAlertKeyProjectNightwatchBonus": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionRewardBody", msg: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionRewardBody",
sub: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionFourTitle", sub: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionFourTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png" icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png"
@ -2607,140 +2607,140 @@ const goalMessagesByKey: Record<string, { sndr: string; msg: string; sub: string
icon: "/Lotus/Interface/Icons/Npcs/Entrati/Father.png" icon: "/Lotus/Interface/Icons/Npcs/Entrati/Father.png"
}, },
"/Lotus/Types/Keys/TacAlertKeyAnniversary2019E": { "/Lotus/Types/Keys/TacAlertKeyAnniversary2019E": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgB", msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgB",
sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleB", sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleB",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png", icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"] arg: ["PLAYER_NAME"]
}, },
"/Lotus/Types/Keys/TacAlertKeyAnniversary2020F": { "/Lotus/Types/Keys/TacAlertKeyAnniversary2020F": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgC", msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgC",
sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleB", sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleB",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png", icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"] arg: ["PLAYER_NAME"]
}, },
"/Lotus/Types/Keys/TacAlertKeyAnniversary2024ChallengeModeA": { "/Lotus/Types/Keys/TacAlertKeyAnniversary2024ChallengeModeA": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgD", msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgD",
sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleD", sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleD",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png", icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"] arg: ["PLAYER_NAME"]
}, },
"/Lotus/Types/Keys/TacAlertKeyAnniversary2017C": { "/Lotus/Types/Keys/TacAlertKeyAnniversary2017C": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2019RewardMsgC", msg: "/Lotus/Language/Messages/Anniversary2019RewardMsgC",
sub: "/Lotus/Language/Messages/Anniversary2019MissionTitleC", sub: "/Lotus/Language/Messages/Anniversary2019MissionTitleC",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png", icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"] arg: ["PLAYER_NAME"]
}, },
"/Lotus/Types/Keys/TacAlertKeyAnniversary2020H": { "/Lotus/Types/Keys/TacAlertKeyAnniversary2020H": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2020RewardMsgH", msg: "/Lotus/Language/Messages/Anniversary2020RewardMsgH",
sub: "/Lotus/Language/Messages/Anniversary2020MissionTitleH", sub: "/Lotus/Language/Messages/Anniversary2020MissionTitleH",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png", icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"] arg: ["PLAYER_NAME"]
}, },
"/Lotus/Types/Keys/TacAlertKeyAnniversary2022J": { "/Lotus/Types/Keys/TacAlertKeyAnniversary2022J": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2022RewardMsgJ", msg: "/Lotus/Language/Messages/Anniversary2022RewardMsgJ",
sub: "/Lotus/Language/Messages/Anniversary2022MissionTitleJ", sub: "/Lotus/Language/Messages/Anniversary2022MissionTitleJ",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png", icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"] arg: ["PLAYER_NAME"]
}, },
"/Lotus/Types/Keys/TacAlertKeyAnniversary2025D": { "/Lotus/Types/Keys/TacAlertKeyAnniversary2025D": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2025RewardMsgB", msg: "/Lotus/Language/Messages/Anniversary2025RewardMsgB",
sub: "/Lotus/Language/Messages/Anniversary2025MissionTitleB", sub: "/Lotus/Language/Messages/Anniversary2025MissionTitleB",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png", icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"] arg: ["PLAYER_NAME"]
}, },
"/Lotus/Types/Keys/TacAlertKeyAnniversary2025ChallengeModeA": { "/Lotus/Types/Keys/TacAlertKeyAnniversary2025ChallengeModeA": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2025RewardMsgC", msg: "/Lotus/Language/Messages/Anniversary2025RewardMsgC",
sub: "/Lotus/Language/Messages/Anniversary2025MissionTitleC", sub: "/Lotus/Language/Messages/Anniversary2025MissionTitleC",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png", icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"] arg: ["PLAYER_NAME"]
}, },
"/Lotus/Types/Keys/TacAlertKeyAnniversary2020G": { "/Lotus/Types/Keys/TacAlertKeyAnniversary2020G": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2020RewardMsgG", msg: "/Lotus/Language/Messages/Anniversary2020RewardMsgG",
sub: "/Lotus/Language/Messages/Anniversary2020MissionTitleG", sub: "/Lotus/Language/Messages/Anniversary2020MissionTitleG",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png", icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"] arg: ["PLAYER_NAME"]
}, },
"/Lotus/Types/Keys/TacAlertKeyAnniversary2017B": { "/Lotus/Types/Keys/TacAlertKeyAnniversary2017B": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2019RewardMsgB", msg: "/Lotus/Language/Messages/Anniversary2019RewardMsgB",
sub: "/Lotus/Language/Messages/Anniversary2019MissionTitleB", sub: "/Lotus/Language/Messages/Anniversary2019MissionTitleB",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png", icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"] arg: ["PLAYER_NAME"]
}, },
"/Lotus/Types/Keys/TacAlertKeyAnniversary2017A": { "/Lotus/Types/Keys/TacAlertKeyAnniversary2017A": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2019RewardMsgA", msg: "/Lotus/Language/Messages/Anniversary2019RewardMsgA",
sub: "/Lotus/Language/Messages/Anniversary2019MissionTitleA", sub: "/Lotus/Language/Messages/Anniversary2019MissionTitleA",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png", icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"] arg: ["PLAYER_NAME"]
}, },
"/Lotus/Types/Keys/TacAlertKeyAnniversary2023K": { "/Lotus/Types/Keys/TacAlertKeyAnniversary2023K": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2025RewardMsgG", msg: "/Lotus/Language/Messages/Anniversary2025RewardMsgG",
sub: "/Lotus/Language/Messages/Anniversary2025MissionTitleG", sub: "/Lotus/Language/Messages/Anniversary2025MissionTitleG",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png", icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"] arg: ["PLAYER_NAME"]
}, },
"/Lotus/Types/Keys/TacAlertKeyAnniversary2025ChallengeModeB": { "/Lotus/Types/Keys/TacAlertKeyAnniversary2025ChallengeModeB": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2025RewardMsgD", msg: "/Lotus/Language/Messages/Anniversary2025RewardMsgD",
sub: "/Lotus/Language/Messages/Anniversary2025MissionTitleD", sub: "/Lotus/Language/Messages/Anniversary2025MissionTitleD",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png", icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"] arg: ["PLAYER_NAME"]
}, },
"/Lotus/Types/Keys/TacAlertKeyAnniversary2025A": { "/Lotus/Types/Keys/TacAlertKeyAnniversary2025A": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2025RewardMsgA", msg: "/Lotus/Language/Messages/Anniversary2025RewardMsgA",
sub: "/Lotus/Language/Messages/Anniversary2025MissionTitleA", sub: "/Lotus/Language/Messages/Anniversary2025MissionTitleA",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png", icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"] arg: ["PLAYER_NAME"]
}, },
"/Lotus/Types/Keys/TacAlertKeyAnniversary2018D": { "/Lotus/Types/Keys/TacAlertKeyAnniversary2018D": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgG", msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgG",
sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleG", sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleG",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png", icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"] arg: ["PLAYER_NAME"]
}, },
"/Lotus/Types/Keys/TacAlertKeyAnniversary2025C": { "/Lotus/Types/Keys/TacAlertKeyAnniversary2025C": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgF", msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgF",
sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleF", sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleF",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png", icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"] arg: ["PLAYER_NAME"]
}, },
"/Lotus/Types/Keys/TacAlertKeyAnniversary2024L": { "/Lotus/Types/Keys/TacAlertKeyAnniversary2024L": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgA", msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgA",
sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleA", sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleA",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png", icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"] arg: ["PLAYER_NAME"]
}, },
"/Lotus/Types/Keys/TacAlertKeyAnniversary2024ChallengeModeB": { "/Lotus/Types/Keys/TacAlertKeyAnniversary2024ChallengeModeB": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgE", msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgE",
sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleE", sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleE",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png", icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"] arg: ["PLAYER_NAME"]
}, },
"/Lotus/Types/Keys/TacAlertKeyAnniversary2021I": { "/Lotus/Types/Keys/TacAlertKeyAnniversary2021I": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgH", msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgH",
sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleH", sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleH",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png", icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"] arg: ["PLAYER_NAME"]
}, },
"/Lotus/Types/Keys/TacAlertKeyAnniversary2025B": { "/Lotus/Types/Keys/TacAlertKeyAnniversary2025B": {
sndr: "/Lotus/Language/Bosses/Lotus", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2025RewardMsgE", msg: "/Lotus/Language/Messages/Anniversary2025RewardMsgE",
sub: "/Lotus/Language/Messages/Anniversary2025MissionTitleE", sub: "/Lotus/Language/Messages/Anniversary2025MissionTitleE",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png", icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",

View File

@ -74,7 +74,7 @@ export const updateQuestStage = (
if (!questStage) { if (!questStage) {
const questStageIndex = const questStageIndex =
quest.Progress.push({ quest.Progress.push({
c: questStageUpdate.c ?? 0, c: questStageUpdate.c ?? -1,
i: questStageUpdate.i ?? false, i: questStageUpdate.i ?? false,
m: questStageUpdate.m ?? false, m: questStageUpdate.m ?? false,
b: questStageUpdate.b ?? [] b: questStageUpdate.b ?? []
@ -331,7 +331,7 @@ export const giveKeyChainMessage = async (
): Promise<void> => { ): Promise<void> => {
const keyChainMessage = getKeyChainMessage(keyChainInfo); const keyChainMessage = getKeyChainMessage(keyChainInfo);
if (questKey.Progress![0].c > 0) { if ((questKey.Progress?.[0]?.c ?? 0) > 0) {
keyChainMessage.att = []; keyChainMessage.att = [];
keyChainMessage.countedAtt = []; keyChainMessage.countedAtt = [];
} }

View File

@ -139,22 +139,16 @@ export const handleInventoryItemConfigChange = async (
case "WeaponSkins": { case "WeaponSkins": {
const itemEntries = equipment as IItemEntry; const itemEntries = equipment as IItemEntry;
for (const [itemId, itemConfigEntries] of Object.entries(itemEntries)) { for (const [itemId, itemConfigEntries] of Object.entries(itemEntries)) {
if (itemId.startsWith("ca70ca70ca70ca70")) { const inventoryItem = inventory.WeaponSkins.id(itemId);
logger.warn( if (!inventoryItem) {
`unlockAllSkins does not work with favoriting items because you don't actually own it` logger.warn(`inventory item WeaponSkins not found with id ${itemId}`);
); continue;
} else { }
const inventoryItem = inventory.WeaponSkins.id(itemId); if ("Favorite" in itemConfigEntries) {
if (!inventoryItem) { inventoryItem.Favorite = itemConfigEntries.Favorite;
logger.warn(`inventory item WeaponSkins not found with id ${itemId}`); }
continue; if ("IsNew" in itemConfigEntries) {
} inventoryItem.IsNew = itemConfigEntries.IsNew;
if ("Favorite" in itemConfigEntries) {
inventoryItem.Favorite = itemConfigEntries.Favorite;
}
if ("IsNew" in itemConfigEntries) {
inventoryItem.IsNew = itemConfigEntries.IsNew;
}
} }
} }
break; break;

View File

@ -280,6 +280,14 @@ export const getSortie = (day: number): ISortie => {
} }
} }
const willHaveAssassination = boss != "SORTIE_BOSS_CORRUPTED_VOR" && rng.randomInt(0, 2) == 2;
if (willHaveAssassination) {
const index = nodes.indexOf(sortieBossNode[boss]);
if (index != -1) {
nodes.splice(index, 1);
}
}
const selectedNodes: ISortieMission[] = []; const selectedNodes: ISortieMission[] = [];
const missionTypes = new Set(); const missionTypes = new Set();
@ -309,7 +317,7 @@ export const getSortie = (day: number): ISortie => {
"SORTIE_MODIFIER_BOW_ONLY" "SORTIE_MODIFIER_BOW_ONLY"
]; ];
if (i == 2 && boss != "SORTIE_BOSS_CORRUPTED_VOR" && rng.randomInt(0, 2) == 2) { if (i == 2 && willHaveAssassination) {
const tileset = sortieTilesets[sortieBossNode[boss] as keyof typeof sortieTilesets] as TSortieTileset; const tileset = sortieTilesets[sortieBossNode[boss] as keyof typeof sortieTilesets] as TSortieTileset;
pushTilesetModifiers(modifiers, tileset); pushTilesetModifiers(modifiers, tileset);
@ -361,7 +369,9 @@ export const getSortie = (day: number): ISortie => {
Activation: { $date: { $numberLong: dayStart.toString() } }, Activation: { $date: { $numberLong: dayStart.toString() } },
Expiry: { $date: { $numberLong: dayEnd.toString() } }, Expiry: { $date: { $numberLong: dayEnd.toString() } },
Reward: "/Lotus/Types/Game/MissionDecks/SortieRewards", Reward: "/Lotus/Types/Game/MissionDecks/SortieRewards",
Seed: seed, Seed: selectedNodes.find(x => x.tileset == "CorpusIcePlanetTileset")
? 2081 // this seed produces 12 zeroes in a row if asked to pick (0, 1); this way the CorpusIcePlanetTileset image is always index 0, the 'correct' choice.
: seed,
Boss: boss, Boss: boss,
Variants: selectedNodes Variants: selectedNodes
}; };
@ -2504,6 +2514,37 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
BonusReward: { items: ["/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem"] } BonusReward: { items: ["/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem"] }
}); });
} }
const isOctober = date.getUTCMonth() == 9; // October = month index 9
if (config.worldState?.naberusNightsOverride ?? isOctober) {
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()
}
},
Count: 0,
Goal: 0,
Success: 0,
Personal: true,
Desc: "/Lotus/Language/Events/HalloweenNaberusName",
ToolTip: "/Lotus/Language/Events/HalloweenNaberusDesc",
Icon: "/Lotus/Interface/Icons/JackOLanternColour.png",
Tag: "DeimosHalloween",
Node: "DeimosHub"
});
}
if (config.worldState?.bellyOfTheBeast) { if (config.worldState?.bellyOfTheBeast) {
worldState.Goals.push({ worldState.Goals.push({
_id: { $oid: "67a5035c2a198564d62e165e" }, _id: { $oid: "67a5035c2a198564d62e165e" },

View File

@ -526,6 +526,24 @@
</div> </div>
</div> </div>
</div> </div>
<div class="row g-3 mb-3">
<div class="col-lg-6">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_weaponSkins"></h5>
<div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('WeaponSkins');return false;">
<input class="form-control" id="acquire-type-WeaponSkins" list="datalist-WeaponSkins" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<div class="overflow-auto">
<table class="table table-hover w-100">
<tbody id="WeaponSkins-list"></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="card"> <div class="card">
<h5 class="card-header" data-loc="general_bulkActions"></h5> <h5 class="card-header" data-loc="general_bulkActions"></h5>
<div class="card-body"> <div class="card-body">
@ -538,6 +556,7 @@
<button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['SentinelWeapons']);" data-loc="inventory_bulkAddSentinelWeapons"></button> <button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['SentinelWeapons']);" data-loc="inventory_bulkAddSentinelWeapons"></button>
<button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['FlavourItems']);" data-loc="inventory_bulkAddFlavourItems"></button> <button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['FlavourItems']);" data-loc="inventory_bulkAddFlavourItems"></button>
<button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['ShipDecorations']);" data-loc="inventory_bulkAddShipDecorations"></button> <button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['ShipDecorations']);" data-loc="inventory_bulkAddShipDecorations"></button>
<button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['WeaponSkins']);" data-loc="inventory_bulkAddWeaponSkins"></button>
<button class="btn btn-primary" onclick="debounce(addMissingEvolutionProgress);" data-loc="inventory_bulkAddEvolutionProgress"></button> <button class="btn btn-primary" onclick="debounce(addMissingEvolutionProgress);" data-loc="inventory_bulkAddEvolutionProgress"></button>
</div> </div>
<div class="mb-2 d-flex flex-wrap gap-2"> <div class="mb-2 d-flex flex-wrap gap-2">
@ -1093,10 +1112,6 @@
<input class="form-check-input" type="checkbox" id="skipTutorial" /> <input class="form-check-input" type="checkbox" id="skipTutorial" />
<label class="form-check-label" for="skipTutorial" data-loc="cheats_skipTutorial"></label> <label class="form-check-label" for="skipTutorial" data-loc="cheats_skipTutorial"></label>
</div> </div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="unlockAllSkins" />
<label class="form-check-label" for="unlockAllSkins" data-loc="cheats_unlockAllSkins"></label>
</div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="fullyStockedVendors" /> <input class="form-check-input" type="checkbox" id="fullyStockedVendors" />
<label class="form-check-label" for="fullyStockedVendors" data-loc="cheats_fullyStockedVendors"></label> <label class="form-check-label" for="fullyStockedVendors" data-loc="cheats_fullyStockedVendors"></label>
@ -1207,6 +1222,14 @@
</select> </select>
</div> </div>
</div> </div>
<div class="form-group mt-2">
<label class="form-label" for="worldState.naberusNightsOverride" data-loc="worldState_naberusNights"></label>
<select class="form-control" id="worldState.naberusNightsOverride" data-default="null">
<option value="null" data-loc="normal"></option>
<option value="true" data-loc="enabled"></option>
<option value="false" data-loc="disabled"></option>
</select>
</div>
<div class="form-group mt-2 d-flex gap-2"> <div class="form-group mt-2 d-flex gap-2">
<div class="flex-fill"> <div class="flex-fill">
<label class="form-label" for="worldState.proxyRebellion" data-loc="worldState_proxyRebellion"></label> <label class="form-label" for="worldState.proxyRebellion" data-loc="worldState_proxyRebellion"></label>
@ -1495,6 +1518,7 @@
<datalist id="datalist-VaultDecoRecipes"></datalist> <datalist id="datalist-VaultDecoRecipes"></datalist>
<datalist id="datalist-FlavourItems"></datalist> <datalist id="datalist-FlavourItems"></datalist>
<datalist id="datalist-ShipDecorations"></datalist> <datalist id="datalist-ShipDecorations"></datalist>
<datalist id="datalist-WeaponSkins"></datalist>
<datalist id="datalist-circuitGameModes"> <datalist id="datalist-circuitGameModes">
<option>Survival</option> <option>Survival</option>
<option>VoidFlood</option> <option>VoidFlood</option>

View File

@ -599,6 +599,46 @@ function fetchItemList() {
} }
itemMap[item.uniqueName] = { ...item, type }; 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 { } else {
const nameToItems = {}; const nameToItems = {};
items.forEach(item => { items.forEach(item => {
@ -837,10 +877,9 @@ function updateInventory() {
a.href = "#"; a.href = "#";
a.onclick = function (event) { a.onclick = function (event) {
event.preventDefault(); event.preventDefault();
revalidateAuthz().then(() => { revalidateAuthz().then(async () => {
const promises = [];
if (item.XP < maxXP) { if (item.XP < maxXP) {
promises.push(addGearExp(category, item.ItemId.$oid, maxXP - item.XP)); await addGearExp(category, item.ItemId.$oid, maxXP - item.XP);
} }
if ("exalted" in itemMap[item.ItemType]) { if ("exalted" in itemMap[item.ItemType]) {
for (const exaltedType of itemMap[item.ItemType].exalted) { for (const exaltedType of itemMap[item.ItemType].exalted) {
@ -851,20 +890,16 @@ function updateInventory() {
const exaltedCap = const exaltedCap =
itemMap[exaltedType]?.type == "weapons" ? 800_000 : 1_600_000; itemMap[exaltedType]?.type == "weapons" ? 800_000 : 1_600_000;
if (exaltedItem.XP < exaltedCap) { if (exaltedItem.XP < exaltedCap) {
promises.push( await addGearExp(
addGearExp( "SpecialItems",
"SpecialItems", exaltedItem.ItemId.$oid,
exaltedItem.ItemId.$oid, exaltedCap - exaltedItem.XP
exaltedCap - exaltedItem.XP
)
); );
} }
} }
} }
} }
Promise.all(promises).then(() => { updateInventory();
updateInventory();
});
}); });
}; };
a.title = loc("code_maxRank"); a.title = loc("code_maxRank");
@ -1108,6 +1143,44 @@ function updateInventory() {
document.getElementById("FlavourItems-list").appendChild(tr); 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 = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"/></svg>`;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("WeaponSkins-list").appendChild(tr);
});
const datalistEvolutionProgress = document.querySelectorAll("#datalist-EvolutionProgress option"); const datalistEvolutionProgress = document.querySelectorAll("#datalist-EvolutionProgress option");
const formEvolutionProgress = document.querySelector('form[onsubmit*="doAcquireEvolution()"]'); const formEvolutionProgress = document.querySelector('form[onsubmit*="doAcquireEvolution()"]');
@ -1207,7 +1280,7 @@ function updateInventory() {
a.href = "#"; a.href = "#";
a.onclick = function (event) { a.onclick = function (event) {
event.preventDefault(); event.preventDefault();
doQuestUpdate("setInactive", item.ItemType); debounce(doQuestUpdate, "setInactive", item.ItemType);
}; };
a.title = loc("code_setInactive"); a.title = loc("code_setInactive");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M464 256A208 208 0 1 0 48 256a208 208 0 1 0 416 0zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256zm192-96l128 0c17.7 0 32 14.3 32 32l0 128c0 17.7-14.3 32-32 32l-128 0c-17.7 0-32-14.3-32-32l0-128c0-17.7 14.3-32 32-32z"/></svg>`; a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M464 256A208 208 0 1 0 48 256a208 208 0 1 0 416 0zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256zm192-96l128 0c17.7 0 32 14.3 32 32l0 128c0 17.7-14.3 32-32 32l-128 0c-17.7 0-32-14.3-32-32l0-128c0-17.7 14.3-32 32-32z"/></svg>`;
@ -1218,7 +1291,7 @@ function updateInventory() {
a.href = "#"; a.href = "#";
a.onclick = function (event) { a.onclick = function (event) {
event.preventDefault(); event.preventDefault();
doQuestUpdate("resetKey", item.ItemType); debounce(doQuestUpdate, "resetKey", item.ItemType);
}; };
a.title = loc("code_reset"); a.title = loc("code_reset");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M463.5 224l8.5 0c13.3 0 24-10.7 24-24l0-128c0-9.7-5.8-18.5-14.8-22.2s-19.3-1.7-26.2 5.2L413.4 96.6c-87.6-86.5-228.7-86.2-315.8 1c-87.5 87.5-87.5 229.3 0 316.8s229.3 87.5 316.8 0c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0c-62.5 62.5-163.8 62.5-226.3 0s-62.5-163.8 0-226.3c62.2-62.2 162.7-62.5 225.3-1L327 183c-6.9 6.9-8.9 17.2-5.2 26.2s12.5 14.8 22.2 14.8l119.5 0z"/></svg>`; a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M463.5 224l8.5 0c13.3 0 24-10.7 24-24l0-128c0-9.7-5.8-18.5-14.8-22.2s-19.3-1.7-26.2 5.2L413.4 96.6c-87.6-86.5-228.7-86.2-315.8 1c-87.5 87.5-87.5 229.3 0 316.8s229.3 87.5 316.8 0c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0c-62.5 62.5-163.8 62.5-226.3 0s-62.5-163.8 0-226.3c62.2-62.2 162.7-62.5 225.3-1L327 183c-6.9 6.9-8.9 17.2-5.2 26.2s12.5 14.8 22.2 14.8l119.5 0z"/></svg>`;
@ -1229,7 +1302,7 @@ function updateInventory() {
a.href = "#"; a.href = "#";
a.onclick = function (event) { a.onclick = function (event) {
event.preventDefault(); event.preventDefault();
doQuestUpdate("completeKey", item.ItemType); debounce(doQuestUpdate, "completeKey", item.ItemType);
}; };
a.title = loc("code_complete"); a.title = loc("code_complete");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M438.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L160 338.7 393.4 105.4c12.5-12.5 32.8-12.5 45.3 0z"/></svg>`; a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M438.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L160 338.7 393.4 105.4c12.5-12.5 32.8-12.5 45.3 0z"/></svg>`;
@ -1240,7 +1313,7 @@ function updateInventory() {
a.href = "#"; a.href = "#";
a.onclick = function (event) { a.onclick = function (event) {
event.preventDefault(); event.preventDefault();
doQuestUpdate("prevStage", item.ItemType); debounce(doQuestUpdate, "prevStage", item.ItemType);
}; };
a.title = loc("code_prevStage"); a.title = loc("code_prevStage");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M41.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 256 246.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z"/></svg>`; a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M41.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 256 246.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z"/></svg>`;
@ -1255,7 +1328,7 @@ function updateInventory() {
a.href = "#"; a.href = "#";
a.onclick = function (event) { a.onclick = function (event) {
event.preventDefault(); event.preventDefault();
doQuestUpdate("nextStage", item.ItemType); debounce(doQuestUpdate, "nextStage", item.ItemType);
}; };
a.title = loc("code_nextStage"); a.title = loc("code_nextStage");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M278.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-160 160c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L210.7 256 73.4 118.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l160 160z"/></svg>`; a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M278.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-160 160c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L210.7 256 73.4 118.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l160 160z"/></svg>`;
@ -1267,7 +1340,7 @@ function updateInventory() {
a.onclick = function (event) { a.onclick = function (event) {
event.preventDefault(); event.preventDefault();
reAddToItemList(itemMap, "QuestKeys", item.ItemType); reAddToItemList(itemMap, "QuestKeys", item.ItemType);
doQuestUpdate("deleteKey", item.ItemType); debounce(doQuestUpdate, "deleteKey", item.ItemType);
}; };
a.title = loc("code_remove"); a.title = loc("code_remove");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"/></svg>`; a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"/></svg>`;
@ -3200,13 +3273,16 @@ function doIntrinsicsUnlockAll() {
document.querySelectorAll("#account-cheats input[type=checkbox]").forEach(elm => { document.querySelectorAll("#account-cheats input[type=checkbox]").forEach(elm => {
elm.onchange = function () { elm.onchange = function () {
revalidateAuthz().then(() => { revalidateAuthz().then(() => {
const value = elm.checked;
$.post({ $.post({
url: "/custom/setAccountCheat?" + window.authz, url: "/custom/setAccountCheat?" + window.authz,
contentType: "application/json", contentType: "application/json",
data: JSON.stringify({ data: JSON.stringify({
key: elm.id, key: elm.id,
value: elm.checked value: value
}) })
}).done(() => {
elm.checked = value;
}); });
}); });
}; };
@ -3242,6 +3318,8 @@ document.querySelectorAll("#account-cheats .input-group").forEach(grp => {
key: input.id, key: input.id,
value: parseInt(value) value: parseInt(value)
}) })
}).done(() => {
btn.value = value;
}); });
}); });
}; };

View File

@ -75,6 +75,10 @@ dict = {
code_funded: `[UNTRANSLATED] Funded`, code_funded: `[UNTRANSLATED] Funded`,
code_replays: `[UNTRANSLATED] Replays`, code_replays: `[UNTRANSLATED] Replays`,
code_stalker: `Stalker`, 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_succChange: `Erfolgreich geändert.`,
code_requiredInvigorationUpgrade: `[UNTRANSLATED] You must select both an offensive & utility upgrade.`, code_requiredInvigorationUpgrade: `[UNTRANSLATED] You must select both an offensive & utility upgrade.`,
login_description: `Melde dich mit deinem OpenWF-Account an (denselben Angaben wie im Spiel, wenn du dich mit diesem Server verbindest).`, login_description: `Melde dich mit deinem OpenWF-Account an (denselben Angaben wie im Spiel, wenn du dich mit diesem Server verbindest).`,
@ -112,6 +116,7 @@ dict = {
inventory_boosters: `Booster`, inventory_boosters: `Booster`,
inventory_flavourItems: `<abbr title="Animationssets, Glyphen, Farbpaletten usw.">Sammlerstücke</abbr>`, inventory_flavourItems: `<abbr title="Animationssets, Glyphen, Farbpaletten usw.">Sammlerstücke</abbr>`,
inventory_shipDecorations: `Schiffsdekorationen`, inventory_shipDecorations: `Schiffsdekorationen`,
inventory_weaponSkins: `Skins`,
inventory_bulkAddSuits: `Fehlende Warframes hinzufügen`, inventory_bulkAddSuits: `Fehlende Warframes hinzufügen`,
inventory_bulkAddWeapons: `Fehlende Waffen hinzufügen`, inventory_bulkAddWeapons: `Fehlende Waffen hinzufügen`,
inventory_bulkAddSpaceSuits: `Fehlende Archwings hinzufügen`, inventory_bulkAddSpaceSuits: `Fehlende Archwings hinzufügen`,
@ -121,6 +126,7 @@ dict = {
inventory_bulkAddFlavourItems: `[UNTRANSLATED] Add Missing Flavour Items`, inventory_bulkAddFlavourItems: `[UNTRANSLATED] Add Missing Flavour Items`,
inventory_bulkAddShipDecorations: `[UNTRANSLATED] Add Missing Ship Decorations`, inventory_bulkAddShipDecorations: `[UNTRANSLATED] Add Missing Ship Decorations`,
inventory_bulkAddEvolutionProgress: `Fehlende Incarnon-Entwicklungsfortschritte hinzufügen`, inventory_bulkAddEvolutionProgress: `Fehlende Incarnon-Entwicklungsfortschritte hinzufügen`,
inventory_bulkAddWeaponSkins: `[UNTRANSLATED] Add Missing Skins`,
inventory_bulkRankUpSuits: `Alle Warframes auf Max. Rang`, inventory_bulkRankUpSuits: `Alle Warframes auf Max. Rang`,
inventory_bulkRankUpWeapons: `Alle Waffen auf Max. Rang`, inventory_bulkRankUpWeapons: `Alle Waffen auf Max. Rang`,
inventory_bulkRankUpSpaceSuits: `Alle Archwings auf Max. Rang`, inventory_bulkRankUpSpaceSuits: `Alle Archwings auf Max. Rang`,
@ -193,7 +199,7 @@ dict = {
cheats_skipTutorial: `Tutorial überspringen`, cheats_skipTutorial: `Tutorial überspringen`,
cheats_skipAllDialogue: `Alle Dialoge überspringen`, cheats_skipAllDialogue: `Alle Dialoge überspringen`,
cheats_unlockAllScans: `Alle Scans freischalten`, cheats_unlockAllScans: `Alle Scans freischalten`,
cheats_unlockSuccRelog: `[UNTRANSLATED] Success. Please that you'll need to relog for the client to refresh this.`, cheats_unlockSuccRelog: `[UNTRANSLATED] Success. Please note that you'll need to relog for the client to refresh this.`,
cheats_unlockAllMissions: `Alle Missionen freischalten`, 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_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`, cheats_infiniteCredits: `Unendlich Credits`,
@ -209,7 +215,6 @@ dict = {
cheats_dontSubtractVoidTraces: `Void-Spuren nicht verbrauchen`, cheats_dontSubtractVoidTraces: `Void-Spuren nicht verbrauchen`,
cheats_dontSubtractConsumables: `Verbrauchsgegenstände (Ausrüstung) nicht verbrauchen`, cheats_dontSubtractConsumables: `Verbrauchsgegenstände (Ausrüstung) nicht verbrauchen`,
cheats_unlockAllShipFeatures: `Alle Schiffs-Funktionen freischalten`, cheats_unlockAllShipFeatures: `Alle Schiffs-Funktionen freischalten`,
cheats_unlockAllSkins: `Alle Skins freischalten`,
cheats_unlockAllCapturaScenes: `Alle Photora-Szenen freischalten`, cheats_unlockAllCapturaScenes: `Alle Photora-Szenen freischalten`,
cheats_universalPolarityEverywhere: `Universelle Polarität überall`, cheats_universalPolarityEverywhere: `Universelle Polarität überall`,
cheats_unlockDoubleCapacityPotatoesEverywhere: `Orokin Reaktor & Beschleuniger überall`, cheats_unlockDoubleCapacityPotatoesEverywhere: `Orokin Reaktor & Beschleuniger überall`,
@ -227,7 +232,7 @@ dict = {
cheats_baroFullyStocked: `Baro hat volles Inventar`, cheats_baroFullyStocked: `Baro hat volles Inventar`,
cheats_syndicateMissionsRepeatable: `Syndikat-Missionen wiederholbar`, cheats_syndicateMissionsRepeatable: `Syndikat-Missionen wiederholbar`,
cheats_unlockAllProfitTakerStages: `Alle Profiteintreiber-Phasen freischalten`, 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, visiting a dojo/relay, or relogging..`, 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_instantFinishRivenChallenge: `Riven-Mod Herausforderung sofort abschließen`, cheats_instantFinishRivenChallenge: `Riven-Mod Herausforderung sofort abschließen`,
cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`, cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`,
cheats_noResourceExtractorDronesDamage: `Kein Schaden für Ressourcen-Extraktor-Drohnen`, cheats_noResourceExtractorDronesDamage: `Kein Schaden für Ressourcen-Extraktor-Drohnen`,
@ -284,6 +289,7 @@ dict = {
worldState_hallowedFlame: `Geweihte Flamme`, worldState_hallowedFlame: `Geweihte Flamme`,
worldState_hallowedNightmares: `Geweihte Albträume`, worldState_hallowedNightmares: `Geweihte Albträume`,
worldState_hallowedNightmaresRewards: `[UNTRANSLATED] Hallowed Nightmares Rewards`, worldState_hallowedNightmaresRewards: `[UNTRANSLATED] Hallowed Nightmares Rewards`,
worldState_naberusNights: `[UNTRANSLATED] Nights of Naberus`,
worldState_proxyRebellion: `Proxy-Rebellion`, worldState_proxyRebellion: `Proxy-Rebellion`,
worldState_proxyRebellionRewards: `[UNTRANSLATED] Proxy Rebellion Rewards`, worldState_proxyRebellionRewards: `[UNTRANSLATED] Proxy Rebellion Rewards`,
worldState_bellyOfTheBeast: `Das Innere der Bestie`, worldState_bellyOfTheBeast: `Das Innere der Bestie`,

View File

@ -1,5 +1,5 @@
dict = { dict = {
general_inventoryUpdateNote: `Note: To see changes in-game, you need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging.`, general_inventoryUpdateNote: `Note: To see changes in-game, you need to resync your inventory, e.g. using the bootstrapper's /sync command in game chat, visiting a dojo/relay, or relogging.`,
general_inventoryUpdateNoteGameWs: `Note: You may need to reopen any menu you are on for changes to be reflected.`, general_inventoryUpdateNoteGameWs: `Note: You may need to reopen any menu you are on for changes to be reflected.`,
general_addButton: `Add`, general_addButton: `Add`,
general_setButton: `Set`, general_setButton: `Set`,
@ -74,6 +74,10 @@ dict = {
code_funded: `Funded`, code_funded: `Funded`,
code_replays: `Replays`, code_replays: `Replays`,
code_stalker: `Stalker`, 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_succChange: `Successfully changed.`,
code_requiredInvigorationUpgrade: `You must select both an offensive & utility upgrade.`, 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).`, 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_boosters: `Boosters`,
inventory_flavourItems: `<abbr title="Animation Sets, Glyphs, Palettes, etc.">Flavour Items</abbr>`, inventory_flavourItems: `<abbr title="Animation Sets, Glyphs, Palettes, etc.">Flavour Items</abbr>`,
inventory_shipDecorations: `Ship Decorations`, inventory_shipDecorations: `Ship Decorations`,
inventory_weaponSkins: `Skins`,
inventory_bulkAddSuits: `Add Missing Warframes`, inventory_bulkAddSuits: `Add Missing Warframes`,
inventory_bulkAddWeapons: `Add Missing Weapons`, inventory_bulkAddWeapons: `Add Missing Weapons`,
inventory_bulkAddSpaceSuits: `Add Missing Archwings`, inventory_bulkAddSpaceSuits: `Add Missing Archwings`,
@ -120,6 +125,7 @@ dict = {
inventory_bulkAddFlavourItems: `Add Missing Flavour Items`, inventory_bulkAddFlavourItems: `Add Missing Flavour Items`,
inventory_bulkAddShipDecorations: `Add Missing Ship Decorations`, inventory_bulkAddShipDecorations: `Add Missing Ship Decorations`,
inventory_bulkAddEvolutionProgress: `Add Missing Incarnon Evolution Progress`, inventory_bulkAddEvolutionProgress: `Add Missing Incarnon Evolution Progress`,
inventory_bulkAddWeaponSkins: `Add Missing Skins`,
inventory_bulkRankUpSuits: `Max Rank All Warframes`, inventory_bulkRankUpSuits: `Max Rank All Warframes`,
inventory_bulkRankUpWeapons: `Max Rank All Weapons`, inventory_bulkRankUpWeapons: `Max Rank All Weapons`,
inventory_bulkRankUpSpaceSuits: `Max Rank All Archwings`, inventory_bulkRankUpSpaceSuits: `Max Rank All Archwings`,
@ -192,7 +198,7 @@ dict = {
cheats_skipTutorial: `Skip Tutorial`, cheats_skipTutorial: `Skip Tutorial`,
cheats_skipAllDialogue: `Skip All Dialogue`, cheats_skipAllDialogue: `Skip All Dialogue`,
cheats_unlockAllScans: `Unlock All Scans`, cheats_unlockAllScans: `Unlock All Scans`,
cheats_unlockSuccRelog: `Success. Please that you'll need to relog for the client to refresh this.`, cheats_unlockSuccRelog: `Success. Please note that you'll need to relog for the client to refresh this.`,
cheats_unlockAllMissions: `Unlock All Missions`, cheats_unlockAllMissions: `Unlock All Missions`,
cheats_unlockAllMissions_ok: `Success. Please note that you'll need to enter a dojo/relay or relog for the client to refresh the star chart.`, cheats_unlockAllMissions_ok: `Success. Please note that you'll need to enter a dojo/relay or relog for the client to refresh the star chart.`,
cheats_infiniteCredits: `Infinite Credits`, cheats_infiniteCredits: `Infinite Credits`,
@ -208,7 +214,6 @@ dict = {
cheats_dontSubtractVoidTraces: `Don't Subtract Void Traces`, cheats_dontSubtractVoidTraces: `Don't Subtract Void Traces`,
cheats_dontSubtractConsumables: `Don't Subtract Consumables`, cheats_dontSubtractConsumables: `Don't Subtract Consumables`,
cheats_unlockAllShipFeatures: `Unlock All Ship Features`, cheats_unlockAllShipFeatures: `Unlock All Ship Features`,
cheats_unlockAllSkins: `Unlock All Skins`,
cheats_unlockAllCapturaScenes: `Unlock All Captura Scenes`, cheats_unlockAllCapturaScenes: `Unlock All Captura Scenes`,
cheats_universalPolarityEverywhere: `Universal Polarity Everywhere`, cheats_universalPolarityEverywhere: `Universal Polarity Everywhere`,
cheats_unlockDoubleCapacityPotatoesEverywhere: `Potatoes Everywhere`, cheats_unlockDoubleCapacityPotatoesEverywhere: `Potatoes Everywhere`,
@ -226,7 +231,7 @@ dict = {
cheats_baroFullyStocked: `Baro Fully Stocked`, cheats_baroFullyStocked: `Baro Fully Stocked`,
cheats_syndicateMissionsRepeatable: `Syndicate Missions Repeatable`, cheats_syndicateMissionsRepeatable: `Syndicate Missions Repeatable`,
cheats_unlockAllProfitTakerStages: `Unlock All Profit Taker Stages`, cheats_unlockAllProfitTakerStages: `Unlock All Profit Taker Stages`,
cheats_unlockSuccInventory: `Success. Please note that you'll need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging..`, cheats_unlockSuccInventory: `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_instantFinishRivenChallenge: `Instant Finish Riven Challenge`, cheats_instantFinishRivenChallenge: `Instant Finish Riven Challenge`,
cheats_instantResourceExtractorDrones: `Instant Resource Extractor Drones`, cheats_instantResourceExtractorDrones: `Instant Resource Extractor Drones`,
cheats_noResourceExtractorDronesDamage: `No Resource Extractor Drones Damage`, cheats_noResourceExtractorDronesDamage: `No Resource Extractor Drones Damage`,
@ -283,6 +288,7 @@ dict = {
worldState_hallowedFlame: `Hallowed Flame`, worldState_hallowedFlame: `Hallowed Flame`,
worldState_hallowedNightmares: `Hallowed Nightmares`, worldState_hallowedNightmares: `Hallowed Nightmares`,
worldState_hallowedNightmaresRewards: `Hallowed Nightmares Rewards`, worldState_hallowedNightmaresRewards: `Hallowed Nightmares Rewards`,
worldState_naberusNights: `Nights of Naberus`,
worldState_proxyRebellion: `Proxy Rebellion`, worldState_proxyRebellion: `Proxy Rebellion`,
worldState_proxyRebellionRewards: `Proxy Rebellion Rewards`, worldState_proxyRebellionRewards: `Proxy Rebellion Rewards`,
worldState_bellyOfTheBeast: `Belly of the Beast`, worldState_bellyOfTheBeast: `Belly of the Beast`,

View File

@ -1,4 +1,4 @@
// Spanish translation by hxedcl // Spanish translation by hxedcl, Slayer55555
dict = { dict = {
general_inventoryUpdateNote: `Para ver los cambios en el juego, necesitas volver a sincronizar tu inventario, por ejemplo, usando el comando /sync del bootstrapper, visitando un dojo o repetidor, o volviendo a iniciar sesión.`, general_inventoryUpdateNote: `Para ver los cambios en el juego, necesitas volver a sincronizar tu inventario, por ejemplo, usando el comando /sync del bootstrapper, visitando un dojo o repetidor, o volviendo a iniciar sesión.`,
general_inventoryUpdateNoteGameWs: `Nota: Puede que necesites reabrir cualquier menú en el que te encuentres para que los cambios se reflejen.`, general_inventoryUpdateNoteGameWs: `Nota: Puede que necesites reabrir cualquier menú en el que te encuentres para que los cambios se reflejen.`,
@ -75,6 +75,10 @@ dict = {
code_funded: `Financiado`, code_funded: `Financiado`,
code_replays: `Repeticiones`, code_replays: `Repeticiones`,
code_stalker: `Stalker`, 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_succChange: `Cambiado correctamente`,
code_requiredInvigorationUpgrade: `Debes seleccionar una mejora ofensiva y una mejora de utilidad.`, 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).`, 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_boosters: `Potenciadores`,
inventory_flavourItems: `<abbr title="Conjuntos de animaciones, glifos, paletas, etc.">Ítems estéticos</abbr>`, inventory_flavourItems: `<abbr title="Conjuntos de animaciones, glifos, paletas, etc.">Ítems estéticos</abbr>`,
inventory_shipDecorations: `Decoraciones de nave`, inventory_shipDecorations: `Decoraciones de nave`,
inventory_weaponSkins: `Diseños`,
inventory_bulkAddSuits: `Agregar Warframes faltantes`, inventory_bulkAddSuits: `Agregar Warframes faltantes`,
inventory_bulkAddWeapons: `Agregar armas faltantes`, inventory_bulkAddWeapons: `Agregar armas faltantes`,
inventory_bulkAddSpaceSuits: `Agregar Archwings faltantes`, inventory_bulkAddSpaceSuits: `Agregar Archwings faltantes`,
@ -121,6 +126,7 @@ dict = {
inventory_bulkAddFlavourItems: `Añadir items estéticos faltantes`, inventory_bulkAddFlavourItems: `Añadir items estéticos faltantes`,
inventory_bulkAddShipDecorations: `Añadir decoraciones de Nave Faltantes`, inventory_bulkAddShipDecorations: `Añadir decoraciones de Nave Faltantes`,
inventory_bulkAddEvolutionProgress: `Completar el progreso de evolución Incarnon faltante`, 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_bulkRankUpSuits: `Maximizar rango de todos los Warframes`,
inventory_bulkRankUpWeapons: `Maximizar rango de todas las armas`, inventory_bulkRankUpWeapons: `Maximizar rango de todas las armas`,
inventory_bulkRankUpSpaceSuits: `Maximizar rango de todos los Archwings`, inventory_bulkRankUpSpaceSuits: `Maximizar rango de todos los Archwings`,
@ -209,7 +215,6 @@ dict = {
cheats_dontSubtractVoidTraces: `No descontar vestigios del Vacío`, cheats_dontSubtractVoidTraces: `No descontar vestigios del Vacío`,
cheats_dontSubtractConsumables: `No restar consumibles`, cheats_dontSubtractConsumables: `No restar consumibles`,
cheats_unlockAllShipFeatures: `Desbloquear todas las funciones de nave`, cheats_unlockAllShipFeatures: `Desbloquear todas las funciones de nave`,
cheats_unlockAllSkins: `Desbloquear todas las skins`,
cheats_unlockAllCapturaScenes: `Desbloquear todas las escenas de Captura`, cheats_unlockAllCapturaScenes: `Desbloquear todas las escenas de Captura`,
cheats_universalPolarityEverywhere: `Polaridad universal en todas partes`, cheats_universalPolarityEverywhere: `Polaridad universal en todas partes`,
cheats_unlockDoubleCapacityPotatoesEverywhere: `Patatas en todas partes`, cheats_unlockDoubleCapacityPotatoesEverywhere: `Patatas en todas partes`,
@ -284,6 +289,7 @@ dict = {
worldState_hallowedFlame: `Llama Sagrada`, worldState_hallowedFlame: `Llama Sagrada`,
worldState_hallowedNightmares: `Pesadillas Sagradas`, worldState_hallowedNightmares: `Pesadillas Sagradas`,
worldState_hallowedNightmaresRewards: `Recompensas de Pesadillas Sagradas`, worldState_hallowedNightmaresRewards: `Recompensas de Pesadillas Sagradas`,
worldState_naberusNights: `Noches de Naberus`,
worldState_proxyRebellion: `Rebelión Proxy`, worldState_proxyRebellion: `Rebelión Proxy`,
worldState_proxyRebellionRewards: `Recompensas de Rebelión Proxy`, worldState_proxyRebellionRewards: `Recompensas de Rebelión Proxy`,
worldState_bellyOfTheBeast: `Vientre de la Bestia`, worldState_bellyOfTheBeast: `Vientre de la Bestia`,

View File

@ -75,6 +75,10 @@ dict = {
code_funded: `Complété`, code_funded: `Complété`,
code_replays: `[UNTRANSLATED] Replays`, code_replays: `[UNTRANSLATED] Replays`,
code_stalker: `Stalker`, code_stalker: `Stalker`,
code_cutName: `[UNTRANSLATED] Cut |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_succChange: `Changement effectué.`,
code_requiredInvigorationUpgrade: `[UNTRANSLATED] You must select both an offensive & utility upgrade.`, code_requiredInvigorationUpgrade: `[UNTRANSLATED] You must select both an offensive & utility upgrade.`,
login_description: `Connexion avec les informations de connexion OpenWF.`, login_description: `Connexion avec les informations de connexion OpenWF.`,
@ -112,6 +116,7 @@ dict = {
inventory_boosters: `Boosters`, inventory_boosters: `Boosters`,
inventory_flavourItems: `[UNTRANSLATED] <abbr title="Animation Sets, Glyphs, Palettes, etc.">Flavour Items</abbr>`, inventory_flavourItems: `[UNTRANSLATED] <abbr title="Animation Sets, Glyphs, Palettes, etc.">Flavour Items</abbr>`,
inventory_shipDecorations: `Décorations du vaisseau`, inventory_shipDecorations: `Décorations du vaisseau`,
inventory_weaponSkins: `Aspects`,
inventory_bulkAddSuits: `Ajouter les Warframes manquantes`, inventory_bulkAddSuits: `Ajouter les Warframes manquantes`,
inventory_bulkAddWeapons: `Ajouter les armes manquantes`, inventory_bulkAddWeapons: `Ajouter les armes manquantes`,
inventory_bulkAddSpaceSuits: `Ajouter les Archwings manquants`, inventory_bulkAddSpaceSuits: `Ajouter les Archwings manquants`,
@ -121,6 +126,7 @@ dict = {
inventory_bulkAddFlavourItems: `[UNTRANSLATED] Add Missing Flavour Items`, inventory_bulkAddFlavourItems: `[UNTRANSLATED] Add Missing Flavour Items`,
inventory_bulkAddShipDecorations: `[UNTRANSLATED] Add Missing Ship Decorations`, inventory_bulkAddShipDecorations: `[UNTRANSLATED] Add Missing Ship Decorations`,
inventory_bulkAddEvolutionProgress: `Ajouter les évolutions Incarnon manquantes`, inventory_bulkAddEvolutionProgress: `Ajouter les évolutions Incarnon manquantes`,
inventory_bulkAddWeaponSkins: `[UNTRANSLATED] Add Missing Skins`,
inventory_bulkRankUpSuits: `Toutes les Warframes au rang max`, inventory_bulkRankUpSuits: `Toutes les Warframes au rang max`,
inventory_bulkRankUpWeapons: `Toutes les armes au rang max`, inventory_bulkRankUpWeapons: `Toutes les armes au rang max`,
inventory_bulkRankUpSpaceSuits: `Tous les Archwings au rang max`, inventory_bulkRankUpSpaceSuits: `Tous les Archwings au rang max`,
@ -209,7 +215,6 @@ dict = {
cheats_dontSubtractVoidTraces: `Ne pas consommer de Void Traces`, cheats_dontSubtractVoidTraces: `Ne pas consommer de Void Traces`,
cheats_dontSubtractConsumables: `Ne pas retirer de consommables`, cheats_dontSubtractConsumables: `Ne pas retirer de consommables`,
cheats_unlockAllShipFeatures: `Débloquer tous les segments du vaisseau`, cheats_unlockAllShipFeatures: `Débloquer tous les segments du vaisseau`,
cheats_unlockAllSkins: `Débloquer tous les skins`,
cheats_unlockAllCapturaScenes: `Débloquer toutes les scènes captura`, cheats_unlockAllCapturaScenes: `Débloquer toutes les scènes captura`,
cheats_universalPolarityEverywhere: `Polarités universelles partout`, cheats_universalPolarityEverywhere: `Polarités universelles partout`,
cheats_unlockDoubleCapacityPotatoesEverywhere: `Réacteurs et Catalyseurs partout`, cheats_unlockDoubleCapacityPotatoesEverywhere: `Réacteurs et Catalyseurs partout`,
@ -284,6 +289,7 @@ dict = {
worldState_hallowedFlame: `Flamme Hantée`, worldState_hallowedFlame: `Flamme Hantée`,
worldState_hallowedNightmares: `Cauchemars Hantés`, worldState_hallowedNightmares: `Cauchemars Hantés`,
worldState_hallowedNightmaresRewards: `Récompenses Flamme Hantée Cauchemar`, worldState_hallowedNightmaresRewards: `Récompenses Flamme Hantée Cauchemar`,
worldState_naberusNights: `[UNTRANSLATED] Nights of Naberus`,
worldState_proxyRebellion: `Rébellion Proxy`, worldState_proxyRebellion: `Rébellion Proxy`,
worldState_proxyRebellionRewards: `Récompenses Rébellion Proxy`, worldState_proxyRebellionRewards: `Récompenses Rébellion Proxy`,
worldState_bellyOfTheBeast: `Ventre de la Bête`, worldState_bellyOfTheBeast: `Ventre de la Bête`,

View File

@ -1,6 +1,6 @@
// Russian translation by AMelonInsideLemon, LoseFace // Russian translation by AMelonInsideLemon, LoseFace
dict = { dict = {
general_inventoryUpdateNote: `Примечание: Чтобы увидеть изменения в игре, вам нужно повторно синхронизировать свой инвентарь, например, используя команду /sync в программе bootstrapper, посетив Додзё/Реле или перезагрузив игру.`, general_inventoryUpdateNote: `Примечание: Чтобы увидеть изменения в игре, вам нужно повторно синхронизировать свой инвентарь, например, используя команду загрузчика /sync в чате игры, посетив Додзё/Реле или перезагрузив игру.`,
general_inventoryUpdateNoteGameWs: `Примечание: для того, чтобы изменения вступили в силу, может потребоваться повторно открыть меню, в котором вы находитесь.`, general_inventoryUpdateNoteGameWs: `Примечание: для того, чтобы изменения вступили в силу, может потребоваться повторно открыть меню, в котором вы находитесь.`,
general_addButton: `Добавить`, general_addButton: `Добавить`,
general_setButton: `Установить`, general_setButton: `Установить`,
@ -45,7 +45,7 @@ dict = {
code_rank: `Ранг`, code_rank: `Ранг`,
code_rankUp: `Повысить ранг`, code_rankUp: `Повысить ранг`,
code_rankDown: `Понизить ранг`, code_rankDown: `Понизить ранг`,
code_unlockLevelCap: `[UNTRANSLATED] Unlock level cap`, code_unlockLevelCap: `Разблокировать ограничение уровня`,
code_count: `Количество`, code_count: `Количество`,
code_focusAllUnlocked: `Все школы Фокуса уже разблокированы.`, code_focusAllUnlocked: `Все школы Фокуса уже разблокированы.`,
code_focusUnlocked: `Разблокировано |COUNT| новых школ Фокуса! Для отображения изменений в игре потребуется обновление инвентаря. Посещение навигации — самый простой способ этого добиться.`, code_focusUnlocked: `Разблокировано |COUNT| новых школ Фокуса! Для отображения изменений в игре потребуется обновление инвентаря. Посещение навигации — самый простой способ этого добиться.`,
@ -75,6 +75,10 @@ dict = {
code_funded: `Профинансировано`, code_funded: `Профинансировано`,
code_replays: `Повторов`, code_replays: `Повторов`,
code_stalker: `Сталкер`, code_stalker: `Сталкер`,
code_cutName: `Причёска: |INDEX|`,
code_drifterBeardName: `Борода скитальца: |INDEX|`,
code_drifterFaceName: `Внешность скитальца: |INDEX|`,
code_operatorFaceName: `Внешность оператора: |INDEX|`,
code_succChange: `Успешно изменено.`, code_succChange: `Успешно изменено.`,
code_requiredInvigorationUpgrade: `Вы должны выбрать как атакующее, так и вспомогательное улучшение.`, code_requiredInvigorationUpgrade: `Вы должны выбрать как атакующее, так и вспомогательное улучшение.`,
login_description: `Войдите, используя учетные данные OpenWF (те же, что и в игре при подключении к этому серверу).`, login_description: `Войдите, используя учетные данные OpenWF (те же, что и в игре при подключении к этому серверу).`,
@ -112,6 +116,7 @@ dict = {
inventory_boosters: `Бустеры`, inventory_boosters: `Бустеры`,
inventory_flavourItems: `<abbr title="Наборы анимаций, глифы, палитры и т. д.">Уникальные предметы</abbr>`, inventory_flavourItems: `<abbr title="Наборы анимаций, глифы, палитры и т. д.">Уникальные предметы</abbr>`,
inventory_shipDecorations: `Украшения корабля`, inventory_shipDecorations: `Украшения корабля`,
inventory_weaponSkins: `Скины`,
inventory_bulkAddSuits: `Добавить отсутствующие Варфреймы`, inventory_bulkAddSuits: `Добавить отсутствующие Варфреймы`,
inventory_bulkAddWeapons: `Добавить отсутствующее оружие`, inventory_bulkAddWeapons: `Добавить отсутствующее оружие`,
inventory_bulkAddSpaceSuits: `Добавить отсутствующие Арчвинги`, inventory_bulkAddSpaceSuits: `Добавить отсутствующие Арчвинги`,
@ -121,6 +126,7 @@ dict = {
inventory_bulkAddFlavourItems: `Добавить отсутствующие уникальные предметы`, inventory_bulkAddFlavourItems: `Добавить отсутствующие уникальные предметы`,
inventory_bulkAddShipDecorations: `Добавить отсутствующие украшения корабля`, inventory_bulkAddShipDecorations: `Добавить отсутствующие украшения корабля`,
inventory_bulkAddEvolutionProgress: `Добавить отсутствующий прогресс эволюции Инкарнонов`, inventory_bulkAddEvolutionProgress: `Добавить отсутствующий прогресс эволюции Инкарнонов`,
inventory_bulkAddWeaponSkins: `Добавить отсутствующие скины`,
inventory_bulkRankUpSuits: `Макс. ранг всех Варфреймов`, inventory_bulkRankUpSuits: `Макс. ранг всех Варфреймов`,
inventory_bulkRankUpWeapons: `Макс. ранг всего оружия`, inventory_bulkRankUpWeapons: `Макс. ранг всего оружия`,
inventory_bulkRankUpSpaceSuits: `Макс. ранг всех Арчвингов`, inventory_bulkRankUpSpaceSuits: `Макс. ранг всех Арчвингов`,
@ -209,7 +215,6 @@ dict = {
cheats_dontSubtractVoidTraces: `Не вычитать количество Отголосков Бездны`, cheats_dontSubtractVoidTraces: `Не вычитать количество Отголосков Бездны`,
cheats_dontSubtractConsumables: `Не вычитать количество расходников`, cheats_dontSubtractConsumables: `Не вычитать количество расходников`,
cheats_unlockAllShipFeatures: `Разблокировать все функции корабля`, cheats_unlockAllShipFeatures: `Разблокировать все функции корабля`,
cheats_unlockAllSkins: `Разблокировать все скины`,
cheats_unlockAllCapturaScenes: `Разблокировать все сцены Каптуры`, cheats_unlockAllCapturaScenes: `Разблокировать все сцены Каптуры`,
cheats_universalPolarityEverywhere: `Универсальная полярность везде`, cheats_universalPolarityEverywhere: `Универсальная полярность везде`,
cheats_unlockDoubleCapacityPotatoesEverywhere: `Реакторы/Катализаторы орокин везде`, cheats_unlockDoubleCapacityPotatoesEverywhere: `Реакторы/Катализаторы орокин везде`,
@ -227,7 +232,7 @@ dict = {
cheats_baroFullyStocked: `Баро полностью укомплектован`, cheats_baroFullyStocked: `Баро полностью укомплектован`,
cheats_syndicateMissionsRepeatable: `Повторять миссии синдиката`, cheats_syndicateMissionsRepeatable: `Повторять миссии синдиката`,
cheats_unlockAllProfitTakerStages: `Разблокировать все этапы Сферы извлечения прибыли`, cheats_unlockAllProfitTakerStages: `Разблокировать все этапы Сферы извлечения прибыли`,
cheats_unlockSuccInventory: `Успех. Обратите внимание, что вам необходимо будет повторно синхронизировать свой инвентарь, например, с помощью команды /sync в программе bootstrapper, посетив Додзё/Реле или повторно войдя в игру.`, cheats_unlockSuccInventory: `Успех. Обратите внимание, что вам необходимо будет повторно синхронизировать свой инвентарь, например, с помощью команды загрузчика /sync в чате игры, посетив Додзё/Реле или повторно войдя в игру.`,
cheats_instantFinishRivenChallenge: `Мгновенное завершение испытания мода Разлома`, cheats_instantFinishRivenChallenge: `Мгновенное завершение испытания мода Разлома`,
cheats_instantResourceExtractorDrones: `Мгновенно добывающие Дроны-сборщики`, cheats_instantResourceExtractorDrones: `Мгновенно добывающие Дроны-сборщики`,
cheats_noResourceExtractorDronesDamage: `Без урона по Дронам-сборщикам`, cheats_noResourceExtractorDronesDamage: `Без урона по Дронам-сборщикам`,
@ -257,12 +262,12 @@ dict = {
cheats_changeButton: `Изменить`, cheats_changeButton: `Изменить`,
cheats_markAllAsRead: `Пометить все входящие как прочитанные`, cheats_markAllAsRead: `Пометить все входящие как прочитанные`,
cheats_finishInvasionsInOneMission: `Завершать вторжение за одну миссию`, cheats_finishInvasionsInOneMission: `Завершать вторжение за одну миссию`,
cheats_nemesisHenchmenKillsMultiplierGrineer: `[UNTRANSLATED] Rage Progess Multiplier (Grineer)`, cheats_nemesisHenchmenKillsMultiplierGrineer: `Мультипликатор прогресса ярости (Гринир)`,
cheats_nemesisHenchmenKillsMultiplierCorpus: `[UNTRANSLATED] Rage Progess Multiplier (Corpus)`, cheats_nemesisHenchmenKillsMultiplierCorpus: `Мультипликатор прогресса ярости (Корпус)`,
cheats_nemesisAntivirusGainMultiplier: `[UNTRANSLATED] Antivirus Progress Multiplier`, cheats_nemesisAntivirusGainMultiplier: `Мультипликатор прогресса антивируса`,
cheats_nemesisHintProgressMultiplierGrineer: `[UNTRANSLATED] Hint Progress Multiplier (Grineer)`, cheats_nemesisHintProgressMultiplierGrineer: `Мультипликатор прогресса подсказки (Гринир)`,
cheats_nemesisHintProgressMultiplierCorpus: `[UNTRANSLATED] Hint Progress Multiplier (Corpus)`, cheats_nemesisHintProgressMultiplierCorpus: `Мультипликатор прогресса подсказки (Корпус)`,
cheats_nemesisExtraWeapon: `[UNTRANSLATED] Extra Nemesis Weapon / Token On Vanquish (0 to disable)`, cheats_nemesisExtraWeapon: `Дополнительное оружие/активный Кардиомиоцит за победу над Противником (0 для отключения)`,
worldState: `Состояние мира`, worldState: `Состояние мира`,
worldState_creditBoost: `Глобальный бустер Кредитов`, worldState_creditBoost: `Глобальный бустер Кредитов`,
@ -284,6 +289,7 @@ dict = {
worldState_hallowedFlame: `Священное пламя`, worldState_hallowedFlame: `Священное пламя`,
worldState_hallowedNightmares: `Священные кошмары`, worldState_hallowedNightmares: `Священные кошмары`,
worldState_hallowedNightmaresRewards: `Награды Священных кошмаров`, worldState_hallowedNightmaresRewards: `Награды Священных кошмаров`,
worldState_naberusNights: `Ночи Наберуса`,
worldState_proxyRebellion: `Восстание роботов`, worldState_proxyRebellion: `Восстание роботов`,
worldState_proxyRebellionRewards: `Награды Восстания роботов`, worldState_proxyRebellionRewards: `Награды Восстания роботов`,
worldState_bellyOfTheBeast: `Чрево зверя`, worldState_bellyOfTheBeast: `Чрево зверя`,

View File

@ -1,6 +1,6 @@
// Ukrainian translation by LoseFace // Ukrainian translation by LoseFace
dict = { dict = {
general_inventoryUpdateNote: `Пам'ятка: Щоб побачити зміни в грі, вам потрібно повторно синхронізувати своє спорядження, наприклад, використовуючи команду /sync в програмі bootstrapper, відвідавши Доджьо/Реле або перезавантаживши гру.`, general_inventoryUpdateNote: `Пам'ятка: Щоб побачити зміни в грі, вам потрібно повторно синхронізувати своє спорядження, наприклад, використовуючи команду завантажувача /sync у чаті гри, відвідавши Доджьо/Реле або перезавантаживши гру.`,
general_inventoryUpdateNoteGameWs: `Примітка: для відображення змін може знадобитися повторно відкрити меню, в якому ви перебуваєте.`, general_inventoryUpdateNoteGameWs: `Примітка: для відображення змін може знадобитися повторно відкрити меню, в якому ви перебуваєте.`,
general_addButton: `Добавити`, general_addButton: `Добавити`,
general_setButton: `Встановити`, general_setButton: `Встановити`,
@ -45,7 +45,7 @@ dict = {
code_rank: `Рівень`, code_rank: `Рівень`,
code_rankUp: `Підвищити рівень`, code_rankUp: `Підвищити рівень`,
code_rankDown: `Понизити рівень`, code_rankDown: `Понизити рівень`,
code_unlockLevelCap: `[UNTRANSLATED] Unlock level cap`, code_unlockLevelCap: `Розблокувати обмеження рівня`,
code_count: `Кількість`, code_count: `Кількість`,
code_focusAllUnlocked: `Всі школи Фокусу вже розблоковані.`, code_focusAllUnlocked: `Всі школи Фокусу вже розблоковані.`,
code_focusUnlocked: `Розблоковано |COUNT| нових шкіл Фокусу! Для відображення змін в грі знадобиться оновлення спорядження. Відвідування навігації — найпростіший спосіб цього досягти.`, code_focusUnlocked: `Розблоковано |COUNT| нових шкіл Фокусу! Для відображення змін в грі знадобиться оновлення спорядження. Відвідування навігації — найпростіший спосіб цього досягти.`,
@ -75,6 +75,10 @@ dict = {
code_funded: `Профінансовано`, code_funded: `Профінансовано`,
code_replays: `Повтори`, code_replays: `Повтори`,
code_stalker: `Сталкер`, code_stalker: `Сталкер`,
code_cutName: `Зачіска: |INDEX|`,
code_drifterBeardName: `Борода мандрівника: |INDEX|`,
code_drifterFaceName: `Зовнішність мандрівника: |INDEX|`,
code_operatorFaceName: `Зовнішність оператора: |INDEX|`,
code_succChange: `Успішно змінено.`, code_succChange: `Успішно змінено.`,
code_requiredInvigorationUpgrade: `Ви повинні вибрати як атакуюче, так і допоміжне вдосконалення.`, code_requiredInvigorationUpgrade: `Ви повинні вибрати як атакуюче, так і допоміжне вдосконалення.`,
login_description: `Увійдіть, використовуючи облікові дані OpenWF (ті ж, що й у грі при підключенні до цього серверу).`, login_description: `Увійдіть, використовуючи облікові дані OpenWF (ті ж, що й у грі при підключенні до цього серверу).`,
@ -112,6 +116,7 @@ dict = {
inventory_boosters: `Посилення`, inventory_boosters: `Посилення`,
inventory_flavourItems: `<abbr title="Набори анімацій, гліфи, палітри і т. д.">Унікальні предмети</abbr>`, inventory_flavourItems: `<abbr title="Набори анімацій, гліфи, палітри і т. д.">Унікальні предмети</abbr>`,
inventory_shipDecorations: `Прикраси судна`, inventory_shipDecorations: `Прикраси судна`,
inventory_weaponSkins: `Вигляди`,
inventory_bulkAddSuits: `Додати відсутні Ворфрейми`, inventory_bulkAddSuits: `Додати відсутні Ворфрейми`,
inventory_bulkAddWeapons: `Додати відсутню зброю`, inventory_bulkAddWeapons: `Додати відсутню зброю`,
inventory_bulkAddSpaceSuits: `Додати відсутні Арквінґи`, inventory_bulkAddSpaceSuits: `Додати відсутні Арквінґи`,
@ -121,6 +126,7 @@ dict = {
inventory_bulkAddFlavourItems: `Додати відсутні унікальні предмети`, inventory_bulkAddFlavourItems: `Додати відсутні унікальні предмети`,
inventory_bulkAddShipDecorations: `Додати відсутні оздоби корабля`, inventory_bulkAddShipDecorations: `Додати відсутні оздоби корабля`,
inventory_bulkAddEvolutionProgress: `Додати відсутній прогрес еволюції Інкарнонів`, inventory_bulkAddEvolutionProgress: `Додати відсутній прогрес еволюції Інкарнонів`,
inventory_bulkAddWeaponSkins: `[UNTRANSLATED] Add Missing Skins`,
inventory_bulkRankUpSuits: `Макс. рівень всіх Ворфреймів`, inventory_bulkRankUpSuits: `Макс. рівень всіх Ворфреймів`,
inventory_bulkRankUpWeapons: `Макс. рівень всієї зброї`, inventory_bulkRankUpWeapons: `Макс. рівень всієї зброї`,
inventory_bulkRankUpSpaceSuits: `Макс. рівень всіх Арквінґів`, inventory_bulkRankUpSpaceSuits: `Макс. рівень всіх Арквінґів`,
@ -209,7 +215,6 @@ dict = {
cheats_dontSubtractVoidTraces: `Не вираховувати кількість Відлуння`, cheats_dontSubtractVoidTraces: `Не вираховувати кількість Відлуння`,
cheats_dontSubtractConsumables: `Не вираховувати кількість витратних матеріалів`, cheats_dontSubtractConsumables: `Не вираховувати кількість витратних матеріалів`,
cheats_unlockAllShipFeatures: `Розблокувати всі функції судна`, cheats_unlockAllShipFeatures: `Розблокувати всі функції судна`,
cheats_unlockAllSkins: `Розблокувати всі скіни`,
cheats_unlockAllCapturaScenes: `Розблокувати всі сцени Світлописця`, cheats_unlockAllCapturaScenes: `Розблокувати всі сцени Світлописця`,
cheats_universalPolarityEverywhere: `Будь-яка полярність скрізь`, cheats_universalPolarityEverywhere: `Будь-яка полярність скрізь`,
cheats_unlockDoubleCapacityPotatoesEverywhere: `Орокінські Реактори/Каталізатори скрізь`, cheats_unlockDoubleCapacityPotatoesEverywhere: `Орокінські Реактори/Каталізатори скрізь`,
@ -227,7 +232,7 @@ dict = {
cheats_baroFullyStocked: `Баро повністю укомплектований`, cheats_baroFullyStocked: `Баро повністю укомплектований`,
cheats_syndicateMissionsRepeatable: `Повторювати місії синдиката`, cheats_syndicateMissionsRepeatable: `Повторювати місії синдиката`,
cheats_unlockAllProfitTakerStages: `Розблокувати всі етапи Привласнювачки`, cheats_unlockAllProfitTakerStages: `Розблокувати всі етапи Привласнювачки`,
cheats_unlockSuccInventory: `Успішно. Зверніть увагу, що вам потрібно буде повторно синхронізувати своє спорядження, наприклад, за допомогою команди /sync в програмі bootstrapper, відвідавши Доджьо/Реле або повторно увійшовши в гру.`, cheats_unlockSuccInventory: `Успішно. Зверніть увагу, що вам потрібно буде повторно синхронізувати своє спорядження, наприклад, за допомогою команди завантажувача /sync у чаті гри, відвідавши Доджьо/Реле або повторно увійшовши в гру.`,
cheats_instantFinishRivenChallenge: `Миттєве завершення випробування модифікатора Розколу`, cheats_instantFinishRivenChallenge: `Миттєве завершення випробування модифікатора Розколу`,
cheats_instantResourceExtractorDrones: `Миттєво добуваючі Дрони-видобувачі`, cheats_instantResourceExtractorDrones: `Миттєво добуваючі Дрони-видобувачі`,
cheats_noResourceExtractorDronesDamage: `Без шкоди по Дронам-видобувачам`, cheats_noResourceExtractorDronesDamage: `Без шкоди по Дронам-видобувачам`,
@ -257,12 +262,12 @@ dict = {
cheats_changeButton: `Змінити`, cheats_changeButton: `Змінити`,
cheats_markAllAsRead: `Помітити всі вхідні як прочитані`, cheats_markAllAsRead: `Помітити всі вхідні як прочитані`,
cheats_finishInvasionsInOneMission: `Завершувати вторгнення за одну місію`, cheats_finishInvasionsInOneMission: `Завершувати вторгнення за одну місію`,
cheats_nemesisHenchmenKillsMultiplierGrineer: `[UNTRANSLATED] Rage Progess Multiplier (Grineer)`, cheats_nemesisHenchmenKillsMultiplierGrineer: `Множник прогресу люті (Ґрінери)`,
cheats_nemesisHenchmenKillsMultiplierCorpus: `[UNTRANSLATED] Rage Progess Multiplier (Corpus)`, cheats_nemesisHenchmenKillsMultiplierCorpus: `Множник прогресу люті (Корпус)`,
cheats_nemesisAntivirusGainMultiplier: `[UNTRANSLATED] Antivirus Progress Multiplier`, cheats_nemesisAntivirusGainMultiplier: `Мультиплікатор прогресу антивіруса`,
cheats_nemesisHintProgressMultiplierGrineer: `[UNTRANSLATED] Hint Progress Multiplier (Grineer)`, cheats_nemesisHintProgressMultiplierGrineer: `Множник прогресу підсказки (Ґрінери)`,
cheats_nemesisHintProgressMultiplierCorpus: `[UNTRANSLATED] Hint Progress Multiplier (Corpus)`, cheats_nemesisHintProgressMultiplierCorpus: `Множник прогресу підсказки (Корпус)`,
cheats_nemesisExtraWeapon: `[UNTRANSLATED] Extra Nemesis Weapon / Token On Vanquish (0 to disable)`, cheats_nemesisExtraWeapon: `Додаткова зброя/Жива сердцевина за перемогу над Недругом (0 для вимкнення)`,
worldState: `Стан світу`, worldState: `Стан світу`,
worldState_creditBoost: `Глобальне посилення Кредитів`, worldState_creditBoost: `Глобальне посилення Кредитів`,
@ -284,6 +289,7 @@ dict = {
worldState_hallowedFlame: `Священне полум'я`, worldState_hallowedFlame: `Священне полум'я`,
worldState_hallowedNightmares: `Священні жахіття`, worldState_hallowedNightmares: `Священні жахіття`,
worldState_hallowedNightmaresRewards: `Нагороди Священних жахіть`, worldState_hallowedNightmaresRewards: `Нагороди Священних жахіть`,
worldState_naberusNights: `Наберові ночі`,
worldState_proxyRebellion: `Повстання роботів`, worldState_proxyRebellion: `Повстання роботів`,
worldState_proxyRebellionRewards: `Нагороди Повстання роботів`, worldState_proxyRebellionRewards: `Нагороди Повстання роботів`,
worldState_bellyOfTheBeast: `У лігві звіра`, worldState_bellyOfTheBeast: `У лігві звіра`,

View File

@ -1,4 +1,4 @@
// Chinese translation by meb154, bishan178, nyaoouo, qianlishun, CrazyZhang, Corvus, & qingchun // Chinese translation by meb154, bishan178, nyaoouo, qianlishun, CrazyZhang, Corvus, qingchun
dict = { dict = {
general_inventoryUpdateNote: `注意: 要在游戏中查看更改,您需要重新同步库存,例如使用客户端的 /sync 命令,访问道场/中继站或重新登录.`, general_inventoryUpdateNote: `注意: 要在游戏中查看更改,您需要重新同步库存,例如使用客户端的 /sync 命令,访问道场/中继站或重新登录.`,
general_inventoryUpdateNoteGameWs: `[UNTRANSLATED] Note: You may need to reopen any menu you are on for changes to be reflected.`, general_inventoryUpdateNoteGameWs: `[UNTRANSLATED] Note: You may need to reopen any menu you are on for changes to be reflected.`,
@ -75,6 +75,10 @@ dict = {
code_funded: `[UNTRANSLATED] Funded`, code_funded: `[UNTRANSLATED] Funded`,
code_replays: `[UNTRANSLATED] Replays`, code_replays: `[UNTRANSLATED] Replays`,
code_stalker: `追猎者`, code_stalker: `追猎者`,
code_cutName: `[UNTRANSLATED] Cut |INDEX|`,
code_drifterBeardName: `漂泊者胡须 |INDEX|`,
code_drifterFaceName: `漂泊者面部 |INDEX|`,
code_operatorFaceName: `指挥官面部 |INDEX|`,
code_succChange: `更改成功`, code_succChange: `更改成功`,
code_requiredInvigorationUpgrade: `[UNTRANSLATED] You must select both an offensive & utility upgrade.`, code_requiredInvigorationUpgrade: `[UNTRANSLATED] You must select both an offensive & utility upgrade.`,
login_description: `使用您的 OpenWF 账户凭证登录(与游戏内连接本服务器时使用的昵称相同)`, login_description: `使用您的 OpenWF 账户凭证登录(与游戏内连接本服务器时使用的昵称相同)`,
@ -112,6 +116,7 @@ dict = {
inventory_boosters: `加成器`, inventory_boosters: `加成器`,
inventory_flavourItems: `<abbr title="动作表情、浮印、调色板等">装饰物品</abbr>`, inventory_flavourItems: `<abbr title="动作表情、浮印、调色板等">装饰物品</abbr>`,
inventory_shipDecorations: `飞船装饰`, inventory_shipDecorations: `飞船装饰`,
inventory_weaponSkins: `外观`,
inventory_bulkAddSuits: `添加缺失战甲`, inventory_bulkAddSuits: `添加缺失战甲`,
inventory_bulkAddWeapons: `添加缺失武器`, inventory_bulkAddWeapons: `添加缺失武器`,
inventory_bulkAddSpaceSuits: `添加缺失载具`, inventory_bulkAddSpaceSuits: `添加缺失载具`,
@ -121,6 +126,7 @@ dict = {
inventory_bulkAddFlavourItems: `[UNTRANSLATED] Add Missing Flavour Items`, inventory_bulkAddFlavourItems: `[UNTRANSLATED] Add Missing Flavour Items`,
inventory_bulkAddShipDecorations: `[UNTRANSLATED] Add Missing Ship Decorations`, inventory_bulkAddShipDecorations: `[UNTRANSLATED] Add Missing Ship Decorations`,
inventory_bulkAddEvolutionProgress: `添加缺失的灵化之源进度`, inventory_bulkAddEvolutionProgress: `添加缺失的灵化之源进度`,
inventory_bulkAddWeaponSkins: `[UNTRANSLATED] Add Missing Skins`,
inventory_bulkRankUpSuits: `所有战甲升满级`, inventory_bulkRankUpSuits: `所有战甲升满级`,
inventory_bulkRankUpWeapons: `所有武器升满级`, inventory_bulkRankUpWeapons: `所有武器升满级`,
inventory_bulkRankUpSpaceSuits: `所有载具升满级`, inventory_bulkRankUpSpaceSuits: `所有载具升满级`,
@ -193,7 +199,7 @@ dict = {
cheats_skipTutorial: `跳过教程`, cheats_skipTutorial: `跳过教程`,
cheats_skipAllDialogue: `跳过所有对话`, cheats_skipAllDialogue: `跳过所有对话`,
cheats_unlockAllScans: `解锁所有扫描`, cheats_unlockAllScans: `解锁所有扫描`,
cheats_unlockSuccRelog: `[UNTRANSLATED] Success. Please that you'll need to relog for the client to refresh this.`, cheats_unlockSuccRelog: `[UNTRANSLATED] Success. Please note that you'll need to relog for the client to refresh this.`,
cheats_unlockAllMissions: `解锁所有星图`, cheats_unlockAllMissions: `解锁所有星图`,
cheats_unlockAllMissions_ok: `操作成功.请注意,您需要进入道场/中继站或重新登录以刷新星图数据.`, cheats_unlockAllMissions_ok: `操作成功.请注意,您需要进入道场/中继站或重新登录以刷新星图数据.`,
cheats_infiniteCredits: `无限现金`, cheats_infiniteCredits: `无限现金`,
@ -209,7 +215,6 @@ dict = {
cheats_dontSubtractVoidTraces: `虚空光体无消耗`, cheats_dontSubtractVoidTraces: `虚空光体无消耗`,
cheats_dontSubtractConsumables: `消耗物品使用时无损耗`, cheats_dontSubtractConsumables: `消耗物品使用时无损耗`,
cheats_unlockAllShipFeatures: `解锁所有飞船功能`, cheats_unlockAllShipFeatures: `解锁所有飞船功能`,
cheats_unlockAllSkins: `解锁所有外观`,
cheats_unlockAllCapturaScenes: `解锁所有Captura场景`, cheats_unlockAllCapturaScenes: `解锁所有Captura场景`,
cheats_universalPolarityEverywhere: `全局万用极性`, cheats_universalPolarityEverywhere: `全局万用极性`,
cheats_unlockDoubleCapacityPotatoesEverywhere: `全物品自带Orokin反应堆`, cheats_unlockDoubleCapacityPotatoesEverywhere: `全物品自带Orokin反应堆`,
@ -227,7 +232,7 @@ dict = {
cheats_baroFullyStocked: `虚空商人贩卖所有商品`, cheats_baroFullyStocked: `虚空商人贩卖所有商品`,
cheats_syndicateMissionsRepeatable: `集团任务可重复完成`, cheats_syndicateMissionsRepeatable: `集团任务可重复完成`,
cheats_unlockAllProfitTakerStages: `解锁利润收割者圆蛛所有阶段`, cheats_unlockAllProfitTakerStages: `解锁利润收割者圆蛛所有阶段`,
cheats_unlockSuccInventory: `[UNTRANSLATED] Success. Please note that you'll need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging..`, 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_instantFinishRivenChallenge: `立即完成裂罅挑战`, cheats_instantFinishRivenChallenge: `立即完成裂罅挑战`,
cheats_instantResourceExtractorDrones: `资源无人机即时完成`, cheats_instantResourceExtractorDrones: `资源无人机即时完成`,
cheats_noResourceExtractorDronesDamage: `资源无人机不会损毁`, cheats_noResourceExtractorDronesDamage: `资源无人机不会损毁`,
@ -284,6 +289,7 @@ dict = {
worldState_hallowedFlame: `万圣之焰`, worldState_hallowedFlame: `万圣之焰`,
worldState_hallowedNightmares: `万圣噩梦`, worldState_hallowedNightmares: `万圣噩梦`,
worldState_hallowedNightmaresRewards: `万圣噩梦奖励设置`, worldState_hallowedNightmaresRewards: `万圣噩梦奖励设置`,
worldState_naberusNights: `[UNTRANSLATED] Nights of Naberus`,
worldState_proxyRebellion: `机械叛乱`, worldState_proxyRebellion: `机械叛乱`,
worldState_proxyRebellionRewards: `机械叛乱奖励设置`, worldState_proxyRebellionRewards: `机械叛乱奖励设置`,
worldState_bellyOfTheBeast: `兽之腹`, worldState_bellyOfTheBeast: `兽之腹`,