Something went wrong. Try again.
Starship Simulator Controls thoriumsim.com
Something went wrong. Try again.
15 kB · 486 lines
TSX
at develop
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487import { pubsub } from "@thorium/.server/init/pubsub";import { t } from "@thorium/.server/init/t";import { cancelLoopingSound, playShipSound } from "@thorium/utils/.server/playRangedSound";import { checkSystemStability } from "@thorium/utils/.server/ship/checkSystemStability";import { clearAutopilotState, deactivateForwardAutopilot,} from "@thorium/utils/.server/ship/clearAutopilotState";import { getShipSystem } from "@thorium/utils/.server/ship/getShipSystem";import type { Entity } from "@thorium/utils/ecs";import { pathfinder } from "@thorium/utils/starmap/pathfinder.server";import { Vector3 } from "three";import z from "zod";
export const pilot = t.router({ impulseEngines: t.router({ get: t.procedure .input(z.object({ shipId: z.number() })) .filter((publish: { shipId: number; systemId: number }, { input }) => { if (publish && publish.shipId !== input.shipId) return false; return true; }) .autoPublish( ["isImpulseEngines"], (entity) => entity.components.isShipSystem && { shipId: entity.components.isShipSystem.shipId, systemId: entity.id, }, ) .request(({ ctx, input: { shipId } }) => { // Currently only support one impulse engines const impulseEngines = getShipSystem(ctx.ecs, { systemType: "impulseEngines", shipId, }); const targetSpeed = impulseEngines.components.isImpulseEngines?.targetSpeed || 0; const cruisingSpeed = impulseEngines.components.isImpulseEngines?.cruisingSpeed || 1;
return { id: impulseEngines.id, name: impulseEngines.components.identity?.name || "Impulse", targetSpeed, cruisingSpeed, emergencySpeed: impulseEngines.components.isImpulseEngines?.emergencySpeed || 1, speeds: impulseEngines.components.isImpulseEngines?.speeds || [], }; }), ambiance: t.procedure .input(z.object({ shipId: z.number() })) .autoPublish(["isImpulseEngines"], () => null)
.request(({ ctx, input: { shipId } }) => { const engine = getShipSystem(ctx.ecs, { systemType: "impulseEngines", shipId, }); const { currentPower, powerLevels } = engine.components.power || { currentPower: 0, powerLevels: [0], }; const maxSafePower = powerLevels[powerLevels.length - 1]; const requiredPower = powerLevels[0]; return [ { id: engine.id, volumePercent: Math.min(1, currentPower / requiredPower), playbackRate: currentPower / maxSafePower, ambiance: engine.components.soundEffects?.soundBank.ambiance, }, ]; }), setSpeed: t.procedure .input( z.object({ shipId: z.number(), systemId: z.number().optional(), speed: z.number(), }), ) .output( z.object({ shipId: z.number(), systemId: z.number().optional(), speed: z.number(), }), ) .meta({ event: true }) .send(({ ctx, input: { shipId, systemId, speed } }) => { const system = systemId ? getShipSystem(ctx.ecs, { systemId, }) : getShipSystem(ctx.ecs, { systemType: "impulseEngines", shipId, });
if (!system.components.isImpulseEngines) throw new Error("System is not a impulse engine");
checkSystemStability(system, "Failed to set impulse speed");
// Deactivate autopilot when manually setting impulse speed const ship = ctx.ecs.getEntityById(shipId); if (ship) deactivateForwardAutopilot(ship);
system.updateComponent("isImpulseEngines", { targetSpeed: speed, });
pubsub.publish.pilot.impulseEngines.get({ shipId, systemId: system.id, }); return { shipId, systemId: system.id, speed }; }), }), warpEngines: t.router({ get: t.procedure .input(z.object({ shipId: z.number() })) .filter((publish: { shipId: number; systemId: number }, { input }) => { if (publish && publish.shipId !== input.shipId) return false; return true; }) .autoPublish( ["isWarpEngines"], (entity) => entity.components.isShipSystem && { shipId: entity.components.isShipSystem.shipId, systemId: entity.id, }, )
.request(({ ctx, input }) => { // Currently only support one warp engines const warpEngines = getShipSystem(ctx.ecs, { systemType: "warpEngines", shipId: input.shipId, }); return { id: warpEngines.id, maxVelocity: warpEngines.components.isWarpEngines?.maxVelocity || 0, currentWarpFactor: warpEngines.components.isWarpEngines?.currentWarpFactor || 0, interstellarCruisingSpeed: warpEngines.components.isWarpEngines?.interstellarCruisingSpeed || 599600000000, solarCruisingSpeed: warpEngines.components.isWarpEngines?.solarCruisingSpeed || 29980000, speeds: warpEngines.components.isWarpEngines?.speeds || [], }; }), setWarpFactor: t.procedure .input( z.object({ shipId: z.number(), systemId: z.number().optional(), factor: z.number(), }), ) .output( z.object({ shipId: z.number(), systemId: z.number().optional(), factor: z.number(), }), ) .meta({ event: true }) .send(({ ctx, input: { systemId, shipId, factor } }) => { const system = systemId ? getShipSystem(ctx.ecs, { systemId, }) : getShipSystem(ctx.ecs, { systemType: "warpEngines", shipId, }); if (!system.components.isWarpEngines) throw new Error("System is not a warp engine");
checkSystemStability(system, "Failed to set warp engine factor");
// Deactivate autopilot when manually setting warp factor const ship = ctx.ecs.getEntityById(shipId); if (ship) deactivateForwardAutopilot(ship);
system.updateComponent("isWarpEngines", { currentWarpFactor: factor, });
pubsub.publish.pilot.warpEngines.get({ shipId, systemId: system.id, }); return { systemId: system.id, shipId, factor }; }), }), autopilot: t.router({ get: t.procedure .input(z.object({ shipId: z.number() }))
.filter((publish: { shipId: number }, { input }) => { if (publish && publish.shipId !== input.shipId) return false; return true; }) .autoPublish(["autopilot", "facingWaypoints"], (entity) => ({ shipId: entity.id, }))
.request(({ ctx, input }) => { const ship = ctx.ecs.getEntityById(input.shipId); const waypointId = ship?.components.autopilot?.destinationWaypointId; let destinationName = ""; let waypoint: Entity | null | undefined; if (typeof waypointId === "number") { waypoint = ctx.flight?.ecs.getEntityById(waypointId); destinationName = waypoint?.components.identity?.name.replace(" Waypoint", "").trim() || ""; } const waypointParentId = waypoint?.components.position?.parentId;
const waypointSystemPosition = typeof waypointParentId === "number" ? ctx.flight?.ecs.getEntityById(waypointParentId)?.components.position || null : null;
return { forwardAutopilot: ship?.components.autopilot?.forwardAutopilot, destinationName, destinationWaypointId: waypointId ?? null, destinationPosition: waypoint?.components.position || null, destinationSystemPosition: waypointSystemPosition, locked: !!ship?.components.autopilot?.desiredCoordinates, facingWaypointIds: ship?.components.facingWaypoints?.ids ?? [], }; }), lockCourse: t.procedure .meta({ event: true }) .input(z.object({ waypointId: z.number(), shipId: z.number() })) .output(z.object({ waypointId: z.number(), shipId: z.number() })) .send(({ ctx, input }) => { const waypoint = ctx.flight?.ecs.getEntityById(input.waypointId); if (waypoint?.components.isWaypoint?.assignedShipId !== input.shipId) throw new Error("Invalid waypoint for ship"); const position = waypoint?.components.position; if (!waypoint || !position) throw new Error("Waypoint not found."); const ship = ctx.ecs.getEntityById(input.shipId);
const desiredCoordinates = { x: position.x, y: position.y, z: position.z, }; const path = position.parentId === ship?.components.position?.parentId ? pathfinder(ship, new Vector3(position.x, position.y, position.z)) : []; const nextCoordinates = path?.shift(); ship?.updateComponent("autopilot", { destinationWaypointId: input.waypointId, desiredCoordinates, path, nextCoordinates, desiredSolarSystemId: position.parentId, rotationAutopilot: true, forwardAutopilot: false, });
pubsub.publish.pilot.autopilot.get({ shipId: input.shipId }); pubsub.publish.starmapCore.autopilot({ systemId: ship?.components.position?.parentId || null, }); return input; }), unlockCourse: t.procedure .meta({ event: true }) .input(z.object({ shipId: z.number() })) .output(z.object({ shipId: z.number() })) .send(({ ctx, input }) => { const ship = ctx.ecs.getEntityById(input.shipId); if (ship) clearAutopilotState(ship);
// Clear out the current thruster adjustments const thrusters = getShipSystem(ctx.ecs, { systemType: "thrusters", shipId: ship?.id || -1, }); thrusters?.updateComponent("isThrusters", { rotationDelta: { x: 0, y: 0, z: 0 }, });
pubsub.publish.pilot.autopilot.get({ shipId: input.shipId }); pubsub.publish.starmapCore.autopilot({ systemId: ship?.components.position?.parentId || null, }); return input; }), activate: t.procedure .meta({ event: true }) .input(z.object({ shipId: z.number() })) .output(z.object({ shipId: z.number() })) .send(({ ctx, input }) => { const ship = ctx.ecs.getEntityById(input.shipId);
ship?.updateComponent("autopilot", { forwardAutopilot: true, }); pubsub.publish.pilot.autopilot.get({ shipId: input.shipId }); pubsub.publish.starmapCore.autopilot({ systemId: ship?.components.position?.parentId || null, }); return input; }), deactivate: t.procedure .meta({ event: true }) .input(z.object({ shipId: z.number() })) .output(z.object({ shipId: z.number() })) .send(({ ctx, input }) => { const ship = ctx.ecs.getEntityById(input.shipId); ship?.updateComponent("autopilot", { forwardAutopilot: false, }); // We specifically won't clear out the impulse and warp because // we want the ship to maintain its current speed. pubsub.publish.pilot.autopilot.get({ shipId: input.shipId }); pubsub.publish.starmapCore.autopilot({ systemId: ship?.components.position?.parentId || null, });
return input; }), }), thrusters: t.router({ setDirection: t.procedure .meta({ event: true }) .input( z.object({ shipId: z.number(), systemId: z.number().optional(), direction: z.object({ x: z.number().optional(), y: z.number().optional(), z: z.number().optional(), }), }), ) .output( z.object({ shipId: z.number(), systemId: z.number().optional(), direction: z.object({ x: z.number().optional(), y: z.number().optional(), z: z.number().optional(), }), }), ) .send(({ ctx, input: { systemId, shipId, direction } }) => { const system = systemId ? getShipSystem(ctx.ecs, { systemId, }) : getShipSystem(ctx.ecs, { systemType: "thrusters", shipId, }); if (!system.components.isThrusters) throw new Error("System is not thrusters");
checkSystemStability(system, "Failed to set thruster direction");
// Deactivate autopilot when manually using direction thrusters const ship = ctx.ecs.getEntityById(shipId); if (ship) deactivateForwardAutopilot(ship);
const current = system.components.isThrusters.direction; system.updateComponent("isThrusters", { direction: { x: typeof direction.x === "number" ? direction.x : current.x, y: typeof direction.y === "number" ? direction.y : current.y, z: typeof direction.z === "number" ? direction.z : current.z, }, });
pubsub.publish.legacy.thrusters.get({ shipId });
if (!direction.x && !direction.y && !direction.z) { // Cancel the looping sound cancelLoopingSound(system, "thrust"); } else if (system.components.soundEffects?.soundBank.thrust) { // Only play one instance of the sound if (!system.components.soundEffects.looping.some((s) => s.key === "thrust")) { const ship = ctx.ecs.getEntityById(shipId); playShipSound(system, ship!, "thrust"); } }
return { systemId, shipId, direction }; }), setRotationDelta: t.procedure .input( z.object({ shipId: z.number(), systemId: z.number().optional(), rotation: z.object({ x: z.number().optional(), y: z.number().optional(), z: z.number().optional(), }), }), ) .output( z.object({ shipId: z.number(), systemId: z.number().optional(), rotation: z.object({ x: z.number().optional(), y: z.number().optional(), z: z.number().optional(), }), }), ) .meta({ event: true }) .send(({ ctx, input: { systemId, shipId, rotation } }) => { const system = systemId ? getShipSystem(ctx.ecs, { systemId, }) : getShipSystem(ctx.ecs, { systemType: "thrusters", shipId, }); if (!system.components.isThrusters) throw new Error("System is not thrusters");
checkSystemStability(system, "Failed to rotate");
// Deactivate autopilot when manually using rotation thrusters const ship = ctx.ecs.getEntityById(shipId); if (ship) deactivateForwardAutopilot(ship);
const current = system.components.isThrusters.rotationDelta; system.updateComponent("isThrusters", { rotationDelta: { x: typeof rotation.x === "number" ? rotation.x : current.x, y: typeof rotation.y === "number" ? rotation.y : current.y, z: typeof rotation.z === "number" ? rotation.z : current.z, }, });
if (!rotation.x && !rotation.y && !rotation.z) { // Cancel the looping sound cancelLoopingSound(system, "thrust"); } else if (system.components.soundEffects?.soundBank.thrust) { // Only play one instance of the sound if (!system.components.soundEffects.looping.some((s) => s.key === "thrust")) { const ship = ctx.ecs.getEntityById(shipId); playShipSound(system, ship!, "thrust"); } }
return { systemId, shipId, rotation }; }), }), stream: t.procedure .input(z.object({ shipId: z.number(), systemId: z.number().nullable() })) .dataStream(({ ctx, input }) => { const set = new Set<Entity>(); const ship = ctx.ecs.getEntityById(input.shipId); const systemId = input?.systemId || ship?.components.position?.parentId; if (typeof systemId === "undefined") { return set; } for (const entity of ctx.ecs.componentCache.get("isImpulseEngines") || []) { if (entity.components.isShipSystem?.shipId === input.shipId) { set.add(entity); } } for (const entity of ctx.ecs.componentCache.get("isWarpEngines") || []) { if (entity.components.isShipSystem?.shipId === input.shipId) { set.add(entity); } }
for (const entity of ctx.ecs.componentCache.get("position") || []) { if (entity.components.position?.parentId === systemId) { set.add(entity); } } return set; }),});