Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions app/.server/classes/Plugins/Aspect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,18 @@ export abstract class Aspect extends DataStore {
plugin: BasePlugin;
constructor(
params: { name: string },
aspectConfig: { kind: AspectKinds; subPath?: `/${string}` },
aspectConfig: { kind: AspectKinds; subPath?: `/${string}`; manifestFile?: string },
Comment thread
mechatronics-studio marked this conversation as resolved.
Outdated
plugin: BasePlugin,
options: DataStoreOptions,
) {
const { kind, subPath = "/" } = aspectConfig;
const { kind, subPath = "/", manifestFile = "manifest.yml" } = aspectConfig;
const name = generateIncrementedName(
params.name || `New ${kind}`,
plugin.aspects[kind].map((aspect) => aspect.name),
);
super(params, {
meta: {
filePath: `/plugins/${plugin.id}/${kind}${subPath}${name}/manifest.yml`,
filePath: `/plugins/${plugin.id}/${kind}${subPath}${name}/${manifestFile}`,
},
...options,
});
Expand Down
85 changes: 85 additions & 0 deletions app/.server/classes/Plugins/Bridge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import type BasePlugin from ".";
import { Aspect } from "./Aspect";
import { generateIncrementedName } from "@thorium/utils/generateIncrementedName";

export interface BridgeClientAssignment {
clientName: string;
stationId: string | null;
isSoundPlayer: boolean;
Comment thread
mechatronics-studio marked this conversation as resolved.
tags: string[];
}

export interface BridgeViewscreen {
id: string;
name: string;
tags: string[];
isMainViewscreen?: boolean;
defaultPose: { poseId: string; pluginId: string } | null;
showGizmos?: boolean;
showLayout?: boolean;
brokenMode?: "fullyBroken" | "cameraBrokenOnly" | "invincible";
/** Camera field of view in degrees (10–80). Defaults to 45. */
fov?: number;
}

export type BridgeMapElementType = "station" | "viewscreen";

export interface BridgeMapElement {
id: string;
type: BridgeMapElementType;
x: number;
y: number;
rotation: number;
pitch?: number;
width?: number;
height?: number;
Comment thread
mechatronics-studio marked this conversation as resolved.
Outdated
label?: string;
viewscreenId?: string;
stationName?: string;
clientName?: string;
}
Comment thread
mechatronics-studio marked this conversation as resolved.
Outdated

export interface BridgeLevel {
Comment thread
mechatronics-studio marked this conversation as resolved.
Outdated
id: string;
name: string;
backgroundUrl: string;
imageWidth: number;
Comment thread
mechatronics-studio marked this conversation as resolved.
Outdated
imageHeight: number;
elements: BridgeMapElement[];
}

export interface SavedStationAssignment {
clientAssignments: BridgeClientAssignment[];
elementStations: Record<string, string>; // elementId -> stationName
}

export default class BridgePlugin extends Aspect {
apiVersion = "bridges/v1" as const;
kind = "bridges" as const;
name!: string;
description!: string;
stationComplementRef?: { pluginId: string; stationComplementId: string };
clientAssignments!: BridgeClientAssignment[];
savedStationAssignments!: Record<string, SavedStationAssignment>;
Comment thread
mechatronics-studio marked this conversation as resolved.
Outdated
viewscreens!: BridgeViewscreen[];
levels!: BridgeLevel[];
assets!: Record<string, string>;
constructor(params: Partial<BridgePlugin>, plugin: BasePlugin) {
const name = generateIncrementedName(
params.name || "New Bridge",
plugin.aspects.bridges.map((b) => b.name),
);
super({ ...params, name }, { kind: "bridges", manifestFile: "manifest.json" }, plugin, {});

this.name = this.name || name;
this.description = this.description || params.description || "";
this.stationComplementRef = this.stationComplementRef || params.stationComplementRef || undefined;
this.clientAssignments = this.clientAssignments || params.clientAssignments || [];
this.savedStationAssignments = this.savedStationAssignments || params.savedStationAssignments || {};
this.viewscreens = this.viewscreens || params.viewscreens || [];
this.levels = this.levels || params.levels || [
{ id: crypto.randomUUID(), name: "Main", backgroundUrl: "", imageWidth: 800, imageHeight: 800, elements: [] },
];
this.assets = this.assets || {};
}
}
3 changes: 3 additions & 0 deletions app/.server/classes/Plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { MacroPlugin } from "./Macro";
import ReportPlugin from "@thorium/.server/classes/Plugins/Report";
import MissionPlugin from "./Mission";
import TrainingPlugin from "@thorium/.server/classes/Plugins/Training";
import BridgePlugin from "./Bridge";
import ConversationPlugin from "@thorium/.server/classes/Plugins/Conversation";
import { ShipSystemTypes } from "@thorium/.server/classes/Plugins/ShipSystems/shipSystemTypes";

Expand All @@ -34,6 +35,7 @@ const Aspects = {
reports: ReportPlugin,
trainings: TrainingPlugin,
conversations: ConversationPlugin,
bridges: BridgePlugin,
};

export type AspectsMap = {
Expand Down Expand Up @@ -114,6 +116,7 @@ export default class BasePlugin extends DataStore {
reports: [],
trainings: [],
conversations: [],
bridges: [],
};
pluginAspects.set(this, aspects);
}
Expand Down
24 changes: 21 additions & 3 deletions app/.server/data/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { selectAvailableTimelines } from "@thorium/utils/.server/executeBlocks";
import type { Entity } from "@thorium/utils/ecs";
import { z } from "zod";
import MarkdownIt from "markdown-it";
import { tryBridgeAutoAssign } from "@thorium/.server/init/bridgeAutoAssign";
const md = MarkdownIt();

export const client = t.router({
Expand Down Expand Up @@ -51,13 +52,18 @@ export const client = t.router({
return clients;
}),
setName: t.procedure
.input(z.object({ clientId: z.string(), name: z.string().min(2) }))
.input(z.object({ clientId: z.string(), name: z.string().min(1) }))
.send(({ ctx, input }) => {
const client = ctx.getClient(input.clientId);
client.name = input.name;
pubsub.publish.client.all();
pubsub.publish.client.get({ clientId: client.id });

// Auto-assign if the new name matches a bridge assignment
if (ctx.flight) {
tryBridgeAutoAssign(ctx, client.id);
}

return { clientId: client.id, name: client.name };
}),
setStation: t.procedure
Expand All @@ -77,6 +83,11 @@ export const client = t.router({
throw new Error("No flight has been started.");
}

// Prevent reassignment of bridge-assigned clients
if (flightClient.components.flightClient?.bridgeAssigned) {
throw new Error("Cannot reassign a bridge-assigned client");
}

// If shipId is null, we're removing ourselves from the flight.
if (input.shipId === null) {
flightClient.updateComponent("flightClient", {
Expand All @@ -95,8 +106,15 @@ export const client = t.router({
if (!ship?.components.isShip) {
throw new Error("No ship with that ID exists.");
}
const station = staticStations
.concat(ship.components.stationComplement?.stations || [])
const complementStations = ship.components.stationComplement?.stations || [];
const hasViewscreenStations = complementStations.some(
(s) => s.cards.some((c) => c.component === "Viewscreen"),
);
const filteredStatic = hasViewscreenStations
? staticStations.filter((s) => s.name !== "Viewscreen")
: staticStations;
const station = filteredStatic
.concat(complementStations)
.find((station) => station.name === input.stationId);

if (!station) {
Expand Down
Loading
Loading