Compare commits

..

No commits in common. "main" and "main" have entirely different histories.
main ... main

88 changed files with 1054 additions and 4286 deletions

View File

@ -4,9 +4,9 @@ on:
branches:
- main
jobs:
docker:
docker-amd64:
if: github.repository == 'OpenWF/SpaceNinjaServer'
runs-on: ubuntu-latest
runs-on: amd64
steps:
- name: Set up Docker buildx
uses: docker/setup-buildx-action@v3
@ -18,10 +18,27 @@ jobs:
- name: Build and push
uses: docker/build-push-action@v6
with:
platforms: linux/arm64,linux/amd64
platforms: linux/amd64
push: true
tags: |
openwf/spaceninjaserver:latest
openwf/spaceninjaserver:latest-arm64
openwf/spaceninjaserver:${{ github.sha }}
docker-arm64:
if: github.repository == 'OpenWF/SpaceNinjaServer'
runs-on: arm64
steps:
- name: Set up Docker buildx
uses: docker/setup-buildx-action@v3
- name: Log in to container registry
uses: docker/login-action@v3
with:
username: openwf
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
platforms: linux/arm64
push: true
tags: |
openwf/spaceninjaserver:latest-arm64
openwf/spaceninjaserver:${{ github.sha }}-arm64

View File

@ -14,13 +14,11 @@ SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [confi
- `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`.
- `ircExecutable` can be provided with a relative path to an EXE which will be ran as a child process of SpaceNinjaServer.
- `ircAddress`, `hubAddress`, and `nrsAddress` can be provided if these secondary servers are on a different machine.
- `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.
- `worldState.nightwaveOverride` will lock the nightwave season, assuming the client is new enough for it. Valid values:
- `RadioLegionIntermission14Syndicate` for Nora's Mix: Dreams of the Dead
- `RadioLegionIntermission13Syndicate` for Nora's Mix Vol. 9
- `RadioLegionIntermission12Syndicate` for Nora's Mix Vol. 8
- `RadioLegionIntermission11Syndicate` for Nora's Mix Vol. 7
@ -37,5 +35,5 @@ SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [confi
- `RadioLegion2Syndicate` for The Emissary
- `RadioLegionIntermissionSyndicate` for Intermission I
- `RadioLegionSyndicate` for The Wolf of Saturn Six
- `worldState.allTheFissures` can be set to `normal` or `hard` to enable all fissures either in normal or steel path, respectively.
- `allTheFissures` can be set to `normal` or `hard` to enable all fissures either in normal or steel path, respectively.
- `worldState.circuitGameModes` can be set to an array of game modes which will override the otherwise-random pattern in The Circuit. Valid element values are `Survival`, `VoidFlood`, `Excavation`, `Defense`, `Exterminate`, `Assassination`, and `Alchemy`.

View File

@ -8,16 +8,15 @@
"bindAddress": "0.0.0.0",
"httpPort": 80,
"httpsPort": 443,
"ircExecutable": null,
"ircAddress": null,
"hubAddress": null,
"nrsAddress": null,
"administratorNames": [],
"autoCreateAccount": true,
"skipTutorial": false,
"unlockAllSkins": false,
"fullyStockedVendors": false,
"skipClanKeyCrafting": false,
"spoofMasteryRank": -1,
"relicRewardItemCountMultiplier": 1,
"nightwaveStandingMultiplier": 1,
"unfaithfulBugFixes": {
"ignore1999LastRegionPlayed": false,
"fixXtraCheeseTimer": false,
@ -32,21 +31,15 @@
"baroAlwaysAvailable": false,
"baroFullyStocked": false,
"varziaFullyStocked": false,
"wolfHunt": null,
"wolfHunt": false,
"orphixVenom": false,
"longShadow": false,
"hallowedFlame": false,
"anniversary": null,
"hallowedNightmares": false,
"hallowedNightmaresRewardsOverride": 0,
"naberusNightsOverride": null,
"proxyRebellion": false,
"proxyRebellionRewardsOverride": 0,
"voidCorruption2025Week1": false,
"voidCorruption2025Week2": false,
"voidCorruption2025Week3": false,
"voidCorruption2025Week4": false,
"qtccAlerts": false,
"galleonOfGhouls": 0,
"ghoulEmergenceOverride": null,
"plagueStarOverride": null,

View File

@ -1,5 +1,6 @@
services:
spaceninjaserver:
# The image to use. If you have an ARM CPU, replace 'latest' with 'latest-arm64'.
image: openwf/spaceninjaserver:latest
volumes:
@ -18,6 +19,9 @@ services:
- mongodb
mongodb:
image: docker.io/library/mongo:8.0.0-noble
environment:
MONGO_INITDB_ROOT_USERNAME: openwfagent
MONGO_INITDB_ROOT_PASSWORD: spaceninjaserver
volumes:
- ./docker-data/database:/data/db
command: mongod --quiet --logpath /dev/null

View File

@ -2,7 +2,7 @@
set -e
if [ ! -f conf/config.json ]; then
jq --arg value "mongodb://mongodb:27017/openWF" '.mongodbUrl = $value' /app/config-vanilla.json > /app/conf/config.json
jq --arg value "mongodb://openwfagent:spaceninjaserver@mongodb:27017/" '.mongodbUrl = $value' /app/config-vanilla.json > /app/conf/config.json
fi
exec npm run raw -- --configPath conf/config.json

10
package-lock.json generated
View File

@ -9,7 +9,6 @@
"version": "0.1.0",
"license": "GNU",
"dependencies": {
"body-parser": "^2.2.0",
"chokidar": "^4.0.3",
"crc-32": "^1.2.2",
"express": "^5",
@ -18,7 +17,7 @@
"morgan": "^1.10.0",
"ncp": "^2.0.0",
"undici": "^7.10.0",
"warframe-public-export-plus": "^0.5.93",
"warframe-public-export-plus": "^0.5.89",
"warframe-riven-info": "^0.1.2",
"winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0",
@ -38,7 +37,6 @@
"node": ">=20.18.1"
},
"optionalDependencies": {
"@types/body-parser": "^1.19.6",
"@types/express": "^5",
"@types/morgan": "^1.9.9",
"@types/websocket": "^1.0.10",
@ -5534,9 +5532,9 @@
}
},
"node_modules/warframe-public-export-plus": {
"version": "0.5.93",
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.93.tgz",
"integrity": "sha512-A8LSFJoyg7sU1n4L0zhLK1g0CREh8Fxvk7eXKoT8nMTroQg6YgEw02gK0MUi9U3rWTnlaGTsXZMp/tgC7HWUKw=="
"version": "0.5.89",
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.89.tgz",
"integrity": "sha512-a6dM1MirzofSsuv3LlRQHFLSSIGKPVSN93dcXSDmA3njsWqOGjJJdWyXqcyxxYw8rEB8CNowSHst/MUmKvKlRg=="
},
"node_modules/warframe-riven-info": {
"version": "0.1.2",

View File

@ -10,6 +10,7 @@
"build:dev": "tsgo --inlineSourceMap",
"build:dev:tsc": "tsc --incremental --inlineSourceMap",
"build-and-start": "npm run build && npm run start",
"build-and-start:bun": "npm run verify && npm run bun-run",
"dev": "node scripts/dev.cjs",
"dev:bun": "bun scripts/dev.cjs",
"verify": "tsgo --noEmit",
@ -21,12 +22,11 @@
"lint:fix": "eslint --fix --ext .ts .",
"prettier": "prettier --write .",
"update-translations": "cd scripts && node update-translations.cjs",
"fix": "npm run update-translations && npm run prettier"
"fix": "npm run update-translations && npm run lint:fix"
},
"license": "GNU",
"type": "module",
"dependencies": {
"body-parser": "^2.2.0",
"chokidar": "^4.0.3",
"crc-32": "^1.2.2",
"express": "^5",
@ -35,14 +35,13 @@
"morgan": "^1.10.0",
"ncp": "^2.0.0",
"undici": "^7.10.0",
"warframe-public-export-plus": "^0.5.93",
"warframe-public-export-plus": "^0.5.89",
"warframe-riven-info": "^0.1.2",
"winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0",
"ws": "^8.18.2"
},
"optionalDependencies": {
"@types/body-parser": "^1.19.6",
"@types/express": "^5",
"@types/morgan": "^1.9.9",
"@types/websocket": "^1.0.10",

View File

@ -40,10 +40,7 @@ function run(changedFile) {
runproc = undefined;
}
const thisbuildproc = spawn(
[process.versions.bun ? "bun" : "npm", "run", cangoraw ? "verify" : "build:dev"].join(" "),
spawnopts
);
const thisbuildproc = spawn("npm", ["run", cangoraw ? "verify" : "build:dev"], spawnopts);
const thisbuildstart = Date.now();
buildproc = thisbuildproc;
buildproc.on("exit", code => {
@ -54,13 +51,8 @@ function run(changedFile) {
if (code === 0) {
console.log(`${cangoraw ? "Verified" : "Built"} in ${Date.now() - thisbuildstart} ms`);
runproc = spawn(
[
process.versions.bun ? "bun" : "npm",
"run",
cangoraw ? (process.versions.bun ? "raw:bun" : "raw") : "start",
"--",
...args
].join(" "),
"npm",
["run", cangoraw ? (process.versions.bun ? "raw:bun" : "raw") : "start", "--", ...args],
spawnopts
);
runproc.on("exit", () => {

View File

@ -17,7 +17,7 @@ const app = express();
app.use((req, _res, next) => {
// 38.5.0 introduced "ezip" for encrypted body blobs and "e" for request verification only (encrypted body blobs with no application data).
// The client patch is expected to decrypt it for us but having an unsupported Content-Encoding here would still be an issue for Express, so removing it.
// The bootstrapper decrypts it for us but having an unsupported Content-Encoding here would still be an issue for Express, so removing it.
if (req.headers["content-encoding"] == "ezip" || req.headers["content-encoding"] == "e") {
req.headers["content-encoding"] = undefined;
}

View File

@ -1,5 +1,3 @@
export const EPOCH = 1734307200_000; // Monday, Dec 16, 2024 @ 00:00 UTC+0; should logically be the start of winter in 1999 iteration 0
const millisecondsPerSecond = 1000;
const secondsPerMinute = 60;
const minutesPerHour = 60;

View File

@ -31,13 +31,12 @@ export const abortDojoComponentController: RequestHandler = async (req, res) =>
if (request.DecoId) {
removeDojoDeco(guild, request.ComponentId, request.DecoId);
await guild.save();
res.json(await getDojoClient(guild, 0, request.ComponentId));
} else {
await removeDojoRoom(guild, request.ComponentId);
await guild.save();
res.json(await getDojoClient(guild, 0));
}
await guild.save();
res.json(await getDojoClient(guild, 0, request.ComponentId));
};
interface IAbortDojoComponentRequest {

View File

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

View File

@ -1,62 +0,0 @@
import type { RequestHandler } from "express";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { addMiscItem, getInventory } from "../../services/inventoryService.ts";
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
import { logger } from "../../utils/logger.ts";
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
export const feedPrinceController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "MiscItems NokkoColony NodeIntrosCompleted");
const payload = getJSONfromString<IFeedPrinceRequest>(String(req.body));
switch (payload.Mode) {
case "r": {
inventory.NokkoColony ??= {
FeedLevel: 0,
JournalEntries: []
};
const InventoryChanges: IInventoryChanges = {};
inventory.NokkoColony.FeedLevel += payload.Amount;
if (
(!inventory.NodeIntrosCompleted.includes("CompletedVision1") && inventory.NokkoColony.FeedLevel > 20) ||
(!inventory.NodeIntrosCompleted.includes("CompletedVision2") && inventory.NokkoColony.FeedLevel > 60)
) {
res.json({
FeedSucceeded: false,
FeedLevel: inventory.NokkoColony.FeedLevel - payload.Amount,
InventoryChanges
} satisfies IFeedPrinceResponse);
} else {
addMiscItem(
inventory,
"/Lotus/Types/Items/MiscItems/MushroomFood",
payload.Amount * -1,
InventoryChanges
);
await inventory.save();
res.json({
FeedSucceeded: true,
FeedLevel: inventory.NokkoColony.FeedLevel,
InventoryChanges
} satisfies IFeedPrinceResponse);
}
break;
}
default:
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
throw new Error(`unknown feedPrince mode: ${payload.Mode}`);
}
};
interface IFeedPrinceRequest {
Mode: string; // r
Amount: number;
}
interface IFeedPrinceResponse {
FeedSucceeded: boolean;
FeedLevel: number;
InventoryChanges: IInventoryChanges;
}

View File

@ -1,91 +1,23 @@
import type { RequestHandler } from "express";
import { getAccountForRequest } from "../../services/loginService.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getInventory, addMiscItems, addEquipment, occupySlot } from "../../services/inventoryService.ts";
import type { IMiscItem, TFocusPolarity, TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts";
import { InventorySlot } from "../../types/inventoryTypes/inventoryTypes.ts";
import { logger } from "../../utils/logger.ts";
import { ExportFocusUpgrades } from "warframe-public-export-plus";
import { Inventory } from "../../models/inventoryModels/inventoryModel.ts";
import { version_compare } from "../../helpers/inventoryHelpers.ts";
export const focusController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
let op = req.query.op as string;
const focus2 = account.BuildLabel && version_compare(account.BuildLabel, "2022.04.29.12.53") < 0;
if (focus2) {
// Focus 2.0
switch (req.query.op) {
case Focus2Operation.InstallLens:
op = "InstallLens";
break;
case Focus2Operation.UnlockWay:
op = "UnlockWay";
break;
case Focus2Operation.UnlockUpgrade:
op = "UnlockUpgrade";
break;
case Focus2Operation.IncreasePool:
op = "IncreasePool";
break;
case Focus2Operation.LevelUpUpgrade:
op = "LevelUpUpgrade";
break;
case Focus2Operation.ActivateWay:
op = "ActivateWay";
break;
case Focus2Operation.UpdateUpgrade:
op = "UpdateUpgrade";
break;
case Focus2Operation.SentTrainingAmplifier:
op = "SentTrainingAmplifier";
break;
case Focus2Operation.UnbindUpgrade:
op = "UnbindUpgrade";
break;
case Focus2Operation.ConvertShard:
op = "ConvertShard";
break;
}
} else {
// Focus 3.0
switch (req.query.op) {
case Focus3Operation.InstallLens:
op = "InstallLens";
break;
case Focus3Operation.UnlockWay:
op = "UnlockWay";
break;
case Focus3Operation.UnlockUpgrade:
op = "UnlockUpgrade";
break;
case Focus3Operation.LevelUpUpgrade:
op = "LevelUpUpgrade";
break;
case Focus3Operation.ActivateWay:
op = "ActivateWay";
break;
case Focus3Operation.SentTrainingAmplifier:
op = "SentTrainingAmplifier";
break;
case Focus3Operation.UnbindUpgrade:
op = "UnbindUpgrade";
break;
case Focus3Operation.ConvertShard:
op = "ConvertShard";
break;
}
}
switch (op) {
const accountId = await getAccountIdForRequest(req);
switch (req.query.op) {
default:
logger.error("Unhandled focus op type: " + String(req.query.op));
logger.debug(String(req.body));
res.end();
break;
case "InstallLens": {
case FocusOperation.InstallLens: {
const request = JSON.parse(String(req.body)) as ILensInstallRequest;
const inventory = await getInventory(account._id.toString());
const inventory = await getInventory(accountId);
const item = inventory[request.Category].id(request.WeaponId);
if (item) {
item.FocusLens = request.LensType;
@ -103,10 +35,10 @@ export const focusController: RequestHandler = async (req, res) => {
});
break;
}
case "UnlockWay": {
case FocusOperation.UnlockWay: {
const focusType = (JSON.parse(String(req.body)) as IWayRequest).FocusType;
const focusPolarity = focusTypeToPolarity(focusType);
const inventory = await getInventory(account._id.toString(), "FocusAbility FocusUpgrades FocusXP");
const inventory = await getInventory(accountId, "FocusAbility FocusUpgrades FocusXP");
const cost = inventory.FocusAbility ? 50_000 : 0;
inventory.FocusAbility ??= focusType;
inventory.FocusUpgrades.push({ ItemType: focusType });
@ -120,29 +52,12 @@ export const focusController: RequestHandler = async (req, res) => {
});
break;
}
case "IncreasePool": {
const request = JSON.parse(String(req.body)) as IIncreasePoolRequest;
const focusPolarity = focusTypeToPolarity(request.FocusType);
const inventory = await getInventory(account._id.toString(), "FocusXP FocusCapacity");
let cost = 0;
for (let capacity = request.CurrentTotalCapacity; capacity != request.NewTotalCapacity; ++capacity) {
cost += increasePoolCost[capacity - 5];
}
inventory.FocusXP![focusPolarity]! -= cost;
inventory.FocusCapacity = request.NewTotalCapacity;
await inventory.save();
res.json({
TotalCapacity: request.NewTotalCapacity,
FocusPointCosts: { [focusPolarity]: cost }
});
break;
}
case "ActivateWay": {
case FocusOperation.ActivateWay: {
const focusType = (JSON.parse(String(req.body)) as IWayRequest).FocusType;
await Inventory.updateOne(
{
accountOwnerId: account._id.toString()
accountOwnerId: accountId
},
{
FocusAbility: focusType
@ -154,20 +69,13 @@ export const focusController: RequestHandler = async (req, res) => {
});
break;
}
case "UnlockUpgrade": {
case FocusOperation.UnlockUpgrade: {
const request = JSON.parse(String(req.body)) as IUnlockUpgradeRequest;
const focusPolarity = focusTypeToPolarity(request.FocusTypes[0]);
const inventory = await getInventory(account._id.toString());
const inventory = await getInventory(accountId);
let cost = 0;
for (const focusType of request.FocusTypes) {
if (focusType in ExportFocusUpgrades) {
cost += ExportFocusUpgrades[focusType].baseFocusPointCost;
} else if (focusType == "/Lotus/Upgrades/Focus/Power/Residual/ChannelEfficiencyFocusUpgrade") {
// Zenurik's Inner Might (Focus 2.0)
cost += 50_000;
} else {
logger.warn(`unknown focus upgrade ${focusType}, will unlock it for free`);
}
cost += ExportFocusUpgrades[focusType].baseFocusPointCost;
inventory.FocusUpgrades.push({ ItemType: focusType, Level: 0 });
}
inventory.FocusXP![focusPolarity]! -= cost;
@ -178,20 +86,15 @@ export const focusController: RequestHandler = async (req, res) => {
});
break;
}
case "LevelUpUpgrade":
case "UpdateUpgrade": {
case FocusOperation.LevelUpUpgrade: {
const request = JSON.parse(String(req.body)) as ILevelUpUpgradeRequest;
const focusPolarity = focusTypeToPolarity(request.FocusInfos[0].ItemType);
const inventory = await getInventory(account._id.toString());
const inventory = await getInventory(accountId);
let cost = 0;
for (const focusUpgrade of request.FocusInfos) {
cost += focusUpgrade.FocusXpCost;
const focusUpgradeDb = inventory.FocusUpgrades.find(entry => entry.ItemType == focusUpgrade.ItemType)!;
if (op == "UpdateUpgrade") {
focusUpgradeDb.IsActive = focusUpgrade.IsActive;
} else {
focusUpgradeDb.Level = focusUpgrade.Level;
}
focusUpgradeDb.Level = focusUpgrade.Level;
}
inventory.FocusXP![focusPolarity]! -= cost;
await inventory.save();
@ -201,9 +104,9 @@ export const focusController: RequestHandler = async (req, res) => {
});
break;
}
case "SentTrainingAmplifier": {
case FocusOperation.SentTrainingAmplifier: {
const request = JSON.parse(String(req.body)) as ISentTrainingAmplifierRequest;
const inventory = await getInventory(account._id.toString());
const inventory = await getInventory(accountId);
const inventoryChanges = addEquipment(inventory, "OperatorAmps", request.StartingWeaponType, {
ModularParts: [
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingGrip",
@ -216,10 +119,10 @@ export const focusController: RequestHandler = async (req, res) => {
res.json(inventoryChanges.OperatorAmps![0]);
break;
}
case "UnbindUpgrade": {
case FocusOperation.UnbindUpgrade: {
const request = JSON.parse(String(req.body)) as IUnbindUpgradeRequest;
const focusPolarity = focusTypeToPolarity(request.FocusTypes[0]);
const inventory = await getInventory(account._id.toString());
const inventory = await getInventory(accountId);
inventory.FocusXP![focusPolarity]! -= 750_000 * request.FocusTypes.length;
addMiscItems(inventory, [
{
@ -246,7 +149,7 @@ export const focusController: RequestHandler = async (req, res) => {
});
break;
}
case "ConvertShard": {
case FocusOperation.ConvertShard: {
const request = JSON.parse(String(req.body)) as IConvertShardRequest;
// Tally XP
let xp = 0;
@ -264,7 +167,7 @@ export const focusController: RequestHandler = async (req, res) => {
for (const shard of request.Shards) {
shard.ItemCount *= -1;
}
const inventory = await getInventory(account._id.toString());
const inventory = await getInventory(accountId);
const polarity = request.Polarity;
inventory.FocusXP ??= {};
inventory.FocusXP[polarity] ??= 0;
@ -276,8 +179,7 @@ export const focusController: RequestHandler = async (req, res) => {
}
};
// Focus 3.0
enum Focus3Operation {
enum FocusOperation {
InstallLens = "1",
UnlockWay = "2",
UnlockUpgrade = "3",
@ -288,20 +190,6 @@ enum Focus3Operation {
ConvertShard = "9"
}
// Focus 2.0
enum Focus2Operation {
InstallLens = "1",
UnlockWay = "2",
UnlockUpgrade = "3",
IncreasePool = "4",
LevelUpUpgrade = "5",
ActivateWay = "6",
UpdateUpgrade = "7", // used to change the IsActive state, same format as ILevelUpUpgradeRequest
SentTrainingAmplifier = "9",
UnbindUpgrade = "10",
ConvertShard = "11"
}
// For UnlockWay & ActivateWay
interface IWayRequest {
FocusType: string;
@ -311,13 +199,6 @@ interface IUnlockUpgradeRequest {
FocusTypes: string[];
}
// Focus 2.0
interface IIncreasePoolRequest {
FocusType: string;
CurrentTotalCapacity: number;
NewTotalCapacity: number;
}
interface ILevelUpUpgradeRequest {
FocusInfos: {
ItemType: string;
@ -325,7 +206,6 @@ interface ILevelUpUpgradeRequest {
IsUniversal: boolean;
Level: number;
IsActiveAbility: boolean;
IsActive?: number; // Focus 2.0
}[];
}
@ -360,19 +240,3 @@ const shardValues = {
"/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantItem": 25_000,
"/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantTierTwoItem": 40_000
};
// Starting at a capacity of 5 (Source: https://wiki.warframe.com/w/Focus_2.0)
const increasePoolCost = [
2576, 3099, 3638, 4190, 4755, 5331, 5918, 6514, 7120, 7734, 8357, 8988, 9626, 10271, 10923, 11582, 12247, 12918,
13595, 14277, 14965, 15659, 16357, 17061, 17769, 18482, 19200, 19922, 20649, 21380, 22115, 22854, 23597, 24344,
25095, 25850, 26609, 27371, 28136, 28905, 29678, 30454, 31233, 32015, 32801, 33590, 34382, 35176, 35974, 36775,
37579, 38386, 39195, 40008, 40823, 41641, 42461, 43284, 44110, 44938, 45769, 46603, 47439, 48277, 49118, 49961,
50807, 51655, 52505, 53357, 54212, 55069, 55929, 56790, 57654, 58520, 59388, 60258, 61130, 62005, 62881, 63759,
64640, 65522, 66407, 67293, 68182, 69072, 69964, 70858, 71754, 72652, 73552, 74453, 75357, 76262, 77169, 78078,
78988, 79900, 80814, 81730, 82648, 83567, 84488, 85410, 86334, 87260, 88188, 89117, 90047, 90980, 91914, 92849,
93786, 94725, 95665, 96607, 97550, 98495, 99441, 100389, 101338, 102289, 103241, 104195, 105150, 106107, 107065,
108024, 108985, 109948, 110911, 111877, 112843, 113811, 114780, 115751, 116723, 117696, 118671, 119647, 120624,
121603, 122583, 123564, 124547, 125531, 126516, 127503, 128490, 129479, 130470, 131461, 132454, 133448, 134443,
135440, 136438, 137437, 138437, 139438, 140441, 141444, 142449, 143455, 144463, 145471, 146481, 147492, 148503,
149517
];

View File

@ -1,27 +0,0 @@
import type { RequestHandler } from "express";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
import { getInventory } from "../../services/inventoryService.ts";
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
export const forceRemoveItemController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "MiscItems");
const body = getJSONfromString<IForceRemoveItemRequest>(String(req.body));
const inventoryChanges: IInventoryChanges = {};
for (const item of body.items) {
const index = inventory.MiscItems.findIndex(x => x.ItemType == item);
if (index != -1) {
inventoryChanges.MiscItems ??= [];
inventoryChanges.MiscItems.push({ ItemType: item, ItemCount: inventory.MiscItems[index].ItemCount * -1 });
inventory.MiscItems.splice(index, 1);
}
}
await inventory.save();
res.json({ InventoryChanges: inventoryChanges });
};
interface IForceRemoveItemRequest {
items: string[];
}

View File

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

View File

@ -1,8 +1,8 @@
import type { RequestHandler } from "express";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getInventory } from "../../services/inventoryService.ts";
import { getSeasonChallengePools, getWorldState, pushWeeklyActs } from "../../services/worldStateService.ts";
import { EPOCH, unixTimesInMs } from "../../constants/timeConstants.ts";
import { EPOCH, getSeasonChallengePools, getWorldState, pushWeeklyActs } from "../../services/worldStateService.ts";
import { unixTimesInMs } from "../../constants/timeConstants.ts";
import type { ISeasonChallenge } from "../../types/worldStateTypes.ts";
import { ExportChallenges } from "warframe-public-export-plus";

View File

@ -11,11 +11,7 @@ export const getVoidProjectionRewardsController: RequestHandler = async (req, re
if (data.ParticipantInfo.QualifiesForReward && !data.ParticipantInfo.HaveRewardResponse) {
const inventory = await getInventory(accountId);
const reward = await crackRelic(inventory, data.ParticipantInfo);
if (!inventory.MissionRelicRewards || inventory.MissionRelicRewards.length >= data.CurrentWave) {
inventory.MissionRelicRewards = [];
}
inventory.MissionRelicRewards.push({ ItemType: reward.type, ItemCount: reward.itemCount });
await crackRelic(inventory, data.ParticipantInfo);
await inventory.save();
}

View File

@ -1,12 +1,12 @@
import type { RequestHandler } from "express";
import { parseString } from "../../helpers/general.ts";
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
import { getInventory } from "../../services/inventoryService.ts";
import { giveKeyChainItem } from "../../services/questService.ts";
import type { IKeyChainRequest } from "../../types/requestTypes.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts";
export const giveKeyChainTriggeredItemsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const accountId = parseString(req.query.accountId);
const keyChainInfo = getJSONfromString<IKeyChainRequest>((req.body as string).toString());
const inventory = await getInventory(accountId);

View File

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

View File

@ -177,7 +177,7 @@ export const inventoryController: RequestHandler = async (request, response) =>
}
}
await cleanupInventory(inventory);
cleanupInventory(inventory);
inventory.NextRefill = new Date((today + 1) * 86400000); // tomorrow at 0 UTC
//await inventory.save();
@ -335,29 +335,28 @@ export const getInventoryResponse = async (
}
if (config.unlockAllSkins) {
const ownedWeaponSkins = new Set<string>(inventoryResponse.WeaponSkins.map(x => x.ItemType));
for (const [uniqueName, meta] of Object.entries(ExportCustoms)) {
if (!meta.alwaysAvailable && !ownedWeaponSkins.has(uniqueName)) {
inventoryResponse.WeaponSkins.push({
ItemId: {
$oid: "ca70ca70ca70ca70" + catBreadHash(uniqueName).toString(16).padStart(8, "0")
},
ItemType: uniqueName
});
}
const missingWeaponSkins = new Set(Object.keys(ExportCustoms));
inventoryResponse.WeaponSkins.forEach(x => missingWeaponSkins.delete(x.ItemType));
for (const uniqueName of missingWeaponSkins) {
inventoryResponse.WeaponSkins.push({
ItemId: {
$oid: "ca70ca70ca70ca70" + catBreadHash(uniqueName).toString(16).padStart(8, "0")
},
ItemType: uniqueName
});
}
}
if (inventory.spoofMasteryRank && inventory.spoofMasteryRank >= 0) {
inventoryResponse.PlayerLevel = inventory.spoofMasteryRank;
if (typeof config.spoofMasteryRank === "number" && config.spoofMasteryRank >= 0) {
inventoryResponse.PlayerLevel = config.spoofMasteryRank;
if (!xpBasedLevelCapDisabled) {
// This client has not been patched to accept any mastery rank, need to fake the XP.
inventoryResponse.XPInfo = [];
let numFrames = getExpRequiredForMr(Math.min(inventory.spoofMasteryRank, 5030)) / (30 * 200);
let numFrames = getExpRequiredForMr(Math.min(config.spoofMasteryRank, 5030)) / 6000;
while (numFrames-- > 0) {
inventoryResponse.XPInfo.push({
ItemType: "/Lotus/Powersuits/Mag/Mag",
XP: 900_000 // Enough for rank 30 as per https://wiki.warframe.com/w/Affinity
XP: 1_600_000
});
}
}
@ -461,9 +460,6 @@ export const getInventoryResponse = async (
toLegacyOid(id);
}
}
if (inventoryResponse.GuildId) {
toLegacyOid(inventoryResponse.GuildId);
}
}
}
}

View File

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

View File

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

View File

@ -1,4 +1,4 @@
import { fromDbOid, toMongoDate, version_compare } from "../../helpers/inventoryHelpers.ts";
import { fromDbOid, version_compare } from "../../helpers/inventoryHelpers.ts";
import type { IKnifeResponse } from "../../helpers/nemesisHelpers.ts";
import {
antivirusMods,
@ -149,10 +149,7 @@ export const nemesisController: RequestHandler = async (req, res) => {
break;
}
}
const antivirusGainMultiplier = (
await getInventory(account._id.toString(), "nemesisAntivirusGainMultiplier")
).nemesisAntivirusGainMultiplier;
inventory.Nemesis!.HenchmenKilled += antivirusGain * (antivirusGainMultiplier ?? 1);
inventory.Nemesis!.HenchmenKilled += antivirusGain;
if (inventory.Nemesis!.HenchmenKilled >= 100) {
inventory.Nemesis!.HenchmenKilled = 100;
@ -310,26 +307,6 @@ export const nemesisController: RequestHandler = async (req, res) => {
res.json({
target: inventory.toJSON().Nemesis
});
} else if ((req.query.mode as string) == "t") {
const inventory = await getInventory(account._id.toString(), "LastNemesisAllySpawnTime");
//const body = getJSONfromString<IUpdateAllySpawnTimeRequest>(String(req.body));
const now = new Date(Math.trunc(Date.now() / 1000) * 1000);
inventory.LastNemesisAllySpawnTime = now;
await inventory.save();
res.json({
NewTime: toMongoDate(now)
} satisfies IUpdateAllySpawnTimeResponse);
} else if ((req.query.mode as string) == "d") {
const inventory = await getInventory(account._id.toString(), "NemesisHistory");
const body = getJSONfromString<IRelinquishAdversariesRequest>(String(req.body));
for (const fp of body.nemesisFingerprints) {
const index = inventory.NemesisHistory!.findIndex(x => x.fp == fp);
if (index != -1) {
inventory.NemesisHistory!.splice(index, 1);
}
}
await inventory.save();
res.json(body);
} else if ((req.query.mode as string) == "w") {
const inventory = await getInventory(account._id.toString(), "Nemesis");
//const body = getJSONfromString<INemesisWeakenRequest>(String(req.body));
@ -467,15 +444,3 @@ const consumeModCharge = (
response.UpgradeNew.push(true);
}
};
interface IRelinquishAdversariesRequest {
nemesisFingerprints: (bigint | number)[];
}
// interface IUpdateAllySpawnTimeRequest {
// LastSpawnTime: IMongoDate;
// }
interface IUpdateAllySpawnTimeResponse {
NewTime: IMongoDate;
}

View File

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

View File

@ -1,117 +0,0 @@
import type { RequestHandler } from "express";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { addMiscItem, getInventory } from "../../services/inventoryService.ts";
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
import { logger } from "../../utils/logger.ts";
import type { IJournalEntry } from "../../types/inventoryTypes/inventoryTypes.ts";
import type { IAffiliationMods } from "../../types/purchaseTypes.ts";
export const researchMushroomController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "MiscItems NokkoColony Affiliations");
const payload = getJSONfromString<IResearchMushroom>(String(req.body));
switch (payload.Mode) {
case "r": {
const InventoryChanges = {};
const AffiliationMods: IAffiliationMods[] = [];
addMiscItem(inventory, payload.MushroomItem, payload.Amount * -1, InventoryChanges);
if (payload.Convert) {
addMiscItem(inventory, "/Lotus/Types/Items/MiscItems/MushroomFood", payload.Amount, InventoryChanges);
}
inventory.NokkoColony ??= {
FeedLevel: 0,
JournalEntries: []
};
let journalEntry = inventory.NokkoColony.JournalEntries.find(x => x.EntryType == payload.MushroomItem);
if (!journalEntry) {
journalEntry = { EntryType: payload.MushroomItem, Progress: 0 };
inventory.NokkoColony.JournalEntries.push(journalEntry);
}
let syndicate = inventory.Affiliations.find(x => x.Tag == "NightcapJournalSyndicate");
if (!syndicate) {
syndicate = { Tag: "NightcapJournalSyndicate", Title: 0, Standing: 0 };
inventory.Affiliations.push(syndicate);
}
const completedBefore = inventory.NokkoColony.JournalEntries.filter(
entry => getJournalRank(entry) === 3
).length;
const PrevRank = syndicateTitleThresholds.reduce(
(rank, threshold, i) => (completedBefore >= threshold ? i : rank),
0
);
if (getJournalRank(journalEntry) < 3) journalEntry.Progress += payload.Amount;
const completedAfter = inventory.NokkoColony.JournalEntries.filter(
entry => getJournalRank(entry) === 3
).length;
const NewRank = syndicateTitleThresholds.reduce(
(rank, threshold, i) => (completedAfter >= threshold ? i : rank),
0
);
if (NewRank > (syndicate.Title ?? 0)) {
syndicate.Title = NewRank;
AffiliationMods.push({ Tag: "NightcapJournalSyndicate", Title: NewRank });
}
await inventory.save();
res.json({
PrevRank,
NewRank,
Progress: journalEntry.Progress,
InventoryChanges,
AffiliationMods
});
break;
}
default:
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
throw new Error(`unknown researchMushroom mode: ${payload.Mode}`);
}
};
interface IResearchMushroom {
Mode: string; // r
MushroomItem: string;
Amount: number;
Convert: boolean;
}
const journalEntriesRank: Record<string, number> = {
"/Lotus/Types/Items/MushroomJournal/PlainMushroomJournalItem": 1,
"/Lotus/Types/Items/MushroomJournal/GasMushroomJournalItem": 4,
"/Lotus/Types/Items/MushroomJournal/ToxinMushroomJournalItem": 3,
"/Lotus/Types/Items/MushroomJournal/ViralMushroomJournalItem": 4,
"/Lotus/Types/Items/MushroomJournal/MagneticMushroomJournalItem": 4,
"/Lotus/Types/Items/MushroomJournal/ElectricMushroomJournalItem": 3,
"/Lotus/Types/Items/MushroomJournal/TauMushroomJournalItem": 5,
"/Lotus/Types/Items/MushroomJournal/SlashMushroomJournalItem": 3,
"/Lotus/Types/Items/MushroomJournal/BlastMushroomJournalItem": 4,
"/Lotus/Types/Items/MushroomJournal/ImpactMushroomJournalItem": 3,
"/Lotus/Types/Items/MushroomJournal/ColdMushroomJournalItem": 3,
"/Lotus/Types/Items/MushroomJournal/CorrosiveMushroomJournalItem": 4,
"/Lotus/Types/Items/MushroomJournal/PunctureMushroomJournalItem": 3,
"/Lotus/Types/Items/MushroomJournal/HeatMushroomJournalItem": 3,
"/Lotus/Types/Items/MushroomJournal/RadiationMushroomJournalItem": 4,
"/Lotus/Types/Items/MushroomJournal/VoidMushroomJournalItem": 5
};
const syndicateTitleThresholds = [0, 1, 2, 6, 12, 16];
const getJournalRank = (journalEntry: IJournalEntry): number => {
const k = journalEntriesRank[journalEntry.EntryType];
if (!k) return 0;
const thresholds = [k * 1, k * 3, k * 6];
if (journalEntry.Progress >= thresholds[2]) return 3;
if (journalEntry.Progress >= thresholds[1]) return 2;
if (journalEntry.Progress >= thresholds[0]) return 1;
return 0;
};

View File

@ -1,17 +0,0 @@
import type { RequestHandler } from "express";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { resetQuestKeyToStage } from "../../services/questService.ts";
import { getInventory } from "../../services/inventoryService.ts";
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
import type { IKeyChainRequest } from "../../types/requestTypes.ts";
export const reverseQuestProgressController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const keyChainInfo = getJSONfromString<IKeyChainRequest>((req.body as string).toString());
const inventory = await getInventory(accountId);
resetQuestKeyToStage(inventory, keyChainInfo);
await inventory.save();
res.end();
};

View File

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

View File

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

View File

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

View File

@ -1,20 +1,11 @@
import type { RequestHandler } from "express";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getInventory } from "../../services/inventoryService.ts";
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
import { unlockShipFeature } from "../../services/personalRoomsService.ts";
import { updateShipFeature } from "../../services/personalRoomsService.ts";
import type { IUnlockShipFeatureRequest } from "../../types/requestTypes.ts";
import { parseString } from "../../helpers/general.ts";
export const unlockShipFeatureController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "MiscItems accountOwnerId");
const request = getJSONfromString<IUnlockShipFeatureRequest>(String(req.body));
await unlockShipFeature(inventory, request.Feature);
await inventory.save();
const accountId = parseString(req.query.accountId);
const shipFeatureRequest = JSON.parse((req.body as string).toString()) as IUnlockShipFeatureRequest;
await updateShipFeature(accountId, shipFeatureRequest.Feature);
res.send([]);
};
interface IUnlockShipFeatureRequest {
Feature: string;
KeyChain: string;
ChainStage: number;
}

View File

@ -14,7 +14,7 @@ export const updateChallengeProgressController: RequestHandler = async (req, res
const inventory = await getInventory(
account._id.toString(),
"ChallengesFixVersion ChallengeProgress SeasonChallengeHistory Affiliations CalendarProgress nightwaveStandingMultiplier"
"ChallengesFixVersion ChallengeProgress SeasonChallengeHistory Affiliations CalendarProgress"
);
let affiliationMods: IAffiliationMods[] = [];
if (challenges.ChallengeProgress) {

View File

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

View File

@ -96,15 +96,10 @@ export const upgradesController: RequestHandler = async (req, res) => {
case "/Lotus/Types/Items/MiscItems/WeaponPrimaryArcaneUnlocker":
case "/Lotus/Types/Items/MiscItems/WeaponSecondaryArcaneUnlocker":
case "/Lotus/Types/Items/MiscItems/WeaponMeleeArcaneUnlocker":
case "/Lotus/Types/Items/MiscItems/WeaponAmpArcaneUnlocker":
case "/Lotus/Types/Items/MiscItems/WeaponArchGunArcaneUnlocker": {
case "/Lotus/Types/Items/MiscItems/WeaponAmpArcaneUnlocker": {
const item = inventory[payload.ItemCategory].id(payload.ItemId.$oid)!;
item.Features ??= 0;
if (operation.OperationType == "UOT_ARCANE_UNLOCK_1") {
item.Features |= EquipmentFeatures.SECOND_ARCANE_SLOT;
} else {
item.Features |= EquipmentFeatures.ARCANE_SLOT;
}
item.Features |= EquipmentFeatures.ARCANE_SLOT;
break;
}
case "/Lotus/Types/Items/MiscItems/ValenceAdapter": {

View File

@ -0,0 +1,36 @@
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getInventory } from "../../services/inventoryService.ts";
import type { RequestHandler } from "express";
import { getGuildForRequestEx, hasGuildPermission } from "../../services/guildService.ts";
import { GuildPermission } from "../../types/guildTypes.ts";
import type { ITypeCount } from "../../types/commonTypes.ts";
export const addVaultDecoRecipeController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const requests = req.body as ITypeCount[];
const inventory = await getInventory(accountId, "GuildId");
const guild = await getGuildForRequestEx(req, inventory);
if (!(await hasGuildPermission(guild, accountId, GuildPermission.Architect))) {
res.status(400).send("-1").end();
return;
}
guild.VaultDecoRecipes ??= [];
for (const request of requests) {
const index = guild.VaultDecoRecipes.findIndex(x => x.ItemType === request.ItemType);
if (index == -1) {
guild.VaultDecoRecipes.push({
ItemType: request.ItemType,
ItemCount: request.ItemCount
});
} else {
guild.VaultDecoRecipes[index].ItemCount += request.ItemCount;
if (guild.VaultDecoRecipes[index].ItemCount < 1) {
guild.VaultDecoRecipes.splice(index, 1);
}
}
}
await guild.save();
res.end();
};

View File

@ -1,43 +0,0 @@
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getInventory } from "../../services/inventoryService.ts";
import type { RequestHandler } from "express";
import { getGuildForRequestEx, hasGuildPermission } from "../../services/guildService.ts";
import { GuildPermission } from "../../types/guildTypes.ts";
import type { ITypeCount } from "../../types/commonTypes.ts";
export const addVaultTypeCountController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const { vaultType, items } = req.body as {
vaultType: keyof typeof vaultConfig;
items: ITypeCount[];
};
const inventory = await getInventory(accountId, "GuildId");
const guild = await getGuildForRequestEx(req, inventory);
if (!(await hasGuildPermission(guild, accountId, vaultConfig[vaultType]))) {
res.status(400).send("-1").end();
return;
}
guild[vaultType] ??= [];
for (const item of items) {
const index = guild[vaultType].findIndex(x => x.ItemType === item.ItemType);
if (index === -1) {
guild[vaultType].push({
ItemType: item.ItemType,
ItemCount: item.ItemCount
});
} else {
guild[vaultType][index].ItemCount += item.ItemCount;
if (guild[vaultType][index].ItemCount < 1) {
guild[vaultType].splice(index, 1);
}
}
}
await guild.save();
res.end();
};
const vaultConfig = {
VaultShipDecorations: GuildPermission.Treasurer,
VaultMiscItems: GuildPermission.Treasurer,
VaultDecoRecipes: GuildPermission.Architect
} as const;

View File

@ -36,11 +36,6 @@ export const completeAllMissionsController: RequestHandler = async (req, res) =>
}
addString(inventory.NodeIntrosCompleted, "TeshinHardModeUnlocked");
addString(inventory.NodeIntrosCompleted, "CetusSyndicate_IntroJob");
let syndicate = inventory.Affiliations.find(x => x.Tag == "CetusSyndicate");
if (!syndicate) {
syndicate =
inventory.Affiliations[inventory.Affiliations.push({ Tag: "CetusSyndicate", Standing: 250, Title: 0 })]; // Non-zero standing avoids Konzu's "prove yourself" text. 250 is identical to newbie bounty + bonus
}
await inventory.save();
res.end();
};

View File

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

View File

@ -38,7 +38,6 @@ interface ListedItem {
parazon?: boolean;
alwaysAvailable?: boolean;
maxLevelCap?: number;
eligibleForVault?: boolean;
}
interface ItemLists {
@ -67,7 +66,6 @@ interface ItemLists {
VaultDecoRecipes: ListedItem[];
FlavourItems: ListedItem[];
ShipDecorations: ListedItem[];
WeaponSkins: ListedItem[];
//circuitGameModes: ListedItem[];
}
@ -109,8 +107,7 @@ const getItemListsController: RequestHandler = (req, response) => {
TechProjects: [],
VaultDecoRecipes: [],
FlavourItems: [],
ShipDecorations: [],
WeaponSkins: []
ShipDecorations: []
/*circuitGameModes: [
{
uniqueName: "Survival",
@ -142,12 +139,6 @@ const getItemListsController: RequestHandler = (req, response) => {
}
]*/
};
const eligibleForVault = new Set<string>([
...Object.values(ExportDojoRecipes.research).flatMap(r => r.ingredients.map(i => i.ItemType)),
...Object.values(ExportDojoRecipes.fabrications).flatMap(f => f.ingredients.map(i => i.ItemType)),
...Object.values(ExportDojoRecipes.rooms).flatMap(r => r.ingredients.map(i => i.ItemType)),
...Object.values(ExportDojoRecipes.decos).flatMap(d => d.ingredients.map(i => i.ItemType))
]);
for (const [uniqueName, item] of Object.entries(ExportWarframes)) {
res[item.productCategory].push({
uniqueName,
@ -253,8 +244,7 @@ const getItemListsController: RequestHandler = (req, response) => {
res.miscitems.push({
uniqueName: uniqueName,
name: name,
subtype: "Resource",
...(eligibleForVault.has(uniqueName) && { eligibleForVault: true })
subtype: "Resource"
});
}
}
@ -308,18 +298,10 @@ const getItemListsController: RequestHandler = (req, response) => {
});
}
for (const [uniqueName, item] of Object.entries(ExportCustoms)) {
if (
item.productCategory == "WeaponSkins" &&
!uniqueName.startsWith("/Lotus/Types/Game/Lotus") && // Base Items
!uniqueName.endsWith("ProjectileSkin") && // UnrealTournament ProjectileSkins
!uniqueName.endsWith("Coat") // Frost Prime stuff
) {
res.WeaponSkins.push({
uniqueName: uniqueName,
name: getString(item.name, lang),
alwaysAvailable: item.alwaysAvailable
});
}
res.miscitems.push({
uniqueName: uniqueName,
name: getString(item.name, lang)
});
}
for (const [uniqueName, upgrade] of Object.entries(ExportUpgrades)) {

View File

@ -61,7 +61,6 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
break;
}
inventory.QuestKeys.pull({ ItemType: questItemType });
if (inventory.ActiveQuest == questItemType) inventory.ActiveQuest = "";
break;
}
case "completeKey": {
@ -116,7 +115,7 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
if (stage > 0) {
await giveKeyChainStageTriggered(inventory, {
KeyChain: questKey.ItemType,
ChainStage: stage - 1
ChainStage: stage
});
}
}
@ -137,7 +136,7 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
if (currentStage + 1 == questManifest.chainStages?.length) {
logger.debug(`Trying to complete last stage with nextStage, calling completeQuest instead`);
await completeQuest(inventory, questKey.ItemType, true);
await completeQuest(inventory, questKey.ItemType);
} else {
if (run > 0) {
questKey.Progress[currentStage + 1].c = run;

View File

@ -1,24 +0,0 @@
import { getInventory } from "../../services/inventoryService.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import type { RequestHandler } from "express";
import { equipmentKeys } from "../../types/inventoryTypes/inventoryTypes.ts";
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
export const removeIsNewController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const filteredEquipmentKeys = equipmentKeys.filter(k => k !== "CrewShipWeapons" && k !== "CrewShipSalvagedWeapons");
const inventory = await getInventory(accountId, [...filteredEquipmentKeys, "WeaponSkins"].join(" "));
for (const key of filteredEquipmentKeys) {
if (key in inventory) {
for (const equipment of inventory[key]) {
if (equipment.IsNew) equipment.IsNew = false;
}
}
}
for (const equipment of inventory.WeaponSkins) {
if (equipment.IsNew) equipment.IsNew = false;
}
await inventory.save();
res.end();
broadcastInventoryUpdate(req);
};

View File

@ -1,31 +1,22 @@
import { getInventory } from "../../services/inventoryService.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { sendWsBroadcastEx, sendWsBroadcastTo } from "../../services/wsService.ts";
import { sendWsBroadcastTo } from "../../services/wsService.ts";
import type { IAccountCheats } from "../../types/inventoryTypes/inventoryTypes.ts";
import type { RequestHandler } from "express";
import { logger } from "../../utils/logger.ts";
export const setAccountCheatController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const payload = req.body as ISetAccountCheatRequest;
const inventory = await getInventory(accountId, payload.key);
if (payload.value == undefined) {
logger.warn(`Aborting setting ${payload.key} as undefined!`);
return;
}
inventory[payload.key] = payload.value as never;
inventory[payload.key] = payload.value;
await inventory.save();
res.end();
if (["infiniteCredits", "infinitePlatinum", "infiniteEndo", "infiniteRegalAya"].indexOf(payload.key) != -1) {
sendWsBroadcastTo(accountId, { update_inventory: true, sync_inventory: true });
} else {
sendWsBroadcastEx({ update_inventory: true }, accountId, parseInt(String(req.query.wsid)));
}
};
interface ISetAccountCheatRequest {
key: keyof IAccountCheats;
value: IAccountCheats[keyof IAccountCheats];
value: boolean;
}

View File

@ -1,9 +1,16 @@
import type { RequestHandler } from "express";
import type { ITunables } from "../../types/bootstrapperTypes.ts";
// This endpoint is specific to the OpenWF Bootstrapper: https://openwf.io/bootstrapper-manual
export const tunablesController: RequestHandler = (_req, res) => {
interface ITunables {
prohibit_skip_mission_start_timer?: boolean;
prohibit_fov_override?: boolean;
prohibit_freecam?: boolean;
prohibit_teleport?: boolean;
prohibit_scripts?: boolean;
}
const tunablesController: RequestHandler = (_req, res) => {
const tunables: ITunables = {};
//tunables.prohibit_skip_mission_start_timer = true;
//tunables.prohibit_fov_override = true;
@ -12,3 +19,5 @@ export const tunablesController: RequestHandler = (_req, res) => {
//tunables.prohibit_scripts = true;
res.json(tunables);
};
export { tunablesController };

View File

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

View File

@ -1,12 +1,5 @@
import type { RequestHandler } from "express";
import { getAccountForRequest } from "../../services/loginService.ts";
import { version_compare } from "../../helpers/inventoryHelpers.ts";
export const getSkuCatalogController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
if (!account.BuildLabel || version_compare(account.BuildLabel, "2025.10.14.16.10") >= 0) {
res.sendFile("static/fixed_responses/getSkuCatalogU40.json", { root: "./" });
} else {
res.sendFile("static/fixed_responses/getSkuCatalog.json", { root: "./" });
}
export const getSkuCatalogController: RequestHandler = (_req, res) => {
res.sendFile("static/fixed_responses/getSkuCatalog.json", { root: "./" });
};

View File

@ -8,6 +8,7 @@ import { logger } from "../utils/logger.ts";
import { addMiscItems, combineInventoryChanges } from "../services/inventoryService.ts";
import { handleStoreItemAcquisition } from "../services/purchaseService.ts";
import type { IInventoryChanges } from "../types/purchaseTypes.ts";
import { config } from "../services/configService.ts";
export const crackRelic = async (
inventory: TInventoryDatabaseDocument,
@ -28,10 +29,10 @@ export const crackRelic = async (
ExportRewards[relic.rewardManifest][0] as { type: string; itemCount: number; rarity: TRarity }[], // rarity is nullable in PE+ typings, but always present for relics
weights
)!;
if (inventory.relicRewardItemCountMultiplier && inventory.relicRewardItemCountMultiplier != 1) {
if (config.relicRewardItemCountMultiplier !== undefined && (config.relicRewardItemCountMultiplier ?? 1) != 1) {
reward = {
...reward,
itemCount: reward.itemCount * inventory.relicRewardItemCountMultiplier
itemCount: reward.itemCount * config.relicRewardItemCountMultiplier
};
}
logger.debug(`relic rolled`, reward);
@ -53,9 +54,6 @@ export const crackRelic = async (
(await handleStoreItemAcquisition(reward.type, inventory, reward.itemCount)).InventoryChanges
);
// Client has picked its own reward (for lack of choice)
participant.ChosenRewardOwner = participant.AccountId;
return reward;
};

View File

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

View File

@ -19,7 +19,6 @@ logger.info("Starting up...");
// 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 path from "path";
import child_process from "child_process";
import { JSONStringify } from "json-with-bigint";
import { startWebServer } from "./services/webService.ts";
import { validateConfig } from "./services/configWatcherService.ts";
@ -44,17 +43,6 @@ mongoose
startWebServer();
if (config.ircExecutable) {
logger.info(`Starting IRC server: ${config.ircExecutable}`);
child_process.execFile(config.ircExecutable, (error, _stdout, _stderr) => {
if (error) {
logger.warn(`Failed to start IRC server`, error);
} else {
logger.warn(`IRC server terminated unexpectedly`);
}
});
}
void updateWorldStateCollections();
setInterval(() => {
void updateWorldStateCollections();

View File

@ -88,9 +88,7 @@ import type {
IGoalProgressDatabase,
IGoalProgressClient,
IKubrowPetPrintClient,
IKubrowPetPrintDatabase,
INokkoColony,
IJournalEntry
IKubrowPetPrintDatabase
} from "../../types/inventoryTypes/inventoryTypes.ts";
import { equipmentKeys } from "../../types/inventoryTypes/inventoryTypes.ts";
import type { IOid, ITypeCount } from "../../types/commonTypes.ts";
@ -148,8 +146,7 @@ const focusUpgradeSchema = new Schema<IFocusUpgrade>(
{
ItemType: String,
Level: Number,
IsUniversal: Boolean,
IsActive: Number
IsUniversal: Boolean
},
{ _id: false }
);
@ -1426,22 +1423,6 @@ const hubNpcCustomizationSchema = new Schema<IHubNpcCustomization>(
{ _id: false }
);
const journalEntrySchema = new Schema<IJournalEntry>(
{
EntryType: String,
Progress: Number
},
{ _id: false }
);
const nokkoColonySchema = new Schema<INokkoColony>(
{
FeedLevel: Number,
JournalEntries: [journalEntrySchema]
},
{ _id: false }
);
const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
{
accountOwnerId: Schema.Types.ObjectId,
@ -1481,23 +1462,11 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
flawlessRelicsAlwaysGiveSilverReward: Boolean,
radiantRelicsAlwaysGiveGoldReward: Boolean,
disableDailyTribute: Boolean,
nemesisHenchmenKillsMultiplierGrineer: Number,
nemesisHenchmenKillsMultiplierCorpus: Number,
nemesisAntivirusGainMultiplier: Number,
nemesisHintProgressMultiplierGrineer: Number,
nemesisHintProgressMultiplierCorpus: Number,
nemesisExtraWeapon: Number,
spoofMasteryRank: { type: Number, default: -1 },
relicRewardItemCountMultiplier: { type: Number, default: 1 },
nightwaveStandingMultiplier: { type: Number, default: 1 },
SubscribedToEmails: { type: Number, default: 0 },
SubscribedToEmailsPersonalized: { type: Number, default: 0 },
RewardSeed: BigInt,
// Temporary data so we can show all relic rewards from an endless mission at EOM
MissionRelicRewards: { type: [typeCountSchema], default: undefined },
//Credit
RegularCredits: { type: Number, default: 0 },
//Platinum
@ -1564,8 +1533,6 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
FocusAbility: String,
//The treeways of the Focus school.(Active and passive Ability)
FocusUpgrades: [focusUpgradeSchema],
//Focus 2.0 Pool
FocusCapacity: Number,
//Achievement
ChallengeProgress: [challengeProgressSchema],
@ -1767,7 +1734,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
NemesisAbandonedRewards: { type: [String], default: [] },
Nemesis: nemesisSchema,
NemesisHistory: { type: [nemesisSchema], default: undefined },
LastNemesisAllySpawnTime: { type: Date, default: undefined },
//LastNemesisAllySpawnTime: Schema.Types.Mixed,
//TradingRulesConfirmed,ShowFriendInvNotifications(Option->Social)
Settings: settingsSchema,
@ -1858,9 +1825,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
ClaimedJunctionChallengeRewards: { type: [String], default: undefined },
SpecialItemRewardAttenuation: { type: [rewardAttenutationSchema], default: undefined },
NokkoColony: { type: nokkoColonySchema, default: undefined }
SpecialItemRewardAttenuation: { type: [rewardAttenutationSchema], default: undefined }
},
{ timestamps: { createdAt: "Created", updatedAt: false } }
);
@ -1870,7 +1835,6 @@ inventorySchema.set("toJSON", {
delete returnedObject._id;
delete returnedObject.__v;
delete returnedObject.accountOwnerId;
delete returnedObject.MissionRelicRewards;
const inventoryDatabase = returnedObject as Partial<IInventoryDatabase>;
const inventoryResponse = returnedObject as IInventoryClient;
@ -1890,9 +1854,6 @@ inventorySchema.set("toJSON", {
if (inventoryDatabase.BlessingCooldown) {
inventoryResponse.BlessingCooldown = toMongoDate(inventoryDatabase.BlessingCooldown);
}
if (inventoryDatabase.LastNemesisAllySpawnTime) {
inventoryResponse.LastNemesisAllySpawnTime = toMongoDate(inventoryDatabase.LastNemesisAllySpawnTime);
}
if (inventoryDatabase.NextRefill) {
inventoryResponse.NextRefill = toMongoDate(inventoryDatabase.NextRefill);
}

View File

@ -50,11 +50,9 @@ import { dronesController } from "../controllers/api/dronesController.ts";
import { endlessXpController } from "../controllers/api/endlessXpController.ts";
import { entratiLabConquestModeController } from "../controllers/api/entratiLabConquestModeController.ts";
import { evolveWeaponController } from "../controllers/api/evolveWeaponController.ts";
import { feedPrinceController } from "../controllers/api/feedPrinceController.ts";
import { findSessionsController } from "../controllers/api/findSessionsController.ts";
import { fishmongerController } from "../controllers/api/fishmongerController.ts";
import { focusController } from "../controllers/api/focusController.ts";
import { forceRemoveItemController } from "../controllers/api/forceRemoveItemController.ts";
import { fusionTreasuresController } from "../controllers/api/fusionTreasuresController.ts";
import { gardeningController } from "../controllers/api/gardeningController.ts";
import { genericUpdateController } from "../controllers/api/genericUpdateController.ts";
@ -68,8 +66,8 @@ import { getGuildEventScoreController } from "../controllers/api/getGuildEventSc
import { getGuildLogController } from "../controllers/api/getGuildLogController.ts";
import { getIgnoredUsersController } from "../controllers/api/getIgnoredUsersController.ts";
import { getNewRewardSeedController } from "../controllers/api/getNewRewardSeedController.ts";
import { getPastWeeklyChallengesController } from "../controllers/api/getPastWeeklyChallengesController.ts";
import { getProfileViewingDataPostController } from "../controllers/dynamic/getProfileViewingDataController.ts";
import { getPastWeeklyChallengesController } from "../controllers/api/getPastWeeklyChallengesController.ts";
import { getShipController } from "../controllers/api/getShipController.ts";
import { getVendorInfoController } from "../controllers/api/getVendorInfoController.ts";
import { getVoidProjectionRewardsController } from "../controllers/api/getVoidProjectionRewardsController.ts";
@ -117,10 +115,8 @@ import { removeFromGuildController } from "../controllers/api/removeFromGuildCon
import { removeIgnoredUserController } from "../controllers/api/removeIgnoredUserController.ts";
import { renamePetController } from "../controllers/api/renamePetController.ts";
import { rerollRandomModController } from "../controllers/api/rerollRandomModController.ts";
import { researchMushroomController } from "../controllers/api/researchMushroomController.ts";
import { resetQuestProgressController } from "../controllers/api/resetQuestProgressController.ts";
import { retrievePetFromStasisController } from "../controllers/api/retrievePetFromStasisController.ts";
import { reverseQuestProgressController } from "../controllers/api/reverseQuestProgressController.ts";
import { saveDialogueController } from "../controllers/api/saveDialogueController.ts";
import { saveLoadoutController } from "../controllers/api/saveLoadoutController.ts";
import { saveSettingsController } from "../controllers/api/saveSettingsController.ts";
@ -274,11 +270,9 @@ apiRouter.post("/drones.php", dronesController);
apiRouter.post("/endlessXp.php", endlessXpController);
apiRouter.post("/entratiLabConquestMode.php", entratiLabConquestModeController);
apiRouter.post("/evolveWeapon.php", evolveWeaponController);
apiRouter.post("/feedPrince.php", feedPrinceController);
apiRouter.post("/findSessions.php", findSessionsController);
apiRouter.post("/fishmonger.php", fishmongerController);
apiRouter.post("/focus.php", focusController);
apiRouter.post("/forceRemoveItem.php", forceRemoveItemController);
apiRouter.post("/fusionTreasures.php", fusionTreasuresController);
apiRouter.post("/gardening.php", gardeningController);
apiRouter.post("/genericUpdate.php", genericUpdateController);
@ -322,9 +316,7 @@ apiRouter.post("/removeFromGuild.php", removeFromGuildController);
apiRouter.post("/removeIgnoredUser.php", removeIgnoredUserController);
apiRouter.post("/renamePet.php", renamePetController);
apiRouter.post("/rerollRandomMod.php", rerollRandomModController);
apiRouter.post("/researchMushroom.php", researchMushroomController);
apiRouter.post("/retrievePetFromStasis.php", retrievePetFromStasisController);
apiRouter.post("/reverseQuestProgress.php", reverseQuestProgressController);
apiRouter.post("/saveDialogue.php", saveDialogueController);
apiRouter.post("/saveLoadout.php", saveLoadoutController);
apiRouter.post("/saveSettings.php", saveSettingsController);

View File

@ -22,7 +22,6 @@ import { unlockAllScansController } from "../controllers/custom/unlockAllScansCo
import { unlockAllShipFeaturesController } from "../controllers/custom/unlockAllShipFeaturesController.ts";
import { unlockAllCapturaScenesController } from "../controllers/custom/unlockAllCapturaScenesController.ts";
import { removeCustomizationController } from "../controllers/custom/removeCustomizationController.ts";
import { removeIsNewController } from "../controllers/custom/removeIsNewController.ts";
import { abilityOverrideController } from "../controllers/custom/abilityOverrideController.ts";
import { createAccountController } from "../controllers/custom/createAccountController.ts";
@ -35,7 +34,7 @@ import {
fundTechProjectController,
removeTechProjectController
} from "../controllers/custom/techProjectController.ts";
import { addVaultTypeCountController } from "../controllers/custom/addVaultTypeCountController.ts";
import { addVaultDecoRecipeController } from "../controllers/custom/addVaultDecoRecipeController.ts";
import { addXpController } from "../controllers/custom/addXpController.ts";
import { importController } from "../controllers/custom/importController.ts";
import { manageQuestsController } from "../controllers/custom/manageQuestsController.ts";
@ -74,7 +73,6 @@ customRouter.get("/unlockAllScans", unlockAllScansController);
customRouter.get("/unlockAllShipFeatures", unlockAllShipFeaturesController);
customRouter.get("/unlockAllCapturaScenes", unlockAllCapturaScenesController);
customRouter.get("/removeCustomization", removeCustomizationController);
customRouter.get("/removeIsNew", removeIsNewController);
customRouter.post("/abilityOverride", abilityOverrideController);
customRouter.post("/createAccount", createAccountController);
@ -83,7 +81,7 @@ customRouter.post("/addCurrency", addCurrencyController);
customRouter.post("/addItems", addItemsController);
customRouter.post("/addTechProject", addTechProjectController);
customRouter.post("/removeTechProject", removeTechProjectController);
customRouter.post("/addVaultTypeCount", addVaultTypeCountController);
customRouter.post("/addVaultDecoRecipe", addVaultDecoRecipeController);
customRouter.post("/fundTechProject", fundTechProjectController);
customRouter.post("/completeTechProject", completeTechProjectsController);
customRouter.post("/addXp", addXpController);

View File

@ -15,7 +15,6 @@ export interface IConfig {
bindAddress?: string;
httpPort?: number;
httpsPort?: number;
ircExecutable?: string;
ircAddress?: string;
hubAddress?: string;
nrsAddress?: string;
@ -25,6 +24,9 @@ export interface IConfig {
unlockAllSkins?: boolean;
fullyStockedVendors?: boolean;
skipClanKeyCrafting?: boolean;
spoofMasteryRank?: number;
relicRewardItemCountMultiplier?: number;
nightwaveStandingMultiplier?: number;
unfaithfulBugFixes?: {
ignore1999LastRegionPlayed?: boolean;
fixXtraCheeseTimer?: boolean;
@ -39,21 +41,15 @@ export interface IConfig {
baroAlwaysAvailable?: boolean;
baroFullyStocked?: boolean;
varziaFullyStocked?: boolean;
wolfHunt?: number;
wolfHunt?: boolean;
orphixVenom?: boolean;
longShadow?: boolean;
hallowedFlame?: boolean;
anniversary?: number;
hallowedNightmares?: boolean;
hallowedNightmaresRewardsOverride?: number;
naberusNightsOverride?: boolean;
proxyRebellion?: boolean;
proxyRebellionRewardsOverride?: number;
voidCorruption2025Week1?: boolean;
voidCorruption2025Week2?: boolean;
voidCorruption2025Week3?: boolean;
voidCorruption2025Week4?: boolean;
qtccAlerts?: boolean;
galleonOfGhouls?: number;
ghoulEmergenceOverride?: boolean;
plagueStarOverride?: boolean;
@ -81,21 +77,6 @@ export interface IConfig {
}
export const configRemovedOptionsKeys = [
"unlockallShipFeatures",
"testQuestKey",
"lockTime",
"starDays",
"platformCDNs",
"completeAllQuests",
"worldSeed",
"unlockAllQuests",
"unlockAllMissions",
"version",
"matchmakingBuildId",
"buildLabel",
"infiniteResources",
"testMission",
"skipStoryModeChoice",
"NRS",
"myIrcAddresses",
"skipAllDialogue",
@ -147,10 +128,7 @@ export const configRemovedOptionsKeys = [
"fastClanAscension",
"unlockAllFlavourItems",
"unlockAllShipDecorations",
"unlockAllDecoRecipes",
"spoofMasteryRank",
"relicRewardItemCountMultiplier",
"nightwaveStandingMultiplier"
"unlockAllDecoRecipes"
];
export const configPath = path.join(repoDir, args.configPath ?? "config.json");

View File

@ -1,425 +0,0 @@
import type { TFaction, TMissionType } from "warframe-public-export-plus";
import type { CalendarSeasonType, IConquest, IConquestMission, TConquestType } from "../types/worldStateTypes.ts";
import { mixSeeds, SRng } from "./rngService.ts";
import { EPOCH } from "../constants/timeConstants.ts";
const missionAndFactionTypes: Record<TConquestType, Partial<Record<TMissionType, TFaction[]>>> = {
CT_LAB: {
MT_EXTERMINATION: ["FC_MITW"],
MT_SURVIVAL: ["FC_MITW"],
MT_ALCHEMY: ["FC_MITW"],
MT_DEFENSE: ["FC_MITW"],
MT_ARTIFACT: ["FC_MITW"]
},
CT_HEX: {
MT_EXTERMINATION: ["FC_SCALDRA", "FC_TECHROT"],
MT_SURVIVAL: ["FC_SCALDRA", "FC_TECHROT"],
MT_DEFENSE: ["FC_SCALDRA"],
MT_ENDLESS_CAPTURE: ["FC_TECHROT"]
}
};
const assassinationFactionOptions: Record<TConquestType, TFaction[]> = {
CT_LAB: ["FC_MITW"],
CT_HEX: ["FC_SCALDRA"]
};
type TConquestDifficulty = "CD_NORMAL" | "CD_HARD";
interface IConquestConditional {
tag: string;
missionType?: TMissionType;
conquest?: TConquestType;
difficulty?: TConquestDifficulty;
season?: CalendarSeasonType;
}
const deviations: readonly IConquestConditional[] = [
{
tag: "AlchemicalShields",
missionType: "MT_ALCHEMY"
},
{
tag: "ContaminationZone",
missionType: "MT_SURVIVAL",
conquest: "CT_HEX"
},
{
tag: "DoubleTrouble",
missionType: "MT_ARTIFACT"
},
{
tag: "EscalateImmediately",
missionType: "MT_EXTERMINATION",
conquest: "CT_HEX"
},
{
tag: "EximusGrenadiers",
missionType: "MT_ALCHEMY"
},
{
tag: "FortifiedFoes",
missionType: "MT_EXTERMINATION"
},
{
tag: "FragileNodes",
missionType: "MT_ARTIFACT"
},
{
tag: "GrowingIncursion",
missionType: "MT_EXTERMINATION",
conquest: "CT_LAB"
},
{
tag: "HarshWords",
missionType: "MT_DEFENSE",
conquest: "CT_LAB"
},
{
tag: "HighScalingLegacyte",
missionType: "MT_ENDLESS_CAPTURE",
conquest: "CT_HEX"
},
{
tag: "DoubleTroubleLegacyte",
missionType: "MT_ENDLESS_CAPTURE",
conquest: "CT_HEX"
},
{
tag: "HostileSecurity",
missionType: "MT_DEFENSE",
conquest: "CT_LAB"
},
{
tag: "InfiniteTide",
missionType: "MT_ASSASSINATION",
conquest: "CT_LAB"
},
{
tag: "LostInTranslation",
missionType: "MT_DEFENSE",
conquest: "CT_LAB"
},
{
tag: "MutatedEnemies",
missionType: "MT_ENDLESS_CAPTURE",
conquest: "CT_HEX"
},
{
tag: "NecramechActivation",
missionType: "MT_SURVIVAL",
conquest: "CT_LAB"
},
{
tag: "Reinforcements",
missionType: "MT_ASSASSINATION",
conquest: "CT_LAB"
},
{
tag: "StickyFingers",
missionType: "MT_ARTIFACT",
conquest: "CT_LAB"
},
{
tag: "TankStrongArmor",
missionType: "MT_ASSASSINATION",
conquest: "CT_HEX"
},
{
tag: "TankReinforcements",
missionType: "MT_ASSASSINATION",
conquest: "CT_HEX"
},
{
tag: "TankSuperToxic",
missionType: "MT_ASSASSINATION",
conquest: "CT_HEX"
},
{
tag: "TechrotConjunction",
missionType: "MT_SURVIVAL",
conquest: "CT_HEX"
},
{
tag: "UnpoweredCapsules",
missionType: "MT_SURVIVAL",
conquest: "CT_LAB"
},
{
tag: "VolatileGrenades",
missionType: "MT_ALCHEMY"
},
{
tag: "GestatingTumors",
missionType: "MT_SURVIVAL",
conquest: "CT_HEX"
},
{
tag: "ChemicalNoise",
missionType: "MT_DEFENSE",
conquest: "CT_HEX"
},
{
tag: "ExplosiveEnergy",
missionType: "MT_DEFENSE",
conquest: "CT_HEX"
},
{
tag: "DisruptiveSounds",
missionType: "MT_DEFENSE",
conquest: "CT_HEX"
}
];
const risks: readonly IConquestConditional[] = [
{
tag: "Voidburst"
},
{
tag: "RegeneratingEnemies"
},
{
tag: "VoidAberration"
},
{
tag: "ShieldedFoes"
},
{
tag: "PointBlank"
},
{
tag: "Deflectors",
conquest: "CT_LAB"
},
{
tag: "AcceleratedEnemies"
},
{
tag: "DrainingResiduals"
},
{
tag: "Quicksand"
},
{
tag: "AntiMaterialWeapons",
conquest: "CT_LAB"
},
{
tag: "ExplosiveCrawlers",
conquest: "CT_LAB"
},
{
tag: "EMPBlackHole",
conquest: "CT_LAB"
},
{
tag: "ArtilleryBeacons",
conquest: "CT_HEX"
},
{
tag: "InfectedTechrot",
conquest: "CT_HEX"
},
{
tag: "BalloonFest",
conquest: "CT_HEX"
},
{
tag: "MiasmiteHive",
conquest: "CT_HEX"
},
{
tag: "CompetitionSpillover",
conquest: "CT_HEX"
},
{
tag: "HostileOvergrowth",
conquest: "CT_HEX"
},
{
tag: "MurmurIncursion",
conquest: "CT_HEX"
},
{
tag: "FactionSwarm_Techrot",
conquest: "CT_HEX",
difficulty: "CD_NORMAL"
},
{
tag: "FactionSwarm_Scaldra",
conquest: "CT_HEX",
difficulty: "CD_NORMAL"
},
{
tag: "HeavyWarfare",
conquest: "CT_HEX"
},
{
tag: "ArcadeAutomata",
conquest: "CT_HEX",
difficulty: "CD_NORMAL"
},
{
tag: "EfervonFog",
conquest: "CT_HEX"
},
{
tag: "WinterFrost",
conquest: "CT_HEX",
season: "CST_WINTER"
},
{
tag: "JadeSpring",
conquest: "CT_HEX",
season: "CST_SPRING"
},
{
tag: "ExplosiveSummer",
conquest: "CT_HEX",
season: "CST_SUMMER"
},
{
tag: "FallFog",
conquest: "CT_HEX",
season: "CST_FALL"
}
];
const filterConditionals = (
arr: readonly IConquestConditional[],
missionType: TMissionType | null,
conquest: TConquestType | null,
difficulty: TConquestDifficulty | null,
season: CalendarSeasonType | null
): string[] => {
const applicable = [];
for (const cond of arr) {
if (
(!cond.missionType || cond.missionType == missionType) &&
(!cond.conquest || cond.conquest == conquest) &&
(!cond.difficulty || cond.difficulty == difficulty) &&
(!cond.season || cond.season == season)
) {
applicable.push(cond.tag);
}
}
return applicable;
};
const buildMission = (
rng: SRng,
conquest: TConquestType,
missionType: TMissionType,
faction: TFaction,
season: CalendarSeasonType | null
): IConquestMission => {
const deviation = rng.randomElement(filterConditionals(deviations, missionType, conquest, null, season))!;
const easyRisk = rng.randomElement(filterConditionals(risks, missionType, conquest, "CD_NORMAL", season))!;
const hardRiskOptions = filterConditionals(risks, missionType, conquest, "CD_HARD", season);
{
const i = hardRiskOptions.indexOf(easyRisk);
if (i != -1) {
hardRiskOptions.splice(i, 1);
}
}
const hardRisk = rng.randomElement(hardRiskOptions)!;
return {
faction,
missionType,
difficulties: [
{
type: "CD_NORMAL",
deviation,
risks: [easyRisk]
},
{
type: "CD_HARD",
deviation,
risks: [easyRisk, hardRisk]
}
]
};
};
const conquestStartingDay: Record<TConquestType, number> = {
CT_LAB: 3703,
CT_HEX: 4053
};
// This function produces identical results to clients pre-40.0.0.
const getFrameVariables = (conquestType: TConquestType, time: number): [string, string, string, string] => {
const day = Math.floor((time - 1391990400_000) / 86400_000) - conquestStartingDay[conquestType];
const week = Math.floor(day / 7) + 1;
const frameVariables = [
"Framecurse",
"Knifestep",
"Exhaustion",
"Gearless",
"TimeDilation",
"Armorless",
"Starvation",
"ShieldDelay",
"Withering",
"ContactDamage",
"AbilityLockout",
"OperatorLockout",
"EnergyStarved",
"OverSensitive",
"AntiGuard",
"DecayingFlesh",
"VoidEnergyOverload",
"DullBlades",
"Undersupplied"
];
const mag = Math.floor(frameVariables.length / 4);
const rng = new SRng(conquestStartingDay[conquestType] + Math.floor(week / mag));
rng.shuffleArray(frameVariables);
const i = week % mag;
return [frameVariables[i], frameVariables[i + 1], frameVariables[i + 2], frameVariables[i + 3]];
};
export const getConquest = (
conquestType: TConquestType,
week: number,
season: CalendarSeasonType | null
): IConquest => {
const rng = new SRng(mixSeeds(conquestStartingDay[conquestType], week));
const missions: IConquestMission[] = [];
{
const missionOptions = Object.entries(missionAndFactionTypes[conquestType]);
{
const i = rng.randomInt(0, missionOptions.length - 1);
const [missionType, factionOptions] = missionOptions.splice(i, 1)[0];
missions.push(
buildMission(rng, conquestType, missionType as TMissionType, rng.randomElement(factionOptions)!, season)
);
}
{
const i = rng.randomInt(0, missionOptions.length - 1);
const [missionType, factionOptions] = missionOptions.splice(i, 1)[0];
missions.push(
buildMission(rng, conquestType, missionType as TMissionType, rng.randomElement(factionOptions)!, season)
);
}
missionOptions.push(["MT_ASSASSINATION", assassinationFactionOptions[conquestType]]);
{
const i = rng.randomInt(0, missionOptions.length - 1);
const [missionType, factionOptions] = missionOptions.splice(i, 1)[0];
missions.push(
buildMission(rng, conquestType, missionType as TMissionType, rng.randomElement(factionOptions)!, season)
);
}
}
const weekStart = EPOCH + week * 604800000;
const weekEnd = weekStart + 604800000;
return {
Activation: { $date: { $numberLong: weekStart.toString() } },
Expiry: { $date: { $numberLong: weekEnd.toString() } },
Type: conquestType,
Missions: missions,
Variables: getFrameVariables(conquestType, weekStart),
RandomSeed: rng.randomInt(0, 1_000_000)
};
};

View File

@ -1,5 +1,6 @@
import type { IFriendInfo } from "../types/friendTypes.ts";
import { getInventory } from "./inventoryService.ts";
import { config } from "./configService.ts";
import { Account } from "../models/loginModel.ts";
import type { Types } from "mongoose";
import { Friendship } from "../models/friendModel.ts";
@ -12,8 +13,8 @@ export const addAccountDataToFriendInfo = async (info: IFriendInfo): Promise<voi
};
export const addInventoryDataToFriendInfo = async (info: IFriendInfo): Promise<void> => {
const inventory = await getInventory(fromOid(info._id), "PlayerLevel ActiveAvatarImageType spoofMasteryRank");
info.PlayerLevel = inventory.spoofMasteryRank == -1 ? inventory.PlayerLevel : inventory.spoofMasteryRank;
const inventory = await getInventory(fromOid(info._id), "PlayerLevel ActiveAvatarImageType");
info.PlayerLevel = config.spoofMasteryRank == -1 ? inventory.PlayerLevel : config.spoofMasteryRank;
info.ActiveAvatarImageType = inventory.ActiveAvatarImageType;
};

View File

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

View File

@ -6,7 +6,6 @@ import type {
} from "../types/inventoryTypes/commonInventoryTypes.ts";
import type { IMongoDate } from "../types/commonTypes.ts";
import type {
IBooster,
IDialogueClient,
IDialogueDatabase,
IDialogueHistoryClient,
@ -73,7 +72,6 @@ import type {
ITailorShop,
ITailorShopDatabase
} from "../types/personalRoomsTypes.ts";
import { fromMongoDate } from "../helpers/inventoryHelpers.ts";
const convertDate = (value: IMongoDate): Date => {
return new Date(parseInt(value.$date.$numberLong));
@ -327,15 +325,7 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
"GiftsRemaining",
"ChallengesFixVersion",
"Founder",
"Guide",
"BountyScore",
"EntratiVaultCountLastPeriod",
"EntratiLabConquestUnlocked",
"EntratiLabConquestHardModeStatus",
"EntratiLabConquestCacheScoreMission",
"EchoesHexConquestUnlocked",
"EchoesHexConquestHardModeStatus",
"EchoesHexConquestCacheScoreMission"
"Guide"
] as const) {
if (client[key] !== undefined) {
db[key] = client[key];
@ -363,28 +353,12 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
"NodeIntrosCompleted",
"DeathMarks",
"Wishlist",
"NemesisAbandonedRewards",
"EntratiLabConquestActiveFrameVariants",
"EchoesHexConquestActiveFrameVariants",
"EchoesHexConquestActiveStickers"
"NemesisAbandonedRewards"
] as const) {
if (client[key] !== undefined) {
db[key] = client[key];
}
}
// IMongoDate
for (const key of [
"Created",
"TrainingDate",
"BlessingCooldown",
"LastNemesisAllySpawnTime",
"NextRefill",
"EntratiVaultCountResetDate"
] as const) {
if (client[key] !== undefined) {
db[key] = fromMongoDate(client[key]);
}
}
// IRewardAtten[]
for (const key of ["SortieRewardAttenuation", "SpecialItemRewardAttenuation"] as const) {
if (client[key] !== undefined) {
@ -489,9 +463,6 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
if (client.Accolades !== undefined) {
db.Accolades = client.Accolades;
}
if (client.Boosters !== undefined) {
replaceArray<IBooster>(db.Boosters, client.Boosters);
}
};
export const importLoadOutConfig = (client: ILoadoutConfigClient): ILoadoutConfigDatabase => {

View File

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

View File

@ -92,7 +92,6 @@ import type {
} from "../types/equipmentTypes.ts";
import { EquipmentFeatures, Status } from "../types/equipmentTypes.ts";
import type { ITypeCount } from "../types/commonTypes.ts";
import { skinLookupTable } from "../helpers/skinLookupTable.ts";
export const createInventory = async (
accountOwnerId: Types.ObjectId,
@ -117,7 +116,6 @@ export const createInventory = async (
inventory.PlayedParkourTutorial = true;
await addStartingGear(inventory);
await completeQuest(inventory, "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain");
await completeQuest(inventory, "/Lotus/Types/Keys/ModQuest/ModQuestKeyChain");
const completedMissions = ["SolNode27", "SolNode89", "SolNode63", "SolNode85", "SolNode15", "SolNode79"];
@ -135,6 +133,16 @@ export const createInventory = async (
}
};
//TODO: RawUpgrades might need to return a LastAdded
const awakeningRewards = [
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem1",
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem2",
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem3",
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem4",
"/Lotus/Types/Restoratives/LisetAutoHack",
"/Lotus/Upgrades/Mods/Warframe/AvatarShieldMaxMod"
];
export const addStartingGear = async (
inventory: TInventoryDatabaseDocument,
startingGear?: TPartialStartingGear
@ -187,14 +195,6 @@ export const addStartingGear = async (
inventory.RegularCredits = 3000;
inventoryChanges.RegularCredits = 3000;
const awakeningRewards = [
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem1",
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem2",
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem3",
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem4",
"/Lotus/Types/Restoratives/LisetAutoHack"
];
for (const item of awakeningRewards) {
const inventoryDelta = await addItem(inventory, item);
combineInventoryChanges(inventoryChanges, inventoryDelta);
@ -388,7 +388,7 @@ export const addItem = async (
};
} else if (ExportResources[typeName].productCategory == "CrewShips") {
return {
...(await addCrewShip(inventory, typeName)),
...addCrewShip(inventory, typeName),
// fix to unlock railjack modding, item bellow supposed to be obtained from archwing quest
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
...(!inventory.CrewShipHarnesses?.length
@ -1391,10 +1391,7 @@ export const addStanding = (
// TODO: AffiliationMods support (Nightwave).
export const updateGeneric = async (data: IGenericUpdate, accountId: string): Promise<IUpdateNodeIntrosResponse> => {
const inventory = await getInventory(
accountId,
"NodeIntrosCompleted MiscItems ShipDecorations FlavourItems WeaponSkins"
);
const inventory = await getInventory(accountId, "NodeIntrosCompleted MiscItems ShipDecorations");
// Make it an array for easier parsing.
if (typeof data.NodeIntrosCompleted === "string") {
@ -1403,12 +1400,6 @@ export const updateGeneric = async (data: IGenericUpdate, accountId: string): Pr
const inventoryChanges: IInventoryChanges = {};
for (const node of data.NodeIntrosCompleted) {
if (inventory.NodeIntrosCompleted.indexOf(node) != -1) {
continue;
}
inventory.NodeIntrosCompleted.push(node);
logger.debug(`completed dialogue/cutscene for ${node}`);
if (node == "TC2025") {
inventoryChanges.ShipDecorations = [
{
@ -1429,15 +1420,16 @@ export const updateGeneric = async (data: IGenericUpdate, accountId: string): Pr
await addEmailItem(inventory, "/Lotus/Types/Items/EmailItems/BeatCaliberChicksEmailItem", inventoryChanges);
} else if (node == "ClearedFiveLoops") {
await addEmailItem(inventory, "/Lotus/Types/Items/EmailItems/ClearedFiveLoopsEmailItem", inventoryChanges);
} else if (node == "NokkoVisions_AllCompleted") {
addCustomization(
inventory,
"/Lotus/Types/StoreItems/AvatarImages/Warframes/NokkoBabySecretGlyph",
inventoryChanges
);
addSkin(inventory, "/Lotus/Upgrades/Skins/Clan/NokkoBabySecretEmblemItem", inventoryChanges);
}
}
// Combine the two arrays into one.
data.NodeIntrosCompleted = inventory.NodeIntrosCompleted.concat(data.NodeIntrosCompleted);
// Remove duplicate entries.
const nodes = [...new Set(data.NodeIntrosCompleted)];
inventory.NodeIntrosCompleted = nodes;
await inventory.save();
return {
@ -1497,13 +1489,7 @@ export const addSkin = (
inventoryChanges: IInventoryChanges = {}
): IInventoryChanges => {
if (inventory.WeaponSkins.some(x => x.ItemType == typeName)) {
if (typeName == "/Lotus/Upgrades/Skins/Clan/BountyHunterBadgeItem") {
logger.debug(`account already owns stratos emblem, increasing bounty score instead`);
inventory.BountyScore ??= 0;
inventory.BountyScore += 1;
} else {
logger.debug(`refusing to add WeaponSkin ${typeName} because account already owns it`);
}
logger.debug(`refusing to add WeaponSkin ${typeName} because account already owns it`);
} else {
const index =
inventory.WeaponSkins.push({
@ -1551,31 +1537,17 @@ export const addCrewShipSalvagedWeaponSkin = (
return inventoryChanges;
};
const addCrewShip = async (
const addCrewShip = (
inventory: TInventoryDatabaseDocument,
typeName: string,
inventoryChanges: IInventoryChanges = {}
): Promise<IInventoryChanges> => {
): IInventoryChanges => {
if (inventory.CrewShips.length != 0) {
logger.warn("refusing to add CrewShip because account already has one");
} else {
const index = inventory.CrewShips.push({ ItemType: typeName }) - 1;
inventoryChanges.CrewShips ??= [];
inventoryChanges.CrewShips.push(inventory.CrewShips[index].toJSON<IEquipmentClient>());
const railjackQuest = inventory.QuestKeys.find(
qk => qk.ItemType === "/Lotus/Types/Keys/RailJackBuildQuest/RailjackBuildQuestKeyChain"
);
if (!railjackQuest || !railjackQuest.Completed) {
const questChanges = await completeQuest(
inventory,
"/Lotus/Types/Keys/RailJackBuildQuest/RailjackBuildQuestKeyChain",
false
);
if (questChanges) {
inventoryChanges.QuestKeys ??= [];
inventoryChanges.QuestKeys.push(questChanges);
}
}
}
return inventoryChanges;
};
@ -2152,7 +2124,7 @@ export const addChallenges = async (
];
}
const standingToAdd = meta.standing! * (inventory.nightwaveStandingMultiplier ?? 1);
const standingToAdd = meta.standing! * (config.nightwaveStandingMultiplier ?? 1);
affiliation.Standing += standingToAdd;
if (affiliationMods.length == 0) {
affiliationMods.push({ Tag: nightwaveSyndicateTag });
@ -2180,7 +2152,7 @@ export const addMissionComplete = (inventory: TInventoryDatabaseDocument, { Tag,
if (itemIndex !== -1) {
Missions[itemIndex].Completes += Completes;
if (Completes && Tier) {
if (Tier) {
Missions[itemIndex].Tier = Tier;
}
} else {
@ -2282,7 +2254,7 @@ export const setupKahlSyndicate = (inventory: TInventoryDatabaseDocument): void
});
};
export const cleanupInventory = async (inventory: TInventoryDatabaseDocument): Promise<void> => {
export const cleanupInventory = (inventory: TInventoryDatabaseDocument): void => {
inventory.CurrentLoadOutIds = inventory.CurrentLoadOutIds.map(fromDbOid);
let index = inventory.MiscItems.findIndex(x => x.ItemType == "");
@ -2336,99 +2308,6 @@ export const cleanupInventory = async (inventory: TInventoryDatabaseDocument): P
logger.debug(`removed ModularParts from ${numFixed} non-modular items`);
}
}
{
const weaponMap = new Map<string, string>();
for (const skin of inventory.WeaponSkins) {
weaponMap.set(skin.ItemType, skin._id.toString());
}
const itemsToAdd = new Set<string>();
for (const key of equipmentKeys) {
if (key in inventory) {
for (const equipment of inventory[key]) {
for (const config of equipment.Configs) {
if (config.Skins) collectSkins(config.Skins, weaponMap, itemsToAdd);
}
}
}
}
for (const key of ["AdultOperatorLoadOuts", "OperatorLoadOuts", "KahlLoadOuts"] as const) {
if (key in inventory) {
for (const loadOut of inventory[key]) {
if (loadOut.Skins) collectSkins(loadOut.Skins, weaponMap, itemsToAdd);
}
}
}
if (inventory.LotusCustomization?.Skins)
collectSkins(inventory.LotusCustomization.Skins, weaponMap, itemsToAdd);
if (itemsToAdd.size > 0) {
logger.debug(`Adding ${itemsToAdd.size} items due to migration from unlockAllSkins cheat`);
const inventoryChanges = await addItems(inventory, Array.from(itemsToAdd));
if (inventoryChanges.WeaponSkins) {
for (const skin of inventoryChanges.WeaponSkins as IWeaponSkinClient[]) {
weaponMap.set(skin.ItemType, skin.ItemId.toString());
}
}
for (const key of equipmentKeys) {
if (key in inventory) {
for (const equipment of inventory[key]) {
for (const config of equipment.Configs) {
if (config.Skins) replaceSkinIds(config.Skins, weaponMap);
}
}
}
}
for (const key of ["AdultOperatorLoadOuts", "OperatorLoadOuts", "KahlLoadOuts"] as const) {
if (key in inventory) {
for (const loadOut of inventory[key]) {
if (loadOut.Skins) replaceSkinIds(loadOut.Skins, weaponMap);
}
}
}
if (inventory.LotusCustomization?.Skins) replaceSkinIds(inventory.LotusCustomization.Skins, weaponMap);
}
}
};
const collectSkins = (skins: string[], weaponMap: Map<string, string>, itemsToAdd: Set<string>): void => {
for (const skinId of skins) {
if (skinId.startsWith("ca70ca70ca70ca70")) {
const typeName = skinLookupTable[parseInt(skinId.slice(16), 16)];
if (!weaponMap.has(typeName)) {
const { requirement } = ExportCustoms[typeName];
if (typeof requirement == "string") {
itemsToAdd.add(requirement);
} else {
itemsToAdd.add(typeName);
}
}
}
}
};
const replaceSkinIds = (skins: string[], weaponMap: Map<string, string>): void => {
for (let i = 0; i < skins.length; i++) {
const skinId = skins[i];
if (skinId.startsWith("ca70ca70ca70ca70")) {
const typeName = skinLookupTable[parseInt(skinId.slice(16), 16)];
const inventoryId = weaponMap.get(typeName);
if (inventoryId) {
skins[i] = inventoryId;
} else if (typeName in ExportCustoms) {
const { requirement } = ExportCustoms[typeName];
skins[i] = typeof requirement == "string" ? typeName : "";
}
}
}
};
export const getDialogue = (inventory: TInventoryDatabaseDocument, dialogueName: string): IDialogueDatabase => {

View File

@ -81,8 +81,7 @@ export const getRecipe = (uniqueName: string): IRecipe | undefined => {
ItemCount: 600
}
],
excludeFromMarket: true,
tradable: false
excludeFromMarket: true
};
}

View File

@ -41,8 +41,8 @@ export const createAccount = async (accountData: IDatabaseAccountRequiredFields)
await account.save();
const loadoutId = await createLoadout(account._id);
const shipId = await createShip(account._id);
await createPersonalRooms(account._id, shipId);
await createInventory(account._id, { loadOutPresetId: loadoutId, ship: shipId });
await createPersonalRooms(account._id, shipId);
await createStats(account._id.toString());
return account.toJSON();
} catch (error) {
@ -64,6 +64,17 @@ export const createPersonalRooms = async (accountId: Types.ObjectId, shipId: Typ
personalRoomsOwnerId: accountId,
activeShipId: shipId
});
if (config.skipTutorial) {
// unlocked during Vor's Prize
const defaultFeatures = [
"/Lotus/Types/Items/ShipFeatureItems/MercuryNavigationFeatureItem",
"/Lotus/Types/Items/ShipFeatureItems/ArsenalFeatureItem",
"/Lotus/Types/Items/ShipFeatureItems/SocialMenuFeatureItem",
"/Lotus/Types/Items/ShipFeatureItems/FoundryFeatureItem",
"/Lotus/Types/Items/ShipFeatureItems/ModsFeatureItem"
];
personalRooms.Ship.Features.push(...defaultFeatures);
}
await personalRooms.save();
};

View File

@ -8,7 +8,6 @@ import {
ExportAnimals,
ExportEnemies,
ExportFusionBundles,
ExportKeys,
ExportRegions,
ExportRelics,
ExportRewards
@ -211,29 +210,10 @@ export const addMissionInventoryUpdates = async (
inventory.NemesisAbandonedRewards = inventoryUpdates.RewardInfo.NemesisAbandonedRewards;
}
if (inventoryUpdates.RewardInfo.NemesisHenchmenKills && inventory.Nemesis) {
let HenchmenKilledMultiplier = 1;
switch (inventory.Nemesis.Faction) {
case "FC_GRINEER":
HenchmenKilledMultiplier = inventory.nemesisHenchmenKillsMultiplierGrineer ?? 1;
break;
case "FC_CORPUS":
HenchmenKilledMultiplier = inventory.nemesisHenchmenKillsMultiplierCorpus ?? 1;
break;
}
inventory.Nemesis.HenchmenKilled +=
inventoryUpdates.RewardInfo.NemesisHenchmenKills * HenchmenKilledMultiplier;
inventory.Nemesis.HenchmenKilled += inventoryUpdates.RewardInfo.NemesisHenchmenKills;
}
if (inventoryUpdates.RewardInfo.NemesisHintProgress && inventory.Nemesis) {
let HintProgressMultiplier = 1;
switch (inventory.Nemesis.Faction) {
case "FC_GRINEER":
HintProgressMultiplier = inventory.nemesisHintProgressMultiplierGrineer ?? 1;
break;
case "FC_CORPUS":
HintProgressMultiplier = inventory.nemesisHintProgressMultiplierCorpus ?? 1;
break;
}
inventory.Nemesis.HintProgress += inventoryUpdates.RewardInfo.NemesisHintProgress * HintProgressMultiplier;
inventory.Nemesis.HintProgress += inventoryUpdates.RewardInfo.NemesisHintProgress;
if (inventory.Nemesis.Faction != "FC_INFESTATION" && inventory.Nemesis.Hints.length != 3) {
const progressNeeded = [35, 60, 100][inventory.Nemesis.Hints.length];
if (inventory.Nemesis.HintProgress >= progressNeeded) {
@ -243,7 +223,7 @@ export const addMissionInventoryUpdates = async (
}
}
}
if (inventoryUpdates.RewardInfo.jobId) {
if (inventoryUpdates.MissionStatus == "GS_SUCCESS" && inventoryUpdates.RewardInfo.jobId) {
// e.g. for Profit-Taker Phase 1:
// JobTier: -6,
// jobId: '/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyOne_-6_SolarisUnitedHub1_663a71c80000000000000025_EudicoHeists',
@ -251,10 +231,7 @@ export const addMissionInventoryUpdates = async (
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [bounty, tier, hub, id, tag] = inventoryUpdates.RewardInfo.jobId.split("_");
if (
(tag == "EudicoHeists" && inventoryUpdates.MissionStatus == "GS_SUCCESS") ||
(tag == "NokkoColony" && inventoryUpdates.RewardInfo.JobStage == 4)
) {
if (tag == "EudicoHeists") {
inventory.CompletedJobChains ??= [];
let chain = inventory.CompletedJobChains.find(x => x.LocationTag == tag);
if (!chain) {
@ -842,8 +819,6 @@ export const addMissionInventoryUpdates = async (
const att: string[] = [];
let countedAtt: ITypeCount[] | undefined;
const extraWeaponCheat = inventory.nemesisExtraWeapon ?? 0; // 0 means no extra weapon and token
if (value.killed) {
if (
value.weaponLoc &&
@ -852,20 +827,6 @@ export const addMissionInventoryUpdates = async (
const weaponType = manifest.weapons[inventory.Nemesis.WeaponIdx];
giveNemesisWeaponRecipe(inventory, weaponType, value.nemesisName, value.weaponLoc, profile);
att.push(weaponType);
if (extraWeaponCheat >= 1) {
for (let i = 0; i < extraWeaponCheat; i++) {
const randomIndex = Math.floor(Math.random() * manifest.weapons.length);
const randomWeapon = manifest.weapons[randomIndex];
giveNemesisWeaponRecipe(
inventory,
randomWeapon,
value.nemesisName,
undefined,
profile
);
att.push(randomWeapon);
}
}
}
//if (value.petLoc) {
if (profile.petHead) {
@ -909,7 +870,7 @@ export const addMissionInventoryUpdates = async (
countedAtt = [
{
ItemType: "/Lotus/Types/Items/MiscItems/CodaWeaponBucks",
ItemCount: getKillTokenRewardCount(inventory.Nemesis.fp) * (extraWeaponCheat + 1)
ItemCount: getKillTokenRewardCount(inventory.Nemesis.fp)
}
];
addMiscItems(inventory, countedAtt);
@ -1132,8 +1093,7 @@ export const addMissionRewards = async (
RegularCredits: creditDrops,
VoidTearParticipantsCurrWave: voidTearWave,
StrippedItems: strippedItems,
AffiliationChanges: AffiliationMods,
InvasionProgress: invasionProgress
AffiliationChanges: AffiliationMods
}: IMissionInventoryUpdateRequest,
firstCompletion: boolean
): Promise<AddMissionRewardsReturnType> => {
@ -1149,7 +1109,6 @@ export const addMissionRewards = async (
const MissionRewards: IMissionReward[] = getRandomMissionDrops(
inventory,
rewardInfo,
levelKeyName,
missions,
wagerTier,
firstCompletion
@ -1160,42 +1119,8 @@ export const addMissionRewards = async (
let ConquestCompletedMissionsCount;
let missionCompletionCredits = 0;
if (rewardInfo.alertId) {
const alert = getWorldState().Alerts.find(x => x._id.$oid == rewardInfo.alertId);
if (!alert) {
logger.warn(`mission completed unknown alert`, { alertId: rewardInfo.alertId });
} else {
if (inventory.CompletedAlerts.includes(alert._id.$oid)) {
logger.debug(`alert ${alert._id.$oid} already completed, skipping alert reward`);
} else {
inventory.CompletedAlerts.push(alert._id.$oid);
if (alert.MissionInfo.missionReward) {
missionCompletionCredits += addFixedLevelRewards(
alert.MissionInfo.missionReward,
MissionRewards,
rewardInfo
);
}
}
}
}
//inventory change is what the client has not rewarded itself, also the client needs to know the credit changes for display
if (invasionProgress) {
for (const clientProgress of invasionProgress) {
const dbProgress = inventory.QualifyingInvasions.find(x => x.invasionId.equals(clientProgress._id.$oid));
if (dbProgress) {
const run =
(clientProgress.AttackerScore > clientProgress.DefenderScore
? dbProgress.AttackerScore
: dbProgress.DefenderScore) - 1;
missionCompletionCredits += 1000 * Math.min(run, 10);
}
}
}
if (rewardInfo.goalId) {
const goal = getWorldState().Goals.find(x => x._id.$oid == rewardInfo.goalId);
if (goal) {
@ -1374,21 +1299,13 @@ export const addMissionRewards = async (
rngRewardCredits: inventoryChanges.RegularCredits ?? 0
});
if (voidTearWave && voidTearWave.Participants[0].QualifiesForReward) {
if (!voidTearWave.Participants[0].HaveRewardResponse) {
// non-endless fissure; giving reward now
const reward = await crackRelic(inventory, voidTearWave.Participants[0], inventoryChanges);
MissionRewards.push({ StoreItem: reward.type, ItemCount: reward.itemCount });
} else if (inventory.MissionRelicRewards) {
// endless fissure; already gave reward(s) but should still show in EOM screen
for (const reward of inventory.MissionRelicRewards) {
MissionRewards.push({
StoreItem: reward.ItemType,
ItemCount: reward.ItemCount
});
}
inventory.MissionRelicRewards = undefined;
}
if (
voidTearWave &&
voidTearWave.Participants[0].QualifiesForReward &&
!voidTearWave.Participants[0].HaveRewardResponse
) {
const reward = await crackRelic(inventory, voidTearWave.Participants[0], inventoryChanges);
MissionRewards.push({ StoreItem: reward.type, ItemCount: reward.itemCount });
}
if (strippedItems) {
@ -1483,9 +1400,7 @@ export const addMissionRewards = async (
if (inventory.Nemesis.Faction == "FC_INFESTATION") {
inventory.Nemesis.MissionCount += 1;
let antivirusGain = 5;
antivirusGain *= inventory.nemesisAntivirusGainMultiplier ?? 1;
inventory.Nemesis.HenchmenKilled = Math.min(inventory.Nemesis.HenchmenKilled + antivirusGain, 95); // 5 progress per mission until 95
inventory.Nemesis.HenchmenKilled = Math.min(inventory.Nemesis.HenchmenKilled + 5, 95); // 5 progress per mission until 95
inventoryChanges.Nemesis.MissionCount ??= 0;
inventoryChanges.Nemesis.MissionCount += 1;
@ -1517,7 +1432,7 @@ export const addMissionRewards = async (
syndicateEntry = Goals.find(m => m._id.$oid === syndicateMissionId);
if (syndicateEntry) syndicateEntry.Tag = syndicateEntry.JobAffiliationTag!;
}
if (syndicateEntry && syndicateEntry.Jobs && !jobType.startsWith("/Lotus/Types/Gameplay/NokkoColony/Jobs")) {
if (syndicateEntry && syndicateEntry.Jobs) {
let currentJob = syndicateEntry.Jobs[rewardInfo.JobTier!];
if (
[
@ -1707,8 +1622,8 @@ export const addCredits = async (
finalCredits.TotalCredits[1] += finalCredits.TotalCredits[1];
}
if ((inventory.Boosters.find(x => x.ItemType == "/Lotus/Types/Boosters/CreditBlessing")?.ExpiryDate ?? 0) > now) {
inventory.RegularCredits += finalCredits.TotalCredits[1] * 0.25;
finalCredits.TotalCredits[1] += finalCredits.TotalCredits[1] * 0.25;
inventory.RegularCredits += finalCredits.TotalCredits[1];
finalCredits.TotalCredits[1] += finalCredits.TotalCredits[1];
}
return finalCredits;
@ -1775,7 +1690,6 @@ function getLevelCreditRewards(node: IRegion): number {
function getRandomMissionDrops(
inventory: TInventoryDatabaseDocument,
RewardInfo: IRewardInfo,
levelKeyName: string | undefined,
mission: IMission | undefined,
tierOverride: number | undefined,
firstCompletion: boolean
@ -2025,19 +1939,6 @@ function getRandomMissionDrops(
xpAmounts: [1000]
};
RewardInfo.Q = false; // Just in case
} else if (jobType.startsWith("/Lotus/Types/Gameplay/NokkoColony/Jobs/NokkoJob")) {
job = {
rewards: jobType
.replace("SteelPath", "Steel")
.replace(
"/Lotus/Types/Gameplay/NokkoColony/Jobs/NokkoJob",
"/Lotus/Types/Game/MissionDecks/NokkoColonyRewards/NokkoColonyRewards"
),
masteryReq: 0,
minEnemyLevel: 30,
maxEnemyLevel: 40,
xpAmounts: [0, 0, 0, 0, 0]
};
} else {
const tierMap = {
Two: "B",
@ -2081,22 +1982,14 @@ function getRandomMissionDrops(
} else if (totalStage == 5 && curentStage == 4) {
tableIndex = 2;
}
if (jobType.startsWith("/Lotus/Types/Gameplay/NokkoColony/Jobs/NokkoJob")) {
if (RewardInfo.JobStage === job.xpAmounts.length - 1) {
rotations = [0, 1, 2];
} else {
rewardManifests = [];
}
} else {
rotations = [tableIndex];
}
rotations = [tableIndex];
} else {
rotations = [0];
}
if (
RewardInfo.Q &&
(RewardInfo.JobStage === job.xpAmounts.length - 1 || jobType.endsWith("VaultBounty")) &&
!jobType.startsWith("/Lotus/Types/Gameplay/NokkoColony/Jobs/NokkoJob") &&
!isEndlessJob
) {
rotations.push(ExportRewards[job.rewards].length - 1);
@ -2234,7 +2127,7 @@ function getRandomMissionDrops(
}
}
if (region.cacheRewardManifest && RewardInfo.EnemyCachesFound && !RewardInfo.goalId) {
if (region.cacheRewardManifest && RewardInfo.EnemyCachesFound) {
const deck = ExportRewards[region.cacheRewardManifest];
for (let rotation = 0; rotation != RewardInfo.EnemyCachesFound; ++rotation) {
const drop = getRandomRewardByChance(deck[rotation]);
@ -2300,71 +2193,6 @@ function getRandomMissionDrops(
}
}
if (RewardInfo.EnemyCachesFound) {
if (RewardInfo.goalId) {
const goal = getWorldState().Goals.find(x => x._id.$oid == RewardInfo.goalId);
if (goal) {
let currentMissionKey: string | undefined;
if (RewardInfo.node == goal.Node) {
currentMissionKey = goal.MissionKeyName;
} else if (goal.ConcurrentNodes && goal.ConcurrentMissionKeyNames) {
for (let i = 0; i < goal.ConcurrentNodes.length; i++) {
if (RewardInfo.node == goal.ConcurrentNodes[i]) {
currentMissionKey = goal.ConcurrentMissionKeyNames[i];
break;
}
}
}
if (currentMissionKey) {
const keyMeta = ExportKeys[currentMissionKey];
if (keyMeta.cacheRewardManifest) {
const deck = ExportRewards[keyMeta.cacheRewardManifest];
for (let rotation = 0; rotation != RewardInfo.EnemyCachesFound; ++rotation) {
const drop = getRandomRewardByChance(deck[rotation]);
if (drop) {
drops.push({
StoreItem: drop.type,
ItemCount: drop.itemCount,
FromEnemyCache: true
});
}
}
}
}
}
} else if (RewardInfo.alertId) {
const alert = getWorldState().Alerts.find(x => x._id.$oid == RewardInfo.alertId);
if (alert && alert.MissionInfo.enemyCacheOverride) {
const deck = ExportRewards[alert.MissionInfo.enemyCacheOverride];
for (let rotation = 0; rotation != RewardInfo.EnemyCachesFound; ++rotation) {
const drop = getRandomRewardByChance(deck[rotation]);
if (drop) {
drops.push({
StoreItem: drop.type,
ItemCount: drop.itemCount,
FromEnemyCache: true
});
}
}
}
} else if (levelKeyName) {
const keyMeta = ExportKeys[levelKeyName];
if (keyMeta.cacheRewardManifest) {
const deck = ExportRewards[keyMeta.cacheRewardManifest];
for (let rotation = 0; rotation != RewardInfo.EnemyCachesFound; ++rotation) {
const drop = getRandomRewardByChance(deck[rotation]);
if (drop) {
drops.push({
StoreItem: drop.type,
ItemCount: drop.itemCount,
FromEnemyCache: true
});
}
}
}
}
}
if (inventory.missionsCanGiveAllRelics) {
for (const drop of drops) {
const itemType = fromStoreItem(drop.StoreItem);
@ -2585,18 +2413,6 @@ const goalMessagesByKey: Record<string, { sndr: string; msg: string; sub: string
sub: "/Lotus/Language/Inbox/WaterFightRewardSubjectD",
icon: "/Lotus/Interface/Icons/Npcs/Grineer/KelaDeThaym.png"
},
"/Lotus/Types/Keys/WolfTacAlertA": {
sndr: "/Lotus/Language/Bosses/NoraNight",
msg: "/Lotus/Language/Inbox/WolfTacAlertBody",
sub: "/Lotus/Language/Inbox/WolfTacAlertTitle",
icon: "/Lotus/Interface/Icons/Npcs/Seasonal/NoraNight.png"
},
"/Lotus/Types/Keys/WolfTacAlertxB": {
sndr: "/Lotus/Language/Bosses/NoraNight",
msg: "/Lotus/Language/Inbox/WolfTacAlertHardBody",
sub: "/Lotus/Language/Inbox/WolfTacAlertHardTitle",
icon: "/Lotus/Interface/Icons/Npcs/Seasonal/NoraNight.png"
},
"/Lotus/Types/Keys/WolfTacAlertReduxA": {
sndr: "/Lotus/Language/Bosses/NoraNight",
msg: "/Lotus/Language/Inbox/WolfTacAlertBody",
@ -2622,95 +2438,95 @@ const goalMessagesByKey: Record<string, { sndr: string; msg: string; sub: string
icon: "/Lotus/Interface/Icons/Npcs/Seasonal/NoraNight.png"
},
"/Lotus/Types/Keys/LanternEndlessEventKeyA": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/GenericEventRewardMsgDesc",
sub: "/Lotus/Language/G1Quests/GenericTacAlertRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
},
"/Lotus/Types/Keys/LanternEndlessEventKeyB": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/GenericEventRewardMsgDesc",
sub: "/Lotus/Language/G1Quests/GenericTacAlertRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
},
"/Lotus/Types/Keys/LanternEndlessEventKeyD": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/GenericEventRewardMsgDesc",
sub: "/Lotus/Language/G1Quests/GenericTacAlertRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
},
"/Lotus/Types/Keys/LanternEndlessEventKeyC": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/GenericEventRewardMsgDesc",
sub: "/Lotus/Language/G1Quests/GenericTacAlertRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
},
"/Lotus/Types/Keys/TacAlertKeyHalloween": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsBonusBody",
sub: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsBonusTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
},
"/Lotus/Types/Keys/TacAlertKeyHalloweenBonus": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsBody",
sub: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
},
"/Lotus/Types/Keys/TacAlertKeyHalloweenTimeAttack": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsBody",
sub: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
},
"/Lotus/Types/Keys/TacAlertKeyProxyRebellionOne": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/RazorbackArmadaRewardBody",
sub: "/Lotus/Language/G1Quests/GenericTacAlertSmallRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["CREDIT_REWARD"]
},
"/Lotus/Types/Keys/TacAlertKeyProxyRebellionTwo": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/RazorbackArmadaRewardBody",
sub: "/Lotus/Language/G1Quests/GenericTacAlertSmallRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["CREDIT_REWARD"]
},
"/Lotus/Types/Keys/TacAlertKeyProxyRebellionThree": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/RazorbackArmadaRewardBody",
sub: "/Lotus/Language/G1Quests/GenericTacAlertSmallRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["CREDIT_REWARD"]
},
"/Lotus/Types/Keys/TacAlertKeyProxyRebellionFour": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/GenericTacAlertBadgeRewardMsgDesc",
sub: "/Lotus/Language/G1Quests/GenericTacAlertBadgeRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png"
},
"/Lotus/Types/Keys/TacAlertKeyProjectNightwatchEasy": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/ProjectNightwatchRewardMsgA",
sub: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionOneTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["CREDIT_REWARD"]
},
"/Lotus/Types/Keys/TacAlertKeyProjectNightwatch": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionRewardBody",
sub: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionTwoTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png"
},
"/Lotus/Types/Keys/TacAlertKeyProjectNightwatchHard": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionRewardBody",
sub: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionThreeTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png"
},
"/Lotus/Types/Keys/TacAlertKeyProjectNightwatchBonus": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionRewardBody",
sub: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionFourTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png"
@ -2746,140 +2562,140 @@ const goalMessagesByKey: Record<string, { sndr: string; msg: string; sub: string
icon: "/Lotus/Interface/Icons/Npcs/Entrati/Father.png"
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2019E": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgB",
sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleB",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2020F": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgC",
sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleB",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2024ChallengeModeA": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgD",
sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleD",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2017C": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2019RewardMsgC",
sub: "/Lotus/Language/Messages/Anniversary2019MissionTitleC",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2020H": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2020RewardMsgH",
sub: "/Lotus/Language/Messages/Anniversary2020MissionTitleH",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2022J": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2022RewardMsgJ",
sub: "/Lotus/Language/Messages/Anniversary2022MissionTitleJ",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2025D": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2025RewardMsgB",
sub: "/Lotus/Language/Messages/Anniversary2025MissionTitleB",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2025ChallengeModeA": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2025RewardMsgC",
sub: "/Lotus/Language/Messages/Anniversary2025MissionTitleC",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2020G": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2020RewardMsgG",
sub: "/Lotus/Language/Messages/Anniversary2020MissionTitleG",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2017B": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2019RewardMsgB",
sub: "/Lotus/Language/Messages/Anniversary2019MissionTitleB",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2017A": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2019RewardMsgA",
sub: "/Lotus/Language/Messages/Anniversary2019MissionTitleA",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2023K": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2025RewardMsgG",
sub: "/Lotus/Language/Messages/Anniversary2025MissionTitleG",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2025ChallengeModeB": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2025RewardMsgD",
sub: "/Lotus/Language/Messages/Anniversary2025MissionTitleD",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2025A": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2025RewardMsgA",
sub: "/Lotus/Language/Messages/Anniversary2025MissionTitleA",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2018D": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgG",
sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleG",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2025C": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgF",
sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleF",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2024L": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgA",
sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleA",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2024ChallengeModeB": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgE",
sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleE",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2021I": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgH",
sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleH",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2025B": {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/Messages/Anniversary2025RewardMsgE",
sub: "/Lotus/Language/Messages/Anniversary2025MissionTitleE",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",

View File

@ -1,9 +1,7 @@
import { PersonalRooms } from "../models/personalRoomsModel.ts";
import { addItem } from "./inventoryService.ts";
import { addItem, getInventory } from "./inventoryService.ts";
import type { IGardeningDatabase, TPersonalRoomsDatabaseDocument } from "../types/personalRoomsTypes.ts";
import { getRandomElement } from "./rngService.ts";
import type { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel.ts";
import { logger } from "../utils/logger.ts";
export const getPersonalRooms = async (
accountId: string,
@ -17,17 +15,19 @@ export const getPersonalRooms = async (
return personalRooms;
};
export const unlockShipFeature = async (inventory: TInventoryDatabaseDocument, shipFeature: string): Promise<void> => {
const personalRooms = await getPersonalRooms(inventory.accountOwnerId.toString());
export const updateShipFeature = async (accountId: string, shipFeature: string): Promise<void> => {
const personalRooms = await getPersonalRooms(accountId);
if (personalRooms.Ship.Features.includes(shipFeature)) {
logger.warn(`ship feature ${shipFeature} already unlocked`);
} else {
personalRooms.Ship.Features.push(shipFeature);
await personalRooms.save();
throw new Error(`ship feature ${shipFeature} already unlocked`);
}
const miscItem = inventory.MiscItems.find(x => x.ItemType === shipFeature);
if (miscItem && miscItem.ItemCount > 0) await addItem(inventory, shipFeature, miscItem.ItemCount * -1);
personalRooms.Ship.Features.push(shipFeature);
await personalRooms.save();
const inventory = await getInventory(accountId);
await addItem(inventory, shipFeature, -1);
await inventory.save();
};
export const createGarden = (): IGardeningDatabase => {

View File

@ -2,28 +2,18 @@ import type { IKeyChainRequest } from "../types/requestTypes.ts";
import { isEmptyObject } from "../helpers/general.ts";
import type { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel.ts";
import { createMessage } from "./inboxService.ts";
import {
addEquipment,
addItem,
addItems,
addKeyChainItems,
addPowerSuit,
setupKahlSyndicate
} from "./inventoryService.ts";
import { addItem, addItems, addKeyChainItems, setupKahlSyndicate } from "./inventoryService.ts";
import { fromStoreItem, getKeyChainMessage, getLevelKeyRewards } from "./itemDataService.ts";
import type { IQuestKeyClient, IQuestKeyDatabase, IQuestStage } from "../types/inventoryTypes/inventoryTypes.ts";
import { logger } from "../utils/logger.ts";
import { ExportKeys, ExportRecipes } from "warframe-public-export-plus";
import { ExportKeys } from "warframe-public-export-plus";
import { addFixedLevelRewards } from "./missionInventoryUpdateService.ts";
import type { IInventoryChanges } from "../types/purchaseTypes.ts";
import questCompletionItems from "../../static/fixed_responses/questCompletionRewards.json" with { type: "json" };
import type { ITypeCount } from "../types/commonTypes.ts";
import { addString } from "../helpers/stringHelpers.ts";
import { unlockShipFeature } from "./personalRoomsService.ts";
import { EquipmentFeatures } from "../types/equipmentTypes.ts";
export interface IUpdateQuestRequest {
QuestKeys: IQuestKeyClient[];
QuestKeys: Omit<IQuestKeyDatabase, "CompletionDate">[];
PS: string;
questCompletion: boolean;
PlayerShipEvents: unknown[];
@ -46,7 +36,6 @@ export const updateQuestKey = async (
throw new Error(`quest key ${questKeyUpdate[0].ItemType} not found`);
}
delete questKeyUpdate[0].CompletionDate;
inventory.QuestKeys[questKeyIndex].overwrite(questKeyUpdate[0]);
const inventoryChanges: IInventoryChanges = {};
@ -85,7 +74,7 @@ export const updateQuestStage = (
if (!questStage) {
const questStageIndex =
quest.Progress.push({
c: questStageUpdate.c ?? -1,
c: questStageUpdate.c ?? 0,
i: questStageUpdate.i ?? false,
m: questStageUpdate.m ?? false,
b: questStageUpdate.b ?? []
@ -101,26 +90,6 @@ export const updateQuestStage = (
}
};
export const resetQuestKeyToStage = (
inventory: TInventoryDatabaseDocument,
{ KeyChain, ChainStage }: IKeyChainRequest
): void => {
const quest = inventory.QuestKeys.find(quest => quest.ItemType === KeyChain);
if (!quest) {
throw new Error(`Quest ${KeyChain} not found in QuestKeys`);
}
quest.Progress ??= [];
const run = quest.Progress[0]?.c ?? 0;
if (run >= 0) {
for (let i = ChainStage; i < quest.Progress.length; ++i) {
quest.Progress[i].c = run - 1;
}
}
};
export const addQuestKey = (
inventory: TInventoryDatabaseDocument,
questKey: IQuestKeyDatabase
@ -146,11 +115,7 @@ export const addQuestKey = (
return inventory.QuestKeys[index - 1].toJSON<IQuestKeyClient>();
};
export const completeQuest = async (
inventory: TInventoryDatabaseDocument,
questKey: string,
sendMessages: boolean = false
): Promise<void | IQuestKeyClient> => {
export const completeQuest = async (inventory: TInventoryDatabaseDocument, questKey: string): Promise<void> => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const chainStages = ExportKeys[questKey]?.chainStages;
@ -168,8 +133,8 @@ export const completeQuest = async (
unlock: true,
Progress: Array.from({ length: chainStageTotal }, () => ({
c: 0,
i: true,
m: true,
i: false,
m: false,
b: []
}))
};
@ -196,9 +161,8 @@ export const completeQuest = async (
const stage = existingQuestKey.Progress[i];
if (stage.c <= run) {
stage.c = run;
await giveKeyChainStageTriggered(inventory, { KeyChain: questKey, ChainStage: i }, sendMessages);
await giveKeyChainStageTriggered(inventory, { KeyChain: questKey, ChainStage: i });
await giveKeyChainMissionReward(inventory, { KeyChain: questKey, ChainStage: i });
await installShipFeatures(inventory, { KeyChain: questKey, ChainStage: i });
}
}
@ -206,14 +170,7 @@ export const completeQuest = async (
existingQuestKey.Completed = true;
existingQuestKey.CompletionDate = new Date();
await handleQuestCompletion(inventory, questKey, undefined, run > 0);
if (questKey == "/Lotus/Types/Keys/ModQuest/ModQuestKeyChain") {
// This would be set by the client during the equilogue, but since we're cheating through, we have to do it ourselves.
addString(inventory.NodeIntrosCompleted, "ModQuestTeshinAccess");
}
}
return existingQuestKey.toJSON<IQuestKeyClient>();
};
const getQuestCompletionItems = (questKey: string): ITypeCount[] | undefined => {
@ -335,9 +292,6 @@ const handleQuestCompletion = async (
logger.debug(`quest completion items`, questCompletionItems);
if (questCompletionItems) {
await addItems(inventory, questCompletionItems, inventoryChanges);
for (const item of questCompletionItems) {
await removeRequiredItems(inventory, item.ItemType);
}
}
};
@ -373,27 +327,16 @@ export const giveKeyChainItem = async (
export const giveKeyChainMessage = async (
inventory: TInventoryDatabaseDocument,
keyChainInfo: IKeyChainRequest,
questKey: IQuestKeyDatabase,
sendMessage: boolean = true
questKey: IQuestKeyDatabase
): Promise<void> => {
const keyChainMessage = getKeyChainMessage(keyChainInfo);
if ((questKey.Progress?.[0]?.c ?? 0) > 0) {
if (questKey.Progress![0].c > 0) {
keyChainMessage.att = [];
keyChainMessage.countedAtt = [];
}
if (sendMessage) {
await createMessage(inventory.accountOwnerId, [keyChainMessage]);
} else {
if (keyChainMessage.countedAtt?.length) await addItems(inventory, keyChainMessage.countedAtt);
if (keyChainMessage.att?.length) {
await addItems(inventory, keyChainMessage.att);
for (const reward of keyChainMessage.att) {
await removeRequiredItems(inventory, reward);
}
}
}
await createMessage(inventory.accountOwnerId, [keyChainMessage]);
updateQuestStage(inventory, keyChainInfo, { m: true });
};
@ -417,7 +360,6 @@ export const giveKeyChainMissionReward = async (
for (const reward of missionRewards) {
await addItem(inventory, fromStoreItem(reward.StoreItem), reward.ItemCount);
await removeRequiredItems(inventory, fromStoreItem(reward.StoreItem));
}
updateQuestStage(inventory, keyChainInfo, { c: run });
@ -431,7 +373,6 @@ export const giveKeyChainMissionReward = async (
await addItem(inventory, fromStoreItem(reward.itemType), reward.amount);
} else {
await addItem(inventory, fromStoreItem(reward.itemType));
await removeRequiredItems(inventory, fromStoreItem(reward.itemType));
}
}
@ -443,8 +384,7 @@ export const giveKeyChainMissionReward = async (
export const giveKeyChainStageTriggered = async (
inventory: TInventoryDatabaseDocument,
keyChainInfo: IKeyChainRequest,
sendMessage: boolean = true
keyChainInfo: IKeyChainRequest
): Promise<void> => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const chainStages = ExportKeys[keyChainInfo.KeyChain]?.chainStages;
@ -456,341 +396,7 @@ export const giveKeyChainStageTriggered = async (
}
if (chainStages[keyChainInfo.ChainStage].messageToSendWhenTriggered) {
await giveKeyChainMessage(inventory, keyChainInfo, questKey, sendMessage);
await giveKeyChainMessage(inventory, keyChainInfo, questKey);
}
}
};
export const installShipFeatures = async (
inventory: TInventoryDatabaseDocument,
keyChainInfo: IKeyChainRequest
): Promise<void> => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const chainStages = ExportKeys[keyChainInfo.KeyChain]?.chainStages;
const questKey = inventory.QuestKeys.find(qk => qk.ItemType === keyChainInfo.KeyChain);
if (chainStages && questKey) {
if (keyChainInfo.ChainStage - 1 >= 0) {
const prevStage = chainStages[keyChainInfo.ChainStage - 1];
for (const item of prevStage.itemsToGiveWhenTriggered) {
if (item.startsWith("/Lotus/StoreItems/Types/Items/ShipFeatureItems/")) {
logger.debug(`installing ship feature ${fromStoreItem(item)}`);
await unlockShipFeature(inventory, fromStoreItem(item));
}
}
if (prevStage.key) {
const fixedLevelRewards = getLevelKeyRewards(prevStage.key);
if (fixedLevelRewards.levelKeyRewards?.items) {
for (const item of fixedLevelRewards.levelKeyRewards.items) {
if (item.startsWith("/Lotus/StoreItems/Types/Items/ShipFeatureItems/")) {
logger.debug(`installing ship feature ${fromStoreItem(item)}`);
await unlockShipFeature(inventory, fromStoreItem(item));
}
}
}
if (fixedLevelRewards.levelKeyRewards2) {
for (const item of fixedLevelRewards.levelKeyRewards2) {
if (
item.rewardType == "RT_STORE_ITEM" &&
item.itemType.startsWith("/Lotus/StoreItems/Types/Items/ShipFeatureItems/")
) {
logger.debug(`installing ship feature ${fromStoreItem(item.itemType)}`);
await unlockShipFeature(inventory, fromStoreItem(item.itemType));
}
}
}
}
}
}
};
export const removeRequiredItems = async (inventory: TInventoryDatabaseDocument, typeName: string): Promise<void> => {
switch (typeName) {
case "/Lotus/Types/Recipes/WarframeRecipes/MagicianHelmetBlueprint": {
const recipeItem = inventory.Recipes.find(
i => i.ItemType == "/Lotus/Types/Keys/LimboQuest/LimboHelmetKeyBlueprint"
);
if (recipeItem && recipeItem.ItemCount > 0) {
const recipe = ExportRecipes[recipeItem.ItemType];
if (!inventory.MiscItems.find(i => i.ItemType == recipe.resultType)) {
await addItem(inventory, recipe.resultType);
if (recipe.consumeOnUse) await addItem(inventory, recipeItem.ItemType, -1);
}
}
break;
}
case "/Lotus/Types/Recipes/WarframeRecipes/MagicianSystemsBlueprint": {
const recipeItem = inventory.Recipes.find(
i => i.ItemType == "/Lotus/Types/Keys/LimboQuest/LimboSystemsKeyBlueprint"
);
if (recipeItem && recipeItem.ItemCount > 0) {
const recipe = ExportRecipes[recipeItem.ItemType];
if (!inventory.MiscItems.find(i => i.ItemType == recipe.resultType)) {
await addItem(inventory, recipe.resultType);
if (recipe.consumeOnUse) await addItem(inventory, recipeItem.ItemType, -1);
}
}
break;
}
case "/Lotus/Types/Recipes/WarframeRecipes/MagicianChassisBlueprint": {
const recipeItem = inventory.Recipes.find(
i => i.ItemType == "/Lotus/Types/Keys/LimboQuest/LimboChassisKeyBlueprint"
);
if (recipeItem && recipeItem.ItemCount > 0) {
const recipe = ExportRecipes[recipeItem.ItemType];
if (!inventory.MiscItems.find(i => i.ItemType == recipe.resultType)) {
await addItem(inventory, recipe.resultType);
if (recipe.consumeOnUse) await addItem(inventory, recipeItem.ItemType, -1);
}
}
break;
}
case "/Lotus/Types/Recipes/WarframeRecipes/BrawlerBlueprint": {
const recipeItem = inventory.Recipes.find(
i => i.ItemType == "/Lotus/Types/Recipes/Components/InfestedIrradiatedBaitBallBlueprint"
);
if (recipeItem && recipeItem.ItemCount > 0) {
const recipe = ExportRecipes[recipeItem.ItemType];
if (!inventory.MiscItems.find(i => i.ItemType == recipe.resultType)) {
await addItem(inventory, recipe.resultType);
await addItem(inventory, recipe.resultType, -1, false, undefined, undefined, true);
if (recipe.consumeOnUse) await addItem(inventory, recipeItem.ItemType, -1);
}
}
break;
}
case "/Lotus/Types/Game/CrewShip/RailJack/DefaultHarness": {
const recipeItem = inventory.Recipes.find(
i => i.ItemType == "/Lotus/Types/Recipes/ArchwingRecipes/StandardArchwing/StandardArchwingBlueprint"
);
if (recipeItem && recipeItem.ItemCount > 0) {
const recipe = ExportRecipes[recipeItem.ItemType];
if (!inventory.MiscItems.find(i => i.ItemType == recipe.resultType)) {
await addItem(inventory, recipe.resultType);
if (recipe.consumeOnUse) await addItem(inventory, recipeItem.ItemType, recipeItem.ItemCount * -1);
await addItems(inventory, [
{
ItemType:
"/Lotus/Types/Recipes/ArchwingRecipes/StandardArchwing/StandardArchwingWingsBlueprint",
ItemCount: -1
},
{
ItemType:
"/Lotus/Types/Recipes/ArchwingRecipes/StandardArchwing/StandardArchwingChassisBlueprint",
ItemCount: -1
},
{
ItemType:
"/Lotus/Types/Recipes/ArchwingRecipes/StandardArchwing/StandardArchwingSystemsBlueprint",
ItemCount: -1
}
]);
}
}
break;
}
case "/Lotus/Types/Keys/ModQuest/ModQuestKeyChain": {
const recipeItem = inventory.Recipes.find(
i => i.ItemType == "/Lotus/Types/Recipes/Components/VorBoltRemoverBlueprint"
);
if (recipeItem && recipeItem.ItemCount > 0) {
const recipe = ExportRecipes[recipeItem.ItemType];
if (!inventory.MiscItems.find(i => i.ItemType == recipe.resultType)) {
await addItem(inventory, recipe.resultType);
if (recipe.consumeOnUse) await addItem(inventory, recipeItem.ItemType, -1);
}
}
break;
}
case "/Lotus/Types/Recipes/WarframeRecipes/ChromaBlueprint": {
await addItems(inventory, [
{
ItemType: "/Lotus/Types/Recipes/WarframeRecipes/ChromaBeaconABlueprint",
ItemCount: -1
},
{
ItemType: "/Lotus/Types/Recipes/WarframeRecipes/ChromaBeaconBBlueprint",
ItemCount: -1
},
{
ItemType: "/Lotus/Types/Recipes/WarframeRecipes/ChromaBeaconCBlueprint",
ItemCount: -1
}
]);
break;
}
case "/Lotus/Types/Recipes/WarframeRecipes/OctaviaBlueprint": {
const recipeItem = inventory.Recipes.find(
i => i.ItemType == "/Lotus/Types/Keys/BardQuest/BardQuestSequencerBlueprint"
);
if (recipeItem && recipeItem.ItemCount > 0) {
const recipe = ExportRecipes[recipeItem.ItemType];
if (!inventory.MiscItems.find(i => i.ItemType == recipe.resultType)) {
await addItem(inventory, recipe.resultType);
if (recipe.consumeOnUse) await addItem(inventory, recipeItem.ItemType, -1);
await addItems(inventory, [
{
ItemType: "/Lotus/Types/Keys/BardQuest/BardQuestSequencerPartA",
ItemCount: -1
},
{
ItemType: "/Lotus/Types/Keys/BardQuest/BardQuestSequencerPartB",
ItemCount: -1
},
{
ItemType: "/Lotus/Types/Keys/BardQuest/BardQuestSequencerPartC",
ItemCount: -1
}
]);
}
}
break;
}
case "/Lotus/Types/Game/CrewShip/Ships/RailJack": {
const recipeItem = inventory.Recipes.find(
i => i.ItemType == "/Lotus/Types/Recipes/Railjack/RailjackCephalonBlueprint"
);
if (recipeItem && recipeItem.ItemCount > 0) {
const recipe = ExportRecipes[recipeItem.ItemType];
if (!inventory.MiscItems.find(i => i.ItemType == recipe.resultType)) {
await addItem(inventory, recipe.resultType);
if (recipe.consumeOnUse) await addItem(inventory, recipeItem.ItemType, recipeItem.ItemCount * -1);
await unlockShipFeature(inventory, recipe.resultType);
}
}
break;
}
case "/Lotus/Types/Items/ShipDecos/MummyQuestVessel": {
const gearItem = inventory.Consumables.find(
i => i.ItemType == "/Lotus/Types/Keys/MummyQuest/MummyArtifact01GearItem"
);
if (gearItem && gearItem.ItemCount > 0) {
await addItem(inventory, gearItem.ItemType, gearItem.ItemCount * -1);
}
break;
}
case "/Lotus/Types/Recipes/WarframeRecipes/ConcreteFrameBlueprint": {
const recipeItem = inventory.Recipes.find(
i => i.ItemType == "/Lotus/Types/Gameplay/EntratiLab/Quest/GargoyleRecipeItem"
);
if (recipeItem && recipeItem.ItemCount > 0) {
const recipe = ExportRecipes[recipeItem.ItemType];
if (!inventory.MiscItems.find(i => i.ItemType == recipe.resultType)) {
await addItem(inventory, recipe.resultType);
if (recipe.consumeOnUse) await addItem(inventory, recipeItem.ItemType, recipeItem.ItemCount * -1);
}
}
break;
}
case "/Lotus/Upgrades/Skins/Umbra/UmbraAltHelmet": {
const recipeItem = inventory.Recipes.find(
i => i.ItemType == "/Lotus/Types/Recipes/WarframeRecipes/ExcaliburUmbraBlueprint"
);
if (recipeItem && recipeItem.ItemCount > 0) {
const recipe = ExportRecipes[recipeItem.ItemType];
if (!inventory.MiscItems.find(i => i.ItemType == recipe.resultType)) {
const umbraModA = (
await addItem(
inventory,
"/Lotus/Upgrades/Mods/Sets/Umbra/WarframeUmbraModA",
1,
false,
undefined,
`{"lvl":5}`
)
).Upgrades![0];
const umbraModB = (
await addItem(
inventory,
"/Lotus/Upgrades/Mods/Sets/Umbra/WarframeUmbraModB",
1,
false,
undefined,
`{"lvl":5}`
)
).Upgrades![0];
const umbraModC = (
await addItem(
inventory,
"/Lotus/Upgrades/Mods/Sets/Umbra/WarframeUmbraModC",
1,
false,
undefined,
`{"lvl":5}`
)
).Upgrades![0];
const sacrificeModA = (
await addItem(
inventory,
"/Lotus/Upgrades/Mods/Sets/Sacrifice/MeleeSacrificeModA",
1,
false,
undefined,
`{"lvl":5}`
)
).Upgrades![0];
const sacrificeModB = (
await addItem(
inventory,
"/Lotus/Upgrades/Mods/Sets/Sacrifice/MeleeSacrificeModB",
1,
false,
undefined,
`{"lvl":5}`
)
).Upgrades![0];
await addPowerSuit(inventory, "/Lotus/Powersuits/Excalibur/ExcaliburUmbra", {
Configs: [
{
Upgrades: [
"",
"",
"",
"",
"",
umbraModA.ItemId.$oid,
umbraModB.ItemId.$oid,
umbraModC.ItemId.$oid
]
}
],
XP: 900_000,
Features: EquipmentFeatures.DOUBLE_CAPACITY
});
inventory.XPInfo.push({
ItemType: "/Lotus/Powersuits/Excalibur/ExcaliburUmbra",
XP: 900_000
});
addEquipment(inventory, "Melee", "/Lotus/Weapons/Tenno/Melee/Swords/UmbraKatana/UmbraKatana", {
Configs: [
{
Upgrades: ["", "", "", "", "", "", sacrificeModA.ItemId.$oid, sacrificeModB.ItemId.$oid]
}
],
XP: 450_000,
Features: EquipmentFeatures.DOUBLE_CAPACITY
});
inventory.XPInfo.push({
ItemType: "/Lotus/Weapons/Tenno/Melee/Swords/UmbraKatana/UmbraKatana",
XP: 450_000
});
if (recipe.consumeOnUse) await addItem(inventory, recipeItem.ItemType, recipeItem.ItemCount * -1);
}
}
break;
}
default:
break;
}
};

View File

@ -12,8 +12,8 @@ let httpServer: http.Server | undefined;
let httpsServer: https.Server | undefined;
const tlsOptions = {
key: fs.readFileSync("static/cert/key.pem"),
cert: fs.readFileSync("static/cert/cert.pem")
key: fs.readFileSync("static/certs/key.pem"),
cert: fs.readFileSync("static/certs/cert.pem")
};
export const startWebServer = (): void => {

View File

@ -10,7 +10,7 @@ import invasionNodes from "../../static/fixed_responses/worldState/invasionNodes
import invasionRewards from "../../static/fixed_responses/worldState/invasionRewards.json" with { type: "json" };
import pvpChallenges from "../../static/fixed_responses/worldState/pvpChallenges.json" with { type: "json" };
import { buildConfig } from "./buildConfigService.ts";
import { EPOCH, unixTimesInMs } from "../constants/timeConstants.ts";
import { unixTimesInMs } from "../constants/timeConstants.ts";
import { config } from "./configService.ts";
import { getRandomElement, getRandomInt, sequentiallyUniqueRandomElement, SRng } from "./rngService.ts";
import type { IMissionReward, IRegion, TFaction } from "warframe-public-export-plus";
@ -19,7 +19,6 @@ import type {
ICalendarDay,
ICalendarEvent,
ICalendarSeason,
IAlert,
IGoal,
IInvasion,
ILiteSortie,
@ -35,13 +34,11 @@ import type {
IVoidTrader,
IVoidTraderOffer,
IWorldState,
TCircuitGameMode,
IFlashSale
TCircuitGameMode
} from "../types/worldStateTypes.ts";
import { toMongoDate, toOid, version_compare } from "../helpers/inventoryHelpers.ts";
import { logger } from "../utils/logger.ts";
import { DailyDeal, Fissure } from "../models/worldStateModel.ts";
import { getConquest } from "./conquestService.ts";
const sortieBosses = [
"SORTIE_BOSS_HYENA",
@ -64,7 +61,7 @@ const sortieBosses = [
type TSortieBoss = (typeof sortieBosses)[number];
const sortieBossToFaction: Record<TSortieBoss, TFaction> = {
const sortieBossToFaction: Record<TSortieBoss, string> = {
SORTIE_BOSS_HYENA: "FC_CORPUS",
SORTIE_BOSS_KELA: "FC_GRINEER",
SORTIE_BOSS_VOR: "FC_GRINEER",
@ -115,89 +112,6 @@ const sortieBossNode: Record<Exclude<TSortieBoss, "SORTIE_BOSS_CORRUPTED_VOR">,
SORTIE_BOSS_VOR: "SolNode108"
};
const configAlerts: Record<string, IAlert> = {
voidCorruption2025Week1: {
_id: { $oid: "677d452e2f324ee7b90f8ccf" },
Activation: { $date: { $numberLong: "1736524800000" } },
Expiry: { $date: { $numberLong: "2000000000000" } },
MissionInfo: {
location: "SolNode61",
missionType: "MT_SABOTAGE",
faction: "FC_CORPUS",
difficulty: 1,
missionReward: {
credits: 30000,
items: ["/Lotus/StoreItems/Upgrades/Mods/Pistol/DualStat/CorruptedFireRateDamagePistol"]
},
levelOverride: "/Lotus/Levels/Proc/Corpus/CorpusShipCoreSabotage",
enemySpec: "/Lotus/Types/Game/EnemySpecs/CorpusShipEnemySpecs/CorpusShipSquadA",
extraEnemySpec: "/Lotus/Types/Game/EnemySpecs/GamemodeExtraEnemySpecs/CorpusSabotageTiersA",
minEnemyLevel: 10,
maxEnemyLevel: 15
}
},
voidCorruption2025Week2: {
_id: { $oid: "677d45811daeae9de40e8c0f" },
Activation: { $date: { $numberLong: "1737129600000" } },
Expiry: { $date: { $numberLong: "2000000000000" } },
MissionInfo: {
location: "SettlementNode11",
missionType: "MT_DEFENSE",
faction: "FC_CORPUS",
difficulty: 1,
missionReward: {
credits: 30000,
items: ["/Lotus/StoreItems/Upgrades/Mods/Pistol/DualStat/CorruptedCritChanceFireRatePistol"]
},
levelOverride: "/Lotus/Levels/Proc/Corpus/CorpusShipDefense",
enemySpec: "/Lotus/Types/Game/EnemySpecs/CorpusShipEnemySpecs/CorpusShipSquadDefenseB",
minEnemyLevel: 20,
maxEnemyLevel: 25,
maxRotations: 2
}
},
voidCorruption2025Week3: {
_id: { $oid: "677d45a494ad716c90006b9a" },
Activation: { $date: { $numberLong: "1737734400000" } },
Expiry: { $date: { $numberLong: "2000000000000" } },
MissionInfo: {
location: "SolNode118",
missionType: "MT_ARTIFACT",
faction: "FC_CORPUS",
difficulty: 1,
missionReward: {
credits: 30000,
items: ["/Lotus/StoreItems/Upgrades/Mods/Pistol/DualStat/CorruptedCritDamagePistol"]
},
levelOverride: "/Lotus/Levels/Proc/Corpus/CorpusShipDisruption",
enemySpec: "/Lotus/Types/Game/EnemySpecs/CorpusShipEnemySpecs/CorpusShipSurvivalA",
extraEnemySpec: "/Lotus/Types/Game/EnemySpecs/SpecialMissionSpecs/DisruptionCorpusShip",
customAdvancedSpawners: ["/Lotus/Types/Enemies/AdvancedSpawners/ErrantSpecterInvasion"],
minEnemyLevel: 30,
maxEnemyLevel: 35
}
},
voidCorruption2025Week4: {
_id: { $oid: "677d4700682d173abb0e19fe" },
Activation: { $date: { $numberLong: "1738339200000" } },
Expiry: { $date: { $numberLong: "2000000000000" } },
MissionInfo: {
location: "SolNode4",
missionType: "MT_EXTERMINATION",
faction: "FC_CORPUS",
difficulty: 1,
missionReward: {
credits: 30000,
items: ["/Lotus/StoreItems/Upgrades/Mods/Pistol/DualStat/CorruptedDamageRecoilPistol"]
},
levelOverride: "/Lotus/Levels/Proc/Corpus/CorpusShipExterminate",
enemySpec: "/Lotus/Types/Game/EnemySpecs/CorpusShipEnemySpecs/CorpusShipExterminateMixed",
minEnemyLevel: 40,
maxEnemyLevel: 45
}
}
};
const eidolonJobs: readonly string[] = [
"/Lotus/Types/Gameplay/Eidolon/Jobs/AssassinateBountyAss",
"/Lotus/Types/Gameplay/Eidolon/Jobs/AssassinateBountyCap",
@ -277,6 +191,8 @@ const microplanetEndlessJobs: readonly string[] = [
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessPurifyBounty"
];
export const EPOCH = 1734307200 * 1000; // Monday, Dec 16, 2024 @ 00:00 UTC+0; should logically be winter in 1999 iteration 0
const isBeforeNextExpectedWorldStateRefresh = (nowMs: number, thenMs: number): boolean => {
return nowMs + 300_000 > thenMs;
};
@ -364,22 +280,9 @@ export const getSortie = (day: number): ISortie => {
}
}
const willHaveAssassination = boss != "SORTIE_BOSS_CORRUPTED_VOR" && rng.randomInt(0, 2) == 2;
if (willHaveAssassination) {
const index = nodes.indexOf(sortieBossNode[boss]);
if (index != -1) {
nodes.splice(index, 1);
}
}
const selectedNodes: ISortieMission[] = [];
const missionTypes = new Set();
if (enemyFaction == "FC_INFESTATION") {
// MT_RETRIEVAL may not be chosen for infested enemies (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2907)
missionTypes.add("MT_RETRIEVAL");
}
for (let i = 0; i < 3; i++) {
const randomIndex = rng.randomInt(0, nodes.length - 1);
const node = nodes[randomIndex];
@ -406,7 +309,7 @@ export const getSortie = (day: number): ISortie => {
"SORTIE_MODIFIER_BOW_ONLY"
];
if (i == 2 && willHaveAssassination) {
if (i == 2 && boss != "SORTIE_BOSS_CORRUPTED_VOR" && rng.randomInt(0, 2) == 2) {
const tileset = sortieTilesets[sortieBossNode[boss] as keyof typeof sortieTilesets] as TSortieTileset;
pushTilesetModifiers(modifiers, tileset);
@ -458,9 +361,7 @@ export const getSortie = (day: number): ISortie => {
Activation: { $date: { $numberLong: dayStart.toString() } },
Expiry: { $date: { $numberLong: dayEnd.toString() } },
Reward: "/Lotus/Types/Game/MissionDecks/SortieRewards",
Seed: selectedNodes.find(x => x.tileset == "CorpusIcePlanetTileset")
? 2081 // this seed produces 12 zeroes in a row if asked to pick (0, 1); this way the CorpusIcePlanetTileset image is always index 0, the 'correct' choice.
: seed,
Seed: seed,
Boss: boss,
Variants: selectedNodes
};
@ -1481,7 +1382,6 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
const weekStart = EPOCH + week * 604800000;
const weekEnd = weekStart + 604800000;
const date = new Date(timeMs);
const defenseWavesPerRotation = buildLabel && version_compare(buildLabel, "2025.03.18.16.07") < 0 ? 5 : 3;
const worldState: IWorldState = {
BuildLabel: typeof buildLabel == "string" ? buildLabel.split(" ").join("+") : buildConfig.buildLabel,
@ -1571,168 +1471,6 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
worldState.VoidTraders.push(vt);
fullyStockBaro(vt);
}
if (config.worldState) {
for (const [key, alert] of Object.entries(configAlerts)) {
if (config.worldState[key as keyof typeof config.worldState]) {
if (alert.MissionInfo.missionType == "MT_DEFENSE") {
alert.MissionInfo.maxWaveNum = defenseWavesPerRotation * (alert.MissionInfo.maxRotations ?? 1);
alert.MissionInfo.maxRotations = undefined;
}
worldState.Alerts.push(alert);
}
}
}
if (config.worldState?.qtccAlerts) {
const activationTimeStamp = "1759327200000";
const expiryTimeStamp = "2000000000000";
worldState.Alerts.push(
{
_id: {
$oid: "68dc23c42e9d3acfa708ff3b"
},
Activation: { $date: { $numberLong: activationTimeStamp } },
Expiry: { $date: { $numberLong: expiryTimeStamp } },
MissionInfo: {
location: "SolNode123",
missionType: "MT_SURVIVAL",
faction: "FC_CORPUS",
difficulty: 1,
missionReward: {
credits: 10000,
items: ["/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/Plushy2021QTCC"]
},
levelOverride: "/Lotus/Levels/Proc/Corpus/CorpusShipSurvivalRaid",
enemySpec: "/Lotus/Types/Game/EnemySpecs/CorpusShipEnemySpecs/CorpusShipSurvivalA",
minEnemyLevel: 20,
maxEnemyLevel: 30,
descText: "/Lotus/Language/Alerts/TennoUnitedAlert",
maxWaveNum: 5
},
Tag: "LotusGift",
ForceUnlock: true
},
{
_id: {
$oid: "68dc2466e298b4f04206687a"
},
Activation: { $date: { $numberLong: activationTimeStamp } },
Expiry: { $date: { $numberLong: expiryTimeStamp } },
MissionInfo: {
location: "SolNode149",
missionType: "MT_DEFENSE",
faction: "FC_GRINEER",
difficulty: 1,
missionReward: {
credits: 10000,
items: ["/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/Plushy2022QTCC"]
},
levelOverride: "/Lotus/Levels/Proc/Grineer/GrineerShipyardsDefense",
enemySpec: "/Lotus/Types/Game/EnemySpecs/GrineerShipyardsDefenseA",
minEnemyLevel: 20,
maxEnemyLevel: 30,
descText: "/Lotus/Language/Alerts/TennoUnitedAlert",
maxWaveNum: defenseWavesPerRotation * 1
},
Tag: "LotusGift",
ForceUnlock: true
},
{
_id: {
$oid: "68dc26865e7cb56b820b4252"
},
Activation: { $date: { $numberLong: activationTimeStamp } },
Expiry: { $date: { $numberLong: expiryTimeStamp } },
MissionInfo: {
location: "SolNode39",
missionType: "MT_EXCAVATE",
faction: "FC_GRINEER",
difficulty: 1,
missionReward: {
credits: 10000,
items: ["/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyVirminkQTCC"]
},
levelOverride: "/Lotus/Levels/Proc/Grineer/GrineerForestExcavation",
enemySpec: "/Lotus/Types/Game/EnemySpecs/ForestGrineerExcavationA",
minEnemyLevel: 20,
maxEnemyLevel: 30,
descText: "/Lotus/Language/Alerts/TennoUnitedAlert",
maxWaveNum: 5
},
Tag: "LotusGift",
ForceUnlock: true
}
);
const storeItems: (Partial<IFlashSale> & { TypeName: string })[] = [
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/ImageConquera2021D", RegularOverride: 1 },
{ TypeName: "/Lotus/Upgrades/Skins/Operator/Tattoos/TattooTennoI", RegularOverride: 1 },
{ TypeName: "/Lotus/Upgrades/Skins/Operator/Tattoos/TattooTennoH", RegularOverride: 1 },
{ TypeName: "/Lotus/Upgrades/Skins/Clan/QTCC2024EmblemItem", RegularOverride: 1 },
{ TypeName: "/Lotus/Types/Items/ShipDecos/Conquera2024Display", RegularOverride: 1 },
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageConqueraGlyphVII", RegularOverride: 1 },
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageConqueraGlyphVI", RegularOverride: 1 },
{ TypeName: "/Lotus/Interface/Graphics/CustomUI/ConqueraStyle", RegularOverride: 1 },
{ TypeName: "/Lotus/Interface/Graphics/CustomUI/Backgrounds/ConqueraBackground", RegularOverride: 1 },
{ TypeName: "/Lotus/Types/Items/ShipDecos/Conquera2021Deco", RegularOverride: 1 },
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/ImageConquera2022A", RegularOverride: 1 },
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/ImageConquera2021B", RegularOverride: 1 },
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/ImageConquera2021A", RegularOverride: 1 },
{ TypeName: "/Lotus/Upgrades/Skins/Scarves/TnCharityRibbonSyandana", RegularOverride: 1 },
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/ImageConquera2021C", RegularOverride: 1 },
{ TypeName: "/Lotus/Types/Items/ShipDecos/Venus/Conquera2023CommunityDisplay", RegularOverride: 1 },
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageConqueraGlyphUpdated", RegularOverride: 1 },
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/ImageConquera", RegularOverride: 1 },
{ TypeName: "/Lotus/Upgrades/Skins/Sigils/QTCC2023ConqueraSigil", RegularOverride: 1 },
{ TypeName: "/Lotus/Upgrades/Skins/Sigils/ConqueraSigil", RegularOverride: 1 },
{ TypeName: "/Lotus/Upgrades/Skins/Effects/Conquera2022Ephemera", RegularOverride: 1 },
{ TypeName: "/Lotus/Upgrades/Skins/Effects/ConqueraEphemera", RegularOverride: 1 },
{ TypeName: "/Lotus/Upgrades/Skins/Armor/TnCharityRibbonArmor/ConqueraArmorL", RegularOverride: 1 },
{ TypeName: "/Lotus/Upgrades/Skins/Armor/TnCharityRibbonArmor/ConqueraArmorA", RegularOverride: 1 },
{ TypeName: "/Lotus/Upgrades/Skins/Armor/TnCharityRibbonArmor/ConqueraChestRibbon", RegularOverride: 1 },
{ TypeName: "/Lotus/Types/Items/ShipDecos/Plushies/PlushyProtectorStalker", PremiumOverride: 35 }
];
worldState.FlashSales.push(
...storeItems.map(item => ({
...{
StartDate: { $date: { $numberLong: activationTimeStamp } },
EndDate: { $date: { $numberLong: expiryTimeStamp } },
ProductExpiryOverride: { $date: { $numberLong: expiryTimeStamp } },
ShowInMarket: item.ShowInMarket ?? true,
HideFromMarket: item.HideFromMarket ?? false,
SupporterPack: item.SupporterPack ?? false,
Discount: item.Discount ?? 0,
BogoBuy: item.BogoBuy ?? 0,
BogoGet: item.BogoGet ?? 0,
RegularOverride: item.RegularOverride ?? 0,
PremiumOverride: item.PremiumOverride ?? 0
},
...item
}))
);
const seasonalItems = storeItems.map(item => item.TypeName);
const seasonalCategory = worldState.InGameMarket.LandingPage.Categories.find(
c => c.CategoryName == "COMMUNITY"
);
if (seasonalCategory) {
seasonalCategory.Items ??= [];
seasonalCategory.Items.push(...seasonalItems);
} else {
worldState.InGameMarket.LandingPage.Categories.push({
CategoryName: "COMMUNITY",
Name: "/Lotus/Language/Store/CommunityCategoryTitle",
Icon: "community",
AddToMenu: true,
Items: seasonalItems
});
}
}
const isFebruary = date.getUTCMonth() == 1;
if (config.worldState?.starDaysOverride ?? isFebruary) {
worldState.Goals.push({
@ -1797,7 +1535,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
Personal: true,
Bounty: true,
ClampNodeScores: true,
Node: "EventNode28", // Incompatible with Wolf Hunt, Orphix Venom, Warframe Anniversary
Node: "EventNode28", // Incompatible with Wolf Hunt (2025), Orphix Venom, Warframe Anniversary
MissionKeyName: "/Lotus/Types/Keys/GalleonRobberyAlertB",
Desc: "/Lotus/Language/Events/GalleonRobberyEventMissionTitle",
Icon: "/Lotus/Interface/Icons/Player/GalleonRobberiesEvent.png",
@ -2098,39 +1836,82 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
NightLevel: "/Lotus/Levels/GrineerBeach/GrineerBeachEventNight.level"
});
const storeItems: (Partial<IFlashSale> & { TypeName: string })[] = [
{ TypeName: "/Lotus/Types/StoreItems/Packages/WaterFightNoggleBundle", PremiumOverride: 240 },
{ TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFBeastMasterBobbleHead", PremiumOverride: 35 },
{ TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFChargerBobbleHead", PremiumOverride: 35 },
{ TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFEngineerBobbleHead", PremiumOverride: 35 },
{ TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFGruntBobbleHead", PremiumOverride: 35 },
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/ImagePopsicleGrineerPurple", RegularOverride: 1 },
{ TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFHealerBobbleHead", PremiumOverride: 35 },
{ TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFHeavyBobbleHead", PremiumOverride: 35 },
{ TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFHellionBobbleHead", PremiumOverride: 35 },
{ TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFSniperBobbleHead", PremiumOverride: 35 },
{ TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFTankBobbleHead", PremiumOverride: 35 },
{ TypeName: "/Lotus/Types/StoreItems/SuitCustomizations/ColourPickerRollers", PremiumOverride: 75 }
const baseStoreItem = {
ShowInMarket: true,
HideFromMarket: false,
SupporterPack: false,
Discount: 0,
BogoBuy: 0,
BogoGet: 0,
StartDate: { $date: { $numberLong: activationTimeStamp } },
EndDate: { $date: { $numberLong: expiryTimeStamp } },
ProductExpiryOverride: { $date: { $numberLong: expiryTimeStamp } }
};
const storeItems = [
{
TypeName: "/Lotus/Types/StoreItems/Packages/WaterFightNoggleBundle",
PremiumOverride: 240,
RegularOverride: 0
},
{
TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFBeastMasterBobbleHead",
PremiumOverride: 35,
RegularOverride: 0
},
{
TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFChargerBobbleHead",
PremiumOverride: 35,
RegularOverride: 0
},
{
TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFEngineerBobbleHead",
PremiumOverride: 35,
RegularOverride: 0
},
{
TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFGruntBobbleHead",
PremiumOverride: 35,
RegularOverride: 0
},
{
TypeName: "/Lotus/Types/StoreItems/AvatarImages/ImagePopsicleGrineerPurple",
PremiumOverride: 0,
RegularOverride: 1
},
{
TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFHealerBobbleHead",
PremiumOverride: 35,
RegularOverride: 0
},
{
TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFHeavyBobbleHead",
PremiumOverride: 35,
RegularOverride: 0
},
{
TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFHellionBobbleHead",
PremiumOverride: 35,
RegularOverride: 0
},
{
TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFSniperBobbleHead",
PremiumOverride: 35,
RegularOverride: 0
},
{
TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFTankBobbleHead",
PremiumOverride: 35,
RegularOverride: 0
},
{
TypeName: "/Lotus/Types/StoreItems/SuitCustomizations/ColourPickerRollers",
PremiumOverride: 75,
RegularOverride: 0
}
];
worldState.FlashSales.push(
...storeItems.map(item => ({
...{
StartDate: { $date: { $numberLong: activationTimeStamp } },
EndDate: { $date: { $numberLong: expiryTimeStamp } },
ProductExpiryOverride: { $date: { $numberLong: expiryTimeStamp } },
ShowInMarket: item.ShowInMarket ?? true,
HideFromMarket: item.HideFromMarket ?? false,
SupporterPack: item.SupporterPack ?? false,
Discount: item.Discount ?? 0,
BogoBuy: item.BogoBuy ?? 0,
BogoGet: item.BogoGet ?? 0,
RegularOverride: item.RegularOverride ?? 0,
PremiumOverride: item.PremiumOverride ?? 0
},
...item
}))
);
worldState.FlashSales.push(...storeItems.map(item => ({ ...baseStoreItem, ...item })));
const seasonalItems = storeItems.map(item => item.TypeName);
@ -2151,7 +1932,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
}
if (config.worldState?.anniversary != undefined) {
// Incompatible with: Use Tag from Warframe Anniversary for old Events, Wolf Hunt, Galleon Of Ghouls, Hallowed Flame, Hallowed Nightmares, Dog Days, Proxy Rebellion, Long Shadow
// Incompatible with: Use Tag from Warframe Anniversary for old Events, Wolf Hunt (2025), Galleon Of Ghouls, Hallowed Flame, Hallowed Nightmares, Dog Days, Proxy Rebellion, Long Shadow
const goalsByWeek: Partial<IGoal>[][] = [
[
{
@ -2370,108 +2151,65 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
});
}
if (config.worldState?.wolfHunt != undefined) {
if (config.worldState.wolfHunt == 0) {
worldState.Goals.push({
_id: {
$oid: "67ed7672798d6466172e3b9c"
},
Activation: {
$date: {
$numberLong: "1743616800000"
}
},
Expiry: {
$date: {
$numberLong: "2000000000000"
}
},
Count: 0,
Goal: 1,
BonusGoal: 2,
Success: 0,
Personal: true,
Bounty: true,
ClampNodeScores: true,
Node: "EventNode29",
ConcurrentMissionKeyNames: ["/Lotus/Types/Keys/WolfTacAlertB"],
ConcurrentNodeReqs: [1],
ConcurrentNodes: ["EventNode28"], // Incompatible with Galleon Of Ghouls, Orphix Venom, Warframe Anniversary
MissionKeyName: "/Lotus/Types/Keys/WolfTacAlertA",
Faction: "FC_GRINEER",
Desc: "/Lotus/Language/Alerts/WolfAlert",
Icon: "/Lotus/Interface/Icons/Npcs/Seasonal/WolfStalker.png",
Tag: "WolfHuntRedux", // unfaithful
Reward: {
countedItems: [{ ItemType: "/Lotus/Types/Items/MiscItems/Alertium", ItemCount: 10 }]
},
BonusReward: {
items: [
"/Lotus/StoreItems/Upgrades/Mods/Randomized/RawRifleRandomMod",
"/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem"
]
if (config.worldState?.wolfHunt) {
worldState.Goals.push({
_id: {
$oid: "67ed7672798d6466172e3b9d"
},
Activation: {
$date: {
$numberLong: "1743616800000"
}
});
} else if (config.worldState.wolfHunt == 1) {
worldState.Goals.push({
_id: {
$oid: "67ed7672798d6466172e3b9d"
},
Activation: {
$date: {
$numberLong: "1743616800000"
}
},
Expiry: {
$date: {
$numberLong: "2000000000000"
}
},
Count: 0,
Goal: 3,
InterimGoals: [1, 2],
BonusGoal: 4,
Success: 0,
Personal: true,
Bounty: true,
ClampNodeScores: true,
Node: "EventNode29",
ConcurrentMissionKeyNames: [
"/Lotus/Types/Keys/WolfTacAlertReduxB",
"/Lotus/Types/Keys/WolfTacAlertReduxC",
"/Lotus/Types/Keys/WolfTacAlertReduxD"
],
ConcurrentNodeReqs: [1, 2, 3],
ConcurrentNodes: ["EventNode28", "EventNode39", "EventNode40"], // Incompatible with Galleon Of Ghouls, Orphix Venom, Warframe Anniversary
MissionKeyName: "/Lotus/Types/Keys/WolfTacAlertReduxA",
Faction: "FC_GRINEER",
Desc: "/Lotus/Language/Alerts/WolfAlert",
Icon: "/Lotus/Interface/Icons/Npcs/Seasonal/WolfStalker.png",
Tag: "WolfHuntRedux",
InterimRewards: [
{
credits: 50000,
items: ["/Lotus/StoreItems/Types/Recipes/Weapons/WeaponParts/ThrowingHammerHandle"]
},
{
credits: 50000,
items: ["/Lotus/StoreItems/Types/Recipes/Weapons/WeaponParts/ThrowingHammerHead"]
}
],
Reward: {
credits: 50000,
items: ["/Lotus/StoreItems/Types/Recipes/Weapons/WeaponParts/ThrowingHammerMotor"]
},
BonusReward: {
credits: 50000,
items: [
"/Lotus/StoreItems/Types/Recipes/Weapons/ThrowingHammerBlueprint",
"/Lotus/StoreItems/Types/Items/MiscItems/OrokinCatalyst",
"/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem"
]
},
Expiry: {
$date: {
$numberLong: "2000000000000"
}
});
}
},
Count: 0,
Goal: 3,
InterimGoals: [1, 2],
BonusGoal: 4,
Success: 0,
Personal: true,
Bounty: true,
ClampNodeScores: true,
Node: "EventNode29",
ConcurrentMissionKeyNames: [
"/Lotus/Types/Keys/WolfTacAlertReduxB",
"/Lotus/Types/Keys/WolfTacAlertReduxC",
"/Lotus/Types/Keys/WolfTacAlertReduxD"
],
ConcurrentNodeReqs: [1, 2, 3],
ConcurrentNodes: ["EventNode28", "EventNode39", "EventNode40"], // Incompatible with Galleon Of Ghouls, Orphix Venom, Warframe Anniversary
MissionKeyName: "/Lotus/Types/Keys/WolfTacAlertReduxA",
Faction: "FC_GRINEER",
Desc: "/Lotus/Language/Alerts/WolfAlert",
Icon: "/Lotus/Interface/Icons/Npcs/Seasonal/WolfStalker.png",
Tag: "WolfHuntRedux",
InterimRewards: [
{
credits: 50000,
items: ["/Lotus/StoreItems/Types/Recipes/Weapons/WeaponParts/ThrowingHammerHandle"]
},
{
credits: 50000,
items: ["/Lotus/StoreItems/Types/Recipes/Weapons/WeaponParts/ThrowingHammerHead"]
}
],
Reward: {
credits: 50000,
items: ["/Lotus/StoreItems/Types/Recipes/Weapons/WeaponParts/ThrowingHammerMotor"]
},
BonusReward: {
credits: 50000,
items: [
"/Lotus/StoreItems/Types/Recipes/Weapons/ThrowingHammerBlueprint",
"/Lotus/StoreItems/Types/Items/MiscItems/OrokinCatalyst",
"/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem"
]
}
});
}
const tagsForOlderGoals: string[] = [
@ -2568,22 +2306,22 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
// 2016
[
{
items: ["/Lotus/StoreItems/Types/Items/MiscItems/OrokinCatalyst"]
items: ["/Lotus/StoreItems/Upgrades/Skins/Sigils/OrokinCatalyst"]
},
{
items: ["/Lotus/StoreItems/Upgrades/Skins/Sigils/DotD2016Sigil"]
},
{
items: [
"/Lotus/StoreItems/Upgrades/Skins/Sigils/DotD2016Sigil",
"/Lotus/StoreItems/Types/Items/MiscItems/OrokinReactor",
"/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem"
]
},
{
items: ["/Lotus/StoreItems/Types/Items/MiscItems/OrokinReactor"]
}
],
// 2015
[
{
items: ["/Lotus/StoreItems/Types/Items/MiscItems/OrokinCatalyst"]
items: ["/Lotus/StoreItems/Upgrades/Skins/Sigils/OrokinCatalyst"]
},
{
items: ["/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem"]
@ -2766,208 +2504,6 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
BonusReward: { items: ["/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem"] }
});
}
const isOctober = date.getUTCMonth() == 9; // October = month index 9
if (config.worldState?.naberusNightsOverride ?? isOctober) {
const activationTimeStamp = config.worldState?.naberusNightsOverride
? "1727881200000"
: Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1).toString();
const expiryTimeStamp = config.worldState?.naberusNightsOverride
? "2000000000000"
: Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 1).toString();
worldState.Goals.push({
_id: { $oid: "66fd602de1778d583419e8e7" },
Activation: { $date: { $numberLong: activationTimeStamp } },
Expiry: { $date: { $numberLong: expiryTimeStamp } },
Count: 0,
Goal: 0,
Success: 0,
Personal: true,
Desc: "/Lotus/Language/Events/HalloweenNaberusName",
ToolTip: "/Lotus/Language/Events/HalloweenNaberusDesc",
Icon: "/Lotus/Interface/Icons/JackOLanternColour.png",
Tag: "DeimosHalloween",
Node: "DeimosHub"
});
const storeItems: (Partial<IFlashSale> & { TypeName: string })[] = [
{ TypeName: "/Lotus/Types/StoreItems/Packages/Halloween2023GlyphBundleA", PremiumOverride: 65 },
{ TypeName: "/Lotus/Types/StoreItems/Packages/Halloween2021GlyphBundle", PremiumOverride: 65 },
{ TypeName: "/Lotus/Types/StoreItems/Packages/Halloween2019GlyphBundleA", PremiumOverride: 65 },
{ TypeName: "/Lotus/Types/StoreItems/Packages/Halloween2019GlyphBundleB", PremiumOverride: 65 },
{ TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenGlyphBundle", PremiumOverride: 65 },
{ TypeName: "/Lotus/Types/StoreItems/Packages/Halloween2023ArmorBundle", PremiumOverride: 125 },
{ TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenCrpCircArmorPack", PremiumOverride: 100 },
{ TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenScarfBundleB", PremiumOverride: 80 },
{ TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenSkinPack", PremiumOverride: 175 },
{ TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenShipSkinBundle", PremiumOverride: 80 },
{ TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenSkinPackC", PremiumOverride: 175 },
{ TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenSkinPackII", PremiumOverride: 145 },
{ TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenScarfBundle", PremiumOverride: 130 },
{ TypeName: "/Lotus/Types/StoreItems/Packages/AcolyteNoggleBundle", PremiumOverride: 160 },
{ TypeName: "/Lotus/Types/Items/ShipDecos/AcolyteAreaCasterBobbleHead", PremiumOverride: 35 },
{ TypeName: "/Lotus/Types/Items/ShipDecos/AcolyteDuellistBobbleHead", PremiumOverride: 35 },
{ TypeName: "/Lotus/Types/Items/ShipDecos/AcolyteControlBobbleHead", PremiumOverride: 35 },
{ TypeName: "/Lotus/Types/Items/ShipDecos/AcolyteHeavyBobbleHead", PremiumOverride: 35 },
{ TypeName: "/Lotus/Types/Items/ShipDecos/AcolyteRogueBobbleHead", PremiumOverride: 35 },
{ TypeName: "/Lotus/Types/Items/ShipDecos/AcolyteStrikerBobbleHead", PremiumOverride: 35 },
{ TypeName: "/Lotus/Types/StoreItems/SuitCustomizations/ColourPickerHalloweenItemA", RegularOverride: 1 },
{ TypeName: "/Lotus/Upgrades/Skins/Armor/Halloween2014Wings/Halloween2014ArmArmor", PremiumOverride: 50 },
{ TypeName: "/Lotus/Upgrades/Skins/Festivities/PumpkinHead", RegularOverride: 1 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenRegorAxeShield", PremiumOverride: 20 },
{
TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/Halloween2019CheshireKavat",
PremiumOverride: 20
},
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenAkvasto", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenAngstrum", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenBoltor", PremiumOverride: 20 },
{
TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/Halloween2019GhostChibiWisp",
PremiumOverride: 20
},
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenBraton", PremiumOverride: 20 },
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageHalloween2016A", PremiumOverride: 20 },
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageHalloween2016C", PremiumOverride: 20 },
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageHalloween2016B", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenBuzlok", PremiumOverride: 20 },
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageHalloween2016D", PremiumOverride: 20 },
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/Halloween2019CreepyClem", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenDaikyu", PremiumOverride: 20 },
{
TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageHalloween2021Dethcube",
PremiumOverride: 20
},
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenDragonNikana", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenDualZoren", PremiumOverride: 20 },
{
TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/Halloween2019FrankenCorpus",
PremiumOverride: 20
},
{
TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageHalloween2021Grineer",
PremiumOverride: 20
},
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenGlaive", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenGalatine", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenGrakata", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenGorgon", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenGlaxion", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenTwinGremlins", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenGrinlok", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Scarves/HalloweenFireFlyScarf", PremiumOverride: 90 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenImperator", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenKronen", PremiumOverride: 20 },
{
TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageHalloween2021Lotus",
PremiumOverride: 20
},
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenJatKittag", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Scarves/HalloweenKyropteraScarf", PremiumOverride: 50 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenKunai", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Liset/LisetSkinHalloween", PremiumOverride: 50 },
{ TypeName: "/Lotus/Upgrades/Skins/Liset/LisetInsectSkinHalloween", PremiumOverride: 50 },
{
TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageChillingGlyphFour",
PremiumOverride: 20
},
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenMarelok", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenNikana", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenNukor", PremiumOverride: 20 },
{
TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageHalloween2021Loid",
PremiumOverride: 20
},
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenOpticor", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenOrthos", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenParis", PremiumOverride: 20 },
{
TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageChillingGlyphTwo",
PremiumOverride: 20
},
{ TypeName: "/Lotus/Upgrades/Skins/Armor/CrpCircleArmour/HalloweenCrpCircC", PremiumOverride: 45 },
{ TypeName: "/Lotus/Upgrades/Skins/Armor/CrpCircleArmour/HalloweenCrpCircA", PremiumOverride: 50 },
{ TypeName: "/Lotus/Upgrades/Skins/Armor/CrpCircleArmour/HalloweenCrpCircL", PremiumOverride: 35 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenScindo", PremiumOverride: 20 },
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/Halloween2019GhoulGrave", PremiumOverride: 20 },
{
TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageHalloween2021Pumpkin",
PremiumOverride: 20
},
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenSarpa", PremiumOverride: 20 },
{
TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageChillingGlyphThree",
PremiumOverride: 20
},
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenSilvaAndAegis", PremiumOverride: 20 },
{
TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageChillingGlyphOne",
PremiumOverride: 20
},
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenSoma", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenSkana", PremiumOverride: 20 },
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/Halloween2019SlimeLoki", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenSobek", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenSonicor", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenSimulor", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenTonkor", PremiumOverride: 20 },
{
TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/Halloween2019TrickOrBalas",
PremiumOverride: 20
},
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenSpira", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenStradavar", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenTwinGrakatas", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenArchSword", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenLato", PremiumOverride: 20 },
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/Halloween2019Werefested", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Scarves/HalloweenErosionCape", PremiumOverride: 50 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenVasto", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenDarkSplitSword", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenDarkDagger", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Scarves/HalloweenGrnBannerScarf", PremiumOverride: 75 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenAmprex", PremiumOverride: 20 },
{ TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenSkinPackD", PremiumOverride: 180 }
];
worldState.FlashSales.push(
...storeItems.map(item => ({
...{
StartDate: { $date: { $numberLong: activationTimeStamp } },
EndDate: { $date: { $numberLong: expiryTimeStamp } },
ProductExpiryOverride: { $date: { $numberLong: expiryTimeStamp } },
ShowInMarket: item.ShowInMarket ?? true,
HideFromMarket: item.HideFromMarket ?? false,
SupporterPack: item.SupporterPack ?? false,
Discount: item.Discount ?? 0,
BogoBuy: item.BogoBuy ?? 0,
BogoGet: item.BogoGet ?? 0,
RegularOverride: item.RegularOverride ?? 0,
PremiumOverride: item.PremiumOverride ?? 0
},
...item
}))
);
const seasonalItems = storeItems.map(item => item.TypeName);
const seasonalCategory = worldState.InGameMarket.LandingPage.Categories.find(c => c.CategoryName == "SEASONAL");
if (seasonalCategory) {
seasonalCategory.Items ??= [];
seasonalCategory.Items.push(...seasonalItems);
} else {
worldState.InGameMarket.LandingPage.Categories.push({
CategoryName: "SEASONAL",
Name: "/Lotus/Language/Store/SeasonalCategoryTitle",
Icon: "seasonal",
AddToMenu: true,
Items: seasonalItems
});
}
}
if (config.worldState?.bellyOfTheBeast) {
worldState.Goals.push({
_id: { $oid: "67a5035c2a198564d62e165e" },
@ -3043,7 +2579,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
Success: 0,
Personal: true,
Best: true,
Node: "EventNode28", // Incompatible with Galleon Of Ghouls, Wolf Hunt
Node: "EventNode28", // Incompatible with Galleon Of Ghouls, Wolf Hunt (2025)
MissionKeyName: "/Lotus/Types/Keys/MechSurvivalGrineerGalleon",
Faction: "FC_SENTIENT",
Desc: "/Lotus/Language/Events/MechEventMissionTier2",
@ -3181,7 +2717,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
// Nightwave Challenges
const nightwaveSyndicateTag = getNightwaveSyndicateTag(buildLabel);
if (nightwaveSyndicateTag) {
const nightwaveStartTimestamp = nightwaveTagToActivation[nightwaveSyndicateTag] ?? 1747851300000;
const nightwaveStartTimestamp = 1747851300000;
const nightwaveSeason = nightwaveTagToSeason[nightwaveSyndicateTag];
worldState.SeasonInfo = {
Activation: { $date: { $numberLong: nightwaveStartTimestamp.toString() } },
@ -3468,18 +3004,6 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
}
}
// Void Storms
const hour = Math.trunc(timeMs / unixTimesInMs.hour);
const overLastHourStormExpiry = hour * unixTimesInMs.hour + 10 * unixTimesInMs.minute;
const thisHourStormActivation = hour * unixTimesInMs.hour + 40 * unixTimesInMs.minute;
if (overLastHourStormExpiry > timeMs) {
pushVoidStorms(worldState.VoidStorms, hour - 2);
}
pushVoidStorms(worldState.VoidStorms, hour - 1);
if (isBeforeNextExpectedWorldStateRefresh(timeMs, thisHourStormActivation)) {
pushVoidStorms(worldState.VoidStorms, hour);
}
// Sortie & syndicate missions cycling every day (at 16:00 or 17:00 UTC depending on if London, OT is observing DST)
{
const rollover = getSortieTime(day);
@ -3562,18 +3086,16 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
worldState.KnownCalendarSeasons.push(getCalendarSeason(week + 1));
}
if (!buildLabel || version_compare(buildLabel, "2025.10.14.16.10") >= 0) {
worldState.Conquests = [];
{
const season = (["CST_WINTER", "CST_SPRING", "CST_SUMMER", "CST_FALL"] as const)[week % 4];
worldState.Conquests.push(getConquest("CT_LAB", week, null));
worldState.Conquests.push(getConquest("CT_HEX", week, season));
}
if (isBeforeNextExpectedWorldStateRefresh(timeMs, weekEnd)) {
const season = (["CST_WINTER", "CST_SPRING", "CST_SUMMER", "CST_FALL"] as const)[(week + 1) % 4];
worldState.Conquests.push(getConquest("CT_LAB", week, null));
worldState.Conquests.push(getConquest("CT_HEX", week, season));
}
// Void Storms
const hour = Math.trunc(timeMs / unixTimesInMs.hour);
const overLastHourStormExpiry = hour * unixTimesInMs.hour + 10 * unixTimesInMs.minute;
const thisHourStormActivation = hour * unixTimesInMs.hour + 40 * unixTimesInMs.minute;
if (overLastHourStormExpiry > timeMs) {
pushVoidStorms(worldState.VoidStorms, hour - 2);
}
pushVoidStorms(worldState.VoidStorms, hour - 1);
if (isBeforeNextExpectedWorldStateRefresh(timeMs, thisHourStormActivation)) {
pushVoidStorms(worldState.VoidStorms, hour);
}
// Sentient Anomaly + Xtra Cheese cycles
@ -3716,8 +3238,7 @@ export const getLiteSortie = (week: number): ILiteSortie => {
value.missionType != "MT_ASSASSINATION" &&
value.missionType != "MT_JUNCTION" &&
value.missionType != "MT_LANDSCAPE" &&
value.missionType != "MT_RAILJACK" &&
key != "SolNode63" // This node uses GrineerForestTilesetCaves which only supports MT_CAPTURE, which is not valid for LiteSorties.
value.missionType != "MT_RAILJACK"
) {
nodes.push(key);
}
@ -3790,10 +3311,7 @@ export const getNightwaveSyndicateTag = (buildLabel: string | undefined): string
valid_values: Object.keys(nightwaveTagToSeason)
});
}
if (!buildLabel || version_compare(buildLabel, "2025.10.14.16.10") >= 0) {
return "RadioLegionIntermission14Syndicate";
}
if (version_compare(buildLabel, "2025.05.20.10.18") >= 0) {
if (!buildLabel || version_compare(buildLabel, "2025.05.20.10.18") >= 0) {
return "RadioLegionIntermission13Syndicate";
}
if (version_compare(buildLabel, "2025.02.05.11.19") >= 0) {
@ -3803,7 +3321,6 @@ export const getNightwaveSyndicateTag = (buildLabel: string | undefined): string
};
const nightwaveTagToSeason: Record<string, number> = {
RadioLegionIntermission14Syndicate: 16, // Nora's Mix: Dreams of the Dead
RadioLegionIntermission13Syndicate: 15, // Nora's Mix Vol. 9
RadioLegionIntermission12Syndicate: 14, // Nora's Mix Vol. 8
RadioLegionIntermission11Syndicate: 13, // Nora's Mix Vol. 7
@ -3822,10 +3339,6 @@ const nightwaveTagToSeason: Record<string, number> = {
RadioLegionSyndicate: 0 // The Wolf of Saturn Six
};
const nightwaveTagToActivation: Record<string, number> = {
RadioLegionIntermission14Syndicate: 1761589199000
};
const updateFissures = async (): Promise<void> => {
const fissures = await Fissure.find();

View File

@ -8,7 +8,6 @@ import type { IDatabaseAccountJson } from "../types/loginTypes.ts";
import type { HydratedDocument } from "mongoose";
import { logError, logger } from "../utils/logger.ts";
import type { Request } from "express";
import type { ITunables } from "../types/bootstrapperTypes.ts";
let wsServer: WebSocketServer | undefined;
let wssServer: WebSocketServer | undefined;
@ -90,9 +89,8 @@ interface IWsMsgToClient {
logged_out?: boolean;
have_game_ws?: boolean;
// to game/bootstrapper (https://openwf.io/bootstrapper-manual)
// to game
sync_inventory?: boolean;
tunables?: ITunables;
}
const wsOnConnect = (ws: WebSocket, req: http.IncomingMessage): void => {

View File

@ -1,9 +0,0 @@
// This is specific to the OpenWF Bootstrapper: https://openwf.io/bootstrapper-manual
export interface ITunables {
token?: string;
prohibit_skip_mission_start_timer?: boolean;
prohibit_fov_override?: boolean;
prohibit_freecam?: boolean;
prohibit_teleport?: boolean;
prohibit_scripts?: boolean;
}

View File

@ -25,7 +25,6 @@ export enum EquipmentFeatures {
GRAVIMAG_INSTALLED = 4,
GILDED = 8,
ARCANE_SLOT = 32,
SECOND_ARCANE_SLOT = 64,
INCARNON_GENESIS = 512,
VALENCE_SWAP = 1024
}

View File

@ -14,7 +14,6 @@ import type { IOrbiterClient } from "../personalRoomsTypes.ts";
import type { ICountedStoreItem } from "warframe-public-export-plus";
import type { IEquipmentClient, IEquipmentDatabase, ITraits } from "../equipmentTypes.ts";
import type { ILoadOutPresets } from "../saveLoadoutTypes.ts";
import type { CalendarSeasonType } from "../worldStateTypes.ts";
export type InventoryDatabaseEquipment = {
[_ in TEquipmentKey]: IEquipmentDatabase[];
@ -56,15 +55,6 @@ export interface IAccountCheats {
flawlessRelicsAlwaysGiveSilverReward?: boolean;
radiantRelicsAlwaysGiveGoldReward?: boolean;
disableDailyTribute?: boolean;
nemesisHenchmenKillsMultiplierGrineer?: number;
nemesisHenchmenKillsMultiplierCorpus?: number;
nemesisAntivirusGainMultiplier?: number;
nemesisHintProgressMultiplierGrineer?: number;
nemesisHintProgressMultiplierCorpus?: number;
nemesisExtraWeapon?: number;
spoofMasteryRank?: number;
relicRewardItemCountMultiplier?: number;
nightwaveStandingMultiplier?: number;
}
export interface IInventoryDatabase
@ -96,7 +86,6 @@ export interface IInventoryDatabase
| "NextRefill"
| "Nemesis"
| "NemesisHistory"
| "LastNemesisAllySpawnTime"
| "EntratiVaultCountResetDate"
| "BrandedSuits"
| "LockedWeaponGroup"
@ -141,7 +130,6 @@ export interface IInventoryDatabase
NextRefill?: Date;
Nemesis?: INemesisDatabase;
NemesisHistory?: INemesisBaseDatabase[];
LastNemesisAllySpawnTime?: Date;
EntratiVaultCountResetDate?: Date;
BrandedSuits?: Types.ObjectId[];
LockedWeaponGroup?: ILockedWeaponGroupDatabase;
@ -153,7 +141,6 @@ export interface IInventoryDatabase
LastInventorySync?: Types.ObjectId;
EndlessXP?: IEndlessXpProgressDatabase[];
PersonalGoalProgress?: IGoalProgressDatabase[];
MissionRelicRewards?: ITypeCount[];
}
export interface IQuestKeyDatabase {
@ -344,7 +331,6 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
EmailItems: ITypeCount[];
CompletedSyndicates: string[];
FocusXP?: IFocusXP;
FocusCapacity?: number;
Wishlist: string[];
Alignment?: IAlignment;
CompletedSorties: string[];
@ -367,7 +353,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
ThemeStyle: string;
ThemeBackground: string;
ThemeSounds: string;
BountyScore?: number;
BountyScore: number;
//ChallengeInstanceStates: IChallengeInstanceState[];
LoginMilestoneRewards: string[];
RecentVendorPurchases?: IRecentVendorPurchaseClient[];
@ -379,7 +365,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
//InvasionChainProgress: IInvasionChainProgress[];
Nemesis?: INemesisClient;
NemesisHistory?: INemesisBaseClient[];
LastNemesisAllySpawnTime?: IMongoDate;
//LastNemesisAllySpawnTime?: IMongoDate;
Settings?: ISettings;
PersonalTechProjects: IPersonalTechProjectClient[];
PlayerSkills: IPlayerSkills;
@ -437,7 +423,6 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
Ship?: IOrbiterClient; // U22 and below, response only
ClaimedJunctionChallengeRewards?: string[]; // U39
SpecialItemRewardAttenuation?: IRewardAttenuation[]; // Baro's Void Surplus
NokkoColony?: INokkoColony; // Field Guide
}
export interface IAffiliation {
@ -644,7 +629,6 @@ export interface IFocusUpgrade {
ItemType: string;
Level?: number;
IsUniversal?: boolean;
IsActive?: number; // Focus 2.0
}
export interface IFocusXP {
@ -1181,7 +1165,7 @@ export interface IMarker {
}
export interface ISeasonProgress {
SeasonType: CalendarSeasonType;
SeasonType: "CST_WINTER" | "CST_SPRING" | "CST_SUMMER" | "CST_FALL";
LastCompletedDayIdx: number;
LastCompletedChallengeDayIdx: number;
ActivatedChallenges: string[];
@ -1222,13 +1206,3 @@ export interface IHubNpcCustomization {
Pattern: string;
Tag: string;
}
export interface IJournalEntry {
EntryType: string;
Progress: number;
}
export interface INokkoColony {
FeedLevel: number;
JournalEntries: IJournalEntry[];
}

View File

@ -9,8 +9,7 @@ import type {
TEquipmentKey,
ICrewMemberClient,
IKubrowPetPrintClient,
IUpgradeClient,
IQuestKeyClient
IUpgradeClient
} from "./inventoryTypes/inventoryTypes.ts";
export enum PurchaseSource {
@ -84,7 +83,6 @@ export type IInventoryChanges = {
CrewMembers?: ICrewMemberClient[];
KubrowPetPrints?: IKubrowPetPrintClient[];
Upgrades?: IUpgradeClient[]; // TOVERIFY
QuestKeys?: IQuestKeyClient[];
} & Record<
Exclude<
string,

View File

@ -178,7 +178,6 @@ export interface IRewardInfo {
goalManifest?: string;
invasionId?: string;
invasionAllyFaction?: "FC_GRINEER" | "FC_CORPUS";
alertId?: string;
sortieId?: string;
sortieTag?: "Mission1" | "Mission2" | "Final";
sortiePrereqs?: string[];
@ -234,6 +233,11 @@ export interface IUpgradeOperation {
PolarizeValue: ArtifactPolarity;
PolarityRemap: IPolarity[];
}
export interface IUnlockShipFeatureRequest {
Feature: string;
KeyChain: string;
ChainStage: number;
}
export interface IVoidTearParticipantInfo {
AccountId: string;

View File

@ -1,4 +1,4 @@
import type { IMissionReward, TFaction, TMissionType } from "warframe-public-export-plus";
import type { IMissionReward } from "warframe-public-export-plus";
import type { IMongoDate, IOid } from "./commonTypes.ts";
export interface IWorldState {
@ -7,7 +7,7 @@ export interface IWorldState {
Time: number;
InGameMarket: IInGameMarket;
Goals: IGoal[];
Alerts: IAlert[];
Alerts: [];
Sorties: ISortie[];
LiteSorties: ILiteSortie[];
SyndicateMissions: ISyndicateMissionInfo[];
@ -32,38 +32,9 @@ export interface IWorldState {
ActiveChallenges: ISeasonChallenge[];
};
KnownCalendarSeasons: ICalendarSeason[];
Conquests?: IConquest[];
Tmp?: string;
}
export interface IAlert {
_id: IOid;
Activation: IMongoDate;
Expiry: IMongoDate;
MissionInfo: IAlertMissionInfo;
Tag?: string;
ForceUnlock?: true;
}
export interface IAlertMissionInfo {
location: string;
missionType: TMissionType;
faction: TFaction;
difficulty: number;
missionReward?: IMissionReward;
levelOverride?: string;
enemySpec?: string;
extraEnemySpec?: string;
customAdvancedSpawners?: string[];
minEnemyLevel?: number;
maxEnemyLevel?: number;
maxWaveNum?: number;
descText?: string;
enemyCacheOverride?: string;
maxRotations?: number; // SNS specific field
}
export interface IGoal {
_id: IOid;
Activation: IMongoDate;
@ -353,11 +324,10 @@ export interface ISeasonChallenge {
Challenge: string;
}
export type CalendarSeasonType = "CST_WINTER" | "CST_SPRING" | "CST_SUMMER" | "CST_FALL";
export interface ICalendarSeason {
Activation: IMongoDate;
Expiry: IMongoDate;
Season: CalendarSeasonType;
Season: "CST_WINTER" | "CST_SPRING" | "CST_SUMMER" | "CST_FALL";
Days: ICalendarDay[];
YearIteration: number;
Version: number;
@ -418,33 +388,6 @@ export interface IGameMarketCategory {
Items?: string[];
}
// >= 40.0.0
export type TConquestType = "CT_LAB" | "CT_HEX";
export interface IConquest {
Activation: IMongoDate;
Expiry: IMongoDate;
Type: TConquestType;
Missions: IConquestMission[];
Variables: [string, string, string, string];
RandomSeed: number;
}
export interface IConquestMission {
faction: TFaction;
missionType: TMissionType;
difficulties: [
{
type: "CD_NORMAL";
deviation: string;
risks: [string];
},
{
type: "CD_HARD";
deviation: string;
risks: [string, string];
}
];
}
export interface ITmp {
cavabegin: string;
PurchasePlatformLockEnabled: boolean; // Seems unused
@ -452,8 +395,6 @@ export interface ITmp {
ennnd?: boolean; // True if 1999 demo is available (no effect for >=38.6.0)
mbrt?: boolean; // Related to mobile app rating request
fbst: IFbst;
lqo?: IConquestOverride;
hqo?: IConquestOverride;
sfn: number;
edg?: TCircuitGameMode[]; // The Circuit game modes overwrite
}
@ -482,12 +423,3 @@ interface IFbst {
e: number;
n: number;
}
// < 40.0.0
interface IConquestOverride {
mt?: string[]; // mission types but "Exterminate" instead of "MT_EXTERMINATION", etc. and "DualDefense" instead of "Defense" for hex conquest
mv?: string[];
mf?: number[]; // hex conquest only
c?: [string, string][];
fv?: string[];
}

View File

@ -1,70 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIF/DCCBGSgAwIBAgIQH1nGumKdC869SnXDdjpRizANBgkqhkiG9w0BAQsFADBg
MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTcwNQYDVQQD
Ey5TZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gQ0EgRFYgUjM2
MB4XDTI1MTAzMTAwMDAwMFoXDTI2MDMwNjIzNTk1OVowGDEWMBQGA1UEAwwNKi5m
YWtldGxzLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKZt+8pe
ZABBMSORWDlJBKykSbbNeEX2vPNrsQBqa3aBzQLdp6dm8qw099a5mn/vuQzdtFhc
QbaemxoVViszzM3dpTlOgLyl2r4cr8KMXJ/rRMqLeaYks+BVYJyxKAlVIduJCpP+
cxYUIiz+zNGMucuFdanzeNfCRhakDkzHH/LKZFYDHeqdR8vdUnOH8n9xuGR7sC6I
PbfML8SXCnazMpcPAeIFzJeZW3fbFIQ5R6B7X38k9+wUzKXrJo0+Q8U1mIAsV7fh
4gn+xgrFOJ7T2Vd8EtHgnr5nzNRDk4prE+ecTxBveL4QGuNoPaABtor6OLBR54AQ
8ycj29lQ094BrJcCAwEAAaOCAngwggJ0MB8GA1UdIwQYMBaAFGjAEhYYDq/O9oem
MlejRlFdywcnMB0GA1UdDgQWBBRqYi9dBjbiV9wE6GEtzyzUM6cQmjAOBgNVHQ8B
Af8EBAMCBaAwDAYDVR0TAQH/BAIwADATBgNVHSUEDDAKBggrBgEFBQcDATBJBgNV
HSAEQjBAMDQGCysGAQQBsjEBAgIHMCUwIwYIKwYBBQUHAgEWF2h0dHBzOi8vc2Vj
dGlnby5jb20vQ1BTMAgGBmeBDAECATCBhAYIKwYBBQUHAQEEeDB2ME8GCCsGAQUF
BzAChkNodHRwOi8vY3J0LnNlY3RpZ28uY29tL1NlY3RpZ29QdWJsaWNTZXJ2ZXJB
dXRoZW50aWNhdGlvbkNBRFZSMzYuY3J0MCMGCCsGAQUFBzABhhdodHRwOi8vb2Nz
cC5zZWN0aWdvLmNvbTAlBgNVHREEHjAcgg0qLmZha2V0bHMuY29tggtmYWtldGxz
LmNvbTCCAQQGCisGAQQB1nkCBAIEgfUEgfIA8AB2AJaXZL9VWJet90OHaDcIQnfp
8DrV9qTzNm5GpD8PyqnGAAABmjqerq4AAAQDAEcwRQIgY1YdLRDkbq9U9qGLpX5M
IESNpCLFKVgoPLyNQQejtPUCIQCGlFB13fGO7GpVZLXyuOrXIWRZQUxnm1GqhvOP
BoovPgB2ANFuqaVoB35mNaA/N6XdvAOlPEESFNSIGPXpMbMjy5UEAAABmjqerwkA
AAQDAEcwRQIhAMxyLWnCpof354KMPYiWQmqd+2D+yyV5ZL7rmvIJw0ojAiAxxsid
se3hTmdo39MJLKyVqlZKUua5dmLckUYUImsogTANBgkqhkiG9w0BAQsFAAOCAYEA
ByJlO00LMIgL6d5xfouZHl1OL2w0DDiWHQa87BM9AqUFSgE1IsWyUAMTVoxNp3bM
c4fiBoJ9P32hDft2gJRCtJ+xOlU9ufQqCfpN9pBxe8eEldJdnHi7q9Y1dIVwRA+y
xY2r1xgTmjYgv1Uo5mQoOjgvrRdUNTyW0Rkp9+9zr6NFxi4orqypt4KKOFY2DlV9
wKmS4HPXSNR6QEylUR1+IsCP7iQzgTcZDYYLH2vAekJ1vFJkxZyB8a1vjuUxohPi
JoXnOqBu5T9JdE81/Rm01bHQ6XAiAHpQRCyZZ3TBUgpPd10DrV8K/gMuBduKOkgk
BnwNZjOCcCwgI66F5kmoeT95ovn8ApKO9aT3EfYXx3XM+xaWjEQRpXulB9AYjQvg
wxdW+cpHAa/ttPEnrb4y2Az4uYMM2016U5p1o6SP3/feGbWSsJ33OF4VvSaS1x0h
DgzfqIX4/8yeYsfNNsoSpcwV9UGBZ7Zs0Avy1Mm5ah0Z6CGGKVWb7Qqse1YR9MSa
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIGTDCCBDSgAwIBAgIQOXpmzCdWNi4NqofKbqvjsTANBgkqhkiG9w0BAQwFADBf
MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQD
Ey1TZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYw
HhcNMjEwMzIyMDAwMDAwWhcNMzYwMzIxMjM1OTU5WjBgMQswCQYDVQQGEwJHQjEY
MBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTcwNQYDVQQDEy5TZWN0aWdvIFB1Ymxp
YyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gQ0EgRFYgUjM2MIIBojANBgkqhkiG9w0B
AQEFAAOCAY8AMIIBigKCAYEAljZf2HIz7+SPUPQCQObZYcrxLTHYdf1ZtMRe7Yeq
RPSwygz16qJ9cAWtWNTcuICc++p8Dct7zNGxCpqmEtqifO7NvuB5dEVexXn9RFFH
12Hm+NtPRQgXIFjx6MSJcNWuVO3XGE57L1mHlcQYj+g4hny90aFh2SCZCDEVkAja
EMMfYPKuCjHuuF+bzHFb/9gV8P9+ekcHENF2nR1efGWSKwnfG5RawlkaQDpRtZTm
M64TIsv/r7cyFO4nSjs1jLdXYdz5q3a4L0NoabZfbdxVb+CUEHfB0bpulZQtH1Rv
38e/lIdP7OTTIlZh6OYL6NhxP8So0/sht/4J9mqIGxRFc0/pC8suja+wcIUna0HB
pXKfXTKpzgis+zmXDL06ASJf5E4A2/m+Hp6b84sfPAwQ766rI65mh50S0Di9E3Pn
2WcaJc+PILsBmYpgtmgWTR9eV9otfKRUBfzHUHcVgarub/XluEpRlTtZudU5xbFN
xx/DgMrXLUAPaI60fZ6wA+PTAgMBAAGjggGBMIIBfTAfBgNVHSMEGDAWgBRWc1hk
lfmSGrASKgRieaFAFYghSTAdBgNVHQ4EFgQUaMASFhgOr872h6YyV6NGUV3LBycw
DgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0lBBYwFAYI
KwYBBQUHAwEGCCsGAQUFBwMCMBsGA1UdIAQUMBIwBgYEVR0gADAIBgZngQwBAgEw
VAYDVR0fBE0wSzBJoEegRYZDaHR0cDovL2NybC5zZWN0aWdvLmNvbS9TZWN0aWdv
UHVibGljU2VydmVyQXV0aGVudGljYXRpb25Sb290UjQ2LmNybDCBhAYIKwYBBQUH
AQEEeDB2ME8GCCsGAQUFBzAChkNodHRwOi8vY3J0LnNlY3RpZ28uY29tL1NlY3Rp
Z29QdWJsaWNTZXJ2ZXJBdXRoZW50aWNhdGlvblJvb3RSNDYucDdjMCMGCCsGAQUF
BzABhhdodHRwOi8vb2NzcC5zZWN0aWdvLmNvbTANBgkqhkiG9w0BAQwFAAOCAgEA
YtOC9Fy+TqECFw40IospI92kLGgoSZGPOSQXMBqmsGWZUQ7rux7cj1du6d9rD6C8
ze1B2eQjkrGkIL/OF1s7vSmgYVafsRoZd/IHUrkoQvX8FZwUsmPu7amgBfaY3g+d
q1x0jNGKb6I6Bzdl6LgMD9qxp+3i7GQOnd9J8LFSietY6Z4jUBzVoOoz8iAU84OF
h2HhAuiPw1ai0VnY38RTI+8kepGWVfGxfBWzwH9uIjeooIeaosVFvE8cmYUB4TSH
5dUyD0jHct2+8ceKEtIoFU/FfHq/mDaVnvcDCZXtIgitdMFQdMZaVehmObyhRdDD
4NQCs0gaI9AAgFj4L9QtkARzhQLNyRf87Kln+YU0lgCGr9HLg3rGO8q+Y4ppLsOd
unQZ6ZxPNGIfOApbPVf5hCe58EZwiWdHIMn9lPP6+F404y8NNugbQixBber+x536
WrZhFZLjEkhp7fFXf9r32rNPfb74X/U90Bdy4lzp3+X1ukh1BuMxA/EEhDoTOS3l
7ABvc7BYSQubQ2490OcdkIzUh3ZwDrakMVrbaTxUM2p24N6dB+ns2zptWCva6jzW
r8IWKIMxzxLPv5Kt3ePKcUdvkBU/smqujSczTzzSjIoR5QqQA6lN1ZRSnuHIWCvh
JEltkYnTAH41QJ6SAWO66GrrUESwN/cgZzL4JLEqz1Y=
-----END CERTIFICATE-----

View File

@ -1,28 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCmbfvKXmQAQTEj
kVg5SQSspEm2zXhF9rzza7EAamt2gc0C3aenZvKsNPfWuZp/77kM3bRYXEG2npsa
FVYrM8zN3aU5ToC8pdq+HK/CjFyf60TKi3mmJLPgVWCcsSgJVSHbiQqT/nMWFCIs
/szRjLnLhXWp83jXwkYWpA5Mxx/yymRWAx3qnUfL3VJzh/J/cbhke7AuiD23zC/E
lwp2szKXDwHiBcyXmVt32xSEOUege19/JPfsFMyl6yaNPkPFNZiALFe34eIJ/sYK
xTie09lXfBLR4J6+Z8zUQ5OKaxPnnE8Qb3i+EBrjaD2gAbaK+jiwUeeAEPMnI9vZ
UNPeAayXAgMBAAECggEAOYwMJVRwFZp1KExIij5STHPePURc0yxW94CESpWBpQ+K
2PPV1c+GF7+U9v1ki9pTTTyX8HmuCzxaezFngza9GW4LhH49i3153oTCzW2FVZKf
Tb3eiXFldStwZZ3oLxntxCBltPilyLubeZ19KvQTBmmWXvaeEVTOsWN2wluUE3oT
QEEFJmvWSj6Ow5HwA2xZP5dD4gv7nCY1swTePVLgWtJDC3AWDmKv3mB28brmtaDw
wP/Oq9LLlHJDK+AzJEuUs+VDFQi7j6yqXWglSjO3Jwkkfkqg/SSoZ9BKdd6i0DLs
UnUWHHATpv0Lwsa7w8csQBwfskUxsXECZpwzpCgsIQKBgQDYl2wvwjjG6qZtr060
rj4PDkFLxFbtlmBiyW9ShM6iqK6FWQJJ1FNr6MdM2zBqw+n4tldpU3DnuZQphgZK
57jejWFsHH5DYkS8k0k021AYT2IyCFHoEF43LlbRDXQguw1fADnoy6FpJBaRzuxx
jPEHdB/aYqngmTXglFYkizN2BwKBgQDEthR1FV5iPdQXKSR+Yu2irBzpxY1xhdd2
V5082etd2vBvAT7e4k8MroNMuYqL8miu2SeTJEyNkpCVoxQPF4uavandoRuEFIZl
8VPVvG5xqYIqXAJ4XlSImEgUww+jJfyLzHT0TZi6rrdHV/8zUXEimjc6OBZVafDg
FP6Lqo/w8QKBgQCxx3B8rv3tgDNFOqzur0qvDvNXnnv/nfvVeiPO5sW5S52cRJgV
Q5uJqlLUaeGO8Oo+RGTxRhUZjwDnKGRH3XWn7wI1PBoDc0iaRIbFRPK0UYx3Js8c
HTtILdgC1fko2IA8JzJhO6tsYrvHyMHY3mgExzNSDMQFX5ySjw86BawixwKBgBGc
J0qwBgoPdOw53618179HXzNCXz45eCd9AnOPIrX9Qqb9Wo6DfgYpnVGCDrgmlF6K
zDMs/bly1ITA26vaNMI+lnVj1d3GJJ39s76fpteAEEoQgJwb/b9YuqM5Ly4w2WH+
hL3WMIUN3RSC+TKz6MfrPGR23vD4kfrNhlgkhcxRAoGAEb6nXwyWe5hg9+qE2fcf
W27VApoalmnDOGFHtGhUitbBw7mHPb6wsSh4mx5Ucccfb5WUO3Cq5aW3qsDdkM+a
YrBDbvw7PPcAX0vokpyFQvHH35AsIXQuYlQyGzcCdZkpFPIB5gVbLvbgxpBvEbVZ
mGJpw7X/6Pg0ux0Ze11cr2c=
-----END PRIVATE KEY-----

71
static/certs/cert.pem Normal file
View File

@ -0,0 +1,71 @@
-----BEGIN CERTIFICATE-----
MIIGMDCCBRigAwIBAgIQX4800cgswlDH/QexMSnnnjANBgkqhkiG9w0BAQsFADCB
jzELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
A1UEBxMHU2FsZm9yZDEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTcwNQYDVQQD
Ey5TZWN0aWdvIFJTQSBEb21haW4gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENB
MB4XDTI1MDMwNjAwMDAwMFoXDTI2MDMwNjIzNTk1OVowGDEWMBQGA1UEAwwNKi5m
YWtldGxzLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMe42XWK
HJuR7doFTX79zrEKfTlD2hjRIif3dHKJNTJNvZa52mIoHelP7RVUuFOhp7aZCNLh
IEzDyZObl8vwO6L2PVu5tbBEEoNixbpfhc8ZICEBuVo2UAhnJFcMJtuvtrCq+7ye
oczM/k/nh8FBz2WnLzWs4CZt1sa5knZXFmBmsHJQtQIC6vx7QzVcKGOlAosIEHSK
X4nIz5fLgWSzor1Gay56j31PTk+qRvlPQM2aKiLWnlLfRED4zHJqLe94itu8llPX
b6g+cLxxRKUpMqtG/15cDdBZwv40Dja7bmNfe1u4w2QCVLjvHVaVpNXbcRay/Mhn
M1w5LzDZmV58b18CAwEAAaOCAvwwggL4MB8GA1UdIwQYMBaAFI2MXsRUrYrhd+mb
+ZsF4bgBjWHhMB0GA1UdDgQWBBS6/x/N38wMJrQq/cE1oIcRERMonTAOBgNVHQ8B
Af8EBAMCBaAwDAYDVR0TAQH/BAIwADAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB
BQUHAwIwSQYDVR0gBEIwQDA0BgsrBgEEAbIxAQICBzAlMCMGCCsGAQUFBwIBFhdo
dHRwczovL3NlY3RpZ28uY29tL0NQUzAIBgZngQwBAgEwgYQGCCsGAQUFBwEBBHgw
djBPBggrBgEFBQcwAoZDaHR0cDovL2NydC5zZWN0aWdvLmNvbS9TZWN0aWdvUlNB
RG9tYWluVmFsaWRhdGlvblNlY3VyZVNlcnZlckNBLmNydDAjBggrBgEFBQcwAYYX
aHR0cDovL29jc3Auc2VjdGlnby5jb20wJQYDVR0RBB4wHIINKi5mYWtldGxzLmNv
bYILZmFrZXRscy5jb20wggF+BgorBgEEAdZ5AgQCBIIBbgSCAWoBaAB2AJaXZL9V
WJet90OHaDcIQnfp8DrV9qTzNm5GpD8PyqnGAAABlWsz5fgAAAQDAEcwRQIgTN7Y
/mDqiD3RbGVLEOQK2wvXsboBolBRwGJFuFEsDScCIQCQ0qfb/0V8qqSxrkx/PiVS
1lSn5gBEnQUiQOkefcnW0gB2ABmG1Mcoqm/+ugNveCpNAZGqzi1yMQ+uzl1wQS0l
TMfUAAABlWsz5dAAAAQDAEcwRQIhAJnQJyrSCWWdi9Kyoa7XuMGyDKt183jJMY0E
71abTuBOAiBC+WnK1esG6xr8aVGHRcc+1U/I7LiaG3LCRMYtCKrTGwB2AMs49xWJ
fIShRF9bwd37yW7ymlnNRwppBYWwyxTDFFjnAAABlWsz5f4AAAQDAEcwRQIhAJUs
4PWDwyQJnCxCyEwFlFUY2uYQkGrQPA9f9Sw5Xk1fAiB63eQtZQGjvzvhOghy6z9a
8oGYbDfDQ/zfisMYO7rM6zANBgkqhkiG9w0BAQsFAAOCAQEAEHnSoeBbWiK3CS3a
px0BL+YXxRxdUcTMHgn5o+LlI9sWlpf+JLXmn7Z4QA6fAwT4k/Ue7xsmIq0OraDk
/pEVXWm1HO/9wUkGQg0DBi77BpfHircd7OWIMdt250Q8UAmZkOyhVgnwBcScqMwq
2T5CPaYvYGgYWx/qkIBv7JqhVbrP82rnF9b9ZUZ8GIE31chBmtMva9AsnAN5dmRw
81bVvPWXUfX30CYu5sxeWL06Zpy9nfJumxZri1SWXNTBjSvud2jsZ8tSCUAWLL/4
ui3Vien9m2oMOpaA8xbS88ZTk9Alm/o5febEKJZUPlytQzij8gQpiovFw2v+Cdei
+tFXKw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIGEzCCA/ugAwIBAgIQfVtRJrR2uhHbdBYLvFMNpzANBgkqhkiG9w0BAQwFADCB
iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl
cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV
BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTgx
MTAyMDAwMDAwWhcNMzAxMjMxMjM1OTU5WjCBjzELMAkGA1UEBhMCR0IxGzAZBgNV
BAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEYMBYGA1UE
ChMPU2VjdGlnbyBMaW1pdGVkMTcwNQYDVQQDEy5TZWN0aWdvIFJTQSBEb21haW4g
VmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEA1nMz1tc8INAA0hdFuNY+B6I/x0HuMjDJsGz99J/LEpgPLT+N
TQEMgg8Xf2Iu6bhIefsWg06t1zIlk7cHv7lQP6lMw0Aq6Tn/2YHKHxYyQdqAJrkj
eocgHuP/IJo8lURvh3UGkEC0MpMWCRAIIz7S3YcPb11RFGoKacVPAXJpz9OTTG0E
oKMbgn6xmrntxZ7FN3ifmgg0+1YuWMQJDgZkW7w33PGfKGioVrCSo1yfu4iYCBsk
Haswha6vsC6eep3BwEIc4gLw6uBK0u+QDrTBQBbwb4VCSmT3pDCg/r8uoydajotY
uK3DGReEY+1vVv2Dy2A0xHS+5p3b4eTlygxfFQIDAQABo4IBbjCCAWowHwYDVR0j
BBgwFoAUU3m/WqorSs9UgOHYm8Cd8rIDZsswHQYDVR0OBBYEFI2MXsRUrYrhd+mb
+ZsF4bgBjWHhMA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/AgEAMB0G
A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAbBgNVHSAEFDASMAYGBFUdIAAw
CAYGZ4EMAQIBMFAGA1UdHwRJMEcwRaBDoEGGP2h0dHA6Ly9jcmwudXNlcnRydXN0
LmNvbS9VU0VSVHJ1c3RSU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDB2Bggr
BgEFBQcBAQRqMGgwPwYIKwYBBQUHMAKGM2h0dHA6Ly9jcnQudXNlcnRydXN0LmNv
bS9VU0VSVHJ1c3RSU0FBZGRUcnVzdENBLmNydDAlBggrBgEFBQcwAYYZaHR0cDov
L29jc3AudXNlcnRydXN0LmNvbTANBgkqhkiG9w0BAQwFAAOCAgEAMr9hvQ5Iw0/H
ukdN+Jx4GQHcEx2Ab/zDcLRSmjEzmldS+zGea6TvVKqJjUAXaPgREHzSyrHxVYbH
7rM2kYb2OVG/Rr8PoLq0935JxCo2F57kaDl6r5ROVm+yezu/Coa9zcV3HAO4OLGi
H19+24rcRki2aArPsrW04jTkZ6k4Zgle0rj8nSg6F0AnwnJOKf0hPHzPE/uWLMUx
RP0T7dWbqWlod3zu4f+k+TY4CFM5ooQ0nBnzvg6s1SQ36yOoeNDT5++SR2RiOSLv
xvcRviKFxmZEJCaOEDKNyJOuB56DPi/Z+fVGjmO+wea03KbNIaiGCpXZLoUmGv38
sbZXQm2V0TP2ORQGgkE49Y9Y3IBbpNV9lXj9p5v//cWoaasm56ekBYdbqbe4oyAL
l6lFhd2zi+WJN44pDfwGF/Y4QA5C5BIG+3vzxhFoYt/jmPQT2BVPi7Fp2RBgvGQq
6jG35LWjOhSbJuMLe/0CjraZwTiXWTb2qHSihrZe68Zk6s+go/lunrotEbaGmAhY
LcmsJWTyXnW0OMGuf1pGg+pRyrbxmRE1a6Vqe8YAsOf4vmSyrcjC8azjUeqkk+B5
yOGBQMkKW+ESPMFgKuOXwIlCypTPRpgSabuY0MLTDXJLR27lk8QyKGOHQ+SwMj4K
00u/I5sUKUErmgQfky3xxzlIPK1aEn8=
-----END CERTIFICATE-----

28
static/certs/key.pem Normal file
View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDHuNl1ihybke3a
BU1+/c6xCn05Q9oY0SIn93RyiTUyTb2WudpiKB3pT+0VVLhToae2mQjS4SBMw8mT
m5fL8Dui9j1bubWwRBKDYsW6X4XPGSAhAblaNlAIZyRXDCbbr7awqvu8nqHMzP5P
54fBQc9lpy81rOAmbdbGuZJ2VxZgZrByULUCAur8e0M1XChjpQKLCBB0il+JyM+X
y4Fks6K9Rmsueo99T05Pqkb5T0DNmioi1p5S30RA+Mxyai3veIrbvJZT12+oPnC8
cUSlKTKrRv9eXA3QWcL+NA42u25jX3tbuMNkAlS47x1WlaTV23EWsvzIZzNcOS8w
2ZlefG9fAgMBAAECggEAT1Tti/LASks8300b60WFxG0WMJjzGMh5eMaiSpyVtNWM
aUKJrFOjDfnhgoeUcCPWKoG/L4Sc/+EFQMydDzTte120IasysEFZ2TZytAUdcZXZ
XUMCDQNl5vCRTsJU7Q5u0t4YAGRCgMcsfTDKi8lISGiQKBHzN1CJ74Xm13rgOInd
lAc0wd5S89sL6RYmRTj1LvuZ95EHXHqQGdv0fIFEyP3pF1iPwcoTuIVEeICqnEvW
vd8CVO68eH3HFIwioqjp4qW3pxPZMhVq4161805uAMkoQlE+7MtEVenmP++1u1gM
FjvAs3j9CZqOHZKcLlOtcGSwDlD++fCMMT4slLgLgQKBgQDy58E5nuYXdxlFQQk4
QccUKpyJ2aVXyp9xvTFBot/5Pik1SkuDzv2XU1OTxdxf3EongLy91nMJ2/6/39Je
lf0/2MjzCtJ/lSzZ/zpJAu86UkBkWBAA5loGIof6OKedbEIgqpJqtK59S+j3ExO9
eqa+uFrtt1UfaJG4A7TT+dIvIwKBgQDSfSOdSM5Dh3KsQHVnIWcIkzwTtlJlO+rG
6rDEADxw6Kp8VIL/dq4Foe8yW4VqLVrWUuZsU6jzC9GdnyYi6VaqZ/iSUtGkBMOT
WTTYhqXlURaQ13jhqdwCZJRbVI72JbXn2OGEv8DgXnk//QKED/8VdKqAzCSr1t1f
3yfwei0AlQKBgD19KU66yKg7/+umEP1quUiDmOjUbaSRqFcUe3mQD356m9ffnMob
BdrevxNzTNv/Wc4yKpUryic+x3gu4oQLF/annAbaQHsHejkdANYmpgRvedls6XAw
360Z5K4U1WlmVD8Mrs/QOTOCmdChxad7euZgqLPwat3ujKS2W3oljW1dAoGBAM4/
AB6lsDZLCfnuTxt2h1bHrh5CkAnR5AJ1BC+Ja6/WyvZ4eMOIroumWJKnStr3BgLr
yAxtDSbZddNUljGvIdRnfBEkRXbJlDlVN4rSpMtF4S6bcz7rCUDu/M9g05Qs70j2
IkPJAFzZNUWVzFlKs096uXbqkSQvrUq7ho8DqAThAoGBAL7Nrbr5LWcBgvwEhEla
VRfYb0FUrDwLIrVWntJjW566/pVQQ4BmatsblLjlQYWk9MCIYXWZbnB+2fRx9yjQ
Adggez7Dws/Mrh/wVudKgayHCy5Lgd8rYjNgC+VZf8XGrWX3QXMJ6UWAyQLTeoO7
hToW9o9CQMIhaR43G8di1kjF
-----END PRIVATE KEY-----

View File

@ -136,7 +136,5 @@
"ConquestSetupIntro",
"EntratiLabConquestHardModeUnlocked",
"/Lotus/Language/Npcs/KonzuPostNewWar",
"/Lotus/Language/SolarisVenus/EudicoPostNewWar",
"/Lotus/Language/NokkoColony/NokkoVendorName",
"NokkoVisions_FirstVisit"
"/Lotus/Language/SolarisVenus/EudicoPostNewWar"
]

View File

@ -1,409 +0,0 @@
{
"Skus": [
{
"productId": 17,
"listPrice": {
"formatted": "4.99 USD",
"raw": "4.99",
"value": 4.99
},
"basePrice": {
"formatted": "4.99 USD",
"raw": "4.99",
"value": 4.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 18,
"listPrice": {
"formatted": "9.99 USD",
"raw": "9.99",
"value": 9.99
},
"basePrice": {
"formatted": "9.99 USD",
"raw": "9.99",
"value": 9.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 160,
"listPrice": {
"formatted": "19.99 USD",
"raw": "19.99",
"value": 19.99
},
"basePrice": {
"formatted": "19.99 USD",
"raw": "19.99",
"value": 19.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 257,
"listPrice": {
"formatted": "199.99 USD",
"raw": "199.99",
"value": 199.99
},
"basePrice": {
"formatted": "199.99 USD",
"raw": "199.99",
"value": 199.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 258,
"listPrice": {
"formatted": "99.99 USD",
"raw": "99.99",
"value": 99.99
},
"basePrice": {
"formatted": "99.99 USD",
"raw": "99.99",
"value": 99.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 259,
"listPrice": {
"formatted": "49.99 USD",
"raw": "49.99",
"value": 49.99
},
"basePrice": {
"formatted": "49.99 USD",
"raw": "49.99",
"value": 49.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 785,
"listPrice": {
"formatted": "19.99 USD",
"raw": "19.99",
"value": 19.99
},
"basePrice": {
"formatted": "19.99 USD",
"raw": "19.99",
"value": 19.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 786,
"listPrice": {
"formatted": "39.99 USD",
"raw": "39.99",
"value": 39.99
},
"basePrice": {
"formatted": "39.99 USD",
"raw": "39.99",
"value": 39.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 787,
"listPrice": {
"formatted": "79.99 USD",
"raw": "79.99",
"value": 79.99
},
"basePrice": {
"formatted": "79.99 USD",
"raw": "79.99",
"value": 79.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 979,
"listPrice": {
"formatted": "4.99 USD",
"raw": "4.99",
"value": 4.99
},
"basePrice": {
"formatted": "4.99 USD",
"raw": "4.99",
"value": 4.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10037,
"listPrice": {
"formatted": "54.99 USD",
"raw": "54.99",
"value": 54.99
},
"basePrice": {
"formatted": "54.99 USD",
"raw": "54.99",
"value": 54.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10054,
"listPrice": {
"formatted": "49.99 USD",
"raw": "49.99",
"value": 49.99
},
"basePrice": {
"formatted": "49.99 USD",
"raw": "49.99",
"value": 49.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10061,
"listPrice": {
"formatted": "0.99 USD",
"raw": "0.99",
"value": 0.99
},
"basePrice": {
"formatted": "0.99 USD",
"raw": "0.99",
"value": 0.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10076,
"listPrice": {
"formatted": "29.99 USD",
"raw": "29.99",
"value": 29.99
},
"basePrice": {
"formatted": "29.99 USD",
"raw": "29.99",
"value": 29.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10078,
"listPrice": {
"formatted": "24.99 USD",
"raw": "24.99",
"value": 24.99
},
"basePrice": {
"formatted": "24.99 USD",
"raw": "24.99",
"value": 24.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10080,
"listPrice": {
"formatted": "49.99 USD",
"raw": "49.99",
"value": 49.99
},
"basePrice": {
"formatted": "49.99 USD",
"raw": "49.99",
"value": 49.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10081,
"listPrice": {
"formatted": "79.99 USD",
"raw": "79.99",
"value": 79.99
},
"basePrice": {
"formatted": "79.99 USD",
"raw": "79.99",
"value": 79.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10082,
"listPrice": {
"formatted": "139.99 USD",
"raw": "139.99",
"value": 139.99
},
"basePrice": {
"formatted": "139.99 USD",
"raw": "139.99",
"value": 139.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10084,
"listPrice": {
"formatted": "49.99 USD",
"raw": "49.99",
"value": 49.99
},
"basePrice": {
"formatted": "49.99 USD",
"raw": "49.99",
"value": 49.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10085,
"listPrice": {
"formatted": "30.00 USD",
"raw": "30.00",
"value": 30
},
"basePrice": {
"formatted": "30.00 USD",
"raw": "30.00",
"value": 30
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10086,
"listPrice": {
"formatted": "60.00 USD",
"raw": "60.00",
"value": 60
},
"basePrice": {
"formatted": "60.00 USD",
"raw": "60.00",
"value": 60
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10087,
"listPrice": {
"formatted": "90.00 USD",
"raw": "90.00",
"value": 90
},
"basePrice": {
"formatted": "90.00 USD",
"raw": "90.00",
"value": 90
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10088,
"listPrice": {
"formatted": "90.00 USD",
"raw": "90.00",
"value": 90
},
"basePrice": {
"formatted": "90.00 USD",
"raw": "90.00",
"value": 90
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10089,
"listPrice": {
"formatted": "40.01 USD",
"raw": "40.01",
"value": 40.01
},
"basePrice": {
"formatted": "40.01 USD",
"raw": "40.01",
"value": 40.01
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10090,
"listPrice": {
"formatted": "10.01 USD",
"raw": "10.01",
"value": 10.01
},
"basePrice": {
"formatted": "10.01 USD",
"raw": "10.01",
"value": 10.01
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10094,
"listPrice": {
"formatted": "24.99 USD",
"raw": "24.99",
"value": 24.99
},
"basePrice": {
"formatted": "24.99 USD",
"raw": "24.99",
"value": 24.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10095,
"listPrice": {
"formatted": "14.99 USD",
"raw": "14.99",
"value": 14.99
},
"basePrice": {
"formatted": "14.99 USD",
"raw": "14.99",
"value": 14.99
},
"currencyCode": "USD",
"owned": false
}
]
}

View File

@ -1,4 +1,10 @@
{
"/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain": [
{
"ItemType": "/Lotus/Types/NeutralCreatures/ErsatzHorse/ErsatzHorsePowerSuit",
"ItemCount": 1
}
],
"/Lotus/Types/Keys/InfestedMicroplanetQuest/InfestedMicroplanetQuestKeyChain": [{ "ItemType": "/Lotus/Types/Recipes/WarframeRecipes/BrokenFrameBlueprint", "ItemCount": 1 }],
"/Lotus/Types/Keys/OrokinMoonQuest/OrokinMoonQuestKeyChain": [
{

View File

@ -83,7 +83,7 @@
<p data-loc="login_description"></p>
<form onsubmit="doLogin();return false;">
<label for="email" data-loc="login_emailLabel"></label>
<input class="form-control" type="text" id="email" required />
<input class="form-control" type="email" id="email" required />
<br />
<label for="password" data-loc="login_passwordLabel"></label>
<input class="form-control" type="password" id="password" required />
@ -110,7 +110,7 @@
<div class="tab-pane" id="miscItems-tab-content">
<form class="card-body input-group" onsubmit="doAcquireCountItems('miscitems');return false;">
<input class="form-control" id="miscitems-count" type="number" value="1" />
<input class="form-control w-50" id="acquire-type-miscitems" list="datalist-miscitems" autocomplete="off" />
<input class="form-control w-50" id="acquire-type-miscitems" list="datalist-miscitems" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
</div>
@ -182,7 +182,7 @@
<h5 class="card-header" data-loc="inventory_suits"></h5>
<div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('Suits');return false;">
<input class="form-control" id="acquire-type-Suits" list="datalist-Suits" autocomplete="off" />
<input class="form-control" id="acquire-type-Suits" list="datalist-Suits" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<div class="overflow-auto">
@ -198,13 +198,13 @@
<h5 class="card-header" data-loc="inventory_longGuns"></h5>
<div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="handleModularSelection('LongGuns');return false;">
<input class="form-control" id="acquire-type-LongGuns" list="datalist-LongGuns" autocomplete="off" />
<input class="form-control" id="acquire-type-LongGuns" list="datalist-LongGuns" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<form class="input-group mb-3 d-none" id="modular-LongGuns">
<input class="form-control" id="acquire-type-LongGuns-GUN_BARREL" list="datalist-ModularParts-GUN_BARREL" autocomplete="off" />
<input class="form-control" id="acquire-type-LongGuns-GUN_PRIMARY_HANDLE" list="datalist-ModularParts-GUN_PRIMARY_HANDLE" autocomplete="off" />
<input class="form-control" id="acquire-type-LongGuns-GUN_CLIP" list="datalist-ModularParts-GUN_CLIP" autocomplete="off" />
<input class="form-control" id="acquire-type-LongGuns-GUN_BARREL" list="datalist-ModularParts-GUN_BARREL" />
<input class="form-control" id="acquire-type-LongGuns-GUN_PRIMARY_HANDLE" list="datalist-ModularParts-GUN_PRIMARY_HANDLE" />
<input class="form-control" id="acquire-type-LongGuns-GUN_CLIP" list="datalist-ModularParts-GUN_CLIP" />
</form>
<div class="overflow-auto">
<table class="table table-hover w-100">
@ -221,13 +221,13 @@
<h5 class="card-header" data-loc="inventory_pistols"></h5>
<div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="handleModularSelection('Pistols');return false;">
<input class="form-control" id="acquire-type-Pistols" list="datalist-Pistols" autocomplete="off" />
<input class="form-control" id="acquire-type-Pistols" list="datalist-Pistols" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<form class="input-group mb-3 d-none" id="modular-Pistols">
<input class="form-control" id="acquire-type-Pistols-GUN_BARREL" list="datalist-ModularParts-GUN_BARREL" autocomplete="off" />
<input class="form-control" id="acquire-type-Pistols-GUN_SECONDARY_HANDLE" list="datalist-ModularParts-GUN_SECONDARY_HANDLE" autocomplete="off" />
<input class="form-control" id="acquire-type-Pistols-GUN_CLIP" list="datalist-ModularParts-GUN_CLIP" autocomplete="off" />
<input class="form-control" id="acquire-type-Pistols-GUN_BARREL" list="datalist-ModularParts-GUN_BARREL" />
<input class="form-control" id="acquire-type-Pistols-GUN_SECONDARY_HANDLE" list="datalist-ModularParts-GUN_SECONDARY_HANDLE" />
<input class="form-control" id="acquire-type-Pistols-GUN_CLIP" list="datalist-ModularParts-GUN_CLIP" />
</form>
<div class="overflow-auto">
<table class="table table-hover w-100">
@ -242,13 +242,13 @@
<h5 class="card-header" data-loc="inventory_melee"></h5>
<div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="handleModularSelection('Melee');return false;">
<input class="form-control" id="acquire-type-Melee" list="datalist-Melee" autocomplete="off" />
<input class="form-control" id="acquire-type-Melee" list="datalist-Melee" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<form class="input-group mb-3 d-none" id="modular-Melee">
<input class="form-control" id="acquire-type-Melee-BLADE" list="datalist-ModularParts-BLADE" autocomplete="off" />
<input class="form-control" id="acquire-type-Melee-HILT" list="datalist-ModularParts-HILT" autocomplete="off" />
<input class="form-control" id="acquire-type-Melee-HILT_WEIGHT" list="datalist-ModularParts-HILT_WEIGHT" autocomplete="off" />
<input class="form-control" id="acquire-type-Melee-BLADE" list="datalist-ModularParts-BLADE" />
<input class="form-control" id="acquire-type-Melee-HILT" list="datalist-ModularParts-HILT" />
<input class="form-control" id="acquire-type-Melee-HILT_WEIGHT" list="datalist-ModularParts-HILT_WEIGHT" />
</form>
<div class="overflow-auto">
<table class="table table-hover w-100">
@ -265,7 +265,7 @@
<h5 class="card-header" data-loc="inventory_spaceSuits"></h5>
<div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('SpaceSuits');return false;">
<input class="form-control" id="acquire-type-SpaceSuits" list="datalist-SpaceSuits" autocomplete="off" />
<input class="form-control" id="acquire-type-SpaceSuits" list="datalist-SpaceSuits" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<div class="overflow-auto">
@ -281,7 +281,7 @@
<h5 class="card-header" data-loc="inventory_spaceGuns"></h5>
<div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('SpaceGuns');return false;">
<input class="form-control" id="acquire-type-SpaceGuns" list="datalist-SpaceGuns" autocomplete="off" />
<input class="form-control" id="acquire-type-SpaceGuns" list="datalist-SpaceGuns" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<div class="overflow-auto">
@ -299,7 +299,7 @@
<h5 class="card-header" data-loc="inventory_spaceMelee"></h5>
<div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('SpaceMelee');return false;">
<input class="form-control" id="acquire-type-SpaceMelee" list="datalist-SpaceMelee" autocomplete="off" />
<input class="form-control" id="acquire-type-SpaceMelee" list="datalist-SpaceMelee" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<div class="overflow-auto">
@ -315,7 +315,7 @@
<h5 class="card-header" data-loc="inventory_mechSuits"></h5>
<div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('MechSuits');return false;">
<input class="form-control" id="acquire-type-MechSuits" list="datalist-MechSuits" autocomplete="off" />
<input class="form-control" id="acquire-type-MechSuits" list="datalist-MechSuits" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<div class="overflow-auto">
@ -333,7 +333,7 @@
<h5 class="card-header" data-loc="inventory_sentinels"></h5>
<div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('Sentinels');return false;">
<input class="form-control" id="acquire-type-Sentinels" list="datalist-Sentinels" autocomplete="off" />
<input class="form-control" id="acquire-type-Sentinels" list="datalist-Sentinels" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<div class="overflow-auto">
@ -349,20 +349,20 @@
<h5 class="card-header" data-loc="inventory_moaPets"></h5>
<div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="handleModularSelection('MoaPets');return false;">
<input class="form-control" id="acquire-type-MoaPets" list="datalist-MoaPets" autocomplete="off" />
<input class="form-control" id="acquire-type-MoaPets" list="datalist-MoaPets" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<form class="input-group mb-3 d-none" id="modular-MoaPets-Moa">
<input class="form-control" id="acquire-type-MoaPets-MOA_ENGINE" list="datalist-ModularParts-MOA_ENGINE" autocomplete="off" />
<input class="form-control" id="acquire-type-MoaPets-MOA_PAYLOAD" list="datalist-ModularParts-MOA_PAYLOAD" autocomplete="off" />
<input class="form-control" id="acquire-type-MoaPets-MOA_HEAD" list="datalist-ModularParts-MOA_HEAD" autocomplete="off" />
<input class="form-control" id="acquire-type-MoaPets-MOA_LEG" list="datalist-ModularParts-MOA_LEG" autocomplete="off" />
<input class="form-control" id="acquire-type-MoaPets-MOA_ENGINE" list="datalist-ModularParts-MOA_ENGINE" />
<input class="form-control" id="acquire-type-MoaPets-MOA_PAYLOAD" list="datalist-ModularParts-MOA_PAYLOAD" />
<input class="form-control" id="acquire-type-MoaPets-MOA_HEAD" list="datalist-ModularParts-MOA_HEAD" />
<input class="form-control" id="acquire-type-MoaPets-MOA_LEG" list="datalist-ModularParts-MOA_LEG" />
</form>
<form class="input-group mb-3 d-none" id="modular-MoaPets-Zanuka">
<input class="form-control" id="acquire-type-MoaPets-ZANUKA_HEAD" list="datalist-ModularParts-ZANUKA_HEAD" autocomplete="off" />
<input class="form-control" id="acquire-type-MoaPets-ZANUKA_BODY" list="datalist-ModularParts-ZANUKA_BODY" autocomplete="off" />
<input class="form-control" id="acquire-type-MoaPets-ZANUKA_LEG" list="datalist-ModularParts-ZANUKA_LEG" autocomplete="off" />
<input class="form-control" id="acquire-type-MoaPets-ZANUKA_TAIL" list="datalist-ModularParts-ZANUKA_TAIL" autocomplete="off" />
<input class="form-control" id="acquire-type-MoaPets-ZANUKA_HEAD" list="datalist-ModularParts-ZANUKA_HEAD" />
<input class="form-control" id="acquire-type-MoaPets-ZANUKA_BODY" list="datalist-ModularParts-ZANUKA_BODY" />
<input class="form-control" id="acquire-type-MoaPets-ZANUKA_LEG" list="datalist-ModularParts-ZANUKA_LEG" />
<input class="form-control" id="acquire-type-MoaPets-ZANUKA_TAIL" list="datalist-ModularParts-ZANUKA_TAIL" />
</form>
<div class="overflow-auto">
<table class="table table-hover w-100">
@ -379,16 +379,16 @@
<h5 class="card-header" data-loc="inventory_kubrowPets"></h5>
<div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="handleModularSelection('KubrowPets');return false;">
<input class="form-control" id="acquire-type-KubrowPets" list="datalist-KubrowPets" autocomplete="off" />
<input class="form-control" id="acquire-type-KubrowPets" list="datalist-KubrowPets" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<form class="input-group mb-3 d-none" id="modular-KubrowPets-Catbrow">
<input class="form-control" id="acquire-type-KubrowPets-CATBROW_ANTIGEN" list="datalist-ModularParts-CATBROW_ANTIGEN" autocomplete="off" />
<input class="form-control" id="acquire-type-KubrowPets-CATBROW_MUTAGEN" list="datalist-ModularParts-CATBROW_MUTAGEN" autocomplete="off" />
<input class="form-control" id="acquire-type-KubrowPets-CATBROW_ANTIGEN" list="datalist-ModularParts-CATBROW_ANTIGEN" />
<input class="form-control" id="acquire-type-KubrowPets-CATBROW_MUTAGEN" list="datalist-ModularParts-CATBROW_MUTAGEN" />
</form>
<form class="input-group mb-3 d-none" id="modular-KubrowPets-Kubrow">
<input class="form-control" id="acquire-type-KubrowPets-KUBROW_ANTIGEN" list="datalist-ModularParts-KUBROW_ANTIGEN" autocomplete="off" />
<input class="form-control" id="acquire-type-KubrowPets-KUBROW_MUTAGEN" list="datalist-ModularParts-KUBROW_MUTAGEN" autocomplete="off" />
<input class="form-control" id="acquire-type-KubrowPets-KUBROW_ANTIGEN" list="datalist-ModularParts-KUBROW_ANTIGEN" />
<input class="form-control" id="acquire-type-KubrowPets-KUBROW_MUTAGEN" list="datalist-ModularParts-KUBROW_MUTAGEN" />
</form>
<div class="overflow-auto">
<table class="table table-hover w-100">
@ -403,7 +403,7 @@
<h5 class="card-header" data-loc="inventory_sentinelWeapons"></h5>
<div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('SentinelWeapons');return false;">
<input class="form-control" id="acquire-type-SentinelWeapons" list="datalist-SentinelWeapons" autocomplete="off" />
<input class="form-control" id="acquire-type-SentinelWeapons" list="datalist-SentinelWeapons" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<div class="overflow-auto">
@ -421,13 +421,13 @@
<h5 class="card-header" data-loc="inventory_operatorAmps"></h5>
<div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="handleModularSelection('OperatorAmps');return false;">
<input class="form-control" id="acquire-type-OperatorAmps" list="datalist-OperatorAmps" autocomplete="off" />
<input class="form-control" id="acquire-type-OperatorAmps" list="datalist-OperatorAmps" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<form class="input-group mb-3 d-none" id="modular-OperatorAmps">
<input class="form-control" id="acquire-type-OperatorAmps-AMP_OCULUS" list="datalist-ModularParts-AMP_OCULUS" autocomplete="off" />
<input class="form-control" id="acquire-type-OperatorAmps-AMP_CORE" list="datalist-ModularParts-AMP_CORE" autocomplete="off" />
<input class="form-control" id="acquire-type-OperatorAmps-AMP_BRACE" list="datalist-ModularParts-AMP_BRACE" autocomplete="off" />
<input class="form-control" id="acquire-type-OperatorAmps-AMP_OCULUS" list="datalist-ModularParts-AMP_OCULUS" />
<input class="form-control" id="acquire-type-OperatorAmps-AMP_CORE" list="datalist-ModularParts-AMP_CORE" />
<input class="form-control" id="acquire-type-OperatorAmps-AMP_BRACE" list="datalist-ModularParts-AMP_BRACE" />
</form>
<div class="overflow-auto">
<table class="table table-hover w-100">
@ -442,10 +442,10 @@
<h5 class="card-header" data-loc="inventory_hoverboards"></h5>
<div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="doAcquireModularEquipment('Hoverboards');return false;">
<input class="form-control" id="acquire-type-Hoverboards-HB_DECK" list="datalist-ModularParts-HB_DECK" autocomplete="off" />
<input class="form-control" id="acquire-type-Hoverboards-HB_ENGINE" list="datalist-ModularParts-HB_ENGINE" autocomplete="off" />
<input class="form-control" id="acquire-type-Hoverboards-HB_FRONT" list="datalist-ModularParts-HB_FRONT" autocomplete="off" />
<input class="form-control" id="acquire-type-Hoverboards-HB_JET" list="datalist-ModularParts-HB_JET" autocomplete="off" />
<input class="form-control" id="acquire-type-Hoverboards-HB_DECK" list="datalist-ModularParts-HB_DECK" />
<input class="form-control" id="acquire-type-Hoverboards-HB_ENGINE" list="datalist-ModularParts-HB_ENGINE" />
<input class="form-control" id="acquire-type-Hoverboards-HB_FRONT" list="datalist-ModularParts-HB_FRONT" />
<input class="form-control" id="acquire-type-Hoverboards-HB_JET" list="datalist-ModularParts-HB_JET" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<div class="overflow-auto">
@ -463,7 +463,7 @@
<h5 class="card-header" data-loc="inventory_evolutionProgress"></h5>
<div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="doAcquireEvolution();return false;">
<input class="form-control" id="acquire-type-EvolutionProgress" list="datalist-EvolutionProgress" autocomplete="off" />
<input class="form-control" id="acquire-type-EvolutionProgress" list="datalist-EvolutionProgress" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<div class="overflow-auto">
@ -479,7 +479,7 @@
<h5 class="card-header" data-loc="inventory_boosters"></h5>
<div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="doAcquireBooster();return false;">
<input class="form-control" id="acquire-type-Boosters" list="datalist-Boosters" autocomplete="off" />
<input class="form-control" id="acquire-type-Boosters" list="datalist-Boosters" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<div class="overflow-auto">
@ -497,7 +497,7 @@
<h5 class="card-header" data-loc="inventory_flavourItems"></h5>
<div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('FlavourItems');return false;">
<input class="form-control" id="acquire-type-FlavourItems" list="datalist-FlavourItems" autocomplete="off" />
<input class="form-control" id="acquire-type-FlavourItems" list="datalist-FlavourItems" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<div class="overflow-auto">
@ -514,7 +514,7 @@
<div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="doAcquireCountItems('ShipDecorations');return false;">
<input class="form-control" id="ShipDecorations-count" type="number" value="1" />
<input class="form-control w-50" id="acquire-type-ShipDecorations" list="datalist-ShipDecorations" autocomplete="off" />
<input class="form-control w-50" id="acquire-type-ShipDecorations" list="datalist-ShipDecorations" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<div class="overflow-auto">
@ -526,24 +526,6 @@
</div>
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-lg-6">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_weaponSkins"></h5>
<div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('WeaponSkins');return false;">
<input class="form-control" id="acquire-type-WeaponSkins" list="datalist-WeaponSkins" autocomplete="off" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<div class="overflow-auto">
<table class="table table-hover w-100">
<tbody id="WeaponSkins-list"></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<h5 class="card-header" data-loc="general_bulkActions"></h5>
<div class="card-body">
@ -556,9 +538,7 @@
<button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['SentinelWeapons']);" data-loc="inventory_bulkAddSentinelWeapons"></button>
<button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['FlavourItems']);" data-loc="inventory_bulkAddFlavourItems"></button>
<button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['ShipDecorations']);" data-loc="inventory_bulkAddShipDecorations"></button>
<button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['WeaponSkins']);" data-loc="inventory_bulkAddWeaponSkins"></button>
<button class="btn btn-primary" onclick="debounce(addMissingEvolutionProgress);" data-loc="inventory_bulkAddEvolutionProgress"></button>
<button class="btn btn-primary" onclick="removeIsNew();" data-loc="inventory_removeIsNew"></button>
</div>
<div class="mb-2 d-flex flex-wrap gap-2">
<button class="btn btn-success" onclick="debounce(maxRankAllEquipment, ['Suits']);" data-loc="inventory_bulkRankUpSuits"></button>
@ -610,7 +590,7 @@
<h5 class="card-header" data-loc="guildView_techProjects"></h5>
<div class="card-body d-flex flex-column">
<form id="techProjects-form" class="input-group mb-3 d-none" onsubmit="addGuildTechProject();return false;">
<input class="form-control" id="acquire-type-TechProjects" list="datalist-TechProjects" autocomplete="off" />
<input class="form-control" id="acquire-type-TechProjects" list="datalist-TechProjects" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<div class="overflow-auto">
@ -625,8 +605,8 @@
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="guildView_vaultDecoRecipes"></h5>
<div class="card-body d-flex flex-column">
<form id="vaultDecoRecipes-form" class="input-group mb-3 d-none" onsubmit="addVaultItem('VaultDecoRecipes');return false;">
<input class="form-control" id="acquire-type-VaultDecoRecipes" list="datalist-VaultDecoRecipes" autocomplete="off" />
<form id="vaultDecoRecipes-form" class="input-group mb-3 d-none" onsubmit="addVaultDecoRecipe();return false;">
<input class="form-control" id="acquire-type-VaultDecoRecipes" list="datalist-VaultDecoRecipes" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<div class="overflow-auto">
@ -638,42 +618,6 @@
</div>
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-lg-6">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="guildView_vaultMiscItems"></h5>
<div class="card-body d-flex flex-column">
<form id="vaultMiscItems-form" class="input-group mb-3 d-none" onsubmit="addVaultItem('VaultMiscItems');return false;">
<input class="form-control" id="VaultMiscItems-count" type="number" value="1" />
<input class="form-control w-50" id="acquire-type-VaultMiscItems" list="datalist-VaultMiscItems" autocomplete="off" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<div class="overflow-auto">
<table class="table table-hover w-100">
<tbody id="VaultMiscItems-list"></tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="guildView_vaultShipDecorations"></h5>
<div class="card-body d-flex flex-column">
<form id="vaultShipDecorations-form" class="input-group mb-3 d-none" onsubmit="addVaultItem('VaultShipDecorations');return false;">
<input class="form-control" id="VaultShipDecorations-count" type="number" value="1" />
<input class="form-control w-50" id="acquire-type-VaultShipDecorations" list="datalist-ShipDecorations" autocomplete="off" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<div class="overflow-auto">
<table class="table table-hover w-100">
<tbody id="VaultShipDecorations-list"></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-lg-6">
<div class="card" style="height: 400px;">
@ -703,8 +647,7 @@
<div class="card-body" id="guild-actions">
<div class="mb-2 d-flex flex-wrap gap-2">
<button class="btn btn-primary" onclick="debounce(addMissingTechProjects);" data-loc="guildView_bulkAddTechProjects"></button>
<button class="btn btn-primary" onclick="debounce(addMissingVaultItems, ['VaultDecoRecipes']);" data-loc="guildView_bulkAddVaultDecoRecipes"></button>
<button class="btn btn-primary" onclick="debounce(addMissingVaultItems, ['VaultShipDecorations']);" data-loc="guildView_bulkAddVaultShipDecorations"></button>
<button class="btn btn-primary" onclick="debounce(addMissingVaultDecoRecipes);" data-loc="guildView_bulkAddVaultDecoRecipes"></button>
</div>
<div class="mb-2 d-flex flex-wrap gap-2">
<button class="btn btn-success" onclick="debounce(fundAllTechProjects);" data-loc="guildView_bulkFundTechProjects"></button>
@ -767,7 +710,7 @@
<form class="input-group mb-3" onsubmit="doPushArchonCrystalUpgrade();return false;">
<input type="number" id="archon-crystal-add-count" min="1" max="10000" value="1" class="form-control" style="max-width:100px" />
<span class="input-group-text">x</span>
<input class="form-control" list="datalist-archonCrystalUpgrades" autocomplete="off" />
<input class="form-control" list="datalist-archonCrystalUpgrades" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<table class="table table-hover w-100">
@ -877,7 +820,7 @@
<div class="card-body">
<form class="input-group mb-3" onsubmit="doAcquireMod();return false;">
<input class="form-control" id="mod-count" type="number" value="1"/>
<input class="form-control w-50" id="mod-to-acquire" list="datalist-mods" autocomplete="off" />
<input class="form-control w-50" id="mod-to-acquire" list="datalist-mods" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
<button class="btn btn-success" onclick="window.maxed=true" data-loc="mods_addMax"></button>
</form>
@ -905,7 +848,7 @@
<h5 class="card-header" data-loc="quests_list"></h5>
<div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('QuestKeys');return false;">
<input class="form-control" id="acquire-type-QuestKeys" list="datalist-QuestKeys" autocomplete="off" />
<input class="form-control" id="acquire-type-QuestKeys" list="datalist-QuestKeys" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<div class="overflow-auto">
@ -1072,76 +1015,6 @@
<input class="form-check-input" type="checkbox" id="finishInvasionsInOneMission" />
<label class="form-check-label" for="finishInvasionsInOneMission" data-loc="cheats_finishInvasionsInOneMission"></label>
</div>
<form class="form-group mt-2" onsubmit="doChangeSupportedSyndicate(); return false;">
<label class="form-label" for="changeSyndicate" data-loc="cheats_changeSupportedSyndicate"></label>
<div class="input-group">
<select class="form-control" id="changeSyndicate"></select>
<button class="btn btn-secondary" type="submit" data-loc="cheats_changeButton"></button>
</div>
</form>
<form class="form-group mt-2">
<label class="form-label" for="spoofMasteryRank" data-loc="cheats_spoofMasteryRank"></label>
<div class="input-group">
<input class="form-control" id="spoofMasteryRank" type="number" min="-1" max="65535" data-default="-1" />
<button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2">
<label class="form-label" for="nemesisHenchmenKillsMultiplierGrineer" data-loc="cheats_nemesisHenchmenKillsMultiplierGrineer"></label>
<div class="input-group">
<input class="form-control" id="nemesisHenchmenKillsMultiplierGrineer" type="number" min="0" max="65535" data-default="1" />
<button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2">
<label class="form-label" for="nemesisHenchmenKillsMultiplierCorpus" data-loc="cheats_nemesisHenchmenKillsMultiplierCorpus"></label>
<div class="input-group">
<input class="form-control" id="nemesisHenchmenKillsMultiplierCorpus" type="number" min="0" max="65535" data-default="1" />
<button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2">
<label class="form-label" for="nemesisAntivirusGainMultiplier" data-loc="cheats_nemesisAntivirusGainMultiplier"></label>
<div class="input-group">
<input class="form-control" id="nemesisAntivirusGainMultiplier" type="number" min="0" max="65535" data-default="1" />
<button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2">
<label class="form-label" for="nemesisHintProgressMultiplierGrineer" data-loc="cheats_nemesisHintProgressMultiplierGrineer"></label>
<div class="input-group">
<input class="form-control" id="nemesisHintProgressMultiplierGrineer" type="number" min="0" max="65535" data-default="1" />
<button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2">
<label class="form-label" for="nemesisHintProgressMultiplierCorpus" data-loc="cheats_nemesisHintProgressMultiplierCorpus"></label>
<div class="input-group">
<input class="form-control" id="nemesisHintProgressMultiplierCorpus" type="number" min="0" max="65535" data-default="1" />
<button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2">
<label class="form-label" for="nemesisExtraWeapon" data-loc="cheats_nemesisExtraWeapon"></label>
<div class="input-group">
<input class="form-control" id="nemesisExtraWeapon" type="number" min="0" max="65535" data-default="0" />
<button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2">
<label class="form-label" for="relicRewardItemCountMultiplier" data-loc="cheats_relicRewardItemCountMultiplier"></label>
<div class="input-group">
<input class="form-control" id="relicRewardItemCountMultiplier" type="number" min="1" max="1000000" data-default="1" />
<button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2">
<label class="form-label" for="nightwaveStandingMultiplier" data-loc="cheats_nightwaveStandingMultiplier"></label>
<div class="input-group">
<input class="form-control" id="nightwaveStandingMultiplier" type="number" min="1" max="1000000" data-default="1" />
<button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
</div>
</form>
<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>
@ -1152,10 +1025,17 @@
<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>
<button class="btn btn-primary" onclick="doIntrinsicsUnlockAll();" data-loc="cheats_intrinsicsUnlockAll"></button>
<button class="btn btn-primary" onclick="debounce(doMaxPlexus);" data-loc="cheats_maxPlexus"></button>
<button class="btn btn-primary" onclick="debounce(doMaxPlexus);" data-loc="inventory_maxPlexus"></button>
<button class="btn btn-primary" onclick="debounce(unlockAllProfitTakerStages);" data-loc="cheats_unlockAllProfitTakerStages"></button>
<button class="btn btn-primary" onclick="debounce(unlockAllSimarisResearchEntries);" data-loc="cheats_unlockAllSimarisResearchEntries"></button>
</div>
<form class="mt-2" onsubmit="doChangeSupportedSyndicate(); return false;">
<label class="form-label" for="changeSyndicate" data-loc="cheats_changeSupportedSyndicate"></label>
<div class="input-group">
<select class="form-control" id="changeSyndicate"></select>
<button class="btn btn-primary" type="submit" data-loc="cheats_changeButton"></button>
</div>
</form>
</div>
</div>
</div>
@ -1183,6 +1063,27 @@
<input class="form-check-input" type="checkbox" id="skipClanKeyCrafting" />
<label class="form-check-label" for="skipClanKeyCrafting" data-loc="cheats_skipClanKeyCrafting"></label>
</div>
<form class="form-group mt-2" onsubmit="doSaveConfigInt('spoofMasteryRank'); return false;">
<label class="form-label" for="spoofMasteryRank" data-loc="cheats_spoofMasteryRank"></label>
<div class="input-group">
<input class="form-control" id="spoofMasteryRank" type="number" min="-1" max="65535" data-default="-1" />
<button class="btn btn-secondary" type="submit" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2" onsubmit="doSaveConfigInt('relicRewardItemCountMultiplier'); return false;">
<label class="form-label" for="relicRewardItemCountMultiplier" data-loc="cheats_relicRewardItemCountMultiplier"></label>
<div class="input-group">
<input class="form-control" id="relicRewardItemCountMultiplier" type="number" min="1" max="1000000" data-default="1" />
<button class="btn btn-secondary" type="submit" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2" onsubmit="doSaveConfigInt('nightwaveStandingMultiplier'); return false;">
<label class="form-label" for="nightwaveStandingMultiplier" data-loc="cheats_nightwaveStandingMultiplier"></label>
<div class="input-group">
<input class="form-control" id="nightwaveStandingMultiplier" type="number" min="1" max="1000000" data-default="1" />
<button class="btn btn-secondary" type="submit" data-loc="cheats_save"></button>
</div>
</form>
</div>
</div>
</div>
@ -1226,6 +1127,11 @@
<label class="form-check-label" for="unfaithfulBugFixes.useAnniversaryTagForOldGoals" data-loc="worldState_useAnniversaryTagForOldGoals"></label>
<abbr data-loc-inc="worldState_anniversary"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320C64 461.4 178.6 576 320 576zM320 200C333.3 200 344 210.7 344 224L344 336C344 349.3 333.3 360 320 360C306.7 360 296 349.3 296 336L296 224C296 210.7 306.7 200 320 200zM293.3 416C292.7 406.1 297.6 396.7 306.1 391.5C314.6 386.4 325.3 386.4 333.8 391.5C342.3 396.7 347.2 406.1 346.6 416C347.2 425.9 342.3 435.3 333.8 440.5C325.3 445.6 314.6 445.6 306.1 440.5C297.6 435.3 292.7 425.9 293.3 416z"/></svg></abbr>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.wolfHunt" />
<label class="form-check-label" for="worldState.wolfHunt" data-loc="worldState_wolfHunt"></label>
<abbr data-loc-inc="worldState_galleonOfGhouls|worldState_orphixVenom|worldState_anniversary"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320C64 461.4 178.6 576 320 576zM320 200C333.3 200 344 210.7 344 224L344 336C344 349.3 333.3 360 320 360C306.7 360 296 349.3 296 336L296 224C296 210.7 306.7 200 320 200zM293.3 416C292.7 406.1 297.6 396.7 306.1 391.5C314.6 386.4 325.3 386.4 333.8 391.5C342.3 396.7 347.2 406.1 346.6 416C347.2 425.9 342.3 435.3 333.8 440.5C325.3 445.6 314.6 445.6 306.1 440.5C297.6 435.3 292.7 425.9 293.3 416z"/></svg></abbr>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.orphixVenom" />
<label class="form-check-label" for="worldState.orphixVenom" data-loc="worldState_orphixVenom"></label>
@ -1241,19 +1147,6 @@
<label class="form-check-label" for="worldState.hallowedFlame" data-loc="worldState_hallowedFlame"></label>
<abbr data-loc-inc="worldState_hallowedNightmares|worldState_dogDays|worldState_anniversary"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320C64 461.4 178.6 576 320 576zM320 200C333.3 200 344 210.7 344 224L344 336C344 349.3 333.3 360 320 360C306.7 360 296 349.3 296 336L296 224C296 210.7 306.7 200 320 200zM293.3 416C292.7 406.1 297.6 396.7 306.1 391.5C314.6 386.4 325.3 386.4 333.8 391.5C342.3 396.7 347.2 406.1 346.6 416C347.2 425.9 342.3 435.3 333.8 440.5C325.3 445.6 314.6 445.6 306.1 440.5C297.6 435.3 292.7 425.9 293.3 416z"/></svg></abbr>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.qtccAlerts" />
<label class="form-check-label" for="worldState.qtccAlerts" data-loc="worldState_qtccAlerts"></label>
</div>
<div class="form-group mt-2">
<label class="form-label" for="worldState.wolfHunt" data-loc="worldState_wolfHunt"></label>
<abbr data-loc-inc="worldState_galleonOfGhouls|worldState_orphixVenom|worldState_anniversary"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320C64 461.4 178.6 576 320 576zM320 200C333.3 200 344 210.7 344 224L344 336C344 349.3 333.3 360 320 360C306.7 360 296 349.3 296 336L296 224C296 210.7 306.7 200 320 200zM293.3 416C292.7 406.1 297.6 396.7 306.1 391.5C314.6 386.4 325.3 386.4 333.8 391.5C342.3 396.7 347.2 406.1 346.6 416C347.2 425.9 342.3 435.3 333.8 440.5C325.3 445.6 314.6 445.6 306.1 440.5C297.6 435.3 292.7 425.9 293.3 416z"/></svg></abbr>
<select class="form-control" id="worldState.wolfHunt" data-default="null">
<option value="null" data-loc="disabled"></option>
<option value="1" data-loc="worldState_from_year" data-loc-replace="2025"></option>
<option value="0" data-loc="worldState_from_year" data-loc-replace="2019"></option>
</select>
</div>
<div class="form-group mt-2 d-flex gap-2">
<div class="flex-fill">
<label class="form-label" for="worldState.hallowedNightmares" data-loc="worldState_hallowedNightmares"></label>
@ -1272,14 +1165,6 @@
</select>
</div>
</div>
<div class="form-group mt-2">
<label class="form-label" for="worldState.naberusNightsOverride" data-loc="worldState_naberusNights"></label>
<select class="form-control" id="worldState.naberusNightsOverride" data-default="null">
<option value="null" data-loc="normal"></option>
<option value="true" data-loc="enabled"></option>
<option value="false" data-loc="disabled"></option>
</select>
</div>
<div class="form-group mt-2 d-flex gap-2">
<div class="flex-fill">
<label class="form-label" for="worldState.proxyRebellion" data-loc="worldState_proxyRebellion"></label>
@ -1297,27 +1182,6 @@
</select>
</div>
</div>
<div class="mt-2">
<label class="form-label" data-loc="worldState_voidCorruption" data-loc-replace="2025"></label>
<div class="d-flex flex-wrap gap-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.voidCorruption2025Week1" />
<label class="form-check-label" for="worldState.voidCorruption2025Week1" data-loc="worldState_week" data-loc-replace="1"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.voidCorruption2025Week2" />
<label class="form-check-label" for="worldState.voidCorruption2025Week2" data-loc="worldState_week" data-loc-replace="2"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.voidCorruption2025Week3" />
<label class="form-check-label" for="worldState.voidCorruption2025Week3" data-loc="worldState_week" data-loc-replace="3"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.voidCorruption2025Week4" />
<label class="form-check-label" for="worldState.voidCorruption2025Week4" data-loc="worldState_week" data-loc-replace="4"></label>
</div>
</div>
</div>
<div class="form-group mt-2">
<label class="form-label" for="worldState.galleonOfGhouls" data-loc="worldState_galleonOfGhouls"></label>
<abbr data-loc-inc="worldState_wolfHunt|worldState_anniversary|worldState_orphixVenom"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320C64 461.4 178.6 576 320 576zM320 200C333.3 200 344 210.7 344 224L344 336C344 349.3 333.3 360 320 360C306.7 360 296 349.3 296 336L296 224C296 210.7 306.7 200 320 200zM293.3 416C292.7 406.1 297.6 396.7 306.1 391.5C314.6 386.4 325.3 386.4 333.8 391.5C342.3 396.7 347.2 406.1 346.6 416C347.2 425.9 342.3 435.3 333.8 440.5C325.3 445.6 314.6 445.6 306.1 440.5C297.6 435.3 292.7 425.9 293.3 416z"/></svg></abbr>
@ -1474,7 +1338,6 @@
<label class="form-label" for="worldState.nightwaveOverride" data-loc="worldState_nightwaveOverride"></label>
<select class="form-control" id="worldState.nightwaveOverride" data-default="">
<option value="" data-loc="disabled"></option>
<option value="RadioLegionIntermission14Syndicate" data-loc="worldState_RadioLegionIntermission14Syndicate"></option>
<option value="RadioLegionIntermission13Syndicate" data-loc="worldState_RadioLegionIntermission13Syndicate"></option>
<option value="RadioLegionIntermission12Syndicate" data-loc="worldState_RadioLegionIntermission12Syndicate"></option>
<option value="RadioLegionIntermission11Syndicate" data-loc="worldState_RadioLegionIntermission11Syndicate"></option>
@ -1508,7 +1371,7 @@
<form class="form-group mt-2" onsubmit="doSaveConfigStringArray('worldState.circuitGameModes'); return false;">
<label class="form-label" for="worldState.circuitGameModes" data-loc="worldState_theCircuitOverride"></label>
<div class="input-group">
<input id="worldState.circuitGameModes" type="text" class="form-control tags-input" list="datalist-circuitGameModes" autocomplete="off" />
<input id="worldState.circuitGameModes" type="text" class="form-control tags-input" list="datalist-circuitGameModes" />
<button class="btn btn-secondary" type="submit" data-loc="cheats_save"></button>
</div>
</form>
@ -1590,8 +1453,6 @@
<datalist id="datalist-VaultDecoRecipes"></datalist>
<datalist id="datalist-FlavourItems"></datalist>
<datalist id="datalist-ShipDecorations"></datalist>
<datalist id="datalist-WeaponSkins"></datalist>
<datalist id="datalist-VaultMiscItems"></datalist>
<datalist id="datalist-circuitGameModes">
<option>Survival</option>
<option>VoidFlood</option>

View File

@ -559,7 +559,7 @@ function fetchItemList() {
});
} else if (type == "Syndicates") {
items.forEach(item => {
if (["ConclaveSyndicate", "NightcapJournalSyndicate"].includes(item.uniqueName)) {
if (item.uniqueName === "ConclaveSyndicate") {
return;
}
if (item.uniqueName.startsWith("RadioLegion")) {
@ -599,46 +599,6 @@ function fetchItemList() {
}
itemMap[item.uniqueName] = { ...item, type };
});
} else if (type == "WeaponSkins") {
let beardNumber = 1;
let cutNumber = 13;
let adultHeadNumber = 1;
let headNumber = 1;
items.forEach(item => {
if (item.name == "") {
if (item.uniqueName.includes("/Beards/")) {
item.name = loc("code_drifterBeardName")
.split("|INDEX|")
.join(beardNumber.toString().padStart(3, "0"));
beardNumber++;
} else if (item.uniqueName.includes("/Hair/")) {
item.name = loc("code_cutName")
.split("|INDEX|")
.join(cutNumber.toString().padStart(3, "0"));
cutNumber++;
if (cutNumber == 19) cutNumber = 21;
} else if (item.uniqueName.includes("/Heads/Adult")) {
item.name = loc("code_drifterFaceName")
.split("|INDEX|")
.join(adultHeadNumber.toString().padStart(3, "0"));
adultHeadNumber++;
} else if (item.uniqueName.includes("/Heads/")) {
item.name = loc("code_operatorFaceName")
.split("|INDEX|")
.join(headNumber.toString().padStart(3, "0"));
headNumber++;
} else {
item.name = item.uniqueName;
}
}
if (!item.alwaysAvailable) {
const option = document.createElement("option");
option.setAttribute("data-key", item.uniqueName);
option.value = item.name;
document.getElementById("datalist-" + type).appendChild(option);
}
itemMap[item.uniqueName] = { ...item, type };
});
} else {
const nameToItems = {};
items.forEach(item => {
@ -646,7 +606,7 @@ function fetchItemList() {
if ("badReason" in item) {
if (item.badReason == "starter") {
item.name = loc("code_starter").split("|MOD|").join(item.name);
} else if (item.badReason != "notraw") {
} else {
item.name += " " + loc("code_badItem");
}
}
@ -711,10 +671,6 @@ function fetchItemList() {
option.value += " (" + item.subtype + ")";
}
document.getElementById("datalist-" + type).appendChild(option);
if (item.eligibleForVault) {
const vaultOption = option.cloneNode(true);
document.getElementById("datalist-VaultMiscItems").appendChild(vaultOption);
}
} else {
//console.log(`Not adding ${item.uniqueName} to datalist for ${type} due to duplicate display name: ${item.name}`);
}
@ -832,7 +788,7 @@ function updateInventory() {
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
let maxXP = Math.pow(itemMap[item.ItemType]?.maxLevelCap ?? 30, 2) * 1000;
let maxXP = Math.pow(itemMap[item.ItemType].maxLevelCap ?? 30, 2) * 1000;
if (
category != "Suits" &&
category != "SpaceSuits" &&
@ -859,7 +815,7 @@ function updateInventory() {
}
}
if (
itemMap[item.ItemType]?.maxLevelCap > 30 &&
itemMap[item.ItemType].maxLevelCap > 30 &&
(item.Polarized ?? 0) < (itemMap[item.ItemType].maxLevelCap - 30) / 2
) {
const a = document.createElement("a");
@ -881,9 +837,10 @@ function updateInventory() {
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
revalidateAuthz().then(async () => {
revalidateAuthz().then(() => {
const promises = [];
if (item.XP < maxXP) {
await addGearExp(category, item.ItemId.$oid, maxXP - item.XP);
promises.push(addGearExp(category, item.ItemId.$oid, maxXP - item.XP));
}
if ("exalted" in itemMap[item.ItemType]) {
for (const exaltedType of itemMap[item.ItemType].exalted) {
@ -894,16 +851,20 @@ function updateInventory() {
const exaltedCap =
itemMap[exaltedType]?.type == "weapons" ? 800_000 : 1_600_000;
if (exaltedItem.XP < exaltedCap) {
await addGearExp(
"SpecialItems",
exaltedItem.ItemId.$oid,
exaltedCap - exaltedItem.XP
promises.push(
addGearExp(
"SpecialItems",
exaltedItem.ItemId.$oid,
exaltedCap - exaltedItem.XP
)
);
}
}
}
}
updateInventory();
Promise.all(promises).then(() => {
updateInventory();
});
});
};
a.title = loc("code_maxRank");
@ -1147,44 +1108,6 @@ function updateInventory() {
document.getElementById("FlavourItems-list").appendChild(tr);
});
document.getElementById("WeaponSkins-list").innerHTML = "";
data.WeaponSkins.forEach(item => {
if (item.ItemId.$oid.startsWith("ca70ca70ca70ca70")) return;
const datalist = document.getElementById("datalist-WeaponSkins");
const optionToRemove = datalist.querySelector(`option[data-key="${item.ItemType}"]`);
if (optionToRemove) {
datalist.removeChild(optionToRemove);
}
const tr = document.createElement("tr");
{
const td = document.createElement("td");
const name = itemMap[item.ItemType]?.name?.trim();
td.textContent = name || item.ItemType;
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
document.getElementById("WeaponSkins-list").removeChild(tr);
reAddToItemList(itemMap, "WeaponSkins", item.ItemType);
disposeOfGear("WeaponSkins", item.ItemId.$oid);
};
a.title = loc("code_remove");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"/></svg>`;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("WeaponSkins-list").appendChild(tr);
});
const datalistEvolutionProgress = document.querySelectorAll("#datalist-EvolutionProgress option");
const formEvolutionProgress = document.querySelector('form[onsubmit*="doAcquireEvolution()"]');
@ -1284,7 +1207,7 @@ function updateInventory() {
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(doQuestUpdate, "setInactive", item.ItemType);
doQuestUpdate("setInactive", item.ItemType);
};
a.title = loc("code_setInactive");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M464 256A208 208 0 1 0 48 256a208 208 0 1 0 416 0zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256zm192-96l128 0c17.7 0 32 14.3 32 32l0 128c0 17.7-14.3 32-32 32l-128 0c-17.7 0-32-14.3-32-32l0-128c0-17.7 14.3-32 32-32z"/></svg>`;
@ -1295,7 +1218,7 @@ function updateInventory() {
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(doQuestUpdate, "resetKey", item.ItemType);
doQuestUpdate("resetKey", item.ItemType);
};
a.title = loc("code_reset");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M463.5 224l8.5 0c13.3 0 24-10.7 24-24l0-128c0-9.7-5.8-18.5-14.8-22.2s-19.3-1.7-26.2 5.2L413.4 96.6c-87.6-86.5-228.7-86.2-315.8 1c-87.5 87.5-87.5 229.3 0 316.8s229.3 87.5 316.8 0c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0c-62.5 62.5-163.8 62.5-226.3 0s-62.5-163.8 0-226.3c62.2-62.2 162.7-62.5 225.3-1L327 183c-6.9 6.9-8.9 17.2-5.2 26.2s12.5 14.8 22.2 14.8l119.5 0z"/></svg>`;
@ -1306,7 +1229,7 @@ function updateInventory() {
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(doQuestUpdate, "completeKey", item.ItemType);
doQuestUpdate("completeKey", item.ItemType);
};
a.title = loc("code_complete");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M438.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L160 338.7 393.4 105.4c12.5-12.5 32.8-12.5 45.3 0z"/></svg>`;
@ -1317,7 +1240,7 @@ function updateInventory() {
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(doQuestUpdate, "prevStage", item.ItemType);
doQuestUpdate("prevStage", item.ItemType);
};
a.title = loc("code_prevStage");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M41.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 256 246.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z"/></svg>`;
@ -1332,7 +1255,7 @@ function updateInventory() {
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(doQuestUpdate, "nextStage", item.ItemType);
doQuestUpdate("nextStage", item.ItemType);
};
a.title = loc("code_nextStage");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M278.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-160 160c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L210.7 256 73.4 118.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l160 160z"/></svg>`;
@ -1344,7 +1267,7 @@ function updateInventory() {
a.onclick = function (event) {
event.preventDefault();
reAddToItemList(itemMap, "QuestKeys", item.ItemType);
debounce(doQuestUpdate, "deleteKey", item.ItemType);
doQuestUpdate("deleteKey", item.ItemType);
};
a.title = loc("code_remove");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"/></svg>`;
@ -1454,11 +1377,7 @@ function updateInventory() {
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
if (itemMap[item.ItemType]?.badReason == "notraw") {
// Assuming this is a riven with a pending challenge, so rank would be N/A, but otherwise it's fine.
} else {
td.innerHTML += " <span title='" + loc("code_rank") + "'>★ " + rank + "/" + maxRank + "</span>";
}
td.innerHTML += " <span title='" + loc("code_rank") + "'>★ " + rank + "/" + maxRank + "</span>";
tr.appendChild(td);
}
{
@ -1546,17 +1465,14 @@ function updateInventory() {
if (item) {
document.getElementById("detailedView-loading").classList.add("d-none");
const itemName = itemMap[item.ItemType]?.name ?? item.ItemType;
if (item.ItemName) {
const pipeIndex = item.ItemName.indexOf("|");
if (pipeIndex != -1) {
$("#detailedView-title").text(item.ItemName.substr(1 + pipeIndex) + " " + itemName);
} else {
$("#detailedView-title").text(item.ItemName);
$("#detailedView-route .text-body-secondary").text(itemName);
}
$("#detailedView-title").text(item.ItemName);
$("#detailedView-route .text-body-secondary").text(
itemMap[item.ItemType]?.name ?? item.ItemType
);
} else {
$("#detailedView-title").text(itemName);
$("#detailedView-title").text(itemMap[item.ItemType]?.name ?? item.ItemType);
}
if (category == "Suits") {
@ -1813,8 +1729,6 @@ function updateInventory() {
document.getElementById("VaultRegularCredits-owned").classList.remove("mb-0");
document.getElementById("vaultPremiumCredits-form").classList.remove("d-none");
document.getElementById("VaultPremiumCredits-owned").classList.remove("mb-0");
document.getElementById("vaultMiscItems-form").classList.remove("d-none");
document.getElementById("vaultShipDecorations-form").classList.remove("d-none");
}
if (userGuildMember.Rank <= 1) {
document.querySelectorAll("#guild-actions button").forEach(btn => {
@ -1862,7 +1776,7 @@ function updateInventory() {
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(fundGuildTechProject, item.ItemType);
fundGuildTechProject(item.ItemType);
};
a.title = loc("code_fund");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free v7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M214.6 17.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 117.3 160 488c0 17.7 14.3 32 32 32s32-14.3 32-32l0-370.7 105.4 105.4c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z"/></svg>`;
@ -1879,7 +1793,7 @@ function updateInventory() {
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(completeGuildTechProject, item.ItemType);
completeGuildTechProject(item.ItemType);
};
a.title = loc("code_complete");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--!Font Awesome Free v7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M371.7 43.1C360.1 32 343 28.9 328.3 35.2S304 56 304 72l0 136.3-172.3-165.1C120.1 32 103 28.9 88.3 35.2S64 56 64 72l0 368c0 16 9.6 30.5 24.3 36.8s31.8 3.2 43.4-7.9L304 303.7 304 440c0 16 9.6 30.5 24.3 36.8s31.8 3.2 43.4-7.9l192-184c7.9-7.5 12.3-18 12.3-28.9s-4.5-21.3-12.3-28.9l-192-184z"/></svg>`;
@ -1892,7 +1806,7 @@ function updateInventory() {
a.onclick = function (event) {
event.preventDefault();
reAddToItemList(itemMap, "TechProjects", item.ItemType);
debounce(removeGuildTechProject, item.ItemType);
removeGuildTechProject(item.ItemType);
};
a.title = loc("code_remove");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"/></svg>`;
@ -1905,55 +1819,42 @@ function updateInventory() {
document.getElementById("TechProjects-list").appendChild(tr);
});
["VaultDecoRecipes", "VaultMiscItems", "VaultShipDecorations"].forEach(vaultKey => {
document.getElementById(vaultKey + "-list").innerHTML = "";
(guildData[vaultKey] ??= []).forEach(item => {
if (vaultKey == "VaultDecoRecipes") {
const datalist = document.getElementById("datalist-VaultDecoRecipes");
const optionToRemove = datalist.querySelector(
`option[data-key="${item.ItemType}"]`
);
if (optionToRemove) {
datalist.removeChild(optionToRemove);
}
document.getElementById("VaultDecoRecipes-list").innerHTML = "";
guildData.VaultDecoRecipes ??= [];
guildData.VaultDecoRecipes.forEach(item => {
const datalist = document.getElementById("datalist-VaultDecoRecipes");
const optionToRemove = datalist.querySelector(`option[data-key="${item.ItemType}"]`);
if (optionToRemove) {
datalist.removeChild(optionToRemove);
}
const tr = document.createElement("tr");
tr.setAttribute("data-item-type", item.ItemType);
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
if (userGuildMember && userGuildMember.Rank <= 1) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
reAddToItemList(itemMap, "VaultDecoRecipes", item.ItemType);
removeVaultDecoRecipe(item.ItemType);
};
a.title = loc("code_remove");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"/></svg>`;
td.appendChild(a);
}
const tr = document.createElement("tr");
tr.setAttribute("data-item-type", item.ItemType);
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
if (item.ItemCount > 1) {
td.innerHTML += ` <span title='${loc("code_count")}'>🗍 ${parseInt(item.ItemCount)}</span>`;
}
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
const canRemove =
vaultKey === "VaultDecoRecipes"
? userGuildMember && userGuildMember.Rank <= 1
: userGuildPermissions && userGuildPermissions & 64;
if (canRemove) {
const a = document.createElement("a");
a.href = "#";
a.title = loc("code_remove");
a.onclick = e => {
e.preventDefault();
if (vaultKey == "VaultDecoRecipes") {
reAddToItemList(itemMap, vaultKey, item.ItemType);
}
removeVaultItem(vaultKey, item.ItemType, item.ItemCount * -1);
};
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"/></svg>`;
td.appendChild(a);
}
tr.appendChild(td);
}
tr.appendChild(td);
}
document.getElementById(vaultKey + "-list").appendChild(tr);
});
document.getElementById("VaultDecoRecipes-list").appendChild(tr);
});
document.getElementById("Members-list").innerHTML = "";
@ -2090,11 +1991,7 @@ function updateInventory() {
}
for (const elm of accountCheats) {
if (elm.type === "checkbox") {
elm.checked = !!data[elm.id];
} else if (elm.type === "number") {
elm.value = data[elm.id] !== undefined ? data[elm.id] : elm.getAttribute("data-default") || "";
}
elm.checked = !!data[elm.id];
}
});
});
@ -2184,15 +2081,6 @@ function removeCustomization(uniqueName) {
});
}
function removeIsNew() {
revalidateAuthz().then(() => {
const req = $.get("/custom/removeIsNew?" + window.authz);
req.done(() => {
updateInventory();
});
});
}
function getRequiredParts(category, WeaponType) {
switch (category) {
case "Hoverboards":
@ -2381,52 +2269,41 @@ function addMissingEquipment(categories) {
}
}
function addVaultItem(vaultType) {
const ItemType = getKey(document.getElementById(`acquire-type-${vaultType}`));
if (!ItemType) {
$(`#acquire-type-${vaultType}`).addClass("is-invalid").focus();
function addVaultDecoRecipe() {
const uniqueName = getKey(document.getElementById("acquire-type-VaultDecoRecipes"));
if (!uniqueName) {
$("#acquire-type-VaultDecoRecipes").addClass("is-invalid").focus();
return;
}
const ItemCount = ["VaultMiscItems", "VaultShipDecorations"].includes(vaultType)
? parseInt($(`#${vaultType}-count`).val())
: 1;
if (ItemCount != 0) {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/addVaultTypeCount?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/json",
data: JSON.stringify({
vaultType,
items: [
{
ItemType,
ItemCount
}
]
})
});
req.done(() => {
document.getElementById(`acquire-type-${vaultType}`).value = "";
updateInventory();
});
});
}
}
function removeVaultItem(vaultType, ItemType, ItemCount) {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/addVaultTypeCount?" + window.authz + "&guildId=" + window.guildId,
url: "/custom/addVaultDecoRecipe?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/json",
data: JSON.stringify({
vaultType,
items: [
{
ItemType,
ItemCount
}
]
})
data: JSON.stringify([
{
ItemType: uniqueName,
ItemCount: 1
}
])
});
req.done(() => {
document.getElementById("acquire-type-VaultDecoRecipes").value = "";
updateInventory();
});
});
}
function removeVaultDecoRecipe(uniqueName) {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/addVaultDecoRecipe?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/json",
data: JSON.stringify([
{
ItemType: uniqueName,
ItemCount: -1
}
])
});
req.done(() => {
updateInventory();
@ -2508,16 +2385,13 @@ function fundGuildTechProject(uniqueName) {
});
}
function dispatchAddVaultItemsBatch(requests, vaultType) {
function dispatchAddVaultDecoRecipesBatch(requests) {
return new Promise(resolve => {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/addVaultItems?" + window.authz + "&guildId=" + window.guildId,
url: "/custom/addVaultDecoRecipe?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/json",
data: JSON.stringify({
vaultType,
items: requests
})
data: JSON.stringify(requests)
});
req.done(() => {
updateInventory();
@ -2527,23 +2401,20 @@ function dispatchAddVaultItemsBatch(requests, vaultType) {
});
}
function addMissingVaultItems(vaultType) {
function addMissingVaultDecoRecipes() {
const requests = [];
document.querySelectorAll(`#datalist-${vaultType} option`).forEach(elm => {
const datalist = vaultType === "VaultShipDecorations" ? "ShipDecorations" : vaultType;
if (!document.querySelector(`#${datalist}-list [data-item-type='${elm.getAttribute("data-key")}']`)) {
let ItemCount = 1;
if (category == "VaultShipDecorations") ItemCount = 999999;
requests.push({ ItemType: elm.getAttribute("data-key"), ItemCount });
document.querySelectorAll("#datalist-VaultDecoRecipes" + " option").forEach(elm => {
if (!document.querySelector("#VaultDecoRecipes-list [data-item-type='" + elm.getAttribute("data-key") + "']")) {
requests.push({ ItemType: elm.getAttribute("data-key"), ItemCount: 1 });
}
});
if (
requests.length != 0 &&
window.confirm(loc("code_addVaultItemsConfirm").split("|COUNT|").join(requests.length))
window.confirm(loc("code_addDecoRecipesConfirm").split("|COUNT|").join(requests.length))
) {
return dispatchAddVaultItemsBatch(requests, vaultType);
return dispatchAddVaultDecoRecipesBatch(requests);
}
}
@ -2974,8 +2845,8 @@ function removeCountItems(uniqueName, count) {
function addItemByItemType() {
const ItemType = document.getElementById("typeName-type").value;
// Must start with "/Lotus/", contain only letters AZ, digits 09, no "//", and not end with "/"
if (!ItemType || !/^\/Lotus\/(?:[A-Za-z0-9]+(?:\/[A-Za-z0-9]+)*)$/.test(ItemType)) {
// Must start with "/Lotus/", contain only AZ letters, no "//", and not end with "/"
if (!ItemType || !/^\/Lotus\/(?:[A-Za-z]+(?:\/[A-Za-z]+)*)$/.test(ItemType)) {
$("#typeName-type").addClass("is-invalid").focus();
return;
}
@ -3292,13 +3163,13 @@ function unlockFocusSchool(upgradeType) {
return new Promise(resolve => {
// Deselect current FocusAbility so we will be able to unlock the way for free
$.post({
url: "/api/focus.php?" + window.authz + "&op=ActivateWay",
url: "/api/focus.php?" + window.authz + "&op=5",
contentType: "text/plain",
data: JSON.stringify({ FocusType: null })
}).done(function () {
// Unlock the way now
$.post({
url: "/api/focus.php?" + window.authz + "&op=UnlockWay",
url: "/api/focus.php?" + window.authz + "&op=2",
contentType: "text/plain",
data: JSON.stringify({
FocusType: upgradeType
@ -3325,53 +3196,13 @@ function doIntrinsicsUnlockAll() {
document.querySelectorAll("#account-cheats input[type=checkbox]").forEach(elm => {
elm.onchange = function () {
revalidateAuthz().then(() => {
const value = elm.checked;
$.post({
url: "/custom/setAccountCheat?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
key: elm.id,
value: value
value: elm.checked
})
}).done(() => {
elm.checked = value;
});
});
};
});
document.querySelectorAll("#account-cheats .input-group").forEach(grp => {
const input = grp.querySelector("input");
const select = grp.querySelector("select");
const btn = grp.querySelector("button");
if (input) {
input.oninput = input.onchange = function () {
btn.classList.remove("btn-secondary");
btn.classList.add("btn-primary");
};
}
if (select) {
select.oninput = select.onchange = function () {
btn.classList.remove("btn-secondary");
btn.classList.add("btn-primary");
};
}
btn.onclick = function () {
btn.classList.remove("btn-primary");
btn.classList.add("btn-secondary");
const input = btn.closest(".input-group").querySelector('input[type="number"]');
if (!input) return;
revalidateAuthz().then(() => {
const value = input.value;
$.post({
url: "/custom/setAccountCheat?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
key: input.id,
value: parseInt(value)
})
}).done(() => {
btn.value = value;
});
});
};
@ -3497,12 +3328,6 @@ single.getRoute("#guild-route").on("beforeload", function () {
document.getElementById("VaultDecoRecipes-list").innerHTML = "";
document.getElementById("vaultDecoRecipes-form").classList.add("d-none");
document.getElementById("acquire-type-VaultDecoRecipes").value = "";
document.getElementById("VaultMiscItems-list").innerHTML = "";
document.getElementById("vaultMiscItems-form").classList.add("d-none");
document.getElementById("acquire-type-VaultMiscItems").value = "";
document.getElementById("VaultShipDecorations-list").innerHTML = "";
document.getElementById("vaultShipDecorations-form").classList.add("d-none");
document.getElementById("acquire-type-VaultShipDecorations").value = "";
document.getElementById("Alliance-list").innerHTML = "";
document.getElementById("guildView-alliance").textContent = "";
document.getElementById("Members-list").innerHTML = "";

View File

@ -1,17 +1,17 @@
// German translation by Animan8000
dict = {
general_inventoryUpdateNote: `Hinweis: Um Änderungen im Spiel zu sehen, musst du dein Inventar neu synchronisieren, z. B. durch Besuch eines Dojo/Relais oder durch erneutes Anmelden.`,
general_inventoryUpdateNoteGameWs: `Hinweis: Möglicherweise musst du ein Menü neu öffnen, damit die Änderungen sichtbar werden.`,
general_inventoryUpdateNote: `Hinweis: Um Änderungen im Spiel zu sehen, musst du dein Inventar neu synchronisieren, z. B. mit dem /sync Befehl des Bootstrappers, durch Besuch eines Dojo/Relais oder durch erneutes Einloggen.`,
general_inventoryUpdateNoteGameWs: `[UNTRANSLATED] Note: You may need to reopen any menu you are on for changes to be reflected.`,
general_addButton: `Hinzufügen`,
general_setButton: `Festlegen`,
general_none: `Nichts`,
general_none: `Keines`,
general_bulkActions: `Massenaktionen`,
general_loading: `Lädt...`,
code_loginFail: `Anmeldung fehlgeschlagen. Bitte überprüfe deine Angaben.`,
code_regFail: `Registrierung fehlgeschlagen. Account existiert bereits?`,
code_changeNameConfirm: `In welchen Namen möchtest du deinen Account umbenennen?`,
code_changeNameRetry: `|NAME| ist bereits vergeben.`,
code_changeNameRetry: `[UNTRANSLATED] |NAME| is already taken.`,
code_deleteAccountConfirm: `Bist du sicher, dass du deinen Account |DISPLAYNAME| (|EMAIL|) löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.`,
code_archgun: `Arch-Gewehr`,
code_melee: `Nahkampf`,
@ -32,8 +32,8 @@ dict = {
code_renamePrompt: `Neuen benutzerdefinierten Namen eingeben:`,
code_remove: `Entfernen`,
code_addItemsConfirm: `Bist du sicher, dass du |COUNT| Gegenstände zu deinem Account hinzufügen möchtest?`,
code_addTechProjectsConfirm: `Bist du sicher, dass du |COUNT| Forschungen zu deinem Clan hinzufügen möchtest?`,
code_addVaultItemsConfirm: `Bist du sicher, dass du |COUNT| Gegenstände zu deinem Clan-Tresor hinzufügen möchtest?`,
code_addTechProjectsConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| research to your clan?`,
code_addDecoRecipesConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| deco recipes to your clan?`,
code_succRankUp: `Erfolgreich aufgestiegen.`,
code_noEquipmentToRankUp: `Keine Ausstattung zum Rangaufstieg verfügbar.`,
code_succAdded: `Erfolgreich hinzugefügt.`,
@ -45,13 +45,13 @@ dict = {
code_rank: `Rang`,
code_rankUp: `Rang erhöhen`,
code_rankDown: `Rang verringern`,
code_unlockLevelCap: `Level-Cap freischalten`,
code_unlockLevelCap: `[UNTRANSLATED] Unlock level cap`,
code_count: `Anzahl`,
code_focusAllUnlocked: `Alle Fokus-Schulen sind bereits freigeschaltet.`,
code_focusUnlocked: `|COUNT| neue Fokus-Schulen freigeschaltet! Ein Inventar-Update wird benötigt, damit die Änderungen im Spiel sichtbar werden.`,
code_focusUnlocked: `|COUNT| neue Fokus-Schulen freigeschaltet! Ein Inventar-Update wird benötigt, damit die Änderungen im Spiel sichtbar werden. Die Sternenkarte zu besuchen, sollte der einfachste Weg sein, dies auszulösen.`,
code_addModsConfirm: `Bist du sicher, dass du |COUNT| Mods zu deinem Account hinzufügen möchtest?`,
code_succImport: `Erfolgreich importiert.`,
code_succRelog: `Fertig. Bitte beachte, dass du dich neu anmelden musst, um Änderungen im Spiel zu sehen.`,
code_succRelog: `Fertig. Bitte beachte, dass du dich neu einloggen musst, um Änderungen im Spiel zu sehen.`,
code_nothingToDo: `Fertig. Es gab nichts zu tun.`,
code_gild: `Veredeln`,
code_moa: `Moa`,
@ -65,22 +65,18 @@ dict = {
code_completed: `Abgeschlossen`,
code_active: `Aktiv`,
code_pigment: `Pigment`,
code_controller: `Controller-Cursor`,
code_mouseLine: `Linien-Cursor`,
code_mouse: `Cursor`,
code_controller: `[UNTRANSLATED] Controller cursor`,
code_mouseLine: `[UNTRANSLATED] Line cursor`,
code_mouse: `[UNTRANSLATED] Cursor`,
code_itemColorPalette: `|ITEM| Farbpalette`,
code_mature: `Für den Kampf auswachsen lassen`,
code_unmature: `Genetisches Altern zurücksetzen`,
code_fund: `Spenden`,
code_funded: `Gespendet`,
code_replays: `Wiederholungen`,
code_fund: `[UNTRANSLATED] Fund`,
code_funded: `[UNTRANSLATED] Funded`,
code_replays: `[UNTRANSLATED] Replays`,
code_stalker: `Stalker`,
code_cutName: `Frisur: |INDEX|`,
code_drifterBeardName: `Drifter-Bart: |INDEX|`,
code_drifterFaceName: `Drifter-Gesicht: |INDEX|`,
code_operatorFaceName: `Operator-Gesicht: |INDEX|`,
code_succChange: `Erfolgreich geändert.`,
code_requiredInvigorationUpgrade: `Du musst sowohl ein Offensiv- als auch ein Support-Upgrade auswählen.`,
code_requiredInvigorationUpgrade: `[UNTRANSLATED] You must select both an offensive & utility upgrade.`,
login_description: `Melde dich mit deinem OpenWF-Account an (denselben Angaben wie im Spiel, wenn du dich mit diesem Server verbindest).`,
login_emailLabel: `E-Mail-Adresse`,
login_passwordLabel: `Passwort`,
@ -96,8 +92,8 @@ dict = {
navbar_cheats: `Cheats`,
navbar_import: `Importieren`,
inventory_addItems: `Gegenstände hinzufügen`,
inventory_addItemByItemType: `Roh`,
inventory_addItemByItemType_warning: `Verwende diese Funktion auf eigene Gefahr. Sie kann dein Inventar beschädigen und du musst Gegenstände manuell entfernen, falls etwas schiefgeht.`,
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_suits: `Warframes`,
inventory_longGuns: `Primärwaffen`,
inventory_pistols: `Sekundärwaffen`,
@ -116,17 +112,15 @@ dict = {
inventory_boosters: `Booster`,
inventory_flavourItems: `<abbr title="Animationssets, Glyphen, Farbpaletten usw.">Sammlerstücke</abbr>`,
inventory_shipDecorations: `Schiffsdekorationen`,
inventory_weaponSkins: `Skins`,
inventory_bulkAddSuits: `Fehlende Warframes hinzufügen`,
inventory_bulkAddWeapons: `Fehlende Waffen hinzufügen`,
inventory_bulkAddSpaceSuits: `Fehlende Archwings hinzufügen`,
inventory_bulkAddSpaceWeapons: `Fehlende Archwing-Waffen hinzufügen`,
inventory_bulkAddSentinels: `Fehlende Wächter hinzufügen`,
inventory_bulkAddSentinelWeapons: `Fehlende Wächter-Waffen hinzufügen`,
inventory_bulkAddFlavourItems: `Fehlende Sammlerstücke hinzufügen`,
inventory_bulkAddShipDecorations: `Fehlende Schiffsdekorationen hinzufügen`,
inventory_bulkAddFlavourItems: `[UNTRANSLATED] Add Missing Flavour Items`,
inventory_bulkAddShipDecorations: `[UNTRANSLATED] Add Missing Ship Decorations`,
inventory_bulkAddEvolutionProgress: `Fehlende Incarnon-Entwicklungsfortschritte hinzufügen`,
inventory_bulkAddWeaponSkins: `Fehlende Skins hinzufügen`,
inventory_bulkRankUpSuits: `Alle Warframes auf Max. Rang`,
inventory_bulkRankUpWeapons: `Alle Waffen auf Max. Rang`,
inventory_bulkRankUpSpaceSuits: `Alle Archwings auf Max. Rang`,
@ -134,7 +128,7 @@ dict = {
inventory_bulkRankUpSentinels: `Alle Wächter auf Max. Rang`,
inventory_bulkRankUpSentinelWeapons: `Alle Wächter-Waffen auf Max. Rang`,
inventory_bulkRankUpEvolutionProgress: `Alle Incarnon-Entwicklungsfortschritte auf Max. Rang`,
inventory_removeIsNew: `Entferne Ausrufezeichen bei neuem Equipment`,
inventory_maxPlexus: `Plexus auf Max. Rang`,
quests_list: `Quests`,
quests_completeAll: `Alle Quests abschließen`,
@ -179,8 +173,8 @@ dict = {
invigorations_utility_EnergyRegen: `+2 Energieregeneration pro Sekunde`,
detailedView_invigorationOffensiveLabel: `Offensives Upgrade`,
detailedView_invigorationUtilityLabel: `Support Upgrade`,
detailedView_invigorationExpiryLabel: `Kräftigungs-Ablaufdatum (optional)`,
detailedView_invigorationUtilityLabel: `[UNTRANSLATED] Utility Upgrade`,
detailedView_invigorationExpiryLabel: `[UNTRANSLATED] Invigoration Expiry (optional)`,
abilityOverride_label: `Fähigkeitsüberschreibung`,
abilityOverride_onSlot: `auf Slot`,
@ -199,9 +193,9 @@ dict = {
cheats_skipTutorial: `Tutorial überspringen`,
cheats_skipAllDialogue: `Alle Dialoge überspringen`,
cheats_unlockAllScans: `Alle Scans freischalten`,
cheats_unlockSuccRelog: `Erfolgreich. Bitte beachte, dass du dich neu anmelden musst, damit der Client dies aktualisiert.`,
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 anmelden musst, damit die Sternenkarte aktualisiert wird.`,
cheats_unlockAllMissions_ok: `Erfolgreich. Bitte beachte, dass du ein Dojo/Relais besuchen oder dich neu einloggen musst, damit die Sternenkarte aktualisiert wird.`,
cheats_infiniteCredits: `Unendlich Credits`,
cheats_infinitePlatinum: `Unendlich Platinum`,
cheats_infiniteEndo: `Unendlich Endo`,
@ -213,7 +207,7 @@ dict = {
cheats_dontSubtractPurchaseItemCost: `Gegenstände beim Kauf nicht verbrauchen`,
cheats_dontSubtractPurchaseStandingCost: `Ansehen beim Kauf nicht verbrauchen`,
cheats_dontSubtractVoidTraces: `Void-Spuren nicht verbrauchen`,
cheats_dontSubtractConsumables: `Verbrauchsgüter (Ausrüstung) nicht verbrauchen`,
cheats_dontSubtractConsumables: `Verbrauchsgegenstände (Ausrüstung) nicht verbrauchen`,
cheats_unlockAllShipFeatures: `Alle Schiffs-Funktionen freischalten`,
cheats_unlockAllSkins: `Alle Skins freischalten`,
cheats_unlockAllCapturaScenes: `Alle Photora-Szenen freischalten`,
@ -233,7 +227,7 @@ dict = {
cheats_baroFullyStocked: `Baro hat volles Inventar`,
cheats_syndicateMissionsRepeatable: `Syndikat-Missionen wiederholbar`,
cheats_unlockAllProfitTakerStages: `Alle Profiteintreiber-Phasen freischalten`,
cheats_unlockSuccInventory: `Erfolgreich. Bitte beachte, dass du dein Inventar neu synchronisieren musst, z. B. durch Besuch eines Dojo/Relais oder durch erneutes Anmelden.`,
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`,
@ -259,53 +253,43 @@ dict = {
cheats_helminthUnlockAll: `Helminth vollständig aufleveln`,
cheats_addMissingSubsumedAbilities: `Fehlende konsumierte Fähigkeiten hinzufügen`,
cheats_intrinsicsUnlockAll: `Alle Inhärenzen auf Max. Rang`,
cheats_maxPlexus: `Plexus auf Max. Rang`,
cheats_changeSupportedSyndicate: `Unterstütztes Syndikat`,
cheats_changeButton: `Ändern`,
cheats_markAllAsRead: `Posteingang als gelesen markieren`,
cheats_finishInvasionsInOneMission: `Invasionen in einer Mission abschließen`,
cheats_nemesisHenchmenKillsMultiplierGrineer: `Wut-Fortschrittsmultiplikator (Grineer)`,
cheats_nemesisHenchmenKillsMultiplierCorpus: `Wut-Fortschrittsmultiplikator (Corpus)`,
cheats_nemesisAntivirusGainMultiplier: `Antivirus-Fortschrittsmultiplikator`,
cheats_nemesisHintProgressMultiplierGrineer: `Hinweis-Fortschrittsmultiplikator (Grineer)`,
cheats_nemesisHintProgressMultiplierCorpus: `Hinweis-Fortschrittsmultiplikator (Corpus)`,
cheats_nemesisExtraWeapon: `Zusätzliche Nemesis-Waffe/-Symbol bei Besiegung (0 zum deaktivieren)`,
cheats_finishInvasionsInOneMission: `[UNTRANSLATED] Finish Invasions in One Mission`,
worldState: `Weltstatus`,
worldState_creditBoost: `Event Booster: Credit`,
worldState_affinityBoost: `Event Booster: Erfahrung`,
worldState_resourceBoost: `Event Booster: Ressourcen`,
worldState_tennoLiveRelay: `TennoLive Relais`,
worldState_baroTennoConRelay: `Baros TennoCon-Relais`,
worldState_baroTennoConRelay: `Baros TennoCon Relais`,
worldState_starDays: `Sternen-Tage`,
worldState_galleonOfGhouls: `Galeone der Ghule`,
worldState_anniversary: `Warframe Jubiläum`,
worldState_useAnniversaryTagForOldGoals: `Verwende <code>Tag</code> vom Warframe Jubiläum für alte Events`,
worldState_anniversary: `[UNTRANSLATED] Warframe Anniversary`,
worldState_useAnniversaryTagForOldGoals: `[UNTRANSLATED] Use <code>Tag</code> from Warframe Anniversary for old Events`,
worldState_ghoulEmergence: `Ghul Ausrottung`,
worldState_plagueStar: `Plagenstern`,
worldState_dogDays: `Hitzefrei`,
worldState_dogDaysRewards: `Hitzefrei-Belohnungen`,
worldState_wolfHunt: `Wolfsjagd`,
worldState_voidCorruption: `Void-Korruption (|VAL|)`,
worldState_dogDaysRewards: `[UNTRANSLATED] Dog Days Rewards`,
worldState_wolfHunt: `Wolfsjagd (2025)`,
worldState_orphixVenom: `Orphix Gift`,
worldState_longShadow: `Lange Schatten`,
worldState_hallowedFlame: `Geweihte Flamme`,
worldState_hallowedNightmares: `Geweihte Albträume`,
worldState_hallowedNightmaresRewards: `Geweihte Albträume-Belohnungen`,
worldState_naberusNights: `Naberus Nacht`,
worldState_hallowedNightmaresRewards: `[UNTRANSLATED] Hallowed Nightmares Rewards`,
worldState_proxyRebellion: `Proxy-Rebellion`,
worldState_proxyRebellionRewards: `Proxy-Rebellion-Belohnungen`,
worldState_proxyRebellionRewards: `[UNTRANSLATED] Proxy Rebellion Rewards`,
worldState_bellyOfTheBeast: `Das Innere der Bestie`,
worldState_bellyOfTheBeastProgressOverride: `Das Innere der Bestie-Fortschritt`,
worldState_bellyOfTheBeastProgressOverride: `[UNTRANSLATED] Belly of the Beast Progress`,
worldState_eightClaw: `Acht Klauen`,
worldState_eightClawProgressOverride: `Acht Klauen-Fortschritt`,
worldState_eightClawProgressOverride: `[UNTRANSLATED] Eight Claw Progress`,
worldState_thermiaFractures: `Thermische Risse`,
worldState_thermiaFracturesProgressOverride: `Thermische Risse-Fortschritt`,
worldState_qtccAlerts: `Quest zum Kampf gegen Krebs-Alarmierungen`,
worldState_from_year: `Von |VAL|`,
worldState_pre_year: `Vor |VAL|`,
worldState_week: `Woche |VAL|`,
worldState_incompatibleWith: `Inkompatibel mit:`,
worldState_thermiaFracturesProgressOverride: `[UNTRANSLATED] Thermia Fractures Progress`,
worldState_from_year: `[UNTRANSLATED] from |VAL|`,
worldState_pre_year: `[UNTRANSLATED] pre |VAL|`,
worldState_week: `[UNTRANSLATED] Week |VAL|`,
worldState_incompatibleWith: `[UNTRANSLATED] Incompatible with:`,
enabled: `Aktiviert`,
disabled: `Deaktiviert`,
worldState_we1: `Wochenende 1`,
@ -324,7 +308,6 @@ dict = {
worldState_sorrow: `Trauer`,
worldState_fear: `Angst`,
worldState_nightwaveOverride: `Nightwave-Überschreibung`,
worldState_RadioLegionIntermission14Syndicate: `Noras Mix: Träume der Toten`,
worldState_RadioLegionIntermission13Syndicate: `Noras Mix - Vol. 9`,
worldState_RadioLegionIntermission12Syndicate: `Noras Mix - Vol. 8`,
worldState_RadioLegionIntermission11Syndicate: `Noras Mix - Vol. 7`,
@ -350,7 +333,7 @@ dict = {
worldState_varziaFullyStocked: `Varzia hat volles Inventar`,
worldState_varziaOverride: `Varzia-Angebotsüberschreibung`,
import_importNote: `Du kannst hier eine vollständige oder teilweise <code>inventory.php</code> oder <code>getShip.php</code> Antwort (Client-Darstellung) einfügen.`,
import_importNote: `[UNTRANSLATED] You can provide a full or partial <code>inventory.php</code> or <code>getShip.php</code> response (client representation) here.`,
import_importNote2: `Alle Felder, die vom Importer unterstützt werden, <b>werden in deinem Account überschrieben</b>.`,
import_submit: `Absenden`,
import_samples: `Beispiele:`,
@ -386,10 +369,10 @@ dict = {
upgrade_AvatarAbilityRange: `+7.5% Fähigkeitsreichweite`,
upgrade_AvatarAbilityEfficiency: `+5% Fähigkeitseffizienz`,
upgrade_AvatarEnergyRegen: `+0.5 Energieregeneration pro Sekunde`,
upgrade_AvatarEnemyRadar: `+5m Gegnerradar`,
upgrade_AvatarEnemyRadar: `+5m Feindradar`,
upgrade_AvatarLootRadar: `+7m Beuteradar`,
upgrade_WeaponAmmoMax: `+15% Max. Munition`,
upgrade_EnemyArmorReductionAura: `-3% Rüstung für Gegner`,
upgrade_EnemyArmorReductionAura: `-3% Rüstung bei Feinden`,
upgrade_OnExecutionAmmo: `+100% Magazinfüllung für Primär- und Sekundärwaffen bei Gnadenstoß`,
upgrade_OnExecutionHealthDrop: `+100% Gesundheitskugel Chance bei Gnadenstoß`,
upgrade_OnExecutionEnergyDrop: `+50% Energiekugel Chance bei Gnadenstoß`,
@ -399,7 +382,7 @@ dict = {
upgrade_OnExecutionParkourSpeed: `+60% Parkourgeschwindigkeit für 15s nach Gnadenstoß`,
upgrade_AvatarTimeLimitIncrease: `+8s extra Zeit beim Hacken`,
upgrade_ElectrifyOnHack: `Setze beim Hacken Gegner innerhalb von 20m unter Strom`,
upgrade_OnExecutionTerrify: `+50% Chance bei Gnadenstoß, dass Gegner 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_OnExecutionBlind: `Blende bei einem Gnadenstoß Gegner innerhalb von 18m`,
upgrade_OnExecutionDrainPower: `Nächste Fähigkeit erhält +50% Fähigkeitsstärke nach Gnadenstoß`,
@ -418,11 +401,9 @@ dict = {
theme_dark: `Dunkles Design`,
theme_light: `Helles Design`,
guildView_cheats: `Clan-Cheats`,
guildView_cheats: `[UNTRANSLATED] Clan Cheats`,
guildView_techProjects: `Forschung`,
guildView_vaultDecoRecipes: `Dojo-Deko-Baupläne`,
guildView_vaultMiscItems: `Ressourcen im Tresor`,
guildView_vaultShipDecorations: `Dekorationen im Tresor`,
guildView_vaultDecoRecipes: `[UNTRANSLATED] Dojo Deco Recipes`,
guildView_alliance: `Allianz`,
guildView_members: `Mitglieder`,
guildView_pending: `Ausstehend`,
@ -442,12 +423,11 @@ dict = {
guildView_rank_soldier: `Soldat`,
guildView_rank_utility: `Versorger`,
guildView_rank_warlord: `Kriegsherr`,
guildView_currency_owned: `|COUNT| im Tresor.`,
guildView_bulkAddTechProjects: `Fehlende Forschungen hinzufügen`,
guildView_bulkAddVaultDecoRecipes: `Fehlende Dojo-Deko-Baupläne hinzufügen`,
guildView_bulkAddVaultShipDecorations: `Fehlende Dekorationen im Tresor hinzufügen`,
guildView_bulkFundTechProjects: `Alle Forschungen spenden`,
guildView_bulkCompleteTechProjects: `Alle Forschungen abschließen`,
guildView_currency_owned: `[UNTRANSLATED] |COUNT| in Vault.`,
guildView_bulkAddTechProjects: `[UNTRANSLATED] Add Missing Research`,
guildView_bulkAddVaultDecoRecipes: `[UNTRANSLATED] Add Missing Dojo Deco Recipes`,
guildView_bulkFundTechProjects: `[UNTRANSLATED] Fund All Research`,
guildView_bulkCompleteTechProjects: `[UNTRANSLATED] Complete All Research`,
guildView_promote: `Befördern`,
guildView_demote: `Degradieren`,

View File

@ -1,5 +1,5 @@
dict = {
general_inventoryUpdateNote: `Note: To see changes in-game, you need to resync your inventory, e.g. by visiting a dojo/relay or relogging.`,
general_inventoryUpdateNote: `Note: To see changes in-game, you need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging.`,
general_inventoryUpdateNoteGameWs: `Note: You may need to reopen any menu you are on for changes to be reflected.`,
general_addButton: `Add`,
general_setButton: `Set`,
@ -32,7 +32,7 @@ dict = {
code_remove: `Remove`,
code_addItemsConfirm: `Are you sure you want to add |COUNT| items to your account?`,
code_addTechProjectsConfirm: `Are you sure you want to add |COUNT| research to your clan?`,
code_addVaultItemsConfirm: `Are you sure you want to add |COUNT| items to your clan vault?`,
code_addDecoRecipesConfirm: `Are you sure you want to add |COUNT| deco recipes to your clan?`,
code_succRankUp: `Successfully ranked up.`,
code_noEquipmentToRankUp: `No equipment to rank up.`,
code_succAdded: `Successfully added.`,
@ -47,7 +47,7 @@ dict = {
code_unlockLevelCap: `Unlock level cap`,
code_count: `Count`,
code_focusAllUnlocked: `All focus schools are already unlocked.`,
code_focusUnlocked: `Unlocked |COUNT| new focus schools! An inventory update will be needed for the changes to be reflected in-game.`,
code_focusUnlocked: `Unlocked |COUNT| new focus schools! An inventory update will be needed for the changes to be reflected in-game. Visiting the navigation should be the easiest way to trigger that.`,
code_addModsConfirm: `Are you sure you want to add |COUNT| mods to your account?`,
code_succImport: `Successfully imported.`,
code_succRelog: `Done. Please note that you'll need to relog to see a difference in-game.`,
@ -74,10 +74,6 @@ dict = {
code_funded: `Funded`,
code_replays: `Replays`,
code_stalker: `Stalker`,
code_cutName: `Cut |INDEX|`,
code_drifterBeardName: `Drifter Beard |INDEX|`,
code_drifterFaceName: `Drifter Visage |INDEX|`,
code_operatorFaceName: `Operator Visage |INDEX|`,
code_succChange: `Successfully changed.`,
code_requiredInvigorationUpgrade: `You must select both an offensive & utility upgrade.`,
login_description: `Login using your OpenWF account credentials (same as in-game when connecting to this server).`,
@ -115,7 +111,6 @@ dict = {
inventory_boosters: `Boosters`,
inventory_flavourItems: `<abbr title="Animation Sets, Glyphs, Palettes, etc.">Flavour Items</abbr>`,
inventory_shipDecorations: `Ship Decorations`,
inventory_weaponSkins: `Skins`,
inventory_bulkAddSuits: `Add Missing Warframes`,
inventory_bulkAddWeapons: `Add Missing Weapons`,
inventory_bulkAddSpaceSuits: `Add Missing Archwings`,
@ -125,7 +120,6 @@ dict = {
inventory_bulkAddFlavourItems: `Add Missing Flavour Items`,
inventory_bulkAddShipDecorations: `Add Missing Ship Decorations`,
inventory_bulkAddEvolutionProgress: `Add Missing Incarnon Evolution Progress`,
inventory_bulkAddWeaponSkins: `Add Missing Skins`,
inventory_bulkRankUpSuits: `Max Rank All Warframes`,
inventory_bulkRankUpWeapons: `Max Rank All Weapons`,
inventory_bulkRankUpSpaceSuits: `Max Rank All Archwings`,
@ -133,7 +127,7 @@ dict = {
inventory_bulkRankUpSentinels: `Max Rank All Sentinels`,
inventory_bulkRankUpSentinelWeapons: `Max Rank All Sentinel Weapons`,
inventory_bulkRankUpEvolutionProgress: `Max Rank All Incarnon Evolution Progress`,
inventory_removeIsNew: `Remove New Equipment Exclamation Icon`,
inventory_maxPlexus: `Max Rank Plexus`,
quests_list: `Quests`,
quests_completeAll: `Complete All Quests`,
@ -198,7 +192,7 @@ dict = {
cheats_skipTutorial: `Skip Tutorial`,
cheats_skipAllDialogue: `Skip All Dialogue`,
cheats_unlockAllScans: `Unlock All Scans`,
cheats_unlockSuccRelog: `Success. Please note that you'll need to relog for the client to refresh this.`,
cheats_unlockSuccRelog: `Success. Please that you'll need to relog for the client to refresh this.`,
cheats_unlockAllMissions: `Unlock All Missions`,
cheats_unlockAllMissions_ok: `Success. Please note that you'll need to enter a dojo/relay or relog for the client to refresh the star chart.`,
cheats_infiniteCredits: `Infinite Credits`,
@ -232,7 +226,7 @@ dict = {
cheats_baroFullyStocked: `Baro Fully Stocked`,
cheats_syndicateMissionsRepeatable: `Syndicate Missions Repeatable`,
cheats_unlockAllProfitTakerStages: `Unlock All Profit Taker Stages`,
cheats_unlockSuccInventory: `Success. Please note that you'll need to resync your inventory, e.g. by visiting a dojo/relay or relogging.`,
cheats_unlockSuccInventory: `Success. Please note that you'll need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging..`,
cheats_instantFinishRivenChallenge: `Instant Finish Riven Challenge`,
cheats_instantResourceExtractorDrones: `Instant Resource Extractor Drones`,
cheats_noResourceExtractorDronesDamage: `No Resource Extractor Drones Damage`,
@ -258,17 +252,10 @@ dict = {
cheats_helminthUnlockAll: `Fully Level Up Helminth`,
cheats_addMissingSubsumedAbilities: `Add Missing Subsumed Abilities`,
cheats_intrinsicsUnlockAll: `Max Rank All Intrinsics`,
cheats_maxPlexus: `Max Rank Plexus`,
cheats_changeSupportedSyndicate: `Supported syndicate`,
cheats_changeButton: `Change`,
cheats_markAllAsRead: `Mark Inbox As Read`,
cheats_finishInvasionsInOneMission: `Finish Invasions in One Mission`,
cheats_nemesisHenchmenKillsMultiplierGrineer: `Rage Progess Multiplier (Grineer)`,
cheats_nemesisHenchmenKillsMultiplierCorpus: `Rage Progess Multiplier (Corpus)`,
cheats_nemesisAntivirusGainMultiplier: `Antivirus Progress Multiplier`,
cheats_nemesisHintProgressMultiplierGrineer: `Hint Progress Multiplier (Grineer)`,
cheats_nemesisHintProgressMultiplierCorpus: `Hint Progress Multiplier (Corpus)`,
cheats_nemesisExtraWeapon: `Extra Nemesis Weapon / Token On Vanquish (0 to disable)`,
worldState: `World State`,
worldState_creditBoost: `Credit Boost`,
@ -284,14 +271,12 @@ dict = {
worldState_plagueStar: `Plague Star`,
worldState_dogDays: `Dog Days`,
worldState_dogDaysRewards: `Dog Days Rewards`,
worldState_wolfHunt: `Wolf Hunt`,
worldState_voidCorruption: `Void Corruption (|VAL|)`,
worldState_wolfHunt: `Wolf Hunt (2025)`,
worldState_orphixVenom: `Orphix Venom`,
worldState_longShadow: `Long Shadow`,
worldState_hallowedFlame: `Hallowed Flame`,
worldState_hallowedNightmares: `Hallowed Nightmares`,
worldState_hallowedNightmaresRewards: `Hallowed Nightmares Rewards`,
worldState_naberusNights: `Nights of Naberus`,
worldState_proxyRebellion: `Proxy Rebellion`,
worldState_proxyRebellionRewards: `Proxy Rebellion Rewards`,
worldState_bellyOfTheBeast: `Belly of the Beast`,
@ -300,9 +285,8 @@ dict = {
worldState_eightClawProgressOverride: `Eight Claw Progress`,
worldState_thermiaFractures: `Thermia Fractures`,
worldState_thermiaFracturesProgressOverride: `Thermia Fractures Progress`,
worldState_qtccAlerts: `Quest to Conquer Cancer Alerts`,
worldState_from_year: `From |VAL|`,
worldState_pre_year: `Pre-|VAL|`,
worldState_from_year: `from |VAL|`,
worldState_pre_year: `pre |VAL|`,
worldState_week: `Week |VAL|`,
worldState_incompatibleWith: `Incompatible with:`,
enabled: `Enabled`,
@ -323,7 +307,6 @@ dict = {
worldState_sorrow: `Sorrow`,
worldState_fear: `Fear`,
worldState_nightwaveOverride: `Nightwave Override`,
worldState_RadioLegionIntermission14Syndicate: `Nora's Mix: Dreams of the Dead`,
worldState_RadioLegionIntermission13Syndicate: `Nora's Mix Vol. 9`,
worldState_RadioLegionIntermission12Syndicate: `Nora's Mix Vol. 8`,
worldState_RadioLegionIntermission11Syndicate: `Nora's Mix Vol. 7`,
@ -420,8 +403,6 @@ dict = {
guildView_cheats: `Clan Cheats`,
guildView_techProjects: `Research`,
guildView_vaultDecoRecipes: `Dojo Deco Recipes`,
guildView_vaultMiscItems: `Resources in Vault`,
guildView_vaultShipDecorations: `Decorations in Vault`,
guildView_alliance: `Alliance`,
guildView_members: `Members`,
guildView_pending: `Pending`,
@ -444,7 +425,6 @@ dict = {
guildView_currency_owned: `|COUNT| in Vault.`,
guildView_bulkAddTechProjects: `Add Missing Research`,
guildView_bulkAddVaultDecoRecipes: `Add Missing Dojo Deco Recipes`,
guildView_bulkAddVaultShipDecorations: `Add Missing Decorations in Vault`,
guildView_bulkFundTechProjects: `Fund All Research`,
guildView_bulkCompleteTechProjects: `Complete All Research`,
guildView_promote: `Promote`,

View File

@ -1,6 +1,6 @@
// Spanish translation by hxedcl, Slayer55555
// Spanish translation by hxedcl
dict = {
general_inventoryUpdateNote: `[UNTRANSLATED] Note: To see changes in-game, you need to resync your inventory, e.g. by visiting a dojo/relay or relogging.`,
general_inventoryUpdateNote: `Para ver los cambios en el juego, necesitas volver a sincronizar tu inventario, por ejemplo, usando el comando /sync del bootstrapper, visitando un dojo o repetidor, o volviendo a iniciar sesión.`,
general_inventoryUpdateNoteGameWs: `Nota: Puede que necesites reabrir cualquier menú en el que te encuentres para que los cambios se reflejen.`,
general_addButton: `Agregar`,
general_setButton: `Establecer`,
@ -33,7 +33,7 @@ dict = {
code_remove: `Quitar`,
code_addItemsConfirm: `¿Estás seguro de que deseas agregar |COUNT| objetos a tu cuenta?`,
code_addTechProjectsConfirm: `¿Estás seguro de que quieres añadir |COUNT| proyectos de investigación a tu clan?`,
code_addVaultItemsConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| items to your clan vault?`,
code_addDecoRecipesConfirm: `¿Estás seguro de que quieres añadir |COUNT| planos de decoración a tu clan?`,
code_succRankUp: `Ascenso exitoso.`,
code_noEquipmentToRankUp: `No hay equipo para ascender.`,
code_succAdded: `Agregado exitosamente.`,
@ -48,7 +48,7 @@ dict = {
code_unlockLevelCap: `Desbloquear level cap`,
code_count: `Cantidad`,
code_focusAllUnlocked: `Todas las escuelas de enfoque ya están desbloqueadas.`,
code_focusUnlocked: `¡Desbloqueadas |COUNT| nuevas escuelas de enfoque! Se necesita una actualización del inventario para reflejar los cambios en el juego.`,
code_focusUnlocked: `¡Desbloqueadas |COUNT| nuevas escuelas de enfoque! Se necesita una actualización del inventario para reflejar los cambios en el juego. Visitar la navegación debería ser la forma más sencilla de activarlo.`,
code_addModsConfirm: `¿Estás seguro de que deseas agregar |COUNT| modificadores a tu cuenta?`,
code_succImport: `Importación exitosa.`,
code_succRelog: `Hecho. Ten en cuenta que deberás volver a iniciar sesión para ver los cambios en el juego.`,
@ -75,12 +75,8 @@ dict = {
code_funded: `Financiado`,
code_replays: `Repeticiones`,
code_stalker: `Stalker`,
code_cutName: `[UNTRANSLATED] Cut |INDEX|`,
code_drifterBeardName: `Barba del Viajero: |INDEX|`,
code_drifterFaceName: `Rostro del Viajero |INDEX|`,
code_operatorFaceName: `Rostro del operador |INDEX|`,
code_succChange: `Cambiado correctamente`,
code_requiredInvigorationUpgrade: `Debes seleccionar una mejora ofensiva y una mejora de utilidad.`,
code_requiredInvigorationUpgrade: `[UNTRANSLATED] You must select both an offensive & utility upgrade.`,
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_passwordLabel: `Contraseña`,
@ -116,7 +112,6 @@ dict = {
inventory_boosters: `Potenciadores`,
inventory_flavourItems: `<abbr title="Conjuntos de animaciones, glifos, paletas, etc.">Ítems estéticos</abbr>`,
inventory_shipDecorations: `Decoraciones de nave`,
inventory_weaponSkins: `Diseños`,
inventory_bulkAddSuits: `Agregar Warframes faltantes`,
inventory_bulkAddWeapons: `Agregar armas faltantes`,
inventory_bulkAddSpaceSuits: `Agregar Archwings faltantes`,
@ -126,7 +121,6 @@ dict = {
inventory_bulkAddFlavourItems: `Añadir items estéticos faltantes`,
inventory_bulkAddShipDecorations: `Añadir decoraciones de Nave Faltantes`,
inventory_bulkAddEvolutionProgress: `Completar el progreso de evolución Incarnon faltante`,
inventory_bulkAddWeaponSkins: `[UNTRANSLATED] Add Missing Skins`,
inventory_bulkRankUpSuits: `Maximizar rango de todos los Warframes`,
inventory_bulkRankUpWeapons: `Maximizar rango de todas las armas`,
inventory_bulkRankUpSpaceSuits: `Maximizar rango de todos los Archwings`,
@ -134,7 +128,7 @@ dict = {
inventory_bulkRankUpSentinels: `Maximizar rango de todos los centinelas`,
inventory_bulkRankUpSentinelWeapons: `Maximizar rango de todas las armas de centinela`,
inventory_bulkRankUpEvolutionProgress: `Maximizar todo el progreso de evolución Incarnon`,
inventory_removeIsNew: `[UNTRANSLATED] Remove New Equipment Exclamation Icon`,
inventory_maxPlexus: `Rango máximo de Plexus`,
quests_list: `Misiones`,
quests_completeAll: `Completar todas las misiones`,
@ -179,8 +173,8 @@ dict = {
invigorations_utility_EnergyRegen: `+2 Regeneración de Energía/s`,
detailedView_invigorationOffensiveLabel: `Mejora Ofensiva`,
detailedView_invigorationUtilityLabel: `Mejora de Utilidad`,
detailedView_invigorationExpiryLabel: `Caducidad del Fortalecimiento (opcional)`,
detailedView_invigorationUtilityLabel: `[UNTRANSLATED] Utility Upgrade`,
detailedView_invigorationExpiryLabel: `[UNTRANSLATED] Invigoration Expiry (optional)`,
abilityOverride_label: `Intercambio de Habilidad`,
abilityOverride_onSlot: `en el espacio`,
@ -233,7 +227,7 @@ dict = {
cheats_baroFullyStocked: `Baro con stock completo`,
cheats_syndicateMissionsRepeatable: `Misiones de sindicato rejugables`,
cheats_unlockAllProfitTakerStages: `Desbloquea todas las etapas del Roba-ganancias`,
cheats_unlockSuccInventory: `[UNTRANSLATED] Success. Please note that you'll need to resync your inventory, e.g. by visiting a dojo/relay or relogging.`,
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`,
@ -259,17 +253,10 @@ dict = {
cheats_helminthUnlockAll: `Subir al máximo el Helminto`,
cheats_addMissingSubsumedAbilities: `Agregar habilidades subsumidas faltantes`,
cheats_intrinsicsUnlockAll: `Maximizar todos los intrínsecos`,
cheats_maxPlexus: `Rango máximo de Plexus`,
cheats_changeSupportedSyndicate: `Sindicatos disponibles`,
cheats_changeButton: `Cambiar`,
cheats_markAllAsRead: `Marcar bandeja de entrada como leída`,
cheats_finishInvasionsInOneMission: `Finaliza Invasión en una mision`,
cheats_nemesisHenchmenKillsMultiplierGrineer: `[UNTRANSLATED] Rage Progess Multiplier (Grineer)`,
cheats_nemesisHenchmenKillsMultiplierCorpus: `[UNTRANSLATED] Rage Progess Multiplier (Corpus)`,
cheats_nemesisAntivirusGainMultiplier: `[UNTRANSLATED] Antivirus Progress Multiplier`,
cheats_nemesisHintProgressMultiplierGrineer: `[UNTRANSLATED] Hint Progress Multiplier (Grineer)`,
cheats_nemesisHintProgressMultiplierCorpus: `[UNTRANSLATED] Hint Progress Multiplier (Corpus)`,
cheats_nemesisExtraWeapon: `[UNTRANSLATED] Extra Nemesis Weapon / Token On Vanquish (0 to disable)`,
worldState: `Estado del mundo`,
worldState_creditBoost: `Potenciador de Créditos`,
@ -285,14 +272,12 @@ dict = {
worldState_plagueStar: `Estrella Infestada`,
worldState_dogDays: `Canícula`,
worldState_dogDaysRewards: `Recompensas de Canícula`,
worldState_wolfHunt: `Cacería del Lobo`,
worldState_voidCorruption: `Corrupción del Vacío (|VAL|)`,
worldState_wolfHunt: `Cacería del Lobo (2025)`,
worldState_orphixVenom: `Veneno de Orphix`,
worldState_longShadow: `Sombra Prolongada`,
worldState_hallowedFlame: `Llama Sagrada`,
worldState_hallowedNightmares: `Pesadillas Sagradas`,
worldState_hallowedNightmaresRewards: `Recompensas de Pesadillas Sagradas`,
worldState_naberusNights: `Noches de Naberus`,
worldState_proxyRebellion: `Rebelión Proxy`,
worldState_proxyRebellionRewards: `Recompensas de Rebelión Proxy`,
worldState_bellyOfTheBeast: `Vientre de la Bestia`,
@ -301,9 +286,8 @@ dict = {
worldState_eightClawProgressOverride: `Progreso de Octava Garra`,
worldState_thermiaFractures: `Fracturas Thermia`,
worldState_thermiaFracturesProgressOverride: `Progreso de Fracturas Thermia`,
worldState_qtccAlerts: `[UNTRANSLATED] Quest to Conquer Cancer Alerts`,
worldState_from_year: `De |VAL|`,
worldState_pre_year: `Antes de |VAL|`,
worldState_from_year: `de |VAL|`,
worldState_pre_year: `antes de |VAL|`,
worldState_week: `Semana |VAL|`,
worldState_incompatibleWith: `No compatible con:`,
enabled: `Activado`,
@ -324,7 +308,6 @@ dict = {
worldState_sorrow: `Tristeza`,
worldState_fear: `Miedo`,
worldState_nightwaveOverride: `Volúmen de Onda Nocturna`,
worldState_RadioLegionIntermission14Syndicate: `[UNTRANSLATED] Nora's Mix: Dreams of the Dead`,
worldState_RadioLegionIntermission13Syndicate: `Mix de Nora Vol. 9`,
worldState_RadioLegionIntermission12Syndicate: `Mix de Nora Vol. 8`,
worldState_RadioLegionIntermission11Syndicate: `Mix de Nora Vol. 7`,
@ -421,8 +404,6 @@ dict = {
guildView_cheats: `Trucos de Clan`,
guildView_techProjects: `Investigación`,
guildView_vaultDecoRecipes: `Planos de Decoración de Dojo`,
guildView_vaultMiscItems: `[UNTRANSLATED] Resources in Vault`,
guildView_vaultShipDecorations: `[UNTRANSLATED] Decorations in Vault`,
guildView_alliance: `Alianza`,
guildView_members: `Miembros`,
guildView_pending: `Pendiente`,
@ -445,7 +426,6 @@ dict = {
guildView_currency_owned: `|COUNT| en la Bóveda.`,
guildView_bulkAddTechProjects: `Añadir proyectos de Investigación Faltantes`,
guildView_bulkAddVaultDecoRecipes: `Añadir planos de Decoración de Dojo Faltantes`,
guildView_bulkAddVaultShipDecorations: `[UNTRANSLATED] Add Missing Decorations in Vault`,
guildView_bulkFundTechProjects: `Financiar toda la Investigación`,
guildView_bulkCompleteTechProjects: `Completar toda la Investigación`,
guildView_promote: `Promover`,

View File

@ -1,6 +1,6 @@
// French translation by Vitruvio
dict = {
general_inventoryUpdateNote: `[UNTRANSLATED] Note: To see changes in-game, you need to resync your inventory, e.g. by 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_inventoryUpdateNoteGameWs: `Note : Rouvrir un menu est nécessaire pour voir les changements.`,
general_addButton: `Ajouter`,
general_setButton: `Définir`,
@ -33,7 +33,7 @@ dict = {
code_remove: `Retirer`,
code_addItemsConfirm: `Ajouter |COUNT| items à l'inventaire ?`,
code_addTechProjectsConfirm: `Ajouter |COUNT| recherches au clan ?`,
code_addVaultItemsConfirm: `Ajouter |COUNT| objets au coffre de clan ?`,
code_addDecoRecipesConfirm: `Ajouter |COUNT| décorations au clan ?`,
code_succRankUp: `Montée de niveau effectuée.`,
code_noEquipmentToRankUp: `Aucun équipement à monter de niveau.`,
code_succAdded: `Ajouté.`,
@ -45,7 +45,7 @@ dict = {
code_rank: `Rang`,
code_rankUp: `Monter de rang`,
code_rankDown: `Baisser de rang`,
code_unlockLevelCap: `Débloquer le level cap (9999)`,
code_unlockLevelCap: `[UNTRANSLATED] Unlock level cap`,
code_count: `Quantité`,
code_focusAllUnlocked: `Les écoles de Focus sont déjà déverrouillées.`,
code_focusUnlocked: `|COUNT| écoles de Focus déverrouillées ! Synchronisation de l'inventaire nécessaire.`,
@ -65,22 +65,18 @@ dict = {
code_completed: `Complétée`,
code_active: `Active`,
code_pigment: `Pigment`,
code_controller: `Curseur manette`,
code_mouseLine: `Curseur linéaire`,
code_mouse: `Curseur`,
code_controller: `[UNTRANSLATED] Controller cursor`,
code_mouseLine: `[UNTRANSLATED] Line cursor`,
code_mouse: `[UNTRANSLATED] Cursor`,
code_itemColorPalette: `Palette de couleurs |ITEM|`,
code_mature: `Maturer pour le combat`,
code_unmature: `Régrésser l'âge génétique`,
code_fund: `Financer`,
code_funded: `Complété`,
code_replays: `Rejouée`,
code_replays: `[UNTRANSLATED] Replays`,
code_stalker: `Stalker`,
code_cutName: `Coupe |INDEX|`,
code_drifterBeardName: `Barbe du Voyageur |INDEX|`,
code_drifterFaceName: `Visage du Voyageur |INDEX|`,
code_operatorFaceName: `Visage de l'Opérateur |INDEX|`,
code_succChange: `Changement effectué.`,
code_requiredInvigorationUpgrade: `Invigoration offensive et défensive requises.`,
code_requiredInvigorationUpgrade: `[UNTRANSLATED] You must select both an offensive & utility upgrade.`,
login_description: `Connexion avec les informations de connexion OpenWF.`,
login_emailLabel: `Email`,
login_passwordLabel: `Mot de passe`,
@ -114,19 +110,17 @@ dict = {
inventory_kubrowPets: `Bêtes`,
inventory_evolutionProgress: `Progrès de l'évolution Incarnon`,
inventory_boosters: `Boosters`,
inventory_flavourItems: `<abbr title="Animation Sets, Glyphs, Palettes, etc.">Collectables</abbr>`,
inventory_flavourItems: `[UNTRANSLATED] <abbr title="Animation Sets, Glyphs, Palettes, etc.">Flavour Items</abbr>`,
inventory_shipDecorations: `Décorations du vaisseau`,
inventory_weaponSkins: `Aspects`,
inventory_bulkAddSuits: `Ajouter les Warframes manquantes`,
inventory_bulkAddWeapons: `Ajouter les armes manquantes`,
inventory_bulkAddSpaceSuits: `Ajouter les Archwings manquants`,
inventory_bulkAddSpaceWeapons: `Ajouter les armes d'Archwing manquantes`,
inventory_bulkAddSentinels: `Ajouter les Sentinelles manquantes`,
inventory_bulkAddSentinelWeapons: `Ajouter les armes de Sentinelles manquantes`,
inventory_bulkAddFlavourItems: `Ajouter les collectables manquants ?`,
inventory_bulkAddShipDecorations: `Ajouter les décorations de vaisseau manquantes`,
inventory_bulkAddFlavourItems: `[UNTRANSLATED] Add Missing Flavour Items`,
inventory_bulkAddShipDecorations: `[UNTRANSLATED] Add Missing Ship Decorations`,
inventory_bulkAddEvolutionProgress: `Ajouter les évolutions Incarnon manquantes`,
inventory_bulkAddWeaponSkins: `Ajouter les skins manquants`,
inventory_bulkRankUpSuits: `Toutes les Warframes au rang max`,
inventory_bulkRankUpWeapons: `Toutes les armes au rang max`,
inventory_bulkRankUpSpaceSuits: `Tous les Archwings au rang max`,
@ -134,7 +128,7 @@ dict = {
inventory_bulkRankUpSentinels: `Toutes les Sentinelles au rang max`,
inventory_bulkRankUpSentinelWeapons: `Toutes les armes de Sentinelles au rang max`,
inventory_bulkRankUpEvolutionProgress: `Toutes les évolutions Incarnon au rang max`,
inventory_removeIsNew: `[UNTRANSLATED] Remove New Equipment Exclamation Icon`,
inventory_maxPlexus: `Plexus au rang max`,
quests_list: `Quêtes`,
quests_completeAll: `Compléter toutes les quêtes`,
@ -179,8 +173,8 @@ dict = {
invigorations_utility_EnergyRegen: `+2 d'énergie régénérés/s`,
detailedView_invigorationOffensiveLabel: `Amélioration offensive`,
detailedView_invigorationUtilityLabel: `Amélioration défensive`,
detailedView_invigorationExpiryLabel: `Expiration de l'invigoration (optionnel)`,
detailedView_invigorationUtilityLabel: `[UNTRANSLATED] Utility Upgrade`,
detailedView_invigorationExpiryLabel: `[UNTRANSLATED] Invigoration Expiry (optional)`,
abilityOverride_label: `Remplacement de pouvoir`,
abilityOverride_onSlot: `Sur l'emplacement`,
@ -233,7 +227,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_unlockSuccInventory: `[UNTRANSLATED] Success. Please note that you'll need to resync your inventory, e.g. by visiting a dojo/relay or relogging.`,
cheats_unlockSuccInventory: `Succès. Une resynchronisation est nécessaire en tapant "/sync" dans le tchat, en visitant un relais ou en se reconnectant.`,
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`,
@ -259,17 +253,10 @@ dict = {
cheats_helminthUnlockAll: `Helminth niveau max`,
cheats_addMissingSubsumedAbilities: `Ajouter les capacités subsumées manquantes`,
cheats_intrinsicsUnlockAll: `Inhérences niveau max`,
cheats_maxPlexus: `Plexus au rang max`,
cheats_changeSupportedSyndicate: `Allégeance`,
cheats_changeButton: `Changer`,
cheats_markAllAsRead: `Marquer la boîte de réception comme lue`,
cheats_finishInvasionsInOneMission: `Compléter les invasions en une mission.`,
cheats_nemesisHenchmenKillsMultiplierGrineer: `Multiplicateur de rage (Grineer)`,
cheats_nemesisHenchmenKillsMultiplierCorpus: `Multiplicateur de rage (Corpus)`,
cheats_nemesisAntivirusGainMultiplier: `Multiplicateur de l'Antivirus`,
cheats_nemesisHintProgressMultiplierGrineer: `Multiplicateur d'indices (Grineer)`,
cheats_nemesisHintProgressMultiplierCorpus: `Multiplicateur d'indices (Corpus)`,
cheats_nemesisExtraWeapon: `Arme de Nemesis/jeton supplémentaire sur exécution (0 pour désactiver)`,
worldState: `Carte Solaire`,
worldState_creditBoost: `Booster de Crédit`,
@ -285,14 +272,12 @@ dict = {
worldState_plagueStar: `Fléau Céleste`,
worldState_dogDays: `Bataille d'Eau`,
worldState_dogDaysRewards: `Récompenses de la Bataille d'Eau`,
worldState_wolfHunt: `Chasse au Loup`,
worldState_voidCorruption: `Corruption du Néant (|VAL|)`,
worldState_wolfHunt: `Chasse au Loup (2025)`,
worldState_orphixVenom: `Venin Orphix`,
worldState_longShadow: `La Propagation des Ombres`,
worldState_hallowedFlame: `Flamme Hantée`,
worldState_hallowedNightmares: `Cauchemars Hantés`,
worldState_hallowedNightmaresRewards: `Récompenses Flamme Hantée Cauchemar`,
worldState_naberusNights: `Les Nuits de Naberus`,
worldState_proxyRebellion: `Rébellion Proxy`,
worldState_proxyRebellionRewards: `Récompenses Rébellion Proxy`,
worldState_bellyOfTheBeast: `Ventre de la Bête`,
@ -301,9 +286,8 @@ dict = {
worldState_eightClawProgressOverride: `Progrès de la Huitième Griffe`,
worldState_thermiaFractures: `Crevasses Thermia`,
worldState_thermiaFracturesProgressOverride: `Progrès des Fractures Thermia`,
worldState_qtccAlerts: `Alertes Quête pour Vaincre le Cancer`,
worldState_from_year: `De |VAL|`,
worldState_pre_year: `Pre-|VAL|`,
worldState_from_year: `de |VAL|`,
worldState_pre_year: `pre-|VAL|`,
worldState_week: `Semaine |VAL|`,
worldState_incompatibleWith: `Incompatible avec :`,
enabled: `Activé`,
@ -324,7 +308,6 @@ dict = {
worldState_sorrow: `hagrin`,
worldState_fear: `Peur`,
worldState_nightwaveOverride: `Saison d'Ondes Nocturnes`,
worldState_RadioLegionIntermission14Syndicate: `[UNTRANSLATED] Nora's Mix: Dreams of the Dead`,
worldState_RadioLegionIntermission13Syndicate: `Mix de Nora Vol. 9`,
worldState_RadioLegionIntermission12Syndicate: `Mix de Nora Vol. 8`,
worldState_RadioLegionIntermission11Syndicate: `Mix de Nora Vol. 7`,
@ -418,11 +401,9 @@ dict = {
theme_dark: `Thème sombre`,
theme_light: `Thème clair`,
guildView_cheats: `Clan`,
guildView_cheats: `[UNTRANSLATED] Clan Cheats`,
guildView_techProjects: `Recherche`,
guildView_vaultDecoRecipes: `Schémas de décorations de dojo`,
guildView_vaultMiscItems: `Ressources dans le coffre`,
guildView_vaultShipDecorations: `Décorations dans le coffre`,
guildView_alliance: `Alliance`,
guildView_members: `Members`,
guildView_pending: `En Attente`,
@ -445,7 +426,6 @@ dict = {
guildView_currency_owned: `|COUNT| dans le coffre.`,
guildView_bulkAddTechProjects: `Ajouter les recherches manquantes`,
guildView_bulkAddVaultDecoRecipes: `Ajouter les schémas de décorations de dojo manquantes`,
guildView_bulkAddVaultShipDecorations: `Ajouter dans le coffre les décorations manquantes`,
guildView_bulkFundTechProjects: `Financer toutes les recherches`,
guildView_bulkCompleteTechProjects: `Compléter toutes les recherches`,
guildView_promote: `Promouvoir`,

View File

@ -1,6 +1,6 @@
// Russian translation by AMelonInsideLemon, LoseFace
dict = {
general_inventoryUpdateNote: `[UNTRANSLATED] Note: To see changes in-game, you need to resync your inventory, e.g. by visiting a dojo/relay or relogging.`,
general_inventoryUpdateNote: `Примечание: Чтобы увидеть изменения в игре, вам нужно повторно синхронизировать свой инвентарь, например, используя команду /sync в программе bootstrapper, посетив Додзё/Реле или перезагрузив игру.`,
general_inventoryUpdateNoteGameWs: `Примечание: для того, чтобы изменения вступили в силу, может потребоваться повторно открыть меню, в котором вы находитесь.`,
general_addButton: `Добавить`,
general_setButton: `Установить`,
@ -33,7 +33,7 @@ dict = {
code_remove: `Удалить`,
code_addItemsConfirm: `Вы уверены, что хотите добавить |COUNT| предметов на ваш аккаунт?`,
code_addTechProjectsConfirm: `Вы уверены, что хотите добавить |COUNT| исследований в свой клан?`,
code_addVaultItemsConfirm: `Вы уверены, что хотите добавить |COUNT| предметов в хранилище своего клана?`,
code_addDecoRecipesConfirm: `Вы уверены, что хотите добавить |COUNT| рецептов декораций в свой клан?`,
code_succRankUp: `Ранг успешно повышен.`,
code_noEquipmentToRankUp: `Нет снаряжения для повышения ранга.`,
code_succAdded: `Успешно добавлено.`,
@ -45,10 +45,10 @@ dict = {
code_rank: `Ранг`,
code_rankUp: `Повысить ранг`,
code_rankDown: `Понизить ранг`,
code_unlockLevelCap: `Разблокировать ограничение уровня`,
code_unlockLevelCap: `[UNTRANSLATED] Unlock level cap`,
code_count: `Количество`,
code_focusAllUnlocked: `Все школы Фокуса уже разблокированы.`,
code_focusUnlocked: `[UNTRANSLATED] Unlocked |COUNT| new focus schools! An inventory update will be needed for the changes to be reflected in-game.`,
code_focusUnlocked: `Разблокировано |COUNT| новых школ Фокуса! Для отображения изменений в игре потребуется обновление инвентаря. Посещение навигации — самый простой способ этого добиться.`,
code_addModsConfirm: `Вы уверены, что хотите добавить |COUNT| модов на ваш аккаунт?`,
code_succImport: `Успешно импортировано.`,
code_succRelog: `Готово. Обратите внимание, что вам нужно будет перезайти, чтобы увидеть изменения в игре.`,
@ -75,10 +75,6 @@ dict = {
code_funded: `Профинансировано`,
code_replays: `Повторов`,
code_stalker: `Сталкер`,
code_cutName: `Причёска: |INDEX|`,
code_drifterBeardName: `Борода скитальца: |INDEX|`,
code_drifterFaceName: `Внешность скитальца: |INDEX|`,
code_operatorFaceName: `Внешность оператора: |INDEX|`,
code_succChange: `Успешно изменено.`,
code_requiredInvigorationUpgrade: `Вы должны выбрать как атакующее, так и вспомогательное улучшение.`,
login_description: `Войдите, используя учетные данные OpenWF (те же, что и в игре при подключении к этому серверу).`,
@ -116,7 +112,6 @@ dict = {
inventory_boosters: `Бустеры`,
inventory_flavourItems: `<abbr title="Наборы анимаций, глифы, палитры и т. д.">Уникальные предметы</abbr>`,
inventory_shipDecorations: `Украшения корабля`,
inventory_weaponSkins: `Скины`,
inventory_bulkAddSuits: `Добавить отсутствующие Варфреймы`,
inventory_bulkAddWeapons: `Добавить отсутствующее оружие`,
inventory_bulkAddSpaceSuits: `Добавить отсутствующие Арчвинги`,
@ -126,7 +121,6 @@ dict = {
inventory_bulkAddFlavourItems: `Добавить отсутствующие уникальные предметы`,
inventory_bulkAddShipDecorations: `Добавить отсутствующие украшения корабля`,
inventory_bulkAddEvolutionProgress: `Добавить отсутствующий прогресс эволюции Инкарнонов`,
inventory_bulkAddWeaponSkins: `Добавить отсутствующие скины`,
inventory_bulkRankUpSuits: `Макс. ранг всех Варфреймов`,
inventory_bulkRankUpWeapons: `Макс. ранг всего оружия`,
inventory_bulkRankUpSpaceSuits: `Макс. ранг всех Арчвингов`,
@ -134,7 +128,7 @@ dict = {
inventory_bulkRankUpSentinels: `Макс. ранг всех Стражей`,
inventory_bulkRankUpSentinelWeapons: `Макс. ранг всего оружия Стражей`,
inventory_bulkRankUpEvolutionProgress: `Макс. ранг всех эволюций Инкарнонов`,
inventory_removeIsNew: `Удалить значок восклицательного знака нового снаряжения`,
inventory_maxPlexus: `Макс. ранг Плексуса`,
quests_list: `Квесты`,
quests_completeAll: `Завершить все квесты`,
@ -233,7 +227,7 @@ dict = {
cheats_baroFullyStocked: `Баро полностью укомплектован`,
cheats_syndicateMissionsRepeatable: `Повторять миссии синдиката`,
cheats_unlockAllProfitTakerStages: `Разблокировать все этапы Сферы извлечения прибыли`,
cheats_unlockSuccInventory: `[UNTRANSLATED] Success. Please note that you'll need to resync your inventory, e.g. by visiting a dojo/relay or relogging.`,
cheats_unlockSuccInventory: `Успех. Обратите внимание, что вам необходимо будет повторно синхронизировать свой инвентарь, например, с помощью команды /sync в программе bootstrapper, посетив Додзё/Реле или повторно войдя в игру.`,
cheats_instantFinishRivenChallenge: `Мгновенное завершение испытания мода Разлома`,
cheats_instantResourceExtractorDrones: `Мгновенно добывающие Дроны-сборщики`,
cheats_noResourceExtractorDronesDamage: `Без урона по Дронам-сборщикам`,
@ -259,17 +253,10 @@ dict = {
cheats_helminthUnlockAll: `Полностью улучшить Гельминта`,
cheats_addMissingSubsumedAbilities: `Добавить отсутствующие поглощённые способности`,
cheats_intrinsicsUnlockAll: `Полностью улучшить Модуляры`,
cheats_maxPlexus: `Макс. ранг Плексуса`,
cheats_changeSupportedSyndicate: `Поддерживаемый синдикат`,
cheats_changeButton: `Изменить`,
cheats_markAllAsRead: `Пометить все входящие как прочитанные`,
cheats_finishInvasionsInOneMission: `Завершать вторжение за одну миссию`,
cheats_nemesisHenchmenKillsMultiplierGrineer: `Мультипликатор прогресса ярости (Гринир)`,
cheats_nemesisHenchmenKillsMultiplierCorpus: `Мультипликатор прогресса ярости (Корпус)`,
cheats_nemesisAntivirusGainMultiplier: `Мультипликатор прогресса антивируса`,
cheats_nemesisHintProgressMultiplierGrineer: `Мультипликатор прогресса подсказки (Гринир)`,
cheats_nemesisHintProgressMultiplierCorpus: `Мультипликатор прогресса подсказки (Корпус)`,
cheats_nemesisExtraWeapon: `Дополнительное оружие/активный Кардиомиоцит за победу над Противником (0 для отключения)`,
worldState: `Состояние мира`,
worldState_creditBoost: `Глобальный бустер Кредитов`,
@ -285,14 +272,12 @@ dict = {
worldState_plagueStar: `Чумная звезда`,
worldState_dogDays: `Знойные дни`,
worldState_dogDaysRewards: `Награды Знойных дней`,
worldState_wolfHunt: `Волчья Охота`,
worldState_voidCorruption: `Искажение Бездны (|VAL|)`,
worldState_wolfHunt: `Волчья Охота (2025)`,
worldState_orphixVenom: `Яд Орфикса`,
worldState_longShadow: `Длинная Тень`,
worldState_hallowedFlame: `Священное пламя`,
worldState_hallowedNightmares: `Священные кошмары`,
worldState_hallowedNightmaresRewards: `Награды Священных кошмаров`,
worldState_naberusNights: `Ночи Наберуса`,
worldState_proxyRebellion: `Восстание роботов`,
worldState_proxyRebellionRewards: `Награды Восстания роботов`,
worldState_bellyOfTheBeast: `Чрево зверя`,
@ -301,9 +286,8 @@ dict = {
worldState_eightClawProgressOverride: `Прогресс Восьми когтей`,
worldState_thermiaFractures: `Разломы Термии`,
worldState_thermiaFracturesProgressOverride: `Прогресс Разломов Термии`,
worldState_qtccAlerts: `Тревоги Quest to Conquer Cancer`,
worldState_from_year: `Из |VAL|`,
worldState_pre_year: `До |VAL|`,
worldState_from_year: `из |VAL|`,
worldState_pre_year: `до |VAL|`,
worldState_week: `Неделя |VAL|`,
worldState_incompatibleWith: `Несовместимо с:`,
enabled: `Включено`,
@ -324,7 +308,6 @@ dict = {
worldState_sorrow: `Печаль`,
worldState_fear: `Страх`,
worldState_nightwaveOverride: `Сезон Ночной волны`,
worldState_RadioLegionIntermission14Syndicate: `[UNTRANSLATED] Nora's Mix: Dreams of the Dead`,
worldState_RadioLegionIntermission13Syndicate: `Микс Норы, Диск 9`,
worldState_RadioLegionIntermission12Syndicate: `Микс Норы, Диск 8`,
worldState_RadioLegionIntermission11Syndicate: `Микс Норы, Диск 7`,
@ -421,8 +404,6 @@ dict = {
guildView_cheats: `Читы Клана`,
guildView_techProjects: `Исследовения`,
guildView_vaultDecoRecipes: `Рецепты декораций Додзё`,
guildView_vaultMiscItems: `Ресурсы в Хранилище`,
guildView_vaultShipDecorations: `Укращения в Хранилище`,
guildView_alliance: `Альянс`,
guildView_members: `Товарищи`,
guildView_pending: `Ожидание`,
@ -445,7 +426,6 @@ dict = {
guildView_currency_owned: `В хранилище |COUNT|.`,
guildView_bulkAddTechProjects: `Добавить отсутствующие исследования`,
guildView_bulkAddVaultDecoRecipes: `Добавить отсутствующие рецепты декораций Дoдзё`,
guildView_bulkAddVaultShipDecorations: `Добавить отсутствующие укращения в Хранилище`,
guildView_bulkFundTechProjects: `Профинансировать все исследования`,
guildView_bulkCompleteTechProjects: `Завершить все исследования`,
guildView_promote: `Повысить`,

View File

@ -1,6 +1,6 @@
// Ukrainian translation by LoseFace
dict = {
general_inventoryUpdateNote: `[UNTRANSLATED] Note: To see changes in-game, you need to resync your inventory, e.g. by visiting a dojo/relay or relogging.`,
general_inventoryUpdateNote: `Пам'ятка: Щоб побачити зміни в грі, вам потрібно повторно синхронізувати своє спорядження, наприклад, використовуючи команду /sync в програмі bootstrapper, відвідавши Доджьо/Реле або перезавантаживши гру.`,
general_inventoryUpdateNoteGameWs: `Примітка: для відображення змін може знадобитися повторно відкрити меню, в якому ви перебуваєте.`,
general_addButton: `Добавити`,
general_setButton: `Встановити`,
@ -33,7 +33,7 @@ dict = {
code_remove: `Видалити`,
code_addItemsConfirm: `Ви впевнені, що хочете додати |COUNT| предметів на ваш обліковий запис?`,
code_addTechProjectsConfirm: `Ви впевнені, що хочете додати |COUNT| досліджень до свого клану?`,
code_addVaultItemsConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| items to your clan vault?`,
code_addDecoRecipesConfirm: `Ви впевнені, що хочете додати |COUNT| рецептів оздоблень до свого клану?`,
code_succRankUp: `Рівень успішно підвищено`,
code_noEquipmentToRankUp: `Немає спорядження для підвищення рівня.`,
code_succAdded: `Успішно додано.`,
@ -45,10 +45,10 @@ dict = {
code_rank: `Рівень`,
code_rankUp: `Підвищити рівень`,
code_rankDown: `Понизити рівень`,
code_unlockLevelCap: `Розблокувати обмеження рівня`,
code_unlockLevelCap: `[UNTRANSLATED] Unlock level cap`,
code_count: `Кількість`,
code_focusAllUnlocked: `Всі школи Фокусу вже розблоковані.`,
code_focusUnlocked: `Розблоковано |COUNT| нових шкіл Фокусу! Для відображення змін в грі знадобиться оновлення спорядження.`,
code_focusUnlocked: `Розблоковано |COUNT| нових шкіл Фокусу! Для відображення змін в грі знадобиться оновлення спорядження. Відвідування навігації — найпростіший спосіб цього досягти.`,
code_addModsConfirm: `Ви впевнені, що хочете додати |COUNT| модифікаторів на ваш обліковий запис?`,
code_succImport: `Успішно імпортовано.`,
code_succRelog: `Готово. Зверніть увагу, що вам потрібно буде перезайти, щоб побачити зміни в грі.`,
@ -75,10 +75,6 @@ dict = {
code_funded: `Профінансовано`,
code_replays: `Повтори`,
code_stalker: `Сталкер`,
code_cutName: `Зачіска: |INDEX|`,
code_drifterBeardName: `Борода мандрівника: |INDEX|`,
code_drifterFaceName: `Зовнішність мандрівника: |INDEX|`,
code_operatorFaceName: `Зовнішність оператора: |INDEX|`,
code_succChange: `Успішно змінено.`,
code_requiredInvigorationUpgrade: `Ви повинні вибрати як атакуюче, так і допоміжне вдосконалення.`,
login_description: `Увійдіть, використовуючи облікові дані OpenWF (ті ж, що й у грі при підключенні до цього серверу).`,
@ -116,7 +112,6 @@ dict = {
inventory_boosters: `Посилення`,
inventory_flavourItems: `<abbr title="Набори анімацій, гліфи, палітри і т. д.">Унікальні предмети</abbr>`,
inventory_shipDecorations: `Прикраси судна`,
inventory_weaponSkins: `Вигляди`,
inventory_bulkAddSuits: `Додати відсутні Ворфрейми`,
inventory_bulkAddWeapons: `Додати відсутню зброю`,
inventory_bulkAddSpaceSuits: `Додати відсутні Арквінґи`,
@ -126,7 +121,6 @@ dict = {
inventory_bulkAddFlavourItems: `Додати відсутні унікальні предмети`,
inventory_bulkAddShipDecorations: `Додати відсутні оздоби корабля`,
inventory_bulkAddEvolutionProgress: `Додати відсутній прогрес еволюції Інкарнонів`,
inventory_bulkAddWeaponSkins: `[UNTRANSLATED] Add Missing Skins`,
inventory_bulkRankUpSuits: `Макс. рівень всіх Ворфреймів`,
inventory_bulkRankUpWeapons: `Макс. рівень всієї зброї`,
inventory_bulkRankUpSpaceSuits: `Макс. рівень всіх Арквінґів`,
@ -134,7 +128,7 @@ dict = {
inventory_bulkRankUpSentinels: `Макс. рівень всіх Вартових`,
inventory_bulkRankUpSentinelWeapons: `Макс. рівень всієї зброї Вартових`,
inventory_bulkRankUpEvolutionProgress: `Макс. рівень всіх еволюцій Інкарнонів`,
inventory_removeIsNew: `[UNTRANSLATED] Remove New Equipment Exclamation Icon`,
inventory_maxPlexus: `Макс. рівень Плексу`,
quests_list: `Пригоди`,
quests_completeAll: `Закінчити всі пригоди`,
@ -233,7 +227,7 @@ dict = {
cheats_baroFullyStocked: `Баро повністю укомплектований`,
cheats_syndicateMissionsRepeatable: `Повторювати місії синдиката`,
cheats_unlockAllProfitTakerStages: `Розблокувати всі етапи Привласнювачки`,
cheats_unlockSuccInventory: `[UNTRANSLATED] Success. Please note that you'll need to resync your inventory, e.g. by visiting a dojo/relay or relogging.`,
cheats_unlockSuccInventory: `Успішно. Зверніть увагу, що вам потрібно буде повторно синхронізувати своє спорядження, наприклад, за допомогою команди /sync в програмі bootstrapper, відвідавши Доджьо/Реле або повторно увійшовши в гру.`,
cheats_instantFinishRivenChallenge: `Миттєве завершення випробування модифікатора Розколу`,
cheats_instantResourceExtractorDrones: `Миттєво добуваючі Дрони-видобувачі`,
cheats_noResourceExtractorDronesDamage: `Без шкоди по Дронам-видобувачам`,
@ -259,17 +253,10 @@ dict = {
cheats_helminthUnlockAll: `Повністю покращити Гельмінта`,
cheats_addMissingSubsumedAbilities: `Додати відсутні поглинуті здібності`,
cheats_intrinsicsUnlockAll: `Повністю покращити Кваліфікації`,
cheats_maxPlexus: `Макс. рівень Плексу`,
cheats_changeSupportedSyndicate: `Підтримуваний синдикат`,
cheats_changeButton: `Змінити`,
cheats_markAllAsRead: `Помітити всі вхідні як прочитані`,
cheats_finishInvasionsInOneMission: `Завершувати вторгнення за одну місію`,
cheats_nemesisHenchmenKillsMultiplierGrineer: `Множник прогресу люті (Ґрінери)`,
cheats_nemesisHenchmenKillsMultiplierCorpus: `Множник прогресу люті (Корпус)`,
cheats_nemesisAntivirusGainMultiplier: `Мультиплікатор прогресу антивіруса`,
cheats_nemesisHintProgressMultiplierGrineer: `Множник прогресу підсказки (Ґрінери)`,
cheats_nemesisHintProgressMultiplierCorpus: `Множник прогресу підсказки (Корпус)`,
cheats_nemesisExtraWeapon: `Додаткова зброя/Жива сердцевина за перемогу над Недругом (0 для вимкнення)`,
worldState: `Стан світу`,
worldState_creditBoost: `Глобальне посилення Кредитів`,
@ -285,14 +272,12 @@ dict = {
worldState_plagueStar: `Морова зірка`,
worldState_dogDays: `Спекотні дні`,
worldState_dogDaysRewards: `Нагороди Спекотних днів`,
worldState_wolfHunt: `Полювання на Вовка`,
worldState_voidCorruption: `Викривлення Порожнечі (|VAL|)`,
worldState_wolfHunt: `Полювання на Вовка (2025)`,
worldState_orphixVenom: `Орфіксова отрута`,
worldState_longShadow: `Довга тінь`,
worldState_hallowedFlame: `Священне полум'я`,
worldState_hallowedNightmares: `Священні жахіття`,
worldState_hallowedNightmaresRewards: `Нагороди Священних жахіть`,
worldState_naberusNights: `Наберові ночі`,
worldState_proxyRebellion: `Повстання роботів`,
worldState_proxyRebellionRewards: `Нагороди Повстання роботів`,
worldState_bellyOfTheBeast: `У лігві звіра`,
@ -301,9 +286,8 @@ dict = {
worldState_eightClawProgressOverride: `Прогрес Восьми кігтів`,
worldState_thermiaFractures: `Розломи термії`,
worldState_thermiaFracturesProgressOverride: `Прогрес Розломів термії`,
worldState_qtccAlerts: `[UNTRANSLATED] Quest to Conquer Cancer Alerts`,
worldState_from_year: `З |VAL|`,
worldState_pre_year: `До |VAL|`,
worldState_from_year: `з |VAL|`,
worldState_pre_year: `до |VAL|`,
worldState_week: `Тиждень |VAL|`,
worldState_incompatibleWith: `Несумісне з:`,
enabled: `Увімкнено`,
@ -324,7 +308,6 @@ dict = {
worldState_sorrow: `Журба`,
worldState_fear: `Страх`,
worldState_nightwaveOverride: `Сезон Нічної хвилі`,
worldState_RadioLegionIntermission14Syndicate: `[UNTRANSLATED] Nora's Mix: Dreams of the Dead`,
worldState_RadioLegionIntermission13Syndicate: `Вибірка Нори 9`,
worldState_RadioLegionIntermission12Syndicate: `Вибірка Нори 8`,
worldState_RadioLegionIntermission11Syndicate: `Вибірка Нори 7`,
@ -421,8 +404,6 @@ dict = {
guildView_cheats: `Кланові чити`,
guildView_techProjects: `Дослідження`,
guildView_vaultDecoRecipes: `Рецепти оздоблень Доджьо`,
guildView_vaultMiscItems: `[UNTRANSLATED] Resources in Vault`,
guildView_vaultShipDecorations: `[UNTRANSLATED] Decorations in Vault`,
guildView_alliance: `Альянс`,
guildView_members: `Учасники`,
guildView_pending: `Очікування`,
@ -445,7 +426,6 @@ dict = {
guildView_currency_owned: `В сховищі |COUNT|.`,
guildView_bulkAddTechProjects: `Додати відсутні дослідження`,
guildView_bulkAddVaultDecoRecipes: `Додати відсутні рецепти оздоблень Доджьо`,
guildView_bulkAddVaultShipDecorations: `[UNTRANSLATED] Add Missing Decorations in Vault`,
guildView_bulkFundTechProjects: `Фінансувати всі дослідження`,
guildView_bulkCompleteTechProjects: `Завершити всі дослідження`,
guildView_promote: `Підвищити звання`,

View File

@ -1,6 +1,6 @@
// Chinese translation by meb154, bishan178, nyaoouo, qianlishun, CrazyZhang, Corvus, qingchun
// Chinese translation by meb154, bishan178, nyaoouo, qianlishun, CrazyZhang, Corvus, & qingchun
dict = {
general_inventoryUpdateNote: `[UNTRANSLATED] Note: To see changes in-game, you need to resync your inventory, e.g. by visiting a dojo/relay or relogging.`,
general_inventoryUpdateNote: `注意: 要在游戏中查看更改,您需要重新同步库存,例如使用客户端的 /sync 命令,访问道场/中继站或重新登录.`,
general_inventoryUpdateNoteGameWs: `[UNTRANSLATED] Note: You may need to reopen any menu you are on for changes to be reflected.`,
general_addButton: `添加`,
general_setButton: `设置`,
@ -33,7 +33,7 @@ dict = {
code_remove: `移除`,
code_addItemsConfirm: `确定要向您的账户添加 |COUNT| 件物品吗?`,
code_addTechProjectsConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| research to your clan?`,
code_addVaultItemsConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| items to your clan vault?`,
code_addDecoRecipesConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| deco recipes to your clan?`,
code_succRankUp: `等级已提升`,
code_noEquipmentToRankUp: `没有可升级的装备`,
code_succAdded: `添加成功`,
@ -48,7 +48,7 @@ dict = {
code_unlockLevelCap: `[UNTRANSLATED] Unlock level cap`,
code_count: `数量`,
code_focusAllUnlocked: `所有专精学派均已解锁`,
code_focusUnlocked: `已解锁 |COUNT| 个新专精学派!需要游戏内仓库更新才能生效.`,
code_focusUnlocked: `已解锁 |COUNT| 个新专精学派!需要游戏内仓库更新才能生效,您可以通过访问星图来触发仓库更新.`,
code_addModsConfirm: `确定要向您的账户添加 |COUNT| 张MOD吗?`,
code_succImport: `导入成功`,
code_succRelog: `完成.需要重新登录游戏才能看到变化.`,
@ -75,10 +75,6 @@ dict = {
code_funded: `[UNTRANSLATED] Funded`,
code_replays: `[UNTRANSLATED] Replays`,
code_stalker: `追猎者`,
code_cutName: `[UNTRANSLATED] Cut |INDEX|`,
code_drifterBeardName: `漂泊者胡须 |INDEX|`,
code_drifterFaceName: `漂泊者面部 |INDEX|`,
code_operatorFaceName: `指挥官面部 |INDEX|`,
code_succChange: `更改成功`,
code_requiredInvigorationUpgrade: `[UNTRANSLATED] You must select both an offensive & utility upgrade.`,
login_description: `使用您的 OpenWF 账户凭证登录(与游戏内连接本服务器时使用的昵称相同)`,
@ -116,7 +112,6 @@ dict = {
inventory_boosters: `加成器`,
inventory_flavourItems: `<abbr title="动作表情、浮印、调色板等">装饰物品</abbr>`,
inventory_shipDecorations: `飞船装饰`,
inventory_weaponSkins: `外观`,
inventory_bulkAddSuits: `添加缺失战甲`,
inventory_bulkAddWeapons: `添加缺失武器`,
inventory_bulkAddSpaceSuits: `添加缺失载具`,
@ -126,7 +121,6 @@ dict = {
inventory_bulkAddFlavourItems: `[UNTRANSLATED] Add Missing Flavour Items`,
inventory_bulkAddShipDecorations: `[UNTRANSLATED] Add Missing Ship Decorations`,
inventory_bulkAddEvolutionProgress: `添加缺失的灵化之源进度`,
inventory_bulkAddWeaponSkins: `[UNTRANSLATED] Add Missing Skins`,
inventory_bulkRankUpSuits: `所有战甲升满级`,
inventory_bulkRankUpWeapons: `所有武器升满级`,
inventory_bulkRankUpSpaceSuits: `所有载具升满级`,
@ -134,7 +128,7 @@ dict = {
inventory_bulkRankUpSentinels: `所有守护升满级`,
inventory_bulkRankUpSentinelWeapons: `所有守护武器升满级`,
inventory_bulkRankUpEvolutionProgress: `所有灵化之源进度最大等级`,
inventory_removeIsNew: `[UNTRANSLATED] Remove New Equipment Exclamation Icon`,
inventory_maxPlexus: `最大深控等级`,
quests_list: `系列任务`,
quests_completeAll: `完成所有任务`,
@ -199,7 +193,7 @@ dict = {
cheats_skipTutorial: `跳过教程`,
cheats_skipAllDialogue: `跳过所有对话`,
cheats_unlockAllScans: `解锁所有扫描`,
cheats_unlockSuccRelog: `[UNTRANSLATED] Success. Please note that you'll need to relog for the client to refresh this.`,
cheats_unlockSuccRelog: `[UNTRANSLATED] Success. Please that you'll need to relog for the client to refresh this.`,
cheats_unlockAllMissions: `解锁所有星图`,
cheats_unlockAllMissions_ok: `操作成功.请注意,您需要进入道场/中继站或重新登录以刷新星图数据.`,
cheats_infiniteCredits: `无限现金`,
@ -233,7 +227,7 @@ dict = {
cheats_baroFullyStocked: `虚空商人贩卖所有商品`,
cheats_syndicateMissionsRepeatable: `集团任务可重复完成`,
cheats_unlockAllProfitTakerStages: `解锁利润收割者圆蛛所有阶段`,
cheats_unlockSuccInventory: `[UNTRANSLATED] Success. Please note that you'll need to resync your inventory, e.g. by visiting a dojo/relay or relogging.`,
cheats_unlockSuccInventory: `[UNTRANSLATED] Success. Please note that you'll need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging..`,
cheats_instantFinishRivenChallenge: `立即完成裂罅挑战`,
cheats_instantResourceExtractorDrones: `资源无人机即时完成`,
cheats_noResourceExtractorDronesDamage: `资源无人机不会损毁`,
@ -259,17 +253,10 @@ dict = {
cheats_helminthUnlockAll: `完全升级Helminth`,
cheats_addMissingSubsumedAbilities: `添加Helminth未汲取的战甲技能`,
cheats_intrinsicsUnlockAll: `所有内源之力最大等级`,
cheats_maxPlexus: `最大深控等级`,
cheats_changeSupportedSyndicate: `支持的集团`,
cheats_changeButton: `更改`,
cheats_markAllAsRead: `收件箱全部标记为已读`,
cheats_finishInvasionsInOneMission: `一场任务完成整场入侵`,
cheats_nemesisHenchmenKillsMultiplierGrineer: `玄骸怒气倍率 (Grineer)`,
cheats_nemesisHenchmenKillsMultiplierCorpus: `玄骸怒气倍率 (Corpus)`,
cheats_nemesisAntivirusGainMultiplier: `杀毒进度倍率 (科腐者)`,
cheats_nemesisHintProgressMultiplierGrineer: `解密进度倍率 (Grineer)`,
cheats_nemesisHintProgressMultiplierCorpus: `解密进度倍率 (Corpus)`,
cheats_nemesisExtraWeapon: `额外玄骸武器/代币 (0为禁用)`,
worldState: `世界状态配置`,
worldState_creditBoost: `现金加成`,
@ -285,14 +272,12 @@ dict = {
worldState_plagueStar: `瘟疫之星`,
worldState_dogDays: `三伏天`,
worldState_dogDaysRewards: `三伏天奖励设置`,
worldState_wolfHunt: `恶狼狩猎`,
worldState_voidCorruption: `虚空堕落 (|VAL|)`,
worldState_wolfHunt: `恶狼狩猎 (2025)`,
worldState_orphixVenom: `奥影之毒`,
worldState_longShadow: `暗夜长影`,
worldState_hallowedFlame: `万圣之焰`,
worldState_hallowedNightmares: `万圣噩梦`,
worldState_hallowedNightmaresRewards: `万圣噩梦奖励设置`,
worldState_naberusNights: `[UNTRANSLATED] Nights of Naberus`,
worldState_proxyRebellion: `机械叛乱`,
worldState_proxyRebellionRewards: `机械叛乱奖励设置`,
worldState_bellyOfTheBeast: `兽之腹`,
@ -301,7 +286,6 @@ dict = {
worldState_eightClawProgressOverride: `大帝金币收集进度(%)`,
worldState_thermiaFractures: `热美亚裂缝`,
worldState_thermiaFracturesProgressOverride: `[UNTRANSLATED] Thermia Fractures Progress`,
worldState_qtccAlerts: `[UNTRANSLATED] Quest to Conquer Cancer Alerts`,
worldState_from_year: `|VAL|`,
worldState_pre_year: `|VAL|之前`,
worldState_week: `第|VAL|周`,
@ -324,7 +308,6 @@ dict = {
worldState_sorrow: `悲伤`,
worldState_fear: `恐惧`,
worldState_nightwaveOverride: `午夜电波系列`,
worldState_RadioLegionIntermission14Syndicate: `[UNTRANSLATED] Nora's Mix: Dreams of the Dead`,
worldState_RadioLegionIntermission13Syndicate: `诺拉的混选VOL.9`,
worldState_RadioLegionIntermission12Syndicate: `诺拉的混选VOL.8`,
worldState_RadioLegionIntermission11Syndicate: `诺拉的混选VOL.7`,
@ -421,8 +404,6 @@ dict = {
guildView_cheats: `[UNTRANSLATED] Clan Cheats`,
guildView_techProjects: `研究`,
guildView_vaultDecoRecipes: `[UNTRANSLATED] Dojo Deco Recipes`,
guildView_vaultMiscItems: `[UNTRANSLATED] Resources in Vault`,
guildView_vaultShipDecorations: `[UNTRANSLATED] Decorations in Vault`,
guildView_alliance: `联盟`,
guildView_members: `成员`,
guildView_pending: `待处理`,
@ -445,7 +426,6 @@ dict = {
guildView_currency_owned: `[UNTRANSLATED] |COUNT| in Vault.`,
guildView_bulkAddTechProjects: `[UNTRANSLATED] Add Missing Research`,
guildView_bulkAddVaultDecoRecipes: `[UNTRANSLATED] Add Missing Dojo Deco Recipes`,
guildView_bulkAddVaultShipDecorations: `[UNTRANSLATED] Add Missing Decorations in Vault`,
guildView_bulkFundTechProjects: `[UNTRANSLATED] Fund All Research`,
guildView_bulkCompleteTechProjects: `[UNTRANSLATED] Complete All Research`,
guildView_promote: `升级`,