using System; using System.Collections.Generic; using System.Reflection; using Sandbox.ModAPI.Ingame; using VRage; using VRage.Game; using VRage.Game.ModAPI.Ingame; using VRageMath; using Xunit; using P = AutoMiningScript.Program; namespace AutoMiningScript.Tests { public class DepartureMassTests { sealed class Rig { public readonly P Program; public readonly P.ShipHardware Hardware; public readonly P.Resources Resources; public readonly P.FlightController Flight; public readonly Stub Grid; public readonly Stub Connector; public readonly Stub Runtime; public readonly Stub World; public readonly Stub Body, Armour; public readonly Stub Cargo, Drill; public int Cells, PhysicsReads; public Rig(string departure = "0") { Program = TestRig.Program("[Flight]\nDepartureMass=" + departure); Grid = new Stub().Set("EntityId", 10L).Set("Min", Vector3I.Zero).Set("Max", new Vector3I(2, 0, 0)); TestRig.SetBase(Program, "Me", new Stub().Set("CubeGrid", Grid.Value).Value); Runtime = new Stub().Set("MaxInstructionCount", 50000).Set("CurrentInstructionCount", 0); TestRig.SetBase(Program, "Runtime", Runtime.Value); World = new Stub().Set("InventoryMultiplier", 1f); TestRig.SetBase(Program, "World", World.Value); Body = new Stub().Set("CubeGrid", Grid.Value).Set("Position", Vector3I.Zero).Set("Mass", 1000f); Armour = new Stub().Set("CubeGrid", Grid.Value).Set("Position", new Vector3I(2, 0, 0)).Set("Mass", 200f); Grid.Method("GetCubeBlock", args => { Cells++; return ((Vector3I)args[0]).X == 2 ? Armour.Value : Body.Value; }); Hardware = new P.ShipHardware(Program); Connector = TestRig.Connector(Program, true); Hardware.Connector = Connector.Value; ((List)typeof(P.ShipHardware).GetField("connectors", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(Hardware)).Add(Connector.Value); Hardware.Controller = new Stub().Set("CubeGrid", Grid.Value).Set("WorldMatrix", MatrixD.Identity) .Method("CalculateShipMass", args => { PhysicsReads++; return new MyShipMass(3000, 9000000, 9000000); }).Value; foreach (Vector3D direction in new[] { Vector3D.Right, Vector3D.Left, Vector3D.Up, Vector3D.Down, Vector3D.Forward, Vector3D.Backward }) { var basis = MatrixD.CreateWorld(Vector3D.Zero, -direction, Math.Abs(direction.Y) > .9 ? Vector3D.Forward : Vector3D.Up); Hardware.Thrusters.Add(new Stub().Set("CubeGrid", Grid.Value).Set("Enabled", true).Set("IsFunctional", true) .Set("MaxEffectiveThrust", 10000000f).Set("WorldMatrix", basis).Value); } Cargo = Inventory(100); Drill = Inventory(200); AddInventory(Cargo, Grid.Value); AddInventory(Drill, Grid.Value); Resources = new P.Resources(Program, Hardware); Flight = new P.FlightController(Program, Hardware); } public static Stub Inventory(double mass) { return new Stub().Set("CurrentMass", (MyFixedPoint)mass).Set("MaxVolume", (MyFixedPoint)100); } public void AddInventory(Stub inventory, IMyCubeGrid grid) { Hardware.InventoryBlocks.Add(new Stub().Set("CubeGrid", grid).Set("HasInventory", true).Set("InventoryCount", 1) .Method("GetInventory", args => inventory.Value).Value); } public void InventoryScan() { Resources.Update(.5, P.FlightState.Servicing, 0, 0); } public void Measure() { InventoryScan(); Hardware.UpdateDepartureMass(); } public bool Supports() { return Flight.CanSupport(Vector3D.Forward, Vector3D.Up, .3); } public double ControlMass { get { return (double)typeof(P.FlightController).GetField("mass", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(Flight); } } } [Fact] public void DockedMeasurementIncludesArmourCountsMulticellBlocksOnceAndExcludesMother() { var r = new Rig(); r.AddInventory(Rig.Inventory(9000000), new Stub().Value); r.Measure(); Assert.Equal(1500, r.Hardware.AutoDepartureMass); Assert.True(r.Supports()); Assert.Equal(1500, r.ControlMass); Assert.Equal(0, r.PhysicsReads); Assert.Equal(MyShipConnectorStatus.Connected, r.Connector.Value.Status); } [Fact] public void EachCompleteInventorySnapshotChangesMeasuredMassWithoutUsingOldFlightMass() { var r = new Rig(); r.Flight.LastFlightMass = 9000000; r.Measure(); r.Cargo.Set("CurrentMass", (MyFixedPoint)700); r.Program.Now = .5; r.InventoryScan(); Assert.Equal(2100, r.Hardware.AutoDepartureMass); Assert.True(r.Supports()); Assert.Equal(2100, r.ControlMass); r.Drill.Set("CurrentMass", (MyFixedPoint)0); r.Cargo.Set("CurrentMass", (MyFixedPoint)0); r.InventoryScan(); Assert.Equal(1200, r.Hardware.AutoDepartureMass); } [Fact] public void WorldInventoryMultiplierConvertsCargoToPhysicalMass() { var r = new Rig(); r.World.Set("InventoryMultiplier", 10f); r.Measure(); Assert.Equal(1230, r.Hardware.AutoDepartureMass); Assert.True(r.Supports()); Assert.Equal(1230, r.ControlMass); } [Fact] public void PositiveSettingRemainsAnExplicitDepartureOverride() { var r = new Rig("5000"); r.Body.Set("Mass", float.NaN); r.Measure(); Assert.True(r.Supports()); Assert.Equal(5000, r.ControlMass); Assert.Equal(0, r.Cells); Assert.Equal(0, r.PhysicsReads); } [Theory] [InlineData("NaN")][InlineData("Infinity")][InlineData("-1")][InlineData("10000000001")] public void InvalidConfiguredMassIsRejected(string value) { var r = new Rig(value); Assert.Throws(() => r.Hardware.UpdateDepartureMass()); } [Fact] public void GridMeasurementIsBoundedAndNeverExposesPartialMass() { var r = new Rig(); r.Grid.Set("Max", new Vector3I(256, 0, 0)); r.Measure(); Assert.Equal(128, r.Cells); Assert.Equal(0, r.Hardware.AutoDepartureMass); Assert.False(r.Supports()); r.Hardware.UpdateDepartureMass(); Assert.Equal(256, r.Cells); Assert.Equal(0, r.Hardware.AutoDepartureMass); r.Hardware.UpdateDepartureMass(); Assert.Equal(257, r.Cells); Assert.Equal(1500, r.Hardware.AutoDepartureMass); } [Fact] public void HighInstructionUseDefersMassScanUntilTheNextInvocation() { var r = new Rig(); r.Runtime.Set("CurrentInstructionCount", 23000); r.Measure(); Assert.Equal(0, r.Cells); Assert.False(r.Supports()); r.Runtime.Set("CurrentInstructionCount", 0); r.Hardware.UpdateDepartureMass(); Assert.Equal(1500, r.Hardware.AutoDepartureMass); Assert.True(r.Supports()); } [Fact] public void StaleOrInvalidInventorySnapshotCannotAuthorizeDeparture() { var r = new Rig(); r.Measure(); r.Program.Now = 1; Assert.Equal(0, r.Hardware.AutoDepartureMass); Assert.False(r.Supports()); Assert.Equal(P.L.MinerAutoMassInventoryPending, r.Flight.Problem); r.InventoryScan(); Assert.True(r.Supports()); r.Cargo.Set("CurrentMass", (MyFixedPoint)(-1)); r.InventoryScan(); Assert.False(r.Supports()); } [Theory][InlineData(float.NaN)][InlineData(float.PositiveInfinity)][InlineData(-1f)] public void InvalidBlockMassStaysDocked(float value) { var r = new Rig(); r.Body.Set("Mass", value); r.Measure(); Assert.False(r.Supports()); Assert.Equal(P.L.MinerAutoMassInvalid, r.Flight.Problem); Assert.Equal(MyShipConnectorStatus.Connected, r.Connector.Value.Status); } [Theory][InlineData(float.NaN)][InlineData(0f)][InlineData(-1f)] public void InvalidWorldMultiplierCannotYieldAValidMeasurement(float value) { var r = new Rig(); r.World.Set("InventoryMultiplier", value); r.Measure(); Assert.False(r.Supports()); } [Fact] public void OversizedGridIsRejectedWithoutAnUnboundedScan() { var r = new Rig(); r.Grid.Set("Max", new Vector3I(1000000, 0, 0)); r.Measure(); Assert.Equal(0, r.Cells); Assert.False(r.Supports()); Assert.Equal(P.L.MinerAutoMassGridTooLarge, r.Flight.Problem); } [Fact] public void RescanAndRedockingRequireFreshGeometry() { var r = new Rig(); r.Measure(); r.Hardware.InvalidateDepartureMass(); Assert.Equal(0, r.Hardware.AutoDepartureMass); r.Body.Set("Mass", 2000f); r.Hardware.UpdateDepartureMass(); Assert.Equal(2500, r.Hardware.AutoDepartureMass); r.Connector.Set("Status", MyShipConnectorStatus.Unconnected); r.Hardware.UpdateDepartureMass(); Assert.Equal(0, r.Hardware.AutoDepartureMass); r.Connector.Set("Status", MyShipConnectorStatus.Connected); r.Body.Set("Mass", 3000f); r.Hardware.UpdateDepartureMass(); Assert.Equal(3500, r.Hardware.AutoDepartureMass); } [Fact] public void GeometryIsRecheckedAfterFiveSecondsEvenWhenBoundsAreUnchanged() { var r = new Rig(); r.Measure(); r.Program.Now = 6; r.Body.Set("Mass", 2000f); r.Measure(); Assert.Equal(2500, r.Hardware.AutoDepartureMass); Assert.Equal(6, r.Cells); } [Fact] public void FreeFlightUsesLivePhysicsInsteadOfTheDepartureOverride() { var r = new Rig("5000"); r.Measure(); r.Connector.Set("Status", MyShipConnectorStatus.Unconnected); Assert.True(r.Supports()); Assert.Equal(9000000, r.ControlMass); Assert.Equal(1, r.PhysicsReads); } static MyInventoryItem Ore(double amount) { object item = default(MyInventoryItem); typeof(MyInventoryItem).GetField("Type").SetValue(item, MyItemType.MakeOre("Iron")); typeof(MyInventoryItem).GetField("Amount").SetValue(item, (MyFixedPoint)amount); return (MyInventoryItem)item; } [Fact] public void InventoryLimitPreservesLastCompleteSampleAndClearsDeparturePermission() { var r = new Rig(); double amount = 10; r.Cargo.Method("GetItems", args => { ((List)args[0]).Add(Ore(amount)); return null; }); r.Measure(); Assert.Equal(10, r.Resources.Ores["Iron"]); r.Resources.Empty = true; amount = 999; r.Drill.Set("ItemCount", 513); r.InventoryScan(); Assert.Equal(P.L.MinerInventoryScanLimit, r.Resources.ScanProblem); Assert.Equal(10, r.Resources.Ores["Iron"]); Assert.Equal(10, r.Resources.OreAmount); Assert.False(r.Resources.Empty); Assert.False(r.Resources.SamplingValid); Assert.Equal(0, r.Hardware.AutoDepartureMass); Assert.False(r.Supports()); } [Fact] public void InventoryCountLimitCannotRetainAnEmptyPermit() { var r = new Rig(); r.Measure(); r.InventoryScan(); Assert.True(r.Resources.Empty); for (int i = 0; i < 127; i++) r.AddInventory(Rig.Inventory(0), r.Grid.Value); r.InventoryScan(); Assert.Equal(P.L.MinerInventoryScanLimit, r.Resources.ScanProblem); Assert.False(r.Resources.Empty); Assert.False(r.Supports()); } [Fact] public void OverflowLatchedUntilRescanDoesNotKeepThrowingOrDisableReturnChecks() { var r = new Rig(); r.Measure(); r.Drill.Set("ItemCount", 513); r.InventoryScan(); string reason; Assert.True(r.Resources.MustReturn(out reason)); Assert.Equal(P.L.MinerInventoryScanLimit, reason); Assert.False(r.Resources.ReadyForSortie(0, 0, out reason)); r.Drill.Set("ItemCount", 0); r.InventoryScan(); Assert.Equal(P.L.MinerInventoryScanLimit, r.Resources.ScanProblem); r.Resources.ResetInventoryScan(); r.InventoryScan(); Assert.Equal("", r.Resources.ScanProblem); Assert.False(r.Resources.Empty); r.InventoryScan(); Assert.True(r.Resources.Empty); Assert.Equal(1500, r.Hardware.AutoDepartureMass); } } }