feat(webui): equipment features in detailed view #2987

Merged
Sainan merged 4 commits from AMelonInsideLemon/SpaceNinjaServer:webui-equipment-features into main 2025-11-06 00:16:59 -08:00
13 changed files with 210 additions and 34 deletions
Showing only changes of commit 3961daed86 - Show all commits

View File

@@ -34,10 +34,9 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
const weapon = inventory[data.Category][weaponIndex];
weapon.Features ??= 0;
weapon.Features |= EquipmentFeatures.GILDED;
if (data.Recipe != "webui") {
weapon.ItemName = data.ItemName;
weapon.XP = 0;
}
weapon.ItemName = data.ItemName;
weapon.XP = 0;
if (data.Category != "OperatorAmps" && data.PolarizeSlot && data.PolarizeValue) {
weapon.Polarity = [
{
@@ -52,22 +51,20 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
const affiliationMods = [];
if (data.Recipe != "webui") {
const recipe = ExportRecipes[data.Recipe];
inventoryChanges.MiscItems = recipe.secretIngredients!.map(ingredient => ({
ItemType: ingredient.ItemType,
ItemCount: ingredient.ItemCount * -1
}));
addMiscItems(inventory, inventoryChanges.MiscItems);
const recipe = ExportRecipes[data.Recipe];
inventoryChanges.MiscItems = recipe.secretIngredients!.map(ingredient => ({
ItemType: ingredient.ItemType,
ItemCount: ingredient.ItemCount * -1
}));
addMiscItems(inventory, inventoryChanges.MiscItems);
if (recipe.syndicateStandingChange) {
const affiliation = inventory.Affiliations.find(x => x.Tag == recipe.syndicateStandingChange!.tag)!;
affiliation.Standing += recipe.syndicateStandingChange.value;
affiliationMods.push({
Tag: recipe.syndicateStandingChange.tag,
Standing: recipe.syndicateStandingChange.value
});
}
if (recipe.syndicateStandingChange) {
const affiliation = inventory.Affiliations.find(x => x.Tag == recipe.syndicateStandingChange!.tag)!;
affiliation.Standing += recipe.syndicateStandingChange.value;
affiliationMods.push({
Tag: recipe.syndicateStandingChange.tag,
Standing: recipe.syndicateStandingChange.value
});
}
await inventory.save();

View File

@@ -410,6 +410,7 @@ export const getInventoryResponse = async (
for (const equipment of inventoryResponse[key]) {
equipment.Features ??= 0;
equipment.Features |= EquipmentFeatures.ARCANE_SLOT;
equipment.Features |= EquipmentFeatures.SECOND_ARCANE_SLOT;
}
}
}

View File

@@ -0,0 +1,34 @@
import type { RequestHandler } from "express";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts";
import { getInventory } from "../../services/inventoryService.ts";
import { EquipmentFeatures } from "../../types/equipmentTypes.ts";
import { sendWsBroadcastTo } from "../../services/wsService.ts";
export const equipmentFeaturesController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const category = req.query.Category as TEquipmentKey;
const inventory = await getInventory(
accountId,
`${category} unlockDoubleCapacityPotatoesEverywhere unlockExilusEverywhere unlockArcanesEverywhere`
);
const bit = Number(req.query.bit) as EquipmentFeatures;
if (
(inventory.unlockDoubleCapacityPotatoesEverywhere && bit === EquipmentFeatures.DOUBLE_CAPACITY) ||
(inventory.unlockExilusEverywhere && bit === EquipmentFeatures.UTILITY_SLOT) ||
(inventory.unlockArcanesEverywhere &&
(bit === EquipmentFeatures.ARCANE_SLOT || bit === EquipmentFeatures.SECOND_ARCANE_SLOT))
) {
res.status(400).end();
}
const item = inventory[category].id(req.query.ItemId as string);
if (item) {
item.Features ??= 0;
item.Features ^= bit;
await inventory.save();
sendWsBroadcastTo(accountId, { sync_inventory: true });
res.status(200).end();
} else {
res.status(400).end();
}
};

View File

@@ -1,6 +1,7 @@
import express from "express";
import { tunablesController } from "../controllers/custom/tunablesController.ts";
import { equipmentFeaturesController } from "../controllers/custom/equipmentFeaturesController.ts";
import { getItemListsController } from "../controllers/custom/getItemListsController.ts";
import { pushArchonCrystalUpgradeController } from "../controllers/custom/pushArchonCrystalUpgradeController.ts";
import { popArchonCrystalUpgradeController } from "../controllers/custom/popArchonCrystalUpgradeController.ts";
@@ -53,6 +54,7 @@ import { getConfigController, setConfigController } from "../controllers/custom/
const customRouter = express.Router();
customRouter.get("/tunables.json", tunablesController);
customRouter.get("/equipmentFeatures", equipmentFeaturesController);
customRouter.get("/getItemLists", getItemListsController);
customRouter.get("/pushArchonCrystalUpgrade", pushArchonCrystalUpgradeController);
customRouter.get("/popArchonCrystalUpgrade", popArchonCrystalUpgradeController);

View File

@@ -840,6 +840,10 @@
</form>
</div>
</div>
<div id="equipmentFeatures-card" class="card mb-3 d-none">
<h5 class="card-header" data-loc="detailedView_equipmentFeaturesLabel"></h5>
<div id="equipmentFeaturesButtons-card" class="card-body d-flex flex-wrap gap-2"></div>
</div>
</div>
<div data-route="/webui/mods" data-title="Mods | OpenWF WebUI">
<p class="mb-3 inventory-update-note"></p>

View File

@@ -912,12 +912,7 @@ function updateInventory() {
td.appendChild(a);
}
if (
["Suits", "LongGuns", "Pistols", "Melee", "SpaceGuns", "SpaceMelee"].includes(
category
) ||
modularWeapons.includes(item.ItemType)
) {
{
const a = document.createElement("a");
a.href =
"/webui/detailedView?productCategory=" + category + "&itemId=" + item.ItemId.$oid;
@@ -930,7 +925,7 @@ function updateInventory() {
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
gildEquipment(category, item.ItemId.$oid);
equipmentFeatures(category, item.ItemId.$oid, 8);
};
a.title = loc("code_gild");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M316.9 18C311.6 7 300.4 0 288.1 0s-23.4 7-28.8 18L195 150.3 51.4 171.5c-12 1.8-22 10.2-25.7 21.7s-.7 24.2 7.9 32.7L137.8 329 113.2 474.7c-2 12 3 24.2 12.9 31.3s23 8 33.8 2.3l128.3-68.5 128.3 68.5c10.8 5.7 23.9 4.9 33.8-2.3s14.9-19.3 12.9-31.3L438.5 329 542.7 225.9c8.6-8.5 11.7-21.2 7.9-32.7s-13.7-19.9-25.7-21.7L381.2 150.3 316.9 18z"/></svg>`;
@@ -1560,6 +1555,46 @@ function updateInventory() {
$("#detailedView-title").text(itemName);
}
{
document.getElementById("equipmentFeatures-card").classList.remove("d-none");
const buttonsCard = document.getElementById("equipmentFeaturesButtons-card");
buttonsCard.innerHTML = "";
item.Features ??= 0;
const bits = [1];
if (["Suits", "LongGuns", "Pistols", "Melee"].includes(category)) bits.push(2);
if (modularWeapons.includes(item.ItemType)) bits.push(8);
if (["LongGuns", "Pistols", "Melee", "SpaceGuns", "OperatorAmps"].includes(category))
bits.push(32);
if (category == "SpaceGuns") bits.push(4, 64);
if (
["LongGuns", "Pistols", "Melee", "SpaceGuns", "SpaceMelee"].includes(category) &&
item.UpgradeFingerprint
)
bits.push(1024);
for (const bit of bits.sort((a, b) => a - b)) {
const isRemove = item.Features & bit;
const button = document.createElement("button");
button.classList = isRemove ? "btn btn-danger" : "btn btn-primary";
button.href = "#";
const locale = isRemove ? `code_removeFeature_${bit}` : `code_addFeature_${bit}`;
button.setAttribute("data-loc", locale);
button.innerHTML = loc(locale);
button.onclick = function (event) {
event.preventDefault();
equipmentFeatures(category, oid, bit);
};
if (
(data.unlockDoubleCapacityPotatoesEverywhere && bit === 1) ||
(data.unlockExilusEverywhere && bit === 2) ||
(data.unlockArcanesEverywhere && (bit === 32 || bit === 64))
) {
button.disabled = true;
}
buttonsCard.appendChild(button);
}
}
if (category == "Suits") {
document.getElementById("archonShards-card").classList.remove("d-none");
@@ -2864,15 +2899,11 @@ function disposeOfItems(category, type, count) {
});
}
function gildEquipment(category, oid) {
function equipmentFeatures(category, oid, bit) {
revalidateAuthz().then(() => {
$.post({
url: "/api/gildWeapon.php?" + window.authz + "&ItemId=" + oid + "&Category=" + category,
contentType: "application/octet-stream",
data: JSON.stringify({
Recipe: "webui"
})
}).done(function () {
$.get(
"/custom/equipmentFeatures?" + window.authz + "&ItemId=" + oid + "&Category=" + category + "&bit=" + bit
).done(function () {
updateInventory();
});
});
@@ -3478,6 +3509,8 @@ single.getRoute("#detailedView-route").on("beforeload", function () {
document.getElementById("modularParts-card").classList.add("d-none");
document.getElementById("modularParts-form").innerHTML = "";
document.getElementById("valenceBonus-card").classList.add("d-none");
document.getElementById("equipmentFeatures-card").classList.add("d-none");
document.getElementById("equipmentFeaturesButtons-card").innerHTML = "";
if (window.didInitialInventoryUpdate) {
updateInventory();
}

View File

@@ -81,6 +81,20 @@ dict = {
code_operatorFaceName: `Operator-Gesicht: |INDEX|`,
code_succChange: `Erfolgreich geändert.`,
code_requiredInvigorationUpgrade: `Du musst sowohl ein Offensiv- als auch ein Support-Upgrade auswählen.`,
code_addFeature_1: `[UNTRANSLATED] Install Orokin Reactor`,
code_addFeature_2: `[UNTRANSLATED] Unlock Exilus slot`,
code_addFeature_4: `[UNTRANSLATED] Install Gravimag`,
code_addFeature_8: `[UNTRANSLATED] Gild Weapon`,
code_addFeature_32: `[UNTRANSLATED] Unlock Arcane slot`,
code_addFeature_64: `[UNTRANSLATED] Unlock Second Arcane slot`,
code_addFeature_1024: `[UNTRANSLATED] Unlock Valence Override`,
code_removeFeature_1: `[UNTRANSLATED] Remove Orokin Reactor`,
code_removeFeature_2: `[UNTRANSLATED] Lock Exilus slot`,
code_removeFeature_4: `[UNTRANSLATED] Remove Gravimag`,
code_removeFeature_8: `[UNTRANSLATED] Ungild Weapon`,
code_removeFeature_32: `[UNTRANSLATED] Lock Arcane slot`,
code_removeFeature_64: `[UNTRANSLATED] Lock Second Arcane slot`,
code_removeFeature_1024: `[UNTRANSLATED] Lock Valence Override`,
login_description: `Melde dich mit deinem OpenWF-Account an (denselben Angaben wie im Spiel, wenn du dich mit diesem Server verbindest).`,
login_emailLabel: `E-Mail-Adresse`,
login_passwordLabel: `Passwort`,
@@ -155,6 +169,7 @@ dict = {
detailedView_modularPartsLabel: `Modulare Teile ändern`,
detailedView_invigorationLabel: `Kräftigung`,
detailedView_loadoutLabel: `Loadouts`,
detailedView_equipmentFeaturesLabel: `[UNTRANSLATED] Equipment Features`,
invigorations_offensive_AbilityStrength: `+200% Fähigkeitsstärke`,
invigorations_offensive_AbilityRange: `+100% Fähigkeitsreichweite`,

View File

@@ -80,6 +80,20 @@ dict = {
code_operatorFaceName: `Operator Visage |INDEX|`,
code_succChange: `Successfully changed.`,
code_requiredInvigorationUpgrade: `You must select both an offensive & utility upgrade.`,
code_addFeature_1: `Install Orokin Reactor`,
code_addFeature_2: `Unlock Exilus slot`,
code_addFeature_4: `Install Gravimag`,
code_addFeature_8: `Gild Weapon`,
code_addFeature_32: `Unlock Arcane slot`,
code_addFeature_64: `Unlock Second Arcane slot`,
code_addFeature_1024: `Unlock Valence Override`,
code_removeFeature_1: `Remove Orokin Reactor`,
code_removeFeature_2: `Lock Exilus slot`,
code_removeFeature_4: `Remove Gravimag`,
code_removeFeature_8: `Ungild Weapon`,
code_removeFeature_32: `Lock Arcane slot`,
code_removeFeature_64: `Lock Second Arcane slot`,
code_removeFeature_1024: `Lock Valence Override`,
login_description: `Login using your OpenWF account credentials (same as in-game when connecting to this server).`,
login_emailLabel: `Email address`,
login_passwordLabel: `Password`,
@@ -154,6 +168,7 @@ dict = {
detailedView_modularPartsLabel: `Change Modular Parts`,
detailedView_invigorationLabel: `Invigoration`,
detailedView_loadoutLabel: `Loadouts`,
detailedView_equipmentFeaturesLabel: `Equipment Features`,
invigorations_offensive_AbilityStrength: `+200% Ability Strength`,
invigorations_offensive_AbilityRange: `+100% Ability Range`,

View File

@@ -81,6 +81,20 @@ dict = {
code_operatorFaceName: `Rostro del operador |INDEX|`,
code_succChange: `Cambiado correctamente`,
code_requiredInvigorationUpgrade: `Debes seleccionar una mejora ofensiva y una mejora de utilidad.`,
code_addFeature_1: `[UNTRANSLATED] Install Orokin Reactor`,
code_addFeature_2: `[UNTRANSLATED] Unlock Exilus slot`,
code_addFeature_4: `[UNTRANSLATED] Install Gravimag`,
code_addFeature_8: `[UNTRANSLATED] Gild Weapon`,
code_addFeature_32: `[UNTRANSLATED] Unlock Arcane slot`,
code_addFeature_64: `[UNTRANSLATED] Unlock Second Arcane slot`,
code_addFeature_1024: `[UNTRANSLATED] Unlock Valence Override`,
code_removeFeature_1: `[UNTRANSLATED] Remove Orokin Reactor`,
code_removeFeature_2: `[UNTRANSLATED] Lock Exilus slot`,
code_removeFeature_4: `[UNTRANSLATED] Remove Gravimag`,
code_removeFeature_8: `[UNTRANSLATED] Ungild Weapon`,
code_removeFeature_32: `[UNTRANSLATED] Lock Arcane slot`,
code_removeFeature_64: `[UNTRANSLATED] Lock Second Arcane slot`,
code_removeFeature_1024: `[UNTRANSLATED] Lock Valence Override`,
login_description: `Inicia sesión con las credenciales de tu cuenta OpenWF (las mismas que usas en el juego al conectarte a este servidor).`,
login_emailLabel: `Dirección de correo electrónico`,
login_passwordLabel: `Contraseña`,
@@ -155,6 +169,7 @@ dict = {
detailedView_modularPartsLabel: `Cambiar partes modulares`,
detailedView_invigorationLabel: `Fortalecimiento`,
detailedView_loadoutLabel: `Equipamientos`,
detailedView_equipmentFeaturesLabel: `[UNTRANSLATED] Equipment Features`,
invigorations_offensive_AbilityStrength: `+200% Fuerza de Habilidad`,
invigorations_offensive_AbilityRange: `+100% Alcance de Habilidad`,

View File

@@ -81,6 +81,20 @@ dict = {
code_operatorFaceName: `Visage de l'Opérateur |INDEX|`,
code_succChange: `Changement effectué.`,
code_requiredInvigorationUpgrade: `Invigoration offensive et défensive requises.`,
code_addFeature_1: `[UNTRANSLATED] Install Orokin Reactor`,
code_addFeature_2: `[UNTRANSLATED] Unlock Exilus slot`,
code_addFeature_4: `[UNTRANSLATED] Install Gravimag`,
code_addFeature_8: `[UNTRANSLATED] Gild Weapon`,
code_addFeature_32: `[UNTRANSLATED] Unlock Arcane slot`,
code_addFeature_64: `[UNTRANSLATED] Unlock Second Arcane slot`,
code_addFeature_1024: `[UNTRANSLATED] Unlock Valence Override`,
code_removeFeature_1: `[UNTRANSLATED] Remove Orokin Reactor`,
code_removeFeature_2: `[UNTRANSLATED] Lock Exilus slot`,
code_removeFeature_4: `[UNTRANSLATED] Remove Gravimag`,
code_removeFeature_8: `[UNTRANSLATED] Ungild Weapon`,
code_removeFeature_32: `[UNTRANSLATED] Lock Arcane slot`,
code_removeFeature_64: `[UNTRANSLATED] Lock Second Arcane slot`,
code_removeFeature_1024: `[UNTRANSLATED] Lock Valence Override`,
login_description: `Connexion avec les informations de connexion OpenWF.`,
login_emailLabel: `Email`,
login_passwordLabel: `Mot de passe`,
@@ -155,6 +169,7 @@ dict = {
detailedView_modularPartsLabel: `Changer l'équipement modulaire`,
detailedView_invigorationLabel: `Dynamisation`,
detailedView_loadoutLabel: `Équipements`,
detailedView_equipmentFeaturesLabel: `[UNTRANSLATED] Equipment Features`,
invigorations_offensive_AbilityStrength: `+200% de puissance de pouvoir`,
invigorations_offensive_AbilityRange: `+100% de portée de pouvoir`,

View File

@@ -81,6 +81,20 @@ dict = {
code_operatorFaceName: `Внешность оператора: |INDEX|`,
code_succChange: `Успешно изменено.`,
code_requiredInvigorationUpgrade: `Вы должны выбрать как атакующее, так и вспомогательное улучшение.`,
code_addFeature_1: `Установить Реактор Орокин`,
code_addFeature_2: `Разблокировать Эксилус слот`,
code_addFeature_4: `Установить Гравимаг`,
code_addFeature_8: `Улучшить Оружие`,
code_addFeature_32: `Разблокировать слот Мистификатора`,
code_addFeature_64: `Разблокировать Второй слот Мистификатора`,
code_addFeature_1024: `Разблокировать Переопределение валентности`,
code_removeFeature_1: `Удалить Реактор Орокин`,
code_removeFeature_2: `Заблокировать Эксилус слот`,
code_removeFeature_4: `Удалить Гравимаг`,
code_removeFeature_8: `[UNTRANSLATED] Ungild Weapon`,
code_removeFeature_32: `Заблокировать слот Мистификатора`,
code_removeFeature_64: `Заблокировать Второй слот Мистификатора`,
code_removeFeature_1024: `Заблокировать Переопределение валентности`,
login_description: `Войдите, используя учетные данные OpenWF (те же, что и в игре при подключении к этому серверу).`,
login_emailLabel: `Адрес электронной почты`,
login_passwordLabel: `Пароль`,
@@ -155,6 +169,7 @@ dict = {
detailedView_modularPartsLabel: `Изменить модульные части`,
detailedView_invigorationLabel: `Воодушевление`,
detailedView_loadoutLabel: `Конфигурации`,
detailedView_equipmentFeaturesLabel: `Модификаторы снаряжения`,
invigorations_offensive_AbilityStrength: `+200% к силе способностей.`,
invigorations_offensive_AbilityRange: `+100% к зоне поражения способностей.`,

View File

@@ -81,6 +81,20 @@ dict = {
code_operatorFaceName: `Зовнішність оператора: |INDEX|`,
code_succChange: `Успішно змінено.`,
code_requiredInvigorationUpgrade: `Ви повинні вибрати як атакуюче, так і допоміжне вдосконалення.`,
code_addFeature_1: `[UNTRANSLATED] Install Orokin Reactor`,
code_addFeature_2: `[UNTRANSLATED] Unlock Exilus slot`,
code_addFeature_4: `[UNTRANSLATED] Install Gravimag`,
code_addFeature_8: `[UNTRANSLATED] Gild Weapon`,
code_addFeature_32: `[UNTRANSLATED] Unlock Arcane slot`,
code_addFeature_64: `[UNTRANSLATED] Unlock Second Arcane slot`,
code_addFeature_1024: `[UNTRANSLATED] Unlock Valence Override`,
code_removeFeature_1: `[UNTRANSLATED] Remove Orokin Reactor`,
code_removeFeature_2: `[UNTRANSLATED] Lock Exilus slot`,
code_removeFeature_4: `[UNTRANSLATED] Remove Gravimag`,
code_removeFeature_8: `[UNTRANSLATED] Ungild Weapon`,
code_removeFeature_32: `[UNTRANSLATED] Lock Arcane slot`,
code_removeFeature_64: `[UNTRANSLATED] Lock Second Arcane slot`,
code_removeFeature_1024: `[UNTRANSLATED] Lock Valence Override`,
login_description: `Увійдіть, використовуючи облікові дані OpenWF (ті ж, що й у грі при підключенні до цього серверу).`,
login_emailLabel: `Адреса електронної пошти`,
login_passwordLabel: `Пароль`,
@@ -155,6 +169,7 @@ dict = {
detailedView_modularPartsLabel: `Змінити модульні частини`,
detailedView_invigorationLabel: `Зміцнення`,
detailedView_loadoutLabel: `Конфігурації`,
detailedView_equipmentFeaturesLabel: `[UNTRANSLATED] Equipment Features`,
invigorations_offensive_AbilityStrength: `+200% до потужності здібностей.`,
invigorations_offensive_AbilityRange: `+100% до досяжності здібностей.`,

View File

@@ -81,6 +81,20 @@ dict = {
code_operatorFaceName: `指挥官面部 |INDEX|`,
code_succChange: `更改成功`,
code_requiredInvigorationUpgrade: `[UNTRANSLATED] You must select both an offensive & utility upgrade.`,
code_addFeature_1: `[UNTRANSLATED] Install Orokin Reactor`,
code_addFeature_2: `[UNTRANSLATED] Unlock Exilus slot`,
code_addFeature_4: `[UNTRANSLATED] Install Gravimag`,
code_addFeature_8: `[UNTRANSLATED] Gild Weapon`,
code_addFeature_32: `[UNTRANSLATED] Unlock Arcane slot`,
code_addFeature_64: `[UNTRANSLATED] Unlock Second Arcane slot`,
code_addFeature_1024: `[UNTRANSLATED] Unlock Valence Override`,
code_removeFeature_1: `[UNTRANSLATED] Remove Orokin Reactor`,
code_removeFeature_2: `[UNTRANSLATED] Lock Exilus slot`,
code_removeFeature_4: `[UNTRANSLATED] Remove Gravimag`,
code_removeFeature_8: `[UNTRANSLATED] Ungild Weapon`,
code_removeFeature_32: `[UNTRANSLATED] Lock Arcane slot`,
code_removeFeature_64: `[UNTRANSLATED] Lock Second Arcane slot`,
code_removeFeature_1024: `[UNTRANSLATED] Lock Valence Override`,
login_description: `使用您的 OpenWF 账户凭证登录(与游戏内连接本服务器时使用的昵称相同)`,
login_emailLabel: `电子邮箱`,
login_passwordLabel: `密码`,
@@ -155,6 +169,7 @@ dict = {
detailedView_modularPartsLabel: `更换部件`,
detailedView_invigorationLabel: `活化`,
detailedView_loadoutLabel: `配置`,
detailedView_equipmentFeaturesLabel: `[UNTRANSLATED] Equipment Features`,
invigorations_offensive_AbilityStrength: `+200%技能强度`,
invigorations_offensive_AbilityRange: `+100%技能范围`,