Convert mechanical door script to use MDK2.

This commit is contained in:
Anna Rose 2025-02-07 15:07:37 -05:00
parent f0f7aa9030
commit 13e1f9d63c
10 changed files with 414 additions and 246 deletions

32
.gitignore vendored Normal file
View File

@ -0,0 +1,32 @@
*.swp
*.*~
project.lock.json
.DS_Store
*.pyc
nupkg/
# Visual Studio Code
.vscode/*
!.vscode/settings.json
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
build/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
msbuild.log
msbuild.err
msbuild.wrn

57
MechanicalDoor/Door.cs Normal file
View File

@ -0,0 +1,57 @@
using Sandbox.ModAPI.Ingame;
using System.Collections.Generic;
namespace IngameScript
{
public class Door
{
public Door()
{
Hinges = new List<DoorHinge>();
}
// Add a hinge to the door
public void AddHinge(IMyMotorStator hinge)
{
Hinges.Add(new DoorHinge(hinge));
}
public void OpenDoor()
{
foreach (DoorHinge hinge in Hinges)
{
hinge.OpenDoorHinge();
}
}
public void CloseDoor()
{
foreach (DoorHinge hinge in Hinges)
{
hinge.CloseDoorHinge();
}
}
// Process the door's movement. This will return true when the door is done moving.
public bool Actuate()
{
bool done = true;
foreach (DoorHinge hinge in Hinges)
{
if (!hinge.Actuate()) done = false;
}
return done;
}
public bool Locked()
{
foreach (DoorHinge hinge in Hinges)
{
if (!hinge.Locked()) return false;
}
return true;
}
private List<DoorHinge> Hinges;
}
}

View File

@ -0,0 +1,84 @@
using Sandbox.ModAPI.Ingame;
using System.Collections.Generic;
using System;
namespace IngameScript
{
public class DoorHinge
{
public DoorHinge(IMyMotorStator hinge)
{
Hinge = hinge;
}
// For these two functions, IMyMotorStator.Angle reports radians, but
// IMyMotorStator.RotateToAngle() expects degrees...
public void OpenDoorHinge()
{
Hinge.RotorLock = false;
TargetAngle = DegToRad(OpenAngle);
Hinge.RotateToAngle(MyRotationDirection.AUTO, OpenAngle, Velocity);
}
public void CloseDoorHinge()
{
Hinge.RotorLock = false;
TargetAngle = DegToRad(ClosedAngle);
Hinge.RotateToAngle(MyRotationDirection.AUTO, ClosedAngle, Velocity);
}
// Process the hinge's movement.
// It will return true when the panel has finished moving.
// TODO: Add a mechanism to determine when a door gets stuck or can't reach the target angle.
public bool Actuate()
{
if (Math.Abs(Hinge.Angle - TargetAngle) < 0.1 && Hinge.Angle == LastAngle)
{
Hinge.RotorLock = true;
}
LastAngle = Hinge.Angle;
return Locked();
}
public bool Locked()
{
return Hinge.RotorLock;
}
private IMyMotorStator Hinge { get; set; }
private float TargetAngle { get; set; }
private float LastAngle { get; set; }
private float OpenAngle { get; set; } = 90F;
private float ClosedAngle { get; set; } = 0F;
private float Velocity { get; set; } = 5F;
private void ParseConfig()
{
string[] lines = Hinge.CustomData.Split('\n');
foreach (string line in lines)
{
string[] tokens = line.Split('=');
if (tokens.Length != 2) continue;
switch (tokens[0])
{
case "OpenAngle":
OpenAngle = float.Parse(tokens[1]);
break;
case "ClosedAngle":
ClosedAngle = float.Parse(tokens[1]);
break;
case "Velocity":
Velocity = float.Parse(tokens[1]);
break;
}
}
}
// TODO: a utility class or function would be lovely...
// In general, the encapsulation feels a little screwy here.
private float DegToRad(float degrees)
{
return degrees * ((float)Math.PI / 180F);
}
}
}

View File

@ -0,0 +1,27 @@
<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/JobDispatcher/JobDispatcher.projitems" Label="JobDispatcher" />
</Project>

View File

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

View File

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

165
MechanicalDoor/Program.cs Normal file
View File

@ -0,0 +1,165 @@
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
{
MyCommandLine cli;
List<IEnumerator<bool>> jobs;
Dictionary<string, Door> doors;
int tickCount;
public Program()
{
tickCount = 0;
cli = new MyCommandLine();
jobs = new List<IEnumerator<bool>>();
doors = new Dictionary<string, Door>();
List<IMyMotorStator> allHinges = new List<IMyMotorStator>();
GridTerminalSystem.GetBlocksOfType(allHinges);
foreach (IMyMotorStator hinge in allHinges)
{
if (hinge.CustomName.StartsWith("Door"))
{
string doorName = hinge.CustomName.Split(' ')[0];
if (!doors.ContainsKey(doorName))
{
doors[doorName] = new Door();
}
doors[doorName].AddHinge(hinge);
}
}
Echo($"Found {doors.Keys.Count} doors.");
}
public void Main(string argument, UpdateType updateSource)
{
Echo($"index: {tickCount++}");
if (updateSource == UpdateType.Trigger || updateSource == UpdateType.Terminal)
{
// Create a new job
cli.TryParse(argument);
List<Door> doorsToControl = new List<Door>();
if (cli.ArgumentCount == 0)
{
Echo("No arguments passed. Controlling all doors.");
foreach (Door door in doors.Values)
{
if (!door.Locked())
{
continue;
}
doorsToControl.Add(door);
}
}
for (int i = 0; i < cli.ArgumentCount; i++)
{
string key = "Door" + cli.Argument(i);
if (!doors.ContainsKey(key))
{
Echo($"Door with identifier {key} not found. Skipping.");
continue;
}
if (!doors[key].Locked())
{
Echo($"Door {key} already moving. Skipping.");
continue;
}
doorsToControl.Add(doors[key]);
}
if (doorsToControl.Count == 0)
{
Echo("No doors found. Not creating new job.");
}
else
{
Echo("Creating new job.");
if (cli.Switch("close")) jobs.Add(CloseDoors(doorsToControl));
else jobs.Add(OpenDoors(doorsToControl));
Runtime.UpdateFrequency = UpdateFrequency.Update1;
}
}
// Process running jobs
for (int i = 0; i < jobs.Count; i++)
{
if (!jobs[i].MoveNext())
{
jobs[i].Dispose();
jobs.Remove(jobs[i]);
i--;
Echo("Operation Complete.");
}
}
if (jobs.Count == 0)
{
Runtime.UpdateFrequency = UpdateFrequency.None;
}
}
private IEnumerator<bool> OpenDoors(List<Door> doorsToControl)
{
Echo("Opening doors.");
foreach (Door door in doorsToControl)
{
door.OpenDoor();
}
return ActuateDoors(doorsToControl);
}
private IEnumerator<bool> CloseDoors(List<Door> doorsToControl)
{
Echo("Closing doors.");
foreach (Door door in doorsToControl)
{
door.CloseDoor();
}
return ActuateDoors(doorsToControl);
}
private IEnumerator<bool> ActuateDoors(List<Door> doorsToControl)
{
while (true)
{
Echo("Actuating doors.");
bool done = true; // assume we've finished, then falsify it below
foreach (Door door in doorsToControl)
{
if (!door.Actuate())
{
done = false;
}
}
if (done) yield break;
yield return true;
}
}
}
}

View File

@ -0,0 +1,20 @@
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

BIN
MechanicalDoor/thumb.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 KiB

View File

@ -1,246 +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
MyCommandLine cli;
List<IEnumerator<bool>> jobs;
Dictionary<string, Door> doors;
int tickCount;
public class DoorHinge {
public DoorHinge(Program p, IMyMotorStator hinge) {
P = p;
Hinge = hinge;
}
// For these two functions, IMyMotorStator.Angle reports radians, but
// IMyMotorStator.RotateToAngle() expects degrees...
public void OpenDoorHinge() {
Hinge.RotorLock = false;
TargetAngle = DegToRad(OpenAngle);
Hinge.RotateToAngle(MyRotationDirection.AUTO, OpenAngle, Velocity);
}
public void CloseDoorHinge() {
Hinge.RotorLock = false;
TargetAngle = DegToRad(ClosedAngle);
Hinge.RotateToAngle(MyRotationDirection.AUTO, ClosedAngle, Velocity);
}
// Process the hinge's movement.
// It will return true when the panel has finished moving.
// TODO: Add a mechanism to determine when a door gets stuck or can't reach the target angle.
public bool Actuate() {
if (Math.Abs(Hinge.Angle - TargetAngle) < 0.1 && Hinge.Angle == LastAngle) {
Hinge.RotorLock = true;
}
LastAngle = Hinge.Angle;
return Locked();
}
public bool Locked() {
return Hinge.RotorLock;
}
private Program P; // for access to Echo, etc.
private IMyMotorStator Hinge { get; set; }
private float TargetAngle { get; set; }
private float LastAngle {get; set; }
private float OpenAngle { get; set; } = 90F;
private float ClosedAngle { get; set; } = 0F;
private float Velocity { get; set; } = 5F;
private void ParseConfig() {
string[] lines = Hinge.CustomData.Split('\n');
foreach (string line in lines) {
string[] tokens = line.Split('=');
if (tokens.Length != 2) continue;
switch(tokens[0]) {
case "OpenAngle":
OpenAngle = float.Parse(tokens[1]);
break;
case "ClosedAngle":
ClosedAngle = float.Parse(tokens[1]);
break;
case "Velocity":
Velocity = float.Parse(tokens[1]);
break;
}
}
}
// TODO: a utility class or function would be lovely...
// In general, the encapsulation feels a little screwy here.
private float DegToRad(float degrees) {
return degrees * ((float)Math.PI / 180F);
}
}
public class Door {
public Door(Program p) {
P = p;
Hinges = new List<DoorHinge>();
}
// Add a hinge to the door
public void AddHinge(IMyMotorStator hinge) {
Hinges.Add(new DoorHinge(P, hinge));
}
public void OpenDoor() {
foreach (DoorHinge hinge in Hinges) {
hinge.OpenDoorHinge();
}
}
public void CloseDoor() {
foreach (DoorHinge hinge in Hinges) {
hinge.CloseDoorHinge();
}
}
// Process the door's movement. This will return true when the door is done moving.
public bool Actuate() {
bool done = true;
foreach (DoorHinge hinge in Hinges) {
if (!hinge.Actuate()) done = false;
}
return done;
}
public bool Locked() {
foreach (DoorHinge hinge in Hinges) {
if (!hinge.Locked()) return false;
}
return true;
}
private List<DoorHinge> Hinges;
private Program P;
}
public Program() {
tickCount = 0;
cli = new MyCommandLine();
jobs = new List<IEnumerator<bool>>();
doors = new Dictionary<string, Door>();
List<IMyMotorStator> allHinges = new List<IMyMotorStator>();
GridTerminalSystem.GetBlocksOfType(allHinges);
foreach(IMyMotorStator hinge in allHinges) {
if (hinge.CustomName.StartsWith("Door")) {
string doorName = hinge.CustomName.Split(' ')[0];
if (!doors.ContainsKey(doorName)) {
doors[doorName] = new Door(this);
}
doors[doorName].AddHinge(hinge);
}
}
Echo($"Found {doors.Keys.Count} doors.");
}
public void Main(string argument, UpdateType updateSource) {
Echo($"index: {tickCount++}");
if (updateSource == UpdateType.Trigger || updateSource == UpdateType.Terminal) {
// Create a new job
cli.TryParse(argument);
List<Door> doorsToControl = new List<Door>();
if (cli.ArgumentCount == 0) {
Echo("No arguments passed. Controlling all doors.");
foreach (Door door in doors.Values) {
if (!door.Locked()) {
continue;
}
doorsToControl.Add(door);
}
}
for (int i=0; i < cli.ArgumentCount; i++) {
string key = "Door" + cli.Argument(i);
if (!doors.ContainsKey(key)) {
Echo($"Door with identifier {key} not found. Skipping.");
continue;
}
if (!doors[key].Locked()) {
Echo($"Door {key} already moving. Skipping.");
continue;
}
doorsToControl.Add(doors[key]);
}
if (doorsToControl.Count == 0) {
Echo("No doors found. Not creating new job.");
} else {
Echo("Creating new job.");
if (cli.Switch("close")) jobs.Add(CloseDoors(doorsToControl));
else jobs.Add(OpenDoors(doorsToControl));
Runtime.UpdateFrequency = UpdateFrequency.Update1;
}
}
// Process running jobs
for (int i=0; i < jobs.Count; i++) {
if (!jobs[i].MoveNext()) {
jobs[i].Dispose();
jobs.Remove(jobs[i]);
i--;
Echo("Operation Complete.");
}
}
if (jobs.Count == 0) {
Runtime.UpdateFrequency = UpdateFrequency.None;
}
}
private IEnumerator<bool> OpenDoors(List<Door> doorsToControl) {
Echo("Opening doors.");
foreach (Door door in doorsToControl) {
door.OpenDoor();
}
return ActuateDoors(doorsToControl);
}
private IEnumerator<bool> CloseDoors(List<Door> doorsToControl) {
Echo("Closing doors.");
foreach (Door door in doorsToControl) {
door.CloseDoor();
}
return ActuateDoors(doorsToControl);
}
private IEnumerator<bool> ActuateDoors(List<Door> doorsToControl) {
while (true) {
Echo("Actuating doors.");
bool done = true; // assume we've finished, then falsify it below
foreach (Door door in doorsToControl) {
if (!door.Actuate()) {
done = false;
}
}
if (done) yield break;
yield return true;
}
}