import type { Sound } from "@thorium/ecs-components/sound"; import { generateIncrementedName } from "@thorium/utils/generateIncrementedName"; import type { Kelvin, KelvinPerSecond, MegaWatt } from "@thorium/utils/unitTypes"; import z from "zod"; import type BasePlugin from ".."; import { Aspect } from "../Aspect"; import type { ShipSystemFlags, ShipSystemTypes } from "./shipSystemTypes"; const systemPlugins: Record = {}; export function registerSystem(name: string, sys: typeof BaseShipSystemPlugin) { systemPlugins[name] = sys; } export const baseShipSystemSchema = z.object({ name: z.string(), description: z.string(), tags: z.string().array(), allowMultiple: z.boolean(), powerLevels: z.number().array(), powerActivated: z.boolean(), connectedBatteryType: z.enum(["none", "capacity", "output", "median"]), powerToHeat: z.number(), heatDissipationRate: z.number(), nominalHeat: z.number(), maxSafeHeat: z.number(), maxHeat: z.number(), coolantVolume: z.number(), coolantTransferRate: z.number(), coolantConsumptionRate: z.number(), offlineEfficiency: z.number(), onlineEfficiency: z.number(), overloadDamageMultiplier: z.number(), minSignature: z.number(), maxSignature: z.number(), signatureSpike: z.number(), signatureSpikeDuration: z.number(), entropyMultiplier: z.number(), }); /** * The base class to use when creating system plugins * * Eventually this will include generic properties for power, heat, and efficiency */ export default class BaseShipSystemPlugin extends Aspect { static flags: ShipSystemFlags[] = ["damage", "heat", "power"]; static flightModes = ["nova", "legacy"]; apiVersion = "shipSystems/v1" as const; kind = "shipSystems" as const; name: string; description: string; type: keyof typeof ShipSystemTypes; tags: string[]; /** * Extend the subclass to include the specific images and sound effects * for this system */ assets: Record; soundEffects?: Record; allowMultiple: boolean; /////////// // Power // /////////// /** * The power levels for a system. If no entries are provided, the system will operate * without any power. The lowest entry is the minimum amount of power required to * make this system operate in megawatts. The highest is the maximum threshold of power * usage for safely using this system in megawatts **/ powerLevels: MegaWatt[]; /** Whether the system is connected to reactor power */ powerActivated: boolean; /** What type, of the available batteries, should this system connect itself to? */ connectedBatteryType: "none" | "capacity" | "output" | "median"; ////////// // Heat // ////////// /** * The rate at which heat can transfer in or out. * Heat generated by this entity isn't bound by this number */ powerToHeat: KelvinPerSecond; /** * The standard heat level. When plotted, this * represents the very bottom of the heat bar. */ nominalHeat: Kelvin; /** * The temperature at which this system starts experiencing * efficiency decreases due to overheating. */ maxSafeHeat: Kelvin; /** * The maximum possible temperature. Represents the very top * of the heat bar graph. */ maxHeat: Kelvin; /** * How much volume the coolant takes up in this system in m^3. * This affects both how fast systems heat up and how quickly * they cool down when attached to the coolant loop. **/ coolantVolume: number; coolantTransferRate: number; coolantConsumptionRate: number; //////////// // Damage // //////////// offlineEfficiency: number; onlineEfficiency: number; overloadDamageMultiplier: number; minSignature: number; maxSignature: number; signatureSpike: number; signatureSpikeDuration: number; entropyMultiplier: number; constructor(params: Partial, plugin: BasePlugin) { const name = generateIncrementedName( params.name || `New ${params.type}`, plugin.aspects.shipSystems.map((sys) => sys.name), ); super({ name, ...params }, { kind: "shipSystems" }, plugin); this.name = name || ""; this.type = params.type || "generic"; this.description = params.description || ""; this.tags = params.tags || []; this.assets = params.assets || {}; this.soundEffects = params.soundEffects; this.allowMultiple = params.allowMultiple ?? false; this.powerLevels = params.powerLevels || [5, 20]; this.powerActivated = params.powerActivated ?? true; this.connectedBatteryType = params.connectedBatteryType || "none"; this.powerToHeat = params.powerToHeat || 0.1; this.nominalHeat = params.nominalHeat || 295.37; this.maxSafeHeat = params.maxSafeHeat || 1000; this.maxHeat = params.maxHeat || 2500; this.coolantVolume = params.coolantVolume || 100; this.offlineEfficiency = params.offlineEfficiency || 0.5; this.onlineEfficiency = params.onlineEfficiency || 0.8; this.overloadDamageMultiplier = params.overloadDamageMultiplier || 0.015; this.minSignature = params.minSignature || 0; this.maxSignature = params.maxSignature || 1; this.signatureSpike = params.signatureSpike || 2; this.signatureSpikeDuration = params.signatureSpikeDuration || 5; // This provides about 15% damage to all systems over the course of a 2.5 hour mission this.entropyMultiplier = params.entropyMultiplier || 0.00025; this.coolantTransferRate = params.coolantTransferRate || 1; this.coolantConsumptionRate = params.coolantConsumptionRate || 1; if (this.constructor.name === "BaseShipSystemPlugin") { if (systemPlugins[this.type]) { return new systemPlugins[this.type](params, plugin); } } } }