using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; using Sandbox.ModAPI.Ingame; using VRage.Game.GUI.TextPanel; using VRage.Game.ModAPI.Ingame.Utilities; using VRageMath; using Xunit; using P=AutoMiningScript.Program; namespace AutoMiningScript.Tests { public class ExplorationMapTests { static MyDetectedEntityInfo Hit(long id=42, MyDetectedEntityType type=MyDetectedEntityType.Asteroid) { return new MyDetectedEntityInfo(id,"rock",type,new Vector3D(80,60,-20),MatrixD.Identity,Vector3.Zero, VRage.Game.MyRelationsBetweenPlayerAndBlock.NoOwnership,new BoundingBoxD(new Vector3D(50,30,-40),new Vector3D(110,90,20)),1); } static P.ScanRecord Scan(long id=42) { return P.ScanRecord.Capture(Vector3D.Zero,new Vector3D(300,225,-75),Hit(id)); } sealed class MapRole:P.RoleLogic { public readonly List Jobs=new List(); public readonly List Fleet=new List(); public P.MiningRegion Region=new P.MiningRegion {Id="search-1",Search=true,Radius=300,Center=Vector3D.Zero}; public MapRole(P p):base(p){} public override List GetJobs(){return Jobs;} public override List GetTelemetry(){return Fleet;} public override P.MiningRegion MapRegion(string id){return Region.Id==id?Region:null;} } sealed class Rig { public readonly P Program=TestRig.Program("[Display]\nBuiltInPage=map\n"); public readonly List Sprites=new List(); public readonly P.Dashboard Dashboard; public readonly MapRole Role; public Rig(int width=1024,int height=512) { var surface=new Stub().Set("SurfaceSize",new Vector2(width,height)).Set("TextureSize",new Vector2(width,height)) .Method("MeasureStringInPixels",a=>new Vector2(a[0].ToString().Sum(ch=>ch>=0x2e80?32:16),32)*(float)a[2]) .Method("DrawFrame",a=>new MySpriteDrawFrame(f=>{Sprites.Clear();f.AddToList(Sprites);})); var pb=new Stub().Set("EntityId",20L).Set("CubeGrid",Program.Me.CubeGrid).Set("CustomData","") .Set("CustomName","PB").Set("SurfaceCount",1).Method("GetSurface",a=>surface.Value); TestRig.SetBase(Program,"Me",pb.Value);TestRig.SetBase(Program,"GridTerminalSystem",new Stub().Value); Dashboard=new P.Dashboard(Program);Role=new MapRole(Program); } public void Add(P.ScanRecord scan=null,string outcome="",long entity=0) { Role.Jobs.Add(new P.Job {Id="ray-"+Role.Jobs.Count,RegionId=Role.Region.Id,Kind=P.JobKind.Survey,Entry=Vector3D.Zero,Depth=300,Scan=scan,Outcome=outcome,EntityId=entity}); } public void Draw(double now=0){Program.Now=now;Dashboard.Draw(Role);} public string[] Text=>Sprites.Where(s=>s.Type==SpriteType.TEXT).Select(s=>s.Data).ToArray(); public MySprite[] Cells=>Sprites.Where(s=>s.Color==new Color(34,84,113)).ToArray(); public void Export(string suffix="") { string path=Environment.GetEnvironmentVariable("AMS_MAP_PREVIEW");if(string.IsNullOrEmpty(path))return; File.WriteAllLines(path+suffix,Sprites.Select(s=>string.Join("\t",new[]{s.Type.ToString(),Convert.ToBase64String(Encoding.UTF8.GetBytes(s.Data??"")), (s.Position??Vector2.Zero).X.ToString(P.Data.Culture),(s.Position??Vector2.Zero).Y.ToString(P.Data.Culture),(s.Size??Vector2.Zero).X.ToString(P.Data.Culture),(s.Size??Vector2.Zero).Y.ToString(P.Data.Culture), s.RotationOrScale.ToString(P.Data.Culture),s.Alignment.ToString(),(s.Color??Color.White).R.ToString(),(s.Color??Color.White).G.ToString(),(s.Color??Color.White).B.ToString(),(s.Color??Color.White).A.ToString()})),Encoding.UTF8); } } [Fact] public void ActualHitStopsTheRecordedRayAndCarriesWorldBoundsThroughMessageAndJob() { var scan=Scan();Assert.Equal(Hit().HitPosition,scan.End);Assert.NotEqual(new Vector3D(300,225,-75),scan.End); var report=P.JobReport.FromIni(new P.JobReport {Scan=scan,Outcome="SurveyHit"}.ToIni()); var job=P.Job.FromIni(new P.Job {Id="j",Scan=report.Scan}.ToIni()); Assert.Equal(42,job.Scan.Asteroid);Assert.Equal(Hit().BoundingBox.Center,job.Scan.Center);Assert.Equal(new Vector3D(60),job.Scan.Size);Assert.Equal(Vector3D.Zero,job.Scan.Start); } [Fact] public void LegacyMessagesAndJobsHaveNoInventedRayOrSize() { Assert.Null(P.JobReport.FromIni(new MyIni()).Scan);Assert.Null(P.Job.FromIni(new P.Job {Id="old"}.ToIni()).Scan); var r=new Rig();r.Add(outcome:"SurveyEmpty");r.Draw();Assert.Empty(r.Cells); Assert.Contains(P.L.F(P.L.DisplayScanSummary,0,1,0,0),r.Text); } [Fact] public void EmptyRayIsRecordedWhilePlanetAndGridAreNotCountedAsAsteroids() { var end=new Vector3D(20,30,40);var empty=P.ScanRecord.Capture(Vector3D.Zero,end,new MyDetectedEntityInfo()); Assert.Equal(end,empty.End);Assert.Equal(0,empty.Asteroid); Assert.Equal(0,P.ScanRecord.Capture(Vector3D.Zero,end,Hit(type:MyDetectedEntityType.Planet)).Asteroid); Assert.Equal(0,P.ScanRecord.Capture(Vector3D.Zero,end,Hit(type:MyDetectedEntityType.LargeGrid)).Asteroid); } [Fact] public void InvalidAndMissingCoordinatesDoNotInventMapEvidence() { Assert.Null(P.ScanRecord.Capture(new Vector3D(double.NaN),Vector3D.Zero,Hit())); var i=new MyIni();i.Set("job","ScanStart","NaN,0,0");Assert.Null(P.ScanRecord.Read(i,"job")); var info=new MyDetectedEntityInfo(42,"rock",MyDetectedEntityType.Asteroid,null,MatrixD.Identity,Vector3.Zero,VRage.Game.MyRelationsBetweenPlayerAndBlock.NoOwnership,new BoundingBoxD(),1); Assert.Null(P.ScanRecord.Capture(Vector3D.Zero,Vector3D.Forward,info)); } [Fact] public void RepeatedHitsCountOneAsteroidAndOneCircularBound() { var r=new Rig();r.Add(Scan(),"SurveyHit");r.Add(Scan(),"SurveyHit");r.Draw(); Assert.Contains(P.L.F(P.L.DisplayScanSummary,2,2,1,0),r.Text); Assert.Contains(P.L.F(P.L.DisplayRockArea,"8482 m2",1,1,1),r.Text); Assert.Single(r.Text,s=>s.StartsWith("A1 "));Assert.DoesNotContain(r.Text,s=>s.StartsWith("A2 ")); } [Fact] public void UnknownSizeRemainsUnknownAndCanBeReplacedByMeasuredBounds() { var r=new Rig();var unknown=Scan();unknown.Size=Vector3D.Zero;r.Add(unknown,"SurveyHit");r.Draw(); Assert.Contains("A1 ?",r.Text);Assert.Contains(P.L.F(P.L.DisplayScanSummary,1,1,1,1),r.Text); r.Add(Scan(),"SurveyHit");r.Draw(1);Assert.Contains("A1 ~8482 m2",r.Text);Assert.Contains(P.L.F(P.L.DisplayScanSummary,2,2,1,0),r.Text); } [Fact] public void CircularBoundContainsTheMeasuredBoxRegardlessOfMapRotation() { var r=new Rig();r.Role.Region.Direction=Vector3D.Right;r.Role.Region.Up=Vector3D.Up; var scan=Scan();scan.Size=new Vector3D(40,50,60);r.Add(scan,"SurveyHit");r.Draw(); Assert.Contains("A1 ~6048 m2",r.Text); } [Fact] public void UnexecutedAndBlockedWithoutScanDoNotPaintCoverage() { var r=new Rig();r.Add();r.Add(outcome:"Blocked");r.Draw();Assert.Empty(r.Cells); r.Add(new P.ScanRecord {Start=Vector3D.Zero,End=new Vector3D(90,0,0)},"SurveyEmpty");r.Draw(1); Assert.NotEmpty(r.Cells);Assert.All(r.Cells,s=>Assert.InRange(s.Position.Value.X,512,555)); Assert.Contains(P.L.DisplayScanCaution,r.Text); } [Fact] public void ParallelRayEntirelyOutsideSearchDoesNotPaintAnEdge() { var r=new Rig();r.Add(new P.ScanRecord {Start=new Vector3D(400,-200,0),End=new Vector3D(400,200,0)},"SurveyEmpty");r.Draw();Assert.Empty(r.Cells); } [Theory] [InlineData(512,512)] [InlineData(1024,512)] [InlineData(512,1024)] public void SixteenCoincidentMinersKeepDistinctIdRowsAndMarkOffline(int width,int height) { var r=new Rig(width,height);for(int n=0;n<8;n++)r.Add(Scan(42+n),"SurveyHit"); for(int n=1;n<=16;n++)r.Role.Fleet.Add(new P.Telemetry {Id="miner-"+n.ToString("00"),State=P.FlightState.Transit,ReceivedAt=n==16?-100:0}); r.Draw();var labels=r.Sprites.Where(s=>s.Type==SpriteType.TEXT && s.Data.StartsWith("miner-")).ToArray(); Assert.Equal(16,labels.Length);Assert.Equal(16,labels.Select(s=>s.Position.Value).Distinct().Count()); Assert.Contains(labels,s=>s.Data.Contains(P.L.DisplayOffline)); Assert.All(labels,s=>{Assert.InRange(s.Position.Value.X,0,width);Assert.InRange(s.Position.Value.Y,0,height);}); Assert.All(labels,label=>Assert.Contains(r.Sprites,s=>s.Type==SpriteType.TEXTURE&&s.Color==new Color(9,17,27) &&s.Size.Value.X==169&&s.Position.Value.X-s.Size.Value.X/2==label.Position.Value.X-2 &&s.Position.Value.Y-s.Size.Value.Y/2==label.Position.Value.Y-1)); var boxes=r.Sprites.Where(s=>s.Type==SpriteType.TEXTURE&&s.Color==new Color(9,17,27)&&(s.Size.Value.X==169||s.Size.Value.X==173)).ToArray(); Assert.Equal(24,boxes.Length); for(int a=0;a=(boxes[a].Size.Value.X+boxes[b].Size.Value.X)/2 ||Math.Abs(boxes[a].Position.Value.Y-boxes[b].Position.Value.Y)>=(boxes[a].Size.Value.Y+boxes[b].Size.Value.Y)/2); r.Export("."+width+"x"+height+".tsv"); } [Fact] public void OverallScanProgressIncludesOtherRegionsAndExcludesMiningJobs() { var r=new Rig();r.Add(Scan(),"SurveyHit");r.Add(); r.Role.Jobs.Add(new P.Job {Id="other-ray",RegionId="search-2",Kind=P.JobKind.Survey,Outcome="SurveyEmpty"}); r.Role.Jobs.Add(new P.Job {Id="sample",RegionId="deposit-1",Kind=P.JobKind.Probe}); r.Role.Jobs.Add(new P.Job {Id="manual",RegionId="manual-1",Kind=P.JobKind.Manual}); r.Draw();Assert.Contains(P.L.F(P.L.DisplayScanProgress,"67%",2,3),r.Text); } [Fact] public void AsteroidUsesRoundOutlineAndDoesNotInferOreFromTheRequestedTarget() { var r=new Rig();r.Add(Scan(),"SurveyHit");r.Role.Jobs[0].TargetOre="Iron";r.Draw(); var ring=r.Sprites.Where(s=>s.Type==SpriteType.TEXTURE&&s.Data=="SquareSimple"&&s.Size.Value.Y==2&&s.Position.Value.Y>165&&s.Position.Value.Y<389).ToArray(); Assert.Equal(32,ring.Length);Assert.Single(ring.Select(s=>s.Color).Distinct());Assert.DoesNotContain("Fe",r.Text); var scale=212f/600;var center=new Vector2(512+80*scale,277-60*scale); var radius=(float)(new Vector3D(60).Length()*scale/2*Math.Cos(Math.PI/32)); Assert.All(ring,s=>Assert.InRange((s.Position.Value-center).Length(),radius-.001f,radius+.001f)); Assert.True(ring.Select(s=>s.RotationOrScale).Distinct().Count()>8); } [Fact] public void ConfirmedOresAggregateAcrossDepositRegionsAndRenderMultipleColoredArcs() { var r=new Rig();r.Add(Scan(),"SurveyHit"); r.Role.Jobs.Add(new P.Job {Id="probe-1",RegionId="deposit-1",Kind=P.JobKind.Probe,EntityId=42,ConfirmedOres="Iron,Cobalt,Stone"}); r.Role.Jobs.Add(new P.Job {Id="probe-2",RegionId="deposit-2",Kind=P.JobKind.Probe,EntityId=42,ConfirmedOres="Cobalt,Nickel"}); r.Draw();var ring=r.Sprites.Where(s=>s.Type==SpriteType.TEXTURE&&s.Data=="SquareSimple"&&s.Size.Value.Y==2&&s.Position.Value.Y>165&&s.Position.Value.Y<389).ToArray(); Assert.Equal(32,ring.Length);Assert.Equal(4,ring.Select(s=>s.Color).Distinct().Count()); foreach(var symbol in new[]{"Fe","Co","St","Ni"}) { var label=Assert.Single(r.Sprites,s=>s.Type==SpriteType.TEXT&&s.Data==symbol); Assert.Contains(ring,s=>s.Color==label.Color); } var restored=P.Job.FromIni(r.Role.Jobs[1].ToIni());Assert.Equal("Iron,Cobalt,Stone",restored.ConfirmedOres); Assert.Equal("",P.Job.FromIni(new P.Job {Id="legacy"}.ToIni()).ConfirmedOres); } [Fact] public void AllElevenOreKindsKeepVisibleSymbolsAndDistinctColorsOnOneAsteroid() { var r=new Rig(512,512);r.Add(Scan(),"SurveyHit"); r.Role.Jobs[0].ConfirmedOres="Iron,Cobalt,Nickel,Silicon,Magnesium,Silver,Gold,Platinum,Uranium,Ice,Stone"; r.Draw();var labels=r.Sprites.Where(s=>s.Type==SpriteType.TEXT&&new[]{"Fe","Co","Ni","Si","Mg","Ag","Au","Pt","U","Ice","St"}.Contains(s.Data)).ToArray(); Assert.Equal(11,labels.Length);Assert.Equal(11,labels.Select(s=>s.Color).Distinct().Count()); Assert.All(labels,label=>Assert.Contains(r.Sprites,s=>s.Type==SpriteType.TEXTURE&&s.Size.Value.Y==2&&s.Color==label.Color)); } [Fact] public void MoreAsteroidsCarouselWithoutLosingTotalOrCoverage() { var r=new Rig();for(int n=0;n<12;n++)r.Add(Scan(42+n),"SurveyHit");r.Draw();var cells=r.Cells.Length; Assert.Contains(r.Text,s=>s.StartsWith("A1 "));Assert.DoesNotContain(r.Text,s=>s.StartsWith("A9 ")); r.Draw(8);Assert.Contains(r.Text,s=>s.StartsWith("A9 "));Assert.Equal(cells,r.Cells.Length); Assert.Contains(P.L.F(P.L.DisplayScanSummary,12,12,12,0),r.Text); } [Fact] public void PlannerCheckpointRetainsBoundsAndClearTasksRemovesThem() { var planner=new P.TaskPlanner(TestRig.Program());planner.Survey(Vector3D.Zero,Vector3D.Zero,Vector3D.Forward,Vector3D.Up,300); var job=planner.Jobs.First();planner.Report(job,new P.JobReport {JobId=job.Id,Outcome="SurveyHit",Position=Scan().End,Direction=Vector3D.Forward,EntityId=42,Scan=Scan()}); var save=new MyIni();planner.Save(save);var restored=new P.TaskPlanner(TestRig.Program());restored.Load(save); Assert.Equal(new Vector3D(60),restored.Find(job.Id).Scan.Size);restored.ClearTasks();Assert.Empty(restored.Jobs); } [Fact] public void CameraSurveySendsActualRayEvidenceToTheFleet() { var r=new MinerStateTests.MinerRig();r.Task();var flags=BindingFlags.Instance|BindingFlags.NonPublic; var job=(P.Job)r.Miner.GetType().GetField("job",flags).GetValue(r.Miner);job.Kind=P.JobKind.Survey;r.CameraHit=Hit();r.State(P.FlightState.Survey); r.Miner.GetType().GetMethod("Survey",flags).Invoke(r.Miner,new object[]{.1}); var report=P.JobReport.FromIni(Assert.Single(r.Sent,p=>p.Kind=="RESULT").Body); Assert.Equal(r.Position,report.Scan.Start);Assert.Equal(Hit().HitPosition,report.Scan.End);Assert.True(report.Scan.Sized); } [Fact] public void RepresentativeMapFrameContainsRaysRocksAndMinerIds() { var r=new Rig();for(int n=0;n<24;n++){double a=n*Math.PI/12;r.Add(new P.ScanRecord {Start=Vector3D.Zero,End=new Vector3D(Math.Cos(a)*280,Math.Sin(a)*280,0)},"SurveyEmpty");} r.Add(Scan(),"SurveyHit");var other=Scan(99);other.Center=new Vector3D(-120,-100,0);other.End=other.Center;other.Size=new Vector3D(90,70,30);r.Add(other,"SurveyHit"); r.Role.Jobs.Add(new P.Job {Id="probe",RegionId="deposit-1",Kind=P.JobKind.Probe,EntityId=42,ConfirmedOres="Iron,Cobalt,Nickel,Silicon,Stone"}); r.Role.Jobs.Add(new P.Job {Id="probe-2",RegionId="deposit-2",Kind=P.JobKind.Probe,EntityId=99,ConfirmedOres="Gold,Uranium,Ice"}); for(int n=1;n<=5;n++)r.Role.Fleet.Add(new P.Telemetry {Id=P.L.Language=="zh-CN"?"深空工业"+n+"号":"Miner-"+n,Position=new Vector3D((n-3)*40,40,0),ReceivedAt=n==5?-100:0}); r.Draw();Assert.NotEmpty(r.Cells);Assert.Contains("A1 ~8482 m2",r.Text); r.Export(); } } }