diff --git a/ActionSequencer/Program.cs b/ActionSequencer/Program.cs index 3ce704b..3def67d 100644 --- a/ActionSequencer/Program.cs +++ b/ActionSequencer/Program.cs @@ -27,7 +27,7 @@ namespace IngameScript private MyCommandLine _cli = new MyCommandLine(); private List> _jobs = new List>(); - private Dictionary _sequences = new Dictionary(); + private Dictionary _actionGroups = new Dictionary(); public Program() { @@ -35,36 +35,48 @@ namespace IngameScript // initialize all the sequencers List blocks = new List(); - GridTerminalSystem.GetBlocksOfType(blocks, block => MyIni.HasSection(block.CustomData, "sequence")); + GridTerminalSystem.GetBlocksOfType(blocks, block => MyIni.HasSection(block.CustomData, "sequencer")); + foreach (IMyTerminalBlock block in blocks) { Ini.TryParse(block.CustomData); - string id = Ini.Get("sequence", "id").ToString(); + string id = Ini.Get("sequencer", "groupName").ToString().Trim(); if (id == "") { - Console.Print($"No id found for '{block.CustomName}'. Skipping."); + Console.Print($"No groupName found for '{block.CustomName}'. Skipping."); continue; } - if (id == "all") + + string actionNames = Ini.Get("sequencer", "actions").ToString().Trim(); ; + if (actionNames == "") { - Console.Print($"'All' is a reserved keyword. Skipping '{block.CustomName}'."); + Console.Print($"No actions defined for '{block.CustomName}'. Skipping."); continue; } + if (!_actionGroups.ContainsKey(id)) _actionGroups[id] = new ActionGroup(Console, id); - if (!_sequences.ContainsKey(id)) _sequences[id] = new Sequencer(this, id); - - ISequenceable wrapped = SequenceableFactory.MakeSequenceable(this, block, "sequence"); - if (wrapped == null) + // Find the custom section for each action and parse the block into it. + foreach (string actionName in actionNames.Split(',')) { - Console.Print($"Skipping incompatible block '{block.CustomName}'."); - continue; + string key = "action" + actionName.Trim(); + if (!MyIni.HasSection(block.CustomData, key)) + { + Console.Print($"Missing config section '{key}'; skipping action."); + continue; + } + int step = Int32.Parse(Ini.Get(key, "step").ToString("0")); + IBlockAction blockAction = buildBlockAction(block, key); + if (blockAction == null) + { + Console.Print($"Failed to add '{block.CustomName}' to action '{actionName}'."); + continue; + } + _actionGroups[id].AddActionBlock(actionName.Trim(), step, blockAction); } - - _sequences[id].AddBlock(wrapped); } - Console.Print($"Found {_sequences.Count} sequences."); + Console.Print($"Found {_actionGroups.Count} sequences."); Console.UpdateTickCount(); } @@ -76,30 +88,22 @@ namespace IngameScript { _cli.TryParse(argument); - List sequencesToRun = new List(); - bool deploy = !_cli.Switch("stow"); - - if (_cli.ArgumentCount > 0 && _cli.Argument(0) == "all") + if (_cli.ArgumentCount != 2) { - sequencesToRun = _sequences.Values.ToList(); + Console.Print("Must call script with exactly 2 arguments."); } - for (int i = 0; i < _cli.ArgumentCount; i++) + else { - string id = _cli.Argument(i); - if (!_sequences.ContainsKey(id)) + if (_actionGroups.ContainsKey(_cli.Argument(0))) { - Console.Print($"Ignoring non-existent sequence '{id}'"); - continue; + _jobs.Add(_actionGroups[_cli.Argument(0)].RunAction(_cli.Argument(1))); + Runtime.UpdateFrequency |= UpdateFrequency.Update10; + } + else + { + Console.Print($"Failed to find action group '{_cli.Argument(0)}'"); } - sequencesToRun.Add(_sequences[id]); } - - foreach (Sequencer sequence in sequencesToRun) - { - Console.Print($"Activating sequence '{sequence.Name}'"); - _jobs.Add(sequence.RunSequence(deploy)); - } - if (_jobs.Count > 0) Runtime.UpdateFrequency = UpdateFrequency.Update10; } // Process running jobs @@ -113,10 +117,76 @@ namespace IngameScript Console.Print("Operation Complete."); } - if (_jobs.Count == 0) + if (_jobs.Count == 0) Runtime.UpdateFrequency = UpdateFrequency.None; + } + + // Prerequisite: Ini.Parse has already been called for this block. + private IBlockAction buildBlockAction(IMyTerminalBlock block, string key) + { + if (block is IMyDoor) { - Runtime.UpdateFrequency = UpdateFrequency.None; + BlockActionDoor.DoorAction action; + switch (Ini.Get(key, "action").ToString("open")) + { + case "open": + action = BlockActionDoor.DoorAction.Open; + break; + case "close": + action = BlockActionDoor.DoorAction.Close; + break; + default: + Console.Print($"Invalid door action for '{block.CustomName}'. Defaulting to open."); + action = BlockActionDoor.DoorAction.Open; + break; + } + + return new BlockActionDoor( + block as IMyDoor, + action, + Ini.Get(key, "lock").ToBoolean(true) + ); } + else if (block is IMyMotorStator) + { + MyRotationDirection direction; + switch (Ini.Get(key, "direction").ToString("auto")) + { + case "auto": + direction = MyRotationDirection.AUTO; + break; + case "cw": + case "clockwise": + direction = MyRotationDirection.CW; + break; + case "ccw": + case "counterclockwise": + case "anticlockwise": + direction = MyRotationDirection.CCW; + break; + default: + Console.Print($"Invalid direction for '{block.CustomName}'. Defaulting to auto."); + direction = MyRotationDirection.AUTO; + break; + } + + return new BlockActionRotor( + block as IMyMotorStator, + Ini.Get(key, "angle").ToSingle(0f), + Ini.Get(key, "velocity").ToSingle(5f), + direction + ); + } + else if (block is IMyPistonBase) + { + return new BlockActionPiston( + block as IMyPistonBase, + Ini.Get(key, "position").ToSingle(0f), + Ini.Get(key, "velocity").ToSingle(2f) + ); + } + + Console.Print($"Can't add unsupported block '{block.CustomName}'"); + return null; } } } diff --git a/AirMonitor/AirZone.cs b/AirMonitor/AirZone.cs index e5450ac..fdb4dd3 100644 --- a/AirMonitor/AirZone.cs +++ b/AirMonitor/AirZone.cs @@ -44,7 +44,7 @@ namespace IngameScript private List _lights = new List(); private List _doors = new List(); private PrefixedConsole _console; - private Sequencer _sequencer; + private ActionSequence _sealBulkheads; // actions to perform when the pressure alarm is triggered private const float TriggerLevel = 0.75F; @@ -53,22 +53,18 @@ namespace IngameScript Name = zoneName; _program = program; _console = new PrefixedConsole(_program.Console, Name); - _sequencer = new Sequencer(_program, Name); + _sealBulkheads = new ActionSequence(Name); } public void AddDoor(AirDoor door) { _doors.Add(door); IMyDoor doorBlock = door.Door; - SequenceableDoor wrapped = SequenceableFactory.MakeSequenceable( - _program, - doorBlock as IMyDoor, - "airMonitor") as SequenceableDoor; - wrapped.Step = 0; - wrapped.DeployOpen = false; - wrapped.LockOpen = false; - wrapped.LockClosed = true; - _sequencer.AddBlock(wrapped); + _sealBulkheads.Add(0, new BlockActionDoor( + doorBlock, + BlockActionDoor.DoorAction.Close, + true + )); } public void AddBlock(IMyTerminalBlock block) @@ -97,7 +93,7 @@ namespace IngameScript { _console.Print($"Low pressure alarm triggered."); // close the doors - IEnumerator job = _sequencer.RunSequence(true); + IEnumerator job = _sealBulkheads.Run(); while (job.MoveNext()) { // It would be nice if the API had UpdateFrequency.Once10, e.g. "re-run in 10 ticks and then clear the flag" diff --git a/MechanicalDoor/MechanicalDoor.csproj b/MechanicalDoor/MechanicalDoor.csproj deleted file mode 100644 index 44a8516..0000000 --- a/MechanicalDoor/MechanicalDoor.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - 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/MechanicalDoor/MechanicalDoor.mdk.ini b/MechanicalDoor/MechanicalDoor.mdk.ini deleted file mode 100644 index 5add8f4..0000000 --- a/MechanicalDoor/MechanicalDoor.mdk.ini +++ /dev/null @@ -1,22 +0,0 @@ -; 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/MechanicalDoor/MechanicalDoor.mdk.local.ini b/MechanicalDoor/MechanicalDoor.mdk.local.ini deleted file mode 100644 index 4b66820..0000000 --- a/MechanicalDoor/MechanicalDoor.mdk.local.ini +++ /dev/null @@ -1,7 +0,0 @@ -; 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/MechanicalDoor/Program.cs b/MechanicalDoor/Program.cs deleted file mode 100644 index 501b35f..0000000 --- a/MechanicalDoor/Program.cs +++ /dev/null @@ -1,113 +0,0 @@ -using Sandbox.ModAPI.Ingame; -using SpaceEngineers.Game.ModAPI.Ingame; -using System.Collections.Generic; -using VRage.Game.ModAPI.Ingame.Utilities; - -namespace IngameScript -{ - public partial class Program : MyGridProgram, IConsoleProgram - { - public MyIni Ini { get; } = new MyIni(); - public IConsole Console { get; private set; } - - private MyCommandLine _cli = new MyCommandLine(); - private List> _jobs = new List>(); - private Dictionary _doors = new Dictionary(); - - public Program() - { - Console = new MainConsole(this, "Door Controller"); - - List doorBlocks = new List(); - GridTerminalSystem.GetBlocksOfType(doorBlocks, block => MyIni.HasSection(block.CustomData, "mechDoor")); - foreach (IMyTerminalBlock block in doorBlocks) - { - Ini.TryParse(block.CustomData); - string doorName = Ini.Get("mechDoor", "id").ToString(); - // Create the door if this is a new id - if (!_doors.ContainsKey(doorName)) - { - _doors[doorName] = new Sequencer(this, doorName); - } - - // Add the part; the Door object handles typing and sequencing. - ISequenceable wrapped = SequenceableFactory.MakeSequenceable(this, block, "mechDoor"); - if (!(block is IMyShipMergeBlock)) wrapped.Step = 1; // TODO: actually support merge blocks here - if (wrapped == null) { Console.Print($"Tried to add incompatible block '{block.CustomName}'"); continue; } - _doors[doorName].AddBlock(wrapped); - } - - Console.Print($"Found {_doors.Keys.Count} doors."); - Console.UpdateTickCount(); - } - - public void Main(string argument, UpdateType updateSource) - { - Console.UpdateTickCount(); - - if (updateSource == UpdateType.Trigger || updateSource == UpdateType.Terminal) - { - // Create a new job - _cli.TryParse(argument); - List doorsToControl = new List(); - - if (_cli.ArgumentCount == 0) - { - Console.Print("No arguments passed. Controlling all doors."); - foreach (Sequencer door in _doors.Values) - { - if (door.Running) continue; - doorsToControl.Add(door); - } - } - - for (int i = 0; i < _cli.ArgumentCount; i++) - { - string key = _cli.Argument(i); - if (!_doors.ContainsKey(key)) - { - Console.Print($"Door '{key}' not found. Skipping."); - continue; - } - if (_doors[key].Running) - { - Console.Print($"Door '{key}' already moving. Skipping."); - continue; - } - doorsToControl.Add(_doors[key]); - } - - if (doorsToControl.Count == 0) - { - Console.Print("No doors found. Not creating new job."); - } - else - { - Console.Print("Creating new job(s)."); - bool deploy = _cli.Switch("deploy") || _cli.Switch("open"); - foreach (Sequencer door in doorsToControl) - { - _jobs.Add(door.RunSequence(deploy)); - } - Runtime.UpdateFrequency |= UpdateFrequency.Update10; - } - } - - // Process running jobs - for (int i = 0; i < _jobs.Count; i++) - { - if (_jobs[i].MoveNext()) continue; - - _jobs[i].Dispose(); - _jobs.Remove(_jobs[i]); - i--; - Console.Print("Operation Complete."); - } - - if (_jobs.Count == 0) - { - Runtime.UpdateFrequency = UpdateFrequency.None; - } - } - } -} diff --git a/MechanicalDoor/instructions.readme b/MechanicalDoor/instructions.readme deleted file mode 100644 index 4a5d285..0000000 --- a/MechanicalDoor/instructions.readme +++ /dev/null @@ -1,20 +0,0 @@ -Script for controlling "mechanical" doors, aka custom doors using hinges. - -Future features: - * Rotor and piston support. - * "Multi-stage doors", e.g. actuate a hinge, then a piston. - -Currently this operates on hinges whose names start with "DoorX", where X is an arbitrary identifier. -Usage: - Script argument should be a space-separated list of identifiers and either - `-open` (default behavior) or `-close`. - Failing to pass any identifiers will cause the action to happen to all doors on the grid. - -By default "open" is assumed to be 90 degrees and "closed" is assumed to be 0 degrees. - However, each hinge can have this behavior configured via custom data. A sample config - (with the default values): - -OpenAngle=90 -ClosedAngle=0 -Velocity=5 - diff --git a/MechanicalDoor/thumb.png b/MechanicalDoor/thumb.png deleted file mode 100644 index a8dc2ef..0000000 Binary files a/MechanicalDoor/thumb.png and /dev/null differ diff --git a/Mixins/Sequencer/ActionGroup.cs b/Mixins/Sequencer/ActionGroup.cs new file mode 100644 index 0000000..aa9118b --- /dev/null +++ b/Mixins/Sequencer/ActionGroup.cs @@ -0,0 +1,62 @@ +// Represents a series of ActionSequences +// that are mutually exclusive. Typically +// this is designed to represent multiple ActionSequences +// that act on the same set of blocks, such as an +// "Extend" and "Retract" action for a multi-stage mechanical +// construction, or perhaps "Open" and "Close" sequences for a set of +// mechanical doors. + +using System.Collections.Generic; +using System.Linq; + +namespace IngameScript +{ + partial class Program + { + public class ActionGroup + { + public string Name { get; private set; } + + public bool Running { get; private set; } = false; + + private Dictionary _actions = new Dictionary(); + private IConsole _console; + + public ActionGroup(IConsole console, string name) + { + // Todo: use a PrefixedConsole here? + _console = console; + Name = name; + } + + // Add an action to the sequence called actionName, which will be created if it doesn't already exist. + public void AddActionBlock(string actionName, int step, IBlockAction actionBlock) + { + if (!_actions.ContainsKey(actionName)) _actions[actionName] = new ActionSequence(actionName); + _actions[actionName].Add(step, actionBlock); + } + + public IEnumerator RunAction(string actionName) + { + if (Running) + { + _console.Print($"Ignoring action '{actionName}' on '{Name}'. Already running an action."); + yield break; + } + + if (!_actions.ContainsKey(actionName)) + { + _console.Print($"No action '{actionName}' defined for '{Name}'"); + yield break; + } + + Running = true; + + IEnumerator job = _actions[actionName].Run(); + while (job.MoveNext()) yield return true; + + Running = false; + } + } + } +} \ No newline at end of file diff --git a/Mixins/Sequencer/ActionSequence.cs b/Mixins/Sequencer/ActionSequence.cs new file mode 100644 index 0000000..15f7b23 --- /dev/null +++ b/Mixins/Sequencer/ActionSequence.cs @@ -0,0 +1,65 @@ +// An ActionSequence encapsulates a series of IBlockActions to perform. +// It represents a single logical coordinated "action". The action may have +// multiple steps, each with multiple actions. It provides a method to asynchronously +// perform those steps in sequence. + +using System.Collections.Generic; + +namespace IngameScript +{ + partial class Program + { + public class ActionSequence + { + public string Name { get; private set; } + public bool Running { get; private set; } = false; + + private SortedDictionary> _steps = new SortedDictionary>(); + + public ActionSequence(string name) + { + Name = name; + } + + public void Add(int step, IBlockAction action) + { + if (!_steps.ContainsKey(step)) _steps[step] = new List(); + _steps[step].Add(action); + } + + public IEnumerator Run() + { + if (Running) yield break; + Running = true; + + List> jobs = new List>(); + foreach (List step in _steps.Values) + { + foreach (IBlockAction action in step) + { + jobs.Add(action.Run()); + } + + bool stepRunning = true; + while (stepRunning) + { + stepRunning = false; + foreach (IEnumerator job in jobs) + { + if (job.MoveNext()) stepRunning = true; + } + yield return true; + } + + foreach (IEnumerator job in jobs) + { + job.Dispose(); + } + jobs.Clear(); + } + + Running = false; + } + } + } +} \ No newline at end of file diff --git a/Mixins/Sequencer/BlockActionDoor.cs b/Mixins/Sequencer/BlockActionDoor.cs new file mode 100644 index 0000000..300b014 --- /dev/null +++ b/Mixins/Sequencer/BlockActionDoor.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using Sandbox.ModAPI.Ingame; + +namespace IngameScript +{ + partial class Program + { + public class BlockActionDoor : IBlockAction + { + public enum DoorAction + { + Open, + Close, + } + + public bool Running { get; private set; } = false; + + private IMyDoor _door; + private DoorAction _action; + private bool _lockDoor; + + public BlockActionDoor( + IMyDoor door, + DoorAction action, + bool lockDoor = true + ) + { + _door = door; + _action = action; + _lockDoor = lockDoor; + } + + public IEnumerator Run() + { + switch (_action) + { + case DoorAction.Open: + _door.Enabled = true; + _door.OpenDoor(); + while (_door.Status != DoorStatus.Open) yield return true; + break; + case DoorAction.Close: + _door.Enabled = true; + _door.CloseDoor(); + while (_door.Status != DoorStatus.Closed) yield return true; + break; + } + if (_lockDoor) _door.Enabled = false; + } + } + } +} \ No newline at end of file diff --git a/Mixins/Sequencer/BlockActionPiston.cs b/Mixins/Sequencer/BlockActionPiston.cs new file mode 100644 index 0000000..45a8778 --- /dev/null +++ b/Mixins/Sequencer/BlockActionPiston.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using Sandbox.ModAPI.Ingame; + +namespace IngameScript +{ + partial class Program + { + public class BlockActionPiston : IBlockAction + { + public bool Running { get; private set; } = false; + + private IMyPistonBase _piston; + private float _position; + private float _velocity; + + public BlockActionPiston( + IMyPistonBase piston, + float position, + float velocity = 2f + ) + { + _piston = piston; + _position = position; + _velocity = velocity; + } + + public IEnumerator Run() + { + _piston.MoveToPosition(_position, _velocity); + + float lastValue = -1f; + while (lastValue != _piston.CurrentPosition) + { + lastValue = _piston.CurrentPosition; + yield return true; + } + } + } + } +} \ No newline at end of file diff --git a/Mixins/Sequencer/BlockActionRotor.cs b/Mixins/Sequencer/BlockActionRotor.cs new file mode 100644 index 0000000..f752932 --- /dev/null +++ b/Mixins/Sequencer/BlockActionRotor.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using Sandbox.ModAPI.Ingame; + +namespace IngameScript +{ + partial class Program + { + public class BlockActionRotor : IBlockAction + { + public bool Running { get; private set; } = false; + + private IMyMotorStator _rotor; + private float _angle; + private float _velocity; + private MyRotationDirection _direction; + + public BlockActionRotor( + IMyMotorStator rotor, + float angle, + float velocity = 5f, + MyRotationDirection direction = MyRotationDirection.AUTO + ) + { + _rotor = rotor; + _angle = angle; + _velocity = velocity; + _direction = direction; + } + + public IEnumerator Run() + { + _rotor.RotorLock = false; + _rotor.RotateToAngle(_direction, _angle, _velocity); + + float _lastAngle = -1; + while (_rotor.Angle != _lastAngle) + { + _lastAngle = _rotor.Angle; + yield return true; + } + + _rotor.RotorLock = true; + } + } + } +} \ No newline at end of file diff --git a/Mixins/Sequencer/BlockActionTemplate.cs b/Mixins/Sequencer/BlockActionTemplate.cs new file mode 100644 index 0000000..132b963 --- /dev/null +++ b/Mixins/Sequencer/BlockActionTemplate.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using Sandbox.ModAPI.Ingame; + +namespace IngameScript +{ + partial class Program + { + public class BlockActionRotor : IBlockAction + { + private IMyMotorRotor _rotor; + private float _targetAngle; + private float _velocity = 5f; + private RotationDirection _direction = RotationDirection.AUTO; + + public BlockActionRotor( + IMyMotorRotor rotor, + float targetAngle, + float velocity = 5f, + + ) + { + _rotor = rotor; + } + + public IEnumerator Run() + { + // code to actually execute the action goes here + // any loops that wait for a condition to be true should + // `yield return true` inside the loop. + // If the action ends prematurely, use `yield break` + yield return true; + } + } + } +} \ No newline at end of file diff --git a/Mixins/Sequencer/IBlockAction.cs b/Mixins/Sequencer/IBlockAction.cs new file mode 100644 index 0000000..5b05de5 --- /dev/null +++ b/Mixins/Sequencer/IBlockAction.cs @@ -0,0 +1,17 @@ +// An interface to represent a pre-configured action performed asynchronously +// on a block. Implements a Run() method that executes the action and provides an +// Enumerator to monitor when the action is complete. + +using System.Collections.Generic; + +namespace IngameScript +{ + partial class Program + { + public interface IBlockAction + { + bool Running { get; } + IEnumerator Run(); + } + } +} \ No newline at end of file diff --git a/Mixins/Sequencer/ISequenceable.cs b/Mixins/Sequencer/ISequenceable.cs deleted file mode 100644 index bfe2212..0000000 --- a/Mixins/Sequencer/ISequenceable.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; - -namespace IngameScript -{ - partial class Program - { - public interface ISequenceable - { - bool Running { get; } - int Step { get; set; } - IEnumerator Run(bool deploy); - } - } -} \ No newline at end of file diff --git a/Mixins/Sequencer/ISequenceableAction.cs b/Mixins/Sequencer/ISequenceableAction.cs new file mode 100644 index 0000000..63bf653 --- /dev/null +++ b/Mixins/Sequencer/ISequenceableAction.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace IngameScript +{ + partial class Program + { + + } +} \ No newline at end of file diff --git a/Mixins/Sequencer/SequenceableBlock.cs b/Mixins/Sequencer/SequenceableBlock.cs new file mode 100644 index 0000000..4d92a3d --- /dev/null +++ b/Mixins/Sequencer/SequenceableBlock.cs @@ -0,0 +1,132 @@ +// A SequenceableBlock holds a reference to a compatible block type, +// and a list of available actions sorted by name. +// It provides a RunAction() method that allows the caller to +// run the configured actions. + +using System; +using System.Collections.Generic; +using Sandbox.ModAPI.Ingame; +using VRage.Game.ModAPI.Ingame.Utilities; + +namespace IngameScript +{ + partial class Program + { + public interface ISequenceableAction + { + string Name { get; } + IEnumerator Run(); + } + + public class SequenceableBlock + { + private IConsole _console; + private IMyTerminalBlock _block; + private Dictionary _actions = new Dictionary(); + + public SequenceableBlock(IConsole console, IMyTerminalBlock block) + { + _console = console; + if (!(_block is IMyDoor || _block is IMyPistonBase || _block is IMyMotorRotor)) + { + _console.Print("ERROR: Incompatible block '{_block.CustomName}' being sequenced."); + } + _block = block; + } + + // We employ a pseudo-factory pattern to parse the settings + // into ISequenceableAction objects. + public void Parse(MyIni ini, string sectionName = "sequencer") + { + ini.TryParse(_block.CustomData, sectionName); + List keys = new List(); + ini.GetKeys(sectionName, keys); + + if (_block is IMyDoor) + { + parseDoor(ini, keys); + } + else if (_block is IMyPistonBase) + { + parsePiston(ini, keys); + } + else if (_block is IMyMotorRotor) + { + parseRotor(ini, keys); + } + } + + // Alternatively, the user can parse manually and override anything they + // need to. + public void AddAction(ISequenceableAction action, string name) + { + _actions.Add(action.Name, action); + } + + private void parseDoor(MyIni ini, List keys) + { + foreach (MyIniKey key in keys) + { + string[] values = ini.Get(key).ToString().Split(','); + string actionType = values[1]; + bool lockDoor = true; + if (values.Length >= 3) lockDoor = values[2] == "true" ? true : false; + + SequenceableActionDoor action = new SequenceableActionDoor( + _console, + key.Name, + _block as IMyDoor, + actionType, + lockDoor + ); + _actions.Add(key.Name, action); + } + } + + private void parsePiston(MyIni ini, List keys) + { + foreach (MyIniKey key in keys) + { + string[] values = ini.Get(key).ToString().Split(','); + float angle = Single.Parse(values[1]); + float velocity = 5f; + if (values.Length >= 3) velocity = Single.Parse(values[2]); + + MyRotationDirection dir = MyRotationDirection.AUTO; + if (values.Length >= 4) + { + switch (values[3]) + { + case "cw": + dir = MyRotationDirection.CW; + break; + case "ccw": + dir = MyRotationDirection.CCW; + break; + } + } + + SequenceableActionPiston action = new SequenceableActionPiston( + _console, + key.Name, + _block as IMyPistonBase, + + ); + _actions.Add(key.Name, action); + } + } + + private void parseRotor(MyIni ini, List keys) + { + foreach (MyIniKey key in keys) + { + string[] values = ini.Get(key).ToString().Split(','); + + SequenceableActionRotor action = new SequenceableActionRotor(); + _actions.Add(key.Name, action); + } + + } + } + } +} \ No newline at end of file diff --git a/Mixins/Sequencer/SequenceableDoor.cs b/Mixins/Sequencer/SequenceableDoor.cs deleted file mode 100644 index 3374117..0000000 --- a/Mixins/Sequencer/SequenceableDoor.cs +++ /dev/null @@ -1,74 +0,0 @@ -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; private set; } = false; - public int Step { get; set; } - - public bool DeployOpen { get; set; } - public bool LockOpen { get; set; } - public bool LockClosed { get; set; } - - private IMyDoor _door; - - public SequenceableDoor( - IConsoleProgram _program, - IMyDoor door, - string sectionName) - { - _door = door; - - MyIni ini = _program.Ini; - 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 (Running) yield break; - Running = true; - - if (deploy && DeployOpen || !deploy && !DeployOpen) - { - foreach (bool tick in _openDoor()) yield return true; - } - else - { - foreach (bool tick in _closeDoor()) yield return true; - } - Running = false; - } - - 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 deleted file mode 100644 index 1446e79..0000000 --- a/Mixins/Sequencer/SequenceableFactory.cs +++ /dev/null @@ -1,38 +0,0 @@ -// I hate Factories, but when the shoe fits... - -using Sandbox.ModAPI.Ingame; -using SpaceEngineers.Game.ModAPI.Ingame; -using VRage.Game.ModAPI.Ingame.Utilities; - -namespace IngameScript -{ - partial class Program - { - public class SequenceableFactory - { - public static ISequenceable MakeSequenceable( - IConsoleProgram program, - IMyTerminalBlock block, - string sectionName = "sequence") - { - if (block is IMyMotorStator) - { - return new SequenceableRotor(program, block as IMyMotorStator, sectionName); - } - if (block is IMyPistonBase) - { - return new SequenceablePiston(program, block as IMyPistonBase, sectionName); - } - if (block is IMyShipMergeBlock) - { - // return new SequenceableMergeBlock(block as IMyShipMergeBlock, step); - } - if (block is IMyDoor) - { - return new SequenceableDoor(program, block as IMyDoor, sectionName); - } - return null; - } - } - } -} \ No newline at end of file diff --git a/Mixins/Sequencer/SequenceablePiston.cs b/Mixins/Sequencer/SequenceablePiston.cs deleted file mode 100644 index 0e8df13..0000000 --- a/Mixins/Sequencer/SequenceablePiston.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Sandbox.ModAPI.Ingame; -using System; -using System.Collections.Generic; -using VRage.Game.ModAPI.Ingame.Utilities; - -namespace IngameScript -{ - partial class Program - { - public class SequenceablePiston : ISequenceable - { - public bool Running { get; private set; } = false; - public int Step { get; set; } - - private IConsoleProgram _program; - private IMyPistonBase _piston; - private float _deployPosition; - private float _stowPosition; - private float _velocity; - - public SequenceablePiston(IConsoleProgram program, IMyPistonBase piston, string sectionName) - { - _program = program; - _piston = piston; - - MyIni ini = _program.Ini; - ini.TryParse(piston.CustomData); - - _deployPosition = ini.Get(sectionName, "deployPosition").ToSingle(10F); - _stowPosition = ini.Get(sectionName, "stowPosition").ToSingle(0F); - _velocity = ini.Get(sectionName, "velocity").ToSingle(5F); - Step = ini.Get(sectionName, "step").ToInt32(0); - } - - public IEnumerator Run(bool deploy) - { - if (Running) yield break; - Running = true; - - float targetValue = _stowPosition; - float lastValue = -1; - if (deploy) targetValue = _deployPosition; - _piston.MoveToPosition(targetValue, _velocity); - - while (lastValue != _piston.CurrentPosition) - { - lastValue = _piston.CurrentPosition; - yield return true; - } - - Running = false; - } - } - } -} \ No newline at end of file diff --git a/Mixins/Sequencer/SequenceableRotor.cs b/Mixins/Sequencer/SequenceableRotor.cs deleted file mode 100644 index fa769a8..0000000 --- a/Mixins/Sequencer/SequenceableRotor.cs +++ /dev/null @@ -1,81 +0,0 @@ -using Sandbox.ModAPI.Ingame; -using System; -using System.Collections.Generic; -using VRage.Game.ModAPI.Ingame.Utilities; - -namespace IngameScript -{ - partial class Program - { - public class SequenceableRotor : ISequenceable - { - public bool Running { get; private set; } = false; - public int Step { get; set; } - - private IConsoleProgram _program; - private float _velocity; - private float _deployAngle; - private float _stowAngle; - private IMyMotorStator _rotor; - private MyRotationDirection _deployDirection; - private MyRotationDirection _stowDirection; - - public SequenceableRotor( - IConsoleProgram program, - IMyMotorStator rotor, - string sectionName) - { - _program = program; - _rotor = rotor; - - MyIni ini = _program.Ini; - ini.TryParse(rotor.CustomData); - - _deployAngle = ini.Get(sectionName, "deployAngle").ToSingle(90F); - _stowAngle = ini.Get(sectionName, "stowAngle").ToSingle(0F); - _velocity = ini.Get(sectionName, "velocity").ToSingle(5F); - switch (ini.Get(sectionName, "deployDirection").ToString("auto")) - { - case "auto": - _deployDirection = MyRotationDirection.AUTO; - _stowDirection = MyRotationDirection.AUTO; - break; - case "cw": - _deployDirection = MyRotationDirection.CW; - _stowDirection = MyRotationDirection.CCW; - break; - case "ccw": - _deployDirection = MyRotationDirection.CCW; - _stowDirection = MyRotationDirection.CW; - break; - } - Step = ini.Get(sectionName, "step").ToInt32(0); - } - - public IEnumerator Run(bool deploy = true) - { - Running = true; - float degAngle = deploy ? _deployAngle : _stowAngle; - MyRotationDirection dir = deploy ? _deployDirection : _stowDirection; - - _rotor.RotorLock = false; - _rotor.RotateToAngle(dir, degAngle, _velocity); - - float _lastAngle = -1; - while (_rotor.Angle != _lastAngle) - { - _lastAngle = _rotor.Angle; - yield return true; - } - - _rotor.RotorLock = true; - Running = false; - } - - private float degToRad(float degrees) - { - return degrees * ((float)Math.PI / 180F); - } - } - } -} \ No newline at end of file diff --git a/Mixins/Sequencer/Sequencer.cs b/Mixins/Sequencer/Sequencer.cs deleted file mode 100644 index 29847d8..0000000 --- a/Mixins/Sequencer/Sequencer.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Represents a single set of blocks to run in order, waiting for each group's completion before -// moving on to the next one. - -using Sandbox.ModAPI.Ingame; -using System.Collections.Generic; -using System.Linq; - -namespace IngameScript -{ - partial class Program - { - public class Sequencer - { - public bool Running { get; private set; } - public string Name { get; private set; } - - private IConsole _console; - - private SortedDictionary> _sequence = new SortedDictionary>(); - - public Sequencer(IConsoleProgram program, string name) - { - Name = name; - _console = new PrefixedConsole(program.Console, Name); - } - - public void AddBlock(ISequenceable block) - { - if (!_sequence.ContainsKey(block.Step)) _sequence[block.Step] = new List(); - _sequence[block.Step].Add(block); - } - - // To activate the Sequencer, call this once, then call `MoveNext()` once per tick - // on the returned object until it returns false. - // (then be sure to call `Dispose() on that object`) - public IEnumerator RunSequence(bool deploy = true) - { - if (Running) - { - _console.Print("Already running, ignoring invocation."); - yield break; - } - - // This is a bit convoluted, but we're extracting the iterator for use in the foreach directly. - // This allows us to iterate in reverse when reversing/"closing" the sequence. - IEnumerable>> steps = deploy ? _sequence : _sequence.Reverse(); - foreach (KeyValuePair> kvp in steps) - { - List blocks = kvp.Value; - List> subJobs = new List>(); - Running = true; - - foreach (ISequenceable block in blocks) - { - // TODO: add some sort of handling for block.Running == true, maybe? - // It *should* be an impossible case, but... - subJobs.Add(block.Run(deploy)); - } - - while (Running) - { - Running = false; - foreach (IEnumerator subJob in subJobs) - { - if (subJob.MoveNext()) Running = true; - } - yield return true; - } - foreach (IEnumerator subJob in subJobs) subJob.Dispose(); // clean up after ourselves - } - } - } - } -} \ No newline at end of file diff --git a/Mixins/Sequencer/Sequencer.projitems b/Mixins/Sequencer/Sequencer.projitems index 141cea5..a265255 100644 --- a/Mixins/Sequencer/Sequencer.projitems +++ b/Mixins/Sequencer/Sequencer.projitems @@ -6,6 +6,11 @@ 8a3cdcc5-4b55-4d87-a415-698a0e1ff06f - + + + + + + \ No newline at end of file diff --git a/SpaceEngineers.sln b/SpaceEngineers.sln index 73e4c59..5207d23 100644 --- a/SpaceEngineers.sln +++ b/SpaceEngineers.sln @@ -5,33 +5,24 @@ VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Airlock", "Airlock\Airlock.csproj", "{4B2DDF72-E914-46E9-946C-954D45721158}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MechanicalDoor", "MechanicalDoor\MechanicalDoor.csproj", "{67FE627E-176A-4973-9FB7-FA5133CC8288}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ActionSequencer", "ActionSequencer\ActionSequencer.csproj", "{1F5CCD1E-028F-4C08-B2FE-43C0670028DB}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AirMonitor", "AirMonitor\AirMonitor.csproj", "{40B352CD-100E-4F87-A7A0-FF00F4B8846C}" EndProject -Project("{8a3cdcc5-4b55-4d87-a415-698a0e1ff06f}") = "Sequencer", "Mixins\Sequencer\Sequencer.shproj", "{722a2146-2e38-477c-9587-55075b8fa0e6}" +Project("{8A3CDCC5-4B55-4D87-A415-698A0E1FF06F}") = "Sequencer", "Mixins\Sequencer\Sequencer.shproj", "{722A2146-2E38-477C-9587-55075B8FA0E6}" EndProject -Project("{8a3cdcc5-4b55-4d87-a415-698a0e1ff06f}") = "Console", "Mixins\Console\Console.shproj", "{323e8400-84c2-46a0-b2e9-fcd3b31681da}" +Project("{8A3CDCC5-4B55-4D87-A415-698A0E1FF06F}") = "Console", "Mixins\Console\Console.shproj", "{323E8400-84C2-46A0-B2E9-FCD3B31681DA}" 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 {4B2DDF72-E914-46E9-946C-954D45721158}.Debug|Any CPU.ActiveCfg = Debug|x64 {4B2DDF72-E914-46E9-946C-954D45721158}.Debug|Any CPU.Build.0 = Debug|x64 {4B2DDF72-E914-46E9-946C-954D45721158}.Release|Any CPU.ActiveCfg = Release|x64 {4B2DDF72-E914-46E9-946C-954D45721158}.Release|Any CPU.Build.0 = Release|x64 - {67FE627E-176A-4973-9FB7-FA5133CC8288}.Debug|Any CPU.ActiveCfg = Debug|x64 - {67FE627E-176A-4973-9FB7-FA5133CC8288}.Debug|Any CPU.Build.0 = Debug|x64 - {67FE627E-176A-4973-9FB7-FA5133CC8288}.Release|Any CPU.ActiveCfg = Release|x64 - {67FE627E-176A-4973-9FB7-FA5133CC8288}.Release|Any CPU.Build.0 = Release|x64 {1F5CCD1E-028F-4C08-B2FE-43C0670028DB}.Debug|Any CPU.ActiveCfg = Debug|x64 {1F5CCD1E-028F-4C08-B2FE-43C0670028DB}.Debug|Any CPU.Build.0 = Debug|x64 {1F5CCD1E-028F-4C08-B2FE-43C0670028DB}.Release|Any CPU.ActiveCfg = Release|x64 @@ -41,4 +32,7 @@ Global {40B352CD-100E-4F87-A7A0-FF00F4B8846C}.Release|Any CPU.ActiveCfg = Release|x64 {40B352CD-100E-4F87-A7A0-FF00F4B8846C}.Release|Any CPU.Build.0 = Release|x64 EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection EndGlobal