Skip to content

Instantly share code, notes, and snippets.

@Arlen22
Last active June 22, 2023 14:14
Show Gist options
  • Save Arlen22/18ae808bd218d129e5017ead83a342e2 to your computer and use it in GitHub Desktop.
Save Arlen22/18ae808bd218d129e5017ead83a342e2 to your computer and use it in GitHub Desktop.
Space Engineers Vectors Script 3
#if DEV
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 WorkingCopy {
partial class Program : MyGridProgram {
#endif
List <IMyTerminalBlock> containers;
void Main()
{
List <IMyTerminalBlock> assemblers = new List<IMyTerminalBlock>();
containers = new List<IMyTerminalBlock>();
GridTerminalSystem.GetBlocksOfType<IMyAssembler>(assemblers);
GridTerminalSystem.GetBlocksOfType<IMyCargoContainer>(containers);
Echo("Checking " + assemblers.Count());
for (int i = 0; i < assemblers.Count(); i++)
{
cleanAssembler(assemblers[i] as IMyAssembler);
}
}
void cleanAssembler(IMyAssembler assembler)
{
if (assembler.IsProducing)
return;
IMyInventory containerDestination = null;
// search our containers until we find an empty one
for (int n = 0; n < containers.Count; n++)
{
var container = containers[n];
var containerInv = container.GetInventory(0);
if (!IsFull(containerInv)) {
containerDestination = containerInv;
break;
}
}
if (containerDestination == null)
return;
var assemblerInv = assembler.GetInventory(0);
var assemblerItems = new List<MyInventoryItem>();
assemblerInv.GetItems(assemblerItems);
for (int i = assemblerItems.Count() - 1; i >= 0; i--)
{
assemblerInv.TransferItemTo(containerDestination, i, null, true, null);
}
}
float getPercent(IMyInventory inv)
{
return ((float)inv.CurrentVolume / (float)inv.MaxVolume) * 100f;
}
bool IsFull(IMyInventory inv)
{
if (getPercent(inv) >= 99)
return true;
else
return false;
}
#if DEV
}
}
#endif
Center <<Cargo>>
Cargoall {T:Large Cargo Container}
Inventoryx T:* +Ingot
Inventoryx T:* +ice
Center <<Tanks>>
Tanks T:* Hydrogen
Tanks T:* Oxygen
Powertime
Powerstored T:*
//If the argument is not working check to see that all spelling and formating is correct
//as well as that all blocks belong to you. Also that the name you are using for the blocks
//is sufficiently unique (egs. the name "light" will affect anything named "Spotlight" as well
//as anything named "Interior Light")
//If after checking these things it still dosn't work then let me know the block and the
//argument used and I will see if I can fix the problem.
//Have fun, and happy engineering!
//List Of Valid Slider Inputs, To use these you must follow format below
// (Slider name from list:Value to set slider to/Name of blocks to affect)
List<string> sliderList = new List<string>()
{
"Radius", //Antenna, Beacon, Lights, Spherical Gravity Generator
"Power", //Gyroscope, Wheels
"Yaw", //Gyroscope
"Pitch", //Gyroscope
"Roll", //Gyroscope
"Gravity", //Gravity Generator
"Width", //Gravity Generator
"Depth", //Gravity Generator
"BreakForce", //Landing Gear
"FontSize", //LCD
"ChangeIntervalSlider", //LCD
"Falloff", //Lights
"Intensity", //Lights
"Blink Interval", //Lights
"Blink Length", //Lights
"Blink Offset", //Lights
"X", //Projector
"Y", //Projector
"Z", //Projector
"RotX", //Projector
"RotY", //Projector
"RotZ", //Projector
"Velocity", //Piston, Rotors
"UpperLimit", //Piston, Rotors
"LowerLimit", //Piston, Rotors
"Displacement", //Rotors
"BrakingTorque", //Rotors
"Torque", //Rotors
"Left", //Sensor
"Right", //Sensor
"Top", //Sensor
"Bottom", //Sensor
"Back", //Sensor
"Front", //Sensor
"VolumeSlider", //Sound Block
"RangeSlider", //Sound Block
"LoopableSlider", //Sound Block
"Restitution", //Space Ball
"Friction", //Space Ball
"VirtualMass", //Space Ball
"Override", //Thrusters
"TriggerDelay", //Timer Block
"Range", //Turrets
"DetonationTime", //Warhead
"MaxSteerAngle", //Wheels
"SteerSpeed", //Wheels
"SteerReturnSpeed", //Wheels
"Damping", //Wheels
"Strength", //Wheels
"Height", //Wheels
"Travel", //Wheels
};
//List of Valid Action Inputs, To use these you must follow format below
// (Action name from list/Name of blocks to affect)
List<string> actionList = new List<string>()
{
"OnOff", //Most
"OnOff_On", //Most
"OnOff_Off", //Most
"ShowInTerminal", //Most
"ShowInToolbarConfig", //Most
"ShowOnHUD", //Most
"UseConveyor", //Everything that can Use a conveyor... duh
"Depressurize", //Air Vent
"Depressurize_On", //Air Vent
"Depressurize_Off", //Air Vent
"EnableBroadCast", //Antenna, SpaceBall
"ShowShipName", //Antenna
"slaveMode", //Assembler
"Recharge", //Battery
"SemiAuto", //Battery
"AnyOneCanUse", //Button Panel
"ControlThrusters", //Cockpits, Cryo Chamber
"ControlWheels", //Cockpits, Cryo Chamber
"HandBrake", //Cockpits, Cryo Chamber
"MainCockpit", //Cockpits, Cryo Chamber
"DampenersOverride", //Cockpits, Cryo Chamber
"ThrowOut", //Connector
"CollectAll", //Connector
"Lock", //Connector, Landing Gear
"Unlock", //Connector, Landing Gear
"SwitchLock", //Connector, Landing Gear
"AutoLock", //Connector, Landing Gear
"DrainAll", //Convayor Sorter
"Open", //Door, Hanger Door
"Open_On", //Door, Hanger Door
"Open_Off", //Door, Hanger Door
"Override", //Gyroscope
"Idle", //Lazer Antenna
"PastGpsCoords", //Lazer Antenna
"ConectGPS", //Lazer Antenna
"isPerm", //Lazer Antenna
"BroadcastUsingAntennas", //Ore Detector
"Refill", //Oxygen Tank, Oxygen Generator
"Auto-Refill", //Oxygen Tank, Oxygen Generator
"Stockpile", //Oxygen Tank
"Stockpile_On", //Oxygen Tank
"Stockpile_Off", //Oxygen Tank
"Run", //Programing Block
"SpawnProjection", //Projector
"KeepProjection", //Projector
"ShowOnlyBuildable", //Projector
"Extend", //Piston
"Retract", //Piston
"Reverse", //Rotors, Pistons
"Detach", //Rotors
"Attach", //Rotors
"DockingMode", //Remote Controle
"DockingMode_On", //Remote Controle
"DockingMode_Off", //Remote Controle
"AutoPilot", //Remote Controle
"AutoPilot_On", //Remote Controle
"AutoPilot_Off", //Remote Controle
"Detect Players", //Sensor
"Detect Players_On", //Sensor
"Detect Players_Off", //Sensor
"Detect Floating Objects", //Sensor
"Detect Floating Objects_On", //Sensor
"Detect Floating Objects_Off", //Sensor
"Detect Small Ships", //Sensor
"Detect Small Ships_On", //Sensor
"Detect Small Ships_Off", //Sensor
"Detect Large Ships", //Sensor
"Detect Large Ships_On", //Sensor
"Detect Large Ships_Off", //Sensor
"Detect Stations", //Sensor
"Detect Stations_On", //Sensor
"Detect Stations_Off", //Sensor
"Detect Asteroids", //Sensor
"Detect Asteroids_On", //Sensor
"Detect Asteroids_Off", //Sensor
"Detect Owner", //Sensor
"Detect Owner_On", //Sensor
"Detect Owner_Off", //Sensor
"Detect Friendly", //Sensor
"Detect Friendly_On", //Sensor
"Detect Friendly_Off", //Sensor
"Detect Neutral", //Sensor
"Detect Neutral_On", //Sensor
"Detect Neutral_Off", //Sensor
"Detect Enemy", //Sensor
"Detect Enemy_On", //Sensor
"Detect Enemy_Off", //Sensor
"AudibleProximityAlert", //Sensor (may not work)
"PlaySound", //Sound Block
"StopSound", //Sound Block
"TriggerNow", //Timer Block
"Start", //Timer Block
"Stop", //Timer Block
"EnableIdleMovement", //Turrets
"EnableIdleMovement_On", //Turrets
"EnableIdleMovement_Off", //Turrets
"Shoot", //Turrets, Rocket Launcher
"Shoot_On", //Turrets, Rocket Launcher
"Shoot_Off", //Turrets, Rocket Launcher
"ShootOnce", //Turrets, Rocket Launcher
"TargetMeteors", //Turrets
"TargetMeteors_On", //Turrets
"TargetMeteors_Off", //Turrets
"TargetMoving", //Turrets
"TargetMoving_On", //Turrets
"TargetMoving_Off", //Turrets
"TargetMissiles", //Turrets
"TargetMissiles_On", //Turrets
"TargetMissiles_Off", //Turrets
"TargetSmallShips", //Turrets
"TargetSmallShips_On", //Turrets
"TargetSmallShips_Off", //Turrets
"TargetLargeShips", //Turrets
"TargetLargeShips_On", //Turrets
"TargetLargeShips_Off", //Turrets
"TargetCharactors", //Turrets
"TargetCharactors_On", //Turrets
"TargetCharactors_Off", //Turrets
"TargetStations", //Turrets
"TargetStations_On", //Turrets
"TargetStations_Off", //Turrets
"StartCountdown", //WarHead
"StopCountdown", //Warhead
"Safety", //Warhead
"Detonate", //Warhead
"helpOthers", //Welder
"Steering", //Wheels
"InvertSteering", //Wheels
"Propulsion", //Wheels
"ResetHight", //Wheels
"ResetTravel", //Wheels
};
void Main(string argument)
{
if(argument.StartsWith('[')) {
var lines = Me.CustomData.Split('\n');
var started = false;
for (var a = 0; a < lines.Count(); a++)
{
if (lines[a] == argument) started = true;
else if (lines[a].StartsWith('[')) started = false;
else if (started) try { Run(lines[a]); } catch (Exception e) { Echo("Error"); Echo(lines[a]); }
}
} else {
Run(argument);
}
}
void Run(string argument)
{
IMyTextPanel debugPanel = (IMyTextPanel)GridTerminalSystem.GetBlockWithName("LCD Panel");
string arg = argument;
List<string> groops = new List<string>();
//Takes the argument and grabs the pieses between the parentheses and stuffs them in the groops list
//using an indexOf while loop
int start = 0;
while ((start = arg.IndexOf ('(', start)) != -1)
{
int end = (start >=0) ? arg.IndexOf (')', start) : -1;
string result = (end >= 0) ? arg.Substring(start + 1, end - start - 1) : "";
groops.Add(result);
start++;
}
List<IMyTerminalBlock> selected = new List<IMyTerminalBlock>();
//Main Action Nest
for (var a = 0; a < groops.Count; a++)
{
string selectedGroop = groops[a];
List<string> args = new List<string>(selectedGroop.Split('/'));
string toApply = args[0];
List<String> sliderApply = new List<string>(toApply.Split(':'));
if (sliderList.Contains (sliderApply[0]) && toApply.Contains (":") ) //applyes slider spec to blocks
{
var sliderApplyValue = float.Parse(sliderApply[1]);
toApply = sliderApply[0];
for (var b = 1; b < args.Count; b++)
{
string selectedArg = args[b];
GridTerminalSystem.SearchBlocksOfName(selectedArg ,selected);
for (var c = 0; c < selected.Count; c++)
{
var applyToSelected = selected[c];
applyToSelected.SetValueFloat(toApply, sliderApplyValue);
}
}
}else if (actionList.Contains (toApply)) //applyes action to blocks
{
for (var b = 1; b < args.Count; b++)
{
string selectedArg = args[b];
GridTerminalSystem.SearchBlocksOfName(selectedArg ,selected);
for (var c = 0; c < selected.Count; c++)
{
var applyToSelected = selected[c];
applyToSelected.ApplyAction(toApply);
}
}
}
}
}
/*
* R e a d m e
* -----------
*
* Unlike the previous script, this script does not have the concept of a single
* registered controller. Instead, any occupied controller block with the name tag
* may be used as a controller. All controllers (including the reference
* controller) need the tag [PFC] or [PFC:?] in their name, where ? is "X" (for no
* screen) or a number specifying which screen to display the status on. The text
* "PFC" can be changed in the CustomData config of the script block.
*
* A controller is any block which can be used to give mouse and WASD inputs to
* control the ship. Remote controls, cockpits, helms, and anything else that
* accepts movement and rotation inputs are valid controllers.
*
* The reference controller also needs the line "[Reference Controller]" in its
* CustomData.
*
*/
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 WorkingCopy
{
partial class Program : MyGridProgram
{
// This script was deployed at 2022-03-13 21:22
// these are the direction enums used in the config
public enum Directions : byte { forward, backward, left, right, up, down, }
// these are the control scope enums used in the config
public enum ControlScope : byte { grid, construct, everything }
// these are constants which may be changed if necessary
const float const_IdleThrust = 0.1F;
const double const_MaxRollLimit = 85 * degToRad;
const double const_ThrustApproach = 15;
class RollerController : ModeController
{
public RollerController(Program root) : base(root) { }
public override string Command { get; } = "roller";
public bool DoTorque { get; set; }
double rollResponse { get { return Config.RollerRollResponse.Value; } }
double turnResponse { get { return Config.RollerTurnResponse.Value; } }
double pitchResponse { get { return Config.RollerPitchResponse.Value; } }
double maxRoll { get { return Config.RollerMaxRoll.Value * degToRad; } }
double minRoll { get { return Config.RollerMinRoll.Value * degToRad; } }
double turnRamp { get { return Config.RollerTurnRamp.Value; } }
double desiredTurn, desiredPitch, setSpeed;
public override void Tick()
{
DoTorque = Active;
if (!Active) { return; }
// roller only works in gravity
if (gravity.IsZero()) { Stop(); return; }
Vector3D gravNorm = -gravity;
double gravLeng = gravNorm.Normalize();
Ticker.WorldMatrix = Controller.WorldMatrix.ByDirection(B6D.Up, gravNorm);
MatrixD matrix = Controller.WorldMatrix;
Vector3D worldSpeed = velocity.ProjectOnPlane(gravNorm);
Vector3D gravForward = matrix.Forward.ProjectOnPlane(gravNorm).Norm();
Vector3D gravLeft = matrix.Left.ProjectOnPlane(gravNorm).Norm();
Vector3D current = matrix.Up.AngleFromVector(gravNorm);
Vector3D desired = Vector3D.Zero;
bool stopZ, stopX, moveZ, moveX, speedZ, speedX, pointZ, skipgyro, holdXZ = Ticker.Hold.Active;
stopZ = stopX = moveZ = moveX = speedZ = speedX = pointZ = skipgyro = false;
switch (Mode)
{
case Modes.move: { stopX = stopZ = moveX = moveZ = true; break; }
case Modes.fly: { stopX = moveX = moveZ = true; break; }
case Modes.freefly: { moveX = moveZ = true; break; }
case Modes.cruise: { stopX = stopZ = speedZ = true; break; }
case Modes.stop: { stopX = stopZ = true; break; }
case Modes.glide: { stopX = moveX = true; break; }
case Modes.pitchglide: { pointZ = stopX = moveX = true; break; }
case Modes.pitchlevel: { pointZ = true; break; }
case Modes.freeglide: { break; }
case Modes.disabled: { skipgyro = true; DoTorque = false; break; }
default: { return; }
}
if (holdXZ)
{
gravForward = Ticker.Hold.StartForward.ProjectOnPlane(gravNorm).Norm();
gravLeft = gravForward.Cross(gravity).Norm();
speedX = moveX = false;
stopX = true;
desired += -Ticker.Hold.Error.ProjectOnPlane(gravNorm).ClampToSphere(setSpeed);
}
Ticker.Input = Ticker.LinearInput();
desiredTurn += Ticker.InputAng.ByMatrix(Controller).Y * turnResponse / turnRamp - desiredTurn / turnRamp;
if (pointZ) { desiredPitch += Ticker.InputAng.ByMatrix(Controller).X * pitchResponse / 60; }
if (speedZ) { desired += -gravForward * setSpeed; }
if (speedX) { desired += -gravLeft * setSpeed; }
if (stopZ) { desired += velocity.ProjectOnVector(gravForward); }
if (stopX) { desired += velocity.ProjectOnVector(gravLeft); }
if (moveZ) { desired += gravForward * Ticker.Input.Z * setSpeed; }
if (moveX) { desired += gravLeft * Ticker.Input.X * setSpeed; }
var showSpeed = moveZ || moveX || speedZ || speedX;
Status("Roller: " + Mode.ToString() + (showSpeed ? " + " + setSpeed.ToString("N1") : ""));
PrintLine(Mode.ToString() + " @ " + setSpeed.ToString("N1"));
var rollThrust = matrix.Down * Ticker.ScopeGridsThrust(matrix.Down, false) / mass;
var maxThrustRoll = Math.Acos(gravLeng / rollThrust.Length()).NotNan();// adjacent / hypotenuse
var desiredRoll = Math.Atan2(desired.Length(), gravLeng);// opposite / adjacent
var finalRoll = Min(const_MaxRollLimit, maxRoll, Math.Max(maxThrustRoll, minRoll), desiredRoll);
var thrust = matrix.Down.ProjectOnPlane(gravNorm).Norm() * gravLeng * Math.Tan(current.Length());
Ticker.Thrust += pointZ ? thrust.ProjectOnVector(gravLeft) : thrust;
PrintLine($"Current Roll: {current.Length() * radToDeg:N3}");
PrintLine($"Desired Roll: {desiredRoll * radToDeg:N3}");
PrintLine($"Final Roll: {finalRoll * radToDeg:N3}");
PrintLine($"Thrust Roll: {maxThrustRoll * radToDeg:N3}");
PrintLine($"Thrust: {thrust.Length():N3}");
// remove damper for directions which are active
if (speedZ || moveZ || stopZ) { Ticker.Damper = Ticker.Damper.ProjectOnPlane(gravForward); }
if (speedX || moveX || stopX) { Ticker.Damper = Ticker.Damper.ProjectOnPlane(gravLeft); }
// remove inputs for controlled directions
Ticker.Input.X *= (speedX || moveX ? 0 : 1);
Ticker.Input.Z *= (speedZ || moveZ ? 0 : 1);
// set inputs relative to the level matrix we set earlier
Ticker.Input = Ticker.Input.ByMatrix(Ticker.WorldMatrix, true);
Status(Ticker.Input.Length());
if (skipgyro) { return; }
Ticker.DoTorque = true;
var axle = desired.Cross(gravNorm).Norm();
// IMPORTANT: desired and current must be added, not subtracted.
if (pointZ)
{
Ticker.Torque = Ticker.Torque
+ matrix.Left * desiredPitch * degToRad * 0.3
+ matrix.Forward * finalRoll * axle.DotSign(matrix.Forward)
+ current.ProjectOnPlane(matrix.Down);
}
else
{
desiredPitch = 0;
Ticker.Torque += axle * finalRoll + current;
}
Ticker.Torque += -gravNorm * desiredTurn * degToRad;
}
Modes Mode = Modes.disabled;
enum Modes { stop, glide, freeglide, disabled, move, fly, freefly, cruise, pitchglide, pitchlevel }
public override void Run(string[] args)
{
Active = true;
Mode = argEnum(args, 1, Modes.disabled);
if (Mode == Modes.disabled)
{
if (args[1] != Modes.disabled.ToString())
Details(string.Join("\n", args[1] + " not found.\nExpected one of " + ConfigOption.GetEnumOptions<Modes>()));
Details("Disabled mode is active. It does freeglide calculations, but does not set gyros");
}
setSpeed = arg(args, 2, 0.0);
}
}
class ForceStaticController : SimpleController
{
public ForceStaticController(Program p) : base(p, "STATIC") { }
public override void Tick() { Ticker.ForceStatic = Active; }
}
class UndampController : SimpleController
{
public UndampController(Program p) : base(p, "undamp") { }
public override void Tick() { if (Active) Ticker.DoThrust = false; }
}
class CruiseController : SimpleController
{
public CruiseController(Program p) : base(p, "cruise") { }
double TargetSpeed;
B6D Direction;
double Error;
public override void Run(string[] args)
{
base.Run(args);
TargetSpeed = arg(args, 1, 0.0);
Direction = arg(args, 2, B6D.Forward);
Error = 0;
}
public override void Tick()
{
if (!Active) { return; }
var fwd = Ticker.Hold.Active ? Ticker.Hold.StartForward : Ticker.WorldMatrix.GetDirectionVector(Direction);
Ticker.AddSpeedThrust(fwd * TargetSpeed, ref Error);
Ticker.ControlPlane(fwd);
}
}
class AltitudeController : SimpleController
{
public AltitudeController(Program p) : base(p, "altitude") { }
double TargetAltitude;
MyPlanetElevation Reference = MyPlanetElevation.Surface;
public override void Run(string[] args)
{
base.Run(args);
TargetAltitude = arg(args, 1, 90.0);
Reference = argEnum(args, 2, MyPlanetElevation.Surface);
}
public override void Tick()
{
if (!Active) { return; }
double height = Controller.PlanetHeight(Reference);
if (height == 0) { PrintLine("Height not found"); return; }
var dir = -gravity.Norm() * (TargetAltitude - height);
Ticker.AddDistanceThrust(dir);
Ticker.ControlPlane(gravity);
PrintLine("Altitude");
}
}
class DistanceController : SimpleController
{
public DistanceController(Program p) : base(p, "distance") { }
double TargetDistance;
B6D Direction;
Vector3D Target, Vector;
public override void Run(string[] args)
{
base.Run(args);
TargetDistance = arg(args, 1, 0.0);
Direction = arg(args, 2, B6D.Forward);
Vector = Ticker.WorldMatrix.GetDirectionVector(Direction);
Target = position + Vector * TargetDistance;
}
public override void Tick()
{
if (!Active) { return; }
PrintLine("Distance");
Ticker.AddDistanceThrust(-(position - Target).Reject(Vector));
Ticker.AddDistanceThrust(-(position - Target).ProjectOnVector(Vector));
Ticker.ControlNone();
Ticker.Torque -= Vector.AngleFromVector(Ticker.WorldMatrix.Forward);
Ticker.DoTorque = true;
}
}
class HoldController : SimpleController
{
public HoldController(Program p) : base(p, "hold") { }
public Vector3D StartForward { get { return IsTarget ? -Vector : Vector; } }
Vector3D Origin, Vector;
public bool IsTarget { get; private set; }
public Vector3D Error { get { return -(position - Origin).Reject(Vector); } }
public override void Run(string[] args)
{
base.Run(args);
Vector = Ticker.WorldMatrix.Forward;
Origin = position;
IsTarget = false;
}
public void Run(Vector3D vec, Vector3D tar)
{
base.Run();
Vector = vec;
Origin = tar;
IsTarget = true;
}
public override void Tick()
{
if (!Active) { return; }
PrintLine("Hold");
Ticker.AddDistanceThrust(Error);
Ticker.ControlVector(StartForward);
Ticker.Torque -= StartForward.AngleFromVector(Ticker.WorldMatrix.Forward);
Ticker.DoTorque = true;
}
}
class TargetController : SimpleController
{
public TargetController(Program p) : base(p, "target") { }
double AmmoSpeed;
B6D Direction;
double Error;
List<IMyLargeTurretBase> Directors = new List<IMyLargeTurretBase>();
IMyLargeTurretBase ActiveDirector;
public override void Run(string[] args)
{
base.Run(args);
AmmoSpeed = arg(args, 1, 0.0);
Direction = arg(args, 2, B6D.Forward);
Init();
}
public void Init()
{
Active = false;
Error = 0;
Directors.Clear();
GTS.GetBlocksOfType<IMyLargeTurretBase>(Directors);
foreach (var Director in Directors)
{
if (Director.CubeGrid == Me.CubeGrid && Director.HasTarget)
{
ActiveDirector = Director;
Active = true;
break;
}
}
}
public override void Tick()
{
if (!Active) { return; }
Status(ActiveDirector.HasTarget.ToString());
if (!ActiveDirector.HasTarget) { Init(); return; }
var fwd = Ticker.WorldMatrix.GetDirectionVector(Direction);
var target = ActiveDirector.GetTargetedEntity();
//GetAimDirection(Vector3D intercept, Vector3D tarVelocity, Vector3D ownVelocity, double shot, Vector3D gravity, Action < string > debug)
var aim = GetAimDirection(
target.Position - ActiveDirector.GetPosition(),
target.Velocity,
velocity,
AmmoSpeed,
gravity,
PrintLine);
// Ballistic(Vector3D intercept, Vector3D gravity, double velocity)
//var ball = Ballistic(aim, gravity, AmmoSpeed);
Status(aim);
var diff = aim.AngleFromVector(fwd);
Error = diff.Normalize() + Error * 0.8;
Ticker.Torque -= diff * Error;
//Ticker.AddSpeedThrust(target.Velocity, ref Error);
//Ticker.ControlPlane(fwd);
Ticker.DoTorque = true;
}
}
class Subgrid : ControllerBase
{
public readonly List<IMyShipController> controllers = new List<IMyShipController>();
public readonly List<IMyShipConnector> connectors = new List<IMyShipConnector>();
public readonly List<IMyLandingGear> locks = new List<IMyLandingGear>();
public readonly List<IMyThrust> thrusters = new List<IMyThrust>();
public readonly List<IMyGyro> gyros = new List<IMyGyro>();
public readonly VectorPair maxThrust = new VectorPair();
public readonly List<bool> enabled = new List<bool>();
public readonly IEnumerable<IMyLandingGear> LockedLocks;
public readonly IEnumerable<IMyThrust> EnabledThrusters;
public readonly IEnumerable<IMyGyro> EnabledGyros;
public readonly IMyCubeGrid grid;
public bool InScope
{
get
{
var scope = Config.GeneralControlScope.Value;
if (scope == ControlScope.grid) { return grid == HomeGrid; }
if (scope == ControlScope.construct) { return grid.IsSameConstructAs(HomeGrid); }
return true;
}
}
public Subgrid(Program prog, IMyCubeGrid grid) : base(prog)
{
this.grid = grid;
LockedLocks = locks.Where(e => e.IsLocked);
EnabledGyros = gyros.Where(e => e.IsWorking);
EnabledThrusters = thrusters.Where((e, i) => enabled[i] && e.IsWorking);
TestThrust = e => e.MaxEffectiveThrust / e.MaxThrust > Config.ThrustersMinimumThrust.Value;
}
Func<IMyThrust, bool> TestThrust;
public void CalculateGrid()
{
maxThrust.Clear();
enabled.Clear();
if (thrusters.Count == 0) { return; }
enabled.AddRange(thrusters.Select(TestThrust));
foreach (var t in EnabledThrusters)
{
Vector3D engineForce = t.Orientation.Forward.Cast();
engineForce *= t.MaxEffectiveThrust;
maxThrust.Add(engineForce);
}
}
public double lastThrust = 0;
public int lastGyroCount = 0;
public double GetThrust(Vector3D direction) => lastThrust = maxThrust.Quadrant(direction).Dot(direction);
public int GetGyroCount() => lastGyroCount = EnabledGyros.Count();
public void SetThrust(Vector3D move)
{
if (thrusters.Count == 0) { return; }
bool zero = move.IsZero();
for (var i = 0; i < thrusters.Count; i++)
{
IMyThrust t = thrusters[i];
if (zero) { t.ThrustOverride = const_IdleThrust; continue; }
if (!enabled[i]) { if (t.Enabled || t.ThrustOverride != 0) { t.Enabled = false; t.ThrustOverride = 0; } continue; }
if (!t.IsWorking && t.Enabled) { continue; }
var fullThrust = maxThrust.Quadrant(t.Orientation.Forward, true).Sum;
double rel = t.MaxEffectiveThrust / fullThrust;
double comp = t.MaxThrust / t.MaxEffectiveThrust;
Vector3D desired = move * rel * comp;
Vector3D project = desired.ProjectOnRay(t.WorldMatrix.Forward);
t.ThrustOverride = project.IsZero() ? const_IdleThrust : (float)project.Length();
t.Enabled = true;
}
}
public void SetTorque(Vector3D vector)
{
bool zero = vector.IsZero();
foreach (var g in EnabledGyros)
{
var axis = zero ? vector : vector.ByMatrix(g).ClampToSphere(g.GetMaximum<float>("Roll"));
g.Pitch = 0 - (float)axis.X;
g.Yaw = 0 - (float)axis.Y;
g.Roll = 0 - (float)axis.Z;
g.GyroOverride = true;
}
}
}
class TickerController : RootController
{
double DistanceSpeed { get { return Config.GeneralDistanceSpeed.Value; } }
double HoldSpeed { get { return Config.UndampStop.Value; } }
double Accel { get { return Config.UndampAccel.Value; } }
double Decel { get { return Config.UndampDecel.Value; } }
public TickerController(Program p) : base(p) { }
public Vector3D Thrust, Torque, Damper, Input, InputAng, lastAngular, lastVelocity, lastThrust, lastTorque;
public MatrixD WorldMatrix;
public bool DoTorque, DoThrust, ForceStatic = false;
public void AddSpeedThrust(Vector3D speed) => AddSpeedThrust(speed, velocity.ProjectOnVector(speed));
public void AddSpeedThrust(Vector3D speed, Vector3D velocity) { Thrust -= speed - velocity; }
public void AddSpeedThrust(Vector3D speed, ref double error)
{
var diff = speed - velocity;
error = diff.Normalize() + error * 0.9;
Thrust -= diff * error;
}
public void AddDistanceThrust(Vector3D direction, double max = double.NaN) =>
AddDistanceThrust(direction, velocity.ProjectOnVector(direction),
max.IsNan(DistanceSpeed), Ticker.ScopeGridsThrust(direction) / mass);
public void AddDistanceThrust(Vector3D direction, Vector3D velocity, double maxspeed, double maxbrake)
{
var dir = direction;
var dist = dir.Normalize();
var vel = velocity.Dot(dir);
var curbrake = (vel * vel / dist / 2);
var expected = Math.Sqrt(dist * maxbrake * 2);
var target = Math.Min(expected - curbrake, maxspeed);
var speed = dir * target - dir * vel;
DeNan(ref speed);
Thrust -= speed;
PrintLine($"Max Brake: {maxbrake:N5}");
PrintLine($"T Velocity: {expected:N5}");
PrintLine($"T Brake: {curbrake:N5}");
PrintLine($"C Distance: {dist:N5}");
PrintLine($"C Velocity: {vel:N5}");
PrintLine($"Result: {speed.Length():N5}");
}
public void ControlNone() { Ticker.Input = Vector3D.Zero; Ticker.Damper = Vector3D.Zero; }
public void ControlPlane(Vector3D dir) { Ticker.Input = Ticker.Input.ProjectOnPlane(dir); Ticker.Damper = Ticker.Damper.ProjectOnPlane(dir); }
public void ControlVector(Vector3D dir) { Ticker.Input = Ticker.Input.ProjectOnVector(dir); Ticker.Damper = Ticker.Damper.ProjectOnVector(dir); }
public Vector3D LinearInput() => ActiveControllers.Aggregate(Vector3D.Zero, (n, e) => n + e.MoveIndicator);
public Vector3D AngularInput() => ActiveControllers.Aggregate(Vector3D.Zero, (n, e) => n + new Vector3D(e.RotationIndicator, e.RollIndicator));
protected override void DoTick()
{
if (Controller == null) { return; }
WorldMatrix = Controller.WorldMatrix;
_root.Status.Get().Clear();
CheckSubgrids();
var BaseMass = Controller.CalculateShipMass().BaseMass;
var height = Controller.PlanetHeight(MyPlanetElevation.Surface);
if (Math.Abs(lastBaseMass - BaseMass) > 0.01) { SetupSubgrids(); lastBaseMass = BaseMass; }
bool isStatic = ForceStatic || (HasStatic || HasLocked) && velocity.IsZero() && Config.GeneralSpeedCheck.Value;
if (isStatic) { Status("||| STATIC |||"); }
Status(String.Join("", Modes.Select((e) => e.Active ? "^" : "_").ToArray()));
Status($"Speed: {Controller.GetShipSpeed():N3}");
Status($"Height: {height:N3}");
Status($"Base mass: {BaseMass:N0}");
Status($"Max mass: {ScopeGridsThrust(gravity, false) / gravity.Length():N0}");
Status($"Weight: {mass:N0}");
Thrust = Torque = Vector3D.Zero;
Damper = velocity;
DoTorque = false;
DoThrust = true;
Input = LinearInput().ByMatrix(WorldMatrix, true);
InputAng = AngularInput().ByMatrix(WorldMatrix, true);
Modes.ForEach(e => { e.Tick(); });
if (!Input.IsZero())
{
var InputBrake = ScopeGridsThrust(Input) / mass;
Thrust += -Input * Math.Min(InputBrake, Accel);
Thrust += (velocity.ProjectOnRay(-Input).ClampToSphere(InputBrake) * const_ThrustApproach)
.ClampToSphere(Math.Max(Decel - Math.Min(InputBrake, Accel), 0));
Damper = Damper.ProjectOnPlane(Input);
}
if (Controller.DampenersOverride)
{
var DamperThrust = ScopeGridsThrust(Damper) / mass;
Thrust += (Damper.ClampToSphere(DamperThrust) * const_ThrustApproach)
.ClampToSphere(HoldSpeed);
}
bool doTorque = DoTorque, doThrust = DoThrust;
if (isStatic) { Thrust = Vector3D.Zero; } else { Thrust += gravity; }
Thrust *= mass;
// ATTEMPTED: Do not clamp thrust vector to max thrust as it will cause strange behaviour if
// there is no available thruster on a side. The standard mode does not account for excessive bank.
// attempt 1 : after gravity
// attempt 2 : before gravity
var totalThrust = ScopeGridsThrust(Thrust, false);
var thrust = Thrust;
var torque = (Torque - angular).ClampToSphere(const_torque_input_peak) + angular;
foreach (var grid in ScopeGrids)
{
if (doThrust) { grid.SetThrust(thrust * (grid.lastThrust / totalThrust).NotNan()); } else { foreach (var t in grid.EnabledThrusters) { t.ThrustOverride = 0; } }
if (doTorque) { grid.SetTorque(torque); } else { foreach (var g in grid.EnabledGyros) { g.GyroOverride = false; } }
}
if (Config.DevDebugStatus.Value) { _root.Debug.Get().Insert(0, _root.Status.Get().ToString()); }
StatusScreens.ForEach(e => e.WriteText(_root.Status.Get().ToString()));
lastVelocity = velocity;
lastAngular = angular;
lastThrust = Thrust;
lastTorque = Torque;
}
public double ScopeGridsThrust(Vector3D dir, bool grav = true) => ((dir = dir.Norm()).IsZero()) ? 0 : (ScopeGrids.Sum(e => e.GetThrust(dir.ByMatrix(e.grid))) + (grav ? dir.Dot(-gravity * mass) : 0));
}
void TorqueCalculator(double output)
{
double
k = const_torque_start,
a = k * output,
b = -60,
c = output,
e = b * b - 4 * a * c,
f = b * b
;
var t = Exts.QuadraticPair(const_torque_start * output, -60.0, output);
}
abstract class RootController : ControllerBase
{
List<IMyTerminalBlock> blocks { get; } = new List<IMyTerminalBlock>();
List<IMyShipController> ctrlrs { get; } = new List<IMyShipController>();
Dictionary<IMyCubeGrid, Subgrid> grids { get; } = new Dictionary<IMyCubeGrid, Subgrid>();
public List<IMyTextSurface> StatusScreens { get; } = new List<IMyTextSurface>();
public readonly IEnumerable<IMyShipController> ActiveControllers;
public readonly IEnumerable<Subgrid> ScopeGrids;
public readonly RollerController Roller;
public readonly AltitudeController Altitude;
public readonly DistanceController Distance;
public readonly CruiseController Cruise;
public readonly HoldController Hold;
public readonly List<ModeController> Modes = new List<ModeController>();
public double lastBaseMass = 0;
public bool HasLocked, HasStatic;
protected RootController(Program p) : base(p)
{
// roller needs to be first because it resets the input
Modes.Add(Roller = new RollerController(_root));
Modes.Add(new ForceStaticController(_root));
Modes.Add(new UndampController(_root));
Modes.Add(Hold = new HoldController(_root));
Modes.Add(Cruise = new CruiseController(_root));
Modes.Add(new TargetController(_root));
Modes.Add(Altitude = new AltitudeController(_root));
Modes.Add(Distance = new DistanceController(_root));
ScopeGrids = grids.Select(e => e.Value).Where(e => e.InScope);
ActiveControllers = ScopeGrids.SelectMany(e => e.controllers)
.Where(ctrl => ctrl.IsUnderControl && !(ctrl is IMyCryoChamber) && HasTag(ctrl.CustomName));
}
protected abstract void DoTick();
public void Run(string argument, bool isTick)
{
if (isTick) { DoTick(); return; }
var arg = new ArgumentParser(argument);
Details.Get().Clear();
State.Init(_root.Storage, false);
Config.Init(Me.CustomData, arg.resetconfig);
SetupSubgrids();
if (arg.resetconfig || arg.updateconfig || Me.CustomData == "")
Me.CustomData = Config.ToString();
if (arg.reset || Controller == null)
{
if (!GetController(arg.resetcontroller))
{
_root.Runtime.UpdateFrequency = UpdateFrequency.None;
Details(string_controller_not_found);
return;
}
_root.Runtime.UpdateFrequency = UpdateFrequency.Update1;
CheckSubgrids();
lastBaseMass = Controller.CalculateShipMass().BaseMass;
if (Config.GeneralResetEnablesBlocks.Value)
_root.StopAllOverrides(true, true, InGravity(), Config.GeneralControlScope.Value != ControlScope.grid);
}
_root.Save();
if (arg.updateconfig || arg.none) { return; }
if (arg.reset || arg.stop) { foreach (var m in Modes) { m.Stop(); } Details("Stopped"); return; }
var mode = Modes.FindIndex(e => arg.cmd.StartsWith(e.Command));
if (mode == -1)
Details(string_mode_not_found(arg.cmd, Modes.Select(e => e.Command).ToArray()));
else
Modes[mode].Toggle(arg.args);
}
bool SetupDisplay(IMyTerminalBlock block, Func<int, IMyTextSurface> func, Func<string, int, string> err)
{
var state = ParseNameTag(block);
if (state?.Item3 == TagState.invalid) { Details(string_surface_not_parsed(block.CustomName, state?.Item1)); }
if (state?.Item3 != TagState.parsed) { if (state != null) Details($"{state?.Item3}"); return false; }
Details($"{block.CustomName}");
var screen = func(state.Value.Item2);
if (screen != null)
{
var debug = block.HasDataTag(tag_DebugScreen);
(debug ? _root.debugscreens : StatusScreens).Add(screen);
}
else { Details(err(state.Value.Item1, state.Value.Item2)); }
return false;
}
void SetupBlocks<T>(Func<Subgrid, List<T>> selector) where T : class
{
blocks.Clear();
GTS.GetBlocksOfType<T>(blocks);
foreach (IMyTerminalBlock block in blocks)
{
if (!grids.ContainsKey(block.CubeGrid))
grids[block.CubeGrid] = new Subgrid(_root, block.CubeGrid);
selector(grids[block.CubeGrid]).Add((T)block);
}
}
protected void SetupSubgrids()
{
grids.Clear();
SetupBlocks(e => e.controllers);
SetupBlocks(e => e.connectors);
SetupBlocks(e => e.thrusters);
SetupBlocks(e => e.gyros);
SetupBlocks(e => e.locks);
StatusScreens.Clear();
_root.debugscreens.Clear();
GTS.GetBlocksOfType<IMyTextSurfaceProvider>(null, (e) => SetupDisplay(e as IMyTerminalBlock, e.GetSurface, string_surface_outof_range));
GTS.GetBlocksOfType<IMyTextSurface>(null, (e) => SetupDisplay(e as IMyTerminalBlock, i => i == 0 ? e : null, string_surface_only_one));
Details($"Found {StatusScreens.Count} surface{(StatusScreens.Count == 1 ? "" : "s")}.");
Details($"Found {_root.debugscreens.Count} debug screen{(_root.debugscreens.Count == 1 ? "" : "s")}.");
}
public void CheckSubgrids()
{
HasLocked = ScopeGrids.Sum(e => e.LockedLocks.Count()) > 0;
HasStatic = grids.Keys.Where(e => e.IsStatic).Count() > 0;
foreach (var grid in grids) { grid.Value.CalculateGrid(); }
}
bool GetController(bool reset)
{
ctrlrs.Clear();
if (!reset && State.ControllerEntityID.IsValid())
{
GTS.GetBlocksOfType(ctrlrs, x => x.EntityId == State.ControllerEntityID.Value);
if (ctrlrs.Count == 1) { return SetController(ctrlrs[0]); }
}
return SetController(ActiveControllers.First());
}
bool SetController(IMyShipController a)
{
if (a == null) { return false; }
_root.Controller = a;
State.ControllerEntityID.Value = Controller.EntityId;
return true;
}
string NameTagStart { get { return $"[{Config.GeneralNameTag.Value}"; } }
bool HasTag(string name) => name.Contains($"{NameTagStart}:") || name.Contains($"{NameTagStart}]");
enum TagState { nothing, empty, invalid, disabled, parsed }
MyTuple<string, int, TagState>? ParseNameTag(IMyTerminalBlock block)
{
if (block == null) { return null; }
if (!grids.ContainsKey(block.CubeGrid)) { grids[block.CubeGrid] = new Subgrid(_root, block.CubeGrid); }
if (!grids[block.CubeGrid].InScope) { return null; }
return ParseTagNumber(block.CustomName);
}
MyTuple<string, int, TagState>? ParseTagNumber(string name)
{
if (!HasTag(name)) { return null; }
int start = name.IndexOf(NameTagStart) + NameTagStart.Length + 1, end = name.IndexOf("]", start - 1), surface;
var num = end < start ? "" : name.Substring(start, end - start);
var result = num == "X" ? int.MinValue : int.TryParse(num, out surface) ? surface : int.MaxValue;
var state = result == int.MaxValue ? (num.Length > 0 ? TagState.invalid : TagState.empty)
: result == int.MinValue ? TagState.disabled : TagState.parsed;
return MyTuple.Create(num, result, state);
}
}
struct BlockTag
{
public string name; public int index; public IMyTerminalBlock tag;
public bool parsed { get { return index != int.MaxValue && index != int.MinValue; } }
public BlockTag(int b, IMyTerminalBlock d) { name = d.CustomName; index = b; tag = d; }
}
UserConfig Config;
StateConfig State;
IMyShipController Controller;
readonly List<IMyTextSurface> debugscreens = new List<IMyTextSurface>();
readonly TickerController Ticker;
readonly SBAL Debug = NSB(), Details = NSB(), Status = NSB();
public Program()
{
Config = ConfigBase.CreateConfig<UserConfig>();
State = ConfigBase.CreateConfig<StateConfig>();
Runtime.UpdateFrequency = UpdateFrequency.Once;
Ticker = new TickerController(this);
}
void StopAllOverrides(bool wholeConstruct = true)
{
var config = new MyIni();
config.TryParse(Me.CustomData, "ESTOP");
bool reset_thrusters = config.Get("ESTOP", ESTOP_reset_thrusters).ToBoolean(true);
bool reset_gyros = config.Get("ESTOP", ESTOP_reset_gyros).ToBoolean(true);
bool reset_dampener = config.Get("ESTOP", ESTOP_enable_stock_dampener).ToBoolean(true);
bool whole_construct = config.Get("ESTOP", ESTOP_whole_construct).ToBoolean(true);
StopAllOverrides(reset_thrusters, reset_gyros, reset_dampener, whole_construct);
}
void StopAllOverrides(bool t, bool g, bool d, bool whole_construct)
{
Func<IMyTerminalBlock, bool> test = (e) => whole_construct && e.IsSameConstructAs(Me) || e.CubeGrid == Me.CubeGrid;
// Turn off all thruster overrides on all grids
try { if (t) { GridTerminalSystem.GetBlocksOfType<IMyThrust>(null, e => { if (test(e)) { e.ThrustOverride = 0; } return false; }); } }
catch { }
// Turn off all gyro overrides on all grids
try { if (g) { GridTerminalSystem.GetBlocksOfType<IMyGyro>(null, e => { if (test(e)) { e.Pitch = e.Yaw = e.Roll = 0; e.GyroOverride = false; } return false; }); } }
catch { }
// Turn on inertial dampeners
try { if (d) { Controller.DampenersOverride = true; } }
catch { }
}
bool ESTOPPED = false;
public void Main(string argument, UpdateType updateType)
{
if (argument == "ESTOP")
{
ESTOPPED = true;
Echo("ESTOP");
StopAllOverrides();
Runtime.UpdateFrequency = UpdateFrequency.None;
return;
}
else if (ESTOPPED)
{
Echo("ESTOP triggered. Recompile to return to normal.");
return;
}
const UpdateType updateFlags = UpdateType.Update1 | UpdateType.Update10 | UpdateType.Update100;
bool isTick = string.IsNullOrEmpty(argument) && 0 != (updateType & updateFlags);
try { Ticker.Run(argument, isTick); }
catch (Exception e)
{
Debug(e.ToString());
Me.CustomData += "\n---\n" + e.ToString();
if (Config.DevExceptionESTOP.Value) { Main("ESTOP", UpdateType.None); } else { StopAllOverrides(); }
}
finally
{
Echo(Details.Get().ToString());
Echo(Debug.Get().ToString());
debugscreens.ForEach(f => f.WriteText(Debug.Get().ToString()));
Debug.Get().Clear();
}
}
public void Save() { Storage = State.ToString(); }
class ArgumentParser
{
public bool none, stop, reset, resetconfig, updateconfig, resetcontroller;
public string[] args;
public string cmd;
public ArgumentParser(string arg)
{
none = true;
if (arg == null) { return; }
args = arg.Split(' ');
cmd = args[0];
none = arg == "";
stop = arg.StartsWith("stop");
reset = arg.StartsWith("reset");
resetconfig = arg == "resetconfig";
updateconfig = arg == "updateconfig";
resetcontroller = arg == "resetcontroller";
}
}
class UserConfig : ConfigBase
{
const string
General = nameof(General),
Thrusters = nameof(Thrusters),
Gyros = nameof(Gyros),
Undamp = nameof(Undamp),
Roller = nameof(Roller),
Cruise = nameof(Cruise),
Altitude = nameof(Altitude),
Groups = nameof(Groups),
ESTOP = nameof(ESTOP),
Dev = nameof(Dev);
protected override void OnInit()
{
config.SetSectionComment(Undamp, FormatDescription(
"Better (lack of) dampeners [improved] (aka undamp) \n" +
"keeps the ship moving in a straight line when inertial\n" +
"dampener is turned off. It also sets movement inputs \n" +
"to a defined speed so you don't hit the ceiling."));
config.SetSectionComment(Groups, FormatDescription(
"If set, the script will use only the blocks in that \n" +
"group. The name tag is not required, but may still be \n" +
"used to set the index."
));
config.SetSectionComment(ESTOP, FormatDescription(
"This section is parsed by a separate config instance \n" +
"each time ESTOP is run. If anything cannot be parsed \n" +
"it defaults to true. These settings also apply whenever \n" +
"an exception occurs, regardless of ESTOP on Exception. "
));
}
public COT<bool> GeneralResetEnablesBlocks { get; } = Rg(new COT<bool>(General, "Reset Enables Blocks", true,
"(bool) Reset, world load, and recompile enable all usable blocks."));
public COT<string> GeneralNameTag { get; } = Rg(new COT<string>(General, "Name Tag", "PFC",
"(string) Name tag marking important blocks."));
public COT<ControlScope> GeneralControlScope { get; } = Rg(new COT<ControlScope>(General, "Block Scope", ControlScope.construct,
"(enum) Scope relative to programmable block."));
public COT<float> GeneralDistanceSpeed { get; } = Rg(new COT<float>(General, "Max Distance Speed", 99F,
"(float) Maximum speed to use when going a certain distance. \n" +
"This should not be higher than the game speed limit."));
public COT<bool> GeneralSpeedCheck { get; } = Rg(new COT<bool>(General, "Speed Check", false,
"(bool) Set thrust to idle when locked and not moving."));
public COT<float> ThrustersMinimumThrust { get; } = Rg(new COT<float>(Thrusters, "Minimum Effective Thrust", 0.3F,
"(float: 0-1) Minimum effective thrust needed to use thrusters."));
public COT<float> ThrusterSpeedResponse { get; } = Rg(new COT<float>(Thrusters, "Speed Response", 1f,
"(float) How aggressively to maintain target speed, such as cruise. \n" +
"Should be at least 1."));
public COT<float> ThrusterDistanceResponse { get; } = Rg(new COT<float>(Thrusters, "Distance Response", 1f,
"(float) How aggressively to maintain target distances, including altitude. \n" +
"Should be at least 1."));
public COT<float> UndampAccel { get; } = Rg(new COT<float>(Undamp, "Input Acceleration", 5,
"(float) Acceleration for move inputs in the direction of travel."));
public COT<float> UndampDecel { get; } = Rg(new COT<float>(Undamp, "Input Deceleration", 100,
"(float) Deceleration for move inputs opposing the direction of travel."));
public COT<float> UndampStop { get; } = Rg(new COT<float>(Undamp, "Inertial Dampening", 15,
"(float) Deceleration for inertial dampeners when no move inputs are present."));
public COT<bool> UndampEnableAll { get; } = Rg(new COT<bool>(Undamp, "Enable All Thrusters", true,
"(bool) Enable all thrusters in scope when undamp is activated."));
public COT<float> RollerMaxRoll { get; } = Rg(new COT<float>(Roller, "Max Roll Angle", 35,
"(float: 0-85) Maximum roll angle limit, regardless of available thrust."));
public COT<float> RollerMinRoll { get; } = Rg(new COT<float>(Roller, "Min Roll Angle", 2,
"(float: 0-max) Minimum roll angle limit, regardless of available thrust."));
public COT<float> RollerTurnRamp { get; } = Rg(new COT<float>(Roller, "Turn Ramp", 15f,
"(float) Divide inputs for running average."));
public COT<float> RollerTurnResponse { get; } = Rg(new COT<float>(Roller, "Turn Response", 1,
"(float) Scale mouse yaw inputs."));
public COT<float> RollerPitchResponse { get; } = Rg(new COT<float>(Roller, "Pitch Response", 1,
"(float) Scale mouse pitch inputs."));
public COT<float> RollerRollResponse { get; } = Rg(new COT<float>(Roller, "Roll Response", 1,
"(float) Scale movement roll."));
public COT<string> AdvSpeedCheckGroup { get; } = Rg(new COT<string>(Groups, "Speed Check Group", "",
"(string) Group name of landing gear, magnetic plates, and connectors. \n" +
"The script will only speed check if one of these is locked."));
public COT<string> AdvThrusterGroup { get; } = Rg(new COT<string>(Groups, "Thruster Group", "",
"(string) Only control thrusters in this group."));
public COT<string> AdvGyroGroup { get; } = Rg(new COT<string>(Groups, "Gyro Group", "",
"(string) Only control gyros in this group."));
public COT<string> AdvControllerGroup { get; } = Rg(new COT<string>(Groups, "Controller Group", "",
"(string) Only use controllers in this group."));
public COT<string> AdvConnectorGroup { get; } = Rg(new COT<string>(Groups, "Connector Group", "",
"(string) Only use connectors in this group."));
public COT<string> AdvLCDGroup { get; } = Rg(new COT<string>(Groups, "LCD Group", "",
"(string) Only use LCD panels in this group for status and debug."));
COT<bool> ESTOP1 { get; } = Rg(new COT<bool>(ESTOP, ESTOP_whole_construct, true,
"Whether ESTOP resets thrusters and gyros on subgrids. \n" +
"This does not include grids connected via connectors."));
COT<bool> ESTOP2 { get; } = Rg(new COT<bool>(ESTOP, ESTOP_reset_thrusters, true,
"ESTOP resets thrusters."));
COT<bool> ESTOP3 { get; } = Rg(new COT<bool>(ESTOP, ESTOP_reset_gyros, true,
"ESTOP resets gyros."));
COT<bool> ESTOP4 { get; } = Rg(new COT<bool>(ESTOP, ESTOP_enable_stock_dampener, true,
"ESTOP enables stock dampener. "));
public COT<bool> DevDebugStatus { get; } = Rg(new COT<bool>(Dev, "Print Status to Debug", true,
"(bool) Print status text on debug screens."));
public COT<bool> DevExceptionESTOP { get; } = Rg(new COT<bool>(Dev, "ESTOP On Exception", true,
"(bool) Run full ESTOP if an uncaught exception occurs. \n" +
"If the exception occurs before the config is parsed, this \n" +
"setting will be true and ESTOP will occur."));
}
class StateConfig : ConfigBase
{
protected override void OnInit() { }
public COT<long> ControllerEntityID = Rg(new COT<long>("Controller", "EntityID", 0, ""));
}
class RefConfig : ConfigBase
{
protected override void OnInit() { }
}
// public
public struct B6D : IEquatable<byte>, IEquatable<B6D>
{
public static readonly B6D Forward = 0, Backward = 1, Left = 2, Right = 3, Up = 4, Down = 5;
enum eB6D : byte { Forward = 0, Backward = 1, Left = 2, Right = 3, Up = 4, Down = 5 }
public byte Value { get; private set; }
public B6D(byte v) { Value = v; }
public B6D Opposite() => Base6Directions.GetOppositeDirection(this);
public Vector3D GetVector() => Base6Directions.GetVector(Value);
public bool Equals(byte other) => Value.Equals(other);
public bool Equals(B6D other) => Value.Equals(other.Value);
public override string ToString() => ((eB6D)Value).ToString();
public static implicit operator B6D(byte d) => new B6D(d);
public static implicit operator byte(B6D d) => d.Value;
public static implicit operator Vector3D(B6D d) => d.GetVector();
public static implicit operator B6D(Directions d) => (byte)d;
public static implicit operator B6D(Base6Directions.Direction d) => (byte)d;
public static implicit operator Directions(B6D d) => (Directions)d.Value;
public static implicit operator Base6Directions.Direction(B6D d) => (Base6Directions.Direction)d.Value;
}
public class VectorPair
{
public Vector3D pos { get; private set; }
public Vector3D neg { get; private set; }
public VectorPair() { Clear(); }
public void Clear() { pos = neg = Vector3D.Zero; }
public void Add(Vector3D add) { Vector3D temp = (add + Vector3D.Abs(add)) / 2; pos += temp; neg += add - temp; }
public Vector3D Quadrant(B6D mask, bool abs = false) => Quadrant(mask.GetVector(), abs);
public Vector3D Quadrant(Vector3D mask, bool abs = false)
{
mask.X = mask.X > 0 ? pos.X : mask.X < 0 ? (abs ? -neg.X : neg.X) : 0;
mask.Y = mask.Y > 0 ? pos.Y : mask.Y < 0 ? (abs ? -neg.Y : neg.Y) : 0;
mask.Z = mask.Z > 0 ? pos.Z : mask.Z < 0 ? (abs ? -neg.Z : neg.Z) : 0;
return mask;
}
}
public delegate bool TS<TSource, TResult>(TSource source, out TResult result);
delegate T A<T>(string[] a, int i, COT<T> o);
public delegate StringBuilder SBAL(string s);
public static SBAL NSB() => new StringBuilder().AppendLine;
class ControllerBase
{
protected ControllerBase(Program _r) { _root = _r; }
protected Program _root;
protected UserConfig Config { get { return _root.Config; } }
protected StateConfig State { get { return _root.State; } }
protected IMyShipController Controller { get { return _root.Controller; } }
protected IMyProgrammableBlock Me { get { return _root.Me; } }
protected TickerController Ticker { get { return _root.Ticker; } }
protected IMyGridTerminalSystem GTS { get { return _root.GridTerminalSystem; } }
protected IMyCubeGrid HomeGrid { get { return Me.CubeGrid; } }
/// <summary> controller center of mass </summary>
protected Vector3D position { get { return Controller.CenterOfMass; } }
/// <summary> controller natural and artificial gravity </summary>
protected Vector3D gravity { get { return Controller.GetNaturalGravity(); } }
/// <summary> controller linear velocity </summary>
protected Vector3D velocity { get { return Controller.GetShipVelocities().LinearVelocity; } }
/// <summary> controller angular velocity </summary>
protected Vector3D angular { get { return Controller.GetShipVelocities().AngularVelocity; } }
/// <summary> controller physical ship mass </summary>
protected float mass { get { return Controller.CalculateShipMass().PhysicalMass; } }
protected void Status(string line) { _root.Status(line); }
protected void Status(double line) { Status($"{line:N3}"); }
protected void Status(int line) { Status($"{line:N0}"); }
protected void Status(Vector3D line) { Status(line.Length()); }
protected void PrintLine(string l) { _root.Debug(l); }
protected void PrintLine(double l, string f = "N8") { PrintLine(l.ToString(f)); }
protected void PrintLine(int l, string f = "N8") { PrintLine(l.ToString(f)); }
protected void PrintLine(Vector3D s) { s.Print(PrintLine); }
protected SBAL Details { get { return _root.Details; } }
protected int GetInterval()
{
switch (_root.Runtime.UpdateFrequency)
{
case UpdateFrequency.Update1: { return 1; }
case UpdateFrequency.Update10: { return 10; }
case UpdateFrequency.Update100: { return 100; }
}
return 0;
}
/// <summary> false if natural gravity IsZero() </summary>
protected bool InGravity() => false == gravity.IsZero();
}
abstract class ModeController : ControllerBase
{
protected ModeController(Program _r) : base(_r) { }
public bool Active { get; set; } = false;
public abstract string Command { get; }
public virtual void Run(string[] args) { Active = true; }
public virtual void Stop() { Active = false; }
public abstract void Tick();
public void Toggle(string[] args)
{
var makeActive = false;
if (args[0].EndsWith("on"))
{
makeActive = true;
Run(args);
}
else if (args[0].EndsWith("off"))
{
makeActive = false;
Stop();
}
else if (Active == false)
{
makeActive = true;
Run(args);
}
else
{
makeActive = false;
Stop();
}
if (makeActive && !Active) { Details("Command failed."); }
}
protected T arg<T>(string[] a, int i, T def, string key = "arg")
{
if (a.Length <= i) { return def; }
var name = typeof(T).Name;
if (!ConfigOption.F.ContainsKey(name)) { Details($"{name} is not a registered type"); }
ConfigOption.F[name].Item1(new MyIniValue(new MyIniKey(Command, key), a[i]), def);
return (T)ConfigOption.r2;
}
protected B6D arg(string[] a, int i, B6D d) => arg<Directions>(a, i, d);
protected T argEnum<T>(string[] a, int i, T def) where T : struct
{
if (a.Length <= i) { return def; }
ConfigOption.E<T>(new MyIniValue(new MyIniKey(Command, $"arg{i}"), a[i]), def);
return (T)ConfigOption.r2;
}
}
class SimpleController : ModeController
{
public SimpleController(Program p, string command) : base(p) { Command = command; }
public override string Command { get; }
public override void Run(string[] args) { Run(); }
public void Run() { Active = true; }
public override void Tick() { }
}
abstract class ConfigBase
{
private static List<ConfigOption> RegisterList;
protected abstract void OnInit();
protected static T Rg<T>(T item) where T : ConfigOption { RegisterList.Add(item); return item; }
/// <summary> ConfigBase.CreateConfig<MyConfig>(); </summary>
public static T CreateConfig<T>() where T : ConfigBase, new()
{
// each item should be declared like
// public COT<T> prop = Rg(new COT<T>(...));
RegisterList = new List<ConfigOption>();
var config = new T { ConfigDefaults = RegisterList };
foreach (var item in RegisterList) { item.config = config.config; }
RegisterList = null;
return config;
}
public List<ConfigOption> ConfigDefaults { get; private set; }
public MyIni config = new MyIni();
public void Init(string source, bool reset)
{
if (reset == true) { config.Clear(); } else { config.TryParse(source); }
foreach (var e in ConfigDefaults)
{
if (reset || !e.IsValid()) { e.ValueObject = e.DefValObj; }
if (e.Description.Length > 0) { config.SetComment(e.Section, e.Key, e.Description); }
e.cached = false;
}
// save the end content manually regardless of the outcome
var end = source.IndexOf("\n---");
if (end > -1 && end + 5 < source.Length) { config.EndContent = source.Substring(end + 5); }
OnInit();
}
public override string ToString() => config.ToString();
}
static string FormatDescription(string s) => string.Join("\n", s.Split('\n').Select(e => " " + e));
class ConfigOption
{
public static string GetEnumOptions<T>() => string.Join(", ", Enum.GetNames(typeof(T)));
public string Section, Key, Description; public object DefValObj; public MyIni config;
public Type T { get { return DefValObj.GetType(); } }
public bool IsValid() { F[T.Name].Item1(config.Get(Section, Key), DefValObj); return ok; }
public void Delete() { config.Delete(Section, Key); }
public object ValueObject
{
get { F[T.Name].Item1(config.Get(Section, Key), DefValObj); return r2; }
set { cached = false; config.Set(Section, Key, F[T.Name].Item2(value)); }
}
public static bool ok = false; public static object r2 = null; public bool cached = false;
public delegate bool TryParse<T>(out T val);
public static void G<T>(TryParse<T> parser, object def)
{
T val;
ok = parser(out val);
r2 = ok ? val : def;
}
public static void E<T>(MyIniValue e, object def) where T : struct
{
string s;
T val;
ok = e.TryGetString(out s);
r2 = ok && (ok = Enum.TryParse(s, true, out val)) ? val : def;
}
public class ConfigTypes : TupleDict<string, Action<MyIniValue, object>, Func<object, string>> { }
public static readonly ConfigTypes F;
static ConfigOption()
{
var fmt = System.Globalization.CultureInfo.InvariantCulture;
F = new ConfigTypes() {
{"Int32", (e,d) => G<Int32>(e.TryGetInt32,d), e => ((Int32)e).ToString(fmt) },
{"Int64", (e,d) => G<Int64>(e.TryGetInt64,d), e => ((Int64)e).ToString(fmt) },
{"Single", (e,d) => G<Single>(e.TryGetSingle,d), e => ((Single)e).ToString("G9", fmt)},
{"Double", (e,d) => G<Double>(e.TryGetDouble,d), e => ((Double)e).ToString("G17", fmt)},
{"Boolean", (e,d) => G<Boolean>(e.TryGetBoolean,d), e => (Boolean)e ? "true" : "false"},
{"String", (e,d) => G<String>(e.TryGetString,d), e => (String)e },
{nameof(Directions), E<Directions>, e => e.ToString() },
{nameof(ControlScope), E<ControlScope>, e => e.ToString() },
};
}
}
/// <summary> ConfigOptionTyped </summary>
class COT<T> : ConfigOption
{
static readonly Type[] enums = new Type[] { typeof(Directions), typeof(ControlScope) };
T cache;
public T DefVal { get { return (T)DefValObj; } }
public T Value { get { if (!cached) cache = (T)ValueObject; cached = true; return cache; } set { ValueObject = value; } }
public COT(string section, string key, T val, string desc = "")
{
Section = section;
Key = key;
DefValObj = val;
Description = desc;
if (enums.Contains(typeof(T))) { Description += "\n" + GetEnumOptions<T>(); }
Description = FormatDescription(Description);
}
}
class TupleDict<A, B, C> : Dictionary<A, MyTuple<B, C>> { public void Add(A a, B b, C c) => Add(a, MyTuple.Create(b, c)); }
static void DeNan(ref Vector3D val) { if (val.X.IsNan()) { val.X = 0; } if (val.Y.IsNan()) { val.Y = 0; } if (val.Z.IsNan()) { val.Z = 0; } }
// these are global strings which may be changed if desired
const string tag_SpeedCheck = "Never Speed Check";
const string tag_ConnectorLock = "Auto Settle Lock";
const string tag_DebugScreen = "Debug Screen";
const string tag_Controller = "Reference Controller";
const string ESTOP_enable_stock_dampener = "Enable Stock Dampener";
const string ESTOP_whole_construct = "Reset Entire Construct";
const string ESTOP_reset_thrusters = "Reset Thrusters";
const string ESTOP_reset_gyros = "Reset Gyros";
// these are calculation constants which should not be changed
// because they are specific to the calculation involved
const double const_torque_start = 119 / (halfPiSquared);
const double const_torque_output_peak = 8.63967979146;
const double const_torque_input_peak = 0.143994663191;
const double halfPi = Math.PI / 2;
const double halfPiSquared = halfPi * halfPi;
const double radToDeg = 180 / Math.PI;
const double degToRad = Math.PI / 180;
const double rpmToRad = MathHelper.RPMToRadiansPerSecond;
static double TorqueOutput(double diff) => 60f * diff / TorqueSteps(diff);
static int TorqueSteps(double diff) => (int)(Math.Min(halfPiSquared, diff * diff) * const_torque_start) + 1;
/// <summary> Calculates the single-step input for a desired output </summary>
static double TorqueSingleStepInput(double output) => Math.Min(output, const_torque_output_peak) / 60;
/// <summary> Calculates the multi-step input for a desired output </summary>
static double TorqueMultiStepInput(double output) => Exts.Quadratic(const_torque_start * output, -60.0, 0, 1);
static double Max(params double[] vs) => vs.Max();
static double Min(params double[] vs) => vs.Min();
/// <summary> tan() = opposite (Y) / adjacent (X) </summary>
static Vector2D Ballistic(double range, double altitude, double velocity, double gravity)
{
double v = velocity, g = gravity, y = altitude, x = range;
return new Vector2D(g * x, v * v - Math.Sqrt(v * v * v * v - g * (g * x * x + 2 * y * v * v)));
}
static Vector3D Ballistic(Vector3D intercept, Vector3D gravity, double velocity)
{
if (gravity.IsZero()) { return intercept; } // nothing to do here
Vector3D gy = intercept.ProjectOnVector(gravity), gx = intercept.ProjectOnPlane(gravity);
var res = Ballistic(gx.Normalize(), gy.Normalize(), velocity, gravity.Length());
return gx * res.X + gy * res.Y; // return distance to aim at
}
static Vector3D Intercept(Vector3D ownPosition, Vector3D ownVelocity, Vector3D tarPosition, Vector3D tarVelocity, double shotSpeed, Action<string> debug)
{
Vector3D
vector = tarPosition - ownPosition,
shotOpp = tarVelocity.ProjectOnPlane(vector) - ownVelocity.ProjectOnPlane(vector);
double
distance = vector.Normalize() + tarVelocity.Dot(vector).NotNan() - ownVelocity.Dot(vector).NotNan(),
shotAdj = Math.Sqrt(shotSpeed * shotSpeed - shotOpp.LengthSquared());
return (shotOpp + vector * shotAdj).ClampToSphere(distance);
}
static Vector3D GetAimDirection(Vector3D intercept, Vector3D tarVelocity, Vector3D ownVelocity, double shot, Vector3D gravity, Action<string> debug)
{
Vector3D miss = tarVelocity - intercept.Norm() * shot;
if (!tarVelocity.IsZero() || !ownVelocity.IsZero())
{
tarVelocity -= ownVelocity;
ownVelocity = Vector3D.Zero;
double num = Vector3D.Dot(tarVelocity, tarVelocity) - shot * shot;
double num2 = 2.0 * Vector3D.Dot(tarVelocity, intercept);
double num3 = Vector3D.Dot(intercept, intercept);
double num4 = num2 * num2 - 4.0 * num * num3;
double timeToIntercept = (num4 <= 0.0) ? -1.0 : 2.0 * num3 / (Math.Sqrt(num4) - num2);
if (timeToIntercept > 0.0)
{
intercept += tarVelocity * timeToIntercept;
}
}
double correction = intercept.Length() / miss.Length();
return intercept - (gravity * 0.5 * (correction * correction));
}
const string string_controller_not_found = "Cannot find a cockpit. Please sit in the cockpit or control the remote control and try again. ";
static string string_mode_not_found(string cmd, string[] modes)
{
SBAL f = NSB();
f("'" + cmd + "' does not start with a command: ");
f(string.Join(", ", modes) + ", stop, reset");
f("and does not equal an operator: ");
f("resetconfig, updateconfig, resetcontroller, ESTOP");
return f.Get().ToString();
}
static string string_surface_not_parsed(string name, string index) =>
$"Can't parse index '{index}' as an integer in '{name}'. Please format it like [tag] or [tag:0].";
static string string_surface_only_one(string name, int index) =>
$"Block '{name}' only has one screen. Please remove the number from the tag or change it to 0. ";
static string string_surface_outof_range(string name, int index) =>
$"Can't find surface {index} on {name}. The first screen is number 0. The last screen is one less than the total number of screens";
}
internal static class Exts
{
public static void PrintAvgSpread<T>(this List<T> a, Func<T, Vector3D> v, Action<string> p)
{
var b = a.Select(v).ToList();
Func<Vector3D, double> X = (e) => e.X, Y = (e) => e.Y, Z = (e) => e.Z;
if (b.Count == 0) { p($"{typeof(T).Name} list is empty"); return; }
p($"{(b.Max(X) - b.Min(X)):N5} - {b.Average(X):N5}");
p($"{(b.Max(Y) - b.Min(Y)):N5} - {b.Average(Y):N5}");
p($"{(b.Max(Z) - b.Min(Z)):N5} - {b.Average(Z):N5}");
}
public static bool HasDataTag(this IMyTerminalBlock block, string tag)
{
foreach (var f in block.CustomData.Split('\n'))
if (f.Trim() == $"[{tag}]")
return true;
else if (f.Trim() == "---")
return false;
return false;
}
public static Vector3D PlanetPosition(this IMyShipController Controller) { Vector3D position; return Controller.TryGetPlanetPosition(out position) ? position : Vector3D.Zero; }
public static double PlanetHeight(this IMyShipController Controller, MyPlanetElevation type, double def = 0) { double height; return Controller.TryGetPlanetElevation(type, out height) ? height : def; }
public static double Offset(this IMyShipController controller, Program.B6D direction, Vector3D target)
{
var offset = controller.WorldMatrix.Translation - controller.CenterOfMass;
var offroot = offset.ProjectOnVector(ByMatrix(direction, controller, true)).Length();
var offcurr = offset.ProjectOnVector(target).Length();
return offroot - offcurr;
}
public static MatrixD ByDirection(this MatrixD mat, Program.B6D dir, Vector3D vec) { mat.SetDirectionVector(dir, vec); return mat; }
public static Vector3D GetVector(this MatrixD mat, Program.B6D dir) => mat.GetDirectionVector(dir);
public static StringBuilder Get(this Program.SBAL del) => (StringBuilder)del.Target;
public static Program.B6D Cast(this Program.Directions a) => a;
public static Program.B6D Cast(this Base6Directions.Direction a) => a;
public static bool IsNan(this double val) => double.IsNaN(val);
public static double IsNan(this double val, double def) => double.IsNaN(val) ? def : val;
public static double NotNan(this double val) => IsNan(val) ? 0 : val;
public static int NotNan(this int val) => IsNan(val) ? 0 : val;
public static double Dot(this Vector3D a, Vector3D b) => Vector3D.Dot(a, b);
public static double DotSign(this Vector3D a, Vector3D b) => Math.Sign(Vector3D.Dot(a, b));
public static double DotScale(this Vector3D a, Vector3D b) => ((1 - Dot(a, b)) / 2);
public static Vector3D Abs(this Vector3D a) => Vector3D.Abs(a);
public static Vector3D Max(this Vector3D a, Vector3D b) => Vector3D.Max(a, b);
public static Vector3D Min(this Vector3D a, Vector3D b) => Vector3D.Min(a, b);
public static Vector3D AngleFromPlane(this Vector3D a, Vector3D b) => AngleFromVector(a, a.ProjectOnPlane(b).Norm());
public static Vector3D AngleFromVector(this Vector3D a, Vector3D b) => (a = a.Norm()).Cross(b = b.Norm()).Norm() * Math.Acos(MathHelper.Clamp(a.Dot(b), -1, 1));
public static Vector3D ClampToSphere(this Vector3D vec, double radius) => Vector3D.ClampToSphere(vec, radius);
public static Vector3D Norm(this Vector3D vec) => vec.IsZero() ? vec : Vector3D.Normalize(vec);
public static Vector3D ByMatrix(this Vector3D vector, IMyEntity block, bool isLocalVector = false) => vector.ByMatrix(block.WorldMatrix, isLocalVector);
public static Vector3D ByMatrix(this Vector3D vector, MatrixD matrix, bool isLocalVector = false) => Vector3D.TransformNormal(vector, isLocalVector ? matrix : MatrixD.Transpose(matrix));
public static Vector3D NotNan(this Vector3D val) { if (val.X.IsNan()) { val.X = 0; } if (val.Y.IsNan()) { val.Y = 0; } if (val.Z.IsNan()) { val.Z = 0; } return val; }
public static Vector3D ProjectOnPlane(this Vector3D a, Vector3D b) => Vector3D.ProjectOnPlane(ref a, ref b).NotNan();
public static Vector3D ProjectOnVector(this Vector3D a, Vector3D b) => Vector3D.ProjectOnVector(ref a, ref b).NotNan();
public static Vector3D ProjectOnRay(this Vector3D a, Vector3D b) => Vector3D.Dot(a = a.ProjectOnVector(b), b) >= 0 ? a : Vector3D.Zero;
public static Vector3D Reject(this Vector3D a, Vector3D b) => Vector3D.Reject(a, b);
public static Vector3D Signs(this Vector3D a) => Vector3D.Sign(a);
public static bool IsZero(this Vector3I a) => a == Vector3I.Zero;
public static void Print(this Vector3D v, Action<string> p, string f = "N8") { p(string.Join("\n", v.X.ToString(f), v.Y.ToString(f), v.Z.ToString(f))); }
public static IEnumerable<TR> TrySelectAll<TS, TR>(this IEnumerable<TS> s, out bool ok, TR d, Program.TS<TS, TR> p) { var _ok = true; var r = s.Select(e => { TR v; if (p(e, out v)) return v; else { _ok = false; return d; } }); ok = _ok; return r; }
public static void AppendLine(this StringBuilder b, string p, double v, string f = "N8") { b.AppendLine(p + v.ToString(f)); }
public static void AppendLine(this StringBuilder build, double value, string format = "N8") { build.AppendLine(value.ToString(format)); }
public static Vector3D Quadratic(Vector3D v, Func<double, double[]> f) => new Vector3D(Quadratic(f(v.X)), Quadratic(f(v.Y)), Quadratic(f(v.Z)));
public static double Quadratic(params double[] v)
{
var r = QuadraticPair(v[0], v[1], v[2]);
return r.Length == 2 ? r[(v.Length <= 3 || v[3] == 0 ? v[1] : v[3]) > 0 ? 0 : 1] : r.Length == 1 ? r[0] : double.NaN;
}
public static double[] QuadraticPair(params double[] v)
{
double a = v[0], b = v[1], c = v[2], e = b * b - 4 * a * c, f = 2 * a, g = e > 0 ? Math.Sqrt(e) : 0;
return (e < 0 || a == 0) ? new double[0] : (e == 0) ? new double[1] { -b / f } : new double[2] { (-b + g) / f, (-b - g) / f };
}
}
}
#if DEV
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 WorkingCopy
{
partial class Program : MyGridProgram
{
#endif
// This script was deployed at 2022-05-15 16:35
// these are the direction enums used in the config
public enum Directions : byte { forward, backward, left, right, up, down, }
// these are the control scope enums used in the config
public enum ControlScope : byte { grid, construct, everything }
// these are constants which may be changed if necessary
const float const_IdleThrust = 0.001F;
const double const_MaxRollLimit = 85 * degToRad;
const double const_ThrustApproach = 15;
class RollerController : ModeController
{
public RollerController(Program root) : base(root) { }
public override string Command { get; } = "roller";
public bool DoTorque { get; set; }
double rollResponse { get { return Config.RollerRollResponse.Value; } }
double turnResponse { get { return Config.RollerTurnResponse.Value; } }
double pitchResponse { get { return Config.RollerPitchResponse.Value; } }
double maxRoll { get { return Config.RollerMaxRoll.Value * degToRad; } }
double minRoll { get { return Config.RollerMinRoll.Value * degToRad; } }
double turnRamp { get { return Config.RollerTurnRamp.Value; } }
double desiredTurn, setSpeed;
double[] desiredPoint = new double[2];
public static bool Flag(Modes a, params Modes[] b) => b.All(c => (a & c) == c);
public static bool FlagOR(Modes a, params Modes[] b) => b.Any(c => (a & c) == c);
Vector3D GetDesired(Modes act, Vector3D gravForward, double inputLin, double inputAng, byte index)
{
Vector3D desired = Vector3D.Zero;
if (Flag(act, Modes.point))
desiredPoint[index] += (inputAng * pitchResponse / 60);
else
desiredPoint[index] = 0;
if (Flag(act, Modes.speed))
desired += -gravForward * setSpeed;
if (Flag(act, Modes.stop))
desired += velocity.ProjectOnVector(gravForward);
if (Flag(act, Modes.fly))
desired += gravForward * inputLin * setSpeed;
return desired;
}
// double GetSpeedAngle(double speed, double max, double brake){}
public override void Tick()
{
if (!Active) { return; }
// roller only works in gravity
if (gravity.IsZero()) { Stop(); return; }
Ticker.WorldMatrix = Controller.WorldMatrix.ByDirection(B6D.Down, gravity);
MatrixD matrix = Controller.WorldMatrix;
Vector3D gravNorm = -gravity;
double gravLength = gravNorm.Normalize();
Vector3D worldSpeed = velocity.ProjPlane(gravNorm);
Vector3D gravForward = matrix.Forward.ProjPlane(gravNorm).Norm();
Vector3D gravLeft = matrix.Left.ProjPlane(gravNorm).Norm();
Vector3D current = matrix.Up.AngleFromVector(gravNorm);
Vector3D desired = Vector3D.Zero;
bool holdXZ = Ticker.Hold.Active;
Ticker.Input = Ticker.Input.ByMatrix(Controller);
Ticker.InputAng = Ticker.InputAng.ByMatrix(Controller);
if (holdXZ)
{
gravForward = Ticker.Hold.StartForward.ProjPlane(gravNorm).Norm();
gravLeft = gravForward.Cross(-gravNorm).Norm();
desired += GetDesired(ModeZ, gravForward, Ticker.Input.Z, Ticker.InputAng.X, 0);
desired += Ticker.Hold.Error.ProjPlane(gravNorm);
}
else
{
desired += GetDesired(ModeZ, gravForward, Ticker.Input.Z, Ticker.InputAng.X, 0);
desired += GetDesired(ModeX, gravLeft, Ticker.Input.X, Ticker.InputAng.Z, 1);
}
desiredTurn += Ticker.InputAng.Y * turnResponse / turnRamp - desiredTurn / turnRamp;
var modeStr = $"Roller: {ModeZ} {(holdXZ ? "hold" : ModeX.ToString())} " +
$"{(FlagOR(ModeZ | ModeX, Modes.fly, Modes.speed) ? ($" @ {setSpeed:N1}") : "")}";
Status(modeStr);
PrintLine(modeStr);
Vector3D rollThrust = matrix.Down * Ticker.ScopeGridsThrust(matrix.Down, false) / mass;
double maxThrustRoll = Math.Acos(gravLength / rollThrust.Length()).NotNan();// adjacent / hypotenuse
//double desiredRoll = Math.Atan2(desired.Length(), gravLength);// opposite / adjacent
double desiredRoll = (gravity + -desired).AngleFromVector(-gravNorm).Length();
double finalRoll = Min(const_MaxRollLimit, maxRoll, Math.Max(maxThrustRoll, minRoll), desiredRoll);
Vector3D thrust = (matrix.Down * gravLength / Math.Cos(current.Length())).ProjPlane(gravNorm);
if (Flag(ModeZ, Modes.point)) { thrust = thrust.ProjPlane(gravForward); }
if (Flag(ModeX, Modes.point)) { thrust = thrust.ProjPlane(gravLeft); }
Ticker.Thrust += thrust;
PrintLine($"Current Roll: {current.Length() * radToDeg:N3}");
PrintLine($"Desired Roll: {desiredRoll * radToDeg:N3}");
PrintLine($"Final Roll: {finalRoll * radToDeg:N3}");
PrintLine($"Thrust Roll: {maxThrustRoll * radToDeg:N3}");
PrintLine($"Thrust: {thrust.Length():N3}");
// remove damper for directions which are active
if (FlagOR(ModeZ, Modes.fly, Modes.stop, Modes.speed))
Ticker.Damper = Ticker.Damper.ProjPlane(gravForward);
if (FlagOR(ModeX, Modes.fly, Modes.stop, Modes.speed))
Ticker.Damper = Ticker.Damper.ProjPlane(gravLeft);
// remove inputs for controlled directions
Ticker.Input.Z *= FlagOR(ModeZ, Modes.fly, Modes.speed) ? 0 : 1;
Ticker.Input.X *= FlagOR(ModeX, Modes.fly, Modes.speed) ? 0 : 1;
// set inputs relative to the level matrix we set earlier
Ticker.Input = Ticker.Input.ByMatrix(Ticker.WorldMatrix, true);
// IMPORTANT: desired and current must be added, not subtracted.
if (false) Ticker.Torque = Ticker.Torque
+ matrix.Left * desiredPoint[0] * degToRad * 0.3
+ matrix.Forward * desiredPoint[1] * degToRad * 0.3
+ desired.Cross(gravNorm).Norm() * finalRoll
+ current.ProjPlane(matrix.Down);
Ticker.Torque += desired.Cross(gravNorm).Norm() * finalRoll + current;
Ticker.Torque += -gravNorm * desiredTurn * degToRad;
Ticker.DoTorque = true;
}
public enum Modes { none = 0, level = 1, point = 2, speed = 4, stop = 8, fly = 16, move = Modes.stop | Modes.fly, cruise = Modes.stop | Modes.speed, }
Modes ModeZ = Modes.none;
Modes ModeX = Modes.none;
public override void Run(string[] args)
{
Active = true;
ModeZ = argEnum(args, 1, Modes.none);
ModeX = argEnum(args, 2, Modes.none);
setSpeed = arg(args, 3, 0.0);
}
}
class ForceStaticController : SimpleController
{
public ForceStaticController(Program p) : base(p, "STATIC") { }
public override void Tick() { Ticker.ForceStatic = Active; }
}
class UndampController : SimpleController
{
public UndampController(Program p) : base(p, "undamp") { }
public override void Tick() { if (Active) Ticker.DoThrust = false; }
}
class UnrollController : SimpleController
{
public UnrollController(Program p) : base(p, "unroll") { }
public override void Tick() { if (Active) Ticker.DoTorque = false; }
}
class SolarTrackerController : SimpleController
{
public SolarTrackerController(Program p) : base(p, "solar") { }
B6D SolarVector;
double SolarTicks, TickCount;
Vector3D StartVector;
public override void Run(string[] args)
{
base.Run(args);
SolarVector = arg(args, 1, B6D.Forward);
SolarTicks = arg(args, 2, 120) * 60 * 60;
StartVector = Ticker.WorldMatrix.GetDirectionVector(SolarVector).ProjPlane(new Vector3(0, 1, 0)).Norm();
TickCount = 0;
}
public override void Tick()
{
if (!Active) { return; }
TickCount++;
var dir = Ticker.WorldMatrix.GetDirectionVector(SolarVector);
var axle = new Vector3D(0, 1, 0);
var angle1 = dir.AngleFromPlane(axle);
PrintLine($"{TickCount:N0} / {SolarTicks:N0} = {TickCount / SolarTicks:N2}");
var theta = (TickCount / SolarTicks) * Math.PI * 2;
var angle3 =
StartVector * Math.Cos(theta)
+ (axle.Cross(StartVector)) * Math.Sin(theta)
+ axle * (axle.Dot(StartVector)) * (1 - Math.Cos(theta));
if (Ticker.Roller.Active) angle3 = angle3.ProjPlane(gravity);
PrintLine(theta);
PrintLine(angle1);
PrintLine(angle3);
Ticker.Torque += angle1;
Ticker.Torque += dir.AngleFromVector(angle3);
}
}
class CruiseController : SimpleController
{
public CruiseController(Program p) : base(p, "cruise") { }
double TargetSpeed;
B6D Direction;
public override void Run(string[] args)
{
base.Run(args);
TargetSpeed = arg(args, 1, 0.0);
Direction = arg(args, 2, B6D.Forward);
}
public override void Tick()
{
if (!Active) { return; }
var fwd = Ticker.Hold.Active ? Ticker.Hold.StartForward : Ticker.WorldMatrix.GetDirectionVector(Direction);
Ticker.AddSpeedThrust(fwd * TargetSpeed);
Ticker.ControlPlane(fwd);
}
}
class AltitudeController : SimpleController
{
public AltitudeController(Program p) : base(p, "altitude") { }
double TargetAltitude;
MyPlanetElevation Reference = MyPlanetElevation.Surface;
public override void Run(string[] args)
{
base.Run(args);
TargetAltitude = arg(args, 1, 90.0);
Reference = argEnum(args, 2, MyPlanetElevation.Surface);
}
public override void Tick()
{
if (!Active) { return; }
double height = Controller.PlanetHeight(Reference);
if (height == 0) { PrintLine("Height not found"); return; }
var dir = -gravity.Norm() * (TargetAltitude - height);
Ticker.AddDistanceThrust(dir);
Ticker.ControlPlane(gravity);
PrintLine("Altitude");
}
}
class DistanceController : SimpleController
{
public DistanceController(Program p) : base(p, "distance") { }
double TargetDistance;
B6D Direction;
Vector3D Target, Vector;
public override void Run(string[] args)
{
base.Run(args);
TargetDistance = arg(args, 1, 0.0);
Direction = arg(args, 2, B6D.Forward);
Vector = Ticker.WorldMatrix.GetDirectionVector(Direction);
Target = position + Vector * TargetDistance;
}
public override void Tick()
{
if (!Active) { return; }
PrintLine("Distance");
Ticker.AddDistanceThrust(-(position - Target).Reject(Vector));
Ticker.AddDistanceThrust(-(position - Target).ProjectOnVector(Vector));
Ticker.ControlNone();
Ticker.Torque -= Vector.AngleFromVector(Ticker.WorldMatrix.Forward);
Ticker.DoTorque = true;
}
}
class HoldController : SimpleController
{
public HoldController(Program p) : base(p, "hold") { }
public Vector3D StartForward { get { return IsTarget ? -Vector : Vector; } }
Vector3D Origin, Vector;
public bool IsTarget { get; private set; }
public Vector3D Error { get { return -(position - Origin).Reject(Vector); } }
public override void Run(string[] args)
{
base.Run(args);
Vector = Ticker.WorldMatrix.Forward;
Origin = position;
IsTarget = false;
}
public void Run(Vector3D vec, Vector3D tar)
{
base.Run();
Vector = vec;
Origin = tar;
IsTarget = true;
}
public override void Tick()
{
if (!Active) { return; }
PrintLine("Hold");
Ticker.AddDistanceThrust(Error);
Ticker.ControlVector(StartForward);
Ticker.Torque -= StartForward.AngleFromVector(Ticker.WorldMatrix.Forward);
Ticker.DoTorque = true;
}
}
class TargetController : SimpleController
{
public TargetController(Program p) : base(p, "target") { }
double AmmoSpeed, Error;
Vector3D Intercept;
B6D Direction;
List<IMyLargeTurretBase> Directors = new List<IMyLargeTurretBase>();
IMyLargeTurretBase ActiveDirector;
public override void Run(string[] args)
{
base.Run(args);
AmmoSpeed = arg(args, 1, 0.0);
Direction = arg(args, 2, B6D.Forward);
Init();
}
public void Init()
{
Active = false;
Error = 0;
Directors.Clear();
GTS.GetBlocksOfType<IMyLargeTurretBase>(Directors);
foreach (var Director in Directors)
{
if (Director.HasTarget)
{
ActiveDirector = Director;
Active = true;
break;
}
}
}
public override void Tick()
{
if (!Active) { return; }
Status(ActiveDirector.HasTarget.ToString());
if (!ActiveDirector.HasTarget) { Init(); return; }
var fwd = Ticker.WorldMatrix.GetDirectionVector(Direction);
var target = ActiveDirector.GetTargetedEntity();
Intercept = (target.HitPosition ?? target.Position) - ActiveDirector.GetPosition();
Intercept = GetAimDirection(Intercept, target.Velocity, velocity, AmmoSpeed, gravity, PrintLine);
var diff = Intercept.AngleFromVector(fwd);
Error = diff.Normalize() + Error * 0.8;
Ticker.Torque -= diff * Error;
Ticker.DoTorque = true;
Status(Intercept);
}
}
class Subgrid : ControllerBase
{
public readonly List<IMyShipController> controllers = new List<IMyShipController>();
public readonly List<IMyShipConnector> connectors = new List<IMyShipConnector>();
public readonly List<IMyLandingGear> locks = new List<IMyLandingGear>();
public readonly List<IMyThrust> thrusters = new List<IMyThrust>();
public readonly List<IMyGyro> gyros = new List<IMyGyro>();
public readonly VectorPair maxThrust = new VectorPair();
public readonly List<bool> enabled = new List<bool>();
public readonly IEnumerable<IMyLandingGear> LockedLocks;
public readonly IEnumerable<IMyThrust> EnabledThrusters;
public readonly IEnumerable<IMyGyro> EnabledGyros;
public readonly IMyCubeGrid grid;
public bool InScope
{
get
{
var scope = Config.GeneralControlScope.Value;
if (scope == ControlScope.grid) { return grid == HomeGrid; }
if (scope == ControlScope.construct) { return grid.IsSameConstructAs(HomeGrid); }
return true;
}
}
public Subgrid(Program prog, IMyCubeGrid grid) : base(prog)
{
this.grid = grid;
LockedLocks = locks.Where(e => !e.Closed && e.IsLocked);
EnabledGyros = gyros.Where(e => !e.Closed && e.IsWorking);
EnabledThrusters = thrusters.Where((e, i) => !e.Closed && enabled[i] && e.IsWorking);
TestThrust = e => e.MaxEffectiveThrust / e.MaxThrust > Config.ThrustersMinimumThrust.Value;
}
Func<IMyThrust, bool> TestThrust;
public void CalculateGrid()
{
maxThrust.Clear();
enabled.Clear();
if (thrusters.Count == 0) { return; }
enabled.AddRange(thrusters.Select(TestThrust));
foreach (var t in EnabledThrusters)
{
Vector3D engineForce = t.Orientation.Forward.Cast();
engineForce *= t.MaxEffectiveThrust;
maxThrust.Add(engineForce);
}
}
public double lastThrust = 0;
public int lastGyroCount = 0;
public double GetThrust(Vector3D direction) => lastThrust = maxThrust.Quadrant(direction).Dot(direction);
public int GetGyroCount() => lastGyroCount = EnabledGyros.Count();
public void SetThrust(Vector3D move)
{
if (thrusters.Count == 0) { return; }
bool zero = move.IsZero();
for (var i = 0; i < thrusters.Count; i++)
{
IMyThrust t = thrusters[i];
if (t.Closed) { continue; }
if (zero) { t.ThrustOverride = const_IdleThrust; continue; }
if (!enabled[i]) { if (t.Enabled || t.ThrustOverride != 0) { t.Enabled = false; t.ThrustOverride = 0; } continue; }
if (!t.IsWorking && t.Enabled) { continue; }
if (!t.Enabled && !Config.UndampEnableAll.Value) { continue; }
var fullThrust = maxThrust.Quadrant(t.Orientation.Forward, true).Sum;
double rel = t.MaxEffectiveThrust / fullThrust;
double comp = t.MaxThrust / t.MaxEffectiveThrust;
Vector3D desired = move * rel * comp;
Vector3D project = desired.ProjectOnRay(t.WorldMatrix.Forward);
t.ThrustOverride = project.IsZero() ? const_IdleThrust : (float)project.Length();
t.Enabled = true;
}
}
public void SetTorque(Vector3D vector)
{
bool zero = vector.IsZero();
foreach (var g in EnabledGyros)
{
var axis = zero ? vector : vector.ByMatrix(g).ClampToSphere(g.GetMaximum<float>("Roll"));
g.Pitch = 0 - (float)axis.X;
g.Yaw = 0 - (float)axis.Y;
g.Roll = 0 - (float)axis.Z;
g.GyroOverride = true;
}
}
}
class TickerController : RootController
{
double DistanceSpeed { get { return Config.GeneralDistanceSpeed.Value; } }
double HoldSpeed { get { return Config.UndampStop.Value; } }
double Accel { get { return Config.UndampAccel.Value; } }
double Decel { get { return Config.UndampDecel.Value; } }
public TickerController(Program p) : base(p) { }
public Vector3D Thrust, Torque, Damper, Input, InputAng, lastAngular, lastVelocity, lastThrust, lastTorque;
public MatrixD WorldMatrix;
public bool DoThrust, DoTorque, ForceStatic = false;
public void AddSpeedThrust(Vector3D speed) => AddSpeedThrust(speed, velocity.ProjectOnVector(speed));
public void AddSpeedThrust(Vector3D speed, Vector3D velocity) { Thrust -= speed - velocity; }
public void AddDistanceThrust(Vector3D direction, double max = double.NaN) =>
AddDistanceThrust(direction, velocity.ProjectOnVector(direction),
max.IsNan(DistanceSpeed), Ticker.ScopeGridsThrust(direction) / mass);
public void AddDistanceThrust(Vector3D direction, Vector3D velocity, double maxspeed, double maxbrake)
{
// we have a problem here where the target speed is distance travelled per second, not per tick
// this causes a bit of lag, overshoot, and wobble
Vector3D dir = direction;
// distance we need to travel
double dist = dir.Normalize();
// meters per tick in that direction
double vel = velocity.Dot(dir) / 60;
// reduce "distance per tick" by this much by next tick
double curbrake = (vel * vel / dist / 2);
// the m/t we want to be at right now based on current distance
double expected = Math.Sqrt(dist * maxbrake * 2);
// the m/t we want to be at by next tick
var target = Math.Min(expected - curbrake, maxspeed);
// thrust vector as m / t / t
var speed = dir * target - dir * vel;
DeNan(ref speed);
// speed * 60 * 60 does not work
Thrust -= speed;
PrintLine($"Max Brake: {maxbrake:N5}");
PrintLine($"T Velocity: {expected:N5}");
PrintLine($"T Brake: {curbrake:N5}");
PrintLine($"C Distance: {dist:N5}");
PrintLine($"C Velocity: {vel:N5}");
PrintLine($"Result: {speed.Length():N5}");
}
public void ControlNone() { Ticker.Input = Vector3D.Zero; Ticker.Damper = Vector3D.Zero; }
public void ControlPlane(Vector3D dir) { Ticker.Input = Ticker.Input.ProjPlane(dir); Ticker.Damper = Ticker.Damper.ProjPlane(dir); }
public void ControlVector(Vector3D dir) { Ticker.Input = Ticker.Input.ProjectOnVector(dir); Ticker.Damper = Ticker.Damper.ProjectOnVector(dir); }
public Vector3D LinearInput() => ActiveControllers.Aggregate(Vector3D.Zero, (n, e) => n + e.MoveIndicator);
public Vector3D AngularInput() => ActiveControllers.Aggregate(Vector3D.Zero, (n, e) => n + new Vector3D(e.RotationIndicator, e.RollIndicator));
protected override void DoTick()
{
if (Controller == null) { return; }
WorldMatrix = Controller.WorldMatrix;
_root.Status.Get().Clear();
CheckSubgrids();
var BaseMass = Controller.CalculateShipMass().BaseMass;
var height = Controller.PlanetHeight(MyPlanetElevation.Surface);
if (Math.Abs(lastBaseMass - BaseMass) > 0.01) { SetupSubgrids(); lastBaseMass = BaseMass; }
bool isStatic = ForceStatic || (HasStatic || HasLocked) && velocity.IsZero() && Config.GeneralSpeedCheck.Value;
if (isStatic) { Status("||| STATIC |||"); }
Status(String.Join("", Modes.Select((e) => e.Active ? "^" : "_").ToArray()));
Status($"Speed: {Controller.GetShipSpeed():N3}");
Status($"Height: {height:N3}");
Status($"Base mass: {BaseMass:N0}");
Status($"Max mass: {ScopeGridsThrust(gravity, false) / gravity.Length():N0}");
Status($"Weight: {mass:N0}");
Thrust = Torque = Vector3D.Zero;
DoThrust = true;
DoTorque = true;
var dir = velocity;
var speed = dir.Normalize();
Damper = velocity.ClampToSphere(HoldSpeed);
Input = LinearInput().ByMatrix(WorldMatrix, true);
InputAng = AngularInput().ByMatrix(WorldMatrix, true);
Modes.ForEach(e => { e.Tick(); });
if (!Input.IsZero())
{
var InputBrake = ScopeGridsThrust(Input) / mass;
Thrust += -Input * Math.Min(InputBrake, Accel);
Thrust += (velocity.ProjectOnRay(-Input).ClampToSphere(InputBrake) * const_ThrustApproach)
.ClampToSphere(Math.Max(Decel - Math.Min(InputBrake, Accel), 0));
Damper = Damper.ProjPlane(Input);
}
if (!InputAng.IsZero())
{
Torque -= InputAng;
}
if (Controller.DampenersOverride)
{
//var DamperThrust = ScopeGridsThrust(Damper) / mass;
//Thrust += (Damper.ClampToSphere(DamperThrust) * const_ThrustApproach)
// .ClampToSphere(HoldSpeed);
Thrust += Damper;
}
bool doTorque = DoTorque, doThrust = DoThrust;
if (isStatic) { Thrust = Vector3D.Zero; } else { Thrust += gravity; }
Thrust *= mass;
// ATTEMPTED: Do not clamp thrust vector to max thrust as it will cause strange behaviour if
// there is no available thruster on a side. The standard mode does not account for excessive bank.
// attempt 1 : after gravity
// attempt 2 : before gravity
var totalThrust = ScopeGridsThrust(Thrust, false);
var thrust = Thrust;
var torque = (Torque - angular).ClampToSphere(const_torque_input_peak) + angular;
foreach (var grid in ScopeGrids)
{
if (doThrust) { grid.SetThrust(thrust * (grid.lastThrust / totalThrust).NotNan()); } else { foreach (var t in grid.EnabledThrusters) { t.ThrustOverride = 0; } }
if (doTorque) { grid.SetTorque(torque); } else { foreach (var g in grid.EnabledGyros) { g.GyroOverride = false; } }
}
if (Config.DevDebugStatus.Value) { _root.Debug.Get().Insert(0, _root.Status.Get().ToString()); }
StatusScreens.ForEach(e => e.WriteText(_root.Status.Get().ToString()));
lastVelocity = velocity;
lastAngular = angular;
lastThrust = Thrust;
lastTorque = Torque;
}
public double ScopeGridsThrust(Vector3D dir, bool grav = true) => ((dir = dir.Norm()).IsZero()) ? 0 : (ScopeGrids.Sum(e => e.GetThrust(dir.ByMatrix(e.grid))) + (grav ? dir.Dot(-gravity * mass) : 0));
}
void TorqueCalculator(double output)
{
double
k = const_torque_start,
a = k * output,
b = -60,
c = output,
e = b * b - 4 * a * c,
f = b * b
;
var t = Exts.QuadraticPair(const_torque_start * output, -60.0, output);
}
abstract class RootController : ControllerBase
{
List<IMyTerminalBlock> blocks { get; } = new List<IMyTerminalBlock>();
List<IMyShipController> ctrlrs { get; } = new List<IMyShipController>();
Dictionary<IMyCubeGrid, Subgrid> grids { get; } = new Dictionary<IMyCubeGrid, Subgrid>();
public List<IMyTextSurface> StatusScreens { get; } = new List<IMyTextSurface>();
public readonly IEnumerable<IMyShipController> ActiveControllers;
public readonly IEnumerable<Subgrid> ScopeGrids;
public readonly RollerController Roller;
public readonly AltitudeController Altitude;
public readonly DistanceController Distance;
public readonly CruiseController Cruise;
public readonly HoldController Hold;
public readonly List<ModeController> Modes = new List<ModeController>();
public double lastBaseMass = 0;
public bool HasLocked, HasStatic;
protected RootController(Program p) : base(p)
{
// roller needs to be first because it resets the input
Modes.Add(Roller = new RollerController(_root));
Modes.Add(new ForceStaticController(_root));
Modes.Add(new UndampController(_root));
Modes.Add(new UnrollController(_root));
Modes.Add(new SolarTrackerController(_root));
Modes.Add(Hold = new HoldController(_root));
Modes.Add(Cruise = new CruiseController(_root));
Modes.Add(Altitude = new AltitudeController(_root));
Modes.Add(Distance = new DistanceController(_root));
ScopeGrids = grids.Select(e => e.Value).Where(e => e.InScope);
ActiveControllers = ScopeGrids.SelectMany(e => e.controllers)
.Where(ctrl => ctrl.IsUnderControl && !(ctrl is IMyCryoChamber) && HasTag(ctrl.CustomName));
}
protected abstract void DoTick();
public void Run(string argument, bool isTick)
{
if (isTick) { DoTick(); return; }
var arg = new ArgumentParser(argument);
Details.Get().Clear();
State.Init(_root.Storage, false);
Config.Init(Me.CustomData, arg.resetconfig);
SetupSubgrids();
if (arg.resetconfig || arg.updateconfig || Me.CustomData == "")
Me.CustomData = Config.ToString();
if (arg.reset || Controller == null)
{
if (!GetController(arg.resetcontroller))
{
_root.Runtime.UpdateFrequency = UpdateFrequency.None;
Details(string_controller_not_found);
return;
}
Details(Controller.CustomName);
_root.Runtime.UpdateFrequency = UpdateFrequency.Update1;
CheckSubgrids();
lastBaseMass = Controller.CalculateShipMass().BaseMass;
if (Config.GeneralResetEnablesBlocks.Value)
_root.StopAllOverrides(true, true, InGravity(), Config.GeneralControlScope.Value != ControlScope.grid);
}
_root.Save();
if (arg.updateconfig || arg.none) { return; }
if (arg.reset || arg.stop) { foreach (var m in Modes) { m.Stop(); } Details("Stopped"); return; }
var mode = Modes.FindIndex(e => arg.cmd.StartsWith(e.Command));
if (mode == -1)
Details(string_mode_not_found(arg.cmd, Modes.Select(e => e.Command).ToArray()));
else
Modes[mode].Toggle(arg.args);
}
bool SetupDisplay(IMyTerminalBlock block, Func<int, IMyTextSurface> func, Func<string, int, string> err)
{
var state = ParseNameTag(block);
if (state?.Item3 == TagState.invalid) { Details(string_surface_not_parsed(block.CustomName, state?.Item1)); }
if (state?.Item3 != TagState.parsed) { if (state != null) Details($"{state?.Item3}"); return false; }
Details($"{block.CustomName}");
var screen = func(state.Value.Item2);
if (screen != null)
{
var debug = block.HasDataTag(tag_DebugScreen);
(debug ? _root.debugscreens : StatusScreens).Add(screen);
}
else { Details(err(state.Value.Item1, state.Value.Item2)); }
return false;
}
void SetupBlocks<T>(Func<Subgrid, List<T>> selector) where T : class
{
blocks.Clear();
GTS.GetBlocksOfType<T>(blocks);
foreach (IMyTerminalBlock block in blocks)
{
if (!grids.ContainsKey(block.CubeGrid))
grids[block.CubeGrid] = new Subgrid(_root, block.CubeGrid);
selector(grids[block.CubeGrid]).Add((T)block);
}
}
protected void SetupSubgrids()
{
grids.Clear();
SetupBlocks(e => e.controllers);
SetupBlocks(e => e.connectors);
SetupBlocks(e => e.thrusters);
SetupBlocks(e => e.gyros);
SetupBlocks(e => e.locks);
StatusScreens.Clear();
_root.debugscreens.Clear();
GTS.GetBlocksOfType<IMyTextSurfaceProvider>(null, (e) => SetupDisplay(e as IMyTerminalBlock, e.GetSurface, string_surface_outof_range));
GTS.GetBlocksOfType<IMyTextSurface>(null, (e) => SetupDisplay(e as IMyTerminalBlock, i => i == 0 ? e : null, string_surface_only_one));
Details($"Found {StatusScreens.Count} surface{(StatusScreens.Count == 1 ? "" : "s")}.");
Details($"Found {_root.debugscreens.Count} debug screen{(_root.debugscreens.Count == 1 ? "" : "s")}.");
}
public void CheckSubgrids()
{
HasLocked = ScopeGrids.Sum(e => e.LockedLocks.Count()) > 0;
HasStatic = grids.Keys.Where(e => e.IsStatic).Count() > 0;
foreach (var grid in grids) { grid.Value.CalculateGrid(); }
}
bool GetController(bool reset)
{
ctrlrs.Clear();
if (!reset && State.ControllerEntityID.IsValid())
{
GTS.GetBlocksOfType(ctrlrs, x => x.EntityId == State.ControllerEntityID.Value);
if (ctrlrs.Count == 1) { return SetController(ctrlrs[0]); }
}
return SetController(ActiveControllers.First());
}
bool SetController(IMyShipController a)
{
if (a == null) { return false; }
_root.Controller = a;
State.ControllerEntityID.Value = Controller.EntityId;
return true;
}
string NameTagStart { get { return $"[{Config.GeneralNameTag.Value}"; } }
bool HasTag(string name) => name.Contains($"{NameTagStart}:") || name.Contains($"{NameTagStart}]");
enum TagState { nothing, empty, invalid, disabled, parsed }
MyTuple<string, int, TagState>? ParseNameTag(IMyTerminalBlock block)
{
if (block == null) { return null; }
if (!grids.ContainsKey(block.CubeGrid)) { grids[block.CubeGrid] = new Subgrid(_root, block.CubeGrid); }
if (!grids[block.CubeGrid].InScope) { return null; }
return ParseTagNumber(block.CustomName);
}
MyTuple<string, int, TagState>? ParseTagNumber(string name)
{
if (!HasTag(name)) { return null; }
int start = name.IndexOf(NameTagStart) + NameTagStart.Length + 1, end = name.IndexOf("]", start - 1), surface;
var num = end < start ? "" : name.Substring(start, end - start);
var result = num == "X" ? int.MinValue : int.TryParse(num, out surface) ? surface : int.MaxValue;
var state = result == int.MaxValue ? (num.Length > 0 ? TagState.invalid : TagState.empty)
: result == int.MinValue ? TagState.disabled : TagState.parsed;
return MyTuple.Create(num, result, state);
}
}
struct BlockTag
{
public string name; public int index; public IMyTerminalBlock tag;
public bool parsed { get { return index != int.MaxValue && index != int.MinValue; } }
public BlockTag(int b, IMyTerminalBlock d) { name = d.CustomName; index = b; tag = d; }
}
UserConfig Config;
StateConfig State;
IMyShipController Controller;
readonly List<IMyTextSurface> debugscreens = new List<IMyTextSurface>();
readonly TickerController Ticker;
readonly SBAL Debug = NSB(), Details = NSB(), Status = NSB();
public Program()
{
Config = ConfigBase.CreateConfig<UserConfig>();
State = ConfigBase.CreateConfig<StateConfig>();
Runtime.UpdateFrequency = UpdateFrequency.Once;
Ticker = new TickerController(this);
}
void StopAllOverrides(bool wholeConstruct = true)
{
var config = new MyIni();
config.TryParse(Me.CustomData, "ESTOP");
bool reset_thrusters = config.Get("ESTOP", ESTOP_reset_thrusters).ToBoolean(true);
bool reset_gyros = config.Get("ESTOP", ESTOP_reset_gyros).ToBoolean(true);
bool reset_dampener = config.Get("ESTOP", ESTOP_enable_stock_dampener).ToBoolean(true);
bool whole_construct = config.Get("ESTOP", ESTOP_whole_construct).ToBoolean(true);
StopAllOverrides(reset_thrusters, reset_gyros, reset_dampener, whole_construct);
}
void StopAllOverrides(bool t, bool g, bool d, bool whole_construct)
{
Func<IMyTerminalBlock, bool> test = (e) => whole_construct && e.IsSameConstructAs(Me) || e.CubeGrid == Me.CubeGrid;
// Turn off all thruster overrides on all grids
try { if (t) { GridTerminalSystem.GetBlocksOfType<IMyThrust>(null, e => { if (test(e)) { e.ThrustOverride = 0; } return false; }); } }
catch { }
// Turn off all gyro overrides on all grids
try { if (g) { GridTerminalSystem.GetBlocksOfType<IMyGyro>(null, e => { if (test(e)) { e.Pitch = e.Yaw = e.Roll = 0; e.GyroOverride = false; } return false; }); } }
catch { }
// Turn on inertial dampeners
try { if (d) { Controller.DampenersOverride = true; } }
catch { }
}
bool ESTOPPED = false;
public void Main(string argument, UpdateType updateType)
{
if (argument == "ESTOP")
{
ESTOPPED = true;
Echo("ESTOP");
StopAllOverrides();
Runtime.UpdateFrequency = UpdateFrequency.None;
return;
}
else if (ESTOPPED)
{
Echo("ESTOP triggered. Recompile to return to normal.");
return;
}
const UpdateType updateFlags = UpdateType.Update1 | UpdateType.Update10 | UpdateType.Update100;
bool isTick = string.IsNullOrEmpty(argument) && 0 != (updateType & updateFlags);
try { Ticker.Run(argument, isTick); }
catch (Exception e)
{
Debug(e.ToString());
Me.CustomData += "\n---\n" + e.ToString();
if (Config.DevExceptionESTOP.Value) { Main("ESTOP", UpdateType.None); } else { StopAllOverrides(); }
}
finally
{
Echo(Details.Get().ToString());
Echo(Debug.Get().ToString());
debugscreens.ForEach(f => f.WriteText(Debug.Get().ToString()));
Debug.Get().Clear();
}
}
public void Save() { Storage = State.ToString(); }
class ArgumentParser
{
public bool none, stop, reset, resetconfig, updateconfig, resetcontroller;
public string[] args;
public string cmd;
public ArgumentParser(string arg)
{
none = true;
if (arg == null) { return; }
args = arg.Split(' ');
cmd = args[0];
none = arg == "";
stop = arg.StartsWith("stop");
reset = arg.StartsWith("reset");
resetconfig = arg == "resetconfig";
updateconfig = arg == "updateconfig";
resetcontroller = arg == "resetcontroller";
}
}
class UserConfig : ConfigBase
{
const string
General = nameof(General),
Thrusters = nameof(Thrusters),
Gyros = nameof(Gyros),
Undamp = nameof(Undamp),
Roller = nameof(Roller),
Cruise = nameof(Cruise),
Altitude = nameof(Altitude),
Groups = nameof(Groups),
ESTOP = nameof(ESTOP),
Dev = nameof(Dev);
protected override void OnInit()
{
config.SetSectionComment(Undamp, FormatDescription(
"Better (lack of) dampeners [improved] (aka undamp) \n" +
"keeps the ship moving in a straight line when inertial\n" +
"dampener is turned off. It also sets movement inputs \n" +
"to a defined speed so you don't hit the ceiling."));
config.SetSectionComment(Groups, FormatDescription(
"If set, the script will use only the blocks in that \n" +
"group. The name tag is not required, but may still be \n" +
"used to set the index."
));
config.SetSectionComment(ESTOP, FormatDescription(
"This section is parsed by a separate config instance \n" +
"each time ESTOP is run. If anything cannot be parsed \n" +
"it defaults to true. These settings also apply whenever \n" +
"an exception occurs, regardless of ESTOP on Exception. "
));
}
public COT<bool> GeneralResetEnablesBlocks { get; } = Rg(new COT<bool>(General, "Reset Enables Blocks", true,
"(bool) Reset, world load, and recompile enable all usable blocks."));
public COT<string> GeneralNameTag { get; } = Rg(new COT<string>(General, "Name Tag", "PFC",
"(string) Name tag marking important blocks."));
public COT<ControlScope> GeneralControlScope { get; } = Rg(new COT<ControlScope>(General, "Block Scope", ControlScope.construct,
"(enum) Scope relative to programmable block."));
public COT<float> GeneralDistanceSpeed { get; } = Rg(new COT<float>(General, "Max Distance Speed", 99F,
"(float) Maximum speed to use when going a certain distance. \n" +
"This should not be higher than the game speed limit."));
public COT<bool> GeneralSpeedCheck { get; } = Rg(new COT<bool>(General, "Speed Check", false,
"(bool) Set thrust to idle when locked and not moving."));
public COT<float> ThrustersMinimumThrust { get; } = Rg(new COT<float>(Thrusters, "Minimum Effective Thrust", 0.3F,
"(float: 0-1) Minimum effective thrust needed to use thrusters."));
public COT<float> ThrusterSpeedResponse { get; } = Rg(new COT<float>(Thrusters, "Speed Response", 1f,
"(float) How aggressively to maintain target speed, such as cruise. \n" +
"Should be at least 1."));
public COT<float> ThrusterDistanceResponse { get; } = Rg(new COT<float>(Thrusters, "Distance Response", 1f,
"(float) How aggressively to maintain target distances, including altitude. \n" +
"Should be at least 1."));
public COT<float> UndampAccel { get; } = Rg(new COT<float>(Undamp, "Input Acceleration", 5,
"(float) Acceleration for move inputs in the direction of travel."));
public COT<float> UndampDecel { get; } = Rg(new COT<float>(Undamp, "Input Deceleration", 100,
"(float) Deceleration for move inputs opposing the direction of travel."));
public COT<float> UndampStop { get; } = Rg(new COT<float>(Undamp, "Inertial Dampening", 15,
"(float) Deceleration for inertial dampeners when no move inputs are present."));
public COT<bool> UndampEnableAll { get; } = Rg(new COT<bool>(Undamp, "Enable All Thrusters", true,
"(bool) Enable all thrusters in scope when undamp is activated."));
public COT<float> RollerMaxRoll { get; } = Rg(new COT<float>(Roller, "Max Roll Angle", 35,
"(float: 0-85) Maximum roll angle limit, regardless of available thrust."));
public COT<float> RollerMinRoll { get; } = Rg(new COT<float>(Roller, "Min Roll Angle", 2,
"(float: 0-max) Minimum roll angle limit, regardless of available thrust."));
public COT<float> RollerTurnRamp { get; } = Rg(new COT<float>(Roller, "Turn Ramp", 15f,
"(float) Divide inputs for running average."));
public COT<float> RollerTurnResponse { get; } = Rg(new COT<float>(Roller, "Turn Response", 1,
"(float) Scale mouse yaw inputs."));
public COT<float> RollerPitchResponse { get; } = Rg(new COT<float>(Roller, "Pitch Response", 1,
"(float) Scale mouse pitch inputs."));
public COT<float> RollerRollResponse { get; } = Rg(new COT<float>(Roller, "Roll Response", 1,
"(float) Scale movement roll."));
public COT<string> AdvSpeedCheckGroup { get; } = Rg(new COT<string>(Groups, "Speed Check Group", "",
"(string) Group name of landing gear, magnetic plates, and connectors. \n" +
"The script will only speed check if one of these is locked."));
public COT<string> AdvThrusterGroup { get; } = Rg(new COT<string>(Groups, "Thruster Group", "",
"(string) Only control thrusters in this group."));
public COT<string> AdvGyroGroup { get; } = Rg(new COT<string>(Groups, "Gyro Group", "",
"(string) Only control gyros in this group."));
public COT<string> AdvControllerGroup { get; } = Rg(new COT<string>(Groups, "Controller Group", "",
"(string) Only use controllers in this group."));
public COT<string> AdvConnectorGroup { get; } = Rg(new COT<string>(Groups, "Connector Group", "",
"(string) Only use connectors in this group."));
public COT<string> AdvLCDGroup { get; } = Rg(new COT<string>(Groups, "LCD Group", "",
"(string) Only use LCD panels in this group for status and debug."));
COT<bool> ESTOP1 { get; } = Rg(new COT<bool>(ESTOP, ESTOP_whole_construct, true,
"Whether ESTOP resets thrusters and gyros on subgrids. \n" +
"This does not include grids connected via connectors."));
COT<bool> ESTOP2 { get; } = Rg(new COT<bool>(ESTOP, ESTOP_reset_thrusters, true,
"ESTOP resets thrusters."));
COT<bool> ESTOP3 { get; } = Rg(new COT<bool>(ESTOP, ESTOP_reset_gyros, true,
"ESTOP resets gyros."));
COT<bool> ESTOP4 { get; } = Rg(new COT<bool>(ESTOP, ESTOP_enable_stock_dampener, true,
"ESTOP enables stock dampener. "));
public COT<bool> DevDebugStatus { get; } = Rg(new COT<bool>(Dev, "Print Status to Debug", true,
"(bool) Print status text on debug screens."));
public COT<bool> DevExceptionESTOP { get; } = Rg(new COT<bool>(Dev, "ESTOP On Exception", true,
"(bool) Run full ESTOP if an uncaught exception occurs. \n" +
"If the exception occurs before the config is parsed, this \n" +
"setting will be true and ESTOP will occur."));
}
class StateConfig : ConfigBase
{
protected override void OnInit() { }
public COT<long> ControllerEntityID = Rg(new COT<long>("Controller", "EntityID", 0, ""));
}
class RefConfig : ConfigBase
{
protected override void OnInit() { }
}
// public
public struct B6D : IEquatable<byte>, IEquatable<B6D>
{
public static readonly B6D Forward = 0, Backward = 1, Left = 2, Right = 3, Up = 4, Down = 5;
enum eB6D : byte { Forward = 0, Backward = 1, Left = 2, Right = 3, Up = 4, Down = 5 }
public byte Value { get; private set; }
public B6D(byte v) { Value = v; }
public B6D Opposite() => Base6Directions.GetOppositeDirection(this);
public Vector3D GetVector() => Base6Directions.GetVector(Value);
public bool Equals(byte other) => Value.Equals(other);
public bool Equals(B6D other) => Value.Equals(other.Value);
public override string ToString() => ((eB6D)Value).ToString();
public static implicit operator B6D(byte d) => new B6D(d);
public static implicit operator byte(B6D d) => d.Value;
public static implicit operator Vector3D(B6D d) => d.GetVector();
public static implicit operator B6D(Directions d) => (byte)d;
public static implicit operator B6D(Base6Directions.Direction d) => (byte)d;
public static implicit operator Directions(B6D d) => (Directions)d.Value;
public static implicit operator Base6Directions.Direction(B6D d) => (Base6Directions.Direction)d.Value;
}
public class VectorPair
{
public Vector3D pos { get; private set; }
public Vector3D neg { get; private set; }
public VectorPair() { Clear(); }
public void Clear() { pos = neg = Vector3D.Zero; }
public void Add(Vector3D add) { Vector3D temp = (add + Vector3D.Abs(add)) / 2; pos += temp; neg += add - temp; }
public Vector3D Quadrant(B6D mask, bool abs = false) => Quadrant(mask.GetVector(), abs);
public Vector3D Quadrant(Vector3D mask, bool abs = false)
{
mask.X = mask.X > 0 ? pos.X : mask.X < 0 ? (abs ? -neg.X : neg.X) : 0;
mask.Y = mask.Y > 0 ? pos.Y : mask.Y < 0 ? (abs ? -neg.Y : neg.Y) : 0;
mask.Z = mask.Z > 0 ? pos.Z : mask.Z < 0 ? (abs ? -neg.Z : neg.Z) : 0;
return mask;
}
}
public delegate bool TS<TSource, TResult>(TSource source, out TResult result);
delegate T A<T>(string[] a, int i, COT<T> o);
public delegate StringBuilder SBAL(string s);
public static SBAL NSB() => new StringBuilder().AppendLine;
class ControllerBase
{
protected ControllerBase(Program _r) { _root = _r; }
protected Program _root;
protected UserConfig Config { get { return _root.Config; } }
protected StateConfig State { get { return _root.State; } }
protected IMyShipController Controller { get { return _root.Controller; } }
protected IMyProgrammableBlock Me { get { return _root.Me; } }
protected TickerController Ticker { get { return _root.Ticker; } }
protected IMyGridTerminalSystem GTS { get { return _root.GridTerminalSystem; } }
protected IMyCubeGrid HomeGrid { get { return Me.CubeGrid; } }
/// <summary> controller center of mass </summary>
protected Vector3D position { get { return Controller.CenterOfMass; } }
/// <summary> controller natural and artificial gravity </summary>
protected Vector3D gravity { get { return Controller.GetNaturalGravity(); } }
/// <summary> controller linear velocity </summary>
protected Vector3D velocity { get { return Controller.GetShipVelocities().LinearVelocity; } }
/// <summary> controller angular velocity </summary>
protected Vector3D angular { get { return Controller.GetShipVelocities().AngularVelocity; } }
/// <summary> controller physical ship mass </summary>
protected float mass { get { return Controller.CalculateShipMass().PhysicalMass; } }
protected void Status(string line) { _root.Status(line); }
protected void Status(double line) { Status($"{line:N3}"); }
protected void Status(int line) { Status($"{line:N0}"); }
protected void Status(Vector3D line) { Status(line.Length()); }
protected void PrintLine(string l) { _root.Debug(l); }
protected void PrintLine(double l, string f = "N8") { PrintLine(l.ToString(f)); }
protected void PrintLine(int l, string f = "N8") { PrintLine(l.ToString(f)); }
protected void PrintLine(Vector3D s) { s.Print(PrintLine); }
protected SBAL Details { get { return _root.Details; } }
protected int GetInterval()
{
switch (_root.Runtime.UpdateFrequency)
{
case UpdateFrequency.Update1: { return 1; }
case UpdateFrequency.Update10: { return 10; }
case UpdateFrequency.Update100: { return 100; }
}
return 0;
}
/// <summary> false if natural gravity IsZero() </summary>
protected bool InGravity() => false == gravity.IsZero();
}
abstract class ModeController : ControllerBase
{
protected ModeController(Program _r) : base(_r) { }
public bool Active { get; set; } = false;
public abstract string Command { get; }
public virtual void Run(string[] args) { Active = true; }
public virtual void Stop() { Active = false; }
public abstract void Tick();
public void Toggle(string[] args)
{
var makeActive = false;
if (args[0].EndsWith("on"))
{
makeActive = true;
Run(args);
}
else if (args[0].EndsWith("off"))
{
makeActive = false;
Stop();
}
else if (Active == false)
{
makeActive = true;
Run(args);
}
else
{
makeActive = false;
Stop();
}
if (makeActive && !Active) { Details("Command failed."); }
}
protected T arg<T>(string[] a, int i, T def, string key = "arg")
{
if (a.Length <= i) { return def; }
var name = typeof(T).Name;
if (!ConfigOption.F.ContainsKey(name)) { Details($"{name} is not a registered type"); }
ConfigOption.F[name].Item1(new MyIniValue(new MyIniKey(Command, key), a[i]), def);
return (T)ConfigOption.r2;
}
protected B6D arg(string[] a, int i, B6D d) => arg<Directions>(a, i, d);
protected T argEnum<T>(string[] a, int i, T def) where T : struct
{
if (a.Length <= i) { return def; }
ConfigOption.E<T>(new MyIniValue(new MyIniKey(Command, $"arg{i}"), a[i]), def);
return (T)ConfigOption.r2;
}
}
class SimpleController : ModeController
{
public SimpleController(Program p, string command) : base(p) { Command = command; }
public override string Command { get; }
public override void Run(string[] args) { Run(); }
public void Run() { Active = true; }
public override void Tick() { }
}
abstract class ConfigBase
{
private static List<ConfigOption> RegisterList;
protected abstract void OnInit();
protected static T Rg<T>(T item) where T : ConfigOption { RegisterList.Add(item); return item; }
/// <summary> ConfigBase.CreateConfig<MyConfig>(); </summary>
public static T CreateConfig<T>() where T : ConfigBase, new()
{
// each item should be declared like
// public COT<T> prop = Rg(new COT<T>(...));
RegisterList = new List<ConfigOption>();
var config = new T { ConfigDefaults = RegisterList };
foreach (var item in RegisterList) { item.config = config.config; }
RegisterList = null;
return config;
}
public List<ConfigOption> ConfigDefaults { get; private set; }
public MyIni config = new MyIni();
public void Init(string source, bool reset)
{
if (reset == true) { config.Clear(); } else { config.TryParse(source); }
foreach (var e in ConfigDefaults)
{
if (reset || !e.IsValid()) { e.ValueObject = e.DefValObj; }
if (e.Description.Length > 0) { config.SetComment(e.Section, e.Key, e.Description); }
e.cached = false;
}
// save the end content manually regardless of the outcome
var end = source.IndexOf("\n---");
if (end > -1 && end + 5 < source.Length) { config.EndContent = source.Substring(end + 5); }
OnInit();
}
public override string ToString() => config.ToString();
}
static string FormatDescription(string s) => string.Join("\n", s.Split('\n').Select(e => " " + e));
class ConfigOption
{
public static string GetEnumOptions<T>() => string.Join(", ", Enum.GetNames(typeof(T)));
public string Section, Key, Description; public object DefValObj; public MyIni config;
public Type T { get { return DefValObj.GetType(); } }
public bool IsValid() { F[T.Name].Item1(config.Get(Section, Key), DefValObj); return ok; }
public void Delete() { config.Delete(Section, Key); }
public object ValueObject
{
get { F[T.Name].Item1(config.Get(Section, Key), DefValObj); return r2; }
set { cached = false; config.Set(Section, Key, F[T.Name].Item2(value)); }
}
public static bool ok = false; public static object r2 = null; public bool cached = false;
public delegate bool TryParse<T>(out T val);
public static void G<T>(TryParse<T> parser, object def)
{
T val;
ok = parser(out val);
r2 = ok ? val : def;
}
public static void E<T>(MyIniValue e, object def) where T : struct
{
string s;
T val;
ok = e.TryGetString(out s);
r2 = ok && (ok = Enum.TryParse(s, true, out val)) ? val : def;
}
public class ConfigTypes : TupleDict<string, Action<MyIniValue, object>, Func<object, string>> { }
public static readonly ConfigTypes F;
static ConfigOption()
{
var fmt = System.Globalization.CultureInfo.InvariantCulture;
F = new ConfigTypes() {
{"Int32", (e,d) => G<Int32>(e.TryGetInt32,d), e => ((Int32)e).ToString(fmt) },
{"Int64", (e,d) => G<Int64>(e.TryGetInt64,d), e => ((Int64)e).ToString(fmt) },
{"Single", (e,d) => G<Single>(e.TryGetSingle,d), e => ((Single)e).ToString("G9", fmt)},
{"Double", (e,d) => G<Double>(e.TryGetDouble,d), e => ((Double)e).ToString("G17", fmt)},
{"Boolean", (e,d) => G<Boolean>(e.TryGetBoolean,d), e => (Boolean)e ? "true" : "false"},
{"String", (e,d) => G<String>(e.TryGetString,d), e => (String)e },
{nameof(Directions), E<Directions>, e => e.ToString() },
{nameof(ControlScope), E<ControlScope>, e => e.ToString() },
};
}
}
/// <summary> ConfigOptionTyped </summary>
class COT<T> : ConfigOption
{
static readonly Type[] enums = new Type[] { typeof(Directions), typeof(ControlScope) };
T cache;
public T DefVal { get { return (T)DefValObj; } }
public T Value { get { if (!cached) cache = (T)ValueObject; cached = true; return cache; } set { ValueObject = value; } }
public COT(string section, string key, T val, string desc = "")
{
Section = section;
Key = key;
DefValObj = val;
Description = desc;
if (enums.Contains(typeof(T))) { Description += "\n" + GetEnumOptions<T>(); }
Description = FormatDescription(Description);
}
}
class TupleDict<A, B, C> : Dictionary<A, MyTuple<B, C>> { public void Add(A a, B b, C c) => Add(a, MyTuple.Create(b, c)); }
static void DeNan(ref Vector3D val) { if (val.X.IsNan()) { val.X = 0; } if (val.Y.IsNan()) { val.Y = 0; } if (val.Z.IsNan()) { val.Z = 0; } }
// these are global strings which may be changed if desired
const string tag_SpeedCheck = "Never Speed Check";
const string tag_ConnectorLock = "Auto Settle Lock";
const string tag_DebugScreen = "Debug Screen";
const string tag_Controller = "Reference Controller";
const string ESTOP_enable_stock_dampener = "Enable Stock Dampener";
const string ESTOP_whole_construct = "Reset Entire Construct";
const string ESTOP_reset_thrusters = "Reset Thrusters";
const string ESTOP_reset_gyros = "Reset Gyros";
// these are calculation constants which should not be changed
// because they are specific to the calculation involved
const double const_torque_start = 119 / (halfPiSquared);
const double const_torque_output_peak = 8.63967979146;
const double const_torque_input_peak = 0.143994663191;
const double halfPi = Math.PI / 2;
const double halfPiSquared = halfPi * halfPi;
const double radToDeg = 180 / Math.PI;
const double degToRad = Math.PI / 180;
const double rpmToRad = MathHelper.RPMToRadiansPerSecond;
static double TorqueOutput(double diff) => 60f * diff / TorqueSteps(diff);
static int TorqueSteps(double diff) => (int)(Math.Min(halfPiSquared, diff * diff) * const_torque_start) + 1;
/// <summary> Calculates the single-step input for a desired output </summary>
static double TorqueSingleStepInput(double output) => Math.Min(output, const_torque_output_peak) / 60;
/// <summary> Calculates the multi-step input for a desired output </summary>
static double TorqueMultiStepInput(double output) => Exts.Quadratic(const_torque_start * output, -60.0, 0, 1);
static double Max(params double[] vs) => vs.Max();
static double Min(params double[] vs) => vs.Min();
/// <summary> tan() = opposite (Y) / adjacent (X) </summary>
static Vector2D Ballistic(double range, double altitude, double velocity, double gravity)
{
double v = velocity, g = gravity, y = altitude, x = range;
return new Vector2D(g * x, v * v - Math.Sqrt(v * v * v * v - g * (g * x * x + 2 * y * v * v)));
}
static Vector3D Ballistic(Vector3D intercept, Vector3D gravity, double velocity)
{
if (gravity.IsZero()) { return intercept; } // nothing to do here
Vector3D gy = intercept.ProjectOnVector(gravity), gx = intercept.ProjPlane(gravity);
var res = Ballistic(gx.Normalize(), gy.Normalize(), velocity, gravity.Length());
return gx * res.X + gy * res.Y; // return distance to aim at
}
static Vector3D Intercept(Vector3D ownPosition, Vector3D ownVelocity, Vector3D tarPosition, Vector3D tarVelocity, double shotSpeed, Action<string> debug)
{
Vector3D
vector = tarPosition - ownPosition,
shotOpp = tarVelocity.ProjPlane(vector) - ownVelocity.ProjPlane(vector);
double
distance = vector.Normalize(),
shotAdj = Math.Sqrt(shotSpeed * shotSpeed - shotOpp.LengthSquared()),
ratio = (distance + tarVelocity.Dot(vector).NotNan() - ownVelocity.Dot(vector).NotNan()) / shotAdj;
return (shotOpp + vector * shotAdj) * ratio;
}
static Vector3D GetAimDirection(Vector3D intercept, Vector3D tarVelocity, Vector3D ownVelocity, double shot, Vector3D gravity, Action<string> debug)
{
Vector3D miss = tarVelocity - intercept.Norm() * shot;
if (!tarVelocity.IsZero() || !ownVelocity.IsZero())
{
tarVelocity -= ownVelocity;
ownVelocity = Vector3D.Zero;
double num = Vector3D.Dot(tarVelocity, tarVelocity) - shot * shot;
double num2 = 2.0 * Vector3D.Dot(tarVelocity, intercept);
double num3 = Vector3D.Dot(intercept, intercept);
double num4 = num2 * num2 - 4.0 * num * num3;
double timeToIntercept = (num4 <= 0.0) ? -1.0 : 2.0 * num3 / (Math.Sqrt(num4) - num2);
if (timeToIntercept > 0.0)
{
intercept += tarVelocity * timeToIntercept;
}
}
double correction = intercept.Length() / miss.Length();
return intercept - (gravity * 0.5 * (correction * correction));
}
const string string_controller_not_found = "Cannot find a cockpit. Please sit in the cockpit or control the remote control and try again. ";
static string string_mode_not_found(string cmd, string[] modes)
{
SBAL f = NSB();
f("'" + cmd + "' does not start with a command: ");
f(string.Join(", ", modes) + ", stop, reset");
f("and does not equal an operator: ");
f("resetconfig, updateconfig, resetcontroller, ESTOP");
return f.Get().ToString();
}
static string string_surface_not_parsed(string name, string index) =>
$"Can't parse index '{index}' as an integer in '{name}'. Please format it like [tag] or [tag:0].";
static string string_surface_only_one(string name, int index) =>
$"Block '{name}' only has one screen. Please remove the number from the tag or change it to 0. ";
static string string_surface_outof_range(string name, int index) =>
$"Can't find surface {index} on {name}. The first screen is number 0. The last screen is one less than the total number of screens";
}
internal static class Exts
{
public static void PrintAvgSpread<T>(this List<T> a, Func<T, Vector3D> v, Action<string> p)
{
var b = a.Select(v).ToList();
Func<Vector3D, double> X = (e) => e.X, Y = (e) => e.Y, Z = (e) => e.Z;
if (b.Count == 0) { p($"{typeof(T).Name} list is empty"); return; }
p($"{(b.Max(X) - b.Min(X)):N5} - {b.Average(X):N5}");
p($"{(b.Max(Y) - b.Min(Y)):N5} - {b.Average(Y):N5}");
p($"{(b.Max(Z) - b.Min(Z)):N5} - {b.Average(Z):N5}");
}
public static bool HasDataTag(this IMyTerminalBlock block, string tag)
{
foreach (var f in block.CustomData.Split('\n'))
{
if (f.Trim() == $"[{tag}]")
return true;
if (f.Trim() == "---")
break;
}
return false;
}
public static Vector3D PlanetPosition(this IMyShipController Controller) { Vector3D position; return Controller.TryGetPlanetPosition(out position) ? position : Vector3D.Zero; }
public static double PlanetHeight(this IMyShipController Controller, MyPlanetElevation type, double def = 0) { double height; return Controller.TryGetPlanetElevation(type, out height) ? height : def; }
public static double Offset(this IMyShipController controller, Program.B6D direction, Vector3D target)
{
var offset = controller.WorldMatrix.Translation - controller.CenterOfMass;
var offroot = offset.ProjectOnVector(ByMatrix(direction, controller, true)).Length();
var offcurr = offset.ProjectOnVector(target).Length();
return offroot - offcurr;
}
public static MatrixD ByDirection(this MatrixD mat, Program.B6D dir, Vector3D vec) { mat.SetDirectionVector(dir, vec); return mat; }
public static Vector3D GetVector(this MatrixD mat, Program.B6D dir) => mat.GetDirectionVector(dir);
public static StringBuilder Get(this Program.SBAL del) => (StringBuilder)del.Target;
public static Program.B6D Cast(this Program.Directions a) => a;
public static Program.B6D Cast(this Base6Directions.Direction a) => a;
public static bool IsNan(this double val) => double.IsNaN(val);
public static double IsNan(this double val, double def) => double.IsNaN(val) ? def : val;
public static double NotNan(this double val) => IsNan(val) ? 0 : val;
public static int NotNan(this int val) => IsNan(val) ? 0 : val;
public static double Dot(this Vector3D a, Vector3D b) => Vector3D.Dot(a, b);
public static double DotSign(this Vector3D a, Vector3D b) => Math.Sign(Vector3D.Dot(a, b));
public static double DotScale(this Vector3D a, Vector3D b) => ((1 - Dot(a, b)) / 2);
public static Vector3D Abs(this Vector3D a) => Vector3D.Abs(a);
public static Vector3D Max(this Vector3D a, Vector3D b) => Vector3D.Max(a, b);
public static Vector3D Min(this Vector3D a, Vector3D b) => Vector3D.Min(a, b);
public static Vector3D AngleFromPlane(this Vector3D a, Vector3D b) => AngleFromVector(a, a.ProjPlane(b).Norm());
public static Vector3D AngleFromVector(this Vector3D a, Vector3D b) => (a = a.Norm()).Cross(b = b.Norm()).Norm() * Math.Acos(MathHelper.Clamp(a.Dot(b), -1, 1));
public static Vector3D ClampToSphere(this Vector3D vec, double radius) => Vector3D.ClampToSphere(vec, radius);
public static Vector3D Norm(this Vector3D vec) => vec.IsZero() ? vec : Vector3D.Normalize(vec);
public static Vector3D ByMatrix(this Vector3D vector, IMyEntity block, bool isLocalVector = false) => vector.ByMatrix(block.WorldMatrix, isLocalVector);
public static Vector3D ByMatrix(this Vector3D vector, MatrixD matrix, bool isLocalVector = false) => Vector3D.TransformNormal(vector, isLocalVector ? matrix : MatrixD.Transpose(matrix));
public static Vector3D NotNan(this Vector3D val) { if (val.X.IsNan()) { val.X = 0; } if (val.Y.IsNan()) { val.Y = 0; } if (val.Z.IsNan()) { val.Z = 0; } return val; }
public static Vector3D ProjPlane(this Vector3D a, Vector3D b) => Vector3D.ProjectOnPlane(ref a, ref b).NotNan();
public static Vector3D ProjectOnVector(this Vector3D a, Vector3D b) => Vector3D.ProjectOnVector(ref a, ref b).NotNan();
public static Vector3D ProjectOnRay(this Vector3D a, Vector3D b) => Vector3D.Dot(a = a.ProjectOnVector(b), b) >= 0 ? a : Vector3D.Zero;
public static Vector3D Reject(this Vector3D a, Vector3D b) => Vector3D.Reject(a, b);
public static Vector3D Signs(this Vector3D a) => Vector3D.Sign(a);
public static bool IsZero(this Vector3I a) => a == Vector3I.Zero;
public static void Print(this Vector3D v, Action<string> p, string f = "N8") { p(string.Join("\n", v.X.ToString(f), v.Y.ToString(f), v.Z.ToString(f))); }
public static IEnumerable<TR> TrySelectAll<TS, TR>(this IEnumerable<TS> s, out bool ok, TR d, Program.TS<TS, TR> p) { var _ok = true; var r = s.Select(e => { TR v; if (p(e, out v)) return v; else { _ok = false; return d; } }); ok = _ok; return r; }
public static void AppendLine(this StringBuilder b, string p, double v, string f = "N8") { b.AppendLine(p + v.ToString(f)); }
public static void AppendLine(this StringBuilder build, double value, string format = "N8") { build.AppendLine(value.ToString(format)); }
public static Vector3D Quadratic(Vector3D v, Func<double, double[]> f) => new Vector3D(Quadratic(f(v.X)), Quadratic(f(v.Y)), Quadratic(f(v.Z)));
public static double Quadratic(params double[] v)
{
var r = QuadraticPair(v[0], v[1], v[2]);
return r.Length == 2 ? r[(v.Length <= 3 || v[3] == 0 ? v[1] : v[3]) > 0 ? 0 : 1] : r.Length == 1 ? r[0] : double.NaN;
}
public static double[] QuadraticPair(params double[] v)
{
double a = v[0], b = v[1], c = v[2], e = b * b - 4 * a * c, f = 2 * a, g = e > 0 ? Math.Sqrt(e) : 0;
return (e < 0 || a == 0) ? new double[0] : (e == 0) ? new double[1] { -b / f } : new double[2] { (-b + g) / f, (-b - g) / f };
}
#if DEV
}
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment