Space Engineers

Space Engineers

AutoLevel
Gate 23 Sep, 2021 @ 7:43am
Script refactor
Comments / Suggestions for features?

/* ======= ARCHON'S AUTOLEVEL SCRIPT ====== Use instructions: 1. Ensure that the ship has a remote control (if there are multiple, you can use the REMOTE_CONTROL_NAME variable to select a specific controller) and at least one gyroscope 1.1 Ensure that the remote control is correctly orientated (the round light should be facing "forwards" relative to your ship 2. Change the script variables to suit your preferences : 2.1 CONTROL_COEFF is used to define the turn speed, if your ship is overshooting, reduce this, if it is turning too slowly increase it 2.2 GYRO_COUNT_LIMIT is used to define a maximum number of gyroscopes for the script to use Set this value to -1 to use all available gyros If you want to be able to turn the ship whilst the script is running, set this to a lower value than the number of gyroscopes on your ship 2.3 _stopWhenLevel is used to decide whether the script should stop running once the ship is level (true) or continue to run (false) 2.4 If you want the ship to still be controllable (i.e. to allow you to turn - you can set cockpitFeaturesEnabled to true 3. Run the script 4. If _stopWhenLevel is false, you can stop the script by running it again Note - Please let me know in the comments on the script if you run into any issues and I'll get them fixed as soon as I can */ /// <summary> /// Sets the name of the remote control to orient on (e.g. "remote control 1")<br /> /// Set to "" to use the first remote control found /// </summary> private const string REMOTE_CONTROL_NAME = ""; /// <summary> /// Sets the turn speed<br/> /// Lower this if you tend to overshoot, Raise it if turning too slowly /// </summary> private const double CONTROL_COEFF = 0.3; /// <summary> /// Set this to a number lower than the number of gyros you have if you want to be able to<br /> /// rotate the ship whilst leveling<br /> /// Otherwise set to -1 to use all gyros on the ship /// </summary> private const int GYRO_COUNT_LIMIT = -1; /// <summary> /// Set this as true if you want the script to stop when the ship is level /// </summary> private const bool STOP_WHEN_LEVEL = false; /// <summary> /// Set this as true if you want the script to automatically suspend, to allow you to turn the ship /// </summary> private bool COCKPIT_FEATURES_ENABLED = true; /// <summary> /// AutoShutoff - Monitor connectors? /// </summary> private const bool MONITOR_CONNECTORS = true; //============================== // DO NOT EDIT BELOW THIS LINE //============================== private double angle; private bool isRunning; private IMyCockpit cockpit; private IMyRemoteControl remoteControl; private readonly List<IMyGyro> gyroList; private readonly List<IMyShipConnector> connectorList; /// <summary> /// Constructor /// </summary> /// <remarks> /// Run only once update world load, or program compilation /// </remarks> public Program() { gyroList = new List<IMyGyro>(); connectorList = new List<IMyShipConnector>(); Runtime.UpdateFrequency = UpdateFrequency.None; Initialize(); Stop(); } /// <summary> /// Script entrypoint /// </summary> /// <param name="argument"></param> /// <param name="updateSource"></param> // ReSharper disable once UnusedMember.Global // ReSharper disable once HeuristicUnreachableCode public void Main(string argument, UpdateType updateSource) { if ((updateSource & UpdateType.Update10) != 0) { if (remoteControl == null) { Initialize(); } if (isRunning) { if (angle < 0.01 && STOP_WHEN_LEVEL) { Stop(); } if (COCKPIT_FEATURES_ENABLED && (cockpit.MoveIndicator.Sum != 0 || cockpit.RollIndicator != 0 || cockpit.RotationIndicator.X + cockpit.RotationIndicator.Y != 0)) { // If cockpit controls are enabled and movement is detected then stop temporarily Suspend(); } else { Level(); } } } else if ((updateSource & UpdateType.Update100) != 0) { // ReSharper disable once RedundantLogicalConditionalExpressionOperand if (MONITOR_CONNECTORS && connectorList.Any(x => x.Status == MyShipConnectorStatus.Connected)) { Stop(); } } else if ((updateSource & UpdateType.Terminal) != 0 || (updateSource & UpdateType.Trigger) != 0) { // Script was run via terminal, or via the tool-bar // As we don't care about arguments, just toggle the state Toggle(); if (!isRunning) { Stop(); } } } /// <summary> /// Toggle the running state of the script /// </summary> private void Toggle() { UpdateFrequency freq = UpdateFrequency.None; isRunning = !isRunning; if (isRunning) { if (MONITOR_CONNECTORS) { freq |= UpdateFrequency.Update100; } freq |= UpdateFrequency.Update10; } else { freq = UpdateFrequency.None; } Runtime.UpdateFrequency = freq; } /// <summary> /// Overrides the gyros to force the ship to level /// </summary> /// <remarks> /// This is the meat of the script /// </remarks> private void Level() { if (!isRunning) { return; } Echo("Level"); //Get orientation from remoteControl Matrix orientation; remoteControl.Orientation.GetMatrix(out orientation); Vector3D down = orientation.Down; Vector3D grav = remoteControl.GetNaturalGravity(); grav.Normalize(); // ReSharper disable once ConditionIsAlwaysTrueOrFalse // ReSharper disable once UnreachableCode int limit = GYRO_COUNT_LIMIT > 0 ? GYRO_COUNT_LIMIT : gyroList.Count; for (int i = 0; i < limit; i++) { IMyGyro g = gyroList;

