using System;
using System.Collections.Generic;
using System.Reflection;
using Sandbox.ModAPI.Ingame;
using VRage;
using VRage.Game.ModAPI.Ingame;
using VRage.Game.ModAPI.Ingame.Utilities;
using VRageMath;
using Xunit;
using P=AutoMiningScript.Program;
using FC=AutoMiningScript.Program.FlightController;
namespace AutoMiningScript.Tests
{
public class FlightResourceTests
{
static void Near(Vector3D a,Vector3D b) {Assert.True(Vector3D.Distance(a,b)<1e-6,a+" vs "+b);}
[Fact] public void RotationErrorTracksWorldAxesAtArbitraryOrientations()
{
Near(FC.RotationError(MatrixD.Identity,MatrixD.Identity),Vector3D.Zero);
for(int i=0;i<80;i++)
{
Vector3D axis=Vector3D.Normalize(new Vector3D(Math.Sin(i*.41)+.1,Math.Cos(i*.37)+.2,Math.Sin(i*.29)+.3));
double angle=.01+i*.038;MatrixD current=MatrixD.CreateFromAxisAngle(Vector3D.Normalize(new Vector3D(1,2,3)),.77);
Near(FC.RotationError(current,current*MatrixD.CreateFromAxisAngle(axis,angle)),axis*angle);
}
Near(FC.RotationError(MatrixD.Identity,MatrixD.CreateFromAxisAngle(Vector3D.Up,Math.PI)),Vector3D.Up*Math.PI);
}
[Fact] public void OffsetAndRotatedConnectorMapsToTheActualContact()
{
MatrixD local=MatrixD.CreateFromYawPitchRoll(.7,.4,.2),port=MatrixD.CreateFromYawPitchRoll(-.4,.8,-.3);
MatrixD reference=FC.ReferenceOrientationForConnector(local.Forward,local.Up,port.Forward,port.Up);
Near(Vector3D.TransformNormal(local.Forward,reference),-port.Forward);Near(Vector3D.TransformNormal(local.Up,reference),port.Up);
Vector3D contact=new Vector3D(100,20,-50),offset=new Vector3D(2,-3,4);
Near(FC.ReferencePositionForPoint(contact,offset,reference)+Vector3D.TransformNormal(offset,reference),contact);
}
[Fact] public void RotatingMotherUsesPointVelocityAndRejectsDegenerateAxes()
{
Near(FC.PointVelocity(new Vector3D(1,2,3),new Vector3D(0,1,0),new Vector3D(3,0,0),Vector3D.Zero),new Vector3D(1,2,0));
MatrixD invalid;Assert.False(FC.Frame(Vector3D.Forward,Vector3D.Forward,out invalid));Assert.False(FC.Frame(new Vector3D(double.NaN,0,0),Vector3D.Up,out invalid));
}
[Fact] public void WeightedBatteriesAndHydrogenDoNotAveragePercentages()
{
var p=TestRig.Program();var h=new P.ShipHardware(p);h.Connector=TestRig.Connector(p,true).Value;
h.Batteries.Add(TestRig.Battery(p,1,1).Value);h.Batteries.Add(TestRig.Battery(p,0,9).Value);
h.UsesHydrogen=true;h.Tanks.Add(new Stub<IMyGasTank>().Set("CubeGrid",p.Me.CubeGrid).Set("IsFunctional",true).Set("Capacity",10f).Set("FilledRatio",1d).Value);
h.Tanks.Add(new Stub<IMyGasTank>().Set("CubeGrid",p.Me.CubeGrid).Set("IsFunctional",true).Set("Capacity",90f).Set("FilledRatio",0d).Value);
var r=new P.Resources(p,h);r.Update(.5,P.FlightState.Servicing,0,0);
Assert.Equal(.1,r.Battery,6);Assert.Equal(.1,r.Hydrogen,6);
}
[Fact] public void BaseBatteriesAreExcludedEvenWhenInTheCachedList()
{
var p=TestRig.Program();var h=new P.ShipHardware(p);h.Connector=TestRig.Connector(p,true).Value;
h.Batteries.Add(TestRig.Battery(p,.5f,1).Value);
h.Batteries.Add(TestRig.Battery(p,1000,1000).Set("CubeGrid",new Stub<IMyCubeGrid>().Value).Value);
var r=new P.Resources(p,h);r.Update(.5,P.FlightState.Servicing,0,0);Assert.Equal(.5,r.Battery,6);Assert.Equal(1,r.CapacityMWh,6);
}
[Fact] public void UnloadingNeedsTwoCompleteScansAndNeverUsesTime()
{
var p=TestRig.Program();var h=new P.ShipHardware(p);h.Connector=TestRig.Connector(p,true).Value;h.Batteries.Add(TestRig.Battery(p,1,1).Value);
var r=new P.Resources(p,h);r.Update(.5,P.FlightState.Servicing,0,0);Assert.False(r.Empty);
p.Now=10000;r.Service(.5);Assert.False(r.Empty);
r.Update(.5,P.FlightState.Servicing,0,0);Assert.True(r.Empty);
h.Connector=TestRig.Connector(p,false).Value;r.Update(.5,P.FlightState.Transit,0,0);Assert.False(r.Empty);
}
[Fact] public void ChargeLatchRequiresTargetAndWholeSortieBudget()
{
var p=TestRig.Program();var h=new P.ShipHardware(p);h.Connector=TestRig.Connector(p,true).Value;var battery=TestRig.Battery(p,.89f,1);h.Batteries.Add(battery.Value);
h.InventoryBlocks.Add(Inventory(p,new List<MyInventoryItem>(),0));
var r=new P.Resources(p,h);r.NeedsCharge=true;r.Update(.5,P.FlightState.Servicing,0,0);r.Update(.5,P.FlightState.Servicing,0,0);
string why;Assert.False(r.ReadyForSortie(10,1,out why));Assert.True(r.NeedsCharge);
battery.Set("CurrentStoredPower",.91f);r.Update(.5,P.FlightState.Servicing,0,0);Assert.True(r.ReadyForSortie(10,1,out why));Assert.False(r.NeedsCharge);
Assert.False(r.ReadyForSortie(1000000,1000,out why));Assert.Equal(P.L.MinerSortieOutOfRange,why);
}
[Fact] public void SufficientEnergyCanDepartWithoutWaitingForNinetyPercent()
{
var p=TestRig.Program();var h=new P.ShipHardware(p);h.Connector=TestRig.Connector(p,true).Value;h.Batteries.Add(TestRig.Battery(p,.6f,1).Value);
h.InventoryBlocks.Add(Inventory(p,new List<MyInventoryItem>(),0));
var r=new P.Resources(p,h);r.Update(.5,P.FlightState.Servicing,0,0);r.Update(.5,P.FlightState.Servicing,0,0);
string why;Assert.False(r.NeedsCharge);Assert.True(r.ReadyForSortie(10,1,out why));
}
[Fact] public void DeepDrillingRequiresMoreReturnReserveThanTheEntrance()
{
var p=TestRig.Program();var h=new P.ShipHardware(p);h.Connector=TestRig.Connector(p,false).Value;h.Batteries.Add(TestRig.Battery(p,1,1).Value);
var r=new P.Resources(p,h);r.Update(.5,P.FlightState.Drilling,500,0);double shallow=r.ReturnBudgetMWh;r.Update(.5,P.FlightState.Drilling,500,100);Assert.True(r.ReturnBudgetMWh>shallow);
}
[Fact] public void EnergyStorageRetainsLatchButRequiresNewEmptyScan()
{
var p=TestRig.Program();var h=new P.ShipHardware(p);var r=new P.Resources(p,h) {NeedsCharge=true,NeedsHydrogen=true,Empty=true};var ini=new MyIni();r.Save(ini);
var restored=new P.Resources(p,h);restored.Load(ini);Assert.True(restored.NeedsCharge);Assert.True(restored.NeedsHydrogen);Assert.False(restored.Empty);
}
static MyInventoryItem Ore(string type,double amount)
{
object boxed=default(MyInventoryItem);
typeof(MyInventoryItem).GetField("Type").SetValue(boxed,MyItemType.MakeOre(type));
typeof(MyInventoryItem).GetField("Amount").SetValue(boxed,(MyFixedPoint)amount);
return (MyInventoryItem)boxed;
}
static IMyTerminalBlock Inventory(P p,List<MyInventoryItem> items,double fill=0.1)
{
var inventory=new Stub<IMyInventory>().Set("CurrentVolume",(MyFixedPoint)fill).Set("MaxVolume",(MyFixedPoint)1)
.Method("GetItems",a=> {((List<MyInventoryItem>)a[0]).AddRange(items);return null;});
return new Stub<IMyCargoContainer>().Set("CubeGrid",p.Me.CubeGrid).Set("HasInventory",true).Set("InventoryCount",1).Method("GetInventory",a=>inventory.Value).Value;
}
[Fact] public void StoneAndIceInTransportInventoriesPreventEmptyUntilBothAreGone()
{
var p=TestRig.Program();var h=new P.ShipHardware(p);h.Connector=TestRig.Connector(p,true).Value;h.Batteries.Add(TestRig.Battery(p,1,1).Value);
var drill=new List<MyInventoryItem> {Ore("Stone",5)};var connector=new List<MyInventoryItem> {Ore("Ice",3)};
h.InventoryBlocks.Add(Inventory(p,drill));h.InventoryBlocks.Add(Inventory(p,connector));var r=new P.Resources(p,h);
r.Update(.5,P.FlightState.Servicing,0,0);Assert.Equal(8,r.OreAmount);Assert.False(r.Empty);
drill.Clear();r.Update(.5,P.FlightState.Servicing,0,0);Assert.Equal(3,r.OreAmount);Assert.False(r.Empty);
connector.Clear();r.Update(.5,P.FlightState.Servicing,0,0);Assert.False(r.Empty);r.Update(.5,P.FlightState.Servicing,0,0);Assert.True(r.Empty);
}
[Fact] public void InternalTransferDoesNotCreateOreAndInvalidSamplesStayLatched()
{
var p=TestRig.Program();var h=new P.ShipHardware(p);h.Connector=TestRig.Connector(p,false).Value;h.Batteries.Add(TestRig.Battery(p,1,1).Value);h.SamplingIsolated=true;
var drill=new List<MyInventoryItem> {Ore("Iron",10)};var cargo=new List<MyInventoryItem>();h.InventoryBlocks.Add(Inventory(p,drill));h.InventoryBlocks.Add(Inventory(p,cargo));
var r=new P.Resources(p,h);r.Update(.5,P.FlightState.Drilling,0,0);r.ResetSampleValidation();
drill.Clear();cargo.Add(Ore("Iron",10));r.Update(.5,P.FlightState.Drilling,0,0);Assert.Equal(10,r.Ores["Iron"]);Assert.True(r.SamplingValid);
cargo.Clear();cargo.Add(Ore("Iron",5));r.Update(.5,P.FlightState.Drilling,0,0);Assert.False(r.SamplingValid);
r.Update(.5,P.FlightState.Drilling,0,0);Assert.False(r.SamplingValid);r.ResetSampleValidation();r.Update(.5,P.FlightState.Drilling,0,0);Assert.True(r.SamplingValid);
}
[Fact] public void MovingMotherBudgetUsesInterceptTimeAndRejectsUnreachableVelocity()
{
Assert.Equal(10,P.Data.InterceptTime(new Vector3D(100,0,0),Vector3D.Zero,10),6);
Assert.Equal(20,P.Data.InterceptTime(new Vector3D(100,0,0),new Vector3D(5,0,0),10),6);
Assert.Equal(-1,P.Data.InterceptTime(new Vector3D(100,0,0),new Vector3D(20,0,0),10));
Assert.Equal(0,P.Data.InterceptTime(Vector3D.Zero,Vector3D.Zero,10));
}
sealed class CargoRig
{
public readonly P Program;
public readonly P.ShipHardware Hardware;
public readonly P.Resources Resources;
public readonly Stub<IMyInventory> Cargo;
public readonly Stub<IMyShipConnector> Connector;
public readonly Stub<IMyBatteryBlock> Battery;
public readonly List<MyInventoryItem> Items=new List<MyInventoryItem> {Ore("Iron",10)};
public CargoRig(string config="")
{
Program=TestRig.Program(config);Hardware=new P.ShipHardware(Program);
Connector=TestRig.Connector(Program,true);Hardware.Connector=Connector.Value;
Battery=TestRig.Battery(Program,1,1);Hardware.Batteries.Add(Battery.Value);
Cargo=Store(5,100,Items);Add(Cargo,Program.Me.CubeGrid);Resources=new P.Resources(Program,Hardware);
}
public static Stub<IMyInventory> Store(double volume,double capacity,List<MyInventoryItem> contents)
{
return new Stub<IMyInventory>().Set("CurrentMass",(MyFixedPoint)1).Set("CurrentVolume",(MyFixedPoint)volume).Set("MaxVolume",(MyFixedPoint)capacity)
.Method("get_ItemCount",a=>contents.Count).Method("GetItems",a=> {((List<MyInventoryItem>)a[0]).AddRange(contents);return null;});
}
public void Add(Stub<IMyInventory> inventory,IMyCubeGrid grid,bool drill=false)
{
if(drill) Hardware.InventoryBlocks.Add(new Stub<IMyShipDrill>().Set("CubeGrid",grid).Set("HasInventory",true).Set("InventoryCount",1).Method("GetInventory",a=>inventory.Value).Value);
else Hardware.InventoryBlocks.Add(new Stub<IMyCargoContainer>().Set("CubeGrid",grid).Set("HasInventory",true).Set("InventoryCount",1).Method("GetInventory",a=>inventory.Value).Value);
}
public void Scan() {Program.Now+=.5;Resources.Update(.5,P.FlightState.Servicing,0,0);}
public bool CanDepart(out string reason) {return Resources.ReadyForSortie(10,1,out reason);}
}
[Fact] public void DefaultFivePercentDepartureNeedsTwoScansAndDoesNotClaimOreIsEmpty()
{
var r=new CargoRig();Assert.Equal(.05,r.Resources.DepartureFill,8);
r.Scan();Assert.False(r.Resources.DepartureReady);Assert.Equal(P.L.MinerWaitDepartureScans,r.Resources.DepartureWaitReason);
r.Scan();Assert.True(r.Resources.DepartureReady);Assert.False(r.Resources.Empty);Assert.Equal(10,r.Resources.OreAmount);
string reason;Assert.True(r.CanDepart(out reason));
}
[Theory][InlineData(5.0001,false)][InlineData(5,true)][InlineData(4.9999,true)][InlineData(0,true)]
public void DepartureVolumeThresholdIncludesItsBoundary(double volume,bool expected)
{
var r=new CargoRig();r.Cargo.Set("CurrentVolume",(MyFixedPoint)volume);r.Scan();r.Scan();Assert.Equal(expected,r.Resources.DepartureReady);
}
[Theory][InlineData("NaN")][InlineData("Infinity")][InlineData("-0.01")][InlineData("0.95")][InlineData("1")]
public void InvalidDepartureFillIsRejectedWhileConfigurationLoads(string value)
{Assert.Throws<ArgumentException>(()=>new CargoRig("[Cargo]\nDepartureFill="+value));}
[Fact] public void DepartureFillMustStayBelowTheConfiguredReturnFill()
{
Assert.Throws<ArgumentException>(()=>new CargoRig("[Cargo]\nReturnFill=0.2\nDepartureFill=0.2"));
var r=new CargoRig("[Cargo]\nReturnFill=0.2\nDepartureFill=0.1999");Assert.Equal(.1999,r.Resources.DepartureFill,8);
}
[Fact] public void RisingCargoRevokesPermissionAndNeedsTwoNewScans()
{
var r=new CargoRig();r.Scan();r.Scan();Assert.True(r.Resources.DepartureReady);
r.Cargo.Set("CurrentVolume",(MyFixedPoint)6);r.Scan();Assert.False(r.Resources.DepartureReady);
r.Cargo.Set("CurrentVolume",(MyFixedPoint)5);r.Scan();Assert.False(r.Resources.DepartureReady);r.Scan();Assert.True(r.Resources.DepartureReady);
}
[Fact] public void AllMinerInventoryVolumeCountsIncludingSuppliesButMotherCargoIsExcluded()
{
var r=new CargoRig();r.Items.Clear();
object component=default(MyInventoryItem);typeof(MyInventoryItem).GetField("Type").SetValue(component,MyItemType.MakeComponent("SteelPlate"));
typeof(MyInventoryItem).GetField("Amount").SetValue(component,(MyFixedPoint)1);r.Items.Add((MyInventoryItem)component);
var supplies=CargoRig.Store(10,100,new List<MyInventoryItem>());r.Add(supplies,r.Program.Me.CubeGrid);
r.Add(CargoRig.Store(100000,100000,new List<MyInventoryItem>{Ore("Gold",100)}),new Stub<IMyCubeGrid>().Value);
r.Scan();r.Scan();Assert.True(r.Resources.Empty);Assert.False(r.Resources.DepartureReady);Assert.Equal(.075,r.Resources.Cargo,8);Assert.Equal(0,r.Resources.OreAmount);
supplies.Set("CurrentVolume",(MyFixedPoint)0);r.Scan();r.Scan();Assert.True(r.Resources.DepartureReady);Assert.Equal(.025,r.Resources.Cargo,8);
}
[Fact] public void ZeroDepartureFillRestoresOreOnlyEmptyRuleAndKeepsSupplyVolume()
{
var r=new CargoRig("[Cargo]\nDepartureFill=0");r.Cargo.Set("CurrentVolume",(MyFixedPoint)50);r.Scan();r.Scan();Assert.False(r.Resources.DepartureReady);
r.Items.Clear();r.Scan();Assert.False(r.Resources.DepartureReady);r.Scan();Assert.True(r.Resources.Empty);Assert.True(r.Resources.DepartureReady);
Assert.Equal(.5,r.Resources.Cargo,8);string reason;Assert.True(r.CanDepart(out reason));
}
[Fact] public void StrictOreUnloadCannotLaunchAtTheReturnThresholdWithSupplies()
{
var r=new CargoRig("[Cargo]\nDepartureFill=0");r.Items.Clear();r.Cargo.Set("CurrentVolume",(MyFixedPoint)95);r.Scan();r.Scan();
Assert.True(r.Resources.Empty);Assert.True(r.Resources.DepartureReady);string reason;Assert.False(r.CanDepart(out reason));Assert.Equal(P.L.MinerCargoFull,reason);
}
[Fact] public void ScanFaultRescanReloadAndStaleDataCannotKeepDeparturePermission()
{
var r=new CargoRig();r.Scan();r.Scan();Assert.True(r.Resources.DepartureReady);
r.Cargo.Set("ItemCount",513);r.Scan();Assert.False(r.Resources.DepartureReady);Assert.NotEmpty(r.Resources.ScanProblem);
r.Cargo.Set("ItemCount",1);r.Resources.ResetInventoryScan();r.Scan();Assert.False(r.Resources.DepartureReady);r.Scan();Assert.True(r.Resources.DepartureReady);
var stored=new MyIni();r.Resources.Save(stored);r.Resources.Load(stored);Assert.False(r.Resources.DepartureReady);
r.Scan();Assert.False(r.Resources.DepartureReady);r.Scan();Assert.True(r.Resources.DepartureReady);
r.Program.Now+=1;string reason;Assert.False(r.CanDepart(out reason));Assert.False(r.Resources.DepartureReady);
r.Scan();Assert.False(r.Resources.DepartureReady);r.Scan();Assert.True(r.Resources.DepartureReady);
}
[Fact] public void DisconnectAndInventoryExceptionsImmediatelyRevokeDeparturePermission()
{
var r=new CargoRig();r.Scan();r.Scan();r.Connector.Set("Status",MyShipConnectorStatus.Unconnected);r.Resources.Service(.1);Assert.False(r.Resources.DepartureReady);
r.Connector.Set("Status",MyShipConnectorStatus.Connected);r.Scan();Assert.False(r.Resources.DepartureReady);r.Scan();Assert.True(r.Resources.DepartureReady);
r.Cargo.Method("GetItems",a=>{throw new InvalidOperationException("broken inventory");});r.Scan();Assert.False(r.Resources.DepartureReady);Assert.Equal("broken inventory",r.Resources.ScanProblem);
}
[Fact] public void InvalidInventoryMassOrBlockedDrillCannotPassTheSmallCargoGate()
{
var r=new CargoRig();r.Scan();r.Scan();r.Cargo.Set("CurrentMass",(MyFixedPoint)(-1));r.Scan();Assert.False(r.Resources.DepartureReady);
r.Cargo.Set("CurrentMass",(MyFixedPoint)1);var drill=CargoRig.Store(.99,1,new List<MyInventoryItem>());r.Add(drill,r.Program.Me.CubeGrid,true);
r.Cargo.Set("CurrentVolume",(MyFixedPoint)0);r.Scan();r.Scan();Assert.True(r.Resources.Cargo<.05);Assert.False(r.Resources.DepartureReady);Assert.Equal(P.L.MinerDrillBlocked,r.Resources.DepartureWaitReason);
}
[Fact] public void FivePercentCargoDoesNotBypassBatteryOrHydrogenService()
{
var r=new CargoRig();r.Resources.NeedsCharge=true;r.Battery.Set("CurrentStoredPower",.5f);r.Scan();r.Scan();Assert.True(r.Resources.DepartureReady);
string reason;Assert.False(r.CanDepart(out reason));Assert.Equal(P.L.MinerWaitChargeTarget,reason);
r.Battery.Set("CurrentStoredPower",1f);r.Hardware.UsesHydrogen=true;
r.Hardware.Tanks.Add(new Stub<IMyGasTank>().Set("CubeGrid",r.Program.Me.CubeGrid).Set("IsFunctional",true).Set("Capacity",1000f).Set("FilledRatio",.5d).Value);
r.Resources.NeedsHydrogen=true;r.Scan();Assert.False(r.CanDepart(out reason));Assert.Equal(P.L.MinerWaitHydrogenTarget,reason);
}
[Theory][InlineData(float.NaN)][InlineData(float.PositiveInfinity)][InlineData(-1f)]
public void InvalidBatteryObservationsRevokeBothScanConfirmations(float stored)
{
var r=new CargoRig();r.Scan();r.Scan();Assert.True(r.Resources.DepartureReady);r.Battery.Set("CurrentStoredPower",stored);
Assert.Throws<InvalidOperationException>(()=>r.Scan());Assert.False(r.Resources.DepartureReady);
r.Battery.Set("CurrentStoredPower",1f);r.Scan();Assert.False(r.Resources.DepartureReady);r.Scan();Assert.True(r.Resources.DepartureReady);
}
[Fact] public void InvalidHydrogenRatioCannotBypassDepartureEnergyChecks()
{
var r=new CargoRig();r.Scan();r.Scan();r.Hardware.UsesHydrogen=true;
r.Hardware.Tanks.Add(new Stub<IMyGasTank>().Set("CubeGrid",r.Program.Me.CubeGrid).Set("IsFunctional",true).Set("Capacity",1000f).Set("FilledRatio",double.NaN).Value);
Assert.Throws<InvalidOperationException>(()=>r.Scan());Assert.False(r.Resources.DepartureReady);string reason;Assert.False(r.CanDepart(out reason));
}
}
}
using System;
using System.Collections.Generic;
using System.Reflection;
using Sandbox.ModAPI.Ingame;
using VRage;
using VRage.Game.ModAPI.Ingame;
using VRage.Game.ModAPI.Ingame.Utilities;
using VRageMath;
using Xunit;
using P=AutoMiningScript.Program;
using FC=AutoMiningScript.Program.FlightController;
namespace AutoMiningScript.Tests
{
public class FlightResourceTests
{
static void Near(Vector3D a,Vector3D b) {Assert.True(Vector3D.Distance(a,b)<1e-6,a+" vs "+b);}
[Fact] public void RotationErrorTracksWorldAxesAtArbitraryOrientations()
{
Near(FC.RotationError(MatrixD.Identity,MatrixD.Identity),Vector3D.Zero);
for(int i=0;i<80;i++)
{
Vector3D axis=Vector3D.Normalize(new Vector3D(Math.Sin(i*.41)+.1,Math.Cos(i*.37)+.2,Math.Sin(i*.29)+.3));
double angle=.01+i*.038;MatrixD current=MatrixD.CreateFromAxisAngle(Vector3D.Normalize(new Vector3D(1,2,3)),.77);
Near(FC.RotationError(current,current*MatrixD.CreateFromAxisAngle(axis,angle)),axis*angle);
}
Near(FC.RotationError(MatrixD.Identity,MatrixD.CreateFromAxisAngle(Vector3D.Up,Math.PI)),Vector3D.Up*Math.PI);
}
[Fact] public void OffsetAndRotatedConnectorMapsToTheActualContact()
{
MatrixD local=MatrixD.CreateFromYawPitchRoll(.7,.4,.2),port=MatrixD.CreateFromYawPitchRoll(-.4,.8,-.3);
MatrixD reference=FC.ReferenceOrientationForConnector(local.Forward,local.Up,port.Forward,port.Up);
Near(Vector3D.TransformNormal(local.Forward,reference),-port.Forward);Near(Vector3D.TransformNormal(local.Up,reference),port.Up);
Vector3D contact=new Vector3D(100,20,-50),offset=new Vector3D(2,-3,4);
Near(FC.ReferencePositionForPoint(contact,offset,reference)+Vector3D.TransformNormal(offset,reference),contact);
}
[Fact] public void RotatingMotherUsesPointVelocityAndRejectsDegenerateAxes()
{
Near(FC.PointVelocity(new Vector3D(1,2,3),new Vector3D(0,1,0),new Vector3D(3,0,0),Vector3D.Zero),new Vector3D(1,2,0));
MatrixD invalid;Assert.False(FC.Frame(Vector3D.Forward,Vector3D.Forward,out invalid));Assert.False(FC.Frame(new Vector3D(double.NaN,0,0),Vector3D.Up,out invalid));
}
[Fact] public void WeightedBatteriesAndHydrogenDoNotAveragePercentages()
{
var p=TestRig.Program();var h=new P.ShipHardware(p);h.Connector=TestRig.Connector(p,true).Value;
h.Batteries.Add(TestRig.Battery(p,1,1).Value);h.Batteries.Add(TestRig.Battery(p,0,9).Value);
h.UsesHydrogen=true;h.Tanks.Add(new Stub<IMyGasTank>().Set("CubeGrid",p.Me.CubeGrid).Set("IsFunctional",true).Set("Capacity",10f).Set("FilledRatio",1d).Value);
h.Tanks.Add(new Stub<IMyGasTank>().Set("CubeGrid",p.Me.CubeGrid).Set("IsFunctional",true).Set("Capacity",90f).Set("FilledRatio",0d).Value);
var r=new P.Resources(p,h);r.Update(.5,P.FlightState.Servicing,0,0);
Assert.Equal(.1,r.Battery,6);Assert.Equal(.1,r.Hydrogen,6);
}
[Fact] public void BaseBatteriesAreExcludedEvenWhenInTheCachedList()
{
var p=TestRig.Program();var h=new P.ShipHardware(p);h.Connector=TestRig.Connector(p,true).Value;
h.Batteries.Add(TestRig.Battery(p,.5f,1).Value);
h.Batteries.Add(TestRig.Battery(p,1000,1000).Set("CubeGrid",new Stub<IMyCubeGrid>().Value).Value);
var r=new P.Resources(p,h);r.Update(.5,P.FlightState.Servicing,0,0);Assert.Equal(.5,r.Battery,6);Assert.Equal(1,r.CapacityMWh,6);
}
[Fact] public void UnloadingNeedsTwoCompleteScansAndNeverUsesTime()
{
var p=TestRig.Program();var h=new P.ShipHardware(p);h.Connector=TestRig.Connector(p,true).Value;h.Batteries.Add(TestRig.Battery(p,1,1).Value);
var r=new P.Resources(p,h);r.Update(.5,P.FlightState.Servicing,0,0);Assert.False(r.Empty);
p.Now=10000;r.Service(.5);Assert.False(r.Empty);
r.Update(.5,P.FlightState.Servicing,0,0);Assert.True(r.Empty);
h.Connector=TestRig.Connector(p,false).Value;r.Update(.5,P.FlightState.Transit,0,0);Assert.False(r.Empty);
}
[Fact] public void ChargeLatchRequiresTargetAndWholeSortieBudget()
{
var p=TestRig.Program();var h=new P.ShipHardware(p);h.Connector=TestRig.Connector(p,true).Value;var battery=TestRig.Battery(p,.89f,1);h.Batteries.Add(battery.Value);
h.InventoryBlocks.Add(Inventory(p,new List<MyInventoryItem>(),0));
var r=new P.Resources(p,h);r.NeedsCharge=true;r.Update(.5,P.FlightState.Servicing,0,0);r.Update(.5,P.FlightState.Servicing,0,0);
string why;Assert.False(r.ReadyForSortie(10,1,out why));Assert.True(r.NeedsCharge);
battery.Set("CurrentStoredPower",.91f);r.Update(.5,P.FlightState.Servicing,0,0);Assert.True(r.ReadyForSortie(10,1,out why));Assert.False(r.NeedsCharge);
Assert.False(r.ReadyForSortie(1000000,1000,out why));Assert.Equal(P.L.MinerSortieOutOfRange,why);
}
[Fact] public void SufficientEnergyCanDepartWithoutWaitingForNinetyPercent()
{
var p=TestRig.Program();var h=new P.ShipHardware(p);h.Connector=TestRig.Connector(p,true).Value;h.Batteries.Add(TestRig.Battery(p,.6f,1).Value);
h.InventoryBlocks.Add(Inventory(p,new List<MyInventoryItem>(),0));
var r=new P.Resources(p,h);r.Update(.5,P.FlightState.Servicing,0,0);r.Update(.5,P.FlightState.Servicing,0,0);
string why;Assert.False(r.NeedsCharge);Assert.True(r.ReadyForSortie(10,1,out why));
}
[Fact] public void DeepDrillingRequiresMoreReturnReserveThanTheEntrance()
{
var p=TestRig.Program();var h=new P.ShipHardware(p);h.Connector=TestRig.Connector(p,false).Value;h.Batteries.Add(TestRig.Battery(p,1,1).Value);
var r=new P.Resources(p,h);r.Update(.5,P.FlightState.Drilling,500,0);double shallow=r.ReturnBudgetMWh;r.Update(.5,P.FlightState.Drilling,500,100);Assert.True(r.ReturnBudgetMWh>shallow);
}
[Fact] public void EnergyStorageRetainsLatchButRequiresNewEmptyScan()
{
var p=TestRig.Program();var h=new P.ShipHardware(p);var r=new P.Resources(p,h) {NeedsCharge=true,NeedsHydrogen=true,Empty=true};var ini=new MyIni();r.Save(ini);
var restored=new P.Resources(p,h);restored.Load(ini);Assert.True(restored.NeedsCharge);Assert.True(restored.NeedsHydrogen);Assert.False(restored.Empty);
}
static MyInventoryItem Ore(string type,double amount)
{
object boxed=default(MyInventoryItem);
typeof(MyInventoryItem).GetField("Type").SetValue(boxed,MyItemType.MakeOre(type));
typeof(MyInventoryItem).GetField("Amount").SetValue(boxed,(MyFixedPoint)amount);
return (MyInventoryItem)boxed;
}
static IMyTerminalBlock Inventory(P p,List<MyInventoryItem> items,double fill=0.1)
{
var inventory=new Stub<IMyInventory>().Set("CurrentVolume",(MyFixedPoint)fill).Set("MaxVolume",(MyFixedPoint)1)
.Method("GetItems",a=> {((List<MyInventoryItem>)a[0]).AddRange(items);return null;});
return new Stub<IMyCargoContainer>().Set("CubeGrid",p.Me.CubeGrid).Set("HasInventory",true).Set("InventoryCount",1).Method("GetInventory",a=>inventory.Value).Value;
}
[Fact] public void StoneAndIceInTransportInventoriesPreventEmptyUntilBothAreGone()
{
var p=TestRig.Program();var h=new P.ShipHardware(p);h.Connector=TestRig.Connector(p,true).Value;h.Batteries.Add(TestRig.Battery(p,1,1).Value);
var drill=new List<MyInventoryItem> {Ore("Stone",5)};var connector=new List<MyInventoryItem> {Ore("Ice",3)};
h.InventoryBlocks.Add(Inventory(p,drill));h.InventoryBlocks.Add(Inventory(p,connector));var r=new P.Resources(p,h);
r.Update(.5,P.FlightState.Servicing,0,0);Assert.Equal(8,r.OreAmount);Assert.False(r.Empty);
drill.Clear();r.Update(.5,P.FlightState.Servicing,0,0);Assert.Equal(3,r.OreAmount);Assert.False(r.Empty);
connector.Clear();r.Update(.5,P.FlightState.Servicing,0,0);Assert.False(r.Empty);r.Update(.5,P.FlightState.Servicing,0,0);Assert.True(r.Empty);
}
[Fact] public void InternalTransferDoesNotCreateOreAndInvalidSamplesStayLatched()
{
var p=TestRig.Program();var h=new P.ShipHardware(p);h.Connector=TestRig.Connector(p,false).Value;h.Batteries.Add(TestRig.Battery(p,1,1).Value);h.SamplingIsolated=true;
var drill=new List<MyInventoryItem> {Ore("Iron",10)};var cargo=new List<MyInventoryItem>();h.InventoryBlocks.Add(Inventory(p,drill));h.InventoryBlocks.Add(Inventory(p,cargo));
var r=new P.Resources(p,h);r.Update(.5,P.FlightState.Drilling,0,0);r.ResetSampleValidation();
drill.Clear();cargo.Add(Ore("Iron",10));r.Update(.5,P.FlightState.Drilling,0,0);Assert.Equal(10,r.Ores["Iron"]);Assert.True(r.SamplingValid);
cargo.Clear();cargo.Add(Ore("Iron",5));r.Update(.5,P.FlightState.Drilling,0,0);Assert.False(r.SamplingValid);
r.Update(.5,P.FlightState.Drilling,0,0);Assert.False(r.SamplingValid);r.ResetSampleValidation();r.Update(.5,P.FlightState.Drilling,0,0);Assert.True(r.SamplingValid);
}
[Fact] public void MovingMotherBudgetUsesInterceptTimeAndRejectsUnreachableVelocity()
{
Assert.Equal(10,P.Data.InterceptTime(new Vector3D(100,0,0),Vector3D.Zero,10),6);
Assert.Equal(20,P.Data.InterceptTime(new Vector3D(100,0,0),new Vector3D(5,0,0),10),6);
Assert.Equal(-1,P.Data.InterceptTime(new Vector3D(100,0,0),new Vector3D(20,0,0),10));
Assert.Equal(0,P.Data.InterceptTime(Vector3D.Zero,Vector3D.Zero,10));
}
sealed class CargoRig
{
public readonly P Program;
public readonly P.ShipHardware Hardware;
public readonly P.Resources Resources;
public readonly Stub<IMyInventory> Cargo;
public readonly Stub<IMyShipConnector> Connector;
public readonly Stub<IMyBatteryBlock> Battery;
public readonly List<MyInventoryItem> Items=new List<MyInventoryItem> {Ore("Iron",10)};
public CargoRig(string config="")
{
Program=TestRig.Program(config);Hardware=new P.ShipHardware(Program);
Connector=TestRig.Connector(Program,true);Hardware.Connector=Connector.Value;
Battery=TestRig.Battery(Program,1,1);Hardware.Batteries.Add(Battery.Value);
Cargo=Store(5,100,Items);Add(Cargo,Program.Me.CubeGrid);Resources=new P.Resources(Program,Hardware);
}
public static Stub<IMyInventory> Store(double volume,double capacity,List<MyInventoryItem> contents)
{
return new Stub<IMyInventory>().Set("CurrentMass",(MyFixedPoint)1).Set("CurrentVolume",(MyFixedPoint)volume).Set("MaxVolume",(MyFixedPoint)capacity)
.Method("get_ItemCount",a=>contents.Count).Method("GetItems",a=> {((List<MyInventoryItem>)a[0]).AddRange(contents);return null;});
}
public void Add(Stub<IMyInventory> inventory,IMyCubeGrid grid,bool drill=false)
{
if(drill) Hardware.InventoryBlocks.Add(new Stub<IMyShipDrill>().Set("CubeGrid",grid).Set("HasInventory",true).Set("InventoryCount",1).Method("GetInventory",a=>inventory.Value).Value);
else Hardware.InventoryBlocks.Add(new Stub<IMyCargoContainer>().Set("CubeGrid",grid).Set("HasInventory",true).Set("InventoryCount",1).Method("GetInventory",a=>inventory.Value).Value);
}
public void Scan() {Program.Now+=.5;Resources.Update(.5,P.FlightState.Servicing,0,0);}
public bool CanDepart(out string reason) {return Resources.ReadyForSortie(10,1,out reason);}
}
[Fact] public void DefaultFivePercentDepartureNeedsTwoScansAndDoesNotClaimOreIsEmpty()
{
var r=new CargoRig();Assert.Equal(.05,r.Resources.DepartureFill,8);
r.Scan();Assert.False(r.Resources.DepartureReady);Assert.Equal(P.L.MinerWaitDepartureScans,r.Resources.DepartureWaitReason);
r.Scan();Assert.True(r.Resources.DepartureReady);Assert.False(r.Resources.Empty);Assert.Equal(10,r.Resources.OreAmount);
string reason;Assert.True(r.CanDepart(out reason));
}
[Theory][InlineData(5.0001,false)][InlineData(5,true)][InlineData(4.9999,true)][InlineData(0,true)]
public void DepartureVolumeThresholdIncludesItsBoundary(double volume,bool expected)
{
var r=new CargoRig();r.Cargo.Set("CurrentVolume",(MyFixedPoint)volume);r.Scan();r.Scan();Assert.Equal(expected,r.Resources.DepartureReady);
}
[Theory][InlineData("NaN")][InlineData("Infinity")][InlineData("-0.01")][InlineData("0.95")][InlineData("1")]
public void InvalidDepartureFillIsRejectedWhileConfigurationLoads(string value)
{Assert.Throws<ArgumentException>(()=>new CargoRig("[Cargo]\nDepartureFill="+value));}
[Fact] public void DepartureFillMustStayBelowTheConfiguredReturnFill()
{
Assert.Throws<ArgumentException>(()=>new CargoRig("[Cargo]\nReturnFill=0.2\nDepartureFill=0.2"));
var r=new CargoRig("[Cargo]\nReturnFill=0.2\nDepartureFill=0.1999");Assert.Equal(.1999,r.Resources.DepartureFill,8);
}
[Fact] public void RisingCargoRevokesPermissionAndNeedsTwoNewScans()
{
var r=new CargoRig();r.Scan();r.Scan();Assert.True(r.Resources.DepartureReady);
r.Cargo.Set("CurrentVolume",(MyFixedPoint)6);r.Scan();Assert.False(r.Resources.DepartureReady);
r.Cargo.Set("CurrentVolume",(MyFixedPoint)5);r.Scan();Assert.False(r.Resources.DepartureReady);r.Scan();Assert.True(r.Resources.DepartureReady);
}
[Fact] public void AllMinerInventoryVolumeCountsIncludingSuppliesButMotherCargoIsExcluded()
{
var r=new CargoRig();r.Items.Clear();
object component=default(MyInventoryItem);typeof(MyInventoryItem).GetField("Type").SetValue(component,MyItemType.MakeComponent("SteelPlate"));
typeof(MyInventoryItem).GetField("Amount").SetValue(component,(MyFixedPoint)1);r.Items.Add((MyInventoryItem)component);
var supplies=CargoRig.Store(10,100,new List<MyInventoryItem>());r.Add(supplies,r.Program.Me.CubeGrid);
r.Add(CargoRig.Store(100000,100000,new List<MyInventoryItem>{Ore("Gold",100)}),new Stub<IMyCubeGrid>().Value);
r.Scan();r.Scan();Assert.True(r.Resources.Empty);Assert.False(r.Resources.DepartureReady);Assert.Equal(.075,r.Resources.Cargo,8);Assert.Equal(0,r.Resources.OreAmount);
supplies.Set("CurrentVolume",(MyFixedPoint)0);r.Scan();r.Scan();Assert.True(r.Resources.DepartureReady);Assert.Equal(.025,r.Resources.Cargo,8);
}
[Fact] public void ZeroDepartureFillRestoresOreOnlyEmptyRuleAndKeepsSupplyVolume()
{
var r=new CargoRig("[Cargo]\nDepartureFill=0");r.Cargo.Set("CurrentVolume",(MyFixedPoint)50);r.Scan();r.Scan();Assert.False(r.Resources.DepartureReady);
r.Items.Clear();r.Scan();Assert.False(r.Resources.DepartureReady);r.Scan();Assert.True(r.Resources.Empty);Assert.True(r.Resources.DepartureReady);
Assert.Equal(.5,r.Resources.Cargo,8);string reason;Assert.True(r.CanDepart(out reason));
}
[Fact] public void StrictOreUnloadCannotLaunchAtTheReturnThresholdWithSupplies()
{
var r=new CargoRig("[Cargo]\nDepartureFill=0");r.Items.Clear();r.Cargo.Set("CurrentVolume",(MyFixedPoint)95);r.Scan();r.Scan();
Assert.True(r.Resources.Empty);Assert.True(r.Resources.DepartureReady);string reason;Assert.False(r.CanDepart(out reason));Assert.Equal(P.L.MinerCargoFull,reason);
}
[Fact] public void ScanFaultRescanReloadAndStaleDataCannotKeepDeparturePermission()
{
var r=new CargoRig();r.Scan();r.Scan();Assert.True(r.Resources.DepartureReady);
r.Cargo.Set("ItemCount",513);r.Scan();Assert.False(r.Resources.DepartureReady);Assert.NotEmpty(r.Resources.ScanProblem);
r.Cargo.Set("ItemCount",1);r.Resources.ResetInventoryScan();r.Scan();Assert.False(r.Resources.DepartureReady);r.Scan();Assert.True(r.Resources.DepartureReady);
var stored=new MyIni();r.Resources.Save(stored);r.Resources.Load(stored);Assert.False(r.Resources.DepartureReady);
r.Scan();Assert.False(r.Resources.DepartureReady);r.Scan();Assert.True(r.Resources.DepartureReady);
r.Program.Now+=1;string reason;Assert.False(r.CanDepart(out reason));Assert.False(r.Resources.DepartureReady);
r.Scan();Assert.False(r.Resources.DepartureReady);r.Scan();Assert.True(r.Resources.DepartureReady);
}
[Fact] public void DisconnectAndInventoryExceptionsImmediatelyRevokeDeparturePermission()
{
var r=new CargoRig();r.Scan();r.Scan();r.Connector.Set("Status",MyShipConnectorStatus.Unconnected);r.Resources.Service(.1);Assert.False(r.Resources.DepartureReady);
r.Connector.Set("Status",MyShipConnectorStatus.Connected);r.Scan();Assert.False(r.Resources.DepartureReady);r.Scan();Assert.True(r.Resources.DepartureReady);
r.Cargo.Method("GetItems",a=>{throw new InvalidOperationException("broken inventory");});r.Scan();Assert.False(r.Resources.DepartureReady);Assert.Equal("broken inventory",r.Resources.ScanProblem);
}
[Fact] public void InvalidInventoryMassOrBlockedDrillCannotPassTheSmallCargoGate()
{
var r=new CargoRig();r.Scan();r.Scan();r.Cargo.Set("CurrentMass",(MyFixedPoint)(-1));r.Scan();Assert.False(r.Resources.DepartureReady);
r.Cargo.Set("CurrentMass",(MyFixedPoint)1);var drill=CargoRig.Store(.99,1,new List<MyInventoryItem>());r.Add(drill,r.Program.Me.CubeGrid,true);
r.Cargo.Set("CurrentVolume",(MyFixedPoint)0);r.Scan();r.Scan();Assert.True(r.Resources.Cargo<.05);Assert.False(r.Resources.DepartureReady);Assert.Equal(P.L.MinerDrillBlocked,r.Resources.DepartureWaitReason);
}
[Fact] public void FivePercentCargoDoesNotBypassBatteryOrHydrogenService()
{
var r=new CargoRig();r.Resources.NeedsCharge=true;r.Battery.Set("CurrentStoredPower",.5f);r.Scan();r.Scan();Assert.True(r.Resources.DepartureReady);
string reason;Assert.False(r.CanDepart(out reason));Assert.Equal(P.L.MinerWaitChargeTarget,reason);
r.Battery.Set("CurrentStoredPower",1f);r.Hardware.UsesHydrogen=true;
r.Hardware.Tanks.Add(new Stub<IMyGasTank>().Set("CubeGrid",r.Program.Me.CubeGrid).Set("IsFunctional",true).Set("Capacity",1000f).Set("FilledRatio",.5d).Value);
r.Resources.NeedsHydrogen=true;r.Scan();Assert.False(r.CanDepart(out reason));Assert.Equal(P.L.MinerWaitHydrogenTarget,reason);
}
[Theory][InlineData(float.NaN)][InlineData(float.PositiveInfinity)][InlineData(-1f)]
public void InvalidBatteryObservationsRevokeBothScanConfirmations(float stored)
{
var r=new CargoRig();r.Scan();r.Scan();Assert.True(r.Resources.DepartureReady);r.Battery.Set("CurrentStoredPower",stored);
Assert.Throws<InvalidOperationException>(()=>r.Scan());Assert.False(r.Resources.DepartureReady);
r.Battery.Set("CurrentStoredPower",1f);r.Scan();Assert.False(r.Resources.DepartureReady);r.Scan();Assert.True(r.Resources.DepartureReady);
}
[Fact] public void InvalidHydrogenRatioCannotBypassDepartureEnergyChecks()
{
var r=new CargoRig();r.Scan();r.Scan();r.Hardware.UsesHydrogen=true;
r.Hardware.Tanks.Add(new Stub<IMyGasTank>().Set("CubeGrid",r.Program.Me.CubeGrid).Set("IsFunctional",true).Set("Capacity",1000f).Set("FilledRatio",double.NaN).Value);
Assert.Throws<InvalidOperationException>(()=>r.Scan());Assert.False(r.Resources.DepartureReady);string reason;Assert.False(r.CanDepart(out reason));
}
}
}