From 8088044ec8fd1af1be5a746c9c976354d54c49d8 Mon Sep 17 00:00:00 2001
From: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Date: Tue, 11 Nov 2025 23:26:01 -0800
Subject: [PATCH] feat(webui): echoes of umbra (#3019)
Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/3019
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/setUmbraEchoesController.ts | 22 ++++++++++++++++
src/routes/custom.ts | 2 ++
static/webui/index.html | 16 ++++++++++++
static/webui/script.js | 26 +++++++++++++++++++
static/webui/translations/de.js | 4 +++
static/webui/translations/en.js | 4 +++
static/webui/translations/es.js | 4 +++
static/webui/translations/fr.js | 4 +++
static/webui/translations/ru.js | 4 +++
static/webui/translations/uk.js | 4 +++
static/webui/translations/zh.js | 4 +++
11 files changed, 94 insertions(+)
create mode 100644 src/controllers/custom/setUmbraEchoesController.ts
diff --git a/src/controllers/custom/setUmbraEchoesController.ts b/src/controllers/custom/setUmbraEchoesController.ts
new file mode 100644
index 00000000..290ea764
--- /dev/null
+++ b/src/controllers/custom/setUmbraEchoesController.ts
@@ -0,0 +1,22 @@
+import { getAccountIdForRequest } from "../../services/loginService.ts";
+import { getInventory } from "../../services/inventoryService.ts";
+import type { RequestHandler } from "express";
+import { broadcastInventoryUpdate } from "../../services/wsService.ts";
+
+export const setUmbraEchoesController: RequestHandler = async (req, res) => {
+ const accountId = await getAccountIdForRequest(req);
+ const request = req.body as ISetUmbraEchoesRequest;
+ const inventory = await getInventory(accountId, "Suits");
+ const suit = inventory.Suits.id(request.oid);
+ if (suit) {
+ suit.UmbraDate = request.UmbraDate ? new Date(request.UmbraDate) : undefined;
+ await inventory.save();
+ broadcastInventoryUpdate(req);
+ }
+ res.end();
+};
+
+interface ISetUmbraEchoesRequest {
+ oid: string;
+ UmbraDate: number;
+}
diff --git a/src/routes/custom.ts b/src/routes/custom.ts
index 1a6fca1f..3978a41f 100644
--- a/src/routes/custom.ts
+++ b/src/routes/custom.ts
@@ -46,6 +46,7 @@ import { updateFingerprintController } from "../controllers/custom/updateFingerp
import { unlockLevelCapController } from "../controllers/custom/unlockLevelCapController.ts";
import { changeModularPartsController } from "../controllers/custom/changeModularPartsController.ts";
import { setInvigorationController } from "../controllers/custom/setInvigorationController.ts";
+import { setUmbraEchoesController } from "../controllers/custom/setUmbraEchoesController.ts";
import { setAccountCheatController } from "../controllers/custom/setAccountCheatController.ts";
import { setGuildCheatController } from "../controllers/custom/setGuildCheatController.ts";
@@ -97,6 +98,7 @@ customRouter.post("/updateFingerprint", updateFingerprintController);
customRouter.post("/unlockLevelCap", unlockLevelCapController);
customRouter.post("/changeModularParts", changeModularPartsController);
customRouter.post("/setInvigoration", setInvigorationController);
+customRouter.post("/setUmbraEchoes", setUmbraEchoesController);
customRouter.post("/setAccountCheat", setAccountCheatController);
customRouter.post("/setGuildCheat", setGuildCheatController);
diff --git a/static/webui/index.html b/static/webui/index.html
index 7c052786..6f356c97 100644
--- a/static/webui/index.html
+++ b/static/webui/index.html
@@ -822,6 +822,22 @@
+
diff --git a/static/webui/script.js b/static/webui/script.js
index ace99d86..7eabcebe 100644
--- a/static/webui/script.js
+++ b/static/webui/script.js
@@ -1652,6 +1652,12 @@ function updateInventory() {
formatDatetime("%Y-%m-%d %H:%M", Number(item.UpgradesExpiry?.$date.$numberLong)) || "";
}
+ if (item.ItemType != "/Lotus/Powersuits/Excalibur/ExcaliburUmbra") {
+ document.getElementById("umbraEchoes-card").classList.remove("d-none");
+ document.getElementById("umbraEchoes-expiry").value =
+ formatDatetime("%Y-%m-%d %H:%M", Number(item.UmbraDate?.$date.$numberLong)) || "";
+ }
+
{
document.getElementById("loadout-card").classList.remove("d-none");
const maxModConfigNum = Math.min(2 + (item.ModSlotPurchases ?? 0), 5);
@@ -3519,6 +3525,7 @@ single.getRoute("#detailedView-route").on("beforeload", function () {
document.getElementById("loadout-card").classList.add("d-none");
document.getElementById("archonShards-card").classList.add("d-none");
document.getElementById("edit-suit-invigorations-card").classList.add("d-none");
+ document.getElementById("umbraEchoes-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");
@@ -4257,6 +4264,25 @@ function setInvigoration(data) {
});
}
+function submitUmbraEchoes(event) {
+ event.preventDefault();
+ const expiry = document.getElementById("umbraEchoes-expiry").value;
+ setUmbraEchoes({
+ UmbraDate: expiry ? new Date(expiry).getTime() : Date.now() + 1 * 24 * 60 * 60 * 1000
+ });
+}
+
+function setUmbraEchoes(data) {
+ const oid = new URLSearchParams(window.location.search).get("itemId");
+ $.post({
+ url: "/custom/setUmbraEchoes?" + window.authz,
+ contentType: "application/json",
+ data: JSON.stringify({ oid, ...data })
+ }).done(function () {
+ updateInventory();
+ });
+}
+
function handleAbilityOverride(event, configIndex) {
event.preventDefault();
const urlParams = new URLSearchParams(window.location.search);
diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js
index 2ce7e4c9..37fb291c 100644
--- a/static/webui/translations/de.js
+++ b/static/webui/translations/de.js
@@ -163,6 +163,7 @@ dict = {
detailedView_invigorationLabel: `Kräftigung`,
detailedView_loadoutLabel: `Loadouts`,
detailedView_equipmentFeaturesLabel: `[UNTRANSLATED] Equipment Features`,
+ detailedView_umbraEchoesLabel: `[UNTRANSLATED] Echoes Of Umbra`,
invigorations_offensive_AbilityStrength: `+200% Fähigkeitsstärke`,
invigorations_offensive_AbilityRange: `+100% Fähigkeitsreichweite`,
@@ -193,6 +194,9 @@ dict = {
abilityOverride_label: `Fähigkeitsüberschreibung`,
abilityOverride_onSlot: `auf Slot`,
+ detailedView_umbraEchoesDescription: `Wird ein Warframe mit dieser Flüssigkeit injiziert, kann er selbstständig an der Seite des Operators kämpfen.`,
+ detailedView_umbraEchoesExpiryLabel: `[UNTRANSLATED] Echo Expiry (optional)`,
+
mods_addRiven: `Riven hinzufügen`,
mods_fingerprint: `Fingerabdruck`,
mods_fingerprintHelp: `Benötigst du Hilfe mit dem Fingerabdruck?`,
diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js
index dcb8267a..47ef0d1d 100644
--- a/static/webui/translations/en.js
+++ b/static/webui/translations/en.js
@@ -162,6 +162,7 @@ dict = {
detailedView_invigorationLabel: `Invigoration`,
detailedView_loadoutLabel: `Loadouts`,
detailedView_equipmentFeaturesLabel: `Equipment Features`,
+ detailedView_umbraEchoesLabel: `Echoes Of Umbra`,
invigorations_offensive_AbilityStrength: `+200% Ability Strength`,
invigorations_offensive_AbilityRange: `+100% Ability Range`,
@@ -192,6 +193,9 @@ dict = {
abilityOverride_label: `Ability Override`,
abilityOverride_onSlot: `on slot`,
+ detailedView_umbraEchoesDescription: `Injecting this fluid into a Warframe will imbue it with the ability to fight autonomously alongside the Operator.`,
+ detailedView_umbraEchoesExpiryLabel: `Echo Expiry (optional)`,
+
mods_addRiven: `Add Riven`,
mods_fingerprint: `Fingerprint`,
mods_fingerprintHelp: `Need help with the fingerprint?`,
diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js
index 836b7c4c..e8109a60 100644
--- a/static/webui/translations/es.js
+++ b/static/webui/translations/es.js
@@ -163,6 +163,7 @@ dict = {
detailedView_invigorationLabel: `Fortalecimiento`,
detailedView_loadoutLabel: `Equipamientos`,
detailedView_equipmentFeaturesLabel: `[UNTRANSLATED] Equipment Features`,
+ detailedView_umbraEchoesLabel: `[UNTRANSLATED] Echoes Of Umbra`,
invigorations_offensive_AbilityStrength: `+200% Fuerza de Habilidad`,
invigorations_offensive_AbilityRange: `+100% Alcance de Habilidad`,
@@ -193,6 +194,9 @@ dict = {
abilityOverride_label: `Intercambio de Habilidad`,
abilityOverride_onSlot: `en el espacio`,
+ detailedView_umbraEchoesDescription: `Inyectar este fluido en un warframe lo imbuirá con la capacidad para luchar autónomamente junto a su operador.`,
+ detailedView_umbraEchoesExpiryLabel: `[UNTRANSLATED] Echo Expiry (optional)`,
+
mods_addRiven: `Agregar Agrietado`,
mods_fingerprint: `Huella digital`,
mods_fingerprintHelp: `¿Necesitas ayuda con la huella digital?`,
diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js
index 7d0119eb..606d68f8 100644
--- a/static/webui/translations/fr.js
+++ b/static/webui/translations/fr.js
@@ -163,6 +163,7 @@ dict = {
detailedView_invigorationLabel: `Dynamisation`,
detailedView_loadoutLabel: `Équipements`,
detailedView_equipmentFeaturesLabel: `[UNTRANSLATED] Equipment Features`,
+ detailedView_umbraEchoesLabel: `[UNTRANSLATED] Echoes Of Umbra`,
invigorations_offensive_AbilityStrength: `+200% de puissance de pouvoir`,
invigorations_offensive_AbilityRange: `+100% de portée de pouvoir`,
@@ -193,6 +194,9 @@ dict = {
abilityOverride_label: `Remplacement de pouvoir`,
abilityOverride_onSlot: `Sur l'emplacement`,
+ detailedView_umbraEchoesDescription: `L'injection de ce fluide dans une Warframe lui donnera la possibilité de se battre de manière autonome aux côtés de l'Opérateur.`,
+ detailedView_umbraEchoesExpiryLabel: `[UNTRANSLATED] Echo Expiry (optional)`,
+
mods_addRiven: `Ajouter un riven`,
mods_fingerprint: `Empreinte`,
mods_fingerprintHelp: `Besoin d'aide pour l'empreinte ?`,
diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js
index 567d4736..727d8d3f 100644
--- a/static/webui/translations/ru.js
+++ b/static/webui/translations/ru.js
@@ -163,6 +163,7 @@ dict = {
detailedView_invigorationLabel: `Воодушевление`,
detailedView_loadoutLabel: `Конфигурации`,
detailedView_equipmentFeaturesLabel: `Модификаторы снаряжения`,
+ detailedView_umbraEchoesLabel: `Эхо Умбры`,
invigorations_offensive_AbilityStrength: `+200% к силе способностей.`,
invigorations_offensive_AbilityRange: `+100% к зоне поражения способностей.`,
@@ -193,6 +194,9 @@ dict = {
abilityOverride_label: `Переопределение способности`,
abilityOverride_onSlot: `в ячейке`,
+ detailedView_umbraEchoesDescription: `Введение этой жидкости в варфрейм позволит ему автономно сражаться бок о бок с оператором.`,
+ detailedView_umbraEchoesExpiryLabel: `Срок действия Эха (необязательно)`,
+
mods_addRiven: `Добавить мод Разлома`,
mods_fingerprint: `Отпечаток`,
mods_fingerprintHelp: `Нужна помощь с отпечатком?`,
diff --git a/static/webui/translations/uk.js b/static/webui/translations/uk.js
index 64e99c88..49fa93b1 100644
--- a/static/webui/translations/uk.js
+++ b/static/webui/translations/uk.js
@@ -163,6 +163,7 @@ dict = {
detailedView_invigorationLabel: `Зміцнення`,
detailedView_loadoutLabel: `Конфігурації`,
detailedView_equipmentFeaturesLabel: `[UNTRANSLATED] Equipment Features`,
+ detailedView_umbraEchoesLabel: `[UNTRANSLATED] Echoes Of Umbra`,
invigorations_offensive_AbilityStrength: `+200% до потужності здібностей.`,
invigorations_offensive_AbilityRange: `+100% до досяжності здібностей.`,
@@ -193,6 +194,9 @@ dict = {
abilityOverride_label: `Перевизначення здібностей`,
abilityOverride_onSlot: `у комірці`,
+ detailedView_umbraEchoesDescription: `Рідина, яка після введення дозволяє ворфрейму використовувати єдність і битися самостійно пліч-о-пліч з оператором.`,
+ detailedView_umbraEchoesExpiryLabel: `[UNTRANSLATED] Echo Expiry (optional)`,
+
mods_addRiven: `Добавити модифікатор Розколу`,
mods_fingerprint: `Відбиток`,
mods_fingerprintHelp: `Потрібна допомога з відбитком?`,
diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js
index 8d36c529..a5df62fc 100644
--- a/static/webui/translations/zh.js
+++ b/static/webui/translations/zh.js
@@ -163,6 +163,7 @@ dict = {
detailedView_invigorationLabel: `活化`,
detailedView_loadoutLabel: `配置`,
detailedView_equipmentFeaturesLabel: `[UNTRANSLATED] Equipment Features`,
+ detailedView_umbraEchoesLabel: `[UNTRANSLATED] Echoes Of Umbra`,
invigorations_offensive_AbilityStrength: `+200%技能强度`,
invigorations_offensive_AbilityRange: `+100%技能范围`,
@@ -193,6 +194,9 @@ dict = {
abilityOverride_label: `技能替换`,
abilityOverride_onSlot: `槽位`,
+ detailedView_umbraEchoesDescription: `将这种液体注入战甲内,使其具有与指挥官并肩作战的能力。`,
+ detailedView_umbraEchoesExpiryLabel: `[UNTRANSLATED] Echo Expiry (optional)`,
+
mods_addRiven: `添加裂罅MOD`,
mods_fingerprint: `印记`,
mods_fingerprintHelp: `需要印记相关的帮助?`,