g.Orientation.GetMatrix(out orientation);
Vector3D localDown = Vector3D.Transform(down, MatrixD.Transpose(orientation));
Vector3D localGrav = Vector3D.Transform(grav, MatrixD.Transpose(g.WorldMatrix.GetOrientation()));
Vector3D rot = Vector3D.Cross(localDown, localGrav);

angle = rot.Length();
angle = Math.Atan2(angle, Math.Sqrt(Math.Max(0.0, 1.0 - angle * angle)));

double controlCoeff = g.GetMaximum<float>("Yaw") * (angle / Math.PI) * CONTROL_COEFF;

controlCoeff = Math.Min(g.GetMaximum<float>("Yaw"), controlCoeff);
controlCoeff = Math.Max(0.01, controlCoeff); //Gyros don't work well at very low speeds

rot.Normalize();
rot *= controlCoeff;

g.SetValueFloat("Pitch", (float) rot.GetDim(0));
g.SetValueFloat("Yaw", -(float) rot.GetDim(1));
g.SetValueFloat("Roll", -(float) rot.GetDim(2));

g.SetValueFloat("Power", 1.0f);
g.SetValueBool("Override", true);
}
}

/// <summary>
/// Stop overriding gyros
/// </summary>
private void Stop()
{
Echo("Stop");

isRunning = false;
Runtime.UpdateFrequency = UpdateFrequency.None;

foreach (IMyGyro g in gyroList) {
g.SetValueBool("Override", false);
}
}

/// <summary>
/// Suspend overrides for cockpit control
/// </summary>
private void Suspend()
{
Echo("Override");

foreach (IMyGyro g in gyroList) {
g.SetValueBool("Override", false);
}
}

/// <summary>
/// Do Initialize
/// </summary>
/// <remarks>
/// This is the initialization function<br />
/// Call it whenever the ships configuration changes
/// </remarks>
private void Initialize()
{
Me.CustomData = "";

//
// find a remote control
//

// ReSharper disable once ConditionIsAlwaysTrueOrFalse
// ReSharper disable once HeuristicUnreachableCode
if (REMOTE_CONTROL_NAME != "") {
remoteControl = GridTerminalSystem.GetBlockOfTypeWithName<IMyRemoteControl>(REMOTE_CONTROL_NAME, Me);
}

if (remoteControl == null) {
remoteControl = GridTerminalSystem.GetBlockOfType<IMyRemoteControl>(x => x.IsSameConstructAs(Me));

if (remoteControl == null) {
const string err = "Warning: No Remote Control found on the ship.\n";
Me.CustomData += err;
Echo(err);

COCKPIT_FEATURES_ENABLED = false;
}
}

//
// Find a cockpit
//

cockpit = GridTerminalSystem.GetBlockOfType<IMyCockpit>(x => x.IsSameConstructAs(Me) && x.IsUnderControl);

if (cockpit == null) {
const string err = "Warning: No cockpit on the ship, cockpit control features have been disabled.\n";
Me.CustomData += err;
Echo(err);

COCKPIT_FEATURES_ENABLED = false;
}

//
// Find the gyros
//

GridTerminalSystem.GetBlocksOfType(gyroList, x => x.IsSameConstructAs(Me));

if (gyroList.Count == 0) {
const string err = "Error: No Gyroscopes found on the current ship.\n";
Me.CustomData += err;
Echo(err);
}

//
// Find connector
//

GridTerminalSystem.GetBlocksOfType(connectorList, x=>x.IsSameConstructAs(Me));
}

}

// =======================================================
// Class Extensions
// =======================================================

/// <summary>
/// Class containing extension methods for the following classes<br />
/// <br />
/// <see cref="IMyGridTerminalSystem">GridTerminalSystem</see>
/// </summary>
// ReSharper disable once UnusedType.Global
public static class Extensions
{
/// <summary>
/// Get a single block of type T, filtered by Func<br />
/// </summary>
/// <remarks>
/// Caution: This function allocates a list every call.<br />
/// </remarks>
/// <typeparam name="T">type of the block to return</typeparam>
/// <param name="gts">reference to GridTerminalSystem</param>
/// <param name="collect">function to determine if a block should be added to collection</param>
/// <returns>T block, or null on no block found</returns>
public static T GetBlockOfType<T>(this IMyGridTerminalSystem gts, Func<T, bool> collect = null) where T : class
{
List<T> blockCache = new List<T>();

gts.GetBlocksOfType(blockCache, collect);

T result = blockCache.Count > 0 ? blockCache[0] as T : null;

return result;
}

/// <summary>
/// Get a single block to type T, whose name starts with 'name', and exists on the same grid (or sub-grid) as
/// 'anyBlock'<br />
/// </summary>
/// <remarks>
/// Sub-grids are those mechanically connected to a grid (via rotors, pistons, etc),<br />
/// But not including those connected by connectors.
/// </remarks>
/// <typeparam name="T">type of the block to return</typeparam>
/// <param name="gts">reference to GridTerminalSystem</param>
/// <param name="name">name of the block to search for</param>
/// <param name="anyBlock">any block existing on the "grid" to filter with</param>
/// <returns>T block</returns>
public static T GetBlockOfTypeWithName<T>(this IMyGridTerminalSystem gts, string name, IMyTerminalBlock anyBlock = null) where T : class
{
if (anyBlock == null) {
return GetBlockOfType<IMyTerminalBlock>(gts, block => block is T && block.CustomName.StartsWith(name)) as T;
}

return GetBlockOfType<IMyTerminalBlock>(gts, block => block is T && block.IsSameConstructAs(anyBlock) && block.CustomName.Contains(name)) as T;
}[/code]
Last edited by Gate; 5 Oct, 2021 @ 12:45pm