// Airlock logic. // // We don't use a Sequencer for Airlocks for a number of reasons. Most notably: // // * One door in each cycle needs to be both closed and then opened. // * The lights (and in the future, displays) need to emit signal information at several stages. // // We could add functionality to the Sequencer for these use cases, but right now we'll keep the logic separate, // even though *for the most part* it follows the same "Enumerator that waits for various bits of state" logic. using Sandbox.ModAPI.Ingame; using SpaceEngineers.Game.ModAPI.Ingame; using System.Collections.Generic; using VRage.Game.ModAPI.Ingame.Utilities; using VRageMath; namespace IngameScript { partial class Program { public class Airlock { enum AirlockLightState { Off, Cycling, Cooldown, Error, } public bool DoorsClosed { get { return _innerDoor.Status == DoorStatus.Closed && _outerDoor.Status == DoorStatus.Closed; } } // TODO: this should check for depressurizing to the *reference* amount public bool PressureStabilized { get { return ((_airVent.Depressurize && _airVent.GetOxygenLevel() <= _targetOxygenLevel) || (!_airVent.Depressurize && _airVent.Status == VentStatus.Pressurized)); } } public bool OxygenBalanced { get { return _oxygenTank.FilledRatio > 0.25; } } public bool Functional { get { return (_innerDoor != null && _outerDoor != null && _airVent != null && _oxygenTank != null); } } public bool Cycling { get; private set; } = false; public IMyAirVent ReferenceVent { get; set; } private bool DoorOpened { get { return _innerDoor.Status == DoorStatus.Open || _outerDoor.Status == DoorStatus.Open; } } private string _name; private PrefixedConsole _console; private MyIni _ini; private List _lights = new List(); private IMyDoor _innerDoor; private IMyDoor _outerDoor; private IMyGasTank _oxygenTank; private IMyAirVent _airVent; private float _targetOxygenLevel = 0.0F; private const int CooldownTicks = 12; private const int SealTimeoutTicks = 30; public Airlock(string name, Program _program) { _ini = _program.Ini; _name = name; _console = new PrefixedConsole(_program.Console, _name); } public void AddBlock(IMyTerminalBlock block) { if (block is IMyDoor) addDoor(block as IMyDoor); else if (block is IMyLightingBlock) _lights.Add(block as IMyLightingBlock); else if (block is IMyAirVent) addVent(block as IMyAirVent); else if (block is IMyGasTank) addOxygenTank(block as IMyGasTank); else _console.Print($"Tried to add invalid block '{block.CustomName}'"); } // Precondition: _ini.TryParse() should be called on the block before calling this function. private void addDoor(IMyDoor door) { if (_ini.Get("airlock", "doorPosition").ToString() == "inner" && _innerDoor == null) { _innerDoor = door; return; } if (_ini.Get("airlock", "doorPosition").ToString() == "outer" && _outerDoor == null) { _outerDoor = door; return; } _console.Print($"Couldn't add door '{door.CustomName}'"); } // Precondition: _ini.TryParse() should be called on the block before calling this function. private void addVent(IMyAirVent vent) { if (_airVent == null) { _airVent = vent; return; } _console.Print($"Couldn't add air vent '{vent.CustomName}'"); } private void addOxygenTank(IMyGasTank tank) { if (_oxygenTank == null) { _oxygenTank = tank; return; } _console.Print($"Couldn't add oxygen tank '{tank.CustomName}'"); } public IEnumerator CycleAirlock() { if (Cycling) yield break; Cycling = true; setLights(AirlockLightState.Cycling); closeDoors(); while (!DoorsClosed) { yield return true; } lockDoors(); int ticks = 0; while (!_airVent.CanPressurize && ticks < SealTimeoutTicks) { ticks++; yield return true; } if (!_airVent.CanPressurize) { error("Airlock is not airtight."); Cycling = false; yield break; } 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 int cooldown = 0; while (cooldown < CooldownTicks) { cooldown++; yield return true; } setLights(AirlockLightState.Off); _console.Print("Cycling Complete."); Cycling = false; } private void closeDoors() { _console.Print("Closing Doors"); // close the doors _innerDoor.Enabled = true; _outerDoor.Enabled = true; _innerDoor.CloseDoor(); _outerDoor.CloseDoor(); } private void pressurizeDepressurize() { _console.Print("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 && ReferenceVent != null) { _targetOxygenLevel = ReferenceVent.GetOxygenLevel(); _console.Print($"Set depressurization target to {_targetOxygenLevel}"); } } // Open the appropriate door based on pressure state. private void openDoor() { _console.Print($"Opening Door"); if (_airVent.Status == VentStatus.Pressurized) { _innerDoor.Enabled = true; _innerDoor.OpenDoor(); } else { _outerDoor.Enabled = true; _outerDoor.OpenDoor(); } } // 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 setDisplays(string text) { // foreach (IMyTextSurface display in _displays) { // display.WriteText(text); // } // } // 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; } _console.Print($"{_name}: Balancing Oxygen Tank"); // Configure the vent to suck in Oxygen. _airVent.Depressurize = true; _airVent.Enabled = true; return true; } private void lockDoors() { _innerDoor.Enabled = false; _outerDoor.Enabled = false; } private void error(string error) { _console.Print(error); setLights(AirlockLightState.Error); } } } }