space_engineers/airlock.cs
2025-02-06 19:02:13 -05:00

258 lines
6.9 KiB
C#

enum AirlockState {
Off,
DoorsClosing,
Cycling,
DoorOpening,
Cooldown,
}
enum AirlockLightState {
Off,
Cycling,
Cooldown,
Error,
}
string airlockName;
IEnumerator<bool> state;
float targetOxygenLevel = 0.0F;
// Blocks we need to track
IMyDoor innerDoor;
IMyDoor outerDoor;
List<IMyLightingBlock> lights;
IMyAirVent airVent;
IMyAirVent airSensor; // TODO: we don't use this yet
IMyGasTank oxygenTank;
public Program()
{
airlockName = Me.CustomName.Split(' ')[0];
state = null;
// Find the correct objects
InitDoors();
InitVents();
InitLights();
InitOxygen();
}
private void InitDoors() {
List<IMyDoor> onlyDoors = new List<IMyDoor>();
GridTerminalSystem.GetBlocksOfType(onlyDoors);
foreach (IMyDoor door in onlyDoors) {
if (door.CustomName.StartsWith(airlockName)) {
if (door.CustomName.Contains("Inner")) {
innerDoor = door;
}
else if (door.CustomName.Contains("Outer")) {
outerDoor = door;
}
}
if (innerDoor != null && outerDoor != null) {
break;
}
}
}
private void InitVents() {
List<IMyAirVent> onlyFans = new List<IMyAirVent>();
GridTerminalSystem.GetBlocksOfType(onlyFans);
foreach (IMyAirVent vent in onlyFans) {
if (vent.CustomName.StartsWith(airlockName)) {
if (vent.CustomName.Contains("Main")) {
airVent = vent;
}
else if (vent.CustomName.Contains("Reference")) {
airSensor = vent;
}
}
// A global reference vent will be used if we don't have one specific to our airlock.
// A specific vent found later will overwrite this assignment.
if(vent.CustomName.StartsWith("Airlock Reference") && airSensor == null) {
airSensor = vent;
}
}
}
private void InitLights() {
lights = new List<IMyLightingBlock>();
List<IMyLightingBlock> allLights = new List<IMyLightingBlock>();
GridTerminalSystem.GetBlocksOfType(allLights);
foreach (IMyLightingBlock light in allLights) {
if (light.CustomName.StartsWith(airlockName)) {
lights.Add(light);
}
}
}
private void InitOxygen() {
List<IMyGasTank> allTanks = new List<IMyGasTank>();
GridTerminalSystem.GetBlocksOfType(allTanks);
foreach (IMyGasTank tank in allTanks) {
if (tank.CustomName.StartsWith(airlockName)) {
oxygenTank = tank;
break;
}
}
}
public void Main(string argument, UpdateType updateSource)
{
if (state == null) {
state = CycleAirlock();
Runtime.UpdateFrequency = UpdateFrequency.Update1;
return;
}
if (!state.MoveNext()) {
state.Dispose();
state = null;
Runtime.UpdateFrequency = UpdateFrequency.None;
}
}
private IEnumerator<bool> CycleAirlock() {
SetLights(AirlockLightState.Cycling);
CloseDoors();
while(!DoorsClosed()) { yield return true; }
LockDoors();
if (!airVent.CanPressurize) {
Error("Airlock is not airtight.");
yield return false;
}
PressurizeDepressurize();
while(!PressureStabilized()) { yield return true; }
airVent.Enabled = false;
OpenDoor();
while(!DoorOpened()) { yield return true; }
LockDoors();
// Balance oxygen storage
SetLights(AirlockLightState.Cooldown);
if (BalanceOxygen()) {
while(!OxygenBalanced()) { yield return true; }
}
airVent.Enabled = false;
// Cooldown period
Runtime.UpdateFrequency = UpdateFrequency.Update100;
yield return true;
Runtime.UpdateFrequency = UpdateFrequency.Update1;
SetLights(AirlockLightState.Off);
}
private void CloseDoors() {
Echo("DEBUG: Closing Doors");
// close the doors
innerDoor.Enabled = true;
outerDoor.Enabled = true;
innerDoor.CloseDoor();
outerDoor.CloseDoor();
}
private bool DoorsClosed() {
return innerDoor.Status == DoorStatus.Closed && outerDoor.Status == DoorStatus.Closed;
}
private void PressurizeDepressurize() {
Echo("DEBUG: Cycling");
// toggle the current state
airVent.Depressurize = !airVent.Depressurize;
airVent.Enabled = true;
// When depressurizing, check the external pressure and only depressurize to that value.
// TODO: test this for floating point errors
if (airVent.Depressurize && airSensor != null) {
targetOxygenLevel = airSensor.GetOxygenLevel();
Echo($"Set depressurization target to {targetOxygenLevel}");
}
}
// TODO: this should check for depressurizing to the *reference* amount
private bool PressureStabilized() {
return ((airVent.Depressurize && airVent.GetOxygenLevel() <= targetOxygenLevel) ||
(!airVent.Depressurize && airVent.Status == VentStatus.Pressurized));
}
// Open the appropriate door based on pressure state.
private void OpenDoor() {
Echo("DEBUG: Opening Door");
if (airVent.Status == VentStatus.Pressurized) {
innerDoor.Enabled = true;
innerDoor.OpenDoor();
} else {
outerDoor.Enabled = true;
outerDoor.OpenDoor();
}
}
private bool DoorOpened() {
return innerDoor.Status == DoorStatus.Open || outerDoor.Status == DoorStatus.Open;
}
// Returns false if we are in a state where we can't or don't need to balance
private bool BalanceOxygen() {
if (innerDoor.Status == DoorStatus.Closed || outerDoor.Status == DoorStatus.Open || OxygenBalanced()) { return false; }
Echo("DEBUG: Balancing Oxygen Tank");
// Configure the vent to suck in Oxygen.
airVent.Depressurize = true;
airVent.Enabled = true;
return true;
}
private bool OxygenBalanced() {
return oxygenTank.FilledRatio > 0.25;
}
// TODO: blinkenlights are unsatisfying right now...
private void SetLights(AirlockLightState lightState) {
float blinkLength = 1.0F;
float blinkInterval = 0.0F;
Color color = Color.Red;
switch(lightState) {
case AirlockLightState.Off:
color = Color.Green;
break;
case AirlockLightState.Cycling:
blinkInterval = 1.0F;
blinkLength = 0.75F;
break;
case AirlockLightState.Cooldown:
color = Color.Yellow;
break;
case AirlockLightState.Error:
// the defaults already set this correctly
break;
}
foreach (IMyLightingBlock light in lights) {
light.Enabled = true;
light.BlinkIntervalSeconds = blinkInterval;
light.BlinkLength = blinkLength;
light.Color = color;
}
}
private void LockDoors() {
innerDoor.Enabled = false;
outerDoor.Enabled = false;
}
private void Error(string error) {
Echo(error);
SetLights(AirlockLightState.Error);
}