2 Commits

Author SHA1 Message Date
a85b65fca8 add description for revive booster
All checks were successful
Build / build (pull_request) Successful in 1m23s
2025-11-16 04:40:06 +01:00
d5d7728cf9 feat(webui): revive booster
All checks were successful
Build / build (pull_request) Successful in 2m0s
2025-11-11 02:54:25 +01:00
58 changed files with 502 additions and 1401 deletions

151
LICENSE
View File

@@ -1,15 +1,5 @@
“Commons Clause” License Condition v1.0
The Software is provided to you by the Licensor under the License, as defined below, subject to the following condition.
Without limiting other conditions in the License, the grant of rights under the License will not include, and the License does not grant to you, the right to Sell the Software.
For purposes of the foregoing, “Sell” means practicing any or all of the rights granted to you under the License to provide to third parties, for a fee or other consideration (including without limitation fees for hosting or consulting/ support services related to the Software), a product or service whose value derives, entirely or substantially, from the functionality of the Software. Any license notice or attribution required by the License must also include this Commons Clause License Condition notice.
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
@@ -17,15 +7,17 @@ For purposes of the foregoing, “Sell” means practicing any or all of the rig
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
@@ -34,34 +26,44 @@ them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
@@ -70,7 +72,7 @@ modification follow.
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
@@ -547,45 +549,35 @@ to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
@@ -643,29 +635,40 @@ the "copyright" line and a pointer to where the full notice is found.
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
GNU General Public License for more details.
You should have received a copy of the GNU Affero General Public License
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

View File

