Last active
June 22, 2023 14:14
-
-
Save Arlen22/18ae808bd218d129e5017ead83a342e2 to your computer and use it in GitHub Desktop.
Space Engineers Vectors Script 3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Center <<Cargo>> | |
Cargoall {T:Large Cargo Container} | |
Inventoryx T:* +Ingot | |
Inventoryx T:* +ice | |
Center <<Tanks>> | |
Tanks T:* Hydrogen | |
Tanks T:* Oxygen | |
Powertime | |
Powerstored T:* | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//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); | |
} | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
https://github.com/malware-dev/MDK-Debug | |
https://github.com/icsharpcode/ILSpy | |
https://github.com/malware-dev/MDK-SE/wiki/Advanced-debugging-with-dnSpy | |
https://github.com/THDigi/SE-ModScript-Examples/wiki/Quick-Intro-to-Space-Engineers-Modding | |
https://github.com/malware-dev/MDK-SE/wiki/Mixin-Projects | |
https://github.com/THDigi/SE-ModScript-Examples/wiki/Visual-Studio-Setup | |
https://github.com/THDigi/SE-ModScript-Examples/wiki/Development-tools | |
https://spaceengineers.fandom.com/wiki/Modding | |
https://www.spaceengineersgame.com/modding-guides/ | |
https://github.com/TorchAPI |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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 }; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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