Last active
March 27, 2026 15:18
-
-
Save leylaso/683d3fc602af753354c197c26d8ecb64 to your computer and use it in GitHub Desktop.
Simple airlock script for paired doors in Space Engineers
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
| /******************************************************************************* | |
| LEY LA'S AUTOMATIC AIRLOCKS | |
| v 0.2 | |
| This script is intended to be run on pairs of doors with the naming | |
| convention: | |
| [prefix] LSAS Inner | |
| or: | |
| [prefix] LSAS Outer | |
| [prefix] should be unique to each pair of doors and need not include square | |
| brackets. The script will use LSAS Inner and LSAS Outer to find their | |
| respective doors. These may be configured in Custom Data. | |
| This script is configured to run every 10 ticks. It may still be possible | |
| to lock the script by opening one of the automatic doors during a brief window | |
| when they are enabled during processing. | |
| So don't do that. | |
| *******************************************************************************/ | |
| // LSAS Debug Class Definition | |
| // DELETE THIS before publication | |
| public static class LSASDebug | |
| { | |
| // Debug door closures | |
| public static void CloseDoor(IMyDoor door, string append = "") | |
| { | |
| LSASLog.Line("Closing door: " + door.CustomName + append); | |
| door.CloseDoor(); | |
| } | |
| } | |
| // LSAS Log Class Definition | |
| public static class LSASLog | |
| { | |
| public static string TheLog = ""; | |
| public static bool Enabled = true; | |
| public static bool Reset() | |
| { | |
| TheLog = ""; | |
| return Enabled; | |
| } | |
| public static bool Start() | |
| { | |
| if (Enabled) | |
| TheLog = System.Environment.NewLine + DateTime.Now.ToString() + System.Environment.NewLine; | |
| return Enabled; | |
| } | |
| public static bool Line(string logLine, string ind=" ") | |
| { | |
| if (Enabled) | |
| TheLog += ind + logLine + System.Environment.NewLine; | |
| return Enabled; | |
| } | |
| public static string Print() | |
| { | |
| return TheLog; | |
| } | |
| } | |
| // LSASTimer Class Def | |
| public class LSASTimer | |
| { | |
| public int Val; | |
| private static int LockTime = 30; | |
| private static int DisableTime = 29; | |
| private static int HalfTime = 23; | |
| private static int EnableTime = 17; | |
| private static int TimeBuffer = 5; | |
| // Constructor | |
| public LSASTimer (int val = 0) | |
| { | |
| Val = val; | |
| } | |
| // Set lock time values | |
| public static int SetLockTime (int val) | |
| { | |
| LockTime = val+TimeBuffer; | |
| DisableTime = (val/5*4)+TimeBuffer; | |
| HalfTime = (val/5*3)+TimeBuffer; | |
| EnableTime = (val/5*2)+TimeBuffer; | |
| return LockTime; | |
| } | |
| // Set the timer if it is valid | |
| public int Timer (int val = -1) | |
| { | |
| if (val > -1) | |
| Val = val; | |
| return Val; | |
| } | |
| // Return LockTime | |
| public static int Lock() | |
| { | |
| return LockTime; | |
| } | |
| // Return true if it is disable time | |
| public bool Disable() | |
| { | |
| return (Val == DisableTime); | |
| } | |
| // Return true if we are before halftime | |
| public bool FirstHalf() | |
| { | |
| return (Val > HalfTime); | |
| } | |
| // Return true if it is halfTime | |
| public bool Half() | |
| { | |
| return (Val == HalfTime); | |
| } | |
| // Return true if it is before enable time | |
| public bool BeforeEnable() | |
| { | |
| return (Val > EnableTime); | |
| } | |
| // Return true if it is enable time | |
| public bool Enable() | |
| { | |
| return (Val == EnableTime); | |
| } | |
| // Return TimeBuffer | |
| public int Buffer() | |
| { | |
| return TimeBuffer; | |
| } | |
| // Helper Function to get the timer status | |
| public string TimerState() | |
| { | |
| string Out = "Timer is now " + Val; | |
| Out += System.Environment.NewLine + " Half time at " + HalfTime; | |
| Out += System.Environment.NewLine + " Enable time at " + EnableTime; | |
| return Out; | |
| } | |
| // Tick the timer | |
| public int Tick() | |
| { | |
| // First call on new timer | |
| if (Val == 0) | |
| { | |
| Val = LockTime; | |
| } | |
| else | |
| { | |
| Val -= 1; | |
| } | |
| return Val; | |
| } | |
| // return the timer value when called as an int | |
| public static implicit operator int (LSASTimer t) | |
| { | |
| return t.Val; | |
| } | |
| // Overload ToString method | |
| public override string ToString() | |
| { | |
| return Val.ToString(); | |
| } | |
| } | |
| // LSAS Class Definition | |
| public class LSAS | |
| { | |
| public static string InnerTerm = "LSAS Inner"; | |
| public static string OuterTerm = "LSAS Outer"; | |
| private LSASTimer LockTimer; | |
| public IMyDoor Inner; | |
| public IMyDoor Outer; | |
| public string Group; | |
| private IMyDoor Priority = null; | |
| // Default Constructor | |
| public LSAS () | |
| { | |
| Group = null; | |
| Inner = null; | |
| Outer = null; | |
| LockTimer = new LSASTimer(); | |
| } | |
| // Constructor with group value | |
| public LSAS (string grouped) | |
| { | |
| Group = grouped; | |
| Inner = null; | |
| Outer = null; | |
| LockTimer = new LSASTimer(); | |
| } | |
| // Safe way to set lock time respecting minimum value | |
| public static int SetLockTime (int val = 30) | |
| { | |
| if (val < 30) | |
| val = 30; | |
| return LSASTimer.SetLockTime(val); | |
| } | |
| // Parser | |
| public static string[] Parse (IMyDoor door, char sep = ' ') | |
| { | |
| return door.CustomName.Split(sep); | |
| } | |
| // Helper to check for inner or outer door | |
| private static bool CheckDoor (IMyDoor door, string searchTerm) | |
| { | |
| return (door != null && door.CustomName.Contains(searchTerm)); | |
| } | |
| // Helper to check for valid airlock before doing anything to it | |
| private bool ValidSAS () | |
| { | |
| if (Inner == null) | |
| throw new Exception("No inner door"); | |
| else if (Outer == null) | |
| throw new Exception("No outer door"); | |
| else if (Outer.Status != DoorStatus.Closed && Inner.Status != DoorStatus.Closed) | |
| throw new Exception("Both doors are open in " + Group); | |
| else | |
| return true; | |
| } | |
| // Check for outer door | |
| public static bool IsOuter (IMyDoor door) | |
| { | |
| return CheckDoor(door, OuterTerm); | |
| } | |
| // Check for inner door | |
| public static bool IsInner (IMyDoor door) | |
| { | |
| return CheckDoor(door, InnerTerm); | |
| } | |
| // Get a valid group or null on an invalid group | |
| public static string GetGroup (IMyDoor door) | |
| { | |
| string[] Parsed = Parse(door); | |
| if (Parsed[0] == null || InnerTerm.Contains(Parsed[0]) || OuterTerm.Contains(Parsed[0])) | |
| return null; | |
| else | |
| return Parsed[0]; | |
| } | |
| // Safely add doors | |
| public bool AddDoor (IMyDoor door, string grouped) | |
| { | |
| if (grouped != Group) | |
| return false; | |
| if (IsInner(door)) | |
| { | |
| Inner = door; | |
| return true; | |
| } | |
| if (IsOuter(door)) | |
| { | |
| Outer = door; | |
| return true; | |
| } | |
| return false; | |
| } | |
| // Helper method to never disable doors that are not fully open or closed | |
| private bool SafeDisable (IMyDoor door, DoorStatus state = DoorStatus.Closed) | |
| { | |
| if (door.Status == state) | |
| door.Enabled = false; | |
| else | |
| door.Enabled = true; | |
| return door.Enabled; | |
| } | |
| // General paired door handling logic | |
| private bool HandleDoorPair (IMyDoor observed, IMyDoor affected) | |
| { | |
| // no action is needed if the door is closed and timer has run out | |
| if (observed.Status == DoorStatus.Closed && LockTimer == 0) | |
| { | |
| observed.Enabled = true; | |
| affected.Enabled = true; | |
| return false; | |
| } | |
| else | |
| { | |
| // Give the observed door priority if it is available | |
| if (Priority == null) | |
| Priority = observed; | |
| // Otherwise check the observed door has priority | |
| else if (Priority == observed) | |
| { | |
| LockTimer.Tick(); | |
| if (LockTimer == LSASTimer.Lock()) | |
| affected.CloseDoor(); // First close the opposite door if it is open | |
| else if (LockTimer.FirstHalf()) // Disable both doors | |
| { | |
| SafeDisable(affected); | |
| SafeDisable(observed, DoorStatus.Open); | |
| } | |
| else if (LockTimer.Half()) // Close the original door | |
| { | |
| observed.Enabled = true; | |
| observed.CloseDoor(); | |
| } | |
| else if (LockTimer.BeforeEnable()) // Disable both doors | |
| { | |
| SafeDisable(affected); | |
| SafeDisable(observed); | |
| } | |
| else if (LockTimer.Enable()) // Open the opposite door | |
| { | |
| //LSASLog.Line("Timer is Enable"); | |
| affected.Enabled = true; | |
| affected.OpenDoor(); | |
| } | |
| else if (LockTimer > LockTimer.Buffer()) // Disable both doors | |
| { | |
| //LSASLog.Line("Timer > Buffer"); | |
| SafeDisable(affected, DoorStatus.Open); | |
| SafeDisable(observed); | |
| } | |
| else if (LockTimer == LockTimer.Buffer()) | |
| { | |
| affected.Enabled = true; | |
| affected.CloseDoor(); | |
| } | |
| else if (LockTimer > 0) | |
| SafeDisable(affected); | |
| else if (affected.Status != DoorStatus.Closed) | |
| affected.CloseDoor(); | |
| else | |
| { | |
| affected.Enabled = true; | |
| observed.Enabled = true; | |
| Priority = null; | |
| } | |
| } | |
| return true; | |
| } | |
| } | |
| // Outer door handling | |
| private bool HandleOuter () | |
| { | |
| // LSASLog.Line("HandleOuter called"); | |
| return HandleDoorPair(Outer, Inner); | |
| } | |
| // Inner door handling | |
| private bool HandleInner () | |
| { | |
| // LSASLog.Line("HandleInner called"); | |
| return HandleDoorPair(Inner, Outer); | |
| } | |
| // Airlock Handling | |
| public bool HandleLock (bool trigger = false) | |
| { | |
| if (!ValidSAS()) | |
| return false; | |
| else if (trigger && Priority != null) | |
| { | |
| LSASLog.Line("Ignored triggered call"); | |
| return false; // Ignore the triggered call if a door has priority at the moment | |
| } | |
| else if (HandleOuter() | HandleInner()) | |
| { | |
| LSASLog.Line(Status(Group)); | |
| return true; | |
| } | |
| else | |
| { | |
| LockTimer.Timer(0); | |
| return false; | |
| } | |
| } | |
| // Helper to print the status of a door | |
| private static string DoorState(IMyDoor door, string ind = " ") | |
| { | |
| string Out = ind + door.CustomName + ":" + System.Environment.NewLine; | |
| Out += ind + ind + "Enabled: " + door.Enabled.ToString() + System.Environment.NewLine; | |
| Out += ind + ind + "Status: " + door.Status + System.Environment.NewLine; | |
| return Out; | |
| } | |
| // Status of the airlock | |
| public string Status(string grouped, string ind = " ") | |
| { | |
| string Out = null; | |
| if (grouped != Group) | |
| { | |
| Out += System.Environment.NewLine + "Wrong key: " + grouped; | |
| Out += System.Environment.NewLine + "Should be: " + Group; | |
| } | |
| else | |
| { | |
| Out += System.Environment.NewLine + Group + System.Environment.NewLine; | |
| if (Inner == null) | |
| Out += System.Environment.NewLine + ind + "No inner door" + System.Environment.NewLine; | |
| else | |
| Out += DoorState(Inner, ind); | |
| if (Outer == null) | |
| Out += System.Environment.NewLine + ind + "No outer door" + System.Environment.NewLine; | |
| else | |
| Out += DoorState(Outer, ind); | |
| Out += "Airlock Timer: " + LockTimer.ToString() + System.Environment.NewLine; | |
| if (Priority != null) | |
| Out += Priority.CustomName + " has priority" + System.Environment.NewLine; | |
| } | |
| return Out; | |
| } | |
| } | |
| // Dictionary of LSAS Definition | |
| public class LSASDictionary : Dictionary<string, LSAS> | |
| { | |
| // Method to intelligently add a door to an existing SAS or create a new one | |
| public bool AddDoor (IMyDoor door) | |
| { | |
| string grouped = LSAS.GetGroup(door); | |
| if (grouped == null) | |
| return false; | |
| if (ContainsKey(grouped)) | |
| return this[grouped].AddDoor(door, grouped); | |
| else | |
| { | |
| Add(grouped, new LSAS(grouped)); | |
| return this[grouped].AddDoor(door, grouped); | |
| } | |
| } | |
| // Method to print full contents of dictionary | |
| public string Status () | |
| { | |
| string Output = null; | |
| foreach (var SAS in this) | |
| { | |
| if (SAS.Value != null) | |
| Output += SAS.Value.Status(SAS.Key); | |
| } | |
| return Output; | |
| } | |
| // Method to handle all the locks | |
| public bool HandleLocks(bool trigger = false) | |
| { | |
| bool Out = false; | |
| foreach (var SAS in this) | |
| { | |
| if (SAS.Value != null) | |
| { | |
| if (SAS.Value.HandleLock(trigger)) | |
| Out = true; | |
| } | |
| } | |
| return Out; | |
| } | |
| } | |
| // Instantiate a shared instance of the parser | |
| MyIni _ini = new MyIni(); | |
| bool _firstRun = false; | |
| // Instantiate a dict of Airlocks | |
| LSASDictionary LeyLaSASPairs = new LSASDictionary(); | |
| // Initial setup | |
| // Must be recompiled when doors are added or removed | |
| public Program() | |
| { | |
| // Set the Update Frequency | |
| Runtime.UpdateFrequency = UpdateFrequency.Update10; | |
| // Check if this is a first run | |
| _ini.TryParse(Storage); | |
| _firstRun = _ini.Get("Two Door Airlock Config", "Reset").ToBoolean(true); | |
| if (_firstRun) | |
| { | |
| _firstRun = false; | |
| Echo(System.Environment.NewLine + "WARNING: First run of this script!"); | |
| // Set the initial custom data values | |
| _ini.Clear(); | |
| _ini.Set("Two Door Airlock Config", "Inner Tag", LSAS.InnerTerm); | |
| _ini.Set("Two Door Airlock Config", "Outer Tag", LSAS.OuterTerm); | |
| _ini.Set("Two Door Airlock Config", "Airlock Timer", LSASTimer.Lock()); | |
| Me.CustomData = _ini.ToString(); | |
| // Store the first run variable so we don't do this again | |
| _ini.Set("Two Door Airlock Config", "Reset", false); | |
| Storage = _ini.ToString(); | |
| } | |
| // Parse the contents of customdata | |
| MyIniParseResult result; | |
| if (!_ini.TryParse(Me.CustomData, out result)) | |
| throw new Exception(result.ToString()); | |
| // Set the search terms to find airlock doors | |
| LSAS.InnerTerm = _ini.Get("Two Door Airlock Config", "Inner Tag").ToString(LSAS.InnerTerm); | |
| LSAS.OuterTerm = _ini.Get("Two Door Airlock Config", "Outer Tag").ToString(LSAS.OuterTerm); | |
| LSAS.SetLockTime(_ini.Get("Two Door Airlock Config", "Airlock Timer").ToInt32(LSASTimer.Lock())); | |
| // Populate LeyLaSASPairs with relevant doors | |
| GridTerminalSystem.GetBlocksOfType<IMyDoor>(null, (d) => | |
| { | |
| var door = d as IMyDoor; | |
| //string grouped = null; | |
| if (LSAS.IsInner(door) || LSAS.IsOuter(door)) | |
| return LeyLaSASPairs.AddDoor (door); | |
| return false; | |
| }); | |
| } | |
| public void Save() | |
| { | |
| _ini.Clear(); | |
| _ini.Set("Two Door Airlock Config", "Inner Tag", LSAS.InnerTerm); | |
| _ini.Set("Two Door Airlock Config", "Outer Tag", LSAS.OuterTerm); | |
| _ini.Set("Two Door Airlock Config", "Airlock Timer", LSASTimer.Lock()); | |
| // Store the first run variable so we don't do this again | |
| _ini.Set("Two Door Airlock Config", "Reset", _firstRun); | |
| Storage = _ini.ToString(); | |
| } | |
| // Do the things | |
| public void Main(string arg, UpdateType uptype) | |
| { | |
| LSASLog.Start(); | |
| if ((uptype & (UpdateType.Trigger | UpdateType.Terminal)) != 0) | |
| { | |
| LeyLaSASPairs.HandleLocks(true); | |
| } | |
| if ((uptype & (UpdateType.Update10 | UpdateType.Update100)) != 0) | |
| LeyLaSASPairs.HandleLocks(); | |
| Echo (LSASLog.Print()); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment