merge upstream

This commit is contained in:
2025-11-18 09:11:20 -08:00
33 changed files with 791 additions and 276 deletions

9
package-lock.json generated
View File

@@ -552,6 +552,7 @@
"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,6 +1181,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -2057,6 +2059,7 @@
"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",
@@ -2223,6 +2226,7 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@@ -4362,6 +4366,7 @@
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -5203,6 +5208,7 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -5421,6 +5427,7 @@
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
"devOptional": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -5480,6 +5487,7 @@
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"peer": true,
"dependencies": {
"napi-postinstall": "^0.3.0"
},
@@ -5675,6 +5683,7 @@
"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,4 +1,4 @@
import { toOid } from "../../helpers/inventoryHelpers.ts";
import { toOid2 } from "../../helpers/inventoryHelpers.ts";
import {
createVeiledRivenFingerprint,
createUnveiledRivenFingerprint,
@@ -6,13 +6,14 @@ import {
} from "../../helpers/rivenHelper.ts";
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
import { addMods, getInventory } from "../../services/inventoryService.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getAccountForRequest } 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 accountId = await getAccountIdForRequest(req);
const account = await getAccountForRequest(req);
const accountId = account._id.toString();
const inventory = await getInventory(accountId, "RawUpgrades Upgrades instantFinishRivenChallenge");
const request = getJSONfromString<IActiveRandomModRequest>(String(req.body));
addMods(inventory, [
@@ -36,7 +37,7 @@ export const activateRandomModController: RequestHandler = async (req, res) => {
NewMod: {
UpgradeFingerprint: fingerprint,
ItemType: rivenType,
ItemId: toOid(inventory.Upgrades[upgradeIndex]._id)
ItemId: toOid2(inventory.Upgrades[upgradeIndex]._id, account.BuildLabel)
}
});
};

View File

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

View File

@@ -1,63 +1,126 @@
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getAccountForRequest } from "../../services/loginService.ts";
import type { RequestHandler } from "express";
import type { IInventoryClient, IUpgradeClient } from "../../types/inventoryTypes/inventoryTypes.ts";
import type {
IInventoryClient,
IUpgradeClient,
IUpgradeFromClient
} 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 accountId = await getAccountIdForRequest(req);
const account = await getAccountForRequest(req);
const accountId = account._id.toString();
const artifactsData = getJSONfromString<IArtifactsRequest>(String(req.body));
const { Upgrade, LevelDiff, Cost, FusionPointCost } = artifactsData;
const { Upgrade, LevelDiff, Cost, FusionPointCost, Consumed, Fingerprint } = artifactsData;
const inventory = await getInventory(accountId);
const { Upgrades } = inventory;
const { ItemType, UpgradeFingerprint, ItemId } = Upgrade;
const safeUpgradeFingerprint = UpgradeFingerprint || '{"lvl":0}';
const parsedUpgradeFingerprint = JSON.parse(safeUpgradeFingerprint) as { lvl: number };
parsedUpgradeFingerprint.lvl += LevelDiff;
const stringifiedUpgradeFingerprint = JSON.stringify(parsedUpgradeFingerprint);
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);
let itemIndex = Upgrades.findIndex(upgrade => upgrade._id.equals(ItemId.$oid));
let itemIndex = Upgrades.findIndex(upgrade => upgrade._id.equals(fromOid(ItemId)));
if (itemIndex !== -1) {
Upgrades[itemIndex].UpgradeFingerprint = stringifiedUpgradeFingerprint;
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);
} else {
itemIndex =
Upgrades.push({
UpgradeFingerprint: stringifiedUpgradeFingerprint,
ItemType
}) - 1;
// 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);
addMods(inventory, [{ ItemType, ItemCount: -1 }]);
}
let itemIndex = Upgrades.findIndex(upgrade => upgrade._id.equals(ItemId.$id));
if (!inventory.infiniteCredits) {
inventory.RegularCredits -= Cost;
}
if (!inventory.infiniteEndo) {
inventory.FusionPoints -= FusionPointCost;
}
if (itemIndex !== -1) {
Upgrades[itemIndex].UpgradeFingerprint = stringifiedUpgradeFingerprint;
} else {
itemIndex =
Upgrades.push({
UpgradeFingerprint: stringifiedUpgradeFingerprint,
ItemType
}) - 1;
if (artifactsData.LegendaryFusion) {
addMods(inventory, [
{
ItemType: "/Lotus/Upgrades/Mods/Fusers/LegendaryModFuser",
ItemCount: -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);
}
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);
};
@@ -67,4 +130,6 @@ interface IArtifactsRequest {
Cost: number;
FusionPointCost: number;
LegendaryFusion?: boolean;
Fingerprint?: string;
Consumed?: IUpgradeFromClient[];
}

View File

@@ -262,9 +262,9 @@ const claimCompletedRecipe = async (
"",
"",
"",
umbraModA.ItemId.$oid,
umbraModB.ItemId.$oid,
umbraModC.ItemId.$oid
fromOid(umbraModA.ItemId),
fromOid(umbraModB.ItemId),
fromOid(umbraModC.ItemId)
]
}
],
@@ -284,7 +284,18 @@ const claimCompletedRecipe = async (
"/Lotus/Weapons/Tenno/Melee/Swords/UmbraKatana/UmbraKatana",
{
Configs: [
{ Upgrades: ["", "", "", "", "", "", sacrificeModA.ItemId.$oid, sacrificeModB.ItemId.$oid] }
{
Upgrades: [
"",
"",
"",
"",
"",
"",
fromOid(sacrificeModA.ItemId),
fromOid(sacrificeModB.ItemId)
]
}
],
XP: 450_000,
Features: EquipmentFeatures.DOUBLE_CAPACITY

View File

@@ -6,7 +6,7 @@ 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 } from "../../types/inventoryTypes/inventoryTypes.ts";
import type { IInventoryClient, IShipInventory, IUpgradeClient } 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 +30,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, version_compare } from "../../helpers/inventoryHelpers.ts";
import { toLegacyOid, toOid, toOid2, version_compare } from "../../helpers/inventoryHelpers.ts";
import { Inbox } from "../../models/inboxModel.ts";
import { unixTimesInMs } from "../../constants/timeConstants.ts";
import { DailyDeal } from "../../models/worldStateModel.ts";
@@ -454,28 +454,140 @@ export const getInventoryResponse = async (
inventoryResponse.Nemesis = undefined;
}
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, "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, "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);
}
}
}
for (const upgrade of inventoryResponse.Upgrades) {
toLegacyOid(upgrade.ItemId);
}
if (inventoryResponse.BrandedSuits) {
for (const id of inventoryResponse.BrandedSuits) {
toLegacyOid(id);
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);
}
}
if (inventoryResponse.GuildId) {
toLegacyOid(inventoryResponse.GuildId);
}
}
}

