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

AutoMiningScript

【SpaceEngineer】全自动挖矿脚本

公开
关注 0 Fork 0 Star 0
UTF-8
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Remoting;
using Sandbox.ModAPI.Ingame;
using VRage.Game.ModAPI.Ingame;
using VRageMath;
using Xunit;
using P=AutoMiningScript.Program;

namespace AutoMiningScript.Tests
{
    public class DockPowerCameraTests
    {
        const BindingFlags Private=BindingFlags.Instance|BindingFlags.NonPublic;
        static T Read<T>(object target,string name) => (T)target.GetType().GetField(name,Private).GetValue(target);
        static void Write(object target,string name,object value) => target.GetType().GetField(name,Private).SetValue(target,value);
        static void Call(object target,string name,params object[] args) => target.GetType().GetMethod(name,Private).Invoke(target,args);
        static readonly Vector3D[] Directions={Vector3D.Forward,Vector3D.Backward,Vector3D.Right,Vector3D.Left,Vector3D.Up,Vector3D.Down};

        [Theory]
        [InlineData(P.FlightState.Servicing)]
        [InlineData(P.FlightState.Docked)]
        [InlineData(P.FlightState.Ready)]
        [InlineData(P.FlightState.Fault)]
        [InlineData(P.FlightState.Manual)]
        public void EveryConnectedStateSwitchesThrustersOff(P.FlightState state)
        {
            var r=new MinerStateTests.MinerRig();r.PrepareDeparture("valid-lease");r.State(state);
            Write(r.Miner,"running",false);
            foreach(var t in r.Hardware.Thrusters)t.ThrustOverridePercentage=.6f;
            r.Miner.Tick(.1);
            Assert.All(r.Hardware.Thrusters,t=>{Assert.False(t.Enabled);Assert.Equal(0,t.ThrustOverridePercentage);});
            r.Hardware.Thrusters[0].Enabled=true;r.Miner.Tick(.1);
            Assert.False(r.Hardware.Thrusters[0].Enabled);
        }

        [Fact]
        public void AllOwnGridThrustersParkEvenOutsideControlGroupWithoutTouchingMotherShip()
        {
            var r=new MinerStateTests.MinerRig();
            r.Program.Config=new P.Settings("[Hardware]\nGroup=managed","miner","test");
            var extra=new Stub<IMyThrust>().Set("CubeGrid",r.Program.Me.CubeGrid).Set("Enabled",true).Set("ThrustOverridePercentage",.8f);
            var mother=new Stub<IMyThrust>().Set("CubeGrid",new Stub<IMyCubeGrid>().Value).Set("Enabled",true).Set("ThrustOverridePercentage",.9f);
            var managed=new List<IMyTerminalBlock>{r.Controller.Value,r.Connector.Value};
            foreach(var t in r.Hardware.Thrusters)
            {var definition=t.BlockDefinition;definition.SubtypeName="MockThruster";((Stub<IMyThrust>)RemotingServices.GetRealProxy(t)).Set("BlockDefinition",definition);}
            managed.AddRange(r.Hardware.Thrusters);managed.AddRange(r.Hardware.Gyros);managed.AddRange(r.Hardware.Drills);
            managed.AddRange(r.Hardware.Cameras);managed.AddRange(r.Hardware.Batteries);
            var all=new List<IMyTerminalBlock>(managed){extra.Value,mother.Value};
            Func<object[],IEnumerable<IMyTerminalBlock>,object> fill=(args,blocks)=>{
                var target=(IList)args[0];var filter=args.Length>1?args[1] as Delegate:null;
                foreach(var b in blocks)if(filter==null || (bool)filter.DynamicInvoke(b))target.Add(b);return null;
            };
            var group=new Stub<IMyBlockGroup>().Method("GetBlocks",a=>fill(a,managed));
            TestRig.SetBase(r.Program,"GridTerminalSystem",new Stub<IMyGridTerminalSystem>()
                .Method("GetBlocksOfType",a=>fill(a,all)).Method("GetBlockGroupWithName",a=>group.Value).Value);
            r.Connector.Set("Status",MyShipConnectorStatus.Connected);
            string error;Assert.True(r.Hardware.Scan(out error),error);
            Assert.DoesNotContain(r.Hardware.Thrusters,t=>ReferenceEquals(t,extra.Value));Assert.False(extra.Value.Enabled);Assert.Equal(0,extra.Value.ThrustOverridePercentage);
            Assert.True(mother.Value.Enabled);Assert.Equal(.9f,mother.Value.ThrustOverridePercentage);
            r.Hardware.PrepareFlight();Assert.True(extra.Value.Enabled);Assert.All(r.Hardware.Thrusters,t=>Assert.True(t.Enabled));
            Assert.Equal(.9f,mother.Value.ThrustOverridePercentage);
        }

