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

AutoMiningScript

【SpaceEngineer】全自动挖矿脚本

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

namespace AutoMiningScript.Tests
{
    public class WaitingBerthLayoutTests
    {
        const string WaitingOnly = "[Dock]\nStableSeconds=30\n";
        static readonly Vector3D RowOrigin = new Vector3D(-18, 7, 21);

        static MatrixD SetPort(FleetStateTests.Rig rig, int index, Vector3D position, Vector3D forward, double roll)
        {
            forward = Vector3D.Normalize(forward);
            var up = Vector3D.TransformNormal(P.Data.Perpendicular(forward, Vector3D.Up), MatrixD.CreateFromAxisAngle(forward, roll * Math.PI / 180));
            var pose = MatrixD.CreateWorld(position, forward, up);
            rig.SetPort(index, position, forward);
            var ports = (List<Stub<IMyShipConnector>>)typeof(FleetStateTests.Rig).GetField("portStubs", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(rig);
            ports[index].Set("WorldMatrix", pose);
            return pose;
        }
        static void SetHorizontalPorts(FleetStateTests.Rig rig, int count)
        {
            for (int i = 0; i < count; i++) SetPort(rig, i, RowOrigin + Vector3D.Right * (12 * i), Vector3D.Forward, i * 90);
        }
        static void RequestPoses(FleetStateTests.Rig rig, IEnumerable<P.Telemetry> miners)
        {
            var list = miners.ToList(); rig.Sent.Clear();
            var ports = (List<Stub<IMyShipConnector>>)typeof(FleetStateTests.Rig).GetField("portStubs", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(rig);
            for (int i = 0; i < ports.Count; i++)
            {
                var occupant = new Stub<IMyShipConnector>().Set("EntityId", 9000L + i);
                ports[i].Set("Status", MyShipConnectorStatus.Connected).Set("OtherConnector", occupant.Value);
            }
            foreach (var miner in list)
            {
                miner.State = P.FlightState.Holding; miner.Connected = false;
                rig.Receive("HELLO", miner); rig.Receive("DOCK_REQUEST", miner);
            }
            rig.Tick(20);
            // Occupied physical connectors keep these as waiting poses. Base
            // motion and stability no longer delay allocation of a free berth.
            Assert.DoesNotContain(rig.Sent, p => p.Kind == "DOCK_GRANT");
        }
        static P.DockFrame Pose(FleetStateTests.Rig rig, string miner)
        {
            return P.DockFrame.FromIni(rig.Sent.Last(p => p.Kind == "DOCKPOSE" && p.Target == miner).Body);
        }
        static Vector3D WaitingPoint(FleetStateTests.Rig rig, P.DockFrame pose)
        {
            pose.ReceivedAt = rig.Program.Now;
            return Vector3D.Transform(pose.WaitingLocal, pose.Predicted(rig.Program.Now));
        }
        static void Near(Vector3D expected, Vector3D actual)
        { Assert.True(Vector3D.Distance(expected, actual) < .000001, expected + " vs " + actual); }
        static P.Telemetry AddMiner(FleetStateTests.Rig rig, string id)
        {
            var miner = new P.Telemetry { Id = id, State = P.FlightState.Holding, Position = new Vector3D(1000, 1000, -1000), Radius = 3, Battery = 1, Hydrogen = 1 };
            rig.Miners.Add(miner); return miner;
        }

        [Theory]
        [InlineData(0)]
        [InlineData(90)]
        [InlineData(180)]
        public void ConnectorRollDoesNotRotateAHorizontalWaitingRow(double roll)
        {
            var rig = new FleetStateTests.Rig(4, false, WaitingOnly);
            var ports = Enumerable.Range(0, 4).Select(i => SetPort(rig, i, RowOrigin + Vector3D.Right * (12 * i), Vector3D.Forward, roll)).ToArray();
            RequestPoses(rig, rig.Miners);
            var points = new List<Vector3D>();
            for (int i = 0; i < 4; i++)
            {
                var pose = Pose(rig, rig.Miners[i].Id); var point = WaitingPoint(rig, pose); points.Add(point);
                Assert.Equal(100L + i, pose.ConnectorId);
                Near(ports[i].Translation + ports[i].Forward * 80, point);
                Near(ports[i].Up, pose.Up); // Mating attitude still follows the real connector roll.
                Near(Vector3D.Zero, Vector3D.Cross(point - ports[i].Translation, ports[i].Forward));
            }
            for (int i = 1; i < points.Count; i++) Near(Vector3D.Right * 12, points[i] - points[i - 1]);
        }

        [Fact]
        public void MixedConnectorRollsKeepEachWaitingPointInFrontOfItsOwnHorizontalBerth()
        {
            var rig = new FleetStateTests.Rig(4, false, WaitingOnly);
            var rolls = new[] { 0d, 90d, 180d, -45d };
            var ports = Enumerable.Range(0, 4).Select(i => SetPort(rig, i, RowOrigin + Vector3D.Right * (12 * i), Vector3D.Forward, rolls[i])).ToArray();
            RequestPoses(rig, rig.Miners);
            for (int i = 0; i < 4; i++)
            {
                var pose = Pose(rig, rig.Miners[i].Id);
                Assert.Equal(100L + i, pose.ConnectorId);
                Near(RowOrigin + Vector3D.Right * (12 * i) + Vector3D.Forward * 80, WaitingPoint(rig, pose));
                Near(ports[i].Up, pose.Up);
            }
        }

        [Fact]
        public void DifferentlyFacingBerthsPlaceWaitingPointsAlongTheirActualOpeningDirections()
        {
            var rig = new FleetStateTests.Rig(3, false, WaitingOnly);
            var forwards = new[] { Vector3D.Forward, Vector3D.Right, Vector3D.Normalize(new Vector3D(-1, .2, 1)) };
            var ports = Enumerable.Range(0, 3).Select(i => SetPort(rig, i, RowOrigin + Vector3D.Right * (30 * i), forwards[i], i * 90)).ToArray();
            RequestPoses(rig, rig.Miners);
            for (int i = 0; i < 3; i++)
            {
                var pose = Pose(rig, rig.Miners[i].Id);
                Assert.Equal(100L + i, pose.ConnectorId);
                Near(ports[i].Translation + ports[i].Forward * 80, WaitingPoint(rig, pose));
                Near(ports[i].Forward, pose.Forward); Near(ports[i].Up, pose.Up);
            }
        }

        [Theory]
        [InlineData(40, 10)]
        [InlineData(180, 35)]
        [InlineData(500, 100)]
        public void AdditionalWaitingRowsUseTheSameBerthsAndConfiguredForwardSeparation(int distance, int spacing)
        {
            var rig = new FleetStateTests.Rig(2, false, WaitingOnly + "WaitingDistance=" + distance + "\nWaitingSpacing=" + spacing + "\n");
            SetHorizontalPorts(rig, 2);
            for (int i = 2; i < 6; i++) AddMiner(rig, "miner-" + i);
            RequestPoses(rig, rig.Miners);
            var points = new List<Vector3D>();
            for (int i = 0; i < 6; i++)
            {
                var telemetry = rig.Fleet.GetTelemetry().Single(t => t.Id == rig.Miners[i].Id);
                Assert.Equal(i, telemetry.WaitingSlot);
                var pose = Pose(rig, telemetry.Id); var point = WaitingPoint(rig, pose); points.Add(point);
                Assert.Equal(100L + i % 2, pose.ConnectorId);
                var firstRowDistance=Math.Max(distance,pose.ApproachDistance+spacing);
                Near(RowOrigin + Vector3D.Right * (12 * (i % 2)) + Vector3D.Forward * (firstRowDistance + i / 2 * spacing), point);
                if (i >= 2) Near(Vector3D.Forward * spacing, points[i] - points[i - 2]);
            }
        }

        [Fact]
        public void ConnectorApproachDistancesAreUniqueAndStableAcrossRotationEnumerationAndReload()
        {
            var rig=new FleetStateTests.Rig(6,false,WaitingOnly);SetHorizontalPorts(rig,6);
            RequestPoses(rig,rig.Miners);
            var initial=rig.Miners.Select(t=>Pose(rig,t.Id)).ToDictionary(p=>p.ConnectorId,p=>p.ApproachDistance);
            Assert.Equal(new[]{30d,40d,50d,60d,70d,80d},initial.Values.OrderBy(d=>d));

            var rotation=MatrixD.CreateFromAxisAngle(Vector3D.Normalize(new Vector3D(1,2,3)),.7);
            var actual=new Dictionary<long,MatrixD>();
            for(int n=0;n<6;n++)
            {
                var position=Vector3D.TransformNormal(RowOrigin+Vector3D.Right*(12*n),rotation)+new Vector3D(7,-11,17);
                actual[100+n]=SetPort(rig,n,position,Vector3D.TransformNormal(Vector3D.Forward,rotation),n*60+90);
            }
            RequestPoses(rig,rig.Miners);
            foreach(var miner in rig.Miners)
            {
                var pose=Pose(rig,miner.Id);var port=actual[pose.ConnectorId];
                Assert.Equal(initial[pose.ConnectorId],pose.ApproachDistance);
                Near(port.Forward,pose.Forward);Near(port.Up,pose.Up);
                var entry=pose.Position+pose.Forward*pose.ApproachDistance;
                Near(port.Translation+port.Forward*initial[pose.ConnectorId],entry);
                Near(Vector3D.Zero,Vector3D.Cross(entry-port.Translation,port.Forward));
                Assert.True(Vector3D.Dot(WaitingPoint(rig,pose)-port.Translation,port.Forward)>=pose.ApproachDistance+20-.000001);
            }

            // Rescan a genuinely reversed terminal enumeration, so cached offsets
            // must be rebuilt for the same IDs instead of merely staying cached.
            var group=rig.Program.GridTerminalSystem.GetBlockGroupWithName("AMS Docks");
            var source=(Stub<IMyBlockGroup>)RemotingServices.GetRealProxy(group);
            var fill=source.Methods["GetBlocksOfType"];
            source.Method("GetBlocksOfType",args=>{var result=fill(args);((List<IMyShipConnector>)args[0]).Reverse();return result;});
            typeof(P.FleetController).GetMethod("Scan",BindingFlags.NonPublic|BindingFlags.Instance).Invoke(rig.Fleet,new object[0]);
            RequestPoses(rig,rig.Miners);
            foreach(var miner in rig.Miners){var pose=Pose(rig,miner.Id);Assert.Equal(initial[pose.ConnectorId],pose.ApproachDistance);}

            var restored=new FleetStateTests.Rig(6,false,WaitingOnly);SetHorizontalPorts(restored,6);restored.Restore(rig.Checkpoint());
            RequestPoses(restored,restored.Miners);
            foreach(var miner in restored.Miners){var pose=Pose(restored,miner.Id);Assert.Equal(initial[pose.ConnectorId],pose.ApproachDistance);}
        }

        [Fact]
        public void ConfiguredApproachBaseIsRespectedAndOldFramesRemainCompatible()
        {
            var rig=new FleetStateTests.Rig(3,false,WaitingOnly+"ApproachDistance=45\n");SetHorizontalPorts(rig,3);
            RequestPoses(rig,rig.Miners);
            Assert.Equal(new[]{45d,55d,65d},rig.Miners.Select(t=>Pose(rig,t.Id).ApproachDistance).OrderBy(d=>d));
            Assert.Equal(0,P.DockFrame.FromIni(new MyIni()).ApproachDistance);
            var sent=new P.DockFrame {ApproachDistance=95};Assert.Equal(95,P.DockFrame.FromIni(sent.ToIni()).ApproachDistance);
        }

        [Fact]
        public void AnActiveAssignmentKeepsItsApproachDistanceWhenTheConfiguredBaseChanges()
        {
            var rig=new FleetStateTests.Rig(3,false);
            foreach(var miner in rig.Miners){miner.State=P.FlightState.Holding;miner.Position+=Vector3D.Forward*100;}
            rig.RegisterAll();rig.Tick(30);rig.Receive("DOCK_REQUEST",rig.Miners[0]);rig.Tick(12);
            var granted=P.DockFrame.FromIni(Assert.Single(rig.Sent,p=>p.Kind=="DOCK_GRANT").Body);

            rig.Program.Config=new P.Settings("[System]\nId=base\nBaseId=base\nFleetId=mining\n[Dock]\nApproachDistance=70\n","fleet","base");
            rig.Sent.Clear();rig.Receive("TELEMETRY",rig.Miners[0]);rig.Tick(12);
            var updated=Pose(rig,rig.Miners[0].Id);
            Assert.Equal(granted.Token,updated.Token);
            Assert.Equal(granted.ApproachDistance,updated.ApproachDistance);
            Assert.Equal(granted.ApproachDistance,P.Data.ReadNumber(rig.Save(),"fleet","LaneApproachDistance",0));

            rig.Sent.Clear();rig.Receive("DOCK_REQUEST",rig.Miners[1]);rig.Tick(12);
            var next=P.DockFrame.FromIni(Assert.Single(rig.Sent,p=>p.Kind=="DOCK_GRANT").Body);
            Assert.InRange(next.ApproachDistance,70,90);
            Assert.Equal(granted.ApproachDistance,Pose(rig,rig.Miners[0].Id).ApproachDistance);
        }

        [Fact]
        public void RemovingAnOfflineMinerAndReloadingDoNotMoveSurvivingWaitingAssignments()
        {
            var before = new FleetStateTests.Rig(3, false, WaitingOnly); SetHorizontalPorts(before, 3);
            RequestPoses(before, before.Miners);
            var original = before.Miners.Skip(1).ToDictionary(t => t.Id, t => WaitingPoint(before, Pose(before, t.Id)));
            before.Program.Now += 10;
            foreach (var miner in before.Miners.Skip(1)) before.Receive("TELEMETRY", miner);
            before.Fleet.Command("clear-offline");
            Assert.DoesNotContain(before.Fleet.GetTelemetry(), t => t.Id == "miner-0");
            var newcomer = AddMiner(before, "newcomer");
            RequestPoses(before, before.Miners.Skip(1));
            foreach (var pair in original) Near(pair.Value, WaitingPoint(before, Pose(before, pair.Key)));
            Assert.Equal(3, before.Fleet.GetTelemetry().Single(t => t.Id == newcomer.Id).WaitingSlot);
            var newcomerPoint = WaitingPoint(before, Pose(before, newcomer.Id));
            Near(RowOrigin + Vector3D.Forward * 100, newcomerPoint);

            var after = new FleetStateTests.Rig(3, false, WaitingOnly); SetHorizontalPorts(after, 3);
            after.Restore(before.Checkpoint()); var restoredNewcomer = AddMiner(after, newcomer.Id);
            // Client values must not reallocate the server's persisted waiting slot.
            foreach (var miner in after.Miners.Skip(1)) miner.WaitingSlot = 999;
            RequestPoses(after, after.Miners.Skip(1));
            foreach (var pair in original) Near(pair.Value, WaitingPoint(after, Pose(after, pair.Key)));
            Near(newcomerPoint, WaitingPoint(after, Pose(after, restoredNewcomer.Id)));
            Assert.Equal(new[] { 1, 2, 3 }, after.Fleet.GetTelemetry().Select(t => t.WaitingSlot).OrderBy(s => s).ToArray());
            var next = AddMiner(after, "next"); after.Receive("HELLO", next);
            Assert.Equal(4, after.Fleet.GetTelemetry().Single(t => t.Id == next.Id).WaitingSlot);
        }
    }
}