using System; using System.Linq; 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 ContinuousExplorationTests { static readonly BindingFlags Private=BindingFlags.Instance|BindingFlags.NonPublic; static T Read(object o,string name) {return (T)o.GetType().GetField(name,Private).GetValue(o);} static void Write(object o,string name,object value) {o.GetType().GetField(name,Private).SetValue(o,value);} static void Call(object o,string name,params object[] args) {o.GetType().GetMethod(name,Private).Invoke(o,args);} static MinerStateTests.MinerRig CompletedRay() { var r=new MinerStateTests.MinerRig();r.Task();Read(r.Miner,"job").Kind=P.JobKind.Survey; Write(r.Miner,"home",new P.DockFrame {ReceivedAt=r.Program.Now});r.State(P.FlightState.Survey);Call(r.Miner,"Survey",.1); Assert.Equal(P.FlightState.Survey,r.StateNow);Assert.Null(Read(r.Miner,"job"));return r; } static P.Packet NextJob(MinerStateTests.MinerRig r,string after="hole") { var job=new P.Job {Id="next-hole",RegionId="area",Kind=P.JobKind.Survey,Entry=Vector3D.Forward*30,Depth=200,Owner=r.Program.Config.Id}; var body=job.ToIni();body.Set("next","After",after); return new P.Packet {Kind="JOB",From=r.Program.Config.BaseId,Body=body}; } [Fact] public void OneRayCompletesAndNextRayStartsWithoutADockingRoundTrip() { var r=CompletedRay();Assert.Equal("hole",r.Miner.GetTelemetry()[0].NextAfter); r.Resources.Cargo=.6;r.Resources.Battery=.8;r.Resources.NeedsCharge=true; r.Miner.Receive(NextJob(r));Assert.Equal(P.FlightState.Transit,r.StateNow); Assert.Equal("next-hole",r.Miner.GetJobs().Single().Id);Assert.Empty(r.Miner.GetTelemetry()[0].NextAfter); Assert.DoesNotContain(r.Sent,p=>p.Kind=="DOCK_REQUEST"); } [Theory] [InlineData("pause")] [InlineData("return")] [InlineData("release")] public void LateContinuationNeverOverridesOperatorControl(string command) { var r=CompletedRay();r.Miner.Command(command);P.FlightState stopped=r.StateNow; r.Miner.Receive(NextJob(r));Assert.Equal(stopped,r.StateNow);Assert.False(Read(r.Miner,"running")); } [Fact] public void UnrelatedLateJobCannotReplaceAnAirborneContinuation() { var r=CompletedRay();r.Miner.Receive(NextJob(r,"old-hole"));Assert.Empty(r.Miner.GetJobs());Assert.Equal(P.FlightState.Survey,r.StateNow); } [Theory] [InlineData(.95,10)] [InlineData(.1,.01)] public void CargoOrInsufficientNextLegBudgetReturnsWithTheAssignedTaskPreserved(double cargo,double energy) { var r=CompletedRay();r.Resources.Cargo=cargo;r.Resources.StoredMWh=energy; r.Miner.Receive(NextJob(r));Assert.Equal(P.FlightState.Returning,r.StateNow);Assert.Equal("next-hole",r.Miner.GetJobs().Single().Id); } [Fact] public void CompletingAProbeContinuesOnlyAfterLeavingTheBore() { var r=new MinerStateTests.MinerRig();r.Task(40);r.State(P.FlightState.Retreat);Write(r.Miner,"completion","Complete"); r.Position=Vector3D.Forward*10;Call(r.Miner,"Retreat",.1);Assert.Equal(P.FlightState.Retreat,r.StateNow);Assert.DoesNotContain(r.Sent,p=>p.Kind=="RESULT"); r.Position=Vector3D.Backward*22;Call(r.Miner,"Retreat",.1); Assert.Equal(P.FlightState.Survey,r.StateNow);Assert.False(r.Drill.Value.Enabled);Assert.Equal("hole",r.Miner.GetTelemetry()[0].NextAfter); } [Fact] public void NavigationVoxelHitBecomesASurveyResultBeforeReachingTheObstructedObservation() { var r=new MinerStateTests.MinerRig();r.Task();var job=Read(r.Miner,"job");job.Kind=P.JobKind.Survey;job.Entry=Vector3D.Forward*50; r.Camera.Set("WorldMatrix",MatrixD.Identity).Set("RaycastConeLimit",45f).Set("RaycastDistanceLimit",-1d); r.CameraHit=MinerStateTests.MinerRig.Hit(MyDetectedEntityType.Asteroid);r.Position=Vector3D.Backward*20;r.State(P.FlightState.Transit); Call(r.Miner,"Transit",.1); var report=P.JobReport.FromIni(Assert.Single(r.Sent,p=>p.Kind=="RESULT").Body); Assert.Equal("SurveyHit",report.Outcome);Assert.Equal(42,report.EntityId);Assert.Equal(Vector3D.Zero,report.Position); Assert.Equal(P.FlightState.Survey,r.StateNow);Assert.Equal(Vector3D.Backward*20,r.Position); } [Fact] public void WaitingResultSurvivesReloadAndRequiresExplicitResume() { var before=CompletedRay();var save=new MyIni();before.Miner.Save(save); var after=new MinerStateTests.MinerRig();after.Miner.Load(save);Assert.Equal(P.FlightState.Paused,after.StateNow); Assert.Empty(after.Miner.GetTelemetry()[0].NextAfter);after.Miner.Command("resume");Assert.Equal(P.FlightState.Survey,after.StateNow); after.Program.Now=3;Call(after.Miner,"Survey",.1); Assert.Equal("hole",P.JobReport.FromIni(Assert.Single(after.Sent,p=>p.Kind=="RESULT").Body).JobId); } [Fact] public void OnlyMatchingRegionCompletionReturnsAnAwaitingMiner() { var r=CompletedRay();var body=new MyIni();body.Set("next","Done",true);body.Set("next","After","obsolete"); var packet=new P.Packet {Kind="NEXT",From=r.Program.Config.BaseId,Body=body};r.Miner.Receive(packet);Assert.Equal(P.FlightState.Survey,r.StateNow); body.Set("next","After","hole");r.Miner.Receive(packet);Assert.Equal(P.FlightState.Returning,r.StateNow); } static FleetStateTests.Rig FleetAfterRay(bool hit=false) { var r=new FleetStateTests.Rig(1,true);r.RegisterAll();r.Fleet.Command("survey 300");while(r.Planner.Generating)r.Tick(1); var j=r.Planner.Jobs.First();r.Planner.SetOwner(j,r.Miners[0].Id);r.SetConnected(0,false); var t=r.Miners[0];t.Position=j.Entry;t.State=P.FlightState.Survey;t.JobId=j.Id;r.Receive("TELEMETRY",t); var result=new P.JobReport {JobId=j.Id,Outcome=hit?"SurveyHit":"SurveyEmpty",Position=j.Entry+j.Direction*10,Direction=j.Direction,Up=j.Up,EntityId=hit?42:0}; r.Fleet.Receive(new P.Packet {Kind="RESULT",From=t.Id,Source=5000,Body=result.ToIni()}); t.JobId="";t.NextAfter=j.Id;r.Receive("TELEMETRY",t);r.Sent.Clear();return r; } static void Dispatch(FleetStateTests.Rig r) { r.Fleet.Command("start");for(int n=0;n<400 && !r.Sent.Any(p=>p.Kind=="JOB" || p.Kind=="NEXT");n++) {if(n%5==0)r.Receive("TELEMETRY",r.Miners[0]);r.Tick(1);} } [Theory] [InlineData(false)] [InlineData(true)] public void FleetReassignsInSpaceAndPrefersNewProbesToFurtherRays(bool hit) { var r=FleetAfterRay(hit);Dispatch(r);var packet=Assert.Single(r.Sent,p=>p.Kind=="JOB");var j=P.Job.FromIni(packet.Body); Assert.Equal(hit?P.JobKind.Probe:P.JobKind.Survey,j.Kind);Assert.Equal(r.Miners[0].NextAfter,P.Data.Text(packet.Body,"next","After")); if(!hit)Assert.Equal(r.Miners[0].Position,j.Entry); Assert.Single(r.Planner.OwnedJobs);Assert.Equal(r.Miners[0].Id,j.Owner); } [Fact] public void FleetDoesNotAcceptAnotherMinersCompletionReceipt() { var r=FleetAfterRay();r.Miners[0].NextAfter=r.Planner.Jobs.Last().Id;r.Receive("TELEMETRY",r.Miners[0]);Dispatch(r); Assert.DoesNotContain(r.Sent,p=>p.Kind=="JOB" || p.Kind=="NEXT"); } [Fact] public void FinishedRegionReturnsWithoutTakingWorkFromADifferentSearchArea() { var r=FleetAfterRay();foreach(var j in r.Planner.Jobs.ToList())if(!P.TaskPlanner.Finished(j))r.Planner.Report(j,new P.JobReport {Outcome="SurveyEmpty"}); r.Planner.Survey(Vector3D.Right*3000,Vector3D.Zero,Vector3D.Forward,Vector3D.Up,300);r.Tick(30);Dispatch(r); Assert.True(r.Sent.Any(p=>p.Kind=="NEXT"),r.Fleet.Diagnostics+" / last="+r.Planner.Find(r.Miners[0].NextAfter).LastOwner+" / "+r.Fleet.GetTelemetry()[0].ToIni()); var packet=Assert.Single(r.Sent,p=>p.Kind=="NEXT");Assert.True(P.Data.Flag(packet.Body,"next","Done"));Assert.DoesNotContain(r.Sent,p=>p.Kind=="JOB"); } [Fact] public void BusyRegionWaitsInsteadOfBeingReportedComplete() { var r=FleetAfterRay();foreach(var j in r.Planner.Jobs)if(!P.TaskPlanner.Finished(j))r.Planner.SetOwner(j,"occupied-"+j.Id); // Use one outstanding task to keep this scenario within the fleet limit. var remaining=r.Planner.Jobs.Where(j=>!P.TaskPlanner.Finished(j)).ToList();foreach(var j in remaining.Skip(1))r.Planner.Report(j,new P.JobReport {Outcome="SurveyEmpty"}); Dispatch(r);Assert.True(r.Sent.Any(p=>p.Kind=="NEXT"),r.Fleet.Diagnostics+" / last="+r.Planner.Find(r.Miners[0].NextAfter).LastOwner+" / "+r.Fleet.GetTelemetry()[0].ToIni());Assert.False(P.Data.Flag(Assert.Single(r.Sent,p=>p.Kind=="NEXT").Body,"next","Done")); } [Fact] public void FleetCheckpointRetainsCompletionIdentityForAirborneContinuation() { var before=FleetAfterRay();string saved=before.Checkpoint();var after=new FleetStateTests.Rig(1,false);after.Restore(saved); after.Miners[0]=before.Miners[0];after.Receive("HELLO",after.Miners[0]);Dispatch(after); Assert.Contains(after.Sent,p=>p.Kind=="JOB" && P.Data.Text(p.Body,"next","After")==before.Miners[0].NextAfter); } } }