using System; using System.Collections.Generic; using System.Reflection; using Sandbox.ModAPI.Ingame; using VRageMath; using Xunit; using P = AutoMiningScript.Program; namespace AutoMiningScript.Tests { public class ReturnStateTests { static readonly BindingFlags Private = BindingFlags.Instance | BindingFlags.NonPublic; static T Read(object instance, string name) { return (T)instance.GetType().GetField(name, Private).GetValue(instance); } static void Write(object instance, string name, object value) { instance.GetType().GetField(name, Private).SetValue(instance, value); } static void Call(object instance, string name, params object[] args) { instance.GetType().GetMethod(name, Private).Invoke(instance, args); } static P.DockFrame Waiting(MinerStateTests.MinerRig r,bool granted=false) { r.PrepareDeparture("valid-lease"); r.Connector.Set("Status", MyShipConnectorStatus.Unconnected); // This fixture has one real forward lens, rather than a zero-matrix mock // that reports every direction as covered regardless of ship orientation. r.Camera.Set("RaycastConeLimit",45f).Set("RaycastDistanceLimit",-1d) .Method("get_WorldMatrix",a=>r.Controller.Value.WorldMatrix) .Method("CanScan",a=>{ var ray=Vector3D.TransformNormal((Vector3D)a[0]-r.Position,MatrixD.Transpose(r.Controller.Value.WorldMatrix)); return -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; }); Write(r.Miner, "laneKind", "dock"); if(!granted)Write(r.Miner,"lane",""); Write(r.Miner, "stableSince", -1d); r.State(P.FlightState.Holding); var dock = Read(r.Miner, "dock"); dock.WaitingLocal = new Vector3D(0, 0, -80); return dock; } static void Step(MinerStateTests.MinerRig r, P.DockFrame dock, double seconds = .1) { r.Program.Now += seconds; dock.ReceivedAt = r.Program.Now; Call(r.Miner, "Waiting", seconds); } static MatrixD Desired(MinerStateTests.MinerRig r) { return Read(Read(r.Miner, "flight"), "previousDesired"); } [Fact] public void WaitingKeepsTravelAndHoldingControlUntilTheGrantAllowsDirectApproach() { var r = new MinerStateTests.MinerRig(); var dock = Waiting(r); r.Position = new Vector3D(100, 0, -80); MatrixD orientation = MatrixD.Identity; Vector3D velocity = Vector3D.Zero; r.Controller.Method("get_WorldMatrix", a => orientation).Method("GetShipVelocities", a => new MyShipVelocities(velocity, Vector3D.Zero)); Step(r, dock); Assert.True(Vector3D.Dot(Desired(r).Forward, Vector3D.Left) > .999); Assert.False(Read(r.Miner, "waitingAligning")); Assert.Equal(P.FlightState.Holding, r.StateNow); r.Position = dock.WaitingLocal + Vector3D.Right; orientation = MatrixD.CreateWorld(Vector3D.Zero, Vector3D.Left, Vector3D.Up); velocity = Vector3D.Left * .4; Step(r, dock); Assert.False(Read(r.Miner, "waitingAligning")); velocity = Vector3D.Zero; Step(r, dock); Assert.True(Read(r.Miner, "waitingAligning")); Assert.True(Vector3D.Dot(Desired(r).Forward, Vector3D.Forward) > .999); Assert.Equal(-1d, Read(r.Miner, "stableSince")); Assert.Equal(P.FlightState.Holding, r.StateNow); orientation = MatrixD.Identity; Step(r, dock); Assert.Equal(P.FlightState.Holding,r.StateNow); Assert.Contains(P.L.MinerDockGrantMissing,Read(r.Miner,"reason")); Write(r.Miner,"lane","valid-lease");Step(r,dock); Assert.Equal(P.FlightState.DockAlign, r.StateNow); Assert.Equal("valid-lease", Read(r.Miner, "lane")); } [Fact] public void WaitingAlignmentLatchAvoidsAlternatingAttitudesButResetsAfterFourMetresDrift() { var r = new MinerStateTests.MinerRig(); var dock = Waiting(r); r.Position = dock.WaitingLocal; Step(r, dock); Assert.True(Read(r.Miner, "waitingAligning")); r.Position += Vector3D.Right * 3; Step(r, dock); Assert.True(Read(r.Miner, "waitingAligning")); Assert.True(Vector3D.Dot(Desired(r).Forward, Vector3D.Forward) > .999); r.Position += Vector3D.Right * 2; Step(r, dock); Assert.False(Read(r.Miner, "waitingAligning")); Assert.True(Vector3D.Dot(Desired(r).Forward, Vector3D.Left) > .999); Assert.Equal(-1d, Read(r.Miner, "stableSince")); } [Fact] public void NearPortWaitingUsesItsCoveredEscapeRouteWithoutUnnecessaryRotation() { var r = new MinerStateTests.MinerRig(); var dock = Waiting(r); r.Position = Vector3D.Right * 10;int rays=0; r.Camera.Method("Raycast",a=>{rays++;return default(MyDetectedEntityInfo);});Step(r, dock); Assert.False(Read(r.Miner, "waitingAligning")); Assert.True(r.Camera.Value.CanScan(dock.WaitingLocal)); Assert.True(Vector3D.Dot(Desired(r).Forward,Vector3D.Forward) > .999); var flight=Read(r.Miner,"flight"); Assert.True(rays>0);Assert.Equal(dock.WaitingLocal,flight.RouteTarget); Assert.True(Vector3D.Dot(Read(flight,"scanDirection"),Vector3D.Normalize(dock.WaitingLocal-r.Position))>.999); Assert.Equal(0,flight.ScanClearance);Assert.All(r.Hardware.Thrusters,t=>Assert.Equal(0,t.ThrustOverridePercentage)); Assert.Equal(P.FlightState.Holding, r.StateNow); } [Theory] [InlineData(P.FlightState.Returning)] [InlineData(P.FlightState.Holding)] [InlineData(P.FlightState.DockAlign)] [InlineData(P.FlightState.DockApproach)] [InlineData(P.FlightState.DockRetreat)] public void RepeatedReturnKeepsNavigationProgressScanAndExistingLane(P.FlightState state) { var r = new MinerStateTests.MinerRig(); Waiting(r,true); r.State(state); Read>(r.Miner, "breadcrumbs").AddRange(new[] { Vector3D.Zero, Vector3D.Right * 20, Vector3D.Right * 40 }); Write(r.Miner, "returnIndex", 1); Write(r.Miner, "waitingAligning", true); Write(r.Miner, "stableSince", 2d); var flight = Read(r.Miner, "flight"); Write(flight, "scanIndex", 7); Write(flight, "scanning", true); double since = Read(r.Miner, "stateSince"); for (int n = 0; n < 3; n++) { r.Program.Now++; r.Miner.Command("return"); } Assert.Equal(state, r.StateNow); Assert.Equal(1, Read(r.Miner, "returnIndex")); Assert.Equal(7, Read(flight, "scanIndex")); Assert.True(Read(flight, "scanning")); Assert.True(Read(r.Miner, "waitingAligning")); Assert.Equal(2d, Read(r.Miner, "stableSince")); Assert.Equal(since, Read(r.Miner, "stateSince")); Assert.Equal("valid-lease", Read(r.Miner, "lane")); Assert.DoesNotContain(r.Sent, p => p.Kind == "LANE_RELEASE"); } [Fact] public void ReturnDuringDepartureFinishesStraightRetreatBeforeReleasingTheDepartureLane() { var r = new MinerStateTests.MinerRig(); r.PrepareDeparture("valid-lease"); r.Connector.Set("Status", MyShipConnectorStatus.Unconnected); r.Position = Vector3D.Forward * 5; r.State(P.FlightState.Departing); var dock = Read(r.Miner, "dock"); r.Miner.Command("return"); Assert.Equal(P.FlightState.DockRetreat, r.StateNow); Vector3D retreat = Read(r.Miner, "abortPoint"); Assert.True(Vector3D.Dot(retreat - r.Position, dock.Forward) >= 30); Assert.True(Vector3D.Cross(retreat - r.Position, dock.Forward).Length() < .00001); Assert.Equal("valid-lease", Read(r.Miner, "lane")); r.Program.Now += .1; r.Miner.Command("return"); Assert.Equal(retreat, Read(r.Miner, "abortPoint")); Call(r.Miner, "DockRetreat", .1); Assert.True(Vector3D.Dot(Desired(r).Forward, Vector3D.Forward) > .999); Assert.DoesNotContain(r.Sent, p => p.Kind == "LANE_RELEASE"); r.Position = retreat; Call(r.Miner, "DockRetreat", .1); Assert.Equal(P.FlightState.Holding, r.StateNow); Assert.Equal("", Read(r.Miner, "lane")); Assert.Single(r.Sent, p => p.Kind == "LANE_RELEASE"); } } }