diff --git a/app/.server/classes/Plugins/ShipSystems/MainCamera.ts b/app/.server/classes/Plugins/ShipSystems/MainCamera.ts new file mode 100644 index 00000000..8d48b28e --- /dev/null +++ b/app/.server/classes/Plugins/ShipSystems/MainCamera.ts @@ -0,0 +1,15 @@ +import type BasePlugin from ".."; +import BaseShipSystemPlugin, { registerSystem } from "./BaseSystem"; +import type { ShipSystemFlags } from "./shipSystemTypes"; + +export default class MainCameraPlugin extends BaseShipSystemPlugin { + static flags: ShipSystemFlags[] = []; + type = "mainCamera" as const; + fov: number; + + constructor(params: Partial, plugin: BasePlugin) { + super(params, plugin); + this.fov = params.fov ?? 45; + } +} +registerSystem("mainCamera", MainCameraPlugin); diff --git a/app/.server/classes/Plugins/ShipSystems/shipSystemTypes.ts b/app/.server/classes/Plugins/ShipSystems/shipSystemTypes.ts index 560c4c1b..1e99042b 100644 --- a/app/.server/classes/Plugins/ShipSystems/shipSystemTypes.ts +++ b/app/.server/classes/Plugins/ShipSystems/shipSystemTypes.ts @@ -14,6 +14,7 @@ import MainComputerPlugin from "@thorium/.server/classes/Plugins/ShipSystems/Mai import CoolantTankSystemPlugin from "@thorium/.server/classes/Plugins/ShipSystems/CoolantTank"; import NavigationPlugin from "@thorium/.server/classes/Plugins/ShipSystems/Navigation"; import LongRangeCommPlugin from "@thorium/.server/classes/Plugins/ShipSystems/LongRangeComm"; +import MainCameraPlugin from "@thorium/.server/classes/Plugins/ShipSystems/MainCamera"; // Make sure you update the isShipSystem component when adding a new ship system type // We can't derive the isShipSystem list from this list because ECS components @@ -35,6 +36,7 @@ export const ShipSystemTypes = { coolantTank: CoolantTankSystemPlugin, navigation: NavigationPlugin, longRangeComm: LongRangeCommPlugin, + mainCamera: MainCameraPlugin, }; export type ShipSystemFlags = "power" | "heat" | "damage" | "sounds"; diff --git a/app/.server/data/plugins/systems/index.ts b/app/.server/data/plugins/systems/index.ts index 40afc0e5..1f523da8 100644 --- a/app/.server/data/plugins/systems/index.ts +++ b/app/.server/data/plugins/systems/index.ts @@ -25,6 +25,7 @@ import { sensors } from "./sensors"; import { mainComputer } from "./mainComputer"; import { navigation } from "@thorium/.server/data/plugins/systems/navigation"; import { longRangeComm } from "@thorium/.server/data/plugins/systems/longRangeComm"; +import { mainCamera } from "@thorium/.server/data/plugins/systems/mainCamera"; const systemTypes = createUnionSchema( Object.keys(ShipSystemTypes) as (keyof typeof ShipSystemTypes)[], @@ -44,6 +45,7 @@ export const systems = t.router({ mainComputer, navigation, longRangeComm, + mainCamera, all: t.procedure .input(z.object({ pluginId: z.string() }).optional()) .filter((publish: { pluginId: string } | null, { input }) => { diff --git a/app/.server/data/plugins/systems/mainCamera.ts b/app/.server/data/plugins/systems/mainCamera.ts new file mode 100644 index 00000000..aea240c4 --- /dev/null +++ b/app/.server/data/plugins/systems/mainCamera.ts @@ -0,0 +1,59 @@ +import { pubsub } from "@thorium/.server/init/pubsub"; +import { t } from "@thorium/.server/init/t"; +import inputAuth from "@thorium/utils/.server/inputAuth"; +import { z } from "zod"; +import { + getShipSystem, + getShipSystemForInput, + pluginFilter, + systemInput, +} from "../utils"; +import type MainCameraPlugin from "@thorium/.server/classes/Plugins/ShipSystems/MainCamera"; + +export const mainCamera = t.router({ + get: t.procedure + .input(systemInput) + .filter(pluginFilter) + .request(({ ctx, input }) => { + const system = getShipSystem({ input, ctx }); + + if (system.type !== "mainCamera") + throw new Error("System is not Main Camera"); + + return system as MainCameraPlugin; + }), + update: t.procedure + .input( + z.object({ + pluginId: z.string(), + systemId: z.string(), + shipPluginId: z.string().optional(), + shipId: z.string().optional(), + fov: z.number().min(10).max(120).optional(), + }), + ) + .send(({ ctx, input }) => { + inputAuth(ctx); + const [system, override] = getShipSystemForInput<"mainCamera">( + ctx, + input, + ); + const shipSystem = override || system; + + if (typeof input.fov !== "undefined") { + shipSystem.fov = input.fov; + } + + pubsub.publish.plugin.systems.get({ + pluginId: input.pluginId, + }); + if (input.shipPluginId && input.shipId) { + pubsub.publish.plugin.ship.get({ + pluginId: input.shipPluginId, + shipId: input.shipId, + }); + } + + return shipSystem; + }), +}); diff --git a/app/cards/DamageReports/systemCategories.tsx b/app/cards/DamageReports/systemCategories.tsx index 32caaebb..1bfc0bb6 100644 --- a/app/cards/DamageReports/systemCategories.tsx +++ b/app/cards/DamageReports/systemCategories.tsx @@ -28,6 +28,7 @@ export const systemCategories: Record< thrusters: "Propulsion", torpedoLauncher: "Defense", warpEngines: "Propulsion", + mainCamera: "Misc.", }; export const systemSortValues = ["Name", "Type", "Offline", "Damage"]; diff --git a/app/cards/Viewscreen/NoSignal.tsx b/app/cards/Viewscreen/NoSignal.tsx new file mode 100644 index 00000000..2d7d2f35 --- /dev/null +++ b/app/cards/Viewscreen/NoSignal.tsx @@ -0,0 +1,33 @@ +const colorBars = [ + "#ffffff", + "#ffff00", + "#00ffff", + "#00ff00", + "#ff00ff", + "#ff0000", + "#0000ff", +]; + +export function NoSignal() { + return ( +
+
+
+ {colorBars.map((color) => ( +
+ ))} +
+ + No Signal Found + +

+ Main Camera System Missing +

+
+
+ ); +} diff --git a/app/cards/Viewscreen/data.server.ts b/app/cards/Viewscreen/data.server.ts index efb64b71..391ff75d 100644 --- a/app/cards/Viewscreen/data.server.ts +++ b/app/cards/Viewscreen/data.server.ts @@ -2,6 +2,22 @@ import { t } from "@thorium/.server/init/t"; import { z } from "zod"; export const viewscreen = t.router({ + camera: t.procedure + .input(z.object({ shipId: z.number() })) + .autoPublish([], () => null) + .request(({ ctx, input }) => { + if (!ctx.flight?.ecs) return null; + for (const [, entity] of ctx.flight.ecs.entities) { + if ( + entity.components.isShipSystem?.type === "mainCamera" && + entity.components.isShipSystem?.shipId === input.shipId && + entity.components.isMainCamera + ) { + return { fov: entity.components.isMainCamera.fov }; + } + } + return null; + }), system: t.procedure .input(z.object({ clientId: z.string() })) .autoPublish([], () => null) diff --git a/app/cards/Viewscreen/index.tsx b/app/cards/Viewscreen/index.tsx index 24383814..ce891b74 100644 --- a/app/cards/Viewscreen/index.tsx +++ b/app/cards/Viewscreen/index.tsx @@ -15,6 +15,7 @@ import { WarpStars } from "./WarpStars"; import { CircleGridStoreProvider } from "@thorium/cards/Pilot/useCircleGridStore"; import { useStation } from "@thorium/routes/station/useStation"; import { Gizmos } from "./gizmos"; +import { NoSignal } from "./NoSignal"; const forwardQuaternion = new Quaternion(0, 1, 0, 0); @@ -58,12 +59,17 @@ export function Viewscreen() { const currentSystem = useStarmapStore((store) => store.currentSystem); const [initialized, setInitialized] = useState(false); const { shipId } = useStation(); + const [camera] = q.viewscreen.camera.useNetRequest({ shipId }); q.viewscreen.stream.useDataStream({ shipId }); + if (!camera) { + return ; + } + return (
- + setInitialized(true)} /> {initialized ? ( <> diff --git a/app/components/Starmap/StarmapCanvas.tsx b/app/components/Starmap/StarmapCanvas.tsx index 3cccda5d..88a61738 100644 --- a/app/components/Starmap/StarmapCanvas.tsx +++ b/app/components/Starmap/StarmapCanvas.tsx @@ -36,12 +36,14 @@ export default function StarmapCanvas({ shouldRender = true, alpha = true, className = "", + fov = 45, ...props }: { children: ReactNode; shouldRender?: boolean; alpha?: boolean; className?: string; + fov?: number; } & CanvasProps) { const client = useQueryClient(); @@ -61,7 +63,7 @@ export default function StarmapCanvas({ e.preventDefault(); }} gl={{ antialias: true, logarithmicDepthBuffer: true, alpha }} - camera={{ fov: 45, near: 0.01, far: FAR }} + camera={{ fov, near: 0.01, far: FAR }} frameloop={shouldRender ? "always" : "demand"} {...props} > diff --git a/app/ecs-components/shipSystems/index.ts b/app/ecs-components/shipSystems/index.ts index c03019c9..ca9d2911 100644 --- a/app/ecs-components/shipSystems/index.ts +++ b/app/ecs-components/shipSystems/index.ts @@ -17,3 +17,4 @@ export * from "./isLegacySensors"; export * from "./isLegacySensorScanning"; export * from "./isNavigation"; export * from "./isLongRangeComm"; +export * from "./isMainCamera"; diff --git a/app/ecs-components/shipSystems/isMainCamera.ts b/app/ecs-components/shipSystems/isMainCamera.ts new file mode 100644 index 00000000..26733455 --- /dev/null +++ b/app/ecs-components/shipSystems/isMainCamera.ts @@ -0,0 +1,7 @@ +import { z } from "zod"; + +export const isMainCamera = z + .object({ + fov: z.number().default(45), + }) + .default({}); diff --git a/app/ecs-components/shipSystems/isShipSystem.ts b/app/ecs-components/shipSystems/isShipSystem.ts index 53e6c9cc..65890c6c 100644 --- a/app/ecs-components/shipSystems/isShipSystem.ts +++ b/app/ecs-components/shipSystems/isShipSystem.ts @@ -17,6 +17,7 @@ const shipSystemTypes = z.enum([ "coolantTank", "navigation", "longRangeComm", + "mainCamera", ]); export type ShipSystemTypes = z.infer; diff --git a/app/routes/config/systems/SystemConfigs/mainCamera.tsx b/app/routes/config/systems/SystemConfigs/mainCamera.tsx new file mode 100644 index 00000000..7b0e32c7 --- /dev/null +++ b/app/routes/config/systems/SystemConfigs/mainCamera.tsx @@ -0,0 +1,64 @@ +import { q } from "@thorium/context/AppContext"; +import Input from "@thorium/ui/Input"; +import { useContext, useReducer } from "react"; +import { useParams } from "react-router"; +import { ShipPluginIdContext } from "@thorium/context/ShipSystemOverrideContext"; +import { OverrideResetButton } from "../OverrideResetButton"; +import { Navigate } from "@thorium/components/Navigate"; + +export default function MainCameraConfig() { + const { pluginId, systemId, shipId } = useParams() as { + pluginId: string; + systemId: string; + shipId: string; + }; + const shipPluginId = useContext(ShipPluginIdContext); + + const [system] = q.plugin.systems.mainCamera.get.useNetRequest({ + pluginId, + systemId, + shipId, + shipPluginId, + }); + const [rekey, setRekey] = useReducer(() => Math.random(), Math.random()); + const key = `${systemId}${rekey}`; + if (!system) return ; + + return ( +
+
+
+
+ { + const val = Number(e.target.value); + if (!Number.isNaN(val) && val >= 10 && val <= 120) { + q.plugin.systems.mainCamera.update.netSend({ + pluginId, + systemId, + shipId, + shipPluginId, + fov: val, + }); + } + }} + /> + +
+
+
+
+ ); +}