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();
}
}
}