using System; using System.Collections.Generic; using System.Reflection; using Sandbox.ModAPI.Ingame; using VRage.Game.ModAPI.Ingame.Utilities; using VRageMath; using Xunit; using P=AutoMiningScript.Program; namespace AutoMiningScript.Tests { public class BreadcrumbCompressionTests { static readonly BindingFlags Private=BindingFlags.Instance|BindingFlags.NonPublic; static T Read(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[] arguments)=>target.GetType().GetMethod(name,Private).Invoke(target,arguments); static List Path(MinerStateTests.MinerRig r)=>Read>(r.Miner,"breadcrumbs"); static MinerStateTests.MinerRig Rig() {var r=new MinerStateTests.MinerRig();r.Task();r.State(P.FlightState.Transit);return r;} static void Record(MinerStateTests.MinerRig r,Vector3D point) {r.Position=point;Call(r.Miner,"RecordBreadcrumb");} [Theory] [InlineData(false)] [InlineData(true)] public void FiveKilometreStraightTravelRetainsBothEndpointsWithoutExhaustingTheBudget(bool smallNoise) { var r=Rig();var origin=new Vector3D(-67700,-123600,-162000); for(int n=0;n<=500;n++)Record(r,origin+Vector3D.Right*(n*10.1)+Vector3D.Up*(smallNoise && n>1?Math.Sin(n)*.05:0)); Assert.Equal(P.FlightState.Transit,r.StateNow);Assert.Equal(2,Path(r).Count); Assert.Equal(origin,Path(r)[0]);Assert.Equal(r.Position,Path(r)[1]); } [Theory] [InlineData(90)] [InlineData(180)] [InlineData(1)] public void ARealCornerOrReversalKeepsItsTurningPoint(double degrees) { var r=Rig();Record(r,Vector3D.Zero);Record(r,Vector3D.Right*11);Record(r,Vector3D.Right*22); var corner=r.Position;var direction=new Vector3D(Math.Cos(degrees*Math.PI/180),Math.Sin(degrees*Math.PI/180),0); Record(r,corner+direction*11);Record(r,corner+direction*22); Assert.Equal(3,Path(r).Count);Assert.Equal(corner,Path(r)[1]);Assert.Equal(r.Position,Path(r)[2]); } [Fact] public void AGradualCurveCannotAccumulateUnboundedDeviationFromRetainedChords() { var r=Rig();var samples=new List(); // Adjacent headings differ by only 0.063 degrees; repeatedly refitting the // last chord would otherwise hide an increasingly large circular arc. for(int n=0;n<=300;n++) {double angle=n*.0011;var point=new Vector3D(10000*Math.Sin(angle),10000*(1-Math.Cos(angle)),0);samples.Add(point);Record(r,point);} Assert.Equal(P.FlightState.Transit,r.StateNow);Assert.InRange(Path(r).Count,20,127); int index=0; for(int n=1;n(r.Miner,"reason"));Assert.Equal(127,Read(r.Miner,"returnIndex")); } [Fact] public void RestoringACompressedPathKeepsItsPriorSegmentAndStartsANewBearing() { var r=Rig();Record(r,Vector3D.Zero);Record(r,Vector3D.Right*11);Record(r,Vector3D.Right*1000); var saved=new MyIni();r.Miner.Save(saved);Assert.Equal(2,saved.Get("miner","BreadcrumbCount").ToInt32()); var restored=Rig();restored.Miner.Load(saved);Assert.Equal(Path(r),Path(restored)); restored.State(P.FlightState.Transit);Record(restored,Vector3D.Right*1011);Record(restored,Vector3D.Right*1022); Assert.Equal(3,Path(restored).Count);Assert.Equal(Vector3D.Right*1000,Path(restored)[1]); } [Theory] [InlineData(P.FlightState.Returning,1,0,2)] [InlineData(P.FlightState.Returning,-1,0,0)] [InlineData(P.FlightState.Holding,-1,0,0)] [InlineData(P.FlightState.DockAlign,-1,0,0)] [InlineData(P.FlightState.DockApproach,-1,0,0)] [InlineData(P.FlightState.DockRetreat,-1,2,0)] [InlineData(P.FlightState.Fault,-1,3,0)] [InlineData(P.FlightState.Transit,-1,1,3)] [InlineData(P.FlightState.Drilling,-1,0,3)] [InlineData(P.FlightState.Fault,-1,0,3)] public void ReloadResumesOnlyUnfinishedReturnLegsAndRetainsOutboundPaths(P.FlightState state,int remaining,int failures,int expected) { var r=Rig();var points=new[]{Vector3D.Zero,Vector3D.Right*20,Vector3D.Right*20+Vector3D.Up*1000}; Path(r).AddRange(points);r.State(state);Write(r.Miner,"returnIndex",remaining);Write(r.Miner,"retry",failures); var saved=new MyIni();r.Miner.Save(saved); Assert.Equal(3,Path(r).Count);Assert.Equal(expected,saved.Get("miner","BreadcrumbCount").ToInt32()); var restored=Rig();restored.Connector.Set("Status",MyShipConnectorStatus.Unconnected);restored.Miner.Load(saved); Assert.Equal(expected,Path(restored).Count); for(int n=0;n(restored.Miner,"returnIndex")); Assert.Null(Read(restored.Miner,"dock")); } [Fact] public void ReturningAlongACompressedSegmentStillRequiresFreshEnvelopeRays() { var r=Rig();for(int n=0;n<=200;n++)Record(r,Vector3D.Forward*(n*11)); r.State(P.FlightState.Returning);Write(r.Miner,"returnIndex",Path(r).Count-1); r.Controller.Method("get_WorldMatrix",a=>MatrixD.CreateWorld(r.Position,Vector3D.Backward,Vector3D.Up)); bool ready=false;int rays=0; r.Camera.Set("RaycastConeLimit",45f).Set("RaycastDistanceLimit",-1d) .Method("get_WorldMatrix",a=>r.Controller.Value.WorldMatrix).Method("CanScan",a=>ready) .Method("Raycast",a=>{rays++;return default(MyDetectedEntityInfo);}); var flight=Read(r.Miner,"flight"); for(int n=0;n<30;n++){r.Program.Now+=.05;Call(r.Miner,"ReturnHome",.05);} Assert.Equal(0,rays);Assert.Equal(0,flight.CommandSpeed);Assert.All(r.Hardware.Thrusters,t=>Assert.Equal(0,t.ThrustOverridePercentage)); ready=true;for(int n=0;n<15;n++){r.Program.Now+=.05;Call(r.Miner,"ReturnHome",.05);} Assert.True(rays>=18);Assert.True(flight.ScanClearance>0);Assert.True(flight.CommandSpeed>0); } } }