        [Fact]
        public void DeparturePowersThrustersBeforeCheckingCapabilityAndUnlocking()
        {
            var r=new MinerStateTests.MinerRig();r.PrepareDeparture("valid-lease");int disconnects=0;
            r.Connector.Method("Disconnect",a=>{
                Assert.All(r.Hardware.Thrusters,t=>{Assert.True(t.Enabled);Assert.Equal(0,t.ThrustOverridePercentage);});
                disconnects++;r.Connector.Set("Status",MyShipConnectorStatus.Unconnected);return null;
            });
            r.Hardware.ParkThrusters(true);Call(r.Miner,"DockedTick",.1);
            Assert.Equal(1,disconnects);Assert.Equal(P.FlightState.Departing,r.StateNow);
        }

        [Fact]
        public void FailedDepartureCapabilityLeavesThrustersOffAndConnectorLocked()
        {
            var r=new MinerStateTests.MinerRig();r.PrepareDeparture("valid-lease");
            foreach(var t in r.Hardware.Thrusters)((Stub<IMyThrust>)RemotingServices.GetRealProxy(t)).Set("MaxEffectiveThrust",0f);
            Call(r.Miner,"DockedTick",.1);
            Assert.All(r.Hardware.Thrusters,t=>Assert.False(t.Enabled));Assert.Equal(MyShipConnectorStatus.Connected,r.Connector.Value.Status);
        }

        [Theory]
        [InlineData(false)]
        [InlineData(true)]
        public void AnUnexpectedDisconnectRestoresParkedThrustersIncludingManualControl(bool pilot)
        {
            var r=new MinerStateTests.MinerRig();r.PrepareDeparture("valid-lease");Write(r.Miner,"running",false);
            r.Miner.Tick(.1);Assert.All(r.Hardware.Thrusters,t=>Assert.False(t.Enabled));
            r.Connector.Set("Status",MyShipConnectorStatus.Unconnected);r.Controller.Set("IsUnderControl",pilot);r.Miner.Tick(.1);
            Assert.All(r.Hardware.Thrusters,t=>Assert.True(t.Enabled));
            if(pilot)Assert.Equal(P.FlightState.Manual,r.StateNow);
            else Assert.Contains(r.StateNow,new[]{P.FlightState.Returning,P.FlightState.Holding});
        }

        [Fact]
        public void NormalManualReleasePreservesDisabledThrusters()
        {
            var r=new MinerStateTests.MinerRig();r.Hardware.Thrusters[0].Enabled=false;r.Miner.Command("release");r.Miner.Tick(.1);
            Assert.False(r.Hardware.Thrusters[0].Enabled);Assert.All(r.Hardware.Thrusters,t=>Assert.Equal(0,t.ThrustOverridePercentage));
        }

        [Fact]
        public void AirborneInitializationRestoresThrustersDisabledByAnEarlierDockedProgram()
        {
            var r=new MinerStateTests.MinerRig();r.State(P.FlightState.Paused);
            foreach(var t in r.Hardware.Thrusters)t.Enabled=false;
            r.Miner.Tick(.1);Assert.All(r.Hardware.Thrusters,t=>Assert.True(t.Enabled));
        }

        [Fact]
        public void MagneticCaptureParksOnTheSameTick()
        {
            var r=new MinerStateTests.MinerRig();r.State(P.FlightState.Returning);r.Connector.Set("Status",MyShipConnectorStatus.Connectable);
            r.Connector.Method("Connect",a=>{r.Connector.Set("Status",MyShipConnectorStatus.Connected);return null;});
            Call(r.Miner,"TryConnect");Assert.All(r.Hardware.Thrusters,t=>Assert.False(t.Enabled));
        }

