forked from OpenWF/SpaceNinjaServer
		
	Compare commits
	
		
			42 Commits
		
	
	
		
			fc0090e4c7
			...
			317fb8324f
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 317fb8324f | |||
| 05037a3ca2 | |||
| c4434b87c1 | |||
| f665a8e585 | |||
| 6b24a20ee1 | |||
| 8addb1e4f6 | |||
| 8549afd14e | |||
| a83d96289f | |||
| 6fa80dab38 | |||
| f5c1b83598 | |||
| 30f380f37e | |||
| 0f7a85db59 | |||
| 43bc12713a | |||
| 6022bf97b5 | |||
| 159e151dc0 | |||
| 56954260c8 | |||
| c535044af8 | |||
| f5146be129 | |||
| d38ec06ed6 | |||
| 060f65900f | |||
| 66d3057d40 | |||
| b14a5925df | |||
| 9da47c406a | |||
| 09065bdb4e | |||
| 8f04fc5fdf | |||
| 230ee5f638 | |||
| 21db6ce265 | |||
| 1ecf53c96b | |||
| e67ef63b77 | |||
| 5772ebe746 | |||
| 0136e4d152 | |||
| 8b3ee4b4f5 | |||
| 6e8800f048 | |||
| d65a667acd | |||
| c6a3e86d2b | |||
| a8e41c95e7 | |||
| 9426359370 | |||
| e5247700df | |||
| 1c3f1e2276 | |||
| 7710e7c13f | |||
| a64c5ea3c1 | |||
| 17e1eb86dd | 
@ -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`.
 | 
				
			||||||
 | 
				
			|||||||
@ -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,
 | 
				
			||||||
 | 
				
			|||||||
@ -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 {
 | 
				
			||||||
 | 
				
			|||||||
@ -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();
 | 
				
			||||||
 | 
				
			|||||||
@ -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] == "{") {
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -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));
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
 | 
				
			|||||||
@ -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)[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -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 {
 | 
				
			||||||
 | 
				
			|||||||
@ -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:
 | 
				
			||||||
 | 
				
			|||||||
@ -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();
 | 
				
			||||||
 | 
				
			|||||||
@ -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
 | 
				
			||||||
 | 
				
			|||||||
@ -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 });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -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();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -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,11 +300,19 @@ 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 (
 | 
				
			||||||
 | 
					            item.productCategory == "WeaponSkins" &&
 | 
				
			||||||
 | 
					            !uniqueName.startsWith("/Lotus/Types/Game/Lotus") && // Base Items
 | 
				
			||||||
 | 
					            !uniqueName.endsWith("ProjectileSkin") && // UnrealTournament ProjectileSkins
 | 
				
			||||||
 | 
					            !uniqueName.endsWith("Coat") // Frost Prime stuff
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            res.WeaponSkins.push({
 | 
				
			||||||
                uniqueName: uniqueName,
 | 
					                uniqueName: uniqueName,
 | 
				
			||||||
            name: getString(item.name, lang)
 | 
					                name: getString(item.name, lang),
 | 
				
			||||||
 | 
					                alwaysAvailable: item.alwaysAvailable
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (const [uniqueName, upgrade] of Object.entries(ExportUpgrades)) {
 | 
					    for (const [uniqueName, upgrade] of Object.entries(ExportUpgrades)) {
 | 
				
			||||||
        const mod: ListedItem = {
 | 
					        const mod: ListedItem = {
 | 
				
			||||||
 | 
				
			|||||||
@ -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
 | 
				
			||||||
                    });
 | 
					                    });
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
				
			|||||||
@ -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)));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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]);
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										8
									
								
								src/helpers/skinLookupTable.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/helpers/skinLookupTable.ts
									
									
									
									
									
										Normal 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;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -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");
 | 
				
			||||||
 | 
				
			|||||||
@ -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
 | 
				
			||||||
 | 
				
			|||||||
@ -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 => {
 | 
				
			||||||
 | 
				
			|||||||
@ -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 (
 | 
				
			||||||
 | 
				
			|||||||
@ -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 => {
 | 
				
			||||||
 | 
				
			|||||||
@ -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",
 | 
				
			||||||
 | 
				
			|||||||
@ -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 = [];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -139,11 +139,6 @@ 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")) {
 | 
					 | 
				
			||||||
                        logger.warn(
 | 
					 | 
				
			||||||
                            `unlockAllSkins does not work with favoriting items because you don't actually own it`
 | 
					 | 
				
			||||||
                        );
 | 
					 | 
				
			||||||
                    } else {
 | 
					 | 
				
			||||||
                    const inventoryItem = inventory.WeaponSkins.id(itemId);
 | 
					                    const inventoryItem = inventory.WeaponSkins.id(itemId);
 | 
				
			||||||
                    if (!inventoryItem) {
 | 
					                    if (!inventoryItem) {
 | 
				
			||||||
                        logger.warn(`inventory item WeaponSkins not found with id ${itemId}`);
 | 
					                        logger.warn(`inventory item WeaponSkins not found with id ${itemId}`);
 | 
				
			||||||
@ -156,7 +151,6 @@ export const handleInventoryItemConfigChange = async (
 | 
				
			|||||||
                        inventoryItem.IsNew = itemConfigEntries.IsNew;
 | 
					                        inventoryItem.IsNew = itemConfigEntries.IsNew;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            case "LotusCustomization": {
 | 
					            case "LotusCustomization": {
 | 
				
			||||||
 | 
				
			|||||||
@ -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" },
 | 
				
			||||||
 | 
				
			|||||||
@ -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>
 | 
				
			||||||
 | 
				
			|||||||
@ -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,21 +890,17 @@ 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");
 | 
				
			||||||
                                a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M214.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 141.2V448c0 17.7 14.3 32 32 32s32-14.3 32-32V141.2L329.4 246.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z"/></svg>`;
 | 
					                                a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M214.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 141.2V448c0 17.7 14.3 32 32 32s32-14.3 32-32V141.2L329.4 246.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z"/></svg>`;
 | 
				
			||||||
@ -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;
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
				
			|||||||
@ -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`,
 | 
				
			||||||
 | 
				
			|||||||
@ -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`,
 | 
				
			||||||
 | 
				
			|||||||
@ -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`,
 | 
				
			||||||
 | 
				
			|||||||
@ -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`,
 | 
				
			||||||
 | 
				
			|||||||
@ -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: `Чрево зверя`,
 | 
				
			||||||
 | 
				
			|||||||
@ -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: `У лігві звіра`,
 | 
				
			||||||
 | 
				
			|||||||
@ -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: `兽之腹`,
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user