Completely rework the Sequencery library and abandon MechDoor in favor of just using the action sequencer script.

This commit is contained in:
Anna Rose 2025-02-14 15:52:28 -05:00
parent cd9ee88172
commit f5d3dbc275
25 changed files with 583 additions and 586 deletions

View File

@ -27,7 +27,7 @@ namespace IngameScript
private MyCommandLine _cli = new MyCommandLine();
private List<IEnumerator<bool>> _jobs = new List<IEnumerator<bool>>();
private Dictionary<string, Sequencer> _sequences = new Dictionary<string, Sequencer>();
private Dictionary<string, ActionGroup> _actionGroups = new Dictionary<string, ActionGroup>();
public Program()
{
@ -35,36 +35,48 @@ namespace IngameScript
// initialize all the sequencers
List<IMyTerminalBlock> blocks = new List<IMyTerminalBlock>();
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<Sequencer> sequencesToRun = new List<Sequencer>();
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;
}
}
}

View File

@ -44,7 +44,7 @@ namespace IngameScript
private List<IMyLightingBlock> _lights = new List<IMyLightingBlock>();
private List<AirDoor> _doors = new List<AirDoor>();
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<bool> job = _sequencer.RunSequence(true);
IEnumerator<bool> 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"

View File

@ -1,29 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netframework48</TargetFramework>
<RootNamespace>IngameScript</RootNamespace>
<LangVersion>6</LangVersion>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<Configurations>Release;Debug</Configurations>
<Platforms>x64</Platforms>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Mal.Mdk2.PbAnalyzers" Version="2.1.11">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Mal.Mdk2.PbPackager" Version="2.1.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Mal.Mdk2.References" Version="2.2.4" />
</ItemGroup>
<ItemGroup>
<None Remove="Instructions.readme" />
<AdditionalFiles Include="Instructions.readme" />
<AdditionalFiles Include="thumb.png" />
</ItemGroup>
<Import Project="..\Mixins\Console\Console.projitems" Label="shared" />
<Import Project="..\Mixins\Sequencer\Sequencer.projitems" Label="shared" />
<Import Project="..\Mixins\Utils\Utils.projitems" Label="shared" />
</Project>

View File

@ -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

View File

@ -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

View File

@ -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<IEnumerator<bool>> _jobs = new List<IEnumerator<bool>>();
private Dictionary<string, Sequencer> _doors = new Dictionary<string, Sequencer>();
public Program()
{
Console = new MainConsole(this, "Door Controller");
List<IMyTerminalBlock> doorBlocks = new List<IMyTerminalBlock>();
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<Sequencer> doorsToControl = new List<Sequencer>();
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;
}
}
}
}

View File

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 389 KiB

View File

@ -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<string, ActionSequence> _actions = new Dictionary<string, ActionSequence>();
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<bool> 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<bool> job = _actions[actionName].Run();
while (job.MoveNext()) yield return true;
Running = false;
}
}
}
}

View File

@ -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<int, List<IBlockAction>> _steps = new SortedDictionary<int, List<IBlockAction>>();
public ActionSequence(string name)
{
Name = name;
}
public void Add(int step, IBlockAction action)
{
if (!_steps.ContainsKey(step)) _steps[step] = new List<IBlockAction>();
_steps[step].Add(action);
}
public IEnumerator<bool> Run()
{
if (Running) yield break;
Running = true;
List<IEnumerator<bool>> jobs = new List<IEnumerator<bool>>();
foreach (List<IBlockAction> step in _steps.Values)
{
foreach (IBlockAction action in step)
{
jobs.Add(action.Run());
}
bool stepRunning = true;
while (stepRunning)
{
stepRunning = false;
foreach (IEnumerator<bool> job in jobs)
{
if (job.MoveNext()) stepRunning = true;
}
yield return true;
}
foreach (IEnumerator<bool> job in jobs)
{
job.Dispose();
}
jobs.Clear();
}
Running = false;
}
}
}
}

View File

@ -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<bool> 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;
}
}
}
}

View File

@ -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<bool> Run()
{
_piston.MoveToPosition(_position, _velocity);
float lastValue = -1f;
while (lastValue != _piston.CurrentPosition)
{
lastValue = _piston.CurrentPosition;
yield return true;
}
}
}
}
}

View File

@ -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<bool> 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;
}
}
}
}

View File

@ -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<bool> 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;
}
}
}
}

View File

@ -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<bool> Run();
}
}
}

View File

@ -1,14 +0,0 @@
using System.Collections.Generic;
namespace IngameScript
{
partial class Program
{
public interface ISequenceable
{
bool Running { get; }
int Step { get; set; }
IEnumerator<bool> Run(bool deploy);
}
}
}

View File

@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace IngameScript
{
partial class Program
{
}
}

View File

@ -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<bool> Run();
}
public class SequenceableBlock
{
private IConsole _console;
private IMyTerminalBlock _block;
private Dictionary<string, ISequenceableAction> _actions = new Dictionary<string, ISequenceableAction>();
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<MyIniKey> keys = new List<MyIniKey>();
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<MyIniKey> 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<MyIniKey> 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<MyIniKey> keys)
{
foreach (MyIniKey key in keys)
{
string[] values = ini.Get(key).ToString().Split(',');
SequenceableActionRotor action = new SequenceableActionRotor();
_actions.Add(key.Name, action);
}
}
}
}
}

View File

@ -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<bool> 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<bool> _openDoor()
{
_door.Enabled = true;
_door.OpenDoor();
while (_door.Status != DoorStatus.Open) yield return true;
if (LockOpen)
{
_door.Enabled = false;
}
}
public IEnumerable<bool> _closeDoor()
{
_door.Enabled = true;
_door.CloseDoor();
while (_door.Status != DoorStatus.Closed) yield return true;
if (LockClosed)
{
_door.Enabled = false;
}
}
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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<bool> 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;
}
}
}
}

View File

@ -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<bool> 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);
}
}
}
}

View File

@ -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<int, List<ISequenceable>> _sequence = new SortedDictionary<int, List<ISequenceable>>();
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<ISequenceable>();
_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<bool> 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<KeyValuePair<int, List<ISequenceable>>> steps = deploy ? _sequence : _sequence.Reverse();
foreach (KeyValuePair<int, List<ISequenceable>> kvp in steps)
{
List<ISequenceable> blocks = kvp.Value;
List<IEnumerator<bool>> subJobs = new List<IEnumerator<bool>>();
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<bool> subJob in subJobs)
{
if (subJob.MoveNext()) Running = true;
}
yield return true;
}
foreach (IEnumerator<bool> subJob in subJobs) subJob.Dispose(); // clean up after ourselves
}
}
}
}
}

View File

@ -6,6 +6,11 @@
<SharedGUID>8a3cdcc5-4b55-4d87-a415-698a0e1ff06f</SharedGUID>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)/**/*.cs" Visible=" '$(ShowCommonFiles)' == 'true' " />
<Compile Include="$(MSBuildThisFileDirectory)ActionGroup.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ActionSequence.cs" />
<Compile Include="$(MSBuildThisFileDirectory)BlockActionDoor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)BlockActionPiston.cs" />
<Compile Include="$(MSBuildThisFileDirectory)BlockActionRotor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)IBlockAction.cs" />
</ItemGroup>
</Project>

View File

@ -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