        sealed class CameraRig
        {
            public readonly MinerStateTests.MinerRig R=new MinerStateTests.MinerRig();
            public readonly P.FlightController Flight;
            public readonly P.Job Job;
            public MatrixD Orientation=MatrixD.Identity;
            public readonly List<int> Rays=new List<int>();
            public CameraRig(Vector3D direction)
            {
                R.Task();Job=Read<P.Job>(R.Miner,"job");Job.Kind=P.JobKind.Survey;Job.Depth=100;Job.Direction=direction;Job.Up=P.Data.Perpendicular(direction,Vector3D.Up);
                R.State(P.FlightState.Survey);Flight=Read<P.FlightController>(R.Miner,"flight");R.Hardware.Cameras.Clear();
                R.Controller.Method("get_WorldMatrix",a=>Orientation);
                foreach(var t in R.Hardware.Thrusters)
                {
                    var proxy=(Stub<IMyThrust>)RemotingServices.GetRealProxy(t);var local=t.WorldMatrix;proxy.Values.Remove("WorldMatrix");proxy.Method("get_WorldMatrix",a=>local*Orientation);
                }
                var gyro=(Stub<IMyGyro>)RemotingServices.GetRealProxy(R.Hardware.Gyros[0]);gyro.Values.Remove("WorldMatrix");gyro.Method("get_WorldMatrix",a=>Orientation);
            }
            public Stub<IMyCameraBlock> Add(int id,Vector3D direction,Func<bool> charged=null,Vector3D offset=default(Vector3D))
            {
                var local=MatrixD.CreateWorld(Vector3D.Zero,direction,P.Data.Perpendicular(direction,Vector3D.Up));
                var camera=new Stub<IMyCameraBlock>().Set("EntityId",(long)id).Set("CubeGrid",R.Program.Me.CubeGrid)
                    .Set("Enabled",true).Set("IsFunctional",true).Set("IsWorking",true).Set("EnableRaycast",true)
                    .Set("RaycastConeLimit",45f).Set("RaycastDistanceLimit",-1d).Set("AvailableScanRange",1000d)
                    .Method("get_WorldMatrix",a=>local*Orientation).Method("GetPosition",a=>R.Position+Vector3D.TransformNormal(offset,Orientation))
                    .Method("CanScan",a=>{
                        var ray=Vector3D.TransformNormal((Vector3D)a[0]-R.Position-Vector3D.TransformNormal(offset,Orientation),MatrixD.Transpose(local*Orientation));
                        return (charged==null || charged()) && -ray.Z>0 && Math.Abs(Math.Atan2(ray.X,-ray.Z))<=Math.PI/4 &&
                            Math.Abs(Math.Atan2(ray.Y,Math.Sqrt(ray.X*ray.X+ray.Z*ray.Z)))<=Math.PI/4;
                    }).Method("Raycast",a=>{Rays.Add(id);return default(MyDetectedEntityInfo);});
                R.Hardware.Cameras.Add(camera.Value);return camera;
            }
            public void Tick()
            {R.Program.Now+=.1;Call(R.Miner,"Survey",.1);}
        }

        [Theory]
        [InlineData(0)] [InlineData(1)] [InlineData(2)] [InlineData(3)] [InlineData(4)] [InlineData(5)]
        public void SixDirectionSurveyUsesTheInstalledCameraWithoutRotating(int axis)
        {
            var r=new CameraRig(Directions[axis]);for(int n=0;n<6;n++)r.Add(n,Directions[n]);r.Tick();
            Assert.Equal(new[]{axis},r.Rays);Assert.Equal(MatrixD.Identity,Read<MatrixD>(r.Flight,"previousDesired"));
            Assert.Equal("SurveyEmpty",P.JobReport.FromIni(Assert.Single(r.R.Sent,p=>p.Kind=="RESULT").Body).Outcome);
        }

