From 7e7e4e2eea711b1d5f858c9970293b41ee5f0531 Mon Sep 17 00:00:00 2001 From: Sainan Date: Wed, 12 Feb 2025 14:06:48 -0800 Subject: [PATCH] feat: change dojo spawn room (#949) Closes #524 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/949 Co-authored-by: Sainan Co-committed-by: Sainan --- .../api/changeDojoRootController.ts | 90 +++++++++++++++++++ src/routes/api.ts | 2 + src/types/guildTypes.ts | 4 +- 3 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 src/controllers/api/changeDojoRootController.ts diff --git a/src/controllers/api/changeDojoRootController.ts b/src/controllers/api/changeDojoRootController.ts new file mode 100644 index 00000000..568bf688 --- /dev/null +++ b/src/controllers/api/changeDojoRootController.ts @@ -0,0 +1,90 @@ +import { RequestHandler } from "express"; +import { getDojoClient, getGuildForRequest } from "@/src/services/guildService"; +import { logger } from "@/src/utils/logger"; +import { IDojoComponentDatabase } from "@/src/types/guildTypes"; +import { Types } from "mongoose"; + +export const changeDojoRootController: RequestHandler = async (req, res) => { + const guild = await getGuildForRequest(req); + // At this point, we know that a member of the guild is making this request. Assuming they are allowed to change the root. + + const idToNode: Record = {}; + guild.DojoComponents!.forEach(x => { + idToNode[x._id.toString()] = { + component: x, + parent: undefined, + children: [] + }; + }); + + let oldRoot: INode | undefined; + guild.DojoComponents!.forEach(x => { + const node = idToNode[x._id.toString()]; + if (x.pi) { + idToNode[x.pi.toString()].children.push(node); + node.parent = idToNode[x.pi.toString()]; + } else { + oldRoot = node; + } + }); + logger.debug("Old tree:\n" + treeToString(oldRoot!)); + + const newRoot = idToNode[req.query.newRoot as string]; + recursivelyTurnParentsIntoChildren(newRoot); + newRoot.component.pi = undefined; + newRoot.component.op = undefined; + newRoot.component.pp = undefined; + newRoot.parent = undefined; + + // Don't even ask me why this is needed because I don't know either + const stack: INode[] = [newRoot]; + let i = 0; + const idMap: Record = {}; + while (stack.length != 0) { + const top = stack.shift()!; + idMap[top.component._id.toString()] = new Types.ObjectId( + (++i).toString(16).padStart(8, "0") + top.component._id.toString().substr(8) + ); + top.children.forEach(x => stack.push(x)); + } + guild.DojoComponents!.forEach(x => { + x._id = idMap[x._id.toString()]; + if (x.pi) { + x.pi = idMap[x.pi.toString()]; + } + }); + + logger.debug("New tree:\n" + treeToString(newRoot)); + + await guild.save(); + + res.json(getDojoClient(guild, 0)); +}; + +interface INode { + component: IDojoComponentDatabase; + parent: INode | undefined; + children: INode[]; +} + +const treeToString = (root: INode, depth: number = 0): string => { + let str = " ".repeat(depth * 4) + root.component.pf + " (" + root.component._id.toString() + ")\n"; + root.children.forEach(x => { + str += treeToString(x, depth + 1); + }); + return str; +}; + +const recursivelyTurnParentsIntoChildren = (node: INode): void => { + if (node.parent!.parent) { + recursivelyTurnParentsIntoChildren(node.parent!); + } + + node.parent!.component.pi = node.component._id; + node.parent!.component.op = node.component.pp; + node.parent!.component.pp = node.component.op; + + node.parent!.parent = node; + node.parent!.children.splice(node.parent!.children.indexOf(node), 1); + node.children.push(node.parent!); +}; diff --git a/src/routes/api.ts b/src/routes/api.ts index a0eeba20..d42a9f82 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -4,6 +4,7 @@ import { addFriendImageController } from "@/src/controllers/api/addFriendImageCo import { arcaneCommonController } from "@/src/controllers/api/arcaneCommonController"; import { archonFusionController } from "@/src/controllers/api/archonFusionController"; import { artifactsController } from "../controllers/api/artifactsController"; +import { changeDojoRootController } from "../controllers/api/changeDojoRootController"; import { checkDailyMissionBonusController } from "@/src/controllers/api/checkDailyMissionBonusController"; import { claimCompletedRecipeController } from "@/src/controllers/api/claimCompletedRecipeController"; import { clearDialogueHistoryController } from "@/src/controllers/api/clearDialogueHistoryController"; @@ -127,6 +128,7 @@ apiRouter.post("/addFriendImage.php", addFriendImageController); apiRouter.post("/arcaneCommon.php", arcaneCommonController); apiRouter.post("/archonFusion.php", archonFusionController); apiRouter.post("/artifacts.php", artifactsController); +apiRouter.post("/changeDojoRoot.php", changeDojoRootController); apiRouter.post("/claimCompletedRecipe.php", claimCompletedRecipeController); apiRouter.post("/clearDialogueHistory.php", clearDialogueHistoryController); apiRouter.post("/createGuild.php", createGuildController); diff --git a/src/types/guildTypes.ts b/src/types/guildTypes.ts index 827e5d9a..28a51c71 100644 --- a/src/types/guildTypes.ts +++ b/src/types/guildTypes.ts @@ -32,8 +32,8 @@ export interface IDojoComponentClient { pf: string; // Prefab (.level) ppf: string; pi?: IOid; // Parent ID. N/A to root. - op?: string; // "Open Portal"? N/A to root. - pp?: string; // "Parent Portal"? N/A to root. + op?: string; // Name of the door within this room that leads to its parent. N/A to root. + pp?: string; // Name of the door within the parent that leads to this room. N/A to root. Name?: string; Message?: string; RegularCredits?: number; // "Collecting Materials" state: Number of credits that were donated.