Compare commits

..

9 Commits

Author SHA1 Message Date
fc0090e4c7 chore: add a helper for skinLookupTable
All checks were successful
Build / build (pull_request) Successful in 1m15s
2025-09-30 09:18:37 +02:00
3a3d5de5dc move migration to cleanupInventory
All checks were successful
Build / build (pull_request) Successful in 1m10s
2025-09-29 21:21:16 +02:00
f2d3837dc0 give all missing skins
All checks were successful
Build / build (pull_request) Successful in 1m10s
2025-09-29 17:36:04 +02:00
186d0559ec typo
All checks were successful
Build / build (pull_request) Successful in 56s
2025-09-27 22:03:17 +02:00
35971b914c Update saveLoadoutService.ts
Some checks failed
Build / build (pull_request) Failing after 56s
2025-09-27 22:00:48 +02:00
a9f994ea4c readd unlockAllSkins as account cheat
Some checks failed
Build / build (pull_request) Failing after 57s
2025-09-27 21:59:41 +02:00
785a83a2ea Update inventoryController.ts
All checks were successful
Build / build (pull_request) Successful in 1m11s
2025-09-27 03:30:56 +02:00
4f2d636a4a add warning for other loadouts with skins
All checks were successful
Build / build (pull_request) Successful in 1m8s
2025-09-27 03:20:16 +02:00
a31e293a6e feat(webui): skins
All checks were successful
Build / build (pull_request) Successful in 1m13s
Re #2361
2025-09-27 02:13:18 +02:00
32 changed files with 240 additions and 388 deletions

View File

@ -35,5 +35,5 @@ SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [confi
- `RadioLegion2Syndicate` for The Emissary
- `RadioLegionIntermissionSyndicate` for Intermission I
- `RadioLegionSyndicate` for The Wolf of Saturn Six
- `worldState.allTheFissures` can be set to `normal` or `hard` to enable all fissures either in normal or steel path, respectively.
- `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`.

View File

@ -37,7 +37,6 @@
"anniversary": null,
"hallowedNightmares": false,
"hallowedNightmaresRewardsOverride": 0,
"naberusNightsOverride": null,
"proxyRebellion": false,
"proxyRebellionRewardsOverride": 0,
"galleonOfGhouls": 0,

View File

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

View File

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

View File

@ -1,13 +1,12 @@
import type { Request, RequestHandler } from "express";
import type { RequestHandler } from "express";
import { Inbox } from "../../models/inboxModel.ts";
import {
createMessage,
createNewEventMessages,
deleteAllMessagesRead,
deleteAllMessagesReadNonCin,
deleteMessageRead,
getAllMessagesSorted,
getMessage,
type IMessageCreationTemplate
getMessage
} from "../../services/inboxService.ts";
import { getAccountForRequest, getAccountFromSuffixedName, getSuffixedName } from "../../services/loginService.ts";
import {
@ -22,9 +21,6 @@ import { ExportFlavour } from "warframe-public-export-plus";
import { handleStoreItemAcquisition } from "../../services/purchaseService.ts";
import { fromStoreItem, isStoreItem } from "../../services/itemDataService.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) => {
const { deleteId, lastMessage: latestClientMessageId, messageId } = req.query;
@ -35,11 +31,11 @@ export const inboxController: RequestHandler = async (req, res) => {
if (deleteId) {
if (deleteId === "DeleteAllRead") {
await deleteAllMessagesRead(accountId);
} else if (deleteId === "DeleteAllReadNonCin") {
await deleteAllMessagesReadNonCin(accountId);
} else {
await deleteMessageRead(parseOid(deleteId as string));
res.status(200).end();
return;
}
await deleteMessageRead(parseOid(deleteId as string));
res.status(200).end();
} else if (messageId) {
const message = await getMessage(parseOid(messageId as string));
@ -138,119 +134,6 @@ 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
const parseOid = (oid: string): string => {
if (oid[0] == "{") {

View File

@ -446,9 +446,6 @@ export const getInventoryResponse = async (
toLegacyOid(id);
}
}
if (inventoryResponse.GuildId) {
toLegacyOid(inventoryResponse.GuildId);
}
}
}
}

View File

@ -9,9 +9,6 @@ import type { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "../../
import { logger } from "../../utils/logger.ts";
import { version_compare } from "../../helpers/inventoryHelpers.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) => {
const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object
@ -79,24 +76,6 @@ export const loginController: RequestHandler = async (request, response) => {
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));
};

View File

@ -129,22 +129,14 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
res.json(deltas);
} else if (missionReport.RewardInfo) {
logger.debug(`classic mission completion, sending everything`);
const inventoryResponse = await getInventoryResponse(
inventory,
"xpBasedLevelCapDisabled" in req.query,
account.BuildLabel
);
const inventoryResponse = await getInventoryResponse(inventory, true, account.BuildLabel);
res.json({
InventoryJson: JSON.stringify(inventoryResponse),
...deltas
} satisfies IMissionInventoryUpdateResponse);
} else {
logger.debug(`no reward info, assuming this wasn't a mission completion and we should just sync inventory`);
const inventoryResponse = await getInventoryResponse(
inventory,
"xpBasedLevelCapDisabled" in req.query,
account.BuildLabel
);
const inventoryResponse = await getInventoryResponse(inventory, true, account.BuildLabel);
res.json({
InventoryJson: JSON.stringify(inventoryResponse)
} satisfies IMissionInventoryUpdateResponseBackToDryDock);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import type { RequestHandler } from "express";
import { getAccountForRequest } from "../../services/loginService.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { Account, Ignore } from "../../models/loginModel.ts";
import { Inbox } from "../../models/inboxModel.ts";
import { Inventory } from "../../models/inventoryModels/inventoryModel.ts";
@ -12,44 +12,33 @@ import { Leaderboard } from "../../models/leaderboardModel.ts";
import { deleteGuild } from "../../services/guildService.ts";
import { Friendship } from "../../models/friendModel.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) => {
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();
}
}
const accountId = await getAccountIdForRequest(req);
// If account is the founding warlord of a guild, delete that guild as well.
const guildMember = await GuildMember.findOne({ accountId: account._id, rank: 0, status: 0 });
const guildMember = await GuildMember.findOne({ accountId, rank: 0, status: 0 });
if (guildMember) {
await deleteGuild(guildMember.guildId);
}
await Promise.all([
Account.deleteOne({ _id: account._id }),
Friendship.deleteMany({ owner: account._id }),
Friendship.deleteMany({ friend: account._id }),
GuildMember.deleteMany({ accountId: account._id }),
Ignore.deleteMany({ ignorer: account._id }),
Ignore.deleteMany({ ignoree: account._id }),
Inbox.deleteMany({ ownerId: account._id }),
Inventory.deleteOne({ accountOwnerId: account._id }),
Leaderboard.deleteMany({ ownerId: account._id }),
Loadout.deleteOne({ loadoutOwnerId: account._id }),
PersonalRooms.deleteOne({ personalRoomsOwnerId: account._id }),
Ship.deleteMany({ ShipOwnerId: account._id }),
Stats.deleteOne({ accountOwnerId: account._id })
Account.deleteOne({ _id: accountId }),
Friendship.deleteMany({ owner: accountId }),
Friendship.deleteMany({ friend: accountId }),
GuildMember.deleteMany({ accountId: accountId }),
Ignore.deleteMany({ ignorer: accountId }),
Ignore.deleteMany({ ignoree: accountId }),
Inbox.deleteMany({ ownerId: accountId }),
Inventory.deleteOne({ accountOwnerId: accountId }),
Leaderboard.deleteMany({ ownerId: accountId }),
Loadout.deleteOne({ loadoutOwnerId: accountId }),
PersonalRooms.deleteOne({ personalRoomsOwnerId: accountId }),
Ship.deleteMany({ ShipOwnerId: accountId }),
Stats.deleteOne({ accountOwnerId: accountId })
]);
sendWsBroadcastTo(account._id.toString(), { logged_out: true });
sendWsBroadcastTo(accountId, { logged_out: true });
res.end();
};

View File

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

View File

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

View File

@ -47,7 +47,6 @@ export interface IConfig {
anniversary?: number;
hallowedNightmares?: boolean;
hallowedNightmaresRewardsOverride?: number;
naberusNightsOverride?: boolean;
proxyRebellion?: boolean;
proxyRebellionRewardsOverride?: number;
galleonOfGhouls?: number;

View File

@ -22,7 +22,7 @@ import type {
ITechProjectDatabase
} from "../types/guildTypes.ts";
import { GuildPermission } from "../types/guildTypes.ts";
import { toMongoDate, toOid, toOid2, version_compare } from "../helpers/inventoryHelpers.ts";
import { toMongoDate, toOid, toOid2 } from "../helpers/inventoryHelpers.ts";
import type { Types } from "mongoose";
import type { IDojoBuild, IDojoResearch } from "warframe-public-export-plus";
import { ExportDojoRecipes, ExportResources } from "warframe-public-export-plus";
@ -68,15 +68,9 @@ export const getGuildClient = async (
let missingEntry = true;
const dataFillInPromises: Promise<void>[] = [];
for (const guildMember of guildMembers) {
// Use 1-based indexing for clan ranks for versions before U24. In my testing, 2018.06.14.23.21 and below used 1-based indexing and 2019.04.04.21.31 and above used 0-based indexing. I didn't narrow it down further, but I think U24 is a good spot for them to have changed it.
let rankBase = 0;
if (account.BuildLabel && version_compare(account.BuildLabel, "2018.11.08.14.45") < 0) {
rankBase += 1;
}
const member: IGuildMemberClient = {
_id: toOid2(guildMember.accountId, account.BuildLabel),
Rank: guildMember.rank + rankBase,
Rank: guildMember.rank,
Status: guildMember.status,
Note: guildMember.RequestMsg,
RequestExpiry: guildMember.RequestExpiry ? toMongoDate(guildMember.RequestExpiry) : undefined

View File

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

View File

@ -1,6 +1,11 @@
import type { IMessageDatabase } from "../models/inboxModel.ts";
import { Inbox } from "../models/inboxModel.ts";
import type { HydratedDocument, Types } from "mongoose";
import { getAccountForRequest } from "./loginService.ts";
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>[]> => {
const inbox = await Inbox.find({ ownerId: accountId }).sort({ date: -1 });
@ -24,8 +29,117 @@ export const deleteAllMessagesRead = async (accountId: string): Promise<void> =>
await Inbox.deleteMany({ ownerId: accountId, r: true });
};
export const deleteAllMessagesReadNonCin = async (accountId: string): Promise<void> => {
await Inbox.deleteMany({ ownerId: accountId, r: true, cinematic: null });
export 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();
};
export const createMessage = async (

View File

@ -309,6 +309,9 @@ export const addMissionInventoryUpdates = async (
}
break;
}
case "Missions":
addMissionComplete(inventory, value);
break;
case "LastRegionPlayed":
if (!(config.unfaithfulBugFixes?.ignore1999LastRegionPlayed && value === "1999MapName")) {
inventory.LastRegionPlayed = value;
@ -1205,9 +1208,6 @@ export const addMissionRewards = async (
if (missions && missions.Tag in ExportRegions) {
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
if (isEligibleForCreditReward(rewardInfo, missions, 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"
},
"/Lotus/Types/Keys/LanternEndlessEventKeyA": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/GenericEventRewardMsgDesc",
sub: "/Lotus/Language/G1Quests/GenericTacAlertRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
},
"/Lotus/Types/Keys/LanternEndlessEventKeyB": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/GenericEventRewardMsgDesc",
sub: "/Lotus/Language/G1Quests/GenericTacAlertRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
},
"/Lotus/Types/Keys/LanternEndlessEventKeyD": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/GenericEventRewardMsgDesc",
sub: "/Lotus/Language/G1Quests/GenericTacAlertRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
},
"/Lotus/Types/Keys/LanternEndlessEventKeyC": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/GenericEventRewardMsgDesc",
sub: "/Lotus/Language/G1Quests/GenericTacAlertRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
},
"/Lotus/Types/Keys/TacAlertKeyHalloween": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsBonusBody",
sub: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsBonusTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
},
"/Lotus/Types/Keys/TacAlertKeyHalloweenBonus": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsBody",
sub: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
},
"/Lotus/Types/Keys/TacAlertKeyHalloweenTimeAttack": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsBody",
sub: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
},
"/Lotus/Types/Keys/TacAlertKeyProxyRebellionOne": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/RazorbackArmadaRewardBody",
sub: "/Lotus/Language/G1Quests/GenericTacAlertSmallRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["CREDIT_REWARD"]
},
"/Lotus/Types/Keys/TacAlertKeyProxyRebellionTwo": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/RazorbackArmadaRewardBody",
sub: "/Lotus/Language/G1Quests/GenericTacAlertSmallRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["CREDIT_REWARD"]
},
"/Lotus/Types/Keys/TacAlertKeyProxyRebellionThree": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/RazorbackArmadaRewardBody",
sub: "/Lotus/Language/G1Quests/GenericTacAlertSmallRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["CREDIT_REWARD"]
},
"/Lotus/Types/Keys/TacAlertKeyProxyRebellionFour": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/GenericTacAlertBadgeRewardMsgDesc",
sub: "/Lotus/Language/G1Quests/GenericTacAlertBadgeRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png"
},
"/Lotus/Types/Keys/TacAlertKeyProjectNightwatchEasy": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/ProjectNightwatchRewardMsgA",
sub: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionOneTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["CREDIT_REWARD"]
},
"/Lotus/Types/Keys/TacAlertKeyProjectNightwatch": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionRewardBody",
sub: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionTwoTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png"
},
"/Lotus/Types/Keys/TacAlertKeyProjectNightwatchHard": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionRewardBody",
sub: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionThreeTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png"
},
"/Lotus/Types/Keys/TacAlertKeyProjectNightwatchBonus": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionRewardBody",
sub: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionFourTitle",
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"
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2019E": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgB",
sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleB",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2020F": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgC",
sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleB",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2024ChallengeModeA": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgD",
sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleD",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2017C": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2019RewardMsgC",
sub: "/Lotus/Language/Messages/Anniversary2019MissionTitleC",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2020H": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2020RewardMsgH",
sub: "/Lotus/Language/Messages/Anniversary2020MissionTitleH",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2022J": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2022RewardMsgJ",
sub: "/Lotus/Language/Messages/Anniversary2022MissionTitleJ",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2025D": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2025RewardMsgB",
sub: "/Lotus/Language/Messages/Anniversary2025MissionTitleB",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2025ChallengeModeA": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2025RewardMsgC",
sub: "/Lotus/Language/Messages/Anniversary2025MissionTitleC",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2020G": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2020RewardMsgG",
sub: "/Lotus/Language/Messages/Anniversary2020MissionTitleG",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2017B": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2019RewardMsgB",
sub: "/Lotus/Language/Messages/Anniversary2019MissionTitleB",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2017A": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2019RewardMsgA",
sub: "/Lotus/Language/Messages/Anniversary2019MissionTitleA",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2023K": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2025RewardMsgG",
sub: "/Lotus/Language/Messages/Anniversary2025MissionTitleG",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2025ChallengeModeB": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2025RewardMsgD",
sub: "/Lotus/Language/Messages/Anniversary2025MissionTitleD",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2025A": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2025RewardMsgA",
sub: "/Lotus/Language/Messages/Anniversary2025MissionTitleA",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2018D": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgG",
sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleG",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2025C": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgF",
sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleF",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2024L": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgA",
sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleA",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2024ChallengeModeB": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgE",
sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleE",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2021I": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgH",
sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleH",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2025B": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2025RewardMsgE",
sub: "/Lotus/Language/Messages/Anniversary2025MissionTitleE",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",

