/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-floating-promises */
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable no-undef */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
const allPermissions = [];
document.querySelectorAll("[data-enabling-permission]").forEach(elm => {
const perm = elm.getAttribute("data-enabling-permission");
if (allPermissions.indexOf(perm) == -1) {
allPermissions.push(perm);
}
});
let auth_pending = false,
ws_is_open = false,
wsid = 0,
ws_reconnect = false;
const sendAuth = isRegister => {
if (ws_is_open && localStorage.getItem("email") && localStorage.getItem("password")) {
auth_pending = true;
window.ws.send(
JSON.stringify({
auth: {
email: localStorage.getItem("email").toLowerCase(),
password: wp.encSync(localStorage.getItem("password")),
possessing: localStorage.getItem("possessing"),
isRegister,
allPermissions
}
})
);
return true;
}
return false;
};
function openWebSocket() {
const wsProto = location.protocol === "https:" ? "wss://" : "ws://";
window.ws = new WebSocket(wsProto + location.host + "/custom/ws");
window.ws.onopen = () => {
ws_is_open = true;
sendAuth(false);
};
window.ws.onmessage = e => {
const msg = JSON.parse(e.data);
if ("wsid" in msg) {
wsid = msg.wsid;
}
if ("reload" in msg) {
setTimeout(() => {
getWebSocket().then(() => {
location.reload();
});
}, 100);
}
if ("ports" in msg) {
location.port = location.protocol == "https:" ? msg.ports.https : msg.ports.http;
}
if ("config_reloaded" in msg) {
refreshServerConfig();
if (window.authz) {
// Our permissions may have changed, so resync them with the server.
ws.send(JSON.stringify({ allPermissions }));
}
}
if ("auth_succ" in msg) {
auth_pending = false;
const data = msg.auth_succ;
if (single.getCurrentPath() == "/webui/") {
single.loadRoute("/webui/inventory");
}
$("#email").val("");
$("#password").val("");
$(".displayname").text(data.DisplayName);
window.accountId = data.id;
window.authz = "accountId=";
let possessing = localStorage.getItem("possessing");
if (possessing == data.id) {
localStorage.removeItem("possessing");
possessing = null;
}
if (possessing) {
window.accountId = possessing;
window.authz += possessing + "&possesser=";
}
window.authz += data.id + "&nonce=" + data.Nonce + "&wsid=" + wsid;
if (window.dict) {
updateLocElements();
}
if (ws_reconnect) {
ws_reconnect = false;
// Anything could've happened during the time we were disconnected and not getting events.
single.loadRoute(single.getCurrentPath());
}
}
if ("permissions" in msg) {
for (const perm of allPermissions) {
const granted = msg.permissions.indexOf(perm) != -1;
//console.log(`Permission ${perm} was ${granted ? 'granted' : 'denied'}`);
document.querySelectorAll(`[data-enabling-permission='${perm}']`).forEach(elm => {
elm.disabled = !granted;
});
}
}
if ("auth_fail" in msg) {
auth_pending = false;
if (localStorage.getItem("possessing")) {
localStorage.removeItem("possessing");
doAccountSwitch("/webui/users");
} else {
logout();
if (single.getCurrentPath() == "/webui/") {
if (msg.auth_fail == "bad login") {
alert(loc("code_loginFail"));
} else if (msg.auth_fail == "bad register") {
alert(loc("code_regFail"));
} else if (msg.auth_fail == "admin only") {
alert(loc("code_adminOnlyLogin"));
} else if (msg.auth_fail == "registered but admin only") {
alert(loc("code_adminOnlyRegister"));
} else {
alert(msg.auth_fail);
}
} else {
single.loadRoute("/webui/");
}
}
}
if ("nonce_updated" in msg) {
if (!sendAuth()) {
single.loadRoute("/webui/"); // Show login screen
}
}
if ("update_inventory" in msg) {
updateInventory();
}
if ("update_guild" in msg) {
updateGuild();
}
if ("logged_out" in msg) {
if (localStorage.getItem("possessing")) {
localStorage.removeItem("possessing");
doAccountSwitch("/webui/users");
} else {
logout();
single.loadRoute("/webui/"); // Show login screen
}
}
if ("have_game_ws" in msg) {
window.have_game_ws = msg.have_game_ws;
if (window.dict) {
$(".inventory-update-note").text(
loc(msg.have_game_ws ? "general_inventoryUpdateNoteGameWs" : "general_inventoryUpdateNote")
);
}
}
};
window.ws.onclose = function () {
ws_is_open = false;
ws_reconnect = true;
invalidateCachedData();
window.subscribedToGuildId = undefined;
setTimeout(openWebSocket, 3000);
};
}
openWebSocket();
function refreshServerConfig() {
window.is_admin = undefined;
config_data = undefined;
if (single.getCurrentPath() == "/webui/cheats" || single.getCurrentPath() == "/webui/users") {
single.loadRoute(single.getCurrentPath());
}
}
function invalidateCachedData() {
inventory_data = undefined;
guild_data = undefined;
window.is_admin = undefined;
config_data = undefined;
}
function doAccountSwitch(to_route) {
window.authz = undefined;
invalidateCachedData();
single.loadRoute(to_route);
sendAuth();
}
function getWebSocket() {
return new Promise(resolve => {
let interval;
interval = setInterval(() => {
if (ws_is_open) {
clearInterval(interval);
resolve(window.ws);
}
}, 10);
});
}
window.registerSubmit = false;
function doLogin() {
if (auth_pending) {
return;
}
localStorage.setItem("email", $("#email").val());
localStorage.setItem("password", $("#password").val());
sendAuth(registerSubmit);
window.registerSubmit = false;
}
function awaitAuthz() {
return new Promise(resolve => {
let interval;
interval = setInterval(() => {
if (window.authz) {
clearInterval(interval);
resolve();
}
}, 10);
});
}
function revalidateAuthz() {
return new Promise(resolve => {
let interval;
interval = setInterval(() => {
if (ws_is_open && !auth_pending) {
clearInterval(interval);
resolve();
}
}, 10);
});
}
function logout() {
localStorage.removeItem("email");
localStorage.removeItem("password");
window.authz = undefined;
invalidateCachedData();
}
function doLogout() {
if (localStorage.getItem("possessing")) {
localStorage.removeItem("possessing");
doAccountSwitch("/webui/users");
} else {
logout();
if (ws_is_open) {
// Unsubscribe from notifications about nonce invalidation
window.ws.send(JSON.stringify({ logout: true }));
}
location.href = "/webui/";
}
}
function renameAccount(taken_name) {
const newname = window.prompt(
`${taken_name ? loc("code_changeNameRetry").replace("|NAME|", taken_name) + " " : ""}${loc("code_changeNameConfirm")}`
);
if (newname) {
revalidateAuthz().then(() => {
fetch("/custom/renameAccount?" + window.authz + "&newname=" + encodeURIComponent(newname)).then(res => {
if (res.status == 200) {
$(".displayname").text(newname);
updateLocElements();
toast(loc("code_succRelog"));
} else if (res.status == 409) {
renameAccount(newname);
} else {
toast(loc("code_genFail"));
}
});
});
}
}
function deleteAccount() {
if (
window.confirm(
loc(localStorage.getItem("possessing") ? "code_deletePossessedAccountConfirm" : "code_deleteAccountConfirm")
)
) {
revalidateAuthz().then(() => {
fetch("/custom/deleteAccount?" + window.authz);
});
}
}
function changeAccountPassword() {
const cur = window.prompt(loc("code_promptCurrentPassword"));
if (cur === null) {
return;
}
if (!cur.length) {
toast(loc("settings_changeFailed"));
return;
}
const nw = window.prompt(loc("code_promptNewPassword"));
if (nw === null) {
return;
}
if (!nw.length) {
toast(loc("settings_changeFailed"));
return;
}
const cf = window.prompt(loc("code_promptConfirmNewPassword"));
if (cf === null) {
return;
}
if (nw !== cf) {
toast(loc("settings_passwordMismatch"));
return;
}
revalidateAuthz().then(() => {
fetch("/custom/changePassword?" + window.authz, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
currentPassword: wp.encSync(cur),
newPassword: wp.encSync(nw)
})
}).then(res => {
if (res.status == 200) {
localStorage.setItem("password", nw);
res.text().then(text => {
toast(loc(text == "noop" ? "code_nothingToDo" : "settings_passwordSuccess"));
});
} else if (res.status == 403) {
toast(loc("settings_wrongPassword"));
} else {
toast(loc("settings_changeFailed"));
}
});
});
}
function changeAccountEmail() {
const curPw = window.prompt(loc("code_promptCurrentPassword"));
if (curPw === null) {
return;
}
if (!curPw.length) {
toast(loc("settings_changeFailed"));
return;
}
const currentHash = wp.encSync(curPw);
function promptAndSend(conflictEmail) {
const newEmailRaw = window.prompt(
`${conflictEmail ? loc("code_changeEmailRetry").replace("|EMAIL|", conflictEmail) + " " : ""}${loc("code_promptNewEmail")}`
);
if (newEmailRaw === null) {
return;
}
const newEmail = newEmailRaw.trim().toLowerCase();
if (!newEmail.length) {
toast(loc("settings_changeFailed"));
return;
}
if (!newEmail.includes("@")) {
toast(loc("settings_changeFailed"));
return;
}
revalidateAuthz().then(() => {
fetch("/custom/changeEmail?" + window.authz, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
currentPassword: currentHash,
newEmail
})
}).then(res => {
if (res.status == 200) {
localStorage.setItem("email", newEmail);
res.text().then(text => {
toast(loc(text == "noop" ? "code_nothingToDo" : "settings_emailSuccess"));
});
} else if (res.status == 403) {
toast(loc("settings_wrongPassword"));
} else if (res.status == 409) {
promptAndSend(newEmail);
} else {
toast(loc("settings_changeFailed"));
}
});
});
}
promptAndSend();
}
function updateTitle() {
const route = single.getCurrentRoute();
if (route) {
const tag = route.elm.getAttribute("data-title-tag");
if (tag) {
document.querySelector("title").textContent = loc(tag) + " | OpenWF WebUI";
}
}
}
single.on("route_load", function (event) {
if (event.route.paths[0] != "/webui/") {
// Authorised route?
if (!localStorage.getItem("email")) {
// Not logged in?
return single.loadRoute("/webui/"); // Show login screen
}
$("body").addClass("logged-in");
let navPath = event.route.paths[0];
if (navPath == "/webui/details") {
const params = new URLSearchParams(window.location.search);
const category = params.get("productCategory");
navPath = category == "Upgrades" ? "/webui/mods" : "/webui/inventory";
}
$("#sidebar .nav-link").removeClass("active");
const navLink = document.querySelector(".nav-link[href='" + navPath + "']");
if (navLink) {
navLink.classList.add("active");
} else {
console.warn("No sidebar entry for current route!");
}
} else {
$("body").removeClass("logged-in");
}
window.dictPromise.then(() => {
updateTitle();
});
});
function loc(tag) {
return ((window.dict ?? {})[tag] ?? tag)
.replaceAll("|DISPLAYNAME|", document.querySelector(".displayname").textContent)
.replaceAll("|EMAIL|", localStorage.getItem("email"));
}
function updateLocElements() {
document.querySelectorAll("[data-loc]").forEach(elm => {
elm.innerHTML = loc(elm.getAttribute("data-loc"));
});
document.querySelectorAll("[data-loc-placeholder]").forEach(elm => {
elm.placeholder = loc(elm.getAttribute("data-loc-placeholder"));
});
document.querySelectorAll("[data-loc-title]").forEach(elm => {
elm.title = loc(elm.getAttribute("data-loc-title"));
});
$(".inventory-update-note").text(
loc(window.have_game_ws ? "general_inventoryUpdateNoteGameWs" : "general_inventoryUpdateNote")
);
window.itemListPromise.then(itemMap => {
document.querySelectorAll("[data-loc-itemMap]").forEach(elm => {
const key = elm.getAttribute("data-loc-itemMap");
const item = itemMap[key];
elm.innerHTML = item && item.name ? item.name : key;
});
document.querySelectorAll("[data-loc-itemMap-norm]").forEach(elm => {
const key = elm.getAttribute("data-loc-itemMap-norm");
const item = itemMap[key];
elm.innerHTML = item && item.name ? normalizeText(item.name) : key;
});
document.querySelectorAll("[data-loc-itemMap-tc]").forEach(elm => {
const key = elm.getAttribute("data-loc-itemMap-tc");
const item = itemMap[key];
elm.innerHTML = item && item.name ? toTitleCase(item.name) : key;
});
document.querySelectorAll("[data-loc-inc]").forEach(elm => {
const incWith = elm
.getAttribute("data-loc-inc")
.split("|")
.map(key => {
if (key.startsWith("/")) {
return toTitleCase(itemMap[key]?.name ?? key);
}
return loc(key).replace(/<[^>]+>/g, "");
})
.join(", ");
elm.title = `${loc("worldState_incompatibleWith")} ${incWith}`;
});
document.querySelectorAll("[data-loc-replace]").forEach(elm => {
elm.innerHTML = elm.innerHTML
.replaceAll("|val|", "|VAL|")
// de and uk for no reason adds space between value and %
.replaceAll("|VAL| %", "|VAL|%") // de
.replaceAll("|VAL| %", "|VAL|%"); // uk
elm.innerHTML = elm.innerHTML.replace("|VAL|", elm.getAttribute("data-loc-replace"));
if (elm.getAttribute("data-loc") == "worldState_lunarNewYearEntry") {
const animal = loc(
`worldState_lunarNewYearAnimal${((parseInt(elm.getAttribute("data-loc-replace")) - 4) % 12) + 1}`
);
elm.innerHTML = elm.innerHTML.replace("|ANIMAL|", animal);
}
});
});
}
function setActiveLanguage(lang) {
window.lang = lang;
const lang_name = document.querySelector("[data-lang=" + lang + "]").textContent;
document.getElementById("active-lang-name").textContent = lang_name;
document.querySelector("[data-lang].active").classList.remove("active");
document.querySelector("[data-lang=" + lang + "]").classList.add("active");
window.dictPromise = new Promise(resolve => {
const webui_lang = ["en", "ru", "fr", "de", "zh", "es", "uk"].indexOf(lang) == -1 ? "en" : lang;
let script = document.getElementById("translations");
if (script) document.documentElement.removeChild(script);
script = document.createElement("script");
script.id = "translations";
script.src = "/webui/translations/" + webui_lang + ".js";
script.onload = function () {
updateLocElements();
updateTitle();
resolve(window.dict);
};
script.onerror = function () {
location.reload();
};
document.documentElement.appendChild(script);
});
}
setActiveLanguage(localStorage.getItem("lang") ?? webui_conf.defaultLanguage ?? "en");
function setLanguage(lang) {
setActiveLanguage(lang);
localStorage.setItem("lang", lang);
if (window.authz) {
// Not in prelogin state?
fetchItemList();
single.loadRoute(single.getCurrentPath());
}
}
function setActiveTheme(theme) {
document.documentElement.setAttribute("data-bs-theme", theme);
document.querySelector("[data-theme].active").classList.remove("active");
document.querySelector("[data-theme=" + theme + "]").classList.add("active");
}
setActiveTheme(localStorage.getItem("theme") ?? "dark");
function setTheme(theme) {
setActiveTheme(theme);
localStorage.setItem("theme", theme);
}
const webUiModularWeapons = [
"/Lotus/Weapons/Sentients/OperatorAmplifiers/OperatorAmpWeapon",
"/Lotus/Weapons/Ostron/Melee/LotusModularWeapon",
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary",
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary",
"/Lotus/Types/Friendly/Pets/MoaPets/MoaPetPowerSuit",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/VulpineInfestedCatbrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/HornedInfestedCatbrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/ArmoredInfestedCatbrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/VizierPredatorKubrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/PharaohPredatorKubrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/MedjayPredatorKubrowPetPowerSuit"
];
const permanentEvolutionWeapons = new Set([
"/Lotus/Weapons/Tenno/Zariman/LongGuns/PumpShotgun/ZarimanPumpShotgun",
"/Lotus/Weapons/Tenno/Zariman/LongGuns/SemiAutoRifle/ZarimanSemiAutoRifle",
"/Lotus/Weapons/Tenno/Zariman/Melee/Dagger/ZarimanDaggerWeapon",
"/Lotus/Weapons/Tenno/Zariman/Melee/Tonfas/ZarimanTonfaWeapon",
"/Lotus/Weapons/Tenno/Zariman/Pistols/HeavyPistol/ZarimanHeavyPistol",
"/Lotus/Weapons/Thanotech/EntFistIncarnon/EntFistIncarnon",
"/Lotus/Weapons/Thanotech/EntratiWristGun/EntratiWristGunWeapon",
"/Lotus/Weapons/Tenno/Zariman/Melee/HeavyScythe/ZarimanHeavyScythe/ZarimanHeavyScytheWeapon"
]);
const icons = {
angleDown: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"/></svg>`,
angleLeft: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M41.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 256 246.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z"/></svg>`,
angleRight: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M278.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-160 160c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L210.7 256 73.4 118.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l160 160z"/></svg>`,
angleUp: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M233.4 105.4c12.5-12.5 32.8-12.5 45.3 0l192 192c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L256 173.3 86.6 342.6c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3l192-192z"/></svg>`,
arrowRotateRight: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M463.5 224l8.5 0c13.3 0 24-10.7 24-24l0-128c0-9.7-5.8-18.5-14.8-22.2s-19.3-1.7-26.2 5.2L413.4 96.6c-87.6-86.5-228.7-86.2-315.8 1c-87.5 87.5-87.5 229.3 0 316.8s229.3 87.5 316.8 0c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0c-62.5 62.5-163.8 62.5-226.3 0s-62.5-163.8 0-226.3c62.2-62.2 162.7-62.5 225.3-1L327 183c-6.9 6.9-8.9 17.2-5.2 26.2s12.5 14.8 22.2 14.8l119.5 0z"/></svg>`,
arrowUp: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M214.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 141.2V448c0 17.7 14.3 32 32 32s32-14.3 32-32V141.2L329.4 246.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z"/></svg>`,
chartSimple: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M160 80c0-26.5 21.5-48 48-48h32c26.5 0 48 21.5 48 48V432c0 26.5-21.5 48-48 48H208c-26.5 0-48-21.5-48-48V80zM0 272c0-26.5 21.5-48 48-48H80c26.5 0 48 21.5 48 48V432c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V272zM368 96h32c26.5 0 48 21.5 48 48V432c0 26.5-21.5 48-48 48H368c-26.5 0-48-21.5-48-48V144c0-26.5 21.5-48 48-48z"/></svg>`,
check: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M438.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L160 338.7 393.4 105.4c12.5-12.5 32.8-12.5 45.3 0z"/></svg>`,
childReaching: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M256 64A64 64 0 1 0 128 64a64 64 0 1 0 128 0zM152.9 169.3c-23.7-8.4-44.5-24.3-58.8-45.8L74.6 94.2C64.8 79.5 45 75.6 30.2 85.4s-18.7 29.7-8.9 44.4L40.9 159c18.1 27.1 42.8 48.4 71.1 62.4L112 480c0 17.7 14.3 32 32 32s32-14.3 32-32l0-96 32 0 0 96c0 17.7 14.3 32 32 32s32-14.3 32-32l0-258.4c29.1-14.2 54.4-36.2 72.7-64.2l18.2-27.9c9.6-14.8 5.4-34.6-9.4-44.3s-34.6-5.5-44.3 9.4L291 122.4c-21.8 33.4-58.9 53.6-98.8 53.6c-12.6 0-24.9-2-36.6-5.8c-.9-.3-1.8-.7-2.7-.9z"/></svg>`,
circleStop: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M464 256A208 208 0 1 0 48 256a208 208 0 1 0 416 0zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256zm192-96l128 0c17.7 0 32 14.3 32 32l0 128c0 17.7-14.3 32-32 32l-128 0c-17.7 0-32-14.3-32-32l0-128c0-17.7 14.3-32 32-32z"/></svg>`,
feather: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M278.5 215.6L23 471c-9.4 9.4-9.4 24.6 0 33.9s24.6 9.4 33.9 0l57-57h68c49.7 0 97.9-14.4 139-41c11.1-7.2 5.5-23-7.8-23c-5.1 0-9.2-4.1-9.2-9.2c0-4.1 2.7-7.6 6.5-8.8l81-24.3c2.5-.8 4.8-2.1 6.7-4l22.4-22.4c10.1-10.1 2.9-27.3-11.3-27.3l-32.2 0c-5.1 0-9.2-4.1-9.2-9.2c0-4.1 2.7-7.6 6.5-8.8l112-33.6c4-1.2 7.4-3.9 9.3-7.7C506.4 207.6 512 184.1 512 160c0-41-16.3-80.3-45.3-109.3l-5.5-5.5C432.3 16.3 393 0 352 0s-80.3 16.3-109.3 45.3L139 149C91 197 64 262.1 64 330v55.3L253.6 195.8c6.2-6.2 16.4-6.2 22.6 0c5.4 5.4 6.1 13.6 2.2 19.8z"/></svg>`,
forward: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--!Font Awesome Free v7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M371.7 43.1C360.1 32 343 28.9 328.3 35.2S304 56 304 72l0 136.3-172.3-165.1C120.1 32 103 28.9 88.3 35.2S64 56 64 72l0 368c0 16 9.6 30.5 24.3 36.8s31.8 3.2 43.4-7.9L304 303.7 304 440c0 16 9.6 30.5 24.3 36.8s31.8 3.2 43.4-7.9l192-184c7.9-7.5 12.3-18 12.3-28.9s-4.5-21.3-12.3-28.9l-192-184z"/></svg>`,
graduationCap: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--!Font Awesome Free v7.0.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M48 195.8l209.2 86.1c9.8 4 20.2 6.1 30.8 6.1s21-2.1 30.8-6.1l242.4-99.8c9-3.7 14.8-12.4 14.8-22.1s-5.8-18.4-14.8-22.1L318.8 38.1C309 34.1 298.6 32 288 32s-21 2.1-30.8 6.1L14.8 137.9C5.8 141.6 0 150.3 0 160L0 456c0 13.3 10.7 24 24 24s24-10.7 24-24l0-260.2zm48 71.7L96 384c0 53 86 96 192 96s192-43 192-96l0-116.6-142.9 58.9c-15.6 6.4-32.2 9.7-49.1 9.7s-33.5-3.3-49.1-9.7L96 267.4z"/></svg>`,
person: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M112 48a48 48 0 1 1 96 0 48 48 0 1 1 -96 0zm40 304l0 128c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-223.1L59.4 304.5c-9.1 15.1-28.8 20-43.9 10.9s-20-28.8-10.9-43.9l58.3-97c17.4-28.9 48.6-46.6 82.3-46.6l29.7 0c33.7 0 64.9 17.7 82.3 46.6l58.3 97c9.1 15.1 4.2 34.8-10.9 43.9s-34.8 4.2-43.9-10.9L232 256.9 232 480c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-128-16 0z"/></svg>`,
plus: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free v7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M256 64c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 160-160 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l160 0 0 160c0 17.7 14.3 32 32 32s32-14.3 32-32l0-160 160 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-160 0 0-160z" /></svg>`,
star: `<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>`,
tag: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M0 80V229.5c0 17 6.7 33.3 18.7 45.3l176 176c25 25 65.5 25 90.5 0L418.7 317.3c25-25 25-65.5 0-90.5l-176-176c-12-12-28.3-18.7-45.3-18.7H48C21.5 32 0 53.5 0 80zm112 32a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>`,
trash: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"/></svg>`,
note: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320C64 461.4 178.6 576 320 576zM320 200C333.3 200 344 210.7 344 224L344 336C344 349.3 333.3 360 320 360C306.7 360 296 349.3 296 336L296 224C296 210.7 306.7 200 320 200zM293.3 416C292.7 406.1 297.6 396.7 306.1 391.5C314.6 386.4 325.3 386.4 333.8 391.5C342.3 396.7 347.2 406.1 346.6 416C347.2 425.9 342.3 435.3 333.8 440.5C325.3 445.6 314.6 445.6 306.1 440.5C297.6 435.3 292.7 425.9 293.3 416z"/></svg>`
};
const itemToString = item => item.name;
const itemToStringWithSubtype = item => (item.subtype ? item.name + " (" + item.subtype + ")" : item.name);
const itemToStringWithUniqueName = item => item.name + " [" + item.uniqueName.split("/").pop() + "]";
const areItemsUnambiguous = (items, stringifier) => {
const set = new Set();
for (const item of items) {
const str = stringifier(item);
if (set.has(str)) {
return false;
}
set.add(str);
}
return true;
};
function fetchItemList() {
window.itemListPromise = new Promise(resolve => {
const req = $.get("/custom/getItemLists?lang=" + window.lang);
req.done(async data => {
await dictPromise;
document.querySelectorAll('[id^="datalist-"]').forEach(datalist => {
if (datalist.id != "datalist-circuitGameModes" && datalist.id != "datalist-usernames") {
datalist.innerHTML = "";
}
});
const syndicateNone = document.createElement("option");
syndicateNone.value = "";
syndicateNone.textContent = loc("general_none");
document.getElementById("changeSyndicate").innerHTML = "";
document.getElementById("changeSyndicate").appendChild(syndicateNone);
document.getElementById("valenceBonus-innateDamage").innerHTML = "";
const rememberedVarziaOverride = document.getElementById("worldState.varziaOverride").value;
document.getElementById("worldState.varziaOverride").innerHTML = "";
const rememberedNWOverride = document.getElementById("worldState.nightwaveOverride").value;
document.getElementById("worldState.nightwaveOverride").innerHTML = "";
const missingAdditionalDict = new Set();
const getAddDict = locTag => {
const item = data.AdditionalDict.find(i => i.uniqueName === locTag);
if (!item) {
missingAdditionalDict.add(locTag);
return locTag;
}
return item.name
.replaceAll("|OPEN_COLOR|", "") // ArchonCrystal
.replaceAll("|CLOSE_COLOR|", "") // ArchonCrystal
.replaceAll(/<.+>/g, "") // Icons
.replaceAll("| %", "|%") // de
.replaceAll("| %", "|%") // uk
.replaceAll("| с", "|с") // uk
.replaceAll("| м ", "|м ") // uk
.replaceAll("| m ", "|m "); // pl
};
// prettier-ignore
data.archonCrystalUpgrades = {
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeEquilibrium": getAddDict("/Lotus/Language/Items/WarframeModPickupBonusDesc").replaceAll("|val|", "+20"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeEquilibriumMythic": getAddDict("/Lotus/Language/Items/WarframeModPickupBonusDesc").replaceAll("|val|", "+30"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeMeleeCritDamage": getAddDict("/Lotus/Language/Upgrades/MeleeCritDamage").replaceAll("|val|", "+25"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeMeleeCritDamageMythic": getAddDict("/Lotus/Language/Upgrades/MeleeCritDamage").replaceAll("|val|", "+37.5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradePrimaryStatusChance": getAddDict("/Lotus/Language/Upgrades/PrimaryStatusChance").replaceAll("|val|", "+25"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradePrimaryStatusChanceMythic": getAddDict("/Lotus/Language/Upgrades/PrimaryStatusChance").replaceAll("|val|", "+37.5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeSecondaryCritChance": getAddDict("/Lotus/Language/Upgrades/SecondaryCritChance").replaceAll("|val|", "+25"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeSecondaryCritChanceMythic": getAddDict("/Lotus/Language/Upgrades/SecondaryCritChance").replaceAll("|val|", "+37.5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityDuration": getAddDict("/Lotus/Language/Upgrades/AvatarAbilityDurationModDesc").replaceAll("|val|", "+10"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityDurationMythic": getAddDict("/Lotus/Language/Upgrades/AvatarAbilityDurationModDesc").replaceAll("|val|", "+15"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityStrength": getAddDict("/Lotus/Language/Upgrades/AvatarAbilityStrengthModDesc").replaceAll("|val|", "+10"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityStrengthMythic": getAddDict("/Lotus/Language/Upgrades/AvatarAbilityStrengthModDesc").replaceAll("|val|", "+15"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeArmourMax": getAddDict("/Lotus/Language/Upgrades/WarframeArmourMaxFlat").replaceAll("|val|", "+150"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeArmourMaxMythic": getAddDict("/Lotus/Language/Upgrades/WarframeArmourMaxFlat").replaceAll("|val|", "+225"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeBlastProc": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeBlastProcShieldRegen").replaceAll("|val|", "+5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeBlastProcMythic": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeBlastProcShieldRegen").replaceAll("|val|", "+7.5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCastingSpeed": getAddDict("/Lotus/Language/Upgrades/AvatarCastingSpeedModDesc").replaceAll("|val|", "+25"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCastingSpeedMythic": getAddDict("/Lotus/Language/Upgrades/AvatarCastingSpeedModDesc").replaceAll("|val|", "+37.5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCorrosiveDamageBoost": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeDamageBoostFromCorrosive").replaceAll("|val|", "+10"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCorrosiveDamageBoostMythic": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeDamageBoostFromCorrosive").replaceAll("|val|", "+15"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCorrosiveStack": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeMaxCorrosiveStack").replaceAll("|val|", "+2"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCorrosiveStackMythic": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeMaxCorrosiveStack").replaceAll("|val|", "+3"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCritDamageBoost": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeMeleeCritDoubledWithMaxEnergy").replaceAll("|val|", "25").replaceAll("|AMOUNT|", "500"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCritDamageBoostMythic": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeMeleeCritDoubledWithMaxEnergy").replaceAll("|val|", "37").replaceAll("|AMOUNT|", "500"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeElectricDamage": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeElectricDamageFromShards").replaceAll("|val|", "30").replaceAll("|val2|", "+10"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeElectricDamageMythic": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeElectricDamageFromShards").replaceAll("|val|", "45").replaceAll("|val2|", "+15"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeElectricDamageBoost": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeDamageBoostFromElectricity").replaceAll("|val|", "+10"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeElectricDamageBoostMythic": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeDamageBoostFromElectricity").replaceAll("|val|", "+15"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeEnergyMax": getAddDict("/Lotus/Language/Upgrades/WarframeEnergyMaxFlat").replaceAll("|val|", "+50"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeEnergyMaxMythic": getAddDict("/Lotus/Language/Upgrades/WarframeEnergyMaxFlat").replaceAll("|val|", "+75"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeGlobeEffectEnergy": getAddDict("/Lotus/Language/Upgrades/WarframeGlobeEffectEnergy").replaceAll("|val|", "+50"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeGlobeEffectEnergyMythic": getAddDict("/Lotus/Language/Upgrades/WarframeGlobeEffectEnergy").replaceAll("|val|", "+75"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeGlobeEffectHealth": getAddDict("/Lotus/Language/Upgrades/WarframeGlobeEffectHealth").replaceAll("|val|", "+100"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeGlobeEffectHealthMythic": getAddDict("/Lotus/Language/Upgrades/WarframeGlobeEffectHealth").replaceAll("|val|", "+150"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeHealthMax": getAddDict("/Lotus/Language/Upgrades/WarframeHealthMaxFlat").replaceAll("|val|", "+150"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeHealthMaxMythic": getAddDict("/Lotus/Language/Upgrades/WarframeHealthMaxFlat").replaceAll("|val|", "+225"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeHPBoostFromImpact": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeImpactHPIncrease").replaceAll("|val|", "1").replaceAll("|max|", "300"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeHPBoostFromImpactMythic": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeImpactHPIncrease").replaceAll("|val|", "2").replaceAll("|max|", "450"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeParkourVelocity": getAddDict("/Lotus/Language/Upgrades/AvatarParkourBoostModDesc").replaceAll("|val|", "+15"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeParkourVelocityMythic": getAddDict("/Lotus/Language/Upgrades/AvatarParkourBoostModDesc").replaceAll("|val|", "+22.5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRadiationDamageBoost": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeDamageBoostFromRadiation").replaceAll("|val|", "+10"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRadiationDamageBoostMythic": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeDamageBoostFromRadiation").replaceAll("|val|", "+15"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRegen": getAddDict("/Lotus/Language/Upgrades/WarframeHealthRegenFlat").replaceAll("|val|", "+5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRegenMythic": getAddDict("/Lotus/Language/Upgrades/WarframeHealthRegenFlat").replaceAll("|val|", "+7.5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeShieldMax": getAddDict("/Lotus/Language/Upgrades/WarframeShieldMaxFlat").replaceAll("|val|", "+150"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeShieldMaxMythic": getAddDict("/Lotus/Language/Upgrades/WarframeShieldMaxFlat").replaceAll("|val|", "+225"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeStartingEnergy": getAddDict("/Lotus/Language/Upgrades/AvatarSpawnEnergyPercent").replaceAll("|val|", "+30"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeStartingEnergyMythic": getAddDict("/Lotus/Language/Upgrades/AvatarSpawnEnergyPercent").replaceAll("|val|", "+45"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeToxinDamage": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeToxinProcDamage").replaceAll("|val|", "+30"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeToxinDamageMythic": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeToxinProcDamage").replaceAll("|val|", "+45"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeToxinHeal": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeRegenHPOnToxinProc").replaceAll("|val|", "+2"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeToxinHealMythic": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeRegenHPOnToxinProc").replaceAll("|val|", "+3"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWeaponCritBoostFromHeat": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeCritBoostFromHeat").replaceAll("|val|", "1").replaceAll("|max|", "50"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWeaponCritBoostFromHeatMythic": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeCritBoostFromHeat").replaceAll("|val|", "1.5").replaceAll("|max|", "75"),
"/Lotus/Upgrades/Mods/Warframe/AvatarAbilityRangeMod": getAddDict("/Lotus/Language/Upgrades/AvatarAbilityRangeModDesc").replaceAll("|val|", "+7.5"),
"/Lotus/Upgrades/Mods/Warframe/AvatarAbilityEfficiencyMod": getAddDict("/Lotus/Language/Upgrades/AvatarAbilityEfficiencyModDesc").replaceAll("|val|", "+5"),
"/Lotus/Upgrades/Mods/Warframe/AvatarEnergyRegenMod": getAddDict("/Lotus/Language/Upgrades/EnergyRegen").replaceAll("|val|", "+0.5"),
"/Lotus/Upgrades/Mods/Warframe/AvatarEnemyRadarMod": getAddDict("/Lotus/Language/Upgrades/AvatarEnemyRadarModDesc").replaceAll("|val|", "+5"),
"/Lotus/Upgrades/Mods/Warframe/AvatarLootRadarMod": getAddDict("/Lotus/Language/Upgrades/AvatarLootRadarModDesc").replaceAll("|val|", "+7"),
"/Lotus/Upgrades/Mods/Rifle/WeaponAmmoMaxMod": getAddDict("/Lotus/Language/Upgrades/WeaponMaxAmmoModDesc").replaceAll("|val|", "+15"),
"/Lotus/Upgrades/Mods/Aura/EnemyArmorReductionAuraMod": getAddDict("/Lotus/Language/Upgrades/EnemyArmorReductionAuraDesc").replaceAll("|val|", "-3"),
"/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionAmmoMod": getAddDict("/Lotus/Language/Upgrades/DataSpikeOnExecutionAmmo").replaceAll("|AMMO|", "100"),
"/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionHealthDropMod": getAddDict("/Lotus/Language/Upgrades/DataSpikeOnExecutionHealth").replaceAll("|CHANCE|", "100"),
"/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionEnergyDropMod": getAddDict("/Lotus/Language/Upgrades/DataSpikeOnExecutionEnergy").replaceAll("|CHANCE|", "50"),
"/Lotus/Upgrades/Mods/DataSpike/Cipher/OnFailHackResetMod": getAddDict("/Lotus/Language/Upgrades/DataSpikeOnFailRehack").replaceAll("|val|", "+50"),
"/Lotus/Upgrades/Mods/DataSpike/Cipher/DamageReductionOnHackMod": getAddDict("/Lotus/Language/Upgrades/DataSpikeDamageReduceOnHack").replaceAll("|VAL|", "75"),
"/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionReviveCompanionMod": getAddDict("/Lotus/Language/Upgrades/DataSpikeOnExecutionReviveCompanion").replaceAll("|TIME|", "15"),
"/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionParkourSpeedMod": getAddDict("/Lotus/Language/Upgrades/DataSpikeOnExecutionParkour").replaceAll("|VAL|", "75").replaceAll("|DURATION|", "12"),
"/Lotus/Upgrades/Mods/Warframe/AvatarTimeLimitIncreaseMod": getAddDict("/Lotus/Language/Upgrades/TimeLimitIncreaseDesc").replaceAll("|val|", "+8"),
"/Lotus/Upgrades/Mods/DataSpike/Cipher/ElectrifyOnHackMod": getAddDict("/Lotus/Language/Upgrades/DataSpikeElectrifyHack").replaceAll("|RANGE|", "20"),
"/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionTerrifyMod": getAddDict("/Lotus/Language/Upgrades/DataSpikeOnExecutionTerrify").replaceAll("|CHANCE|", "50").replaceAll("|RANGE|", "15").replaceAll("|DURATION|", "8"),
"/Lotus/Upgrades/Mods/DataSpike/Cipher/OnHackLockersMod": getAddDict("/Lotus/Language/Upgrades/DataSpikeOnHackLockers").replaceAll("|COUNT|", "5").replaceAll("|RANGE|", "20"),
"/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionBlindMod": getAddDict("/Lotus/Language/Upgrades/DataSpikeOnExecutionBlind").replaceAll("|RANGE|", "18"),
"/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionDrainPowerMod": getAddDict("/Lotus/Language/Upgrades/DataSpikeOnExecutionDrainPower").replaceAll("|STRENGTH|", "50"),
"/Lotus/Upgrades/Mods/DataSpike/Cipher/OnHackSprintSpeedMod": getAddDict("/Lotus/Language/Upgrades/DataSpikeOnHackSprintSpeed").replaceAll("|VAL|", "75").replaceAll("|DURATION|", "15"),
"/Lotus/Upgrades/Mods/DataSpike/Assassin/SwiftExecuteMod": getAddDict("/Lotus/Language/Upgrades/DataSpikeSwiftMercy").replaceAll("|SPEED|", "50"),
"/Lotus/Upgrades/Mods/DataSpike/Cipher/OnHackInvisMod": getAddDict("/Lotus/Language/Upgrades/DataSpikeOnHackInvis").replaceAll("|DURATION|", "15")
};
window.archonCrystalUpgrades = data.archonCrystalUpgrades;
data.innateDamages = {
InnateElectricityDamage: getAddDict("/Lotus/Language/Game/DT_ELECTRICITY_NoIcon"),
InnateFreezeDamage: getAddDict("/Lotus/Language/Game/DT_FREEZE_NoIcon"),
InnateHeatDamage: getAddDict("/Lotus/Language/Game/DT_FIRE_NoIcon"),
InnateImpactDamage: getAddDict("/Lotus/Language/Game/DT_IMPACT_NoIcon"),
InnateMagDamage: getAddDict("/Lotus/Language/Game/DT_MAGNETIC_NoIcon"),
InnateRadDamage: getAddDict("/Lotus/Language/Game/DT_RADIATION_NoIcon"),
InnateToxinDamage: getAddDict("/Lotus/Language/Game/DT_POISON_NoIcon")
};
// Add mods missing in data sources
data.mods.push(
{
uniqueName: "/Lotus/Upgrades/Mods/Fusers/CommonModFuser",
name: loc("code_fusionCoreCommon"),
fusionLimit: 3
},
{
uniqueName: "/Lotus/Upgrades/Mods/Fusers/UncommonModFuser",
name: loc("code_fusionCoreUncommon")
},
{
uniqueName: "/Lotus/Upgrades/Mods/Fusers/RareModFuser",
name: loc("code_fusionCoreRare")
},
{
uniqueName: "/Lotus/Upgrades/Mods/Fusers/LegacyCommonModFuser",
name: loc("code_ancientFusionCoreCommon"),
fusionLimit: 10
},
{
uniqueName: "/Lotus/Upgrades/Mods/Fusers/LegacyUncommonModFuser",
name: loc("code_ancientFusionCoreUncommon"),
fusionLimit: 10
},
{
uniqueName: "/Lotus/Upgrades/Mods/Fusers/LegacyRareModFuser",
name: loc("code_ancientFusionCoreRare"),
fusionLimit: 10
},
{
uniqueName: "/Lotus/Upgrades/Mods/Fusers/LegendaryModFuser",
name: getAddDict("/Lotus/Language/Items/LegendaryFusionPart"),
fusionLimit: 0
},
{
uniqueName: "/Lotus/Upgrades/CosmeticEnhancers/Peculiars/CyoteMod",
name: getAddDict("/Lotus/Language/Mods/PerculiarCyoteMod")
},
{
uniqueName: "/Lotus/Upgrades/Mods/Warframe/Intermediate/AvatarPickupBonusModIntermediate",
name: getAddDict("/Lotus/Language/Items/WarframeModPickupBonus") + " " + loc("code_badItem")
},
{
uniqueName: "/Lotus/Upgrades/Mods/Warframe/AvatarStaminaCostMultiplierMod",
name: getAddDict("/Lotus/Language/Items/WarframeModStaminaCost")
},
{
uniqueName: "/Lotus/Upgrades/Mods/Warframe/AvatarShieldToStaminaMod",
name: getAddDict("/Lotus/Language/Items/WarframeModShieldToStamina"),
fusionLimit: 3
},
{
uniqueName: "/Lotus/Upgrades/Mods/Warframe/AvatarStaminaMaxMod",
name: getAddDict("/Lotus/Language/Items/WarframeModStaminaMax")
},
{
uniqueName: "/Lotus/Upgrades/Mods/Warframe/AvatarStaminaRechargeRateMod",
name: getAddDict("/Lotus/Language/Items/WarframeModStaminaRechargeRate")
},
{
uniqueName: "/Lotus/Upgrades/Mods/Melee/WeaponStaminaBonusMod",
name: getAddDict("/Lotus/Language/Items/MeleeModStaminaBonus")
}
);
// Add modular weapons
data.OperatorAmps.push({
uniqueName: "/Lotus/Weapons/Sentients/OperatorAmplifiers/OperatorAmpWeapon",
name: getAddDict("/Lotus/Language/Items/OperatorVoidBeam")
});
data.OperatorAmps.push({
uniqueName:
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/OperatorTrainingAmpWeapon",
name: getAddDict("/Lotus/Language/OstronCrafting/OperatorTrainingAmpName")
});
data.Melee.push({
uniqueName: "/Lotus/Weapons/Ostron/Melee/LotusModularWeapon",
name: getAddDict("/Lotus/Language/OstronCrafting/Zaw_Singular")
});
data.LongGuns.push({
uniqueName: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary",
name: getAddDict("/Lotus/Language/SolarisVenus/Kitgun_Singular")
});
data.Pistols.push({
uniqueName: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary",
name: getAddDict("/Lotus/Language/SolarisVenus/Kitgun_Singular")
});
data.MoaPets ??= [];
data.MoaPets.push({
uniqueName: "/Lotus/Types/Friendly/Pets/MoaPets/MoaPetPowerSuit",
name: getAddDict("/Lotus/Language/SolarisVenus/MoaPet_Singular")
});
data.MoaPets.push({
uniqueName: "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetPowerSuit",
name: getAddDict("/Lotus/Language/Pets/ZanukaPetItemName")
});
data.miscitems.push(
{
uniqueName: "/Lotus/Types/Recipes/CosmeticUnenhancerItem",
name: getAddDict("/Lotus/Language/Items/CosmeticUnenhancerName")
},
{
uniqueName: "/Lotus/Types/Items/Research/DojoColors/GenericDojoColorPigment",
name: normalizeText(
getAddDict("/Lotus/Language/Items/DojoColorPigment")
.replaceAll("|COLOUR_NAME|", "")
.replaceAll(" ", "")
)
},
{
uniqueName: "/Lotus/Types/Items/MiscItems/BossNavCode",
name: getAddDict("/Lotus/Language/Items/BossNavCode")
},
{
uniqueName: "/Lotus/Types/Items/MiscItems/Actuator",
name: getAddDict("/Lotus/Language/Items/Actuator")
}
);
data.miscitems.push(
{
uniqueName: "/Lotus/Types/Keys/GolemKeyBlueprint",
name: data.blueprintAndItem.replace(
"|ITEM|",
data.miscitems.find(i => i.uniqueName === "/Lotus/Types/Keys/DerelictGolemKey").name
)
},
{
uniqueName: "/Lotus/Types/Keys/DerelictCaptureKeyBlueprint",
name: data.blueprintAndItem.replace(
"|ITEM|",
data.miscitems.find(i => i.uniqueName === "/Lotus/Types/Keys/DerelictCaptureKey").name
)
},
{
uniqueName: "/Lotus/Types/Keys/DerelictDefenseKeyBlueprint",
name: data.blueprintAndItem.replace(
"|ITEM|",
data.miscitems.find(i => i.uniqueName === "/Lotus/Types/Keys/DerelictDefenseKey").name
)
},
{
uniqueName: "/Lotus/Types/Keys/DerelictExterminateKeyBlueprint",
name: data.blueprintAndItem.replace(
"|ITEM|",
data.miscitems.find(i => i.uniqueName === "/Lotus/Types/Keys/DerelictExterminateKey").name
)
},
{
uniqueName: "/Lotus/Types/Keys/DerelictMobileDefenseKeyBlueprint",
name: data.blueprintAndItem.replace(
"|ITEM|",
data.miscitems.find(i => i.uniqueName === "/Lotus/Types/Keys/DerelictMobileDefenseKey").name
)
},
{
uniqueName: "/Lotus/Types/Keys/DerelictSabotageKeyBlueprint",
name: data.blueprintAndItem.replace(
"|ITEM|",
data.miscitems.find(i => i.uniqueName === "/Lotus/Types/Keys/DerelictSabotageKey").name
)
},
{
uniqueName: "/Lotus/Types/Keys/DerelictSurvivalKeyBlueprint",
name: data.blueprintAndItem.replace(
"|ITEM|",
data.miscitems.find(i => i.uniqueName === "/Lotus/Types/Keys/DerelictSurvivalKey").name
)
},
{
uniqueName: "/Lotus/Types/Recipes/KevinTestRecipe",
name: data.blueprintAndItem.replace("|ITEM|", loc("code_infernoMod"))
},
{
uniqueName: "/Lotus/Types/Recipes/IncendiaryRifleModBlueprint",
name: data.blueprintAndItem.replace("|ITEM|", loc("code_infernoMod"))
}
);
data.VarziaOffers.unshift({
uniqueName: "",
name: loc("disabled")
});
data.Boosters ??= [];
data.Boosters.push({
uniqueName: "/Lotus/Types/Boosters/ReviveBooster",
name: getAddDict("/Lotus/Language/Items/ReviveBoosterName")
});
const itemMap = {
// Generics for rivens
"/Lotus/Weapons/Tenno/Archwing/Primary/ArchGun": { name: loc("code_archgun") },
"/Lotus/Weapons/Tenno/Melee/PlayerMeleeWeapon": { name: loc("code_melee") },
"/Lotus/Weapons/Tenno/Pistol/LotusPistol": { name: loc("code_pistol") },
"/Lotus/Weapons/Tenno/Rifle/LotusRifle": { name: loc("code_rifle") },
"/Lotus/Weapons/Tenno/Shotgun/LotusShotgun": { name: loc("code_shotgun") },
// Modular weapons
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam": {
name: getAddDict("/Lotus/Language/SolarisVenus/Kitgun_Singular")
},
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryLauncher": {
name: getAddDict("/Lotus/Language/SolarisVenus/Kitgun_Singular")
},
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun": {
name: getAddDict("/Lotus/Language/SolarisVenus/Kitgun_Singular")
},
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimarySniper": {
name: getAddDict("/Lotus/Language/SolarisVenus/Kitgun_Singular")
},
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam": {
name: getAddDict("/Lotus/Language/SolarisVenus/Kitgun_Singular")
},
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun": {
name: getAddDict("/Lotus/Language/SolarisVenus/Kitgun_Singular")
},
"/Lotus/Types/Vehicles/Hoverboard/HoverboardSuit": {
name: getAddDict("/Lotus/Language/Hoverboards/Hoverboard_Singular")
},
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetAPowerSuit": {
name: data.ModularParts.find(
i => i.uniqueName === "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA"
).name
},
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetBPowerSuit": {
name: data.ModularParts.find(
i => i.uniqueName === "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadB"
).name
},
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetCPowerSuit": {
name: data.ModularParts.find(
i => i.uniqueName === "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC"
).name
},
"/Lotus/Powersuits/Stalker/Stalker": {
name: getAddDict("/Lotus/Language/Game/Stalker")
},
"/Lotus/Powersuits/Excalibur/DarkExcalibur": {
name: loc("code_darkExcalibur").replace(
"|ITEM|",
data.Suits.find(i => i.uniqueName === "/Lotus/Powersuits/Excalibur/ExcaliburPrime").name
),
exalted: ["/Lotus/Powersuits/Excalibur/DoomSwordCYUmbra"]
},
// U5
"/Lotus/Types/Game/PowerSuit": { name: loc("code_warframe") },
"/Lotus/Types/Game/LotusMeleeWeapon": { name: loc("code_melee") }
};
for (const [type, items] of Object.entries(data)) {
if (type == "archonCrystalUpgrades") {
Object.entries(items).forEach(([uniqueName, name]) => {
const option = document.createElement("option");
option.setAttribute("data-key", uniqueName);
option.value = name;
document.getElementById("datalist-" + type).appendChild(option);
});
} else if (type == "innateDamages") {
Object.entries(items).forEach(([uniqueName, name]) => {
const option = document.createElement("option");
option.value = uniqueName;
option.textContent = name;
document.getElementById("valenceBonus-innateDamage").appendChild(option);
});
} else if (type == "VarziaOffers") {
const select = document.getElementById("worldState.varziaOverride");
items.forEach(item => {
const option = document.createElement("option");
option.value = item.uniqueName;
option.textContent = item.name;
select.appendChild(option);
});
select.value = rememberedVarziaOverride;
} else if (type == "NightwaveTags") {
const select = document.getElementById("worldState.nightwaveOverride");
let oldNWIntermissionLeft = 3;
{
const nwDisabledOption = document.createElement("option");
nwDisabledOption.value = "";
nwDisabledOption.textContent = loc("disabled");
nwDisabledOption.setAttribute("data-loc", "disabled");
select.appendChild(nwDisabledOption);
}
items.forEach(item => {
const option = document.createElement("option");
option.value = item.uniqueName;
option.textContent = item.name;
if (
[
"RadioLegionIntermissionSyndicate",
"RadioLegionIntermission2Syndicate",
"RadioLegionIntermission3Syndicate"
].includes(item.uniqueName)
) {
option.textContent += ` ${"I".repeat(oldNWIntermissionLeft)}`;
oldNWIntermissionLeft--;
}
select.appendChild(option);
});
{
const nwDisableOption = document.createElement("option");
nwDisableOption.value = "disable";
nwDisableOption.textContent = loc("worldState_nightwaveOff");
nwDisableOption.setAttribute("data-loc", "worldState_nightwaveOff");
select.appendChild(nwDisableOption);
}
select.value = rememberedNWOverride;
} else if (type == "Syndicates") {
items.forEach(item => {
if (["ConclaveSyndicate", "NightcapJournalSyndicate"].includes(item.uniqueName)) {
return;
}
if (item.uniqueName.startsWith("RadioLegion")) {
item.name += " (" + item.uniqueName + ")";
}
const option = document.createElement("option");
option.value = item.uniqueName;
option.textContent = item.name;
document.getElementById("changeSyndicate").appendChild(option);
});
} else if (type == "FlavourItems") {
const cursorPrefixes = {
Controller: loc("code_controller"),
MouseGrey: loc("code_mouse"),
MouseLine: loc("code_mouseLine"),
Mouse: loc("code_mouse")
};
items.forEach(item => {
if (item.uniqueName.startsWith("/Lotus/Interface/Graphics/CustomUI/Cursors/")) {
let base = item.uniqueName.replace("/Lotus/Interface/Graphics/CustomUI/Cursors/", "");
for (const key in cursorPrefixes) {
if (base.startsWith(key)) {
const prefix = cursorPrefixes[key];
const suffix = base.slice(key.length);
item.name = prefix + " " + suffix;
break;
}
}
} else if (item.uniqueName.includes("ColourPicker")) {
item.name = getAddDict("/Lotus/Language/Store/ItemColorPalette").replaceAll(
"|ITEM|",
item.name
);
}
if (!item.alwaysAvailable) {
const option = document.createElement("option");
option.setAttribute("data-key", item.uniqueName);
option.value = item.name;
document.getElementById("datalist-" + type).appendChild(option);
}
itemMap[item.uniqueName] = { ...item, type };
});
} else if (type == "WeaponSkins") {
let beardNumber = 1;
items.forEach(item => {
if (item.name == "") {
if (item.uniqueName.includes("/Beards/")) {
item.name = getAddDict(
"/Lotus/Language/Operator/TennoCustomization_DrifterBeardName"
).replaceAll("|INDEX|", beardNumber.toString().padStart(3, "0"));
beardNumber++;
} else {
item.name = item.uniqueName;
}
}
if (!item.alwaysAvailable) {
const option = document.createElement("option");
option.setAttribute("data-key", item.uniqueName);
option.value = item.name;
document.getElementById("datalist-" + type).appendChild(option);
}
itemMap[item.uniqueName] = { ...item, type };
});
} else if (["MissionTypes", "Nodes", "AdditionalDict"].includes(type)) {
items.forEach(item => {
itemMap[item.uniqueName] = { ...item, type };
});
} else if (type != "blueprintAndItem") {
const nameToItems = {};
items.forEach(item => {
item.name = item.name.replace(/<.+>/g, "").trim();
const nameMap = {
"/Lotus/Upgrades/Modules/GrineerMeleeModule":
loc("code_U5Mod").replace("|TYPE|", loc("code_melee")) + " [GrineerMeleeModule]",
"/Lotus/Upgrades/Modules/TennoSwordModule":
loc("code_U5Mod").replace("|TYPE|", loc("code_melee")) + " [TennoSwordModule]",
"/Lotus/Upgrades/Modules/GrineerPistolModule": loc("code_U5Mod").replace(
"|TYPE|",
loc("code_pistol")
),
"/Lotus/Upgrades/Modules/GrineerRifleModule": loc("code_U5Mod").replace(
"|TYPE|",
loc("code_rifle")
),
"/Lotus/Upgrades/Modules/GrineerShotgunModule": loc("code_U5Mod").replace(
"|TYPE|",
loc("code_shotgun")
),
"/Lotus/Upgrades/Modules/OrokinWarframeModule": loc("code_U5Mod").replace(
"|TYPE|",
loc("code_warframe")
),
"/Lotus/Upgrades/Modules/Crafted/IncendiaryRifleMod": loc("code_infernoMod")
};
if (nameMap[item.name]) {
item.name = nameMap[item.name];
}
if (item.name == "") {
//console.log(`changed ${item.uniqueName} name to path`);
item.name = item.uniqueName;
}
if ("badReason" in item) {
if (item.badReason == "starter") {
item.name = loc("code_starter").replaceAll("|MOD|", item.name);
} else if (item.badReason != "notraw") {
item.name += " " + loc("code_badItem");
}
}
nameToItems[item.name] ??= [];
nameToItems[item.name].push(item);
});
items.forEach(item => {
if (type == "ModularParts") {
const supportedModularParts = [
"LWPT_HB_DECK",
"LWPT_HB_ENGINE",
"LWPT_HB_FRONT",
"LWPT_HB_JET",
"LWPT_AMP_OCULUS",
"LWPT_AMP_CORE",
"LWPT_AMP_BRACE",
"LWPT_BLADE",
"LWPT_HILT",
"LWPT_HILT_WEIGHT",
"LWPT_GUN_PRIMARY_HANDLE",
"LWPT_GUN_SECONDARY_HANDLE",
"LWPT_GUN_BARREL",
"LWPT_GUN_CLIP",
"LWPT_MOA_ENGINE",
"LWPT_MOA_PAYLOAD",
"LWPT_MOA_HEAD",
"LWPT_MOA_LEG",
"LWPT_ZANUKA_BODY",
"LWPT_ZANUKA_HEAD",
"LWPT_ZANUKA_LEG",
"LWPT_ZANUKA_TAIL",
"LWPT_CATBROW_ANTIGEN",
"LWPT_CATBROW_MUTAGEN",
"LWPT_KUBROW_ANTIGEN",
"LWPT_KUBROW_MUTAGEN"
];
if (supportedModularParts.includes(item.partType)) {
const option = document.createElement("option");
option.setAttribute("data-key", item.uniqueName);
option.value = item.name;
document
.getElementById("datalist-" + type + "-" + item.partType.slice(5))
.appendChild(option);
}
} else if (item.badReason != "notraw") {
let stringifier = itemToString;
if (nameToItems[item.name].length > 1) {
stringifier = itemToStringWithSubtype;
if (!areItemsUnambiguous(nameToItems[item.name], stringifier)) {
stringifier = itemToStringWithUniqueName;
if (!areItemsUnambiguous(nameToItems[item.name], stringifier)) {
console.log(
`Not adding ${item.uniqueName} to datalist for ${type} due to ambiguous display name: ${item.name}`
);
return;
}
}
}
const option = document.createElement("option");
option.setAttribute("data-key", item.uniqueName);
option.value = stringifier(item);
document.getElementById("datalist-" + type).appendChild(option);
if (item.eligibleForVault) {
const vaultOption = option.cloneNode(true);
document.getElementById("datalist-VaultMiscItems").appendChild(vaultOption);
}
}
itemMap[item.uniqueName] = { ...item, type };
});
}
}
resolve(itemMap);
if (missingAdditionalDict.size) {
console.warn("Missing in AdditionalDict:", [...missingAdditionalDict]);
}
});
});
}
fetchItemList();
const accountCheats = document.querySelectorAll("#account-cheats input[id]");
let inventory_data;
// Assumes that caller revalidates authz
function getInventoryData() {
return new Promise((resolve, reject) => {
if (inventory_data) {
resolve(inventory_data);
} else {
$.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1")
.done(data => {
inventory_data = data;
resolve(inventory_data);
})
.fail(reject);
}
});
}
// Assumes that caller revalidates authz
function updateInventory() {
inventory_data = undefined;
if (
single.getCurrentPath() == "/webui/inventory" ||
single.getCurrentPath().startsWith("/webui/details") ||
single.getCurrentPath() == "/webui/mods" ||
single.getCurrentPath() == "/webui/cheats"
) {
single.loadRoute(single.getCurrentPath());
}
}
const modularWeapons = [
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary",
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam",
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryLauncher",
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun",
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimarySniper",
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary",
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam",
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun",
"/Lotus/Weapons/Ostron/Melee/LotusModularWeapon",
"/Lotus/Weapons/Sentients/OperatorAmplifiers/OperatorAmpWeapon",
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/OperatorTrainingAmpWeapon",
"/Lotus/Types/Vehicles/Hoverboard/HoverboardSuit",
"/Lotus/Types/Friendly/Pets/MoaPets/MoaPetPowerSuit",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetAPowerSuit",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetBPowerSuit",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetCPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/VulpineInfestedCatbrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/HornedInfestedCatbrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/ArmoredInfestedCatbrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/VizierPredatorKubrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/PharaohPredatorKubrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/MedjayPredatorKubrowPetPowerSuit"
];
const U5Mods = [
"/Lotus/Upgrades/Modules/GrineerMeleeModule",
"/Lotus/Upgrades/Modules/GrineerPistolModule",
"/Lotus/Upgrades/Modules/GrineerRifleModule",
"/Lotus/Upgrades/Modules/GrineerShotgunModule",
"/Lotus/Upgrades/Modules/OrokinWarframeModule",
"/Lotus/Upgrades/Modules/TennoSwordModule",
"/Lotus/Upgrades/Modules/Crafted/IncendiaryRifleMod"
];
async function populateInventoryRoute() {
const [data, itemMap] = await Promise.all([awaitAuthz().then(getInventoryData), window.itemListPromise]);
document.getElementById("typeName-tab").classList.remove("active");
document.getElementById("typeName-tab-content").classList.remove("active", "show");
document.getElementById("typeName-type").value = "";
document.getElementById("miscItems-tab").classList.add("active");
document.getElementById("miscItems-tab-content").classList.add("active", "show");
["RegularCredits", "PremiumCredits", "FusionPoints", "PrimeTokens", "CrewShipFusionPoints"].forEach(currency => {
document.getElementById(currency + "-owned").textContent = loc("currency_owned").replaceAll(
"|COUNT|",
data[currency].toLocaleString()
);
});
[
"Suits",
"SpaceSuits",
"Sentinels",
"LongGuns",
"Pistols",
"Melee",
"SpaceGuns",
"SpaceMelee",
"SentinelWeapons",
"Hoverboards",
"OperatorAmps",
"MechSuits",
"MoaPets",
"KubrowPets"
].forEach(category => {
document.getElementById(category + "-list").innerHTML = "";
data[category]
.sort((a, b) => (b.Favorite ? 1 : 0) - (a.Favorite ? 1 : 0))
.forEach(item => {
const tr = document.createElement("tr");
tr.setAttribute("data-item-oid", item.ItemId.$oid);
tr.setAttribute("data-item-type", item.ItemType);
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
if (item.ItemName) {
const pipeIndex = item.ItemName.indexOf("|");
if (pipeIndex != -1) {
td.textContent = item.ItemName.substr(1 + pipeIndex) + " " + td.textContent;
} else {
td.textContent = item.ItemName + " (" + td.textContent + ")";
}
}
if (item.Details?.Name) {
td.textContent = item.Details.Name + " (" + td.textContent + ")";
}
if (item.ModularParts && item.ModularParts.length) {
td.textContent += " [";
item.ModularParts.forEach(part => {
td.textContent += " " + (itemMap[part]?.name ?? part) + ",";
});
td.textContent = td.textContent.slice(0, -1) + " ]";
}
if (
["/Lotus/Powersuits/Stalker/Stalker", "/Lotus/Powersuits/Excalibur/DarkExcalibur"].includes(
item.ItemType
)
) {
td.textContent += " ";
const abbr = document.createElement("abbr");
abbr.innerHTML = icons.note;
abbr.title = loc("code_metadataPatchRequired");
td.appendChild(abbr);
}
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
let maxXP = Math.pow(itemMap[item.ItemType]?.maxLevelCap ?? 30, 2) * 1000;
if (
![
"Suits",
"SpaceSuits",
"Hoverboards",
"MechSuits",
"Sentinels",
"MoaPets",
"KubrowPets"
].includes(category)
) {
maxXP /= 2;
}
let anyExaltedMissingXP = false;
if (item.XP >= maxXP && item.ItemType in itemMap && "exalted" in itemMap[item.ItemType]) {
for (const exaltedType of itemMap[item.ItemType].exalted) {
const exaltedItem = data.SpecialItems.find(x => x.ItemType == exaltedType);
if (exaltedItem) {
const exaltedCap = itemMap[exaltedType]?.type == "weapons" ? 800_000 : 1_600_000;
if (exaltedItem.XP < exaltedCap) {
anyExaltedMissingXP = true;
break;
}
}
}
}
if (
itemMap[item.ItemType]?.maxLevelCap > 30 &&
(item.Polarized ?? 0) < (itemMap[item.ItemType].maxLevelCap - 30) / 2
) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
a.parentNode.removeChild(a);
unlockLevelCap(category, item.ItemId.$oid, (itemMap[item.ItemType].maxLevelCap - 30) / 2);
};
a.title = loc("code_unlockLevelCap");
a.innerHTML = icons.graduationCap;
td.appendChild(a);
}
if (item.XP < maxXP || anyExaltedMissingXP) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
revalidateAuthz().then(async () => {
if (item.XP < maxXP) {
await addGearExp(category, item.ItemId.$oid, maxXP - item.XP);
if (item.AltWeaponModeId) {
const otherCategory = category == "Melee" ? "LongGuns" : "Melee";
await addGearExp(otherCategory, item.AltWeaponModeId.$oid, maxXP - item.XP);
}
}
if ("exalted" in itemMap[item.ItemType]) {
for (const exaltedType of itemMap[item.ItemType].exalted) {
const exaltedItem = data.SpecialItems.find(x => x.ItemType == exaltedType);
if (exaltedItem) {
const exaltedCap =
itemMap[exaltedType]?.type == "weapons" ? 800_000 : 1_600_000;
if (exaltedItem.XP < exaltedCap) {
await addGearExp(
"SpecialItems",
exaltedItem.ItemId.$oid,
exaltedCap - exaltedItem.XP
);
}
}
}
}
updateInventory();
});
};
a.title = loc("code_maxRank");
a.innerHTML = icons.arrowUp;
td.appendChild(a);
}
{
const a = document.createElement("a");
a.href = "/webui/details?productCategory=" + category + "&itemId=" + item.ItemId.$oid;
a.innerHTML = icons.feather;
td.appendChild(a);
}
if (!(item.Features & 8) && modularWeapons.includes(item.ItemType)) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
equipmentFeatures(category, item.ItemId.$oid, 8);
};
a.title = loc("code_gild");
a.innerHTML = icons.star;
td.appendChild(a);
}
if (category == "KubrowPets") {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
maturePet(item.ItemId.$oid, !item.Details.IsPuppy);
};
if (item.Details.IsPuppy) {
a.title = loc("code_mature");
a.innerHTML = icons.person;
} else {
a.title = loc("code_unmature");
a.innerHTML = icons.childReaching;
}
td.appendChild(a);
}
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
const name = prompt(loc("code_renamePrompt"));
if (name !== null) {
renameGear(category, item.ItemId.$oid, name);
}
};
a.title = loc("code_rename");
a.innerHTML = icons.tag;
td.appendChild(a);
}
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
tr.parentNode.removeChild(tr);
disposeOfGear(category, item.ItemId.$oid);
if (item.AltWeaponModeId) {
const otherCategory = category == "Melee" ? "LongGuns" : "Melee";
const otherTr = document.querySelector(
`[data-item-oid="${item.AltWeaponModeId.$oid}"]`
);
if (otherTr) {
otherTr.parentNode.removeChild(otherTr);
disposeOfGear(otherCategory, item.AltWeaponModeId.$oid);
}
}
};
a.title = loc("code_remove");
a.innerHTML = icons.trash;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById(category + "-list").appendChild(tr);
});
});
document.getElementById("EvolutionProgress-list").innerHTML = "";
data.EvolutionProgress?.forEach(item => {
const maxRank = permanentEvolutionWeapons.has(item.ItemType) ? 5 : 4;
const tr = document.createElement("tr");
tr.setAttribute("data-item-type", item.ItemType);
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
if (item.Rank != null) {
td.textContent += " | " + loc("code_rank") + ": [" + item.Rank + "/" + maxRank + "]";
}
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
if (item.Rank < maxRank) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
setEvolutionProgress([{ ItemType: item.ItemType, Rank: maxRank }]);
};
a.title = loc("code_maxRank");
a.innerHTML = icons.arrowUp;
td.appendChild(a);
}
if (item.Rank < maxRank) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
setEvolutionProgress([{ ItemType: item.ItemType, Rank: item.Rank + 1 }]);
};
a.title = loc("code_rankUp");
a.innerHTML = icons.angleUp;
td.appendChild(a);
}
if ((permanentEvolutionWeapons.has(item.ItemType) && item.Rank > 0) || item.Rank > 1) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
setEvolutionProgress([{ ItemType: item.ItemType, Rank: item.Rank - 1 }]);
};
a.title = loc("code_rankDown");
a.innerHTML = icons.angleDown;
td.appendChild(a);
}
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
setEvolutionProgress([{ ItemType: item.ItemType, Delete: true }]);
};
a.title = loc("code_remove");
a.innerHTML = icons.trash;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("EvolutionProgress-list").appendChild(tr);
});
document.getElementById("Boosters-list").innerHTML = "";
data.Boosters.forEach(item => {
if (item.ExpiryDate < Math.floor(Date.now() / 1000)) {
// Booster has expired, skip it
return;
}
const tr = document.createElement("tr");
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
tr.appendChild(td);
if (item.ItemType == "/Lotus/Types/Boosters/ReviveBooster") {
td.textContent += " ";
const abbr = document.createElement("abbr");
abbr.innerHTML = icons.note;
abbr.title = loc("code_reviveBoosterDesc");
td.appendChild(abbr);
}
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
{
const form = document.createElement("form");
form.style.display = "inline-block";
form.onsubmit = function (event) {
event.preventDefault();
const maxDate = new Date(input.max);
const selectedDate = new Date(input.value);
if (selectedDate > maxDate) {
input.value = maxDate.toISOString().slice(0, 16);
}
doChangeBoosterExpiry(item.ItemType, input);
};
const input = document.createElement("input");
input.type = "datetime-local";
input.classList = "form-control form-control-sm";
input.value = formatDatetime("%Y-%m-%d %H:%M:%s", item.ExpiryDate * 1000);
input.max = "2038-01-19T03:14";
input.onblur = function () {
const maxDate = new Date(input.max);
const selectedDate = new Date(input.value);
if (selectedDate > maxDate) {
input.value = maxDate.toISOString().slice(0, 16);
}
doChangeBoosterExpiry(item.ItemType, input);
};
form.appendChild(input);
td.appendChild(form);
}
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
setBooster(item.ItemType, 0);
};
a.title = loc("code_remove");
a.innerHTML = icons.trash;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("Boosters-list").appendChild(tr);
});
document.getElementById("FlavourItems-list").innerHTML = "";
data.FlavourItems.forEach(item => {
const tr = document.createElement("tr");
tr.setAttribute("data-item-type", item.ItemType);
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
tr.parentNode.removeChild(tr);
removeCustomization(item.ItemType);
};
a.title = loc("code_remove");
a.innerHTML = icons.trash;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("FlavourItems-list").appendChild(tr);
});
document.getElementById("WeaponSkins-list").innerHTML = "";
data.WeaponSkins.forEach(item => {
if (item.ItemId.$oid.startsWith("ca70ca70ca70ca70")) return;
const tr = document.createElement("tr");
{
const td = document.createElement("td");
const name = itemMap[item.ItemType]?.name?.trim();
td.textContent = name || item.ItemType;
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
document.getElementById("WeaponSkins-list").removeChild(tr);
disposeOfGear("WeaponSkins", item.ItemId.$oid);
};
a.title = loc("code_remove");
a.innerHTML = icons.trash;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("WeaponSkins-list").appendChild(tr);
});
if (data.CrewShipHarnesses?.length) {
window.plexus = {
id: data.CrewShipHarnesses[0].ItemId.$oid,
xp: data.CrewShipHarnesses[0].XP
};
}
document.getElementById("ShipDecorations-list").innerHTML = "";
data.ShipDecorations.forEach(item => {
if (item.ItemCount > 0) {
const tr = document.createElement("tr");
tr.setAttribute("data-item-type", item.ItemType);
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
if (item.ItemCount > 1) {
td.innerHTML +=
" <span title='" + loc("code_count") + "'>🗍 " + parseInt(item.ItemCount) + "</span>";
}
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
tr.parentNode.removeChild(tr);
removeShipDecoration(item.ItemType, item.ItemCount);
};
a.title = loc("code_remove");
a.innerHTML = icons.trash;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("ShipDecorations-list").appendChild(tr);
}
});
document.getElementById("QuestKeys-list").innerHTML = "";
data.QuestKeys.forEach(item => {
const tr = document.createElement("tr");
tr.setAttribute("data-item-type", item.ItemType);
const run = item.Progress[0]?.c ?? 0;
const stage = run == 0 ? item.Progress.length : item.Progress.map(p => p.c ?? 0).lastIndexOf(run);
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
if (!item.Completed) {
td.textContent +=
" | " + loc("code_stage") + ": [" + stage + "/" + itemMap[item.ItemType]?.chainLength + "]";
} else {
td.textContent += " | " + loc("code_completed");
}
if (run > 0) {
td.textContent += " | " + loc("code_replays") + ": " + (run + 1);
}
if (data.ActiveQuest == item.ItemType) td.textContent += " | " + loc("code_active");
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
if (data.ActiveQuest == item.ItemType && !item.Completed) {
console.log(data.ActiveQuest);
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(doQuestUpdate, "setInactive", item.ItemType);
};
a.title = loc("code_setInactive");
a.innerHTML = icons.circleStop;
td.appendChild(a);
}
if (stage > 0) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(doQuestUpdate, "resetKey", item.ItemType);
};
a.title = loc("code_reset");
a.innerHTML = icons.arrowRotateRight;
td.appendChild(a);
}
if (itemMap[item.ItemType]?.chainLength > stage && !item.Completed) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(doQuestUpdate, "completeKey", item.ItemType);
};
a.title = loc("code_complete");
a.innerHTML = icons.check;
td.appendChild(a);
}
if (stage > 0 && itemMap[item.ItemType]?.chainLength > 1) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(doQuestUpdate, "prevStage", item.ItemType);
};
a.title = loc("code_prevStage");
a.innerHTML = icons.angleLeft;
td.appendChild(a);
}
if (
itemMap[item.ItemType]?.chainLength > stage &&
!item.Completed &&
itemMap[item.ItemType]?.chainLength > 1
) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(doQuestUpdate, "nextStage", item.ItemType);
};
a.title = loc("code_nextStage");
a.innerHTML = icons.angleRight;
td.appendChild(a);
}
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(doQuestUpdate, "deleteKey", item.ItemType);
};
a.title = loc("code_remove");
a.innerHTML = icons.trash;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("QuestKeys-list").appendChild(tr);
});
}
single.getRoute("/webui/inventory").on("beforeload", populateInventoryRoute);
single.getRoute("/webui/mods").on("beforeload", async function () {
const [data, itemMap] = await Promise.all([awaitAuthz().then(getInventoryData), window.itemListPromise]);
document.getElementById("riven-list").innerHTML = "";
document.getElementById("mods-list").innerHTML = "";
data.Upgrades.forEach(item => {
if (item.ItemType.substr(0, 32) == "/Lotus/Upgrades/Mods/Randomized/") {
const rivenType = item.ItemType.substr(32);
const fingerprint = JSON.parse(item.UpgradeFingerprint);
if ("buffs" in fingerprint) {
// Riven has been revealed?
const tr = document.createElement("tr");
{
const td = document.createElement("td");
td.textContent = itemMap[fingerprint.compat]?.name ?? fingerprint.compat;
td.textContent += " ";
try {
td.textContent += RivenParser.parseRiven(rivenType, fingerprint, 1).name;
} catch (e) {
console.warn("malformed riven", { rivenType, fingerprint });
td.textContent += " [Malformed Riven]";
}
td.innerHTML +=
" <span title='" + loc("code_buffsNumber") + "'>▲ " + fingerprint.buffs.length + "</span>";
td.innerHTML +=
" <span title='" + loc("code_cursesNumber") + "'>▼ " + fingerprint.curses.length + "</span>";
td.innerHTML +=
" <span title='" + loc("code_rerollsNumber") + "'>⟳ " + (fingerprint.rerolls ?? 0) + "</span>";
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
{
const a = document.createElement("a");
a.href =
"riven-tool/#" +
encodeURIComponent(
JSON.stringify({
rivenType: rivenType,
omegaAttenuation: 1,
fingerprint: fingerprint
})
);
a.target = "_blank";
a.title = loc("code_viewStats");
a.innerHTML = icons.chartSimple;
td.appendChild(a);
}
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
document.getElementById("riven-list").removeChild(tr);
disposeOfGear("Upgrades", item.ItemId.$oid);
};
a.title = loc("code_remove");
a.innerHTML = icons.trash;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("riven-list").appendChild(tr);
return;
}
}
const tr = document.createElement("tr");
const rank = parseInt(JSON.parse(item.UpgradeFingerprint).lvl);
const maxRank = itemMap[item.ItemType]?.fusionLimit ?? 5;
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
if (
U5Mods.includes(item.ItemType) &&
item.ItemType != "/Lotus/Upgrades/Modules/Crafted/IncendiaryRifleMod"
) {
if (item.UpgradeFingerprint) {
const fingerprint = JSON.parse(item.UpgradeFingerprint);
if (fingerprint.fits) {
td.textContent = loc("code_U5Mod").replace(
"|TYPE|",
itemMap[fingerprint.fits]?.name ?? fingerprint.fits
);
}
if (item.ItemType.endsWith("GrineerMeleeModule") || item.ItemType.endsWith("TennoSwordModule")) {
td.textContent += " [" + item.ItemType.split("/").pop() + "]";
}
}
}
if (itemMap[item.ItemType]?.badReason == "notraw") {
// Assuming this is a riven with a pending challenge, so rank would be N/A, but otherwise it's fine.
} else if (!Number.isNaN(rank)) {
td.innerHTML += " <span title='" + loc("code_rank") + "'>★ " + rank + "/" + maxRank + "</span>";
}
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
if (rank < maxRank) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
setFingerprint(item.ItemType, item.ItemId, { lvl: maxRank });
};
a.title = loc("code_maxRank");
a.innerHTML = icons.arrowUp;
td.appendChild(a);
}
if (U5Mods.includes(item.ItemType)) {
const a = document.createElement("a");
a.href = "/webui/details?productCategory=Upgrades&itemId=" + item.ItemId.$oid;
a.innerHTML = icons.feather;
td.appendChild(a);
}
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
document.getElementById("mods-list").removeChild(tr);
disposeOfGear("Upgrades", item.ItemId.$oid);
};
a.title = loc("code_remove");
a.innerHTML = icons.trash;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("mods-list").appendChild(tr);
});
data.RawUpgrades.forEach(item => {
if (item.ItemCount > 0) {
const maxRank = itemMap[item.ItemType]?.fusionLimit ?? 5;
const tr = document.createElement("tr");
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
if (maxRank > 0) {
td.innerHTML += " <span title='" + loc("code_rank") + "'>★ 0/" + maxRank + "</span>";
}
if (item.ItemCount > 1) {
td.innerHTML +=
" <span title='" + loc("code_count") + "'>🗍 " + parseInt(item.ItemCount) + "</span>";
}
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
if (maxRank != 0) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
setFingerprint(item.ItemType, item.LastAdded, { lvl: maxRank });
};
a.title = loc("code_maxRank");
a.innerHTML = icons.arrowUp;
td.appendChild(a);
}
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
document.getElementById("mods-list").removeChild(tr);
disposeOfItems("Upgrades", item.ItemType, item.ItemCount);
};
a.title = loc("code_remove");
a.innerHTML = icons.trash;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("mods-list").appendChild(tr);
}
});
});
dictPromise.then(() => {
const invenotryHeaders = document.querySelectorAll(".card-header[data-cardtype]");
invenotryHeaders.forEach(elm => {
elm.innerHTML = "";
elm.classList.remove(...elm.classList);
elm.classList.add("card-header", "d-flex", "justify-content-between", "align-items-center");
const cardType = elm.dataset.cardtype;
{
const h5 = document.createElement("h5");
h5.classList.add("mb-0");
const locStr = `inventory_${cardType.charAt(0).toLowerCase() + cardType.slice(1)}`;
h5.textContent = loc(locStr);
h5.setAttribute("data-loc", locStr);
elm.appendChild(h5);
}
{
const butDiv = document.createElement("div");
butDiv.classList.add("d-flex", "gap-2");
if (!["MoaPets", "Hoverboards", "Boosters"].includes(cardType)) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
if (cardType == "EvolutionProgress") {
debounce(addMissingEvolutionProgress);
} else {
debounce(addMissingEquipment, [cardType]);
}
};
a.setAttribute("data-loc-title", "inventory_bulkAdd");
a.title = loc("inventory_bulkAdd");
a.innerHTML = icons.plus;
butDiv.appendChild(a);
}
if (!["QuestKeys", "Boosters", "FlavourItems", "ShipDecorations", "WeaponSkins"].includes(cardType)) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
if (cardType == "EvolutionProgress") {
debounce(maxRankAllEvolutions);
} else {
debounce(maxRankAllEquipment, [cardType]);
}
};
a.setAttribute("data-loc-title", "inventory_bulkRankUp");
a.title = loc("inventory_bulkRankUp");
a.innerHTML = icons.arrowUp;
butDiv.appendChild(a);
}
if (cardType == "QuestKeys") {
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
doBulkQuestUpdate("completeAll");
};
a.setAttribute("data-loc-title", "inventory_bulkComplete");
a.title = loc("inventory_bulkComplete");
a.innerHTML = icons.check;
butDiv.appendChild(a);
}
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
doBulkQuestUpdate("resetAll");
};
a.setAttribute("data-loc-title", "inventory_bulkReset");
a.title = loc("inventory_bulkReset");
a.innerHTML = icons.arrowRotateRight;
butDiv.appendChild(a);
}
}
if (cardType != "Boosters") {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
removeItems(cardType);
};
a.setAttribute("data-loc-title", "inventory_bulkRemove");
a.title = loc("inventory_bulkRemove");
a.innerHTML = icons.trash;
butDiv.appendChild(a);
}
elm.appendChild(butDiv);
}
});
const guildHeaders = document.querySelectorAll(".card-header[data-guildCardType]");
guildHeaders.forEach(elm => {
elm.innerHTML = "";
elm.classList.remove(...elm.classList);
elm.classList.add("card-header", "d-flex", "justify-content-between", "align-items-center");
const cardType = elm.dataset.guildcardtype;
{
const h5 = document.createElement("h5");
h5.classList.add("mb-0");
const locStr = `guildView_${cardType.charAt(0).toLowerCase() + cardType.slice(1)}`;
h5.textContent = loc(locStr);
h5.setAttribute("data-loc", locStr);
elm.appendChild(h5);
}
{
const butDiv = document.createElement("div");
butDiv.classList.add("d-flex", "gap-2");
if (cardType != "VaultMiscItems") {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
if (cardType != "TechProjects") {
debounce(addMissingVaultItems, cardType);
} else {
debounce(addMissingTechProjects);
}
};
a.setAttribute("data-loc-title", "inventory_bulkAdd");
a.title = loc("inventory_bulkAdd");
a.innerHTML = icons.plus;
a.id = `${cardType}-bulkAdd`;
butDiv.appendChild(a);
}
if (cardType == "TechProjects") {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(fundAllTechProjects);
};
a.setAttribute("data-loc-title", "guildView_bulkFund");
a.title = loc("guildView_bulkFund");
a.innerHTML = icons.arrowUp;
a.id = `${cardType}-bulkFund`;
butDiv.appendChild(a);
}
if (cardType == "TechProjects") {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(completeAllTechProjects);
};
a.setAttribute("data-loc-title", "inventory_bulkComplete");
a.title = loc("inventory_bulkComplete");
a.innerHTML = icons.forward;
a.id = `${cardType}-bulkComplete`;
butDiv.appendChild(a);
}
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
if (cardType != "TechProjects") {
debounce(bulkRemoveVaultItems, cardType);
} else {
debounce(bulkRemoveTechProjects);
}
};
a.setAttribute("data-loc-title", "inventory_bulkRemove");
a.title = loc("inventory_bulkRemove");
a.innerHTML = icons.trash;
a.id = `${cardType}-bulkRemove`;
butDiv.appendChild(a);
}
elm.appendChild(butDiv);
}
});
});
function updateGuild() {
guild_data = null;
if (single.getCurrentPath() == "/webui/clan") {
single.loadRoute(single.getCurrentPath());
}
}
function changeGuildRank(guildId, targetId, rankChange) {
revalidateAuthz().then(() => {
const req = $.get(
`/api/changeGuildRank.php?${window.authz}&guildId=${guildId}&targetId=${targetId}&rankChange=${rankChange}`
);
req.done(() => {
updateGuild();
});
});
}
function kickFromGuild(accountId) {
revalidateAuthz().then(() => {
const req = $.post({
url: "/api/removeFromGuild.php?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/octet-stream",
data: JSON.stringify({
userId: accountId
})
});
req.done(() => {
updateGuild();
});
});
}
function kickFromAlliance(guildId) {
revalidateAuthz().then(() => {
const req = $.get("/api/removeFromAlliance.php?" + window.authz + "&guildId=" + guildId);
req.done(() => {
updateGuild();
});
});
}
function getKey(input) {
return document
.getElementById(input.getAttribute("list"))
.querySelector("[value='" + input.value.replaceAll("'", "\\'") + "']")
?.getAttribute("data-key");
}
function doAcquireEquipment(category) {
const uniqueName = getKey(document.getElementById("acquire-type-" + category));
if (!uniqueName) {
$("#acquire-type-" + category)
.addClass("is-invalid")
.focus();
return;
}
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/addItems?" + window.authz,
contentType: "application/json",
data: JSON.stringify([
{
ItemType: uniqueName,
ItemCount: 1
}
])
});
req.done(modifiedPaths => {
document.getElementById("acquire-type-" + category).value = "";
if (modifiedPaths.length) {
updateInventory();
} else {
toast(loc("code_nothingToDo"));
}
});
});
}
function removeCustomization(uniqueName) {
revalidateAuthz().then(() => {
const req = $.get("/custom/removeCustomization?" + window.authz + "&itemType=" + uniqueName);
req.done(() => {
inventory_data.FlavourItems.splice(
inventory_data.FlavourItems.findIndex(x => x.ItemType == uniqueName),
1
);
});
});
}
function removeIsNew() {
revalidateAuthz().then(() => {
$.get("/custom/removeIsNew?" + window.authz);
});
}
function getRequiredParts(category, WeaponType) {
switch (category) {
case "Hoverboards":
return ["HB_DECK", "HB_ENGINE", "HB_FRONT", "HB_JET"];
case "OperatorAmps":
return ["AMP_OCULUS", "AMP_CORE", "AMP_BRACE"];
case "Melee":
return ["BLADE", "HILT", "HILT_WEIGHT"];
case "LongGuns":
return ["GUN_BARREL", "GUN_PRIMARY_HANDLE", "GUN_CLIP"];
case "Pistols":
return ["GUN_BARREL", "GUN_SECONDARY_HANDLE", "GUN_CLIP"];
case "MoaPets":
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) {
Parts.push(partName);
}
});
if (Parts.length != requiredParts.length) {
let isFirstPart = true;
requiredParts.forEach(part => {
const partSelector = document.getElementById("acquire-type-" + category + "-" + part);
if (!getKey(partSelector)) {
if (isFirstPart) {
isFirstPart = false;
$("#acquire-type-" + category + "-" + part)
.addClass("is-invalid")
.focus();
} else {
$("#acquire-type-" + category + "-" + part).addClass("is-invalid");
}
}
});
} else {
const mapping = {
LongGuns: {
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelAPart":
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun",
"/Lotus/Weapons/Infested/Pistols/InfKitGun/Barrels/InfBarrelEgg/InfModularBarrelEggPart":
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun",
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelBPart":
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary",
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelCPart":
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary",
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelDPart":
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam",
"/Lotus/Weapons/Infested/Pistols/InfKitGun/Barrels/InfBarrelBeam/InfModularBarrelBeamPart":
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam"
},
Pistols: {
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelAPart":
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun",
"/Lotus/Weapons/Infested/Pistols/InfKitGun/Barrels/InfBarrelEgg/InfModularBarrelEggPart":
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun",
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelBPart":
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary",
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelCPart":
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary",
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelDPart":
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam",
"/Lotus/Weapons/Infested/Pistols/InfKitGun/Barrels/InfBarrelBeam/InfModularBarrelBeamPart":
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam"
},
MoaPets: {
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA":
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetAPowerSuit",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadB":
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetBPowerSuit",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC":
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetCPowerSuit"
}
};
Parts.forEach(part => {
const categoryMap = mapping[category];
if (categoryMap && categoryMap[part]) {
WeaponType = categoryMap[part];
}
});
if (category == "KubrowPets") Parts.unshift(WeaponType);
revalidateAuthz().then(() => {
const req = $.post({
url: "/api/modularWeaponCrafting.php?" + window.authz,
contentType: "application/octet-stream",
data: JSON.stringify({
WeaponType,
Parts,
isWebUi: true
})
});
req.done(() => {
const mainInput = document.getElementById("acquire-type-" + category);
if (mainInput) {
mainInput.value = "";
if (category === "MoaPets") {
document.getElementById("modular-MoaPets-Zanuka").classList.add("d-none");
document.getElementById("modular-MoaPets-Moa").classList.add("d-none");
} else if (category === "KubrowPets") {
document.getElementById("modular-KubrowPets-Catbrow").classList.add("d-none");
document.getElementById("modular-KubrowPets-Kubrow").classList.add("d-none");
} else {
document.getElementById("modular-" + category).classList.add("d-none");
}
}
requiredParts.forEach(part => {
document.getElementById("acquire-type-" + category + "-" + part).value = "";
});
updateInventory();
});
});
}
}
function doAcquireEvolution() {
const uniqueName = getKey(document.getElementById("acquire-type-EvolutionProgress"));
if (!uniqueName) {
$("#acquire-type-EvolutionProgress").addClass("is-invalid").focus();
return;
}
if (!document.querySelector("#EvolutionProgress-list [data-item-type='" + uniqueName + "']")) {
setEvolutionProgress([{ ItemType: uniqueName, Rank: permanentEvolutionWeapons.has(uniqueName) ? 0 : 1 }]);
} else {
toast(loc("code_nothingToDo"));
}
}
$(document).on("input", "input, textarea", function () {
$(this).removeClass("is-invalid");
});
function dispatchAddItemsRequestsBatch(requests) {
return new Promise(resolve => {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/addItems?" + window.authz,
contentType: "application/json",
data: JSON.stringify(requests)
});
req.done(() => {
updateInventory();
resolve();
});
});
});
}
function addMissingEquipment(categories) {
const requests = [];
categories.forEach(category => {
document.querySelectorAll("#datalist-" + category + " option").forEach(elm => {
if (
!document.querySelector(
"#" + category + "-list [data-item-type='" + elm.getAttribute("data-key") + "']"
)
) {
if (!webUiModularWeapons.includes(elm.getAttribute("data-key"))) {
let ItemCount = 1;
if (category == "ShipDecorations") ItemCount = 999999;
requests.push({ ItemType: elm.getAttribute("data-key"), ItemCount });
}
}
});
});
if (requests.length != 0 && window.confirm(loc("code_addItemsConfirm").replaceAll("|COUNT|", requests.length))) {
return dispatchAddItemsRequestsBatch(requests);
}
}
function addMissingDangerRooms() {
revalidateAuthz().then(() => {
getInventoryData().then(data => {
const requests = [];
window.itemListPromise.then(itemMap => {
const allItemKeys = Object.entries(itemMap)
.filter(([_key, item]) => item.parentName === "/Lotus/Types/Items/DangerRoom/DangerRoomTile")
.map(([key]) => key);
allItemKeys.push("/Lotus/Types/Items/MiscItems/DangerRoomKey"); // also give access to the simulacrum itself
const existingItems = new Set(
data.MiscItems.filter(item => item.ItemCount > 0).map(item => item.ItemType)
);
allItemKeys.forEach(item => {
if (!existingItems.has(item)) {
requests.push({ ItemType: item, ItemCount: 1 });
}
});
if (requests.length == 0) {
toast(loc("code_nothingToDo"));
} else if (window.confirm(loc("code_addItemsConfirm").replaceAll("|COUNT|", requests.length))) {
return dispatchAddItemsRequestsBatch(requests);
}
});
});
});
}
function addVaultItem(vaultType) {
const ItemType = getKey(document.getElementById(`acquire-type-${vaultType}`));
if (!ItemType) {
$(`#acquire-type-${vaultType}`).addClass("is-invalid").focus();
return;
}
const ItemCount = ["VaultMiscItems", "VaultShipDecorations"].includes(vaultType)
? parseInt($(`#${vaultType}-count`).val())
: 1;
if (ItemCount != 0 && !Number.isNaN(ItemCount)) {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/addVaultTypeCount?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/json",
data: JSON.stringify({
vaultType,
items: [
{
ItemType,
ItemCount
}
]
})
});
req.done(() => {
document.getElementById(`acquire-type-${vaultType}`).value = "";
updateGuild();
});
});
}
}
function removeVaultItem(vaultType, ItemType, ItemCount) {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/addVaultTypeCount?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/json",
data: JSON.stringify({
vaultType,
items: [
{
ItemType,
ItemCount
}
]
})
});
req.done(() => {
updateGuild();
});
});
}
function addGuildTechProject() {
const uniqueName = getKey(document.getElementById("acquire-type-TechProjects"));
if (!uniqueName) {
$("#acquire-type-TechProjects").addClass("is-invalid").focus();
return;
}
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/addTechProject?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/json",
data: JSON.stringify([
{
ItemType: uniqueName
}
])
});
req.done(() => {
document.getElementById("acquire-type-TechProjects").value = "";
updateGuild();
});
});
}
function removeGuildTechProject(uniqueName) {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/removeTechProject?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/json",
data: JSON.stringify([
{
ItemType: uniqueName
}
])
});
req.done(() => {
updateGuild();
});
});
}
function completeGuildTechProject(uniqueName) {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/completeTechProject?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/json",
data: JSON.stringify([
{
ItemType: uniqueName
}
])
});
req.done(() => {
updateGuild();
});
});
}
function fundGuildTechProject(uniqueName) {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/fundTechProject?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/json",
data: JSON.stringify([
{
ItemType: uniqueName
}
])
});
req.done(() => {
updateGuild();
});
});
}
function dispatchAddVaultItemsBatch(requests, vaultType) {
return new Promise(resolve => {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/addVaultTypeCount?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/json",
data: JSON.stringify({
vaultType,
items: requests
})
});
req.done(() => {
updateGuild();
resolve();
});
});
});
}
function addMissingVaultItems(vaultType) {
const requests = [];
const datalist = vaultType === "VaultShipDecorations" ? "ShipDecorations" : vaultType;
document.querySelectorAll(`#datalist-${datalist} option`).forEach(elm => {
if (!document.querySelector(`#${vaultType}-list [data-item-type='${elm.getAttribute("data-key")}']`)) {
let ItemCount = 1;
if (vaultType == "VaultShipDecorations") ItemCount = 999999;
requests.push({ ItemType: elm.getAttribute("data-key"), ItemCount });
}
});
if (
requests.length != 0 &&
window.confirm(loc("code_addVaultItemsConfirm").replaceAll("|COUNT|", requests.length))
) {
return dispatchAddVaultItemsBatch(requests, vaultType);
}
}
function dispatchAddTechProjectsBatch(requests) {
return new Promise(resolve => {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/addTechProject?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/json",
data: JSON.stringify(requests)
});
req.done(() => {
updateGuild();
resolve();
});
});
});
}
function addMissingTechProjects() {
const requests = [];
document.querySelectorAll("#datalist-TechProjects option").forEach(elm => {
if (!document.querySelector("#TechProjects-list [data-item-type='" + elm.getAttribute("data-key") + "']")) {
requests.push({ ItemType: elm.getAttribute("data-key") });
}
});
if (
requests.length != 0 &&
window.confirm(loc("code_addTechProjectsConfirm").replaceAll("|COUNT|", requests.length))
) {
return dispatchAddTechProjectsBatch(requests);
}
}
function bulkRemoveVaultItems(vaultType) {
const requests = [];
const datalist = vaultType === "VaultShipDecorations" ? "ShipDecorations" : vaultType;
document.querySelectorAll(`#datalist-${datalist} option`).forEach(elm => {
if (document.querySelector(`#${vaultType}-list [data-item-type='${elm.getAttribute("data-key")}']`)) {
let ItemCount = 1;
if (vaultType == "VaultShipDecorations") ItemCount = 999999;
requests.push({ ItemType: elm.getAttribute("data-key"), ItemCount: ItemCount * -1 });
}
});
if (
requests.length != 0 &&
window.confirm(loc("code_removeVaultItemsConfirm").replaceAll("|COUNT|", requests.length))
) {
return dispatchAddVaultItemsBatch(requests, vaultType);
}
}
function dispatchRemoveTechProjectsBatch(requests) {
return new Promise(resolve => {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/removeTechProject?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/json",
data: JSON.stringify(requests)
});
req.done(() => {
updateGuild();
resolve();
});
});
});
}
function bulkRemoveTechProjects() {
const requests = [];
document.querySelectorAll("#datalist-TechProjects option").forEach(elm => {
if (document.querySelector("#TechProjects-list [data-item-type='" + elm.getAttribute("data-key") + "']")) {
requests.push({ ItemType: elm.getAttribute("data-key") });
}
});
if (
requests.length != 0 &&
window.confirm(loc("code_removeTechProjectsConfirm").replaceAll("|COUNT|", requests.length))
) {
return dispatchRemoveTechProjectsBatch(requests);
}
}
function dispatchFundTechProjectsBatch(requests) {
return new Promise(resolve => {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/fundTechProject?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/json",
data: JSON.stringify(requests)
});
req.done(() => {
updateGuild();
resolve();
});
});
});
}
function fundAllTechProjects() {
revalidateAuthz().then(() => {
getGuildData().done(data => {
const requests = [];
data.TechProjects ??= [];
data.TechProjects.forEach(techProject => {
if (techProject.State != 1) {
requests.push({
ItemType: techProject.ItemType
});
}
});
if (Object.keys(requests).length > 0) {
return dispatchFundTechProjectsBatch(requests);
}
});
});
}
function dispatchCompleteTechProjectsBatch(requests) {
return new Promise(resolve => {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/completeTechProject?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/json",
data: JSON.stringify(requests)
});
req.done(() => {
updateGuild();
resolve();
});
});
});
}
function completeAllTechProjects() {
revalidateAuthz().then(() => {
getGuildData().done(data => {
const requests = [];
data.TechProjects ??= [];
data.TechProjects.forEach(techProject => {
if (techProject.State == 1 && new Date(techProject.CompletionDate) > new Date()) {
requests.push({
ItemType: techProject.ItemType
});
}
});
if (Object.keys(requests).length > 0) {
return dispatchCompleteTechProjectsBatch(requests);
}
});
});
}
async function addMissingHelminthRecipes() {
await revalidateAuthz();
await fetch("/custom/addMissingHelminthBlueprints?" + window.authz);
}
function addMissingEvolutionProgress() {
const requests = [];
document.querySelectorAll("#datalist-EvolutionProgress option").forEach(elm => {
const uniqueName = elm.getAttribute("data-key");
if (!document.querySelector("#EvolutionProgress-list [data-item-type='" + uniqueName + "']")) {
requests.push({ ItemType: uniqueName, Rank: permanentEvolutionWeapons.has(uniqueName) ? 0 : 1 });
}
});
if (requests.length == 0) {
toast(loc("code_nothingToDo"));
} else if (window.confirm(loc("code_addItemsConfirm").replaceAll("|COUNT|", requests.length))) {
setEvolutionProgress(requests);
}
}
function maxRankAllEvolutions() {
revalidateAuthz().then(() => {
getInventoryData().then(data => {
const requests = [];
data.EvolutionProgress.forEach(item => {
const maxRank = permanentEvolutionWeapons.has(item.ItemType) ? 5 : 4;
if (item.Rank < maxRank) {
requests.push({
ItemType: item.ItemType,
Rank: maxRank
});
}
});
if (Object.keys(requests).length > 0) {
return setEvolutionProgress(requests).then(() => {
toast(loc("code_succRankUp"));
});
}
toast(loc("code_noEquipmentToRankUp"));
});
});
}
function maxRankAllEquipment(categories) {
revalidateAuthz().then(() => {
getInventoryData().then(data => {
window.itemListPromise.then(itemMap => {
const batchData = {};
categories.forEach(category => {
data[category].forEach(item => {
const maxXP = [
"Suits",
"SpaceSuits",
"Sentinels",
"Hoverboards",
"MechSuits",
"MoaPets",
"KubrowPets"
].includes(category)
? 1_600_000
: 800_000;
if (item.XP < maxXP) {
if (!batchData[category]) {
batchData[category] = [];
}
batchData[category].push({
ItemId: { $oid: item.ItemId.$oid },
XP: maxXP
});
}
if (item.ItemType in itemMap && "exalted" in itemMap[item.ItemType]) {
for (const exaltedType of itemMap[item.ItemType].exalted) {
const exaltedItem = data["SpecialItems"].find(x => x.ItemType == exaltedType);
if (exaltedItem) {
const exaltedCap = itemMap[exaltedType]?.type == "weapons" ? 800_000 : 1_600_000;
if (exaltedItem.XP < exaltedCap) {
batchData["SpecialItems"] ??= [];
batchData["SpecialItems"].push({
ItemId: { $oid: exaltedItem.ItemId.$oid },
XP: exaltedCap
});
}
}
}
}
});
});
if (Object.keys(batchData).length > 0) {
return sendBatchGearExp(batchData);
}
toast(loc("code_noEquipmentToRankUp"));
});
});
});
}
// Assumes that caller revalidates authz
function addGearExp(category, oid, xp) {
const data = {};
data[category] = [
{
ItemId: { $oid: oid },
XP: xp
}
];
return new Promise((resolve, reject) => {
$.post({
url: "/custom/addXp?" + window.authz,
contentType: "application/json",
data: JSON.stringify(data)
})
.done(resolve)
.fail(reject);
});
}
function sendBatchGearExp(data) {
revalidateAuthz().then(() => {
$.post({
url: "/custom/addXp?" + window.authz,
contentType: "application/json",
data: JSON.stringify(data)
}).done(() => {
toast(loc("code_succRankUp"));
updateInventory();
});
});
}
function renameGear(category, oid, name) {
revalidateAuthz().then(() => {
if (category == "KubrowPets") {
$.post({
url: "/api/renamePet.php?" + window.authz + "&webui=1",
contentType: "text/plain",
data: JSON.stringify({
petId: oid,
name: name
})
}).done(function () {
inventory_data.KubrowPets.find(x => x.ItemId.$oid == oid).Details.Name = name;
populateInventoryRoute();
});
} else {
$.post({
url: "/api/nameWeapon.php?" + window.authz + "&Category=" + category + "&ItemId=" + oid + "&webui=1",
contentType: "text/plain",
data: JSON.stringify({
ItemName: name
})
}).done(function () {
inventory_data[category].find(x => x.ItemId.$oid == oid).ItemName = name;
populateInventoryRoute();
});
}
});
}
function disposeOfGear(category, oid) {
if (category == "KubrowPets") {
revalidateAuthz().then(() => {
$.post({
url: "/api/releasePet.php?" + window.authz,
contentType: "application/octet-stream",
data: JSON.stringify({
Recipe: "webui",
petId: oid
})
}).done(function () {
inventory_data = undefined; // UI is up-to-date now, but subsequent updates will need to grab new data
});
});
} else {
const data = {
SellCurrency: "SC_RegularCredits",
SellPrice: 0,
Items: {}
};
data.Items[category] = [
{
String: oid,
Count: 0
}
];
revalidateAuthz().then(() => {
$.post({
url: "/api/sell.php?" + window.authz,
contentType: "text/plain",
data: JSON.stringify(data)
}).done(function () {
inventory_data = undefined; // UI is up-to-date now, but subsequent updates will need to grab new data
});
});
}
}
function disposeOfItems(category, type, count) {
const data = {
SellCurrency: "SC_RegularCredits",
SellPrice: 0,
Items: {}
};
data.Items[category] = [
{
String: type,
Count: count
}
];
revalidateAuthz().then(() => {
$.post({
url: "/api/sell.php?" + window.authz,
contentType: "text/plain",
data: JSON.stringify(data)
}).done(function () {
inventory_data = undefined; // UI is up-to-date now, but subsequent updates will need to grab new data
});
});
}
function equipmentFeatures(category, oid, bit) {
revalidateAuthz().then(() => {
$.get(
"/custom/equipmentFeatures?" + window.authz + "&ItemId=" + oid + "&Category=" + category + "&bit=" + bit
).done(function () {
inventory_data[category].find(x => x.ItemId.$oid == oid).Features ^= bit;
// toggle state was already updated when the user clicked on it
});
});
}
function unlockLevelCap(category, oid, formas) {
revalidateAuthz().then(() => {
$.post({
url: "/custom/unlockLevelCap?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
Category: category,
ItemId: oid,
Polarized: formas
})
}).done(function () {
inventory_data[category].find(x => x.ItemId.$oid == oid).Polarized = formas;
});
});
}
function maturePet(oid, revert) {
revalidateAuthz().then(() => {
$.post({
url: "/api/maturePet.php?" + window.authz,
contentType: "application/octet-stream",
data: JSON.stringify({
petId: oid,
revert
})
}).done(function () {
inventory_data.KubrowPets.find(x => x.ItemId.$oid == oid).Details.IsPuppy = revert;
populateInventoryRoute();
});
});
}
function setEvolutionProgress(requests) {
return new Promise(resolve => {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/setEvolutionProgress?" + window.authz,
contentType: "application/json",
data: JSON.stringify(requests)
});
req.done(newEvolutionProgress => {
inventory_data.EvolutionProgress = newEvolutionProgress;
populateInventoryRoute();
resolve();
});
});
});
}
function doAcquireCountItems(category) {
const uniqueName = getKey(document.getElementById("acquire-type-" + category));
if (!uniqueName) {
$(`#acquire-type-${category}`).addClass("is-invalid").focus();
return;
}
const count = parseInt($(`#${category}-count`).val());
if (count != 0 && !Number.isNaN(count)) {
revalidateAuthz().then(() => {
$.post({
url: "/custom/addItems?" + window.authz,
contentType: "application/json",
data: JSON.stringify([
{
ItemType: uniqueName,
ItemCount: count
}
])
})
.done(function (modifiedPaths) {
if (modifiedPaths.length) {
if (count > 0) {
toast(loc("code_succAdded"));
} else {
toast(loc("code_succRemoved"));
}
} else {
toast(loc("code_nothingToDo"));
}
if (category != "miscitems") updateInventory();
})
.fail(r => {
toast(r.responseText);
});
});
}
}
function removeShipDecoration(uniqueName, count) {
revalidateAuthz().then(() => {
$.post({
url: "/custom/addItems?" + window.authz,
contentType: "application/json",
data: JSON.stringify([
{
ItemType: uniqueName,
ItemCount: count * -1
}
])
}).done(function () {
inventory_data.ShipDecorations.splice(
inventory_data.ShipDecorations.findIndex(x => x.ItemType == uniqueName),
1
);
toast(loc("code_succRemoved"));
});
});
}
function addItemByItemType() {
const ItemType = document.getElementById("typeName-type").value;
const ItemCount = parseInt($(`#typeName-count`).val());
if (ItemCount != 0 && !Number.isNaN(ItemCount)) {
revalidateAuthz().then(() => {
$.post({
url: "/custom/addItems?" + window.authz,
contentType: "application/json",
data: JSON.stringify([
{
ItemType,
ItemCount
}
])
})
.done(function (modifiedPaths) {
if (modifiedPaths.length) {
if (ItemCount > 0) {
toast(loc("code_succAdded"));
} else {
toast(loc("code_succRemoved"));
}
if (modifiedPaths.some(x => !x.startsWith("MiscItems"))) {
updateInventory();
}
} else {
toast(loc("code_nothingToDo"));
}
})
.fail(r => {
toast(r.responseText);
});
});
}
}
function doAcquireRiven() {
let fingerprint;
try {
fingerprint = JSON.parse($("#addriven-fingerprint").val());
if (typeof fingerprint !== "object") {
fingerprint = JSON.parse(fingerprint);
}
} catch (e) {
/* empty */
}
if (
typeof fingerprint !== "object" ||
!("compat" in fingerprint) ||
!("pol" in fingerprint) ||
!("buffs" in fingerprint)
) {
$("#addriven-fingerprint").addClass("is-invalid").focus();
return;
}
const uniqueName = "/Lotus/Upgrades/Mods/Randomized/" + $("#addriven-type").val();
revalidateAuthz().then(() => {
// Add riven type to inventory
$.post({
url: "/custom/addItems?" + window.authz,
contentType: "application/json",
data: JSON.stringify([
{
ItemType: uniqueName,
ItemCount: 1
}
])
}).done(function () {
// Get riven's assigned id
$.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1").done(data => {
for (const rawUpgrade of data.RawUpgrades) {
if (rawUpgrade.ItemType === uniqueName) {
// Add fingerprint to riven
$.post({
url: "/api/artifacts.php?" + window.authz,
contentType: "text/plain",
data: JSON.stringify({
Upgrade: {
ItemType: uniqueName,
UpgradeFingerprint: JSON.stringify(fingerprint),
ItemId: rawUpgrade.LastAdded
},
LevelDiff: 0,
Cost: 0,
FusionPointCost: 0
})
}).done(function () {
$("#addriven-fingerprint").val("");
updateInventory();
});
break;
}
}
});
});
});
}
function setFingerprint(ItemType, ItemId, fingerprint) {
revalidateAuthz().then(() => {
$.post({
url: "/api/artifacts.php?" + window.authz,
contentType: "text/plain",
data: JSON.stringify({
Upgrade: {
ItemType,
ItemId,
UpgradeFingerprint: JSON.stringify(fingerprint)
},
LevelDiff: 0,
Cost: 0,
FusionPointCost: 0
})
}).done(function () {
updateInventory();
});
});
}
function doAcquireMod() {
const maxed = !!window.maxed;
window.maxed = false;
const uniqueName = getKey(document.getElementById("mod-to-acquire"));
if (!uniqueName) {
$("#mod-to-acquire").addClass("is-invalid").focus();
return;
}
const count = parseInt($("#mod-count").val());
if (count != 0 && !Number.isNaN(count)) {
Promise.all([window.itemListPromise, revalidateAuthz()]).then(([itemList]) => {
$.post({
url: "/custom/addItems?" + window.authz,
contentType: "application/json",
data: JSON.stringify([
{
ItemType: uniqueName,
ItemCount: count,
Fingerprint:
maxed && (itemList[uniqueName].fusionLimit ?? 5) > 0
? JSON.stringify({ lvl: itemList[uniqueName].fusionLimit ?? 5 })
: undefined
}
])
})
.done(function () {
if (count > 0) {
toast(loc("code_succAdded"));
} else {
toast(loc("code_succRemoved"));
}
updateInventory();
})
.fail(r => {
toast(r.responseText);
});
});
}
}
function doAcquireModMax() {
const uniqueName = getKey(document.getElementById("mod-to-acquire"));
alert("doAcquireModMax: " + uniqueName);
}
// Cheats route
const uiConfigs = [...$(".config-form input[id], .config-form select[id]")].map(x => x.id);
for (const id of uiConfigs) {
const elm = document.getElementById(id);
if (elm.tagName == "SELECT") {
elm.onchange = function () {
let value = this.value;
if (value == "true") {
value = true;
} else if (value == "false") {
value = false;
} else if (value == "null") {
value = null;
} else if (!isNaN(parseInt(value))) {
value = parseInt(value);
}
$.post({
url: "/custom/setConfig?" + window.authz,
contentType: "application/json",
data: JSON.stringify({ [id]: value })
}).done(function () {
config_data[id] = value;
});
};
} else if (elm.type == "checkbox") {
elm.onchange = function () {
const value = this.checked;
$.post({
url: "/custom/setConfig?" + window.authz,
contentType: "application/json",
data: JSON.stringify({ [id]: value })
}).done(function () {
config_data[id] = value;
});
};
}
}
document.querySelectorAll(".config-form .input-group").forEach(grp => {
const input = grp.querySelector("input");
const btn = grp.querySelector("button");
input.oninput = input.onchange = function () {
btn.classList.remove("btn-secondary");
btn.classList.add("btn-primary");
};
btn.onclick = function () {
btn.classList.remove("btn-primary");
btn.classList.add("btn-secondary");
};
});
function doSaveConfigInt(id) {
const value = parseInt(document.getElementById(id).value);
$.post({
url: "/custom/setConfig?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
[id]: value
})
}).done(function () {
config_data[id] = value;
});
}
function doSaveConfigFloat(id) {
const value = parseFloat(document.getElementById(id).value);
$.post({
url: "/custom/setConfig?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
[id]: value
})
}).done(function () {
config_data[id] = value;
});
}
function doSaveConfigStringArray(id) {
const value = document
.getElementById(id)
.getAttribute("data-tags-value")
.split(", ")
.filter(x => x);
$.post({
url: "/custom/setConfig?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
[id]: value
})
}).done(function () {
config_data[id] = value;
});
}
let config_data;
// Assumes that caller revalidates authz
function getServerConfig() {
return new Promise((resolve, reject) => {
if (window.is_admin === false) {
config_data = null;
}
if (config_data !== undefined) {
resolve(config_data);
} else {
$.post({
url: "/custom/getConfig?" + window.authz,
contentType: "application/json",
data: JSON.stringify(uiConfigs)
})
.done(json => {
window.is_admin = !!json;
config_data = json;
resolve(config_data);
})
.fail(res => reject(res.responseText));
}
});
}
single.getRoute("/webui/cheats").on("beforeload", function () {
awaitAuthz().then(() => {
getInventoryData().then(data => {
document.getElementById("changeSyndicate").value = data.SupportedSyndicate ?? "";
for (const elm of accountCheats) {
if (elm.type === "checkbox") {
elm.checked = !!data[elm.id];
} else if (elm.type === "number") {
elm.value = data[elm.id] !== undefined ? data[elm.id] : elm.getAttribute("data-default") || "";
}
}
});
getServerConfig().then(json => {
if (json) {
$(".admin-hide").addClass("d-none");
$(".admin-show").removeClass("d-none");
Object.entries(json).forEach(entry => {
const [key, value] = entry;
const elm = document.getElementById(key);
if (elm.type == "checkbox") {
elm.checked = value;
} else if (elm.classList.contains("tags-input")) {
elm.value = (value ?? []).join(", ");
elm.oninput();
} else {
elm.value = value ?? elm.getAttribute("data-default");
}
});
} else {
$(".admin-hide").removeClass("d-none");
$(".admin-show").addClass("d-none");
}
});
});
});
function doUnlockAllFocusSchools() {
revalidateAuthz().then(() => {
getInventoryData().then(async data => {
const missingFocusUpgrades = {
"/Lotus/Upgrades/Focus/Attack/AttackFocusAbility": true,
"/Lotus/Upgrades/Focus/Tactic/TacticFocusAbility": true,
"/Lotus/Upgrades/Focus/Ward/WardFocusAbility": true,
"/Lotus/Upgrades/Focus/Defense/DefenseFocusAbility": true,
"/Lotus/Upgrades/Focus/Power/PowerFocusAbility": true
};
if (data.FocusUpgrades) {
for (const focusUpgrade of data.FocusUpgrades) {
if (focusUpgrade.ItemType in missingFocusUpgrades) {
delete missingFocusUpgrades[focusUpgrade.ItemType];
}
}
}
for (const upgradeType of Object.keys(missingFocusUpgrades)) {
await unlockFocusSchool(upgradeType);
}
if (Object.keys(missingFocusUpgrades).length == 0) {
toast(loc("code_focusAllUnlocked"));
} else {
toast(loc("code_focusUnlocked").replaceAll("|COUNT|", Object.keys(missingFocusUpgrades).length));
if (ws_is_open) {
window.ws.send(JSON.stringify({ sync_inventory: true }));
}
}
});
});
}
function unlockFocusSchool(upgradeType) {
return new Promise(resolve => {
// Deselect current FocusAbility so we will be able to unlock the way for free
$.post({
url: "/api/focus.php?" + window.authz + "&op=ActivateWay",
contentType: "text/plain",
data: JSON.stringify({ FocusType: null })
}).done(function () {
// Unlock the way now
$.post({
url: "/api/focus.php?" + window.authz + "&op=UnlockWay",
contentType: "text/plain",
data: JSON.stringify({
FocusType: upgradeType
})
}).done(function () {
resolve();
});
});
});
}
function doHelminthUnlockAll() {
revalidateAuthz().then(() => {
$.post("/api/infestedFoundry.php?" + window.authz + "&mode=custom_unlockall");
});
}
function doIntrinsicsUnlockAll() {
revalidateAuthz().then(() => {
$.get("/custom/unlockAllIntrinsics?" + window.authz);
});
}
document.querySelectorAll("#account-cheats input[type=checkbox]").forEach(elm => {
elm.onchange = function () {
revalidateAuthz().then(() => {
const value = elm.checked;
$.post({
url: "/custom/setAccountCheat?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
key: elm.id,
value: value
})
}).done(res => {
elm.checked = value;
inventory_data[elm.id] = value;
if (res == "retroactivable") {
if (window.confirm(loc("cheats_retroactivePrompt"))) {
$.get("/custom/retroactivelyApplyCheat?" + window.authz + "&cheat=" + elm.id);
}
}
});
});
};
});
document.querySelectorAll("#account-cheats .input-group").forEach(grp => {
const input = grp.querySelector("input");
const select = grp.querySelector("select");
const btn = grp.querySelector("button");
if (input) {
input.oninput = input.onchange = function () {
btn.classList.remove("btn-secondary");
btn.classList.add("btn-primary");
};
}
if (select) {
select.oninput = select.onchange = function () {
btn.classList.remove("btn-secondary");
btn.classList.add("btn-primary");
};
}
btn.onclick = function () {
btn.classList.remove("btn-primary");
btn.classList.add("btn-secondary");
const input = btn.closest(".input-group").querySelector('input[type="number"]');
if (!input) return;
revalidateAuthz().then(() => {
const value = Math.min(Number(input.value), Number(input.max));
$.post({
url: "/custom/setAccountCheat?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
key: input.id,
value: value
})
}).done(() => {
input.value = value;
inventory_data[elm.id] = value;
});
});
};
});
document.querySelectorAll("#guild-cheats input[type=checkbox]").forEach(elm => {
elm.onchange = function () {
revalidateAuthz().then(() => {
$.post({
url: "/custom/setGuildCheat?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/json",
data: JSON.stringify({
key: elm.id,
value: elm.checked
})
}).done(res => {
if (res == "retroactivable") {
if (window.confirm(loc("cheats_retroactivePrompt"))) {
$.get(
"/custom/retroactivelyApplyGuildCheat?" +
window.authz +
"&guildId=" +
window.guildId +
"&cheat=" +
elm.id
);
}
}
});
});
};
});
// Mods route
function doAddAllMods() {
let modsAll = new Set();
for (const child of document.getElementById("datalist-mods").children) {
modsAll.add(child.getAttribute("data-key"));
}
[
"/Lotus/Upgrades/Mods/Fusers/CommonModFuser",
"/Lotus/Upgrades/Mods/Fusers/UncommonModFuser",
"/Lotus/Upgrades/Mods/Fusers/RareModFuser",
"/Lotus/Upgrades/Mods/Fusers/LegendaryModFuser",
"/Lotus/Upgrades/Modules/GrineerMeleeModule",
"/Lotus/Upgrades/Modules/GrineerPistolModule",
"/Lotus/Upgrades/Modules/GrineerRifleModule",
"/Lotus/Upgrades/Modules/GrineerShotgunModule",
"/Lotus/Upgrades/Modules/OrokinWarframeModule",
"/Lotus/Upgrades/Modules/Crafted/IncendiaryRifleMod"
].forEach(path => modsAll.delete(path));
revalidateAuthz().then(() => {
getInventoryData().then(data => {
for (const modOwned of data.RawUpgrades) {
if ((modOwned.ItemCount ?? 1) > 0) {
modsAll.delete(modOwned.ItemType);
}
}
modsAll = Array.from(modsAll);
if (
modsAll.length != 0 &&
window.confirm(loc("code_addModsConfirm").replaceAll("|COUNT|", modsAll.length))
) {
$.post({
url: "/custom/addItems?" + window.authz,
contentType: "application/json",
data: JSON.stringify(
modsAll.map(mod => ({
ItemType: mod,
ItemCount: 21 // To fully upgrade certain arcanes
}))
)
}).done(function () {
updateInventory();
});
}
});
});
}
function doRemoveUnrankedMods() {
revalidateAuthz().then(() => {
getInventoryData().then(data => {
window.itemListPromise.then(itemMap => {
// Excluding unranked parazon mods and instead including ranked parazon mods because worse is better for them.
const upgradesToSell = data.RawUpgrades.filter(upgrade => !itemMap[upgrade.ItemType]?.parazon).map(
upgrade => ({ String: upgrade.ItemType, Count: upgrade.ItemCount })
);
for (const upgrade of data.Upgrades) {
if (itemMap[upgrade.ItemType]?.parazon) {
upgradesToSell.push({ String: upgrade.ItemId.$oid, Count: 0 });
}
}
$.post({
url: "/api/sell.php?" + window.authz,
contentType: "text/plain",
data: JSON.stringify({
SellCurrency: "SC_RegularCredits",
SellPrice: 0,
Items: {
Upgrades: upgradesToSell
}
})
}).done(function () {
updateInventory();
});
});
});
});
}
function doRemoveRankedMods() {
revalidateAuthz().then(() => {
getInventoryData().then(data => {
window.itemListPromise.then(itemMap => {
// Excluding ranked parazon mods and instead including unranked parazon mods because worse is better for them.
const upgradesToSell = data.Upgrades.filter(upgrade => !itemMap[upgrade.ItemType]?.parazon).map(
upgrade => ({ String: upgrade.ItemId.$oid, Count: 0 })
);
for (const upgrade of data.RawUpgrades) {
if (itemMap[upgrade.ItemType]?.parazon) {
upgradesToSell.push({ String: upgrade.ItemType, Count: upgrade.ItemCount });
}
}
$.post({
url: "/api/sell.php?" + window.authz,
contentType: "text/plain",
data: JSON.stringify({
SellCurrency: "SC_RegularCredits",
SellPrice: 0,
Items: {
Upgrades: upgradesToSell
}
})
}).done(function () {
updateInventory();
});
});
});
});
}
function doAddMissingMaxRankMods() {
revalidateAuthz().then(() => {
fetch("/custom/addMissingMaxRankMods?" + window.authz).then(() => {
updateInventory();
});
});
}
// DetailedView Route
single.getRoute("#detailedView-route").on("beforeload", async function () {
if (!inventory_data) {
document.getElementById("detailedView-loading").classList.remove("d-none");
document.getElementById("detailedView-title").textContent = "";
document.querySelector("#detailedView-route .text-body-secondary").textContent = "";
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");
document.getElementById("u5ModEdit-card").classList.add("d-none");
document.getElementById("equipmentFeatures-card").classList.add("d-none");
document.getElementById("equipmentFeaturesButtons-card").innerHTML = "";
}
populateDetailedViewRoute();
});
async function populateDetailedViewRoute() {
const [data, itemMap] = await Promise.all([
inventory_data ? Promise.resolve(inventory_data) : awaitAuthz().then(getInventoryData),
window.itemListPromise
]);
const urlParams = new URLSearchParams(window.location.search);
const oid = urlParams.get("itemId");
const category = urlParams.get("productCategory");
const item = data[category].find(x => x.ItemId.$oid == oid);
if (item) {
document.getElementById("detailedView-loading").classList.add("d-none");
const itemName = itemMap[item.ItemType]?.name ?? item.ItemType;
if (item.ItemName) {
const pipeIndex = item.ItemName.indexOf("|");
if (pipeIndex != -1) {
$("#detailedView-title").text(item.ItemName.substr(1 + pipeIndex) + " " + itemName);
document.querySelector("#detailedView-route .text-body-secondary").textContent = "";
} else {
$("#detailedView-title").text(item.ItemName);
$("#detailedView-route .text-body-secondary").text(itemName);
}
} else {
$("#detailedView-title").text(itemName);
document.querySelector("#detailedView-route .text-body-secondary").textContent = "";
}
if (category != "Upgrades") {
document.getElementById("equipmentFeatures-card").classList.remove("d-none");
const buttonsCard = document.getElementById("equipmentFeaturesButtons-card");
buttonsCard.innerHTML = "";
item.Features ??= 0;
const bits = [];
const evolutionWeapons = Array.from(document.querySelectorAll("#datalist-EvolutionProgress option")).map(
option => option.dataset.key
);
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 (
[item.ItemType, itemMap[item.ItemType]?.parentName].some(
type => type && evolutionWeapons.includes(type) && !permanentEvolutionWeapons.has(type)
)
)
bits.push(512);
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;
let locString = `code_feature_${bit}`;
if (
!["Suits", "SpaceSuits", "Hoverboards", "MechSuits", "Sentinels", "MoaPets", "KubrowPets"].includes(
category
) &&
bit === 1
) {
locString = `code_feature_${bit}_alt`;
}
label.innerHTML = loc(locString);
label.setAttribute("data-loc", locString);
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);
}
} else {
document.getElementById("equipmentFeatures-card").classList.add("d-none");
document.getElementById("equipmentFeaturesButtons-card").innerHTML = "";
}
if (category == "Suits") {
document.getElementById("archonShards-card").classList.remove("d-none");
const uniqueUpgrades = {};
(item.ArchonCrystalUpgrades ?? []).forEach(upgrade => {
if (upgrade && upgrade.UpgradeType) {
uniqueUpgrades[upgrade.UpgradeType] ??= 0;
uniqueUpgrades[upgrade.UpgradeType] += 1;
}
});
document.getElementById("crystals-list").innerHTML = "";
Object.entries(uniqueUpgrades).forEach(([upgradeType, count]) => {
const tr = document.createElement("tr");
{
const td = document.createElement("td");
td.textContent = count + "x " + (archonCrystalUpgrades[upgradeType] ?? upgradeType);
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
doPopArchonCrystalUpgrade(upgradeType);
};
a.title = loc("code_remove");
a.innerHTML = icons.trash;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("crystals-list").appendChild(tr);
});
{
document.getElementById("edit-suit-invigorations-card").classList.remove("d-none");
document.getElementById("invigoration-offensive").value = item.OffensiveUpgrade || "";
document.getElementById("invigoration-defensive").value = item.DefensiveUpgrade || "";
document.getElementById("invigoration-expiry").value =
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);
const configs = item.Configs ?? [];
const loadoutTabs = document.getElementById("loadoutTabs");
const loadoutTabsContent = document.getElementById("loadoutTabsContent");
loadoutTabs.innerHTML = "";
loadoutTabsContent.innerHTML = "";
for (let i = 0; i <= maxModConfigNum; i++) {
const config = configs[i] ?? {};
{
const li = document.createElement("li");
li.classList.add("nav-item");
const button = document.createElement("button");
button.classList.add("nav-link");
if (i === 0) button.classList.add("active");
button.id = `config${i}-tab`;
button.setAttribute("data-bs-toggle", "tab");
button.setAttribute("data-bs-target", `#config${i}`);
button.innerHTML = config.Name?.trim() || String.fromCharCode(65 + i);
li.appendChild(button);
loadoutTabs.appendChild(li);
}
{
const tabDiv = document.createElement("div");
tabDiv.classList = "tab-pane";
if (i === 0) tabDiv.classList.add("show", "active");
tabDiv.id = `config${i}`;
{
const abilityOverrideForm = document.createElement("form");
abilityOverrideForm.classList = "form-group mt-2";
abilityOverrideForm.setAttribute(
"onsubmit",
`handleAbilityOverride(event, ${i});return false;`
);
const abilityOverrideFormLabel = document.createElement("label");
abilityOverrideFormLabel.setAttribute("data-loc", "abilityOverride_label");
abilityOverrideFormLabel.innerHTML = loc("abilityOverride_label");
abilityOverrideFormLabel.classList = "form-label";
abilityOverrideFormLabel.setAttribute("for", `abilityOverride-ability-config-${i}`);
abilityOverrideForm.appendChild(abilityOverrideFormLabel);
const abilityOverrideInputGroup = document.createElement("div");
abilityOverrideInputGroup.classList = "input-group";
abilityOverrideForm.appendChild(abilityOverrideInputGroup);
const abilityOverrideInput = document.createElement("input");
abilityOverrideInput.id = `abilityOverride-ability-config-${i}`;
abilityOverrideInput.classList = "form-control";
abilityOverrideInput.setAttribute("list", "datalist-Abilities");
if (config.AbilityOverride) {
const datalist = document.getElementById("datalist-Abilities");
const options = Array.from(datalist.options);
abilityOverrideInput.value =
options.find(
option => config.AbilityOverride.Ability == option.getAttribute("data-key")
)?.value || "";
}
abilityOverrideInputGroup.appendChild(abilityOverrideInput);
const abilityOverrideOnSlot = document.createElement("span");
abilityOverrideOnSlot.classList = "input-group-text";
abilityOverrideOnSlot.setAttribute("data-loc", "abilityOverride_onSlot");
abilityOverrideOnSlot.innerHTML = loc("abilityOverride_onSlot");
abilityOverrideInputGroup.appendChild(abilityOverrideOnSlot);
const abilityOverrideSecondInput = document.createElement("input");
abilityOverrideSecondInput.id = `abilityOverride-ability-index-config-${i}`;
abilityOverrideSecondInput.classList = "form-control";
abilityOverrideSecondInput.setAttribute("type", "number");
abilityOverrideSecondInput.setAttribute("min", "1");
abilityOverrideSecondInput.setAttribute("max", "4");
if (config.AbilityOverride)
abilityOverrideSecondInput.value = config.AbilityOverride.Index + 1;
abilityOverrideInputGroup.appendChild(abilityOverrideSecondInput);
const abilityOverrideSetButton = document.createElement("button");
abilityOverrideSetButton.classList = "btn btn-primary";
abilityOverrideSetButton.setAttribute("type", "submit");
abilityOverrideSetButton.setAttribute("value", "set");
abilityOverrideSetButton.setAttribute("data-loc", "general_setButton");
abilityOverrideSetButton.innerHTML = loc("general_setButton");
abilityOverrideInputGroup.appendChild(abilityOverrideSetButton);
const abilityOverrideRemoveButton = document.createElement("button");
abilityOverrideRemoveButton.classList = "btn btn-danger";
abilityOverrideRemoveButton.setAttribute("type", "submit");
abilityOverrideRemoveButton.setAttribute("value", "remove");
abilityOverrideRemoveButton.setAttribute("data-loc", "code_remove");
abilityOverrideRemoveButton.innerHTML = loc("code_remove");
abilityOverrideInputGroup.appendChild(abilityOverrideRemoveButton);
abilityOverrideForm.appendChild(abilityOverrideInputGroup);
tabDiv.appendChild(abilityOverrideForm);
}
loadoutTabsContent.appendChild(tabDiv);
}
}
}
} else {
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");
}
if (["LongGuns", "Pistols", "Melee", "SpaceGuns", "SpaceMelee"].includes(category)) {
document.getElementById("valenceBonus-card").classList.remove("d-none");
document.getElementById("valenceBonus-innateDamage").value = "";
document.getElementById("valenceBonus-procent").value = 25;
if (item.UpgradeFingerprint) {
const buff = JSON.parse(item.UpgradeFingerprint).buffs[0];
const buffValue = fromUpgradeFingerprintValue(buff.Value, 0.25);
document.getElementById("valenceBonus-innateDamage").value = buff.Tag ?? "";
document.getElementById("valenceBonus-procent").value = Math.round(buffValue * 1000) / 10;
}
} else {
document.getElementById("valenceBonus-card").classList.add("d-none");
}
if (U5Mods.includes(item.ItemType)) {
document.getElementById("u5ModEdit-card").classList.remove("d-none");
const oldFitsSelect = document.getElementById("u5ModEdit-fits");
const fitsSelect = oldFitsSelect.cloneNode(false);
oldFitsSelect.parentNode.replaceChild(fitsSelect, oldFitsSelect);
const upgradeFields = document.getElementById("u5ModEdit-upgradeFields");
upgradeFields.innerHTML = "";
const fits = itemMap[item.ItemType].fits;
const fp = JSON.parse(item.UpgradeFingerprint);
// const statAtten = fits.find(fit => fit.type == fp.fits).statAtten || 1;
const statAtten = 1;
fits.forEach(fit => {
const option = document.createElement("option");
option.value = fit.type;
option.textContent = itemMap[fit.type]?.name ?? fit.type;
// + (fit.statAtten ? ` (${loc("code_statAtten")}: ${fit.statAtten || 1})` : "");
fitsSelect.appendChild(option);
});
fitsSelect.addEventListener("change", () => {
upgradeFields.querySelectorAll(".upgrade-group").forEach(group => {
updateUpgradeInput(itemMap[item.ItemType], group);
});
});
upgradeFields.setAttribute("data-stat-atten", statAtten);
document.getElementById("u5ModEdit-reqLevel").value = fp.reqLevel ?? 0;
document.getElementById("u5ModEdit-fits").value = fp.fits ?? fits[0].type;
fp.upgrades.forEach(up => {
createUpgradeInput(upgradeFields, itemMap, item.ItemType, up);
});
createUpgradeInput(upgradeFields, itemMap, item.ItemType, null);
} else {
document.getElementById("u5ModEdit-card").classList.add("d-none");
}
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 {
document.getElementById("modularParts-card").classList.add("d-none");
document.getElementById("modularParts-form").innerHTML = "";
}
} else {
single.loadRoute("/webui/inventory");
}
}
let guild_data;
// Assumes that caller revalidates authz
function getGuildData() {
return new Promise(resolve => {
if (guild_data) {
resolve(guild_data);
} else {
$.get("/custom/getGuild?" + window.authz).done(guildData => {
guild_data = guildData;
window.guildId = guildData?._id ?? null;
if (window.subscribedToGuildId != guildId) {
window.subscribedToGuildId = guildId;
ws.send(JSON.stringify({ guildId }));
}
resolve(guild_data);
});
}
});
}
function guildView_clear() {
document.getElementById("guildView-title").textContent = "";
document.getElementById("guildView-tier").textContent = "";
document.getElementById("guildView-class").textContent = "";
document.getElementById("vaultRegularCredits-form").classList.add("d-none");
document.getElementById("vaultPremiumCredits-form").classList.add("d-none");
document.getElementById("VaultRegularCredits-owned").classList.add("mb-0");
document.getElementById("VaultPremiumCredits-owned").classList.add("mb-0");
document.getElementById("TechProjects-list").innerHTML = "";
document.getElementById("techProjects-form").classList.add("d-none");
document.getElementById("acquire-type-TechProjects").value = "";
document.getElementById("VaultDecoRecipes-list").innerHTML = "";
document.getElementById("vaultDecoRecipes-form").classList.add("d-none");
document.getElementById("acquire-type-VaultDecoRecipes").value = "";
document.getElementById("VaultMiscItems-list").innerHTML = "";
document.getElementById("vaultMiscItems-form").classList.add("d-none");
document.getElementById("acquire-type-VaultMiscItems").value = "";
document.getElementById("VaultShipDecorations-list").innerHTML = "";
document.getElementById("vaultShipDecorations-form").classList.add("d-none");
document.getElementById("acquire-type-VaultShipDecorations").value = "";
document.getElementById("Alliance-list").innerHTML = "";
document.getElementById("guildView-alliance").textContent = "";
document.getElementById("Members-list").innerHTML = "";
$("#guild-route > .row").addClass("d-none");
}
single.getRoute("#guild-route").on("beforeload", function () {
if (guild_data === undefined) {
document.getElementById("guildView-loading").classList.remove("d-none");
document.getElementById("guildView-na").classList.add("d-none");
document.getElementById("guildView-naDescription").classList.add("d-none");
guildView_clear();
}
awaitAuthz().then(() => {
getGuildData().then(guildData => {
if (!guildData) {
document.getElementById("guildView-loading").classList.add("d-none");
document.getElementById("guildView-na").classList.remove("d-none");
document.getElementById("guildView-naDescription").classList.remove("d-none");
guildView_clear();
return;
}
window.itemListPromise.then(itemMap => {
document.getElementById("TechProjects-bulkAdd").classList.add("d-none");
document.getElementById("TechProjects-bulkFund").classList.add("d-none");
document.getElementById("TechProjects-bulkComplete").classList.add("d-none");
document.getElementById("TechProjects-bulkRemove").classList.add("d-none");
document.getElementById("VaultDecoRecipes-bulkAdd").classList.add("d-none");
document.getElementById("VaultDecoRecipes-bulkRemove").classList.add("d-none");
document.getElementById("VaultShipDecorations-bulkRemove").classList.add("d-none");
document.getElementById("VaultShipDecorations-bulkAdd").classList.add("d-none");
document.getElementById("VaultShipDecorations-bulkRemove").classList.add("d-none");
document.getElementById("VaultMiscItems-bulkRemove").classList.add("d-none");
document.getElementById("guildView-loading").classList.add("d-none");
$("#guild-route > .row").removeClass("d-none");
document.getElementById("guildView-title").textContent = guildData.Name;
document.getElementById("guildView-tier").textContent = normalizeText(
itemMap["/Lotus/Language/Clan/Clan_TierDisplay"].name.replaceAll(
"|TIER|",
itemMap[`/Lotus/Language/Clan/Clan_Tier${guildData.Tier}`].name
)
);
document.getElementById("guildView-class").textContent = itemMap[
"/Lotus/Language/Clan/Clan_ClassLower"
].name.replaceAll("|CLASS|", guildData.Class);
["VaultRegularCredits", "VaultPremiumCredits"].forEach(currency => {
document.getElementById(currency + "-owned").textContent = loc(
"guildView_currency_owned"
).replaceAll("|COUNT|", (guildData[currency] ?? 0).toLocaleString());
});
const userGuildMember = guildData.Members.find(m => m._id.$oid === window.accountId);
let userGuildPermissions;
if (userGuildMember) {
userGuildPermissions = guildData.Ranks[userGuildMember.Rank].Permissions;
// Ruler = 1, // Clan: Change hierarchy. Alliance (Creator only): Kick clans.
// Advertiser = 8192,
// Recruiter = 2, // Send invites (Clans & Alliances)
// Regulator = 4, // Kick members
// Promoter = 8, // Clan: Promote and demote members. Alliance (Creator only): Change clan permissions.
// Architect = 16, // Create and destroy rooms
// Host = 32, // No longer used in modern versions
// Decorator = 1024, // Create and destroy decos
// Treasurer = 64, // Clan: Contribute from vault and edit tax rate. Alliance: Divvy vault.
// Tech = 128, // Queue research
// ChatModerator = 512, // (Clans & Alliances)
// Herald = 2048, // Change MOTD
// Fabricator = 4096 // Replicate research
if (userGuildPermissions & 128) {
document.getElementById("techProjects-form").classList.remove("d-none");
}
if (userGuildPermissions & 16) {
document.getElementById("vaultDecoRecipes-form").classList.remove("d-none");
}
if (userGuildPermissions & 64) {
document.getElementById("vaultRegularCredits-form").classList.remove("d-none");
document.getElementById("VaultRegularCredits-owned").classList.remove("mb-0");
document.getElementById("vaultPremiumCredits-form").classList.remove("d-none");
document.getElementById("VaultPremiumCredits-owned").classList.remove("mb-0");
document.getElementById("vaultMiscItems-form").classList.remove("d-none");
document.getElementById("vaultShipDecorations-form").classList.remove("d-none");
}
if (userGuildMember.Rank <= 1) {
document.getElementById("TechProjects-bulkAdd").classList.remove("d-none");
document.getElementById("TechProjects-bulkFund").classList.remove("d-none");
document.getElementById("TechProjects-bulkComplete").classList.remove("d-none");
document.getElementById("TechProjects-bulkRemove").classList.remove("d-none");
document.getElementById("VaultDecoRecipes-bulkAdd").classList.remove("d-none");
document.getElementById("VaultDecoRecipes-bulkRemove").classList.remove("d-none");
document.getElementById("VaultShipDecorations-bulkRemove").classList.remove("d-none");
document.getElementById("VaultShipDecorations-bulkAdd").classList.remove("d-none");
document.getElementById("VaultShipDecorations-bulkRemove").classList.remove("d-none");
document.getElementById("VaultMiscItems-bulkRemove").classList.remove("d-none");
}
}
const guildCheats = document.querySelectorAll("#guild-cheats input[id]");
for (const elm of guildCheats) {
elm.checked = !!guildData[elm.id];
if (!userGuildMember || userGuildMember.Rank > 1) {
elm.disabled = true;
} else {
elm.disabled = false;
}
}
document.getElementById("TechProjects-list").innerHTML = "";
guildData.TechProjects ??= [];
guildData.TechProjects.forEach(item => {
const tr = document.createElement("tr");
tr.setAttribute("data-item-type", item.ItemType);
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
if (new Date(item.CompletionDate) < new Date()) {
td.textContent += " | " + loc("code_completed");
} else if (item.State == 1) {
td.textContent += " | " + loc("code_funded");
}
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
if (userGuildPermissions && userGuildPermissions & 128 && item.State != 1) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(fundGuildTechProject, item.ItemType);
};
a.title = loc("code_fund");
a.innerHTML = icons.arrowUp;
td.appendChild(a);
}
if (
userGuildPermissions &&
userGuildPermissions & 128 &&
item.State == 1 &&
new Date(item.CompletionDate) > new Date()
) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(completeGuildTechProject, item.ItemType);
};
a.title = loc("code_complete");
a.innerHTML = icons.forward;
td.appendChild(a);
}
if (userGuildMember && userGuildMember.Rank <= 1) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(removeGuildTechProject, item.ItemType);
};
a.title = loc("code_remove");
a.innerHTML = icons.trash;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("TechProjects-list").appendChild(tr);
});
["VaultDecoRecipes", "VaultMiscItems", "VaultShipDecorations"].forEach(vaultKey => {
document.getElementById(vaultKey + "-list").innerHTML = "";
(guildData[vaultKey] ??= []).forEach(item => {
const tr = document.createElement("tr");
tr.setAttribute("data-item-type", item.ItemType);
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
if (item.ItemCount > 1) {
td.innerHTML += ` <span title='${loc("code_count")}'>🗍 ${parseInt(item.ItemCount)}</span>`;
}
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
const canRemove =
vaultKey === "VaultDecoRecipes"
? userGuildMember && userGuildMember.Rank <= 1
: userGuildPermissions && userGuildPermissions & 64;
if (canRemove) {
const a = document.createElement("a");
a.href = "#";
a.title = loc("code_remove");
a.onclick = e => {
e.preventDefault();
removeVaultItem(vaultKey, item.ItemType, item.ItemCount * -1);
};
a.innerHTML = icons.trash;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById(vaultKey + "-list").appendChild(tr);
});
});
document.getElementById("Members-list").innerHTML = "";
guildData.Members.forEach(member => {
const tr = document.createElement("tr");
{
const td = document.createElement("td");
const memberRank = guildData.Ranks[member.Rank];
td.textContent = member.DisplayName;
td.textContent += " | " + itemMap[memberRank.Name]?.name ?? memberRank.Name;
if (member.Status != 0) {
td.textContent += " | " + loc("guildView_pending");
}
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
if (
userGuildMember &&
member.Rank > userGuildMember.Rank &&
userGuildPermissions &&
userGuildPermissions & 8
) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
changeGuildRank(guildId, member._id.$oid, member.Rank - 1);
};
a.title = itemMap["/Lotus/Language/Menu/SocialOverlay_Promote"].name;
a.innerHTML = icons.angleUp;
td.appendChild(a);
}
if (
userGuildMember &&
member.Rank < 8 &&
member.Rank > userGuildMember.Rank &&
userGuildPermissions &&
userGuildPermissions & 8
) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
changeGuildRank(guildId, member._id.$oid, member.Rank + 1);
};
a.title = itemMap["/Lotus/Language/Menu/SocialOverlay_Demote"].name;
a.innerHTML = icons.angleDown;
td.appendChild(a);
}
if (
(userGuildMember &&
member.Rank > userGuildMember.Rank &&
userGuildPermissions &&
userGuildPermissions & 4) ||
(userGuildMember && userGuildMember.Rank != 0 && userGuildMember._id == member._id)
) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
kickFromGuild(member._id.$oid);
};
a.title = loc("code_remove");
a.innerHTML = icons.trash;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("Members-list").appendChild(tr);
});
if (guildData.AllianceId) {
const allianceReq = $.get("/custom/getAlliance?guildId=" + guildId);
allianceReq.done(allianceData => {
document.getElementById("guildView-alliance").textContent =
loc("guildView_alliance") + ": " + allianceData.Name;
let userAlliancePermisssions;
if (userGuildMember && userGuildMember.Rank <= 1) {
userAlliancePermisssions = allianceData.Clans.find(c => c._id.$oid === guildId).Permissions;
}
document.getElementById("Alliance-list").innerHTML = "";
allianceData.Clans.forEach(clan => {
const tr = document.createElement("tr");
{
const td = document.createElement("td");
td.textContent = clan.Name;
if (clan.Pending) {
td.textContent +=
" | " +
normalizeText(itemMap["/Lotus/Language/Menu/SocialOverlay_PendingLabel"].name);
}
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
if (
!(clan.Permissions & 1) &&
userAlliancePermisssions &&
userAlliancePermisssions & 1
) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
kickFromAlliance(clan._id.$oid);
};
a.title = loc("code_remove");
a.innerHTML = icons.trash;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("Alliance-list").appendChild(tr);
});
});
}
});
});
});
});
function doPushArchonCrystalUpgrade() {
const urlParams = new URLSearchParams(window.location.search);
const uniqueName = getKey(document.querySelector("[list='datalist-archonCrystalUpgrades']"));
if (!uniqueName) {
$("[list='datalist-archonCrystalUpgrades']").addClass("is-invalid").focus();
return;
}
revalidateAuthz().then(() => {
$.get(
`/custom/pushArchonCrystalUpgrade?${window.authz}&oid=${urlParams.get("itemId")}&type=${uniqueName}&count=${$("#archon-crystal-add-count").val()}`
).done(function (updatedSuit) {
$("[list='datalist-archonCrystalUpgrades']").val("");
const index = inventory_data.Suits.findIndex(x => x.ItemId.$oid == urlParams.get("itemId"));
inventory_data.Suits[index] = updatedSuit;
populateDetailedViewRoute();
});
});
}
function doPopArchonCrystalUpgrade(type) {
const urlParams = new URLSearchParams(window.location.search);
revalidateAuthz().then(() => {
$.get(
"/custom/popArchonCrystalUpgrade?" + window.authz + "&oid=" + urlParams.get("itemId") + "&type=" + type
).done(function (updatedSuit) {
const index = inventory_data.Suits.findIndex(x => x.ItemId.$oid == urlParams.get("itemId"));
inventory_data.Suits[index] = updatedSuit;
populateDetailedViewRoute();
});
});
}
function doImport() {
revalidateAuthz().then(() => {
try {
$.post({
url: "/custom/import?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
inventory: JSON.parse($("#import-inventory").val())
})
}).then(function (err) {
if (err) {
toast(err == "noKnownKey" ? loc("code_nothingToDo") : err);
} else {
toast(loc("code_succImport"));
}
updateInventory();
});
} catch (e) {
toast(e);
console.error(e);
}
});
}
function doChangeSupportedSyndicate() {
const uniqueName = document.getElementById("changeSyndicate").value;
revalidateAuthz().then(() => {
$.get("/api/setSupportedSyndicate.php?" + window.authz + "&syndicate=" + uniqueName).done(function () {
inventory_data.SupportedSyndicate = uniqueName;
});
});
}
function doAddCurrency(currency) {
revalidateAuthz().then(() => {
$.post({
url: "/custom/addCurrency?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/json",
data: JSON.stringify({
currency,
delta: document.getElementById(currency + "-delta").valueAsNumber
})
}).then(function (newValue) {
if (currency.startsWith("Vault")) {
document.getElementById(currency + "-owned").textContent = loc("guildView_currency_owned").replaceAll(
"|COUNT|",
(newValue ?? 0).toLocaleString()
);
} else {
inventory_data[currency] = newValue;
document.getElementById(currency + "-owned").textContent = loc("currency_owned").replaceAll(
"|COUNT|",
(newValue ?? 0).toLocaleString()
);
}
});
});
}
async function doQuestUpdate(operation, itemType) {
await revalidateAuthz();
await new Promise(resolve => {
$.post({
url: "/custom/manageQuests?" + window.authz + "&operation=" + operation + "&itemType=" + itemType,
contentType: "application/json"
})
.then(function (didAnything) {
if (didAnything) {
updateInventory();
} else {
toast(loc("code_nothingToDo"));
}
})
.always(() => {
resolve();
});
});
}
async function doBulkQuestUpdate(operation) {
await revalidateAuthz();
await new Promise(resolve => {
$.post({
url: "/custom/manageQuests?" + window.authz + "&operation=" + operation,
contentType: "application/json"
})
.then(function (didAnything) {
if (didAnything) {
updateInventory();
} else {
toast(loc("code_nothingToDo"));
}
})
.always(() => {
resolve();
});
});
}
function toast(text, type = "primary") {
const toast = document.createElement("div");
toast.className = `toast align-items-center text-bg-${type} border-0`;
const div = document.createElement("div");
div.className = "d-flex";
const body = document.createElement("div");
body.className = "toast-body";
body.textContent = text;
div.appendChild(body);
const button = document.createElement("button");
button.className = "btn-close btn-close-white me-2 m-auto";
button.setAttribute("data-bs-dismiss", "toast");
div.appendChild(button);
toast.appendChild(div);
new bootstrap.Toast(document.querySelector(".toast-container").appendChild(toast)).show();
}
function handleModularSelection(category) {
const itemType = getKey(document.getElementById("acquire-type-" + category));
if (webUiModularWeapons.includes(itemType)) {
doAcquireModularEquipment(category, itemType);
} else {
doAcquireEquipment(category);
}
}
{
const supportedModularInventoryCategory = ["OperatorAmps", "Melee", "LongGuns", "Pistols", "MoaPets", "KubrowPets"];
supportedModularInventoryCategory.forEach(inventoryCategory => {
document.getElementById("acquire-type-" + inventoryCategory).addEventListener("input", function () {
const modularFields = document.getElementById("modular-" + inventoryCategory);
const modularFieldsMoa = document.getElementById("modular-MoaPets-Moa");
const modularFieldsZanuka = document.getElementById("modular-MoaPets-Zanuka");
const modularFieldsCatbrow = document.getElementById("modular-KubrowPets-Catbrow");
const modularFieldsKubrow = document.getElementById("modular-KubrowPets-Kubrow");
const key = getKey(this);
if (webUiModularWeapons.includes(key)) {
if (inventoryCategory === "MoaPets") {
if (key === "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetPowerSuit") {
modularFieldsMoa.classList.add("d-none");
modularFieldsZanuka.classList.remove("d-none");
} else if (key === "/Lotus/Types/Friendly/Pets/MoaPets/MoaPetPowerSuit") {
modularFieldsMoa.classList.remove("d-none");
modularFieldsZanuka.classList.add("d-none");
}
} else if (inventoryCategory === "KubrowPets") {
if (key.endsWith("InfestedCatbrowPetPowerSuit")) {
modularFieldsCatbrow.classList.remove("d-none");
modularFieldsKubrow.classList.add("d-none");
} else if (key.endsWith("PredatorKubrowPetPowerSuit")) {
modularFieldsCatbrow.classList.add("d-none");
modularFieldsKubrow.classList.remove("d-none");
} else {
modularFieldsCatbrow.classList.add("d-none");
modularFieldsKubrow.classList.add("d-none");
}
} else {
modularFields.classList.remove("d-none");
}
} else {
if (inventoryCategory === "MoaPets") {
modularFieldsMoa.classList.add("d-none");
modularFieldsZanuka.classList.add("d-none");
} else if (inventoryCategory === "KubrowPets") {
modularFieldsCatbrow.classList.add("d-none");
modularFieldsKubrow.classList.add("d-none");
} else {
modularFields.classList.add("d-none");
}
}
});
});
}
function setBooster(ItemType, ExpiryDate) {
revalidateAuthz().then(() => {
$.post({
url: "/custom/setBooster?" + window.authz,
contentType: "application/json",
data: JSON.stringify([
{
ItemType,
ExpiryDate
}
])
}).done(function (newBoosters) {
inventory_data.Boosters = newBoosters;
populateInventoryRoute();
});
});
}
function doAcquireBooster() {
const uniqueName = getKey(document.getElementById("acquire-type-Boosters"));
if (!uniqueName) {
$("#acquire-type-Boosters").addClass("is-invalid").focus();
return;
}
setBooster(uniqueName, Math.floor(Date.now() / 1000 + 3 * 24 * 60 * 60));
document.getElementById("acquire-type-Boosters").value = "";
}
function doChangeBoosterExpiry(ItemType, ExpiryDateInput) {
const ExpiryDate = Math.floor(new Date(ExpiryDateInput.value).getTime() / 1000);
if (isNaN(ExpiryDate)) {
ExpiryDateInput.addClass("is-invalid").focus();
return;
}
setBooster(ItemType, ExpiryDate);
}
function formatDatetime(fmt, date) {
if (typeof date === "number") date = new Date(date);
return fmt.replace(/(%[yY]|%m|%[Dd]|%H|%h|%M|%[Ss]|%[Pp])/g, match => {
switch (match) {
case "%Y":
return date.getFullYear().toString();
case "%y":
return date.getFullYear().toString().slice(-2);
case "%m":
return (date.getMonth() + 1).toString().padStart(2, "0");
case "%D":
case "%d":
return date.getDate().toString().padStart(2, "0");
case "%H":
return date.getHours().toString().padStart(2, "0");
case "%h":
return (date.getHours() % 12).toString().padStart(2, "0");
case "%M":
return date.getMinutes().toString().padStart(2, "0");
case "%S":
case "%s":
return date.getSeconds().toString().padStart(2, "0");
case "%P":
case "%p":
return date.getHours() < 12 ? "am" : "pm";
default:
return match;
}
});
}
const calls_in_flight = new Set();
async function debounce(func, ...args) {
if (!func.name) {
throw new Error(`cannot debounce anonymous functions`);
}
const callid = JSON.stringify({ func: func.name, args });
if (!calls_in_flight.has(callid)) {
calls_in_flight.add(callid);
await func(...args);
calls_in_flight.delete(callid);
} else {
console.log("debouncing", callid);
}
}
async function doMaxPlexus() {
if ((window.plexus?.xp ?? 0) < 900_000) {
await addGearExp("CrewShipHarnesses", window.plexus.id, 900_000 - window.plexus.xp);
window.plexus.xp = 900_000;
toast(loc("code_succRankUp"));
} else {
toast(loc("code_noEquipmentToRankUp"));
}
}
async function doUnlockAllScans() {
await revalidateAuthz();
await fetch("/custom/unlockAllScans?" + window.authz);
toast(loc("cheats_unlockSuccRelog"));
}
async function doUnlockAllShipFeatures() {
await revalidateAuthz();
await fetch("/custom/unlockAllShipFeatures?" + window.authz);
toast(loc(window.have_game_ws ? "code_succAdded" : "cheats_unlockSuccInventory"));
}
async function doUnlockAllCapturaScenes() {
await revalidateAuthz();
await fetch("/custom/unlockAllCapturaScenes?" + window.authz);
toast(loc(window.have_game_ws ? "code_succAdded" : "cheats_unlockSuccInventory"));
}
async function unlockAllMissions() {
await revalidateAuthz();
await fetch("/custom/completeAllMissions?" + window.authz);
toast(loc("cheats_unlockAllMissions_ok"));
}
async function unlockAllJobChainBounties() {
await revalidateAuthz();
await fetch("/custom/unlockAllJobChainBounties?" + window.authz);
toast(loc(window.have_game_ws ? "code_succAdded" : "cheats_unlockSuccInventory"));
}
async function unlockAllSimarisResearchEntries() {
await revalidateAuthz();
await fetch("/custom/unlockAllSimarisResearchEntries?" + window.authz);
toast(loc(window.have_game_ws ? "code_succAdded" : "cheats_unlockSuccInventory"));
}
function setImportSample(key) {
$("#import-inventory").val(JSON.stringify(importSamples[key], null, 2));
}
document.querySelectorAll(".tags-input").forEach(input => {
const datalist = document.getElementById(input.getAttribute("list"));
let shadowDatalist;
const optionsAreUnique = input.classList.contains("tags-input-unique");
input.oninput = function () {
const options = [...datalist.querySelectorAll("option")].map(x => x.textContent);
if (!shadowDatalist) {
shadowDatalist = document.createElement("datalist");
shadowDatalist.setAttribute("id", "shadow-" + datalist.id);
input.setAttribute("list", "shadow-" + datalist.id);
shadowDatalist = document.documentElement.appendChild(shadowDatalist);
}
const value = [];
for (const tag of this.value.split(",")) {
const index = options.map(x => x.toLowerCase()).indexOf(tag.trim().toLowerCase());
if (index != -1) {
value.push(options[index]);
}
}
this.setAttribute("data-tags-value", value.join(", "));
if (value[0] == "all") {
shadowDatalist.innerHTML = "<option>all</option>";
} else {
shadowDatalist.innerHTML = "";
for (const option of options) {
if (!optionsAreUnique || value.indexOf(option) == -1) {
const elm = document.createElement("option");
if (option == "all") {
elm.textContent = "all";
} else {
elm.textContent = [...value, option, ""].join(", ");
}
shadowDatalist.appendChild(elm);
}
}
}
};
input.oninput();
});
function fromUpgradeFingerprintValue(raw, min) {
const range = 0.6 - min;
return min + (raw * range) / 0x3fffffff;
}
function toUpgradeFingerprintValue(value, min) {
const range = 0.6 - min;
return Math.trunc(((value - min) * 0x3fffffff) / range);
}
function fromU5UpgradeFingerprintValue(raw, min, max) {
const range = max - min;
return min + range * raw;
}
function toU5UpgradeFingerprintValue(value, min, max) {
const range = max - min;
return (value - min) / range;
}
function handleValenceBonusChange(event) {
event.preventDefault();
const urlParams = new URLSearchParams(window.location.search);
const action = event.submitter.value;
const Tag = document.getElementById("valenceBonus-innateDamage").value;
const Value = toUpgradeFingerprintValue(document.getElementById("valenceBonus-procent").value / 100, 0.25);
revalidateAuthz().then(() => {
$.post({
url: "/custom/updateFingerprint?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
category: urlParams.get("productCategory"),
oid: urlParams.get("itemId"),
action,
upgradeType: "/Lotus/Weapons/Grineer/KuvaLich/Upgrades/InnateDamageRandomMod",
upgradeFingerprint: {
buffs: [
{
Tag,
Value
}
]
}
})
}).done(function () {
inventory_data = undefined; // UI is up-to-date now, but subsequent updates will need to grab new data
});
});
}
function handleU5ModEdit(event) {
event.preventDefault();
const urlParams = new URLSearchParams(window.location.search);
const action = event.submitter.value;
revalidateAuthz().then(() => {
const upgradeFields = document.getElementById("u5ModEdit-upgradeFields");
const upgradeGroups = Array.from(upgradeFields.children);
const upgrades = upgradeGroups
.map(group => {
const upgradeSelect = group.querySelector("#u5ModEdit-upgradeType");
const raritySelect = group.querySelector("#u5ModEdit-upgradeRarity");
const valueInput = group.querySelector("#u5ModEdit-upgradeValue");
const statAtten = parseFloat(group.parentNode.getAttribute("data-stat-atten"));
const value =
valueInput.valueAsNumber == valueInput.min
? valueInput.valueAsNumber + 0.001
: valueInput.valueAsNumber;
return {
upgrade: upgradeSelect.value,
valueRarity: raritySelect.value,
value: Math.min(
toU5UpgradeFingerprintValue(
value / statAtten,
valueInput.min / statAtten,
valueInput.max / statAtten
),
1
)
};
})
.filter(item => {
return item.upgrade != "" && item.value != 0;
});
$.post({
url: "/custom/updateFingerprint?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
category: urlParams.get("productCategory"),
oid: urlParams.get("itemId"),
action,
upgradeFingerprint: {
reqLevel: Number(document.getElementById("u5ModEdit-reqLevel").value),
fits: document.getElementById("u5ModEdit-fits").value,
upgrades
}
})
}).done(function () {
toast(loc("code_succChange"));
updateInventory();
});
});
}
function createUpgradeInput(targetDiv, itemMap, itemType, upgrade) {
const meta = itemMap[itemType];
const statAtten = targetDiv.getAttribute("data-stat-atten") || 1;
const group = document.createElement("div");
group.classList.add("upgrade-group", "mb-2", "d-flex", "gap-2");
{
const select = document.createElement("select");
select.classList.add("form-control");
select.id = "u5ModEdit-upgradeType";
const optionNone = document.createElement("option");
select.appendChild(optionNone);
meta.upgrades.forEach(up => {
const option = document.createElement("option");
option.value = up.type;
const locType =
(itemType.endsWith("GrineerMeleeModule") || itemType.endsWith("TennoSwordModule")) &&
up.type == "WEAPON_FIRE_RATE"
? "WEAPON_MELEE_FIRE_RATE"
: up.type;
if (
[
"AVATAR_ABILITY_DURATION",
"AVATAR_ABILITY_EFFICIENCY",
"AVATAR_ABILITY_RANGE",
"AVATAR_ABILITY_STRENGTH",
"WEAPON_ARMOR_PIERCING_DAMAGE",
"WEAPON_MELEE_CHARGE_RATE",
"WEAPON_MELEE_HEAVY_DAMAGE",
"WEAPON_STUN_CHANCE"
].includes(locType)
) {
option.textContent = loc(locType);
} else {
option.textContent = toTitleCase(itemMap["/Lotus/Language/Labels/" + locType]?.name) ?? locType;
}
select.appendChild(option);
});
if (upgrade) {
select.value = upgrade.upgrade;
}
select.addEventListener("change", () => {
updateUpgradeInput(meta, group);
checkAddOrDeleteUpgradeInput(itemMap, itemType);
});
group.appendChild(select);
}
{
const select = document.createElement("select");
select.classList.add("form-control");
select.id = "u5ModEdit-upgradeRarity";
["COMMON", "UNCOMMON", "RARE"].forEach(rarity => {
const option = document.createElement("option");
option.value = rarity;
const locKey = "code_rarity" + rarity.charAt(0) + rarity.slice(1).toLowerCase();
option.textContent = loc(locKey);
option.setAttribute("data-loc", locKey);
select.appendChild(option);
});
if (upgrade) {
select.value = upgrade.valueRarity ?? "COMMON";
} else {
select.value = "COMMON";
}
select.addEventListener("change", () => updateUpgradeInput(meta, group));
group.appendChild(select);
}
{
let valueRange = [0, 0];
let upgradeValue = 0;
let displayAsPercent = undefined;
if (upgrade) {
// U5.1 client just igrone Rare value ranges
const targetValueRarity = upgrade.valueRarity == "RARE" ? "UNCOMMON" : upgrade.valueRarity;
const upgradeMeta = meta.upgrades.find(up => up.type == upgrade.upgrade);
valueRange = [...(upgradeMeta.valueRarity[targetValueRarity] || [0, 0])];
const operation = upgradeMeta.operation;
displayAsPercent = upgradeMeta.displayAsPercent || operation == "MULTIPLY";
if (displayAsPercent) {
valueRange[0] = valueRange[0] * 100 - (operation == "MULTIPLY" ? 100 : 0);
valueRange[1] = valueRange[1] * 100 - (operation == "MULTIPLY" ? 100 : 0);
}
upgradeValue = +(
fromU5UpgradeFingerprintValue(upgrade.value, valueRange[0], valueRange[1]) * statAtten
).toFixed(1);
}
const inputGroup = document.createElement("div");
inputGroup.classList.add("input-group", "d-flex", "flex-shrink-0");
inputGroup.style.flexBasis = "200px";
const input = document.createElement("input");
input.type = "number";
input.classList.add("form-control");
input.step = "0.1";
input.id = "u5ModEdit-upgradeValue";
input.min = valueRange[0] ? +(valueRange[0] * statAtten).toFixed(1) : 0;
input.max = valueRange[1] ? +(valueRange[1] * statAtten).toFixed(1) : 0;
input.value = upgradeValue;
input.title = loc("code_range").replace("|MIN|", input.min).replace("|MAX|", input.max);
const rangePrefix = document.createElement("span");
rangePrefix.textContent = "+";
rangePrefix.classList.add("input-group-text");
const rangeSuffix = document.createElement("span");
rangeSuffix.classList.add("input-group-text");
rangeSuffix.id = "u5ModEdit-upgradeValueSuffix";
rangeSuffix.textContent = displayAsPercent ? "%" : itemMap["/Lotus/Language/UiElements/PLACEMENT_METERS"].name;
inputGroup.appendChild(rangePrefix);
inputGroup.appendChild(input);
if (displayAsPercent != undefined) {
inputGroup.appendChild(rangeSuffix);
}
group.appendChild(inputGroup);
}
targetDiv.appendChild(group);
}
function updateUpgradeInput(meta, group) {
const fitsSelect = document.getElementById("u5ModEdit-fits");
const fit = meta.fits.find(f => f.type == fitsSelect.value);
const statAtten = fit?.statAtten || 1;
const upgradeSelect = group.querySelector("#u5ModEdit-upgradeType");
const raritySelect = group.querySelector("#u5ModEdit-upgradeRarity");
const input = group.querySelector("#u5ModEdit-upgradeValue");
let rangeSuffix = group.querySelector("#u5ModEdit-upgradeValueSuffix");
const upgradeMeta = meta.upgrades.find(up => up.type == upgradeSelect.value);
const targetValueRarity = raritySelect.value == "RARE" ? "UNCOMMON" : raritySelect.value;
let valueRange = [...(upgradeMeta?.valueRarity[targetValueRarity] || [0, 0])];
const operation = upgradeMeta?.operation;
const displayAsPercent = upgradeMeta?.displayAsPercent || operation == "MULTIPLY";
if (displayAsPercent) {
valueRange[0] = valueRange[0] * 100 - (operation == "MULTIPLY" ? 100 : 0);
valueRange[1] = valueRange[1] * 100 - (operation == "MULTIPLY" ? 100 : 0);
}
if (upgradeMeta && !rangeSuffix) {
rangeSuffix = document.createElement("span");
rangeSuffix.classList.add("input-group-text");
rangeSuffix.id = "u5ModEdit-upgradeValueSuffix";
input.parentNode.appendChild(rangeSuffix);
}
if (rangeSuffix) rangeSuffix.textContent = displayAsPercent ? "%" : "m";
const oldstatAtten = parseFloat(group.parentNode.getAttribute("data-stat-atten")) || 1;
const oldUpgradeValue = toU5UpgradeFingerprintValue(
input.valueAsNumber / oldstatAtten,
input.min / oldstatAtten,
input.max / oldstatAtten
);
input.min = valueRange[0] ? +(valueRange[0] * statAtten).toFixed(1) : 0;
input.max = valueRange[1] ? +(valueRange[1] * statAtten).toFixed(1) : 0;
input.title = loc("code_range").replace("|MIN|", input.min).replace("|MAX|", input.max);
group.parentNode.setAttribute("data-stat-atten", statAtten);
input.value =
+(fromU5UpgradeFingerprintValue(oldUpgradeValue, valueRange[0], valueRange[1]) * statAtten).toFixed(1) ||
input.max;
if (input.value < input.min) input.value = input.min;
}
function checkAddOrDeleteUpgradeInput(itemMap, itemType) {
const upgradeFields = document.getElementById("u5ModEdit-upgradeFields");
const upgradeGroups = Array.from(upgradeFields.children);
upgradeGroups.forEach((group, index) => {
const upgradeSelect = group.querySelector("#u5ModEdit-upgradeType");
if (!upgradeSelect.value && index !== upgradeGroups.length - 1) {
upgradeFields.removeChild(group);
}
});
const lastUpgradeSelect = upgradeGroups[upgradeGroups.length - 1].querySelector("#u5ModEdit-upgradeType");
if (lastUpgradeSelect.value) {
createUpgradeInput(upgradeFields, itemMap, itemType, null);
}
}
document.querySelectorAll("#sidebar .nav-link").forEach(function (elm) {
elm.addEventListener("click", function () {
window.scrollTo(0, 0);
});
});
async function markAllAsRead() {
await revalidateAuthz();
const { Inbox } = await fetch("/api/inbox.php?" + window.authz).then(x => x.json());
let any = false;
for (const msg of Inbox) {
if (!msg.r) {
await fetch("/api/inbox.php?" + window.authz + "&messageId=" + msg.messageId.$oid);
any = true;
}
}
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"));
inventory_data = undefined; // UI is up-to-date now, but subsequent updates will need to grab new data
});
});
}
}
function submitInvigoration(event) {
event.preventDefault();
const OffensiveUpgrade = document.getElementById("invigoration-offensive").value;
const DefensiveUpgrade = document.getElementById("invigoration-defensive").value;
const expiry = document.getElementById("invigoration-expiry").value;
if (!OffensiveUpgrade || !DefensiveUpgrade) {
toast(loc("code_requiredInvigorationUpgrade"), "warning");
return;
}
setInvigoration({
OffensiveUpgrade,
DefensiveUpgrade,
UpgradesExpiry: expiry ? new Date(expiry).getTime() : Date.now() + 7 * 24 * 60 * 60 * 1000
});
}
function setInvigoration(data) {
const oid = new URLSearchParams(window.location.search).get("itemId");
$.post({
url: "/custom/setInvigoration?" + window.authz,
contentType: "application/json",
data: JSON.stringify({ oid, ...data })
}).done(function (updatedSuit) {
const index = inventory_data.Suits.findIndex(x => x.ItemId.$oid == oid);
inventory_data.Suits[index] = updatedSuit;
populateDetailedViewRoute();
});
}
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 (updatedSuit) {
const index = inventory_data.Suits.findIndex(x => x.ItemId.$oid == oid);
inventory_data.Suits[index] = updatedSuit;
populateDetailedViewRoute();
});
}
function handleAbilityOverride(event, configIndex) {
event.preventDefault();
const urlParams = new URLSearchParams(window.location.search);
const action = event.submitter.value;
const Ability = getKey(document.getElementById(`abilityOverride-ability-config-${configIndex}`));
const input = document.getElementById(`abilityOverride-ability-index-config-${configIndex}`);
if (action == "set") {
if (!Ability) {
$(`#abilityOverride-ability-config-${configIndex}`).addClass("is-invalid").focus();
return;
}
if (!input.value) {
$(`#abilityOverride-ability-index-config-${configIndex}`).addClass("is-invalid").focus();
return;
}
}
const category = urlParams.get("productCategory");
const oid = urlParams.get("itemId");
const Index = input.value - 1;
revalidateAuthz().then(() => {
$.post({
url: "/custom/abilityOverride?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
category,
oid,
configIndex,
action,
AbilityOverride: {
Ability,
Index
}
})
}).done(function (updatedItem) {
const index = inventory_data[category].findIndex(x => x.ItemId.$oid == oid);
inventory_data[category][index] = updatedItem;
if (action != "set") {
populateDetailedViewRoute();
}
toast(loc("code_succChange"));
});
});
}
function removeItems(category) {
revalidateAuthz().then(() => {
const count = document.getElementById(category + "-list").getElementsByTagName("tr").length;
let message = loc("code_bulkRemoveConfirm").replaceAll("|COUNT|", count);
if (category === "Suits") message += "\n" + loc("code_bulkRemoveNote");
if (count != 0 && window.confirm(message)) {
$.get("/custom/removeItems?" + window.authz + "&category=" + category).done(function () {
updateInventory();
});
}
});
}
single.getRoute("/webui/users").on("beforeload", function () {
awaitAuthz().then(() => {
if (window.is_admin === false) {
$(".admin-hide").removeClass("d-none");
$(".admin-show").addClass("d-none");
return;
}
$.get("/custom/getRegisteredLosers?" + window.authz)
.done(users => {
window.is_admin = true;
$(".admin-hide").addClass("d-none");
$(".admin-show").removeClass("d-none");
document.getElementById("registered-losers").innerHTML = "";
document.getElementById("datalist-usernames").innerHTML = "<option>all</option>";
for (const user of users) {
const tr = document.createElement("tr");
{
const td = document.createElement("td");
td.textContent = user.DisplayName;
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.innerHTML = `<code>${user.id}</code>`;
tr.appendChild(td);
}
{
const td = document.createElement("td");
if (window.accountId != user.id) {
const a = document.createElement("a");
a.textContent = loc("admin_possess");
a.setAttribute("data-loc", "admin_possess");
a.href = "#";
a.onclick = function () {
localStorage.setItem("possessing", user.id);
doAccountSwitch("/webui/inventory");
};
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("registered-losers").appendChild(tr);
const option = document.createElement("option");
option.textContent = user.DisplayName;
document.getElementById("datalist-usernames").appendChild(option);
}
})
.fail(res => {
if (res.responseText == "Log-in expired") {
if (ws_is_open && !auth_pending) {
console.warn("Credentials invalidated but the server didn't let us know");
sendAuth();
}
revalidateAuthz().then(() => {
if (single.getCurrentPath() == "/webui/users") {
single.loadRoute("/webui/users");
}
});
} else {
window.is_admin = false;
$(".admin-hide").removeClass("d-none");
$(".admin-show").addClass("d-none");
}
});
});
});
async function doAdminBroadcastInbox() {
await revalidateAuthz();
const sender = document.getElementById("admin-message-sender").value.trim();
const subject = document.getElementById("admin-message-subject").value.trim();
const body = document.getElementById("admin-message-body").value.trim();
const icon = document.getElementById("admin-message-icon").value.trim();
const attRaw = document.getElementById("admin-message-att").value;
const countedAttRaw = document.getElementById("admin-message-counted-att").value.trim();
const highPriority = document.getElementById("admin-message-high-priority").checked;
const submitButton = document.getElementById("admin-broadcast-submit");
if (!sender || !subject || !body) {
if (!sender) {
document.getElementById("admin-message-sender").classList.add("is-invalid");
}
if (!subject) {
document.getElementById("admin-message-subject").classList.add("is-invalid");
}
if (!body) {
document.getElementById("admin-message-body").classList.add("is-invalid");
}
return;
}
let countedAttachments;
if (countedAttRaw) {
try {
countedAttachments = JSON.parse(countedAttRaw);
} catch (_) {}
if (!Array.isArray(countedAttachments)) {
document.getElementById("admin-message-counted-att").classList.add("is-invalid");
document.getElementById("admin-message-counted-att").focus();
return;
}
}
const attachments = attRaw
.split("\n")
.map(x => x.trim())
.filter(x => x);
submitButton.disabled = true;
try {
const users = await $.get("/custom/getRegisteredLosers?" + window.authz);
document.getElementById("admin-message-target").oninput();
const targets = document
.getElementById("admin-message-target")
.getAttribute("data-tags-value")
.split(", ")
.filter(x => x);
let targetIds;
if (targets[0] == "all") {
targetIds = users.map(user => user.id);
} else {
targetIds = users.filter(user => targets.indexOf(user.DisplayName) != -1).map(user => user.id);
}
if (targetIds.length == 0) {
toast(loc("code_nothingToDo"));
return;
}
if (!window.confirm(loc("admin_sendConfirm").replaceAll("|COUNT|", String(targetIds.length)))) {
return;
}
const messages = [
{
sndr: sender,
sub: subject,
msg: body,
highPriority: highPriority
}
];
if (icon) {
messages[0].icon = icon;
}
if (attachments.length > 0) {
messages[0].att = attachments;
}
if (countedAttachments && countedAttachments.length > 0) {
messages[0].countedAtt = countedAttachments;
}
await $.ajax({
url: "/custom/createMessage?" + window.authz,
method: "POST",
contentType: "application/json",
data: JSON.stringify({ targetIds, messages })
});
toast(loc("admin_sendToAllSuccess").replaceAll("|COUNT|", targetIds.length));
} catch (error) {
alert(error);
} finally {
submitButton.disabled = false;
}
}
function normalizeText(str) {
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
}
function toTitleCase(srt) {
return srt
.replace(/[_-]+/g, " ")
.trim()
.toLocaleLowerCase()
.replace(/(^|[\s])\p{L}/gu, match => match.toLocaleUpperCase());
}
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-floating-promises */
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable no-undef */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
const allPermissions = [];
document.querySelectorAll("[data-enabling-permission]").forEach(elm => {
const perm = elm.getAttribute("data-enabling-permission");
if (allPermissions.indexOf(perm) == -1) {
allPermissions.push(perm);
}
});
let auth_pending = false,
ws_is_open = false,
wsid = 0,
ws_reconnect = false;
const sendAuth = isRegister => {
if (ws_is_open && localStorage.getItem("email") && localStorage.getItem("password")) {
auth_pending = true;
window.ws.send(
JSON.stringify({
auth: {
email: localStorage.getItem("email").toLowerCase(),
password: wp.encSync(localStorage.getItem("password")),
possessing: localStorage.getItem("possessing"),
isRegister,
allPermissions
}
})
);
return true;
}
return false;
};
function openWebSocket() {
const wsProto = location.protocol === "https:" ? "wss://" : "ws://";
window.ws = new WebSocket(wsProto + location.host + "/custom/ws");
window.ws.onopen = () => {
ws_is_open = true;
sendAuth(false);
};
window.ws.onmessage = e => {
const msg = JSON.parse(e.data);
if ("wsid" in msg) {
wsid = msg.wsid;
}
if ("reload" in msg) {
setTimeout(() => {
getWebSocket().then(() => {
location.reload();
});
}, 100);
}
if ("ports" in msg) {
location.port = location.protocol == "https:" ? msg.ports.https : msg.ports.http;
}
if ("config_reloaded" in msg) {
refreshServerConfig();
if (window.authz) {
// Our permissions may have changed, so resync them with the server.
ws.send(JSON.stringify({ allPermissions }));
}
}
if ("auth_succ" in msg) {
auth_pending = false;
const data = msg.auth_succ;
if (single.getCurrentPath() == "/webui/") {
single.loadRoute("/webui/inventory");
}
$("#email").val("");
$("#password").val("");
$(".displayname").text(data.DisplayName);
window.accountId = data.id;
window.authz = "accountId=";
let possessing = localStorage.getItem("possessing");
if (possessing == data.id) {
localStorage.removeItem("possessing");
possessing = null;
}
if (possessing) {
window.accountId = possessing;
window.authz += possessing + "&possesser=";
}
window.authz += data.id + "&nonce=" + data.Nonce + "&wsid=" + wsid;
if (window.dict) {
updateLocElements();
}
if (ws_reconnect) {
ws_reconnect = false;
// Anything could've happened during the time we were disconnected and not getting events.
single.loadRoute(single.getCurrentPath());
}
}
if ("permissions" in msg) {
for (const perm of allPermissions) {
const granted = msg.permissions.indexOf(perm) != -1;
//console.log(`Permission ${perm} was ${granted ? 'granted' : 'denied'}`);
document.querySelectorAll(`[data-enabling-permission='${perm}']`).forEach(elm => {
elm.disabled = !granted;
});
}
}
if ("auth_fail" in msg) {
auth_pending = false;
if (localStorage.getItem("possessing")) {
localStorage.removeItem("possessing");
doAccountSwitch("/webui/users");
} else {
logout();
if (single.getCurrentPath() == "/webui/") {
if (msg.auth_fail == "bad login") {
alert(loc("code_loginFail"));
} else if (msg.auth_fail == "bad register") {
alert(loc("code_regFail"));
} else if (msg.auth_fail == "admin only") {
alert(loc("code_adminOnlyLogin"));
} else if (msg.auth_fail == "registered but admin only") {
alert(loc("code_adminOnlyRegister"));
} else {
alert(msg.auth_fail);
}
} else {
single.loadRoute("/webui/");
}
}
}
if ("nonce_updated" in msg) {
if (!sendAuth()) {
single.loadRoute("/webui/"); // Show login screen
}
}
if ("update_inventory" in msg) {
updateInventory();
}
if ("update_guild" in msg) {
updateGuild();
}
if ("logged_out" in msg) {
if (localStorage.getItem("possessing")) {
localStorage.removeItem("possessing");
doAccountSwitch("/webui/users");
} else {
logout();
single.loadRoute("/webui/"); // Show login screen
}
}
if ("have_game_ws" in msg) {
window.have_game_ws = msg.have_game_ws;
if (window.dict) {
$(".inventory-update-note").text(
loc(msg.have_game_ws ? "general_inventoryUpdateNoteGameWs" : "general_inventoryUpdateNote")
);
}
}
};
window.ws.onclose = function () {
ws_is_open = false;
ws_reconnect = true;
invalidateCachedData();
window.subscribedToGuildId = undefined;
setTimeout(openWebSocket, 3000);
};
}
openWebSocket();
function refreshServerConfig() {
window.is_admin = undefined;
config_data = undefined;
if (single.getCurrentPath() == "/webui/cheats" || single.getCurrentPath() == "/webui/users") {
single.loadRoute(single.getCurrentPath());
}
}
function invalidateCachedData() {
inventory_data = undefined;
guild_data = undefined;
window.is_admin = undefined;
config_data = undefined;
}
function doAccountSwitch(to_route) {
window.authz = undefined;
invalidateCachedData();
single.loadRoute(to_route);
sendAuth();
}
function getWebSocket() {
return new Promise(resolve => {
let interval;
interval = setInterval(() => {
if (ws_is_open) {
clearInterval(interval);
resolve(window.ws);
}
}, 10);
});
}
window.registerSubmit = false;
function doLogin() {
if (auth_pending) {
return;
}
localStorage.setItem("email", $("#email").val());
localStorage.setItem("password", $("#password").val());
sendAuth(registerSubmit);
window.registerSubmit = false;
}
function awaitAuthz() {
return new Promise(resolve => {
let interval;
interval = setInterval(() => {
if (window.authz) {
clearInterval(interval);
resolve();
}
}, 10);
});
}
function revalidateAuthz() {
return new Promise(resolve => {
let interval;
interval = setInterval(() => {
if (ws_is_open && !auth_pending) {
clearInterval(interval);
resolve();
}
}, 10);
});
}
function logout() {
localStorage.removeItem("email");
localStorage.removeItem("password");
window.authz = undefined;
invalidateCachedData();
}
function doLogout() {
if (localStorage.getItem("possessing")) {
localStorage.removeItem("possessing");
doAccountSwitch("/webui/users");
} else {
logout();
if (ws_is_open) {
// Unsubscribe from notifications about nonce invalidation
window.ws.send(JSON.stringify({ logout: true }));
}
location.href = "/webui/";
}
}
function renameAccount(taken_name) {
const newname = window.prompt(
`${taken_name ? loc("code_changeNameRetry").replace("|NAME|", taken_name) + " " : ""}${loc("code_changeNameConfirm")}`
);
if (newname) {
revalidateAuthz().then(() => {
fetch("/custom/renameAccount?" + window.authz + "&newname=" + encodeURIComponent(newname)).then(res => {
if (res.status == 200) {
$(".displayname").text(newname);
updateLocElements();
toast(loc("code_succRelog"));
} else if (res.status == 409) {
renameAccount(newname);
} else {
toast(loc("code_genFail"));
}
});
});
}
}
function deleteAccount() {
if (
window.confirm(
loc(localStorage.getItem("possessing") ? "code_deletePossessedAccountConfirm" : "code_deleteAccountConfirm")
)
) {
revalidateAuthz().then(() => {
fetch("/custom/deleteAccount?" + window.authz);
});
}
}
function changeAccountPassword() {
const cur = window.prompt(loc("code_promptCurrentPassword"));
if (cur === null) {
return;
}
if (!cur.length) {
toast(loc("settings_changeFailed"));
return;
}
const nw = window.prompt(loc("code_promptNewPassword"));
if (nw === null) {
return;
}
if (!nw.length) {
toast(loc("settings_changeFailed"));
return;
}
const cf = window.prompt(loc("code_promptConfirmNewPassword"));
if (cf === null) {
return;
}
if (nw !== cf) {
toast(loc("settings_passwordMismatch"));
return;
}
revalidateAuthz().then(() => {
fetch("/custom/changePassword?" + window.authz, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
currentPassword: wp.encSync(cur),
newPassword: wp.encSync(nw)
})
}).then(res => {
if (res.status == 200) {
localStorage.setItem("password", nw);
res.text().then(text => {
toast(loc(text == "noop" ? "code_nothingToDo" : "settings_passwordSuccess"));
});
} else if (res.status == 403) {
toast(loc("settings_wrongPassword"));
} else {
toast(loc("settings_changeFailed"));
}
});
});
}
function changeAccountEmail() {
const curPw = window.prompt(loc("code_promptCurrentPassword"));
if (curPw === null) {
return;
}
if (!curPw.length) {
toast(loc("settings_changeFailed"));
return;
}
const currentHash = wp.encSync(curPw);
function promptAndSend(conflictEmail) {
const newEmailRaw = window.prompt(
`${conflictEmail ? loc("code_changeEmailRetry").replace("|EMAIL|", conflictEmail) + " " : ""}${loc("code_promptNewEmail")}`
);
if (newEmailRaw === null) {
return;
}
const newEmail = newEmailRaw.trim().toLowerCase();
if (!newEmail.length) {
toast(loc("settings_changeFailed"));
return;
}
if (!newEmail.includes("@")) {
toast(loc("settings_changeFailed"));
return;
}
revalidateAuthz().then(() => {
fetch("/custom/changeEmail?" + window.authz, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
currentPassword: currentHash,
newEmail
})
}).then(res => {
if (res.status == 200) {
localStorage.setItem("email", newEmail);
res.text().then(text => {
toast(loc(text == "noop" ? "code_nothingToDo" : "settings_emailSuccess"));
});
} else if (res.status == 403) {
toast(loc("settings_wrongPassword"));
} else if (res.status == 409) {
promptAndSend(newEmail);
} else {
toast(loc("settings_changeFailed"));
}
});
});
}
promptAndSend();
}
function updateTitle() {
const route = single.getCurrentRoute();
if (route) {
const tag = route.elm.getAttribute("data-title-tag");
if (tag) {
document.querySelector("title").textContent = loc(tag) + " | OpenWF WebUI";
}
}
}
single.on("route_load", function (event) {
if (event.route.paths[0] != "/webui/") {
// Authorised route?
if (!localStorage.getItem("email")) {
// Not logged in?
return single.loadRoute("/webui/"); // Show login screen
}
$("body").addClass("logged-in");
let navPath = event.route.paths[0];
if (navPath == "/webui/details") {
const params = new URLSearchParams(window.location.search);
const category = params.get("productCategory");
navPath = category == "Upgrades" ? "/webui/mods" : "/webui/inventory";
}
$("#sidebar .nav-link").removeClass("active");
const navLink = document.querySelector(".nav-link[href='" + navPath + "']");
if (navLink) {
navLink.classList.add("active");
} else {
console.warn("No sidebar entry for current route!");
}
} else {
$("body").removeClass("logged-in");
}
window.dictPromise.then(() => {
updateTitle();
});
});
function loc(tag) {
return ((window.dict ?? {})[tag] ?? tag)
.replaceAll("|DISPLAYNAME|", document.querySelector(".displayname").textContent)
.replaceAll("|EMAIL|", localStorage.getItem("email"));
}
function updateLocElements() {
document.querySelectorAll("[data-loc]").forEach(elm => {
elm.innerHTML = loc(elm.getAttribute("data-loc"));
});
document.querySelectorAll("[data-loc-placeholder]").forEach(elm => {
elm.placeholder = loc(elm.getAttribute("data-loc-placeholder"));
});
document.querySelectorAll("[data-loc-title]").forEach(elm => {
elm.title = loc(elm.getAttribute("data-loc-title"));
});
$(".inventory-update-note").text(
loc(window.have_game_ws ? "general_inventoryUpdateNoteGameWs" : "general_inventoryUpdateNote")
);
window.itemListPromise.then(itemMap => {
document.querySelectorAll("[data-loc-itemMap]").forEach(elm => {
const key = elm.getAttribute("data-loc-itemMap");
const item = itemMap[key];
elm.innerHTML = item && item.name ? item.name : key;
});
document.querySelectorAll("[data-loc-itemMap-norm]").forEach(elm => {
const key = elm.getAttribute("data-loc-itemMap-norm");
const item = itemMap[key];
elm.innerHTML = item && item.name ? normalizeText(item.name) : key;
});
document.querySelectorAll("[data-loc-itemMap-tc]").forEach(elm => {
const key = elm.getAttribute("data-loc-itemMap-tc");
const item = itemMap[key];
elm.innerHTML = item && item.name ? toTitleCase(item.name) : key;
});
document.querySelectorAll("[data-loc-inc]").forEach(elm => {
const incWith = elm
.getAttribute("data-loc-inc")
.split("|")
.map(key => {
if (key.startsWith("/")) {
return toTitleCase(itemMap[key]?.name ?? key);
}
return loc(key).replace(/<[^>]+>/g, "");
})
.join(", ");
elm.title = `${loc("worldState_incompatibleWith")} ${incWith}`;
});
document.querySelectorAll("[data-loc-replace]").forEach(elm => {
elm.innerHTML = elm.innerHTML
.replaceAll("|val|", "|VAL|")
// de and uk for no reason adds space between value and %
.replaceAll("|VAL| %", "|VAL|%") // de
.replaceAll("|VAL| %", "|VAL|%"); // uk
elm.innerHTML = elm.innerHTML.replace("|VAL|", elm.getAttribute("data-loc-replace"));
if (elm.getAttribute("data-loc") == "worldState_lunarNewYearEntry") {
const animal = loc(
`worldState_lunarNewYearAnimal${((parseInt(elm.getAttribute("data-loc-replace")) - 4) % 12) + 1}`
);
elm.innerHTML = elm.innerHTML.replace("|ANIMAL|", animal);
}
});
});
}
function setActiveLanguage(lang) {
window.lang = lang;
const lang_name = document.querySelector("[data-lang=" + lang + "]").textContent;
document.getElementById("active-lang-name").textContent = lang_name;
document.querySelector("[data-lang].active").classList.remove("active");
document.querySelector("[data-lang=" + lang + "]").classList.add("active");
window.dictPromise = new Promise(resolve => {
const webui_lang = ["en", "ru", "fr", "de", "zh", "es", "uk"].indexOf(lang) == -1 ? "en" : lang;
let script = document.getElementById("translations");
if (script) document.documentElement.removeChild(script);
script = document.createElement("script");
script.id = "translations";
script.src = "/webui/translations/" + webui_lang + ".js";
script.onload = function () {
updateLocElements();
updateTitle();
resolve(window.dict);
};
script.onerror = function () {
location.reload();
};
document.documentElement.appendChild(script);
});
}
setActiveLanguage(localStorage.getItem("lang") ?? webui_conf.defaultLanguage ?? "en");
function setLanguage(lang) {
setActiveLanguage(lang);
localStorage.setItem("lang", lang);
if (window.authz) {
// Not in prelogin state?
fetchItemList();
single.loadRoute(single.getCurrentPath());
}
}
function setActiveTheme(theme) {
document.documentElement.setAttribute("data-bs-theme", theme);
document.querySelector("[data-theme].active").classList.remove("active");
document.querySelector("[data-theme=" + theme + "]").classList.add("active");
}
setActiveTheme(localStorage.getItem("theme") ?? "dark");
function setTheme(theme) {
setActiveTheme(theme);
localStorage.setItem("theme", theme);
}
const webUiModularWeapons = [
"/Lotus/Weapons/Sentients/OperatorAmplifiers/OperatorAmpWeapon",
"/Lotus/Weapons/Ostron/Melee/LotusModularWeapon",
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary",
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary",
"/Lotus/Types/Friendly/Pets/MoaPets/MoaPetPowerSuit",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/VulpineInfestedCatbrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/HornedInfestedCatbrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/ArmoredInfestedCatbrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/VizierPredatorKubrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/PharaohPredatorKubrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/MedjayPredatorKubrowPetPowerSuit"
];
const permanentEvolutionWeapons = new Set([
"/Lotus/Weapons/Tenno/Zariman/LongGuns/PumpShotgun/ZarimanPumpShotgun",
"/Lotus/Weapons/Tenno/Zariman/LongGuns/SemiAutoRifle/ZarimanSemiAutoRifle",
"/Lotus/Weapons/Tenno/Zariman/Melee/Dagger/ZarimanDaggerWeapon",
"/Lotus/Weapons/Tenno/Zariman/Melee/Tonfas/ZarimanTonfaWeapon",
"/Lotus/Weapons/Tenno/Zariman/Pistols/HeavyPistol/ZarimanHeavyPistol",
"/Lotus/Weapons/Thanotech/EntFistIncarnon/EntFistIncarnon",
"/Lotus/Weapons/Thanotech/EntratiWristGun/EntratiWristGunWeapon",
"/Lotus/Weapons/Tenno/Zariman/Melee/HeavyScythe/ZarimanHeavyScythe/ZarimanHeavyScytheWeapon"
]);
const icons = {
angleDown: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"/></svg>`,
angleLeft: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M41.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 256 246.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z"/></svg>`,
angleRight: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M278.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-160 160c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L210.7 256 73.4 118.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l160 160z"/></svg>`,
angleUp: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M233.4 105.4c12.5-12.5 32.8-12.5 45.3 0l192 192c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L256 173.3 86.6 342.6c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3l192-192z"/></svg>`,
arrowRotateRight: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M463.5 224l8.5 0c13.3 0 24-10.7 24-24l0-128c0-9.7-5.8-18.5-14.8-22.2s-19.3-1.7-26.2 5.2L413.4 96.6c-87.6-86.5-228.7-86.2-315.8 1c-87.5 87.5-87.5 229.3 0 316.8s229.3 87.5 316.8 0c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0c-62.5 62.5-163.8 62.5-226.3 0s-62.5-163.8 0-226.3c62.2-62.2 162.7-62.5 225.3-1L327 183c-6.9 6.9-8.9 17.2-5.2 26.2s12.5 14.8 22.2 14.8l119.5 0z"/></svg>`,
arrowUp: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M214.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 141.2V448c0 17.7 14.3 32 32 32s32-14.3 32-32V141.2L329.4 246.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z"/></svg>`,
chartSimple: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M160 80c0-26.5 21.5-48 48-48h32c26.5 0 48 21.5 48 48V432c0 26.5-21.5 48-48 48H208c-26.5 0-48-21.5-48-48V80zM0 272c0-26.5 21.5-48 48-48H80c26.5 0 48 21.5 48 48V432c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V272zM368 96h32c26.5 0 48 21.5 48 48V432c0 26.5-21.5 48-48 48H368c-26.5 0-48-21.5-48-48V144c0-26.5 21.5-48 48-48z"/></svg>`,
check: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M438.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L160 338.7 393.4 105.4c12.5-12.5 32.8-12.5 45.3 0z"/></svg>`,
childReaching: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M256 64A64 64 0 1 0 128 64a64 64 0 1 0 128 0zM152.9 169.3c-23.7-8.4-44.5-24.3-58.8-45.8L74.6 94.2C64.8 79.5 45 75.6 30.2 85.4s-18.7 29.7-8.9 44.4L40.9 159c18.1 27.1 42.8 48.4 71.1 62.4L112 480c0 17.7 14.3 32 32 32s32-14.3 32-32l0-96 32 0 0 96c0 17.7 14.3 32 32 32s32-14.3 32-32l0-258.4c29.1-14.2 54.4-36.2 72.7-64.2l18.2-27.9c9.6-14.8 5.4-34.6-9.4-44.3s-34.6-5.5-44.3 9.4L291 122.4c-21.8 33.4-58.9 53.6-98.8 53.6c-12.6 0-24.9-2-36.6-5.8c-.9-.3-1.8-.7-2.7-.9z"/></svg>`,
circleStop: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M464 256A208 208 0 1 0 48 256a208 208 0 1 0 416 0zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256zm192-96l128 0c17.7 0 32 14.3 32 32l0 128c0 17.7-14.3 32-32 32l-128 0c-17.7 0-32-14.3-32-32l0-128c0-17.7 14.3-32 32-32z"/></svg>`,
feather: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M278.5 215.6L23 471c-9.4 9.4-9.4 24.6 0 33.9s24.6 9.4 33.9 0l57-57h68c49.7 0 97.9-14.4 139-41c11.1-7.2 5.5-23-7.8-23c-5.1 0-9.2-4.1-9.2-9.2c0-4.1 2.7-7.6 6.5-8.8l81-24.3c2.5-.8 4.8-2.1 6.7-4l22.4-22.4c10.1-10.1 2.9-27.3-11.3-27.3l-32.2 0c-5.1 0-9.2-4.1-9.2-9.2c0-4.1 2.7-7.6 6.5-8.8l112-33.6c4-1.2 7.4-3.9 9.3-7.7C506.4 207.6 512 184.1 512 160c0-41-16.3-80.3-45.3-109.3l-5.5-5.5C432.3 16.3 393 0 352 0s-80.3 16.3-109.3 45.3L139 149C91 197 64 262.1 64 330v55.3L253.6 195.8c6.2-6.2 16.4-6.2 22.6 0c5.4 5.4 6.1 13.6 2.2 19.8z"/></svg>`,
forward: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--!Font Awesome Free v7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M371.7 43.1C360.1 32 343 28.9 328.3 35.2S304 56 304 72l0 136.3-172.3-165.1C120.1 32 103 28.9 88.3 35.2S64 56 64 72l0 368c0 16 9.6 30.5 24.3 36.8s31.8 3.2 43.4-7.9L304 303.7 304 440c0 16 9.6 30.5 24.3 36.8s31.8 3.2 43.4-7.9l192-184c7.9-7.5 12.3-18 12.3-28.9s-4.5-21.3-12.3-28.9l-192-184z"/></svg>`,
graduationCap: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--!Font Awesome Free v7.0.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M48 195.8l209.2 86.1c9.8 4 20.2 6.1 30.8 6.1s21-2.1 30.8-6.1l242.4-99.8c9-3.7 14.8-12.4 14.8-22.1s-5.8-18.4-14.8-22.1L318.8 38.1C309 34.1 298.6 32 288 32s-21 2.1-30.8 6.1L14.8 137.9C5.8 141.6 0 150.3 0 160L0 456c0 13.3 10.7 24 24 24s24-10.7 24-24l0-260.2zm48 71.7L96 384c0 53 86 96 192 96s192-43 192-96l0-116.6-142.9 58.9c-15.6 6.4-32.2 9.7-49.1 9.7s-33.5-3.3-49.1-9.7L96 267.4z"/></svg>`,
person: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M112 48a48 48 0 1 1 96 0 48 48 0 1 1 -96 0zm40 304l0 128c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-223.1L59.4 304.5c-9.1 15.1-28.8 20-43.9 10.9s-20-28.8-10.9-43.9l58.3-97c17.4-28.9 48.6-46.6 82.3-46.6l29.7 0c33.7 0 64.9 17.7 82.3 46.6l58.3 97c9.1 15.1 4.2 34.8-10.9 43.9s-34.8 4.2-43.9-10.9L232 256.9 232 480c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-128-16 0z"/></svg>`,
plus: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free v7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M256 64c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 160-160 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l160 0 0 160c0 17.7 14.3 32 32 32s32-14.3 32-32l0-160 160 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-160 0 0-160z" /></svg>`,
star: `<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>`,
tag: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M0 80V229.5c0 17 6.7 33.3 18.7 45.3l176 176c25 25 65.5 25 90.5 0L418.7 317.3c25-25 25-65.5 0-90.5l-176-176c-12-12-28.3-18.7-45.3-18.7H48C21.5 32 0 53.5 0 80zm112 32a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>`,
trash: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"/></svg>`,
note: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320C64 461.4 178.6 576 320 576zM320 200C333.3 200 344 210.7 344 224L344 336C344 349.3 333.3 360 320 360C306.7 360 296 349.3 296 336L296 224C296 210.7 306.7 200 320 200zM293.3 416C292.7 406.1 297.6 396.7 306.1 391.5C314.6 386.4 325.3 386.4 333.8 391.5C342.3 396.7 347.2 406.1 346.6 416C347.2 425.9 342.3 435.3 333.8 440.5C325.3 445.6 314.6 445.6 306.1 440.5C297.6 435.3 292.7 425.9 293.3 416z"/></svg>`
};
const itemToString = item => item.name;
const itemToStringWithSubtype = item => (item.subtype ? item.name + " (" + item.subtype + ")" : item.name);
const itemToStringWithUniqueName = item => item.name + " [" + item.uniqueName.split("/").pop() + "]";
const areItemsUnambiguous = (items, stringifier) => {
const set = new Set();
for (const item of items) {
const str = stringifier(item);
if (set.has(str)) {
return false;
}
set.add(str);
}
return true;
};
function fetchItemList() {
window.itemListPromise = new Promise(resolve => {
const req = $.get("/custom/getItemLists?lang=" + window.lang);
req.done(async data => {
await dictPromise;
document.querySelectorAll('[id^="datalist-"]').forEach(datalist => {
if (datalist.id != "datalist-circuitGameModes" && datalist.id != "datalist-usernames") {
datalist.innerHTML = "";
}
});
const syndicateNone = document.createElement("option");
syndicateNone.value = "";
syndicateNone.textContent = loc("general_none");
document.getElementById("changeSyndicate").innerHTML = "";
document.getElementById("changeSyndicate").appendChild(syndicateNone);
document.getElementById("valenceBonus-innateDamage").innerHTML = "";
const rememberedVarziaOverride = document.getElementById("worldState.varziaOverride").value;
document.getElementById("worldState.varziaOverride").innerHTML = "";
const rememberedNWOverride = document.getElementById("worldState.nightwaveOverride").value;
document.getElementById("worldState.nightwaveOverride").innerHTML = "";
const missingAdditionalDict = new Set();
const getAddDict = locTag => {
const item = data.AdditionalDict.find(i => i.uniqueName === locTag);
if (!item) {
missingAdditionalDict.add(locTag);
return locTag;
}
return item.name
.replaceAll("|OPEN_COLOR|", "") // ArchonCrystal
.replaceAll("|CLOSE_COLOR|", "") // ArchonCrystal
.replaceAll(/<.+>/g, "") // Icons
.replaceAll("| %", "|%") // de
.replaceAll("| %", "|%") // uk
.replaceAll("| с", "|с") // uk
.replaceAll("| м ", "|м ") // uk
.replaceAll("| m ", "|m "); // pl
};
// prettier-ignore
data.archonCrystalUpgrades = {
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeEquilibrium": getAddDict("/Lotus/Language/Items/WarframeModPickupBonusDesc").replaceAll("|val|", "+20"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeEquilibriumMythic": getAddDict("/Lotus/Language/Items/WarframeModPickupBonusDesc").replaceAll("|val|", "+30"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeMeleeCritDamage": getAddDict("/Lotus/Language/Upgrades/MeleeCritDamage").replaceAll("|val|", "+25"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeMeleeCritDamageMythic": getAddDict("/Lotus/Language/Upgrades/MeleeCritDamage").replaceAll("|val|", "+37.5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradePrimaryStatusChance": getAddDict("/Lotus/Language/Upgrades/PrimaryStatusChance").replaceAll("|val|", "+25"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradePrimaryStatusChanceMythic": getAddDict("/Lotus/Language/Upgrades/PrimaryStatusChance").replaceAll("|val|", "+37.5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeSecondaryCritChance": getAddDict("/Lotus/Language/Upgrades/SecondaryCritChance").replaceAll("|val|", "+25"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeSecondaryCritChanceMythic": getAddDict("/Lotus/Language/Upgrades/SecondaryCritChance").replaceAll("|val|", "+37.5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityDuration": getAddDict("/Lotus/Language/Upgrades/AvatarAbilityDurationModDesc").replaceAll("|val|", "+10"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityDurationMythic": getAddDict("/Lotus/Language/Upgrades/AvatarAbilityDurationModDesc").replaceAll("|val|", "+15"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityStrength": getAddDict("/Lotus/Language/Upgrades/AvatarAbilityStrengthModDesc").replaceAll("|val|", "+10"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityStrengthMythic": getAddDict("/Lotus/Language/Upgrades/AvatarAbilityStrengthModDesc").replaceAll("|val|", "+15"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeArmourMax": getAddDict("/Lotus/Language/Upgrades/WarframeArmourMaxFlat").replaceAll("|val|", "+150"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeArmourMaxMythic": getAddDict("/Lotus/Language/Upgrades/WarframeArmourMaxFlat").replaceAll("|val|", "+225"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeBlastProc": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeBlastProcShieldRegen").replaceAll("|val|", "+5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeBlastProcMythic": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeBlastProcShieldRegen").replaceAll("|val|", "+7.5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCastingSpeed": getAddDict("/Lotus/Language/Upgrades/AvatarCastingSpeedModDesc").replaceAll("|val|", "+25"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCastingSpeedMythic": getAddDict("/Lotus/Language/Upgrades/AvatarCastingSpeedModDesc").replaceAll("|val|", "+37.5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCorrosiveDamageBoost": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeDamageBoostFromCorrosive").replaceAll("|val|", "+10"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCorrosiveDamageBoostMythic": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeDamageBoostFromCorrosive").replaceAll("|val|", "+15"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCorrosiveStack": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeMaxCorrosiveStack").replaceAll("|val|", "+2"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCorrosiveStackMythic": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeMaxCorrosiveStack").replaceAll("|val|", "+3"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCritDamageBoost": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeMeleeCritDoubledWithMaxEnergy").replaceAll("|val|", "25").replaceAll("|AMOUNT|", "500"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCritDamageBoostMythic": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeMeleeCritDoubledWithMaxEnergy").replaceAll("|val|", "37").replaceAll("|AMOUNT|", "500"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeElectricDamage": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeElectricDamageFromShards").replaceAll("|val|", "30").replaceAll("|val2|", "+10"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeElectricDamageMythic": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeElectricDamageFromShards").replaceAll("|val|", "45").replaceAll("|val2|", "+15"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeElectricDamageBoost": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeDamageBoostFromElectricity").replaceAll("|val|", "+10"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeElectricDamageBoostMythic": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeDamageBoostFromElectricity").replaceAll("|val|", "+15"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeEnergyMax": getAddDict("/Lotus/Language/Upgrades/WarframeEnergyMaxFlat").replaceAll("|val|", "+50"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeEnergyMaxMythic": getAddDict("/Lotus/Language/Upgrades/WarframeEnergyMaxFlat").replaceAll("|val|", "+75"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeGlobeEffectEnergy": getAddDict("/Lotus/Language/Upgrades/WarframeGlobeEffectEnergy").replaceAll("|val|", "+50"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeGlobeEffectEnergyMythic": getAddDict("/Lotus/Language/Upgrades/WarframeGlobeEffectEnergy").replaceAll("|val|", "+75"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeGlobeEffectHealth": getAddDict("/Lotus/Language/Upgrades/WarframeGlobeEffectHealth").replaceAll("|val|", "+100"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeGlobeEffectHealthMythic": getAddDict("/Lotus/Language/Upgrades/WarframeGlobeEffectHealth").replaceAll("|val|", "+150"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeHealthMax": getAddDict("/Lotus/Language/Upgrades/WarframeHealthMaxFlat").replaceAll("|val|", "+150"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeHealthMaxMythic": getAddDict("/Lotus/Language/Upgrades/WarframeHealthMaxFlat").replaceAll("|val|", "+225"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeHPBoostFromImpact": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeImpactHPIncrease").replaceAll("|val|", "1").replaceAll("|max|", "300"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeHPBoostFromImpactMythic": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeImpactHPIncrease").replaceAll("|val|", "2").replaceAll("|max|", "450"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeParkourVelocity": getAddDict("/Lotus/Language/Upgrades/AvatarParkourBoostModDesc").replaceAll("|val|", "+15"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeParkourVelocityMythic": getAddDict("/Lotus/Language/Upgrades/AvatarParkourBoostModDesc").replaceAll("|val|", "+22.5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRadiationDamageBoost": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeDamageBoostFromRadiation").replaceAll("|val|", "+10"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRadiationDamageBoostMythic": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeDamageBoostFromRadiation").replaceAll("|val|", "+15"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRegen": getAddDict("/Lotus/Language/Upgrades/WarframeHealthRegenFlat").replaceAll("|val|", "+5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRegenMythic": getAddDict("/Lotus/Language/Upgrades/WarframeHealthRegenFlat").replaceAll("|val|", "+7.5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeShieldMax": getAddDict("/Lotus/Language/Upgrades/WarframeShieldMaxFlat").replaceAll("|val|", "+150"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeShieldMaxMythic": getAddDict("/Lotus/Language/Upgrades/WarframeShieldMaxFlat").replaceAll("|val|", "+225"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeStartingEnergy": getAddDict("/Lotus/Language/Upgrades/AvatarSpawnEnergyPercent").replaceAll("|val|", "+30"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeStartingEnergyMythic": getAddDict("/Lotus/Language/Upgrades/AvatarSpawnEnergyPercent").replaceAll("|val|", "+45"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeToxinDamage": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeToxinProcDamage").replaceAll("|val|", "+30"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeToxinDamageMythic": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeToxinProcDamage").replaceAll("|val|", "+45"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeToxinHeal": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeRegenHPOnToxinProc").replaceAll("|val|", "+2"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeToxinHealMythic": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeRegenHPOnToxinProc").replaceAll("|val|", "+3"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWeaponCritBoostFromHeat": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeCritBoostFromHeat").replaceAll("|val|", "1").replaceAll("|max|", "50"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWeaponCritBoostFromHeatMythic": getAddDict("/Lotus/Language/Alchemy/ArchonUpgradeWarframeCritBoostFromHeat").replaceAll("|val|", "1.5").replaceAll("|max|", "75"),
"/Lotus/Upgrades/Mods/Warframe/AvatarAbilityRangeMod": getAddDict("/Lotus/Language/Upgrades/AvatarAbilityRangeModDesc").replaceAll("|val|", "+7.5"),
"/Lotus/Upgrades/Mods/Warframe/AvatarAbilityEfficiencyMod": getAddDict("/Lotus/Language/Upgrades/AvatarAbilityEfficiencyModDesc").replaceAll("|val|", "+5"),
"/Lotus/Upgrades/Mods/Warframe/AvatarEnergyRegenMod": getAddDict("/Lotus/Language/Upgrades/EnergyRegen").replaceAll("|val|", "+0.5"),
"/Lotus/Upgrades/Mods/Warframe/AvatarEnemyRadarMod": getAddDict("/Lotus/Language/Upgrades/AvatarEnemyRadarModDesc").replaceAll("|val|", "+5"),
"/Lotus/Upgrades/Mods/Warframe/AvatarLootRadarMod": getAddDict("/Lotus/Language/Upgrades/AvatarLootRadarModDesc").replaceAll("|val|", "+7"),
"/Lotus/Upgrades/Mods/Rifle/WeaponAmmoMaxMod": getAddDict("/Lotus/Language/Upgrades/WeaponMaxAmmoModDesc").replaceAll("|val|", "+15"),
"/Lotus/Upgrades/Mods/Aura/EnemyArmorReductionAuraMod": getAddDict("/Lotus/Language/Upgrades/EnemyArmorReductionAuraDesc").replaceAll("|val|", "-3"),
"/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionAmmoMod": getAddDict("/Lotus/Language/Upgrades/DataSpikeOnExecutionAmmo").replaceAll("|AMMO|", "100"),
"/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionHealthDropMod": getAddDict("/Lotus/Language/Upgrades/DataSpikeOnExecutionHealth").replaceAll("|CHANCE|", "100"),
"/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionEnergyDropMod": getAddDict("/Lotus/Language/Upgrades/DataSpikeOnExecutionEnergy").replaceAll("|CHANCE|", "50"),
"/Lotus/Upgrades/Mods/DataSpike/Cipher/OnFailHackResetMod": getAddDict("/Lotus/Language/Upgrades/DataSpikeOnFailRehack").replaceAll("|val|", "+50"),
"/Lotus/Upgrades/Mods/DataSpike/Cipher/DamageReductionOnHackMod": getAddDict("/Lotus/Language/Upgrades/DataSpikeDamageReduceOnHack").replaceAll("|VAL|", "75"),
"/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionReviveCompanionMod": getAddDict("/Lotus/Language/Upgrades/DataSpikeOnExecutionReviveCompanion").replaceAll("|TIME|", "15"),
"/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionParkourSpeedMod": getAddDict("/Lotus/Language/Upgrades/DataSpikeOnExecutionParkour").replaceAll("|VAL|", "75").replaceAll("|DURATION|", "12"),
"/Lotus/Upgrades/Mods/Warframe/AvatarTimeLimitIncreaseMod": getAddDict("/Lotus/Language/Upgrades/TimeLimitIncreaseDesc").replaceAll("|val|", "+8"),
"/Lotus/Upgrades/Mods/DataSpike/Cipher/ElectrifyOnHackMod": getAddDict("/Lotus/Language/Upgrades/DataSpikeElectrifyHack").replaceAll("|RANGE|", "20"),
"/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionTerrifyMod": getAddDict("/Lotus/Language/Upgrades/DataSpikeOnExecutionTerrify").replaceAll("|CHANCE|", "50").replaceAll("|RANGE|", "15").replaceAll("|DURATION|", "8"),
"/Lotus/Upgrades/Mods/DataSpike/Cipher/OnHackLockersMod": getAddDict("/Lotus/Language/Upgrades/DataSpikeOnHackLockers").replaceAll("|COUNT|", "5").replaceAll("|RANGE|", "20"),
"/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionBlindMod": getAddDict("/Lotus/Language/Upgrades/DataSpikeOnExecutionBlind").replaceAll("|RANGE|", "18"),
"/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionDrainPowerMod": getAddDict("/Lotus/Language/Upgrades/DataSpikeOnExecutionDrainPower").replaceAll("|STRENGTH|", "50"),
"/Lotus/Upgrades/Mods/DataSpike/Cipher/OnHackSprintSpeedMod": getAddDict("/Lotus/Language/Upgrades/DataSpikeOnHackSprintSpeed").replaceAll("|VAL|", "75").replaceAll("|DURATION|", "15"),
"/Lotus/Upgrades/Mods/DataSpike/Assassin/SwiftExecuteMod": getAddDict("/Lotus/Language/Upgrades/DataSpikeSwiftMercy").replaceAll("|SPEED|", "50"),
"/Lotus/Upgrades/Mods/DataSpike/Cipher/OnHackInvisMod": getAddDict("/Lotus/Language/Upgrades/DataSpikeOnHackInvis").replaceAll("|DURATION|", "15")
};
window.archonCrystalUpgrades = data.archonCrystalUpgrades;
data.innateDamages = {
InnateElectricityDamage: getAddDict("/Lotus/Language/Game/DT_ELECTRICITY_NoIcon"),
InnateFreezeDamage: getAddDict("/Lotus/Language/Game/DT_FREEZE_NoIcon"),
InnateHeatDamage: getAddDict("/Lotus/Language/Game/DT_FIRE_NoIcon"),
InnateImpactDamage: getAddDict("/Lotus/Language/Game/DT_IMPACT_NoIcon"),
InnateMagDamage: getAddDict("/Lotus/Language/Game/DT_MAGNETIC_NoIcon"),
InnateRadDamage: getAddDict("/Lotus/Language/Game/DT_RADIATION_NoIcon"),
InnateToxinDamage: getAddDict("/Lotus/Language/Game/DT_POISON_NoIcon")
};
// Add mods missing in data sources
data.mods.push(
{
uniqueName: "/Lotus/Upgrades/Mods/Fusers/CommonModFuser",
name: loc("code_fusionCoreCommon"),
fusionLimit: 3
},
{
uniqueName: "/Lotus/Upgrades/Mods/Fusers/UncommonModFuser",
name: loc("code_fusionCoreUncommon")
},
{
uniqueName: "/Lotus/Upgrades/Mods/Fusers/RareModFuser",
name: loc("code_fusionCoreRare")
},
{
uniqueName: "/Lotus/Upgrades/Mods/Fusers/LegacyCommonModFuser",
name: loc("code_ancientFusionCoreCommon"),
fusionLimit: 10
},
{
uniqueName: "/Lotus/Upgrades/Mods/Fusers/LegacyUncommonModFuser",
name: loc("code_ancientFusionCoreUncommon"),
fusionLimit: 10
},
{
uniqueName: "/Lotus/Upgrades/Mods/Fusers/LegacyRareModFuser",
name: loc("code_ancientFusionCoreRare"),
fusionLimit: 10
},
{
uniqueName: "/Lotus/Upgrades/Mods/Fusers/LegendaryModFuser",
name: getAddDict("/Lotus/Language/Items/LegendaryFusionPart"),
fusionLimit: 0
},
{
uniqueName: "/Lotus/Upgrades/CosmeticEnhancers/Peculiars/CyoteMod",
name: getAddDict("/Lotus/Language/Mods/PerculiarCyoteMod")
},
{
uniqueName: "/Lotus/Upgrades/Mods/Warframe/Intermediate/AvatarPickupBonusModIntermediate",
name: getAddDict("/Lotus/Language/Items/WarframeModPickupBonus") + " " + loc("code_badItem")
},
{
uniqueName: "/Lotus/Upgrades/Mods/Warframe/AvatarStaminaCostMultiplierMod",
name: getAddDict("/Lotus/Language/Items/WarframeModStaminaCost")
},
{
uniqueName: "/Lotus/Upgrades/Mods/Warframe/AvatarShieldToStaminaMod",
name: getAddDict("/Lotus/Language/Items/WarframeModShieldToStamina"),
fusionLimit: 3
},
{
uniqueName: "/Lotus/Upgrades/Mods/Warframe/AvatarStaminaMaxMod",
name: getAddDict("/Lotus/Language/Items/WarframeModStaminaMax")
},
{
uniqueName: "/Lotus/Upgrades/Mods/Warframe/AvatarStaminaRechargeRateMod",
name: getAddDict("/Lotus/Language/Items/WarframeModStaminaRechargeRate")
},
{
uniqueName: "/Lotus/Upgrades/Mods/Melee/WeaponStaminaBonusMod",
name: getAddDict("/Lotus/Language/Items/MeleeModStaminaBonus")
}
);
// Add modular weapons
data.OperatorAmps.push({
uniqueName: "/Lotus/Weapons/Sentients/OperatorAmplifiers/OperatorAmpWeapon",
name: getAddDict("/Lotus/Language/Items/OperatorVoidBeam")
});
data.OperatorAmps.push({
uniqueName:
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/OperatorTrainingAmpWeapon",
name: getAddDict("/Lotus/Language/OstronCrafting/OperatorTrainingAmpName")
});
data.Melee.push({
uniqueName: "/Lotus/Weapons/Ostron/Melee/LotusModularWeapon",
name: getAddDict("/Lotus/Language/OstronCrafting/Zaw_Singular")
});
data.LongGuns.push({
uniqueName: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary",
name: getAddDict("/Lotus/Language/SolarisVenus/Kitgun_Singular")
});
data.Pistols.push({
uniqueName: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary",
name: getAddDict("/Lotus/Language/SolarisVenus/Kitgun_Singular")
});
data.MoaPets ??= [];
data.MoaPets.push({
uniqueName: "/Lotus/Types/Friendly/Pets/MoaPets/MoaPetPowerSuit",
name: getAddDict("/Lotus/Language/SolarisVenus/MoaPet_Singular")
});
data.MoaPets.push({
uniqueName: "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetPowerSuit",
name: getAddDict("/Lotus/Language/Pets/ZanukaPetItemName")
});
data.miscitems.push(
{
uniqueName: "/Lotus/Types/Recipes/CosmeticUnenhancerItem",
name: getAddDict("/Lotus/Language/Items/CosmeticUnenhancerName")
},
{
uniqueName: "/Lotus/Types/Items/Research/DojoColors/GenericDojoColorPigment",
name: normalizeText(
getAddDict("/Lotus/Language/Items/DojoColorPigment")
.replaceAll("|COLOUR_NAME|", "")
.replaceAll(" ", "")
)
},
{
uniqueName: "/Lotus/Types/Items/MiscItems/BossNavCode",
name: getAddDict("/Lotus/Language/Items/BossNavCode")
},
{
uniqueName: "/Lotus/Types/Items/MiscItems/Actuator",
name: getAddDict("/Lotus/Language/Items/Actuator")
}
);
data.miscitems.push(
{
uniqueName: "/Lotus/Types/Keys/GolemKeyBlueprint",
name: data.blueprintAndItem.replace(
"|ITEM|",
data.miscitems.find(i => i.uniqueName === "/Lotus/Types/Keys/DerelictGolemKey").name
)
},
{
uniqueName: "/Lotus/Types/Keys/DerelictCaptureKeyBlueprint",
name: data.blueprintAndItem.replace(
"|ITEM|",
data.miscitems.find(i => i.uniqueName === "/Lotus/Types/Keys/DerelictCaptureKey").name
)
},
{
uniqueName: "/Lotus/Types/Keys/DerelictDefenseKeyBlueprint",
name: data.blueprintAndItem.replace(
"|ITEM|",
data.miscitems.find(i => i.uniqueName === "/Lotus/Types/Keys/DerelictDefenseKey").name
)
},
{
uniqueName: "/Lotus/Types/Keys/DerelictExterminateKeyBlueprint",
name: data.blueprintAndItem.replace(
"|ITEM|",
data.miscitems.find(i => i.uniqueName === "/Lotus/Types/Keys/DerelictExterminateKey").name
)
},
{
uniqueName: "/Lotus/Types/Keys/DerelictMobileDefenseKeyBlueprint",
name: data.blueprintAndItem.replace(
"|ITEM|",
data.miscitems.find(i => i.uniqueName === "/Lotus/Types/Keys/DerelictMobileDefenseKey").name
)
},
{
uniqueName: "/Lotus/Types/Keys/DerelictSabotageKeyBlueprint",
name: data.blueprintAndItem.replace(
"|ITEM|",
data.miscitems.find(i => i.uniqueName === "/Lotus/Types/Keys/DerelictSabotageKey").name
)
},
{
uniqueName: "/Lotus/Types/Keys/DerelictSurvivalKeyBlueprint",
name: data.blueprintAndItem.replace(
"|ITEM|",
data.miscitems.find(i => i.uniqueName === "/Lotus/Types/Keys/DerelictSurvivalKey").name
)
},
{
uniqueName: "/Lotus/Types/Recipes/KevinTestRecipe",
name: data.blueprintAndItem.replace("|ITEM|", loc("code_infernoMod"))
},
{
uniqueName: "/Lotus/Types/Recipes/IncendiaryRifleModBlueprint",
name: data.blueprintAndItem.replace("|ITEM|", loc("code_infernoMod"))
}
);
data.VarziaOffers.unshift({
uniqueName: "",
name: loc("disabled")
});
data.Boosters ??= [];
data.Boosters.push({
uniqueName: "/Lotus/Types/Boosters/ReviveBooster",
name: getAddDict("/Lotus/Language/Items/ReviveBoosterName")
});
const itemMap = {
// Generics for rivens
"/Lotus/Weapons/Tenno/Archwing/Primary/ArchGun": { name: loc("code_archgun") },
"/Lotus/Weapons/Tenno/Melee/PlayerMeleeWeapon": { name: loc("code_melee") },
"/Lotus/Weapons/Tenno/Pistol/LotusPistol": { name: loc("code_pistol") },
"/Lotus/Weapons/Tenno/Rifle/LotusRifle": { name: loc("code_rifle") },
"/Lotus/Weapons/Tenno/Shotgun/LotusShotgun": { name: loc("code_shotgun") },
// Modular weapons
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam": {
name: getAddDict("/Lotus/Language/SolarisVenus/Kitgun_Singular")
},
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryLauncher": {
name: getAddDict("/Lotus/Language/SolarisVenus/Kitgun_Singular")
},
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun": {
name: getAddDict("/Lotus/Language/SolarisVenus/Kitgun_Singular")
},
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimarySniper": {
name: getAddDict("/Lotus/Language/SolarisVenus/Kitgun_Singular")
},
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam": {
name: getAddDict("/Lotus/Language/SolarisVenus/Kitgun_Singular")
},
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun": {
name: getAddDict("/Lotus/Language/SolarisVenus/Kitgun_Singular")
},
"/Lotus/Types/Vehicles/Hoverboard/HoverboardSuit": {
name: getAddDict("/Lotus/Language/Hoverboards/Hoverboard_Singular")
},
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetAPowerSuit": {
name: data.ModularParts.find(
i => i.uniqueName === "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA"
).name
},
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetBPowerSuit": {
name: data.ModularParts.find(
i => i.uniqueName === "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadB"
).name
},
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetCPowerSuit": {
name: data.ModularParts.find(
i => i.uniqueName === "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC"
).name
},
"/Lotus/Powersuits/Stalker/Stalker": {
name: getAddDict("/Lotus/Language/Game/Stalker")
},
"/Lotus/Powersuits/Excalibur/DarkExcalibur": {
name: loc("code_darkExcalibur").replace(
"|ITEM|",
data.Suits.find(i => i.uniqueName === "/Lotus/Powersuits/Excalibur/ExcaliburPrime").name
),
exalted: ["/Lotus/Powersuits/Excalibur/DoomSwordCYUmbra"]
},
// U5
"/Lotus/Types/Game/PowerSuit": { name: loc("code_warframe") },
"/Lotus/Types/Game/LotusMeleeWeapon": { name: loc("code_melee") }
};
for (const [type, items] of Object.entries(data)) {
if (type == "archonCrystalUpgrades") {
Object.entries(items).forEach(([uniqueName, name]) => {
const option = document.createElement("option");
option.setAttribute("data-key", uniqueName);
option.value = name;
document.getElementById("datalist-" + type).appendChild(option);
});
} else if (type == "innateDamages") {
Object.entries(items).forEach(([uniqueName, name]) => {
const option = document.createElement("option");
option.value = uniqueName;
option.textContent = name;
document.getElementById("valenceBonus-innateDamage").appendChild(option);
});
} else if (type == "VarziaOffers") {
const select = document.getElementById("worldState.varziaOverride");
items.forEach(item => {
const option = document.createElement("option");
option.value = item.uniqueName;
option.textContent = item.name;
select.appendChild(option);
});
select.value = rememberedVarziaOverride;
} else if (type == "NightwaveTags") {
const select = document.getElementById("worldState.nightwaveOverride");
let oldNWIntermissionLeft = 3;
{
const nwDisabledOption = document.createElement("option");
nwDisabledOption.value = "";
nwDisabledOption.textContent = loc("disabled");
nwDisabledOption.setAttribute("data-loc", "disabled");
select.appendChild(nwDisabledOption);
}
items.forEach(item => {
const option = document.createElement("option");
option.value = item.uniqueName;
option.textContent = item.name;
if (
[
"RadioLegionIntermissionSyndicate",
"RadioLegionIntermission2Syndicate",
"RadioLegionIntermission3Syndicate"
].includes(item.uniqueName)
) {
option.textContent += ` ${"I".repeat(oldNWIntermissionLeft)}`;
oldNWIntermissionLeft--;
}
select.appendChild(option);
});
{
const nwDisableOption = document.createElement("option");
nwDisableOption.value = "disable";
nwDisableOption.textContent = loc("worldState_nightwaveOff");
nwDisableOption.setAttribute("data-loc", "worldState_nightwaveOff");
select.appendChild(nwDisableOption);
}
select.value = rememberedNWOverride;
} else if (type == "Syndicates") {
items.forEach(item => {
if (["ConclaveSyndicate", "NightcapJournalSyndicate"].includes(item.uniqueName)) {
return;
}
if (item.uniqueName.startsWith("RadioLegion")) {
item.name += " (" + item.uniqueName + ")";
}
const option = document.createElement("option");
option.value = item.uniqueName;
option.textContent = item.name;
document.getElementById("changeSyndicate").appendChild(option);
});
} else if (type == "FlavourItems") {
const cursorPrefixes = {
Controller: loc("code_controller"),
MouseGrey: loc("code_mouse"),
MouseLine: loc("code_mouseLine"),
Mouse: loc("code_mouse")
};
items.forEach(item => {
if (item.uniqueName.startsWith("/Lotus/Interface/Graphics/CustomUI/Cursors/")) {
let base = item.uniqueName.replace("/Lotus/Interface/Graphics/CustomUI/Cursors/", "");
for (const key in cursorPrefixes) {
if (base.startsWith(key)) {
const prefix = cursorPrefixes[key];
const suffix = base.slice(key.length);
item.name = prefix + " " + suffix;
break;
}
}
} else if (item.uniqueName.includes("ColourPicker")) {
item.name = getAddDict("/Lotus/Language/Store/ItemColorPalette").replaceAll(
"|ITEM|",
item.name
);
}
if (!item.alwaysAvailable) {
const option = document.createElement("option");
option.setAttribute("data-key", item.uniqueName);
option.value = item.name;
document.getElementById("datalist-" + type).appendChild(option);
}
itemMap[item.uniqueName] = { ...item, type };
});
} else if (type == "WeaponSkins") {
let beardNumber = 1;
items.forEach(item => {
if (item.name == "") {
if (item.uniqueName.includes("/Beards/")) {
item.name = getAddDict(
"/Lotus/Language/Operator/TennoCustomization_DrifterBeardName"
).replaceAll("|INDEX|", beardNumber.toString().padStart(3, "0"));
beardNumber++;
} else {
item.name = item.uniqueName;
}
}
if (!item.alwaysAvailable) {
const option = document.createElement("option");
option.setAttribute("data-key", item.uniqueName);
option.value = item.name;
document.getElementById("datalist-" + type).appendChild(option);
}
itemMap[item.uniqueName] = { ...item, type };
});
} else if (["MissionTypes", "Nodes", "AdditionalDict"].includes(type)) {
items.forEach(item => {
itemMap[item.uniqueName] = { ...item, type };
});
} else if (type != "blueprintAndItem") {
const nameToItems = {};
items.forEach(item => {
item.name = item.name.replace(/<.+>/g, "").trim();
const nameMap = {
"/Lotus/Upgrades/Modules/GrineerMeleeModule":
loc("code_U5Mod").replace("|TYPE|", loc("code_melee")) + " [GrineerMeleeModule]",
"/Lotus/Upgrades/Modules/TennoSwordModule":
loc("code_U5Mod").replace("|TYPE|", loc("code_melee")) + " [TennoSwordModule]",
"/Lotus/Upgrades/Modules/GrineerPistolModule": loc("code_U5Mod").replace(
"|TYPE|",
loc("code_pistol")
),
"/Lotus/Upgrades/Modules/GrineerRifleModule": loc("code_U5Mod").replace(
"|TYPE|",
loc("code_rifle")
),
"/Lotus/Upgrades/Modules/GrineerShotgunModule": loc("code_U5Mod").replace(
"|TYPE|",
loc("code_shotgun")
),
"/Lotus/Upgrades/Modules/OrokinWarframeModule": loc("code_U5Mod").replace(
"|TYPE|",
loc("code_warframe")
),
"/Lotus/Upgrades/Modules/Crafted/IncendiaryRifleMod": loc("code_infernoMod")
};
if (nameMap[item.name]) {
item.name = nameMap[item.name];
}
if (item.name == "") {
//console.log(`changed ${item.uniqueName} name to path`);
item.name = item.uniqueName;
}
if ("badReason" in item) {
if (item.badReason == "starter") {
item.name = loc("code_starter").replaceAll("|MOD|", item.name);
} else if (item.badReason != "notraw") {
item.name += " " + loc("code_badItem");
}
}
nameToItems[item.name] ??= [];
nameToItems[item.name].push(item);
});
items.forEach(item => {
if (type == "ModularParts") {
const supportedModularParts = [
"LWPT_HB_DECK",
"LWPT_HB_ENGINE",
"LWPT_HB_FRONT",
"LWPT_HB_JET",
"LWPT_AMP_OCULUS",
"LWPT_AMP_CORE",
"LWPT_AMP_BRACE",
"LWPT_BLADE",
"LWPT_HILT",
"LWPT_HILT_WEIGHT",
"LWPT_GUN_PRIMARY_HANDLE",
"LWPT_GUN_SECONDARY_HANDLE",
"LWPT_GUN_BARREL",
"LWPT_GUN_CLIP",
"LWPT_MOA_ENGINE",
"LWPT_MOA_PAYLOAD",
"LWPT_MOA_HEAD",
"LWPT_MOA_LEG",
"LWPT_ZANUKA_BODY",
"LWPT_ZANUKA_HEAD",
"LWPT_ZANUKA_LEG",
"LWPT_ZANUKA_TAIL",
"LWPT_CATBROW_ANTIGEN",
"LWPT_CATBROW_MUTAGEN",
"LWPT_KUBROW_ANTIGEN",
"LWPT_KUBROW_MUTAGEN"
];
if (supportedModularParts.includes(item.partType)) {
const option = document.createElement("option");
option.setAttribute("data-key", item.uniqueName);
option.value = item.name;
document
.getElementById("datalist-" + type + "-" + item.partType.slice(5))
.appendChild(option);
}
} else if (item.badReason != "notraw") {
let stringifier = itemToString;
if (nameToItems[item.name].length > 1) {
stringifier = itemToStringWithSubtype;
if (!areItemsUnambiguous(nameToItems[item.name], stringifier)) {
stringifier = itemToStringWithUniqueName;
if (!areItemsUnambiguous(nameToItems[item.name], stringifier)) {
console.log(
`Not adding ${item.uniqueName} to datalist for ${type} due to ambiguous display name: ${item.name}`
);
return;
}
}
}
const option = document.createElement("option");
option.setAttribute("data-key", item.uniqueName);
option.value = stringifier(item);
document.getElementById("datalist-" + type).appendChild(option);
if (item.eligibleForVault) {
const vaultOption = option.cloneNode(true);
document.getElementById("datalist-VaultMiscItems").appendChild(vaultOption);
}
}
itemMap[item.uniqueName] = { ...item, type };
});
}
}
resolve(itemMap);
if (missingAdditionalDict.size) {
console.warn("Missing in AdditionalDict:", [...missingAdditionalDict]);
}
});
});
}
fetchItemList();
const accountCheats = document.querySelectorAll("#account-cheats input[id]");
let inventory_data;
// Assumes that caller revalidates authz
function getInventoryData() {
return new Promise((resolve, reject) => {
if (inventory_data) {
resolve(inventory_data);
} else {
$.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1")
.done(data => {
inventory_data = data;
resolve(inventory_data);
})
.fail(reject);
}
});
}
// Assumes that caller revalidates authz
function updateInventory() {
inventory_data = undefined;
if (
single.getCurrentPath() == "/webui/inventory" ||
single.getCurrentPath().startsWith("/webui/details") ||
single.getCurrentPath() == "/webui/mods" ||
single.getCurrentPath() == "/webui/cheats"
) {
single.loadRoute(single.getCurrentPath());
}
}
const modularWeapons = [
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary",
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam",
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryLauncher",
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun",
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimarySniper",
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary",
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam",
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun",
"/Lotus/Weapons/Ostron/Melee/LotusModularWeapon",
"/Lotus/Weapons/Sentients/OperatorAmplifiers/OperatorAmpWeapon",
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/OperatorTrainingAmpWeapon",
"/Lotus/Types/Vehicles/Hoverboard/HoverboardSuit",
"/Lotus/Types/Friendly/Pets/MoaPets/MoaPetPowerSuit",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetAPowerSuit",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetBPowerSuit",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetCPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/VulpineInfestedCatbrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/HornedInfestedCatbrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/ArmoredInfestedCatbrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/VizierPredatorKubrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/PharaohPredatorKubrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/MedjayPredatorKubrowPetPowerSuit"
];
const U5Mods = [
"/Lotus/Upgrades/Modules/GrineerMeleeModule",
"/Lotus/Upgrades/Modules/GrineerPistolModule",
"/Lotus/Upgrades/Modules/GrineerRifleModule",
"/Lotus/Upgrades/Modules/GrineerShotgunModule",
"/Lotus/Upgrades/Modules/OrokinWarframeModule",
"/Lotus/Upgrades/Modules/TennoSwordModule",
"/Lotus/Upgrades/Modules/Crafted/IncendiaryRifleMod"
];
async function populateInventoryRoute() {
const [data, itemMap] = await Promise.all([awaitAuthz().then(getInventoryData), window.itemListPromise]);
document.getElementById("typeName-tab").classList.remove("active");
document.getElementById("typeName-tab-content").classList.remove("active", "show");
document.getElementById("typeName-type").value = "";
document.getElementById("miscItems-tab").classList.add("active");
document.getElementById("miscItems-tab-content").classList.add("active", "show");
["RegularCredits", "PremiumCredits", "FusionPoints", "PrimeTokens", "CrewShipFusionPoints"].forEach(currency => {
document.getElementById(currency + "-owned").textContent = loc("currency_owned").replaceAll(
"|COUNT|",
data[currency].toLocaleString()
);
});
[
"Suits",
"SpaceSuits",
"Sentinels",
"LongGuns",
"Pistols",
"Melee",
"SpaceGuns",
"SpaceMelee",
"SentinelWeapons",
"Hoverboards",
"OperatorAmps",
"MechSuits",
"MoaPets",
"KubrowPets"
].forEach(category => {
document.getElementById(category + "-list").innerHTML = "";
data[category]
.sort((a, b) => (b.Favorite ? 1 : 0) - (a.Favorite ? 1 : 0))
.forEach(item => {
const tr = document.createElement("tr");
tr.setAttribute("data-item-oid", item.ItemId.$oid);
tr.setAttribute("data-item-type", item.ItemType);
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
if (item.ItemName) {
const pipeIndex = item.ItemName.indexOf("|");
if (pipeIndex != -1) {
td.textContent = item.ItemName.substr(1 + pipeIndex) + " " + td.textContent;
} else {
td.textContent = item.ItemName + " (" + td.textContent + ")";
}
}
if (item.Details?.Name) {
td.textContent = item.Details.Name + " (" + td.textContent + ")";
}
if (item.ModularParts && item.ModularParts.length) {
td.textContent += " [";
item.ModularParts.forEach(part => {
td.textContent += " " + (itemMap[part]?.name ?? part) + ",";
});
td.textContent = td.textContent.slice(0, -1) + " ]";
}
if (
["/Lotus/Powersuits/Stalker/Stalker", "/Lotus/Powersuits/Excalibur/DarkExcalibur"].includes(
item.ItemType
)
) {
td.textContent += " ";
const abbr = document.createElement("abbr");
abbr.innerHTML = icons.note;
abbr.title = loc("code_metadataPatchRequired");
td.appendChild(abbr);
}
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
let maxXP = Math.pow(itemMap[item.ItemType]?.maxLevelCap ?? 30, 2) * 1000;
if (
![
"Suits",
"SpaceSuits",
"Hoverboards",
"MechSuits",
"Sentinels",
"MoaPets",
"KubrowPets"
].includes(category)
) {
maxXP /= 2;
}
let anyExaltedMissingXP = false;
if (item.XP >= maxXP && item.ItemType in itemMap && "exalted" in itemMap[item.ItemType]) {
for (const exaltedType of itemMap[item.ItemType].exalted) {
const exaltedItem = data.SpecialItems.find(x => x.ItemType == exaltedType);
if (exaltedItem) {
const exaltedCap = itemMap[exaltedType]?.type == "weapons" ? 800_000 : 1_600_000;
if (exaltedItem.XP < exaltedCap) {
anyExaltedMissingXP = true;
break;
}
}
}
}
if (
itemMap[item.ItemType]?.maxLevelCap > 30 &&
(item.Polarized ?? 0) < (itemMap[item.ItemType].maxLevelCap - 30) / 2
) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
a.parentNode.removeChild(a);
unlockLevelCap(category, item.ItemId.$oid, (itemMap[item.ItemType].maxLevelCap - 30) / 2);
};
a.title = loc("code_unlockLevelCap");
a.innerHTML = icons.graduationCap;
td.appendChild(a);
}
if (item.XP < maxXP || anyExaltedMissingXP) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
revalidateAuthz().then(async () => {
if (item.XP < maxXP) {
await addGearExp(category, item.ItemId.$oid, maxXP - item.XP);
if (item.AltWeaponModeId) {
const otherCategory = category == "Melee" ? "LongGuns" : "Melee";
await addGearExp(otherCategory, item.AltWeaponModeId.$oid, maxXP - item.XP);
}
}
if ("exalted" in itemMap[item.ItemType]) {
for (const exaltedType of itemMap[item.ItemType].exalted) {
const exaltedItem = data.SpecialItems.find(x => x.ItemType == exaltedType);
if (exaltedItem) {
const exaltedCap =
itemMap[exaltedType]?.type == "weapons" ? 800_000 : 1_600_000;
if (exaltedItem.XP < exaltedCap) {
await addGearExp(
"SpecialItems",
exaltedItem.ItemId.$oid,
exaltedCap - exaltedItem.XP
);
}
}
}
}
updateInventory();
});
};
a.title = loc("code_maxRank");
a.innerHTML = icons.arrowUp;
td.appendChild(a);
}
{
const a = document.createElement("a");
a.href = "/webui/details?productCategory=" + category + "&itemId=" + item.ItemId.$oid;
a.innerHTML = icons.feather;
td.appendChild(a);
}
if (!(item.Features & 8) && modularWeapons.includes(item.ItemType)) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
equipmentFeatures(category, item.ItemId.$oid, 8);
};
a.title = loc("code_gild");
a.innerHTML = icons.star;
td.appendChild(a);
}
if (category == "KubrowPets") {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
maturePet(item.ItemId.$oid, !item.Details.IsPuppy);
};
if (item.Details.IsPuppy) {
a.title = loc("code_mature");
a.innerHTML = icons.person;
} else {
a.title = loc("code_unmature");
a.innerHTML = icons.childReaching;
}
td.appendChild(a);
}
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
const name = prompt(loc("code_renamePrompt"));
if (name !== null) {
renameGear(category, item.ItemId.$oid, name);
}
};
a.title = loc("code_rename");
a.innerHTML = icons.tag;
td.appendChild(a);
}
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
tr.parentNode.removeChild(tr);
disposeOfGear(category, item.ItemId.$oid);
if (item.AltWeaponModeId) {
const otherCategory = category == "Melee" ? "LongGuns" : "Melee";
const otherTr = document.querySelector(
`[data-item-oid="${item.AltWeaponModeId.$oid}"]`
);
if (otherTr) {
otherTr.parentNode.removeChild(otherTr);
disposeOfGear(otherCategory, item.AltWeaponModeId.$oid);
}
}
};
a.title = loc("code_remove");
a.innerHTML = icons.trash;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById(category + "-list").appendChild(tr);
});
});
document.getElementById("EvolutionProgress-list").innerHTML = "";
data.EvolutionProgress?.forEach(item => {
const maxRank = permanentEvolutionWeapons.has(item.ItemType) ? 5 : 4;
const tr = document.createElement("tr");
tr.setAttribute("data-item-type", item.ItemType);
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
if (item.Rank != null) {
td.textContent += " | " + loc("code_rank") + ": [" + item.Rank + "/" + maxRank + "]";
}
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
if (item.Rank < maxRank) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
setEvolutionProgress([{ ItemType: item.ItemType, Rank: maxRank }]);
};
a.title = loc("code_maxRank");
a.innerHTML = icons.arrowUp;
td.appendChild(a);
}
if (item.Rank < maxRank) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
setEvolutionProgress([{ ItemType: item.ItemType, Rank: item.Rank + 1 }]);
};
a.title = loc("code_rankUp");
a.innerHTML = icons.angleUp;
td.appendChild(a);
}
if ((permanentEvolutionWeapons.has(item.ItemType) && item.Rank > 0) || item.Rank > 1) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
setEvolutionProgress([{ ItemType: item.ItemType, Rank: item.Rank - 1 }]);
};
a.title = loc("code_rankDown");
a.innerHTML = icons.angleDown;
td.appendChild(a);
}
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
setEvolutionProgress([{ ItemType: item.ItemType, Delete: true }]);
};
a.title = loc("code_remove");
a.innerHTML = icons.trash;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("EvolutionProgress-list").appendChild(tr);
});
document.getElementById("Boosters-list").innerHTML = "";
data.Boosters.forEach(item => {
if (item.ExpiryDate < Math.floor(Date.now() / 1000)) {
// Booster has expired, skip it
return;
}
const tr = document.createElement("tr");
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
tr.appendChild(td);
if (item.ItemType == "/Lotus/Types/Boosters/ReviveBooster") {
td.textContent += " ";
const abbr = document.createElement("abbr");
abbr.innerHTML = icons.note;
abbr.title = loc("code_reviveBoosterDesc");
td.appendChild(abbr);
}
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
{
const form = document.createElement("form");
form.style.display = "inline-block";
form.onsubmit = function (event) {
event.preventDefault();
const maxDate = new Date(input.max);
const selectedDate = new Date(input.value);
if (selectedDate > maxDate) {
input.value = maxDate.toISOString().slice(0, 16);
}
doChangeBoosterExpiry(item.ItemType, input);
};
const input = document.createElement("input");
input.type = "datetime-local";
input.classList = "form-control form-control-sm";
input.value = formatDatetime("%Y-%m-%d %H:%M:%s", item.ExpiryDate * 1000);
input.max = "2038-01-19T03:14";
input.onblur = function () {
const maxDate = new Date(input.max);
const selectedDate = new Date(input.value);
if (selectedDate > maxDate) {
input.value = maxDate.toISOString().slice(0, 16);
}
doChangeBoosterExpiry(item.ItemType, input);
};
form.appendChild(input);
td.appendChild(form);
}
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
setBooster(item.ItemType, 0);
};
a.title = loc("code_remove");
a.innerHTML = icons.trash;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("Boosters-list").appendChild(tr);
});
document.getElementById("FlavourItems-list").innerHTML = "";
data.FlavourItems.forEach(item => {
const tr = document.createElement("tr");
tr.setAttribute("data-item-type", item.ItemType);
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
tr.parentNode.removeChild(tr);
removeCustomization(item.ItemType);
};
a.title = loc("code_remove");
a.innerHTML = icons.trash;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("FlavourItems-list").appendChild(tr);
});
document.getElementById("WeaponSkins-list").innerHTML = "";
data.WeaponSkins.forEach(item => {
if (item.ItemId.$oid.startsWith("ca70ca70ca70ca70")) return;
const tr = document.createElement("tr");
{
const td = document.createElement("td");
const name = itemMap[item.ItemType]?.name?.trim();
td.textContent = name || item.ItemType;
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
document.getElementById("WeaponSkins-list").removeChild(tr);
disposeOfGear("WeaponSkins", item.ItemId.$oid);
};
a.title = loc("code_remove");
a.innerHTML = icons.trash;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("WeaponSkins-list").appendChild(tr);
});
if (data.CrewShipHarnesses?.length) {
window.plexus = {
id: data.CrewShipHarnesses[0].ItemId.$oid,
xp: data.CrewShipHarnesses[0].XP
};
}
document.getElementById("ShipDecorations-list").innerHTML = "";
data.ShipDecorations.forEach(item => {
if (item.ItemCount > 0) {
const tr = document.createElement("tr");
tr.setAttribute("data-item-type", item.ItemType);
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
if (item.ItemCount > 1) {
td.innerHTML +=
" <span title='" + loc("code_count") + "'>🗍 " + parseInt(item.ItemCount) + "</span>";
}
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
tr.parentNode.removeChild(tr);
removeShipDecoration(item.ItemType, item.ItemCount);
};
a.title = loc("code_remove");
a.innerHTML = icons.trash;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("ShipDecorations-list").appendChild(tr);
}
});
document.getElementById("QuestKeys-list").innerHTML = "";
data.QuestKeys.forEach(item => {
const tr = document.createElement("tr");
tr.setAttribute("data-item-type", item.ItemType);
const run = item.Progress[0]?.c ?? 0;
const stage = run == 0 ? item.Progress.length : item.Progress.map(p => p.c ?? 0).lastIndexOf(run);
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
if (!item.Completed) {
td.textContent +=
" | " + loc("code_stage") + ": [" + stage + "/" + itemMap[item.ItemType]?.chainLength + "]";
} else {
td.textContent += " | " + loc("code_completed");
}
if (run > 0) {
td.textContent += " | " + loc("code_replays") + ": " + (run + 1);
}
if (data.ActiveQuest == item.ItemType) td.textContent += " | " + loc("code_active");
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
if (data.ActiveQuest == item.ItemType && !item.Completed) {
console.log(data.ActiveQuest);
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(doQuestUpdate, "setInactive", item.ItemType);
};
a.title = loc("code_setInactive");
a.innerHTML = icons.circleStop;
td.appendChild(a);
}
if (stage > 0) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(doQuestUpdate, "resetKey", item.ItemType);
};
a.title = loc("code_reset");
a.innerHTML = icons.arrowRotateRight;
td.appendChild(a);
}
if (itemMap[item.ItemType]?.chainLength > stage && !item.Completed) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(doQuestUpdate, "completeKey", item.ItemType);
};
a.title = loc("code_complete");
a.innerHTML = icons.check;
td.appendChild(a);
}
if (stage > 0 && itemMap[item.ItemType]?.chainLength > 1) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(doQuestUpdate, "prevStage", item.ItemType);
};
a.title = loc("code_prevStage");
a.innerHTML = icons.angleLeft;
td.appendChild(a);
}
if (
itemMap[item.ItemType]?.chainLength > stage &&
!item.Completed &&
itemMap[item.ItemType]?.chainLength > 1
) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(doQuestUpdate, "nextStage", item.ItemType);
};
a.title = loc("code_nextStage");
a.innerHTML = icons.angleRight;
td.appendChild(a);
}
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(doQuestUpdate, "deleteKey", item.ItemType);
};
a.title = loc("code_remove");
a.innerHTML = icons.trash;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("QuestKeys-list").appendChild(tr);
});
}
single.getRoute("/webui/inventory").on("beforeload", populateInventoryRoute);
single.getRoute("/webui/mods").on("beforeload", async function () {
const [data, itemMap] = await Promise.all([awaitAuthz().then(getInventoryData), window.itemListPromise]);
document.getElementById("riven-list").innerHTML = "";
document.getElementById("mods-list").innerHTML = "";
data.Upgrades.forEach(item => {
if (item.ItemType.substr(0, 32) == "/Lotus/Upgrades/Mods/Randomized/") {
const rivenType = item.ItemType.substr(32);
const fingerprint = JSON.parse(item.UpgradeFingerprint);
if ("buffs" in fingerprint) {
// Riven has been revealed?
const tr = document.createElement("tr");
{
const td = document.createElement("td");
td.textContent = itemMap[fingerprint.compat]?.name ?? fingerprint.compat;
td.textContent += " ";
try {
td.textContent += RivenParser.parseRiven(rivenType, fingerprint, 1).name;
} catch (e) {
console.warn("malformed riven", { rivenType, fingerprint });
td.textContent += " [Malformed Riven]";
}
td.innerHTML +=
" <span title='" + loc("code_buffsNumber") + "'>▲ " + fingerprint.buffs.length + "</span>";
td.innerHTML +=
" <span title='" + loc("code_cursesNumber") + "'>▼ " + fingerprint.curses.length + "</span>";
td.innerHTML +=
" <span title='" + loc("code_rerollsNumber") + "'>⟳ " + (fingerprint.rerolls ?? 0) + "</span>";
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
{
const a = document.createElement("a");
a.href =
"riven-tool/#" +
encodeURIComponent(
JSON.stringify({
rivenType: rivenType,
omegaAttenuation: 1,
fingerprint: fingerprint
})
);
a.target = "_blank";
a.title = loc("code_viewStats");
a.innerHTML = icons.chartSimple;
td.appendChild(a);
}
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
document.getElementById("riven-list").removeChild(tr);
disposeOfGear("Upgrades", item.ItemId.$oid);
};
a.title = loc("code_remove");
a.innerHTML = icons.trash;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("riven-list").appendChild(tr);
return;
}
}
const tr = document.createElement("tr");
const rank = parseInt(JSON.parse(item.UpgradeFingerprint).lvl);
const maxRank = itemMap[item.ItemType]?.fusionLimit ?? 5;
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
if (
U5Mods.includes(item.ItemType) &&
item.ItemType != "/Lotus/Upgrades/Modules/Crafted/IncendiaryRifleMod"
) {
if (item.UpgradeFingerprint) {
const fingerprint = JSON.parse(item.UpgradeFingerprint);
if (fingerprint.fits) {
td.textContent = loc("code_U5Mod").replace(
"|TYPE|",
itemMap[fingerprint.fits]?.name ?? fingerprint.fits
);
}
if (item.ItemType.endsWith("GrineerMeleeModule") || item.ItemType.endsWith("TennoSwordModule")) {
td.textContent += " [" + item.ItemType.split("/").pop() + "]";
}
}
}
if (itemMap[item.ItemType]?.badReason == "notraw") {
// Assuming this is a riven with a pending challenge, so rank would be N/A, but otherwise it's fine.
} else if (!Number.isNaN(rank)) {
td.innerHTML += " <span title='" + loc("code_rank") + "'>★ " + rank + "/" + maxRank + "</span>";
}
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
if (rank < maxRank) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
setFingerprint(item.ItemType, item.ItemId, { lvl: maxRank });
};
a.title = loc("code_maxRank");
a.innerHTML = icons.arrowUp;
td.appendChild(a);
}
if (U5Mods.includes(item.ItemType)) {
const a = document.createElement("a");
a.href = "/webui/details?productCategory=Upgrades&itemId=" + item.ItemId.$oid;
a.innerHTML = icons.feather;
td.appendChild(a);
}
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
document.getElementById("mods-list").removeChild(tr);
disposeOfGear("Upgrades", item.ItemId.$oid);
};
a.title = loc("code_remove");
a.innerHTML = icons.trash;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("mods-list").appendChild(tr);
});
data.RawUpgrades.forEach(item => {
if (item.ItemCount > 0) {
const maxRank = itemMap[item.ItemType]?.fusionLimit ?? 5;
const tr = document.createElement("tr");
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
if (maxRank > 0) {
td.innerHTML += " <span title='" + loc("code_rank") + "'>★ 0/" + maxRank + "</span>";
}
if (item.ItemCount > 1) {
td.innerHTML +=
" <span title='" + loc("code_count") + "'>🗍 " + parseInt(item.ItemCount) + "</span>";
}
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
if (maxRank != 0) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
setFingerprint(item.ItemType, item.LastAdded, { lvl: maxRank });
};
a.title = loc("code_maxRank");
a.innerHTML = icons.arrowUp;
td.appendChild(a);
}
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
document.getElementById("mods-list").removeChild(tr);
disposeOfItems("Upgrades", item.ItemType, item.ItemCount);
};
a.title = loc("code_remove");
a.innerHTML = icons.trash;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("mods-list").appendChild(tr);
}
});
});
dictPromise.then(() => {
const invenotryHeaders = document.querySelectorAll(".card-header[data-cardtype]");
invenotryHeaders.forEach(elm => {
elm.innerHTML = "";
elm.classList.remove(...elm.classList);
elm.classList.add("card-header", "d-flex", "justify-content-between", "align-items-center");
const cardType = elm.dataset.cardtype;
{
const h5 = document.createElement("h5");
h5.classList.add("mb-0");
const locStr = `inventory_${cardType.charAt(0).toLowerCase() + cardType.slice(1)}`;
h5.textContent = loc(locStr);
h5.setAttribute("data-loc", locStr);
elm.appendChild(h5);
}
{
const butDiv = document.createElement("div");
butDiv.classList.add("d-flex", "gap-2");
if (!["MoaPets", "Hoverboards", "Boosters"].includes(cardType)) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
if (cardType == "EvolutionProgress") {
debounce(addMissingEvolutionProgress);
} else {
debounce(addMissingEquipment, [cardType]);
}
};
a.setAttribute("data-loc-title", "inventory_bulkAdd");
a.title = loc("inventory_bulkAdd");
a.innerHTML = icons.plus;
butDiv.appendChild(a);
}
if (!["QuestKeys", "Boosters", "FlavourItems", "ShipDecorations", "WeaponSkins"].includes(cardType)) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
if (cardType == "EvolutionProgress") {
debounce(maxRankAllEvolutions);
} else {
debounce(maxRankAllEquipment, [cardType]);
}
};
a.setAttribute("data-loc-title", "inventory_bulkRankUp");
a.title = loc("inventory_bulkRankUp");
a.innerHTML = icons.arrowUp;
butDiv.appendChild(a);
}
if (cardType == "QuestKeys") {
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
doBulkQuestUpdate("completeAll");
};
a.setAttribute("data-loc-title", "inventory_bulkComplete");
a.title = loc("inventory_bulkComplete");
a.innerHTML = icons.check;
butDiv.appendChild(a);
}
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
doBulkQuestUpdate("resetAll");
};
a.setAttribute("data-loc-title", "inventory_bulkReset");
a.title = loc("inventory_bulkReset");
a.innerHTML = icons.arrowRotateRight;
butDiv.appendChild(a);
}
}
if (cardType != "Boosters") {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
removeItems(cardType);
};
a.setAttribute("data-loc-title", "inventory_bulkRemove");
a.title = loc("inventory_bulkRemove");
a.innerHTML = icons.trash;
butDiv.appendChild(a);
}
elm.appendChild(butDiv);
}
});
const guildHeaders = document.querySelectorAll(".card-header[data-guildCardType]");
guildHeaders.forEach(elm => {
elm.innerHTML = "";
elm.classList.remove(...elm.classList);
elm.classList.add("card-header", "d-flex", "justify-content-between", "align-items-center");
const cardType = elm.dataset.guildcardtype;
{
const h5 = document.createElement("h5");
h5.classList.add("mb-0");
const locStr = `guildView_${cardType.charAt(0).toLowerCase() + cardType.slice(1)}`;
h5.textContent = loc(locStr);
h5.setAttribute("data-loc", locStr);
elm.appendChild(h5);
}
{
const butDiv = document.createElement("div");
butDiv.classList.add("d-flex", "gap-2");
if (cardType != "VaultMiscItems") {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
if (cardType != "TechProjects") {
debounce(addMissingVaultItems, cardType);
} else {
debounce(addMissingTechProjects);
}
};
a.setAttribute("data-loc-title", "inventory_bulkAdd");
a.title = loc("inventory_bulkAdd");
a.innerHTML = icons.plus;
a.id = `${cardType}-bulkAdd`;
butDiv.appendChild(a);
}
if (cardType == "TechProjects") {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(fundAllTechProjects);
};
a.setAttribute("data-loc-title", "guildView_bulkFund");
a.title = loc("guildView_bulkFund");
a.innerHTML = icons.arrowUp;
a.id = `${cardType}-bulkFund`;
butDiv.appendChild(a);
}
if (cardType == "TechProjects") {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(completeAllTechProjects);
};
a.setAttribute("data-loc-title", "inventory_bulkComplete");
a.title = loc("inventory_bulkComplete");
a.innerHTML = icons.forward;
a.id = `${cardType}-bulkComplete`;
butDiv.appendChild(a);
}
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
if (cardType != "TechProjects") {
debounce(bulkRemoveVaultItems, cardType);
} else {
debounce(bulkRemoveTechProjects);
}
};
a.setAttribute("data-loc-title", "inventory_bulkRemove");
a.title = loc("inventory_bulkRemove");
a.innerHTML = icons.trash;
a.id = `${cardType}-bulkRemove`;
butDiv.appendChild(a);
}
elm.appendChild(butDiv);
}
});
});
function updateGuild() {
guild_data = null;
if (single.getCurrentPath() == "/webui/clan") {
single.loadRoute(single.getCurrentPath());
}
}
function changeGuildRank(guildId, targetId, rankChange) {
revalidateAuthz().then(() => {
const req = $.get(
`/api/changeGuildRank.php?${window.authz}&guildId=${guildId}&targetId=${targetId}&rankChange=${rankChange}`
);
req.done(() => {
updateGuild();
});
});
}
function kickFromGuild(accountId) {
revalidateAuthz().then(() => {
const req = $.post({
url: "/api/removeFromGuild.php?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/octet-stream",
data: JSON.stringify({
userId: accountId
})
});
req.done(() => {
updateGuild();
});
});
}
function kickFromAlliance(guildId) {
revalidateAuthz().then(() => {
const req = $.get("/api/removeFromAlliance.php?" + window.authz + "&guildId=" + guildId);
req.done(() => {
updateGuild();
});
});
}
function getKey(input) {
return document
.getElementById(input.getAttribute("list"))
.querySelector("[value='" + input.value.replaceAll("'", "\\'") + "']")
?.getAttribute("data-key");
}
function doAcquireEquipment(category) {
const uniqueName = getKey(document.getElementById("acquire-type-" + category));
if (!uniqueName) {
$("#acquire-type-" + category)
.addClass("is-invalid")
.focus();
return;
}
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/addItems?" + window.authz,
contentType: "application/json",
data: JSON.stringify([
{
ItemType: uniqueName,
ItemCount: 1
}
])
});
req.done(modifiedPaths => {
document.getElementById("acquire-type-" + category).value = "";
if (modifiedPaths.length) {
updateInventory();
} else {
toast(loc("code_nothingToDo"));
}
});
});
}
function removeCustomization(uniqueName) {
revalidateAuthz().then(() => {
const req = $.get("/custom/removeCustomization?" + window.authz + "&itemType=" + uniqueName);
req.done(() => {
inventory_data.FlavourItems.splice(
inventory_data.FlavourItems.findIndex(x => x.ItemType == uniqueName),
1
);
});
});
}
function removeIsNew() {
revalidateAuthz().then(() => {
$.get("/custom/removeIsNew?" + window.authz);
});
}
function getRequiredParts(category, WeaponType) {
switch (category) {
case "Hoverboards":
return ["HB_DECK", "HB_ENGINE", "HB_FRONT", "HB_JET"];
case "OperatorAmps":
return ["AMP_OCULUS", "AMP_CORE", "AMP_BRACE"];
case "Melee":
return ["BLADE", "HILT", "HILT_WEIGHT"];
case "LongGuns":
return ["GUN_BARREL", "GUN_PRIMARY_HANDLE", "GUN_CLIP"];
case "Pistols":
return ["GUN_BARREL", "GUN_SECONDARY_HANDLE", "GUN_CLIP"];
case "MoaPets":
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) {
Parts.push(partName);
}
});
if (Parts.length != requiredParts.length) {
let isFirstPart = true;
requiredParts.forEach(part => {
const partSelector = document.getElementById("acquire-type-" + category + "-" + part);
if (!getKey(partSelector)) {
if (isFirstPart) {
isFirstPart = false;
$("#acquire-type-" + category + "-" + part)
.addClass("is-invalid")
.focus();
} else {
$("#acquire-type-" + category + "-" + part).addClass("is-invalid");
}
}
});
} else {
const mapping = {
LongGuns: {
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelAPart":
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun",
"/Lotus/Weapons/Infested/Pistols/InfKitGun/Barrels/InfBarrelEgg/InfModularBarrelEggPart":
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun",
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelBPart":
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary",
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelCPart":
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary",
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelDPart":
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam",
"/Lotus/Weapons/Infested/Pistols/InfKitGun/Barrels/InfBarrelBeam/InfModularBarrelBeamPart":
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam"
},
Pistols: {
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelAPart":
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun",
"/Lotus/Weapons/Infested/Pistols/InfKitGun/Barrels/InfBarrelEgg/InfModularBarrelEggPart":
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun",
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelBPart":
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary",
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelCPart":
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary",
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelDPart":
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam",
"/Lotus/Weapons/Infested/Pistols/InfKitGun/Barrels/InfBarrelBeam/InfModularBarrelBeamPart":
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam"
},
MoaPets: {
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA":
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetAPowerSuit",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadB":
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetBPowerSuit",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC":
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetCPowerSuit"
}
};
Parts.forEach(part => {
const categoryMap = mapping[category];
if (categoryMap && categoryMap[part]) {
WeaponType = categoryMap[part];
}
});
if (category == "KubrowPets") Parts.unshift(WeaponType);
revalidateAuthz().then(() => {
const req = $.post({
url: "/api/modularWeaponCrafting.php?" + window.authz,
contentType: "application/octet-stream",
data: JSON.stringify({
WeaponType,
Parts,
isWebUi: true
})
});
req.done(() => {
const mainInput = document.getElementById("acquire-type-" + category);
if (mainInput) {
mainInput.value = "";
if (category === "MoaPets") {
document.getElementById("modular-MoaPets-Zanuka").classList.add("d-none");
document.getElementById("modular-MoaPets-Moa").classList.add("d-none");
} else if (category === "KubrowPets") {
document.getElementById("modular-KubrowPets-Catbrow").classList.add("d-none");
document.getElementById("modular-KubrowPets-Kubrow").classList.add("d-none");
} else {
document.getElementById("modular-" + category).classList.add("d-none");
}
}
requiredParts.forEach(part => {
document.getElementById("acquire-type-" + category + "-" + part).value = "";
});
updateInventory();
});
});
}
}
function doAcquireEvolution() {
const uniqueName = getKey(document.getElementById("acquire-type-EvolutionProgress"));
if (!uniqueName) {
$("#acquire-type-EvolutionProgress").addClass("is-invalid").focus();
return;
}
if (!document.querySelector("#EvolutionProgress-list [data-item-type='" + uniqueName + "']")) {
setEvolutionProgress([{ ItemType: uniqueName, Rank: permanentEvolutionWeapons.has(uniqueName) ? 0 : 1 }]);
} else {
toast(loc("code_nothingToDo"));
}
}
$(document).on("input", "input, textarea", function () {
$(this).removeClass("is-invalid");
});
function dispatchAddItemsRequestsBatch(requests) {
return new Promise(resolve => {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/addItems?" + window.authz,
contentType: "application/json",
data: JSON.stringify(requests)
});
req.done(() => {
updateInventory();
resolve();
});
});
});
}
function addMissingEquipment(categories) {
const requests = [];
categories.forEach(category => {
document.querySelectorAll("#datalist-" + category + " option").forEach(elm => {
if (
!document.querySelector(
"#" + category + "-list [data-item-type='" + elm.getAttribute("data-key") + "']"
)
) {
if (!webUiModularWeapons.includes(elm.getAttribute("data-key"))) {
let ItemCount = 1;
if (category == "ShipDecorations") ItemCount = 999999;
requests.push({ ItemType: elm.getAttribute("data-key"), ItemCount });
}
}
});
});
if (requests.length != 0 && window.confirm(loc("code_addItemsConfirm").replaceAll("|COUNT|", requests.length))) {
return dispatchAddItemsRequestsBatch(requests);
}
}
function addMissingDangerRooms() {
revalidateAuthz().then(() => {
getInventoryData().then(data => {
const requests = [];
window.itemListPromise.then(itemMap => {
const allItemKeys = Object.entries(itemMap)
.filter(([_key, item]) => item.parentName === "/Lotus/Types/Items/DangerRoom/DangerRoomTile")
.map(([key]) => key);
allItemKeys.push("/Lotus/Types/Items/MiscItems/DangerRoomKey"); // also give access to the simulacrum itself
const existingItems = new Set(
data.MiscItems.filter(item => item.ItemCount > 0).map(item => item.ItemType)
);
allItemKeys.forEach(item => {
if (!existingItems.has(item)) {
requests.push({ ItemType: item, ItemCount: 1 });
}
});
if (requests.length == 0) {
toast(loc("code_nothingToDo"));
} else if (window.confirm(loc("code_addItemsConfirm").replaceAll("|COUNT|", requests.length))) {
return dispatchAddItemsRequestsBatch(requests);
}
});
});
});
}
function addVaultItem(vaultType) {
const ItemType = getKey(document.getElementById(`acquire-type-${vaultType}`));
if (!ItemType) {
$(`#acquire-type-${vaultType}`).addClass("is-invalid").focus();
return;
}
const ItemCount = ["VaultMiscItems", "VaultShipDecorations"].includes(vaultType)
? parseInt($(`#${vaultType}-count`).val())
: 1;
if (ItemCount != 0 && !Number.isNaN(ItemCount)) {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/addVaultTypeCount?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/json",
data: JSON.stringify({
vaultType,
items: [
{
ItemType,
ItemCount
}
]
})
});
req.done(() => {
document.getElementById(`acquire-type-${vaultType}`).value = "";
updateGuild();
});
});
}
}
function removeVaultItem(vaultType, ItemType, ItemCount) {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/addVaultTypeCount?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/json",
data: JSON.stringify({
vaultType,
items: [
{
ItemType,
ItemCount
}
]
})
});
req.done(() => {
updateGuild();
});
});
}
function addGuildTechProject() {
const uniqueName = getKey(document.getElementById("acquire-type-TechProjects"));
if (!uniqueName) {
$("#acquire-type-TechProjects").addClass("is-invalid").focus();
return;
}
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/addTechProject?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/json",
data: JSON.stringify([
{
ItemType: uniqueName
}
])
});
req.done(() => {
document.getElementById("acquire-type-TechProjects").value = "";
updateGuild();
});
});
}
function removeGuildTechProject(uniqueName) {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/removeTechProject?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/json",
data: JSON.stringify([
{
ItemType: uniqueName
}
])
});
req.done(() => {
updateGuild();
});
});
}
function completeGuildTechProject(uniqueName) {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/completeTechProject?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/json",
data: JSON.stringify([
{
ItemType: uniqueName
}
])
});
req.done(() => {
updateGuild();
});
});
}
function fundGuildTechProject(uniqueName) {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/fundTechProject?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/json",
data: JSON.stringify([
{
ItemType: uniqueName
}
])
});
req.done(() => {
updateGuild();
});
});
}
function dispatchAddVaultItemsBatch(requests, vaultType) {
return new Promise(resolve => {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/addVaultTypeCount?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/json",
data: JSON.stringify({
vaultType,
items: requests
})
});
req.done(() => {
updateGuild();
resolve();
});
});
});
}
function addMissingVaultItems(vaultType) {
const requests = [];
const datalist = vaultType === "VaultShipDecorations" ? "ShipDecorations" : vaultType;
document.querySelectorAll(`#datalist-${datalist} option`).forEach(elm => {
if (!document.querySelector(`#${vaultType}-list [data-item-type='${elm.getAttribute("data-key")}']`)) {
let ItemCount = 1;
if (vaultType == "VaultShipDecorations") ItemCount = 999999;
requests.push({ ItemType: elm.getAttribute("data-key"), ItemCount });
}
});
if (
requests.length != 0 &&
window.confirm(loc("code_addVaultItemsConfirm").replaceAll("|COUNT|", requests.length))
) {
return dispatchAddVaultItemsBatch(requests, vaultType);
}
}
function dispatchAddTechProjectsBatch(requests) {
return new Promise(resolve => {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/addTechProject?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/json",
data: JSON.stringify(requests)
});
req.done(() => {
updateGuild();
resolve();
});
});
});
}
function addMissingTechProjects() {
const requests = [];
document.querySelectorAll("#datalist-TechProjects option").forEach(elm => {
if (!document.querySelector("#TechProjects-list [data-item-type='" + elm.getAttribute("data-key") + "']")) {
requests.push({ ItemType: elm.getAttribute("data-key") });
}
});
if (
requests.length != 0 &&
window.confirm(loc("code_addTechProjectsConfirm").replaceAll("|COUNT|", requests.length))
) {
return dispatchAddTechProjectsBatch(requests);
}
}
function bulkRemoveVaultItems(vaultType) {
const requests = [];
const datalist = vaultType === "VaultShipDecorations" ? "ShipDecorations" : vaultType;
document.querySelectorAll(`#datalist-${datalist} option`).forEach(elm => {
if (document.querySelector(`#${vaultType}-list [data-item-type='${elm.getAttribute("data-key")}']`)) {
let ItemCount = 1;
if (vaultType == "VaultShipDecorations") ItemCount = 999999;
requests.push({ ItemType: elm.getAttribute("data-key"), ItemCount: ItemCount * -1 });
}
});
if (
requests.length != 0 &&
window.confirm(loc("code_removeVaultItemsConfirm").replaceAll("|COUNT|", requests.length))
) {
return dispatchAddVaultItemsBatch(requests, vaultType);
}
}
function dispatchRemoveTechProjectsBatch(requests) {
return new Promise(resolve => {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/removeTechProject?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/json",
data: JSON.stringify(requests)
});
req.done(() => {
updateGuild();
resolve();
});
});
});
}
function bulkRemoveTechProjects() {
const requests = [];
document.querySelectorAll("#datalist-TechProjects option").forEach(elm => {
if (document.querySelector("#TechProjects-list [data-item-type='" + elm.getAttribute("data-key") + "']")) {
requests.push({ ItemType: elm.getAttribute("data-key") });
}
});
if (
requests.length != 0 &&
window.confirm(loc("code_removeTechProjectsConfirm").replaceAll("|COUNT|", requests.length))
) {
return dispatchRemoveTechProjectsBatch(requests);
}
}
function dispatchFundTechProjectsBatch(requests) {
return new Promise(resolve => {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/fundTechProject?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/json",
data: JSON.stringify(requests)
});
req.done(() => {
updateGuild();
resolve();
});
});
});
}
function fundAllTechProjects() {
revalidateAuthz().then(() => {
getGuildData().done(data => {
const requests = [];
data.TechProjects ??= [];
data.TechProjects.forEach(techProject => {
if (techProject.State != 1) {
requests.push({
ItemType: techProject.ItemType
});
}
});
if (Object.keys(requests).length > 0) {
return dispatchFundTechProjectsBatch(requests);
}
});
});
}
function dispatchCompleteTechProjectsBatch(requests) {
return new Promise(resolve => {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/completeTechProject?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/json",
data: JSON.stringify(requests)
});
req.done(() => {
updateGuild();
resolve();
});
});
});
}
function completeAllTechProjects() {
revalidateAuthz().then(() => {
getGuildData().done(data => {
const requests = [];
data.TechProjects ??= [];
data.TechProjects.forEach(techProject => {
if (techProject.State == 1 && new Date(techProject.CompletionDate) > new Date()) {
requests.push({
ItemType: techProject.ItemType
});
}
});
if (Object.keys(requests).length > 0) {
return dispatchCompleteTechProjectsBatch(requests);
}
});
});
}
async function addMissingHelminthRecipes() {
await revalidateAuthz();
await fetch("/custom/addMissingHelminthBlueprints?" + window.authz);
}
function addMissingEvolutionProgress() {
const requests = [];
document.querySelectorAll("#datalist-EvolutionProgress option").forEach(elm => {
const uniqueName = elm.getAttribute("data-key");
if (!document.querySelector("#EvolutionProgress-list [data-item-type='" + uniqueName + "']")) {
requests.push({ ItemType: uniqueName, Rank: permanentEvolutionWeapons.has(uniqueName) ? 0 : 1 });
}
});
if (requests.length == 0) {
toast(loc("code_nothingToDo"));
} else if (window.confirm(loc("code_addItemsConfirm").replaceAll("|COUNT|", requests.length))) {
setEvolutionProgress(requests);
}
}
function maxRankAllEvolutions() {
revalidateAuthz().then(() => {
getInventoryData().then(data => {
const requests = [];
data.EvolutionProgress.forEach(item => {
const maxRank = permanentEvolutionWeapons.has(item.ItemType) ? 5 : 4;
if (item.Rank < maxRank) {
requests.push({
ItemType: item.ItemType,
Rank: maxRank
});
}
});
if (Object.keys(requests).length > 0) {
return setEvolutionProgress(requests).then(() => {
toast(loc("code_succRankUp"));
});
}
toast(loc("code_noEquipmentToRankUp"));
});
});
}
function maxRankAllEquipment(categories) {
revalidateAuthz().then(() => {
getInventoryData().then(data => {
window.itemListPromise.then(itemMap => {
const batchData = {};
categories.forEach(category => {
data[category].forEach(item => {
const maxXP = [
"Suits",
"SpaceSuits",
"Sentinels",
"Hoverboards",
"MechSuits",
"MoaPets",
"KubrowPets"
].includes(category)
? 1_600_000
: 800_000;
if (item.XP < maxXP) {
if (!batchData[category]) {
batchData[category] = [];
}
batchData[category].push({
ItemId: { $oid: item.ItemId.$oid },
XP: maxXP
});
}
if (item.ItemType in itemMap && "exalted" in itemMap[item.ItemType]) {
for (const exaltedType of itemMap[item.ItemType].exalted) {
const exaltedItem = data["SpecialItems"].find(x => x.ItemType == exaltedType);
if (exaltedItem) {
const exaltedCap = itemMap[exaltedType]?.type == "weapons" ? 800_000 : 1_600_000;
if (exaltedItem.XP < exaltedCap) {
batchData["SpecialItems"] ??= [];
batchData["SpecialItems"].push({
ItemId: { $oid: exaltedItem.ItemId.$oid },
XP: exaltedCap
});
}
}
}
}
});
});
if (Object.keys(batchData).length > 0) {
return sendBatchGearExp(batchData);
}
toast(loc("code_noEquipmentToRankUp"));
});
});
});
}
// Assumes that caller revalidates authz
function addGearExp(category, oid, xp) {
const data = {};
data[category] = [
{
ItemId: { $oid: oid },
XP: xp
}
];
return new Promise((resolve, reject) => {
$.post({
url: "/custom/addXp?" + window.authz,
contentType: "application/json",
data: JSON.stringify(data)
})
.done(resolve)
.fail(reject);
});
}
function sendBatchGearExp(data) {
revalidateAuthz().then(() => {
$.post({
url: "/custom/addXp?" + window.authz,
contentType: "application/json",
data: JSON.stringify(data)
}).done(() => {
toast(loc("code_succRankUp"));
updateInventory();
});
});
}
function renameGear(category, oid, name) {
revalidateAuthz().then(() => {
if (category == "KubrowPets") {
$.post({
url: "/api/renamePet.php?" + window.authz + "&webui=1",
contentType: "text/plain",
data: JSON.stringify({
petId: oid,
name: name
})
}).done(function () {
inventory_data.KubrowPets.find(x => x.ItemId.$oid == oid).Details.Name = name;
populateInventoryRoute();
});
} else {
$.post({
url: "/api/nameWeapon.php?" + window.authz + "&Category=" + category + "&ItemId=" + oid + "&webui=1",
contentType: "text/plain",
data: JSON.stringify({
ItemName: name
})
}).done(function () {
inventory_data[category].find(x => x.ItemId.$oid == oid).ItemName = name;
populateInventoryRoute();
});
}
});
}
function disposeOfGear(category, oid) {
if (category == "KubrowPets") {
revalidateAuthz().then(() => {
$.post({
url: "/api/releasePet.php?" + window.authz,
contentType: "application/octet-stream",
data: JSON.stringify({
Recipe: "webui",
petId: oid
})
}).done(function () {
inventory_data = undefined; // UI is up-to-date now, but subsequent updates will need to grab new data
});
});
} else {
const data = {
SellCurrency: "SC_RegularCredits",
SellPrice: 0,
Items: {}
};
data.Items[category] = [
{
String: oid,
Count: 0
}
];
revalidateAuthz().then(() => {
$.post({
url: "/api/sell.php?" + window.authz,
contentType: "text/plain",
data: JSON.stringify(data)
}).done(function () {
inventory_data = undefined; // UI is up-to-date now, but subsequent updates will need to grab new data
});
});
}
}
function disposeOfItems(category, type, count) {
const data = {
SellCurrency: "SC_RegularCredits",
SellPrice: 0,
Items: {}
};
data.Items[category] = [
{
String: type,
Count: count
}
];
revalidateAuthz().then(() => {
$.post({
url: "/api/sell.php?" + window.authz,
contentType: "text/plain",
data: JSON.stringify(data)
}).done(function () {
inventory_data = undefined; // UI is up-to-date now, but subsequent updates will need to grab new data
});
});
}
function equipmentFeatures(category, oid, bit) {
revalidateAuthz().then(() => {
$.get(
"/custom/equipmentFeatures?" + window.authz + "&ItemId=" + oid + "&Category=" + category + "&bit=" + bit
).done(function () {
inventory_data[category].find(x => x.ItemId.$oid == oid).Features ^= bit;
// toggle state was already updated when the user clicked on it
});
});
}
function unlockLevelCap(category, oid, formas) {
revalidateAuthz().then(() => {
$.post({
url: "/custom/unlockLevelCap?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
Category: category,
ItemId: oid,
Polarized: formas
})
}).done(function () {
inventory_data[category].find(x => x.ItemId.$oid == oid).Polarized = formas;
});
});
}
function maturePet(oid, revert) {
revalidateAuthz().then(() => {
$.post({
url: "/api/maturePet.php?" + window.authz,
contentType: "application/octet-stream",
data: JSON.stringify({
petId: oid,
revert
})
}).done(function () {
inventory_data.KubrowPets.find(x => x.ItemId.$oid == oid).Details.IsPuppy = revert;
populateInventoryRoute();
});
});
}
function setEvolutionProgress(requests) {
return new Promise(resolve => {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/setEvolutionProgress?" + window.authz,
contentType: "application/json",
data: JSON.stringify(requests)
});
req.done(newEvolutionProgress => {
inventory_data.EvolutionProgress = newEvolutionProgress;
populateInventoryRoute();
resolve();
});
});
});
}
function doAcquireCountItems(category) {
const uniqueName = getKey(document.getElementById("acquire-type-" + category));
if (!uniqueName) {
$(`#acquire-type-${category}`).addClass("is-invalid").focus();
return;
}
const count = parseInt($(`#${category}-count`).val());
if (count != 0 && !Number.isNaN(count)) {
revalidateAuthz().then(() => {
$.post({
url: "/custom/addItems?" + window.authz,
contentType: "application/json",
data: JSON.stringify([
{
ItemType: uniqueName,
ItemCount: count
}
])
})
.done(function (modifiedPaths) {
if (modifiedPaths.length) {
if (count > 0) {
toast(loc("code_succAdded"));
} else {
toast(loc("code_succRemoved"));
}
} else {
toast(loc("code_nothingToDo"));
}
if (category != "miscitems") updateInventory();
})
.fail(r => {
toast(r.responseText);
});
});
}
}
function removeShipDecoration(uniqueName, count) {
revalidateAuthz().then(() => {
$.post({
url: "/custom/addItems?" + window.authz,
contentType: "application/json",
data: JSON.stringify([
{
ItemType: uniqueName,
ItemCount: count * -1
}
])
}).done(function () {
inventory_data.ShipDecorations.splice(
inventory_data.ShipDecorations.findIndex(x => x.ItemType == uniqueName),
1
);
toast(loc("code_succRemoved"));
});
});
}
function addItemByItemType() {
const ItemType = document.getElementById("typeName-type").value;
const ItemCount = parseInt($(`#typeName-count`).val());
if (ItemCount != 0 && !Number.isNaN(ItemCount)) {
revalidateAuthz().then(() => {
$.post({
url: "/custom/addItems?" + window.authz,
contentType: "application/json",
data: JSON.stringify([
{
ItemType,
ItemCount
}
])
})
.done(function (modifiedPaths) {
if (modifiedPaths.length) {
if (ItemCount > 0) {
toast(loc("code_succAdded"));
} else {
toast(loc("code_succRemoved"));
}
if (modifiedPaths.some(x => !x.startsWith("MiscItems"))) {
updateInventory();
}
} else {
toast(loc("code_nothingToDo"));
}
})
.fail(r => {
toast(r.responseText);
});
});
}
}
function doAcquireRiven() {
let fingerprint;
try {
fingerprint = JSON.parse($("#addriven-fingerprint").val());
if (typeof fingerprint !== "object") {
fingerprint = JSON.parse(fingerprint);
}
} catch (e) {
/* empty */
}
if (
typeof fingerprint !== "object" ||
!("compat" in fingerprint) ||
!("pol" in fingerprint) ||
!("buffs" in fingerprint)
) {
$("#addriven-fingerprint").addClass("is-invalid").focus();
return;
}
const uniqueName = "/Lotus/Upgrades/Mods/Randomized/" + $("#addriven-type").val();
revalidateAuthz().then(() => {
// Add riven type to inventory
$.post({
url: "/custom/addItems?" + window.authz,
contentType: "application/json",
data: JSON.stringify([
{
ItemType: uniqueName,
ItemCount: 1
}
])
}).done(function () {
// Get riven's assigned id
$.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1").done(data => {
for (const rawUpgrade of data.RawUpgrades) {
if (rawUpgrade.ItemType === uniqueName) {
// Add fingerprint to riven
$.post({
url: "/api/artifacts.php?" + window.authz,
contentType: "text/plain",
data: JSON.stringify({
Upgrade: {
ItemType: uniqueName,
UpgradeFingerprint: JSON.stringify(fingerprint),
ItemId: rawUpgrade.LastAdded
},
LevelDiff: 0,
Cost: 0,
FusionPointCost: 0
})
}).done(function () {
$("#addriven-fingerprint").val("");
updateInventory();
});
break;
}
}
});
});
});
}
function setFingerprint(ItemType, ItemId, fingerprint) {
revalidateAuthz().then(() => {
$.post({
url: "/api/artifacts.php?" + window.authz,
contentType: "text/plain",
data: JSON.stringify({
Upgrade: {
ItemType,
ItemId,
UpgradeFingerprint: JSON.stringify(fingerprint)
},
LevelDiff: 0,
Cost: 0,
FusionPointCost: 0
})
}).done(function () {
updateInventory();
});
});
}
function doAcquireMod() {
const maxed = !!window.maxed;
window.maxed = false;
const uniqueName = getKey(document.getElementById("mod-to-acquire"));
if (!uniqueName) {
$("#mod-to-acquire").addClass("is-invalid").focus();
return;
}
const count = parseInt($("#mod-count").val());
if (count != 0 && !Number.isNaN(count)) {
Promise.all([window.itemListPromise, revalidateAuthz()]).then(([itemList]) => {
$.post({
url: "/custom/addItems?" + window.authz,
contentType: "application/json",
data: JSON.stringify([
{
ItemType: uniqueName,
ItemCount: count,
Fingerprint:
maxed && (itemList[uniqueName].fusionLimit ?? 5) > 0
? JSON.stringify({ lvl: itemList[uniqueName].fusionLimit ?? 5 })
: undefined
}
])
})
.done(function () {
if (count > 0) {
toast(loc("code_succAdded"));
} else {
toast(loc("code_succRemoved"));
}
updateInventory();
})
.fail(r => {
toast(r.responseText);
});
});
}
}
function doAcquireModMax() {
const uniqueName = getKey(document.getElementById("mod-to-acquire"));
alert("doAcquireModMax: " + uniqueName);
}
// Cheats route
const uiConfigs = [...$(".config-form input[id], .config-form select[id]")].map(x => x.id);
for (const id of uiConfigs) {
const elm = document.getElementById(id);
if (elm.tagName == "SELECT") {
elm.onchange = function () {
let value = this.value;
if (value == "true") {
value = true;
} else if (value == "false") {
value = false;
} else if (value == "null") {
value = null;
} else if (!isNaN(parseInt(value))) {
value = parseInt(value);
}
$.post({
url: "/custom/setConfig?" + window.authz,
contentType: "application/json",
data: JSON.stringify({ [id]: value })
}).done(function () {
config_data[id] = value;
});
};
} else if (elm.type == "checkbox") {
elm.onchange = function () {
const value = this.checked;
$.post({
url: "/custom/setConfig?" + window.authz,
contentType: "application/json",
data: JSON.stringify({ [id]: value })
}).done(function () {
config_data[id] = value;
});
};
}
}
document.querySelectorAll(".config-form .input-group").forEach(grp => {
const input = grp.querySelector("input");
const btn = grp.querySelector("button");
input.oninput = input.onchange = function () {
btn.classList.remove("btn-secondary");
btn.classList.add("btn-primary");
};
btn.onclick = function () {
btn.classList.remove("btn-primary");
btn.classList.add("btn-secondary");
};
});
function doSaveConfigInt(id) {
const value = parseInt(document.getElementById(id).value);
$.post({
url: "/custom/setConfig?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
[id]: value
})
}).done(function () {
config_data[id] = value;
});
}
function doSaveConfigFloat(id) {
const value = parseFloat(document.getElementById(id).value);
$.post({
url: "/custom/setConfig?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
[id]: value
})
}).done(function () {
config_data[id] = value;
});
}
function doSaveConfigStringArray(id) {
const value = document
.getElementById(id)
.getAttribute("data-tags-value")
.split(", ")
.filter(x => x);
$.post({
url: "/custom/setConfig?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
[id]: value
})
}).done(function () {
config_data[id] = value;
});
}
let config_data;
// Assumes that caller revalidates authz
function getServerConfig() {
return new Promise((resolve, reject) => {
if (window.is_admin === false) {
config_data = null;
}
if (config_data !== undefined) {
resolve(config_data);
} else {
$.post({
url: "/custom/getConfig?" + window.authz,
contentType: "application/json",
data: JSON.stringify(uiConfigs)
})
.done(json => {
window.is_admin = !!json;
config_data = json;
resolve(config_data);
})
.fail(res => reject(res.responseText));
}
});
}
single.getRoute("/webui/cheats").on("beforeload", function () {
awaitAuthz().then(() => {
getInventoryData().then(data => {
document.getElementById("changeSyndicate").value = data.SupportedSyndicate ?? "";
for (const elm of accountCheats) {
if (elm.type === "checkbox") {
elm.checked = !!data[elm.id];
} else if (elm.type === "number") {
elm.value = data[elm.id] !== undefined ? data[elm.id] : elm.getAttribute("data-default") || "";
}
}
});
getServerConfig().then(json => {
if (json) {
$(".admin-hide").addClass("d-none");
$(".admin-show").removeClass("d-none");
Object.entries(json).forEach(entry => {
const [key, value] = entry;
const elm = document.getElementById(key);
if (elm.type == "checkbox") {
elm.checked = value;
} else if (elm.classList.contains("tags-input")) {
elm.value = (value ?? []).join(", ");
elm.oninput();
} else {
elm.value = value ?? elm.getAttribute("data-default");
}
});
} else {
$(".admin-hide").removeClass("d-none");
$(".admin-show").addClass("d-none");
}
});
});
});
function doUnlockAllFocusSchools() {
revalidateAuthz().then(() => {
getInventoryData().then(async data => {
const missingFocusUpgrades = {
"/Lotus/Upgrades/Focus/Attack/AttackFocusAbility": true,
"/Lotus/Upgrades/Focus/Tactic/TacticFocusAbility": true,
"/Lotus/Upgrades/Focus/Ward/WardFocusAbility": true,
"/Lotus/Upgrades/Focus/Defense/DefenseFocusAbility": true,
"/Lotus/Upgrades/Focus/Power/PowerFocusAbility": true
};
if (data.FocusUpgrades) {
for (const focusUpgrade of data.FocusUpgrades) {
if (focusUpgrade.ItemType in missingFocusUpgrades) {
delete missingFocusUpgrades[focusUpgrade.ItemType];
}
}
}
for (const upgradeType of Object.keys(missingFocusUpgrades)) {
await unlockFocusSchool(upgradeType);
}
if (Object.keys(missingFocusUpgrades).length == 0) {
toast(loc("code_focusAllUnlocked"));
} else {
toast(loc("code_focusUnlocked").replaceAll("|COUNT|", Object.keys(missingFocusUpgrades).length));
if (ws_is_open) {
window.ws.send(JSON.stringify({ sync_inventory: true }));
}
}
});
});
}
function unlockFocusSchool(upgradeType) {
return new Promise(resolve => {
// Deselect current FocusAbility so we will be able to unlock the way for free
$.post({
url: "/api/focus.php?" + window.authz + "&op=ActivateWay",
contentType: "text/plain",
data: JSON.stringify({ FocusType: null })
}).done(function () {
// Unlock the way now
$.post({
url: "/api/focus.php?" + window.authz + "&op=UnlockWay",
contentType: "text/plain",
data: JSON.stringify({
FocusType: upgradeType
})
}).done(function () {
resolve();
});
});
});
}
function doHelminthUnlockAll() {
revalidateAuthz().then(() => {
$.post("/api/infestedFoundry.php?" + window.authz + "&mode=custom_unlockall");
});
}
function doIntrinsicsUnlockAll() {
revalidateAuthz().then(() => {
$.get("/custom/unlockAllIntrinsics?" + window.authz);
});
}
document.querySelectorAll("#account-cheats input[type=checkbox]").forEach(elm => {
elm.onchange = function () {
revalidateAuthz().then(() => {
const value = elm.checked;
$.post({
url: "/custom/setAccountCheat?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
key: elm.id,
value: value
})
}).done(res => {
elm.checked = value;
inventory_data[elm.id] = value;
if (res == "retroactivable") {
if (window.confirm(loc("cheats_retroactivePrompt"))) {
$.get("/custom/retroactivelyApplyCheat?" + window.authz + "&cheat=" + elm.id);
}
}
});
});
};
});
document.querySelectorAll("#account-cheats .input-group").forEach(grp => {
const input = grp.querySelector("input");
const select = grp.querySelector("select");
const btn = grp.querySelector("button");
if (input) {
input.oninput = input.onchange = function () {
btn.classList.remove("btn-secondary");
btn.classList.add("btn-primary");
};
}
if (select) {
select.oninput = select.onchange = function () {
btn.classList.remove("btn-secondary");
btn.classList.add("btn-primary");
};
}
btn.onclick = function () {
btn.classList.remove("btn-primary");
btn.classList.add("btn-secondary");
const input = btn.closest(".input-group").querySelector('input[type="number"]');
if (!input) return;
revalidateAuthz().then(() => {
const value = Math.min(Number(input.value), Number(input.max));
$.post({
url: "/custom/setAccountCheat?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
key: input.id,
value: value
})
}).done(() => {
input.value = value;
inventory_data[elm.id] = value;
});
});
};
});
document.querySelectorAll("#guild-cheats input[type=checkbox]").forEach(elm => {
elm.onchange = function () {
revalidateAuthz().then(() => {
$.post({
url: "/custom/setGuildCheat?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/json",
data: JSON.stringify({
key: elm.id,
value: elm.checked
})
}).done(res => {
if (res == "retroactivable") {
if (window.confirm(loc("cheats_retroactivePrompt"))) {
$.get(
"/custom/retroactivelyApplyGuildCheat?" +
window.authz +
"&guildId=" +
window.guildId +
"&cheat=" +
elm.id
);
}
}
});
});
};
});
// Mods route
function doAddAllMods() {
let modsAll = new Set();
for (const child of document.getElementById("datalist-mods").children) {
modsAll.add(child.getAttribute("data-key"));
}
[
"/Lotus/Upgrades/Mods/Fusers/CommonModFuser",
"/Lotus/Upgrades/Mods/Fusers/UncommonModFuser",
"/Lotus/Upgrades/Mods/Fusers/RareModFuser",
"/Lotus/Upgrades/Mods/Fusers/LegendaryModFuser",
"/Lotus/Upgrades/Modules/GrineerMeleeModule",
"/Lotus/Upgrades/Modules/GrineerPistolModule",
"/Lotus/Upgrades/Modules/GrineerRifleModule",
"/Lotus/Upgrades/Modules/GrineerShotgunModule",
"/Lotus/Upgrades/Modules/OrokinWarframeModule",
"/Lotus/Upgrades/Modules/Crafted/IncendiaryRifleMod"
].forEach(path => modsAll.delete(path));
revalidateAuthz().then(() => {
getInventoryData().then(data => {
for (const modOwned of data.RawUpgrades) {
if ((modOwned.ItemCount ?? 1) > 0) {
modsAll.delete(modOwned.ItemType);
}
}
modsAll = Array.from(modsAll);
if (
modsAll.length != 0 &&
window.confirm(loc("code_addModsConfirm").replaceAll("|COUNT|", modsAll.length))
) {
$.post({
url: "/custom/addItems?" + window.authz,
contentType: "application/json",
data: JSON.stringify(
modsAll.map(mod => ({
ItemType: mod,
ItemCount: 21 // To fully upgrade certain arcanes
}))
)
}).done(function () {
updateInventory();
});
}
});
});
}
function doRemoveUnrankedMods() {
revalidateAuthz().then(() => {
getInventoryData().then(data => {
window.itemListPromise.then(itemMap => {
// Excluding unranked parazon mods and instead including ranked parazon mods because worse is better for them.
const upgradesToSell = data.RawUpgrades.filter(upgrade => !itemMap[upgrade.ItemType]?.parazon).map(
upgrade => ({ String: upgrade.ItemType, Count: upgrade.ItemCount })
);
for (const upgrade of data.Upgrades) {
if (itemMap[upgrade.ItemType]?.parazon) {
upgradesToSell.push({ String: upgrade.ItemId.$oid, Count: 0 });
}
}
$.post({
url: "/api/sell.php?" + window.authz,
contentType: "text/plain",
data: JSON.stringify({
SellCurrency: "SC_RegularCredits",
SellPrice: 0,
Items: {
Upgrades: upgradesToSell
}
})
}).done(function () {
updateInventory();
});
});
});
});
}
function doRemoveRankedMods() {
revalidateAuthz().then(() => {
getInventoryData().then(data => {
window.itemListPromise.then(itemMap => {
// Excluding ranked parazon mods and instead including unranked parazon mods because worse is better for them.
const upgradesToSell = data.Upgrades.filter(upgrade => !itemMap[upgrade.ItemType]?.parazon).map(
upgrade => ({ String: upgrade.ItemId.$oid, Count: 0 })
);
for (const upgrade of data.RawUpgrades) {
if (itemMap[upgrade.ItemType]?.parazon) {
upgradesToSell.push({ String: upgrade.ItemType, Count: upgrade.ItemCount });
}
}
$.post({
url: "/api/sell.php?" + window.authz,
contentType: "text/plain",
data: JSON.stringify({
SellCurrency: "SC_RegularCredits",
SellPrice: 0,
Items: {
Upgrades: upgradesToSell
}
})
}).done(function () {
updateInventory();
});
});
});
});
}
function doAddMissingMaxRankMods() {
revalidateAuthz().then(() => {
fetch("/custom/addMissingMaxRankMods?" + window.authz).then(() => {
updateInventory();
});
});
}
// DetailedView Route
single.getRoute("#detailedView-route").on("beforeload", async function () {
if (!inventory_data) {
document.getElementById("detailedView-loading").classList.remove("d-none");
document.getElementById("detailedView-title").textContent = "";
document.querySelector("#detailedView-route .text-body-secondary").textContent = "";
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");
document.getElementById("u5ModEdit-card").classList.add("d-none");
document.getElementById("equipmentFeatures-card").classList.add("d-none");
document.getElementById("equipmentFeaturesButtons-card").innerHTML = "";
}
populateDetailedViewRoute();
});
async function populateDetailedViewRoute() {
const [data, itemMap] = await Promise.all([
inventory_data ? Promise.resolve(inventory_data) : awaitAuthz().then(getInventoryData),
window.itemListPromise
]);
const urlParams = new URLSearchParams(window.location.search);
const oid = urlParams.get("itemId");
const category = urlParams.get("productCategory");
const item = data[category].find(x => x.ItemId.$oid == oid);
if (item) {
document.getElementById("detailedView-loading").classList.add("d-none");
const itemName = itemMap[item.ItemType]?.name ?? item.ItemType;
if (item.ItemName) {
const pipeIndex = item.ItemName.indexOf("|");
if (pipeIndex != -1) {
$("#detailedView-title").text(item.ItemName.substr(1 + pipeIndex) + " " + itemName);
document.querySelector("#detailedView-route .text-body-secondary").textContent = "";
} else {
$("#detailedView-title").text(item.ItemName);
$("#detailedView-route .text-body-secondary").text(itemName);
}
} else {
$("#detailedView-title").text(itemName);
document.querySelector("#detailedView-route .text-body-secondary").textContent = "";
}
if (category != "Upgrades") {
document.getElementById("equipmentFeatures-card").classList.remove("d-none");
const buttonsCard = document.getElementById("equipmentFeaturesButtons-card");
buttonsCard.innerHTML = "";
item.Features ??= 0;
const bits = [];
const evolutionWeapons = Array.from(document.querySelectorAll("#datalist-EvolutionProgress option")).map(
option => option.dataset.key
);
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 (
[item.ItemType, itemMap[item.ItemType]?.parentName].some(
type => type && evolutionWeapons.includes(type) && !permanentEvolutionWeapons.has(type)
)
)
bits.push(512);
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;
let locString = `code_feature_${bit}`;
if (
!["Suits", "SpaceSuits", "Hoverboards", "MechSuits", "Sentinels", "MoaPets", "KubrowPets"].includes(
category
) &&
bit === 1
) {
locString = `code_feature_${bit}_alt`;
}
label.innerHTML = loc(locString);
label.setAttribute("data-loc", locString);
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);
}
} else {
document.getElementById("equipmentFeatures-card").classList.add("d-none");
document.getElementById("equipmentFeaturesButtons-card").innerHTML = "";
}
if (category == "Suits") {
document.getElementById("archonShards-card").classList.remove("d-none");
const uniqueUpgrades = {};
(item.ArchonCrystalUpgrades ?? []).forEach(upgrade => {
if (upgrade && upgrade.UpgradeType) {
uniqueUpgrades[upgrade.UpgradeType] ??= 0;
uniqueUpgrades[upgrade.UpgradeType] += 1;
}
});
document.getElementById("crystals-list").innerHTML = "";
Object.entries(uniqueUpgrades).forEach(([upgradeType, count]) => {
const tr = document.createElement("tr");
{
const td = document.createElement("td");
td.textContent = count + "x " + (archonCrystalUpgrades[upgradeType] ?? upgradeType);
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
doPopArchonCrystalUpgrade(upgradeType);
};
a.title = loc("code_remove");
a.innerHTML = icons.trash;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("crystals-list").appendChild(tr);
});
{
document.getElementById("edit-suit-invigorations-card").classList.remove("d-none");
document.getElementById("invigoration-offensive").value = item.OffensiveUpgrade || "";
document.getElementById("invigoration-defensive").value = item.DefensiveUpgrade || "";
document.getElementById("invigoration-expiry").value =
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);
const configs = item.Configs ?? [];
const loadoutTabs = document.getElementById("loadoutTabs");
const loadoutTabsContent = document.getElementById("loadoutTabsContent");
loadoutTabs.innerHTML = "";
loadoutTabsContent.innerHTML = "";
for (let i = 0; i <= maxModConfigNum; i++) {
const config = configs[i] ?? {};
{
const li = document.createElement("li");
li.classList.add("nav-item");
const button = document.createElement("button");
button.classList.add("nav-link");
if (i === 0) button.classList.add("active");
button.id = `config${i}-tab`;
button.setAttribute("data-bs-toggle", "tab");
button.setAttribute("data-bs-target", `#config${i}`);
button.innerHTML = config.Name?.trim() || String.fromCharCode(65 + i);
li.appendChild(button);
loadoutTabs.appendChild(li);
}
{
const tabDiv = document.createElement("div");
tabDiv.classList = "tab-pane";
if (i === 0) tabDiv.classList.add("show", "active");
tabDiv.id = `config${i}`;
{
const abilityOverrideForm = document.createElement("form");
abilityOverrideForm.classList = "form-group mt-2";
abilityOverrideForm.setAttribute(
"onsubmit",
`handleAbilityOverride(event, ${i});return false;`
);
const abilityOverrideFormLabel = document.createElement("label");
abilityOverrideFormLabel.setAttribute("data-loc", "abilityOverride_label");
abilityOverrideFormLabel.innerHTML = loc("abilityOverride_label");
abilityOverrideFormLabel.classList = "form-label";
abilityOverrideFormLabel.setAttribute("for", `abilityOverride-ability-config-${i}`);
abilityOverrideForm.appendChild(abilityOverrideFormLabel);
const abilityOverrideInputGroup = document.createElement("div");
abilityOverrideInputGroup.classList = "input-group";
abilityOverrideForm.appendChild(abilityOverrideInputGroup);
const abilityOverrideInput = document.createElement("input");
abilityOverrideInput.id = `abilityOverride-ability-config-${i}`;
abilityOverrideInput.classList = "form-control";
abilityOverrideInput.setAttribute("list", "datalist-Abilities");
if (config.AbilityOverride) {
const datalist = document.getElementById("datalist-Abilities");
const options = Array.from(datalist.options);
abilityOverrideInput.value =
options.find(
option => config.AbilityOverride.Ability == option.getAttribute("data-key")
)?.value || "";
}
abilityOverrideInputGroup.appendChild(abilityOverrideInput);
const abilityOverrideOnSlot = document.createElement("span");
abilityOverrideOnSlot.classList = "input-group-text";
abilityOverrideOnSlot.setAttribute("data-loc", "abilityOverride_onSlot");
abilityOverrideOnSlot.innerHTML = loc("abilityOverride_onSlot");
abilityOverrideInputGroup.appendChild(abilityOverrideOnSlot);
const abilityOverrideSecondInput = document.createElement("input");
abilityOverrideSecondInput.id = `abilityOverride-ability-index-config-${i}`;
abilityOverrideSecondInput.classList = "form-control";
abilityOverrideSecondInput.setAttribute("type", "number");
abilityOverrideSecondInput.setAttribute("min", "1");
abilityOverrideSecondInput.setAttribute("max", "4");
if (config.AbilityOverride)
abilityOverrideSecondInput.value = config.AbilityOverride.Index + 1;
abilityOverrideInputGroup.appendChild(abilityOverrideSecondInput);
const abilityOverrideSetButton = document.createElement("button");
abilityOverrideSetButton.classList = "btn btn-primary";
abilityOverrideSetButton.setAttribute("type", "submit");
abilityOverrideSetButton.setAttribute("value", "set");
abilityOverrideSetButton.setAttribute("data-loc", "general_setButton");
abilityOverrideSetButton.innerHTML = loc("general_setButton");
abilityOverrideInputGroup.appendChild(abilityOverrideSetButton);
const abilityOverrideRemoveButton = document.createElement("button");
abilityOverrideRemoveButton.classList = "btn btn-danger";
abilityOverrideRemoveButton.setAttribute("type", "submit");
abilityOverrideRemoveButton.setAttribute("value", "remove");
abilityOverrideRemoveButton.setAttribute("data-loc", "code_remove");
abilityOverrideRemoveButton.innerHTML = loc("code_remove");
abilityOverrideInputGroup.appendChild(abilityOverrideRemoveButton);
abilityOverrideForm.appendChild(abilityOverrideInputGroup);
tabDiv.appendChild(abilityOverrideForm);
}
loadoutTabsContent.appendChild(tabDiv);
}
}
}
} else {
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");
}
if (["LongGuns", "Pistols", "Melee", "SpaceGuns", "SpaceMelee"].includes(category)) {
document.getElementById("valenceBonus-card").classList.remove("d-none");
document.getElementById("valenceBonus-innateDamage").value = "";
document.getElementById("valenceBonus-procent").value = 25;
if (item.UpgradeFingerprint) {
const buff = JSON.parse(item.UpgradeFingerprint).buffs[0];
const buffValue = fromUpgradeFingerprintValue(buff.Value, 0.25);
document.getElementById("valenceBonus-innateDamage").value = buff.Tag ?? "";
document.getElementById("valenceBonus-procent").value = Math.round(buffValue * 1000) / 10;
}
} else {
document.getElementById("valenceBonus-card").classList.add("d-none");
}
if (U5Mods.includes(item.ItemType)) {
document.getElementById("u5ModEdit-card").classList.remove("d-none");
const oldFitsSelect = document.getElementById("u5ModEdit-fits");
const fitsSelect = oldFitsSelect.cloneNode(false);
oldFitsSelect.parentNode.replaceChild(fitsSelect, oldFitsSelect);
const upgradeFields = document.getElementById("u5ModEdit-upgradeFields");
upgradeFields.innerHTML = "";
const fits = itemMap[item.ItemType].fits;
const fp = JSON.parse(item.UpgradeFingerprint);
// const statAtten = fits.find(fit => fit.type == fp.fits).statAtten || 1;
const statAtten = 1;
fits.forEach(fit => {
const option = document.createElement("option");
option.value = fit.type;
option.textContent = itemMap[fit.type]?.name ?? fit.type;
// + (fit.statAtten ? ` (${loc("code_statAtten")}: ${fit.statAtten || 1})` : "");
fitsSelect.appendChild(option);
});
fitsSelect.addEventListener("change", () => {
upgradeFields.querySelectorAll(".upgrade-group").forEach(group => {
updateUpgradeInput(itemMap[item.ItemType], group);
});
});
upgradeFields.setAttribute("data-stat-atten", statAtten);
document.getElementById("u5ModEdit-reqLevel").value = fp.reqLevel ?? 0;
document.getElementById("u5ModEdit-fits").value = fp.fits ?? fits[0].type;
fp.upgrades.forEach(up => {
createUpgradeInput(upgradeFields, itemMap, item.ItemType, up);
});
createUpgradeInput(upgradeFields, itemMap, item.ItemType, null);
} else {
document.getElementById("u5ModEdit-card").classList.add("d-none");
}
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 {
document.getElementById("modularParts-card").classList.add("d-none");
document.getElementById("modularParts-form").innerHTML = "";
}
} else {
single.loadRoute("/webui/inventory");
}
}
let guild_data;
// Assumes that caller revalidates authz
function getGuildData() {
return new Promise(resolve => {
if (guild_data) {
resolve(guild_data);
} else {
$.get("/custom/getGuild?" + window.authz).done(guildData => {
guild_data = guildData;
window.guildId = guildData?._id ?? null;
if (window.subscribedToGuildId != guildId) {
window.subscribedToGuildId = guildId;
ws.send(JSON.stringify({ guildId }));
}
resolve(guild_data);
});
}
});
}
function guildView_clear() {
document.getElementById("guildView-title").textContent = "";
document.getElementById("guildView-tier").textContent = "";
document.getElementById("guildView-class").textContent = "";
document.getElementById("vaultRegularCredits-form").classList.add("d-none");
document.getElementById("vaultPremiumCredits-form").classList.add("d-none");
document.getElementById("VaultRegularCredits-owned").classList.add("mb-0");
document.getElementById("VaultPremiumCredits-owned").classList.add("mb-0");
document.getElementById("TechProjects-list").innerHTML = "";
document.getElementById("techProjects-form").classList.add("d-none");
document.getElementById("acquire-type-TechProjects").value = "";
document.getElementById("VaultDecoRecipes-list").innerHTML = "";
document.getElementById("vaultDecoRecipes-form").classList.add("d-none");
document.getElementById("acquire-type-VaultDecoRecipes").value = "";
document.getElementById("VaultMiscItems-list").innerHTML = "";
document.getElementById("vaultMiscItems-form").classList.add("d-none");
document.getElementById("acquire-type-VaultMiscItems").value = "";
document.getElementById("VaultShipDecorations-list").innerHTML = "";
document.getElementById("vaultShipDecorations-form").classList.add("d-none");
document.getElementById("acquire-type-VaultShipDecorations").value = "";
document.getElementById("Alliance-list").innerHTML = "";
document.getElementById("guildView-alliance").textContent = "";
document.getElementById("Members-list").innerHTML = "";
$("#guild-route > .row").addClass("d-none");
}
single.getRoute("#guild-route").on("beforeload", function () {
if (guild_data === undefined) {
document.getElementById("guildView-loading").classList.remove("d-none");
document.getElementById("guildView-na").classList.add("d-none");
document.getElementById("guildView-naDescription").classList.add("d-none");
guildView_clear();
}
awaitAuthz().then(() => {
getGuildData().then(guildData => {
if (!guildData) {
document.getElementById("guildView-loading").classList.add("d-none");
document.getElementById("guildView-na").classList.remove("d-none");
document.getElementById("guildView-naDescription").classList.remove("d-none");
guildView_clear();
return;
}
window.itemListPromise.then(itemMap => {
document.getElementById("TechProjects-bulkAdd").classList.add("d-none");
document.getElementById("TechProjects-bulkFund").classList.add("d-none");
document.getElementById("TechProjects-bulkComplete").classList.add("d-none");
document.getElementById("TechProjects-bulkRemove").classList.add("d-none");
document.getElementById("VaultDecoRecipes-bulkAdd").classList.add("d-none");
document.getElementById("VaultDecoRecipes-bulkRemove").classList.add("d-none");
document.getElementById("VaultShipDecorations-bulkRemove").classList.add("d-none");
document.getElementById("VaultShipDecorations-bulkAdd").classList.add("d-none");
document.getElementById("VaultShipDecorations-bulkRemove").classList.add("d-none");
document.getElementById("VaultMiscItems-bulkRemove").classList.add("d-none");
document.getElementById("guildView-loading").classList.add("d-none");
$("#guild-route > .row").removeClass("d-none");
document.getElementById("guildView-title").textContent = guildData.Name;
document.getElementById("guildView-tier").textContent = normalizeText(
itemMap["/Lotus/Language/Clan/Clan_TierDisplay"].name.replaceAll(
"|TIER|",
itemMap[`/Lotus/Language/Clan/Clan_Tier${guildData.Tier}`].name
)
);
document.getElementById("guildView-class").textContent = itemMap[
"/Lotus/Language/Clan/Clan_ClassLower"
].name.replaceAll("|CLASS|", guildData.Class);
["VaultRegularCredits", "VaultPremiumCredits"].forEach(currency => {
document.getElementById(currency + "-owned").textContent = loc(
"guildView_currency_owned"
).replaceAll("|COUNT|", (guildData[currency] ?? 0).toLocaleString());
});
const userGuildMember = guildData.Members.find(m => m._id.$oid === window.accountId);
let userGuildPermissions;
if (userGuildMember) {
userGuildPermissions = guildData.Ranks[userGuildMember.Rank].Permissions;
// Ruler = 1, // Clan: Change hierarchy. Alliance (Creator only): Kick clans.
// Advertiser = 8192,
// Recruiter = 2, // Send invites (Clans & Alliances)
// Regulator = 4, // Kick members
// Promoter = 8, // Clan: Promote and demote members. Alliance (Creator only): Change clan permissions.
// Architect = 16, // Create and destroy rooms
// Host = 32, // No longer used in modern versions
// Decorator = 1024, // Create and destroy decos
// Treasurer = 64, // Clan: Contribute from vault and edit tax rate. Alliance: Divvy vault.
// Tech = 128, // Queue research
// ChatModerator = 512, // (Clans & Alliances)
// Herald = 2048, // Change MOTD
// Fabricator = 4096 // Replicate research
if (userGuildPermissions & 128) {
document.getElementById("techProjects-form").classList.remove("d-none");
}
if (userGuildPermissions & 16) {
document.getElementById("vaultDecoRecipes-form").classList.remove("d-none");
}
if (userGuildPermissions & 64) {
document.getElementById("vaultRegularCredits-form").classList.remove("d-none");
document.getElementById("VaultRegularCredits-owned").classList.remove("mb-0");
document.getElementById("vaultPremiumCredits-form").classList.remove("d-none");
document.getElementById("VaultPremiumCredits-owned").classList.remove("mb-0");
document.getElementById("vaultMiscItems-form").classList.remove("d-none");
document.getElementById("vaultShipDecorations-form").classList.remove("d-none");
}
if (userGuildMember.Rank <= 1) {
document.getElementById("TechProjects-bulkAdd").classList.remove("d-none");
document.getElementById("TechProjects-bulkFund").classList.remove("d-none");
document.getElementById("TechProjects-bulkComplete").classList.remove("d-none");
document.getElementById("TechProjects-bulkRemove").classList.remove("d-none");
document.getElementById("VaultDecoRecipes-bulkAdd").classList.remove("d-none");
document.getElementById("VaultDecoRecipes-bulkRemove").classList.remove("d-none");
document.getElementById("VaultShipDecorations-bulkRemove").classList.remove("d-none");
document.getElementById("VaultShipDecorations-bulkAdd").classList.remove("d-none");
document.getElementById("VaultShipDecorations-bulkRemove").classList.remove("d-none");
document.getElementById("VaultMiscItems-bulkRemove").classList.remove("d-none");
}
}
const guildCheats = document.querySelectorAll("#guild-cheats input[id]");
for (const elm of guildCheats) {
elm.checked = !!guildData[elm.id];
if (!userGuildMember || userGuildMember.Rank > 1) {
elm.disabled = true;
} else {
elm.disabled = false;
}
}
document.getElementById("TechProjects-list").innerHTML = "";
guildData.TechProjects ??= [];
guildData.TechProjects.forEach(item => {
const tr = document.createElement("tr");
tr.setAttribute("data-item-type", item.ItemType);
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
if (new Date(item.CompletionDate) < new Date()) {
td.textContent += " | " + loc("code_completed");
} else if (item.State == 1) {
td.textContent += " | " + loc("code_funded");
}
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
if (userGuildPermissions && userGuildPermissions & 128 && item.State != 1) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(fundGuildTechProject, item.ItemType);
};
a.title = loc("code_fund");
a.innerHTML = icons.arrowUp;
td.appendChild(a);
}
if (
userGuildPermissions &&
userGuildPermissions & 128 &&
item.State == 1 &&
new Date(item.CompletionDate) > new Date()
) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(completeGuildTechProject, item.ItemType);
};
a.title = loc("code_complete");
a.innerHTML = icons.forward;
td.appendChild(a);
}
if (userGuildMember && userGuildMember.Rank <= 1) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
debounce(removeGuildTechProject, item.ItemType);
};
a.title = loc("code_remove");
a.innerHTML = icons.trash;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("TechProjects-list").appendChild(tr);
});
["VaultDecoRecipes", "VaultMiscItems", "VaultShipDecorations"].forEach(vaultKey => {
document.getElementById(vaultKey + "-list").innerHTML = "";
(guildData[vaultKey] ??= []).forEach(item => {
const tr = document.createElement("tr");
tr.setAttribute("data-item-type", item.ItemType);
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
if (item.ItemCount > 1) {
td.innerHTML += ` <span title='${loc("code_count")}'>🗍 ${parseInt(item.ItemCount)}</span>`;
}
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
const canRemove =
vaultKey === "VaultDecoRecipes"
? userGuildMember && userGuildMember.Rank <= 1
: userGuildPermissions && userGuildPermissions & 64;
if (canRemove) {
const a = document.createElement("a");
a.href = "#";
a.title = loc("code_remove");
a.onclick = e => {
e.preventDefault();
removeVaultItem(vaultKey, item.ItemType, item.ItemCount * -1);
};
a.innerHTML = icons.trash;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById(vaultKey + "-list").appendChild(tr);
});
});
document.getElementById("Members-list").innerHTML = "";
guildData.Members.forEach(member => {
const tr = document.createElement("tr");
{
const td = document.createElement("td");
const memberRank = guildData.Ranks[member.Rank];
td.textContent = member.DisplayName;
td.textContent += " | " + itemMap[memberRank.Name]?.name ?? memberRank.Name;
if (member.Status != 0) {
td.textContent += " | " + loc("guildView_pending");
}
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
if (
userGuildMember &&
member.Rank > userGuildMember.Rank &&
userGuildPermissions &&
userGuildPermissions & 8
) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
changeGuildRank(guildId, member._id.$oid, member.Rank - 1);
};
a.title = itemMap["/Lotus/Language/Menu/SocialOverlay_Promote"].name;
a.innerHTML = icons.angleUp;
td.appendChild(a);
}
if (
userGuildMember &&
member.Rank < 8 &&
member.Rank > userGuildMember.Rank &&
userGuildPermissions &&
userGuildPermissions & 8
) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
changeGuildRank(guildId, member._id.$oid, member.Rank + 1);
};
a.title = itemMap["/Lotus/Language/Menu/SocialOverlay_Demote"].name;
a.innerHTML = icons.angleDown;
td.appendChild(a);
}
if (
(userGuildMember &&
member.Rank > userGuildMember.Rank &&
userGuildPermissions &&
userGuildPermissions & 4) ||
(userGuildMember && userGuildMember.Rank != 0 && userGuildMember._id == member._id)
) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
kickFromGuild(member._id.$oid);
};
a.title = loc("code_remove");
a.innerHTML = icons.trash;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("Members-list").appendChild(tr);
});
if (guildData.AllianceId) {
const allianceReq = $.get("/custom/getAlliance?guildId=" + guildId);
allianceReq.done(allianceData => {
document.getElementById("guildView-alliance").textContent =
loc("guildView_alliance") + ": " + allianceData.Name;
let userAlliancePermisssions;
if (userGuildMember && userGuildMember.Rank <= 1) {
userAlliancePermisssions = allianceData.Clans.find(c => c._id.$oid === guildId).Permissions;
}
document.getElementById("Alliance-list").innerHTML = "";
allianceData.Clans.forEach(clan => {
const tr = document.createElement("tr");
{
const td = document.createElement("td");
td.textContent = clan.Name;
if (clan.Pending) {
td.textContent +=
" | " +
normalizeText(itemMap["/Lotus/Language/Menu/SocialOverlay_PendingLabel"].name);
}
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
if (
!(clan.Permissions & 1) &&
userAlliancePermisssions &&
userAlliancePermisssions & 1
) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
kickFromAlliance(clan._id.$oid);
};
a.title = loc("code_remove");
a.innerHTML = icons.trash;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("Alliance-list").appendChild(tr);
});
});
}
});
});
});
});
function doPushArchonCrystalUpgrade() {
const urlParams = new URLSearchParams(window.location.search);
const uniqueName = getKey(document.querySelector("[list='datalist-archonCrystalUpgrades']"));
if (!uniqueName) {
$("[list='datalist-archonCrystalUpgrades']").addClass("is-invalid").focus();
return;
}
revalidateAuthz().then(() => {
$.get(
`/custom/pushArchonCrystalUpgrade?${window.authz}&oid=${urlParams.get("itemId")}&type=${uniqueName}&count=${$("#archon-crystal-add-count").val()}`
).done(function (updatedSuit) {
$("[list='datalist-archonCrystalUpgrades']").val("");
const index = inventory_data.Suits.findIndex(x => x.ItemId.$oid == urlParams.get("itemId"));
inventory_data.Suits[index] = updatedSuit;
populateDetailedViewRoute();
});
});
}
function doPopArchonCrystalUpgrade(type) {
const urlParams = new URLSearchParams(window.location.search);
revalidateAuthz().then(() => {
$.get(
"/custom/popArchonCrystalUpgrade?" + window.authz + "&oid=" + urlParams.get("itemId") + "&type=" + type
).done(function (updatedSuit) {
const index = inventory_data.Suits.findIndex(x => x.ItemId.$oid == urlParams.get("itemId"));
inventory_data.Suits[index] = updatedSuit;
populateDetailedViewRoute();
});
});
}
function doImport() {
revalidateAuthz().then(() => {
try {
$.post({
url: "/custom/import?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
inventory: JSON.parse($("#import-inventory").val())
})
}).then(function (err) {
if (err) {
toast(err == "noKnownKey" ? loc("code_nothingToDo") : err);
} else {
toast(loc("code_succImport"));
}
updateInventory();
});
} catch (e) {
toast(e);
console.error(e);
}
});
}
function doChangeSupportedSyndicate() {
const uniqueName = document.getElementById("changeSyndicate").value;
revalidateAuthz().then(() => {
$.get("/api/setSupportedSyndicate.php?" + window.authz + "&syndicate=" + uniqueName).done(function () {
inventory_data.SupportedSyndicate = uniqueName;
});
});
}
function doAddCurrency(currency) {
revalidateAuthz().then(() => {
$.post({
url: "/custom/addCurrency?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/json",
data: JSON.stringify({
currency,
delta: document.getElementById(currency + "-delta").valueAsNumber
})
}).then(function (newValue) {
if (currency.startsWith("Vault")) {
document.getElementById(currency + "-owned").textContent = loc("guildView_currency_owned").replaceAll(
"|COUNT|",
(newValue ?? 0).toLocaleString()
);
} else {
inventory_data[currency] = newValue;
document.getElementById(currency + "-owned").textContent = loc("currency_owned").replaceAll(
"|COUNT|",
(newValue ?? 0).toLocaleString()
);
}
});
});
}
async function doQuestUpdate(operation, itemType) {
await revalidateAuthz();
await new Promise(resolve => {
$.post({
url: "/custom/manageQuests?" + window.authz + "&operation=" + operation + "&itemType=" + itemType,
contentType: "application/json"
})
.then(function (didAnything) {
if (didAnything) {
updateInventory();
} else {
toast(loc("code_nothingToDo"));
}
})
.always(() => {
resolve();
});
});
}
async function doBulkQuestUpdate(operation) {
await revalidateAuthz();
await new Promise(resolve => {
$.post({
url: "/custom/manageQuests?" + window.authz + "&operation=" + operation,
contentType: "application/json"
})
.then(function (didAnything) {
if (didAnything) {
updateInventory();
} else {
toast(loc("code_nothingToDo"));
}
})
.always(() => {
resolve();
});
});
}
function toast(text, type = "primary") {
const toast = document.createElement("div");
toast.className = `toast align-items-center text-bg-${type} border-0`;
const div = document.createElement("div");
div.className = "d-flex";
const body = document.createElement("div");
body.className = "toast-body";
body.textContent = text;
div.appendChild(body);
const button = document.createElement("button");
button.className = "btn-close btn-close-white me-2 m-auto";
button.setAttribute("data-bs-dismiss", "toast");
div.appendChild(button);
toast.appendChild(div);
new bootstrap.Toast(document.querySelector(".toast-container").appendChild(toast)).show();
}
function handleModularSelection(category) {
const itemType = getKey(document.getElementById("acquire-type-" + category));
if (webUiModularWeapons.includes(itemType)) {
doAcquireModularEquipment(category, itemType);
} else {
doAcquireEquipment(category);
}
}
{
const supportedModularInventoryCategory = ["OperatorAmps", "Melee", "LongGuns", "Pistols", "MoaPets", "KubrowPets"];
supportedModularInventoryCategory.forEach(inventoryCategory => {
document.getElementById("acquire-type-" + inventoryCategory).addEventListener("input", function () {
const modularFields = document.getElementById("modular-" + inventoryCategory);
const modularFieldsMoa = document.getElementById("modular-MoaPets-Moa");
const modularFieldsZanuka = document.getElementById("modular-MoaPets-Zanuka");
const modularFieldsCatbrow = document.getElementById("modular-KubrowPets-Catbrow");
const modularFieldsKubrow = document.getElementById("modular-KubrowPets-Kubrow");
const key = getKey(this);
if (webUiModularWeapons.includes(key)) {
if (inventoryCategory === "MoaPets") {
if (key === "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetPowerSuit") {
modularFieldsMoa.classList.add("d-none");
modularFieldsZanuka.classList.remove("d-none");
} else if (key === "/Lotus/Types/Friendly/Pets/MoaPets/MoaPetPowerSuit") {
modularFieldsMoa.classList.remove("d-none");
modularFieldsZanuka.classList.add("d-none");
}
} else if (inventoryCategory === "KubrowPets") {
if (key.endsWith("InfestedCatbrowPetPowerSuit")) {
modularFieldsCatbrow.classList.remove("d-none");
modularFieldsKubrow.classList.add("d-none");
} else if (key.endsWith("PredatorKubrowPetPowerSuit")) {
modularFieldsCatbrow.classList.add("d-none");
modularFieldsKubrow.classList.remove("d-none");
} else {
modularFieldsCatbrow.classList.add("d-none");
modularFieldsKubrow.classList.add("d-none");
}
} else {
modularFields.classList.remove("d-none");
}
} else {
if (inventoryCategory === "MoaPets") {
modularFieldsMoa.classList.add("d-none");
modularFieldsZanuka.classList.add("d-none");
} else if (inventoryCategory === "KubrowPets") {
modularFieldsCatbrow.classList.add("d-none");
modularFieldsKubrow.classList.add("d-none");
} else {
modularFields.classList.add("d-none");
}
}
});
});
}
function setBooster(ItemType, ExpiryDate) {
revalidateAuthz().then(() => {
$.post({
url: "/custom/setBooster?" + window.authz,
contentType: "application/json",
data: JSON.stringify([
{
ItemType,
ExpiryDate
}
])
}).done(function (newBoosters) {
inventory_data.Boosters = newBoosters;
populateInventoryRoute();
});
});
}
function doAcquireBooster() {
const uniqueName = getKey(document.getElementById("acquire-type-Boosters"));
if (!uniqueName) {
$("#acquire-type-Boosters").addClass("is-invalid").focus();
return;
}
setBooster(uniqueName, Math.floor(Date.now() / 1000 + 3 * 24 * 60 * 60));
document.getElementById("acquire-type-Boosters").value = "";
}
function doChangeBoosterExpiry(ItemType, ExpiryDateInput) {
const ExpiryDate = Math.floor(new Date(ExpiryDateInput.value).getTime() / 1000);
if (isNaN(ExpiryDate)) {
ExpiryDateInput.addClass("is-invalid").focus();
return;
}
setBooster(ItemType, ExpiryDate);
}
function formatDatetime(fmt, date) {
if (typeof date === "number") date = new Date(date);
return fmt.replace(/(%[yY]|%m|%[Dd]|%H|%h|%M|%[Ss]|%[Pp])/g, match => {
switch (match) {
case "%Y":
return date.getFullYear().toString();
case "%y":
return date.getFullYear().toString().slice(-2);
case "%m":
return (date.getMonth() + 1).toString().padStart(2, "0");
case "%D":
case "%d":
return date.getDate().toString().padStart(2, "0");
case "%H":
return date.getHours().toString().padStart(2, "0");
case "%h":
return (date.getHours() % 12).toString().padStart(2, "0");
case "%M":
return date.getMinutes().toString().padStart(2, "0");
case "%S":
case "%s":
return date.getSeconds().toString().padStart(2, "0");
case "%P":
case "%p":
return date.getHours() < 12 ? "am" : "pm";
default:
return match;
}
});
}
const calls_in_flight = new Set();
async function debounce(func, ...args) {
if (!func.name) {
throw new Error(`cannot debounce anonymous functions`);
}
const callid = JSON.stringify({ func: func.name, args });
if (!calls_in_flight.has(callid)) {
calls_in_flight.add(callid);
await func(...args);
calls_in_flight.delete(callid);
} else {
console.log("debouncing", callid);
}
}
async function doMaxPlexus() {
if ((window.plexus?.xp ?? 0) < 900_000) {
await addGearExp("CrewShipHarnesses", window.plexus.id, 900_000 - window.plexus.xp);
window.plexus.xp = 900_000;
toast(loc("code_succRankUp"));
} else {
toast(loc("code_noEquipmentToRankUp"));
}
}
async function doUnlockAllScans() {
await revalidateAuthz();
await fetch("/custom/unlockAllScans?" + window.authz);
toast(loc("cheats_unlockSuccRelog"));
}
async function doUnlockAllShipFeatures() {
await revalidateAuthz();
await fetch("/custom/unlockAllShipFeatures?" + window.authz);
toast(loc(window.have_game_ws ? "code_succAdded" : "cheats_unlockSuccInventory"));
}
async function doUnlockAllCapturaScenes() {
await revalidateAuthz();
await fetch("/custom/unlockAllCapturaScenes?" + window.authz);
toast(loc(window.have_game_ws ? "code_succAdded" : "cheats_unlockSuccInventory"));
}
async function unlockAllMissions() {
await revalidateAuthz();
await fetch("/custom/completeAllMissions?" + window.authz);
toast(loc("cheats_unlockAllMissions_ok"));
}
async function unlockAllJobChainBounties() {
await revalidateAuthz();
await fetch("/custom/unlockAllJobChainBounties?" + window.authz);
toast(loc(window.have_game_ws ? "code_succAdded" : "cheats_unlockSuccInventory"));
}
async function unlockAllSimarisResearchEntries() {
await revalidateAuthz();
await fetch("/custom/unlockAllSimarisResearchEntries?" + window.authz);
toast(loc(window.have_game_ws ? "code_succAdded" : "cheats_unlockSuccInventory"));
}
function setImportSample(key) {
$("#import-inventory").val(JSON.stringify(importSamples[key], null, 2));
}
document.querySelectorAll(".tags-input").forEach(input => {
const datalist = document.getElementById(input.getAttribute("list"));
let shadowDatalist;
const optionsAreUnique = input.classList.contains("tags-input-unique");
input.oninput = function () {
const options = [...datalist.querySelectorAll("option")].map(x => x.textContent);
if (!shadowDatalist) {
shadowDatalist = document.createElement("datalist");
shadowDatalist.setAttribute("id", "shadow-" + datalist.id);
input.setAttribute("list", "shadow-" + datalist.id);
shadowDatalist = document.documentElement.appendChild(shadowDatalist);
}
const value = [];
for (const tag of this.value.split(",")) {
const index = options.map(x => x.toLowerCase()).indexOf(tag.trim().toLowerCase());
if (index != -1) {
value.push(options[index]);
}
}
this.setAttribute("data-tags-value", value.join(", "));
if (value[0] == "all") {
shadowDatalist.innerHTML = "<option>all</option>";
} else {
shadowDatalist.innerHTML = "";
for (const option of options) {
if (!optionsAreUnique || value.indexOf(option) == -1) {
const elm = document.createElement("option");
if (option == "all") {
elm.textContent = "all";
} else {
elm.textContent = [...value, option, ""].join(", ");
}
shadowDatalist.appendChild(elm);
}
}
}
};
input.oninput();
});
function fromUpgradeFingerprintValue(raw, min) {
const range = 0.6 - min;
return min + (raw * range) / 0x3fffffff;
}
function toUpgradeFingerprintValue(value, min) {
const range = 0.6 - min;
return Math.trunc(((value - min) * 0x3fffffff) / range);
}
function fromU5UpgradeFingerprintValue(raw, min, max) {
const range = max - min;
return min + range * raw;
}
function toU5UpgradeFingerprintValue(value, min, max) {
const range = max - min;
return (value - min) / range;
}
function handleValenceBonusChange(event) {
event.preventDefault();
const urlParams = new URLSearchParams(window.location.search);
const action = event.submitter.value;
const Tag = document.getElementById("valenceBonus-innateDamage").value;
const Value = toUpgradeFingerprintValue(document.getElementById("valenceBonus-procent").value / 100, 0.25);
revalidateAuthz().then(() => {
$.post({
url: "/custom/updateFingerprint?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
category: urlParams.get("productCategory"),
oid: urlParams.get("itemId"),
action,
upgradeType: "/Lotus/Weapons/Grineer/KuvaLich/Upgrades/InnateDamageRandomMod",
upgradeFingerprint: {
buffs: [
{
Tag,
Value
}
]
}
})
}).done(function () {
inventory_data = undefined; // UI is up-to-date now, but subsequent updates will need to grab new data
});
});
}
function handleU5ModEdit(event) {
event.preventDefault();
const urlParams = new URLSearchParams(window.location.search);
const action = event.submitter.value;
revalidateAuthz().then(() => {
const upgradeFields = document.getElementById("u5ModEdit-upgradeFields");
const upgradeGroups = Array.from(upgradeFields.children);
const upgrades = upgradeGroups
.map(group => {
const upgradeSelect = group.querySelector("#u5ModEdit-upgradeType");
const raritySelect = group.querySelector("#u5ModEdit-upgradeRarity");
const valueInput = group.querySelector("#u5ModEdit-upgradeValue");
const statAtten = parseFloat(group.parentNode.getAttribute("data-stat-atten"));
const value =
valueInput.valueAsNumber == valueInput.min
? valueInput.valueAsNumber + 0.001
: valueInput.valueAsNumber;
return {
upgrade: upgradeSelect.value,
valueRarity: raritySelect.value,
value: Math.min(
toU5UpgradeFingerprintValue(
value / statAtten,
valueInput.min / statAtten,
valueInput.max / statAtten
),
1
)
};
})
.filter(item => {
return item.upgrade != "" && item.value != 0;
});
$.post({
url: "/custom/updateFingerprint?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
category: urlParams.get("productCategory"),
oid: urlParams.get("itemId"),
action,
upgradeFingerprint: {
reqLevel: Number(document.getElementById("u5ModEdit-reqLevel").value),
fits: document.getElementById("u5ModEdit-fits").value,
upgrades
}
})
}).done(function () {
toast(loc("code_succChange"));
updateInventory();
});
});
}
function createUpgradeInput(targetDiv, itemMap, itemType, upgrade) {
const meta = itemMap[itemType];
const statAtten = targetDiv.getAttribute("data-stat-atten") || 1;
const group = document.createElement("div");
group.classList.add("upgrade-group", "mb-2", "d-flex", "gap-2");
{
const select = document.createElement("select");
select.classList.add("form-control");
select.id = "u5ModEdit-upgradeType";
const optionNone = document.createElement("option");
select.appendChild(optionNone);
meta.upgrades.forEach(up => {
const option = document.createElement("option");
option.value = up.type;
const locType =
(itemType.endsWith("GrineerMeleeModule") || itemType.endsWith("TennoSwordModule")) &&
up.type == "WEAPON_FIRE_RATE"
? "WEAPON_MELEE_FIRE_RATE"
: up.type;
if (
[
"AVATAR_ABILITY_DURATION",
"AVATAR_ABILITY_EFFICIENCY",
"AVATAR_ABILITY_RANGE",
"AVATAR_ABILITY_STRENGTH",
"WEAPON_ARMOR_PIERCING_DAMAGE",
"WEAPON_MELEE_CHARGE_RATE",
"WEAPON_MELEE_HEAVY_DAMAGE",
"WEAPON_STUN_CHANCE"
].includes(locType)
) {
option.textContent = loc(locType);
} else {
option.textContent = toTitleCase(itemMap["/Lotus/Language/Labels/" + locType]?.name) ?? locType;
}
select.appendChild(option);
});
if (upgrade) {
select.value = upgrade.upgrade;
}
select.addEventListener("change", () => {
updateUpgradeInput(meta, group);
checkAddOrDeleteUpgradeInput(itemMap, itemType);
});
group.appendChild(select);
}
{
const select = document.createElement("select");
select.classList.add("form-control");
select.id = "u5ModEdit-upgradeRarity";
["COMMON", "UNCOMMON", "RARE"].forEach(rarity => {
const option = document.createElement("option");
option.value = rarity;
const locKey = "code_rarity" + rarity.charAt(0) + rarity.slice(1).toLowerCase();
option.textContent = loc(locKey);
option.setAttribute("data-loc", locKey);
select.appendChild(option);
});
if (upgrade) {
select.value = upgrade.valueRarity ?? "COMMON";
} else {
select.value = "COMMON";
}
select.addEventListener("change", () => updateUpgradeInput(meta, group));
group.appendChild(select);
}
{
let valueRange = [0, 0];
let upgradeValue = 0;
let displayAsPercent = undefined;
if (upgrade) {
// U5.1 client just igrone Rare value ranges
const targetValueRarity = upgrade.valueRarity == "RARE" ? "UNCOMMON" : upgrade.valueRarity;
const upgradeMeta = meta.upgrades.find(up => up.type == upgrade.upgrade);
valueRange = [...(upgradeMeta.valueRarity[targetValueRarity] || [0, 0])];
const operation = upgradeMeta.operation;
displayAsPercent = upgradeMeta.displayAsPercent || operation == "MULTIPLY";
if (displayAsPercent) {
valueRange[0] = valueRange[0] * 100 - (operation == "MULTIPLY" ? 100 : 0);
valueRange[1] = valueRange[1] * 100 - (operation == "MULTIPLY" ? 100 : 0);
}
upgradeValue = +(
fromU5UpgradeFingerprintValue(upgrade.value, valueRange[0], valueRange[1]) * statAtten
).toFixed(1);
}
const inputGroup = document.createElement("div");
inputGroup.classList.add("input-group", "d-flex", "flex-shrink-0");
inputGroup.style.flexBasis = "200px";
const input = document.createElement("input");
input.type = "number";
input.classList.add("form-control");
input.step = "0.1";
input.id = "u5ModEdit-upgradeValue";
input.min = valueRange[0] ? +(valueRange[0] * statAtten).toFixed(1) : 0;
input.max = valueRange[1] ? +(valueRange[1] * statAtten).toFixed(1) : 0;
input.value = upgradeValue;
input.title = loc("code_range").replace("|MIN|", input.min).replace("|MAX|", input.max);
const rangePrefix = document.createElement("span");
rangePrefix.textContent = "+";
rangePrefix.classList.add("input-group-text");
const rangeSuffix = document.createElement("span");
rangeSuffix.classList.add("input-group-text");
rangeSuffix.id = "u5ModEdit-upgradeValueSuffix";
rangeSuffix.textContent = displayAsPercent ? "%" : itemMap["/Lotus/Language/UiElements/PLACEMENT_METERS"].name;
inputGroup.appendChild(rangePrefix);
inputGroup.appendChild(input);
if (displayAsPercent != undefined) {
inputGroup.appendChild(rangeSuffix);
}
group.appendChild(inputGroup);
}
targetDiv.appendChild(group);
}
function updateUpgradeInput(meta, group) {
const fitsSelect = document.getElementById("u5ModEdit-fits");
const fit = meta.fits.find(f => f.type == fitsSelect.value);
const statAtten = fit?.statAtten || 1;
const upgradeSelect = group.querySelector("#u5ModEdit-upgradeType");
const raritySelect = group.querySelector("#u5ModEdit-upgradeRarity");
const input = group.querySelector("#u5ModEdit-upgradeValue");
let rangeSuffix = group.querySelector("#u5ModEdit-upgradeValueSuffix");
const upgradeMeta = meta.upgrades.find(up => up.type == upgradeSelect.value);
const targetValueRarity = raritySelect.value == "RARE" ? "UNCOMMON" : raritySelect.value;
let valueRange = [...(upgradeMeta?.valueRarity[targetValueRarity] || [0, 0])];
const operation = upgradeMeta?.operation;
const displayAsPercent = upgradeMeta?.displayAsPercent || operation == "MULTIPLY";
if (displayAsPercent) {
valueRange[0] = valueRange[0] * 100 - (operation == "MULTIPLY" ? 100 : 0);
valueRange[1] = valueRange[1] * 100 - (operation == "MULTIPLY" ? 100 : 0);
}
if (upgradeMeta && !rangeSuffix) {
rangeSuffix = document.createElement("span");
rangeSuffix.classList.add("input-group-text");
rangeSuffix.id = "u5ModEdit-upgradeValueSuffix";
input.parentNode.appendChild(rangeSuffix);
}
if (rangeSuffix) rangeSuffix.textContent = displayAsPercent ? "%" : "m";
const oldstatAtten = parseFloat(group.parentNode.getAttribute("data-stat-atten")) || 1;
const oldUpgradeValue = toU5UpgradeFingerprintValue(
input.valueAsNumber / oldstatAtten,
input.min / oldstatAtten,
input.max / oldstatAtten
);
input.min = valueRange[0] ? +(valueRange[0] * statAtten).toFixed(1) : 0;
input.max = valueRange[1] ? +(valueRange[1] * statAtten).toFixed(1) : 0;
input.title = loc("code_range").replace("|MIN|", input.min).replace("|MAX|", input.max);
group.parentNode.setAttribute("data-stat-atten", statAtten);
input.value =
+(fromU5UpgradeFingerprintValue(oldUpgradeValue, valueRange[0], valueRange[1]) * statAtten).toFixed(1) ||
input.max;
if (input.value < input.min) input.value = input.min;
}
function checkAddOrDeleteUpgradeInput(itemMap, itemType) {
const upgradeFields = document.getElementById("u5ModEdit-upgradeFields");
const upgradeGroups = Array.from(upgradeFields.children);
upgradeGroups.forEach((group, index) => {
const upgradeSelect = group.querySelector("#u5ModEdit-upgradeType");
if (!upgradeSelect.value && index !== upgradeGroups.length - 1) {
upgradeFields.removeChild(group);
}
});
const lastUpgradeSelect = upgradeGroups[upgradeGroups.length - 1].querySelector("#u5ModEdit-upgradeType");
if (lastUpgradeSelect.value) {
createUpgradeInput(upgradeFields, itemMap, itemType, null);
}
}
document.querySelectorAll("#sidebar .nav-link").forEach(function (elm) {
elm.addEventListener("click", function () {
window.scrollTo(0, 0);
});
});
async function markAllAsRead() {
await revalidateAuthz();
const { Inbox } = await fetch("/api/inbox.php?" + window.authz).then(x => x.json());
let any = false;
for (const msg of Inbox) {
if (!msg.r) {
await fetch("/api/inbox.php?" + window.authz + "&messageId=" + msg.messageId.$oid);
any = true;
}
}
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"));
inventory_data = undefined; // UI is up-to-date now, but subsequent updates will need to grab new data
});
});
}
}
function submitInvigoration(event) {
event.preventDefault();
const OffensiveUpgrade = document.getElementById("invigoration-offensive").value;
const DefensiveUpgrade = document.getElementById("invigoration-defensive").value;
const expiry = document.getElementById("invigoration-expiry").value;
if (!OffensiveUpgrade || !DefensiveUpgrade) {
toast(loc("code_requiredInvigorationUpgrade"), "warning");
return;
}
setInvigoration({
OffensiveUpgrade,
DefensiveUpgrade,
UpgradesExpiry: expiry ? new Date(expiry).getTime() : Date.now() + 7 * 24 * 60 * 60 * 1000
});
}
function setInvigoration(data) {
const oid = new URLSearchParams(window.location.search).get("itemId");
$.post({
url: "/custom/setInvigoration?" + window.authz,
contentType: "application/json",
data: JSON.stringify({ oid, ...data })
}).done(function (updatedSuit) {
const index = inventory_data.Suits.findIndex(x => x.ItemId.$oid == oid);
inventory_data.Suits[index] = updatedSuit;
populateDetailedViewRoute();
});
}
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 (updatedSuit) {
const index = inventory_data.Suits.findIndex(x => x.ItemId.$oid == oid);
inventory_data.Suits[index] = updatedSuit;
populateDetailedViewRoute();
});
}
function handleAbilityOverride(event, configIndex) {
event.preventDefault();
const urlParams = new URLSearchParams(window.location.search);
const action = event.submitter.value;
const Ability = getKey(document.getElementById(`abilityOverride-ability-config-${configIndex}`));
const input = document.getElementById(`abilityOverride-ability-index-config-${configIndex}`);
if (action == "set") {
if (!Ability) {
$(`#abilityOverride-ability-config-${configIndex}`).addClass("is-invalid").focus();
return;
}
if (!input.value) {
$(`#abilityOverride-ability-index-config-${configIndex}`).addClass("is-invalid").focus();
return;
}
}
const category = urlParams.get("productCategory");
const oid = urlParams.get("itemId");
const Index = input.value - 1;
revalidateAuthz().then(() => {
$.post({
url: "/custom/abilityOverride?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
category,
oid,
configIndex,
action,
AbilityOverride: {
Ability,
Index
}
})
}).done(function (updatedItem) {
const index = inventory_data[category].findIndex(x => x.ItemId.$oid == oid);
inventory_data[category][index] = updatedItem;
if (action != "set") {
populateDetailedViewRoute();
}
toast(loc("code_succChange"));
});
});
}
function removeItems(category) {
revalidateAuthz().then(() => {
const count = document.getElementById(category + "-list").getElementsByTagName("tr").length;
let message = loc("code_bulkRemoveConfirm").replaceAll("|COUNT|", count);
if (category === "Suits") message += "\n" + loc("code_bulkRemoveNote");
if (count != 0 && window.confirm(message)) {
$.get("/custom/removeItems?" + window.authz + "&category=" + category).done(function () {
updateInventory();
});
}
});
}
single.getRoute("/webui/users").on("beforeload", function () {
awaitAuthz().then(() => {
if (window.is_admin === false) {
$(".admin-hide").removeClass("d-none");
$(".admin-show").addClass("d-none");
return;
}
$.get("/custom/getRegisteredLosers?" + window.authz)
.done(users => {
window.is_admin = true;
$(".admin-hide").addClass("d-none");
$(".admin-show").removeClass("d-none");
document.getElementById("registered-losers").innerHTML = "";
document.getElementById("datalist-usernames").innerHTML = "<option>all</option>";
for (const user of users) {
const tr = document.createElement("tr");
{
const td = document.createElement("td");
td.textContent = user.DisplayName;
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.innerHTML = `<code>${user.id}</code>`;
tr.appendChild(td);
}
{
const td = document.createElement("td");
if (window.accountId != user.id) {
const a = document.createElement("a");
a.textContent = loc("admin_possess");
a.setAttribute("data-loc", "admin_possess");
a.href = "#";
a.onclick = function () {
localStorage.setItem("possessing", user.id);
doAccountSwitch("/webui/inventory");
};
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("registered-losers").appendChild(tr);
const option = document.createElement("option");
option.textContent = user.DisplayName;
document.getElementById("datalist-usernames").appendChild(option);
}
})
.fail(res => {
if (res.responseText == "Log-in expired") {
if (ws_is_open && !auth_pending) {
console.warn("Credentials invalidated but the server didn't let us know");
sendAuth();
}
revalidateAuthz().then(() => {
if (single.getCurrentPath() == "/webui/users") {
single.loadRoute("/webui/users");
}
});
} else {
window.is_admin = false;
$(".admin-hide").removeClass("d-none");
$(".admin-show").addClass("d-none");
}
});
});
});
async function doAdminBroadcastInbox() {
await revalidateAuthz();
const sender = document.getElementById("admin-message-sender").value.trim();
const subject = document.getElementById("admin-message-subject").value.trim();
const body = document.getElementById("admin-message-body").value.trim();
const icon = document.getElementById("admin-message-icon").value.trim();
const attRaw = document.getElementById("admin-message-att").value;
const countedAttRaw = document.getElementById("admin-message-counted-att").value.trim();
const highPriority = document.getElementById("admin-message-high-priority").checked;
const submitButton = document.getElementById("admin-broadcast-submit");
if (!sender || !subject || !body) {
if (!sender) {
document.getElementById("admin-message-sender").classList.add("is-invalid");
}
if (!subject) {
document.getElementById("admin-message-subject").classList.add("is-invalid");
}
if (!body) {
document.getElementById("admin-message-body").classList.add("is-invalid");
}
return;
}
let countedAttachments;
if (countedAttRaw) {
try {
countedAttachments = JSON.parse(countedAttRaw);
} catch (_) {}
if (!Array.isArray(countedAttachments)) {
document.getElementById("admin-message-counted-att").classList.add("is-invalid");
document.getElementById("admin-message-counted-att").focus();
return;
}
}
const attachments = attRaw
.split("\n")
.map(x => x.trim())
.filter(x => x);
submitButton.disabled = true;
try {
const users = await $.get("/custom/getRegisteredLosers?" + window.authz);
document.getElementById("admin-message-target").oninput();
const targets = document
.getElementById("admin-message-target")
.getAttribute("data-tags-value")
.split(", ")
.filter(x => x);
let targetIds;
if (targets[0] == "all") {
targetIds = users.map(user => user.id);
} else {
targetIds = users.filter(user => targets.indexOf(user.DisplayName) != -1).map(user => user.id);
}
if (targetIds.length == 0) {
toast(loc("code_nothingToDo"));
return;
}
if (!window.confirm(loc("admin_sendConfirm").replaceAll("|COUNT|", String(targetIds.length)))) {
return;
}
const messages = [
{
sndr: sender,
sub: subject,
msg: body,
highPriority: highPriority
}
];
if (icon) {
messages[0].icon = icon;
}
if (attachments.length > 0) {
messages[0].att = attachments;
}
if (countedAttachments && countedAttachments.length > 0) {
messages[0].countedAtt = countedAttachments;
}
await $.ajax({
url: "/custom/createMessage?" + window.authz,
method: "POST",
contentType: "application/json",
data: JSON.stringify({ targetIds, messages })
});
toast(loc("admin_sendToAllSuccess").replaceAll("|COUNT|", targetIds.length));
} catch (error) {
alert(error);
} finally {
submitButton.disabled = false;
}
}
function normalizeText(str) {
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
}
function toTitleCase(srt) {
return srt
.replace(/[_-]+/g, " ")
.trim()
.toLocaleLowerCase()
.replace(/(^|[\s])\p{L}/gu, match => match.toLocaleUpperCase());
}