Skip to content

Instantly share code, notes, and snippets.

@TerrorBite
Created July 18, 2015 18:57
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TerrorBite/2935de44c97b0a6d6dd5 to your computer and use it in GitHub Desktop.
Save TerrorBite/2935de44c97b0a6d6dd5 to your computer and use it in GitHub Desktop.
/*
Piston Elevator Controller
A Space Engineers script by TerrorBite
(Not just for elevators! Can be used any time that you want a piston to move to a
certain preset length and then turn off.)
WARNING: You may experience some lag while the piston is moving (especially after running
the game for a long time). This is unavoidable and is a result of the Program Block being
run repeatedly and rapidly by a Timer Block in order to monitor the piston. I have taken
great care to ensure that the program and timer are only running actively this way while
the piston is in motion. As soon as the piston stops, the program will shut down the timer
and go idle.
TO USE THIS SCRIPT:
First, you will obviously need a Programmable Block somewhere on your ship/base to put
this program into! You only need one block, regardless of how many pistons you will be
controlling with it, and you can put it anywhere.
Next, you will need at least one piston to control! Name it anything you want.
I named mine "PistonElevator", and I'll be using that name in the following examples.
You will also need one Timer Block per piston that you want to control. The timer will be
used to rapidly trigger the program, so that it can monitor the exact position of the piston.
(This is needed because there is no way to set exact Min/Max limits from a script.)
Make sure to name the timer block "Timer" followed by a dot and the name of the piston.
This means our example timer is going to be named "Timer.PistonElevator" to match our
example piston's name.
The delay length of the timer block doesn't matter, but the toolbar setup is quite important.
The timer block needs to trigger two toolbar actions. First, the timer needs to trigger
its OWN "Trigger Now" action. It also needs to trigger the Programmable Block's Run action
with the argument set to the piston's name, followed by a comma and the word "UPDATE". For
our example piston, the argument will read "PistonElevator,UPDATE".
Don't start or trigger the timer yourself. The program will take care of triggering the timer
when needed. And in order to prevent unneccessary lag, the program will also stop the timer
from triggering itself once the piston that it's watching reaches its destination or otherwise
stops moving.
Now that your piston and its timer are set up, it's time to add some buttons! I recommend
Digi's Elevator Button Pad + Catwalks mod, as not only does it provide catwalks that fit on
top of a piston without hitting the sides of the shaft, it also provides a neat catwalk with
four buttons that makes a perfect elevator platform! Or for a more conventional lift car, try
Eikester's Elevator Cabin mod. For a completely vanilla experience, you can place a 4-button
panel at each floor, reachable from inside the shaft.
Configuring the buttons to move the piston to a desired height is easy. Just assign the
Programmable Block's Run action to the button. Similar to before, the argument is set to the
piston's name, followed by a comma, then the height to travel to. To send our example piston to
a height of 6.5 meters, use "PistonElevator,6.5".
NOTES: This program controls the piston by turning it off when it reaches the desired height.
It does not modify the distance limits of the piston, or the velocity (except for reversing
it). You can set the speed and limits of your piston as normal and not worry about them being
changed.
Because it works by turning off the piston at the right moment, it may not be perfectly
accurate. The faster the piston is moving, the less precise the stopping position will be.
However, speeds high enough to cause a noticeable error in height are too fast to use as a
working elevator platform, so this shouldn't affect most people. I could fix this by reducing
the piston velocity as it nears the destination height, however I don't have fine control over
velocity (it can only be increased/reduced by a fixed amount, identical to toolbar controls).
*/
const float TOLERANCE = 0.08F;
string status = "";
PistonStatus targetStatus = PistonStatus.Stopped;
Dictionary<string, string> Properties;
void LoadProperties() {
Properties = new Dictionary<string, string>();
if(string.IsNullOrEmpty(Storage)) return;
string[] records = Storage.Split('\x1E');
for(int i=0; i<records.Length; i++) {
string[] fields = records[i].Split('\x1F');
Properties.Add(fields[0], fields[1]);
}
}
void SaveProperties() {
StringBuilder sb = new StringBuilder();
var i = Properties.GetEnumerator();
if(i.MoveNext()) sb.Append(string.Format("{0}\x1F{1}", i.Current.Key, i.Current.Value));
while(i.MoveNext()) {
sb.Append("\x1E");
sb.Append(string.Format("{0}\x1F{1}", i.Current.Key, i.Current.Value));
}
}
void KillTimer(IMyFunctionalBlock timer) {
timer.ApplyAction("OnOff_Off");
timer.ApplyAction("TriggerNow");
}
IMyFunctionalBlock GetTimerForPiston(IMyPistonBase piston) {
string timerName = string.Format("Timer.{0}", piston.CustomName);
IMyFunctionalBlock timer = GridTerminalSystem.GetBlockWithName(timerName) as IMyFunctionalBlock;
if(timer==null) throw new ArgumentException("Timer block \"{0}\" was not found.", timerName);
return timer;
}
void Update(IMyPistonBase piston) {
IMyFunctionalBlock timer = GetTimerForPiston(piston);
if(!timer.Enabled) {
// We end up here immediately after running KillTimer()
// Turn the timer back on so that it fires properly next time
timer.ApplyAction("OnOff_On");
return;
}
string k = string.Format("{0}.target", piston.CustomName);
float targetHeight = Properties.ContainsKey(k) ? float.Parse(Properties[k]) : 0.0F;
Echo(string.Format("{0}\nMonitoring piston... {1:ss}", status, DateTime.Now));
if( piston.Status == targetStatus &&
(piston.Status == PistonStatus.Extending && piston.CurrentPosition >= targetHeight) ||
(piston.Status == PistonStatus.Retracting && piston.CurrentPosition <= targetHeight) ) {
piston.ApplyAction("OnOff_Off");
Echo(string.Format("Stopped {0} at {1}m (target={2}m, {3})", piston.CustomName, piston.CurrentPosition,
targetHeight, piston.Status, status ));
KillTimer(timer);
}
else if(piston.Status!=PistonStatus.Extending &&
piston.Status!=PistonStatus.Retracting && piston.Enabled) {
piston.ApplyAction("OnOff_Off");
Echo(string.Format("{0} found stopped at {1}m (target={2}m)", piston.CustomName, piston.CurrentPosition,
targetHeight, status));
KillTimer(timer);
}
}
void ElevatorSendTo(IMyPistonBase piston, float targetHeight) {
if(piston.CurrentPosition + TOLERANCE > targetHeight &&
piston.CurrentPosition - TOLERANCE < targetHeight) {
Echo("Piston already at destination");
return;
}
Properties[string.Format("{0}.target", piston.CustomName)] = targetHeight.ToString();
// Turn on the piston
piston.ApplyAction("OnOff_On");
// Calculate whether we need to travel up or down, set limits accordingly
if(targetHeight > piston.CurrentPosition) {
// we need to move upwards (extend)
if(piston.Velocity < 0) piston.ApplyAction("Reverse");
targetStatus = PistonStatus.Extending;
} else {
// we need to move downwards (retract)
if(piston.Velocity > 0) piston.ApplyAction("Reverse");
targetStatus = PistonStatus.Retracting;
}
status = string.Format("Piston {0} {2} at {1}m/s", piston.CustomName, piston.Velocity, piston.Status);
Echo(status);
// Trigger the timer
GetTimerForPiston(piston).ApplyAction("TriggerNow");
}
void Main(string argument)
{
if(Properties==null) LoadProperties();
string[] args = argument.Split(',');
if(args.Length < 2) return;
// Get requested piston
IMyPistonBase piston = GridTerminalSystem.GetBlockWithName(args[0]) as IMyPistonBase;
if(piston == null) {
Echo(string.Format("Piston block \"{0}\" was not found.", args[0]));
return;
}
if (args[1] == "UPDATE") Update(piston);
else ElevatorSendTo(piston, float.Parse(args[1]));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment