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<T>(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<P.Job>(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<P.Job>(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<bool>(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<P.Job>(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);
}
}
}
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<T>(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<P.Job>(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<P.Job>(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<bool>(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<P.Job>(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);
}
}
}