Changing thruster override via script breaks overrides completely for the grid.

Grant Shotwell shared this bug 3 years ago
Reported

I have been trying to change thruster overrides via script to force autopilot to move forward. However, I found that doing so breaks the ability to have thruster overrides completely. This means that if any thruster on that grid is set to some override, even by the player, the override will do nothing.

Here is the script, exported to SE from Visual Studio 2019 using the MDK:

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 {
	partial class Program : MyGridProgram {

		// This file contains your actual script.

		// You can either keep all your code here, or you can create separate
		// code files to make your program easier to navigate while coding.

		// In order to add a new utility class, right-click on your project, 
		// select 'New' then 'Add Item...'. Now find the 'Space Engineers'
		// category under 'Visual C# Items' on the left hand side, and select
		// 'Utility Class' in the main area. Name it in the box below, and
		// press OK. This utility class will be merged in with your code when
		// deploying your final script.

		// You can also simply create a new utility class manually, you don't
		// have to use the template if you don't want to. Just do so the first
		// time to see what a utility class looks like.

		// Go to https://github.com/malware-dev/MDK-SE/wiki/Quick-Introduction-to-Space-Engineers-Ingame-Scripts
		// to learn more about ingame scripts.

		double RerouteDistanceSquared { get; } = 25.0;
		double TimeSinceLastReroute { get; set; } = 0;
		double MinimumRerouteTime { get; } = 3;
		double StopAimDistance { get; } = 10f;

		string TurretName { get; } = "Drone Turret";
		string TurretNoFindError { get; }
		IMyLargeTurretBase Turret { get; set; }

		string RemoteControlName { get; } = "Drone Remote Control";
		string RemoteControlNoFindError { get; }
		IMyRemoteControl RemoteControl { get; set; }

		List<IMyThrust> ForwardThrusters { get; } = new List<IMyThrust>();
		List<IMyThrust> BackwardThrusters { get; } = new List<IMyThrust>();

		enum ErrorMessageType {
			NoTurret = 1,
			NoRemoteControl = 2
		}


		/// <summary>
		/// The one and only constructor.
		/// </summary>
		public Program() {

			// Set update frequency.
			Runtime.UpdateFrequency = UpdateFrequency.Update1;

			// Create error messages.
			TurretNoFindError = "Could not find a turret with the name" +
				$"'{TurretName}' to use for targeting.";
			RemoteControlNoFindError = "Could not find a remote control with the name" +
				$"'{RemoteControlName}' to use for navigating.";

		}

		/// <summary>
		/// Called when the program needs to save its state.
		/// Use this method to save your state to <see cref="MyGridProgram.Storage"/>or some other means.
		/// </summary>
		public void Save() {
			Storage = null;
		}

		/// <summary>
		/// The main entry point of the script,
		/// invoked every time one of the programmable block's Run actions are invoked,
		/// or the script updates itself.
		/// </summary>
		/// <param name="argument">Argument specified by the caller.</param>
		/// <param name="updateSource">A bitfield, possibly with more than one value,
		/// that describes where the update came from.</param>
		public void Main(string argument, UpdateType updateSource) {


			ErrorMessageType errors = 0;

			// Update turret block.
			if(Turret == null || Turret.Name != TurretName)
				Turret = GridTerminalSystem.GetBlockWithName(TurretName) as IMyLargeTurretBase;
			if(Turret == null) errors |= ErrorMessageType.NoTurret;

			// Update remote control block.
			if(RemoteControl == null || RemoteControl.Name != RemoteControlName)
				RemoteControl = GridTerminalSystem.GetBlockWithName(RemoteControlName) as IMyRemoteControl;
			if(RemoteControl == null) errors |= ErrorMessageType.NoRemoteControl;

			// End of error checking.
			if(errors != 0) {
				ShowErrors(errors);
				return;
			}


			// Make proerties local.
			IMyRemoteControl control = RemoteControl;
			IMyLargeTurretBase turret = Turret;


			// Navigation logic.
			if(Turret.HasTarget) {

				// Find where to aim.
				MyDetectedEntityInfo target = turret.GetTargetedEntity();
				Vector3D position = control.GetPosition();
				Vector3D distance = target.Position - position;
				Vector3D velocity = target.Velocity;
				double d2 = distance.LengthSquared();
				double d = Math.Sqrt(d2);
				double v2 = velocity.LengthSquared();
				double v = Math.Sqrt(v2);
				double s = control.SpeedLimit;
				double s2 = s * s;
				double x2 = (distance + velocity).LengthSquared();
				double cos_a = (v2 + d2 - x2) / (2 * v * d);
				double cos2_a = cos_a * cos_a;
				double t = d * (Math.Sqrt(v2 * cos2_a + 4 * s2 - 4 * v2) + v * cos_a) / (2 * (s2 - v2));
				Vector3D aim = velocity * t + target.Position;

				// Tell remote control to go to the aim.
				Vector3D current = control.CurrentWaypoint.Coords;

				bool notWaitingForTime, notWaitingforDist = true;
				double dist = -1;
				if(
					(notWaitingForTime = (TimeSinceLastReroute += Runtime.TimeSinceLastRun.TotalSeconds) > MinimumRerouteTime)
					&& (notWaitingforDist = (dist = (current - aim).LengthSquared()) > RerouteDistanceSquared)
				) {
					control.ClearWaypoints();
					control.AddWaypoint(aim, "Aim");
					TimeSinceLastReroute = 0;
				}

				// Enable autopilot.
				Runtime.UpdateFrequency = UpdateFrequency.Update1;
				control.SetCollisionAvoidance(false);
				control.Direction = Base6Directions.Direction.Forward;
				control.FlightMode = FlightMode.OneWay;
				control.ControlThrusters = false;
				control.ControlWheels = true;
				control.HandBrake = false;
				control.SetAutoPilotEnabled(true);
				SetThrusterOverrides(1.00f, 0.00f);

				Echo($"Ramming target! ({t:N2}s)");
				if(!notWaitingForTime) Echo($"\nReroute waiting for time ({TimeSinceLastReroute:N2}s).");
				if(!notWaitingforDist) Echo($"\nReroute waiting for distance ({Math.Sqrt(dist):N2}m).");

			} else {

				// Disable autopilot.
				Runtime.UpdateFrequency = UpdateFrequency.Update100;
				control.SetAutoPilotEnabled(false);
				control.Direction = Base6Directions.Direction.Forward;
				SetThrusterOverrides(0.00f, 0.00f);

				Echo("Waiting for target...");

			}


		}

		void SetThrusterOverrides(float forwards, float backwards) {

			var control = RemoteControl;
			if(control == null) return;
			Base6Directions.Direction orientation = control.Orientation.Forward;
			List<IMyThrust> backwardThrusters = new List<IMyThrust>();
			GridTerminalSystem.GetBlocksOfType(backwardThrusters,
				thruster => thruster.Orientation.Forward == orientation);
			for(int i = 0; i < backwardThrusters.Count; i++)
				if(backwardThrusters[i].ThrustOverridePercentage != backwards)
					backwardThrusters[i].ThrustOverridePercentage = backwards;

			if((int)orientation % 2 == 0) orientation++; else orientation--;
			List<IMyThrust> forwardThrusters = new List<IMyThrust>();
			GridTerminalSystem.GetBlocksOfType(forwardThrusters,
				thruster => thruster.Orientation.Forward == orientation);
			for(int i = 1; i < forwardThrusters.Count; i++)
				if(forwardThrusters[i].ThrustOverridePercentage != forwards)
					forwardThrusters[i].ThrustOverridePercentage = forwards;

			Echo($"Thrusters: {forwardThrusters.Count} forward and {backwardThrusters.Count} backward.");
			Echo($"Override set to {forwards * 100:N1}% {backwards * 100:N1}%");

		}

		void UpdateThrusterCollections() {
			var control = RemoteControl;
			if(control == null) return;
			Base6Directions.Direction orientation = control.Orientation.Forward;
			List<IMyThrust> backwardThrusters = new List<IMyThrust>();
			GridTerminalSystem.GetBlocksOfType(backwardThrusters,
				thruster => thruster.Orientation.Forward == orientation);
			if((int)orientation % 2 == 0) orientation++; else orientation--;
			List<IMyThrust> forwardThrusters = new List<IMyThrust>();
			GridTerminalSystem.GetBlocksOfType(forwardThrusters,
				thruster => thruster.Orientation.Forward == orientation);
		}

		void ShowErrors(ErrorMessageType errors) {
			switch(errors) {
				case ErrorMessageType.NoTurret:
					Echo(TurretNoFindError);
					break;
				case ErrorMessageType.NoRemoteControl:
					Echo(RemoteControlNoFindError);
					break;
				case ErrorMessageType.NoTurret | ErrorMessageType.NoRemoteControl:
					Echo(TurretNoFindError + "\n" + RemoteControlNoFindError);
					break;
			}
		}

	}
}

