From 060f65900fdd9adc195db8e45b7ec51553d6d8d5 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 6 Oct 2025 22:54:22 -0700 Subject: [PATCH 01/11] fix: transform inventoryResponse.GuildId for older versions (#2852) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2852 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/api/inventoryController.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/controllers/api/inventoryController.ts b/src/controllers/api/inventoryController.ts index 76484e6f..f5b2b2b3 100644 --- a/src/controllers/api/inventoryController.ts +++ b/src/controllers/api/inventoryController.ts @@ -461,6 +461,9 @@ export const getInventoryResponse = async ( toLegacyOid(id); } } + if (inventoryResponse.GuildId) { + toLegacyOid(inventoryResponse.GuildId); + } } } } From d38ec06ed6934b7d3adc156968fa25d30e8f6420 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 6 Oct 2025 22:54:35 -0700 Subject: [PATCH 02/11] fix: disallow creating a clan from an account that's already in one (#2853) Just a slight precaution to avoid snowballing problems. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2853 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/api/createGuildController.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/controllers/api/createGuildController.ts b/src/controllers/api/createGuildController.ts index 0db8799b..993774f2 100644 --- a/src/controllers/api/createGuildController.ts +++ b/src/controllers/api/createGuildController.ts @@ -10,6 +10,17 @@ export const createGuildController: RequestHandler = async (req, res) => { const account = await getAccountForRequest(req); const payload = getJSONfromString(String(req.body)); + const inventory = await getInventory(account._id.toString(), "GuildId LevelKeys Recipes"); + if (inventory.GuildId) { + const guild = await Guild.findById(inventory.GuildId); + if (guild) { + res.json({ + ...(await getGuildClient(guild, account)) + }); + return; + } + } + // Remove pending applications for this account await GuildMember.deleteMany({ accountId: account._id, status: 1 }); @@ -27,7 +38,6 @@ export const createGuildController: RequestHandler = async (req, res) => { rank: 0 }); - const inventory = await getInventory(account._id.toString(), "GuildId LevelKeys Recipes"); inventory.GuildId = guild._id; const inventoryChanges: IInventoryChanges = {}; giveClanKey(inventory, inventoryChanges); From f5146be129b64f903323f02ca002bceb9746fbb4 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 6 Oct 2025 22:54:57 -0700 Subject: [PATCH 03/11] fix: handle dojo room build request from old versions (#2854) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2854 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/api/startDojoRecipeController.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/controllers/api/startDojoRecipeController.ts b/src/controllers/api/startDojoRecipeController.ts index bfca8b08..dd5de407 100644 --- a/src/controllers/api/startDojoRecipeController.ts +++ b/src/controllers/api/startDojoRecipeController.ts @@ -13,6 +13,7 @@ import { Types } from "mongoose"; import { ExportDojoRecipes } from "warframe-public-export-plus"; import { getAccountForRequest } from "../../services/loginService.ts"; import { getInventory } from "../../services/inventoryService.ts"; +import { fromOid } from "../../helpers/inventoryHelpers.ts"; interface IStartDojoRecipeRequest { PlacedComponent: IDojoComponentClient; @@ -50,7 +51,7 @@ export const startDojoRecipeController: RequestHandler = async (req, res) => { _id: componentId, pf: request.PlacedComponent.pf, ppf: request.PlacedComponent.ppf, - pi: new Types.ObjectId(request.PlacedComponent.pi!.$oid), + pi: new Types.ObjectId(fromOid(request.PlacedComponent.pi!)), op: request.PlacedComponent.op, pp: request.PlacedComponent.pp, DecoCapacity: room?.decoCapacity From c535044af8a1ca9164d5a429cbd6b15f476998ca Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 6 Oct 2025 22:55:33 -0700 Subject: [PATCH 04/11] fix: use 1-based indexing for clan ranks for versions before U24 (#2857) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2857 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/services/guildService.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/services/guildService.ts b/src/services/guildService.ts index 8f609877..8c5f13a0 100644 --- a/src/services/guildService.ts +++ b/src/services/guildService.ts @@ -22,7 +22,7 @@ import type { ITechProjectDatabase } from "../types/guildTypes.ts"; import { GuildPermission } from "../types/guildTypes.ts"; -import { toMongoDate, toOid, toOid2 } from "../helpers/inventoryHelpers.ts"; +import { toMongoDate, toOid, toOid2, version_compare } from "../helpers/inventoryHelpers.ts"; import type { Types } from "mongoose"; import type { IDojoBuild, IDojoResearch } from "warframe-public-export-plus"; import { ExportDojoRecipes, ExportResources } from "warframe-public-export-plus"; @@ -68,9 +68,15 @@ export const getGuildClient = async ( let missingEntry = true; const dataFillInPromises: Promise[] = []; for (const guildMember of guildMembers) { + // Use 1-based indexing for clan ranks for versions before U24. In my testing, 2018.06.14.23.21 and below used 1-based indexing and 2019.04.04.21.31 and above used 0-based indexing. I didn't narrow it down further, but I think U24 is a good spot for them to have changed it. + let rankBase = 0; + if (account.BuildLabel && version_compare(account.BuildLabel, "2018.11.08.14.45") < 0) { + rankBase += 1; + } + const member: IGuildMemberClient = { _id: toOid2(guildMember.accountId, account.BuildLabel), - Rank: guildMember.rank, + Rank: guildMember.rank + rankBase, Status: guildMember.status, Note: guildMember.RequestMsg, RequestExpiry: guildMember.RequestExpiry ? toMongoDate(guildMember.RequestExpiry) : undefined From 56954260c8d76b54331c2b1abbd120ded7cb2c99 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 6 Oct 2025 22:55:46 -0700 Subject: [PATCH 05/11] chore(webui): debounce quest updates (#2858) Closes #2855 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2858 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- static/webui/script.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/static/webui/script.js b/static/webui/script.js index c6400db7..734e6a1f 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -1202,7 +1202,7 @@ function updateInventory() { a.href = "#"; a.onclick = function (event) { event.preventDefault(); - doQuestUpdate("setInactive", item.ItemType); + debounce(doQuestUpdate, "setInactive", item.ItemType); }; a.title = loc("code_setInactive"); a.innerHTML = ``; @@ -1213,7 +1213,7 @@ function updateInventory() { a.href = "#"; a.onclick = function (event) { event.preventDefault(); - doQuestUpdate("resetKey", item.ItemType); + debounce(doQuestUpdate, "resetKey", item.ItemType); }; a.title = loc("code_reset"); a.innerHTML = ``; @@ -1224,7 +1224,7 @@ function updateInventory() { a.href = "#"; a.onclick = function (event) { event.preventDefault(); - doQuestUpdate("completeKey", item.ItemType); + debounce(doQuestUpdate, "completeKey", item.ItemType); }; a.title = loc("code_complete"); a.innerHTML = ``; @@ -1235,7 +1235,7 @@ function updateInventory() { a.href = "#"; a.onclick = function (event) { event.preventDefault(); - doQuestUpdate("prevStage", item.ItemType); + debounce(doQuestUpdate, "prevStage", item.ItemType); }; a.title = loc("code_prevStage"); a.innerHTML = ``; @@ -1250,7 +1250,7 @@ function updateInventory() { a.href = "#"; a.onclick = function (event) { event.preventDefault(); - doQuestUpdate("nextStage", item.ItemType); + debounce(doQuestUpdate, "nextStage", item.ItemType); }; a.title = loc("code_nextStage"); a.innerHTML = ``; @@ -1262,7 +1262,7 @@ function updateInventory() { a.onclick = function (event) { event.preventDefault(); reAddToItemList(itemMap, "QuestKeys", item.ItemType); - doQuestUpdate("deleteKey", item.ItemType); + debounce(doQuestUpdate, "deleteKey", item.ItemType); }; a.title = loc("code_remove"); a.innerHTML = ``; From 159e151dc06df9ff6ea434d429baaa0ddfa6b7b2 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 6 Oct 2025 22:56:04 -0700 Subject: [PATCH 06/11] chore: check for xpBasedLevelCapDisabled in missionInventoryUpdate (#2859) The bootstrapper provides this field since 0.8.2, so I think this field being absent is now more likely to mean that the patch is not in effect. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2859 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- .../api/missionInventoryUpdateController.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/controllers/api/missionInventoryUpdateController.ts b/src/controllers/api/missionInventoryUpdateController.ts index 3633e02a..eb912e40 100644 --- a/src/controllers/api/missionInventoryUpdateController.ts +++ b/src/controllers/api/missionInventoryUpdateController.ts @@ -129,14 +129,22 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res) res.json(deltas); } else if (missionReport.RewardInfo) { logger.debug(`classic mission completion, sending everything`); - const inventoryResponse = await getInventoryResponse(inventory, true, account.BuildLabel); + const inventoryResponse = await getInventoryResponse( + inventory, + "xpBasedLevelCapDisabled" in req.query, + account.BuildLabel + ); res.json({ InventoryJson: JSON.stringify(inventoryResponse), ...deltas } satisfies IMissionInventoryUpdateResponse); } else { logger.debug(`no reward info, assuming this wasn't a mission completion and we should just sync inventory`); - const inventoryResponse = await getInventoryResponse(inventory, true, account.BuildLabel); + const inventoryResponse = await getInventoryResponse( + inventory, + "xpBasedLevelCapDisabled" in req.query, + account.BuildLabel + ); res.json({ InventoryJson: JSON.stringify(inventoryResponse) } satisfies IMissionInventoryUpdateResponseBackToDryDock); From 6022bf97b5635ac21af208d539d5892b00d5fd1a Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 6 Oct 2025 22:56:20 -0700 Subject: [PATCH 07/11] feat: nemesis mode d (#2860) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2860 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/api/nemesisController.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/controllers/api/nemesisController.ts b/src/controllers/api/nemesisController.ts index cc5602ba..2388d0a7 100644 --- a/src/controllers/api/nemesisController.ts +++ b/src/controllers/api/nemesisController.ts @@ -310,6 +310,17 @@ export const nemesisController: RequestHandler = async (req, res) => { res.json({ target: inventory.toJSON().Nemesis }); + } else if ((req.query.mode as string) == "d") { + const inventory = await getInventory(account._id.toString(), "NemesisHistory"); + const body = getJSONfromString(String(req.body)); + for (const fp of body.nemesisFingerprints) { + const index = inventory.NemesisHistory!.findIndex(x => x.fp == fp); + if (index != -1) { + inventory.NemesisHistory!.splice(index, 1); + } + } + await inventory.save(); + res.json(body); } else if ((req.query.mode as string) == "w") { const inventory = await getInventory(account._id.toString(), "Nemesis"); //const body = getJSONfromString(String(req.body)); @@ -447,3 +458,7 @@ const consumeModCharge = ( response.UpgradeNew.push(true); } }; + +interface IRelinquishAdversariesRequest { + nemesisFingerprints: (bigint | number)[]; +} From 43bc12713a5c282205954dc1da7a3bfbc8a07f47 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 6 Oct 2025 22:56:35 -0700 Subject: [PATCH 08/11] chore(webui): force account cheat element state after request is done (#2862) There's a very slim chance we get an inventory response between sending the setAccountCheat request and receiving the response, in which case the element state would be ingruent. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2862 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- static/webui/script.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/static/webui/script.js b/static/webui/script.js index 734e6a1f..a2615eef 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -3195,13 +3195,16 @@ function doIntrinsicsUnlockAll() { 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: elm.checked + value: value }) + }).done(() => { + elm.checked = value; }); }); }; @@ -3237,6 +3240,8 @@ document.querySelectorAll("#account-cheats .input-group").forEach(grp => { key: input.id, value: parseInt(value) }) + }).done(() => { + btn.value = value; }); }); }; From 0f7a85db5946a0e6d53cffa45fc1f536d72593cc Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 6 Oct 2025 22:56:51 -0700 Subject: [PATCH 09/11] chore(webui): sync account cheats between different webui tabs (#2863) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2863 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/custom/setAccountCheatController.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/controllers/custom/setAccountCheatController.ts b/src/controllers/custom/setAccountCheatController.ts index 586cebb6..efd7f6c8 100644 --- a/src/controllers/custom/setAccountCheatController.ts +++ b/src/controllers/custom/setAccountCheatController.ts @@ -1,6 +1,6 @@ import { getInventory } from "../../services/inventoryService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts"; -import { sendWsBroadcastTo } from "../../services/wsService.ts"; +import { sendWsBroadcastEx, sendWsBroadcastTo } from "../../services/wsService.ts"; import type { IAccountCheats } from "../../types/inventoryTypes/inventoryTypes.ts"; import type { RequestHandler } from "express"; import { logger } from "../../utils/logger.ts"; @@ -20,6 +20,8 @@ export const setAccountCheatController: RequestHandler = async (req, res) => { res.end(); if (["infiniteCredits", "infinitePlatinum", "infiniteEndo", "infiniteRegalAya"].indexOf(payload.key) != -1) { sendWsBroadcastTo(accountId, { update_inventory: true, sync_inventory: true }); + } else { + sendWsBroadcastEx({ update_inventory: true }, accountId, parseInt(String(req.query.wsid))); } }; From 30f380f37e75dd29d7c6db0634624de0d533772b Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 6 Oct 2025 22:57:08 -0700 Subject: [PATCH 10/11] chore(webui): refresh when creating/deleting a clan in-game (#2864) So the clan tab shows/hides instantly as expected. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2864 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/api/createGuildController.ts | 2 ++ src/controllers/api/removeFromGuildController.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/controllers/api/createGuildController.ts b/src/controllers/api/createGuildController.ts index 993774f2..fb9474c6 100644 --- a/src/controllers/api/createGuildController.ts +++ b/src/controllers/api/createGuildController.ts @@ -5,6 +5,7 @@ import { Guild, GuildMember } from "../../models/guildModel.ts"; import { createUniqueClanName, getGuildClient, giveClanKey } from "../../services/guildService.ts"; import { getInventory } from "../../services/inventoryService.ts"; import type { IInventoryChanges } from "../../types/purchaseTypes.ts"; +import { sendWsBroadcastTo } from "../../services/wsService.ts"; export const createGuildController: RequestHandler = async (req, res) => { const account = await getAccountForRequest(req); @@ -47,6 +48,7 @@ export const createGuildController: RequestHandler = async (req, res) => { ...(await getGuildClient(guild, account)), InventoryChanges: inventoryChanges }); + sendWsBroadcastTo(account._id.toString(), { update_inventory: true }); }; interface ICreateGuildRequest { diff --git a/src/controllers/api/removeFromGuildController.ts b/src/controllers/api/removeFromGuildController.ts index 9535d4d7..7d4917d0 100644 --- a/src/controllers/api/removeFromGuildController.ts +++ b/src/controllers/api/removeFromGuildController.ts @@ -10,6 +10,7 @@ import { import { createMessage } from "../../services/inboxService.ts"; import { getInventory } from "../../services/inventoryService.ts"; import { getAccountForRequest, getSuffixedName } from "../../services/loginService.ts"; +import { sendWsBroadcastTo } from "../../services/wsService.ts"; import { GuildPermission } from "../../types/guildTypes.ts"; import type { RequestHandler } from "express"; @@ -85,6 +86,7 @@ export const removeFromGuildController: RequestHandler = async (req, res) => { ItemToRemove: "/Lotus/Types/Keys/DojoKey", RecipeToRemove: "/Lotus/Types/Keys/DojoKeyBlueprint" }); + sendWsBroadcastTo(payload.userId, { update_inventory: true }); }; interface IRemoveFromGuildRequest { From f5c1b8359895df5966c50d9a6aac733b22c25450 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 6 Oct 2025 22:57:18 -0700 Subject: [PATCH 11/11] fix: only commit 'Missions' on successful completion (#2866) Fixes SP missions being marked as completed when failing/quitting. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2866 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/services/missionInventoryUpdateService.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index dfb6c815..eb586eee 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -309,9 +309,6 @@ export const addMissionInventoryUpdates = async ( } break; } - case "Missions": - addMissionComplete(inventory, value); - break; case "LastRegionPlayed": if (!(config.unfaithfulBugFixes?.ignore1999LastRegionPlayed && value === "1999MapName")) { inventory.LastRegionPlayed = value; @@ -1208,6 +1205,9 @@ export const addMissionRewards = async ( if (missions && missions.Tag in ExportRegions) { const node = ExportRegions[missions.Tag]; + // cannot add this with normal updates because { Tier: 1 } would mark the SP node as completed even on a failure + addMissionComplete(inventory, missions); + //node based credit rewards for mission completion if (isEligibleForCreditReward(rewardInfo, missions, node)) { const levelCreditReward = getLevelCreditRewards(node);