feat(webui): equipment features in detailed view #2987
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
34
src/controllers/custom/equipmentFeaturesController.ts
Normal file
34
src/controllers/custom/equipmentFeaturesController.ts
Normal 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();
|
||||
}
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,57 @@ 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 = [];
|
||||
if (category != "OperatorAmps") bits.push(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 wrapper = document.createElement("div");
|
||||
wrapper.classList = "form-check";
|
||||
|
||||
const input = document.createElement("input");
|
||||
input.classList = "form-check-input";
|
||||
input.type = "checkbox";
|
||||
input.id = `detailedView-feature-${bit}`;
|
||||
input.checked = item.Features & bit;
|
||||
|
||||
const label = document.createElement("label");
|
||||
label.classList = "form-check-label";
|
||||
label.htmlFor = input.id;
|
||||
label.innerHTML = loc(`code_feature_${bit}`);
|
||||
label.setAttribute("data-loc", `code_feature_${bit}`);
|
||||
|
||||
input.onchange = function (event) {
|
||||
event.preventDefault();
|
||||
equipmentFeatures(category, oid, bit);
|
||||
};
|
||||
if (
|
||||
(data.unlockDoubleCapacityPotatoesEverywhere && bit === 1) ||
|
||||
(data.unlockExilusEverywhere && bit === 2) ||
|
||||
(data.unlockArcanesEverywhere && (bit === 32 || bit === 64))
|
||||
) {
|
||||
input.disabled = true;
|
||||
}
|
||||
|
||||
wrapper.appendChild(input);
|
||||
wrapper.appendChild(label);
|
||||
buttonsCard.appendChild(wrapper);
|
||||
}
|
||||
}
|
||||
|
||||
if (category == "Suits") {
|
||||
document.getElementById("archonShards-card").classList.remove("d-none");
|
||||
|
||||
@@ -2864,15 +2910,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 +3520,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();
|
||||
}
|
||||
|
||||
@@ -81,6 +81,13 @@ 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_feature_1: `[UNTRANSLATED] Orokin Reactor`,
|
||||
code_feature_2: `[UNTRANSLATED] Exilus Adapter`,
|
||||
code_feature_4: `[UNTRANSLATED] Gravimag`,
|
||||
code_feature_8: `[UNTRANSLATED] Gild`,
|
||||
code_feature_32: `[UNTRANSLATED] Arcane Slot`,
|
||||
code_feature_64: `[UNTRANSLATED] Second Arcane Slot`,
|
||||
code_feature_1024: `[UNTRANSLATED] 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 +162,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`,
|
||||
|
||||
@@ -80,6 +80,13 @@ dict = {
|
||||
code_operatorFaceName: `Operator Visage |INDEX|`,
|
||||
code_succChange: `Successfully changed.`,
|
||||
code_requiredInvigorationUpgrade: `You must select both an offensive & utility upgrade.`,
|
||||
code_feature_1: `Orokin Reactor`,
|
||||
code_feature_2: `Exilus Adapter`,
|
||||
code_feature_4: `Gravimag`,
|
||||
code_feature_8: `Gild`,
|
||||
code_feature_32: `Arcane Slot`,
|
||||
code_feature_64: `Second Arcane Slot`,
|
||||
code_feature_1024: `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 +161,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`,
|
||||
|
||||
@@ -81,6 +81,13 @@ dict = {
|
||||
code_operatorFaceName: `Rostro del operador |INDEX|`,
|
||||
code_succChange: `Cambiado correctamente`,
|
||||
code_requiredInvigorationUpgrade: `Debes seleccionar una mejora ofensiva y una mejora de utilidad.`,
|
||||
code_feature_1: `[UNTRANSLATED] Orokin Reactor`,
|
||||
code_feature_2: `[UNTRANSLATED] Exilus Adapter`,
|
||||
code_feature_4: `[UNTRANSLATED] Gravimag`,
|
||||
code_feature_8: `[UNTRANSLATED] Gild`,
|
||||
code_feature_32: `[UNTRANSLATED] Arcane Slot`,
|
||||
code_feature_64: `[UNTRANSLATED] Second Arcane Slot`,
|
||||
code_feature_1024: `[UNTRANSLATED] 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 +162,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`,
|
||||
|
||||
@@ -81,6 +81,13 @@ dict = {
|
||||
code_operatorFaceName: `Visage de l'Opérateur |INDEX|`,
|
||||
code_succChange: `Changement effectué.`,
|
||||
code_requiredInvigorationUpgrade: `Invigoration offensive et défensive requises.`,
|
||||
code_feature_1: `[UNTRANSLATED] Orokin Reactor`,
|
||||
code_feature_2: `[UNTRANSLATED] Exilus Adapter`,
|
||||
code_feature_4: `[UNTRANSLATED] Gravimag`,
|
||||
code_feature_8: `[UNTRANSLATED] Gild`,
|
||||
code_feature_32: `[UNTRANSLATED] Arcane Slot`,
|
||||
code_feature_64: `[UNTRANSLATED] Second Arcane Slot`,
|
||||
code_feature_1024: `[UNTRANSLATED] Valence Override`,
|
||||
login_description: `Connexion avec les informations de connexion OpenWF.`,
|
||||
login_emailLabel: `Email`,
|
||||
login_passwordLabel: `Mot de passe`,
|
||||
@@ -155,6 +162,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`,
|
||||
|
||||
@@ -81,6 +81,13 @@ dict = {
|
||||
code_operatorFaceName: `Внешность оператора: |INDEX|`,
|
||||
code_succChange: `Успешно изменено.`,
|
||||
code_requiredInvigorationUpgrade: `Вы должны выбрать как атакующее, так и вспомогательное улучшение.`,
|
||||
code_feature_1: `Реактор Орокин`,
|
||||
code_feature_2: `Адаптер Эксилус`,
|
||||
code_feature_4: `Гравимаг`,
|
||||
code_feature_8: `Улучшение`,
|
||||
code_feature_32: `Слот Мистификатора`,
|
||||
code_feature_64: `Второй слот Мистификатора`,
|
||||
code_feature_1024: `Переопределение валентности`,
|
||||
login_description: `Войдите, используя учетные данные OpenWF (те же, что и в игре при подключении к этому серверу).`,
|
||||
login_emailLabel: `Адрес электронной почты`,
|
||||
login_passwordLabel: `Пароль`,
|
||||
@@ -155,6 +162,7 @@ dict = {
|
||||
detailedView_modularPartsLabel: `Изменить модульные части`,
|
||||
detailedView_invigorationLabel: `Воодушевление`,
|
||||
detailedView_loadoutLabel: `Конфигурации`,
|
||||
detailedView_equipmentFeaturesLabel: `Модификаторы снаряжения`,
|
||||
|
||||
invigorations_offensive_AbilityStrength: `+200% к силе способностей.`,
|
||||
invigorations_offensive_AbilityRange: `+100% к зоне поражения способностей.`,
|
||||
|
||||
@@ -81,6 +81,13 @@ dict = {
|
||||
code_operatorFaceName: `Зовнішність оператора: |INDEX|`,
|
||||
code_succChange: `Успішно змінено.`,
|
||||
code_requiredInvigorationUpgrade: `Ви повинні вибрати як атакуюче, так і допоміжне вдосконалення.`,
|
||||
code_feature_1: `[UNTRANSLATED] Orokin Reactor`,
|
||||
code_feature_2: `[UNTRANSLATED] Exilus Adapter`,
|
||||
code_feature_4: `[UNTRANSLATED] Gravimag`,
|
||||
code_feature_8: `[UNTRANSLATED] Gild`,
|
||||
code_feature_32: `[UNTRANSLATED] Arcane Slot`,
|
||||
code_feature_64: `[UNTRANSLATED] Second Arcane Slot`,
|
||||
code_feature_1024: `[UNTRANSLATED] Valence Override`,
|
||||
login_description: `Увійдіть, використовуючи облікові дані OpenWF (ті ж, що й у грі при підключенні до цього серверу).`,
|
||||
login_emailLabel: `Адреса електронної пошти`,
|
||||
login_passwordLabel: `Пароль`,
|
||||
@@ -155,6 +162,7 @@ dict = {
|
||||
detailedView_modularPartsLabel: `Змінити модульні частини`,
|
||||
detailedView_invigorationLabel: `Зміцнення`,
|
||||
detailedView_loadoutLabel: `Конфігурації`,
|
||||
detailedView_equipmentFeaturesLabel: `[UNTRANSLATED] Equipment Features`,
|
||||
|
||||
invigorations_offensive_AbilityStrength: `+200% до потужності здібностей.`,
|
||||
invigorations_offensive_AbilityRange: `+100% до досяжності здібностей.`,
|
||||
|
||||
@@ -81,6 +81,13 @@ dict = {
|
||||
code_operatorFaceName: `指挥官面部 |INDEX|`,
|
||||
code_succChange: `更改成功`,
|
||||
code_requiredInvigorationUpgrade: `[UNTRANSLATED] You must select both an offensive & utility upgrade.`,
|
||||
code_feature_1: `[UNTRANSLATED] Orokin Reactor`,
|
||||
code_feature_2: `[UNTRANSLATED] Exilus Adapter`,
|
||||
code_feature_4: `[UNTRANSLATED] Gravimag`,
|
||||
code_feature_8: `[UNTRANSLATED] Gild`,
|
||||
code_feature_32: `[UNTRANSLATED] Arcane Slot`,
|
||||
code_feature_64: `[UNTRANSLATED] Second Arcane Slot`,
|
||||
code_feature_1024: `[UNTRANSLATED] Valence Override`,
|
||||
login_description: `使用您的 OpenWF 账户凭证登录(与游戏内连接本服务器时使用的昵称相同)`,
|
||||
login_emailLabel: `电子邮箱`,
|
||||
login_passwordLabel: `密码`,
|
||||
@@ -155,6 +162,7 @@ dict = {
|
||||
detailedView_modularPartsLabel: `更换部件`,
|
||||
detailedView_invigorationLabel: `活化`,
|
||||
detailedView_loadoutLabel: `配置`,
|
||||
detailedView_equipmentFeaturesLabel: `[UNTRANSLATED] Equipment Features`,
|
||||
|
||||
invigorations_offensive_AbilityStrength: `+200%技能强度`,
|
||||
invigorations_offensive_AbilityRange: `+100%技能范围`,
|
||||
|
||||
Reference in New Issue
Block a user