using System; using System.Collections; using System.Collections.Generic; using System.Reflection; using Sandbox.ModAPI.Ingame; using SpaceEngineers.Game.ModAPI.Ingame; using VRage; using VRage.Game.ModAPI.Ingame; using VRageMath; using Xunit; using P = AutoMiningScript.Program; namespace AutoMiningScript.Tests { public class CargoInventoryTests { static MyInventoryItem Item(string kind, string subtype, double amount) { object item = default(MyInventoryItem); var type = kind == "Ore" ? MyItemType.MakeOre(subtype) : kind == "Ingot" ? MyItemType.MakeIngot(subtype) : MyItemType.MakeComponent(subtype); typeof(MyInventoryItem).GetField("Type").SetValue(item, type); typeof(MyInventoryItem).GetField("Amount").SetValue(item, (MyFixedPoint)amount); return (MyInventoryItem)item; } static Stub Inventory(double volume, double capacity, double mass, List items) { return new Stub().Set("CurrentMass", (MyFixedPoint)mass) .Set("CurrentVolume", (MyFixedPoint)volume).Set("MaxVolume", (MyFixedPoint)capacity) .Method("get_ItemCount", a => items.Count) .Method("GetItemAmount", a => { MyFixedPoint amount=0; foreach(var item in items)if(item.Type.Equals((MyItemType)a[0]))amount+=item.Amount; return amount; }) .Method("GetItems", a => { ((List)a[0]).AddRange(items); return null; }); } static IMyTerminalBlock Block(IMyCubeGrid grid, Stub inventory) where T : class, IMyTerminalBlock { return new Stub().Set("CubeGrid", grid).Set("HasInventory", true).Set("InventoryCount", 1) .Method("GetInventory", a => inventory.Value).Value; } sealed class Rig { public readonly P Program; public readonly P.ShipHardware Hardware; public readonly P.Resources Resources; public readonly List CargoItems = new List(); public readonly Stub Cargo; public Rig(string config = "") { Program = TestRig.Program(config); Hardware = new P.ShipHardware(Program); Hardware.Connector = TestRig.Connector(Program, true).Value; Hardware.Batteries.Add(TestRig.Battery(Program, 1, 1).Value); Cargo = Inventory(0, 100, 0, CargoItems); Hardware.InventoryBlocks.Add(Block(Program.Me.CubeGrid, Cargo)); Resources = new P.Resources(Program, Hardware); } public Stub Add(double volume, double capacity, double mass, params MyInventoryItem[] items) where T : class, IMyTerminalBlock { var inventory = Inventory(volume, capacity, mass, new List(items)); Hardware.InventoryBlocks.Add(Block(Program.Me.CubeGrid, inventory)); return inventory; } public void Scan(P.FlightState state = P.FlightState.Servicing) { Program.Now += .5; Resources.Update(.5, state, 0, 0); } } [Theory] [InlineData(0)] [InlineData(.05)] public void FunctionalFuelAndProductionSuppliesDoNotRequireUnloadingOrBlockDeparture(double departureFill) { var r = new Rig("[Cargo]\nDepartureFill=" + departureFill.ToString(System.Globalization.CultureInfo.InvariantCulture)); r.Add(100, 100, 200, Item("Ingot", "Uranium", 200)); r.Add(100, 100, 300, Item("Ore", "Ice", 300)); r.Add(100, 100, 400, Item("Ore", "Iron", 400)); r.Add(100, 100, 100, Item("Component", "SteelPlate", 100)); r.Scan(); Assert.False(r.Resources.Empty); Assert.False(r.Resources.DepartureReady); r.Scan(); r.Resources.Service(.5); Assert.Equal(0, r.Resources.Cargo); Assert.Equal(0, r.Resources.OreAmount); Assert.Empty(r.Resources.Ores); Assert.True(r.Resources.Empty); Assert.Equal(0, r.Resources.ServiceFlags); Assert.True(r.Resources.DepartureReady); string reason; Assert.True(r.Resources.ReadyForSortie(10, 1, out reason)); } [Fact] public void FunctionalInventoryCapacityCannotHideFullTransportCargo() { var r = new Rig(); r.Cargo.Set("CurrentVolume", (MyFixedPoint)98); r.Add(0, 10000, 0); r.Scan(); r.Scan(); Assert.Equal(.98, r.Resources.Cargo, 8); Assert.False(r.Resources.DepartureReady); string reason; Assert.True(r.Resources.MustReturn(out reason)); Assert.Equal(P.L.MinerCargoFull, reason); } [Fact] public void TransportedOresIncludeSorterBuffersAndStillExcludeGeneratorSupplies() { var r = new Rig(); r.CargoItems.Add(Item("Ore", "Ice", 30)); var drill = new List { Item("Ore", "Stone", 20) }; var port = new List { Item("Ore", "Uranium", 10) }; var sorter = new List { Item("Ore", "Cobalt", 40) }; r.Hardware.InventoryBlocks.Add(Block(r.Program.Me.CubeGrid, Inventory(1, 100, 20, drill))); r.Hardware.InventoryBlocks.Add(Block(r.Program.Me.CubeGrid, Inventory(1, 100, 10, port))); r.Hardware.InventoryBlocks.Add(Block(r.Program.Me.CubeGrid, Inventory(1, 100, 40, sorter))); r.Add(100, 100, 500, Item("Ore", "Ice", 500)); r.Scan(); Assert.Equal(100, r.Resources.OreAmount); Assert.Equal(30, r.Resources.Ores["Ice"]); Assert.Equal(40,r.Resources.Ores["Cobalt"]); Assert.Equal(1, r.Resources.ServiceFlags); Assert.False(r.Resources.Empty); r.CargoItems.Clear(); drill.Clear(); port.Clear(); sorter.Clear(); r.Scan(); r.Scan(); Assert.Equal(0, r.Resources.OreAmount); Assert.Equal(0, r.Resources.ServiceFlags); Assert.True(r.Resources.Empty); } [Theory] [InlineData(0,false)] [InlineData(.05,true)] public void SorterOnlyOreIsNotEmptyAndFollowsTheConfiguredDepartureThreshold(double departureFill,bool departureReady) { var r=new Rig("[Cargo]\nDepartureFill="+departureFill.ToString(System.Globalization.CultureInfo.InvariantCulture)); var contents=new List {Item("Ore","Stone",1729.732172)}; var inventory=Inventory(.639999,1,1729.732172,contents); r.Hardware.InventoryBlocks.Add(Block(r.Program.Me.CubeGrid,inventory)); r.Scan();r.Scan(); Assert.Equal(1729.732172,r.Resources.OreAmount,6);Assert.False(r.Resources.Empty); Assert.Equal(1,r.Resources.ServiceFlags);Assert.Equal(.639999/101,r.Resources.Cargo,8); Assert.Equal(departureReady,r.Resources.DepartureReady); contents.Clear();inventory.Set("CurrentVolume",(MyFixedPoint)0).Set("CurrentMass",(MyFixedPoint)0); r.Scan();Assert.False(r.Resources.Empty);r.Scan(); Assert.True(r.Resources.Empty);Assert.True(r.Resources.DepartureReady);Assert.Equal(0,r.Resources.ServiceFlags); } [Theory] [InlineData(1)] [InlineData(10)] public void DepartureMassStillIncludesReactorFuelAndGeneratorIce(float multiplier) { var r = new Rig(); var grid = new Stub().Set("EntityId", 10L).Set("Min", Vector3I.Zero).Set("Max", Vector3I.Zero); var body = new Stub().Set("CubeGrid", grid.Value).Set("Position", Vector3I.Zero).Set("Mass", 1000f); grid.Method("GetCubeBlock", a => body.Value); TestRig.SetBase(r.Program, "Me", new Stub().Set("CubeGrid", grid.Value).Value); TestRig.SetBase(r.Program, "World", new Stub().Set("InventoryMultiplier", multiplier).Value); r.Hardware.Connector = TestRig.Connector(r.Program, true).Value; ((List)typeof(P.ShipHardware).GetField("connectors", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(r.Hardware)).Add(r.Hardware.Connector); r.Hardware.InventoryBlocks.Clear(); r.Add(0, 100, 100); r.Add(100, 100, 200, Item("Ingot", "Uranium", 200)); r.Add(100, 100, 300, Item("Ore", "Ice", 300)); r.Scan(); r.Hardware.UpdateDepartureMass(); Assert.Equal(1000 + 600 / multiplier, r.Hardware.AutoDepartureMass, 6); Assert.Equal(0, r.Resources.Cargo); Assert.Equal(0, r.Resources.OreAmount); } [Fact] public void InvalidFunctionalInventoryMassStillRevokesDeparturePermission() { var r = new Rig(); var fuel = r.Add(100, 100, 200, Item("Ingot", "Uranium", 200)); r.Scan(); r.Scan(); Assert.True(r.Resources.DepartureReady); fuel.Set("CurrentMass", (MyFixedPoint)(-1)); r.Scan(); Assert.False(r.Resources.DepartureReady); Assert.Equal(-1, r.Resources.ServiceFlags); } [Fact] public void ConsumingFunctionalSuppliesDoesNotCorruptTheMinedOreSample() { var r = new Rig(); r.Hardware.Connector = TestRig.Connector(r.Program, false).Value; r.Hardware.SamplingIsolated = true; r.CargoItems.Add(Item("Ore", "Iron", 10)); var ice = new List { Item("Ore", "Ice", 500) }; r.Hardware.InventoryBlocks.Add(Block(r.Program.Me.CubeGrid, Inventory(100, 100, 500, ice))); r.Scan(P.FlightState.Drilling); r.Resources.ResetSampleValidation(); ice.Clear(); ice.Add(Item("Ore", "Ice", 400)); r.Scan(P.FlightState.Drilling); Assert.True(r.Resources.SamplingValid); Assert.Equal(10, r.Resources.OreAmount); Assert.False(r.Resources.Ores.ContainsKey("Ice")); } [Theory] [InlineData(false)] [InlineData(true)] public void FleetUnloadsTransportOreWithoutReadingOrRemovingFunctionalSupplies(bool cached) { var r = new FleetStateTests.Rig(1, true); r.RegisterAll(); var minerGrid = new Stub().Set("EntityId", 1000L).Value; var blocks = new List(); int cargoTransfers = 0, supplyReads = 0, supplyTransfers = 0; var received = new List(); var destination = Inventory(0, 1000, 0, received); ((List)typeof(P.FleetController).GetField("cargo", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(r.Fleet)) .Add(Block(r.Program.Me.CubeGrid, destination)); foreach (var type in new[] { typeof(IMyCargoContainer), typeof(IMyShipDrill), typeof(IMyShipConnector), typeof(IMyConveyorSorter), typeof(IMyReactor), typeof(IMyGasGenerator), typeof(IMyRefinery) }) { bool transport = type == typeof(IMyCargoContainer) || type == typeof(IMyShipDrill) || type == typeof(IMyShipConnector) || type == typeof(IMyConveyorSorter); var contents = new List { Item("Ore", "Ice", 10), Item("Ingot", "Uranium", 20), Item("Component", "SteelPlate", 30) }; var inventory = Inventory(1, 100, 10, contents).Method("CanTransferItemTo", a => true) .Method("GetItems", a => { if (!transport) supplyReads++; ((List)a[0]).AddRange(contents); return null; }) .Method("TransferItemTo", a => { if (transport) cargoTransfers++; else supplyTransfers++; var index=(int)a[1];received.Add(contents[index]);contents.RemoveAt(index);return true; }); blocks.Add((IMyTerminalBlock)typeof(CargoInventoryTests).GetMethod("Block", BindingFlags.NonPublic | BindingFlags.Static) .MakeGenericMethod(type).Invoke(null, new object[] { minerGrid, inventory })); } if (cached) ((Dictionary>)typeof(P.FleetController).GetField("connectedBlocks", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(r.Fleet))[1000] = blocks; else TestRig.SetBase(r.Program, "GridTerminalSystem", new Stub().Method("GetBlocksOfType", a => { var list = (IList)a[0]; var filter = a[1] as Delegate; foreach (var block in blocks) if (filter == null || (bool)filter.DynamicInvoke(block)) list.Add(block); return null; }).Value); typeof(P.FleetController).GetMethod("UnloadOne", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(r.Fleet, new object[0]); Assert.Equal(4, cargoTransfers); Assert.Equal(0, supplyReads); Assert.Equal(0, supplyTransfers); Assert.Equal(40, (double)destination.Value.GetItemAmount(MyItemType.MakeOre("Ice"))); Assert.Equal(0, (double)destination.Value.GetItemAmount(MyItemType.MakeIngot("Uranium"))); Assert.Equal(0, (double)destination.Value.GetItemAmount(MyItemType.MakeComponent("SteelPlate"))); } sealed class UnloadRig { public sealed class Sink { public Stub Inventory; public readonly List Items=new List(); public double Room; public int Calls; } public readonly FleetStateTests.Rig Fleet=new FleetStateTests.Rig(1,true); public readonly List Items=new List {Item("Ore","Stone",10)}; public readonly List Sinks=new List(); public readonly Stub Source; public bool ApiResult=true; public UnloadRig(bool sorter=false) { Fleet.RegisterAll(); Source=Inventory(10,100,10,Items).Method("CanTransferItemTo",a=>true).Method("TransferItemTo",a=> { foreach(var sink in Sinks)if(ReferenceEquals(sink.Inventory.Value,a[0])) { sink.Calls++; // SE reports an accepted transfer request even when its actual amount is zero. int index=(int)a[1];var item=Items[index];double moved=Math.Min((double)item.Amount,sink.Room); if(moved>0) { sink.Room-=moved;sink.Items.Add(Item("Ore",item.Type.SubtypeId,moved)); if(moved==(double)item.Amount)Items.RemoveAt(index); else Items[index]=Item("Ore",item.Type.SubtypeId,(double)item.Amount-moved); } return ApiResult; } throw new InvalidOperationException("Unknown transfer destination"); }); var grid=new Stub().Set("EntityId",1000L).Value; Field>>("connectedBlocks")[1000]=new List {sorter?Block(grid,Source):Block(grid,Source)}; } T Field(string name) => (T)typeof(P.FleetController).GetField(name,BindingFlags.NonPublic|BindingFlags.Instance).GetValue(Fleet.Fleet); public Sink Add(double room) { var sink=new Sink {Room=room};sink.Inventory=Inventory(room==0?100:0,room==0?100:room,0,sink.Items); Sinks.Add(sink);Field>("cargo").Add(Block(Fleet.Program.Me.CubeGrid,sink.Inventory));return sink; } public double LastProgress => Field>("unloadProgress")[Fleet.Miners[0].Id]; public void Run(double now) { Fleet.Program.Now=now; typeof(P.FleetController).GetMethod("UnloadOne",BindingFlags.NonPublic|BindingFlags.Instance).Invoke(Fleet.Fleet,new object[0]); } public double Remaining => (double)Source.Value.GetItemAmount(MyItemType.MakeOre("Stone")); public static double Received(Sink sink) => (double)sink.Inventory.Value.GetItemAmount(MyItemType.MakeOre("Stone")); } [Fact] public void SuccessfulNoOpAtFullInventoryContinuesToTheNextEmptyInventory() { var r=new UnloadRig();var full=r.Add(0);var empty=r.Add(100); r.Run(50); Assert.Equal(1,full.Calls);Assert.Equal(1,empty.Calls); Assert.Equal(0,UnloadRig.Received(full));Assert.Equal(10,UnloadRig.Received(empty)); Assert.Equal(0,r.Remaining);Assert.Equal(50,r.LastProgress); } [Theory] [InlineData(true)] [InlineData(false)] public void NoTransferredOreNeverRefreshesProgressRegardlessOfApiReturn(bool apiResult) { var r=new UnloadRig {ApiResult=apiResult};var full=r.Add(0); r.Run(50); Assert.Equal(1,full.Calls);Assert.Equal(10,r.Remaining);Assert.Equal(0,r.LastProgress); Assert.Contains(P.L.F(P.L.FleetUnloadBlocked,r.Fleet.Miners[0].Id),r.Fleet.Fleet.Diagnostics); } [Fact] public void PartialTransfersCountAsProgressWithoutRequiringTheWholeStackToFit() { var r=new UnloadRig();var small=r.Add(3);var second=r.Add(100); r.Run(50); Assert.Equal(3,UnloadRig.Received(small));Assert.Equal(7,r.Remaining); Assert.Equal(0,second.Calls);Assert.Equal(50,r.LastProgress); r.Run(60); Assert.Equal(2,small.Calls);Assert.Equal(7,UnloadRig.Received(second)); Assert.Equal(0,r.Remaining);Assert.Equal(60,r.LastProgress); } [Fact] public void OreQuantityCheckHandlesSeveralStacksOfTheSameType() { var r=new UnloadRig();r.Items.Clear();r.Items.Add(Item("Ore","Stone",6));r.Items.Add(Item("Ore","Stone",4)); var empty=r.Add(100);r.Run(50); Assert.Equal(2,empty.Calls);Assert.Equal(10,UnloadRig.Received(empty)); Assert.Equal(0,r.Remaining);Assert.Equal(50,r.LastProgress); } [Fact] public void FleetEmptiesResidualSorterOreIntoTheNextAvailableMotherInventoryWithoutTakingUraniumIngots() { var r=new UnloadRig(true);r.Items.Clear(); r.Items.Add(Item("Ore","Stone",1729.732172));r.Items.Add(Item("Ingot","Uranium",20)); var full=r.Add(0);var receiver=r.Add(2000);r.Run(50); Assert.Equal(1,full.Calls);Assert.Equal(1,receiver.Calls); Assert.Equal(1729.732172,UnloadRig.Received(receiver),6);Assert.Equal(0,r.Remaining); Assert.Equal(20,(double)r.Source.Value.GetItemAmount(MyItemType.MakeIngot("Uranium"))); Assert.Equal(0,(double)receiver.Inventory.Value.GetItemAmount(MyItemType.MakeIngot("Uranium"))); Assert.Equal(50,r.LastProgress); } } }