diff --git a/AirMonitor/AirMonitor.csproj b/AirMonitor/AirMonitor.csproj
new file mode 100644
index 0000000..0f4379c
--- /dev/null
+++ b/AirMonitor/AirMonitor.csproj
@@ -0,0 +1,28 @@
+
+
+ netframework48
+ IngameScript
+ 6
+ false
+ Release;Debug
+ x64
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/AirMonitor/AirMonitor.mdk.ini b/AirMonitor/AirMonitor.mdk.ini
new file mode 100644
index 0000000..5add8f4
--- /dev/null
+++ b/AirMonitor/AirMonitor.mdk.ini
@@ -0,0 +1,22 @@
+; This file is project specific and should be checked in to source control.
+
+[mdk]
+; This is a programmable block script project.
+; You should not change this.
+type=programmableblock
+
+; Toggle trace (on|off) (verbose output)
+trace=off
+
+; What type of minification to use (none|trim|stripcomments|lite|full)
+; none: No minification
+; trim: Removes unused types (NOT members).
+; stripcomments: trim + removes comments.
+; lite: stripcomments + removes leading/trailing whitespace.
+; full: lite + renames identifiers to shorter names.
+minify=none
+
+; A list of files and folder to ignore when creating the script.
+; This is a comma separated list of glob patterns.
+; See https://code.visualstudio.com/docs/editor/glob-patterns
+ignores=obj/**/*,MDK/**/*,**/*.debug.cs
diff --git a/AirMonitor/AirMonitor.mdk.local.ini b/AirMonitor/AirMonitor.mdk.local.ini
new file mode 100644
index 0000000..4b66820
--- /dev/null
+++ b/AirMonitor/AirMonitor.mdk.local.ini
@@ -0,0 +1,7 @@
+; This file is _local_ to your machine and should not be checked in to source control.
+
+[mdk]
+; Where to output the script to (auto|specific path)
+output=auto
+; Override the default binary path (auto|specific path)
+binarypath=auto
\ No newline at end of file
diff --git a/AirMonitor/AirMonitor.sln b/AirMonitor/AirMonitor.sln
new file mode 100644
index 0000000..8d91c25
--- /dev/null
+++ b/AirMonitor/AirMonitor.sln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AirMonitor", "AirMonitor.csproj", "{DAD911E6-670F-4AA0-8F00-5D239BC5F732}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {DAD911E6-670F-4AA0-8F00-5D239BC5F732}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {DAD911E6-670F-4AA0-8F00-5D239BC5F732}.Debug|Any CPU.Build.0 = Debug|x64
+ {DAD911E6-670F-4AA0-8F00-5D239BC5F732}.Release|Any CPU.ActiveCfg = Release|x64
+ {DAD911E6-670F-4AA0-8F00-5D239BC5F732}.Release|Any CPU.Build.0 = Release|x64
+ EndGlobalSection
+EndGlobal
diff --git a/AirMonitor/AirZone.cs b/AirMonitor/AirZone.cs
new file mode 100644
index 0000000..29a8699
--- /dev/null
+++ b/AirMonitor/AirZone.cs
@@ -0,0 +1,73 @@
+using Sandbox.ModAPI.Ingame;
+using SpaceEngineers.Game.ModAPI.Ingame;
+using System.Collections.Generic;
+using VRage.Game.ModAPI.Ingame.Utilities;
+
+namespace IngameScript {
+ partial class Program {
+ public class AirZone
+ {
+ public bool Triggered = false;
+
+ private MyIni _ini;
+ private List _vents;
+ private PrefixedConsole _console;
+ private Sequencer _sequencer;
+ private const float TriggerLevel = 0.75F;
+
+ public AirZone(string zoneName, MyIni ini, IConsole console)
+ {
+ _ini = ini;
+ _console = new PrefixedConsole(console, zoneName);
+ _sequencer = new Sequencer(zoneName, _console);
+ _vents = new List();
+ }
+
+ public void AddBlock(IMyTerminalBlock block)
+ {
+ if (block is IMyAirVent)
+ {
+ _vents.Add(block as IMyAirVent);
+ } else if (block is IMyDoor) {
+ SequenceableDoor wrapped = SequenceableFactory.MakeSequenceable(block as IMyDoor, _ini, "") as SequenceableDoor;
+ wrapped.Step = 0;
+ wrapped.DeployOpen = false;
+ wrapped.LockOpen = false;
+ wrapped.LockClosed = true;
+ _sequencer.AddBlock(wrapped);
+ } else {
+ _console.Print("Tried to add incompatible block '{block.CustomName}'");
+ }
+ // TODO: support for arbitrary (sub-)sequences here could be powerful
+ }
+
+ // This enumerator should run forever.
+ public IEnumerator Monitor()
+ {
+ while (true)
+ {
+ foreach (IMyAirVent vent in _vents)
+ {
+ if (vent.GetOxygenLevel() < TriggerLevel) Triggered = true;
+ }
+
+ if (Triggered == true)
+ {
+ // close the doors
+ IEnumerator job = _sequencer.RunSequence(true);
+ while (job.MoveNext()) yield return true;
+
+ // noop until manually reset
+ while (Triggered) yield return true;
+
+ // open the doors
+ job = _sequencer.RunSequence(false);
+ while (job.MoveNext()) yield return true;
+ }
+
+ yield return true;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/AirMonitor/Instructions.readme b/AirMonitor/Instructions.readme
new file mode 100644
index 0000000..ed30ab7
--- /dev/null
+++ b/AirMonitor/Instructions.readme
@@ -0,0 +1,5 @@
+R e a d m e
+-----------
+
+In this file you can include any instructions or other comments you want to have injected onto the
+top of your final script. You can safely delete this file if you do not want any such comments.
diff --git a/AirMonitor/Program.cs b/AirMonitor/Program.cs
new file mode 100644
index 0000000..78ada01
--- /dev/null
+++ b/AirMonitor/Program.cs
@@ -0,0 +1,96 @@
+using Sandbox.Game.EntityComponents;
+using Sandbox.ModAPI.Ingame;
+using Sandbox.ModAPI.Interfaces;
+using SpaceEngineers.Game.ModAPI.Ingame;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Text;
+using VRage;
+using VRage.Collections;
+using VRage.Game;
+using VRage.Game.Components;
+using VRage.Game.GUI.TextPanel;
+using VRage.Game.ModAPI.Ingame;
+using VRage.Game.ModAPI.Ingame.Utilities;
+using VRage.Game.ObjectBuilders.Definitions;
+using VRageMath;
+
+namespace IngameScript
+{
+ public partial class Program : MyGridProgram
+ {
+ private MyIni _ini;
+ private Console _console;
+ private MyCommandLine _cli;
+ private Dictionary _zones;
+ private List> _jobs;
+
+ public Program()
+ {
+ _cli = new MyCommandLine();
+ _ini = new MyIni();
+ _console = new Console(this, _ini);
+ _zones = new Dictionary();
+ _jobs = new List>();
+
+ // Find all tagged objects and build out zones
+ List blocks = new List();
+ GridTerminalSystem.GetBlocksOfType(blocks, block => MyIni.HasSection(block.CustomData, "airMonitor"));
+ foreach (IMyTerminalBlock block in blocks)
+ {
+ _ini.TryParse(block.CustomData);
+ string[] zones = new string[] { };
+ if (_ini.Get("airMonitor", "zones").ToString() != "")
+ {
+ zones = _ini.Get("airMonitor", "zones").ToString().Split(',');
+ }
+ else if (_ini.Get("airMonitor", "zone").ToString() != "")
+ {
+ zones = new string[] { _ini.Get("airMonitor", "zone").ToString() };
+ }
+
+ foreach (string zone in zones)
+ {
+ if (!_zones.ContainsKey(zone))
+ {
+ _zones[zone] = new AirZone(zone, _ini, _console);
+ }
+ _zones[zone].AddBlock(block);
+ }
+ }
+
+ foreach (AirZone zone in _zones.Values)
+ {
+ _jobs.Add(zone.Monitor());
+ }
+ Runtime.UpdateFrequency |= UpdateFrequency.Update100;
+ }
+
+ public void Main(string argument, UpdateType updateSource)
+ {
+ if (argument != "")
+ {
+ _cli.TryParse(argument);
+ if (_cli.Switch("reset"))
+ {
+ for (int i = 0; i < _cli.ArgumentCount; i++)
+ {
+ if (_zones.ContainsKey(_cli.Argument(i)))
+ {
+ _zones[_cli.Argument(i)].Triggered = false;
+ }
+ }
+ }
+ }
+
+ foreach (IEnumerator job in _jobs)
+ {
+ if (job.MoveNext()) continue;
+ _console.Print("WARNING: Monitoring job exited. Zone no longer being monitored.");
+ }
+ }
+ }
+}
diff --git a/AirMonitor/thumb.png b/AirMonitor/thumb.png
new file mode 100644
index 0000000..a8dc2ef
Binary files /dev/null and b/AirMonitor/thumb.png differ
diff --git a/Mixins/Sequencer/SequenceableDoor.cs b/Mixins/Sequencer/SequenceableDoor.cs
new file mode 100644
index 0000000..28fa0e6
--- /dev/null
+++ b/Mixins/Sequencer/SequenceableDoor.cs
@@ -0,0 +1,66 @@
+using Sandbox.ModAPI.Ingame;
+using System.Collections.Generic;
+using VRage.Game.ModAPI.Ingame.Utilities;
+
+namespace IngameScript
+{
+ partial class Program
+ {
+ public class SequenceableDoor : ISequenceable
+ {
+ public bool Running { get; }
+ public int Step { get; set; }
+
+ private IMyDoor _door;
+ public bool DeployOpen { get; set; }
+ public bool LockOpen { get; set; }
+ public bool LockClosed { get; set; }
+
+ public SequenceableDoor(
+ IMyDoor door,
+ MyIni ini,
+ string sectionName)
+ {
+ _door = door;
+
+ ini.TryParse(door.CustomData);
+ DeployOpen = ini.Get(sectionName, "deployOpen").ToBoolean(true);
+ LockOpen = ini.Get(sectionName, "lockOpen").ToBoolean(true);
+ LockClosed = ini.Get(sectionName, "lockClosed").ToBoolean(true);
+ Step = ini.Get(sectionName, "step").ToInt32(0);
+ }
+
+ public IEnumerator Run(bool deploy)
+ {
+ if (deploy && DeployOpen || !deploy && !DeployOpen)
+ {
+ foreach (bool tick in _openDoor()) yield return true;
+ }
+ else
+ {
+ foreach (bool tick in _closeDoor()) yield return true;
+ }
+ }
+
+ public IEnumerable _openDoor()
+ {
+ _door.Enabled = true;
+ _door.OpenDoor();
+ while (_door.Status != DoorStatus.Open) yield return true;
+ if (LockOpen) {
+ _door.Enabled = false;
+ }
+ }
+
+ public IEnumerable _closeDoor()
+ {
+ _door.Enabled = true;
+ _door.CloseDoor();
+ while (_door.Status != DoorStatus.Closed) yield return true;
+ if (LockClosed) {
+ _door.Enabled = false;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Mixins/Sequencer/SequenceableFactory.cs b/Mixins/Sequencer/SequenceableFactory.cs
index 59e9bd3..1a45e17 100644
--- a/Mixins/Sequencer/SequenceableFactory.cs
+++ b/Mixins/Sequencer/SequenceableFactory.cs
@@ -27,6 +27,10 @@ namespace IngameScript
{
// return new SequenceableMergeBlock(block as IMyShipMergeBlock, step);
}
+ if (block is IMyDoor)
+ {
+ return new SequenceableDoor(block as IMyDoor, ini, sectionName);
+ }
return null;
}
}