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 GetTelemetry() => new List(); public virtual List GetJobs() => new List(); #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") }; } } } }