diff --git a/src/controllers/api/sellController.ts b/src/controllers/api/sellController.ts index 530d37cc..d2959e6d 100644 --- a/src/controllers/api/sellController.ts +++ b/src/controllers/api/sellController.ts @@ -45,6 +45,11 @@ export const sellController: RequestHandler = async (req, res) => { inventory.Melee.pull({ _id: sellItem.String }); }); } + if (payload.Items.SpecialItems) { + payload.Items.SpecialItems.forEach(sellItem => { + inventory.SpecialItems.pull({ _id: sellItem.String }); + }); + } if (payload.Items.Consumables) { const consumablesChanges = []; for (const sellItem of payload.Items.Consumables) { diff --git a/src/controllers/custom/addItemController.ts b/src/controllers/custom/addItemController.ts index ea5fd535..d9976632 100644 --- a/src/controllers/custom/addItemController.ts +++ b/src/controllers/custom/addItemController.ts @@ -1,7 +1,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService"; import { ItemType, toAddItemRequest } from "@/src/helpers/customHelpers/addItemHelpers"; import { getWeaponType } from "@/src/services/itemDataService"; -import { addPowerSuit, addEquipment } from "@/src/services/inventoryService"; +import { addPowerSuit, addEquipment, addSpecialItem } from "@/src/services/inventoryService"; import { RequestHandler } from "express"; const addItemController: RequestHandler = async (req, res) => { @@ -18,6 +18,10 @@ const addItemController: RequestHandler = async (req, res) => { const weapon = await addEquipment(weaponType, request.InternalName, accountId); res.json(weapon); break; + case ItemType.SpecialItem: + const specialItem = await addSpecialItem(request.InternalName, accountId); + res.json(specialItem); + break; default: res.status(400).json({ error: "something went wrong" }); break; diff --git a/src/controllers/custom/getItemListsController.ts b/src/controllers/custom/getItemListsController.ts index e3fa95a1..5db843f2 100644 --- a/src/controllers/custom/getItemListsController.ts +++ b/src/controllers/custom/getItemListsController.ts @@ -1,10 +1,11 @@ import { RequestHandler } from "express"; -import { getDict, getItemName, getString } from "@/src/services/itemDataService"; +import { getDict, getExalted, getItemName, getString } from "@/src/services/itemDataService"; import { ExportArcanes, ExportGear, ExportRecipes, ExportResources, + ExportSentinels, ExportUpgrades, ExportWarframes, ExportWeapons @@ -15,12 +16,34 @@ interface ListedItem { uniqueName: string; name: string; fusionLimit?: number; + exalted?: { uniqueName: string; name: string }[]; } const getItemListsController: RequestHandler = (req, res) => { const lang = getDict(typeof req.query.lang == "string" ? req.query.lang : "en"); const weapons = []; const miscitems = []; + const warframes = []; + for (const [uniqueName, item] of Object.entries(ExportWarframes)) { + if (item.productCategory == "Suits") { + const warframe: ListedItem = { + uniqueName, + name: getString(item.name, lang) + }; + const exalted = getExalted(uniqueName); + if (exalted) { + warframe.exalted = []; + exalted.forEach(element => { + const exalted = ExportWeapons[element] || ExportSentinels[element]; + warframe.exalted?.push({ + uniqueName: element, + name: getString(exalted.name, lang) + }); + }); + } + warframes.push(warframe); + } + } for (const [uniqueName, item] of Object.entries(ExportWeapons)) { if (item.productCategory !== "OperatorAmps") { if (item.totalDamage !== 0) { @@ -84,14 +107,7 @@ const getItemListsController: RequestHandler = (req, res) => { } res.json({ - warframes: Object.entries(ExportWarframes) - .filter(([_uniqueName, warframe]) => warframe.productCategory == "Suits") - .map(([uniqueName, warframe]) => { - return { - uniqueName, - name: getString(warframe.name, lang) - }; - }), + warframes, weapons, miscitems, mods, diff --git a/src/helpers/customHelpers/addItemHelpers.ts b/src/helpers/customHelpers/addItemHelpers.ts index 5ce2378f..112fd2e6 100644 --- a/src/helpers/customHelpers/addItemHelpers.ts +++ b/src/helpers/customHelpers/addItemHelpers.ts @@ -2,7 +2,8 @@ import { isString } from "@/src/helpers/general"; export enum ItemType { Powersuit = "Powersuit", - Weapon = "Weapon" + Weapon = "Weapon", + SpecialItem = "SpecialItem" } export const isItemType = (itemType: string): itemType is ItemType => { diff --git a/src/services/inventoryService.ts b/src/services/inventoryService.ts index 73acd669..a901b5e4 100644 --- a/src/services/inventoryService.ts +++ b/src/services/inventoryService.ts @@ -29,7 +29,7 @@ import { import { logger } from "@/src/utils/logger"; import { getWeaponType, getExalted } from "@/src/services/itemDataService"; import { ISyndicateSacrifice, ISyndicateSacrificeResponse } from "../types/syndicateTypes"; -import { IEquipmentClient, IItemConfig } from "../types/inventoryTypes/commonInventoryTypes"; +import { EquipmentFeatures, IEquipmentClient, IItemConfig } from "../types/inventoryTypes/commonInventoryTypes"; import { ExportArcanes, ExportCustoms, @@ -433,15 +433,19 @@ export const addMechSuit = async (mechsuitName: string, accountId: string) => { export const addSpecialItem = async (itemName: string, accountId: string) => { const inventory = await getInventory(accountId); - const specialItemIndex = inventory.SpecialItems.push({ - ItemType: itemName, - Configs: [], - Features: 1, - UpgradeVer: 101, - XP: 0 - }); - const changedInventory = await inventory.save(); - return changedInventory.SpecialItems[specialItemIndex - 1].toJSON(); + // According to wiki there is one exalted item per suit type, so we check if we don't already have that item + if (!inventory.SpecialItems.length || inventory.SpecialItems.some(obj => obj.ItemType !== itemName)) { + const specialItemIndex = inventory.SpecialItems.push({ + ItemType: itemName, + Configs: [], + Features: EquipmentFeatures.DOUBLE_CAPACITY, + UpgradeVer: 101, + XP: 0 + }); + const changedInventory = await inventory.save(); + return changedInventory.SpecialItems[specialItemIndex - 1].toJSON(); + } + return; }; export const addSpaceSuit = async (spacesuitName: string, accountId: string) => { diff --git a/src/types/sellTypes.ts b/src/types/sellTypes.ts index cd9fb2dd..fe0174f5 100644 --- a/src/types/sellTypes.ts +++ b/src/types/sellTypes.ts @@ -7,6 +7,7 @@ export interface ISellRequest { Consumables?: ISellItem[]; Recipes?: ISellItem[]; Upgrades?: ISellItem[]; + SpecialItems?: ISellItem[]; }; SellPrice: number; SellCurrency: diff --git a/static/webui/index.html b/static/webui/index.html index 416d1978..b99c3aae 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -126,6 +126,10 @@
+ Note: Changes made here will only be reflected in-game when the game re-downloads your + inventory. Visiting the navigation should be the easiest way to trigger that. +
diff --git a/static/webui/script.js b/static/webui/script.js index 56736c38..c1e7b922 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -449,6 +449,124 @@ function updateInventory() { $("#powersuit-route .text-body-secondary").text(""); } + document.getElementById("exalted-list").innerHTML = ""; + document.getElementById("exalted-list").closest(".card").style = ""; + + const exaltedWeapons = itemMap[item.ItemType].exalted; + if (exaltedWeapons) { + const specialItems = exaltedWeapons + .map(element => { + const specialItem = data.SpecialItems.find(x => x.ItemType == element.uniqueName); + if (specialItem) { + return { + ...specialItem, + name: element.name + }; + } + return undefined; + }) + .filter(item => item !== undefined); + + specialItems.forEach(item => { + const tr = document.createElement("tr"); + { + const td = document.createElement("td"); + td.textContent = item?.name ?? item.ItemType; + if (item.ItemName) { + td.textContent = item.ItemName + " (" + td.textContent + ")"; + } + tr.appendChild(td); + } + { + const td = document.createElement("td"); + td.classList = "text-end"; + if (item.XP < 1_600_000) { + const a = document.createElement("a"); + a.href = "#"; + a.onclick = function (event) { + event.preventDefault(); + addGearExp("SpecialItems", item.ItemId.$oid, 1_600_000 - item.XP); + }; + a.title = "Make Rank 30"; + a.innerHTML = ``; + td.appendChild(a); + } + { + const a = document.createElement("a"); + a.href = "#"; + a.onclick = function (event) { + event.preventDefault(); + const name = prompt("Enter new custom name:"); + if (name !== null) { + renameGear("SpecialItems", item.ItemId.$oid, name); + } + }; + a.title = "Rename"; + a.innerHTML = ``; + td.appendChild(a); + } + { + const a = document.createElement("a"); + a.href = "#"; + a.onclick = function (event) { + event.preventDefault(); + disposeOfGear("SpecialItems", item.ItemId.$oid); + }; + a.title = "Remove"; + a.innerHTML = ``; + td.appendChild(a); + } + tr.appendChild(td); + } + document.getElementById("exalted-list").appendChild(tr); + }); + + const missingItems = exaltedWeapons.filter( + element => !data.SpecialItems.some(x => x.ItemType === element.uniqueName) + ); + if (missingItems) { + missingItems.forEach(item => { + const tr = document.createElement("tr"); + { + const td = document.createElement("td"); + td.textContent = item?.name ?? item.uniqueName; + tr.appendChild(td); + } + { + const td = document.createElement("td"); + td.classList = "text-end"; + { + const a = document.createElement("a"); + a.href = "#"; + a.onclick = function (event) { + event.preventDefault(); + revalidateAuthz(() => { + const req = $.post({ + url: "/custom/addItem?" + window.authz, + contentType: "application/json", + data: JSON.stringify({ + type: "SpecialItem", + internalName: item.uniqueName + }) + }); + req.done(() => { + updateInventory(); + }); + }); + }; + a.title = "Add"; + a.innerHTML = ``; + td.appendChild(a); + } + tr.appendChild(td); + } + document.getElementById("exalted-list").appendChild(tr); + }); + } + } else { + document.getElementById("exalted-list").closest(".card").style.display = "none"; + } + const uniqueUpgrades = {}; (item.ArchonCrystalUpgrades ?? []).forEach(upgrade => { uniqueUpgrades[upgrade.UpgradeType] ??= 0;