Skip to content

Instantly share code, notes, and snippets.

@jimfinnis
Last active November 18, 2025 10:58
Show Gist options
  • Select an option

  • Save jimfinnis/acb2a9ba8b7cb0f7d68356a5dc41b1c3 to your computer and use it in GitHub Desktop.

Select an option

Save jimfinnis/acb2a9ba8b7cb0f7d68356a5dc41b1c3 to your computer and use it in GitHub Desktop.
// 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