View File

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

View File

@ -280,14 +280,6 @@ 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 missionTypes = new Set();
@ -317,7 +309,7 @@ export const getSortie = (day: number): ISortie => {
"SORTIE_MODIFIER_BOW_ONLY"
];
if (i == 2 && willHaveAssassination) {
if (i == 2 && boss != "SORTIE_BOSS_CORRUPTED_VOR" && rng.randomInt(0, 2) == 2) {
const tileset = sortieTilesets[sortieBossNode[boss] as keyof typeof sortieTilesets] as TSortieTileset;
pushTilesetModifiers(modifiers, tileset);
@ -369,9 +361,7 @@ export const getSortie = (day: number): ISortie => {
Activation: { $date: { $numberLong: dayStart.toString() } },
Expiry: { $date: { $numberLong: dayEnd.toString() } },
Reward: "/Lotus/Types/Game/MissionDecks/SortieRewards",
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,
Seed: seed,
Boss: boss,
Variants: selectedNodes
};
@ -2514,37 +2504,6 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
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) {
worldState.Goals.push({
_id: { $oid: "67a5035c2a198564d62e165e" },

View File

@ -1222,14 +1222,6 @@
</select>
</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="flex-fill">
<label class="form-label" for="worldState.proxyRebellion" data-loc="worldState_proxyRebellion"></label>

View File

@ -877,9 +877,10 @@ function updateInventory() {
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
revalidateAuthz().then(async () => {
revalidateAuthz().then(() => {
const promises = [];
if (item.XP < maxXP) {
await addGearExp(category, item.ItemId.$oid, maxXP - item.XP);
promises.push(addGearExp(category, item.ItemId.$oid, maxXP - item.XP));
}
if ("exalted" in itemMap[item.ItemType]) {
for (const exaltedType of itemMap[item.ItemType].exalted) {
@ -890,16 +891,20 @@ function updateInventory() {
const exaltedCap =
itemMap[exaltedType]?.type == "weapons" ? 800_000 : 1_600_000;
if (exaltedItem.XP < exaltedCap) {
await addGearExp(
"SpecialItems",
exaltedItem.ItemId.$oid,
exaltedCap - exaltedItem.XP
promises.push(
addGearExp(
"SpecialItems",
exaltedItem.ItemId.$oid,
exaltedCap - exaltedItem.XP
)
);
}
}
}
}
updateInventory();
Promise.all(promises).then(() => {
updateInventory();
});
});
};
a.title = loc("code_maxRank");
@ -1280,7 +1285,7 @@ function updateInventory() {
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(doQuestUpdate, "setInactive", item.ItemType);
doQuestUpdate("setInactive", item.ItemType);
};
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>`;
@ -1291,7 +1296,7 @@ function updateInventory() {
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(doQuestUpdate, "resetKey", item.ItemType);
doQuestUpdate("resetKey", item.ItemType);
};
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>`;
@ -1302,7 +1307,7 @@ function updateInventory() {
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(doQuestUpdate, "completeKey", item.ItemType);
doQuestUpdate("completeKey", item.ItemType);
};
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>`;
@ -1313,7 +1318,7 @@ function updateInventory() {
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(doQuestUpdate, "prevStage", item.ItemType);
doQuestUpdate("prevStage", item.ItemType);
};
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>`;
@ -1328,7 +1333,7 @@ function updateInventory() {
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(doQuestUpdate, "nextStage", item.ItemType);
doQuestUpdate("nextStage", item.ItemType);
};
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>`;
@ -1340,7 +1345,7 @@ function updateInventory() {
a.onclick = function (event) {
event.preventDefault();
reAddToItemList(itemMap, "QuestKeys", item.ItemType);
debounce(doQuestUpdate, "deleteKey", item.ItemType);
doQuestUpdate("deleteKey", item.ItemType);
};
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>`;
@ -3273,16 +3278,13 @@ function doIntrinsicsUnlockAll() {
document.querySelectorAll("#account-cheats input[type=checkbox]").forEach(elm => {
elm.onchange = function () {
revalidateAuthz().then(() => {
const value = elm.checked;
$.post({
url: "/custom/setAccountCheat?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
key: elm.id,
value: value
value: elm.checked
})
}).done(() => {
elm.checked = value;
});
});
};
@ -3318,8 +3320,6 @@ document.querySelectorAll("#account-cheats .input-group").forEach(grp => {
key: input.id,
value: parseInt(value)
})
}).done(() => {
btn.value = value;
});
});
};

View File

@ -199,7 +199,7 @@ dict = {
cheats_skipTutorial: `Tutorial überspringen`,
cheats_skipAllDialogue: `Alle Dialoge überspringen`,
cheats_unlockAllScans: `Alle Scans freischalten`,
cheats_unlockSuccRelog: `[UNTRANSLATED] Success. Please note that you'll need to relog for the client to refresh this.`,
cheats_unlockSuccRelog: `[UNTRANSLATED] Success. Please that you'll need to relog for the client to refresh this.`,
cheats_unlockAllMissions: `Alle Missionen freischalten`,
cheats_unlockAllMissions_ok: `Erfolgreich. Bitte beachte, dass du ein Dojo/Relais besuchen oder dich neu einloggen musst, damit die Sternenkarte aktualisiert wird.`,
cheats_infiniteCredits: `Unendlich Credits`,
@ -232,7 +232,7 @@ dict = {
cheats_baroFullyStocked: `Baro hat volles Inventar`,
cheats_syndicateMissionsRepeatable: `Syndikat-Missionen wiederholbar`,
cheats_unlockAllProfitTakerStages: `Alle Profiteintreiber-Phasen freischalten`,
cheats_unlockSuccInventory: `[UNTRANSLATED] Success. Please note that you'll need to resync your inventory, e.g. using the bootstrapper's /sync command in game chat, visiting a dojo/relay, or relogging.`,
cheats_unlockSuccInventory: `[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_instantFinishRivenChallenge: `Riven-Mod Herausforderung sofort abschließen`,
cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`,
cheats_noResourceExtractorDronesDamage: `Kein Schaden für Ressourcen-Extraktor-Drohnen`,
@ -289,7 +289,6 @@ dict = {
worldState_hallowedFlame: `Geweihte Flamme`,
worldState_hallowedNightmares: `Geweihte Albträume`,
worldState_hallowedNightmaresRewards: `[UNTRANSLATED] Hallowed Nightmares Rewards`,
worldState_naberusNights: `[UNTRANSLATED] Nights of Naberus`,
worldState_proxyRebellion: `Proxy-Rebellion`,
worldState_proxyRebellionRewards: `[UNTRANSLATED] Proxy Rebellion Rewards`,
worldState_bellyOfTheBeast: `Das Innere der Bestie`,

View File

@ -1,5 +1,5 @@
dict = {
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_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_inventoryUpdateNoteGameWs: `Note: You may need to reopen any menu you are on for changes to be reflected.`,
general_addButton: `Add`,
general_setButton: `Set`,
@ -198,7 +198,7 @@ dict = {
cheats_skipTutorial: `Skip Tutorial`,
cheats_skipAllDialogue: `Skip All Dialogue`,
cheats_unlockAllScans: `Unlock All Scans`,
cheats_unlockSuccRelog: `Success. Please note that you'll need to relog for the client to refresh this.`,
cheats_unlockSuccRelog: `Success. Please that you'll need to relog for the client to refresh this.`,
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_infiniteCredits: `Infinite Credits`,
@ -231,7 +231,7 @@ dict = {
cheats_baroFullyStocked: `Baro Fully Stocked`,
cheats_syndicateMissionsRepeatable: `Syndicate Missions Repeatable`,
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 in game chat, 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, visiting a dojo/relay, or relogging..`,
cheats_instantFinishRivenChallenge: `Instant Finish Riven Challenge`,
cheats_instantResourceExtractorDrones: `Instant Resource Extractor Drones`,
cheats_noResourceExtractorDronesDamage: `No Resource Extractor Drones Damage`,
@ -288,7 +288,6 @@ dict = {
worldState_hallowedFlame: `Hallowed Flame`,
worldState_hallowedNightmares: `Hallowed Nightmares`,
worldState_hallowedNightmaresRewards: `Hallowed Nightmares Rewards`,
worldState_naberusNights: `Nights of Naberus`,
worldState_proxyRebellion: `Proxy Rebellion`,
worldState_proxyRebellionRewards: `Proxy Rebellion Rewards`,
worldState_bellyOfTheBeast: `Belly of the Beast`,

View File

@ -1,4 +1,4 @@
// Spanish translation by hxedcl, Slayer55555
// Spanish translation by hxedcl
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_inventoryUpdateNoteGameWs: `Nota: Puede que necesites reabrir cualquier menú en el que te encuentres para que los cambios se reflejen.`,
@ -289,7 +289,6 @@ dict = {
worldState_hallowedFlame: `Llama Sagrada`,
worldState_hallowedNightmares: `Pesadillas Sagradas`,
worldState_hallowedNightmaresRewards: `Recompensas de Pesadillas Sagradas`,
worldState_naberusNights: `Noches de Naberus`,
worldState_proxyRebellion: `Rebelión Proxy`,
worldState_proxyRebellionRewards: `Recompensas de Rebelión Proxy`,
worldState_bellyOfTheBeast: `Vientre de la Bestia`,

View File

@ -289,7 +289,6 @@ dict = {
worldState_hallowedFlame: `Flamme Hantée`,
worldState_hallowedNightmares: `Cauchemars Hantés`,
worldState_hallowedNightmaresRewards: `Récompenses Flamme Hantée Cauchemar`,
worldState_naberusNights: `[UNTRANSLATED] Nights of Naberus`,
worldState_proxyRebellion: `Rébellion Proxy`,
worldState_proxyRebellionRewards: `Récompenses Rébellion Proxy`,
worldState_bellyOfTheBeast: `Ventre de la Bête`,

View File

@ -1,6 +1,6 @@
// Russian translation by AMelonInsideLemon, LoseFace
dict = {
general_inventoryUpdateNote: `Примечание: Чтобы увидеть изменения в игре, вам нужно повторно синхронизировать свой инвентарь, например, используя команду загрузчика /sync в чате игры, посетив Додзё/Реле или перезагрузив игру.`,
general_inventoryUpdateNote: `Примечание: Чтобы увидеть изменения в игре, вам нужно повторно синхронизировать свой инвентарь, например, используя команду /sync в программе bootstrapper, посетив Додзё/Реле или перезагрузив игру.`,
general_inventoryUpdateNoteGameWs: `Примечание: для того, чтобы изменения вступили в силу, может потребоваться повторно открыть меню, в котором вы находитесь.`,
general_addButton: `Добавить`,
general_setButton: `Установить`,
@ -45,7 +45,7 @@ dict = {
code_rank: `Ранг`,
code_rankUp: `Повысить ранг`,
code_rankDown: `Понизить ранг`,
code_unlockLevelCap: `Разблокировать ограничение уровня`,
code_unlockLevelCap: `[UNTRANSLATED] Unlock level cap`,
code_count: `Количество`,
code_focusAllUnlocked: `Все школы Фокуса уже разблокированы.`,
code_focusUnlocked: `Разблокировано |COUNT| новых школ Фокуса! Для отображения изменений в игре потребуется обновление инвентаря. Посещение навигации — самый простой способ этого добиться.`,
@ -232,7 +232,7 @@ dict = {
cheats_baroFullyStocked: `Баро полностью укомплектован`,
cheats_syndicateMissionsRepeatable: `Повторять миссии синдиката`,
cheats_unlockAllProfitTakerStages: `Разблокировать все этапы Сферы извлечения прибыли`,
cheats_unlockSuccInventory: `Успех. Обратите внимание, что вам необходимо будет повторно синхронизировать свой инвентарь, например, с помощью команды загрузчика /sync в чате игры, посетив Додзё/Реле или повторно войдя в игру.`,
cheats_unlockSuccInventory: `Успех. Обратите внимание, что вам необходимо будет повторно синхронизировать свой инвентарь, например, с помощью команды /sync в программе bootstrapper, посетив Додзё/Реле или повторно войдя в игру.`,
cheats_instantFinishRivenChallenge: `Мгновенное завершение испытания мода Разлома`,
cheats_instantResourceExtractorDrones: `Мгновенно добывающие Дроны-сборщики`,
cheats_noResourceExtractorDronesDamage: `Без урона по Дронам-сборщикам`,
@ -262,12 +262,12 @@ dict = {
cheats_changeButton: `Изменить`,
cheats_markAllAsRead: `Пометить все входящие как прочитанные`,
cheats_finishInvasionsInOneMission: `Завершать вторжение за одну миссию`,
cheats_nemesisHenchmenKillsMultiplierGrineer: `Мультипликатор прогресса ярости (Гринир)`,
cheats_nemesisHenchmenKillsMultiplierCorpus: `Мультипликатор прогресса ярости (Корпус)`,
cheats_nemesisAntivirusGainMultiplier: `Мультипликатор прогресса антивируса`,
cheats_nemesisHintProgressMultiplierGrineer: `Мультипликатор прогресса подсказки (Гринир)`,
cheats_nemesisHintProgressMultiplierCorpus: `Мультипликатор прогресса подсказки (Корпус)`,
cheats_nemesisExtraWeapon: `Дополнительное оружие/активный Кардиомиоцит за победу над Противником (0 для отключения)`,
cheats_nemesisHenchmenKillsMultiplierGrineer: `[UNTRANSLATED] Rage Progess Multiplier (Grineer)`,
cheats_nemesisHenchmenKillsMultiplierCorpus: `[UNTRANSLATED] Rage Progess Multiplier (Corpus)`,
cheats_nemesisAntivirusGainMultiplier: `[UNTRANSLATED] Antivirus Progress Multiplier`,
cheats_nemesisHintProgressMultiplierGrineer: `[UNTRANSLATED] Hint Progress Multiplier (Grineer)`,
cheats_nemesisHintProgressMultiplierCorpus: `[UNTRANSLATED] Hint Progress Multiplier (Corpus)`,
cheats_nemesisExtraWeapon: `[UNTRANSLATED] Extra Nemesis Weapon / Token On Vanquish (0 to disable)`,
worldState: `Состояние мира`,
worldState_creditBoost: `Глобальный бустер Кредитов`,
@ -289,7 +289,6 @@ dict = {
worldState_hallowedFlame: `Священное пламя`,
worldState_hallowedNightmares: `Священные кошмары`,
worldState_hallowedNightmaresRewards: `Награды Священных кошмаров`,
worldState_naberusNights: `Ночи Наберуса`,
worldState_proxyRebellion: `Восстание роботов`,
worldState_proxyRebellionRewards: `Награды Восстания роботов`,
worldState_bellyOfTheBeast: `Чрево зверя`,

View File

@ -1,6 +1,6 @@
// Ukrainian translation by LoseFace
dict = {
general_inventoryUpdateNote: `Пам'ятка: Щоб побачити зміни в грі, вам потрібно повторно синхронізувати своє спорядження, наприклад, використовуючи команду завантажувача /sync у чаті гри, відвідавши Доджьо/Реле або перезавантаживши гру.`,
general_inventoryUpdateNote: `Пам'ятка: Щоб побачити зміни в грі, вам потрібно повторно синхронізувати своє спорядження, наприклад, використовуючи команду /sync в програмі bootstrapper, відвідавши Доджьо/Реле або перезавантаживши гру.`,
general_inventoryUpdateNoteGameWs: `Примітка: для відображення змін може знадобитися повторно відкрити меню, в якому ви перебуваєте.`,
general_addButton: `Добавити`,
general_setButton: `Встановити`,
@ -45,7 +45,7 @@ dict = {
code_rank: `Рівень`,
code_rankUp: `Підвищити рівень`,
code_rankDown: `Понизити рівень`,
code_unlockLevelCap: `Розблокувати обмеження рівня`,
code_unlockLevelCap: `[UNTRANSLATED] Unlock level cap`,
code_count: `Кількість`,
code_focusAllUnlocked: `Всі школи Фокусу вже розблоковані.`,
code_focusUnlocked: `Розблоковано |COUNT| нових шкіл Фокусу! Для відображення змін в грі знадобиться оновлення спорядження. Відвідування навігації — найпростіший спосіб цього досягти.`,
@ -232,7 +232,7 @@ dict = {
cheats_baroFullyStocked: `Баро повністю укомплектований`,
cheats_syndicateMissionsRepeatable: `Повторювати місії синдиката`,
cheats_unlockAllProfitTakerStages: `Розблокувати всі етапи Привласнювачки`,
cheats_unlockSuccInventory: `Успішно. Зверніть увагу, що вам потрібно буде повторно синхронізувати своє спорядження, наприклад, за допомогою команди завантажувача /sync у чаті гри, відвідавши Доджьо/Реле або повторно увійшовши в гру.`,
cheats_unlockSuccInventory: `Успішно. Зверніть увагу, що вам потрібно буде повторно синхронізувати своє спорядження, наприклад, за допомогою команди /sync в програмі bootstrapper, відвідавши Доджьо/Реле або повторно увійшовши в гру.`,
cheats_instantFinishRivenChallenge: `Миттєве завершення випробування модифікатора Розколу`,
cheats_instantResourceExtractorDrones: `Миттєво добуваючі Дрони-видобувачі`,
cheats_noResourceExtractorDronesDamage: `Без шкоди по Дронам-видобувачам`,
@ -262,12 +262,12 @@ dict = {
cheats_changeButton: `Змінити`,
cheats_markAllAsRead: `Помітити всі вхідні як прочитані`,
cheats_finishInvasionsInOneMission: `Завершувати вторгнення за одну місію`,
cheats_nemesisHenchmenKillsMultiplierGrineer: `Множник прогресу люті (Ґрінери)`,
cheats_nemesisHenchmenKillsMultiplierCorpus: `Множник прогресу люті (Корпус)`,
cheats_nemesisAntivirusGainMultiplier: `Мультиплікатор прогресу антивіруса`,
cheats_nemesisHintProgressMultiplierGrineer: `Множник прогресу підсказки (Ґрінери)`,
cheats_nemesisHintProgressMultiplierCorpus: `Множник прогресу підсказки (Корпус)`,
cheats_nemesisExtraWeapon: `Додаткова зброя/Жива сердцевина за перемогу над Недругом (0 для вимкнення)`,
cheats_nemesisHenchmenKillsMultiplierGrineer: `[UNTRANSLATED] Rage Progess Multiplier (Grineer)`,
cheats_nemesisHenchmenKillsMultiplierCorpus: `[UNTRANSLATED] Rage Progess Multiplier (Corpus)`,
cheats_nemesisAntivirusGainMultiplier: `[UNTRANSLATED] Antivirus Progress Multiplier`,
cheats_nemesisHintProgressMultiplierGrineer: `[UNTRANSLATED] Hint Progress Multiplier (Grineer)`,
cheats_nemesisHintProgressMultiplierCorpus: `[UNTRANSLATED] Hint Progress Multiplier (Corpus)`,
cheats_nemesisExtraWeapon: `[UNTRANSLATED] Extra Nemesis Weapon / Token On Vanquish (0 to disable)`,
worldState: `Стан світу`,
worldState_creditBoost: `Глобальне посилення Кредитів`,
@ -289,7 +289,6 @@ dict = {
worldState_hallowedFlame: `Священне полум'я`,
worldState_hallowedNightmares: `Священні жахіття`,
worldState_hallowedNightmaresRewards: `Нагороди Священних жахіть`,
worldState_naberusNights: `Наберові ночі`,
worldState_proxyRebellion: `Повстання роботів`,
worldState_proxyRebellionRewards: `Нагороди Повстання роботів`,
worldState_bellyOfTheBeast: `У лігві звіра`,

View File

@ -1,4 +1,4 @@
// Chinese translation by meb154, bishan178, nyaoouo, qianlishun, CrazyZhang, Corvus, qingchun
// Chinese translation by meb154, bishan178, nyaoouo, qianlishun, CrazyZhang, Corvus, & qingchun
dict = {
general_inventoryUpdateNote: `注意: 要在游戏中查看更改,您需要重新同步库存,例如使用客户端的 /sync 命令,访问道场/中继站或重新登录.`,
general_inventoryUpdateNoteGameWs: `[UNTRANSLATED] Note: You may need to reopen any menu you are on for changes to be reflected.`,
@ -199,7 +199,7 @@ dict = {
cheats_skipTutorial: `跳过教程`,
cheats_skipAllDialogue: `跳过所有对话`,
cheats_unlockAllScans: `解锁所有扫描`,
cheats_unlockSuccRelog: `[UNTRANSLATED] Success. Please note that you'll need to relog for the client to refresh this.`,
cheats_unlockSuccRelog: `[UNTRANSLATED] Success. Please that you'll need to relog for the client to refresh this.`,
cheats_unlockAllMissions: `解锁所有星图`,
cheats_unlockAllMissions_ok: `操作成功.请注意,您需要进入道场/中继站或重新登录以刷新星图数据.`,
cheats_infiniteCredits: `无限现金`,
@ -232,7 +232,7 @@ dict = {
cheats_baroFullyStocked: `虚空商人贩卖所有商品`,
cheats_syndicateMissionsRepeatable: `集团任务可重复完成`,
cheats_unlockAllProfitTakerStages: `解锁利润收割者圆蛛所有阶段`,
cheats_unlockSuccInventory: `[UNTRANSLATED] Success. Please note that you'll need to resync your inventory, e.g. using the bootstrapper's /sync command in game chat, visiting a dojo/relay, or relogging.`,
cheats_unlockSuccInventory: `[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_instantFinishRivenChallenge: `立即完成裂罅挑战`,
cheats_instantResourceExtractorDrones: `资源无人机即时完成`,
cheats_noResourceExtractorDronesDamage: `资源无人机不会损毁`,
@ -289,7 +289,6 @@ dict = {
worldState_hallowedFlame: `万圣之焰`,
worldState_hallowedNightmares: `万圣噩梦`,
worldState_hallowedNightmaresRewards: `万圣噩梦奖励设置`,
worldState_naberusNights: `[UNTRANSLATED] Nights of Naberus`,
worldState_proxyRebellion: `机械叛乱`,
worldState_proxyRebellionRewards: `机械叛乱奖励设置`,
worldState_bellyOfTheBeast: `兽之腹`,