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 GetTelemetry() => Data.CreateList(); public virtual List GetJobs() => Data.CreateList(); #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 CreateList() => new List(); public static Dictionary CreateDictionary() => new Dictionary(); 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") }; } } } }