Compare commits
20 Commits
8d54b2c043
...
6df625e838
| Author | SHA1 | Date | |
|---|---|---|---|
| 6df625e838 | |||
| 9c561dfb75 | |||
| d481b4b26d | |||
| 086a0c467c | |||
| 1066b4a983 | |||
| b9a2cea862 | |||
| 0342f52359 | |||
| ea9012bd56 | |||
| 13400b6d83 | |||
| 8d57eda9d2 | |||
| 6b66cb495b | |||
| f4f7ed00d1 | |||
| 18556cb2f5 | |||
| 648af9ae18 | |||
| e16da9da44 | |||
| 4d8dbd99aa | |||
| 0a3f9549a9 | |||
| 2cfb21b98e | |||
| 3fedc701f1 | |||
| ed596aa3f3 |
17
AGENTS.md
17
AGENTS.md
@ -1,17 +0,0 @@
|
||||
## In General
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Use `npm i` or `npm ci` to install all dependencies.
|
||||
|
||||
### Testing
|
||||
|
||||
Use `npm run verify` to verify that your changes pass TypeScript's checks.
|
||||
|
||||
### Formatting
|
||||
|
||||
Use `npm run prettier` to ensure your formatting matches the expected format. Failing to do so will cause CI failure.
|
||||
|
||||
## WebUI Specific
|
||||
|
||||
The translation system is designed around additions being made to `static/webui/translations/en.js`. They are copied over for translation via `npm run update-translations`. DO NOT produce non-English strings; we want them to be translated by humans who can understand the full context.
|
||||
19
CONTRIBUTING.md
Normal file
19
CONTRIBUTING.md
Normal file
@ -0,0 +1,19 @@
|
||||
## In General
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Use `npm i` or `npm ci` to install all dependencies, including dev dependencies.
|
||||
|
||||
## Development Process
|
||||
|
||||
Auto reloading is supported for server and WebUI development. Simply use `npm run dev` or `npm run dev:bun` to start the server and edit away.
|
||||
|
||||
### Testing
|
||||
|
||||
Before submitting a PR:
|
||||
- Use `npm run verify` to verify that the code is type-safe.
|
||||
- Use `npm run fix` to fix formatting issues as well as be informed of any unfixable issues. Avoid introducing new warnings.
|
||||
|
||||
## WebUI Specific
|
||||
|
||||
The translation system is designed around additions being made to `static/webui/translations/en.js`. They are copied over for translation via `npm run update-translations`. DO NOT provide translations generated by AI or other automated tools.
|
||||
@ -6,14 +6,15 @@ More information for the moment here: [https://discord.gg/PNNZ3asUuY](https://di
|
||||
|
||||
This project is in active development at <https://onlyg.it/OpenWF/SpaceNinjaServer>.
|
||||
|
||||
To get an idea of what functionality you can expect to be missing [have a look through the issues](https://onlyg.it/OpenWF/SpaceNinjaServer/issues?q=&type=all&state=open&labels=-4%2C-10&milestone=0&assignee=0&poster=). However, many things have been implemented and *should* work as expected. Please open an issue for anything where that's not the case and/or the server is reporting errors.
|
||||
To get an idea of what functionality you can expect to be missing [have a look through the issues](https://onlyg.it/OpenWF/SpaceNinjaServer/issues). However, many things have been implemented and *should* work as expected. Please open an issue for anything where that's not the case and/or the server is reporting errors.
|
||||
|
||||
## config.json
|
||||
|
||||
SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [config-vanilla.json](config-vanilla.json), which has most cheats disabled.
|
||||
|
||||
- `skipTutorial` affects only newly created accounts, so you may wish to change it before logging in for the first time.
|
||||
- `logger.level` can be `fatal`, `error`, `warn`, `info`, `http`, `debug`, or `trace`.
|
||||
- `myIrcAddresses` can be used to point to an IRC server. If not provided, defaults to `[ myAddress ]`.
|
||||
- `ircAddress`, `hubAddress`, and `nrsAddress` are not present by default but can be provided if these secondary servers are on a different machine.
|
||||
- `worldState.eidolonOverride` can be set to `day` or `night` to lock the time to day/fass and night/vome on Plains of Eidolon/Cambion Drift.
|
||||
- `worldState.vallisOverride` can be set to `warm` or `cold` to lock the temperature on Orb Vallis.
|
||||
- `worldState.duviriOverride` can be set to `joy`, `anger`, `envy`, `sorrow`, or `fear` to lock the Duviri spiral.
|
||||
|
||||
@ -14,13 +14,18 @@ if %errorlevel% == 0 (
|
||||
)
|
||||
|
||||
echo Updating dependencies...
|
||||
call npm i --omit=dev
|
||||
|
||||
call npm run build
|
||||
node scripts/raw-precheck.js > NUL
|
||||
if %errorlevel% == 0 (
|
||||
call npm run start
|
||||
echo SpaceNinjaServer seems to have crashed.
|
||||
call npm i --omit=dev --omit=optional
|
||||
call npm run raw
|
||||
) else (
|
||||
call npm i --omit=dev
|
||||
call npm run build
|
||||
if %errorlevel% == 0 (
|
||||
call npm run start
|
||||
)
|
||||
)
|
||||
echo SpaceNinjaServer seems to have crashed.
|
||||
)
|
||||
|
||||
:a
|
||||
|
||||
@ -14,11 +14,16 @@ if [ $? -eq 0 ]; then
|
||||
fi
|
||||
|
||||
echo "Updating dependencies..."
|
||||
npm i --omit=dev
|
||||
|
||||
npm run build
|
||||
node scripts/raw-precheck.js > /dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
npm run start
|
||||
echo "SpaceNinjaServer seems to have crashed."
|
||||
npm i --omit=dev --omit=optional
|
||||
npm run raw
|
||||
else
|
||||
npm i --omit=dev
|
||||
npm run build
|
||||
if [ $? -eq 0 ]; then
|
||||
npm run start
|
||||
fi
|
||||
fi
|
||||
echo "SpaceNinjaServer seems to have crashed."
|
||||
fi
|
||||
|
||||
@ -10,12 +10,9 @@
|
||||
"administratorNames": [],
|
||||
"autoCreateAccount": true,
|
||||
"skipTutorial": false,
|
||||
"unlockAllScans": false,
|
||||
"unlockAllShipFeatures": false,
|
||||
"unlockAllShipDecorations": false,
|
||||
"unlockAllFlavourItems": false,
|
||||
"unlockAllSkins": false,
|
||||
"unlockAllCapturaScenes": false,
|
||||
"fullyStockedVendors": false,
|
||||
"skipClanKeyCrafting": false,
|
||||
"noDojoRoomBuildStage": false,
|
||||
|
||||
3
package-lock.json
generated
3
package-lock.json
generated
@ -33,6 +33,9 @@
|
||||
"prettier": "^3.5.3",
|
||||
"tree-kill": "^1.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.18.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@types/express": "^5",
|
||||
"@types/morgan": "^1.9.9",
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
"dev:bun": "bun scripts/dev.cjs",
|
||||
"verify": "tsgo --noEmit",
|
||||
"verify:tsc": "tsc --noEmit",
|
||||
"raw": "node --experimental-transform-types src/index.ts",
|
||||
"raw": "node scripts/raw-precheck.js && node --experimental-transform-types src/index.ts",
|
||||
"raw:bun": "bun src/index.ts",
|
||||
"lint": "eslint --ext .ts .",
|
||||
"lint:ci": "eslint --ext .ts --rule \"prettier/prettier: off\" .",
|
||||
|
||||
@ -13,6 +13,17 @@ args.push("--dev");
|
||||
args.push("--secret");
|
||||
args.push(secret);
|
||||
|
||||
const cangoraw = (() => {
|
||||
if (process.versions.bun) {
|
||||
return true;
|
||||
}
|
||||
const [major, minor] = process.versions.node.split(".").map(x => parseInt(x));
|
||||
if (major > 22 || (major == 22 && minor >= 7)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})();
|
||||
|
||||
let buildproc, runproc;
|
||||
const spawnopts = { stdio: "inherit", shell: true };
|
||||
function run(changedFile) {
|
||||
@ -29,7 +40,7 @@ function run(changedFile) {
|
||||
runproc = undefined;
|
||||
}
|
||||
|
||||
const thisbuildproc = spawn("npm", ["run", process.versions.bun ? "verify" : "build:dev"], spawnopts);
|
||||
const thisbuildproc = spawn("npm", ["run", cangoraw ? "verify" : "build:dev"], spawnopts);
|
||||
const thisbuildstart = Date.now();
|
||||
buildproc = thisbuildproc;
|
||||
buildproc.on("exit", code => {
|
||||
@ -38,8 +49,12 @@ function run(changedFile) {
|
||||
}
|
||||
buildproc = undefined;
|
||||
if (code === 0) {
|
||||
console.log(`${process.versions.bun ? "Verified" : "Built"} in ${Date.now() - thisbuildstart} ms`);
|
||||
runproc = spawn("npm", ["run", process.versions.bun ? "raw:bun" : "start", "--", ...args], spawnopts);
|
||||
console.log(`${cangoraw ? "Verified" : "Built"} in ${Date.now() - thisbuildstart} ms`);
|
||||
runproc = spawn(
|
||||
"npm",
|
||||
["run", cangoraw ? (process.versions.bun ? "raw:bun" : "raw") : "start", "--", ...args],
|
||||
spawnopts
|
||||
);
|
||||
runproc.on("exit", () => {
|
||||
runproc = undefined;
|
||||
});
|
||||
|
||||
9
scripts/raw-precheck.js
Normal file
9
scripts/raw-precheck.js
Normal file
@ -0,0 +1,9 @@
|
||||
const [major, minor] = process.versions.node.split(".").map(x => parseInt(x));
|
||||
if (major > 22 || (major == 22 && minor >= 7)) {
|
||||
// ok
|
||||
} else {
|
||||
console.log("Sorry, your Node version is a bit too old for this. You have 2 options:");
|
||||
console.log("- Update Node.js.");
|
||||
console.log("- Use 'npm run build && npm run start'. Optional libraries must be installed for this.");
|
||||
process.exit(1);
|
||||
}
|
||||
@ -1,6 +1,4 @@
|
||||
import type { RequestHandler } from "express";
|
||||
import { config } from "../../services/configService.ts";
|
||||
import allShipFeatures from "../../../static/fixed_responses/allShipFeatures.json" with { type: "json" };
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { createGarden, getPersonalRooms } from "../../services/personalRoomsService.ts";
|
||||
import type { IGetShipResponse, IPersonalRoomsClient } from "../../types/personalRoomsTypes.ts";
|
||||
@ -31,9 +29,5 @@ export const getShipController: RequestHandler = async (req, res) => {
|
||||
TailorShop: personalRooms.TailorShop
|
||||
};
|
||||
|
||||
if (config.unlockAllShipFeatures) {
|
||||
getShipResponse.Ship.Features = allShipFeatures;
|
||||
}
|
||||
|
||||
res.json(getShipResponse);
|
||||
};
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import type { RequestHandler } from "express";
|
||||
import { config, getReflexiveAddress } from "../../services/configService.ts";
|
||||
|
||||
const hubController: RequestHandler = (_req, res) => {
|
||||
res.json("hub 127.0.0.1:6952");
|
||||
export const hubController: RequestHandler = (req, res) => {
|
||||
res.json(`hub ${config.hubAddress ?? getReflexiveAddress(req).myAddress}:6952`);
|
||||
};
|
||||
|
||||
export { hubController };
|
||||
|
||||
@ -10,7 +10,7 @@ import { equipmentKeys } from "../../types/inventoryTypes/inventoryTypes.ts";
|
||||
import type { IPolarity } from "../../types/inventoryTypes/commonInventoryTypes.ts";
|
||||
import { ArtifactPolarity } from "../../types/inventoryTypes/commonInventoryTypes.ts";
|
||||
import type { ICountedItem } from "warframe-public-export-plus";
|
||||
import { eFaction, ExportCustoms, ExportFlavour, ExportResources, ExportVirtuals } from "warframe-public-export-plus";
|
||||
import { eFaction, ExportCustoms, ExportFlavour, ExportResources } from "warframe-public-export-plus";
|
||||
import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "../../services/infestedFoundryService.ts";
|
||||
import {
|
||||
addEmailItem,
|
||||
@ -358,17 +358,6 @@ export const getInventoryResponse = async (
|
||||
}
|
||||
}
|
||||
|
||||
if (config.unlockAllCapturaScenes) {
|
||||
for (const uniqueName of Object.keys(ExportResources)) {
|
||||
if (resourceInheritsFrom(uniqueName, "/Lotus/Types/Items/MiscItems/PhotoboothTile")) {
|
||||
inventoryResponse.MiscItems.push({
|
||||
ItemType: uniqueName,
|
||||
ItemCount: 1
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof config.spoofMasteryRank === "number" && config.spoofMasteryRank >= 0) {
|
||||
inventoryResponse.PlayerLevel = config.spoofMasteryRank;
|
||||
if (!xpBasedLevelCapDisabled) {
|
||||
@ -495,21 +484,3 @@ const getExpRequiredForMr = (rank: number): number => {
|
||||
}
|
||||
return 2_250_000 + 147_500 * (rank - 30);
|
||||
};
|
||||
|
||||
const resourceInheritsFrom = (resourceName: string, targetName: string): boolean => {
|
||||
let parentName = resourceGetParent(resourceName);
|
||||
for (; parentName != undefined; parentName = resourceGetParent(parentName)) {
|
||||
if (parentName == targetName) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const resourceGetParent = (resourceName: string): string | undefined => {
|
||||
if (resourceName in ExportResources) {
|
||||
return ExportResources[resourceName].parentName;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
return ExportVirtuals[resourceName]?.parentName;
|
||||
};
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { RequestHandler } from "express";
|
||||
|
||||
import { config } from "../../services/configService.ts";
|
||||
import { config, getReflexiveAddress } from "../../services/configService.ts";
|
||||
import { buildConfig } from "../../services/buildConfigService.ts";
|
||||
|
||||
import { Account } from "../../models/loginModel.ts";
|
||||
@ -8,7 +8,7 @@ import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } f
|
||||
import type { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "../../types/loginTypes.ts";
|
||||
import { logger } from "../../utils/logger.ts";
|
||||
import { version_compare } from "../../helpers/inventoryHelpers.ts";
|
||||
import { sendWsBroadcastTo } from "../../services/wsService.ts";
|
||||
import { handleNonceInvalidation } from "../../services/wsService.ts";
|
||||
|
||||
export const loginController: RequestHandler = async (request, response) => {
|
||||
const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object
|
||||
@ -20,21 +20,7 @@ export const loginController: RequestHandler = async (request, response) => {
|
||||
? request.query.buildLabel.split(" ").join("+")
|
||||
: buildConfig.buildLabel;
|
||||
|
||||
let myAddress: string;
|
||||
let myUrlBase: string = request.protocol + "://";
|
||||
if (request.host.indexOf("warframe.com") == -1) {
|
||||
// Client request was redirected cleanly, so we know it can reach us how it's reaching us now.
|
||||
myAddress = request.hostname;
|
||||
myUrlBase += request.host;
|
||||
} else {
|
||||
// Don't know how the client reached us, hoping the config does.
|
||||
myAddress = config.myAddress;
|
||||
myUrlBase += myAddress;
|
||||
const port: number = request.protocol == "http" ? config.httpPort || 80 : config.httpsPort || 443;
|
||||
if (port != (request.protocol == "http" ? 80 : 443)) {
|
||||
myUrlBase += ":" + port;
|
||||
}
|
||||
}
|
||||
const { myAddress, myUrlBase } = getReflexiveAddress(request);
|
||||
|
||||
if (
|
||||
!account &&
|
||||
@ -88,7 +74,7 @@ export const loginController: RequestHandler = async (request, response) => {
|
||||
account.LastLogin = new Date();
|
||||
await account.save();
|
||||
|
||||
sendWsBroadcastTo(account._id.toString(), { nonce_updated: true });
|
||||
handleNonceInvalidation(account._id.toString());
|
||||
|
||||
response.json(createLoginResponse(myAddress, myUrlBase, account.toJSON(), buildLabel));
|
||||
};
|
||||
@ -109,11 +95,11 @@ const createLoginResponse = (
|
||||
BuildLabel: buildLabel
|
||||
};
|
||||
if (version_compare(buildLabel, "2015.02.13.10.41") >= 0) {
|
||||
resp.NRS = [myAddress];
|
||||
resp.NRS = [config.nrsAddress ?? myAddress];
|
||||
}
|
||||
if (version_compare(buildLabel, "2015.05.14.16.29") >= 0) {
|
||||
// U17 and up
|
||||
resp.IRC = config.myIrcAddresses ?? [myAddress];
|
||||
resp.IRC = [config.ircAddress ?? myAddress];
|
||||
}
|
||||
if (version_compare(buildLabel, "2018.11.08.14.45") >= 0) {
|
||||
// U24 and up
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { RequestHandler } from "express";
|
||||
import { Account } from "../../models/loginModel.ts";
|
||||
import { sendWsBroadcastTo } from "../../services/wsService.ts";
|
||||
import { handleNonceInvalidation } from "../../services/wsService.ts";
|
||||
|
||||
export const logoutController: RequestHandler = async (req, res) => {
|
||||
if (!req.query.accountId) {
|
||||
@ -21,7 +21,7 @@ export const logoutController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
);
|
||||
if (stat.modifiedCount) {
|
||||
sendWsBroadcastTo(req.query.accountId as string, { nonce_updated: true });
|
||||
handleNonceInvalidation(req.query.accountId as string);
|
||||
}
|
||||
|
||||
res.writeHead(200, {
|
||||
|
||||
@ -17,7 +17,7 @@ import { InventorySlot } from "../../types/inventoryTypes/inventoryTypes.ts";
|
||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
|
||||
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
|
||||
import type { TInventoryDatabaseDocument } from "../../models/inventoryModels/inventoryModel.ts";
|
||||
import { sendWsBroadcastEx } from "../../services/wsService.ts";
|
||||
import { sendWsBroadcastEx, sendWsBroadcastTo } from "../../services/wsService.ts";
|
||||
import { parseFusionTreasure } from "../../helpers/inventoryHelpers.ts";
|
||||
|
||||
export const sellController: RequestHandler = async (req, res) => {
|
||||
@ -308,6 +308,9 @@ export const sellController: RequestHandler = async (req, res) => {
|
||||
inventoryChanges: inventoryChanges // "inventoryChanges" for this response instead of the usual "InventoryChanges"
|
||||
});
|
||||
sendWsBroadcastEx({ update_inventory: true }, accountId, parseInt(String(req.query.wsid)));
|
||||
if (req.query.wsid) {
|
||||
sendWsBroadcastTo(accountId, { sync_inventory: true });
|
||||
}
|
||||
};
|
||||
|
||||
interface ISellRequest {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { getInventory, addItem } from "../../services/inventoryService.ts";
|
||||
import type { RequestHandler } from "express";
|
||||
import { sendWsBroadcastTo } from "../../services/wsService.ts";
|
||||
|
||||
export const addItemsController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
@ -9,6 +10,7 @@ export const addItemsController: RequestHandler = async (req, res) => {
|
||||
for (const request of requests) {
|
||||
await addItem(inventory, request.ItemType, request.ItemCount, true, undefined, request.Fingerprint, true);
|
||||
}
|
||||
sendWsBroadcastTo(accountId, { sync_inventory: true });
|
||||
await inventory.save();
|
||||
res.end();
|
||||
};
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { applyClientEquipmentUpdates, getInventory } from "../../services/inventoryService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { sendWsBroadcastTo } from "../../services/wsService.ts";
|
||||
import type { IOid } from "../../types/commonTypes.ts";
|
||||
import type { IEquipmentClient } from "../../types/equipmentTypes.ts";
|
||||
import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts";
|
||||
@ -23,6 +24,7 @@ export const addXpController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
applyClientEquipmentUpdates(inventory, gear, category as TEquipmentKey);
|
||||
}
|
||||
sendWsBroadcastTo(accountId, { sync_inventory: true });
|
||||
await inventory.save();
|
||||
res.end();
|
||||
};
|
||||
|
||||
36
src/controllers/custom/unlockAllCapturaScenesController.ts
Normal file
36
src/controllers/custom/unlockAllCapturaScenesController.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import type { RequestHandler } from "express";
|
||||
import { ExportResources, ExportVirtuals } from "warframe-public-export-plus";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { addItem, getInventory } from "../../services/inventoryService.ts";
|
||||
|
||||
export const unlockAllCapturaScenesController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId);
|
||||
|
||||
for (const uniqueName of Object.keys(ExportResources)) {
|
||||
if (resourceInheritsFrom(uniqueName, "/Lotus/Types/Items/MiscItems/PhotoboothTile")) {
|
||||
await addItem(inventory, uniqueName, 1);
|
||||
}
|
||||
}
|
||||
|
||||
await inventory.save();
|
||||
res.end();
|
||||
};
|
||||
|
||||
const resourceInheritsFrom = (resourceName: string, targetName: string): boolean => {
|
||||
let parentName = resourceGetParent(resourceName);
|
||||
for (; parentName != undefined; parentName = resourceGetParent(parentName)) {
|
||||
if (parentName == targetName) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const resourceGetParent = (resourceName: string): string | undefined => {
|
||||
if (resourceName in ExportResources) {
|
||||
return ExportResources[resourceName].parentName;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
return ExportVirtuals[resourceName]?.parentName;
|
||||
};
|
||||
23
src/controllers/custom/unlockAllScansController.ts
Normal file
23
src/controllers/custom/unlockAllScansController.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import type { RequestHandler } from "express";
|
||||
import allScans from "../../../static/fixed_responses/allScans.json" with { type: "json" };
|
||||
import { ExportEnemies } from "warframe-public-export-plus";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { getStats } from "../../services/statsService.ts";
|
||||
|
||||
export const unlockAllScansController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const stats = await getStats(accountId);
|
||||
|
||||
const scanTypes = new Set<string>(allScans);
|
||||
for (const type of Object.keys(ExportEnemies.avatars)) {
|
||||
scanTypes.add(type);
|
||||
}
|
||||
|
||||
stats.Scans = [];
|
||||
for (const type of scanTypes) {
|
||||
stats.Scans.push({ type, scans: 9999 });
|
||||
}
|
||||
|
||||
await stats.save();
|
||||
res.end();
|
||||
};
|
||||
19
src/controllers/custom/unlockAllShipFeaturesController.ts
Normal file
19
src/controllers/custom/unlockAllShipFeaturesController.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import type { RequestHandler } from "express";
|
||||
import allShipFeatures from "../../../static/fixed_responses/allShipFeatures.json" with { type: "json" };
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { getPersonalRooms } from "../../services/personalRoomsService.ts";
|
||||
|
||||
export const unlockAllShipFeaturesController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const personalRooms = await getPersonalRooms(accountId);
|
||||
|
||||
const featureSet = new Set(personalRooms.Ship.Features);
|
||||
for (const feature of allShipFeatures) {
|
||||
if (!featureSet.has(feature)) {
|
||||
personalRooms.Ship.Features.push(feature);
|
||||
}
|
||||
}
|
||||
|
||||
await personalRooms.save();
|
||||
res.end();
|
||||
};
|
||||
@ -1,8 +1,5 @@
|
||||
import type { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { config } from "../../services/configService.ts";
|
||||
import allScans from "../../../static/fixed_responses/allScans.json" with { type: "json" };
|
||||
import { ExportEnemies } from "warframe-public-export-plus";
|
||||
import { getInventory } from "../../services/inventoryService.ts";
|
||||
import { getStats } from "../../services/statsService.ts";
|
||||
import type { IStatsClient } from "../../types/statTypes.ts";
|
||||
@ -12,7 +9,7 @@ const viewController: RequestHandler = async (req, res) => {
|
||||
const inventory = await getInventory(accountId, "XPInfo");
|
||||
const playerStats = await getStats(accountId);
|
||||
|
||||
const responseJson = playerStats.toJSON() as IStatsClient;
|
||||
const responseJson = playerStats.toJSON<IStatsClient>();
|
||||
responseJson.Weapons ??= [];
|
||||
for (const item of inventory.XPInfo) {
|
||||
const weaponIndex = responseJson.Weapons.findIndex(element => element.type == item.ItemType);
|
||||
@ -22,24 +19,6 @@ const viewController: RequestHandler = async (req, res) => {
|
||||
responseJson.Weapons.push({ type: item.ItemType, xp: item.XP });
|
||||
}
|
||||
}
|
||||
if (config.unlockAllScans) {
|
||||
const scans = new Set(allScans);
|
||||
for (const type of Object.keys(ExportEnemies.avatars)) {
|
||||
if (!scans.has(type)) scans.add(type);
|
||||
}
|
||||
|
||||
// Take any existing scans and also set them to 9999
|
||||
if (responseJson.Scans) {
|
||||
for (const scan of responseJson.Scans) {
|
||||
scans.add(scan.type);
|
||||
}
|
||||
}
|
||||
responseJson.Scans = [];
|
||||
|
||||
for (const type of scans) {
|
||||
responseJson.Scans.push({ type: type, scans: 9999 });
|
||||
}
|
||||
}
|
||||
res.json(responseJson);
|
||||
};
|
||||
|
||||
|
||||
@ -16,6 +16,9 @@ import { completeAllMissionsController } from "../controllers/custom/completeAll
|
||||
import { addMissingHelminthBlueprintsController } from "../controllers/custom/addMissingHelminthBlueprintsController.ts";
|
||||
import { unlockAllProfitTakerStagesController } from "../controllers/custom/unlockAllProfitTakerStagesController.ts";
|
||||
import { unlockAllSimarisResearchEntriesController } from "../controllers/custom/unlockAllSimarisResearchEntriesController.ts";
|
||||
import { unlockAllScansController } from "../controllers/custom/unlockAllScansController.ts";
|
||||
import { unlockAllShipFeaturesController } from "../controllers/custom/unlockAllShipFeaturesController.ts";
|
||||
import { unlockAllCapturaScenesController } from "../controllers/custom/unlockAllCapturaScenesController.ts";
|
||||
|
||||
import { abilityOverrideController } from "../controllers/custom/abilityOverrideController.ts";
|
||||
import { createAccountController } from "../controllers/custom/createAccountController.ts";
|
||||
@ -52,6 +55,9 @@ customRouter.get("/completeAllMissions", completeAllMissionsController);
|
||||
customRouter.get("/addMissingHelminthBlueprints", addMissingHelminthBlueprintsController);
|
||||
customRouter.get("/unlockAllProfitTakerStages", unlockAllProfitTakerStagesController);
|
||||
customRouter.get("/unlockAllSimarisResearchEntries", unlockAllSimarisResearchEntriesController);
|
||||
customRouter.get("/unlockAllScans", unlockAllScansController);
|
||||
customRouter.get("/unlockAllShipFeatures", unlockAllShipFeaturesController);
|
||||
customRouter.get("/unlockAllCapturaScenes", unlockAllCapturaScenesController);
|
||||
|
||||
customRouter.post("/abilityOverride", abilityOverrideController);
|
||||
customRouter.post("/createAccount", createAccountController);
|
||||
|
||||
@ -3,6 +3,7 @@ import path from "path";
|
||||
import { repoDir } from "../helpers/pathHelper.ts";
|
||||
import { args } from "../helpers/commandLineArguments.ts";
|
||||
import { Inbox } from "../models/inboxModel.ts";
|
||||
import type { Request } from "express";
|
||||
|
||||
export interface IConfig {
|
||||
mongodbUrl: string;
|
||||
@ -13,16 +14,15 @@ export interface IConfig {
|
||||
myAddress: string;
|
||||
httpPort?: number;
|
||||
httpsPort?: number;
|
||||
myIrcAddresses?: string[];
|
||||
ircAddress?: string;
|
||||
hubAddress?: string;
|
||||
nrsAddress?: string;
|
||||
administratorNames?: string[];
|
||||
autoCreateAccount?: boolean;
|
||||
skipTutorial?: boolean;
|
||||
unlockAllScans?: boolean;
|
||||
unlockAllShipFeatures?: boolean;
|
||||
unlockAllShipDecorations?: boolean;
|
||||
unlockAllFlavourItems?: boolean;
|
||||
unlockAllSkins?: boolean;
|
||||
unlockAllCapturaScenes?: boolean;
|
||||
unlockAllDecoRecipes?: boolean;
|
||||
fullyStockedVendors?: boolean;
|
||||
skipClanKeyCrafting?: boolean;
|
||||
@ -86,6 +86,7 @@ export interface IConfig {
|
||||
|
||||
export const configRemovedOptionsKeys = [
|
||||
"NRS",
|
||||
"myIrcAddresses",
|
||||
"skipAllDialogue",
|
||||
"infiniteCredits",
|
||||
"infinitePlatinum",
|
||||
@ -105,6 +106,9 @@ export const configRemovedOptionsKeys = [
|
||||
"unlockArcanesEverywhere",
|
||||
"unlockAllProfitTakerStages",
|
||||
"unlockAllSimarisResearchEntries",
|
||||
"unlockAllScans",
|
||||
"unlockAllShipFeatures",
|
||||
"unlockAllCapturaScenes",
|
||||
"noDailyStandingLimits",
|
||||
"noDailyFocusLimit",
|
||||
"noArgonCrystalDecay",
|
||||
@ -165,3 +169,22 @@ export const syncConfigWithDatabase = (): void => {
|
||||
void Inbox.deleteMany({ goalTag: "GalleonRobbery" }).then(() => {});
|
||||
}
|
||||
};
|
||||
|
||||
export const getReflexiveAddress = (request: Request): { myAddress: string; myUrlBase: string } => {
|
||||
let myAddress: string;
|
||||
let myUrlBase: string = request.protocol + "://";
|
||||
if (request.host.indexOf("warframe.com") == -1) {
|
||||
// Client request was redirected cleanly, so we know it can reach us how it's reaching us now.
|
||||
myAddress = request.hostname;
|
||||
myUrlBase += request.host;
|
||||
} else {
|
||||
// Don't know how the client reached us, hoping the config does.
|
||||
myAddress = config.myAddress;
|
||||
myUrlBase += myAddress;
|
||||
const port: number = request.protocol == "http" ? config.httpPort || 80 : config.httpsPort || 443;
|
||||
if (port != (request.protocol == "http" ? 80 : 443)) {
|
||||
myUrlBase += ":" + port;
|
||||
}
|
||||
}
|
||||
return { myAddress, myUrlBase };
|
||||
};
|
||||
|
||||
@ -209,7 +209,7 @@ export const handlePurchase = async (
|
||||
inventory,
|
||||
purchaseRequest.PurchaseParams.ExpectedPrice,
|
||||
purchaseRequest.PurchaseParams.UsePremium,
|
||||
prePurchaseInventoryChanges
|
||||
purchaseResponse.InventoryChanges
|
||||
);
|
||||
|
||||
switch (purchaseRequest.PurchaseParams.Source) {
|
||||
|
||||
@ -6,7 +6,7 @@ import { Account } from "../models/loginModel.ts";
|
||||
import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "./loginService.ts";
|
||||
import type { IDatabaseAccountJson } from "../types/loginTypes.ts";
|
||||
import type { HydratedDocument } from "mongoose";
|
||||
import { logError } from "../utils/logger.ts";
|
||||
import { logError, logger } from "../utils/logger.ts";
|
||||
|
||||
let wsServer: WebSocketServer | undefined;
|
||||
let wssServer: WebSocketServer | undefined;
|
||||
@ -47,6 +47,7 @@ let lastWsid: number = 0;
|
||||
interface IWsCustomData extends ws {
|
||||
id: number;
|
||||
accountId?: string;
|
||||
isGame?: boolean;
|
||||
}
|
||||
|
||||
interface IWsMsgFromClient {
|
||||
@ -55,11 +56,18 @@ interface IWsMsgFromClient {
|
||||
password: string;
|
||||
isRegister: boolean;
|
||||
};
|
||||
auth_game?: {
|
||||
accountId: string;
|
||||
nonce: number;
|
||||
};
|
||||
logout?: boolean;
|
||||
}
|
||||
|
||||
interface IWsMsgToClient {
|
||||
//wsid?: number;
|
||||
// common
|
||||
wsid?: number;
|
||||
|
||||
// to webui
|
||||
reload?: boolean;
|
||||
ports?: {
|
||||
http: number | undefined;
|
||||
@ -77,6 +85,9 @@ interface IWsMsgToClient {
|
||||
nonce_updated?: boolean;
|
||||
update_inventory?: boolean;
|
||||
logged_out?: boolean;
|
||||
|
||||
// to game
|
||||
sync_inventory?: boolean;
|
||||
}
|
||||
|
||||
const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => {
|
||||
@ -87,11 +98,12 @@ const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => {
|
||||
}
|
||||
|
||||
(ws as IWsCustomData).id = ++lastWsid;
|
||||
ws.send(JSON.stringify({ wsid: lastWsid }));
|
||||
ws.send(JSON.stringify({ wsid: lastWsid } satisfies IWsMsgToClient));
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
ws.on("message", async msg => {
|
||||
try {
|
||||
//console.log(String(msg));
|
||||
const data = JSON.parse(String(msg)) as IWsMsgFromClient;
|
||||
if (data.auth) {
|
||||
let account: IDatabaseAccountJson | null = await Account.findOne({ email: data.auth.email });
|
||||
@ -137,6 +149,19 @@ const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => {
|
||||
);
|
||||
}
|
||||
}
|
||||
if (data.auth_game) {
|
||||
(ws as IWsCustomData).isGame = true;
|
||||
if (data.auth_game.nonce) {
|
||||
const account: IDatabaseAccountJson | null = await Account.findOne({
|
||||
_id: data.auth_game.accountId,
|
||||
Nonce: data.auth_game.nonce
|
||||
});
|
||||
if (account) {
|
||||
(ws as IWsCustomData).accountId = account.id;
|
||||
logger.debug(`got bootstrapper connection for ${account.id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (data.logout) {
|
||||
const accountId = (ws as IWsCustomData).accountId;
|
||||
(ws as IWsCustomData).accountId = undefined;
|
||||
@ -154,6 +179,17 @@ const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => {
|
||||
logError(e as Error, `processing websocket message`);
|
||||
}
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
ws.on("close", async () => {
|
||||
if ((ws as IWsCustomData).isGame && (ws as IWsCustomData).accountId) {
|
||||
logger.debug(`lost bootstrapper connection for ${(ws as IWsCustomData).accountId}`);
|
||||
const account = await Account.findOne({ _id: (ws as IWsCustomData).accountId });
|
||||
if (account?.Nonce) {
|
||||
account.Dropped = true;
|
||||
await account.save();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const sendWsBroadcast = (data: IWsMsgToClient): void => {
|
||||
@ -211,3 +247,28 @@ export const sendWsBroadcastEx = (data: IWsMsgToClient, accountId?: string, excl
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const handleNonceInvalidation = (accountId: string): void => {
|
||||
if (wsServer) {
|
||||
for (const client of wsServer.clients) {
|
||||
if ((client as IWsCustomData).accountId == accountId) {
|
||||
if ((client as IWsCustomData).isGame) {
|
||||
client.close();
|
||||
} else {
|
||||
client.send(JSON.stringify({ nonce_updated: true } satisfies IWsMsgToClient));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (wssServer) {
|
||||
for (const client of wssServer.clients) {
|
||||
if ((client as IWsCustomData).accountId == accountId) {
|
||||
if ((client as IWsCustomData).isGame) {
|
||||
client.close();
|
||||
} else {
|
||||
client.send(JSON.stringify({ nonce_updated: true } satisfies IWsMsgToClient));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -808,8 +808,11 @@
|
||||
<label class="form-check-label" for="finishInvasionsInOneMission" data-loc="cheats_finishInvasionsInOneMission"></label>
|
||||
</div>
|
||||
<div class="mt-2 mb-2 d-flex flex-wrap gap-2">
|
||||
<button class="btn btn-primary" onclick="debounce(doUnlockAllShipFeatures);" data-loc="cheats_unlockAllShipFeatures"></button>
|
||||
<button class="btn btn-primary" onclick="debounce(unlockAllMissions);" data-loc="cheats_unlockAllMissions"></button>
|
||||
<button class="btn btn-primary" onclick="debounce(markAllAsRead);" data-loc="cheats_markAllAsRead"></button>
|
||||
<button class="btn btn-primary" onclick="debounce(doUnlockAllScans);" data-loc="cheats_unlockAllScans"></button>
|
||||
<button class="btn btn-primary" onclick="debounce(doUnlockAllCapturaScenes);" data-loc="cheats_unlockAllCapturaScenes"></button>
|
||||
<button class="btn btn-primary" onclick="doUnlockAllFocusSchools();" data-loc="cheats_unlockAllFocusSchools"></button>
|
||||
<button class="btn btn-primary" onclick="doHelminthUnlockAll();" data-loc="cheats_helminthUnlockAll"></button>
|
||||
<button class="btn btn-primary" onclick="debounce(addMissingHelminthRecipes);" data-loc="cheats_addMissingSubsumedAbilities"></button>
|
||||
@ -840,14 +843,6 @@
|
||||
<input class="form-check-input" type="checkbox" id="skipTutorial" />
|
||||
<label class="form-check-label" for="skipTutorial" data-loc="cheats_skipTutorial"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="unlockAllScans" />
|
||||
<label class="form-check-label" for="unlockAllScans" data-loc="cheats_unlockAllScans"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="unlockAllShipFeatures" />
|
||||
<label class="form-check-label" for="unlockAllShipFeatures" data-loc="cheats_unlockAllShipFeatures"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="unlockAllShipDecorations" />
|
||||
<label class="form-check-label" for="unlockAllShipDecorations" data-loc="cheats_unlockAllShipDecorations"></label>
|
||||
@ -860,10 +855,6 @@
|
||||
<input class="form-check-input" type="checkbox" id="unlockAllSkins" />
|
||||
<label class="form-check-label" for="unlockAllSkins" data-loc="cheats_unlockAllSkins"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="unlockAllCapturaScenes" />
|
||||
<label class="form-check-label" for="unlockAllCapturaScenes" data-loc="cheats_unlockAllCapturaScenes"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="unlockAllDecoRecipes" />
|
||||
<label class="form-check-label" for="unlockAllDecoRecipes" data-loc="cheats_unlockAllDecoRecipes"></label>
|
||||
|
||||
@ -2339,7 +2339,7 @@ function unlockFocusSchool(upgradeType) {
|
||||
$.post({
|
||||
url: "/api/focus.php?" + window.authz + "&op=5",
|
||||
contentType: "text/plain",
|
||||
data: "{}"
|
||||
data: JSON.stringify({ FocusType: null })
|
||||
}).done(function () {
|
||||
// Unlock the way now
|
||||
$.post({
|
||||
@ -2750,6 +2750,24 @@ async function doMaxPlexus() {
|
||||
}
|
||||
}
|
||||
|
||||
async function doUnlockAllScans() {
|
||||
await revalidateAuthz();
|
||||
await fetch("/custom/unlockAllScans?" + window.authz);
|
||||
toast(loc("cheats_unlockSuccRelog"));
|
||||
}
|
||||
|
||||
async function doUnlockAllShipFeatures() {
|
||||
await revalidateAuthz();
|
||||
await fetch("/custom/unlockAllShipFeatures?" + window.authz);
|
||||
toast(loc("cheats_unlockSuccInventory"));
|
||||
}
|
||||
|
||||
async function doUnlockAllCapturaScenes() {
|
||||
await revalidateAuthz();
|
||||
await fetch("/custom/unlockAllCapturaScenes?" + window.authz);
|
||||
toast(loc("cheats_unlockSuccInventory"));
|
||||
}
|
||||
|
||||
async function unlockAllMissions() {
|
||||
await revalidateAuthz();
|
||||
await fetch("/custom/completeAllMissions?" + window.authz);
|
||||
@ -2759,13 +2777,13 @@ async function unlockAllMissions() {
|
||||
async function unlockAllProfitTakerStages() {
|
||||
await revalidateAuthz();
|
||||
await fetch("/custom/unlockAllProfitTakerStages?" + window.authz);
|
||||
toast(loc("cheats_unlockSucc"));
|
||||
toast(loc("cheats_unlockSuccInventory"));
|
||||
}
|
||||
|
||||
async function unlockAllSimarisResearchEntries() {
|
||||
await revalidateAuthz();
|
||||
await fetch("/custom/unlockAllSimarisResearchEntries?" + window.authz);
|
||||
toast(loc("cheats_unlockSucc"));
|
||||
toast(loc("cheats_unlockSuccInventory"));
|
||||
}
|
||||
|
||||
const importSamples = {
|
||||
|
||||
@ -171,11 +171,12 @@ dict = {
|
||||
mods_addMissingUnrankedMods: `Fehlende Mods ohne Rang hinzufügen`,
|
||||
mods_removeUnranked: `Mods ohne Rang entfernen`,
|
||||
mods_addMissingMaxRankMods: `Fehlende Mods mit Max. Rang hinzufügen`,
|
||||
cheats_administratorRequirement: `Du musst Administrator sein, um diese Funktion nutzen zu können. Um Administrator zu werden, füge <code>|DISPLAYNAME|</code> zu <code>administratorNames</code> in der config.json hinzu.`,
|
||||
cheats_administratorRequirement: `Du musst Administrator sein, um diese Funktion nutzen zu können. Um Administrator zu werden, füge <code>"|DISPLAYNAME|"</code> zu <code>administratorNames</code> in der config.json hinzu.`,
|
||||
cheats_server: `Server`,
|
||||
cheats_skipTutorial: `Tutorial überspringen`,
|
||||
cheats_skipAllDialogue: `Alle Dialoge überspringen`,
|
||||
cheats_unlockAllScans: `Alle Scans freischalten`,
|
||||
cheats_unlockSuccRelog: `[UNTRANSLATED] Success. Please that you'll need to relog for the client to refresh this.`,
|
||||
cheats_unlockAllMissions: `Alle Missionen freischalten`,
|
||||
cheats_unlockAllMissions_ok: `Erfolgreich. Bitte beachte, dass du ein Dojo/Relais besuchen oder dich neu einloggen musst, damit die Sternenkarte aktualisiert wird.`,
|
||||
cheats_infiniteCredits: `Unendlich Credits`,
|
||||
@ -212,7 +213,7 @@ dict = {
|
||||
cheats_baroFullyStocked: `Baro hat volles Inventar`,
|
||||
cheats_syndicateMissionsRepeatable: `Syndikat-Missionen wiederholbar`,
|
||||
cheats_unlockAllProfitTakerStages: `Alle Profiteintreiber-Phasen freischalten`,
|
||||
cheats_unlockSucc: `[UNTRANSLATED] Successfully unlocked.`,
|
||||
cheats_unlockSuccInventory: `[UNTRANSLATED] Success. Please note that you'll need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging..`,
|
||||
cheats_instantFinishRivenChallenge: `Riven-Mod Herausforderung sofort abschließen`,
|
||||
cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`,
|
||||
cheats_noResourceExtractorDronesDamage: `Kein Schaden für Ressourcen-Extraktor-Drohnen`,
|
||||
|
||||
@ -170,11 +170,12 @@ dict = {
|
||||
mods_addMissingUnrankedMods: `Add Missing Unranked Mods`,
|
||||
mods_removeUnranked: `Remove Unranked Mods`,
|
||||
mods_addMissingMaxRankMods: `Add Missing Max Rank Mods`,
|
||||
cheats_administratorRequirement: `You must be an administrator to use this feature. To become an administrator, add <code>|DISPLAYNAME|</code> to <code>administratorNames</code> in the config.json.`,
|
||||
cheats_administratorRequirement: `You must be an administrator to use this feature. To become an administrator, add <code>"|DISPLAYNAME|"</code> to <code>administratorNames</code> in the config.json.`,
|
||||
cheats_server: `Server`,
|
||||
cheats_skipTutorial: `Skip Tutorial`,
|
||||
cheats_skipAllDialogue: `Skip All Dialogue`,
|
||||
cheats_unlockAllScans: `Unlock All Scans`,
|
||||
cheats_unlockSuccRelog: `Success. Please that you'll need to relog for the client to refresh this.`,
|
||||
cheats_unlockAllMissions: `Unlock All Missions`,
|
||||
cheats_unlockAllMissions_ok: `Success. Please note that you'll need to enter a dojo/relay or relog for the client to refresh the star chart.`,
|
||||
cheats_infiniteCredits: `Infinite Credits`,
|
||||
@ -211,7 +212,7 @@ dict = {
|
||||
cheats_baroFullyStocked: `Baro Fully Stocked`,
|
||||
cheats_syndicateMissionsRepeatable: `Syndicate Missions Repeatable`,
|
||||
cheats_unlockAllProfitTakerStages: `Unlock All Profit Taker Stages`,
|
||||
cheats_unlockSucc: `Successfully unlocked.`,
|
||||
cheats_unlockSuccInventory: `Success. Please note that you'll need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging..`,
|
||||
cheats_instantFinishRivenChallenge: `Instant Finish Riven Challenge`,
|
||||
cheats_instantResourceExtractorDrones: `Instant Resource Extractor Drones`,
|
||||
cheats_noResourceExtractorDronesDamage: `No Resource Extractor Drones Damage`,
|
||||
|
||||
@ -79,8 +79,8 @@ dict = {
|
||||
navbar_cheats: `Trucos`,
|
||||
navbar_import: `Importar`,
|
||||
inventory_addItems: `Agregar objetos`,
|
||||
inventory_addItemByItemType: `[UNTRANSLATED] Raw`,
|
||||
inventory_addItemByItemType_warning: `[UNTRANSLATED] Use this feature at your own risk. It may break your inventory, and you will need to remove items manually if something goes wrong.`,
|
||||
inventory_addItemByItemType: `Sin refinar`,
|
||||
inventory_addItemByItemType_warning: `Usa esta función bajo tu propio riesgo. Podría dañar tu inventario, y tendrías que eliminar los objetos manualmente si algo sale mal.`,
|
||||
inventory_suits: `Warframes`,
|
||||
inventory_longGuns: `Armas primarias`,
|
||||
inventory_pistols: `Armas secundarias`,
|
||||
@ -171,11 +171,12 @@ dict = {
|
||||
mods_addMissingUnrankedMods: `Agregar mods sin rango faltantes`,
|
||||
mods_removeUnranked: `Quitar mods sin rango`,
|
||||
mods_addMissingMaxRankMods: `Agregar mods de rango máximo faltantes`,
|
||||
cheats_administratorRequirement: `Debes ser administrador para usar esta función. Para convertirte en administrador, agrega <code>|DISPLAYNAME|</code> a <code>administratorNames</code> en el archivo config.json.`,
|
||||
cheats_administratorRequirement: `Debes ser administrador para usar esta función. Para convertirte en administrador, agrega <code>"|DISPLAYNAME|"</code> a <code>administratorNames</code> en el archivo config.json.`,
|
||||
cheats_server: `Servidor`,
|
||||
cheats_skipTutorial: `Omitir tutorial`,
|
||||
cheats_skipAllDialogue: `Omitir todos los diálogos`,
|
||||
cheats_unlockAllScans: `Desbloquear todos los escaneos`,
|
||||
cheats_unlockSuccRelog: `Éxito. Ten en cuenta que deberás volver a iniciar sesión para que el cliente se actualice.`,
|
||||
cheats_unlockAllMissions: `Desbloquear todas las misiones`,
|
||||
cheats_unlockAllMissions_ok: `Éxito. Ten en cuenta que deberás entrar a un dojo, repetidor o volver a iniciar sesión para que el cliente actualice el mapa estelar.`,
|
||||
cheats_infiniteCredits: `Créditos infinitos`,
|
||||
@ -212,7 +213,7 @@ dict = {
|
||||
cheats_baroFullyStocked: `Baro con stock completo`,
|
||||
cheats_syndicateMissionsRepeatable: `Misiones de sindicato rejugables`,
|
||||
cheats_unlockAllProfitTakerStages: `Desbloquea todas las etapas del Roba-ganancias`,
|
||||
cheats_unlockSucc: `[UNTRANSLATED] Successfully unlocked.`,
|
||||
cheats_unlockSuccInventory: `Éxito. Ten en cuenta que deberás volver a sincronizar tu inventario. Para hacerlo, puedes usar el comando /sync en el Bootstrapper, visitar un dojo o repetidor, o volver a iniciar sesión.`,
|
||||
cheats_instantFinishRivenChallenge: `Terminar desafío de agrietado inmediatamente`,
|
||||
cheats_instantResourceExtractorDrones: `Drones de extracción de recursos instantáneos`,
|
||||
cheats_noResourceExtractorDronesDamage: `Sin daño a los drones extractores de recursos`,
|
||||
@ -241,7 +242,7 @@ dict = {
|
||||
cheats_changeSupportedSyndicate: `Sindicatos disponibles`,
|
||||
cheats_changeButton: `Cambiar`,
|
||||
cheats_markAllAsRead: `Marcar bandeja de entrada como leída`,
|
||||
cheats_finishInvasionsInOneMission: `[UNTRANSLATED] Finish Invasions in One Mission`,
|
||||
cheats_finishInvasionsInOneMission: `Finaliza Invasión en una mision`,
|
||||
|
||||
worldState: `Estado del mundo`,
|
||||
worldState_creditBoost: `Potenciador de Créditos`,
|
||||
|
||||
@ -171,11 +171,12 @@ dict = {
|
||||
mods_addMissingUnrankedMods: `Ajouter les mods sans rang manquants`,
|
||||
mods_removeUnranked: `Retirer les mods sans rang`,
|
||||
mods_addMissingMaxRankMods: `Ajouter les mods niveau max manquants`,
|
||||
cheats_administratorRequirement: `Rôle d'administrateur requis pour cette fonctionnalité. Ajoutez <code>|DISPLAYNAME|</code> à la ligne <code>administratorNames</code> dans le fichier config.json.`,
|
||||
cheats_administratorRequirement: `Rôle d'administrateur requis pour cette fonctionnalité. Ajoutez <code>"|DISPLAYNAME|"</code> à la ligne <code>administratorNames</code> dans le fichier config.json.`,
|
||||
cheats_server: `Serveur`,
|
||||
cheats_skipTutorial: `Passer le tutoriel`,
|
||||
cheats_skipAllDialogue: `Passer les dialogues`,
|
||||
cheats_unlockAllScans: `Débloquer tous les scans`,
|
||||
cheats_unlockSuccRelog: `[UNTRANSLATED] Success. Please that you'll need to relog for the client to refresh this.`,
|
||||
cheats_unlockAllMissions: `Débloquer toutes les missions`,
|
||||
cheats_unlockAllMissions_ok: `Succès. Une actualisation de l'inventaire est nécessaire.`,
|
||||
cheats_infiniteCredits: `Crédits infinis`,
|
||||
@ -212,7 +213,7 @@ dict = {
|
||||
cheats_baroFullyStocked: `Stock de Baro au max`,
|
||||
cheats_syndicateMissionsRepeatable: `Mission syndicat répétables`,
|
||||
cheats_unlockAllProfitTakerStages: `Débloquer toutes les étapes du Preneur de Profit`,
|
||||
cheats_unlockSucc: `[UNTRANSLATED] Successfully unlocked.`,
|
||||
cheats_unlockSuccInventory: `[UNTRANSLATED] Success. Please note that you'll need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging..`,
|
||||
cheats_instantFinishRivenChallenge: `Débloquer le challenge Riven instantanément`,
|
||||
cheats_instantResourceExtractorDrones: `Ressources de drones d'extraction instantannées`,
|
||||
cheats_noResourceExtractorDronesDamage: `Aucun dégâts aux drones d'extraction de resources`,
|
||||
|
||||
@ -171,11 +171,12 @@ dict = {
|
||||
mods_addMissingUnrankedMods: `Добавить недостающие моды без ранга`,
|
||||
mods_removeUnranked: `Удалить моды без ранга`,
|
||||
mods_addMissingMaxRankMods: `Добавить недостающие моды макс. ранга`,
|
||||
cheats_administratorRequirement: `Вы должны быть администратором для использования этой функции. Чтобы стать администратором, добавьте <code>\"|DISPLAYNAME|\"</code> в <code>administratorNames</code> в config.json.`,
|
||||
cheats_administratorRequirement: `Вы должны быть администратором для использования этой функции. Чтобы стать администратором, добавьте <code>"|DISPLAYNAME|"</code> в <code>administratorNames</code> в config.json.`,
|
||||
cheats_server: `Сервер`,
|
||||
cheats_skipTutorial: `Пропустить обучение`,
|
||||
cheats_skipAllDialogue: `Пропустить все диалоги`,
|
||||
cheats_unlockAllScans: `Разблокировать все сканирования`,
|
||||
cheats_unlockSuccRelog: `[UNTRANSLATED] Success. Please that you'll need to relog for the client to refresh this.`,
|
||||
cheats_unlockAllMissions: `Разблокировать все миссии`,
|
||||
cheats_unlockAllMissions_ok: `Успех. Пожалуйста, обратите внимание, что вам нужно будет войти в Додзё/Реле или перезайти, чтобы клиент обновил звездную карту.`,
|
||||
cheats_infiniteCredits: `Бесконечные Кредиты`,
|
||||
@ -212,7 +213,7 @@ dict = {
|
||||
cheats_baroFullyStocked: `Баро полностью укомплектован`,
|
||||
cheats_syndicateMissionsRepeatable: `Повторять миссии синдиката`,
|
||||
cheats_unlockAllProfitTakerStages: `Разблокировать все этапы Сферы извлечения прибыли`,
|
||||
cheats_unlockSucc: `Успешно разблокировано.`,
|
||||
cheats_unlockSuccInventory: `[UNTRANSLATED] Success. Please note that you'll need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging..`,
|
||||
cheats_instantFinishRivenChallenge: `Мгновенное завершение испытания мода Разлома`,
|
||||
cheats_instantResourceExtractorDrones: `Мгновенно добывающие Дроны-сборщики`,
|
||||
cheats_noResourceExtractorDronesDamage: `Без урона по Дронам-сборщикам`,
|
||||
|
||||
@ -171,11 +171,12 @@ dict = {
|
||||
mods_addMissingUnrankedMods: `Добавити недостаючі модифікатори без рівня`,
|
||||
mods_removeUnranked: `Видалити модифікатори без рівня`,
|
||||
mods_addMissingMaxRankMods: `Добавити недостаючі модифікатори макс. рівня`,
|
||||
cheats_administratorRequirement: `Ви повинні бути адміністратором для використання цієї функції. Щоб стати адміністратором, додайте <code>\"|DISPLAYNAME|\"</code> в <code>administratorNames</code> в config.json.`,
|
||||
cheats_administratorRequirement: `Ви повинні бути адміністратором для використання цієї функції. Щоб стати адміністратором, додайте <code>"|DISPLAYNAME|"</code> в <code>administratorNames</code> в config.json.`,
|
||||
cheats_server: `Сервер`,
|
||||
cheats_skipTutorial: `Пропустити навчання`,
|
||||
cheats_skipAllDialogue: `Пропустити всі діалоги`,
|
||||
cheats_unlockAllScans: `Розблокувати всі сканування`,
|
||||
cheats_unlockSuccRelog: `[UNTRANSLATED] Success. Please that you'll need to relog for the client to refresh this.`,
|
||||
cheats_unlockAllMissions: `Розблокувати всі місії`,
|
||||
cheats_unlockAllMissions_ok: `Успіх. Будь ласка, зверніть увагу, що вам потрібно буде увійти в Доджьо/Реле або перезайти, щоб клієнт оновив Зоряну мапу.`,
|
||||
cheats_infiniteCredits: `Бескінечні Кредити`,
|
||||
@ -212,7 +213,7 @@ dict = {
|
||||
cheats_baroFullyStocked: `Баро повністю укомплектований`,
|
||||
cheats_syndicateMissionsRepeatable: `Повторювати місії синдиката`,
|
||||
cheats_unlockAllProfitTakerStages: `Розблокувати всі етапи Привласнювачки`,
|
||||
cheats_unlockSucc: `Успішно розблоковано.`,
|
||||
cheats_unlockSuccInventory: `[UNTRANSLATED] Success. Please note that you'll need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging..`,
|
||||
cheats_instantFinishRivenChallenge: `Миттєве завершення випробування модифікатора Розколу`,
|
||||
cheats_instantResourceExtractorDrones: `Миттєво добуваючі Дрони-видобувачі`,
|
||||
cheats_noResourceExtractorDronesDamage: `Без шкоди по Дронам-видобувачам`,
|
||||
|
||||
@ -171,11 +171,12 @@ dict = {
|
||||
mods_addMissingUnrankedMods: `添加所有缺失的Mods`,
|
||||
mods_removeUnranked: `删除所有未升级的Mods`,
|
||||
mods_addMissingMaxRankMods: `添加所有缺失的满级Mods`,
|
||||
cheats_administratorRequirement: `您必须是管理员才能使用此功能.要成为管理员,请在 config.json 中将 <code>|DISPLAYNAME|</code> 添加到 <code>administratorNames</code>`,
|
||||
cheats_administratorRequirement: `您必须是管理员才能使用此功能.要成为管理员,请在 config.json 中将 <code>"|DISPLAYNAME|"</code> 添加到 <code>administratorNames</code>`,
|
||||
cheats_server: `服务器`,
|
||||
cheats_skipTutorial: `跳过教程`,
|
||||
cheats_skipAllDialogue: `跳过所有对话`,
|
||||
cheats_unlockAllScans: `解锁所有扫描`,
|
||||
cheats_unlockSuccRelog: `[UNTRANSLATED] Success. Please that you'll need to relog for the client to refresh this.`,
|
||||
cheats_unlockAllMissions: `解锁所有星图`,
|
||||
cheats_unlockAllMissions_ok: `操作成功.请注意,您需要进入道场/中继站或重新登录以刷新星图数据.`,
|
||||
cheats_infiniteCredits: `无限现金`,
|
||||
@ -212,7 +213,7 @@ dict = {
|
||||
cheats_baroFullyStocked: `虚空商人贩卖所有商品`,
|
||||
cheats_syndicateMissionsRepeatable: `集团任务可重复完成`,
|
||||
cheats_unlockAllProfitTakerStages: `解锁利润收割者圆蛛所有阶段`,
|
||||
cheats_unlockSucc: `[UNTRANSLATED] Successfully unlocked.`,
|
||||
cheats_unlockSuccInventory: `[UNTRANSLATED] Success. Please note that you'll need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging..`,
|
||||
cheats_instantFinishRivenChallenge: `立即完成裂罅挑战`,
|
||||
cheats_instantResourceExtractorDrones: `资源无人机即时完成`,
|
||||
cheats_noResourceExtractorDronesDamage: `资源无人机不会损毁`,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user