返回提交历史
Modified
src/controllers/api/gildWeaponController.ts
+16
-19
Modified
src/controllers/api/inventoryController.ts
+1
-0
Added
src/controllers/custom/equipmentFeaturesController.ts
+34
-0
Modified
src/routes/custom.ts
+2
-0
Modified
static/webui/index.html
+4
-0
Modified
static/webui/script.js
+59
-15
Modified
static/webui/translations/de.js
+8
-0
Modified
static/webui/translations/en.js
+8
-0
Modified
static/webui/translations/es.js
+8
-0
Modified
static/webui/translations/fr.js
+8
-0
Modified
static/webui/translations/ru.js
+8
-0
Modified
static/webui/translations/uk.js
+8
-0
Modified
static/webui/translations/zh.js
+8
-0
XFEstudio/XFESpaceNinjaServer
feat(webui): equipment features in detailed view (#2987)
Closes #2927 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2987 Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com> Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
98620169
代码差异
13 个文件
+172
-34
@@ -34,10 +34,9 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
34
34
const weapon = inventory[data.Category][weaponIndex];
35
35
weapon.Features ??= 0;
36
36
weapon.Features |= EquipmentFeatures.GILDED;
37
if (data.Recipe != "webui") {
38
weapon.ItemName = data.ItemName;
39
weapon.XP = 0;
40
}
37
weapon.ItemName = data.ItemName;
38
weapon.XP = 0;
39
41
40
if (data.Category != "OperatorAmps" && data.PolarizeSlot && data.PolarizeValue) {
42
41
weapon.Polarity = [
43
42
{
@@ -52,22 +51,20 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
52
51
53
52
const affiliationMods = [];
54
53
55
if (data.Recipe != "webui") {
56
const recipe = ExportRecipes[data.Recipe];
57
inventoryChanges.MiscItems = recipe.secretIngredients!.map(ingredient => ({
58
ItemType: ingredient.ItemType,
59
ItemCount: ingredient.ItemCount * -1
60
}));
61
addMiscItems(inventory, inventoryChanges.MiscItems);
54
const recipe = ExportRecipes[data.Recipe];
55
inventoryChanges.MiscItems = recipe.secretIngredients!.map(ingredient => ({
56
ItemType: ingredient.ItemType,
57
ItemCount: ingredient.ItemCount * -1
58
}));
59
addMiscItems(inventory, inventoryChanges.MiscItems);
62
60
63
if (recipe.syndicateStandingChange) {
64
const affiliation = inventory.Affiliations.find(x => x.Tag == recipe.syndicateStandingChange!.tag)!;
65
affiliation.Standing += recipe.syndicateStandingChange.value;
66
affiliationMods.push({
67
Tag: recipe.syndicateStandingChange.tag,
68
Standing: recipe.syndicateStandingChange.value
69
});
70
}
61
if (recipe.syndicateStandingChange) {
62
const affiliation = inventory.Affiliations.find(x => x.Tag == recipe.syndicateStandingChange!.tag)!;
63
affiliation.Standing += recipe.syndicateStandingChange.value;
64
affiliationMods.push({
65
Tag: recipe.syndicateStandingChange.tag,
66
Standing: recipe.syndicateStandingChange.value
67
});
71
68
}
72
69
73
70
await inventory.save();
@@ -410,6 +410,7 @@ export const getInventoryResponse = async (
410
410
for (const equipment of inventoryResponse[key]) {
411
411
equipment.Features ??= 0;
412
412
equipment.Features |= EquipmentFeatures.ARCANE_SLOT;
413
equipment.Features |= EquipmentFeatures.SECOND_ARCANE_SLOT;
413
414
}
414
415
}
415
416
}
@@ -0,0 +1,34 @@
1
import type { RequestHandler } from "express";
2
import { getAccountIdForRequest } from "../../services/loginService.ts";
3
import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts";
4
import { getInventory } from "../../services/inventoryService.ts";
5
import { EquipmentFeatures } from "../../types/equipmentTypes.ts";
6
import { sendWsBroadcastTo } from "../../services/wsService.ts";
7
8
export const equipmentFeaturesController: RequestHandler = async (req, res) => {
9
const accountId = await getAccountIdForRequest(req);
10
const category = req.query.Category as TEquipmentKey;
11
const inventory = await getInventory(
12
accountId,
13
`${category} unlockDoubleCapacityPotatoesEverywhere unlockExilusEverywhere unlockArcanesEverywhere`
14
);
15
const bit = Number(req.query.bit) as EquipmentFeatures;
16
if (
17
(inventory.unlockDoubleCapacityPotatoesEverywhere && bit === EquipmentFeatures.DOUBLE_CAPACITY) ||
18
(inventory.unlockExilusEverywhere && bit === EquipmentFeatures.UTILITY_SLOT) ||
19
(inventory.unlockArcanesEverywhere &&
20
(bit === EquipmentFeatures.ARCANE_SLOT || bit === EquipmentFeatures.SECOND_ARCANE_SLOT))
21
) {
22
res.status(400).end();
23
}
24
const item = inventory[category].id(req.query.ItemId as string);
25
if (item) {
26
item.Features ??= 0;
27
item.Features ^= bit;
28
await inventory.save();
29
sendWsBroadcastTo(accountId, { sync_inventory: true });
30
res.status(200).end();
31
} else {
32
res.status(400).end();
33
}
34
};
@@ -1,6 +1,7 @@
1
1
import express from "express";
2
2
3
3
import { tunablesController } from "../controllers/custom/tunablesController.ts";
4
import { equipmentFeaturesController } from "../controllers/custom/equipmentFeaturesController.ts";
4
5
import { getItemListsController } from "../controllers/custom/getItemListsController.ts";
5
6
import { pushArchonCrystalUpgradeController } from "../controllers/custom/pushArchonCrystalUpgradeController.ts";
6
7
import { popArchonCrystalUpgradeController } from "../controllers/custom/popArchonCrystalUpgradeController.ts";
@@ -53,6 +54,7 @@ import { getConfigController, setConfigController } from "../controllers/custom/
53
54
const customRouter = express.Router();
54
55
55
56
customRouter.get("/tunables.json", tunablesController);
57
customRouter.get("/equipmentFeatures", equipmentFeaturesController);
56
58
customRouter.get("/getItemLists", getItemListsController);
57
59
customRouter.get("/pushArchonCrystalUpgrade", pushArchonCrystalUpgradeController);
58
60
customRouter.get("/popArchonCrystalUpgrade", popArchonCrystalUpgradeController);
@@ -840,6 +840,10 @@
840
840
</form>
841
841
</div>
842
842
</div>
843
<div id="equipmentFeatures-card" class="card mb-3 d-none">
844
<h5 class="card-header" data-loc="detailedView_equipmentFeaturesLabel"></h5>
845
<div id="equipmentFeaturesButtons-card" class="card-body d-flex flex-wrap gap-2"></div>
846
</div>
843
847
</div>
844
848
<div data-route="/webui/mods" data-title="Mods | OpenWF WebUI">
845
849
<p class="mb-3 inventory-update-note"></p>
@@ -912,12 +912,7 @@ function updateInventory() {
912
912
td.appendChild(a);
913
913
}
914
914
915
if (
916
["Suits", "LongGuns", "Pistols", "Melee", "SpaceGuns", "SpaceMelee"].includes(
917
category
918
) ||
919
modularWeapons.includes(item.ItemType)
920
) {
915
{
921
916
const a = document.createElement("a");
922
917
a.href =
923
918
"/webui/detailedView?productCategory=" + category + "&itemId=" + item.ItemId.$oid;
@@ -930,7 +925,7 @@ function updateInventory() {
930
925
a.href = "#";
931
926
a.onclick = function (event) {
932
927
event.preventDefault();
933
gildEquipment(category, item.ItemId.$oid);
928
equipmentFeatures(category, item.ItemId.$oid, 8);
934
929
};
935
930
a.title = loc("code_gild");
936
931
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() {
1560
1555
$("#detailedView-title").text(itemName);
1561
1556
}
1562
1557
1558
{
1559
document.getElementById("equipmentFeatures-card").classList.remove("d-none");
1560
const buttonsCard = document.getElementById("equipmentFeaturesButtons-card");
1561
buttonsCard.innerHTML = "";
1562
item.Features ??= 0;
1563
const bits = [];
1564
if (category != "OperatorAmps") bits.push(1);
1565
if (["Suits", "LongGuns", "Pistols", "Melee"].includes(category)) bits.push(2);
1566
if (modularWeapons.includes(item.ItemType)) bits.push(8);
1567
if (["LongGuns", "Pistols", "Melee", "SpaceGuns", "OperatorAmps"].includes(category))
1568
bits.push(32);
1569
if (category == "SpaceGuns") bits.push(4, 64);
1570
if (
1571
["LongGuns", "Pistols", "Melee", "SpaceGuns", "SpaceMelee"].includes(category) &&
1572
item.UpgradeFingerprint
1573
)
1574
bits.push(1024);
1575
for (const bit of bits.sort((a, b) => a - b)) {
1576
const wrapper = document.createElement("div");
1577
wrapper.classList = "form-check";
1578
1579
const input = document.createElement("input");
1580
input.classList = "form-check-input";
1581
input.type = "checkbox";
1582
input.id = `detailedView-feature-${bit}`;
1583
input.checked = item.Features & bit;
1584
1585
const label = document.createElement("label");
1586
label.classList = "form-check-label";
1587
label.htmlFor = input.id;
1588
label.innerHTML = loc(`code_feature_${bit}`);
1589
label.setAttribute("data-loc", `code_feature_${bit}`);
1590
1591
input.onchange = function (event) {
1592
event.preventDefault();
1593
equipmentFeatures(category, oid, bit);
1594
};
1595
if (
1596
(data.unlockDoubleCapacityPotatoesEverywhere && bit === 1) ||
1597
(data.unlockExilusEverywhere && bit === 2) ||
1598
(data.unlockArcanesEverywhere && (bit === 32 || bit === 64))
1599
) {
1600
input.disabled = true;
1601
}
1602
1603
wrapper.appendChild(input);
1604
wrapper.appendChild(label);
1605
buttonsCard.appendChild(wrapper);
1606
}
1607
}
1608
1563
1609
if (category == "Suits") {
1564
1610
document.getElementById("archonShards-card").classList.remove("d-none");
1565
1611
@@ -2864,15 +2910,11 @@ function disposeOfItems(category, type, count) {
2864
2910
});
2865
2911
}
2866
2912
2867
function gildEquipment(category, oid) {
2913
function equipmentFeatures(category, oid, bit) {
2868
2914
revalidateAuthz().then(() => {
2869
$.post({
2870
url: "/api/gildWeapon.php?" + window.authz + "&ItemId=" + oid + "&Category=" + category,
2871
contentType: "application/octet-stream",
2872
data: JSON.stringify({
2873
Recipe: "webui"
2874
})
2875
}).done(function () {
2915
$.get(
2916
"/custom/equipmentFeatures?" + window.authz + "&ItemId=" + oid + "&Category=" + category + "&bit=" + bit
2917
).done(function () {
2876
2918
updateInventory();
2877
2919
});
2878
2920
});
@@ -3478,6 +3520,8 @@ single.getRoute("#detailedView-route").on("beforeload", function () {
3478
3520
document.getElementById("modularParts-card").classList.add("d-none");
3479
3521
document.getElementById("modularParts-form").innerHTML = "";
3480
3522
document.getElementById("valenceBonus-card").classList.add("d-none");
3523
document.getElementById("equipmentFeatures-card").classList.add("d-none");
3524
document.getElementById("equipmentFeaturesButtons-card").innerHTML = "";
3481
3525
if (window.didInitialInventoryUpdate) {
3482
3526
updateInventory();
3483
3527
}
@@ -81,6 +81,13 @@ dict = {
81
81
code_operatorFaceName: `Operator-Gesicht: |INDEX|`,
82
82
code_succChange: `Erfolgreich geändert.`,
83
83
code_requiredInvigorationUpgrade: `Du musst sowohl ein Offensiv- als auch ein Support-Upgrade auswählen.`,
84
code_feature_1: `[UNTRANSLATED] Orokin Reactor`,
85
code_feature_2: `[UNTRANSLATED] Exilus Adapter`,
86
code_feature_4: `[UNTRANSLATED] Gravimag`,
87
code_feature_8: `[UNTRANSLATED] Gild`,
88
code_feature_32: `[UNTRANSLATED] Arcane Slot`,
89
code_feature_64: `[UNTRANSLATED] Second Arcane Slot`,
90
code_feature_1024: `[UNTRANSLATED] Valence Override`,
84
91
login_description: `Melde dich mit deinem OpenWF-Account an (denselben Angaben wie im Spiel, wenn du dich mit diesem Server verbindest).`,
85
92
login_emailLabel: `E-Mail-Adresse`,
86
93
login_passwordLabel: `Passwort`,
@@ -155,6 +162,7 @@ dict = {
155
162
detailedView_modularPartsLabel: `Modulare Teile ändern`,
156
163
detailedView_invigorationLabel: `Kräftigung`,
157
164
detailedView_loadoutLabel: `Loadouts`,
165
detailedView_equipmentFeaturesLabel: `[UNTRANSLATED] Equipment Features`,
158
166
159
167
invigorations_offensive_AbilityStrength: `+200% Fähigkeitsstärke`,
160
168
invigorations_offensive_AbilityRange: `+100% Fähigkeitsreichweite`,
@@ -80,6 +80,13 @@ dict = {
80
80
code_operatorFaceName: `Operator Visage |INDEX|`,
81
81
code_succChange: `Successfully changed.`,
82
82
code_requiredInvigorationUpgrade: `You must select both an offensive & utility upgrade.`,
83
code_feature_1: `Orokin Reactor`,
84
code_feature_2: `Exilus Adapter`,
85
code_feature_4: `Gravimag`,
86
code_feature_8: `Gild`,
87
code_feature_32: `Arcane Slot`,
88
code_feature_64: `Second Arcane Slot`,
89
code_feature_1024: `Valence Override`,
83
90
login_description: `Login using your OpenWF account credentials (same as in-game when connecting to this server).`,
84
91
login_emailLabel: `Email address`,
85
92
login_passwordLabel: `Password`,
@@ -154,6 +161,7 @@ dict = {
154
161
detailedView_modularPartsLabel: `Change Modular Parts`,
155
162
detailedView_invigorationLabel: `Invigoration`,
156
163
detailedView_loadoutLabel: `Loadouts`,
164
detailedView_equipmentFeaturesLabel: `Equipment Features`,
157
165
158
166
invigorations_offensive_AbilityStrength: `+200% Ability Strength`,
159
167
invigorations_offensive_AbilityRange: `+100% Ability Range`,
@@ -81,6 +81,13 @@ dict = {
81
81
code_operatorFaceName: `Rostro del operador |INDEX|`,
82
82
code_succChange: `Cambiado correctamente`,
83
83
code_requiredInvigorationUpgrade: `Debes seleccionar una mejora ofensiva y una mejora de utilidad.`,
84
code_feature_1: `[UNTRANSLATED] Orokin Reactor`,
85
code_feature_2: `[UNTRANSLATED] Exilus Adapter`,
86
code_feature_4: `[UNTRANSLATED] Gravimag`,
87
code_feature_8: `[UNTRANSLATED] Gild`,
88
code_feature_32: `[UNTRANSLATED] Arcane Slot`,
89
code_feature_64: `[UNTRANSLATED] Second Arcane Slot`,
90
code_feature_1024: `[UNTRANSLATED] Valence Override`,
84
91
login_description: `Inicia sesión con las credenciales de tu cuenta OpenWF (las mismas que usas en el juego al conectarte a este servidor).`,
85
92
login_emailLabel: `Dirección de correo electrónico`,
86
93
login_passwordLabel: `Contraseña`,
@@ -155,6 +162,7 @@ dict = {
155
162
detailedView_modularPartsLabel: `Cambiar partes modulares`,
156
163
detailedView_invigorationLabel: `Fortalecimiento`,
157
164
detailedView_loadoutLabel: `Equipamientos`,
165
detailedView_equipmentFeaturesLabel: `[UNTRANSLATED] Equipment Features`,
158
166
159
167
invigorations_offensive_AbilityStrength: `+200% Fuerza de Habilidad`,
160
168
invigorations_offensive_AbilityRange: `+100% Alcance de Habilidad`,
@@ -81,6 +81,13 @@ dict = {
81
81
code_operatorFaceName: `Visage de l'Opérateur |INDEX|`,
82
82
code_succChange: `Changement effectué.`,
83
83
code_requiredInvigorationUpgrade: `Invigoration offensive et défensive requises.`,
84
code_feature_1: `[UNTRANSLATED] Orokin Reactor`,
85
code_feature_2: `[UNTRANSLATED] Exilus Adapter`,
86
code_feature_4: `[UNTRANSLATED] Gravimag`,
87
code_feature_8: `[UNTRANSLATED] Gild`,
88
code_feature_32: `[UNTRANSLATED] Arcane Slot`,
89
code_feature_64: `[UNTRANSLATED] Second Arcane Slot`,
90
code_feature_1024: `[UNTRANSLATED] Valence Override`,
84
91
login_description: `Connexion avec les informations de connexion OpenWF.`,
85
92
login_emailLabel: `Email`,
86
93
login_passwordLabel: `Mot de passe`,
@@ -155,6 +162,7 @@ dict = {
155
162
detailedView_modularPartsLabel: `Changer l'équipement modulaire`,
156
163
detailedView_invigorationLabel: `Dynamisation`,
157
164
detailedView_loadoutLabel: `Équipements`,
165
detailedView_equipmentFeaturesLabel: `[UNTRANSLATED] Equipment Features`,
158
166
159
167
invigorations_offensive_AbilityStrength: `+200% de puissance de pouvoir`,
160
168
invigorations_offensive_AbilityRange: `+100% de portée de pouvoir`,
@@ -81,6 +81,13 @@ dict = {
81
81
code_operatorFaceName: `Внешность оператора: |INDEX|`,
82
82
code_succChange: `Успешно изменено.`,
83
83
code_requiredInvigorationUpgrade: `Вы должны выбрать как атакующее, так и вспомогательное улучшение.`,
84
code_feature_1: `Реактор Орокин`,
85
code_feature_2: `Адаптер Эксилус`,
86
code_feature_4: `Гравимаг`,
87
code_feature_8: `Улучшение`,
88
code_feature_32: `Слот Мистификатора`,
89
code_feature_64: `Второй слот Мистификатора`,
90
code_feature_1024: `Переопределение валентности`,
84
91
login_description: `Войдите, используя учетные данные OpenWF (те же, что и в игре при подключении к этому серверу).`,
85
92
login_emailLabel: `Адрес электронной почты`,
86
93
login_passwordLabel: `Пароль`,
@@ -155,6 +162,7 @@ dict = {
155
162
detailedView_modularPartsLabel: `Изменить модульные части`,
156
163
detailedView_invigorationLabel: `Воодушевление`,
157
164
detailedView_loadoutLabel: `Конфигурации`,
165
detailedView_equipmentFeaturesLabel: `Модификаторы снаряжения`,
158
166
159
167
invigorations_offensive_AbilityStrength: `+200% к силе способностей.`,
160
168
invigorations_offensive_AbilityRange: `+100% к зоне поражения способностей.`,
@@ -81,6 +81,13 @@ dict = {
81
81
code_operatorFaceName: `Зовнішність оператора: |INDEX|`,
82
82
code_succChange: `Успішно змінено.`,
83
83
code_requiredInvigorationUpgrade: `Ви повинні вибрати як атакуюче, так і допоміжне вдосконалення.`,
84
code_feature_1: `[UNTRANSLATED] Orokin Reactor`,
85
code_feature_2: `[UNTRANSLATED] Exilus Adapter`,
86
code_feature_4: `[UNTRANSLATED] Gravimag`,
87
code_feature_8: `[UNTRANSLATED] Gild`,
88
code_feature_32: `[UNTRANSLATED] Arcane Slot`,
89
code_feature_64: `[UNTRANSLATED] Second Arcane Slot`,
90
code_feature_1024: `[UNTRANSLATED] Valence Override`,
84
91
login_description: `Увійдіть, використовуючи облікові дані OpenWF (ті ж, що й у грі при підключенні до цього серверу).`,
85
92
login_emailLabel: `Адреса електронної пошти`,
86
93
login_passwordLabel: `Пароль`,
@@ -155,6 +162,7 @@ dict = {
155
162
detailedView_modularPartsLabel: `Змінити модульні частини`,
156
163
detailedView_invigorationLabel: `Зміцнення`,
157
164
detailedView_loadoutLabel: `Конфігурації`,
165
detailedView_equipmentFeaturesLabel: `[UNTRANSLATED] Equipment Features`,
158
166
159
167
invigorations_offensive_AbilityStrength: `+200% до потужності здібностей.`,
160
168
invigorations_offensive_AbilityRange: `+100% до досяжності здібностей.`,