Something went wrong. Try again.
Starship Simulator Controls thoriumsim.com
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360import path from "node:path";
import type ShipPlugin from "@thorium/.server/classes/Plugins/Ship";import type { DeckEdge, DeckNode } from "@thorium/.server/classes/Plugins/Ship/Deck";import type { DataContext } from "@thorium/.server/DataContext";import { pubsub } from "@thorium/.server/init/pubsub";import { t } from "@thorium/.server/init/t";import inputAuth from "@thorium/utils/.server/inputAuth";import { edgeFlagsSchema } from "@thorium/utils/flags/DeckEdge";import { nodeFlagsSchema } from "@thorium/utils/flags/DeckNode";import { generateIncrementedName } from "@thorium/utils/generateIncrementedName";import { moveArrayItem } from "@thorium/utils/operations/moveArrayItem";import uniqid from "@thorium/utils/uniqid";import z from "zod";
import { getPlugin } from "./utils";function getDeck( context: DataContext, { pluginId, shipId, deckId }: { pluginId: string; shipId: string; deckId: string },) { const plugin = getPlugin(context, pluginId); const ship = plugin.aspects.ships.find((ship) => ship.name === shipId); if (!ship) throw new Error("Ship not found");
const deck = ship.decks.find((deck) => deck.name === deckId); if (!deck) throw new Error("Deck not found"); return { ship, deck };}function getNextDeckId(ship: ShipPlugin) { const deckIds = ship.decks.flatMap((deck) => deck.nodes.map((node) => node.id)); return Math.max(0, ...deckIds) + 1;}function getNextEdgeId(ship: ShipPlugin) { const edgeIds = ship.deckEdges.map((edge) => edge.id); return Math.max(0, ...edgeIds) + 1;}
export const deck = t.router({ create: t.procedure .input(z.object({ pluginId: z.string(), shipId: z.string() })) .send(({ ctx, input }) => { inputAuth(ctx); const plugin = getPlugin(ctx, input.pluginId); const ship = plugin.aspects.ships.find((ship) => ship.name === input.shipId); if (!ship) return null;
const deckIndex = ship.addDeck({});
pubsub.publish.plugin.ship.get({ pluginId: input.pluginId, shipId: ship.name, }); return deckIndex; }), delete: t.procedure .input( z.object({ pluginId: z.string(), shipId: z.string(), deckId: z.string(), }), ) .send(({ ctx, input }) => { inputAuth(ctx); const plugin = getPlugin(ctx, input.pluginId); const ship = plugin.aspects.ships.find((ship) => ship.name === input.shipId); if (!ship) return;
const deckIndex = ship.decks.findIndex((deck) => deck.name === input.deckId); ship.removeDeck(deckIndex);
pubsub.publish.plugin.ship.get({ pluginId: input.pluginId, shipId: ship.name, }); }), update: t.procedure .input( z.intersection( z.object({ pluginId: z.string(), shipId: z.string(), deckId: z.string(), }), z.union([ z.object({ generateName: z.string() }), z.object({ newName: z.string() }), z.object({ newIndex: z.number() }), z.object({ backgroundImage: z.instanceof(File).nullish(), }), ]), ), ) .send(async ({ ctx, input }) => { inputAuth(ctx); const { ship, deck } = getDeck(ctx, input);
const deckIndex = ship.decks.findIndex((deck) => deck.name === input.deckId); if ("generateName" in input) { deck.name = generateIncrementedName( input.generateName, ship.decks.map((deck) => deck.name), ); } if ("newName" in input) { deck.name = input.newName; } if ("newIndex" in input && typeof input.newIndex === "number") { moveArrayItem(ship.decks, deckIndex, input.newIndex); } if ("backgroundImage" in input && input.backgroundImage instanceof File) { const ext = path.extname(input.backgroundImage.name); const file = input.backgroundImage; const filePath = `${deck.name}${ext}`; if (!ship) return; deck.backgroundUrl = await ctx.uploadFile.call(ship, file, filePath); ship.write(); } if ("backgroundImage" in input && input.backgroundImage === null) { await ctx.removeFile(deck.backgroundUrl); deck.backgroundUrl = ""; ship.write(true); }
pubsub.publish.plugin.ship.get({ pluginId: input.pluginId, shipId: ship.name, });
return deck; }), addNode: t.procedure .input( z.object({ pluginId: z.string(), shipId: z.string(), deckId: z.string(), x: z.number(), y: z.number(), }), ) .send(({ ctx, input }) => { inputAuth(ctx); const { ship, deck } = getDeck(ctx, input); const node: DeckNode = { x: input.x, y: input.y, id: getNextDeckId(ship), contents: {}, flags: [], icon: "", isRoom: false, name: "", radius: 0, systems: [], volume: 12000, }; deck.nodes.push(node);
pubsub.publish.plugin.ship.get({ pluginId: input.pluginId, shipId: ship.name, }); return node; }), removeNode: t.procedure .input( z.object({ pluginId: z.string(), shipId: z.string(), deckId: z.string(), nodeId: z.number(), }), ) .send(({ ctx, input }) => { inputAuth(ctx); const { ship, deck } = getDeck(ctx, input); const node = deck.nodes.find((node) => node.id === input.nodeId); if (!node) return;
// Remove any connected edges. ship.deckEdges = ship.deckEdges.filter((edge) => { const { from, to } = edge; return node.id !== from && node.id !== to; }); deck.nodes = deck.nodes.filter((node) => node.id !== input.nodeId);
pubsub.publish.plugin.ship.get({ pluginId: input.pluginId, shipId: ship.name, }); }), updateNode: t.procedure .input( z.object({ pluginId: z.string(), shipId: z.string(), deckId: z.string(), nodeId: z.number(), x: z.number().optional(), y: z.number().optional(), name: z.string().optional(), isRoom: z.boolean().optional(),
icon: z.instanceof(File).nullish().optional(),
radius: z.number().optional(), volume: z.number().optional(), flags: nodeFlagsSchema.array().optional(), systems: z.string().array().optional(), }), ) .send(async ({ ctx, input }) => { inputAuth(ctx); const { ship, deck } = getDeck(ctx, input); const node = deck.nodes.find((node) => node.id === input.nodeId); if (!node) return;
if (typeof input.x !== "undefined") { node.x = input.x; } if (typeof input.y !== "undefined") { node.y = input.y; } if (typeof input.name !== "undefined") { node.name = input.name; } if (typeof input.isRoom !== "undefined") { node.isRoom = input.isRoom; } if (typeof input.icon !== "undefined") { const file = input.icon; if (file instanceof File) { const ext = path.extname(file.name); const filePath = `${uniqid(`node-${node.id}`)}${ext}`; node.icon = await ctx.uploadFile.call(ship, file, filePath); ship.write(true); } } if (typeof input.radius !== "undefined") { node.radius = input.radius; } if (typeof input.flags !== "undefined") { node.flags = input.flags; } if (typeof input.systems !== "undefined") { node.systems = input.systems; } if (typeof input.volume !== "undefined") { if (input.volume < 0) { node.volume = 0; } node.volume = input.volume; }
pubsub.publish.plugin.ship.get({ pluginId: input.pluginId, shipId: ship.name, });
return node; }), addEdge: t.procedure .input( z.object({ pluginId: z.string(), shipId: z.string(), from: z.number(), to: z.number(), }), ) .send(({ ctx, input }) => { inputAuth(ctx); const plugin = getPlugin(ctx, input.pluginId); const ship = plugin.aspects.ships.find((ship) => ship.name === input.shipId); if (!ship) throw new Error("Ship not found"); // See if there are already any edges between these two points const existingEdge = ship.deckEdges.find( (e) => (e.from === input.from && e.to === input.to) || (e.from === input.to && e.to === input.from), ); if (existingEdge) return existingEdge; const edge: DeckEdge = { from: input.from, to: input.to, id: getNextEdgeId(ship), flags: [], isOpen: true, weight: 1, }; ship.deckEdges.push(edge);
pubsub.publish.plugin.ship.get({ pluginId: input.pluginId, shipId: ship.name, }); return edge; }), removeEdge: t.procedure .input( z.object({ pluginId: z.string(), shipId: z.string(), edgeId: z.number(), }), ) .send(({ ctx, input }) => { inputAuth(ctx); const plugin = getPlugin(ctx, input.pluginId); const ship = plugin.aspects.ships.find((ship) => ship.name === input.shipId); if (!ship) throw new Error("Ship not found");
const edge = ship.deckEdges.find((edge) => edge.id === input.edgeId); if (!edge) return;
ship.deckEdges = ship.deckEdges.filter((edge) => edge.id !== input.edgeId);
pubsub.publish.plugin.ship.get({ pluginId: input.pluginId, shipId: ship.name, }); }), updateEdge: t.procedure .input( z.intersection( z.object({ pluginId: z.string(), shipId: z.string(), edgeId: z.number(), }), z.union([z.object({ weight: z.number() }), z.object({ flags: edgeFlagsSchema.array() })]), ), ) .send(({ ctx, input }) => { inputAuth(ctx); const plugin = getPlugin(ctx, input.pluginId); const ship = plugin.aspects.ships.find((ship) => ship.name === input.shipId); if (!ship) throw new Error("Ship not found");
const edge = ship.deckEdges.find((edge) => edge.id === input.edgeId); if (!edge) return;
if ("weight" in input) { edge.weight = input.weight; } if ("flags" in input) { edge.flags = input.flags; }
pubsub.publish.plugin.ship.get({ pluginId: input.pluginId, shipId: ship.name, });
return edge; }),});