@@ -14,7 +14,6 @@ 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`.
- `bindAddress`, `httpPort`, `httpsPort` are related to how SpaceNinjaServer is reached on the network. Under Docker, these options are unchangable; modify your `docker-compose.yml`, instead.
- `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.
- `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.

View File

@@ -68,14 +68,6 @@
"circuitGameModes": null,
"darvoStockMultiplier": 1
},
"tunables": {
"useLoginToken": false,
"prohibitSkipMissionStartTimer": false,
"prohibitFovOverride": false,
"prohibitFreecam": false,
"prohibitTeleport": false,
"prohibitScripts": false
},
"dev": {
"keepVendorsExpired": false
}

View File

@@ -7,16 +7,13 @@ services:
- ./docker-data/static-data:/app/static/data
- ./docker-data/logs:/app/logs
ports:
# The lefthand value determines the port you actually connect to. Within the container, SpaceNinjaServer will always use 80 and 443 (righthand values).
- 80:80
- 443:443
# Normally, the image is fetched from Docker Hub, but you can use the local Dockerfile by removing "image" above and adding this:
#build: .
# Works best when using `docker compose up --force-recreate --build`.
# Works best when using `docker-compose up --force-recreate --build`.
environment:
- CHOKIDAR_USEPOLLING=true
depends_on:
- mongodb
mongodb:

View File

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

14
package-lock.json generated
View File

@@ -1,12 +1,13 @@
{
"name": "spaceninjaserver",
"name": "wf-emulator",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "spaceninjaserver",
"name": "wf-emulator",
"version": "0.1.0",
"license": "GNU",
"dependencies": {
"body-parser": "^2.2.0",
"chokidar": "^4.0.3",
@@ -551,7 +552,6 @@
"integrity": "sha512-gTtSdWX9xiMPA/7MV9STjJOOYtWwIJIYxkQxnSV1U3xcE+mnJSH3f6zI0RYP+ew66WSlZ5ed+h0VCxsvdC1jJg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.41.0",
"@typescript-eslint/types": "8.41.0",
@@ -1180,7 +1180,6 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -2058,7 +2057,6 @@
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
@@ -2225,7 +2223,6 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@@ -4365,7 +4362,6 @@
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -5207,7 +5203,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -5426,7 +5421,6 @@
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
"devOptional": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -5486,7 +5480,6 @@
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"peer": true,
"dependencies": {
"napi-postinstall": "^0.3.0"
},
@@ -5682,7 +5675,6 @@
"resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz",
"integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@colors/colors": "^1.6.0",
"@dabh/diagnostics": "^2.0.2",

View File

@@ -1,7 +1,7 @@
{
"name": "spaceninjaserver",
"name": "wf-emulator",
"version": "0.1.0",
"description": "SpaceNinjaServer",
"description": "WF Emulator",
"main": "index.ts",
"scripts": {
"start": "node --enable-source-maps build/src/index.js",
@@ -23,6 +23,7 @@
"update-translations": "cd scripts && node update-translations.cjs",
"fix": "npm run update-translations && npm run prettier"
},
"license": "GNU",
"type": "module",
"dependencies": {
"body-parser": "^2.2.0",

View File

@@ -1,4 +1,4 @@
import { toOid2 } from "../../helpers/inventoryHelpers.ts";
import { toOid } from "../../helpers/inventoryHelpers.ts";
import {
createVeiledRivenFingerprint,
createUnveiledRivenFingerprint,
@@ -6,14 +6,13 @@ import {
} from "../../helpers/rivenHelper.ts";
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
import { addMods, getInventory } from "../../services/inventoryService.ts";
import { getAccountForRequest } from "../../services/loginService.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getRandomElement } from "../../services/rngService.ts";
import type { RequestHandler } from "express";
import { ExportUpgrades } from "warframe-public-export-plus";
export const activateRandomModController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
const accountId = account._id.toString();
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "RawUpgrades Upgrades instantFinishRivenChallenge");
const request = getJSONfromString<IActiveRandomModRequest>(String(req.body));
addMods(inventory, [
@@ -37,7 +36,7 @@ export const activateRandomModController: RequestHandler = async (req, res) => {
NewMod: {
UpgradeFingerprint: fingerprint,
ItemType: rivenType,
ItemId: toOid2(inventory.Upgrades[upgradeIndex]._id, account.BuildLabel)
ItemId: toOid(inventory.Upgrades[upgradeIndex]._id)
}
});
};

View File

@@ -1,7 +1,7 @@
import { fromOid, toOid2 } from "../../helpers/inventoryHelpers.ts";
import { fromOid, toOid } from "../../helpers/inventoryHelpers.ts";
import { createVeiledRivenFingerprint, rivenRawToRealWeighted } from "../../helpers/rivenHelper.ts";
import { addMiscItems, addMods, getInventory } from "../../services/inventoryService.ts";
import { getAccountForRequest } from "../../services/loginService.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getRandomElement, getRandomWeightedReward, getRandomWeightedRewardUc } from "../../services/rngService.ts";
import type { IUpgradeFromClient } from "../../types/inventoryTypes/inventoryTypes.ts";
import type { RequestHandler } from "express";
@@ -9,15 +9,12 @@ import type { TRarity } from "warframe-public-export-plus";
import { ExportBoosterPacks, ExportUpgrades } from "warframe-public-export-plus";
export const artifactTransmutationController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
const accountId = account._id.toString();
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const payload = JSON.parse(String(req.body)) as IArtifactTransmutationRequest;
inventory.RegularCredits -= payload.Cost;
if (payload.FusionPointCost) {
inventory.FusionPoints -= payload.FusionPointCost;
}
inventory.FusionPoints -= payload.FusionPointCost;
if (payload.RivenTransmute) {
addMiscItems(inventory, [
@@ -44,7 +41,7 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
res.json({
NewMods: [
{
ItemId: toOid2(inventory.Upgrades[upgradeIndex]._id, account.BuildLabel),
ItemId: toOid(inventory.Upgrades[upgradeIndex]._id),
ItemType: rivenType,
UpgradeFingerprint: fingerprint
}
@@ -59,10 +56,9 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
};
let forcedPolarity: string | undefined;
payload.Consumed.forEach(upgrade => {
upgrade.ItemCount ??= 1;
const meta = ExportUpgrades[upgrade.ItemType];
counts[meta.rarity] += upgrade.ItemCount;
if (fromOid(upgrade.ItemId) != "" && fromOid(upgrade.ItemId) != "000000000000000000000000") {
if (fromOid(upgrade.ItemId) != "000000000000000000000000") {
inventory.Upgrades.pull({ _id: fromOid(upgrade.ItemId) });
} else {
addMods(inventory, [
@@ -137,7 +133,7 @@ interface IArtifactTransmutationRequest {
LevelDiff: number;
Consumed: IUpgradeFromClient[];
Cost: number;
FusionPointCost?: number;
FusionPointCost: number;
RivenTransmute?: boolean;
}

View File

@@ -1,126 +1,63 @@
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
import { getAccountForRequest } from "../../services/loginService.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import type { RequestHandler } from "express";
import type {
IInventoryClient,
IUpgradeClient,
IUpgradeFromClient
} from "../../types/inventoryTypes/inventoryTypes.ts";
import type { IInventoryClient, IUpgradeClient } from "../../types/inventoryTypes/inventoryTypes.ts";
import { addMods, getInventory } from "../../services/inventoryService.ts";
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
import { fromOid, version_compare } from "../../helpers/inventoryHelpers.ts";
export const artifactsController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
const accountId = account._id.toString();
const accountId = await getAccountIdForRequest(req);
const artifactsData = getJSONfromString<IArtifactsRequest>(String(req.body));
const { Upgrade, LevelDiff, Cost, FusionPointCost, Consumed, Fingerprint } = artifactsData;
const { Upgrade, LevelDiff, Cost, FusionPointCost } = artifactsData;
const inventory = await getInventory(accountId);
const { Upgrades } = inventory;
const { ItemType, UpgradeFingerprint, ItemId } = Upgrade;
if (!account.BuildLabel || version_compare(account.BuildLabel, "2016.08.19.17.12") >= 0) {
const safeUpgradeFingerprint = UpgradeFingerprint || '{"lvl":0}';
const parsedUpgradeFingerprint = JSON.parse(safeUpgradeFingerprint) as { lvl: number };
parsedUpgradeFingerprint.lvl += LevelDiff;
const stringifiedUpgradeFingerprint = JSON.stringify(parsedUpgradeFingerprint);
const safeUpgradeFingerprint = UpgradeFingerprint || '{"lvl":0}';
const parsedUpgradeFingerprint = JSON.parse(safeUpgradeFingerprint) as { lvl: number };
parsedUpgradeFingerprint.lvl += LevelDiff;
const stringifiedUpgradeFingerprint = JSON.stringify(parsedUpgradeFingerprint);
let itemIndex = Upgrades.findIndex(upgrade => upgrade._id.equals(fromOid(ItemId)));
let itemIndex = Upgrades.findIndex(upgrade => upgrade._id.equals(ItemId.$oid));
if (itemIndex !== -1) {
Upgrades[itemIndex].UpgradeFingerprint = stringifiedUpgradeFingerprint;
} else {
itemIndex =
Upgrades.push({
UpgradeFingerprint: stringifiedUpgradeFingerprint,
ItemType
}) - 1;
addMods(inventory, [{ ItemType, ItemCount: -1 }]);
}
if (!inventory.infiniteCredits) {
inventory.RegularCredits -= Cost;
}
if (!inventory.infiniteEndo) {
inventory.FusionPoints -= FusionPointCost;
}
if (artifactsData.LegendaryFusion) {
addMods(inventory, [
{
ItemType: "/Lotus/Upgrades/Mods/Fusers/LegendaryModFuser",
ItemCount: -1
}
]);
}
const changedInventory = (await inventory.save()).toJSON<IInventoryClient>();
const itemId =
changedInventory.Upgrades[itemIndex].ItemId.$oid ?? changedInventory.Upgrades[itemIndex].ItemId.$id;
if (!itemId) {
throw new Error("Item Id not found in upgradeMod");
}
res.send(itemId);
if (itemIndex !== -1) {
Upgrades[itemIndex].UpgradeFingerprint = stringifiedUpgradeFingerprint;
} else {
// Pre-U18.18.0 uses the old pre-Endo fusion system which uses a different UpgradeFingerprint format
// that has to be converted and consumes upgrades in the fusion proccess
const safeUpgradeFingerprint = `{"lvl":${Fingerprint?.substring(4, Fingerprint.lastIndexOf("|"))}}`;
const parsedUpgradeFingerprint = JSON.parse(safeUpgradeFingerprint) as { lvl: number };
if (LevelDiff) {
parsedUpgradeFingerprint.lvl += LevelDiff;
}
const stringifiedUpgradeFingerprint = JSON.stringify(parsedUpgradeFingerprint);
itemIndex =
Upgrades.push({
UpgradeFingerprint: stringifiedUpgradeFingerprint,
ItemType
}) - 1;
let itemIndex = Upgrades.findIndex(upgrade => upgrade._id.equals(ItemId.$id));
if (itemIndex !== -1) {
Upgrades[itemIndex].UpgradeFingerprint = stringifiedUpgradeFingerprint;
} else {
itemIndex =
Upgrades.push({
UpgradeFingerprint: stringifiedUpgradeFingerprint,
ItemType
}) - 1;
addMods(inventory, [{ ItemType, ItemCount: -1 }]);
}
const itemId = Upgrades[itemIndex]._id.toString();
if (!itemId) {
throw new Error("Item Id not found in upgradeMod");
}
if (!inventory.infiniteCredits) {
inventory.RegularCredits -= Cost;
}
if (Consumed && Consumed.length > 0) {
for (const upgrade of Consumed) {
// The client does not send the expected information about the mods, so we have to check if it's an Upgrade or RawUpgrade manually.
if (Upgrades.id(fromOid(upgrade.ItemId))) {
Upgrades.pull({ _id: upgrade.ItemId.$id });
} else {
addMods(inventory, [
{
ItemType: upgrade.ItemType,
ItemCount: -1
}
]);
}
}
itemIndex = Upgrades.findIndex(upgrade => upgrade._id.equals(itemId));
}
await inventory.save();
res.send(itemId);
addMods(inventory, [{ ItemType, ItemCount: -1 }]);
}
if (!inventory.infiniteCredits) {
inventory.RegularCredits -= Cost;
}
if (!inventory.infiniteEndo) {
inventory.FusionPoints -= FusionPointCost;
}
if (artifactsData.LegendaryFusion) {
addMods(inventory, [
{
ItemType: "/Lotus/Upgrades/Mods/Fusers/LegendaryModFuser",
ItemCount: -1
}
]);
}
const changedInventory = await inventory.save();
const itemId = changedInventory.toJSON<IInventoryClient>().Upgrades[itemIndex].ItemId.$oid;
if (!itemId) {
throw new Error("Item Id not found in upgradeMod");
}
res.send(itemId);
broadcastInventoryUpdate(req);
};
@@ -130,6 +67,4 @@ interface IArtifactsRequest {
Cost: number;
FusionPointCost: number;
LegendaryFusion?: boolean;
Fingerprint?: string;
Consumed?: IUpgradeFromClient[];
}

View File

@@ -1,24 +0,0 @@
import { getAccountIdForRequest } from "../../services/loginService.ts";
import type { RequestHandler } from "express";
import { getInventory } from "../../services/inventoryService.ts";
export const checkPendingRecipesController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "PendingRecipes");
const now = Date.now();
const resp: ICheckPendingRecipesResponse = {
PendingRecipes: inventory.PendingRecipes.map(recipe => ({
ItemType: recipe.ItemType,
SecondsRemaining: Math.max(0, Math.floor((recipe.CompletionDate.getTime() - now) / 1000))
}))
};
res.send(resp);
};
interface ICheckPendingRecipesResponse {
PendingRecipes: {
ItemType: string;
SecondsRemaining: number;
}[];
}

View File

@@ -22,15 +22,14 @@ import {
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
import type { IPendingRecipeDatabase } from "../../types/inventoryTypes/inventoryTypes.ts";
import { InventorySlot } from "../../types/inventoryTypes/inventoryTypes.ts";
import { fromOid, toOid2, version_compare } from "../../helpers/inventoryHelpers.ts";
import { fromOid, toOid2 } from "../../helpers/inventoryHelpers.ts";
import type { TInventoryDatabaseDocument } from "../../models/inventoryModels/inventoryModel.ts";
import type { IRecipe } from "warframe-public-export-plus";
import type { IEquipmentClient } from "../../types/equipmentTypes.ts";
import { EquipmentFeatures, Status } from "../../types/equipmentTypes.ts";
interface IClaimCompletedRecipeRequest {
RecipeIds?: IOidWithLegacySupport[]; // U24.4 and beyond
recipeNames?: string[]; // Builds before U24.4 down to U22.20
RecipeIds: IOidWithLegacySupport[];
}
interface IClaimCompletedRecipeResponse {
@@ -39,75 +38,39 @@ interface IClaimCompletedRecipeResponse {
}
export const claimCompletedRecipeController: RequestHandler = async (req, res) => {
const claimCompletedRecipeRequest = getJSONfromString<IClaimCompletedRecipeRequest>(String(req.body));
const account = await getAccountForRequest(req);
const inventory = await getInventory(account._id.toString());
const resp: IClaimCompletedRecipeResponse = {
InventoryChanges: {}
};
if (!req.query.recipeName) {
const claimCompletedRecipeRequest = getJSONfromString<IClaimCompletedRecipeRequest>(String(req.body));
const recipes = claimCompletedRecipeRequest.recipeNames ?? claimCompletedRecipeRequest.RecipeIds;
if (recipes) {
for (const recipeId of recipes) {
let pendingRecipe;
if (typeof recipeId === "string") {
pendingRecipe = inventory.PendingRecipes.find(r => r.ItemType == recipeId);
if (!pendingRecipe) {
throw new Error(`no pending recipe found of type ${recipeId}`);
}
} else {
pendingRecipe = inventory.PendingRecipes.id(fromOid(recipeId));
if (!pendingRecipe) {
throw new Error(`no pending recipe found with id ${fromOid(recipeId)}`);
}
}
//check recipe is indeed ready to be completed
// if (pendingRecipe.CompletionDate > new Date()) {
// throw new Error(`recipe ${pendingRecipe._id} is not ready to be completed`);
// }
inventory.PendingRecipes.pull(pendingRecipe._id);
const recipe = getRecipe(pendingRecipe.ItemType);
if (!recipe) {
throw new Error(`no completed item found for recipe ${pendingRecipe._id.toString()}`);
}
if (req.query.cancel) {
const inventoryChanges: IInventoryChanges = {};
await refundRecipeIngredients(inventory, inventoryChanges, recipe, pendingRecipe);
await inventory.save();
res.json(inventoryChanges); // Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root.
return;
}
await claimCompletedRecipe(account, inventory, recipe, pendingRecipe, resp, req.query.rush);
}
} else {
throw new Error(`recipe list from request was undefined?`);
}
} else {
const recipeName = String(req.query.recipeName); // U8
const pendingRecipe = inventory.PendingRecipes.find(r => r.ItemType == recipeName);
for (const recipeId of claimCompletedRecipeRequest.RecipeIds) {
const pendingRecipe = inventory.PendingRecipes.id(fromOid(recipeId));
if (!pendingRecipe) {
throw new Error(`no pending recipe found with ItemType ${recipeName}`);
throw new Error(`no pending recipe found with id ${fromOid(recipeId)}`);
}
//check recipe is indeed ready to be completed
// if (pendingRecipe.CompletionDate > new Date()) {
// throw new Error(`recipe ${pendingRecipe._id} is not ready to be completed`);
// }
inventory.PendingRecipes.pull(pendingRecipe._id);
const recipe = getRecipe(pendingRecipe.ItemType);
if (!recipe) {
throw new Error(`no completed item found for recipe ${pendingRecipe._id.toString()}`);
}
await claimCompletedRecipe(
account,
inventory,
recipe,
pendingRecipe,
resp,
req.path.includes("instantCompleteRecipe.php") || req.query.rush
);
if (req.query.cancel) {
const inventoryChanges: IInventoryChanges = {};
await refundRecipeIngredients(inventory, inventoryChanges, recipe, pendingRecipe);
await inventory.save();
res.json(inventoryChanges); // Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root.
return;
}
await claimCompletedRecipe(account, inventory, recipe, pendingRecipe, resp, req.query.rush);
}
await inventory.save();
res.json(resp);
@@ -158,20 +121,13 @@ const claimCompletedRecipe = async (
}
if (rush) {
let cost = recipe.skipBuildTimePrice;
const end = Math.trunc(pendingRecipe.CompletionDate.getTime() / 1000);
const start = end - recipe.buildTime;
const secondsElapsed = Math.trunc(Date.now() / 1000) - start;
const progress = secondsElapsed / recipe.buildTime;
logger.debug(`rushing recipe at ${Math.trunc(progress * 100)}% completion`);
// U18 introduced rush cost scaling, don't use it for older versions.
if (account.BuildLabel && version_compare(account.BuildLabel, "2015.12.03.00.00") >= 0) {
// Haven't found the real build label for U18.0.0 yet, but this works
cost =
progress > 0.5
? Math.round(recipe.skipBuildTimePrice * (1 - (progress - 0.5)))
: recipe.skipBuildTimePrice;
}
const cost =
progress > 0.5 ? Math.round(recipe.skipBuildTimePrice * (1 - (progress - 0.5))) : recipe.skipBuildTimePrice;
combineInventoryChanges(resp.InventoryChanges, updateCurrency(inventory, cost, true));
}
@@ -262,9 +218,9 @@ const claimCompletedRecipe = async (
"",
"",
"",
fromOid(umbraModA.ItemId),
fromOid(umbraModB.ItemId),
fromOid(umbraModC.ItemId)
umbraModA.ItemId.$oid,
umbraModB.ItemId.$oid,
umbraModC.ItemId.$oid
]
}
],
@@ -284,18 +240,7 @@ const claimCompletedRecipe = async (
"/Lotus/Weapons/Tenno/Melee/Swords/UmbraKatana/UmbraKatana",
{
Configs: [
{
Upgrades: [
"",
"",
"",
"",
"",
"",
fromOid(sacrificeModA.ItemId),
fromOid(sacrificeModB.ItemId)
]
}
{ Upgrades: ["", "", "", "", "", "", sacrificeModA.ItemId.$oid, sacrificeModB.ItemId.$oid] }
],
XP: 450_000,
Features: EquipmentFeatures.DOUBLE_CAPACITY

View File

@@ -4,9 +4,8 @@ import type { TInventoryDatabaseDocument } from "../../models/inventoryModels/in
import { Inventory } from "../../models/inventoryModels/inventoryModel.ts";
import { config } from "../../services/configService.ts";
import allDialogue from "../../../static/fixed_responses/allDialogue.json" with { type: "json" };
import allPopups from "../../../static/fixed_responses/allPopups.json" with { type: "json" };
import type { ILoadoutDatabase } from "../../types/saveLoadoutTypes.ts";
import type { IInventoryClient, IShipInventory, IUpgradeClient } from "../../types/inventoryTypes/inventoryTypes.ts";
import type { IInventoryClient, IShipInventory } from "../../types/inventoryTypes/inventoryTypes.ts";
import { equipmentKeys } from "../../types/inventoryTypes/inventoryTypes.ts";
import type { IPolarity } from "../../types/inventoryTypes/commonInventoryTypes.ts";
import { ArtifactPolarity } from "../../types/inventoryTypes/commonInventoryTypes.ts";
@@ -30,7 +29,7 @@ import { getNemesisManifest } from "../../helpers/nemesisHelpers.ts";
import { getPersonalRooms } from "../../services/personalRoomsService.ts";
import type { IPersonalRoomsClient } from "../../types/personalRoomsTypes.ts";
import { Ship } from "../../models/shipModel.ts";
import { toLegacyOid, toOid, toOid2, version_compare } from "../../helpers/inventoryHelpers.ts";
import { toLegacyOid, toOid, version_compare } from "../../helpers/inventoryHelpers.ts";
import { Inbox } from "../../models/inboxModel.ts";
import { unixTimesInMs } from "../../constants/timeConstants.ts";
import { DailyDeal } from "../../models/worldStateModel.ts";
@@ -327,12 +326,6 @@ export const getInventoryResponse = async (
}
}
if (inventory.skipAllPopups) {
for (const str of allPopups) {
addString(inventoryResponse.NodeIntrosCompleted, str);
}
}
if (config.worldState?.baroTennoConRelay) {
[
"/Lotus/Types/Items/Events/TennoConRelay2022EarlyAccess",
@@ -454,141 +447,29 @@ export const getInventoryResponse = async (
inventoryResponse.Nemesis = undefined;
}
if (version_compare(buildLabel, "2019.03.07.20.21") < 0) {
// Builds before U24.4.0 handle equipment features differently
for (const category of equipmentKeys) {
for (const item of inventoryResponse[category]) {
if (item.Features && item.Features & EquipmentFeatures.DOUBLE_CAPACITY) {
item.UnlockLevel = 1;
}
if (item.Features && item.Features & EquipmentFeatures.UTILITY_SLOT) {
item.UtilityUnlocked = 1;
}
if (item.Features && item.Features & EquipmentFeatures.GILDED) {
item.Gild = true;
if (version_compare(buildLabel, "2018.02.22.14.34") < 0) {
const personalRoomsDb = await getPersonalRooms(inventory.accountOwnerId.toString());
const personalRooms = personalRoomsDb.toJSON<IPersonalRoomsClient>();
inventoryResponse.Ship = personalRooms.Ship;
if (version_compare(buildLabel, "2016.12.21.19.13") <= 0) {
// U19.5 and below use $id instead of $oid
for (const category of equipmentKeys) {
for (const item of inventoryResponse[category]) {
toLegacyOid(item.ItemId);
}
}
}
if (version_compare(buildLabel, "2018.02.22.14.34") < 0) {
const personalRoomsDb = await getPersonalRooms(inventory.accountOwnerId.toString());
const personalRooms = personalRoomsDb.toJSON<IPersonalRoomsClient>();
inventoryResponse.Ship = personalRooms.Ship;
if (version_compare(buildLabel, "2016.12.21.19.13") <= 0) {
// U19.5 and below use $id instead of $oid
for (const category of equipmentKeys) {
for (const item of inventoryResponse[category]) {
toLegacyOid(item.ItemId);
}
}
if (version_compare(buildLabel, "2014.02.05.00.00") < 0) {
// Pre-U12 builds store mods in an array called Cards, and have no concept of RawUpgrades
inventoryResponse.Cards = [];
for (const rawUpgrade of inventoryResponse.RawUpgrades) {
const id = inventory.RawUpgrades.find(x => x.ItemType == rawUpgrade.ItemType)?._id;
if (id) {
for (let i = 0; i < rawUpgrade.ItemCount; i++) {
const card = {
ItemType: rawUpgrade.ItemType,
ItemId: toOid2(id, buildLabel),
Rank: 0,
AmountRemaining: rawUpgrade.ItemCount
} as IUpgradeClient;
// Client doesn't see the mods unless they are in both Cards and Upgrades
inventoryResponse.Cards.push(card);
inventoryResponse.Upgrades.push(card);
}
}
}
inventoryResponse.RawUpgrades = [];
for (const category of equipmentKeys) {
for (const item of inventoryResponse[category]) {
for (const config of item.Configs) {
if (config.Upgrades) {
// Convert installed upgrades for U10-U11
const convertedUpgrades: { $id: string }[] = [];
config.Upgrades.forEach(upgrade => {
const upgradeId = upgrade as string;
convertedUpgrades.push({ $id: upgradeId });
});
config.Upgrades = convertedUpgrades;
}
}
}
}
}
for (const upgrade of inventoryResponse.Upgrades) {
toLegacyOid(upgrade.ItemId);
if (version_compare(buildLabel, "2016.08.19.17.12") < 0) {
// Pre-U18.18 builds use a different UpgradeFingerprint format
let rank: number = 0;
if (upgrade.UpgradeFingerprint) {
rank = Number.parseFloat(
upgrade.UpgradeFingerprint.substring(
upgrade.UpgradeFingerprint.indexOf(":") + 1,
upgrade.UpgradeFingerprint.lastIndexOf("}")
)
);
}
upgrade.UpgradeFingerprint = `lvl=${rank}|`;
if (version_compare(buildLabel, "2014.04.10.17.47") < 0) {
// Pre-U10 builds
if (
!upgrade.AmountRemaining ||
(upgrade.AmountRemaining && upgrade.AmountRemaining <= 0)
) {
upgrade.AmountRemaining = 1;
}
upgrade.Rank = rank;
if (inventoryResponse.Cards) {
inventoryResponse.Cards.push(upgrade);
}
}
}
}
if (version_compare(buildLabel, "2014.02.05.00.00") < 0) {
// Convert installed mods for pre-U12 builds
for (const category of equipmentKeys) {
for (const item of inventoryResponse[category]) {
for (const config of item.Configs) {
if (config.Upgrades) {
for (let i = 0; i < config.Upgrades.length; i++) {
const id = config.Upgrades[i] as { $id: string | undefined };
const invUpgrade = inventoryResponse.Upgrades.find(
x => x.ItemId.$id == id.$id
);
if (invUpgrade) {
if (id.$id?.startsWith("/Lotus")) {
// Pre-U12 builds have no concept of RawUpgrades, have to convert the db entry to the closest id of an unranked copy
id.$id = inventoryResponse.Upgrades.find(
x => x.ItemType == id.$id
)?.ItemId.$id;
}
// Pre-U10
invUpgrade.ParentId = item.ItemId;
invUpgrade.Slot = i + 1;
}
}
}
}
}
}
}
if (inventoryResponse.BrandedSuits) {
for (const id of inventoryResponse.BrandedSuits) {
toLegacyOid(id);
}
}
if (inventoryResponse.GuildId) {
toLegacyOid(inventoryResponse.GuildId);
for (const upgrade of inventoryResponse.Upgrades) {
toLegacyOid(upgrade.ItemId);
}
if (inventoryResponse.BrandedSuits) {
for (const id of inventoryResponse.BrandedSuits) {
toLegacyOid(id);
}
}
if (inventoryResponse.GuildId) {
toLegacyOid(inventoryResponse.GuildId);
}
}
}
}

View File

@@ -161,16 +161,7 @@ const createLoginResponse = (
if (version_compare(buildLabel, "2022.09.06.19.24") >= 0) {
resp.CrossPlatformAllowed = account.CrossPlatformAllowed;
resp.HUB = `${myUrlBase}/api/`;
// The MatchmakingBuildId is a 64-bit integer represented as a decimal string. On live, the value is seemingly random per build, but really any value that is different across builds should work.
const [year, month, day, hour, minute] = buildLabel.split(".").map(x => parseInt(x));
resp.MatchmakingBuildId = (
year * 1_00_00_00_00 +
month * 1_00_00_00 +
day * 1_00_00 +
hour * 1_00 +
minute
).toString();
resp.MatchmakingBuildId = buildConfig.matchmakingBuildId;
}
if (version_compare(buildLabel, "2023.04.25.23.40") >= 0) {
if (version_compare(buildLabel, "2025.08.26.09.49") >= 0) {

View File

@@ -1,4 +1,4 @@
import { fromDbOid, fromOid, toMongoDate, version_compare } from "../../helpers/inventoryHelpers.ts";
import { fromDbOid, toMongoDate, version_compare } from "../../helpers/inventoryHelpers.ts";
import type { IKnifeResponse } from "../../helpers/nemesisHelpers.ts";
import {
antivirusMods,
@@ -21,7 +21,7 @@ import { Loadout } from "../../models/inventoryModels/loadoutModel.ts";
import { addMods, freeUpSlot, getInventory } from "../../services/inventoryService.ts";
import { getAccountForRequest } from "../../services/loginService.ts";
import { SRng } from "../../services/rngService.ts";
import type { IMongoDate, IOid, IOidWithLegacySupport } from "../../types/commonTypes.ts";
import type { IMongoDate, IOid } from "../../types/commonTypes.ts";
import type { IEquipmentClient } from "../../types/equipmentTypes.ts";
import type {
IInnateDamageFingerprint,
@@ -420,7 +420,7 @@ interface IKnife {
const consumeModCharge = (
response: IKnifeResponse,
inventory: TInventoryDatabaseDocument,
upgrade: { ItemId: IOidWithLegacySupport; ItemType: string },
upgrade: { ItemId: IOid; ItemType: string },
dataknifeUpgrades: string[]
): void => {
response.UpgradeIds ??= [];
@@ -429,13 +429,13 @@ const consumeModCharge = (
response.UpgradeNew ??= [];
response.HasKnife = true;
if (fromOid(upgrade.ItemId) != "000000000000000000000000") {
const dbUpgrade = inventory.Upgrades.id(fromOid(upgrade.ItemId))!;
if (upgrade.ItemId.$oid != "000000000000000000000000") {
const dbUpgrade = inventory.Upgrades.id(upgrade.ItemId.$oid)!;
const fingerprint = JSON.parse(dbUpgrade.UpgradeFingerprint!) as { lvl: number };
fingerprint.lvl += 1;
dbUpgrade.UpgradeFingerprint = JSON.stringify(fingerprint);
response.UpgradeIds.push(fromOid(upgrade.ItemId));
response.UpgradeIds.push(upgrade.ItemId.$oid);
response.UpgradeTypes.push(upgrade.ItemType);
response.UpgradeFingerprints.push(fingerprint);
response.UpgradeNew.push(false);

View File

@@ -1,16 +1,14 @@
import type { RequestHandler } from "express";
import { getAccountForRequest } from "../../services/loginService.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { addMiscItems, getInventory } from "../../services/inventoryService.ts";
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
import type { RivenFingerprint } from "../../helpers/rivenHelper.ts";
import { createUnveiledRivenFingerprint, randomiseRivenStats } from "../../helpers/rivenHelper.ts";
import { ExportUpgrades } from "warframe-public-export-plus";
import type { IOidWithLegacySupport } from "../../types/commonTypes.ts";
import { toObjectId, toOid2 } from "../../helpers/inventoryHelpers.ts";
import type { IOid } from "../../types/commonTypes.ts";
export const rerollRandomModController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
const accountId = account._id.toString();
const accountId = await getAccountIdForRequest(req);
const request = getJSONfromString<RerollRandomModRequest>(String(req.body));
if ("ItemIds" in request) {
const inventory = await getInventory(accountId, "Upgrades MiscItems");
@@ -42,7 +40,7 @@ export const rerollRandomModController: RequestHandler = async (req, res) => {
}
changes.push({
ItemId: toOid2(toObjectId(request.ItemIds[0]), account.BuildLabel),
ItemId: { $oid: request.ItemIds[0] },
UpgradeFingerprint: upgrade.UpgradeFingerprint,
PendingRerollFingerprint: upgrade.PendingRerollFingerprint
});
@@ -78,7 +76,7 @@ interface AwDangitRequest {
}
interface IChange {
ItemId: IOidWithLegacySupport;
ItemId: IOid;
UpgradeFingerprint?: string;
PendingRerollFingerprint?: string;
}

View File

@@ -1,22 +1,18 @@
import type { RequestHandler } from "express";
import type { ISaveLoadoutRequest } from "../../types/saveLoadoutTypes.ts";
import { handleInventoryItemConfigChange } from "../../services/saveLoadoutService.ts";
import { getAccountForRequest } from "../../services/loginService.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
export const saveLoadoutController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
const accountId = await getAccountIdForRequest(req);
const body: ISaveLoadoutRequest = getJSONfromString<ISaveLoadoutRequest>(String(req.body));
// console.log(util.inspect(body, { showHidden: false, depth: null, colors: true }));
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { UpgradeVer, ...equipmentChanges } = body;
const newLoadoutId = await handleInventoryItemConfigChange(
equipmentChanges,
account._id.toString(),
account.BuildLabel
);
const newLoadoutId = await handleInventoryItemConfigChange(equipmentChanges, accountId);
//send back new loadout id, if new loadout was added
if (newLoadoutId) {

View File

@@ -25,24 +25,11 @@ export const sellController: RequestHandler = async (req, res) => {
//console.log(JSON.stringify(payload, null, 2));
const accountId = await getAccountIdForRequest(req);
const requiredFields = new Set<keyof TInventoryDatabaseDocument>();
let sellCurrency = "SC_RegularCredits";
if (payload.SellCurrency) {
sellCurrency = payload.SellCurrency;
} else {
if (payload.SellForFusionPoints || payload.SellForPrimeBucks) {
if (payload.SellForFusionPoints) {
sellCurrency = "SC_FusionPoints";
}
if (payload.SellForPrimeBucks) {
sellCurrency = "SC_PrimeBucks";
}
}
}
if (sellCurrency == "SC_RegularCredits") {
if (payload.SellCurrency == "SC_RegularCredits") {
requiredFields.add("RegularCredits");
} else if (sellCurrency == "SC_FusionPoints") {
} else if (payload.SellCurrency == "SC_FusionPoints") {
requiredFields.add("FusionPoints");
} else if (sellCurrency == "SC_CrewShipFusionPoints") {
} else if (payload.SellCurrency == "SC_CrewShipFusionPoints") {
requiredFields.add("CrewShipFusionPoints");
} else {
requiredFields.add("MiscItems");
@@ -96,27 +83,27 @@ export const sellController: RequestHandler = async (req, res) => {
const inventory = await getInventory(accountId, Array.from(requiredFields).join(" "));
// Give currency
if (sellCurrency == "SC_RegularCredits") {
if (payload.SellCurrency == "SC_RegularCredits") {
inventory.RegularCredits += payload.SellPrice;
} else if (sellCurrency == "SC_FusionPoints") {
} else if (payload.SellCurrency == "SC_FusionPoints") {
addFusionPoints(inventory, payload.SellPrice);
} else if (sellCurrency == "SC_CrewShipFusionPoints") {
} else if (payload.SellCurrency == "SC_CrewShipFusionPoints") {
addCrewShipFusionPoints(inventory, payload.SellPrice);
} else if (sellCurrency == "SC_PrimeBucks") {
} else if (payload.SellCurrency == "SC_PrimeBucks") {
addMiscItems(inventory, [
{
ItemType: "/Lotus/Types/Items/MiscItems/PrimeBucks",
ItemCount: payload.SellPrice
}
]);
} else if (sellCurrency == "SC_DistillPoints") {
} else if (payload.SellCurrency == "SC_DistillPoints") {
addMiscItems(inventory, [
{
ItemType: "/Lotus/Types/Items/MiscItems/DistillPoints",
ItemCount: payload.SellPrice
}
]);
} else if (sellCurrency == "SC_Resources") {
} else if (payload.SellCurrency == "SC_Resources") {
// Will add appropriate MiscItems from CrewShipWeapons or CrewShipWeaponSkins
} else {
throw new Error("Unknown SellCurrency: " + payload.SellCurrency);
@@ -231,7 +218,7 @@ export const sellController: RequestHandler = async (req, res) => {
} else {
const index = inventory.CrewShipWeapons.findIndex(x => x._id.equals(sellItem.String));
if (index != -1) {
if (sellCurrency == "SC_Resources") {
if (payload.SellCurrency == "SC_Resources") {
refundPartialBuildCosts(inventory, inventory.CrewShipWeapons[index].ItemType, inventoryChanges);
}
inventory.CrewShipWeapons.splice(index, 1);
@@ -254,7 +241,7 @@ export const sellController: RequestHandler = async (req, res) => {
} else {
const index = inventory.CrewShipWeaponSkins.findIndex(x => x._id.equals(sellItem.String));
if (index != -1) {
if (sellCurrency == "SC_Resources") {
if (payload.SellCurrency == "SC_Resources") {
refundPartialBuildCosts(
inventory,
inventory.CrewShipWeaponSkins[index].ItemType,
@@ -359,7 +346,7 @@ interface ISellRequest {
WeaponSkins?: ISellItem[]; // SNS specific field
};
SellPrice: number;
SellCurrency?:
SellCurrency:
| "SC_RegularCredits"
| "SC_PrimeBucks"
| "SC_FusionPoints"
@@ -368,9 +355,6 @@ interface ISellRequest {
| "SC_Resources"
| "somethingelsewemightnotknowabout";
buildLabel: string;
// These are used in old builds (undetermined where it changed) instead of SellCurrency
SellForPrimeBucks?: boolean;
SellForFusionPoints?: boolean;
}
interface ISellItem {

View File

@@ -24,8 +24,7 @@ export const startRecipeController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
let recipeName = startRecipeRequest.RecipeName;
if (req.query.recipeName) recipeName = String(req.query.recipeName); // U8
const recipeName = startRecipeRequest.RecipeName;
const recipe = getRecipe(recipeName);
if (!recipe) {
@@ -69,9 +68,7 @@ export const startRecipeController: RequestHandler = async (req, res) => {
freeUpSlot(inventory, InventorySlot.WEAPONS);
}
} else {
const itemType = recipe.ingredients[i].ItemType;
const itemCount = recipe.ingredients[i].ItemCount;
await addItem(inventory, itemType, itemCount * -1);
await addItem(inventory, recipe.ingredients[i].ItemType, recipe.ingredients[i].ItemCount * -1);
}
}

View File

@@ -1,288 +1,155 @@
import type { RequestHandler } from "express";
import { fromOid, version_compare } from "../../helpers/inventoryHelpers.ts";
import type { IUpgradesRequest, IUpgradesRequestLegacy } from "../../types/requestTypes.ts";
import type { IUpgradesRequest } from "../../types/requestTypes.ts";
import type { ArtifactPolarity, IAbilityOverride } from "../../types/inventoryTypes/commonInventoryTypes.ts";
import type { IInventoryClient, IMiscItem } from "../../types/inventoryTypes/inventoryTypes.ts";
import { getAccountForRequest } from "../../services/loginService.ts";
import { addMiscItems, addMods, addRecipes, getInventory, updateCurrency } from "../../services/inventoryService.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { addMiscItems, addRecipes, getInventory, updateCurrency } from "../../services/inventoryService.ts";
import { getRecipeByResult } from "../../services/itemDataService.ts";
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
import { addInfestedFoundryXP, applyCheatsToInfestedFoundry } from "../../services/infestedFoundryService.ts";
import { sendWsBroadcastTo } from "../../services/wsService.ts";
import type { IEquipmentDatabase } from "../../types/equipmentTypes.ts";
import { EquipmentFeatures } from "../../types/equipmentTypes.ts";
import { Types } from "mongoose";
export const upgradesController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
const accountId = account._id.toString();
const accountId = await getAccountIdForRequest(req);
const payload = JSON.parse(String(req.body)) as IUpgradesRequest;
const inventory = await getInventory(accountId);
const inventoryChanges: IInventoryChanges = {};
if (account.BuildLabel && version_compare(account.BuildLabel, "2019.03.07.20.21") < 0) {
// Builds before U24.4.0 have a different request format
const payload = JSON.parse(String(req.body)) as IUpgradesRequestLegacy;
const itemId = fromOid(payload.Weapon.ItemId);
if (itemId) {
if (payload.IsSwappingOperation === true) {
const item = inventory[payload.Category].id(itemId)!;
for (let i = 0; i != payload.PolarityRemap.length; ++i) {
// Can't really be selective here like the newer format, it pushes everything in a way that the comparison fails against...
setSlotPolarity(item, i, payload.PolarityRemap[i].Value);
}
} else {
if (payload.PolarizeReq) {
switch (payload.PolarizeReq) {
case "/Lotus/Types/Items/MiscItems/Forma":
case "/Lotus/Types/Items/MiscItems/FormaUmbra": {
const item = inventory[payload.Category].id(itemId)!;
item.XP = 0;
setSlotPolarity(item, payload.PolarizeSlot, payload.PolarizeValue);
item.Polarized ??= 0;
item.Polarized += 1;
sendWsBroadcastTo(accountId, { update_inventory: true });
break;
}
default:
throw new Error("Unsupported polarize item: " + payload.PolarizeReq);
}
addMiscItems(inventory, [
{
ItemType: payload.PolarizeReq,
ItemCount: -1
} satisfies IMiscItem
]);
}
if (payload.UtilityReq) {
switch (payload.UtilityReq) {
case "/Lotus/Types/Items/MiscItems/UtilityUnlocker": {
const item = inventory[payload.Category].id(itemId)!;
item.Features ??= 0;
item.Features |= EquipmentFeatures.UTILITY_SLOT;
break;
}
default:
throw new Error("Unsupported utility item: " + payload.UtilityReq);
}
addMiscItems(inventory, [
{
ItemType: payload.UtilityReq,
ItemCount: -1
} satisfies IMiscItem
]);
}
if (payload.UpgradeReq) {
switch (payload.UpgradeReq) {
case "/Lotus/Types/Items/MiscItems/OrokinReactor":
case "/Lotus/Types/Items/MiscItems/OrokinCatalyst": {
const item = inventory[payload.Category].id(itemId)!;
item.Features ??= 0;
item.Features |= EquipmentFeatures.DOUBLE_CAPACITY;
break;
}
default:
throw new Error("Unsupported upgrade: " + payload.UpgradeReq);
}
addMiscItems(inventory, [
{
ItemType: payload.UpgradeReq,
ItemCount: -1
} satisfies IMiscItem
]);
}
}
// Handle attaching/detaching mods in U7-U8
if (payload.UpgradesToAttach && payload.UpgradesToAttach.length > 0) {
const item = inventory[payload.Category].id(itemId)!;
if (!item.Configs[0]) {
item.Configs.push({ Upgrades: ["", "", "", "", "", "", "", "", "", "", ""] });
}
if (item.Configs[0].Upgrades && item.Configs[0].Upgrades.length < 11) {
item.Configs[0].Upgrades.length = 11;
}
payload.UpgradesToAttach.forEach(upgrade => {
if (item.Configs[0].Upgrades && upgrade.ItemId.$id && upgrade.Slot) {
const arr = item.Configs[0].Upgrades;
if (arr.indexOf(upgrade.ItemId.$id) != -1) {
// Handle swapping mod to a different slot
arr[arr.indexOf(upgrade.ItemId.$id)] = "";
}
// We need to convert RawUpgrade into Upgrade once it's attached
const rawUpgrade = inventory.RawUpgrades.id(upgrade.ItemId.$id);
if (rawUpgrade) {
const newId = new Types.ObjectId().toString();
arr[upgrade.Slot - 1] = newId;
addMods(inventory, [
{
ItemType: upgrade.ItemType,
ItemCount: -1
}
]);
inventory.Upgrades.push({
UpgradeFingerprint: `{"lvl":0}`,
ItemType: upgrade.ItemType,
_id: newId
});
} else {
arr[upgrade.Slot - 1] = upgrade.ItemId.$id;
}
}
});
}
if (payload.UpgradesToDetach && payload.UpgradesToDetach.length > 0) {
const item = inventory[payload.Category].id(itemId)!;
if (item.Configs[0].Upgrades && item.Configs[0].Upgrades.length < 11) {
item.Configs[0].Upgrades.length = 11;
}
payload.UpgradesToDetach.forEach(upgrade => {
if (item.Configs[0].Upgrades && upgrade.ItemId.$id) {
const arr = item.Configs[0].Upgrades;
arr[arr.indexOf(upgrade.ItemId.$id)] = "";
}
});
for (const operation of payload.Operations) {
if (
operation.UpgradeRequirement == "/Lotus/Types/Items/MiscItems/ModSlotUnlocker" ||
operation.UpgradeRequirement == "/Lotus/Types/Items/MiscItems/CustomizationSlotUnlocker"
) {
updateCurrency(inventory, 10, true);
} else if (
operation.OperationType != "UOT_SWAP_POLARITY" &&
operation.OperationType != "UOT_ABILITY_OVERRIDE"
) {
if (!operation.UpgradeRequirement) {
throw new Error(`${operation.OperationType} operation should be free?`);
}
addMiscItems(inventory, [
{
ItemType: operation.UpgradeRequirement,
ItemCount: -1
} satisfies IMiscItem
]);
}
} else {
const payload = JSON.parse(String(req.body)) as IUpgradesRequest;
for (const operation of payload.Operations) {
if (
operation.UpgradeRequirement == "/Lotus/Types/Items/MiscItems/ModSlotUnlocker" ||
operation.UpgradeRequirement == "/Lotus/Types/Items/MiscItems/CustomizationSlotUnlocker"
) {
updateCurrency(inventory, 10, true);
} else if (
operation.OperationType != "UOT_SWAP_POLARITY" &&
operation.OperationType != "UOT_ABILITY_OVERRIDE"
) {
if (!operation.UpgradeRequirement) {
throw new Error(`${operation.OperationType} operation should be free?`);
if (operation.OperationType == "UOT_ABILITY_OVERRIDE") {
console.assert(payload.ItemCategory == "Suits");
const suit = inventory.Suits.id(payload.ItemId.$oid)!;
let newAbilityOverride: IAbilityOverride | undefined;
let totalPercentagePointsConsumed = 0;
if (operation.UpgradeRequirement != "") {
newAbilityOverride = {
Ability: operation.UpgradeRequirement,
Index: operation.PolarizeSlot
};
const recipe = getRecipeByResult(operation.UpgradeRequirement)!;
for (const ingredient of recipe.ingredients) {
totalPercentagePointsConsumed += ingredient.ItemCount / 10;
if (!inventory.infiniteHelminthMaterials) {
inventory.InfestedFoundry!.Resources!.find(x => x.ItemType == ingredient.ItemType)!.Count -=
ingredient.ItemCount;
}
}
addMiscItems(inventory, [
{
ItemType: operation.UpgradeRequirement,
ItemCount: -1
} satisfies IMiscItem
]);
}
if (operation.OperationType == "UOT_ABILITY_OVERRIDE") {
console.assert(payload.ItemCategory == "Suits");
const suit = inventory.Suits.id(payload.ItemId.$oid)!;
for (const entry of operation.PolarityRemap) {
suit.Configs[entry.Slot] ??= {};
suit.Configs[entry.Slot].AbilityOverride = newAbilityOverride;
}
let newAbilityOverride: IAbilityOverride | undefined;
let totalPercentagePointsConsumed = 0;
if (operation.UpgradeRequirement != "") {
newAbilityOverride = {
Ability: operation.UpgradeRequirement,
Index: operation.PolarizeSlot
};
const recipeChanges = addInfestedFoundryXP(inventory.InfestedFoundry!, totalPercentagePointsConsumed * 8);
addRecipes(inventory, recipeChanges);
const recipe = getRecipeByResult(operation.UpgradeRequirement)!;
for (const ingredient of recipe.ingredients) {
totalPercentagePointsConsumed += ingredient.ItemCount / 10;
if (!inventory.infiniteHelminthMaterials) {
inventory.InfestedFoundry!.Resources!.find(x => x.ItemType == ingredient.ItemType)!.Count -=
ingredient.ItemCount;
inventoryChanges.Recipes = recipeChanges;
inventoryChanges.InfestedFoundry = inventory.toJSON<IInventoryClient>().InfestedFoundry;
applyCheatsToInfestedFoundry(inventory, inventoryChanges.InfestedFoundry!);
} else
switch (operation.UpgradeRequirement) {
case "/Lotus/Types/Items/MiscItems/OrokinReactor":
case "/Lotus/Types/Items/MiscItems/OrokinCatalyst": {
const item = inventory[payload.ItemCategory].id(payload.ItemId.$oid)!;
item.Features ??= 0;
item.Features |= EquipmentFeatures.DOUBLE_CAPACITY;
break;
}
case "/Lotus/Types/Items/MiscItems/UtilityUnlocker":
case "/Lotus/Types/Items/MiscItems/WeaponUtilityUnlocker": {
const item = inventory[payload.ItemCategory].id(payload.ItemId.$oid)!;
item.Features ??= 0;
item.Features |= EquipmentFeatures.UTILITY_SLOT;
break;
}
case "/Lotus/Types/Items/MiscItems/HeavyWeaponCatalyst": {
console.assert(payload.ItemCategory == "SpaceGuns");
const item = inventory[payload.ItemCategory].id(payload.ItemId.$oid)!;
item.Features ??= 0;
item.Features |= EquipmentFeatures.GRAVIMAG_INSTALLED;
break;
}
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": {
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;
}
break;
}
case "/Lotus/Types/Items/MiscItems/ValenceAdapter": {
const item = inventory[payload.ItemCategory].id(payload.ItemId.$oid)!;
item.Features ??= 0;
item.Features |= EquipmentFeatures.VALENCE_SWAP;
break;
}
case "/Lotus/Types/Items/MiscItems/Forma":
case "/Lotus/Types/Items/MiscItems/FormaUmbra":
case "/Lotus/Types/Items/MiscItems/FormaAura":
case "/Lotus/Types/Items/MiscItems/FormaStance": {
const item = inventory[payload.ItemCategory].id(payload.ItemId.$oid)!;
item.XP = 0;
setSlotPolarity(item, operation.PolarizeSlot, operation.PolarizeValue);
item.Polarized ??= 0;
item.Polarized += 1;
sendWsBroadcastTo(accountId, { update_inventory: true }); // webui may need to to re-add "max rank" button
break;
}
case "/Lotus/Types/Items/MiscItems/ModSlotUnlocker": {
const item = inventory[payload.ItemCategory].id(payload.ItemId.$oid)!;
item.ModSlotPurchases ??= 0;
item.ModSlotPurchases += 1;
break;
}
case "/Lotus/Types/Items/MiscItems/CustomizationSlotUnlocker": {
const item = inventory[payload.ItemCategory].id(payload.ItemId.$oid)!;
item.CustomizationSlotPurchases ??= 0;
item.CustomizationSlotPurchases += 1;
break;
}
case "": {
console.assert(operation.OperationType == "UOT_SWAP_POLARITY");
const item = inventory[payload.ItemCategory].id(payload.ItemId.$oid)!;
for (let i = 0; i != operation.PolarityRemap.length; ++i) {
if (operation.PolarityRemap[i].Slot != i) {
setSlotPolarity(item, i, operation.PolarityRemap[i].Value);
}
}
break;
}
for (const entry of operation.PolarityRemap) {
suit.Configs[entry.Slot] ??= {};
suit.Configs[entry.Slot].AbilityOverride = newAbilityOverride;
}
const recipeChanges = addInfestedFoundryXP(
inventory.InfestedFoundry!,
totalPercentagePointsConsumed * 8
);
addRecipes(inventory, recipeChanges);
inventoryChanges.Recipes = recipeChanges;
inventoryChanges.InfestedFoundry = inventory.toJSON<IInventoryClient>().InfestedFoundry;
applyCheatsToInfestedFoundry(inventory, inventoryChanges.InfestedFoundry!);
} else
switch (operation.UpgradeRequirement) {
case "/Lotus/Types/Items/MiscItems/OrokinReactor":
case "/Lotus/Types/Items/MiscItems/OrokinCatalyst": {
const item = inventory[payload.ItemCategory].id(payload.ItemId.$oid)!;
item.Features ??= 0;
item.Features |= EquipmentFeatures.DOUBLE_CAPACITY;
break;
}
case "/Lotus/Types/Items/MiscItems/UtilityUnlocker":
case "/Lotus/Types/Items/MiscItems/WeaponUtilityUnlocker": {
const item = inventory[payload.ItemCategory].id(payload.ItemId.$oid)!;
item.Features ??= 0;
item.Features |= EquipmentFeatures.UTILITY_SLOT;
break;
}
case "/Lotus/Types/Items/MiscItems/HeavyWeaponCatalyst": {
console.assert(payload.ItemCategory == "SpaceGuns");
const item = inventory[payload.ItemCategory].id(payload.ItemId.$oid)!;
item.Features ??= 0;
item.Features |= EquipmentFeatures.GRAVIMAG_INSTALLED;
break;
}
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": {
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;
}
break;
}
case "/Lotus/Types/Items/MiscItems/ValenceAdapter": {
const item = inventory[payload.ItemCategory].id(payload.ItemId.$oid)!;
item.Features ??= 0;
item.Features |= EquipmentFeatures.VALENCE_SWAP;
break;
}
case "/Lotus/Types/Items/MiscItems/Forma":
case "/Lotus/Types/Items/MiscItems/FormaUmbra":
case "/Lotus/Types/Items/MiscItems/FormaAura":
case "/Lotus/Types/Items/MiscItems/FormaStance": {
const item = inventory[payload.ItemCategory].id(payload.ItemId.$oid)!;
item.XP = 0;
setSlotPolarity(item, operation.PolarizeSlot, operation.PolarizeValue);
item.Polarized ??= 0;
item.Polarized += 1;
sendWsBroadcastTo(accountId, { update_inventory: true }); // webui may need to to re-add "max rank" button
break;
}
case "/Lotus/Types/Items/MiscItems/ModSlotUnlocker": {
const item = inventory[payload.ItemCategory].id(payload.ItemId.$oid)!;
item.ModSlotPurchases ??= 0;
item.ModSlotPurchases += 1;
break;
}
case "/Lotus/Types/Items/MiscItems/CustomizationSlotUnlocker": {
const item = inventory[payload.ItemCategory].id(payload.ItemId.$oid)!;
item.CustomizationSlotPurchases ??= 0;
item.CustomizationSlotPurchases += 1;
break;
}
case "": {
console.assert(operation.OperationType == "UOT_SWAP_POLARITY");
const item = inventory[payload.ItemCategory].id(payload.ItemId.$oid)!;
for (let i = 0; i != operation.PolarityRemap.length; ++i) {
if (operation.PolarityRemap[i].Slot != i) {
setSlotPolarity(item, i, operation.PolarityRemap[i].Value);
}
}
break;
}
default:
throw new Error("Unsupported upgrade: " + operation.UpgradeRequirement);
}
}
default:
throw new Error("Unsupported upgrade: " + operation.UpgradeRequirement);
}
}
await inventory.save();
res.json({ InventoryChanges: inventoryChanges });

View File

@@ -35,7 +35,6 @@ export const completeAllMissionsController: RequestHandler = async (req, res) =>
await handleStoreItemAcquisition(reward.StoreItem, inventory, reward.ItemCount, undefined, true);
}
addString(inventory.NodeIntrosCompleted, "TeshinHardModeUnlocked");
addString(inventory.NodeIntrosCompleted, "CetusInvasionNodeIntro");
addString(inventory.NodeIntrosCompleted, "CetusSyndicate_IntroJob");
let syndicate = inventory.Affiliations.find(x => x.Tag == "CetusSyndicate");
if (!syndicate) {

View File

@@ -13,46 +13,33 @@ export const importController: RequestHandler = async (req, res) => {
const request = req.body as IImportRequest;
let anyKnownKey = false;
try {
const inventory = await getInventory(accountId);
importInventory(inventory, request.inventory);
if (inventory.isModified()) {
anyKnownKey = true;
await inventory.save();
}
if ("LoadOutPresets" in request.inventory && request.inventory.LoadOutPresets) {
const loadout = await getLoadout(accountId);
importLoadOutPresets(loadout, request.inventory.LoadOutPresets);
if (loadout.isModified()) {
anyKnownKey = true;
await loadout.save();
}
}
if (
request.inventory.Ship?.Rooms || // very old accounts may have Ship with { Features: [ ... ] }
"Apartment" in request.inventory ||
"TailorShop" in request.inventory
) {
const personalRooms = await getPersonalRooms(accountId);
importPersonalRooms(personalRooms, request.inventory);
if (personalRooms.isModified()) {
anyKnownKey = true;
await personalRooms.save();
}
}
if (!anyKnownKey) {
res.send("noKnownKey").end();
}
broadcastInventoryUpdate(req);
} catch (e) {
console.error(e);
res.send((e as Error).message);
const inventory = await getInventory(accountId);
if (importInventory(inventory, request.inventory)) {
anyKnownKey = true;
await inventory.save();
}
res.end();
if ("LoadOutPresets" in request.inventory && request.inventory.LoadOutPresets) {
anyKnownKey = true;
const loadout = await getLoadout(accountId);
importLoadOutPresets(loadout, request.inventory.LoadOutPresets);
await loadout.save();
}
if (
request.inventory.Ship?.Rooms || // very old accounts may have Ship with { Features: [ ... ] }
"Apartment" in request.inventory ||
"TailorShop" in request.inventory
) {
anyKnownKey = true;
const personalRooms = await getPersonalRooms(accountId);
importPersonalRooms(personalRooms, request.inventory);
await personalRooms.save();
}
res.json(anyKnownKey);
broadcastInventoryUpdate(req);
};
interface IImportRequest {

View File

@@ -1,22 +0,0 @@
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getInventory } from "../../services/inventoryService.ts";
import type { RequestHandler } from "express";
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
export const setUmbraEchoesController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const request = req.body as ISetUmbraEchoesRequest;
const inventory = await getInventory(accountId, "Suits");
const suit = inventory.Suits.id(request.oid);
if (suit) {
suit.UmbraDate = request.UmbraDate ? new Date(request.UmbraDate) : undefined;
await inventory.save();
broadcastInventoryUpdate(req);
}
res.end();
};
interface ISetUmbraEchoesRequest {
oid: string;
UmbraDate: number;
}

View File

@@ -3,11 +3,10 @@ import allScans from "../../../static/fixed_responses/allScans.json" with { type
import { ExportEnemies } from "warframe-public-export-plus";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getStats } from "../../services/statsService.ts";
import { getInventory } from "../../services/inventoryService.ts";
export const unlockAllScansController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const [stats, inventory] = await Promise.all([getStats(accountId), getInventory(accountId, "ChallengeProgress")]);
const stats = await getStats(accountId);
const scanTypes = new Set<string>(allScans);
for (const type of Object.keys(ExportEnemies.avatars)) {
@@ -19,17 +18,6 @@ export const unlockAllScansController: RequestHandler = async (req, res) => {
stats.Scans.push({ type, scans: 9999 });
}
const jsCodex = inventory.ChallengeProgress.find(x => x.Name === "JSCodexScan");
if (jsCodex) {
jsCodex.Progress = 1;
} else {
inventory.ChallengeProgress.push({
Name: "JSCodexScan",
Progress: 1
});
}
await Promise.all([stats.save(), inventory.save()]);
await stats.save();
res.end();
};

View File

@@ -1,7 +1,7 @@
import { getLeaderboard } from "../../services/leaderboardService.ts";
import type { RequestHandler } from "express";
export const leaderboardPostController: RequestHandler = async (req, res) => {
export const leaderboardController: RequestHandler = async (req, res) => {
const payload = JSON.parse(String(req.body)) as ILeaderboardRequest;
res.json({
results: await getLeaderboard(
@@ -15,33 +15,6 @@ export const leaderboardPostController: RequestHandler = async (req, res) => {
});
};
export const leaderboardGetController: RequestHandler = async (req, res) => {
const payload: ILeaderboardRequest = {
field: "archived." + String(req.query.field),
before: Number(req.query.before),
after: Number(req.query.after),
pivotId: req.query.pivotAccountId ? String(req.query.pivotAccountId) : undefined,
guildId: undefined,
guildTier: undefined
};
res.json({
players: (
await getLeaderboard(
payload.field,
payload.before,
payload.after,
payload.pivotId,
payload.guildId,
payload.guildTier
)
).map(entry => ({
DisplayName: entry.n,
score: entry.s,
rank: entry.r
}))
});
};
interface ILeaderboardRequest {
field: string;
before: number;

View File

@@ -2,7 +2,6 @@ interface IArguments {
configPath?: string;
dev?: boolean;
secret?: string;
docker?: boolean;
}
export const args: IArguments = {};
@@ -20,9 +19,5 @@ for (let i = 2; i < process.argv.length; ) {
case "--secret":
args.secret = process.argv[i++];
break;
case "--docker":
args.docker = true;
break;
}
}

View File

@@ -20,10 +20,6 @@ export const version_compare = (a: string, b: string): number => {
return 0;
};
export const toObjectId = (s: string): Types.ObjectId => {
return new Types.ObjectId(s);
};
export const toOid = (objectId: Types.ObjectId): IOid => {
return { $oid: objectId.toString() };
};

View File

@@ -1060,6 +1060,7 @@ const EquipmentSchema = new Schema<IEquipmentDatabase>(
InfestationDays: Number,
InfestationType: String,
ModularParts: { type: [String], default: undefined },
UnlockLevel: Number,
Expiry: Date,
SkillTree: String,
OffensiveUpgrade: String,
@@ -1447,7 +1448,6 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
// SNS account cheats
skipAllDialogue: Boolean,
skipAllPopups: Boolean,
dontSubtractPurchaseCreditCost: Boolean,
dontSubtractPurchasePlatinumCost: Boolean,
dontSubtractPurchaseItemCost: Boolean,
@@ -1492,8 +1492,6 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
relicRewardItemCountMultiplier: { type: Number, default: 1 },
nightwaveStandingMultiplier: { type: Number, default: 1 },
Created: Date,
SubscribedToEmails: { type: Number, default: 0 },
SubscribedToEmailsPersonalized: { type: Number, default: 0 },
RewardSeed: BigInt,

View File

@@ -20,7 +20,6 @@ import { cancelGuildAdvertisementController } from "../controllers/api/cancelGui
import { changeDojoRootController } from "../controllers/api/changeDojoRootController.ts";
import { changeGuildRankController } from "../controllers/api/changeGuildRankController.ts";
import { checkDailyMissionBonusController } from "../controllers/api/checkDailyMissionBonusController.ts";
import { checkPendingRecipesController } from "../controllers/api/checkPendingRecipesController.ts";
import { claimCompletedRecipeController } from "../controllers/api/claimCompletedRecipeController.ts";
import { claimJunctionChallengeRewardController } from "../controllers/api/claimJunctionChallengeRewardController.ts";
import { claimLibraryDailyTaskRewardController } from "../controllers/api/claimLibraryDailyTaskRewardController.ts";
@@ -185,7 +184,6 @@ apiRouter.get("/cancelGuildAdvertisement.php", cancelGuildAdvertisementControlle
apiRouter.get("/changeDojoRoot.php", changeDojoRootController);
apiRouter.get("/changeGuildRank.php", changeGuildRankController);
apiRouter.get("/checkDailyMissionBonus.php", checkDailyMissionBonusController);
apiRouter.get("/checkPendingRecipes.php", checkPendingRecipesController); // U8
apiRouter.get("/claimLibraryDailyTaskReward.php", claimLibraryDailyTaskRewardController);
apiRouter.get("/completeCalendarEvent.php", completeCalendarEventController);
apiRouter.get("/confirmAllianceInvitation.php", confirmAllianceInvitationController);
@@ -307,7 +305,6 @@ apiRouter.post("/guildTech.php", guildTechController);
apiRouter.post("/hostSession.php", hostSessionController);
apiRouter.post("/hubBlessing.php", hubBlessingController);
apiRouter.post("/infestedFoundry.php", infestedFoundryController);
apiRouter.post("/instantCompleteRecipe.php", claimCompletedRecipeController); // U8
apiRouter.post("/inventorySlots.php", inventorySlotsController);
apiRouter.post("/joinSession.php", joinSessionController);
apiRouter.post("/login.php", loginController);

View File

@@ -46,7 +46,6 @@ import { updateFingerprintController } from "../controllers/custom/updateFingerp
import { unlockLevelCapController } from "../controllers/custom/unlockLevelCapController.ts";
import { changeModularPartsController } from "../controllers/custom/changeModularPartsController.ts";
import { setInvigorationController } from "../controllers/custom/setInvigorationController.ts";
import { setUmbraEchoesController } from "../controllers/custom/setUmbraEchoesController.ts";
import { setAccountCheatController } from "../controllers/custom/setAccountCheatController.ts";
import { setGuildCheatController } from "../controllers/custom/setGuildCheatController.ts";
@@ -98,7 +97,6 @@ customRouter.post("/updateFingerprint", updateFingerprintController);
customRouter.post("/unlockLevelCap", unlockLevelCapController);
customRouter.post("/changeModularParts", changeModularPartsController);
customRouter.post("/setInvigoration", setInvigorationController);
customRouter.post("/setUmbraEchoes", setUmbraEchoesController);
customRouter.post("/setAccountCheat", setAccountCheatController);
customRouter.post("/setGuildCheat", setGuildCheatController);

View File

@@ -1,16 +1,14 @@
import express from "express";
import { viewController } from "../controllers/stats/viewController.ts";
import { uploadController } from "../controllers/stats/uploadController.ts";
import { leaderboardPostController, leaderboardGetController } from "../controllers/stats/leaderboardController.ts";
import { leaderboardController } from "../controllers/stats/leaderboardController.ts";
const statsRouter = express.Router();
statsRouter.get("/view.php", viewController);
statsRouter.get("/profileStats.php", viewController);
statsRouter.get("/leaderboard.php", leaderboardGetController);
statsRouter.post("/upload.php", uploadController);
statsRouter.post("/view.php", viewController);
statsRouter.post("/leaderboardWeekly.php", leaderboardPostController);
statsRouter.post("/leaderboardArchived.php", leaderboardPostController);
statsRouter.post("/leaderboardWeekly.php", leaderboardController);
statsRouter.post("/leaderboardArchived.php", leaderboardController);
export { statsRouter };

View File

@@ -5,11 +5,13 @@ import { repoDir } from "../helpers/pathHelper.ts";
interface IBuildConfig {
version: string;
buildLabel: string;
matchmakingBuildId: string;
}
export const buildConfig: IBuildConfig = {
version: "",
buildLabel: ""
buildLabel: "",
matchmakingBuildId: ""
};
const buildConfigPath = path.join(repoDir, "static/data/buildConfig.json");

View File

@@ -160,11 +160,6 @@ export const configRemovedOptionsKeys = [
"relicRewardItemCountMultiplier",
"nightwaveStandingMultiplier"
];
if (args.docker) {
configRemovedOptionsKeys.push("bindAddress");
configRemovedOptionsKeys.push("httpPort");
configRemovedOptionsKeys.push("httpsPort");
}
export const configPath = path.join(repoDir, args.configPath ?? "config.json");

View File

@@ -254,17 +254,21 @@ const convertItemConfig = <T extends IItemConfig>(client: T): T => {
};
};
export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<IInventoryClient>): void => {
export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<IInventoryClient>): boolean => {
let anyKnownKey = false;
for (const key of equipmentKeys) {
if (client[key] !== undefined) {
anyKnownKey = true;
replaceArray<IEquipmentDatabase>(db[key], client[key].map(convertEquipment));
}
}
if (client.WeaponSkins !== undefined) {
anyKnownKey = true;
replaceArray<IWeaponSkinDatabase>(db.WeaponSkins, client.WeaponSkins.map(convertWeaponSkin));
}
for (const key of ["Upgrades", "CrewShipSalvagedWeaponSkins", "CrewShipWeaponSkins"] as const) {
if (client[key] !== undefined) {
anyKnownKey = true;
replaceArray<IUpgradeDatabase>(db[key], client[key].map(convertUpgrade));
}
}
@@ -280,6 +284,7 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
"CrewShipRawSalvage"
] as const) {
if (client[key] !== undefined) {
anyKnownKey = true;
db[key].splice(0, db[key].length);
client[key].forEach(x => {
db[key].push({
@@ -291,11 +296,13 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
}
for (const key of ["AdultOperatorLoadOuts", "OperatorLoadOuts", "KahlLoadOuts"] as const) {
if (client[key] !== undefined) {
anyKnownKey = true;
replaceArray<IOperatorConfigDatabase>(db[key], client[key].map(convertOperatorConfig));
}
}
for (const key of slotNames) {
if (client[key] !== undefined) {
anyKnownKey = true;
replaceSlots(db[key], client[key]);
}
}
@@ -312,6 +319,7 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
"Counselor"
] as const) {
if (client[key] !== undefined) {
anyKnownKey = true;
db[key] = client[key];
}
}
@@ -338,6 +346,7 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
"EchoesHexConquestCacheScoreMission"
] as const) {
if (client[key] !== undefined) {
anyKnownKey = true;
db[key] = client[key];
}
}
@@ -353,6 +362,7 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
"ActiveAvatarImageType"
] as const) {
if (client[key] !== undefined) {
anyKnownKey = true;
db[key] = client[key];
}
}
@@ -369,6 +379,7 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
"EchoesHexConquestActiveStickers"
] as const) {
if (client[key] !== undefined) {
anyKnownKey = true;
db[key] = client[key];
}
}
@@ -382,103 +393,133 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
"EntratiVaultCountResetDate"
] as const) {
if (client[key] !== undefined) {
anyKnownKey = true;
db[key] = fromMongoDate(client[key]);
}
}
// IRewardAtten[]
for (const key of ["SortieRewardAttenuation", "SpecialItemRewardAttenuation"] as const) {
if (client[key] !== undefined) {
anyKnownKey = true;
db[key] = client[key];
}
}
if (client.XPInfo !== undefined) {
anyKnownKey = true;
db.XPInfo = client.XPInfo;
}
if (client.CurrentLoadOutIds !== undefined) {
anyKnownKey = true;
db.CurrentLoadOutIds = client.CurrentLoadOutIds;
}
if (client.Affiliations !== undefined) {
anyKnownKey = true;
db.Affiliations = client.Affiliations;
}
if (client.FusionTreasures !== undefined) {
anyKnownKey = true;
db.FusionTreasures = client.FusionTreasures;
}
if (client.FocusUpgrades !== undefined) {
anyKnownKey = true;
db.FocusUpgrades = client.FocusUpgrades;
}
if (client.EvolutionProgress !== undefined) {
anyKnownKey = true;
db.EvolutionProgress = client.EvolutionProgress;
}
if (client.InfestedFoundry !== undefined) {
anyKnownKey = true;
db.InfestedFoundry = convertInfestedFoundry(client.InfestedFoundry);
}
if (client.DialogueHistory !== undefined) {
anyKnownKey = true;
db.DialogueHistory = convertDialogueHistory(client.DialogueHistory);
}
if (client.CustomMarkers !== undefined) {
anyKnownKey = true;
db.CustomMarkers = client.CustomMarkers;
}
if (client.ChallengeProgress !== undefined) {
anyKnownKey = true;
db.ChallengeProgress = client.ChallengeProgress;
}
if (client.QuestKeys !== undefined) {
anyKnownKey = true;
replaceArray<IQuestKeyDatabase>(db.QuestKeys, client.QuestKeys.map(convertQuestKey));
}
if (client.LastRegionPlayed !== undefined) {
anyKnownKey = true;
db.LastRegionPlayed = client.LastRegionPlayed;
}
if (client.PendingRecipes !== undefined) {
anyKnownKey = true;
replaceArray<IPendingRecipeDatabase>(db.PendingRecipes, client.PendingRecipes.map(convertPendingRecipe));
}
if (client.TauntHistory !== undefined) {
anyKnownKey = true;
db.TauntHistory = client.TauntHistory;
}
if (client.LoreFragmentScans !== undefined) {
anyKnownKey = true;
db.LoreFragmentScans = client.LoreFragmentScans;
}
for (const key of ["PendingSpectreLoadouts", "SpectreLoadouts"] as const) {
if (client[key] !== undefined) {
anyKnownKey = true;
db[key] = client[key];
}
}
if (client.FocusXP !== undefined) {
anyKnownKey = true;
db.FocusXP = client.FocusXP;
}
for (const key of ["Alignment", "AlignmentReplay"] as const) {
if (client[key] !== undefined) {
anyKnownKey = true;
db[key] = client[key];
}
}
if (client.StepSequencers !== undefined) {
anyKnownKey = true;
db.StepSequencers = client.StepSequencers;
}
if (client.CompletedJobChains !== undefined) {
anyKnownKey = true;
db.CompletedJobChains = client.CompletedJobChains;
}
if (client.Nemesis !== undefined) {
anyKnownKey = true;
db.Nemesis = convertNemesis(client.Nemesis);
}
if (client.PlayerSkills !== undefined) {
anyKnownKey = true;
db.PlayerSkills = client.PlayerSkills;
}
if (client.LotusCustomization !== undefined) {
anyKnownKey = true;
db.LotusCustomization = convertItemConfig(client.LotusCustomization);
}
if (client.CollectibleSeries !== undefined) {
anyKnownKey = true;
db.CollectibleSeries = client.CollectibleSeries;
}
for (const key of ["LibraryAvailableDailyTaskInfo", "LibraryActiveDailyTaskInfo"] as const) {
if (client[key] !== undefined) {
anyKnownKey = true;
db[key] = client[key];
}
}
if (client.SongChallenges !== undefined) {
anyKnownKey = true;
db.SongChallenges = client.SongChallenges;
}
if (client.Missions !== undefined) {
anyKnownKey = true;
db.Missions = client.Missions;
}
if (client.FlavourItems !== undefined) {
anyKnownKey = true;
db.FlavourItems.splice(0, db.FlavourItems.length);
client.FlavourItems.forEach(x => {
db.FlavourItems.push({
@@ -487,11 +528,14 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
});
}
if (client.Accolades !== undefined) {
anyKnownKey = true;
db.Accolades = client.Accolades;
}
if (client.Boosters !== undefined) {
anyKnownKey = true;
replaceArray<IBooster>(db.Boosters, client.Boosters);
}
return anyKnownKey;
};
export const importLoadOutConfig = (client: ILoadoutConfigClient): ILoadoutConfigDatabase => {

View File

@@ -33,7 +33,7 @@ import type { IGenericUpdate, IUpdateNodeIntrosResponse } from "../types/generic
import type { IKeyChainRequest, IMissionInventoryUpdateRequest } from "../types/requestTypes.ts";
import { logger } from "../utils/logger.ts";
import { convertInboxMessage, fromStoreItem, getKeyChainItems } from "./itemDataService.ts";
import type { IFlavourItem, IItemConfig, IItemConfigDatabase } from "../types/inventoryTypes/commonInventoryTypes.ts";
import type { IFlavourItem, IItemConfig } from "../types/inventoryTypes/commonInventoryTypes.ts";
import type { IDefaultUpgrade, IPowersuit, ISentinel, TStandingLimitBin } from "warframe-public-export-plus";
import {
ExportArcanes,
@@ -93,11 +93,9 @@ import type {
import { EquipmentFeatures, Status } from "../types/equipmentTypes.ts";
import type { ITypeCount } from "../types/commonTypes.ts";
import { skinLookupTable } from "../helpers/skinLookupTable.ts";
import type { TLoadoutDatabaseDocument } from "../models/inventoryModels/loadoutModel.ts";
export const createInventory = async (
accountOwnerId: Types.ObjectId,
loadout: TLoadoutDatabaseDocument,
defaultItemReferences: { loadOutPresetId: Types.ObjectId; ship: Types.ObjectId }
): Promise<void> => {
try {
@@ -117,29 +115,10 @@ export const createInventory = async (
if (config.skipTutorial) {
inventory.PlayedParkourTutorial = true;
const startingGear = await addStartingGear(inventory);
await addStartingGear(inventory);
await completeQuest(inventory, "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain", undefined);
await completeQuest(inventory, "/Lotus/Types/Keys/ModQuest/ModQuestKeyChain", undefined);
loadout.NORMAL.push({
s: {
ItemId: new Types.ObjectId(fromOid(startingGear.Suits![0].ItemId))
},
l: {
ItemId: new Types.ObjectId(fromOid(startingGear.LongGuns![0].ItemId))
},
p: {
ItemId: new Types.ObjectId(fromOid(startingGear.Pistols![0].ItemId))
},
m: {
ItemId: new Types.ObjectId(fromOid(startingGear.Melee![0].ItemId))
},
a: {
ItemId: new Types.ObjectId(fromOid(startingGear.SpecialItems![0].ItemId))
}
});
await loadout.save();
const completedMissions = ["SolNode27", "SolNode89", "SolNode63", "SolNode85", "SolNode15", "SolNode79"];
inventory.Missions.push(
@@ -398,9 +377,6 @@ export const addItem = async (
FusionTreasures: fusionTreasureChanges
};
} else if (ExportResources[typeName].productCategory == "Ships") {
if (quantity != 1) {
throw new Error(`unexpected acquisition quantity of Ships: got ${quantity}, expected 1`);
}
const oid = await createShip(inventory.accountOwnerId, typeName);
inventory.Ships.push(oid);
return {
@@ -412,9 +388,6 @@ export const addItem = async (
]
};
} else if (ExportResources[typeName].productCategory == "CrewShips") {
if (quantity != 1) {
throw new Error(`unexpected acquisition quantity of CrewShips: got ${quantity}, expected 1`);
}
return {
...(await addCrewShip(inventory, typeName)),
// fix to unlock railjack modding, item bellow supposed to be obtained from archwing quest
@@ -549,11 +522,6 @@ export const addItem = async (
if (typeName in ExportWeapons) {
const weapon = ExportWeapons[typeName];
if (weapon.totalDamage != 0) {
if (quantity != 1) {
throw new Error(
`unexpected acquisition quantity of ${weapon.productCategory}: got ${quantity}, expected 1`
);
}
const defaultOverwrites: Partial<IEquipmentDatabase> = {};
if (premiumPurchase) {
defaultOverwrites.Features = EquipmentFeatures.DOUBLE_CAPACITY;
@@ -610,9 +578,6 @@ export const addItem = async (
};
} else if (targetFingerprint) {
// Sister's Hound
if (quantity != 1) {
throw new Error(`unexpected acquisition quantity of MoaPets: got ${quantity}, expected 1`);
}
const targetFingerprintObj = JSON.parse(targetFingerprint) as INemesisPetTargetFingerprint;
const head = targetFingerprintObj.Parts[0];
const defaultOverwrites: Partial<IEquipmentDatabase> = {
@@ -688,9 +653,6 @@ export const addItem = async (
const key = ExportKeys[typeName];
if (key.chainStages) {
if (quantity != 1) {
throw new Error(`unexpected acquisition quantity of QuestKeys: got ${quantity}, expected 1`);
}
const key = addQuestKey(inventory, { ItemType: typeName });
if (!key) return {};
return { QuestKeys: [key] };
@@ -733,9 +695,6 @@ export const addItem = async (
if (typeName.endsWith("AugmentCard")) break;
switch (typeName.substring(1).split("/")[2]) {
default: {
if (quantity != 1) {
throw new Error(`unexpected acquisition quantity of Suits: got ${quantity}, expected 1`);
}
return {
...(await addPowerSuit(inventory, typeName, {
Features: premiumPurchase ? EquipmentFeatures.DOUBLE_CAPACITY : undefined
@@ -744,9 +703,6 @@ export const addItem = async (
};
}
case "Archwing": {
if (quantity != 1) {
throw new Error(`unexpected acquisition quantity of SpaceSuits: got ${quantity}, expected 1`);
}
inventory.ArchwingEnabled = true;
return {
...addSpaceSuit(
@@ -759,9 +715,6 @@ export const addItem = async (
};
}
case "EntratiMech": {
if (quantity != 1) {
throw new Error(`unexpected acquisition quantity of MechSuits: got ${quantity}, expected 1`);
}
return {
...(await addMechSuit(
inventory,
@@ -794,18 +747,16 @@ export const addItem = async (
case "Boons":
// Can purchase /Lotus/Upgrades/Boons/DuviriVendorBoonItem from Acrithis, doesn't need to be added to inventory.
logger.debug(`acquisition of ${typeName} is not committed to inventory`);
return {};
case "Stickers":
{
const entry = inventory.RawUpgrades.find(x => x.ItemType == typeName);
if (entry && entry.ItemCount >= 10) {
logger.debug(`adding ${quantity} pix chip(s) instead of ${typeName}`);
const miscItemChanges = [
{
ItemType: "/Lotus/Types/Items/MiscItems/1999ConquestBucks",
ItemCount: quantity
ItemCount: 1
}
];
addMiscItems(inventory, miscItemChanges);
@@ -828,9 +779,6 @@ export const addItem = async (
break;
case "Skins": {
if (quantity != 1) {
throw new Error(`unexpected acquisition quantity of Skins: got ${quantity}, expected 1`);
}
return addSkin(inventory, typeName);
}
}
@@ -839,9 +787,6 @@ export const addItem = async (
case "Types":
switch (typeName.substring(1).split("/")[2]) {
case "Sentinels": {
if (quantity != 1) {
throw new Error(`unexpected acquisition quantity of Sentinels: got ${quantity}, expected 1`);
}
return addSentinel(inventory, typeName, premiumPurchase);
}
case "Game": {
@@ -863,24 +808,13 @@ export const addItem = async (
typeName.substring(1).split("/")[3] == "KubrowPet"
) {
if (
typeName != "/Lotus/Types/Game/KubrowPet/Eggs/KubrowEgg" &&
typeName != "/Lotus/Types/Game/KubrowPet/Eggs/KubrowPetEggItem" &&
typeName != "/Lotus/Types/Game/KubrowPet/BlankTraitPrint" &&
typeName != "/Lotus/Types/Game/KubrowPet/ImprintedTraitPrint"
) {
if (quantity != 1) {
throw new Error(
`unexpected acquisition quantity of KubrowPet: got ${quantity}, expected 1`
);
}
return addKubrowPet(inventory, typeName, undefined, premiumPurchase);
}
} else if (typeName.startsWith("/Lotus/Types/Game/CrewShip/CrewMember/")) {
if (quantity != 1) {
throw new Error(
`unexpected acquisition quantity of CrewMember: got ${quantity}, expected 1`
);
}
if (!seed) {
throw new Error(`Expected crew member to have a seed`);
}
@@ -890,22 +824,12 @@ export const addItem = async (
...occupySlot(inventory, InventorySlot.CREWMEMBERS, premiumPurchase)
};
} else if (typeName == "/Lotus/Types/Game/CrewShip/RailJack/DefaultHarness") {
if (quantity != 1) {
throw new Error(
`unexpected acquisition quantity of CrewShipHarness: got ${quantity}, expected 1`
);
}
return addCrewShipHarness(inventory, typeName);
}
break;
}
case "Items": {
if (typeName.substring(1).split("/")[3] == "Emotes") {
if (quantity != 1) {
throw new Error(
`unexpected acquisition quantity of FlavourItems: got ${quantity}, expected 1`
);
}
return addCustomization(inventory, typeName);
}
break;
@@ -915,9 +839,6 @@ export const addItem = async (
logger.warn("refusing to add Horse because account already has one");
return {};
}
if (quantity != 1) {
throw new Error(`unexpected acquisition quantity of Horses: got ${quantity}, expected 1`);
}
const horseIndex = inventory.Horses.push({ ItemType: typeName });
return {
Horses: [inventory.Horses[horseIndex - 1].toJSON<IEquipmentClient>()]
@@ -925,19 +846,11 @@ export const addItem = async (
}
case "Vehicles":
if (typeName == "/Lotus/Types/Vehicles/Motorcycle/MotorcyclePowerSuit") {
if (quantity != 1) {
throw new Error(`unexpected acquisition quantity of Vehicles: got ${quantity}, expected 1`);
}
return addMotorcycle(inventory, typeName);
}
break;
case "Lore":
if (typeName == "/Lotus/Types/Lore/Fragments/GrineerGhoulFragments/GhoulFragmentRewards") {
if (quantity != 1) {
throw new Error(
`unexpected acquisition quantity of LoreFragmentScans: got ${quantity}, expected 1`
);
}
const fragmentType = getRandomElement([
"/Lotus/Types/Lore/Fragments/GrineerGhoulFragments/GhoulFragmentA",
"/Lotus/Types/Lore/Fragments/GrineerGhoulFragments/GhoulFragmentB",
@@ -971,11 +884,6 @@ export const addItem = async (
case "Pistols":
case "LongGuns":
case "Melee": {
if (quantity != 1) {
throw new Error(
`unexpected acquisition quantity of ${productCategory}: got ${quantity}, expected 1`
);
}
const inventoryChanges = addEquipment(inventory, productCategory, typeName);
return {
...inventoryChanges,
@@ -1013,9 +921,9 @@ export const addItems = async (
export const applyDefaultUpgrades = (
inventory: TInventoryDatabaseDocument,
defaultUpgrades: IDefaultUpgrade[] | undefined
): IItemConfigDatabase[] => {
): IItemConfig[] => {
const modsToGive: IRawUpgrade[] = [];
const configs: IItemConfigDatabase[] = [];
const configs: IItemConfig[] = [];
if (defaultUpgrades) {
const upgrades = [];
for (const defaultUpgrade of defaultUpgrades) {
@@ -1940,10 +1848,8 @@ export const addMiscItems = (inventory: TInventoryDatabaseDocument, itemsArray:
if (MiscItems[itemIndex].ItemCount == 0) {
MiscItems.splice(itemIndex, 1);
} else if (MiscItems[itemIndex].ItemCount < 0) {
throw new Error(
`Cannot remove ${ItemCount * -1}x ${ItemType} from MiscItems, would be left with ${MiscItems[itemIndex].ItemCount}`
);
} else if (MiscItems[itemIndex].ItemCount <= 0) {
logger.warn(`inventory.MiscItems has a negative count for ${ItemType}`);
}
});
};
@@ -1964,10 +1870,8 @@ const applyArrayChanges = (
arr[itemIndex].ItemCount += change.ItemCount;
if (arr[itemIndex].ItemCount == 0) {
arr.splice(itemIndex, 1);
} else if (arr[itemIndex].ItemCount < 0) {
throw new Error(
`Cannot remove ${change.ItemCount * -1}x ${change.ItemType} from ${key}, would be left with ${arr[itemIndex].ItemCount}`
);
} else if (arr[itemIndex].ItemCount <= 0) {
logger.warn(`inventory.${key} has a negative count for ${change.ItemType}`);
}
}
}
@@ -2013,10 +1917,8 @@ export const addMods = (inventory: TInventoryDatabaseDocument, itemsArray: IRawU
RawUpgrades[itemIndex].ItemCount += ItemCount;
if (RawUpgrades[itemIndex].ItemCount == 0) {
RawUpgrades.splice(itemIndex, 1);
} else if (RawUpgrades[itemIndex].ItemCount < 0) {
throw new Error(
`Cannot remove ${ItemCount * -1}x ${ItemType} from RawUpgrades, would be left with ${RawUpgrades[itemIndex].ItemCount}`
);
} else if (RawUpgrades[itemIndex].ItemCount <= 0) {
logger.warn(`inventory.RawUpgrades has a negative count for ${ItemType}`);
}
});
};
@@ -2031,9 +1933,7 @@ export const addFusionTreasures = (inventory: TInventoryDatabaseDocument, itemsA
if (FusionTreasures[itemIndex].ItemCount == 0) {
FusionTreasures.splice(itemIndex, 1);
} else if (FusionTreasures[itemIndex].ItemCount <= 0) {
throw new Error(
`Cannot remove ${ItemCount * -1}x ${ItemType} from FusionTreasures, would be left with ${FusionTreasures[itemIndex].ItemCount}`
);
logger.warn(`inventory.FusionTreasures has a negative count for ${ItemType}`);
}
} else {
FusionTreasures.push({ ItemCount, ItemType, Sockets });

View File

@@ -3,7 +3,7 @@ import { createInventory } from "./inventoryService.ts";
import type { IDatabaseAccountJson, IDatabaseAccountRequiredFields } from "../types/loginTypes.ts";
import { createShip } from "./shipService.ts";
import type { Document, Types } from "mongoose";
import { Loadout, type TLoadoutDatabaseDocument } from "../models/inventoryModels/loadoutModel.ts";
import { Loadout } from "../models/inventoryModels/loadoutModel.ts";
import { PersonalRooms } from "../models/personalRoomsModel.ts";
import type { Request } from "express";
import { config } from "./configService.ts";
@@ -39,10 +39,10 @@ export const createAccount = async (accountData: IDatabaseAccountRequiredFields)
const account = new Account(accountData);
try {
await account.save();
const loadout = await createLoadout(account._id);
const loadoutId = await createLoadout(account._id);
const shipId = await createShip(account._id);
await createPersonalRooms(account._id, shipId);
await createInventory(account._id, loadout, { loadOutPresetId: loadout._id, ship: shipId });
await createInventory(account._id, { loadOutPresetId: loadoutId, ship: shipId });
await createStats(account._id.toString());
return account.toJSON();
} catch (error) {
@@ -53,10 +53,10 @@ export const createAccount = async (accountData: IDatabaseAccountRequiredFields)
}
};
export const createLoadout = async (accountId: Types.ObjectId): Promise<TLoadoutDatabaseDocument> => {
export const createLoadout = async (accountId: Types.ObjectId): Promise<Types.ObjectId> => {
const loadout = new Loadout({ loadoutOwnerId: accountId });
const savedLoadout = await loadout.save();
return savedLoadout;
return savedLoadout._id;
};
export const createPersonalRooms = async (accountId: Types.ObjectId, shipId: Types.ObjectId): Promise<void> => {

View File

@@ -84,7 +84,7 @@ import {
import { config } from "./configService.ts";
import libraryDailyTasks from "../../static/fixed_responses/libraryDailyTasks.json" with { type: "json" };
import type { IGoal, ISyndicateMissionInfo } from "../types/worldStateTypes.ts";
import { fromOid, version_compare } from "../helpers/inventoryHelpers.ts";
import { fromOid } from "../helpers/inventoryHelpers.ts";
import type { TAccountDocument } from "./loginService.ts";
import type { ITypeCount } from "../types/commonTypes.ts";
import type { IEquipmentClient } from "../types/equipmentTypes.ts";
@@ -480,32 +480,9 @@ export const addMissionInventoryUpdates = async (
case "Upgrades":
value.forEach(clientUpgrade => {
const id = fromOid(clientUpgrade.ItemId);
// Really old builds (tested U7-U8) do not have the UpgradeFingerprint set for unranked mod drops
clientUpgrade.UpgradeFingerprint ??= "lvl=0|";
// U11 and below also don't initialize ItemCount since RawUpgrade doesn't exist in them
clientUpgrade.ItemCount ??= 1;
if (account.BuildLabel && version_compare(account.BuildLabel, "2016.08.19.17.12") < 0) {
// Acquired Mods have a different UpgradeFingerprint format in pre-U18.18.0 builds, this converts them to the format the database expects
clientUpgrade.UpgradeFingerprint = `{"lvl":${clientUpgrade.UpgradeFingerprint.substring(
clientUpgrade.UpgradeFingerprint.indexOf("=") + 1,
clientUpgrade.UpgradeFingerprint.lastIndexOf("|")
)}}`;
}
// Handle Fusion Core drops
const parsedFingerprint = JSON.parse(clientUpgrade.UpgradeFingerprint) as { lvl: number };
if (parsedFingerprint.lvl != 0) {
inventory.Upgrades.push({
ItemType: clientUpgrade.ItemType,
UpgradeFingerprint: clientUpgrade.UpgradeFingerprint
});
} else if (id == "") {
if (id == "") {
// U19 does not provide RawUpgrades and instead interleaves them with riven progress here
addMods(inventory, [
{
ItemType: clientUpgrade.ItemType,
ItemCount: clientUpgrade.ItemCount
}
]);
addMods(inventory, [clientUpgrade]);
} else {
const upgrade = inventory.Upgrades.id(id)!;
upgrade.UpgradeFingerprint = clientUpgrade.UpgradeFingerprint; // primitive way to copy over the riven challenge progress

View File

@@ -554,9 +554,6 @@ const handleBoosterPackPurchase = async (
BoosterPackItems: "",
InventoryChanges: {}
};
if (quantity < 1) {
throw new Error(`invalid quantity for booster pack purchase: ${quantity}`);
}
if (quantity > 100) {
throw new Error(
"attempt to roll over 100 booster packs in a single go. possible but unlikely to be desirable for the user or the server."

View File

@@ -15,7 +15,6 @@ import type { IQuestKeyClient, IQuestKeyDatabase, IQuestStage } from "../types/i
import { logger } from "../utils/logger.ts";
import { ExportKeys, ExportRecipes } from "warframe-public-export-plus";
import { addFixedLevelRewards } from "./missionInventoryUpdateService.ts";
import { fromOid } from "../helpers/inventoryHelpers.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";
@@ -610,18 +609,21 @@ export const removeRequiredItems = async (inventory: TInventoryDatabaseDocument,
}
case "/Lotus/Types/Recipes/WarframeRecipes/ChromaBlueprint": {
const itemsToRemove = [
"/Lotus/Types/Recipes/WarframeRecipes/ChromaBeaconABlueprint",
"/Lotus/Types/Recipes/WarframeRecipes/ChromaBeaconBBlueprint",
"/Lotus/Types/Recipes/WarframeRecipes/ChromaBeaconCBlueprint"
];
for (const itemToRemove of itemsToRemove) {
try {
await addItem(inventory, itemToRemove, -1, undefined, undefined, undefined, true);
} catch (e) {
logger.debug(`removeRequiredItems: Couldn't remove ${itemToRemove}: ${(e as Error).message}`);
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;
}
@@ -634,20 +636,20 @@ export const removeRequiredItems = async (inventory: TInventoryDatabaseDocument,
if (!inventory.MiscItems.find(i => i.ItemType == recipe.resultType)) {
await addItem(inventory, recipe.resultType);
if (recipe.consumeOnUse) await addItem(inventory, recipeItem.ItemType, -1);
const itemsToRemove = [
"/Lotus/Types/Keys/BardQuest/BardQuestSequencerPartA",
"/Lotus/Types/Keys/BardQuest/BardQuestSequencerPartB",
"/Lotus/Types/Keys/BardQuest/BardQuestSequencerPartC"
];
for (const itemToRemove of itemsToRemove) {
try {
await addItem(inventory, itemToRemove, -1, undefined, undefined, undefined, true);
} catch (e) {
logger.debug(
`removeRequiredItems: Couldn't remove ${itemToRemove}: ${(e as Error).message}`
);
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;
@@ -759,9 +761,9 @@ export const removeRequiredItems = async (inventory: TInventoryDatabaseDocument,
"",
"",
"",
fromOid(umbraModA.ItemId),
fromOid(umbraModB.ItemId),
fromOid(umbraModC.ItemId)
umbraModA.ItemId.$oid,
umbraModB.ItemId.$oid,
umbraModC.ItemId.$oid
]
}
],
@@ -776,16 +778,7 @@ export const removeRequiredItems = async (inventory: TInventoryDatabaseDocument,
addEquipment(inventory, "Melee", "/Lotus/Weapons/Tenno/Melee/Swords/UmbraKatana/UmbraKatana", {
Configs: [
{
Upgrades: [
"",
"",
"",
"",
"",
"",
fromOid(sacrificeModA.ItemId),
fromOid(sacrificeModB.ItemId)
]
Upgrades: ["", "", "", "", "", "", sacrificeModA.ItemId.$oid, sacrificeModB.ItemId.$oid]
}
],
XP: 450_000,

View File

@@ -6,15 +6,14 @@ import type {
ISaveLoadoutRequestNoUpgradeVer
} from "../types/saveLoadoutTypes.ts";
import { Loadout } from "../models/inventoryModels/loadoutModel.ts";
import { addMods, getInventory } from "./inventoryService.ts";
import { getInventory } from "./inventoryService.ts";
import type { IOid } from "../types/commonTypes.ts";
import { Types } from "mongoose";
import { isEmptyObject } from "../helpers/general.ts";
import { version_compare } from "../helpers/inventoryHelpers.ts";
import { logger } from "../utils/logger.ts";
import type { TEquipmentKey } from "../types/inventoryTypes/inventoryTypes.ts";
import { equipmentKeys } from "../types/inventoryTypes/inventoryTypes.ts";
import type { IItemConfig, IItemConfigDatabase } from "../types/inventoryTypes/commonInventoryTypes.ts";
import type { IItemConfig } from "../types/inventoryTypes/commonInventoryTypes.ts";
import { importCrewShipMembers, importCrewShipWeapon, importLoadOutConfig } from "./importService.ts";
//TODO: setup default items on account creation or like originally in giveStartingItems.php
@@ -27,8 +26,7 @@ itemconfig has multiple config ids
*/
export const handleInventoryItemConfigChange = async (
equipmentChanges: ISaveLoadoutRequestNoUpgradeVer,
accountId: string,
buildLabel: string | undefined
accountId: string
): Promise<string | void> => {
const inventory = await getInventory(accountId);
@@ -198,36 +196,7 @@ export const handleInventoryItemConfigChange = async (
for (const [configId, config] of Object.entries(itemConfigEntries)) {
if (/^[0-9]+$/.test(configId)) {
const c = config as IItemConfig;
if (buildLabel && version_compare(buildLabel, "2014.04.10.17.47") < 0) {
if (c.Upgrades) {
// U10-U11 store mods in the item config as $id instead of a string, need to convert that here
const convertedUpgrades: string[] = [];
c.Upgrades.forEach(upgrade => {
const upgradeId = upgrade as { $id: string };
const rawUpgrade = inventory.RawUpgrades.id(upgradeId.$id);
if (rawUpgrade) {
const newId = new Types.ObjectId();
convertedUpgrades.push(newId.toString());
addMods(inventory, [
{
ItemType: rawUpgrade.ItemType,
ItemCount: -1
}
]);
inventory.Upgrades.push({
UpgradeFingerprint: `{"lvl":0}`,
ItemType: rawUpgrade.ItemType,
_id: newId
});
} else {
convertedUpgrades.push(upgradeId.$id);
}
});
c.Upgrades = convertedUpgrades;
}
}
inventoryItem.Configs[parseInt(configId)] = c as IItemConfigDatabase;
inventoryItem.Configs[parseInt(configId)] = config as IItemConfig;
}
}
if ("Favorite" in itemConfigEntries) {

View File

@@ -3819,12 +3819,6 @@ export const getNightwaveSyndicateTag = (buildLabel: string | undefined): string
if (version_compare(buildLabel, "2025.02.05.11.19") >= 0) {
return "RadioLegionIntermission12Syndicate";
}
if (version_compare(buildLabel, "2024.08.21.20.02") >= 0) {
return "RadioLegionIntermission11Syndicate";
}
if (version_compare(buildLabel, "2024.04.29.11.14") >= 0) {
return "RadioLegionIntermission10Syndicate";
}
return undefined;
};

View File

@@ -4,7 +4,6 @@ import type {
ICrewShipCustomization,
IFlavourItem,
IItemConfig,
IItemConfigDatabase,
IPolarity
} from "./inventoryTypes/commonInventoryTypes.ts";
@@ -34,7 +33,7 @@ export enum EquipmentFeatures {
export interface IEquipmentDatabase {
ItemType: string;
ItemName?: string;
Configs: IItemConfigDatabase[];
Configs: IItemConfig[];
UpgradeVer?: number;
XP?: number;
Features?: number;
@@ -49,6 +48,7 @@ export interface IEquipmentDatabase {
InfestationDays?: number;
InfestationType?: string;
ModularParts?: string[];
UnlockLevel?: number;
Expiry?: Date;
SkillTree?: string;
OffensiveUpgrade?: string;
@@ -69,18 +69,9 @@ export interface IEquipmentDatabase {
export interface IEquipmentClient
extends Omit<
IEquipmentDatabase,
| "_id"
| "Configs"
| "InfestationDate"
| "Expiry"
| "UpgradesExpiry"
| "UmbraDate"
| "Weapon"
| "CrewMembers"
| "Details"
"_id" | "InfestationDate" | "Expiry" | "UpgradesExpiry" | "UmbraDate" | "Weapon" | "CrewMembers" | "Details"
> {
ItemId: IOidWithLegacySupport;
Configs: IItemConfig[];
InfestationDate?: IMongoDate;
Expiry?: IMongoDate;
UpgradesExpiry?: IMongoDate;
@@ -88,10 +79,6 @@ export interface IEquipmentClient
Weapon?: ICrewShipWeaponClient;
CrewMembers?: ICrewShipMembersClient;
Details?: IKubrowPetDetailsClient;
// For Pre-U24.4.0 builds
UnlockLevel?: number;
UtilityUnlocked?: number;
Gild?: boolean;
}
export interface IArchonCrystalUpgrade {

View File

@@ -47,7 +47,7 @@ export interface IItemConfig {
facial?: IColor;
syancol?: IColor;
cloth?: IColor;
Upgrades?: string[] | { $id: string }[];
Upgrades?: string[];
Name?: string;
OperatorAmp?: IOid;
Songs?: ISong[];
@@ -56,17 +56,13 @@ export interface IItemConfig {
ugly?: boolean;
}
export interface IItemConfigDatabase extends Omit<IItemConfig, "Upgrades"> {
Upgrades?: string[];
}
export interface ISong {
m?: string;
b?: string;
p?: string;
s: string;
}
export interface IOperatorConfigDatabase extends IItemConfigDatabase {
export interface IOperatorConfigDatabase extends IItemConfig {
_id: Types.ObjectId;
}

View File

@@ -23,7 +23,6 @@ export type InventoryDatabaseEquipment = {
// Fields specific to SNS
export interface IAccountCheats {
skipAllDialogue?: boolean;
skipAllPopups?: boolean;
dontSubtractPurchaseCreditCost?: boolean;
dontSubtractPurchasePlatinumCost?: boolean;
dontSubtractPurchaseItemCost?: boolean;
@@ -318,7 +317,6 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
Accolades?: IAccolades;
Counselor?: boolean;
Upgrades: IUpgradeClient[];
Cards?: IUpgradeClient[]; // U8
EquippedGear: string[];
DeathMarks: string[];
FusionTreasures: IFusionTreasure[];
@@ -593,16 +591,10 @@ export interface IUpgradeClient {
ItemType: string;
UpgradeFingerprint?: string;
PendingRerollFingerprint?: string;
ItemId: IOidWithLegacySupport;
// Stuff for U7-U8
ParentId?: IOidWithLegacySupport;
Slot?: number;
AmountRemaining?: number;
Rank?: number;
ItemId: IOid;
}
export interface IUpgradeDatabase
extends Omit<IUpgradeClient, "ItemId" | "ParentId" | "Slot" | "AmountRemaining" | "Rank"> {
export interface IUpgradeDatabase extends Omit<IUpgradeClient, "ItemId"> {
_id: Types.ObjectId;
}
@@ -610,9 +602,9 @@ export interface IUpgradeFromClient {
ItemType: string;
ItemId: IOidWithLegacySupport;
FromSKU?: boolean;
UpgradeFingerprint?: string;
UpgradeFingerprint: string;
PendingRerollFingerprint: string;
ItemCount?: number;
ItemCount: number;
LastAdded: IOidWithLegacySupport;
}

View File

@@ -1,4 +1,4 @@
import type { IOid, IOidWithLegacySupport, ITypeCount } from "./commonTypes.ts";
import type { IOid, ITypeCount } from "./commonTypes.ts";
import type { ArtifactPolarity, IPolarity } from "./inventoryTypes/commonInventoryTypes.ts";
import type {
IBooster,
@@ -21,8 +21,7 @@ import type {
IInvasionProgressClient,
IWeaponSkinClient,
IKubrowPetEggClient,
INemesisClient,
IUpgradeClient
INemesisClient
} from "./inventoryTypes/inventoryTypes.ts";
import type { IGroup } from "./loginTypes.ts";
import type { ILoadOutPresets } from "./saveLoadoutTypes.ts";
@@ -228,24 +227,6 @@ export interface IUpgradesRequest {
UpgradeVersion: number;
Operations: IUpgradeOperation[];
}
export interface IUpgradesRequestLegacy {
Category: TEquipmentKey;
Weapon: { ItemType: string; ItemId: IOidWithLegacySupport };
UpgradeVer: number;
UnlockLevel: number;
Polarized: number;
UtilityUnlocked: number;
FocusLens?: string;
UpgradeReq?: string;
UtilityReq?: string;
IsSwappingOperation: boolean;
PolarizeReq?: string;
PolarizeSlot: number;
PolarizeValue: ArtifactPolarity;
PolarityRemap: IPolarity[];
UpgradesToAttach?: IUpgradeClient[];
UpgradesToDetach?: IUpgradeClient[];
}
export interface IUpgradeOperation {
OperationType: string;
UpgradeRequirement: string; // uniqueName of item being consumed

View File

@@ -31,7 +31,7 @@ export interface ISession {
export interface IFindSessionRequest {
id?: string;
originalSessionId?: string;
buildId?: number | bigint;
buildId?: number;
gameModeId?: number;
regionId?: number;
maxEloDifference?: number;

View File

@@ -1,6 +1,4 @@
[
"ArcaneWallConsole",
"CetusHub4",
"SolarisUnitedHub1",
"/Lotus/Language/SolarisVenus/FishmongerName",
"/Lotus/Language/SolarisVenus/ProspectorName",
@@ -14,7 +12,6 @@
"SaturnWolf3",
"SaturnWolf4",
"SaturnWolf5",
"SyndicateFirstPledge",
"ConclaveSyndicate",
"ArbitersSyndicate",
"LibrarySyndicate",
@@ -120,7 +117,6 @@
"/Lotus/Language/EntratiLab/EntratiGeneral/Fibonacci",
"/Lotus/Language/EntratiLab/EntratiGeneral/BirdThree",
"/Lotus/Language/Zariman/Quinn",
"/Lotus/Language/EntratiLab/EntratiGeneral/Tagfer",
"/Lotus/Language/EntratiLab/EntratiGeneral/TagferFirstRank1",
"VoidVaultIntro",
"PurchasePlatformLockedNotificationSeen",
@@ -141,7 +137,6 @@
"EntratiLabConquestHardModeUnlocked",
"/Lotus/Language/Npcs/KonzuPostNewWar",
"/Lotus/Language/SolarisVenus/EudicoPostNewWar",
"/Lotus/Language/FiveFates/KoumeiStatueHubName",
"/Lotus/Language/NokkoColony/NokkoVendorName",
"NokkoVisions_FirstVisit"
]

View File

@@ -1,27 +0,0 @@
[
"RailjackPlexusTutorial",
"RailjackIntrinsicsTutorial",
"RailjackDryDockTutorial",
"RailjackStarchartTutorial",
"NPE_poponce_ayatans",
"NPE_poponce_sellmodinfo",
"NPE_poponce_transmuteinfo",
"MarketOpened",
"PrimeTokensTutorial",
"WelcomeScreen_Undermind",
"WelcomeScreen_Jade",
"WelcomeScreen_Isleweaver",
"WelcomeScreen_Techrot",
"WelcomeScreen_1999",
"WelcomeScreen_Koumei",
"WelcomeScreen_Dante",
"WelcomeScreen_Whispers",
"WelcomeScreen_Dagath",
"WelcomeScreen_Kullervo",
"EpisodeIntro_RadioLegionIntermission14Syndicate",
"EpisodeIntro_RadioLegionIntermission13Syndicate",
"EpisodeIntro_RadioLegionIntermission12Syndicate",
"EpisodeIntro_RadioLegionIntermission11Syndicate",
"EpisodeIntro_RadioLegionIntermission10Syndicate",
"EpisodeIntro_RadioLegionIntermission9Syndicate"
]

View File

@@ -822,22 +822,6 @@
</form>
</div>
</div>
<div id="umbraEchoes-card" class="card mb-3 d-none">
<h5 class="card-header" data-loc="detailedView_umbraEchoesLabel"></h5>
<div class="card-body">
<p data-loc="detailedView_umbraEchoesDescription"></p>
<form onsubmit="submitUmbraEchoes(event)">
<div class="mb-3">
<label for="umbraEchoes-expiry" class="form-label" data-loc="detailedView_umbraEchoesExpiryLabel"></label>
<input type="datetime-local" class="form-control" max="2038-01-19T03:14" id="umbraEchoes-expiry" onblur="this.value=new Date(this.value)>new Date(this.max)?new Date(this.max).toISOString().slice(0,16):this.value"/>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary" data-loc="general_setButton"></button>
<button type="button" class="btn btn-danger" onclick="setUmbraEchoes()" data-loc="code_remove"></button>
</div>
</form>
</div>
</div>
<div id="modularParts-card" class="card mb-3 d-none">
<h5 class="card-header" data-loc="detailedView_modularPartsLabel"></h5>
<div class="card-body">
@@ -960,10 +944,6 @@
<input class="form-check-input" type="checkbox" id="skipAllDialogue" />
<label class="form-check-label" for="skipAllDialogue" data-loc="cheats_skipAllDialogue"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="skipAllPopups" />
<label class="form-check-label" for="skipAllPopups" data-loc="cheats_skipAllPopups"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="dontSubtractPurchaseCreditCost" />
<label class="form-check-label" for="dontSubtractPurchaseCreditCost" data-loc="cheats_dontSubtractPurchaseCreditCost"></label>
@@ -1562,7 +1542,6 @@
<p class="mt-3 mb-1" data-loc="import_samples"></p>
<ul>
<li><a href="#" onclick="event.preventDefault();setImportSample('maxFocus');" data-loc="import_samples_maxFocus"></a></li>
<li><a href="#" onclick="event.preventDefault();setImportSample('accolades');" data-loc="import_samples_accolades"></a></li>
</ul>
</div>
</div>

View File

@@ -426,25 +426,9 @@ function fetchItemList() {
};
// Add mods missing in data sources
data.mods.push({
uniqueName: "/Lotus/Upgrades/Mods/Fusers/CommonModFuser",
name: loc("code_fusionCoreCommon"),
fusionLimit: 3
});
data.mods.push({
uniqueName: "/Lotus/Upgrades/Mods/Fusers/UncommonModFuser",
name: loc("code_fusionCoreUncommon"),
fusionLimit: 5
});
data.mods.push({
uniqueName: "/Lotus/Upgrades/Mods/Fusers/RareModFuser",
name: loc("code_fusionCoreRare"),
fusionLimit: 5
});
data.mods.push({
uniqueName: "/Lotus/Upgrades/Mods/Fusers/LegendaryModFuser",
name: loc("code_legendaryCore"),
fusionLimit: 0
name: loc("code_legendaryCore")
});
data.mods.push({
uniqueName: "/Lotus/Upgrades/CosmeticEnhancers/Peculiars/CyoteMod",
@@ -1523,9 +1507,7 @@ function updateInventory() {
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
if (maxRank > 0) {
td.innerHTML += " <span title='" + loc("code_rank") + "'>★ 0/" + maxRank + "</span>";
}
td.innerHTML += " <span title='" + loc("code_rank") + "'>★ 0/" + maxRank + "</span>";
if (item.ItemCount > 1) {
td.innerHTML +=
" <span title='" + loc("code_count") + "'>🗍 " + parseInt(item.ItemCount) + "</span>";
@@ -1683,12 +1665,6 @@ function updateInventory() {
formatDatetime("%Y-%m-%d %H:%M", Number(item.UpgradesExpiry?.$date.$numberLong)) || "";
}
if (item.ItemType != "/Lotus/Powersuits/Excalibur/ExcaliburUmbra") {
document.getElementById("umbraEchoes-card").classList.remove("d-none");
document.getElementById("umbraEchoes-expiry").value =
formatDatetime("%Y-%m-%d %H:%M", Number(item.UmbraDate?.$date.$numberLong)) || "";
}
{
document.getElementById("loadout-card").classList.remove("d-none");
const maxModConfigNum = Math.min(2 + (item.ModSlotPurchases ?? 0), 5);
@@ -3481,9 +3457,6 @@ function doAddAllMods() {
for (const child of document.getElementById("datalist-mods").children) {
modsAll.add(child.getAttribute("data-key"));
}
modsAll.delete("/Lotus/Upgrades/Mods/Fusers/CommonModFuser");
modsAll.delete("/Lotus/Upgrades/Mods/Fusers/UncommonModFuser");
modsAll.delete("/Lotus/Upgrades/Mods/Fusers/RareModFuser");
modsAll.delete("/Lotus/Upgrades/Mods/Fusers/LegendaryModFuser");
revalidateAuthz().then(() => {
@@ -3559,7 +3532,6 @@ single.getRoute("#detailedView-route").on("beforeload", function () {
document.getElementById("loadout-card").classList.add("d-none");
document.getElementById("archonShards-card").classList.add("d-none");
document.getElementById("edit-suit-invigorations-card").classList.add("d-none");
document.getElementById("umbraEchoes-card").classList.add("d-none");
document.getElementById("modularParts-card").classList.add("d-none");
document.getElementById("modularParts-form").innerHTML = "";
document.getElementById("valenceBonus-card").classList.add("d-none");
@@ -3646,12 +3618,8 @@ function doImport() {
data: JSON.stringify({
inventory: JSON.parse($("#import-inventory").val())
})
}).then(function (err) {
if (err) {
toast(err == "noKnownKey" ? loc("code_nothingToDo") : err);
} else {
toast(loc("code_succImport"));
}
}).then(function (anyKnownKey) {
toast(loc(anyKnownKey ? "code_succImport" : "code_nothingToDo"));
updateInventory();
});
} catch (e) {
@@ -4151,22 +4119,6 @@ const importSamples = {
Level: 3
}
]
},
accolades: {
Staff: false,
Founder: 4,
Guide: 2,
Moderator: true,
Partner: true,
Created: {
$date: {
$numberLong: "1356998400000"
}
},
Accolades: {
Heirloom: true
},
Counselor: true
}
};
function setImportSample(key) {
@@ -4318,25 +4270,6 @@ function setInvigoration(data) {
});
}
function submitUmbraEchoes(event) {
event.preventDefault();
const expiry = document.getElementById("umbraEchoes-expiry").value;
setUmbraEchoes({
UmbraDate: expiry ? new Date(expiry).getTime() : Date.now() + 1 * 24 * 60 * 60 * 1000
});
}
function setUmbraEchoes(data) {
const oid = new URLSearchParams(window.location.search).get("itemId");
$.post({
url: "/custom/setUmbraEchoes?" + window.authz,
contentType: "application/json",
data: JSON.stringify({ oid, ...data })
}).done(function () {
updateInventory();
});
}
function handleAbilityOverride(event, configIndex) {
event.preventDefault();
const urlParams = new URLSearchParams(window.location.search);

View File

@@ -23,9 +23,6 @@ dict = {
code_moteAmp: `Anfangsverstärker`,
code_amp: `Verstärker`,
code_kDrive: `K-Drive`,
code_fusionCoreCommon: `[UNTRANSLATED] Fusion Core (Common)`,
code_fusionCoreUncommon: `[UNTRANSLATED] Fusion Core (Uncommon)`,
code_fusionCoreRare: `[UNTRANSLATED] Fusion Core (Rare)`,
code_legendaryCore: `Legendärer Kern`,
code_traumaticPeculiar: `Kuriose Mod: Traumatisch`,
code_starter: `|MOD| (Defekt)`,
@@ -83,7 +80,7 @@ dict = {
code_drifterFaceName: `Drifter-Gesicht: |INDEX|`,
code_operatorFaceName: `Operator-Gesicht: |INDEX|`,
code_reviveBooster: `Wiederbelebungsbooster`,
code_reviveBoosterDesc: `[UNTRANSLATED] Sets revive count to 4, which allows self-revive in Archon Hunts.`,
code_reviveBoosterDesc: `[UNTRANSLATED] Set revive count to 4. Disable self-revive restriction on Archon Hunt missions.`,
code_succChange: `Erfolgreich geändert.`,
code_requiredInvigorationUpgrade: `Du musst sowohl ein Offensiv- als auch ein Support-Upgrade auswählen.`,
code_feature_1: `[UNTRANSLATED] Orokin Reactor`,
@@ -168,7 +165,6 @@ dict = {
detailedView_invigorationLabel: `Kräftigung`,
detailedView_loadoutLabel: `Loadouts`,
detailedView_equipmentFeaturesLabel: `[UNTRANSLATED] Equipment Features`,
detailedView_umbraEchoesLabel: `[UNTRANSLATED] Echoes Of Umbra`,
invigorations_offensive_AbilityStrength: `+200% Fähigkeitsstärke`,
invigorations_offensive_AbilityRange: `+100% Fähigkeitsreichweite`,
@@ -199,9 +195,6 @@ dict = {
abilityOverride_label: `Fähigkeitsüberschreibung`,
abilityOverride_onSlot: `auf Slot`,
detailedView_umbraEchoesDescription: `Wird ein Warframe mit dieser Flüssigkeit injiziert, kann er selbstständig an der Seite des Operators kämpfen.`,
detailedView_umbraEchoesExpiryLabel: `[UNTRANSLATED] Echo Expiry (optional)`,
mods_addRiven: `Riven hinzufügen`,
mods_fingerprint: `Fingerabdruck`,
mods_fingerprintHelp: `Benötigst du Hilfe mit dem Fingerabdruck?`,
@@ -215,7 +208,6 @@ dict = {
cheats_server: `Server`,
cheats_skipTutorial: `Tutorial überspringen`,
cheats_skipAllDialogue: `Alle Dialoge überspringen`,
cheats_skipAllPopups: `Alle Popups überspringen`,
cheats_unlockAllScans: `Alle Scans freischalten`,
cheats_unlockSuccRelog: `Erfolgreich. Bitte beachte, dass du dich neu anmelden musst, damit der Client dies aktualisiert.`,
cheats_unlockAllMissions: `Alle Missionen freischalten`,
@@ -374,7 +366,6 @@ dict = {
import_submit: `Absenden`,
import_samples: `Beispiele:`,
import_samples_maxFocus: `Alle Fokus-Schulen maximiert`,
import_samples_accolades: `Auszeichnungen & Council Chat Zugang`,
upgrade_Equilibrium: `+|VAL|% Energie bei Gesundheitskugeln, +|VAL|% Gesundheit bei Energiekugeln`,
upgrade_MeleeCritDamage: `+|VAL|% Krit. Nahkampfschaden`,

View File

@@ -22,9 +22,6 @@ dict = {
code_moteAmp: `Mote Amp`,
code_amp: `Amp`,
code_kDrive: `K-Drive`,
code_fusionCoreCommon: `Fusion Core (Common)`,
code_fusionCoreUncommon: `Fusion Core (Uncommon)`,
code_fusionCoreRare: `Fusion Core (Rare)`,
code_legendaryCore: `Legendary Core`,
code_traumaticPeculiar: `Traumatic Peculiar`,
code_starter: `|MOD| (Flawed)`,
@@ -82,7 +79,7 @@ dict = {
code_drifterFaceName: `Drifter Visage |INDEX|`,
code_operatorFaceName: `Operator Visage |INDEX|`,
code_reviveBooster: `Revive Booster`,
code_reviveBoosterDesc: `Sets revive count to 4, which allows self-revive in Archon Hunts.`,
code_reviveBoosterDesc: `Set revive count to 4. Disable self-revive restriction on Archon Hunt missions.`,
code_succChange: `Successfully changed.`,
code_requiredInvigorationUpgrade: `You must select both an offensive & utility upgrade.`,
code_feature_1: `Orokin Reactor`,
@@ -167,7 +164,6 @@ dict = {
detailedView_invigorationLabel: `Invigoration`,
detailedView_loadoutLabel: `Loadouts`,
detailedView_equipmentFeaturesLabel: `Equipment Features`,
detailedView_umbraEchoesLabel: `Echoes Of Umbra`,
invigorations_offensive_AbilityStrength: `+200% Ability Strength`,
invigorations_offensive_AbilityRange: `+100% Ability Range`,
@@ -198,9 +194,6 @@ dict = {
abilityOverride_label: `Ability Override`,
abilityOverride_onSlot: `on slot`,
detailedView_umbraEchoesDescription: `Injecting this fluid into a Warframe will imbue it with the ability to fight autonomously alongside the Operator.`,
detailedView_umbraEchoesExpiryLabel: `Echo Expiry (optional)`,
mods_addRiven: `Add Riven`,
mods_fingerprint: `Fingerprint`,
mods_fingerprintHelp: `Need help with the fingerprint?`,
@@ -214,7 +207,6 @@ dict = {
cheats_server: `Server`,
cheats_skipTutorial: `Skip Tutorial`,
cheats_skipAllDialogue: `Skip All Dialogue`,
cheats_skipAllPopups: `Skip All Popups`,
cheats_unlockAllScans: `Unlock All Scans`,
cheats_unlockSuccRelog: `Success. Please note that you'll need to relog for the client to refresh this.`,
cheats_unlockAllMissions: `Unlock All Missions`,
@@ -373,7 +365,6 @@ dict = {
import_submit: `Submit`,
import_samples: `Samples:`,
import_samples_maxFocus: `All Focus Schools Maxed Out`,
import_samples_accolades: `Accolades & Council Chat Access`,
upgrade_Equilibrium: `+|VAL|% Energy from Health pickups, +|VAL|% Health from Energy pickups`,
upgrade_MeleeCritDamage: `+|VAL|% Melee Critical Damage`,

View File

@@ -23,9 +23,6 @@ dict = {
code_moteAmp: `Amp Mota`,
code_amp: `Amp`,
code_kDrive: `K-Drive`,
code_fusionCoreCommon: `[UNTRANSLATED] Fusion Core (Common)`,
code_fusionCoreUncommon: `[UNTRANSLATED] Fusion Core (Uncommon)`,
code_fusionCoreRare: `[UNTRANSLATED] Fusion Core (Rare)`,
code_legendaryCore: `Núcleo legendario`,
code_traumaticPeculiar: `Traumatismo peculiar`,
code_starter: `|MOD| (Defectuoso)`,
@@ -83,7 +80,7 @@ dict = {
code_drifterFaceName: `Rostro del Viajero |INDEX|`,
code_operatorFaceName: `Rostro del operador |INDEX|`,
code_reviveBooster: `Potenciador de reanimaciones`,
code_reviveBoosterDesc: `[UNTRANSLATED] Sets revive count to 4, which allows self-revive in Archon Hunts.`,
code_reviveBoosterDesc: `[UNTRANSLATED] Set revive count to 4. Disable self-revive restriction on Archon Hunt missions.`,
code_succChange: `Cambiado correctamente`,
code_requiredInvigorationUpgrade: `Debes seleccionar una mejora ofensiva y una mejora de utilidad.`,
code_feature_1: `[UNTRANSLATED] Orokin Reactor`,
@@ -168,7 +165,6 @@ dict = {
detailedView_invigorationLabel: `Fortalecimiento`,
detailedView_loadoutLabel: `Equipamientos`,
detailedView_equipmentFeaturesLabel: `[UNTRANSLATED] Equipment Features`,
detailedView_umbraEchoesLabel: `[UNTRANSLATED] Echoes Of Umbra`,
invigorations_offensive_AbilityStrength: `+200% Fuerza de Habilidad`,
invigorations_offensive_AbilityRange: `+100% Alcance de Habilidad`,
@@ -199,9 +195,6 @@ dict = {
abilityOverride_label: `Intercambio de Habilidad`,
abilityOverride_onSlot: `en el espacio`,
detailedView_umbraEchoesDescription: `Inyectar este fluido en un warframe lo imbuirá con la capacidad para luchar autónomamente junto a su operador.`,
detailedView_umbraEchoesExpiryLabel: `[UNTRANSLATED] Echo Expiry (optional)`,
mods_addRiven: `Agregar Agrietado`,
mods_fingerprint: `Huella digital`,
mods_fingerprintHelp: `¿Necesitas ayuda con la huella digital?`,
@@ -215,7 +208,6 @@ dict = {
cheats_server: `Servidor`,
cheats_skipTutorial: `Omitir tutorial`,
cheats_skipAllDialogue: `Omitir todos los diálogos`,
cheats_skipAllPopups: `[UNTRANSLATED] Skip All Popups`,
cheats_unlockAllScans: `Desbloquear todos los escaneos`,
cheats_unlockSuccRelog: `Éxito. Ten en cuenta que deberás volver a iniciar sesión para que el cliente se actualice.`,
cheats_unlockAllMissions: `Desbloquear todas las misiones`,
@@ -374,7 +366,6 @@ dict = {
import_submit: `Enviar`,
import_samples: `Muestras:`,
import_samples_maxFocus: `Todas las escuelas de enfoque al máximo`,
import_samples_accolades: `[UNTRANSLATED] Accolades & Council Chat Access`,
upgrade_Equilibrium: `+|VAL|% de Energía al recoger salud, +|VAL|% de Salud al recoger energía`,
upgrade_MeleeCritDamage: `+|VAL|% de daño crítico cuerpo a cuerpo`,

View File

@@ -23,9 +23,6 @@ dict = {
code_moteAmp: `Amplificateur Faible`,
code_amp: `Amplificateur`,
code_kDrive: `K-Drive`,
code_fusionCoreCommon: `[UNTRANSLATED] Fusion Core (Common)`,
code_fusionCoreUncommon: `[UNTRANSLATED] Fusion Core (Uncommon)`,
code_fusionCoreRare: `[UNTRANSLATED] Fusion Core (Rare)`,
code_legendaryCore: `Coeur Légendaire`,
code_traumaticPeculiar: `Traumatisme Atypique`,
code_starter: `|MOD| (Défectueux)`,
@@ -83,7 +80,7 @@ dict = {
code_drifterFaceName: `Visage du Voyageur |INDEX|`,
code_operatorFaceName: `Visage de l'Opérateur |INDEX|`,
code_reviveBooster: `Booster de Réanimation`,
code_reviveBoosterDesc: `[UNTRANSLATED] Sets revive count to 4, which allows self-revive in Archon Hunts.`,
code_reviveBoosterDesc: `[UNTRANSLATED] Set revive count to 4. Disable self-revive restriction on Archon Hunt missions.`,
code_succChange: `Changement effectué.`,
code_requiredInvigorationUpgrade: `Invigoration offensive et défensive requises.`,
code_feature_1: `[UNTRANSLATED] Orokin Reactor`,
@@ -168,7 +165,6 @@ dict = {
detailedView_invigorationLabel: `Dynamisation`,
detailedView_loadoutLabel: `Équipements`,
detailedView_equipmentFeaturesLabel: `[UNTRANSLATED] Equipment Features`,
detailedView_umbraEchoesLabel: `[UNTRANSLATED] Echoes Of Umbra`,
invigorations_offensive_AbilityStrength: `+200% de puissance de pouvoir`,
invigorations_offensive_AbilityRange: `+100% de portée de pouvoir`,
@@ -199,9 +195,6 @@ dict = {
abilityOverride_label: `Remplacement de pouvoir`,
abilityOverride_onSlot: `Sur l'emplacement`,
detailedView_umbraEchoesDescription: `L'injection de ce fluide dans une Warframe lui donnera la possibilité de se battre de manière autonome aux côtés de l'Opérateur.`,
detailedView_umbraEchoesExpiryLabel: `[UNTRANSLATED] Echo Expiry (optional)`,
mods_addRiven: `Ajouter un riven`,
mods_fingerprint: `Empreinte`,
mods_fingerprintHelp: `Besoin d'aide pour l'empreinte ?`,
@@ -215,7 +208,6 @@ dict = {
cheats_server: `Serveur`,
cheats_skipTutorial: `Passer le tutoriel`,
cheats_skipAllDialogue: `Passer les dialogues`,
cheats_skipAllPopups: `[UNTRANSLATED] Skip All Popups`,
cheats_unlockAllScans: `Débloquer tous les scans`,
cheats_unlockSuccRelog: `Succès. Une reconnexion est requise pour appliquer les changements.`,
cheats_unlockAllMissions: `Débloquer toutes les missions`,
@@ -374,7 +366,6 @@ dict = {
import_submit: `Soumettre`,
import_samples: `Échantillons :`,
import_samples_maxFocus: `Toutes les écoles de focus au rang max`,
import_samples_accolades: `[UNTRANSLATED] Accolades & Council Chat Access`,
upgrade_Equilibrium: `Ramasser de la santé donne +|VAL|% d'énergie supplémentaire. Ramasser de l'énergie donne +|VAL|% de santé supplémentaire.`,
upgrade_MeleeCritDamage: `+|VAL|% de dégâts critique en mêlée`,

View File

@@ -23,9 +23,6 @@ dict = {
code_moteAmp: `Пылинка`,
code_amp: `Усилитель`,
code_kDrive: `К-Драйв`,
code_fusionCoreCommon: `[UNTRANSLATED] Fusion Core (Common)`,
code_fusionCoreUncommon: `[UNTRANSLATED] Fusion Core (Uncommon)`,
code_fusionCoreRare: `[UNTRANSLATED] Fusion Core (Rare)`,
code_legendaryCore: `Легендарное ядро`,
code_traumaticPeculiar: `Травмирующая Странность`,
code_starter: `|MOD| (Повреждённый)`,
@@ -168,7 +165,6 @@ dict = {
detailedView_invigorationLabel: `Воодушевление`,
detailedView_loadoutLabel: `Конфигурации`,
detailedView_equipmentFeaturesLabel: `Модификаторы снаряжения`,
detailedView_umbraEchoesLabel: `Эхо Умбры`,
invigorations_offensive_AbilityStrength: `+200% к силе способностей.`,
invigorations_offensive_AbilityRange: `+100% к зоне поражения способностей.`,
@@ -199,9 +195,6 @@ dict = {
abilityOverride_label: `Переопределение способности`,
abilityOverride_onSlot: `в ячейке`,
detailedView_umbraEchoesDescription: `Введение этой жидкости в варфрейм позволит ему автономно сражаться бок о бок с оператором.`,
detailedView_umbraEchoesExpiryLabel: `Срок действия Эха (необязательно)`,
mods_addRiven: `Добавить мод Разлома`,
mods_fingerprint: `Отпечаток`,
mods_fingerprintHelp: `Нужна помощь с отпечатком?`,
@@ -215,7 +208,6 @@ dict = {
cheats_server: `Сервер`,
cheats_skipTutorial: `Пропустить обучение`,
cheats_skipAllDialogue: `Пропустить все диалоги`,
cheats_skipAllPopups: `[UNTRANSLATED] Skip All Popups`,
cheats_unlockAllScans: `Разблокировать все сканирования`,
cheats_unlockSuccRelog: `Успех. Вам необходимо повторно войти в игру, чтобы клиент обновил эту информацию.`,
cheats_unlockAllMissions: `Разблокировать все миссии`,
@@ -374,7 +366,6 @@ dict = {
import_submit: `Отправить`,
import_samples: `Пример:`,
import_samples_maxFocus: `Все школы Фокуса макс. уровня`,
import_samples_accolades: `[UNTRANSLATED] Accolades & Council Chat Access`,
upgrade_Equilibrium: `Подбор сфер здоровья даёт +|VAL|% энергии. Подбор сфер энергии даёт +|VAL|% здоровья.`,
upgrade_MeleeCritDamage: `+|VAL|% к крит. урону в ближнем бою.`,

View File

@@ -23,9 +23,6 @@ dict = {
code_moteAmp: `Порошинка`,
code_amp: `Підсилювач`,
code_kDrive: `К-Драйв`,
code_fusionCoreCommon: `[UNTRANSLATED] Fusion Core (Common)`,
code_fusionCoreUncommon: `[UNTRANSLATED] Fusion Core (Uncommon)`,
code_fusionCoreRare: `[UNTRANSLATED] Fusion Core (Rare)`,
code_legendaryCore: `Легендарне ядро`,
code_traumaticPeculiar: `Особливе травмування`,
code_starter: `|MOD| (Пошкоджений)`,
@@ -83,7 +80,7 @@ dict = {
code_drifterFaceName: `Зовнішність мандрівника: |INDEX|`,
code_operatorFaceName: `Зовнішність оператора: |INDEX|`,
code_reviveBooster: `Збільшувач зцілення`,
code_reviveBoosterDesc: `[UNTRANSLATED] Sets revive count to 4, which allows self-revive in Archon Hunts.`,
code_reviveBoosterDesc: `[UNTRANSLATED] Set revive count to 4. Disable self-revive restriction on Archon Hunt missions.`,
code_succChange: `Успішно змінено.`,
code_requiredInvigorationUpgrade: `Ви повинні вибрати як атакуюче, так і допоміжне вдосконалення.`,
code_feature_1: `[UNTRANSLATED] Orokin Reactor`,
@@ -168,7 +165,6 @@ dict = {
detailedView_invigorationLabel: `Зміцнення`,
detailedView_loadoutLabel: `Конфігурації`,
detailedView_equipmentFeaturesLabel: `[UNTRANSLATED] Equipment Features`,
detailedView_umbraEchoesLabel: `[UNTRANSLATED] Echoes Of Umbra`,
invigorations_offensive_AbilityStrength: `+200% до потужності здібностей.`,
invigorations_offensive_AbilityRange: `+100% до досяжності здібностей.`,
@@ -199,9 +195,6 @@ dict = {
abilityOverride_label: `Перевизначення здібностей`,
abilityOverride_onSlot: `у комірці`,
detailedView_umbraEchoesDescription: `Рідина, яка після введення дозволяє ворфрейму використовувати єдність і битися самостійно пліч-о-пліч з оператором.`,
detailedView_umbraEchoesExpiryLabel: `[UNTRANSLATED] Echo Expiry (optional)`,
mods_addRiven: `Добавити модифікатор Розколу`,
mods_fingerprint: `Відбиток`,
mods_fingerprintHelp: `Потрібна допомога з відбитком?`,
@@ -215,7 +208,6 @@ dict = {
cheats_server: `Сервер`,
cheats_skipTutorial: `Пропустити навчання`,
cheats_skipAllDialogue: `Пропустити всі діалоги`,
cheats_skipAllPopups: `[UNTRANSLATED] Skip All Popups`,
cheats_unlockAllScans: `Розблокувати всі сканування`,
cheats_unlockSuccRelog: `Успіх. Вам потрібно буде повторно увійти в гру, щоб клієнт оновив цю інформацію.`,
cheats_unlockAllMissions: `Розблокувати всі місії`,
@@ -374,7 +366,6 @@ dict = {
import_submit: `Відправити`,
import_samples: `Приклад:`,
import_samples_maxFocus: `Всі школи Фокусу макс. рівня`,
import_samples_accolades: `[UNTRANSLATED] Accolades & Council Chat Access`,
upgrade_Equilibrium: `Згустки здоров'я дають +|VAL|% енергії, згустки енергії дають +|VAL|% здоров'я.`,
upgrade_MeleeCritDamage: `+|VAL|% до критичної шкоди від холодної зброї.`,

View File

@@ -23,9 +23,6 @@ dict = {
code_moteAmp: `微尘增幅器`,
code_amp: `增幅器`,
code_kDrive: `K式悬浮板`,
code_fusionCoreCommon: `[UNTRANSLATED] Fusion Core (Common)`,
code_fusionCoreUncommon: `[UNTRANSLATED] Fusion Core (Uncommon)`,
code_fusionCoreRare: `[UNTRANSLATED] Fusion Core (Rare)`,
code_legendaryCore: `传奇核心`,
code_traumaticPeculiar: `创伤怪奇`,
code_starter: `|MOD| (有瑕疵的)`,
@@ -83,7 +80,7 @@ dict = {
code_drifterFaceName: `漂泊者面部 |INDEX|`,
code_operatorFaceName: `指挥官面部 |INDEX|`,
code_reviveBooster: `复活加速器`,
code_reviveBoosterDesc: `[UNTRANSLATED] Sets revive count to 4, which allows self-revive in Archon Hunts.`,
code_reviveBoosterDesc: `[UNTRANSLATED] Set revive count to 4. Disable self-revive restriction on Archon Hunt missions.`,
code_succChange: `更改成功`,
code_requiredInvigorationUpgrade: `[UNTRANSLATED] You must select both an offensive & utility upgrade.`,
code_feature_1: `[UNTRANSLATED] Orokin Reactor`,
@@ -168,7 +165,6 @@ dict = {
detailedView_invigorationLabel: `活化`,
detailedView_loadoutLabel: `配置`,
detailedView_equipmentFeaturesLabel: `[UNTRANSLATED] Equipment Features`,
detailedView_umbraEchoesLabel: `[UNTRANSLATED] Echoes Of Umbra`,
invigorations_offensive_AbilityStrength: `+200%技能强度`,
invigorations_offensive_AbilityRange: `+100%技能范围`,
@@ -199,9 +195,6 @@ dict = {
abilityOverride_label: `技能替换`,
abilityOverride_onSlot: `槽位`,
detailedView_umbraEchoesDescription: `将这种液体注入战甲内,使其具有与指挥官并肩作战的能力。`,
detailedView_umbraEchoesExpiryLabel: `[UNTRANSLATED] Echo Expiry (optional)`,
mods_addRiven: `添加裂罅MOD`,
mods_fingerprint: `印记`,
mods_fingerprintHelp: `需要印记相关的帮助?`,
@@ -215,7 +208,6 @@ dict = {
cheats_server: `服务器`,
cheats_skipTutorial: `跳过教程`,
cheats_skipAllDialogue: `跳过所有对话`,
cheats_skipAllPopups: `[UNTRANSLATED] Skip All Popups`,
cheats_unlockAllScans: `解锁所有扫描`,
cheats_unlockSuccRelog: `[UNTRANSLATED] Success. Please note that you'll need to relog for the client to refresh this.`,
cheats_unlockAllMissions: `解锁所有星图`,
@@ -374,7 +366,6 @@ dict = {
import_submit: `提交`,
import_samples: `示例:`,
import_samples_maxFocus: `所有专精学派完全精通`,
import_samples_accolades: `[UNTRANSLATED] Accolades & Council Chat Access`,
upgrade_Equilibrium: `拾取生命球+|VAL|%能量,拾取能量球+|VAL|%生命`,
upgrade_MeleeCritDamage: `+|VAL|%近战暴击伤害`,

1
tsturnonagain Normal file
View File

@@ -0,0 +1 @@
nounusedlocals,