using System;
using System.Collections.Generic;
using Sandbox.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() => new List<Telemetry>();
public virtual List<Job> GetJobs() => new List<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
{
// 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 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 double Clamp(double x, double min, double max) => Math.Max(min, Math.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 || !double.TryParse(a[0], System.Globalization.NumberStyles.Float, Culture, out x) || !double.TryParse(a[1], System.Globalization.NumberStyles.Float, Culture, out y) || !double.TryParse(a[2], System.Globalization.NumberStyles.Float, Culture, out z) || !Finite(x) || !Finite(y) || !Finite(z)) return false;
v = new Vector3D(x, y, z); return true;
}
public static Vector3D ReadVector(MyIni ini, string section, string name, Vector3D fallback)
{
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)
{
if (!ini.ContainsKey(section, name)) return fallback;
double n;
if (!double.TryParse(Data.Text(ini,section, name), System.Globalization.NumberStyles.Float, Culture, out n) || !Finite(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 (!long.TryParse(Data.Text(ini,section, name), System.Globalization.NumberStyles.Integer, Culture, out n)) throw Data.Invalid(L.F(L.CommonIntegerInvalid, section, name));
return n;
}
public static Vector3D Unit(Vector3D v, Vector3D fallback)
{
var length = v.Length(); 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 (projected.LengthSquared() < 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 = d1.LengthSquared(), e = d2.LengthSquared(), f = Data.Dot(d2, r), s, t;
if (a < 1e-9 && e < 1e-9) return r.Length();
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 ((p1 + d1 * s) - (p2 + d2 * t)).Length();
}
public static double InterceptTime(Vector3D relative, Vector3D targetVelocity, double speed)
{
if (speed <= 0 || !Finite(speed)) return -1;
double a = targetVelocity.LengthSquared() - speed * speed, b = 2 * Data.Dot(relative, targetVelocity), c = relative.LengthSquared();
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 Math.Min(x, y); return x > 0 ? x : y > 0 ? y : -1;
}
}
public class Job
{
public ScanRecord Scan;
public string Id = "", RegionId = "", TargetOre = "", 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, "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.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", Data.Zero); j.Direction = Data.ReadVector(i, s, "Direction", Data.Forward); j.Up = Data.ReadVector(i, s, "Up", Data.Up);
if (j.Direction.LengthSquared() < 0.1 || j.Up.LengthSquared() < 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", 0);
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 bool Connected;
public long ConnectorId, PeerConnectorId, Address;
public long CancelledThrough = -1;
public long Epoch;
public int WaitingSlot = -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, "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 = i.ContainsKey(s, "CancelledThrough") ? Data.ReadLong(i, s, "CancelledThrough") : -1;
if (t.CancelledThrough < -1 || t.CancelledThrough > 1000000000) throw Data.Invalid("telemetry.CancelledThrough");
var waitingSlot = Data.ReadLong(i, s, "WaitingSlot", -1);
if (waitingSlot < -1 || waitingSlot > 1000000) throw Data.Invalid("telemetry.WaitingSlot");
t.WaitingSlot = (int)waitingSlot;
t.Position = Data.ReadVector(i, s, "Position", Data.Zero); t.Velocity = Data.ReadVector(i, s, "Velocity", Data.Zero); 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", 0); t.Hydrogen = Data.ReadNumber(i, s, "Hydrogen", 1); t.Cargo = Data.ReadNumber(i, s, "Cargo", 0); t.OreAmount = Data.ReadNumber(i, s, "OreAmount", 0); t.StoredMWh = Data.ReadNumber(i, s, "StoredMWh", 0); t.CapacityMWh = Data.ReadNumber(i, s, "CapacityMWh", 0); t.NetMW = Data.ReadNumber(i, s, "NetMW", 0); t.EnduranceSeconds = Data.ReadNumber(i, s, "EnduranceSeconds", 0);
t.Depth = Data.ReadNumber(i, s, "Depth", 0); t.TargetDepth = Data.ReadNumber(i, s, "TargetDepth", 0); t.DistanceHome = Data.ReadNumber(i, s, "DistanceHome", 0); t.ThrustMargin = Data.ReadNumber(i, s, "ThrustMargin", 0); t.Gravity = Data.ReadNumber(i, s, "Gravity", 0); 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, 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 = AngularVelocity.Length() * 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 + Vector3D.TransformNormal(Position - Center, rotation), Vector3D.TransformNormal(Forward, rotation), Vector3D.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) && PointVelocity(Position).Length() <= cfg.Number("Dock", "MaxBaseSpeed", 5, 0.1, 50) && AngularVelocity.Length() <= cfg.Number("Dock", "MaxBaseAngularDeg", 0.2, 0.01, 5) * Math.PI / 180 && Acceleration.Length() <= 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, "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", Data.Zero); 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", Data.Zero); d.AngularVelocity = Data.ReadVector(i, s, "AngularVelocity", Data.Zero); d.Acceleration = Data.ReadVector(i, s, "Acceleration", Data.Zero); d.WaitingLocal = Data.ReadVector(i, s, "WaitingLocal", Data.Zero); d.LaneClearance = Data.Clamp(Data.ReadNumber(i, s, "LaneClearance", 60), 20, 300); 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) && Size.LengthSquared()<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",Data.Zero), End=Data.ReadVector(i,s,"ScanEnd",Data.Zero),
Asteroid=Data.ReadLong(i,s,"RockId"), Center=Data.ReadVector(i,s,"RockCenter",Data.Zero), Size=Data.ReadVector(i,s,"RockSize",Data.Zero) };
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", 0), Position = Data.ReadVector(i, s, "Position", Data.Zero), 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 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() => new List<Telemetry>();
public virtual List<Job> GetJobs() => new List<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
{
// 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 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 double Clamp(double x, double min, double max) => Math.Max(min, Math.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 || !double.TryParse(a[0], System.Globalization.NumberStyles.Float, Culture, out x) || !double.TryParse(a[1], System.Globalization.NumberStyles.Float, Culture, out y) || !double.TryParse(a[2], System.Globalization.NumberStyles.Float, Culture, out z) || !Finite(x) || !Finite(y) || !Finite(z)) return false;
v = new Vector3D(x, y, z); return true;
}
public static Vector3D ReadVector(MyIni ini, string section, string name, Vector3D fallback)
{
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)
{
if (!ini.ContainsKey(section, name)) return fallback;
double n;
if (!double.TryParse(Data.Text(ini,section, name), System.Globalization.NumberStyles.Float, Culture, out n) || !Finite(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 (!long.TryParse(Data.Text(ini,section, name), System.Globalization.NumberStyles.Integer, Culture, out n)) throw Data.Invalid(L.F(L.CommonIntegerInvalid, section, name));
return n;
}
public static Vector3D Unit(Vector3D v, Vector3D fallback)
{
var length = v.Length(); 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 (projected.LengthSquared() < 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 = d1.LengthSquared(), e = d2.LengthSquared(), f = Data.Dot(d2, r), s, t;
if (a < 1e-9 && e < 1e-9) return r.Length();
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 ((p1 + d1 * s) - (p2 + d2 * t)).Length();
}
public static double InterceptTime(Vector3D relative, Vector3D targetVelocity, double speed)
{
if (speed <= 0 || !Finite(speed)) return -1;
double a = targetVelocity.LengthSquared() - speed * speed, b = 2 * Data.Dot(relative, targetVelocity), c = relative.LengthSquared();
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 Math.Min(x, y); return x > 0 ? x : y > 0 ? y : -1;
}
}
public class Job
{
public ScanRecord Scan;
public string Id = "", RegionId = "", TargetOre = "", 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, "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.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", Data.Zero); j.Direction = Data.ReadVector(i, s, "Direction", Data.Forward); j.Up = Data.ReadVector(i, s, "Up", Data.Up);
if (j.Direction.LengthSquared() < 0.1 || j.Up.LengthSquared() < 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", 0);
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 bool Connected;
public long ConnectorId, PeerConnectorId, Address;
public long CancelledThrough = -1;
public long Epoch;
public int WaitingSlot = -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, "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 = i.ContainsKey(s, "CancelledThrough") ? Data.ReadLong(i, s, "CancelledThrough") : -1;
if (t.CancelledThrough < -1 || t.CancelledThrough > 1000000000) throw Data.Invalid("telemetry.CancelledThrough");
var waitingSlot = Data.ReadLong(i, s, "WaitingSlot", -1);
if (waitingSlot < -1 || waitingSlot > 1000000) throw Data.Invalid("telemetry.WaitingSlot");
t.WaitingSlot = (int)waitingSlot;
t.Position = Data.ReadVector(i, s, "Position", Data.Zero); t.Velocity = Data.ReadVector(i, s, "Velocity", Data.Zero); 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", 0); t.Hydrogen = Data.ReadNumber(i, s, "Hydrogen", 1); t.Cargo = Data.ReadNumber(i, s, "Cargo", 0); t.OreAmount = Data.ReadNumber(i, s, "OreAmount", 0); t.StoredMWh = Data.ReadNumber(i, s, "StoredMWh", 0); t.CapacityMWh = Data.ReadNumber(i, s, "CapacityMWh", 0); t.NetMW = Data.ReadNumber(i, s, "NetMW", 0); t.EnduranceSeconds = Data.ReadNumber(i, s, "EnduranceSeconds", 0);
t.Depth = Data.ReadNumber(i, s, "Depth", 0); t.TargetDepth = Data.ReadNumber(i, s, "TargetDepth", 0); t.DistanceHome = Data.ReadNumber(i, s, "DistanceHome", 0); t.ThrustMargin = Data.ReadNumber(i, s, "ThrustMargin", 0); t.Gravity = Data.ReadNumber(i, s, "Gravity", 0); 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, 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 = AngularVelocity.Length() * 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 + Vector3D.TransformNormal(Position - Center, rotation), Vector3D.TransformNormal(Forward, rotation), Vector3D.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) && PointVelocity(Position).Length() <= cfg.Number("Dock", "MaxBaseSpeed", 5, 0.1, 50) && AngularVelocity.Length() <= cfg.Number("Dock", "MaxBaseAngularDeg", 0.2, 0.01, 5) * Math.PI / 180 && Acceleration.Length() <= 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, "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", Data.Zero); 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", Data.Zero); d.AngularVelocity = Data.ReadVector(i, s, "AngularVelocity", Data.Zero); d.Acceleration = Data.ReadVector(i, s, "Acceleration", Data.Zero); d.WaitingLocal = Data.ReadVector(i, s, "WaitingLocal", Data.Zero); d.LaneClearance = Data.Clamp(Data.ReadNumber(i, s, "LaneClearance", 60), 20, 300); 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) && Size.LengthSquared()<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",Data.Zero), End=Data.ReadVector(i,s,"ScanEnd",Data.Zero),
Asteroid=Data.ReadLong(i,s,"RockId"), Center=Data.ReadVector(i,s,"RockCenter",Data.Zero), Size=Data.ReadVector(i,s,"RockSize",Data.Zero) };
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", 0), Position = Data.ReadVector(i, s, "Position", Data.Zero), Direction = Data.ReadVector(i, s, "Direction", Data.Forward), Up = Data.ReadVector(i, s, "Up", Data.Up), EntityId = Data.ReadLong(i, s, "EntityId") }; }
}
}
}