diff --git a/.gitignore b/.gitignore
index 18db3712..2426cfe8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,4 +9,6 @@ coverage
.turbo
# Copied docs images
-client/public/docs
\ No newline at end of file
+client/public/docs
+icons/name.d.ts
+icons/sprite.svg
diff --git a/client/app/cards/SystemsMonitor/data.ts b/client/app/cards/SystemsMonitor/data.ts
index bad40995..41fdde36 100644
--- a/client/app/cards/SystemsMonitor/data.ts
+++ b/client/app/cards/SystemsMonitor/data.ts
@@ -10,7 +10,7 @@ import { z } from "zod";
export const systemsMonitor = t.router({
reactors: t.router({
get: t.procedure
- .filter((publish: { shipId: number; systemId: number }, { ctx }) => {
+ .filter((publish: { shipId: number }, { ctx }) => {
if (publish && publish.shipId !== ctx.ship?.id) return false;
return true;
})
@@ -41,13 +41,14 @@ export const systemsMonitor = t.router({
maxHeat: r.components.heat!.maxHeat,
reserve,
fuel: r.components.isReactor!.unusedFuel.amount || 0,
+ efficiency: r.components.efficiency?.efficiency,
};
});
}),
}),
batteries: t.router({
get: t.procedure
- .filter((publish: { shipId: number; systemId: number }, { ctx }) => {
+ .filter((publish: { shipId: number }, { ctx }) => {
if (publish && publish.shipId !== ctx.ship?.id) return false;
return true;
})
@@ -56,6 +57,7 @@ export const systemsMonitor = t.router({
return batteries.map((b) => ({
id: b.id,
name: b.components.identity!.name,
+ desiredOutput: getPowerSupplierPowerNeeded(b),
capacity: b.components.isBattery!.capacity,
storage: b.components.isBattery!.storage,
chargeAmount: b.components.isBattery!.chargeAmount,
@@ -108,6 +110,98 @@ export const systemsMonitor = t.router({
return systems;
}),
+ removePowerSource: t.procedure
+ .input(
+ z.object({
+ systemId: z.number(),
+ powerSourceIndex: z.number(),
+ }),
+ )
+ .send(({ input, ctx }) => {
+ const system = ctx.flight?.ecs.getEntityById(input.systemId);
+
+ const shipId = system?.components.isShipSystem?.shipId;
+ if (!shipId) return;
+
+ if (system.components.power) {
+ const newPowerSources = [
+ ...(system?.components.power.powerSources || []),
+ ];
+ newPowerSources.splice(input.powerSourceIndex, 1);
+ system.updateComponent("power", {
+ powerSources: newPowerSources,
+ });
+ }
+ if (system.components.isBattery) {
+ const newPowerSources = [
+ ...(system?.components.isBattery.powerSources || []),
+ ];
+ newPowerSources.splice(input.powerSourceIndex, 1);
+ system.updateComponent("isBattery", {
+ powerSources: newPowerSources,
+ });
+ }
+
+ pubsub.publish.systemsMonitor.systems.get({ shipId });
+ pubsub.publish.systemsMonitor.reactors.get({ shipId });
+ pubsub.publish.systemsMonitor.batteries.get({ shipId });
+ }),
+ addPowerSource: t.procedure
+ .input(
+ z.object({
+ systemId: z.number(),
+ powerSourceId: z.number(),
+ }),
+ )
+ .send(({ input, ctx }) => {
+ const system = ctx.flight?.ecs.getEntityById(input.systemId);
+
+ const shipId = system?.components.isShipSystem?.shipId;
+ if (!shipId) return;
+
+ const powerSource = ctx.flight?.ecs.getEntityById(input.powerSourceId);
+ if (!powerSource)
+ throw new Error(
+ "Invalid power source. Power source must be a reactor or battery.",
+ );
+ const powerSupplied = getPowerSupplierPowerNeeded(powerSource);
+
+ if (
+ powerSource.components.isReactor &&
+ powerSource.components.isReactor?.maxOutput < powerSupplied + 1
+ ) {
+ throw new Error("Reactor is at maximum output.");
+ }
+ if (
+ powerSource.components.isBattery &&
+ powerSource.components.isBattery.outputRate < powerSupplied + 1
+ ) {
+ throw new Error("Battery is at maximum output.");
+ }
+
+ if (system.components.power) {
+ const newPowerSources = [
+ ...(system?.components.power?.powerSources || []),
+ input.powerSourceId,
+ ];
+
+ system.updateComponent("power", {
+ powerSources: newPowerSources,
+ });
+ } else if (system.components.isBattery) {
+ const newPowerSources = [
+ ...(system?.components.isBattery?.powerSources || []),
+ input.powerSourceId,
+ ].slice(0, system.components.isBattery.chargeRate);
+ system.updateComponent("isBattery", {
+ powerSources: newPowerSources,
+ });
+ }
+
+ pubsub.publish.systemsMonitor.systems.get({ shipId });
+ pubsub.publish.systemsMonitor.reactors.get({ shipId });
+ pubsub.publish.systemsMonitor.batteries.get({ shipId });
+ }),
}),
stream: t.procedure.dataStream(({ ctx, entity }) => {
if (!entity) return false;
diff --git a/client/app/cards/SystemsMonitor/index.tsx b/client/app/cards/SystemsMonitor/index.tsx
index bd27949e..2034a56e 100644
--- a/client/app/cards/SystemsMonitor/index.tsx
+++ b/client/app/cards/SystemsMonitor/index.tsx
@@ -1,8 +1,10 @@
import { q } from "@client/context/AppContext";
+import { toast } from "@client/context/ToastContext";
import useAnimationFrame from "@client/hooks/useAnimationFrame";
import type { CardProps } from "@client/routes/flight.station/CardProps";
import { cn } from "@client/utils/cn";
import { useLiveQuery } from "@thorium/live-query/client";
+import { LiveQueryError } from "@thorium/live-query/client/client";
import Button from "@thorium/ui/Button";
import { Icon } from "@thorium/ui/Icon";
import RadialDial from "@thorium/ui/RadialDial";
@@ -34,7 +36,13 @@ export function SystemsMonitor({ cardLoaded }: CardProps) {
number | null
>(null);
return (
-
+ // biome-ignore lint/a11y/useKeyWithClickEvents:
+ {
+ setSelectedPowerSupplier(null);
+ }}
+ >
{reactors.map((reactor, i) => (
r.id)}
/>
))}
@@ -92,6 +101,7 @@ function Reactor({
desiredOutput,
maxOutput,
optimalOutputPercent,
+ efficiency,
cardLoaded,
}: {
name: string;
@@ -107,6 +117,7 @@ function Reactor({
desiredOutput: number;
maxOutput: number;
optimalOutputPercent: number;
+ efficiency?: number;
cardLoaded: boolean;
}) {
const heatRef = useRef
(null);
@@ -122,11 +133,44 @@ function Reactor({
for (const [i, el] of elementRefs.current) {
if (i + 1 <= Math.ceil(currentOutput)) {
- el.classList.add("border-yellow-400");
- el.classList.remove("border-gray-400");
+ if (id === selectedPowerSupplier) {
+ el.classList.add("bg-green-400");
+ el.classList.remove("bg-gray-600", "bg-orange-700", "bg-yellow-400");
+ } else {
+ el.classList.add("bg-yellow-400");
+ el.classList.remove(
+ "bg-gray-600",
+ "bg-orange-700",
+ "bg-green-400",
+ "bg-green-600",
+ );
+ }
+ } else if (i + 1 <= Math.ceil(desiredOutput)) {
+ if (id === selectedPowerSupplier) {
+ el.classList.add("bg-green-600");
+ el.classList.remove(
+ "bg-gray-600",
+ "bg-orange-700",
+ "bg-yellow-400",
+ "bg-green-400",
+ );
+ } else {
+ el.classList.add("bg-orange-700");
+ el.classList.remove(
+ "bg-gray-600",
+ "bg-yellow-400",
+ "bg-green-400",
+ "bg-green-600",
+ );
+ }
} else {
- el.classList.add("border-gray-400");
- el.classList.remove("border-yellow-400");
+ el.classList.add("bg-gray-600");
+ el.classList.remove(
+ "bg-yellow-400",
+ "bg-orange-700",
+ "bg-green-400",
+ "bg-green-600",
+ );
}
}
@@ -143,14 +187,16 @@ function Reactor({
return (
setSelectedPowerSupplier(id)}
+ onClick={(e) => {
+ e.stopPropagation();
+ setSelectedPowerSupplier(id);
+ }}
onKeyDown={(e) => {
if (e.key === "Enter") {
setSelectedPowerSupplier(id);
}
}}
key={id}
- aria-expanded={selectedPowerSupplier === id}
className={cn(
"cursor-pointer text-left relative w-full grid grid-cols-[auto_1fr] items-center gap-x-2 p-2 panel panel-primary overflow-hidden group",
{
@@ -176,12 +222,25 @@ function Reactor({
+ {typeof efficiency === "number" ? (
+
+
+
+
+
+ ) : null}
@@ -208,19 +267,16 @@ function Reactor({
el && elementRefs.current.set(i, el)}
- className={cn(
- "w-3 h-3 mr-1 last-of-type:mr-0 border-2 bg-gray-500",
- {
- "mr-0": i + 1 === maxOutput * optimalOutputPercent,
- },
- )}
+ className={cn("w-3 h-3 mr-1 last-of-type:mr-0 bg-gray-500", {
+ "mr-0": i + 1 === maxOutput * optimalOutputPercent,
+ })}
/>
{i + 1 === maxOutput * optimalOutputPercent && (
-
+
)}
@@ -239,9 +295,11 @@ function Battery({
selectedPowerSupplier,
chargeRate,
outputRate,
+ desiredOutput,
capacity,
powerSources,
cardLoaded,
+ reactorIds,
}: {
id: number;
setSelectedPowerSupplier: Dispatch
>;
@@ -250,11 +308,14 @@ function Battery({
index: number;
chargeRate: number;
outputRate: number;
+ desiredOutput: number;
capacity: number;
powerSources: number[];
cardLoaded: boolean;
+ reactorIds: number[];
}) {
const chargeElementRefs = useRef