View File

@@ -161,7 +161,16 @@ const createLoginResponse = (
if (version_compare(buildLabel, "2022.09.06.19.24") >= 0) {
resp.CrossPlatformAllowed = account.CrossPlatformAllowed;
resp.HUB = `${myUrlBase}/api/`;
resp.MatchmakingBuildId = buildConfig.matchmakingBuildId;
// 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();
}
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, toMongoDate, version_compare } from "../../helpers/inventoryHelpers.ts";
import { fromDbOid, fromOid, 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 } from "../../types/commonTypes.ts";
import type { IMongoDate, IOid, IOidWithLegacySupport } 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: IOid; ItemType: string },
upgrade: { ItemId: IOidWithLegacySupport; ItemType: string },
dataknifeUpgrades: string[]
): void => {
response.UpgradeIds ??= [];
@@ -429,13 +429,13 @@ const consumeModCharge = (
response.UpgradeNew ??= [];
response.HasKnife = true;
if (upgrade.ItemId.$oid != "000000000000000000000000") {
const dbUpgrade = inventory.Upgrades.id(upgrade.ItemId.$oid)!;
if (fromOid(upgrade.ItemId) != "000000000000000000000000") {
const dbUpgrade = inventory.Upgrades.id(fromOid(upgrade.ItemId))!;
const fingerprint = JSON.parse(dbUpgrade.UpgradeFingerprint!) as { lvl: number };
fingerprint.lvl += 1;
dbUpgrade.UpgradeFingerprint = JSON.stringify(fingerprint);
response.UpgradeIds.push(upgrade.ItemId.$oid);
response.UpgradeIds.push(fromOid(upgrade.ItemId));
response.UpgradeTypes.push(upgrade.ItemType);
response.UpgradeFingerprints.push(fingerprint);
response.UpgradeNew.push(false);

View File

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

View File

@@ -1,18 +1,22 @@
import type { RequestHandler } from "express";
import type { ISaveLoadoutRequest } from "../../types/saveLoadoutTypes.ts";
import { handleInventoryItemConfigChange } from "../../services/saveLoadoutService.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getAccountForRequest } from "../../services/loginService.ts";
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
export const saveLoadoutController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const account = await getAccountForRequest(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, accountId);
const newLoadoutId = await handleInventoryItemConfigChange(
equipmentChanges,
account._id.toString(),
account.BuildLabel
);
//send back new loadout id, if new loadout was added
if (newLoadoutId) {

View File

@@ -1,155 +1,288 @@
import type { RequestHandler } from "express";
import type { IUpgradesRequest } from "../../types/requestTypes.ts";
import { fromOid, version_compare } from "../../helpers/inventoryHelpers.ts";
import type { IUpgradesRequest, IUpgradesRequestLegacy } from "../../types/requestTypes.ts";
import type { ArtifactPolarity, IAbilityOverride } from "../../types/inventoryTypes/commonInventoryTypes.ts";
import type { IInventoryClient, IMiscItem } from "../../types/inventoryTypes/inventoryTypes.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { addMiscItems, addRecipes, getInventory, updateCurrency } from "../../services/inventoryService.ts";
import { getAccountForRequest } from "../../services/loginService.ts";
import { addMiscItems, addMods, 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 accountId = await getAccountIdForRequest(req);
const payload = JSON.parse(String(req.body)) as IUpgradesRequest;
const account = await getAccountForRequest(req);
const accountId = account._id.toString();
const inventory = await getInventory(accountId);
const inventoryChanges: IInventoryChanges = {};
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
]);
}
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;
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
]);
}
}
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;
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);
}
break;
addMiscItems(inventory, [
{
ItemType: payload.UtilityReq,
ItemCount: -1
} satisfies IMiscItem
]);
}
case "/Lotus/Types/Items/MiscItems/ValenceAdapter": {
const item = inventory[payload.ItemCategory].id(payload.ItemId.$oid)!;
item.Features ??= 0;
item.Features |= EquipmentFeatures.VALENCE_SWAP;
break;
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
]);
}
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;
}
// 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: ["", "", "", "", "", "", "", "", "", "", ""] });
}
case "/Lotus/Types/Items/MiscItems/ModSlotUnlocker": {
const item = inventory[payload.ItemCategory].id(payload.ItemId.$oid)!;
item.ModSlotPurchases ??= 0;
item.ModSlotPurchases += 1;
break;
if (item.Configs[0].Upgrades && item.Configs[0].Upgrades.length < 11) {
item.Configs[0].Upgrades.length = 11;
}
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);
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;
}
}
break;
}
default:
throw new Error("Unsupported upgrade: " + operation.UpgradeRequirement);
});
}
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)] = "";
}
});
}
}
} 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?`);
}
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)!;
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;
}
}
}
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);
}
}
}
await inventory.save();
res.json({ InventoryChanges: inventoryChanges });

View File

@@ -13,33 +13,41 @@ export const importController: RequestHandler = async (req, res) => {
const request = req.body as IImportRequest;
let anyKnownKey = false;
try {
const inventory = await getInventory(accountId);
if (importInventory(inventory, request.inventory)) {
anyKnownKey = true;
await inventory.save();
}
const inventory = await getInventory(accountId);
if (importInventory(inventory, request.inventory)) {
anyKnownKey = true;
await inventory.save();
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();
}
if (!anyKnownKey) {
res.send("noKnownKey").end();
}
broadcastInventoryUpdate(req);
} catch (e) {
console.error(e);
res.send((e as Error).message);
}
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);
res.end();
};
interface IImportRequest {

View File

@@ -20,6 +20,10 @@ 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,7 +1060,6 @@ const EquipmentSchema = new Schema<IEquipmentDatabase>(
InfestationDays: Number,
InfestationType: String,
ModularParts: { type: [String], default: undefined },
UnlockLevel: Number,
Expiry: Date,
SkillTree: String,
OffensiveUpgrade: String,
@@ -1493,6 +1492,8 @@ 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

@@ -9,6 +9,7 @@ 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);

View File

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

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 } from "../types/inventoryTypes/commonInventoryTypes.ts";
import type { IFlavourItem, IItemConfig, IItemConfigDatabase } from "../types/inventoryTypes/commonInventoryTypes.ts";
import type { IDefaultUpgrade, IPowersuit, ISentinel, TStandingLimitBin } from "warframe-public-export-plus";
import {
ExportArcanes,
@@ -922,9 +922,9 @@ export const addItems = async (
export const applyDefaultUpgrades = (
inventory: TInventoryDatabaseDocument,
defaultUpgrades: IDefaultUpgrade[] | undefined
): IItemConfig[] => {
): IItemConfigDatabase[] => {
const modsToGive: IRawUpgrade[] = [];
const configs: IItemConfig[] = [];
const configs: IItemConfigDatabase[] = [];
if (defaultUpgrades) {
const upgrades = [];
for (const defaultUpgrade of defaultUpgrades) {

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 } from "../helpers/inventoryHelpers.ts";
import { fromOid, version_compare } 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,9 +480,32 @@ export const addMissionInventoryUpdates = async (
case "Upgrades":
value.forEach(clientUpgrade => {
const id = fromOid(clientUpgrade.ItemId);
if (id == "") {
// 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 == "") {
// U19 does not provide RawUpgrades and instead interleaves them with riven progress here
addMods(inventory, [clientUpgrade]);
addMods(inventory, [
{
ItemType: clientUpgrade.ItemType,
ItemCount: clientUpgrade.ItemCount
}
]);
} else {
const upgrade = inventory.Upgrades.id(id)!;
upgrade.UpgradeFingerprint = clientUpgrade.UpgradeFingerprint; // primitive way to copy over the riven challenge progress

View File

@@ -15,6 +15,7 @@ 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";
@@ -761,9 +762,9 @@ export const removeRequiredItems = async (inventory: TInventoryDatabaseDocument,
"",
"",
"",
umbraModA.ItemId.$oid,
umbraModB.ItemId.$oid,
umbraModC.ItemId.$oid
fromOid(umbraModA.ItemId),
fromOid(umbraModB.ItemId),
fromOid(umbraModC.ItemId)
]
}
],
@@ -778,7 +779,16 @@ export const removeRequiredItems = async (inventory: TInventoryDatabaseDocument,
addEquipment(inventory, "Melee", "/Lotus/Weapons/Tenno/Melee/Swords/UmbraKatana/UmbraKatana", {
Configs: [
{
Upgrades: ["", "", "", "", "", "", sacrificeModA.ItemId.$oid, sacrificeModB.ItemId.$oid]
Upgrades: [
"",
"",
"",
"",
"",
"",
fromOid(sacrificeModA.ItemId),
fromOid(sacrificeModB.ItemId)
]
}
],
XP: 450_000,

View File

@@ -6,14 +6,15 @@ import type {
ISaveLoadoutRequestNoUpgradeVer
} from "../types/saveLoadoutTypes.ts";
import { Loadout } from "../models/inventoryModels/loadoutModel.ts";
import { getInventory } from "./inventoryService.ts";
import { addMods, 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 } from "../types/inventoryTypes/commonInventoryTypes.ts";
import type { IItemConfig, IItemConfigDatabase } 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
@@ -26,7 +27,8 @@ itemconfig has multiple config ids
*/
export const handleInventoryItemConfigChange = async (
equipmentChanges: ISaveLoadoutRequestNoUpgradeVer,
accountId: string
accountId: string,
buildLabel: string | undefined
): Promise<string | void> => {
const inventory = await getInventory(accountId);
@@ -196,7 +198,36 @@ export const handleInventoryItemConfigChange = async (
for (const [configId, config] of Object.entries(itemConfigEntries)) {
if (/^[0-9]+$/.test(configId)) {
inventoryItem.Configs[parseInt(configId)] = config as IItemConfig;
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;
}
}
if ("Favorite" in itemConfigEntries) {

View File

@@ -4,6 +4,7 @@ import type {
ICrewShipCustomization,
IFlavourItem,
IItemConfig,
IItemConfigDatabase,
IPolarity
} from "./inventoryTypes/commonInventoryTypes.ts";
@@ -33,7 +34,7 @@ export enum EquipmentFeatures {
export interface IEquipmentDatabase {
ItemType: string;
ItemName?: string;
Configs: IItemConfig[];
Configs: IItemConfigDatabase[];
UpgradeVer?: number;
XP?: number;
Features?: number;
@@ -48,7 +49,6 @@ export interface IEquipmentDatabase {
InfestationDays?: number;
InfestationType?: string;
ModularParts?: string[];
UnlockLevel?: number;
Expiry?: Date;
SkillTree?: string;
OffensiveUpgrade?: string;
@@ -69,9 +69,18 @@ export interface IEquipmentDatabase {
export interface IEquipmentClient
extends Omit<
IEquipmentDatabase,
"_id" | "InfestationDate" | "Expiry" | "UpgradesExpiry" | "UmbraDate" | "Weapon" | "CrewMembers" | "Details"
| "_id"
| "Configs"
| "InfestationDate"
| "Expiry"
| "UpgradesExpiry"
| "UmbraDate"
| "Weapon"
| "CrewMembers"
| "Details"
> {
ItemId: IOidWithLegacySupport;
Configs: IItemConfig[];
InfestationDate?: IMongoDate;
Expiry?: IMongoDate;
UpgradesExpiry?: IMongoDate;
@@ -79,6 +88,10 @@ 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[];
Upgrades?: string[] | { $id: string }[];
Name?: string;
OperatorAmp?: IOid;
Songs?: ISong[];
@@ -56,13 +56,17 @@ 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 IItemConfig {
export interface IOperatorConfigDatabase extends IItemConfigDatabase {
_id: Types.ObjectId;
}

View File

@@ -318,6 +318,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
Accolades?: IAccolades;
Counselor?: boolean;
Upgrades: IUpgradeClient[];
Cards?: IUpgradeClient[]; // U8
EquippedGear: string[];
DeathMarks: string[];
FusionTreasures: IFusionTreasure[];
@@ -592,10 +593,16 @@ export interface IUpgradeClient {
ItemType: string;
UpgradeFingerprint?: string;
PendingRerollFingerprint?: string;
ItemId: IOid;
ItemId: IOidWithLegacySupport;
// Stuff for U7-U8
ParentId?: IOidWithLegacySupport;
Slot?: number;
AmountRemaining?: number;
Rank?: number;
}
export interface IUpgradeDatabase extends Omit<IUpgradeClient, "ItemId"> {
export interface IUpgradeDatabase
extends Omit<IUpgradeClient, "ItemId" | "ParentId" | "Slot" | "AmountRemaining" | "Rank"> {
_id: Types.ObjectId;
}
@@ -603,9 +610,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, ITypeCount } from "./commonTypes.ts";
import type { IOid, IOidWithLegacySupport, ITypeCount } from "./commonTypes.ts";
import type { ArtifactPolarity, IPolarity } from "./inventoryTypes/commonInventoryTypes.ts";
import type {
IBooster,
@@ -21,7 +21,8 @@ import type {
IInvasionProgressClient,
IWeaponSkinClient,
IKubrowPetEggClient,
INemesisClient
INemesisClient,
IUpgradeClient
} from "./inventoryTypes/inventoryTypes.ts";
import type { IGroup } from "./loginTypes.ts";
import type { ILoadOutPresets } from "./saveLoadoutTypes.ts";
@@ -227,6 +228,24 @@ 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;
buildId?: number | bigint;
gameModeId?: number;
regionId?: number;
maxEloDifference?: number;

View File

@@ -426,9 +426,25 @@ 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")
name: loc("code_legendaryCore"),
fusionLimit: 0
});
data.mods.push({
uniqueName: "/Lotus/Upgrades/CosmeticEnhancers/Peculiars/CyoteMod",
@@ -1507,7 +1523,9 @@ function updateInventory() {
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
td.innerHTML += " <span title='" + loc("code_rank") + "'>★ 0/" + maxRank + "</span>";
if (maxRank > 0) {
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>";
@@ -3463,6 +3481,9 @@ 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(() => {
@@ -3625,8 +3646,12 @@ function doImport() {
data: JSON.stringify({
inventory: JSON.parse($("#import-inventory").val())
})
}).then(function (anyKnownKey) {
toast(loc(anyKnownKey ? "code_succImport" : "code_nothingToDo"));
}).then(function (err) {
if (err) {
toast(err == "noKnownKey" ? loc("code_nothingToDo") : err);
} else {
toast(loc("code_succImport"));
}
updateInventory();
});
} catch (e) {

View File

@@ -23,6 +23,9 @@ 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)`,

View File

@@ -22,6 +22,9 @@ 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)`,

View File

@@ -23,6 +23,9 @@ 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)`,

View File

@@ -23,6 +23,9 @@ 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)`,

View File

@@ -23,6 +23,9 @@ 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| (Повреждённый)`,

View File

@@ -23,6 +23,9 @@ 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| (Пошкоджений)`,

View File

@@ -23,6 +23,9 @@ 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| (有瑕疵的)`,