From 77aa1caa8f3717439d650532ac514491276c7dd9 Mon Sep 17 00:00:00 2001 From: Sainan Date: Thu, 6 Mar 2025 07:19:01 -0800 Subject: [PATCH] feat: dojo room destruction stage (#1089) Closes #1074 Based on what I could find, apparently only rooms need 2 hours to destroy and decos are removed instantly. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/1089 --- config.json.example | 1 + .../api/abortDojoComponentController.ts | 3 +- ...abortDojoComponentDestructionController.ts | 12 ++++++++ .../api/changeDojoRootController.ts | 2 +- .../contributeToDojoComponentController.ts | 2 +- .../api/destroyDojoDecoController.ts | 3 +- .../api/dojoComponentRushController.ts | 2 +- src/controllers/api/getGuildDojoController.ts | 2 +- .../api/placeDecoInComponentController.ts | 2 +- ...queueDojoComponentDestructionController.ts | 9 ++++-- .../api/setDojoComponentMessageController.ts | 2 +- .../api/startDojoRecipeController.ts | 2 +- src/routes/api.ts | 2 ++ src/services/configService.ts | 1 + src/services/guildService.ts | 30 ++++++++++++++++--- static/webui/index.html | 4 +++ static/webui/translations/en.js | 1 + static/webui/translations/fr.js | 1 + static/webui/translations/ru.js | 1 + 19 files changed, 64 insertions(+), 18 deletions(-) create mode 100644 src/controllers/api/abortDojoComponentDestructionController.ts diff --git a/config.json.example b/config.json.example index af2e9846..db0f6d41 100644 --- a/config.json.example +++ b/config.json.example @@ -33,6 +33,7 @@ "noDailyStandingLimits": true, "instantResourceExtractorDrones": false, "noDojoRoomBuildStage": true, + "fastDojoRoomDestruction": true, "noDojoResearchCosts": true, "noDojoResearchTime": true, "spoofMasteryRank": -1 diff --git a/src/controllers/api/abortDojoComponentController.ts b/src/controllers/api/abortDojoComponentController.ts index 02c69cec..fbeb3670 100644 --- a/src/controllers/api/abortDojoComponentController.ts +++ b/src/controllers/api/abortDojoComponentController.ts @@ -9,7 +9,6 @@ export const abortDojoComponentController: RequestHandler = async (req, res) => const guild = await getGuildForRequestEx(req, inventory); const request = JSON.parse(String(req.body)) as IAbortDojoComponentRequest; - // TODO: Move already-contributed credits & items to the clan vault if (request.DecoId) { removeDojoDeco(guild, request.ComponentId, request.DecoId); } else { @@ -17,7 +16,7 @@ export const abortDojoComponentController: RequestHandler = async (req, res) => } await guild.save(); - res.json(getDojoClient(guild, 0, request.ComponentId)); + res.json(await getDojoClient(guild, 0, request.ComponentId)); }; interface IAbortDojoComponentRequest { diff --git a/src/controllers/api/abortDojoComponentDestructionController.ts b/src/controllers/api/abortDojoComponentDestructionController.ts new file mode 100644 index 00000000..1df71495 --- /dev/null +++ b/src/controllers/api/abortDojoComponentDestructionController.ts @@ -0,0 +1,12 @@ +import { getDojoClient, getGuildForRequest } from "@/src/services/guildService"; +import { RequestHandler } from "express"; + +export const abortDojoComponentDestructionController: RequestHandler = async (req, res) => { + const guild = await getGuildForRequest(req); + const componentId = req.query.componentId as string; + + guild.DojoComponents.id(componentId)!.DestructionTime = undefined; + + await guild.save(); + res.json(await getDojoClient(guild, 0, componentId)); +}; diff --git a/src/controllers/api/changeDojoRootController.ts b/src/controllers/api/changeDojoRootController.ts index d54e564a..d596aae3 100644 --- a/src/controllers/api/changeDojoRootController.ts +++ b/src/controllers/api/changeDojoRootController.ts @@ -58,7 +58,7 @@ export const changeDojoRootController: RequestHandler = async (req, res) => { await guild.save(); - res.json(getDojoClient(guild, 0)); + res.json(await getDojoClient(guild, 0)); }; interface INode { diff --git a/src/controllers/api/contributeToDojoComponentController.ts b/src/controllers/api/contributeToDojoComponentController.ts index bde6d6ff..40c6b59f 100644 --- a/src/controllers/api/contributeToDojoComponentController.ts +++ b/src/controllers/api/contributeToDojoComponentController.ts @@ -50,7 +50,7 @@ export const contributeToDojoComponentController: RequestHandler = async (req, r await guild.save(); await inventory.save(); res.json({ - ...getDojoClient(guild, 0, component._id), + ...(await getDojoClient(guild, 0, component._id)), InventoryChanges: inventoryChanges }); }; diff --git a/src/controllers/api/destroyDojoDecoController.ts b/src/controllers/api/destroyDojoDecoController.ts index d6884fe4..1b7ec1dd 100644 --- a/src/controllers/api/destroyDojoDecoController.ts +++ b/src/controllers/api/destroyDojoDecoController.ts @@ -6,10 +6,9 @@ export const destroyDojoDecoController: RequestHandler = async (req, res) => { const request = JSON.parse(String(req.body)) as IDestroyDojoDecoRequest; removeDojoDeco(guild, request.ComponentId, request.DecoId); - // TODO: The client says this is supposed to refund the resources to the clan vault, so we should probably do that. await guild.save(); - res.json(getDojoClient(guild, 0, request.ComponentId)); + res.json(await getDojoClient(guild, 0, request.ComponentId)); }; interface IDestroyDojoDecoRequest { diff --git a/src/controllers/api/dojoComponentRushController.ts b/src/controllers/api/dojoComponentRushController.ts index 3058f8ef..a19cfa7e 100644 --- a/src/controllers/api/dojoComponentRushController.ts +++ b/src/controllers/api/dojoComponentRushController.ts @@ -35,7 +35,7 @@ export const dojoComponentRushController: RequestHandler = async (req, res) => { await guild.save(); await inventory.save(); res.json({ - ...getDojoClient(guild, 0, component._id), + ...(await getDojoClient(guild, 0, component._id)), InventoryChanges: inventoryChanges }); }; diff --git a/src/controllers/api/getGuildDojoController.ts b/src/controllers/api/getGuildDojoController.ts index 560bf045..04d701be 100644 --- a/src/controllers/api/getGuildDojoController.ts +++ b/src/controllers/api/getGuildDojoController.ts @@ -24,5 +24,5 @@ export const getGuildDojoController: RequestHandler = async (req, res) => { await guild.save(); } - res.json(getDojoClient(guild, 0)); + res.json(await getDojoClient(guild, 0)); }; diff --git a/src/controllers/api/placeDecoInComponentController.ts b/src/controllers/api/placeDecoInComponentController.ts index d25ac548..b08a1700 100644 --- a/src/controllers/api/placeDecoInComponentController.ts +++ b/src/controllers/api/placeDecoInComponentController.ts @@ -30,7 +30,7 @@ export const placeDecoInComponentController: RequestHandler = async (req, res) = } await guild.save(); - res.json(getDojoClient(guild, 0, component._id)); + res.json(await getDojoClient(guild, 0, component._id)); }; interface IPlaceDecoInComponentRequest { diff --git a/src/controllers/api/queueDojoComponentDestructionController.ts b/src/controllers/api/queueDojoComponentDestructionController.ts index 2e30dc25..a4459cdd 100644 --- a/src/controllers/api/queueDojoComponentDestructionController.ts +++ b/src/controllers/api/queueDojoComponentDestructionController.ts @@ -1,12 +1,15 @@ -import { getDojoClient, getGuildForRequest, removeDojoRoom } from "@/src/services/guildService"; +import { config } from "@/src/services/configService"; +import { getDojoClient, getGuildForRequest } from "@/src/services/guildService"; import { RequestHandler } from "express"; export const queueDojoComponentDestructionController: RequestHandler = async (req, res) => { const guild = await getGuildForRequest(req); const componentId = req.query.componentId as string; - removeDojoRoom(guild, componentId); + guild.DojoComponents.id(componentId)!.DestructionTime = new Date( + Date.now() + (config.fastDojoRoomDestruction ? 5_000 : 2 * 3600_000) + ); await guild.save(); - res.json(getDojoClient(guild, 1)); + res.json(await getDojoClient(guild, 0, componentId)); }; diff --git a/src/controllers/api/setDojoComponentMessageController.ts b/src/controllers/api/setDojoComponentMessageController.ts index 255c4d2e..92931a54 100644 --- a/src/controllers/api/setDojoComponentMessageController.ts +++ b/src/controllers/api/setDojoComponentMessageController.ts @@ -12,7 +12,7 @@ export const setDojoComponentMessageController: RequestHandler = async (req, res component.Message = payload.Message; } await guild.save(); - res.json(getDojoClient(guild, 0, component._id)); + res.json(await getDojoClient(guild, 0, component._id)); }; type SetDojoComponentMessageRequest = { Name: string } | { Message: string }; diff --git a/src/controllers/api/startDojoRecipeController.ts b/src/controllers/api/startDojoRecipeController.ts index 0a0dfc66..30f4ed75 100644 --- a/src/controllers/api/startDojoRecipeController.ts +++ b/src/controllers/api/startDojoRecipeController.ts @@ -37,5 +37,5 @@ export const startDojoRecipeController: RequestHandler = async (req, res) => { component.CompletionTime = new Date(Date.now()); } await guild.save(); - res.json(getDojoClient(guild, 0)); + res.json(await getDojoClient(guild, 0)); }; diff --git a/src/routes/api.ts b/src/routes/api.ts index 72229b15..dcc9fddc 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -1,6 +1,7 @@ import express from "express"; import { abandonLibraryDailyTaskController } from "@/src/controllers/api/abandonLibraryDailyTaskController"; import { abortDojoComponentController } from "@/src/controllers/api/abortDojoComponentController"; +import { abortDojoComponentDestructionController } from "@/src/controllers/api/abortDojoComponentDestructionController"; import { activateRandomModController } from "@/src/controllers/api/activateRandomModController"; import { addFriendImageController } from "@/src/controllers/api/addFriendImageController"; import { arcaneCommonController } from "@/src/controllers/api/arcaneCommonController"; @@ -105,6 +106,7 @@ const apiRouter = express.Router(); // get apiRouter.get("/abandonLibraryDailyTask.php", abandonLibraryDailyTaskController); +apiRouter.get("/abortDojoComponentDestruction.php", abortDojoComponentDestructionController); apiRouter.get("/checkDailyMissionBonus.php", checkDailyMissionBonusController); apiRouter.get("/claimLibraryDailyTaskReward.php", claimLibraryDailyTaskRewardController); apiRouter.get("/credits.php", creditsController); diff --git a/src/services/configService.ts b/src/services/configService.ts index 788ee4bf..e1c79534 100644 --- a/src/services/configService.ts +++ b/src/services/configService.ts @@ -59,6 +59,7 @@ interface IConfig { noDailyStandingLimits?: boolean; instantResourceExtractorDrones?: boolean; noDojoRoomBuildStage?: boolean; + fastDojoRoomDestruction?: boolean; noDojoResearchCosts?: boolean; noDojoResearchTime?: boolean; spoofMasteryRank?: number; diff --git a/src/services/guildService.ts b/src/services/guildService.ts index 19a273f2..bc710c0a 100644 --- a/src/services/guildService.ts +++ b/src/services/guildService.ts @@ -7,6 +7,7 @@ import { IDojoClient, IDojoComponentClient } from "@/src/types/guildTypes"; import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers"; import { Types } from "mongoose"; import { ExportDojoRecipes } from "warframe-public-export-plus"; +import { logger } from "../utils/logger"; export const getGuildForRequest = async (req: Request): Promise => { const accountId = await getAccountIdForRequest(req); @@ -29,11 +30,11 @@ export const getGuildForRequestEx = async ( return guild; }; -export const getDojoClient = ( +export const getDojoClient = async ( guild: TGuildDatabaseDocument, status: number, componentId: Types.ObjectId | string | undefined = undefined -): IDojoClient => { +): Promise => { const dojo: IDojoClient = { _id: { $oid: guild._id.toString() }, Name: guild.Name, @@ -46,6 +47,7 @@ export const getDojoClient = ( DojoRequestStatus: status, DojoComponents: [] }; + const roomsToRemove: Types.ObjectId[] = []; guild.DojoComponents.forEach(dojoComponent => { if (!componentId || dojoComponent._id.equals(componentId)) { const clientComponent: IDojoComponentClient = { @@ -63,6 +65,13 @@ export const getDojoClient = ( } if (dojoComponent.CompletionTime) { clientComponent.CompletionTime = toMongoDate(dojoComponent.CompletionTime); + if (dojoComponent.DestructionTime) { + if (Date.now() >= dojoComponent.DestructionTime.getTime()) { + roomsToRemove.push(dojoComponent._id); + return; + } + clientComponent.DestructionTime = toMongoDate(dojoComponent.DestructionTime); + } } else { clientComponent.RegularCredits = dojoComponent.RegularCredits; clientComponent.MiscItems = dojoComponent.MiscItems; @@ -84,6 +93,13 @@ export const getDojoClient = ( dojo.DojoComponents.push(clientComponent); } }); + if (roomsToRemove.length) { + logger.debug(`removing now-destroyed rooms`, roomsToRemove); + for (const id of roomsToRemove) { + removeDojoRoom(guild, id); + } + await guild.save(); + } return dojo; }; @@ -92,7 +108,7 @@ export const scaleRequiredCount = (count: number): number => { return Math.max(1, Math.trunc(count / 100)); }; -export const removeDojoRoom = (guild: TGuildDatabaseDocument, componentId: string): void => { +export const removeDojoRoom = (guild: TGuildDatabaseDocument, componentId: Types.ObjectId | string): void => { const component = guild.DojoComponents.splice( guild.DojoComponents.findIndex(x => x._id.equals(componentId)), 1 @@ -102,9 +118,14 @@ export const removeDojoRoom = (guild: TGuildDatabaseDocument, componentId: strin guild.DojoCapacity -= meta.capacity; guild.DojoEnergy -= meta.energy; } + // TODO: Add resources spent to the clan vault }; -export const removeDojoDeco = (guild: TGuildDatabaseDocument, componentId: string, decoId: string): void => { +export const removeDojoDeco = ( + guild: TGuildDatabaseDocument, + componentId: Types.ObjectId | string, + decoId: Types.ObjectId | string +): void => { const component = guild.DojoComponents.id(componentId)!; const deco = component.Decos!.splice( component.Decos!.findIndex(x => x._id.equals(decoId)), @@ -114,4 +135,5 @@ export const removeDojoDeco = (guild: TGuildDatabaseDocument, componentId: strin if (meta && meta.capacityCost) { component.DecoCapacity! += meta.capacityCost; } + // TODO: Add resources spent to the clan vault }; diff --git a/static/webui/index.html b/static/webui/index.html index a84fa719..77389791 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -525,6 +525,10 @@ +
+ + +
diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index 628abcd7..bc1472c0 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -111,6 +111,7 @@ dict = { cheats_noDailyStandingLimits: `No Daily Standing Limits`, cheats_instantResourceExtractorDrones: `Instant Resource Extractor Drones`, cheats_noDojoRoomBuildStage: `No Dojo Room Build Stage`, + cheats_fastDojoRoomDestruction: `Fast Dojo Room Destruction`, cheats_noDojoResearchCosts: `No Dojo Research Costs`, cheats_noDojoResearchTime: `No Dojo Research Time`, cheats_spoofMasteryRank: `Spoofed Mastery Rank (-1 to disable)`, diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index 45480bc5..5acce802 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -112,6 +112,7 @@ dict = { cheats_noDailyStandingLimits: `Pas de limite de réputation journalière`, cheats_instantResourceExtractorDrones: `Ressources de drone d'extraction instantannées`, cheats_noDojoRoomBuildStage: `No Dojo Room Build Stage`, + cheats_fastDojoRoomDestruction: `[UNTRANSLATED] Fast Dojo Room Destruction`, cheats_noDojoResearchCosts: `Aucun coût de recherche (Dojo)`, cheats_noDojoResearchTime: `Aucun temps de recherche (Dojo)`, cheats_spoofMasteryRank: `Spoofed Mastery Rank (-1 to disable)`, diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index d109c972..96deecc3 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -112,6 +112,7 @@ dict = { cheats_noDailyStandingLimits: `Без ежедневных ограничений репутации`, cheats_instantResourceExtractorDrones: `[UNTRANSLATED] Instant Resource Extractor Drones`, cheats_noDojoRoomBuildStage: `[UNTRANSLATED] No Dojo Room Build Stage`, + cheats_fastDojoRoomDestruction: `[UNTRANSLATED] Fast Dojo Room Destruction`, cheats_noDojoResearchCosts: `[UNTRANSLATED] No Dojo Research Costs`, cheats_noDojoResearchTime: `[UNTRANSLATED] No Dojo Research Time`, cheats_spoofMasteryRank: `Подделанный ранг мастерства (-1 для отключения)`,