From a16e2716f1107ed138cc77cbc143586a146974df Mon Sep 17 00:00:00 2001 From: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> Date: Fri, 11 Jul 2025 21:15:16 -0700 Subject: [PATCH] feat(webui): Change weapon Modular Parts (#2471) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2471 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> --- .../custom/changeModularPartsController.ts | 65 ++++++++ src/routes/custom.ts | 2 + static/webui/index.html | 6 + static/webui/script.js | 149 ++++++++++++------ static/webui/translations/de.js | 2 + static/webui/translations/en.js | 2 + static/webui/translations/es.js | 2 + static/webui/translations/fr.js | 2 + static/webui/translations/ru.js | 2 + static/webui/translations/zh.js | 2 + 10 files changed, 184 insertions(+), 50 deletions(-) create mode 100644 src/controllers/custom/changeModularPartsController.ts diff --git a/src/controllers/custom/changeModularPartsController.ts b/src/controllers/custom/changeModularPartsController.ts new file mode 100644 index 00000000..ee1fd8f3 --- /dev/null +++ b/src/controllers/custom/changeModularPartsController.ts @@ -0,0 +1,65 @@ +import { getInventory } from "@/src/services/inventoryService"; +import { getAccountIdForRequest } from "@/src/services/loginService"; +import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes"; +import { RequestHandler } from "express"; + +export const changeModularPartsController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const request = req.body as IUpdateFingerPrintRequest; + const inventory = await getInventory(accountId, request.category); + const item = inventory[request.category].id(request.oid); + if (item) { + item.ModularParts = request.modularParts; + + request.modularParts.forEach(part => { + const categoryMap = mapping[part]; + if (categoryMap && categoryMap[request.category]) { + item.ItemType = categoryMap[request.category]!; + } + }); + await inventory.save(); + } + res.end(); +}; + +interface IUpdateFingerPrintRequest { + category: TEquipmentKey; + oid: string; + modularParts: string[]; +} + +const mapping: Partial>>> = { + "/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelAPart": { + LongGuns: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun", + Pistols: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun" + }, + "/Lotus/Weapons/Infested/Pistols/InfKitGun/Barrels/InfBarrelEgg/InfModularBarrelEggPart": { + LongGuns: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun", + Pistols: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun" + }, + "/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelBPart": { + LongGuns: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary", + Pistols: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary" + }, + "/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelCPart": { + LongGuns: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary", + Pistols: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary" + }, + "/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelDPart": { + LongGuns: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam", + Pistols: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam" + }, + "/Lotus/Weapons/Infested/Pistols/InfKitGun/Barrels/InfBarrelBeam/InfModularBarrelBeamPart": { + LongGuns: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam", + Pistols: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam" + }, + "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA": { + MoaPets: "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetAPowerSuit" + }, + "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadB": { + MoaPets: "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetBPowerSuit" + }, + "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC": { + MoaPets: "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetCPowerSuit" + } +}; diff --git a/src/routes/custom.ts b/src/routes/custom.ts index 99536459..bc0427f4 100644 --- a/src/routes/custom.ts +++ b/src/routes/custom.ts @@ -25,6 +25,7 @@ import { manageQuestsController } from "@/src/controllers/custom/manageQuestsCon import { setEvolutionProgressController } from "@/src/controllers/custom/setEvolutionProgressController"; import { setBoosterController } from "@/src/controllers/custom/setBoosterController"; import { updateFingerprintController } from "@/src/controllers/custom/updateFingerprintController"; +import { changeModularPartsController } from "@/src/controllers/custom/changeModularPartsController"; import { getConfigController, setConfigController } from "@/src/controllers/custom/configController"; @@ -55,6 +56,7 @@ customRouter.post("/manageQuests", manageQuestsController); customRouter.post("/setEvolutionProgress", setEvolutionProgressController); customRouter.post("/setBooster", setBoosterController); customRouter.post("/updateFingerprint", updateFingerprintController); +customRouter.post("/changeModularParts", changeModularPartsController); customRouter.post("/getConfig", getConfigController); customRouter.post("/setConfig", setConfigController); diff --git a/static/webui/index.html b/static/webui/index.html index 55676cd8..c6f6baa3 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -478,6 +478,12 @@ +
+
+
+
+
+
diff --git a/static/webui/script.js b/static/webui/script.js index 423d322f..8b041836 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -730,7 +730,10 @@ function updateInventory() { td.appendChild(a); } - if (["Suits", "LongGuns", "Pistols", "Melee", "SpaceGuns", "SpaceMelee"].includes(category)) { + 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; a.innerHTML = ``; @@ -1239,6 +1242,35 @@ function updateInventory() { document.getElementById("valenceBonus-procent").value = Math.round(buffValue * 1000) / 10; } } + if (modularWeapons.includes(item.ItemType)) { + document.getElementById("modularParts-card").classList.remove("d-none"); + const form = document.getElementById("modularParts-form"); + form.innerHTML = ""; + const requiredParts = getRequiredParts(category, item.ItemType); + + requiredParts.forEach(modularPart => { + const input = document.createElement("input"); + input.classList.add("form-control"); + input.id = "detailedView-modularPart-" + modularPart; + input.setAttribute("list", "datalist-ModularParts-" + modularPart); + + const datalist = document.getElementById("datalist-ModularParts-" + modularPart); + const options = Array.from(datalist.options); + + input.value = + options.find(option => item.ModularParts.includes(option.getAttribute("data-key"))) + ?.value || ""; + form.appendChild(input); + }); + + const changeButton = document.createElement("button"); + changeButton.classList.add("btn"); + changeButton.classList.add("btn-primary"); + changeButton.type = "submit"; + changeButton.setAttribute("data-loc", "cheats_changeButton"); + changeButton.innerHTML = loc("cheats_changeButton"); + form.appendChild(changeButton); + } } else { single.loadRoute("/webui/inventory"); } @@ -1338,47 +1370,41 @@ function doAcquireEquipment(category) { }); } -function doAcquireModularEquipment(category, WeaponType) { - let requiredParts; - let Parts = []; +function getRequiredParts(category, WeaponType) { switch (category) { - case "HoverBoards": - WeaponType = "/Lotus/Types/Vehicles/Hoverboard/HoverboardSuit"; - requiredParts = ["HB_DECK", "HB_ENGINE", "HB_FRONT", "HB_JET"]; - break; + case "Hoverboards": + return ["HB_DECK", "HB_ENGINE", "HB_FRONT", "HB_JET"]; + case "OperatorAmps": - requiredParts = ["AMP_OCULUS", "AMP_CORE", "AMP_BRACE"]; - break; + return ["AMP_OCULUS", "AMP_CORE", "AMP_BRACE"]; + case "Melee": - requiredParts = ["BLADE", "HILT", "HILT_WEIGHT"]; - break; + return ["BLADE", "HILT", "HILT_WEIGHT"]; + case "LongGuns": - requiredParts = ["GUN_BARREL", "GUN_PRIMARY_HANDLE", "GUN_CLIP"]; - break; + return ["GUN_BARREL", "GUN_PRIMARY_HANDLE", "GUN_CLIP"]; + case "Pistols": - requiredParts = ["GUN_BARREL", "GUN_SECONDARY_HANDLE", "GUN_CLIP"]; - break; + return ["GUN_BARREL", "GUN_SECONDARY_HANDLE", "GUN_CLIP"]; + case "MoaPets": - if (WeaponType == "/Lotus/Types/Friendly/Pets/MoaPets/MoaPetPowerSuit") { - requiredParts = ["MOA_ENGINE", "MOA_PAYLOAD", "MOA_HEAD", "MOA_LEG"]; - } else { - requiredParts = ["ZANUKA_BODY", "ZANUKA_HEAD", "ZANUKA_LEG", "ZANUKA_TAIL"]; - } - break; - case "KubrowPets": - if ( - [ - "/Lotus/Types/Friendly/Pets/CreaturePets/VulpineInfestedCatbrowPetPowerSuit", - "/Lotus/Types/Friendly/Pets/CreaturePets/HornedInfestedCatbrowPetPowerSuit", - "/Lotus/Types/Friendly/Pets/CreaturePets/ArmoredInfestedCatbrowPetPowerSuit" - ].includes(WeaponType) - ) { - requiredParts = ["CATBROW_ANTIGEN", "CATBROW_MUTAGEN"]; - } else { - requiredParts = ["KUBROW_ANTIGEN", "KUBROW_MUTAGEN"]; - } - break; + return WeaponType === "/Lotus/Types/Friendly/Pets/MoaPets/MoaPetPowerSuit" + ? ["MOA_ENGINE", "MOA_PAYLOAD", "MOA_HEAD", "MOA_LEG"] + : ["ZANUKA_BODY", "ZANUKA_HEAD", "ZANUKA_LEG", "ZANUKA_TAIL"]; + + case "KubrowPets": { + return WeaponType.endsWith("InfestedCatbrowPetPowerSuit") + ? ["CATBROW_ANTIGEN", "CATBROW_MUTAGEN"] + : ["KUBROW_ANTIGEN", "KUBROW_MUTAGEN"]; + } } +} + +function doAcquireModularEquipment(category, WeaponType) { + if (category === "Hoverboards") WeaponType = "/Lotus/Types/Vehicles/Hoverboard/HoverboardSuit"; + const requiredParts = getRequiredParts(category, WeaponType); + let Parts = []; + requiredParts.forEach(part => { const partName = getKey(document.getElementById("acquire-type-" + category + "-" + part)); if (partName) { @@ -1495,7 +1521,7 @@ function doAcquireEvolution() { setEvolutionProgress([{ ItemType: uniqueName, Rank: permanentEvolutionWeapons.has(uniqueName) ? 0 : 1 }]); } -$("input[list]").on("input", function () { +$(document).on("input", "input[list]", function () { $(this).removeClass("is-invalid"); }); @@ -2202,6 +2228,8 @@ single.getRoute("#detailedView-route").on("beforeload", function () { document.getElementById("detailedView-title").textContent = ""; document.querySelector("#detailedView-route .text-body-secondary").textContent = ""; document.getElementById("archonShards-card").classList.add("d-none"); + document.getElementById("modularParts-card").classList.add("d-none"); + document.getElementById("modularParts-form").innerHTML = ""; document.getElementById("valenceBonus-card").classList.add("d-none"); if (window.didInitialInventoryUpdate) { updateInventory(); @@ -2359,22 +2387,10 @@ function handleModularSelection(category) { modularFieldsZanuka.style.display = "none"; } } else if (inventoryCategory === "KubrowPets") { - if ( - [ - "/Lotus/Types/Friendly/Pets/CreaturePets/VulpineInfestedCatbrowPetPowerSuit", - "/Lotus/Types/Friendly/Pets/CreaturePets/HornedInfestedCatbrowPetPowerSuit", - "/Lotus/Types/Friendly/Pets/CreaturePets/ArmoredInfestedCatbrowPetPowerSuit" - ].includes(key) - ) { + if (key.endsWith("InfestedCatbrowPetPowerSuit")) { modularFieldsCatbrow.style.display = ""; modularFieldsKubrow.style.display = "none"; - } else if ( - [ - "/Lotus/Types/Friendly/Pets/CreaturePets/VizierPredatorKubrowPetPowerSuit", - "/Lotus/Types/Friendly/Pets/CreaturePets/PharaohPredatorKubrowPetPowerSuit", - "/Lotus/Types/Friendly/Pets/CreaturePets/MedjayPredatorKubrowPetPowerSuit" - ].includes(key) - ) { + } else if (key.endsWith("PredatorKubrowPetPowerSuit")) { modularFieldsCatbrow.style.display = "none"; modularFieldsKubrow.style.display = ""; } else { @@ -2813,3 +2829,36 @@ async function markAllAsRead() { } toast(loc(any ? "code_succRelog" : "code_nothingToDo")); } + +function handleModularPartsChange(event) { + event.preventDefault(); + const urlParams = new URLSearchParams(window.location.search); + const form = document.getElementById("modularParts-form"); + const inputs = form.querySelectorAll("input"); + const modularParts = []; + inputs.forEach(input => { + const key = getKey(input); + if (!key) { + input.classList.add("is-invalid"); + } else { + modularParts.push(key); + } + }); + + if (inputs.length == modularParts.length) { + revalidateAuthz().then(() => { + $.post({ + url: "/custom/changeModularParts?" + window.authz, + contentType: "application/json", + data: JSON.stringify({ + category: urlParams.get("productCategory"), + oid: urlParams.get("itemId"), + modularParts + }) + }).then(function () { + toast(loc("code_succChange")); + updateInventory(); + }); + }); + } +} diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index 97fd3511..0ddb6eb0 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -61,6 +61,7 @@ dict = { code_pigment: `Pigment`, code_mature: `Für den Kampf auswachsen lassen`, code_unmature: `Genetisches Altern zurücksetzen`, + code_succChange: `[UNTRANSLATED] Successfully changed.`, 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`, @@ -123,6 +124,7 @@ dict = { detailedView_archonShardsDescription2: `Hinweis: Jede Archon-Scherbe benötigt beim Laden etwas Zeit, um angewendet zu werden.`, detailedView_valenceBonusLabel: `Valenz-Bonus`, detailedView_valenceBonusDescription: `[UNTRANSLATED] You can set or remove the Valence Bonus from your weapon.`, + detailedView_modularPartsLabel: `[UNTRANSLATED] Change Modular Parts`, mods_addRiven: `Riven hinzufügen`, mods_fingerprint: `Fingerabdruck`, diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index 02b316f2..b95e2c66 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -60,6 +60,7 @@ dict = { code_pigment: `Pigment`, code_mature: `Mature for combat`, code_unmature: `Regress genetic aging`, + code_succChange: `Successfully changed.`, login_description: `Login using your OpenWF account credentials (same as in-game when connecting to this server).`, login_emailLabel: `Email address`, login_passwordLabel: `Password`, @@ -122,6 +123,7 @@ dict = { detailedView_archonShardsDescription2: `Note that each archon shard takes some time to be applied when loading in.`, detailedView_valenceBonusLabel: `Valence Bonus`, detailedView_valenceBonusDescription: `You can set or remove the Valence Bonus from your weapon.`, + detailedView_modularPartsLabel: `Change Modular Parts`, mods_addRiven: `Add Riven`, mods_fingerprint: `Fingerprint`, diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index 535a024e..6a1fc379 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -61,6 +61,7 @@ dict = { code_pigment: `Pigmento`, code_mature: `Listo para el combate`, code_unmature: `Regresar el envejecimiento genético`, + code_succChange: `[UNTRANSLATED] Successfully changed.`, 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`, @@ -123,6 +124,7 @@ dict = { detailedView_archonShardsDescription2: `Ten en cuenta que cada fragmento de archón tarda un poco en aplicarse al cargar`, detailedView_valenceBonusLabel: `Bônus de Valência`, detailedView_valenceBonusDescription: `Puedes establecer o quitar el bono de valencia de tu arma.`, + detailedView_modularPartsLabel: `[UNTRANSLATED] Change Modular Parts`, mods_addRiven: `Agregar Agrietado`, mods_fingerprint: `Huella digital`, diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index 638f733f..f83433e9 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -61,6 +61,7 @@ dict = { code_pigment: `Pigment`, code_mature: `Maturer pour le combat`, code_unmature: `Régrésser l'âge génétique`, + code_succChange: `[UNTRANSLATED] Successfully changed.`, login_description: `Connexion avec les informations de connexion OpenWF.`, login_emailLabel: `Email`, login_passwordLabel: `Mot de passe`, @@ -123,6 +124,7 @@ dict = { detailedView_archonShardsDescription2: `Un délai sera présent entre l'application des éclats et le chargement en jeu.`, detailedView_valenceBonusLabel: `Bonus de Valence`, detailedView_valenceBonusDescription: `[UNTRANSLATED] You can set or remove the Valence Bonus from your weapon.`, + detailedView_modularPartsLabel: `[UNTRANSLATED] Change Modular Parts`, mods_addRiven: `Ajouter un riven`, mods_fingerprint: `Empreinte`, diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index cb7dc75f..20720f84 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -61,6 +61,7 @@ dict = { code_pigment: `Пигмент`, code_mature: `Подготовить к сражениям`, code_unmature: `Регрессия генетического старения`, + code_succChange: `Успешно изменено.`, login_description: `Войдите, используя учетные данные OpenWF (те же, что и в игре при подключении к этому серверу).`, login_emailLabel: `Адрес электронной почты`, login_passwordLabel: `Пароль`, @@ -123,6 +124,7 @@ dict = { detailedView_archonShardsDescription2: `Обратите внимание: каждый фрагмент архонта применяется с задержкой при загрузке.`, detailedView_valenceBonusLabel: `Бонус Валентности`, detailedView_valenceBonusDescription: `Вы можете установить или убрать бонус валентности с вашего оружия.`, + detailedView_modularPartsLabel: `Изменить Модульные Части`, mods_addRiven: `Добавить Мод Разлома`, mods_fingerprint: `Отпечаток`, diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index b695fc29..4372fb16 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -61,6 +61,7 @@ dict = { code_pigment: `颜料`, code_mature: `成长并战备`, code_unmature: `逆转衰老基因`, + code_succChange: `[UNTRANSLATED] Successfully changed.`, login_description: `使用您的 OpenWF 账户凭证登录(与游戏内连接本服务器时使用的昵称相同).`, login_emailLabel: `电子邮箱`, login_passwordLabel: `密码`, @@ -123,6 +124,7 @@ dict = { detailedView_archonShardsDescription2: `请注意,在加载时,每个执政官源力石都需要一定的时间来生效。`, detailedView_valenceBonusLabel: `效价加成`, detailedView_valenceBonusDescription: `您可以设置或移除武器上的效价加成.`, + detailedView_modularPartsLabel: `[UNTRANSLATED] Change Modular Parts`, mods_addRiven: `添加裂罅MOD`, mods_fingerprint: `印记`,