Skip to content

Commit

Permalink
feat: Model and simulate the transfer of heat.
Browse files Browse the repository at this point in the history
Heat is generated in the reactor, transferred to
coolant, and then radiated into space through three
separate systems
  • Loading branch information
alexanderson1993 committed Feb 2, 2023
1 parent 285da4b commit 04462f4
Show file tree
Hide file tree
Showing 20 changed files with 746 additions and 48 deletions.
1 change: 0 additions & 1 deletion client/src/cards/Pilot/ImpulseControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,6 @@ export const ImpulseControls = ({cardLoaded = true}) => {

useGamepadValue("impulse-speed", value => {
const throttleValue = (value + 1) / 2;
console.log(value);
callback.current(throttleValue * cruisingSpeed);
});

Expand Down
103 changes: 80 additions & 23 deletions client/src/pages/Config/Inventory/InventoryLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {InventoryFlagValues} from "server/src/classes/Plugins/Inventory/Inventor
import {capitalCase} from "capital-case";
import InfoTip from "@thorium/ui/InfoTip";
import {q} from "@client/context/AppContext";
import {FaPencilAlt} from "react-icons/fa";
import {Popover, Transition} from "@headlessui/react";

export const InventoryLayout = () => {
const {inventoryId, pluginId} = useParams() as {
Expand Down Expand Up @@ -223,31 +225,86 @@ export const InventoryLayout = () => {
<label>Inventory Type</label>
{Object.entries(InventoryFlagValues).map(([key, value]) => {
let defaultValue = key in item.flags;
let keyVal = key as keyof typeof InventoryFlagValues;
const flagKey = key as keyof typeof InventoryFlagValues;
return (
<>
<Checkbox
key={key}
type="radio"
name="flags"
defaultChecked={defaultValue}
onChange={e => {
q.plugin.inventory.update.netSend({
pluginId,
inventoryId,
flags: {
...{[key]: e.target.checked ? {} : undefined},
},
});
}}
label={
<span className="flex items-center cursor-pointer">
{capitalCase(key)}
<InfoTip>{value.info}</InfoTip>
</span>
}
/>
{/* TODO July 1 2022 - Figure out some way to configure the properties of inventory flags without it being totally nonsensical*/}
<div className="flex items-center gap-1">
<Checkbox
key={key}
name="flags"
defaultChecked={defaultValue}
onChange={e => {
q.plugin.inventory.update.netSend({
pluginId,
inventoryId,
flags: {
...{
...item.flags,
[key]: e.target.checked ? {} : undefined,
},
},
});
}}
label={capitalCase(key)}
/>
{defaultValue &&
Object.keys(value).filter(t => t !== "info").length > 0 ? (
<Popover className="relative">
<Popover.Button
as={Button}
className="btn-xs btn-warning btn-outline"
>
<FaPencilAlt />
</Popover.Button>
<Transition
enter="transition duration-100 ease-out"
enterFrom="transform scale-95 opacity-0"
enterTo="transform scale-100 opacity-100"
leave="transition duration-75 ease-out"
leaveFrom="transform scale-100 opacity-100"
leaveTo="transform scale-95 opacity-0"
>
<Popover.Panel className="absolute right-0 z-10 bg-black/90 border border-white/50 rounded p-2 w-max max-w-lg">
{Object.entries(value).map(([config, value]) => {
if (config === "info") return null;
return (
<Input
key={config}
label={capitalCase(config)}
type="text"
inputMode="numeric"
pattern="[0-9]*"
defaultValue={
// @ts-expect-error Pain to type these literal keys
item.flags[flagKey]?.[config] ??
value.defaultValue
}
helperText={value.info}
onChange={e => {
if (isNaN(Number(e.target.value))) return;
q.plugin.inventory.update.netSend({
pluginId,
inventoryId,
flags: {
...{
...item.flags,
[key]: {
...item.flags[flagKey],
[config]: Number(e.target.value),
},
},
},
});
}}
/>
);
})}
</Popover.Panel>
</Transition>
</Popover>
) : null}
<InfoTip>{value.info}</InfoTip>
</div>
</>
);
})}
Expand Down
10 changes: 5 additions & 5 deletions client/src/pages/Config/ShipSystems/Heat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,25 +32,25 @@ export function Heat() {
<div className="pb-4 flex">
<Input
labelHidden={false}
label="Coolant Heat Transfer Rate"
helperText="The rate at which heat can transfer from the system to coolant stored the room, in Kelvin / second."
label="Power to Heat Ratio"
helperText="The percentage of power which passes through the system which is turned into heat."
type="text"
inputMode="numeric"
pattern="[0-9]*"
defaultValue={system.coolantHeatTransferRate}
defaultValue={system.powerToHeat}
onBlur={(e: any) => {
if (isNaN(Number(e.target.value))) return;
q.plugin.systems.update.netSend({
pluginId,
systemId: systemId,
shipId,
shipPluginId,
coolantHeatTransferRate: Number(e.target.value),
powerToHeat: Number(e.target.value),
});
}}
/>
<OverrideResetButton
property="coolantHeatTransferRate"
property="powerToHeat"
setRekey={setRekey}
className="mt-6"
/>
Expand Down
6 changes: 3 additions & 3 deletions client/src/pages/Config/data/systems/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export const systems = t.router({
requiredPower: z.number().optional(),
defaultPower: z.number().optional(),
maxSafePower: z.number().optional(),
coolantHeatTransferRate: z.number().optional(),
powerToHeat: z.number().optional(),
heatDissipationRate: z.number().optional(),
nominalHeat: z.number().optional(),
maxSafeHeat: z.number().optional(),
Expand Down Expand Up @@ -157,8 +157,8 @@ export const systems = t.router({
if (typeof input.maxSafePower === "number") {
shipSystem.maxSafePower = input.maxSafePower;
}
if (typeof input.coolantHeatTransferRate === "number") {
shipSystem.coolantHeatTransferRate = input.coolantHeatTransferRate;
if (typeof input.powerToHeat === "number") {
shipSystem.powerToHeat = input.powerToHeat;
}
if (typeof input.heatDissipationRate === "number") {
shipSystem.heatDissipationRate = input.heatDissipationRate;
Expand Down
21 changes: 19 additions & 2 deletions server/src/classes/Plugins/Inventory/InventoryFlags.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import {KiloWattHour, MegaWattHour} from "server/src/utils/unitTypes";
import {
HeatCapacity,
Kilograms,
KiloWattHour,
MegaWattHour,
} from "server/src/utils/unitTypes";

/**
* Jumping the gun a bit of this, but I figure it's helpful to have context
Expand All @@ -25,7 +30,10 @@ export type InventoryFlags = Partial<{
/** How much power is released from one unit of fuel */
fuelDensity: MegaWattHour;
};
coolant: {};
coolant: {
heatCapacity: HeatCapacity;
massPerUnit: Kilograms;
};
// TODO July 1, 2022 - Could be interesting to put torpedo movement properties on the torpedo casing. Max speed, acceleration, turn speed, etc.
torpedoCasing: {};
// TODO July 1, 2022 - Put the damage yield, and perhaps the damage type, here.
Expand Down Expand Up @@ -66,6 +74,15 @@ export const InventoryFlagValues: {
},
coolant: {
info: "Cools down things that get hot, like the reactor, engines, and weapons.",
heatCapacity: {
defaultValue: 4.18,
info: "How quickly this coolant heats or cools.",
},
massPerUnit: {
// The mass of 1 m^3 of water.
defaultValue: 1000,
info: "The mass of one unit (1 m^3) of the coolant. Used with the heat capacity to determine the heat rate.",
},
},
forCrew: {info: "Indicates that this item can be equipped by a crew member."},
science: {
Expand Down
4 changes: 2 additions & 2 deletions server/src/classes/Plugins/ShipSystems/BaseSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export default class BaseShipSystemPlugin extends Aspect {
* The rate at which heat can transfer in or out.
* Heat generated by this entity isn't bound by this number
*/
coolantHeatTransferRate: KelvinPerSecond;
powerToHeat: KelvinPerSecond;

/**
* The effectiveness of transferring heat into space. A multiplier
Expand Down Expand Up @@ -91,7 +91,7 @@ export default class BaseShipSystemPlugin extends Aspect {
this.requiredPower = params.requiredPower || 5;
this.defaultPower = params.defaultPower || 10;
this.maxSafePower = params.maxSafePower || 20;
this.coolantHeatTransferRate = params.coolantHeatTransferRate || 10;
this.powerToHeat = params.powerToHeat || 10;
this.heatDissipationRate = params.heatDissipationRate || 1;
this.nominalHeat = params.nominalHeat || 295.37;
this.maxSafeHeat = params.maxSafeHeat || 1000;
Expand Down
6 changes: 3 additions & 3 deletions server/src/components/heat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ export class HeatComponent extends Component {
heat: Kelvin = 295.37;

/**
* The rate at which heat can transfer to or from coolant.
* Heat generated by this entity isn't bound by this number
* The percentage of power that passes through the system which is turned
* into heat.
*/
coolantHeatTransferRate: KelvinPerSecond = 10;
powerToHeat: number = 0.01;

/**
* The effectiveness of transferring heat into space. A multiplier
Expand Down
5 changes: 2 additions & 3 deletions server/src/spawners/shipSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function spawnShipSystem(
entity.addComponent(componentName as ComponentIDs, template);

const {
coolantHeatTransferRate,
powerToHeat,
heatDissipationRate,
maxHeat,
maxSafeHeat,
Expand All @@ -47,8 +47,7 @@ export function spawnShipSystem(
} = systemPlugin;
if (flags.includes("heat"))
entity.addComponent("heat", {
coolantHeatTransferRate:
overrides.coolantHeatTransferRate || coolantHeatTransferRate,
powerToHeat: overrides.powerToHeat || powerToHeat,
heatDissipationRate:
overrides.heatDissipationRate || heatDissipationRate,
maxHeat: overrides.maxHeat || maxHeat,
Expand Down
54 changes: 54 additions & 0 deletions server/src/systems/HeatDispersionSystem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {getSystemInventory} from "@server/utils/getSystemInventory";
import {MeterSquared, StephanBoltzmannConstant} from "@server/utils/unitTypes";
import {Entity, System} from "../utils/ecs";

// W = A * a * T^5
// W = Watts
// A = area of radiator
// σ = Stefan-Boltzmann constant
// T^4 = radiator temperature
// Increasing it by another power as
// space magic.

// For transferring the heat of the coolant
// into watts
// 𝚫T = (W * 𝚫t) / (c * m)
// 𝚫t = change in time
// c = specific heat
// m = mass
// 𝚫T = change in temperature

// Specific heat is in J/gK, 1J = 1 wattsecond

const RADIATOR_AREA: MeterSquared = 1;

export class HeatDispersionSystem extends System {
test(entity: Entity) {
return !!entity.components.heat;
}
update(entity: Entity, elapsed: number) {
const elapsedInSeconds = elapsed / 1000;
if (!entity.components.heat) return;
const inventory = getSystemInventory(entity) || [];
// Radiate the heat of the coolant into space
for (let item of inventory) {
if (!item.flags?.coolant) continue;
const temp = item.temperature;
const wattsDispersed =
RADIATOR_AREA * StephanBoltzmannConstant * temp ** 5;

const tempDrop =
(wattsDispersed * elapsedInSeconds) /
(item.flags.coolant.heatCapacity *
item.flags.coolant.massPerUnit *
1000 *
item.count);
if (item.room) {
item.room.contents[item.name].temperature = Math.max(
2.7,
temp - tempDrop
);
}
}
}
}
Loading

0 comments on commit 04462f4

Please sign in to comment.