From 32e0e3c4279274ba50528cdefb8eecb4dec9d094 Mon Sep 17 00:00:00 2001 From: VoltPrime Date: Mon, 17 Nov 2025 04:25:26 -0500 Subject: [PATCH] feat: mods in pre-U18.18 builds + equipment features in pre-U24.4 builds --- .../api/activateRandomModController.ts | 9 +- .../api/artifactTransmutationController.ts | 19 +- src/controllers/api/artifactsController.ts | 142 +++++-- .../api/claimCompletedRecipeController.ts | 19 +- src/controllers/api/inventoryController.ts | 152 ++++++- src/controllers/api/nemesisController.ts | 17 +- .../api/rerollRandomModController.ts | 12 +- src/controllers/api/saveLoadoutController.ts | 10 +- src/controllers/api/upgradesController.ts | 386 ++++++++++++------ src/helpers/inventoryHelpers.ts | 4 + src/models/inventoryModels/inventoryModel.ts | 1 - src/services/missionInventoryUpdateService.ts | 26 +- src/services/questService.ts | 18 +- src/services/saveLoadoutService.ts | 36 +- src/types/equipmentTypes.ts | 5 +- .../inventoryTypes/commonInventoryTypes.ts | 2 +- src/types/inventoryTypes/inventoryTypes.ts | 11 +- src/types/requestTypes.ts | 18 + static/webui/script.js | 18 + static/webui/translations/de.js | 3 + static/webui/translations/en.js | 3 + static/webui/translations/es.js | 3 + static/webui/translations/fr.js | 3 + static/webui/translations/ru.js | 3 + static/webui/translations/uk.js | 3 + static/webui/translations/zh.js | 3 + 26 files changed, 695 insertions(+), 231 deletions(-) diff --git a/src/controllers/api/activateRandomModController.ts b/src/controllers/api/activateRandomModController.ts index ce452b37..ff9917b6 100644 --- a/src/controllers/api/activateRandomModController.ts +++ b/src/controllers/api/activateRandomModController.ts @@ -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(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) } }); }; diff --git a/src/controllers/api/artifactTransmutationController.ts b/src/controllers/api/artifactTransmutationController.ts index 69cc41ee..7a728e14 100644 --- a/src/controllers/api/artifactTransmutationController.ts +++ b/src/controllers/api/artifactTransmutationController.ts @@ -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; } diff --git a/src/controllers/api/artifactsController.ts b/src/controllers/api/artifactsController.ts index 362aa971..b9109039 100644 --- a/src/controllers/api/artifactsController.ts +++ b/src/controllers/api/artifactsController.ts @@ -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(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(); + 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().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[]; } diff --git a/src/controllers/api/claimCompletedRecipeController.ts b/src/controllers/api/claimCompletedRecipeController.ts index b4325da9..fb533970 100644 --- a/src/controllers/api/claimCompletedRecipeController.ts +++ b/src/controllers/api/claimCompletedRecipeController.ts @@ -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 diff --git a/src/controllers/api/inventoryController.ts b/src/controllers/api/inventoryController.ts index e68ae983..e07858f1 100644 --- a/src/controllers/api/inventoryController.ts +++ b/src/controllers/api/inventoryController.ts @@ -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(); - 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(); + 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); } } } diff --git a/src/controllers/api/nemesisController.ts b/src/controllers/api/nemesisController.ts index 9f4af582..05ffc1fa 100644 --- a/src/controllers/api/nemesisController.ts +++ b/src/controllers/api/nemesisController.ts @@ -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); diff --git a/src/controllers/api/rerollRandomModController.ts b/src/controllers/api/rerollRandomModController.ts index cbed0276..0428c6df 100644 --- a/src/controllers/api/rerollRandomModController.ts +++ b/src/controllers/api/rerollRandomModController.ts @@ -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(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; } diff --git a/src/controllers/api/saveLoadoutController.ts b/src/controllers/api/saveLoadoutController.ts index 60431610..2573c540 100644 --- a/src/controllers/api/saveLoadoutController.ts +++ b/src/controllers/api/saveLoadoutController.ts @@ -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(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) { diff --git a/src/controllers/api/upgradesController.ts b/src/controllers/api/upgradesController.ts index cccb2cd9..440426c7 100644 --- a/src/controllers/api/upgradesController.ts +++ b/src/controllers/api/upgradesController.ts @@ -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().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().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 }); }; diff --git a/src/helpers/inventoryHelpers.ts b/src/helpers/inventoryHelpers.ts index a5edcaf8..24e2d2eb 100644 --- a/src/helpers/inventoryHelpers.ts +++ b/src/helpers/inventoryHelpers.ts @@ -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() }; }; diff --git a/src/models/inventoryModels/inventoryModel.ts b/src/models/inventoryModels/inventoryModel.ts index fa582fa0..4facc3b9 100644 --- a/src/models/inventoryModels/inventoryModel.ts +++ b/src/models/inventoryModels/inventoryModel.ts @@ -1060,7 +1060,6 @@ const EquipmentSchema = new Schema( InfestationDays: Number, InfestationType: String, ModularParts: { type: [String], default: undefined }, - UnlockLevel: Number, Expiry: Date, SkillTree: String, OffensiveUpgrade: String, diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index 58d57755..1580ea81 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -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 { diff --git a/src/services/questService.ts b/src/services/questService.ts index 10eff9c6..b13732cc 100644 --- a/src/services/questService.ts +++ b/src/services/questService.ts @@ -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, diff --git a/src/services/saveLoadoutService.ts b/src/services/saveLoadoutService.ts index 506a5444..634b8ca9 100644 --- a/src/services/saveLoadoutService.ts +++ b/src/services/saveLoadoutService.ts @@ -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 => { 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) { diff --git a/src/types/equipmentTypes.ts b/src/types/equipmentTypes.ts index f72dcca3..081b86f5 100644 --- a/src/types/equipmentTypes.ts +++ b/src/types/equipmentTypes.ts @@ -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 { diff --git a/src/types/inventoryTypes/commonInventoryTypes.ts b/src/types/inventoryTypes/commonInventoryTypes.ts index f0d139f5..a3027747 100644 --- a/src/types/inventoryTypes/commonInventoryTypes.ts +++ b/src/types/inventoryTypes/commonInventoryTypes.ts @@ -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[]; diff --git a/src/types/inventoryTypes/inventoryTypes.ts b/src/types/inventoryTypes/inventoryTypes.ts index 1bc2c61c..8a963db1 100644 --- a/src/types/inventoryTypes/inventoryTypes.ts +++ b/src/types/inventoryTypes/inventoryTypes.ts @@ -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 { +export interface IUpgradeDatabase + extends Omit { _id: Types.ObjectId; } diff --git a/src/types/requestTypes.ts b/src/types/requestTypes.ts index ed82d296..9e057592 100644 --- a/src/types/requestTypes.ts +++ b/src/types/requestTypes.ts @@ -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 diff --git a/static/webui/script.js b/static/webui/script.js index 7eabcebe..d71b18a2 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -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(() => { diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index 37fb291c..32b4e5ef 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -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)`, diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index 47ef0d1d..96b05cc4 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -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)`, diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index e8109a60..367a2c7f 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -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)`, diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index 606d68f8..54845862 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -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)`, diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index 727d8d3f..e4025a1d 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -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| (Повреждённый)`, diff --git a/static/webui/translations/uk.js b/static/webui/translations/uk.js index 49fa93b1..8fba6840 100644 --- a/static/webui/translations/uk.js +++ b/static/webui/translations/uk.js @@ -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| (Пошкоджений)`, diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index a5df62fc..6f401474 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -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| (有瑕疵的)`,