feat: mods in pre-U18.18 builds + equipment features in pre-U24.4 builds

This commit is contained in:
2025-11-17 04:25:26 -05:00
parent 2eb7f7ed11
commit 32e0e3c427
26 changed files with 695 additions and 231 deletions

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
}
@@ -57,8 +60,8 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
let forcedPolarity: string | undefined;
payload.Consumed.forEach(upgrade => {
const meta = ExportUpgrades[upgrade.ItemType];
counts[meta.rarity] += upgrade.ItemCount;
if (fromOid(upgrade.ItemId) != "000000000000000000000000") {
counts[meta.rarity] += upgrade.ItemCount ? upgrade.ItemCount : 1;
if (fromOid(upgrade.ItemId) && fromOid(upgrade.ItemId) != "000000000000000000000000") {
inventory.Upgrades.pull({ _id: fromOid(upgrade.ItemId) });
} else {
addMods(inventory, [
@@ -133,7 +136,7 @@ interface IArtifactTransmutationRequest {
LevelDiff: number;
Consumed: IUpgradeFromClient[];
Cost: number;
FusionPointCost: number;
FusionPointCost?: number;
RivenTransmute?: boolean;
}

View File

@@ -1,63 +1,129 @@
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getAccountForRequest, getAccountIdForRequest } 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 { toLegacyOid, version_compare } from "../../helpers/inventoryHelpers.ts";
export const artifactsController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
const accountId = await getAccountIdForRequest(req);
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) {
if (version_compare(account.BuildLabel, "2016.12.21.19.13") <= 0) {
toLegacyOid(ItemId);
}
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(ItemId.$oid ?? ItemId.$id));
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.find(u => u._id.equals(upgrade.ItemId.$id))) {
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 +133,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

@@ -29,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, 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";
@@ -447,28 +447,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(new Types.ObjectId(), 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

@@ -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,
@@ -133,7 +133,7 @@ export const nemesisController: RequestHandler = async (req, res) => {
fromDbOid(inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE])
);
const dataknifeConfigIndex = dataknifeLoadout?.s?.mod ?? 0;
const dataknifeUpgrades = inventory.DataKnives[0].Configs[dataknifeConfigIndex].Upgrades!;
const dataknifeUpgrades = inventory.DataKnives[0].Configs[dataknifeConfigIndex].Upgrades! as string[];
for (const upgrade of body.knife!.AttachedUpgrades) {
switch (upgrade.ItemType) {
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndSpeedOnUseMod":
@@ -226,7 +226,8 @@ export const nemesisController: RequestHandler = async (req, res) => {
fromDbOid(inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE])
);
const dataknifeConfigIndex = dataknifeLoadout?.s?.mod ?? 0;
const dataknifeUpgrades = inventory.DataKnives[0].Configs[dataknifeConfigIndex].Upgrades!;
const dataknifeUpgrades = inventory.DataKnives[0].Configs[dataknifeConfigIndex]
.Upgrades! as string[];
for (let i = 3; i != 6; ++i) {
//logger.debug(`subtracting a charge from ${dataknifeUpgrades[i]}`);
const upgrade = parseUpgrade(inventory, dataknifeUpgrades[i]);
@@ -420,7 +421,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 +430,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,9 +1,10 @@
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";
@@ -12,145 +13,274 @@ import type { IEquipmentDatabase } from "../../types/equipmentTypes.ts";
import { EquipmentFeatures } from "../../types/equipmentTypes.ts";
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 as string[];
if (arr.indexOf(upgrade.ItemId.$id) != -1) {
// Handle swapping mod to a different slot
arr[arr.indexOf(upgrade.ItemId.$id)] = "";
}
arr[upgrade.Slot - 1] = upgrade.ItemId.$id;
// We need to convert RawUpgrade into Upgrade once it's attached
const rawUpgrade = inventory.RawUpgrades.find(x => x.ItemType == upgrade.ItemType);
if (rawUpgrade) {
addMods(inventory, [
{
ItemType: upgrade.ItemType,
ItemCount: -1
}
]);
inventory.Upgrades.push({
UpgradeFingerprint: `{"lvl":0}`,
ItemType: upgrade.ItemType,
_id: 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 as string[];
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

@@ -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,

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,7 +480,29 @@ export const addMissionInventoryUpdates = async (
case "Upgrades":
value.forEach(clientUpgrade => {
const id = fromOid(clientUpgrade.ItemId);
if (id == "") {
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
if (!clientUpgrade.UpgradeFingerprint) {
// Really old builds (tested U7-U8) do not have the UpgradeFingerprint set for unranked mod drops
clientUpgrade.UpgradeFingerprint = "lvl=0|";
}
if (!clientUpgrade.ItemCount) {
// U11 and below also don't initialize ItemCount since RawUpgrade doesn't exist in them
clientUpgrade.ItemCount = 1;
}
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]);
} else {

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,10 +6,11 @@ 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";
@@ -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,35 @@ 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) {
if (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 };
convertedUpgrades.push(upgradeId.$id);
const rawUpgrade = inventory.RawUpgrades.id(upgradeId.$id);
if (rawUpgrade) {
addMods(inventory, [
{
ItemType: rawUpgrade.ItemType,
ItemCount: -1
}
]);
inventory.Upgrades.push({
UpgradeFingerprint: `{"lvl":0}`,
ItemType: rawUpgrade.ItemType,
_id: upgradeId.$id
});
}
});
c.Upgrades = convertedUpgrades;
}
}
}
inventoryItem.Configs[parseInt(configId)] = c;
}
}
if ("Favorite" in itemConfigEntries) {

View File

@@ -48,7 +48,6 @@ export interface IEquipmentDatabase {
InfestationDays?: number;
InfestationType?: string;
ModularParts?: string[];
UnlockLevel?: number;
Expiry?: Date;
SkillTree?: string;
OffensiveUpgrade?: string;
@@ -79,6 +78,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[];

View File

@@ -317,6 +317,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
Accolades?: IAccolades;
Counselor?: boolean;
Upgrades: IUpgradeClient[];
Cards?: IUpgradeClient[]; // U8
EquippedGear: string[];
DeathMarks: string[];
FusionTreasures: IFusionTreasure[];
@@ -591,10 +592,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;
}

View File

@@ -227,6 +227,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

@@ -426,6 +426,21 @@ 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")
@@ -3450,6 +3465,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(() => {

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| (有瑕疵的)`,