using System;
using System.Collections.Generic;
using Sandbox.ModAPI.Ingame;
using SpaceEngineers.Game.ModAPI.Ingame;
using VRage.Game.ModAPI.Ingame.Utilities;
using VRageMath;
namespace AutoMiningScript
{
public partial class Program : MyGridProgram
{
public enum FlightState { Boot, Docked, Servicing, Ready, Departing, Transit, Survey, Align, Drilling, Retreat, Returning, Holding, DockAlign, DockApproach, DockRetreat, Paused, Manual, Fault }
public enum JobKind { Manual, Probe, Survey }
public abstract class RoleLogic
{
protected readonly Program P;
protected RoleLogic(Program p) { P = p; }
public virtual void Tick(double dt) { }
public virtual void Command(string command) { }
public virtual void Receive(Packet packet) { }
public virtual void Save(MyIni ini) { }
public virtual bool Checkpoint(MyIni ini) { Save(ini); return true; }
public virtual void Load(MyIni ini) { }
public virtual void Emergency(string reason) { P.Log(reason); }
public virtual List<Telemetry> GetTelemetry() => Data.CreateList<Telemetry>();
public virtual List<Job> GetJobs() => Data.CreateList<Job>();
#if FLEET || TESTS
public virtual MiningRegion MapRegion(string id) => null;
#endif
public virtual double MeasuredDepartureMass => 0;
public virtual string Diagnostics => "";
}
public static class Data
{
public static List<T> CreateList<T>() => new List<T>();
public static Dictionary<TKey,TValue> CreateDictionary<TKey,TValue>() => new Dictionary<TKey,TValue>();
public static bool CargoInventory(IMyTerminalBlock block) => block is IMyCargoContainer || block is IMyShipDrill || block is IMyShipConnector;
// Shared engine API names keep the eight pasteable exports below 100k.
public static readonly Vector3D Zero = Vector3D.Zero, Forward = Vector3D.Forward, Up = Vector3D.Up;
public static readonly StringComparison Ordinal = StringComparison.Ordinal, IgnoreCase = StringComparison.OrdinalIgnoreCase;
public static ArgumentException Invalid(string message) => new ArgumentException(message);
public static double Distance(Vector3D a, Vector3D b) => Vector3D.Distance(a,b);
public static double DistanceSquared(Vector3D a, Vector3D b) => Vector3D.DistanceSquared(a,b);
public static double Dot(Vector3D a, Vector3D b) => Vector3D.Dot(a,b);
public static Vector3D Cross(Vector3D a, Vector3D b) => Vector3D.Cross(a,b);
public static Vector3D TransformNormal(Vector3D vector,MatrixD matrix) => Vector3D.TransformNormal(vector,matrix);
public static MatrixD Transpose(MatrixD matrix) => MatrixD.Transpose(matrix);
public static double Length(Vector3D vector) => vector.Length();
public static float Length(Vector2 vector) => vector.Length();
public static double LengthSquared(Vector3D vector) => vector.LengthSquared();
public static double Max(double a,double b) => Math.Max(a,b);
public static float Max(float a,float b) => Math.Max(a,b);
public static int Max(int a,int b) => Math.Max(a,b);
public static long Max(long a,long b) => Math.Max(a,b);
public static double Min(double a,double b) => Math.Min(a,b);
public static float Min(float a,float b) => Math.Min(a,b);
public static int Min(int a,int b) => Math.Min(a,b);
public static long Min(long a,long b) => Math.Min(a,b);
public static string Text(MyIni ini,string section,string key) => ini.Get(section,key).ToString();
public static bool Flag(MyIni ini,string section,string key) => ini.Get(section,key).ToBoolean();
public static void PutVector(MyIni ini,string section,string key,Vector3D value) {ini.Set(section,key,Vector(value));}
public static readonly System.Globalization.CultureInfo Culture = System.Globalization.CultureInfo.InvariantCulture;
public static bool Finite(double n) => !double.IsNaN(n) && !double.IsInfinity(n);
public static bool Finite(Vector3D v) => Finite(v.X) && Finite(v.Y) && Finite(v.Z);
public static bool TryNumber(string value,out double number)
=> double.TryParse(value,System.Globalization.NumberStyles.Float,Culture,out number) && Finite(number);
public static bool TryLong(string value,out long number)
=> long.TryParse(value,System.Globalization.NumberStyles.Integer,Culture,out number);
public static double Clamp(double x, double min, double max) => Data.Max(min, Data.Min(max, x));
public static string Format(double n) => n.ToString("R", Culture);
public static string Vector(Vector3D v) => Format(v.X) + "," + Format(v.Y) + "," + Format(v.Z);
public static bool TryVector(string value, out Vector3D v)
{
v = Data.Zero;
var a = value.Split(','); double x, y, z;
if (a.Length != 3 || !TryNumber(a[0],out x) || !TryNumber(a[1],out y) || !TryNumber(a[2],out z)) return false;
v = new Vector3D(x, y, z); return true;
}
public static Vector3D ReadVector(MyIni ini, string section, string name, Vector3D fallback=default(Vector3D))
{
if (!ini.ContainsKey(section, name)) return fallback;
Vector3D v;
if (!TryVector(Data.Text(ini,section, name), out v)) throw Data.Invalid(L.F(L.CommonVectorInvalid, section, name));
return v;
}
public static double ReadNumber(MyIni ini, string section, string name, double fallback=0)
{
if (!ini.ContainsKey(section, name)) return fallback;
double n;
if (!TryNumber(Data.Text(ini,section, name),out n)) throw Data.Invalid(L.F(L.CommonNumberInvalid, section, name));
return n;
}
public static long ReadLong(MyIni ini, string section, string name, long fallback = 0)
{
if (!ini.ContainsKey(section, name)) return fallback;
long n;
if (!TryLong(Data.Text(ini,section, name), out n)) throw Data.Invalid(L.F(L.CommonIntegerInvalid, section, name));
return n;
}
public static long ReadIndex(MyIni ini,string section,string name,long maximum)
{
long value=ReadLong(ini,section,name,-1);
if(value < -1 || value > maximum)throw Invalid(section+"."+name);
return value;
}
public static Vector3D Unit(Vector3D v, Vector3D fallback)
{
var length = Length(v); return length > 0.000001 && Finite(length) ? v / length : fallback;
}
public static Vector3D Perpendicular(Vector3D forward, Vector3D up)
{
forward = Unit(forward, Data.Forward);
var projected = up - forward * Data.Dot(up, forward);
if (LengthSquared(projected) < 0.00001) projected = Data.Cross(forward, Math.Abs(forward.Y) < 0.9 ? Data.Up : Vector3D.Right);
return Unit(projected, Data.Up);
}
public static MatrixD Frame(Vector3D position, Vector3D forward, Vector3D up) => MatrixD.CreateWorld(position, Unit(forward, Data.Forward), Perpendicular(forward, up));
public static bool TryGps(string text, out Vector3D position)
{
position = Data.Zero;
var parts = text.Trim().Split(':');
return parts.Length >= 6 && parts[0] == "GPS" && TryVector(parts[2] + "," + parts[3] + "," + parts[4], out position);
}
public static double SegmentDistance(Vector3D p1, Vector3D q1, Vector3D p2, Vector3D q2)
{
Vector3D d1 = q1 - p1, d2 = q2 - p2, r = p1 - p2;
double a = LengthSquared(d1), e = LengthSquared(d2), f = Data.Dot(d2, r), s, t;
if (a < 1e-9 && e < 1e-9) return Length(r);
if (a < 1e-9) { s = 0; t = Clamp(f / e, 0, 1); }
else
{
var c = Data.Dot(d1, r);
if (e < 1e-9) { t = 0; s = Clamp(-c / a, 0, 1); }
else
{
double b = Data.Dot(d1, d2), denom = a * e - b * b;
s = Math.Abs(denom) > 1e-9 ? Clamp((b * f - c * e) / denom, 0, 1) : 0;
t = (b * s + f) / e;
if (t < 0) { t = 0; s = Clamp(-c / a, 0, 1); }
else if (t > 1) { t = 1; s = Clamp((b - c) / a, 0, 1); }
}
}
return Length((p1 + d1 * s) - (p2 + d2 * t));
}
public static double InterceptTime(Vector3D relative, Vector3D targetVelocity, double speed)
{
if (speed <= 0 || !Finite(speed)) return -1;
double a = LengthSquared(targetVelocity) - speed * speed, b = 2 * Data.Dot(relative, targetVelocity), c = LengthSquared(relative);
if (c < 0.01) return 0;
if (Math.Abs(a) < 1e-8) return b < 0 ? -c / b : -1;
var disc = b * b - 4 * a * c; if (disc < 0) return -1;
double x = (-b - Math.Sqrt(disc)) / (2 * a), y = (-b + Math.Sqrt(disc)) / (2 * a);
if (x > 0 && y > 0) return Data.Min(x, y); return x > 0 ? x : y > 0 ? y : -1;
}
}
public class Job
{
public ScanRecord Scan;
public string Id = "", RegionId = "", TargetOre = "", ConfirmedOres = "", Owner = "", Outcome = "", LastOwner = "";
public JobKind Kind;
public Vector3D Entry, Direction = Data.Forward, Up = Data.Up;
public double Depth = 15, Radius = 5, Progress;
public int HoleIndex, Priority;
public long EntityId;
public MyIni ToIni()
{
var i = new MyIni(); Write(i, "job"); return i;
}
public void Write(MyIni i, string s)
{
if (Scan != null) Scan.Write(i, s);
i.Set(s, "ConfirmedOres", ConfirmedOres);
i.Set(s, "LastOwner", LastOwner);
i.Set(s, "Id", Id); i.Set(s, "RegionId", RegionId); i.Set(s, "Kind", (int)Kind); Data.PutVector(i,s, "Entry",Entry); Data.PutVector(i,s, "Direction",Direction); Data.PutVector(i,s, "Up",Up);
i.Set(s, "Depth", Depth); i.Set(s, "Radius", Radius); i.Set(s, "Progress", Progress); i.Set(s, "HoleIndex", HoleIndex); i.Set(s, "EntityId", EntityId); i.Set(s, "Priority", Priority); i.Set(s, "TargetOre", TargetOre); i.Set(s, "Owner", Owner); i.Set(s, "Outcome", Outcome);
}
public static Job FromIni(MyIni i) => Read(i, "job");
public static Job Read(MyIni i, string s)
{
var j = new Job(); j.Id = Data.Text(i,s, "Id"); j.RegionId = Data.Text(i,s, "RegionId");
j.Scan = ScanRecord.Read(i, s);
j.ConfirmedOres = Data.Text(i, s, "ConfirmedOres");
j.LastOwner = Data.Text(i,s, "LastOwner");
int kind = (int)Data.ReadLong(i, s, "Kind"); if (kind < 0 || kind > 2) throw Data.Invalid(L.CommonJobKindInvalid); j.Kind = (JobKind)kind;
j.Entry = Data.ReadVector(i, s, "Entry"); j.Direction = Data.ReadVector(i, s, "Direction", Data.Forward); j.Up = Data.ReadVector(i, s, "Up", Data.Up);
if (Data.LengthSquared(j.Direction) < 0.1 || Data.LengthSquared(j.Up) < 0.1) throw Data.Invalid(L.CommonJobBasisZero);
j.Direction = Data.Unit(j.Direction, Data.Forward); j.Up = Data.Perpendicular(j.Direction, j.Up);
j.Depth = Data.ReadNumber(i, s, "Depth", 15); j.Radius = Data.ReadNumber(i, s, "Radius", 5); j.Progress = Data.ReadNumber(i, s, "Progress");
if (j.Id.Length == 0 || j.Depth <= 0 || j.Depth > 10000 || j.Radius <= 0 || j.Progress < 0 || j.Progress > j.Depth + 1) throw Data.Invalid(L.CommonJobBoundsInvalid);
j.HoleIndex = (int)Data.ReadLong(i, s, "HoleIndex"); j.Priority = (int)Data.ReadLong(i, s, "Priority"); j.EntityId = Data.ReadLong(i, s, "EntityId"); j.TargetOre = Data.Text(i,s, "TargetOre"); j.Owner = Data.Text(i,s, "Owner"); j.Outcome = Data.Text(i,s, "Outcome"); return j;
}
}
public class Telemetry
{
public string Id = "", JobId = "", Reason = "", OreSummary = "", NextAfter = "";
public Vector3D RouteTarget;
public bool HasRoute;
public FlightState State;
public Vector3D Position, Velocity, Forward = Data.Forward, Up = Data.Up;
public double Battery, Hydrogen = 1, Cargo, OreAmount, StoredMWh, CapacityMWh, NetMW, EnduranceSeconds, Depth, TargetDepth, DistanceHome, ThrustMargin, Gravity, Radius = 5, ReceivedAt;
public double BrakeResponse = .625;
public bool Connected;
public long ConnectorId, PeerConnectorId, Address, CancelledThrough = -1, Epoch;
public int WaitingSlot = -1, ServiceFlags = -1;
public MyIni ToIni()
{
var i = new MyIni(); var s = "telemetry";
i.Set(s, "NextAfter", NextAfter); Data.PutVector(i,s, "RouteTarget",RouteTarget); i.Set(s, "HasRoute", HasRoute);
i.Set(s, "CancelledThrough", CancelledThrough);
i.Set(s, "WaitingSlot", WaitingSlot);
i.Set(s, "ServiceFlags", ServiceFlags);
i.Set(s, "BrakeResponse", BrakeResponse);
i.Set(s, "Id", Id); i.Set(s, "State", (int)State); Data.PutVector(i,s, "Position",Position); Data.PutVector(i,s, "Velocity",Velocity); Data.PutVector(i,s, "Forward",Forward); Data.PutVector(i,s, "Up",Up);
i.Set(s, "Battery", Battery); i.Set(s, "Hydrogen", Hydrogen); i.Set(s, "Cargo", Cargo); i.Set(s, "OreAmount", OreAmount); i.Set(s, "StoredMWh", StoredMWh); i.Set(s, "CapacityMWh", CapacityMWh); i.Set(s, "NetMW", NetMW); i.Set(s, "EnduranceSeconds", EnduranceSeconds);
i.Set(s, "Depth", Depth); i.Set(s, "TargetDepth", TargetDepth); i.Set(s, "DistanceHome", DistanceHome); i.Set(s, "ThrustMargin", ThrustMargin); i.Set(s, "Gravity", Gravity); i.Set(s, "Radius", Radius); i.Set(s, "JobId", JobId); i.Set(s, "Reason", Reason); i.Set(s, "OreSummary", OreSummary); i.Set(s, "Connected", Connected); i.Set(s, "ConnectorId", ConnectorId); i.Set(s, "PeerConnectorId", PeerConnectorId); return i;
}
public static Telemetry FromIni(MyIni i)
{
var s = "telemetry"; var t = new Telemetry(); t.Id = Data.Text(i,s, "Id"); int state = (int)Data.ReadLong(i, s, "State"); if (state < 0 || state > (int)FlightState.Fault) throw Data.Invalid(L.CommonFlightStateInvalid); t.State = (FlightState)state;
t.NextAfter = Data.Text(i,s, "NextAfter"); t.RouteTarget = Data.ReadVector(i, s, "RouteTarget", t.Position); t.HasRoute = Data.Flag(i,s, "HasRoute");
t.CancelledThrough = Data.ReadIndex(i, s, "CancelledThrough", 1000000000);
t.WaitingSlot = (int)Data.ReadIndex(i, s, "WaitingSlot", 1000000);
t.ServiceFlags = (int)Data.ReadIndex(i, s, "ServiceFlags", 3);
t.BrakeResponse=Data.ReadNumber(i,s,"BrakeResponse",.625);
if(t.BrakeResponse<.625 || t.BrakeResponse>5)throw Data.Invalid("telemetry.BrakeResponse");
t.Position = Data.ReadVector(i, s, "Position"); t.Velocity = Data.ReadVector(i, s, "Velocity"); t.Forward = Data.ReadVector(i, s, "Forward", Data.Forward); t.Up = Data.ReadVector(i, s, "Up", Data.Up);
t.Battery = Data.ReadNumber(i, s, "Battery"); t.Hydrogen = Data.ReadNumber(i, s, "Hydrogen", 1); t.Cargo = Data.ReadNumber(i, s, "Cargo"); t.OreAmount = Data.ReadNumber(i, s, "OreAmount"); t.StoredMWh = Data.ReadNumber(i, s, "StoredMWh"); t.CapacityMWh = Data.ReadNumber(i, s, "CapacityMWh"); t.NetMW = Data.ReadNumber(i, s, "NetMW"); t.EnduranceSeconds = Data.ReadNumber(i, s, "EnduranceSeconds");
t.Depth = Data.ReadNumber(i, s, "Depth"); t.TargetDepth = Data.ReadNumber(i, s, "TargetDepth"); t.DistanceHome = Data.ReadNumber(i, s, "DistanceHome"); t.ThrustMargin = Data.ReadNumber(i, s, "ThrustMargin"); t.Gravity = Data.ReadNumber(i, s, "Gravity"); t.Radius = Data.ReadNumber(i, s, "Radius", 5); t.JobId = Data.Text(i,s, "JobId"); t.Reason = Data.Text(i,s, "Reason"); t.OreSummary = Data.Text(i,s, "OreSummary"); t.Connected = Data.Flag(i,s, "Connected"); t.ConnectorId = Data.ReadLong(i, s, "ConnectorId"); t.PeerConnectorId = Data.ReadLong(i, s, "PeerConnectorId"); return t;
}
}
public class DockFrame
{
public string BaseId = "", Token = "", Owner = "", Problem = "";
public long ConnectorId, GridId, Source;
public Vector3D Position, Center, Velocity, AngularVelocity, Acceleration, WaitingLocal;
public Vector3D Forward = Data.Forward, Up = Data.Up;
public bool Connected;
public double ReceivedAt, ApproachDistance, LaneClearance = 60;
public Vector3D PointVelocity(Vector3D point) => Velocity + Data.Cross(AngularVelocity, point - Center);
public MatrixD Predicted(double now)
{
var age = Data.Clamp(now - ReceivedAt, 0, 0.5); var angle = Data.Length(AngularVelocity) * age;
MatrixD rotation = angle > 1e-9 ? MatrixD.CreateFromAxisAngle(Data.Unit(AngularVelocity, Data.Up), angle) : MatrixD.Identity;
var center = Center + Velocity * age;
return Data.Frame(center + Data.TransformNormal(Position - Center, rotation), Data.TransformNormal(Forward, rotation), Data.TransformNormal(Up, rotation));
}
public Vector3D GetPoint(double distance, double now) { var f = Predicted(now); return f.Translation + f.Forward * distance; }
public bool IsWindow(Settings cfg, double now)
=> now - ReceivedAt <= cfg.Number("Dock", "MaxTelemetryAge", 0.5, 0.1, 2) && Data.Length(PointVelocity(Position)) <= cfg.Number("Dock", "MaxBaseSpeed", 5, 0.1, 50) && Data.Length(AngularVelocity) <= cfg.Number("Dock", "MaxBaseAngularDeg", 0.2, 0.01, 5) * Math.PI / 180 && Data.Length(Acceleration) <= cfg.Number("Dock", "MaxBaseAcceleration", 0.1, 0.01, 5);
public MyIni ToIni()
{
var i = new MyIni(); var s = "dock"; i.Set(s,"Problem",Problem); i.Set(s, "BaseId", BaseId); i.Set(s, "ConnectorId", ConnectorId); i.Set(s, "GridId", GridId); Data.PutVector(i,s, "Position",Position); Data.PutVector(i,s, "Forward",Forward); Data.PutVector(i,s, "Up",Up); Data.PutVector(i,s, "Center",Center); Data.PutVector(i,s, "Velocity",Velocity); Data.PutVector(i,s, "AngularVelocity",AngularVelocity); Data.PutVector(i,s, "Acceleration",Acceleration); Data.PutVector(i,s, "WaitingLocal",WaitingLocal); i.Set(s, "ApproachDistance", ApproachDistance); i.Set(s, "LaneClearance", LaneClearance); i.Set(s, "Token", Token); i.Set(s, "Owner", Owner); i.Set(s, "Connected", Connected); return i;
}
public static DockFrame FromIni(MyIni i)
{
var s = "dock"; var d = new DockFrame(); d.Problem=Data.Text(i,s,"Problem"); d.BaseId = Data.Text(i,s, "BaseId"); d.ConnectorId = Data.ReadLong(i, s, "ConnectorId"); d.GridId = Data.ReadLong(i, s, "GridId"); d.Position = Data.ReadVector(i, s, "Position"); d.Center = Data.ReadVector(i, s, "Center", d.Position); d.Forward = Data.Unit(Data.ReadVector(i, s, "Forward", Data.Forward), Data.Forward); d.Up = Data.Perpendicular(d.Forward, Data.ReadVector(i, s, "Up", Data.Up)); d.Velocity = Data.ReadVector(i, s, "Velocity"); d.AngularVelocity = Data.ReadVector(i, s, "AngularVelocity"); d.Acceleration = Data.ReadVector(i, s, "Acceleration"); d.WaitingLocal = Data.ReadVector(i, s, "WaitingLocal"); d.ApproachDistance = Data.Clamp(Data.ReadNumber(i,s,"ApproachDistance"),0,10000); d.LaneClearance = Data.Clamp(Data.ReadNumber(i, s, "LaneClearance", 60), 20, 10000); d.Token = Data.Text(i,s, "Token"); d.Owner = Data.Text(i,s, "Owner"); d.Connected = Data.Flag(i,s, "Connected"); return d;
}
}
// Map evidence is optional in old messages/saves. A ray proves only its actual
// segment; an asteroid's world AABB is an estimate, not a mined surface area.
public class ScanRecord
{
public Vector3D Start, End, Center, Size;
public long Asteroid;
public bool Sized => Data.Finite(Center) && Data.Finite(Size) && Data.LengthSquared(Size)<1e14 && Size.X > 0 && Size.Y > 0 && Size.Z > 0;
public static ScanRecord Capture(Vector3D start, Vector3D end, MyDetectedEntityInfo hit)
{
if (!hit.IsEmpty()) { if (!hit.HitPosition.HasValue) return null; end = hit.HitPosition.Value; }
if (!Data.Finite(start) || !Data.Finite(end)) return null;
var scan = new ScanRecord { Start = start, End = end, Center = end };
if (hit.Type == MyDetectedEntityType.Asteroid && hit.EntityId != 0)
{
scan.Asteroid = hit.EntityId; scan.Center = hit.BoundingBox.Center; scan.Size = hit.BoundingBox.Size;
if (!scan.Sized) { scan.Size = Data.Zero; scan.Center = end; }
}
return scan;
}
public void Write(MyIni i, string s)
{
Data.PutVector(i,s,"ScanStart",Start); Data.PutVector(i,s,"ScanEnd",End);
i.Set(s,"RockId",Asteroid); Data.PutVector(i,s,"RockCenter",Center); Data.PutVector(i,s,"RockSize",Size);
}
public static ScanRecord Read(MyIni i, string s)
{
if (!i.ContainsKey(s,"ScanStart")) return null;
try
{
var scan = new ScanRecord { Start=Data.ReadVector(i,s,"ScanStart"), End=Data.ReadVector(i,s,"ScanEnd"),
Asteroid=Data.ReadLong(i,s,"RockId"), Center=Data.ReadVector(i,s,"RockCenter"), Size=Data.ReadVector(i,s,"RockSize") };
if (!scan.Sized) { scan.Size=Data.Zero; scan.Center=scan.End; } return scan;
}
catch (ArgumentException) { return null; }
}
}
public class JobReport
{
public ScanRecord Scan;
public string JobId = "", Outcome = "", Ore = "", Message = "";
public double Progress;
public Vector3D Position, Direction = Data.Forward, Up = Data.Up;
public long EntityId;
public MyIni ToIni() { var i = new MyIni(); var s = "result"; if(Scan!=null)Scan.Write(i,s); i.Set(s, "JobId", JobId); i.Set(s, "Outcome", Outcome); i.Set(s, "Ore", Ore); i.Set(s, "Message", Message); i.Set(s, "Progress", Progress); Data.PutVector(i,s, "Position",Position); Data.PutVector(i,s, "Direction",Direction); Data.PutVector(i,s, "Up",Up); i.Set(s, "EntityId", EntityId); return i; }
public static JobReport FromIni(MyIni i) { var s = "result"; return new JobReport { Scan=ScanRecord.Read(i,s), JobId = Data.Text(i,s, "JobId"), Outcome = Data.Text(i,s, "Outcome"), Ore = Data.Text(i,s, "Ore"), Message = Data.Text(i,s, "Message"), Progress = Data.ReadNumber(i, s, "Progress"), Position = Data.ReadVector(i, s, "Position"), Direction = Data.ReadVector(i, s, "Direction", Data.Forward), Up = Data.ReadVector(i, s, "Up", Data.Up), EntityId = Data.ReadLong(i, s, "EntityId") }; }
}
}
}
using System;
using System.Collections.Generic;
using Sandbox.ModAPI.Ingame;
using SpaceEngineers.Game.ModAPI.Ingame;
using VRage.Game.ModAPI.Ingame.Utilities;
using VRageMath;
namespace AutoMiningScript
{
public partial class Program : MyGridProgram
{
public enum FlightState { Boot, Docked, Servicing, Ready, Departing, Transit, Survey, Align, Drilling, Retreat, Returning, Holding, DockAlign, DockApproach, DockRetreat, Paused, Manual, Fault }
public enum JobKind { Manual, Probe, Survey }
public abstract class RoleLogic
{
protected readonly Program P;
protected RoleLogic(Program p) { P = p; }
public virtual void Tick(double dt) { }
public virtual void Command(string command) { }
public virtual void Receive(Packet packet) { }
public virtual void Save(MyIni ini) { }
public virtual bool Checkpoint(MyIni ini) { Save(ini); return true; }
public virtual void Load(MyIni ini) { }
public virtual void Emergency(string reason) { P.Log(reason); }
public virtual List<Telemetry> GetTelemetry() => Data.CreateList<Telemetry>();
public virtual List<Job> GetJobs() => Data.CreateList<Job>();
#if FLEET || TESTS
public virtual MiningRegion MapRegion(string id) => null;
#endif
public virtual double MeasuredDepartureMass => 0;
public virtual string Diagnostics => "";
}
public static class Data
{
public static List<T> CreateList<T>() => new List<T>();
public static Dictionary<TKey,TValue> CreateDictionary<TKey,TValue>() => new Dictionary<TKey,TValue>();
public static bool CargoInventory(IMyTerminalBlock block) => block is IMyCargoContainer || block is IMyShipDrill || block is IMyShipConnector;
// Shared engine API names keep the eight pasteable exports below 100k.
public static readonly Vector3D Zero = Vector3D.Zero, Forward = Vector3D.Forward, Up = Vector3D.Up;
public static readonly StringComparison Ordinal = StringComparison.Ordinal, IgnoreCase = StringComparison.OrdinalIgnoreCase;
public static ArgumentException Invalid(string message) => new ArgumentException(message);
public static double Distance(Vector3D a, Vector3D b) => Vector3D.Distance(a,b);
public static double DistanceSquared(Vector3D a, Vector3D b) => Vector3D.DistanceSquared(a,b);
public static double Dot(Vector3D a, Vector3D b) => Vector3D.Dot(a,b);
public static Vector3D Cross(Vector3D a, Vector3D b) => Vector3D.Cross(a,b);
public static Vector3D TransformNormal(Vector3D vector,MatrixD matrix) => Vector3D.TransformNormal(vector,matrix);
public static MatrixD Transpose(MatrixD matrix) => MatrixD.Transpose(matrix);
public static double Length(Vector3D vector) => vector.Length();
public static float Length(Vector2 vector) => vector.Length();
public static double LengthSquared(Vector3D vector) => vector.LengthSquared();
public static double Max(double a,double b) => Math.Max(a,b);
public static float Max(float a,float b) => Math.Max(a,b);
public static int Max(int a,int b) => Math.Max(a,b);
public static long Max(long a,long b) => Math.Max(a,b);
public static double Min(double a,double b) => Math.Min(a,b);
public static float Min(float a,float b) => Math.Min(a,b);
public static int Min(int a,int b) => Math.Min(a,b);
public static long Min(long a,long b) => Math.Min(a,b);
public static string Text(MyIni ini,string section,string key) => ini.Get(section,key).ToString();
public static bool Flag(MyIni ini,string section,string key) => ini.Get(section,key).ToBoolean();
public static void PutVector(MyIni ini,string section,string key,Vector3D value) {ini.Set(section,key,Vector(value));}
public static readonly System.Globalization.CultureInfo Culture = System.Globalization.CultureInfo.InvariantCulture;
public static bool Finite(double n) => !double.IsNaN(n) && !double.IsInfinity(n);
public static bool Finite(Vector3D v) => Finite(v.X) && Finite(v.Y) && Finite(v.Z);
public static bool TryNumber(string value,out double number)
=> double.TryParse(value,System.Globalization.NumberStyles.Float,Culture,out number) && Finite(number);
public static bool TryLong(string value,out long number)
=> long.TryParse(value,System.Globalization.NumberStyles.Integer,Culture,out number);
public static double Clamp(double x, double min, double max) => Data.Max(min, Data.Min(max, x));
public static string Format(double n) => n.ToString("R", Culture);
public static string Vector(Vector3D v) => Format(v.X) + "," + Format(v.Y) + "," + Format(v.Z);
public static bool TryVector(string value, out Vector3D v)
{
v = Data.Zero;
var a = value.Split(','); double x, y, z;
if (a.Length != 3 || !TryNumber(a[0],out x) || !TryNumber(a[1],out y) || !TryNumber(a[2],out z)) return false;
v = new Vector3D(x, y, z); return true;
}
public static Vector3D ReadVector(MyIni ini, string section, string name, Vector3D fallback=default(Vector3D))
{
if (!ini.ContainsKey(section, name)) return fallback;
Vector3D v;
if (!TryVector(Data.Text(ini,section, name), out v)) throw Data.Invalid(L.F(L.CommonVectorInvalid, section, name));
return v;
}
public static double ReadNumber(MyIni ini, string section, string name, double fallback=0)
{
if (!ini.ContainsKey(section, name)) return fallback;
double n;
if (!TryNumber(Data.Text(ini,section, name),out n)) throw Data.Invalid(L.F(L.CommonNumberInvalid, section, name));
return n;
}
public static long ReadLong(MyIni ini, string section, string name, long fallback = 0)
{
if (!ini.ContainsKey(section, name)) return fallback;
long n;
if (!TryLong(Data.Text(ini,section, name), out n)) throw Data.Invalid(L.F(L.CommonIntegerInvalid, section, name));
return n;
}
public static long ReadIndex(MyIni ini,string section,string name,long maximum)
{
long value=ReadLong(ini,section,name,-1);
if(value < -1 || value > maximum)throw Invalid(section+"."+name);
return value;
}
public static Vector3D Unit(Vector3D v, Vector3D fallback)
{
var length = Length(v); return length > 0.000001 && Finite(length) ? v / length : fallback;
}
public static Vector3D Perpendicular(Vector3D forward, Vector3D up)
{
forward = Unit(forward, Data.Forward);
var projected = up - forward * Data.Dot(up, forward);
if (LengthSquared(projected) < 0.00001) projected = Data.Cross(forward, Math.Abs(forward.Y) < 0.9 ? Data.Up : Vector3D.Right);
return Unit(projected, Data.Up);
}
public static MatrixD Frame(Vector3D position, Vector3D forward, Vector3D up) => MatrixD.CreateWorld(position, Unit(forward, Data.Forward), Perpendicular(forward, up));
public static bool TryGps(string text, out Vector3D position)
{
position = Data.Zero;
var parts = text.Trim().Split(':');
return parts.Length >= 6 && parts[0] == "GPS" && TryVector(parts[2] + "," + parts[3] + "," + parts[4], out position);
}
public static double SegmentDistance(Vector3D p1, Vector3D q1, Vector3D p2, Vector3D q2)
{
Vector3D d1 = q1 - p1, d2 = q2 - p2, r = p1 - p2;
double a = LengthSquared(d1), e = LengthSquared(d2), f = Data.Dot(d2, r), s, t;
if (a < 1e-9 && e < 1e-9) return Length(r);
if (a < 1e-9) { s = 0; t = Clamp(f / e, 0, 1); }
else
{
var c = Data.Dot(d1, r);
if (e < 1e-9) { t = 0; s = Clamp(-c / a, 0, 1); }
else
{
double b = Data.Dot(d1, d2), denom = a * e - b * b;
s = Math.Abs(denom) > 1e-9 ? Clamp((b * f - c * e) / denom, 0, 1) : 0;
t = (b * s + f) / e;
if (t < 0) { t = 0; s = Clamp(-c / a, 0, 1); }
else if (t > 1) { t = 1; s = Clamp((b - c) / a, 0, 1); }
}
}
return Length((p1 + d1 * s) - (p2 + d2 * t));
}
public static double InterceptTime(Vector3D relative, Vector3D targetVelocity, double speed)
{
if (speed <= 0 || !Finite(speed)) return -1;
double a = LengthSquared(targetVelocity) - speed * speed, b = 2 * Data.Dot(relative, targetVelocity), c = LengthSquared(relative);
if (c < 0.01) return 0;
if (Math.Abs(a) < 1e-8) return b < 0 ? -c / b : -1;
var disc = b * b - 4 * a * c; if (disc < 0) return -1;
double x = (-b - Math.Sqrt(disc)) / (2 * a), y = (-b + Math.Sqrt(disc)) / (2 * a);
if (x > 0 && y > 0) return Data.Min(x, y); return x > 0 ? x : y > 0 ? y : -1;
}
}
public class Job
{
public ScanRecord Scan;
public string Id = "", RegionId = "", TargetOre = "", ConfirmedOres = "", Owner = "", Outcome = "", LastOwner = "";
public JobKind Kind;
public Vector3D Entry, Direction = Data.Forward, Up = Data.Up;
public double Depth = 15, Radius = 5, Progress;
public int HoleIndex, Priority;
public long EntityId;
public MyIni ToIni()
{
var i = new MyIni(); Write(i, "job"); return i;
}
public void Write(MyIni i, string s)
{
if (Scan != null) Scan.Write(i, s);
i.Set(s, "ConfirmedOres", ConfirmedOres);
i.Set(s, "LastOwner", LastOwner);
i.Set(s, "Id", Id); i.Set(s, "RegionId", RegionId); i.Set(s, "Kind", (int)Kind); Data.PutVector(i,s, "Entry",Entry); Data.PutVector(i,s, "Direction",Direction); Data.PutVector(i,s, "Up",Up);
i.Set(s, "Depth", Depth); i.Set(s, "Radius", Radius); i.Set(s, "Progress", Progress); i.Set(s, "HoleIndex", HoleIndex); i.Set(s, "EntityId", EntityId); i.Set(s, "Priority", Priority); i.Set(s, "TargetOre", TargetOre); i.Set(s, "Owner", Owner); i.Set(s, "Outcome", Outcome);
}
public static Job FromIni(MyIni i) => Read(i, "job");
public static Job Read(MyIni i, string s)
{
var j = new Job(); j.Id = Data.Text(i,s, "Id"); j.RegionId = Data.Text(i,s, "RegionId");
j.Scan = ScanRecord.Read(i, s);
j.ConfirmedOres = Data.Text(i, s, "ConfirmedOres");
j.LastOwner = Data.Text(i,s, "LastOwner");
int kind = (int)Data.ReadLong(i, s, "Kind"); if (kind < 0 || kind > 2) throw Data.Invalid(L.CommonJobKindInvalid); j.Kind = (JobKind)kind;
j.Entry = Data.ReadVector(i, s, "Entry"); j.Direction = Data.ReadVector(i, s, "Direction", Data.Forward); j.Up = Data.ReadVector(i, s, "Up", Data.Up);
if (Data.LengthSquared(j.Direction) < 0.1 || Data.LengthSquared(j.Up) < 0.1) throw Data.Invalid(L.CommonJobBasisZero);
j.Direction = Data.Unit(j.Direction, Data.Forward); j.Up = Data.Perpendicular(j.Direction, j.Up);
j.Depth = Data.ReadNumber(i, s, "Depth", 15); j.Radius = Data.ReadNumber(i, s, "Radius", 5); j.Progress = Data.ReadNumber(i, s, "Progress");
if (j.Id.Length == 0 || j.Depth <= 0 || j.Depth > 10000 || j.Radius <= 0 || j.Progress < 0 || j.Progress > j.Depth + 1) throw Data.Invalid(L.CommonJobBoundsInvalid);
j.HoleIndex = (int)Data.ReadLong(i, s, "HoleIndex"); j.Priority = (int)Data.ReadLong(i, s, "Priority"); j.EntityId = Data.ReadLong(i, s, "EntityId"); j.TargetOre = Data.Text(i,s, "TargetOre"); j.Owner = Data.Text(i,s, "Owner"); j.Outcome = Data.Text(i,s, "Outcome"); return j;
}
}
public class Telemetry
{
public string Id = "", JobId = "", Reason = "", OreSummary = "", NextAfter = "";
public Vector3D RouteTarget;
public bool HasRoute;
public FlightState State;
public Vector3D Position, Velocity, Forward = Data.Forward, Up = Data.Up;
public double Battery, Hydrogen = 1, Cargo, OreAmount, StoredMWh, CapacityMWh, NetMW, EnduranceSeconds, Depth, TargetDepth, DistanceHome, ThrustMargin, Gravity, Radius = 5, ReceivedAt;
public double BrakeResponse = .625;
public bool Connected;
public long ConnectorId, PeerConnectorId, Address, CancelledThrough = -1, Epoch;
public int WaitingSlot = -1, ServiceFlags = -1;
public MyIni ToIni()
{
var i = new MyIni(); var s = "telemetry";
i.Set(s, "NextAfter", NextAfter); Data.PutVector(i,s, "RouteTarget",RouteTarget); i.Set(s, "HasRoute", HasRoute);
i.Set(s, "CancelledThrough", CancelledThrough);
i.Set(s, "WaitingSlot", WaitingSlot);
i.Set(s, "ServiceFlags", ServiceFlags);
i.Set(s, "BrakeResponse", BrakeResponse);
i.Set(s, "Id", Id); i.Set(s, "State", (int)State); Data.PutVector(i,s, "Position",Position); Data.PutVector(i,s, "Velocity",Velocity); Data.PutVector(i,s, "Forward",Forward); Data.PutVector(i,s, "Up",Up);
i.Set(s, "Battery", Battery); i.Set(s, "Hydrogen", Hydrogen); i.Set(s, "Cargo", Cargo); i.Set(s, "OreAmount", OreAmount); i.Set(s, "StoredMWh", StoredMWh); i.Set(s, "CapacityMWh", CapacityMWh); i.Set(s, "NetMW", NetMW); i.Set(s, "EnduranceSeconds", EnduranceSeconds);
i.Set(s, "Depth", Depth); i.Set(s, "TargetDepth", TargetDepth); i.Set(s, "DistanceHome", DistanceHome); i.Set(s, "ThrustMargin", ThrustMargin); i.Set(s, "Gravity", Gravity); i.Set(s, "Radius", Radius); i.Set(s, "JobId", JobId); i.Set(s, "Reason", Reason); i.Set(s, "OreSummary", OreSummary); i.Set(s, "Connected", Connected); i.Set(s, "ConnectorId", ConnectorId); i.Set(s, "PeerConnectorId", PeerConnectorId); return i;
}
public static Telemetry FromIni(MyIni i)
{
var s = "telemetry"; var t = new Telemetry(); t.Id = Data.Text(i,s, "Id"); int state = (int)Data.ReadLong(i, s, "State"); if (state < 0 || state > (int)FlightState.Fault) throw Data.Invalid(L.CommonFlightStateInvalid); t.State = (FlightState)state;
t.NextAfter = Data.Text(i,s, "NextAfter"); t.RouteTarget = Data.ReadVector(i, s, "RouteTarget", t.Position); t.HasRoute = Data.Flag(i,s, "HasRoute");
t.CancelledThrough = Data.ReadIndex(i, s, "CancelledThrough", 1000000000);
t.WaitingSlot = (int)Data.ReadIndex(i, s, "WaitingSlot", 1000000);
t.ServiceFlags = (int)Data.ReadIndex(i, s, "ServiceFlags", 3);
t.BrakeResponse=Data.ReadNumber(i,s,"BrakeResponse",.625);
if(t.BrakeResponse<.625 || t.BrakeResponse>5)throw Data.Invalid("telemetry.BrakeResponse");
t.Position = Data.ReadVector(i, s, "Position"); t.Velocity = Data.ReadVector(i, s, "Velocity"); t.Forward = Data.ReadVector(i, s, "Forward", Data.Forward); t.Up = Data.ReadVector(i, s, "Up", Data.Up);
t.Battery = Data.ReadNumber(i, s, "Battery"); t.Hydrogen = Data.ReadNumber(i, s, "Hydrogen", 1); t.Cargo = Data.ReadNumber(i, s, "Cargo"); t.OreAmount = Data.ReadNumber(i, s, "OreAmount"); t.StoredMWh = Data.ReadNumber(i, s, "StoredMWh"); t.CapacityMWh = Data.ReadNumber(i, s, "CapacityMWh"); t.NetMW = Data.ReadNumber(i, s, "NetMW"); t.EnduranceSeconds = Data.ReadNumber(i, s, "EnduranceSeconds");
t.Depth = Data.ReadNumber(i, s, "Depth"); t.TargetDepth = Data.ReadNumber(i, s, "TargetDepth"); t.DistanceHome = Data.ReadNumber(i, s, "DistanceHome"); t.ThrustMargin = Data.ReadNumber(i, s, "ThrustMargin"); t.Gravity = Data.ReadNumber(i, s, "Gravity"); t.Radius = Data.ReadNumber(i, s, "Radius", 5); t.JobId = Data.Text(i,s, "JobId"); t.Reason = Data.Text(i,s, "Reason"); t.OreSummary = Data.Text(i,s, "OreSummary"); t.Connected = Data.Flag(i,s, "Connected"); t.ConnectorId = Data.ReadLong(i, s, "ConnectorId"); t.PeerConnectorId = Data.ReadLong(i, s, "PeerConnectorId"); return t;
}
}
public class DockFrame
{
public string BaseId = "", Token = "", Owner = "", Problem = "";
public long ConnectorId, GridId, Source;
public Vector3D Position, Center, Velocity, AngularVelocity, Acceleration, WaitingLocal;
public Vector3D Forward = Data.Forward, Up = Data.Up;
public bool Connected;
public double ReceivedAt, ApproachDistance, LaneClearance = 60;
public Vector3D PointVelocity(Vector3D point) => Velocity + Data.Cross(AngularVelocity, point - Center);
public MatrixD Predicted(double now)
{
var age = Data.Clamp(now - ReceivedAt, 0, 0.5); var angle = Data.Length(AngularVelocity) * age;
MatrixD rotation = angle > 1e-9 ? MatrixD.CreateFromAxisAngle(Data.Unit(AngularVelocity, Data.Up), angle) : MatrixD.Identity;
var center = Center + Velocity * age;
return Data.Frame(center + Data.TransformNormal(Position - Center, rotation), Data.TransformNormal(Forward, rotation), Data.TransformNormal(Up, rotation));
}
public Vector3D GetPoint(double distance, double now) { var f = Predicted(now); return f.Translation + f.Forward * distance; }
public bool IsWindow(Settings cfg, double now)
=> now - ReceivedAt <= cfg.Number("Dock", "MaxTelemetryAge", 0.5, 0.1, 2) && Data.Length(PointVelocity(Position)) <= cfg.Number("Dock", "MaxBaseSpeed", 5, 0.1, 50) && Data.Length(AngularVelocity) <= cfg.Number("Dock", "MaxBaseAngularDeg", 0.2, 0.01, 5) * Math.PI / 180 && Data.Length(Acceleration) <= cfg.Number("Dock", "MaxBaseAcceleration", 0.1, 0.01, 5);
public MyIni ToIni()
{
var i = new MyIni(); var s = "dock"; i.Set(s,"Problem",Problem); i.Set(s, "BaseId", BaseId); i.Set(s, "ConnectorId", ConnectorId); i.Set(s, "GridId", GridId); Data.PutVector(i,s, "Position",Position); Data.PutVector(i,s, "Forward",Forward); Data.PutVector(i,s, "Up",Up); Data.PutVector(i,s, "Center",Center); Data.PutVector(i,s, "Velocity",Velocity); Data.PutVector(i,s, "AngularVelocity",AngularVelocity); Data.PutVector(i,s, "Acceleration",Acceleration); Data.PutVector(i,s, "WaitingLocal",WaitingLocal); i.Set(s, "ApproachDistance", ApproachDistance); i.Set(s, "LaneClearance", LaneClearance); i.Set(s, "Token", Token); i.Set(s, "Owner", Owner); i.Set(s, "Connected", Connected); return i;
}
public static DockFrame FromIni(MyIni i)
{
var s = "dock"; var d = new DockFrame(); d.Problem=Data.Text(i,s,"Problem"); d.BaseId = Data.Text(i,s, "BaseId"); d.ConnectorId = Data.ReadLong(i, s, "ConnectorId"); d.GridId = Data.ReadLong(i, s, "GridId"); d.Position = Data.ReadVector(i, s, "Position"); d.Center = Data.ReadVector(i, s, "Center", d.Position); d.Forward = Data.Unit(Data.ReadVector(i, s, "Forward", Data.Forward), Data.Forward); d.Up = Data.Perpendicular(d.Forward, Data.ReadVector(i, s, "Up", Data.Up)); d.Velocity = Data.ReadVector(i, s, "Velocity"); d.AngularVelocity = Data.ReadVector(i, s, "AngularVelocity"); d.Acceleration = Data.ReadVector(i, s, "Acceleration"); d.WaitingLocal = Data.ReadVector(i, s, "WaitingLocal"); d.ApproachDistance = Data.Clamp(Data.ReadNumber(i,s,"ApproachDistance"),0,10000); d.LaneClearance = Data.Clamp(Data.ReadNumber(i, s, "LaneClearance", 60), 20, 10000); d.Token = Data.Text(i,s, "Token"); d.Owner = Data.Text(i,s, "Owner"); d.Connected = Data.Flag(i,s, "Connected"); return d;
}
}
// Map evidence is optional in old messages/saves. A ray proves only its actual
// segment; an asteroid's world AABB is an estimate, not a mined surface area.
public class ScanRecord
{
public Vector3D Start, End, Center, Size;
public long Asteroid;
public bool Sized => Data.Finite(Center) && Data.Finite(Size) && Data.LengthSquared(Size)<1e14 && Size.X > 0 && Size.Y > 0 && Size.Z > 0;
public static ScanRecord Capture(Vector3D start, Vector3D end, MyDetectedEntityInfo hit)
{
if (!hit.IsEmpty()) { if (!hit.HitPosition.HasValue) return null; end = hit.HitPosition.Value; }
if (!Data.Finite(start) || !Data.Finite(end)) return null;
var scan = new ScanRecord { Start = start, End = end, Center = end };
if (hit.Type == MyDetectedEntityType.Asteroid && hit.EntityId != 0)
{
scan.Asteroid = hit.EntityId; scan.Center = hit.BoundingBox.Center; scan.Size = hit.BoundingBox.Size;
if (!scan.Sized) { scan.Size = Data.Zero; scan.Center = end; }
}
return scan;
}
public void Write(MyIni i, string s)
{
Data.PutVector(i,s,"ScanStart",Start); Data.PutVector(i,s,"ScanEnd",End);
i.Set(s,"RockId",Asteroid); Data.PutVector(i,s,"RockCenter",Center); Data.PutVector(i,s,"RockSize",Size);
}
public static ScanRecord Read(MyIni i, string s)
{
if (!i.ContainsKey(s,"ScanStart")) return null;
try
{
var scan = new ScanRecord { Start=Data.ReadVector(i,s,"ScanStart"), End=Data.ReadVector(i,s,"ScanEnd"),
Asteroid=Data.ReadLong(i,s,"RockId"), Center=Data.ReadVector(i,s,"RockCenter"), Size=Data.ReadVector(i,s,"RockSize") };
if (!scan.Sized) { scan.Size=Data.Zero; scan.Center=scan.End; } return scan;
}
catch (ArgumentException) { return null; }
}
}
public class JobReport
{
public ScanRecord Scan;
public string JobId = "", Outcome = "", Ore = "", Message = "";
public double Progress;
public Vector3D Position, Direction = Data.Forward, Up = Data.Up;
public long EntityId;
public MyIni ToIni() { var i = new MyIni(); var s = "result"; if(Scan!=null)Scan.Write(i,s); i.Set(s, "JobId", JobId); i.Set(s, "Outcome", Outcome); i.Set(s, "Ore", Ore); i.Set(s, "Message", Message); i.Set(s, "Progress", Progress); Data.PutVector(i,s, "Position",Position); Data.PutVector(i,s, "Direction",Direction); Data.PutVector(i,s, "Up",Up); i.Set(s, "EntityId", EntityId); return i; }
public static JobReport FromIni(MyIni i) { var s = "result"; return new JobReport { Scan=ScanRecord.Read(i,s), JobId = Data.Text(i,s, "JobId"), Outcome = Data.Text(i,s, "Outcome"), Ore = Data.Text(i,s, "Ore"), Message = Data.Text(i,s, "Message"), Progress = Data.ReadNumber(i, s, "Progress"), Position = Data.ReadVector(i, s, "Position"), Direction = Data.ReadVector(i, s, "Direction", Data.Forward), Up = Data.ReadVector(i, s, "Up", Data.Up), EntityId = Data.ReadLong(i, s, "EntityId") }; }
}
}
}