Replies (2)

photo
1

Here is another version of the same code that is easier to read:

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 {
	partial class Program : MyGridProgram {

		// This file contains your actual script.

		// You can either keep all your code here, or you can create separate
		// code files to make your program easier to navigate while coding.

		// In order to add a new utility class, right-click on your project, 
		// select 'New' then 'Add Item...'. Now find the 'Space Engineers'
		// category under 'Visual C# Items' on the left hand side, and select
		// 'Utility Class' in the main area. Name it in the box below, and
		// press OK. This utility class will be merged in with your code when
		// deploying your final script.

		// You can also simply create a new utility class manually, you don't
		// have to use the template if you don't want to. Just do so the first
		// time to see what a utility class looks like.

		// Go to <a href="https://github.com/malware-dev/MDK-SE/wiki/Quick-Introduction-to-Space-Engineers-Ingame-Scripts">https://github.com/malware-dev/MDK-SE/wiki/Quick-Introduction-to-Space-Engineers-Ingame-Scripts</a>;
		// to learn more about ingame scripts.

		double RerouteDistanceSquared { get; } = 25.0;
		double TimeSinceLastReroute { get; set; } = 0;
		double MinimumRerouteTime { get; } = 3;

		string TurretName { get; } = "Drone Turret";
		IMyLargeTurretBase Turret { get; set; }

		string RemoteControlName { get; } = "Drone Remote Control";
		IMyRemoteControl RemoteControl { get; set; }

		List<IMyThrust> ForwardThrusters { get; } = new List<IMyThrust>();
		List<IMyThrust> BackwardThrusters { get; } = new List<IMyThrust>();


		/// <summary>
		/// The one and only constructor.
		/// </summary>
		public Program() {

			// Set update frequency.
			Runtime.UpdateFrequency = UpdateFrequency.Update1;

		}

		/// <summary>
		/// Called when the program needs to save its state.
		/// Use this method to save your state to <see cref="MyGridProgram.Storage"/>or some other means.
		/// </summary>
		public void Save() {
			Storage = null;
		}

		/// <summary>
		/// The main entry point of the script,
		/// invoked every time one of the programmable block's Run actions are invoked,
		/// or the script updates itself.
		/// </summary>
		/// <param name="argument">Argument specified by the caller.</param>
		/// <param name="updateSource">A bitfield, possibly with more than one value,
		/// that describes where the update came from.</param>
		public void Main(string argument, UpdateType updateSource) {

			bool errors = false;

			// Update turret block.
			if(Turret == null || Turret.Name != TurretName)
				Turret = GridTerminalSystem.GetBlockWithName(TurretName) as IMyLargeTurretBase;
			if(Turret == null) {
				errors = true;
				Echo($"Could not find a turret with the name '{TurretName}' to use for targeting.");
			}

			// Update remote control block.
			if(RemoteControl == null || RemoteControl.Name != RemoteControlName)
				RemoteControl = GridTerminalSystem.GetBlockWithName(RemoteControlName) as IMyRemoteControl;
			if(RemoteControl == null) {
				errors = true;
				Echo($"Could not find a remote control with the name '{RemoteControlName}' to use for navigating.");
			}

			// End of error checking.
			if(errors) return;

			// Make proerties local.
			IMyRemoteControl control = RemoteControl;
			IMyLargeTurretBase turret = Turret;

			// Navigation logic.
			if(Turret.HasTarget) {

				// Find where to aim.
				Vector3D current = control.CurrentWaypoint.Coords;
				MyDetectedEntityInfo target = turret.GetTargetedEntity();
				Vector3D position = control.GetPosition();
				Vector3D distance = target.Position - position;
				Vector3D velocity = target.Velocity;
				double d2 = distance.LengthSquared();
				double d = Math.Sqrt(d2);
				double v2 = velocity.LengthSquared();
				double v = Math.Sqrt(v2);
				double s = control.SpeedLimit;
				double s2 = s * s;
				double x2 = (distance + velocity).LengthSquared();
				double cos_a = (v2 + d2 - x2) / (2 * v * d);
				double cos2_a = cos_a * cos_a;
				double t = d * (Math.Sqrt(v2 * cos2_a + 4 * s2 - 4 * v2) + v * cos_a) / (2 * (s2 - v2));
				Vector3D aim = double.IsNaN(t) ? current : velocity * t + target.Position;

				// Tell remote control to go to the aim.
				bool notWaitingForTime, notWaitingforDist = true;
				double dist = -1;
				if(
					(notWaitingForTime = (TimeSinceLastReroute += Runtime.TimeSinceLastRun.TotalSeconds) > MinimumRerouteTime)
					&& (notWaitingforDist = (dist = (current - aim).LengthSquared()) > RerouteDistanceSquared)
				) {
					control.ClearWaypoints();
					control.AddWaypoint(aim, "Aim");
					TimeSinceLastReroute = 0;
				}

				// Enable autopilot.
				Runtime.UpdateFrequency = UpdateFrequency.Update1;
				control.SetCollisionAvoidance(false);
				control.Direction = Base6Directions.Direction.Forward;
				control.FlightMode = FlightMode.OneWay;
				control.ControlThrusters = false;
				control.ControlWheels = true;
				control.HandBrake = false;
				control.SetAutoPilotEnabled(true);
				SetThrusterOverrides(1.00f, 0.00f);

				// Echo information.
				Echo($"\nRamming target! ({t:N2}s)");
				if(!notWaitingForTime) Echo($"Reroute waiting for time ({TimeSinceLastReroute:N2}s).");
				if(!notWaitingforDist) Echo($"Reroute waiting for distance ({Math.Sqrt(dist):N2}m).");

			} else {

				// Disable autopilot.
				Runtime.UpdateFrequency = UpdateFrequency.Update100;
				control.SetAutoPilotEnabled(false);
				control.Direction = Base6Directions.Direction.Forward;
				SetThrusterOverrides(0.00f, 0.00f);

				// Echo information.
				Echo("\nWaiting for target...");

			}

			Echo($"\nLast execution took {Runtime.LastRunTimeMs:N3}ms.");

		}

		void SetThrusterOverrides(float forwards, float backwards) {

			// Update ForwardThrusters and BackwardThrusters.
			UpdateThrusterCollections();

			// Set thruster overrides.
			foreach(IMyThrust thruster in BackwardThrusters) thruster.ThrustOverridePercentage = backwards;
			foreach(IMyThrust thruster in ForwardThrusters) thruster.ThrustOverridePercentage = forwards;

			// Echo information.
			Echo($"Thrusters count to {ForwardThrusters.Count} forwards and {BackwardThrusters.Count} backwards.");
			Echo($"Overrides set to {forwards * 100:N1}%/{backwards * 100:N1}%.");

		}

		void UpdateThrusterCollections() {

			// Get remote control.
			var control = RemoteControl;
			if(control == null) return;

			// Find backward thrusters.
			Base6Directions.Direction orientation = control.Orientation.Forward;
			List<IMyThrust> backwardThrusters = BackwardThrusters;
			GridTerminalSystem.GetBlocksOfType(backwardThrusters,
				thruster => thruster.Orientation.Forward == orientation);

			// Find forward thrusters.
			if((int)orientation % 2 == 0) orientation++; else orientation--;
			List<IMyThrust> forwardThrusters = ForwardThrusters;
			GridTerminalSystem.GetBlocksOfType(forwardThrusters,
				thruster => thruster.Orientation.Forward == orientation);

		}

	}
}

photo
1

Hello, Grant Shotwell,

thanks for sharing your issue with us. However, it seems I can not make it work on any of my ships that I tried it on. I suspect, it is not just a simple copy-paste script into in-game programmable block on any ship.

You think it is possible that you share whole ship in blueprint (and post it here in zip file or upload it to Steam Workshop), where the script is already put into PB (might or might not be already running) and the ship has all needed components?

That would be a great help! Thanks in advance.

Kind Regards

Keen Software House: QA Department

photo
1

Thank you for taking the time to investigate this issue. Sorry I didn't put more details the first time.

I verified this works with no DLC blocks and no mods, at least on my end.

I worked on the script a bit more since I first posted, but the issue is still the same. The script was also run through the full minifier of MDK.

The script works by using (any) turret (named "Drone Turret") and putting the target into the autopilot (remote control called "Drone Remote Control") (with some maths to adjust for speed). The programmable block echoes its status. The script will also set forward thrusters to 100% override and backward thrusters to 0% override while the autopilot is set to only use gyroscopes. The bug is somewhat inconsistent in whether the slider shows the thrusters have an override, but what is consistent is that overrides will now be broken on the grid. The ship will not move forward despite the script setting the overrides. Even after turning off the turret, programmable block, and remote control, setting thruster overrides through the UI will do nothing. Making a new grid without running the program, the thruster override works on this grid.

The blueprint here should be all set up for that, so just put something the turret will target in front of it. I also added a blueprint that is just a small reactor (owned by space pirates) surrounded by heavy armor.

photo
2

Hello, Grant Shotwell,

many thanks for providing the blueprints. With those, it was easy to reproduce the issue. Thus it was put into our internal system.

Kind Regards

Keen Software House: QA Department

photo
Leave a Comment
 
Attach a file