        [Fact]
        public void AChargingSideCameraWaitsWithoutTurningAndAnotherChargedCameraCanTakeItsRay()
        {
            bool charged=false;var r=new CameraRig(Vector3D.Right);r.Add(1,Vector3D.Right,()=>charged);r.Add(2,Vector3D.Forward);
            for(int n=0;n<10;n++)r.Tick();
            Assert.Empty(r.Rays);Assert.DoesNotContain(r.R.Sent,p=>p.Kind=="RESULT");Assert.Equal(MatrixD.Identity,Read<MatrixD>(r.Flight,"previousDesired"));
            r.Add(3,Vector3D.Right);for(int n=0;n<3;n++)r.Tick();Assert.Equal(new[]{3},r.Rays);
        }

        [Fact]
        public void AWorkingSideMountedCameraIsRotatedToFillAMissingDirection()
        {
            var r=new CameraRig(Vector3D.Forward);r.Add(1,Vector3D.Right);r.Tick();
            Assert.Empty(r.Rays);var desired=Read<MatrixD>(r.Flight,"previousDesired");Assert.NotEqual(MatrixD.Identity,desired);
            r.Orientation=desired.GetOrientation();r.Tick();
            Assert.Equal(new[]{1},r.Rays);Assert.Equal("SurveyEmpty",P.JobReport.FromIni(Assert.Single(r.R.Sent,p=>p.Kind=="RESULT").Body).Outcome);
        }

        [Fact]
        public void AForeignCameraDoesNotSuppressRotationOfAnActualMinerCamera()
        {
            var r=new CameraRig(Vector3D.Forward);r.Add(1,Vector3D.Forward).Set("CubeGrid",new Stub<IMyCubeGrid>().Value);r.Add(2,Vector3D.Left);
            r.Tick();Assert.Empty(r.Rays);r.Orientation=Read<MatrixD>(r.Flight,"previousDesired").GetOrientation();r.Tick();Assert.Equal(new[]{2},r.Rays);
        }

        [Theory]
        [InlineData(0)] [InlineData(1)] [InlineData(2)] [InlineData(3)] [InlineData(4)] [InlineData(5)]
        public void SavedMinerSevenCameraLayoutCoversAllAxesWithoutRotating(int axis)
        {
            // Actual 28 lens positions and optical axes relative to the remote control,
            // from the 2026-09-14 14:41:28 save (grid 87777267991379386).
            double[,] layout={
                {1,1,1,0,1,0},{0,1,1.5,0,0,1},{0,1,0,0,0,-1},{2,0,1,1,0,0},
                {0,-.5,-.5,0,0,-1},{0,-1.5,.5,0,0,-1},{-2,0,1,-1,0,0},{1.5,0,2,0,0,1},
                {-1.5,0,2,0,0,1},{-1,1,1,0,1,0},{1,1,1.5,0,1,0},{-1,1,1.5,0,1,0},
                {.5,-1.5,1.5,0,-1,0},{-.5,-1.5,1.5,0,-1,0},{.5,-1.5,1,0,-1,0},{-.5,-1.5,1,0,-1,0},
                {0,-1.5,1.5,0,0,1},{1.5,.5,2,0,0,1},{-1.5,.5,2,0,0,1},{0,0,-.5,0,0,-1},
                {-.5,1,.5,0,0,-1},{.5,1,.5,0,0,-1},{-.5,1,1.5,0,0,1},{.5,1,1.5,0,0,1},
                {-2,0,.5,-1,0,0},{2,0,.5,1,0,0},{-2,0,1.5,-1,0,0},{2,0,1.5,1,0,0}
            };
            var r=new CameraRig(Directions[axis]);
            for(int n=0;n<layout.GetLength(0);n++)r.Add(n,new Vector3D(layout[n,3],layout[n,4],layout[n,5]),null,new Vector3D(layout[n,0],layout[n,1],layout[n,2]));
            r.Tick();Assert.Single(r.Rays);Assert.Equal(MatrixD.Identity,Read<MatrixD>(r.Flight,"previousDesired"));
            var hit=r.R.Hardware.Cameras[r.Rays[0]];Assert.True(Vector3D.Dot(hit.WorldMatrix.Forward,Directions[axis])>.999);
        }
    }
}