XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

AutoMiningScript

【SpaceEngineer】全自动挖矿脚本

公开
关注 0 Fork 0 Star 0
UTF-8
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") }; }
        }
    }
}