Last active
November 18, 2025 10:58
-
-
Save jimfinnis/acb2a9ba8b7cb0f7d68356a5dc41b1c3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // The door name. Inner door, outer door and airvent must have this in the name. | |
| // Doors must also have [OUTER] and [INNER] also, and the displays must have [DISP] | |
| private string doorName = "vingdoor"; | |
| private const bool DISABLE_VENT = true; // if true, disable air vent when not using airlock | |
| private int tick=-1; // how long we have been in the current state; -1 nonsense value at start. | |
| private const float HIGH = 0.95f; // high pressure threshold | |
| private const float LOW = 0.05f; // low pressure threshold | |
| public Program() // empty, this system runs in the Main function. It's a Space Engineers thing. | |
| { | |
| } | |
| // Display some text on components with [DISP] in them (if they can do that) | |
| private void ShowText(string s){ | |
| var tmp = new List<IMyTerminalBlock>(); | |
| GridTerminalSystem.GetBlocksOfType(tmp, x => x.CubeGrid == Me.CubeGrid && | |
| x.CustomName.Contains(doorName) && | |
| x.CustomName.Contains("[DISP]") && | |
| x is IMyTextSurfaceProvider); | |
| foreach (IMyTerminalBlock b in tmp) | |
| { | |
| var p = (b as IMyTextSurfaceProvider).GetSurface(0); | |
| p.ContentType = ContentType.TEXT_AND_IMAGE; | |
| p.WriteText(s, false); | |
| } | |
| } | |
| // run a function on all inner doors | |
| private void onAllInnerDoors(Action<IMyDoor> a){ | |
| var tmp = new List<IMyDoor>(); | |
| GridTerminalSystem.GetBlocksOfType(tmp,x=>x.CubeGrid == Me.CubeGrid && | |
| x.CustomName.Contains(doorName) && | |
| x.CustomName.Contains("[INNER]")); | |
| tmp.ForEach(x=>a(x)); | |
| } | |
| // run a function on all outer doors | |
| private void onAllOuterDoors(Action<IMyDoor> a){ | |
| var tmp = new List<IMyDoor>(); | |
| GridTerminalSystem.GetBlocksOfType(tmp,x=>x.CubeGrid == Me.CubeGrid && | |
| x.CustomName.Contains(doorName) && | |
| x.CustomName.Contains("[OUTER]")); | |
| tmp.ForEach(x=>a(x)); | |
| } | |
| // check if a function which returns boolean is true when run on all doors | |
| private bool checkAllDoorsTrue(Func<IMyDoor,bool> p){ | |
| var tmp = new List<IMyDoor>(); | |
| GridTerminalSystem.GetBlocksOfType(tmp,x=>x.CubeGrid == Me.CubeGrid && | |
| x.CustomName.Contains(doorName)); | |
| bool alltrue=true; | |
| foreach(var x in tmp){ | |
| if(!p(x)){ | |
| alltrue = false; | |
| } | |
| } | |
| return alltrue; | |
| } | |
| // Start closing a door (it can take a little bit of time), but first make | |
| // sure the door is actually turned on. | |
| private void closeDoor(IMyDoor d){ | |
| if(d.Status == DoorStatus.Open){ | |
| d.Enabled = true; | |
| d.CloseDoor(); | |
| } | |
| } | |
| // These are the states | |
| enum State { | |
| IDLE, // default state, not doing anything | |
| WAIT, // unused! | |
| CLOSING, // Waiting for doors to close | |
| PRESSURISING, // Pressurising - waiting for pressure to go high | |
| DEPRESSURISING, // Depressurising - waiting for pressure to go low, or a timeout | |
| DEPRESS_FAILED // Depressurise failed (timeout) - wait 10 ticks and go to idle. | |
| } | |
| // return a very brief string for the state | |
| private String getStateString(State s){ | |
| switch(s) { | |
| case State.IDLE: return "IDLE"; | |
| case State.WAIT: return "WAIT"; | |
| case State.CLOSING: return "CLOSE"; | |
| case State.PRESSURISING: return "PRES"; | |
| case State.DEPRESSURISING: return "DEPR"; | |
| case State.DEPRESS_FAILED: return "DFAIL!"; | |
| default: return "????"; | |
| } | |
| } | |
| State state = State.IDLE; // set default initial state | |
| // go to a given state, telling the system to start updating itself every 100 ticks | |
| // if the state is not IDLE. And zero the tick counter, which says how long we're in | |
| // the current state | |
| private void gotoState(State s){ | |
| Echo($"Goto state {s}"); | |
| ShowText($"{getStateString(s)}"); | |
| if(s != State.IDLE) | |
| Runtime.UpdateFrequency = UpdateFrequency.Update100; | |
| else | |
| Runtime.UpdateFrequency = UpdateFrequency.None; | |
| state = s; | |
| tick=0; | |
| } | |
| public void Main(string argument, UpdateType updateSource) | |
| { | |
| // first get access to the data we need | |
| var tmp = new List<IMyAirVent>(); | |
| GridTerminalSystem.GetBlocksOfType(tmp,x=>x.CubeGrid == Me.CubeGrid && x.CustomName.Contains(doorName)); | |
| if(tmp.Count==0){ | |
| Echo($"No air vent with name containing {doorName}"); | |
| return; | |
| } | |
| var airVent = tmp[0]; | |
| float lev = airVent.GetOxygenLevel(); // 0 to 1 | |
| Echo($"Tick={tick}, vent={airVent.Status} lev={lev}"); | |
| if(updateSource == UpdateType.Update100){ // the code in here runs every 100 ticks in non-idle states | |
| Echo($"state {state} time {tick}"); | |
| switch(state){ | |
| case State.IDLE: | |
| break; | |
| case State.CLOSING: | |
| // doors are closing. Once closed, disable them and check pressure. | |
| if(checkAllDoorsTrue(x=>x.Status==DoorStatus.Closed)) { | |
| onAllOuterDoors(x=>x.Enabled=false); // these two lines turn off all doors so player can't open them | |
| onAllInnerDoors(x=>x.Enabled=false); | |
| // if pressure low or high, start pressurising or depressurising | |
| if(lev<0.5){ | |
| airVent.Depressurize = false; // pressure low, start pressurising | |
| gotoState(State.PRESSURISING); | |
| } else if(lev>0.5){ | |
| airVent.Depressurize = true; // pressure high, start depressurising | |
| gotoState(State.DEPRESSURISING); | |
| } | |
| } | |
| break; | |
| case State.PRESSURISING: | |
| if(lev>HIGH){ | |
| // pressure level is now high, turn on the inner doors so we can get inside and | |
| // turn off the air vent (they use a lot of power). Finally go to the idle state; | |
| // we're done. | |
| onAllInnerDoors(x=>x.Enabled=true); | |
| if(DISABLE_VENT)airVent.Enabled=false; | |
| gotoState(State.IDLE); | |
| } | |
| break; | |
| case State.DEPRESSURISING: | |
| if(tick>10){ | |
| // may not be able to depressurise due to full O2 tanks. Enable the outer doors | |
| // anyway - O2 tanks are full, we can go outside without losing much. | |
| onAllOuterDoors(x=>x.Enabled=true); | |
| // goto a state which will tell the user what's happened. | |
| gotoState(State.DEPRESS_FAILED); | |
| } | |
| else if(lev<LOW){ | |
| // depressurising was successful - turn on the outer doors so we can leave, disable | |
| // the power-hungry airlock and goto the IDLE state. | |
| onAllOuterDoors(x=>x.Enabled=true); | |
| if(DISABLE_VENT)airVent.Enabled=false; | |
| gotoState(State.IDLE); | |
| } | |
| break; | |
| case State.DEPRESS_FAILED: | |
| // this state is really only here for information - the user needs to see that | |
| // the depressure failed, so we have a dummy state. After 10 ticks, or if the | |
| // depressure somehow succeeded, we go to the IDLE state - there's nothing more | |
| // we can do. | |
| if(tick>10 || lev<LOW){ | |
| if(DISABLE_VENT)airVent.Enabled=false; | |
| gotoState(State.IDLE); | |
| } | |
| break; | |
| } | |
| // increment tick count so we know how long we've been in the state. | |
| tick++; | |
| } else { | |
| // This code runs when Main is called without an Update100 - in other words, when we pushed | |
| // the airlock button. Start closing all doors, turn on the air vent, and go to the state | |
| // that waits for the doors to be closed. | |
| onAllOuterDoors(closeDoor); | |
| onAllInnerDoors(closeDoor); | |
| if(DISABLE_VENT)airVent.Enabled=true; | |
| gotoState(State.CLOSING); | |
| } | |
| } | |
| // Mermaid state diagram - plug into mermaid.live to render it. | |
| /* | |
| stateDiagram-v2 | |
| classDef action fill:white,stroke:none | |
| [*] --> CloseAllDoors ::: action | |
| CloseAllDoors : close all doors, enable air vent | |
| CloseAllDoors --> Closing | |
| state check_pressure <<choice>> | |
| Closing --> DisableAllDoors:::action : closed | |
| DisableAllDoors : disable all doors | |
| DisableAllDoors --> check_pressure | |
| check_pressure --> Pressurise:::action : pressure<0.5 | |
| check_pressure --> Depressurise:::action : pressure>0.5 | |
| Pressurise --> Pressurising | |
| Depressurise --> Depressurising | |
| Pressurising --> EnableInnerDoors:::action : pressure>99.7 | |
| Depressurising --> EnableOuterDoors:::action : pressure<0.03 | |
| class EnableOuterDoors2 action | |
| EnableOuterDoors2 : EnableOuterDoors | |
| Depressurising --> EnableOuterDoors2 : tick>10 | |
| EnableOuterDoors2 --> DepressFailed | |
| DepressFailed --> DisableVent:::action : tick>10 | |
| DisableVent : disable air vent | |
| EnableInnerDoors --> DisableVent | |
| EnableOuterDoors --> DisableVent | |
| DisableVent --> [*] | |
| */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment