Compare commits

...

24 Commits

Author SHA1 Message Date
509f7f0d9b feat: selling for Dirac (CrewShipFusionPoints) (#2540)
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Reviewed-on: OpenWF/SpaceNinjaServer#2540
Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com>
Co-authored-by: azdful <mischzaripov@yandex.ru>
Co-committed-by: azdful <mischzaripov@yandex.ru>
2025-07-23 11:09:44 -07:00
aada031a80 chore: update mongoose (#2539)
The transform hook signature was changed in the typings, so I just updated them to be explicit about what we expect.

Reviewed-on: OpenWF/SpaceNinjaServer#2539
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-23 07:51:51 -07:00
a2a441ecb0 fix: getUsernameFromEmail returning wrong value (#2538)
Closes #2537

Reviewed-on: OpenWF/SpaceNinjaServer#2538
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-23 07:51:22 -07:00
c0a0463a68 feat: vista suite backdrop and soundscape customisation (#2534)
Closes #2532

Reviewed-on: OpenWF/SpaceNinjaServer#2534
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-22 07:34:46 -07:00
2307a40833 chore(webui): debounce inventory bulk actions (#2533)
Closes #2513

Reviewed-on: OpenWF/SpaceNinjaServer#2533
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-22 07:34:40 -07:00
304af514e2 fix(webui): handle name already being taken (#2530)
Closes #2528

Reviewed-on: OpenWF/SpaceNinjaServer#2530
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-22 07:34:31 -07:00
ddf3cd49b5 chore: handle new T value for orowyrm chest (#2527)
Closes #2526

Reviewed-on: OpenWF/SpaceNinjaServer#2527
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-22 07:34:22 -07:00
41e3f0136f chore(webui): improve auth state management 2025-07-21 20:28:19 +02:00
c0ca9d9398 fix: add try/catch around websocket message event handler (#2529)
Re #2528

Reviewed-on: OpenWF/SpaceNinjaServer#2529
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-21 07:44:54 -07:00
0f6b55beed chore(webui): update fr (#2525)
Reviewed-on: OpenWF/SpaceNinjaServer#2525
Co-authored-by: Vitruvio <vitruvio@noreply.localhost>
Co-committed-by: Vitruvio <vitruvio@noreply.localhost>
2025-07-21 03:23:12 -07:00
f8550e9afe fix: incorect deimos bounty medallion reward (#2524)
Reviewed-on: OpenWF/SpaceNinjaServer#2524
Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com>
Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
2025-07-21 03:23:05 -07:00
b53c4d9125 feat: reset obstacle course (#2523)
Closes #2520

Reviewed-on: OpenWF/SpaceNinjaServer#2523
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-21 03:22:59 -07:00
922b65cfab chore: print build date when started via docker (#2517)
Docker updates can be a bit confusing so this should help users know if they're up-to-date.

Reviewed-on: OpenWF/SpaceNinjaServer#2517
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-21 03:22:46 -07:00
2f642df20a feat: reset decorations (#2516)
Closes #2514

Reviewed-on: OpenWF/SpaceNinjaServer#2516
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-21 03:22:35 -07:00
62314e89c7 fix: refund personal decos when destroying dojo room (#2522)
Closes #2521

Reviewed-on: OpenWF/SpaceNinjaServer#2522
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-20 10:35:14 -07:00
56aa3e3331 fix: placing decorations in apartment in newer game versions (#2515)
Newer game versions use BootLocation instead of IsApartment

Reviewed-on: OpenWF/SpaceNinjaServer#2515
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-20 01:29:08 -07:00
c3f486488f chore: npm update (#2512)
There were some low severity vulnerabilites audit was complaining about

Reviewed-on: OpenWF/SpaceNinjaServer#2512
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-20 00:28:46 -07:00
49c353d895 chore: disable DTLS (#2511)
Reviewed-on: OpenWF/SpaceNinjaServer#2511
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-20 00:28:37 -07:00
90ab560620 chore(webui): don't refresh inventory for sell on the tab that issued it (#2506)
Reviewed-on: OpenWF/SpaceNinjaServer#2506
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-18 15:36:10 -07:00
b0e80fcfa8 chore(webui): update German translation (#2507)
Reviewed-on: OpenWF/SpaceNinjaServer#2507
Co-authored-by: Animan8000 <animan8000@noreply.localhost>
Co-committed-by: Animan8000 <animan8000@noreply.localhost>
2025-07-17 06:56:23 -07:00
2c62fb3c3c chore: move syncConfigWithDatabase to configService (#2505)
This avoids a cyclic dependency due to configController using this

Reviewed-on: OpenWF/SpaceNinjaServer#2505
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-17 05:04:30 -07:00
5b215733aa chore(webui): update to Spanish translation (#2503)
Reviewed-on: OpenWF/SpaceNinjaServer#2503
Co-authored-by: hxedcl <hxedcl@noreply.localhost>
Co-committed-by: hxedcl <hxedcl@noreply.localhost>
2025-07-16 23:04:26 -07:00
39866b9a2b fix: hide edit suit invigorations card on detailed view load (#2500)
Fixes #2494

Co-authored-by: nyaoouo <64143453+nyaoouo@users.noreply.github.com>
Reviewed-on: OpenWF/SpaceNinjaServer#2500
Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com>
Co-authored-by: nyaoouo <nyaoouo@noreply.localhost>
Co-committed-by: nyaoouo <nyaoouo@noreply.localhost>
2025-07-16 08:29:41 -07:00
fad1ee9314 chore(webui): update Chinese translation (#2501)
Reviewed-on: OpenWF/SpaceNinjaServer#2501
Co-authored-by: Corvus <corvus@noreply.localhost>
Co-committed-by: Corvus <corvus@noreply.localhost>
2025-07-16 08:29:13 -07:00
41 changed files with 907 additions and 706 deletions

View File

@ -21,7 +21,7 @@
"@typescript-eslint/no-unsafe-argument": "error", "@typescript-eslint/no-unsafe-argument": "error",
"@typescript-eslint/no-unsafe-call": "error", "@typescript-eslint/no-unsafe-call": "error",
"@typescript-eslint/no-unsafe-assignment": "error", "@typescript-eslint/no-unsafe-assignment": "error",
"@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/no-explicit-any": "off",
"no-loss-of-precision": "error", "no-loss-of-precision": "error",
"@typescript-eslint/no-unnecessary-condition": "error", "@typescript-eslint/no-unnecessary-condition": "error",
"@typescript-eslint/no-base-to-string": "off", "@typescript-eslint/no-base-to-string": "off",

View File

@ -2,3 +2,4 @@ src/routes/api.ts
static/webui/libs/ static/webui/libs/
*.html *.html
*.md *.md
config.json.example

View File

@ -7,5 +7,6 @@ WORKDIR /app
RUN npm i --omit=dev RUN npm i --omit=dev
RUN npm run build RUN npm run build
RUN date '+%d %B %Y' > BUILD_DATE
ENTRYPOINT ["/app/docker-entrypoint.sh"] ENTRYPOINT ["/app/docker-entrypoint.sh"]

652
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,6 +14,7 @@
"dev": "node scripts/dev.js", "dev": "node scripts/dev.js",
"dev:bun": "bun scripts/dev.js", "dev:bun": "bun scripts/dev.js",
"verify": "tsgo --noEmit", "verify": "tsgo --noEmit",
"verify:tsc": "tsc --noEmit",
"bun-run": "bun src/index.ts", "bun-run": "bun src/index.ts",
"lint": "eslint --ext .ts .", "lint": "eslint --ext .ts .",
"lint:ci": "eslint --ext .ts --rule \"prettier/prettier: off\" .", "lint:ci": "eslint --ext .ts --rule \"prettier/prettier: off\" .",

View File

@ -0,0 +1,22 @@
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getPersonalRooms } from "@/src/services/personalRoomsService";
import { RequestHandler } from "express";
export const apartmentController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const personalRooms = await getPersonalRooms(accountId, "Apartment");
const response: IApartmentResponse = {};
if (req.query.backdrop !== undefined) {
response.NewBackdropItem = personalRooms.Apartment.VideoWallBackdrop = req.query.backdrop as string;
}
if (req.query.soundscape !== undefined) {
response.NewSoundscapeItem = personalRooms.Apartment.Soundscape = req.query.soundscape as string;
}
await personalRooms.save();
res.json(response);
};
interface IApartmentResponse {
NewBackdropItem?: string;
NewSoundscapeItem?: string;
}

View File

@ -88,7 +88,6 @@ export const crewShipFusionController: RequestHandler = async (req, res) => {
} }
} }
superiorItem.UpgradeFingerprint = JSON.stringify(fingerprint); superiorItem.UpgradeFingerprint = JSON.stringify(fingerprint);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
inventoryChanges[category] = [superiorItem.toJSON() as any]; inventoryChanges[category] = [superiorItem.toJSON() as any];
await inventory.save(); await inventory.save();

View File

@ -3,11 +3,13 @@ import {
getGuildForRequestEx, getGuildForRequestEx,
hasAccessToDojo, hasAccessToDojo,
hasGuildPermission, hasGuildPermission,
refundDojoDeco,
removeDojoDeco removeDojoDeco
} from "@/src/services/guildService"; } from "@/src/services/guildService";
import { getInventory } from "@/src/services/inventoryService"; import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { GuildPermission } from "@/src/types/guildTypes"; import { GuildPermission } from "@/src/types/guildTypes";
import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
export const destroyDojoDecoController: RequestHandler = async (req, res) => { export const destroyDojoDecoController: RequestHandler = async (req, res) => {
@ -18,9 +20,20 @@ export const destroyDojoDecoController: RequestHandler = async (req, res) => {
res.json({ DojoRequestStatus: -1 }); res.json({ DojoRequestStatus: -1 });
return; return;
} }
const request = JSON.parse(String(req.body)) as IDestroyDojoDecoRequest; const request = JSON.parse(String(req.body)) as IDestroyDojoDecoRequest | IClearObstacleCourseRequest;
if ("DecoType" in request) {
removeDojoDeco(guild, request.ComponentId, request.DecoId); removeDojoDeco(guild, request.ComponentId, request.DecoId);
} else if (request.Act == "cObst") {
const component = guild.DojoComponents.id(request.ComponentId)!;
if (component.Decos) {
for (const deco of component.Decos) {
refundDojoDeco(guild, component, deco);
}
component.Decos.splice(0, component.Decos.length);
}
} else {
logger.error(`unhandled destroyDojoDeco request`, request);
}
await guild.save(); await guild.save();
res.json(await getDojoClient(guild, 0, request.ComponentId)); res.json(await getDojoClient(guild, 0, request.ComponentId));
@ -31,3 +44,8 @@ interface IDestroyDojoDecoRequest {
ComponentId: string; ComponentId: string;
DecoId: string; DecoId: string;
} }
interface IClearObstacleCourseRequest {
ComponentId: string;
Act: "cObst" | "maybesomethingelsewedontknowabout";
}

View File

@ -130,7 +130,7 @@ const createLoginResponse = (
resp.Groups = []; resp.Groups = [];
} }
if (version_compare(buildLabel, "2021.04.13.19.58") >= 0) { if (version_compare(buildLabel, "2021.04.13.19.58") >= 0) {
resp.DTLS = 99; resp.DTLS = 0; // bit 0 enables DTLS. if enabled, additional bits can be set, e.g. bit 2 to enable logging. on live, the value is 99.
} }
if (version_compare(buildLabel, "2022.04.29.12.53") >= 0) { if (version_compare(buildLabel, "2022.04.29.12.53") >= 0) {
resp.ClientType = account.ClientType; resp.ClientType = account.ClientType;

View File

@ -9,13 +9,14 @@ import {
freeUpSlot, freeUpSlot,
combineInventoryChanges, combineInventoryChanges,
addCrewShipRawSalvage, addCrewShipRawSalvage,
addFusionPoints addFusionPoints,
addCrewShipFusionPoints
} from "@/src/services/inventoryService"; } from "@/src/services/inventoryService";
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes"; import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
import { ExportDojoRecipes } from "warframe-public-export-plus"; import { ExportDojoRecipes } from "warframe-public-export-plus";
import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import { sendWsBroadcastTo } from "@/src/services/wsService"; import { sendWsBroadcastEx } from "@/src/services/wsService";
export const sellController: RequestHandler = async (req, res) => { export const sellController: RequestHandler = async (req, res) => {
const payload = JSON.parse(String(req.body)) as ISellRequest; const payload = JSON.parse(String(req.body)) as ISellRequest;
@ -26,6 +27,8 @@ export const sellController: RequestHandler = async (req, res) => {
requiredFields.add("RegularCredits"); requiredFields.add("RegularCredits");
} else if (payload.SellCurrency == "SC_FusionPoints") { } else if (payload.SellCurrency == "SC_FusionPoints") {
requiredFields.add("FusionPoints"); requiredFields.add("FusionPoints");
} else if (payload.SellCurrency == "SC_CrewShipFusionPoints") {
requiredFields.add("CrewShipFusionPoints");
} else { } else {
requiredFields.add("MiscItems"); requiredFields.add("MiscItems");
} }
@ -79,6 +82,8 @@ export const sellController: RequestHandler = async (req, res) => {
inventory.RegularCredits += payload.SellPrice; inventory.RegularCredits += payload.SellPrice;
} else if (payload.SellCurrency == "SC_FusionPoints") { } else if (payload.SellCurrency == "SC_FusionPoints") {
addFusionPoints(inventory, payload.SellPrice); addFusionPoints(inventory, payload.SellPrice);
} else if (payload.SellCurrency == "SC_CrewShipFusionPoints") {
addCrewShipFusionPoints(inventory, payload.SellPrice);
} else if (payload.SellCurrency == "SC_PrimeBucks") { } else if (payload.SellCurrency == "SC_PrimeBucks") {
addMiscItems(inventory, [ addMiscItems(inventory, [
{ {
@ -295,7 +300,7 @@ export const sellController: RequestHandler = async (req, res) => {
res.json({ res.json({
inventoryChanges: inventoryChanges // "inventoryChanges" for this response instead of the usual "InventoryChanges" inventoryChanges: inventoryChanges // "inventoryChanges" for this response instead of the usual "InventoryChanges"
}); });
sendWsBroadcastTo(accountId, { update_inventory: true }); sendWsBroadcastEx({ update_inventory: true }, accountId, parseInt(String(req.query.wsid)));
}; };
interface ISellRequest { interface ISellRequest {
@ -330,7 +335,8 @@ interface ISellRequest {
| "SC_FusionPoints" | "SC_FusionPoints"
| "SC_DistillPoints" | "SC_DistillPoints"
| "SC_CrewShipFusionPoints" | "SC_CrewShipFusionPoints"
| "SC_Resources"; | "SC_Resources"
| "somethingelsewemightnotknowabout";
buildLabel: string; buildLabel: string;
} }

View File

@ -1,20 +1,17 @@
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { IShipDecorationsRequest } from "@/src/types/personalRoomsTypes"; import { IShipDecorationsRequest, IResetShipDecorationsRequest } from "@/src/types/personalRoomsTypes";
import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { handleSetShipDecorations } from "@/src/services/shipCustomizationsService"; import { handleResetShipDecorations, handleSetShipDecorations } from "@/src/services/shipCustomizationsService";
export const shipDecorationsController: RequestHandler = async (req, res) => { export const shipDecorationsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const shipDecorationsRequest = JSON.parse(req.body as string) as IShipDecorationsRequest; if (req.query.reset == "1") {
const request = JSON.parse(req.body as string) as IResetShipDecorationsRequest;
try { const response = await handleResetShipDecorations(accountId, request);
res.send(response);
} else {
const shipDecorationsRequest = JSON.parse(req.body as string) as IShipDecorationsRequest;
const placedDecoration = await handleSetShipDecorations(accountId, shipDecorationsRequest); const placedDecoration = await handleSetShipDecorations(accountId, shipDecorationsRequest);
res.send(placedDecoration); res.send(placedDecoration);
} catch (error: unknown) {
if (error instanceof Error) {
logger.error(`error in shipDecorationsController: ${error.message}`);
res.status(400).json({ error: error.message });
}
} }
}; };

View File

@ -1,9 +1,8 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { config } from "@/src/services/configService"; import { config, syncConfigWithDatabase } from "@/src/services/configService";
import { getAccountForRequest, isAdministrator } from "@/src/services/loginService"; import { getAccountForRequest, isAdministrator } from "@/src/services/loginService";
import { saveConfig } from "@/src/services/configWriterService"; import { saveConfig } from "@/src/services/configWriterService";
import { sendWsBroadcastExcept } from "@/src/services/wsService"; import { sendWsBroadcastEx } from "@/src/services/wsService";
import { syncConfigWithDatabase } from "@/src/services/configWatcherService";
export const getConfigController: RequestHandler = async (req, res) => { export const getConfigController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req); const account = await getAccountForRequest(req);
@ -26,7 +25,7 @@ export const setConfigController: RequestHandler = async (req, res) => {
const [obj, idx] = configIdToIndexable(id); const [obj, idx] = configIdToIndexable(id);
obj[idx] = value; obj[idx] = value;
} }
sendWsBroadcastExcept(parseInt(String(req.query.wsid)), { config_reloaded: true }); sendWsBroadcastEx({ config_reloaded: true }, undefined, parseInt(String(req.query.wsid)));
syncConfigWithDatabase(); syncConfigWithDatabase();
await saveConfig(); await saveConfig();
res.end(); res.end();

View File

@ -11,6 +11,7 @@ import { GuildMember } from "@/src/models/guildModel";
import { Leaderboard } from "@/src/models/leaderboardModel"; import { Leaderboard } from "@/src/models/leaderboardModel";
import { deleteGuild } from "@/src/services/guildService"; import { deleteGuild } from "@/src/services/guildService";
import { Friendship } from "@/src/models/friendModel"; import { Friendship } from "@/src/models/friendModel";
import { sendWsBroadcastTo } from "@/src/services/wsService";
export const deleteAccountController: RequestHandler = async (req, res) => { export const deleteAccountController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -36,5 +37,8 @@ export const deleteAccountController: RequestHandler = async (req, res) => {
Ship.deleteMany({ ShipOwnerId: accountId }), Ship.deleteMany({ ShipOwnerId: accountId }),
Stats.deleteOne({ accountOwnerId: accountId }) Stats.deleteOne({ accountOwnerId: accountId })
]); ]);
sendWsBroadcastTo(accountId, { logged_out: true });
res.end(); res.end();
}; };

View File

@ -141,7 +141,7 @@ export const getProfileViewingDataGetController: RequestHandler = async (req, re
} }
} }
} else { } else {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
combinedStats[arrayName].push(entry as any); combinedStats[arrayName].push(entry as any);
} }
} }

View File

@ -1,5 +1,5 @@
// First, init config. // First, init config.
import { config, configPath, loadConfig } from "@/src/services/configService"; import { config, configPath, loadConfig, syncConfigWithDatabase } from "@/src/services/configService";
import fs from "fs"; import fs from "fs";
try { try {
loadConfig(); loadConfig();
@ -18,17 +18,23 @@ logger.info("Starting up...");
// Proceed with normal startup: bring up config watcher service, validate config, connect to MongoDB, and finally start listening for HTTP. // Proceed with normal startup: bring up config watcher service, validate config, connect to MongoDB, and finally start listening for HTTP.
import mongoose from "mongoose"; import mongoose from "mongoose";
import path from "path";
import { JSONStringify } from "json-with-bigint"; import { JSONStringify } from "json-with-bigint";
import { startWebServer } from "@/src/services/webService"; import { startWebServer } from "@/src/services/webService";
import { validateConfig } from "@/src/services/configWatcherService";
import { syncConfigWithDatabase, validateConfig } from "@/src/services/configWatcherService";
import { updateWorldStateCollections } from "@/src/services/worldStateService"; import { updateWorldStateCollections } from "@/src/services/worldStateService";
import { repoDir } from "@/src/helpers/pathHelper";
// Patch JSON.stringify to work flawlessly with Bigints. JSON.stringify = JSONStringify; // Patch JSON.stringify to work flawlessly with Bigints.
JSON.stringify = JSONStringify;
validateConfig(); validateConfig();
fs.readFile(path.join(repoDir, "BUILD_DATE"), "utf-8", (err, data) => {
if (!err) {
logger.info(`Docker image was built on ${data.trim()}`);
}
});
mongoose mongoose
.connect(config.mongodbUrl) .connect(config.mongodbUrl)
.then(() => { .then(() => {

View File

@ -1,16 +1,11 @@
import { NextFunction, Request, Response } from "express"; import { NextFunction, Request, Response } from "express";
import { logger } from "@/src/utils/logger"; import { logError } from "@/src/utils/logger";
export const errorHandler = (err: Error, req: Request, res: Response, _next: NextFunction): void => { export const errorHandler = (err: Error, req: Request, res: Response, _next: NextFunction): void => {
if (err.message == "Invalid accountId-nonce pair") { if (err.message == "Invalid accountId-nonce pair") {
res.status(400).send("Log-in expired"); res.status(400).send("Log-in expired");
} else if (err.stack) {
const stackArr = err.stack.split("\n");
stackArr[0] += ` while processing ${req.path} request`;
logger.error(stackArr.join("\n"));
res.status(500).end();
} else { } else {
logger.error(`uncaught error while processing ${req.path} request: ${err.message}`); logError(err, `processing ${req.path} request`);
res.status(500).end(); res.status(500).end();
} }
}; };

View File

@ -150,7 +150,7 @@ messageSchema.virtual("messageId").get(function (this: IMessageDatabase) {
messageSchema.set("toJSON", { messageSchema.set("toJSON", {
virtuals: true, virtuals: true,
transform(_document, returnedObject) { transform(_document, returnedObject: Record<string, any>) {
const messageDatabase = returnedObject as IMessageDatabase; const messageDatabase = returnedObject as IMessageDatabase;
const messageClient = returnedObject as IMessageClient; const messageClient = returnedObject as IMessageClient;

View File

@ -121,7 +121,7 @@ import {
export const typeCountSchema = new Schema<ITypeCount>({ ItemType: String, ItemCount: Number }, { _id: false }); export const typeCountSchema = new Schema<ITypeCount>({ ItemType: String, ItemCount: Number }, { _id: false });
typeCountSchema.set("toJSON", { typeCountSchema.set("toJSON", {
transform(_doc, obj) { transform(_doc, obj: Record<string, any>) {
if (obj.ItemCount > 2147483647) { if (obj.ItemCount > 2147483647) {
obj.ItemCount = 2147483647; obj.ItemCount = 2147483647;
} else if (obj.ItemCount < -2147483648) { } else if (obj.ItemCount < -2147483648) {
@ -189,7 +189,7 @@ operatorConfigSchema.virtual("ItemId").get(function () {
operatorConfigSchema.set("toJSON", { operatorConfigSchema.set("toJSON", {
virtuals: true, virtuals: true,
transform(_document, returnedObject) { transform(_document, returnedObject: Record<string, any>) {
delete returnedObject._id; delete returnedObject._id;
delete returnedObject.__v; delete returnedObject.__v;
} }
@ -226,7 +226,7 @@ const ItemConfigSchema = new Schema<IItemConfig>(
); );
ItemConfigSchema.set("toJSON", { ItemConfigSchema.set("toJSON", {
transform(_document, returnedObject) { transform(_document, returnedObject: Record<string, any>) {
delete returnedObject.__v; delete returnedObject.__v;
} }
}); });
@ -261,7 +261,7 @@ RawUpgrades.virtual("LastAdded").get(function () {
RawUpgrades.set("toJSON", { RawUpgrades.set("toJSON", {
virtuals: true, virtuals: true,
transform(_document, returnedObject) { transform(_document, returnedObject: Record<string, any>) {
delete returnedObject._id; delete returnedObject._id;
delete returnedObject.__v; delete returnedObject.__v;
} }
@ -282,7 +282,7 @@ upgradeSchema.virtual("ItemId").get(function () {
upgradeSchema.set("toJSON", { upgradeSchema.set("toJSON", {
virtuals: true, virtuals: true,
transform(_document, returnedObject) { transform(_document, returnedObject: Record<string, any>) {
delete returnedObject._id; delete returnedObject._id;
delete returnedObject.__v; delete returnedObject.__v;
} }
@ -325,7 +325,7 @@ const crewMemberSchema = new Schema<ICrewMemberDatabase>(
crewMemberSchema.set("toJSON", { crewMemberSchema.set("toJSON", {
virtuals: true, virtuals: true,
transform(_doc, obj) { transform(_doc, obj: Record<string, any>) {
const db = obj as ICrewMemberDatabase; const db = obj as ICrewMemberDatabase;
const client = obj as ICrewMemberClient; const client = obj as ICrewMemberClient;
@ -353,7 +353,7 @@ const FlavourItemSchema = new Schema(
); );
FlavourItemSchema.set("toJSON", { FlavourItemSchema.set("toJSON", {
transform(_document, returnedObject) { transform(_document, returnedObject: Record<string, any>) {
delete returnedObject._id; delete returnedObject._id;
delete returnedObject.__v; delete returnedObject.__v;
} }
@ -367,7 +367,7 @@ FlavourItemSchema.set("toJSON", {
); );
MailboxSchema.set("toJSON", { MailboxSchema.set("toJSON", {
transform(_document, returnedObject) { transform(_document, returnedObject: Record<string, any>) {
const mailboxDatabase = returnedObject as HydratedDocument<IMailboxDatabase, { __v?: number }>; const mailboxDatabase = returnedObject as HydratedDocument<IMailboxDatabase, { __v?: number }>;
delete mailboxDatabase.__v; delete mailboxDatabase.__v;
(returnedObject as IMailboxClient).LastInboxId = toOid(mailboxDatabase.LastInboxId); (returnedObject as IMailboxClient).LastInboxId = toOid(mailboxDatabase.LastInboxId);
@ -386,7 +386,7 @@ const DuviriInfoSchema = new Schema<IDuviriInfo>(
); );
DuviriInfoSchema.set("toJSON", { DuviriInfoSchema.set("toJSON", {
transform(_document, returnedObject) { transform(_document, returnedObject: Record<string, any>) {
delete returnedObject.__v; delete returnedObject.__v;
} }
}); });
@ -416,7 +416,7 @@ const droneSchema = new Schema<IDroneDatabase>(
); );
droneSchema.set("toJSON", { droneSchema.set("toJSON", {
virtuals: true, virtuals: true,
transform(_document, obj) { transform(_document, obj: Record<string, any>) {
const client = obj as IDroneClient; const client = obj as IDroneClient;
const db = obj as IDroneDatabase; const db = obj as IDroneDatabase;
@ -457,7 +457,7 @@ const personalGoalProgressSchema = new Schema<IPersonalGoalProgressDatabase>(
personalGoalProgressSchema.set("toJSON", { personalGoalProgressSchema.set("toJSON", {
virtuals: true, virtuals: true,
transform(_doc, obj) { transform(_doc, obj: Record<string, any>) {
const db = obj as IPersonalGoalProgressDatabase; const db = obj as IPersonalGoalProgressDatabase;
const client = obj as IPersonalGoalProgressClient; const client = obj as IPersonalGoalProgressClient;
@ -502,7 +502,7 @@ StepSequencersSchema.virtual("ItemId").get(function () {
StepSequencersSchema.set("toJSON", { StepSequencersSchema.set("toJSON", {
virtuals: true, virtuals: true,
transform(_document, returnedObject) { transform(_document, returnedObject: Record<string, any>) {
delete returnedObject._id; delete returnedObject._id;
delete returnedObject.__v; delete returnedObject.__v;
} }
@ -516,7 +516,7 @@ const kubrowPetEggSchema = new Schema<IKubrowPetEggDatabase>(
); );
kubrowPetEggSchema.set("toJSON", { kubrowPetEggSchema.set("toJSON", {
virtuals: true, virtuals: true,
transform(_document, obj) { transform(_document, obj: Record<string, any>) {
const client = obj as IKubrowPetEggClient; const client = obj as IKubrowPetEggClient;
const db = obj as IKubrowPetEggDatabase; const db = obj as IKubrowPetEggDatabase;
@ -586,7 +586,7 @@ personalTechProjectSchema.virtual("ItemId").get(function () {
personalTechProjectSchema.set("toJSON", { personalTechProjectSchema.set("toJSON", {
virtuals: true, virtuals: true,
transform(_doc, ret, _options) { transform(_doc, ret: Record<string, any>) {
delete ret._id; delete ret._id;
delete ret.__v; delete ret.__v;
@ -687,7 +687,7 @@ const questKeysSchema = new Schema<IQuestKeyDatabase>(
); );
questKeysSchema.set("toJSON", { questKeysSchema.set("toJSON", {
transform(_doc, ret, _options) { transform(_doc, ret: Record<string, any>) {
const questKeysDatabase = ret as IQuestKeyDatabase; const questKeysDatabase = ret as IQuestKeyDatabase;
if (questKeysDatabase.CompletionDate) { if (questKeysDatabase.CompletionDate) {
@ -709,7 +709,7 @@ const invasionProgressSchema = new Schema<IInvasionProgressDatabase>(
); );
invasionProgressSchema.set("toJSON", { invasionProgressSchema.set("toJSON", {
transform(_doc, obj) { transform(_doc, obj: Record<string, any>) {
const db = obj as IInvasionProgressDatabase; const db = obj as IInvasionProgressDatabase;
const client = obj as IInvasionProgressClient; const client = obj as IInvasionProgressClient;
@ -748,7 +748,7 @@ weaponSkinsSchema.virtual("ItemId").get(function () {
weaponSkinsSchema.set("toJSON", { weaponSkinsSchema.set("toJSON", {
virtuals: true, virtuals: true,
transform(_doc, ret, _options) { transform(_doc, ret: Record<string, any>) {
delete ret._id; delete ret._id;
delete ret.__v; delete ret.__v;
} }
@ -772,7 +772,7 @@ const periodicMissionCompletionsSchema = new Schema<IPeriodicMissionCompletionDa
); );
periodicMissionCompletionsSchema.set("toJSON", { periodicMissionCompletionsSchema.set("toJSON", {
transform(_doc, ret, _options) { transform(_doc, ret: Record<string, any>) {
const periodicMissionCompletionDatabase = ret as IPeriodicMissionCompletionDatabase; const periodicMissionCompletionDatabase = ret as IPeriodicMissionCompletionDatabase;
(periodicMissionCompletionDatabase as unknown as IPeriodicMissionCompletionResponse).date = toMongoDate( (periodicMissionCompletionDatabase as unknown as IPeriodicMissionCompletionResponse).date = toMongoDate(
@ -849,7 +849,7 @@ const endlessXpProgressSchema = new Schema<IEndlessXpProgressDatabase>(
); );
endlessXpProgressSchema.set("toJSON", { endlessXpProgressSchema.set("toJSON", {
transform(_doc, ret) { transform(_doc, ret: Record<string, any>) {
const db = ret as IEndlessXpProgressDatabase; const db = ret as IEndlessXpProgressDatabase;
const client = ret as IEndlessXpProgressClient; const client = ret as IEndlessXpProgressClient;
@ -898,7 +898,7 @@ const crewShipMemberSchema = new Schema<ICrewShipMemberDatabase>(
); );
crewShipMemberSchema.set("toJSON", { crewShipMemberSchema.set("toJSON", {
virtuals: true, virtuals: true,
transform(_doc, obj) { transform(_doc, obj: Record<string, any>) {
const db = obj as ICrewShipMemberDatabase; const db = obj as ICrewShipMemberDatabase;
const client = obj as ICrewShipMemberClient; const client = obj as ICrewShipMemberClient;
if (db.ItemId) { if (db.ItemId) {
@ -951,7 +951,7 @@ const dialogueSchema = new Schema<IDialogueDatabase>(
); );
dialogueSchema.set("toJSON", { dialogueSchema.set("toJSON", {
virtuals: true, virtuals: true,
transform(_doc, ret) { transform(_doc, ret: Record<string, any>) {
const db = ret as IDialogueDatabase; const db = ret as IDialogueDatabase;
const client = ret as IDialogueClient; const client = ret as IDialogueClient;
@ -997,7 +997,7 @@ const kubrowPetPrintSchema = new Schema<IKubrowPetPrintDatabase>({
}); });
kubrowPetPrintSchema.set("toJSON", { kubrowPetPrintSchema.set("toJSON", {
virtuals: true, virtuals: true,
transform(_doc, obj) { transform(_doc, obj: Record<string, any>) {
const db = obj as IKubrowPetPrintDatabase; const db = obj as IKubrowPetPrintDatabase;
const client = obj as IKubrowPetPrintClient; const client = obj as IKubrowPetPrintClient;
@ -1025,7 +1025,7 @@ const detailsSchema = new Schema<IKubrowPetDetailsDatabase>(
); );
detailsSchema.set("toJSON", { detailsSchema.set("toJSON", {
transform(_doc, returnedObject) { transform(_doc, returnedObject: Record<string, any>) {
delete returnedObject.__v; delete returnedObject.__v;
const db = returnedObject as IKubrowPetDetailsDatabase; const db = returnedObject as IKubrowPetDetailsDatabase;
@ -1081,7 +1081,7 @@ EquipmentSchema.virtual("ItemId").get(function () {
EquipmentSchema.set("toJSON", { EquipmentSchema.set("toJSON", {
virtuals: true, virtuals: true,
transform(_document, returnedObject) { transform(_document, returnedObject: Record<string, any>) {
delete returnedObject._id; delete returnedObject._id;
delete returnedObject.__v; delete returnedObject.__v;
@ -1132,7 +1132,7 @@ pendingRecipeSchema.virtual("ItemId").get(function () {
pendingRecipeSchema.set("toJSON", { pendingRecipeSchema.set("toJSON", {
virtuals: true, virtuals: true,
transform(_document, returnedObject) { transform(_document, returnedObject: Record<string, any>) {
delete returnedObject._id; delete returnedObject._id;
delete returnedObject.__v; delete returnedObject.__v;
delete returnedObject.LongGuns; delete returnedObject.LongGuns;
@ -1170,7 +1170,7 @@ const infestedFoundrySchema = new Schema<IInfestedFoundryDatabase>(
); );
infestedFoundrySchema.set("toJSON", { infestedFoundrySchema.set("toJSON", {
transform(_doc, ret, _options) { transform(_doc, ret: Record<string, any>) {
if (ret.AbilityOverrideUnlockCooldown) { if (ret.AbilityOverrideUnlockCooldown) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
ret.AbilityOverrideUnlockCooldown = toMongoDate(ret.AbilityOverrideUnlockCooldown); ret.AbilityOverrideUnlockCooldown = toMongoDate(ret.AbilityOverrideUnlockCooldown);
@ -1243,7 +1243,7 @@ const vendorPurchaseHistoryEntrySchema = new Schema<IVendorPurchaseHistoryEntryD
); );
vendorPurchaseHistoryEntrySchema.set("toJSON", { vendorPurchaseHistoryEntrySchema.set("toJSON", {
transform(_doc, obj) { transform(_doc, obj: Record<string, any>) {
const db = obj as IVendorPurchaseHistoryEntryDatabase; const db = obj as IVendorPurchaseHistoryEntryDatabase;
const client = obj as IVendorPurchaseHistoryEntryClient; const client = obj as IVendorPurchaseHistoryEntryClient;
client.Expiry = toMongoDate(db.Expiry); client.Expiry = toMongoDate(db.Expiry);
@ -1286,7 +1286,7 @@ const pendingCouponSchema = new Schema<IPendingCouponDatabase>(
); );
pendingCouponSchema.set("toJSON", { pendingCouponSchema.set("toJSON", {
transform(_doc, ret, _options) { transform(_doc, ret: Record<string, any>) {
(ret as IPendingCouponClient).Expiry = toMongoDate((ret as IPendingCouponDatabase).Expiry); (ret as IPendingCouponClient).Expiry = toMongoDate((ret as IPendingCouponDatabase).Expiry);
} }
}); });
@ -1353,7 +1353,7 @@ const nemesisSchema = new Schema<INemesisDatabase>(
nemesisSchema.set("toJSON", { nemesisSchema.set("toJSON", {
virtuals: true, virtuals: true,
transform(_doc, obj) { transform(_doc, obj: Record<string, any>) {
const db = obj as INemesisDatabase; const db = obj as INemesisDatabase;
const client = obj as INemesisClient; const client = obj as INemesisClient;
@ -1383,7 +1383,7 @@ const lastSortieRewardSchema = new Schema<ILastSortieRewardDatabase>(
lastSortieRewardSchema.set("toJSON", { lastSortieRewardSchema.set("toJSON", {
virtuals: true, virtuals: true,
transform(_doc, obj) { transform(_doc, obj: Record<string, any>) {
const db = obj as ILastSortieRewardDatabase; const db = obj as ILastSortieRewardDatabase;
const client = obj as ILastSortieRewardClient; const client = obj as ILastSortieRewardClient;
@ -1437,6 +1437,8 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
PremiumCreditsFree: { type: Number, default: 0 }, PremiumCreditsFree: { type: Number, default: 0 },
//Endo //Endo
FusionPoints: { type: Number, default: 0 }, FusionPoints: { type: Number, default: 0 },
//Dirac
CrewShipFusionPoints: { type: Number, default: 0 },
//Regal Aya //Regal Aya
PrimeTokens: { type: Number, default: 0 }, PrimeTokens: { type: Number, default: 0 },
@ -1790,7 +1792,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
); );
inventorySchema.set("toJSON", { inventorySchema.set("toJSON", {
transform(_document, returnedObject) { transform(_document, returnedObject: Record<string, any>) {
delete returnedObject._id; delete returnedObject._id;
delete returnedObject.__v; delete returnedObject.__v;
delete returnedObject.accountOwnerId; delete returnedObject.accountOwnerId;

View File

@ -49,7 +49,7 @@ loadoutConfigSchema.virtual("ItemId").get(function () {
loadoutConfigSchema.set("toJSON", { loadoutConfigSchema.set("toJSON", {
virtuals: true, virtuals: true,
transform(_doc, ret, _options) { transform(_doc, ret: Record<string, any>) {
delete ret._id; delete ret._id;
delete ret.__v; delete ret.__v;
} }
@ -71,7 +71,7 @@ export const loadoutSchema = new Schema<ILoadoutDatabase, loadoutModelType>({
}); });
loadoutSchema.set("toJSON", { loadoutSchema.set("toJSON", {
transform(_doc, ret, _options) { transform(_doc, ret: Record<string, any>) {
delete ret._id; delete ret._id;
delete ret.__v; delete ret.__v;
delete ret.loadoutOwnerId; delete ret.loadoutOwnerId;

View File

@ -32,7 +32,7 @@ const databaseAccountSchema = new Schema<IDatabaseAccountJson>(
); );
databaseAccountSchema.set("toJSON", { databaseAccountSchema.set("toJSON", {
transform(_document, returnedObject) { transform(_document, returnedObject: Record<string, any>) {
delete returnedObject._id; delete returnedObject._id;
delete returnedObject.__v; delete returnedObject.__v;
}, },

View File

@ -55,7 +55,7 @@ placedDecosSchema.virtual("id").get(function (this: IPlacedDecosDatabase) {
placedDecosSchema.set("toJSON", { placedDecosSchema.set("toJSON", {
virtuals: true, virtuals: true,
transform(_document, returnedObject) { transform(_document, returnedObject: Record<string, any>) {
delete returnedObject._id; delete returnedObject._id;
} }
}); });
@ -78,7 +78,7 @@ const favouriteLoadoutSchema = new Schema<IFavouriteLoadoutDatabase>(
); );
favouriteLoadoutSchema.set("toJSON", { favouriteLoadoutSchema.set("toJSON", {
virtuals: true, virtuals: true,
transform(_document, returnedObject) { transform(_document, returnedObject: Record<string, any>) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
returnedObject.LoadoutId = toOid(returnedObject.LoadoutId); returnedObject.LoadoutId = toOid(returnedObject.LoadoutId);
} }
@ -95,7 +95,7 @@ const plantSchema = new Schema<IPlantDatabase>(
plantSchema.set("toJSON", { plantSchema.set("toJSON", {
virtuals: true, virtuals: true,
transform(_doc, obj) { transform(_doc, obj: Record<string, any>) {
const client = obj as IPlantClient; const client = obj as IPlantClient;
const db = obj as IPlantDatabase; const db = obj as IPlantDatabase;
@ -122,7 +122,9 @@ const apartmentSchema = new Schema<IApartmentDatabase>(
{ {
Rooms: [roomSchema], Rooms: [roomSchema],
FavouriteLoadouts: [favouriteLoadoutSchema], FavouriteLoadouts: [favouriteLoadoutSchema],
Gardening: gardeningSchema Gardening: gardeningSchema,
VideoWallBackdrop: String,
Soundscape: String
}, },
{ _id: false } { _id: false }
); );
@ -156,7 +158,7 @@ const orbiterSchema = new Schema<IOrbiterDatabase>(
); );
orbiterSchema.set("toJSON", { orbiterSchema.set("toJSON", {
virtuals: true, virtuals: true,
transform(_doc, obj) { transform(_doc, obj: Record<string, any>) {
const db = obj as IOrbiterDatabase; const db = obj as IOrbiterDatabase;
const client = obj as IOrbiterClient; const client = obj as IOrbiterClient;

View File

@ -22,7 +22,7 @@ shipSchema.virtual("ItemId").get(function () {
shipSchema.set("toJSON", { shipSchema.set("toJSON", {
virtuals: true, virtuals: true,
transform(_document, returnedObject) { transform(_document, returnedObject: Record<string, any>) {
const shipResponse = returnedObject as IShipInventory; const shipResponse = returnedObject as IShipInventory;
const shipDatabase = returnedObject as IShipDatabase; const shipDatabase = returnedObject as IShipDatabase;
delete returnedObject._id; delete returnedObject._id;

View File

@ -101,7 +101,7 @@ const statsSchema = new Schema<IStatsDatabase>({
}); });
statsSchema.set("toJSON", { statsSchema.set("toJSON", {
transform(_document, returnedObject) { transform(_document, returnedObject: Record<string, any>) {
delete returnedObject._id; delete returnedObject._id;
delete returnedObject.__v; delete returnedObject.__v;
delete returnedObject.accountOwnerId; delete returnedObject.accountOwnerId;

View File

@ -10,6 +10,7 @@ import { addPendingFriendController } from "@/src/controllers/api/addPendingFrie
import { addToAllianceController } from "@/src/controllers/api/addToAllianceController"; import { addToAllianceController } from "@/src/controllers/api/addToAllianceController";
import { addToGuildController } from "@/src/controllers/api/addToGuildController"; import { addToGuildController } from "@/src/controllers/api/addToGuildController";
import { adoptPetController } from "@/src/controllers/api/adoptPetController"; import { adoptPetController } from "@/src/controllers/api/adoptPetController";
import { apartmentController } from "@/src/controllers/api/apartmentController";
import { arcaneCommonController } from "@/src/controllers/api/arcaneCommonController"; import { arcaneCommonController } from "@/src/controllers/api/arcaneCommonController";
import { archonFusionController } from "@/src/controllers/api/archonFusionController"; import { archonFusionController } from "@/src/controllers/api/archonFusionController";
import { artifactsController } from "@/src/controllers/api/artifactsController"; import { artifactsController } from "@/src/controllers/api/artifactsController";
@ -168,6 +169,7 @@ const apiRouter = express.Router();
// get // get
apiRouter.get("/abandonLibraryDailyTask.php", abandonLibraryDailyTaskController); apiRouter.get("/abandonLibraryDailyTask.php", abandonLibraryDailyTaskController);
apiRouter.get("/abortDojoComponentDestruction.php", abortDojoComponentDestructionController); apiRouter.get("/abortDojoComponentDestruction.php", abortDojoComponentDestructionController);
apiRouter.get("/apartment.php", apartmentController);
apiRouter.get("/cancelGuildAdvertisement.php", cancelGuildAdvertisementController); apiRouter.get("/cancelGuildAdvertisement.php", cancelGuildAdvertisementController);
apiRouter.get("/changeDojoRoot.php", changeDojoRootController); apiRouter.get("/changeDojoRoot.php", changeDojoRootController);
apiRouter.get("/changeGuildRank.php", changeGuildRankController); apiRouter.get("/changeGuildRank.php", changeGuildRankController);

View File

@ -2,6 +2,7 @@ import fs from "fs";
import path from "path"; import path from "path";
import { repoDir } from "@/src/helpers/pathHelper"; import { repoDir } from "@/src/helpers/pathHelper";
import { args } from "@/src/helpers/commandLineArguments"; import { args } from "@/src/helpers/commandLineArguments";
import { Inbox } from "@/src/models/inboxModel";
export interface IConfig { export interface IConfig {
mongodbUrl: string; mongodbUrl: string;
@ -113,9 +114,26 @@ export const loadConfig = (): void => {
// Set all values to undefined now so if the new config.json omits some fields that were previously present, it's correct in-memory. // Set all values to undefined now so if the new config.json omits some fields that were previously present, it's correct in-memory.
for (const key of Object.keys(config)) { for (const key of Object.keys(config)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(config as any)[key] = undefined; (config as any)[key] = undefined;
} }
Object.assign(config, newConfig); Object.assign(config, newConfig);
}; };
export const syncConfigWithDatabase = (): void => {
// Event messages are deleted after endDate. Since we don't use beginDate/endDate and instead have config toggles, we need to delete the messages once those bools are false.
// Also, for some reason, I can't just do `Inbox.deleteMany(...)`; - it needs this whole circus.
if (!config.worldState?.creditBoost) {
void Inbox.deleteMany({ globaUpgradeId: "5b23106f283a555109666672" }).then(() => {});
}
if (!config.worldState?.affinityBoost) {
void Inbox.deleteMany({ globaUpgradeId: "5b23106f283a555109666673" }).then(() => {});
}
if (!config.worldState?.resourceBoost) {
void Inbox.deleteMany({ globaUpgradeId: "5b23106f283a555109666674" }).then(() => {});
}
if (!config.worldState?.galleonOfGhouls) {
void Inbox.deleteMany({ goalTag: "GalleonRobbery" }).then(() => {});
}
};

View File

@ -1,10 +1,9 @@
import chokidar from "chokidar"; import chokidar from "chokidar";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { config, configPath, loadConfig } from "@/src/services/configService"; import { config, configPath, loadConfig, syncConfigWithDatabase } from "@/src/services/configService";
import { saveConfig, shouldReloadConfig } from "@/src/services/configWriterService"; import { saveConfig, shouldReloadConfig } from "@/src/services/configWriterService";
import { getWebPorts, startWebServer, stopWebServer } from "@/src/services/webService"; import { getWebPorts, startWebServer, stopWebServer } from "@/src/services/webService";
import { sendWsBroadcast } from "@/src/services/wsService"; import { sendWsBroadcast } from "@/src/services/wsService";
import { Inbox } from "@/src/models/inboxModel";
import varzia from "@/static/fixed_responses/worldState/varzia.json"; import varzia from "@/static/fixed_responses/worldState/varzia.json";
chokidar.watch(configPath).on("change", () => { chokidar.watch(configPath).on("change", () => {
@ -68,20 +67,3 @@ export const validateConfig = (): void => {
void saveConfig(); void saveConfig();
} }
}; };
export const syncConfigWithDatabase = (): void => {
// Event messages are deleted after endDate. Since we don't use beginDate/endDate and instead have config toggles, we need to delete the messages once those bools are false.
// Also, for some reason, I can't just do `Inbox.deleteMany(...)`; - it needs this whole circus.
if (!config.worldState?.creditBoost) {
void Inbox.deleteMany({ globaUpgradeId: "5b23106f283a555109666672" }).then(() => {});
}
if (!config.worldState?.affinityBoost) {
void Inbox.deleteMany({ globaUpgradeId: "5b23106f283a555109666673" }).then(() => {});
}
if (!config.worldState?.resourceBoost) {
void Inbox.deleteMany({ globaUpgradeId: "5b23106f283a555109666674" }).then(() => {});
}
if (!config.worldState?.galleonOfGhouls) {
void Inbox.deleteMany({ goalTag: "GalleonRobbery" }).then(() => {});
}
};

View File

@ -13,6 +13,7 @@ import {
IDojoComponentDatabase, IDojoComponentDatabase,
IDojoContributable, IDojoContributable,
IDojoDecoClient, IDojoDecoClient,
IDojoDecoDatabase,
IGuildClient, IGuildClient,
IGuildMemberClient, IGuildMemberClient,
IGuildMemberDatabase, IGuildMemberDatabase,
@ -309,7 +310,7 @@ export const removeDojoRoom = async (
guild.DojoEnergy -= meta.energy; guild.DojoEnergy -= meta.energy;
} }
moveResourcesToVault(guild, component); moveResourcesToVault(guild, component);
component.Decos?.forEach(deco => moveResourcesToVault(guild, deco)); component.Decos?.forEach(deco => refundDojoDeco(guild, component, deco));
if (guild.RoomChanges) { if (guild.RoomChanges) {
const index = guild.RoomChanges.findIndex(x => x.componentId.equals(component._id)); const index = guild.RoomChanges.findIndex(x => x.componentId.equals(component._id));
@ -344,6 +345,14 @@ export const removeDojoDeco = (
component.Decos!.findIndex(x => x._id.equals(decoId)), component.Decos!.findIndex(x => x._id.equals(decoId)),
1 1
)[0]; )[0];
refundDojoDeco(guild, component, deco);
};
export const refundDojoDeco = (
guild: TGuildDatabaseDocument,
component: IDojoComponentDatabase,
deco: IDojoDecoDatabase
): void => {
const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == deco.Type); const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == deco.Type);
if (meta) { if (meta) {
if (meta.capacityCost) { if (meta.capacityCost) {
@ -369,7 +378,7 @@ export const removeDojoDeco = (
]); ]);
} }
} }
moveResourcesToVault(guild, deco); moveResourcesToVault(guild, deco); // Refund resources spent on construction
}; };
const moveResourcesToVault = (guild: TGuildDatabaseDocument, component: IDojoContributable): void => { const moveResourcesToVault = (guild: TGuildDatabaseDocument, component: IDojoContributable): void => {

View File

@ -1241,6 +1241,15 @@ export const addFusionPoints = (inventory: TInventoryDatabaseDocument, add: numb
return add; return add;
}; };
export const addCrewShipFusionPoints = (inventory: TInventoryDatabaseDocument, add: number): number => {
if (inventory.CrewShipFusionPoints + add > 2147483647) {
logger.warn(`capping CrewShipFusionPoints balance at 2147483647`);
add = 2147483647 - inventory.CrewShipFusionPoints;
}
inventory.CrewShipFusionPoints += add;
return add;
};
const standingLimitBinToInventoryKey: Record< const standingLimitBinToInventoryKey: Record<
Exclude<TStandingLimitBin, "STANDING_LIMIT_BIN_NONE">, Exclude<TStandingLimitBin, "STANDING_LIMIT_BIN_NONE">,
keyof IDailyAffiliations keyof IDailyAffiliations

View File

@ -32,7 +32,7 @@ export const getUsernameFromEmail = async (email: string): Promise<string> => {
name = nameFromEmail + suffix; name = nameFromEmail + suffix;
} while (await isNameTaken(name)); } while (await isNameTaken(name));
} }
return nameFromEmail; return name;
}; };
export const createAccount = async (accountData: IDatabaseAccountRequiredFields): Promise<IDatabaseAccountJson> => { export const createAccount = async (accountData: IDatabaseAccountRequiredFields): Promise<IDatabaseAccountJson> => {

View File

@ -1283,9 +1283,7 @@ export const addMissionRewards = async (
} }
} }
} }
let medallionAmount = Math.floor( let medallionAmount = Math.floor(currentJob.xpAmounts[rewardInfo.JobStage] / (rewardInfo.Q ? 0.8 : 1));
Math.min(rewardInfo.JobStage, currentJob.xpAmounts.length - 1) / (rewardInfo.Q ? 0.8 : 1)
);
if ( if (
["DeimosEndlessAreaDefenseBounty", "DeimosEndlessExcavateBounty", "DeimosEndlessPurifyBounty"].some( ["DeimosEndlessAreaDefenseBounty", "DeimosEndlessExcavateBounty", "DeimosEndlessPurifyBounty"].some(
ending => jobType.endsWith(ending) ending => jobType.endsWith(ending)
@ -1637,7 +1635,10 @@ function getRandomMissionDrops(
rewardManifests = [ rewardManifests = [
"/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriMurmurFinalSteelChestRewards" "/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriMurmurFinalSteelChestRewards"
]; ];
} else if (RewardInfo.T == 70) { } else if (
RewardInfo.T == 70 ||
RewardInfo.T == 6 // https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2526
) {
// Orowyrm chest, gives 10 Pathos Clamps, or 15 on Steel Path. // Orowyrm chest, gives 10 Pathos Clamps, or 15 on Steel Path.
drops.push({ drops.push({
StoreItem: "/Lotus/StoreItems/Types/Gameplay/Duviri/Resource/DuviriDragonDropItem", StoreItem: "/Lotus/StoreItems/Types/Gameplay/Duviri/Resource/DuviriDragonDropItem",

View File

@ -1,6 +1,8 @@
import { getPersonalRooms } from "@/src/services/personalRoomsService"; import { getPersonalRooms } from "@/src/services/personalRoomsService";
import { getShip } from "@/src/services/shipService"; import { getShip } from "@/src/services/shipService";
import { import {
IResetShipDecorationsRequest,
IResetShipDecorationsResponse,
ISetPlacedDecoInfoRequest, ISetPlacedDecoInfoRequest,
ISetShipCustomizationsRequest, ISetShipCustomizationsRequest,
IShipDecorationsRequest, IShipDecorationsRequest,
@ -52,12 +54,7 @@ export const handleSetShipDecorations = async (
): Promise<IShipDecorationsResponse> => { ): Promise<IShipDecorationsResponse> => {
const personalRooms = await getPersonalRooms(accountId); const personalRooms = await getPersonalRooms(accountId);
const rooms = const rooms = getRoomsForBootLocation(personalRooms, placedDecoration);
placedDecoration.BootLocation == "SHOP"
? personalRooms.TailorShop.Rooms
: placedDecoration.IsApartment
? personalRooms.Apartment.Rooms
: personalRooms.Ship.Rooms;
const roomToPlaceIn = rooms.find(room => room.Name === placedDecoration.Room); const roomToPlaceIn = rooms.find(room => room.Name === placedDecoration.Room);
@ -159,7 +156,6 @@ export const handleSetShipDecorations = async (
if (!config.unlockAllShipDecorations) { if (!config.unlockAllShipDecorations) {
const inventory = await getInventory(accountId); const inventory = await getInventory(accountId);
const itemType = Object.entries(ExportResources).find(arr => arr[1].deco == placedDecoration.Type)![0];
if (placedDecoration.Sockets !== undefined) { if (placedDecoration.Sockets !== undefined) {
addFusionTreasures(inventory, [{ ItemType: itemType, Sockets: placedDecoration.Sockets, ItemCount: -1 }]); addFusionTreasures(inventory, [{ ItemType: itemType, Sockets: placedDecoration.Sockets, ItemCount: -1 }]);
} else { } else {
@ -192,17 +188,62 @@ export const handleSetShipDecorations = async (
const getRoomsForBootLocation = ( const getRoomsForBootLocation = (
personalRooms: TPersonalRoomsDatabaseDocument, personalRooms: TPersonalRoomsDatabaseDocument,
bootLocation: TBootLocation | undefined request: { BootLocation?: TBootLocation; IsApartment?: boolean }
): RoomsType[] => { ): RoomsType[] => {
if (bootLocation == "SHOP") { if (request.BootLocation == "SHOP") {
return personalRooms.TailorShop.Rooms; return personalRooms.TailorShop.Rooms;
} }
if (bootLocation == "APARTMENT") { if (request.BootLocation == "APARTMENT" || request.IsApartment) {
return personalRooms.Apartment.Rooms; return personalRooms.Apartment.Rooms;
} }
return personalRooms.Ship.Rooms; return personalRooms.Ship.Rooms;
}; };
export const handleResetShipDecorations = async (
accountId: string,
request: IResetShipDecorationsRequest
): Promise<IResetShipDecorationsResponse> => {
const [personalRooms, inventory] = await Promise.all([getPersonalRooms(accountId), getInventory(accountId)]);
const room = getRoomsForBootLocation(personalRooms, request).find(room => room.Name === request.Room);
if (!room) {
throw new Error(`unknown room: ${request.Room}`);
}
for (const deco of room.PlacedDecos) {
const entry = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type);
if (!entry) {
throw new Error(`unknown deco type: ${deco.Type}`);
}
const [itemType, meta] = entry;
if (meta.capacityCost === undefined) {
throw new Error(`unknown deco type: ${deco.Type}`);
}
// refund item
if (!config.unlockAllShipDecorations) {
if (deco.Sockets !== undefined) {
addFusionTreasures(inventory, [{ ItemType: itemType, Sockets: deco.Sockets, ItemCount: 1 }]);
} else {
addShipDecorations(inventory, [{ ItemType: itemType, ItemCount: 1 }]);
}
}
// refund capacity
room.MaxCapacity += meta.capacityCost;
}
// empty room
room.PlacedDecos.splice(0, room.PlacedDecos.length);
await Promise.all([personalRooms.save(), inventory.save()]);
return {
ResetRoom: request.Room,
ClaimedDecos: [], // Not sure what this is for; the client already implies that the decos were returned to inventory.
NewCapacity: room.MaxCapacity
};
};
export const handleSetPlacedDecoInfo = async (accountId: string, req: ISetPlacedDecoInfoRequest): Promise<void> => { export const handleSetPlacedDecoInfo = async (accountId: string, req: ISetPlacedDecoInfoRequest): Promise<void> => {
if (req.GuildId && req.ComponentId) { if (req.GuildId && req.ComponentId) {
const guild = (await Guild.findById(req.GuildId))!; const guild = (await Guild.findById(req.GuildId))!;
@ -217,7 +258,7 @@ export const handleSetPlacedDecoInfo = async (accountId: string, req: ISetPlaced
const personalRooms = await getPersonalRooms(accountId); const personalRooms = await getPersonalRooms(accountId);
const room = getRoomsForBootLocation(personalRooms, req.BootLocation).find(room => room.Name === req.Room); const room = getRoomsForBootLocation(personalRooms, req).find(room => room.Name === req.Room);
if (!room) { if (!room) {
throw new Error(`unknown room: ${req.Room}`); throw new Error(`unknown room: ${req.Room}`);
} }

View File

@ -5,6 +5,7 @@ import { Account } from "@/src/models/loginModel";
import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "@/src/services/loginService"; import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "@/src/services/loginService";
import { IDatabaseAccountJson } from "@/src/types/loginTypes"; import { IDatabaseAccountJson } from "@/src/types/loginTypes";
import { HydratedDocument } from "mongoose"; import { HydratedDocument } from "mongoose";
import { logError } from "@/src/utils/logger";
let wsServer: ws.Server | undefined; let wsServer: ws.Server | undefined;
let wssServer: ws.Server | undefined; let wssServer: ws.Server | undefined;
@ -43,7 +44,7 @@ export const stopWsServers = (promises: Promise<void>[]): void => {
let lastWsid: number = 0; let lastWsid: number = 0;
interface IWsCustomData extends ws { interface IWsCustomData extends ws {
id?: number; id: number;
accountId?: string; accountId?: string;
} }
@ -88,63 +89,67 @@ const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => {
// eslint-disable-next-line @typescript-eslint/no-misused-promises // eslint-disable-next-line @typescript-eslint/no-misused-promises
ws.on("message", async msg => { ws.on("message", async msg => {
const data = JSON.parse(String(msg)) as IWsMsgFromClient; try {
if (data.auth) { const data = JSON.parse(String(msg)) as IWsMsgFromClient;
let account: IDatabaseAccountJson | null = await Account.findOne({ email: data.auth.email }); if (data.auth) {
if (account) { let account: IDatabaseAccountJson | null = await Account.findOne({ email: data.auth.email });
if (isCorrectPassword(data.auth.password, account.password)) { if (account) {
if (!account.Nonce) { if (isCorrectPassword(data.auth.password, account.password)) {
account.ClientType = "webui"; if (!account.Nonce) {
account.Nonce = createNonce(); account.ClientType = "webui";
await (account as HydratedDocument<IDatabaseAccountJson>).save(); account.Nonce = createNonce();
await (account as HydratedDocument<IDatabaseAccountJson>).save();
}
} else {
account = null;
} }
} else if (data.auth.isRegister) {
const name = await getUsernameFromEmail(data.auth.email);
account = await createAccount({
email: data.auth.email,
password: data.auth.password,
ClientType: "webui",
LastLogin: new Date(),
DisplayName: name,
Nonce: createNonce()
});
}
if (account) {
(ws as IWsCustomData).accountId = account.id;
ws.send(
JSON.stringify({
auth_succ: {
id: account.id,
DisplayName: account.DisplayName,
Nonce: account.Nonce
}
} satisfies IWsMsgToClient)
);
} else { } else {
account = null; ws.send(
JSON.stringify({
auth_fail: {
isRegister: data.auth.isRegister
}
} satisfies IWsMsgToClient)
);
} }
} else if (data.auth.isRegister) {
const name = await getUsernameFromEmail(data.auth.email);
account = await createAccount({
email: data.auth.email,
password: data.auth.password,
ClientType: "webui",
LastLogin: new Date(),
DisplayName: name,
Nonce: createNonce()
});
} }
if (account) { if (data.logout) {
(ws as IWsCustomData).accountId = account.id; const accountId = (ws as IWsCustomData).accountId;
ws.send( (ws as IWsCustomData).accountId = undefined;
JSON.stringify({ await Account.updateOne(
auth_succ: { {
id: account.id, _id: accountId,
DisplayName: account.DisplayName, ClientType: "webui"
Nonce: account.Nonce },
} {
} satisfies IWsMsgToClient) Nonce: 0
); }
} else {
ws.send(
JSON.stringify({
auth_fail: {
isRegister: data.auth.isRegister
}
} satisfies IWsMsgToClient)
); );
} }
} } catch (e) {
if (data.logout) { logError(e as Error, `processing websocket message`);
const accountId = (ws as IWsCustomData).accountId;
(ws as IWsCustomData).accountId = undefined;
await Account.updateOne(
{
_id: accountId,
ClientType: "webui"
},
{
Nonce: 0
}
);
} }
}); });
}; };
@ -181,18 +186,24 @@ export const sendWsBroadcastTo = (accountId: string, data: IWsMsgToClient): void
} }
}; };
export const sendWsBroadcastExcept = (wsid: number | undefined, data: IWsMsgToClient): void => { export const sendWsBroadcastEx = (data: IWsMsgToClient, accountId?: string, excludeWsid?: number): void => {
const msg = JSON.stringify(data); const msg = JSON.stringify(data);
if (wsServer) { if (wsServer) {
for (const client of wsServer.clients) { for (const client of wsServer.clients) {
if ((client as IWsCustomData).id != wsid) { if (
(!accountId || (client as IWsCustomData).accountId == accountId) &&
(client as IWsCustomData).id != excludeWsid
) {
client.send(msg); client.send(msg);
} }
} }
} }
if (wssServer) { if (wssServer) {
for (const client of wssServer.clients) { for (const client of wssServer.clients) {
if ((client as IWsCustomData).id != wsid) { if (
(!accountId || (client as IWsCustomData).accountId == accountId) &&
(client as IWsCustomData).id != excludeWsid
) {
client.send(msg); client.send(msg);
} }
} }

View File

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Types } from "mongoose"; import { Types } from "mongoose";
import { IOid, IMongoDate, IOidWithLegacySupport, ITypeCount } from "@/src/types/commonTypes"; import { IOid, IMongoDate, IOidWithLegacySupport, ITypeCount } from "@/src/types/commonTypes";
import { import {
@ -216,6 +215,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
PremiumCredits: number; PremiumCredits: number;
PremiumCreditsFree: number; PremiumCreditsFree: number;
FusionPoints: number; FusionPoints: number;
CrewShipFusionPoints: number; //Dirac (pre-rework Railjack)
PrimeTokens: number; PrimeTokens: number;
SuitBin: ISlots; SuitBin: ISlots;
WeaponBin: ISlots; WeaponBin: ISlots;

View File

@ -91,12 +91,16 @@ export interface IApartmentClient {
Gardening: IGardeningClient; Gardening: IGardeningClient;
Rooms: IRoom[]; Rooms: IRoom[];
FavouriteLoadouts: IFavouriteLoadout[]; FavouriteLoadouts: IFavouriteLoadout[];
VideoWallBackdrop?: string;
Soundscape?: string;
} }
export interface IApartmentDatabase { export interface IApartmentDatabase {
Gardening: IGardeningDatabase; Gardening: IGardeningDatabase;
Rooms: IRoom[]; Rooms: IRoom[];
FavouriteLoadouts: IFavouriteLoadoutDatabase[]; FavouriteLoadouts: IFavouriteLoadoutDatabase[];
VideoWallBackdrop?: string;
Soundscape?: string;
} }
export interface IPlacedDecosDatabase { export interface IPlacedDecosDatabase {
@ -150,6 +154,17 @@ export interface IShipDecorationsResponse {
NewRoom?: string; NewRoom?: string;
} }
export interface IResetShipDecorationsRequest {
Room: string;
BootLocation?: TBootLocation;
}
export interface IResetShipDecorationsResponse {
ResetRoom: string;
ClaimedDecos: [];
NewCapacity: number;
}
export interface ISetPlacedDecoInfoRequest { export interface ISetPlacedDecoInfoRequest {
DecoType: string; DecoType: string;
DecoId: string; DecoId: string;

View File

@ -108,3 +108,13 @@ errorLog.on("new", filename => logger.info(`Using error log file: ${filename}`))
combinedLog.on("new", filename => logger.info(`Using combined log file: ${filename}`)); combinedLog.on("new", filename => logger.info(`Using combined log file: ${filename}`));
errorLog.on("rotate", filename => logger.info(`Rotated error log file: ${filename}`)); errorLog.on("rotate", filename => logger.info(`Rotated error log file: ${filename}`));
combinedLog.on("rotate", filename => logger.info(`Rotated combined log file: ${filename}`)); combinedLog.on("rotate", filename => logger.info(`Rotated combined log file: ${filename}`));
export const logError = (err: Error, context: string): void => {
if (err.stack) {
const stackArr = err.stack.split("\n");
stackArr[0] += ` while ${context}`;
logger.error(stackArr.join("\n"));
} else {
logger.error(`uncaught error while ${context}: ${err.message}`);
}
};

View File

@ -436,22 +436,22 @@
<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">
<div class="mb-2 d-flex flex-wrap gap-2"> <div class="mb-2 d-flex flex-wrap gap-2">
<button class="btn btn-primary" onclick="addMissingEquipment(['Suits']);" data-loc="inventory_bulkAddSuits"></button> <button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['Suits']);" data-loc="inventory_bulkAddSuits"></button>
<button class="btn btn-primary" onclick="addMissingEquipment(['Melee', 'LongGuns', 'Pistols']);" data-loc="inventory_bulkAddWeapons"></button> <button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['Melee', 'LongGuns', 'Pistols']);" data-loc="inventory_bulkAddWeapons"></button>
<button class="btn btn-primary" onclick="addMissingEquipment(['SpaceSuits']);" data-loc="inventory_bulkAddSpaceSuits"></button> <button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['SpaceSuits']);" data-loc="inventory_bulkAddSpaceSuits"></button>
<button class="btn btn-primary" onclick="addMissingEquipment(['SpaceGuns', 'SpaceMelee']);" data-loc="inventory_bulkAddSpaceWeapons"></button> <button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['SpaceGuns', 'SpaceMelee']);" data-loc="inventory_bulkAddSpaceWeapons"></button>
<button class="btn btn-primary" onclick="addMissingEquipment(['Sentinels']);" data-loc="inventory_bulkAddSentinels"></button> <button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['Sentinels']);" data-loc="inventory_bulkAddSentinels"></button>
<button class="btn btn-primary" onclick="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="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">
<button class="btn btn-success" onclick="maxRankAllEquipment(['Suits']);" data-loc="inventory_bulkRankUpSuits"></button> <button class="btn btn-success" onclick="debounce(maxRankAllEquipment, ['Suits']);" data-loc="inventory_bulkRankUpSuits"></button>
<button class="btn btn-success" onclick="maxRankAllEquipment(['Melee', 'LongGuns', 'Pistols']);" data-loc="inventory_bulkRankUpWeapons"></button> <button class="btn btn-success" onclick="debounce(maxRankAllEquipment, ['Melee', 'LongGuns', 'Pistols']);" data-loc="inventory_bulkRankUpWeapons"></button>
<button class="btn btn-success" onclick="maxRankAllEquipment(['SpaceSuits']);" data-loc="inventory_bulkRankUpSpaceSuits"></button> <button class="btn btn-success" onclick="debounce(maxRankAllEquipment, ['SpaceSuits']);" data-loc="inventory_bulkRankUpSpaceSuits"></button>
<button class="btn btn-success" onclick="maxRankAllEquipment(['SpaceGuns', 'SpaceMelee']);" data-loc="inventory_bulkRankUpSpaceWeapons"></button> <button class="btn btn-success" onclick="debounce(maxRankAllEquipment, ['SpaceGuns', 'SpaceMelee']);" data-loc="inventory_bulkRankUpSpaceWeapons"></button>
<button class="btn btn-success" onclick="maxRankAllEquipment(['Sentinels']);" data-loc="inventory_bulkRankUpSentinels"></button> <button class="btn btn-success" onclick="debounce(maxRankAllEquipment, ['Sentinels']);" data-loc="inventory_bulkRankUpSentinels"></button>
<button class="btn btn-success" onclick="maxRankAllEquipment(['SentinelWeapons']);" data-loc="inventory_bulkRankUpSentinelWeapons"></button> <button class="btn btn-success" onclick="debounce(maxRankAllEquipment, ['SentinelWeapons']);" data-loc="inventory_bulkRankUpSentinelWeapons"></button>
<button class="btn btn-success" onclick="maxRankAllEvolutions();" data-loc="inventory_bulkRankUpEvolutionProgress"></button> <button class="btn btn-success" onclick="debounce(maxRankAllEvolutions);" data-loc="inventory_bulkRankUpEvolutionProgress"></button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -118,9 +118,16 @@ function doLogin() {
window.registerSubmit = false; window.registerSubmit = false;
} }
async function revalidateAuthz() { function revalidateAuthz() {
await getWebSocket(); return new Promise(resolve => {
// We have a websocket connection, so authz should be good. let interval;
interval = setInterval(() => {
if (ws_is_open && !auth_pending) {
clearInterval(interval);
resolve();
}
}, 10);
});
} }
function logout() { function logout() {
@ -865,15 +872,11 @@ function updateInventory() {
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()"]');
const giveAllQEvolutionProgress = document.querySelector(
'button[onclick*="addMissingEvolutionProgress()"]'
);
if (datalistEvolutionProgress.length === 0) { if (datalistEvolutionProgress.length === 0) {
formEvolutionProgress.classList.add("disabled"); formEvolutionProgress.classList.add("disabled");
formEvolutionProgress.querySelector("input").disabled = true; formEvolutionProgress.querySelector("input").disabled = true;
formEvolutionProgress.querySelector("button").disabled = true; formEvolutionProgress.querySelector("button").disabled = true;
giveAllQEvolutionProgress.disabled = true;
} }
if (data.CrewShipHarnesses?.length) { if (data.CrewShipHarnesses?.length) {
@ -1534,14 +1537,17 @@ $(document).on("input", "input[list]", function () {
}); });
function dispatchAddItemsRequestsBatch(requests) { function dispatchAddItemsRequestsBatch(requests) {
revalidateAuthz().then(() => { return new Promise(resolve => {
const req = $.post({ revalidateAuthz().then(() => {
url: "/custom/addItems?" + window.authz, const req = $.post({
contentType: "application/json", url: "/custom/addItems?" + window.authz,
data: JSON.stringify(requests) contentType: "application/json",
}); data: JSON.stringify(requests)
req.done(() => { });
updateInventory(); req.done(() => {
updateInventory();
resolve();
});
}); });
}); });
} }
@ -1562,7 +1568,7 @@ function addMissingEquipment(categories) {
}); });
}); });
if (requests.length != 0 && window.confirm(loc("code_addItemsConfirm").split("|COUNT|").join(requests.length))) { if (requests.length != 0 && window.confirm(loc("code_addItemsConfirm").split("|COUNT|").join(requests.length))) {
dispatchAddItemsRequestsBatch(requests); return dispatchAddItemsRequestsBatch(requests);
} }
} }
@ -1578,7 +1584,7 @@ function addMissingEvolutionProgress() {
requests.push({ ItemType: uniqueName, Rank: permanentEvolutionWeapons.has(uniqueName) ? 0 : 1 }); requests.push({ ItemType: uniqueName, Rank: permanentEvolutionWeapons.has(uniqueName) ? 0 : 1 });
}); });
if (requests.length != 0 && window.confirm(loc("code_addItemsConfirm").split("|COUNT|").join(requests.length))) { if (requests.length != 0 && window.confirm(loc("code_addItemsConfirm").split("|COUNT|").join(requests.length))) {
setEvolutionProgress(requests); return setEvolutionProgress(requests);
} }
} }
@ -1746,7 +1752,7 @@ function disposeOfGear(category, oid) {
]; ];
revalidateAuthz().then(() => { revalidateAuthz().then(() => {
$.post({ $.post({
url: "/api/sell.php?" + window.authz, url: "/api/sell.php?" + window.authz + "&wsid=" + wsid,
contentType: "text/plain", contentType: "text/plain",
data: JSON.stringify(data) data: JSON.stringify(data)
}); });
@ -1768,7 +1774,7 @@ function disposeOfItems(category, type, count) {
]; ];
revalidateAuthz().then(() => { revalidateAuthz().then(() => {
$.post({ $.post({
url: "/api/sell.php?" + window.authz, url: "/api/sell.php?" + window.authz + "&wsid=" + wsid,
contentType: "text/plain", contentType: "text/plain",
data: JSON.stringify(data) data: JSON.stringify(data)
}); });
@ -1805,14 +1811,17 @@ function maturePet(oid, revert) {
} }
function setEvolutionProgress(requests) { function setEvolutionProgress(requests) {
revalidateAuthz().then(() => { return new Promise(resolve => {
const req = $.post({ revalidateAuthz().then(() => {
url: "/custom/setEvolutionProgress?" + window.authz, const req = $.post({
contentType: "application/json", url: "/custom/setEvolutionProgress?" + window.authz,
data: JSON.stringify(requests) contentType: "application/json",
}); data: JSON.stringify(requests)
req.done(() => { });
updateInventory(); req.done(() => {
updateInventory();
resolve();
});
}); });
}); });
} }
@ -2080,6 +2089,10 @@ single.getRoute("/webui/cheats").on("beforeload", function () {
}) })
.fail(res => { .fail(res => {
if (res.responseText == "Log-in expired") { if (res.responseText == "Log-in expired") {
if (ws_is_open && !auth_pending) {
console.warn("Credentials invalidated but the server didn't let us know");
sendAuth();
}
revalidateAuthz().then(() => { revalidateAuthz().then(() => {
if (single.getCurrentPath() == "/webui/cheats") { if (single.getCurrentPath() == "/webui/cheats") {
single.loadRoute("/webui/cheats"); single.loadRoute("/webui/cheats");
@ -2202,7 +2215,7 @@ function doRemoveUnrankedMods() {
req.done(inventory => { req.done(inventory => {
window.itemListPromise.then(itemMap => { window.itemListPromise.then(itemMap => {
$.post({ $.post({
url: "/api/sell.php?" + window.authz, url: "/api/sell.php?" + window.authz + "&wsid=" + wsid,
contentType: "text/plain", contentType: "text/plain",
data: JSON.stringify({ data: JSON.stringify({
SellCurrency: "SC_RegularCredits", SellCurrency: "SC_RegularCredits",
@ -2236,6 +2249,7 @@ single.getRoute("#detailedView-route").on("beforeload", function () {
document.getElementById("detailedView-title").textContent = ""; document.getElementById("detailedView-title").textContent = "";
document.querySelector("#detailedView-route .text-body-secondary").textContent = ""; document.querySelector("#detailedView-route .text-body-secondary").textContent = "";
document.getElementById("archonShards-card").classList.add("d-none"); document.getElementById("archonShards-card").classList.add("d-none");
document.getElementById("edit-suit-invigorations-card").classList.add("d-none");
document.getElementById("modularParts-card").classList.add("d-none"); document.getElementById("modularParts-card").classList.add("d-none");
document.getElementById("modularParts-form").innerHTML = ""; document.getElementById("modularParts-form").innerHTML = "";
document.getElementById("valenceBonus-card").classList.add("d-none"); document.getElementById("valenceBonus-card").classList.add("d-none");
@ -2499,9 +2513,17 @@ function formatDatetime(fmt, date) {
const calls_in_flight = new Set(); const calls_in_flight = new Set();
async function debounce(func, ...args) { async function debounce(func, ...args) {
calls_in_flight.add(func); if (!func.name) {
await func(...args); throw new Error(`cannot debounce anonymous functions`);
calls_in_flight.delete(func); }
const callid = JSON.stringify({ func: func.name, args });
if (!calls_in_flight.has(callid)) {
calls_in_flight.add(callid);
await func(...args);
calls_in_flight.delete(callid);
} else {
console.log("debouncing", callid);
}
} }
async function doMaxPlexus() { async function doMaxPlexus() {

View File

@ -63,7 +63,7 @@ dict = {
code_mature: `Für den Kampf auswachsen lassen`, code_mature: `Für den Kampf auswachsen lassen`,
code_unmature: `Genetisches Altern zurücksetzen`, code_unmature: `Genetisches Altern zurücksetzen`,
code_succChange: `Erfolgreich geändert.`, code_succChange: `Erfolgreich geändert.`,
code_requiredInvigorationUpgrade: `[UNTRANSLATED] You must select both an offensive & defensive upgrade.`, code_requiredInvigorationUpgrade: `Du musst sowohl ein offensives & defensives Upgrade auswählen.`,
login_description: `Melde dich mit deinem OpenWF-Account an (denselben Angaben wie im Spiel, wenn du dich mit diesem Server verbindest).`, login_description: `Melde dich mit deinem OpenWF-Account an (denselben Angaben wie im Spiel, wenn du dich mit diesem Server verbindest).`,
login_emailLabel: `E-Mail-Adresse`, login_emailLabel: `E-Mail-Adresse`,
login_passwordLabel: `Passwort`, login_passwordLabel: `Passwort`,
@ -127,33 +127,33 @@ dict = {
detailedView_valenceBonusLabel: `Valenz-Bonus`, detailedView_valenceBonusLabel: `Valenz-Bonus`,
detailedView_valenceBonusDescription: `Du kannst den Valenz-Bonus deiner Waffe festlegen oder entfernen.`, detailedView_valenceBonusDescription: `Du kannst den Valenz-Bonus deiner Waffe festlegen oder entfernen.`,
detailedView_modularPartsLabel: `Modulare Teile ändern`, detailedView_modularPartsLabel: `Modulare Teile ändern`,
detailedView_suitInvigorationLabel: `[UNTRANSLATED] Warframe Invigoration`, detailedView_suitInvigorationLabel: `Warframe-Kräftigung`,
invigorations_offensive_AbilityStrength: `[UNTRANSLATED] +200% Ability Strength`, invigorations_offensive_AbilityStrength: `+200% Fähigkeitsstärke`,
invigorations_offensive_AbilityRange: `[UNTRANSLATED] +100% Ability Range`, invigorations_offensive_AbilityRange: `+100% Fähigkeitsreichweite`,
invigorations_offensive_AbilityDuration: `[UNTRANSLATED] +100% Ability Duration`, invigorations_offensive_AbilityDuration: `+100% Fähigkeitsdauer`,
invigorations_offensive_MeleeDamage: `[UNTRANSLATED] +250% Melee Damage`, invigorations_offensive_MeleeDamage: `+250% Nahkampfschaden`,
invigorations_offensive_PrimaryDamage: `[UNTRANSLATED] +250% Primary Damage`, invigorations_offensive_PrimaryDamage: `+250% Primärwaffen Schaden`,
invigorations_offensive_SecondaryDamage: `[UNTRANSLATED] +250% Secondary Damage`, invigorations_offensive_SecondaryDamage: `+250% Sekundärwaffen Schaden`,
invigorations_offensive_PrimaryCritChance: `[UNTRANSLATED] +200% Primary Critical Chance`, invigorations_offensive_PrimaryCritChance: `+200% Primärwaffen Krit. Chance`,
invigorations_offensive_SecondaryCritChance: `[UNTRANSLATED] +200% Secondary Critical Chance`, invigorations_offensive_SecondaryCritChance: `+200% Sekundärwaffen Krit. Chance`,
invigorations_offensive_MeleeCritChance: `[UNTRANSLATED] +200% Melee Critical Chance`, invigorations_offensive_MeleeCritChance: `+200% Nahkampfwaffen Krit. Chance`,
invigorations_utility_AbilityEfficiency: `[UNTRANSLATED] +75% Ability Efficiency`, invigorations_utility_AbilityEfficiency: `+75% Fähigkeitseffizienz`,
invigorations_utility_SprintSpeed: `[UNTRANSLATED] +75% Sprint Speed`, invigorations_utility_SprintSpeed: `+75% Sprintgeschwindigkeit`,
invigorations_utility_ParkourVelocity: `[UNTRANSLATED] +75% Parkour Velocity`, invigorations_utility_ParkourVelocity: `+75% Parkourgeschwindigkeit`,
invigorations_utility_HealthMax: `[UNTRANSLATED] +1000 Health`, invigorations_utility_HealthMax: `+1000 Gesundheit`,
invigorations_utility_EnergyMax: `[UNTRANSLATED] +200% Energy Max`, invigorations_utility_EnergyMax: `+200% Max. Energie`,
invigorations_utility_StatusImmune: `[UNTRANSLATED] Immune to Status Effects`, invigorations_utility_StatusImmune: `Immun gegen Statuseffekte`,
invigorations_utility_ReloadSpeed: `[UNTRANSLATED] +75% Reload Speed`, invigorations_utility_ReloadSpeed: `+75% Nachladegeschwindigkeit`,
invigorations_utility_HealthRegen: `[UNTRANSLATED] +25 Health Regen/s`, invigorations_utility_HealthRegen: `+25 Gesundheitsregeneration pro Sekunde`,
invigorations_utility_ArmorMax: `[UNTRANSLATED] +1000 Armor`, invigorations_utility_ArmorMax: `+1000 Rüstung`,
invigorations_utility_Jumps: `[UNTRANSLATED] +5 Jump Resets`, invigorations_utility_Jumps: `+5 Sprung-Zurücksetzungen`,
invigorations_utility_EnergyRegen: `[UNTRANSLATED] +2 Energy Regen/s`, invigorations_utility_EnergyRegen: `+2 Energieregeneration pro Sekunde`,
invigorations_offensiveLabel: `[UNTRANSLATED] Offensive Upgrade`, invigorations_offensiveLabel: `Offensives Upgrade`,
invigorations_defensiveLabel: `[UNTRANSLATED] Defensive Upgrade`, invigorations_defensiveLabel: `Defensives Upgrade`,
invigorations_expiryLabel: `[UNTRANSLATED] Upgrades Expiry (optional)`, invigorations_expiryLabel: `Upgrades Ablaufdatum (optional)`,
mods_addRiven: `Riven hinzufügen`, mods_addRiven: `Riven hinzufügen`,
mods_fingerprint: `Fingerabdruck`, mods_fingerprint: `Fingerabdruck`,
@ -306,7 +306,7 @@ dict = {
upgrade_WarframeGlobeEffectHealth: `+|VAL|% Wirksamkeit bei Gesundheitskugeln`, upgrade_WarframeGlobeEffectHealth: `+|VAL|% Wirksamkeit bei Gesundheitskugeln`,
upgrade_WarframeHealthMax: `+|VAL| Gesundheit`, upgrade_WarframeHealthMax: `+|VAL| Gesundheit`,
upgrade_WarframeHPBoostFromImpact: `+|VAL1| Gesundheit beim Töten eines Gegners mit Explosionsschaden (Max. |VAL2| Gesundheit)`, upgrade_WarframeHPBoostFromImpact: `+|VAL1| Gesundheit beim Töten eines Gegners mit Explosionsschaden (Max. |VAL2| Gesundheit)`,
upgrade_WarframeParkourVelocity: `+|VAL|% Parkour-Geschwindigkeit`, upgrade_WarframeParkourVelocity: `+|VAL|% Parkourgeschwindigkeit`,
upgrade_WarframeRadiationDamageBoost: `+|VAL|% Fähigkeitsschaden auf Gegner, die von Strahlungs-Status betroffen sind`, upgrade_WarframeRadiationDamageBoost: `+|VAL|% Fähigkeitsschaden auf Gegner, die von Strahlungs-Status betroffen sind`,
upgrade_WarframeHealthRegen: `+|VAL| Gesundheitsregeneration pro Sekunde`, upgrade_WarframeHealthRegen: `+|VAL| Gesundheitsregeneration pro Sekunde`,
upgrade_WarframeShieldMax: `+|VAL| Schildkapazität`, upgrade_WarframeShieldMax: `+|VAL| Schildkapazität`,
@ -327,15 +327,15 @@ dict = {
upgrade_OnFailHackReset: `+50% Chance, das Hacken bei Fehlschlag zu wiederholen`, upgrade_OnFailHackReset: `+50% Chance, das Hacken bei Fehlschlag zu wiederholen`,
upgrade_DamageReductionOnHack: `+75% Schadensreduktion beim Hacken`, upgrade_DamageReductionOnHack: `+75% Schadensreduktion beim Hacken`,
upgrade_OnExecutionReviveCompanion: `Gnadenstoß-Kills verkürzen die Erholungszeit des Begleiters um 15s`, upgrade_OnExecutionReviveCompanion: `Gnadenstoß-Kills verkürzen die Erholungszeit des Begleiters um 15s`,
upgrade_OnExecutionParkourSpeed: `+60% Parkour-Geschwindigkeit für 15s nach Gnadenstoß`, upgrade_OnExecutionParkourSpeed: `+60% Parkourgeschwindigkeit für 15s nach Gnadenstoß`,
upgrade_AvatarTimeLimitIncrease: `+8s extra Zeit beim Hacken`, upgrade_AvatarTimeLimitIncrease: `+8s extra Zeit beim Hacken`,
upgrade_ElectrifyOnHack: `Setze beim Hacken Gegner innerhalb von 20m unter Strom`, upgrade_ElectrifyOnHack: `Setze beim Hacken Gegner innerhalb von 20m unter Strom`,
upgrade_OnExecutionTerrify: `+50% Chance bei Gnadenstoß, dass Feinde innerhalb von 15m vor Furcht für 8s kauern`, upgrade_OnExecutionTerrify: `+50% Chance bei Gnadenstoß, dass Feinde innerhalb von 15m vor Furcht für 8s kauern`,
upgrade_OnHackLockers: `Schließe nach dem Hacken 5 Spinde innerhalb von 20m auf`, upgrade_OnHackLockers: `Schließe nach dem Hacken 5 Spinde innerhalb von 20m auf`,
upgrade_OnExecutionBlind: `Blende bei einem Gnadenstoß Gegner innerhalb von 18m`, upgrade_OnExecutionBlind: `Blende bei einem Gnadenstoß Gegner innerhalb von 18m`,
upgrade_OnExecutionDrainPower: `Nächste Fähigkeit erhält +50% Fähigkeitsstärke nach Gnadenstoß`, upgrade_OnExecutionDrainPower: `Nächste Fähigkeit erhält +50% Fähigkeitsstärke nach Gnadenstoß`,
upgrade_OnHackSprintSpeed: `+75% Sprint-Geschwindigkeit für 15s nach dem Hacken`, upgrade_OnHackSprintSpeed: `+75% Sprintgeschwindigkeit für 15s nach dem Hacken`,
upgrade_SwiftExecute: `+50% Gnadenstoß-Geschwindigkeit`, upgrade_SwiftExecute: `+50% Gnadenstoßgeschwindigkeit`,
upgrade_OnHackInvis: `+15s Unsichtbarkeit nach dem Hacken`, upgrade_OnHackInvis: `+15s Unsichtbarkeit nach dem Hacken`,
damageType_Electricity: `Elektrizität`, damageType_Electricity: `Elektrizität`,

View File

@ -63,7 +63,7 @@ dict = {
code_mature: `Listo para el combate`, code_mature: `Listo para el combate`,
code_unmature: `Regresar el envejecimiento genético`, code_unmature: `Regresar el envejecimiento genético`,
code_succChange: `Cambiado correctamente`, code_succChange: `Cambiado correctamente`,
code_requiredInvigorationUpgrade: `[UNTRANSLATED] You must select both an offensive & defensive upgrade.`, code_requiredInvigorationUpgrade: `Debes seleccionar una mejora ofensiva y una defensiva.`,
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).`,
login_emailLabel: `Dirección de correo electrónico`, login_emailLabel: `Dirección de correo electrónico`,
login_passwordLabel: `Contraseña`, login_passwordLabel: `Contraseña`,
@ -127,33 +127,33 @@ dict = {
detailedView_valenceBonusLabel: `Bônus de Valência`, detailedView_valenceBonusLabel: `Bônus de Valência`,
detailedView_valenceBonusDescription: `Puedes establecer o quitar el bono de valencia de tu arma.`, detailedView_valenceBonusDescription: `Puedes establecer o quitar el bono de valencia de tu arma.`,
detailedView_modularPartsLabel: `Cambiar partes modulares`, detailedView_modularPartsLabel: `Cambiar partes modulares`,
detailedView_suitInvigorationLabel: `[UNTRANSLATED] Warframe Invigoration`, detailedView_suitInvigorationLabel: `Vigorización de Warframe`,
invigorations_offensive_AbilityStrength: `[UNTRANSLATED] +200% Ability Strength`, invigorations_offensive_AbilityStrength: `+200% Fuerza de Habilidad`,
invigorations_offensive_AbilityRange: `[UNTRANSLATED] +100% Ability Range`, invigorations_offensive_AbilityRange: `+100% Alcance de Habilidad`,
invigorations_offensive_AbilityDuration: `[UNTRANSLATED] +100% Ability Duration`, invigorations_offensive_AbilityDuration: `+100% Duración de Habilidad`,
invigorations_offensive_MeleeDamage: `[UNTRANSLATED] +250% Melee Damage`, invigorations_offensive_MeleeDamage: `+250% Daño Cuerpo a Cuerpo`,
invigorations_offensive_PrimaryDamage: `[UNTRANSLATED] +250% Primary Damage`, invigorations_offensive_PrimaryDamage: `+250% Daño de Arma Principal`,
invigorations_offensive_SecondaryDamage: `[UNTRANSLATED] +250% Secondary Damage`, invigorations_offensive_SecondaryDamage: `+250% Daño de Arma Secundaria`,
invigorations_offensive_PrimaryCritChance: `[UNTRANSLATED] +200% Primary Critical Chance`, invigorations_offensive_PrimaryCritChance: `+200% Probabilidad Crítica de Arma Principal`,
invigorations_offensive_SecondaryCritChance: `[UNTRANSLATED] +200% Secondary Critical Chance`, invigorations_offensive_SecondaryCritChance: `+200% Probabilidad Crítica de Arma Secundaria`,
invigorations_offensive_MeleeCritChance: `[UNTRANSLATED] +200% Melee Critical Chance`, invigorations_offensive_MeleeCritChance: `+200% Probabilidad Crítica Cuerpo a Cuerpo`,
invigorations_utility_AbilityEfficiency: `[UNTRANSLATED] +75% Ability Efficiency`, invigorations_utility_AbilityEfficiency: `+75% Eficiencia de Habilidad`,
invigorations_utility_SprintSpeed: `[UNTRANSLATED] +75% Sprint Speed`, invigorations_utility_SprintSpeed: `+75% Velocidad de Sprint`,
invigorations_utility_ParkourVelocity: `[UNTRANSLATED] +75% Parkour Velocity`, invigorations_utility_ParkourVelocity: `+75% Velocidad de Parkour`,
invigorations_utility_HealthMax: `[UNTRANSLATED] +1000 Health`, invigorations_utility_HealthMax: `+1000 Salud Máx.`,
invigorations_utility_EnergyMax: `[UNTRANSLATED] +200% Energy Max`, invigorations_utility_EnergyMax: `+200% Energía Máx.`,
invigorations_utility_StatusImmune: `[UNTRANSLATED] Immune to Status Effects`, invigorations_utility_StatusImmune: `Inmune a Efectos de Estado`,
invigorations_utility_ReloadSpeed: `[UNTRANSLATED] +75% Reload Speed`, invigorations_utility_ReloadSpeed: `+75% Velocidad de Recarga`,
invigorations_utility_HealthRegen: `[UNTRANSLATED] +25 Health Regen/s`, invigorations_utility_HealthRegen: `+25 Regeneración de Salud/s`,
invigorations_utility_ArmorMax: `[UNTRANSLATED] +1000 Armor`, invigorations_utility_ArmorMax: `+1000 Armadura Máx.`,
invigorations_utility_Jumps: `[UNTRANSLATED] +5 Jump Resets`, invigorations_utility_Jumps: `+5 Restablecimientos de Salto`,
invigorations_utility_EnergyRegen: `[UNTRANSLATED] +2 Energy Regen/s`, invigorations_utility_EnergyRegen: `+2 Regeneración de Energía/s`,
invigorations_offensiveLabel: `[UNTRANSLATED] Offensive Upgrade`, invigorations_offensiveLabel: `Mejora Ofensiva`,
invigorations_defensiveLabel: `[UNTRANSLATED] Defensive Upgrade`, invigorations_defensiveLabel: `Mejora Defensiva`,
invigorations_expiryLabel: `[UNTRANSLATED] Upgrades Expiry (optional)`, invigorations_expiryLabel: `Caducidad de Mejoras (opcional)`,
mods_addRiven: `Agregar Agrietado`, mods_addRiven: `Agregar Agrietado`,
mods_fingerprint: `Huella digital`, mods_fingerprint: `Huella digital`,

View File

@ -1,11 +1,11 @@
// French translation by Vitruvio // French translation by Vitruvio
dict = { dict = {
general_inventoryUpdateNote: `[UNTRANSLATED] 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 : Pour voir les changements en jeu, l'inventaire doit être actualisé. Cela se fait en tapant /sync dans le tchat, en visitant un dojo/relais ou en se reconnectant.`,
general_addButton: `Ajouter`, general_addButton: `Ajouter`,
general_setButton: `[UNTRANSLATED] Set`, general_setButton: `Définir`,
general_none: `Aucun`, general_none: `Aucun`,
general_bulkActions: `Action groupée`, general_bulkActions: `Action groupée`,
general_loading: `[UNTRANSLATED] Loading...`, general_loading: `Chargement...`,
code_loginFail: `Connexion échouée. Vérifiez le mot de passe.`, code_loginFail: `Connexion échouée. Vérifiez le mot de passe.`,
code_regFail: `Enregistrement impossible. Compte existant?`, code_regFail: `Enregistrement impossible. Compte existant?`,
@ -46,8 +46,8 @@ dict = {
code_focusUnlocked: `|COUNT| écoles de Focus déverrouillées ! Synchronisation de l'inventaire nécessaire.`, code_focusUnlocked: `|COUNT| écoles de Focus déverrouillées ! Synchronisation de l'inventaire nécessaire.`,
code_addModsConfirm: `Ajouter |COUNT| mods à l'inventaire ?`, code_addModsConfirm: `Ajouter |COUNT| mods à l'inventaire ?`,
code_succImport: `Importé.`, code_succImport: `Importé.`,
code_succRelog: `[UNTRANSLATED] Done. Please note that you'll need to relog to see a difference in-game.`, code_succRelog: `Succès. Un redémarrage du jeu est nécessaire.`,
code_nothingToDo: `[UNTRANSLATED] Done. There was nothing to do.`, code_nothingToDo: `Succès.`,
code_gild: `Polir`, code_gild: `Polir`,
code_moa: `Moa`, code_moa: `Moa`,
code_zanuka: `Molosse`, code_zanuka: `Molosse`,
@ -62,8 +62,8 @@ dict = {
code_pigment: `Pigment`, code_pigment: `Pigment`,
code_mature: `Maturer pour le combat`, code_mature: `Maturer pour le combat`,
code_unmature: `Régrésser l'âge génétique`, code_unmature: `Régrésser l'âge génétique`,
code_succChange: `[UNTRANSLATED] Successfully changed.`, code_succChange: `Changement effectué.`,
code_requiredInvigorationUpgrade: `[UNTRANSLATED] You must select both an offensive & defensive upgrade.`, code_requiredInvigorationUpgrade: `Augmentation offensive et défensive requises.`,
login_description: `Connexion avec les informations de connexion OpenWF.`, login_description: `Connexion avec les informations de connexion OpenWF.`,
login_emailLabel: `Email`, login_emailLabel: `Email`,
login_passwordLabel: `Mot de passe`, login_passwordLabel: `Mot de passe`,
@ -125,42 +125,42 @@ dict = {
detailedView_archonShardsDescription: `Slots illimités pour appliquer plusieurs améliorations`, detailedView_archonShardsDescription: `Slots illimités pour appliquer plusieurs améliorations`,
detailedView_archonShardsDescription2: `Un délai sera présent entre l'application des éclats et le chargement en jeu.`, detailedView_archonShardsDescription2: `Un délai sera présent entre l'application des éclats et le chargement en jeu.`,
detailedView_valenceBonusLabel: `Bonus de Valence`, detailedView_valenceBonusLabel: `Bonus de Valence`,
detailedView_valenceBonusDescription: `[UNTRANSLATED] You can set or remove the Valence Bonus from your weapon.`, detailedView_valenceBonusDescription: `Définir le Bonus Valence de l'arme.`,
detailedView_modularPartsLabel: `[UNTRANSLATED] Change Modular Parts`, detailedView_modularPartsLabel: `Changer l'équipement modulaire`,
detailedView_suitInvigorationLabel: `[UNTRANSLATED] Warframe Invigoration`, detailedView_suitInvigorationLabel: `Invigoration de Warframe`,
invigorations_offensive_AbilityStrength: `[UNTRANSLATED] +200% Ability Strength`, invigorations_offensive_AbilityStrength: `+200% de puissance de pouvoir`,
invigorations_offensive_AbilityRange: `[UNTRANSLATED] +100% Ability Range`, invigorations_offensive_AbilityRange: `+100% de portée de pouvoir`,
invigorations_offensive_AbilityDuration: `[UNTRANSLATED] +100% Ability Duration`, invigorations_offensive_AbilityDuration: `+100% de durée de pouvoir`,
invigorations_offensive_MeleeDamage: `[UNTRANSLATED] +250% Melee Damage`, invigorations_offensive_MeleeDamage: `+250% de dégâts de mêlée`,
invigorations_offensive_PrimaryDamage: `[UNTRANSLATED] +250% Primary Damage`, invigorations_offensive_PrimaryDamage: `+250% de dégâts d'arme primaire`,
invigorations_offensive_SecondaryDamage: `[UNTRANSLATED] +250% Secondary Damage`, invigorations_offensive_SecondaryDamage: `+250% de dégâts d'arme secondaire`,
invigorations_offensive_PrimaryCritChance: `[UNTRANSLATED] +200% Primary Critical Chance`, invigorations_offensive_PrimaryCritChance: `+200% de chances critique sur arme primaire`,
invigorations_offensive_SecondaryCritChance: `[UNTRANSLATED] +200% Secondary Critical Chance`, invigorations_offensive_SecondaryCritChance: `+200% de chances critique sur arme secondaire`,
invigorations_offensive_MeleeCritChance: `[UNTRANSLATED] +200% Melee Critical Chance`, invigorations_offensive_MeleeCritChance: `+200% de chances critique en mêlée`,
invigorations_utility_AbilityEfficiency: `[UNTRANSLATED] +75% Ability Efficiency`, invigorations_utility_AbilityEfficiency: `+75% d'efficacité de pouvoir`,
invigorations_utility_SprintSpeed: `[UNTRANSLATED] +75% Sprint Speed`, invigorations_utility_SprintSpeed: `+75% de vitesse de course`,
invigorations_utility_ParkourVelocity: `[UNTRANSLATED] +75% Parkour Velocity`, invigorations_utility_ParkourVelocity: `+75% de vélocité de parkour`,
invigorations_utility_HealthMax: `[UNTRANSLATED] +1000 Health`, invigorations_utility_HealthMax: `+1000 de vie`,
invigorations_utility_EnergyMax: `[UNTRANSLATED] +200% Energy Max`, invigorations_utility_EnergyMax: `+200% d'énergie max`,
invigorations_utility_StatusImmune: `[UNTRANSLATED] Immune to Status Effects`, invigorations_utility_StatusImmune: `Immunisé contre les effets de statut`,
invigorations_utility_ReloadSpeed: `[UNTRANSLATED] +75% Reload Speed`, invigorations_utility_ReloadSpeed: `+75% de vitesse de rechargement`,
invigorations_utility_HealthRegen: `[UNTRANSLATED] +25 Health Regen/s`, invigorations_utility_HealthRegen: `+25 de vie régénérés/s`,
invigorations_utility_ArmorMax: `[UNTRANSLATED] +1000 Armor`, invigorations_utility_ArmorMax: `+1000 d'armure`,
invigorations_utility_Jumps: `[UNTRANSLATED] +5 Jump Resets`, invigorations_utility_Jumps: `+5 réinitialisations de saut`,
invigorations_utility_EnergyRegen: `[UNTRANSLATED] +2 Energy Regen/s`, invigorations_utility_EnergyRegen: `+2 d'énergie régénérés/s`,
invigorations_offensiveLabel: `[UNTRANSLATED] Offensive Upgrade`, invigorations_offensiveLabel: `Amélioration offensive`,
invigorations_defensiveLabel: `[UNTRANSLATED] Defensive Upgrade`, invigorations_defensiveLabel: `Amélioration défensive`,
invigorations_expiryLabel: `[UNTRANSLATED] Upgrades Expiry (optional)`, invigorations_expiryLabel: `Expiration de l'invigoration (optionnel)`,
mods_addRiven: `Ajouter un riven`, mods_addRiven: `Ajouter un riven`,
mods_fingerprint: `Empreinte`, mods_fingerprint: `Empreinte`,
mods_fingerprintHelp: `Besoin d'aide pour l'empreinte ?`, mods_fingerprintHelp: `Besoin d'aide pour l'empreinte ?`,
mods_rivens: `Rivens`, mods_rivens: `Rivens`,
mods_mods: `Mods`, mods_mods: `Mods`,
mods_addMax: `[UNTRANSLATED] Add Maxed`, mods_addMax: `Ajouter les mods niveau max`,
mods_addMissingUnrankedMods: `Ajouter les mods sans rang manquants`, mods_addMissingUnrankedMods: `Ajouter les mods sans rang manquants`,
mods_removeUnranked: `Retirer les mods sans rang`, mods_removeUnranked: `Retirer les mods sans rang`,
mods_addMissingMaxRankMods: `Ajouter les mods niveau max manquants`, mods_addMissingMaxRankMods: `Ajouter les mods niveau max manquants`,
@ -170,7 +170,7 @@ dict = {
cheats_skipAllDialogue: `Passer les dialogues`, cheats_skipAllDialogue: `Passer les dialogues`,
cheats_unlockAllScans: `Débloquer tous les scans`, cheats_unlockAllScans: `Débloquer tous les scans`,
cheats_unlockAllMissions: `Débloquer toutes les missions`, cheats_unlockAllMissions: `Débloquer toutes les missions`,
cheats_unlockAllMissions_ok: `[UNTRANSLATED] 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: `Succès. Une actualisation de l'inventaire est nécessaire.`,
cheats_infiniteCredits: `Crédits infinis`, cheats_infiniteCredits: `Crédits infinis`,
cheats_infinitePlatinum: `Platinum infini`, cheats_infinitePlatinum: `Platinum infini`,
cheats_infiniteEndo: `Endo infini`, cheats_infiniteEndo: `Endo infini`,
@ -201,8 +201,8 @@ dict = {
cheats_noDeathMarks: `Aucune marque d'assassin`, cheats_noDeathMarks: `Aucune marque d'assassin`,
cheats_noKimCooldowns: `Aucun cooldown sur le KIM`, cheats_noKimCooldowns: `Aucun cooldown sur le KIM`,
cheats_fullyStockedVendors: `Les vendeurs ont un stock à 100%`, cheats_fullyStockedVendors: `Les vendeurs ont un stock à 100%`,
cheats_baroAlwaysAvailable: `[UNTRANSLATED] Baro Always Available`, cheats_baroAlwaysAvailable: `Baro toujours présent`,
cheats_baroFullyStocked: `[UNTRANSLATED] Baro Fully Stocked`, cheats_baroFullyStocked: `Stock de Baro au max`,
cheats_syndicateMissionsRepeatable: `Mission syndicat répétables`, cheats_syndicateMissionsRepeatable: `Mission syndicat répétables`,
cheats_unlockAllProfitTakerStages: `Débloquer toutes les étapes du Preneur de Profit`, cheats_unlockAllProfitTakerStages: `Débloquer toutes les étapes du Preneur de Profit`,
cheats_instantFinishRivenChallenge: `Débloquer le challenge Riven instantanément`, cheats_instantFinishRivenChallenge: `Débloquer le challenge Riven instantanément`,
@ -216,75 +216,75 @@ dict = {
cheats_noDojoResearchTime: `Aucun temps de recherche (Dojo)`, cheats_noDojoResearchTime: `Aucun temps de recherche (Dojo)`,
cheats_fastClanAscension: `Ascension de clan rapide`, cheats_fastClanAscension: `Ascension de clan rapide`,
cheats_missionsCanGiveAllRelics: `Les missions donnent toutes les reliques`, cheats_missionsCanGiveAllRelics: `Les missions donnent toutes les reliques`,
cheats_exceptionalRelicsAlwaysGiveBronzeReward: `[UNTRANSLATED] Exceptional Relics Always Give Bronze Reward`, cheats_exceptionalRelicsAlwaysGiveBronzeReward: `Les reliques exceptionnelles donnent toujours une récompense en bronze`,
cheats_flawlessRelicsAlwaysGiveSilverReward: `[UNTRANSLATED] Flawless Relics Always Give Silver Reward`, cheats_flawlessRelicsAlwaysGiveSilverReward: `Les reliques parfaites donnent toujours une récompense en argent`,
cheats_radiantRelicsAlwaysGiveGoldReward: `[UNTRANSLATED] Radiant Relics Always Give Gold Reward`, cheats_radiantRelicsAlwaysGiveGoldReward: `Les reliques éclatantes donnent toujours une récompense en or`,
cheats_unlockAllSimarisResearchEntries: `Débloquer toute les recherches chez Simaris`, cheats_unlockAllSimarisResearchEntries: `Débloquer toute les recherches chez Simaris`,
cheats_disableDailyTribute: `[UNTRANSLATED] Disable Daily Tribute`, cheats_disableDailyTribute: `Désactiver la récompense quotidienne de connexion`,
cheats_spoofMasteryRank: `Rang de maîtrise personnalisé (-1 pour désactiver)`, cheats_spoofMasteryRank: `Rang de maîtrise personnalisé (-1 pour désactiver)`,
cheats_relicRewardItemCountMultiplier: `[UNTRANSLATED] Relic Reward Item Count Multiplier`, cheats_relicRewardItemCountMultiplier: `Multiplicateur de récompenses de relique`,
cheats_nightwaveStandingMultiplier: `Multiplicateur de réputation d'Ondes Nocturnes`, cheats_nightwaveStandingMultiplier: `Multiplicateur de réputation d'Ondes Nocturnes`,
cheats_save: `Sauvegarder`, cheats_save: `Sauvegarder`,
cheats_account: `Compte`, cheats_account: `Compte`,
cheats_unlockAllFocusSchools: `Débloquer toutes les écoles de focus`, cheats_unlockAllFocusSchools: `Débloquer toutes les écoles de focus`,
cheats_helminthUnlockAll: `Helminth niveau max`, cheats_helminthUnlockAll: `Helminth niveau max`,
cheats_addMissingSubsumedAbilities: `[UNTRANSLATED] Add Missing Subsumed Abilities`, cheats_addMissingSubsumedAbilities: `Ajouter les capacités subsumées manquantes`,
cheats_intrinsicsUnlockAll: `Inhérences niveau max`, cheats_intrinsicsUnlockAll: `Inhérences niveau max`,
cheats_changeSupportedSyndicate: `Allégeance`, cheats_changeSupportedSyndicate: `Allégeance`,
cheats_changeButton: `Changer`, cheats_changeButton: `Changer`,
cheats_markAllAsRead: `[UNTRANSLATED] Mark Inbox As Read`, cheats_markAllAsRead: `Marquer la boîte de réception comme lue`,
worldState: `[UNTRANSLATED] World State`, worldState: `Carte Solaire`,
worldState_creditBoost: `[UNTRANSLATED] Credit Boost`, worldState_creditBoost: `Booster de Crédit`,
worldState_affinityBoost: `[UNTRANSLATED] Affinity Boost`, worldState_affinityBoost: `Booster d'Affinité`,
worldState_resourceBoost: `[UNTRANSLATED] Resource Boost`, worldState_resourceBoost: `Booster de Ressource`,
worldState_starDays: `[UNTRANSLATED] Star Days`, worldState_starDays: `Jours Stellaires`,
worldState_galleonOfGhouls: `[UNTRANSLATED] Galleon of Ghouls`, worldState_galleonOfGhouls: `Galion des Goules`,
disabled: `[UNTRANSLATED] Disabled`, disabled: `Désactivé`,
worldState_we1: `[UNTRANSLATED] Weekend 1`, worldState_we1: `Weekend 1`,
worldState_we2: `[UNTRANSLATED] Weekend 2`, worldState_we2: `Weekend 2`,
worldState_we3: `[UNTRANSLATED] Weekend 3`, worldState_we3: `Weekend 3`,
worldState_eidolonOverride: `[UNTRANSLATED] Eidolon Override`, worldState_eidolonOverride: `Météo Plaines d'Eidolon`,
worldState_day: `[UNTRANSLATED] Day`, worldState_day: `Jour`,
worldState_night: `[UNTRANSLATED] Night`, worldState_night: `Nuit`,
worldState_vallisOverride: `[UNTRANSLATED] Orb Vallis Override`, worldState_vallisOverride: `Météo Vallée Orbis`,
worldState_warm: `[UNTRANSLATED] Warm`, worldState_warm: `Chaud`,
worldState_cold: `[UNTRANSLATED] Cold`, worldState_cold: `Froid`,
worldState_duviriOverride: `[UNTRANSLATED] Duviri Override`, worldState_duviriOverride: `Spirale Duviri`,
worldState_joy: `[UNTRANSLATED] Joy`, worldState_joy: `Joie`,
worldState_anger: `[UNTRANSLATED] Anger`, worldState_anger: `Colère`,
worldState_envy: `[UNTRANSLATED] Envy`, worldState_envy: `Envie `,
worldState_sorrow: `[UNTRANSLATED] Sorrow`, worldState_sorrow: `hagrin`,
worldState_fear: `[UNTRANSLATED] Fear`, worldState_fear: `Peur`,
worldState_nightwaveOverride: `[UNTRANSLATED] Nightwave Override`, worldState_nightwaveOverride: `Saison d'Ondes Nocturnes`,
worldState_RadioLegionIntermission13Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 9`, worldState_RadioLegionIntermission13Syndicate: `Mix de Nora Vol. 9`,
worldState_RadioLegionIntermission12Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 8`, worldState_RadioLegionIntermission12Syndicate: `Mix de Nora Vol. 8`,
worldState_RadioLegionIntermission11Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 7`, worldState_RadioLegionIntermission11Syndicate: `Mix de Nora Vol. 7`,
worldState_RadioLegionIntermission10Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 6`, worldState_RadioLegionIntermission10Syndicate: `Mix de Nora Vol. 6`,
worldState_RadioLegionIntermission9Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 5`, worldState_RadioLegionIntermission9Syndicate: `Mix de Nora Vol. 5`,
worldState_RadioLegionIntermission8Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 4`, worldState_RadioLegionIntermission8Syndicate: `Mix de Nora Vol. 4`,
worldState_RadioLegionIntermission7Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 3`, worldState_RadioLegionIntermission7Syndicate: `Mix de Nora Vol. 3`,
worldState_RadioLegionIntermission6Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 2`, worldState_RadioLegionIntermission6Syndicate: `Mix de Nora Vol. 2`,
worldState_RadioLegionIntermission5Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 1`, worldState_RadioLegionIntermission5Syndicate: `Mix de Nora Vol. 1`,
worldState_RadioLegionIntermission4Syndicate: `[UNTRANSLATED] Nora's Choice`, worldState_RadioLegionIntermission4Syndicate: `La Sélection de Nora`,
worldState_RadioLegionIntermission3Syndicate: `[UNTRANSLATED] Intermission III`, worldState_RadioLegionIntermission3Syndicate: `Intermission III`,
worldState_RadioLegion3Syndicate: `[UNTRANSLATED] Glassmaker`, worldState_RadioLegion3Syndicate: `Les Mystères du Verre`,
worldState_RadioLegionIntermission2Syndicate: `[UNTRANSLATED] Intermission II`, worldState_RadioLegionIntermission2Syndicate: `Intermission II`,
worldState_RadioLegion2Syndicate: `[UNTRANSLATED] The Emissary`, worldState_RadioLegion2Syndicate: `L'Émissaire`,
worldState_RadioLegionIntermissionSyndicate: `[UNTRANSLATED] Intermission I`, worldState_RadioLegionIntermissionSyndicate: `Intermission I`,
worldState_RadioLegionSyndicate: `[UNTRANSLATED] The Wolf of Saturn Six`, worldState_RadioLegionSyndicate: `Le Loup de Saturne Six`,
worldState_fissures: `[UNTRANSLATED] Fissures`, worldState_fissures: `Fissures`,
normal: `[UNTRANSLATED] Normal`, normal: `Normal`,
worldState_allAtOnceNormal: `[UNTRANSLATED] All At Once, Normal`, worldState_allAtOnceNormal: `Toutes, Normal`,
worldState_allAtOnceSteelPath: `[UNTRANSLATED] All At Once, Steel Path`, worldState_allAtOnceSteelPath: `Toutes, Route de l'Acier`,
worldState_theCircuitOverride: `[UNTRANSLATED] The Circuit Override`, worldState_theCircuitOverride: `Remplacement du Circuit`,
worldState_darvoStockMultiplier: `[UNTRANSLATED] Darvo Stock Multiplier`, worldState_darvoStockMultiplier: `Multiplicateur du stock de Darvo`,
worldState_varziaFullyStocked: `[UNTRANSLATED] Varzia Fully Stocked`, worldState_varziaFullyStocked: `Stock de Varzia au max`,
worldState_varziaOverride: `[UNTRANSLATED] Varzia Rotation Override`, worldState_varziaOverride: `Rotation de Varzia`,
import_importNote: `Import manuel. Toutes les modifcations supportées par l'inventaire <b>écraseront celles présentes dans la base de données</b>.`, import_importNote: `Import manuel. Toutes les modifcations supportées par l'inventaire <b>écraseront celles présentes dans la base de données</b>.`,
import_submit: `Soumettre`, import_submit: `Soumettre`,
import_samples: `Echantillons :`, import_samples: `Échantillons :`,
import_samples_maxFocus: `Toutes les écoles de focus au rang max`, import_samples_maxFocus: `Toutes les écoles de focus au rang max`,
upgrade_Equilibrium: `Ramasser de la santé donne +|VAL|% d'énergie supplémentaire. Ramasser de l'énergie donne +|VAL|% de santé supplémentaire.`, upgrade_Equilibrium: `Ramasser de la santé donne +|VAL|% d'énergie supplémentaire. Ramasser de l'énergie donne +|VAL|% de santé supplémentaire.`,
@ -305,7 +305,7 @@ dict = {
upgrade_WarframeGlobeEffectEnergy: `+|VAL|% d'efficacité d'orbe d'énergie`, upgrade_WarframeGlobeEffectEnergy: `+|VAL|% d'efficacité d'orbe d'énergie`,
upgrade_WarframeGlobeEffectHealth: `+|VAL|% d'efficacité d'orbe de santé`, upgrade_WarframeGlobeEffectHealth: `+|VAL|% d'efficacité d'orbe de santé`,
upgrade_WarframeHealthMax: `+|VAL| de santé`, upgrade_WarframeHealthMax: `+|VAL| de santé`,
upgrade_WarframeHPBoostFromImpact: `[UNTRANSLATED] +|VAL1| Health on kill with Blast Damage (Max |VAL2| Health)`, upgrade_WarframeHPBoostFromImpact: `+|VAL1| de vie sur élimination avec des dégâts d'explostion (Max |VAL2| Health)`,
upgrade_WarframeParkourVelocity: `+|VAL|% de vélocité de parkour`, upgrade_WarframeParkourVelocity: `+|VAL|% de vélocité de parkour`,
upgrade_WarframeRadiationDamageBoost: `+|VAL|% de dégâts de pouvoir sur les ennemis affectés par du statut radiation`, upgrade_WarframeRadiationDamageBoost: `+|VAL|% de dégâts de pouvoir sur les ennemis affectés par du statut radiation`,
upgrade_WarframeHealthRegen: `+|VAL| régénération de santé/s`, upgrade_WarframeHealthRegen: `+|VAL| régénération de santé/s`,
@ -324,7 +324,7 @@ dict = {
upgrade_OnExecutionAmmo: `100% de rechargement des armes primaires et secondaires sur une une miséricorde`, upgrade_OnExecutionAmmo: `100% de rechargement des armes primaires et secondaires sur une une miséricorde`,
upgrade_OnExecutionHealthDrop: `100% de chance de drop une orbe de santé sur une miséricorde`, upgrade_OnExecutionHealthDrop: `100% de chance de drop une orbe de santé sur une miséricorde`,
upgrade_OnExecutionEnergyDrop: `50% de chance de drop une orbe d'énergie sur une miséricorde`, upgrade_OnExecutionEnergyDrop: `50% de chance de drop une orbe d'énergie sur une miséricorde`,
upgrade_OnFailHackReset: `[UNTRANSLATED] +50% Hacking Retry Chance`, upgrade_OnFailHackReset: `+50% de chance de refaire un piratage`,
upgrade_DamageReductionOnHack: `75% de réduction de dégâts pendant un piratage`, upgrade_DamageReductionOnHack: `75% de réduction de dégâts pendant un piratage`,
upgrade_OnExecutionReviveCompanion: `Les miséricordes réduisent le temps de récupération du compagnon de 15s`, upgrade_OnExecutionReviveCompanion: `Les miséricordes réduisent le temps de récupération du compagnon de 15s`,
upgrade_OnExecutionParkourSpeed: `+60% de vitesse de parkour pendant 15s après une miséricorde`, upgrade_OnExecutionParkourSpeed: `+60% de vitesse de parkour pendant 15s après une miséricorde`,
@ -333,10 +333,10 @@ dict = {
upgrade_OnExecutionTerrify: `Les ennemis dans un rayon de 15m ont 50% de chance de s'enfuir après une miséricorde`, upgrade_OnExecutionTerrify: `Les ennemis dans un rayon de 15m ont 50% de chance de s'enfuir après une miséricorde`,
upgrade_OnHackLockers: `5 casiers s'ouvrent dans un rayon de 20m après un piratage`, upgrade_OnHackLockers: `5 casiers s'ouvrent dans un rayon de 20m après un piratage`,
upgrade_OnExecutionBlind: `Les ennemis sont aveuglés dans un rayon de 18 après une miséricorde`, upgrade_OnExecutionBlind: `Les ennemis sont aveuglés dans un rayon de 18 après une miséricorde`,
upgrade_OnExecutionDrainPower: `[UNTRANSLATED] Next ability cast gains +50% Ability Strength on Mercy`, upgrade_OnExecutionDrainPower: `Le prochain pouvoir activé gagne +50% de puissance de pouvoir après une miséricorde`,
upgrade_OnHackSprintSpeed: `+75% de vitesse de course pendant 15s après un piratage`, upgrade_OnHackSprintSpeed: `+75% de vitesse de course pendant 15s après un piratage`,
upgrade_SwiftExecute: `[UNTRANSLATED] +50% Mercy Kill Speed`, upgrade_SwiftExecute: `+50% de vitesse de d'éxecution en miséricorde`,
upgrade_OnHackInvis: `[UNTRANSLATED] Invisible for 15 seconds after Hacking`, upgrade_OnHackInvis: `Invisible pendant 15s après un piratage`,
damageType_Electricity: `Électrique`, damageType_Electricity: `Électrique`,
damageType_Fire: `Feu`, damageType_Fire: `Feu`,
@ -346,8 +346,8 @@ dict = {
damageType_Poison: `Poison`, damageType_Poison: `Poison`,
damageType_Radiation: `Radiations`, damageType_Radiation: `Radiations`,
theme_dark: `[UNTRANSLATED] Dark Theme`, theme_dark: `Thème sombre`,
theme_light: `[UNTRANSLATED] Light Theme`, theme_light: `Thème clair`,
prettier_sucks_ass: `` prettier_sucks_ass: ``
}; };

View File

@ -63,7 +63,7 @@ dict = {
code_mature: `成长并战备`, code_mature: `成长并战备`,
code_unmature: `逆转衰老基因`, code_unmature: `逆转衰老基因`,
code_succChange: `更改成功.`, code_succChange: `更改成功.`,
code_requiredInvigorationUpgrade: `[UNTRANSLATED] You must select both an offensive & defensive upgrade.`, code_requiredInvigorationUpgrade: `您必须同时选择一个进攻型和一个功能型活化属性.`,
login_description: `使用您的 OpenWF 账户凭证登录(与游戏内连接本服务器时使用的昵称相同).`, login_description: `使用您的 OpenWF 账户凭证登录(与游戏内连接本服务器时使用的昵称相同).`,
login_emailLabel: `电子邮箱`, login_emailLabel: `电子邮箱`,
login_passwordLabel: `密码`, login_passwordLabel: `密码`,
@ -127,29 +127,29 @@ dict = {
detailedView_valenceBonusLabel: `效价加成`, detailedView_valenceBonusLabel: `效价加成`,
detailedView_valenceBonusDescription: `您可以设置或移除武器上的效价加成.`, detailedView_valenceBonusDescription: `您可以设置或移除武器上的效价加成.`,
detailedView_modularPartsLabel: `更换部件`, detailedView_modularPartsLabel: `更换部件`,
detailedView_suitInvigorationLabel: `[UNTRANSLATED] Warframe Invigoration`, detailedView_suitInvigorationLabel: `编辑战甲活化属性`,
invigorations_offensive_AbilityStrength: `[UNTRANSLATED] +200% Ability Strength`, invigorations_offensive_AbilityStrength: `+200%技能强度`,
invigorations_offensive_AbilityRange: `[UNTRANSLATED] +100% Ability Range`, invigorations_offensive_AbilityRange: `+100%技能范围`,
invigorations_offensive_AbilityDuration: `[UNTRANSLATED] +100% Ability Duration`, invigorations_offensive_AbilityDuration: `+100%技能持续时间`,
invigorations_offensive_MeleeDamage: `[UNTRANSLATED] +250% Melee Damage`, invigorations_offensive_MeleeDamage: `+250%近战武器伤害`,
invigorations_offensive_PrimaryDamage: `[UNTRANSLATED] +250% Primary Damage`, invigorations_offensive_PrimaryDamage: `+250%主要武器伤害`,
invigorations_offensive_SecondaryDamage: `[UNTRANSLATED] +250% Secondary Damage`, invigorations_offensive_SecondaryDamage: `+250%次要武器伤害`,
invigorations_offensive_PrimaryCritChance: `[UNTRANSLATED] +200% Primary Critical Chance`, invigorations_offensive_PrimaryCritChance: `+200%主要武器暴击几率`,
invigorations_offensive_SecondaryCritChance: `[UNTRANSLATED] +200% Secondary Critical Chance`, invigorations_offensive_SecondaryCritChance: `+200%次要武器暴击几率`,
invigorations_offensive_MeleeCritChance: `[UNTRANSLATED] +200% Melee Critical Chance`, invigorations_offensive_MeleeCritChance: `+200%近战武器暴击几率`,
invigorations_utility_AbilityEfficiency: `[UNTRANSLATED] +75% Ability Efficiency`, invigorations_utility_AbilityEfficiency: `+75%技能效率`,
invigorations_utility_SprintSpeed: `[UNTRANSLATED] +75% Sprint Speed`, invigorations_utility_SprintSpeed: `+75%冲刺速度`,
invigorations_utility_ParkourVelocity: `[UNTRANSLATED] +75% Parkour Velocity`, invigorations_utility_ParkourVelocity: `+75%跑酷速度`,
invigorations_utility_HealthMax: `[UNTRANSLATED] +1000 Health`, invigorations_utility_HealthMax: `+1000生命值`,
invigorations_utility_EnergyMax: `[UNTRANSLATED] +200% Energy Max`, invigorations_utility_EnergyMax: `+200%最大能量`,
invigorations_utility_StatusImmune: `[UNTRANSLATED] Immune to Status Effects`, invigorations_utility_StatusImmune: `异常状态免疫`,
invigorations_utility_ReloadSpeed: `[UNTRANSLATED] +75% Reload Speed`, invigorations_utility_ReloadSpeed: `+75%装填速度`,
invigorations_utility_HealthRegen: `[UNTRANSLATED] +25 Health Regen/s`, invigorations_utility_HealthRegen: `+25/秒生命再生`,
invigorations_utility_ArmorMax: `[UNTRANSLATED] +1000 Armor`, invigorations_utility_ArmorMax: `+1000护甲值`,
invigorations_utility_Jumps: `[UNTRANSLATED] +5 Jump Resets`, invigorations_utility_Jumps: `+5跳跃次数`,
invigorations_utility_EnergyRegen: `[UNTRANSLATED] +2 Energy Regen/s`, invigorations_utility_EnergyRegen: `+2/秒能量再生`,
invigorations_offensiveLabel: `进攻型属性`, invigorations_offensiveLabel: `进攻型属性`,
invigorations_defensiveLabel: `功能型属性`, invigorations_defensiveLabel: `功能型属性`,