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<IMyInventory> Inventory(double volume, double capacity, double mass, List<MyInventoryItem> items)
{
return new Stub<IMyInventory>().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<MyInventoryItem>)a[0]).AddRange(items); return null; });
}
static IMyTerminalBlock Block<T>(IMyCubeGrid grid, Stub<IMyInventory> inventory) where T : class, IMyTerminalBlock
{
return new Stub<T>().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<MyInventoryItem> CargoItems = new List<MyInventoryItem>();
public readonly Stub<IMyInventory> 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<IMyCargoContainer>(Program.Me.CubeGrid, Cargo));
Resources = new P.Resources(Program, Hardware);
}
public Stub<IMyInventory> Add<T>(double volume, double capacity, double mass, params MyInventoryItem[] items) where T : class, IMyTerminalBlock
{
var inventory = Inventory(volume, capacity, mass, new List<MyInventoryItem>(items));
Hardware.InventoryBlocks.Add(Block<T>(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<IMyReactor>(100, 100, 200, Item("Ingot", "Uranium", 200));
r.Add<IMyGasGenerator>(100, 100, 300, Item("Ore", "Ice", 300));
r.Add<IMyRefinery>(100, 100, 400, Item("Ore", "Iron", 400));
r.Add<IMyShipWelder>(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<IMyGasGenerator>(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<MyInventoryItem> { Item("Ore", "Stone", 20) };
var port = new List<MyInventoryItem> { Item("Ore", "Uranium", 10) };
var sorter = new List<MyInventoryItem> { Item("Ore", "Cobalt", 40) };
r.Hardware.InventoryBlocks.Add(Block<IMyShipDrill>(r.Program.Me.CubeGrid, Inventory(1, 100, 20, drill)));
r.Hardware.InventoryBlocks.Add(Block<IMyShipConnector>(r.Program.Me.CubeGrid, Inventory(1, 100, 10, port)));
r.Hardware.InventoryBlocks.Add(Block<IMyConveyorSorter>(r.Program.Me.CubeGrid, Inventory(1, 100, 40, sorter)));
r.Add<IMyGasGenerator>(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<MyInventoryItem> {Item("Ore","Stone",1729.732172)};
var inventory=Inventory(.639999,1,1729.732172,contents);
r.Hardware.InventoryBlocks.Add(Block<IMyConveyorSorter>(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<IMyCubeGrid>().Set("EntityId", 10L).Set("Min", Vector3I.Zero).Set("Max", Vector3I.Zero);
var body = new Stub<IMySlimBlock>().Set("CubeGrid", grid.Value).Set("Position", Vector3I.Zero).Set("Mass", 1000f);
grid.Method("GetCubeBlock", a => body.Value);
TestRig.SetBase(r.Program, "Me", new Stub<IMyProgrammableBlock>().Set("CubeGrid", grid.Value).Value);
TestRig.SetBase(r.Program, "World", new Stub<IMyGridProgramWorldInfo>().Set("InventoryMultiplier", multiplier).Value);
r.Hardware.Connector = TestRig.Connector(r.Program, true).Value;
((List<IMyShipConnector>)typeof(P.ShipHardware).GetField("connectors", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(r.Hardware)).Add(r.Hardware.Connector);
r.Hardware.InventoryBlocks.Clear();
r.Add<IMyCargoContainer>(0, 100, 100);
r.Add<IMyReactor>(100, 100, 200, Item("Ingot", "Uranium", 200));
r.Add<IMyGasGenerator>(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<IMyReactor>(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<MyInventoryItem> { Item("Ore", "Ice", 500) };
r.Hardware.InventoryBlocks.Add(Block<IMyGasGenerator>(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<IMyCubeGrid>().Set("EntityId", 1000L).Value;
var blocks = new List<IMyTerminalBlock>(); int cargoTransfers = 0, supplyReads = 0, supplyTransfers = 0;
var received = new List<MyInventoryItem>();
var destination = Inventory(0, 1000, 0, received);
((List<IMyTerminalBlock>)typeof(P.FleetController).GetField("cargo", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(r.Fleet))
.Add(Block<IMyCargoContainer>(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<MyInventoryItem> { 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<MyInventoryItem>)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<long, List<IMyTerminalBlock>>)typeof(P.FleetController).GetField("connectedBlocks", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(r.Fleet))[1000] = blocks;
else
TestRig.SetBase(r.Program, "GridTerminalSystem", new Stub<IMyGridTerminalSystem>().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<IMyInventory> Inventory;
public readonly List<MyInventoryItem> Items=new List<MyInventoryItem>();
public double Room;
public int Calls;
}
public readonly FleetStateTests.Rig Fleet=new FleetStateTests.Rig(1,true);
public readonly List<MyInventoryItem> Items=new List<MyInventoryItem> {Item("Ore","Stone",10)};
public readonly List<Sink> Sinks=new List<Sink>();
public readonly Stub<IMyInventory> 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<IMyCubeGrid>().Set("EntityId",1000L).Value;
Field<Dictionary<long,List<IMyTerminalBlock>>>("connectedBlocks")[1000]=new List<IMyTerminalBlock> {sorter?Block<IMyConveyorSorter>(grid,Source):Block<IMyShipDrill>(grid,Source)};
}
T Field<T>(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<List<IMyTerminalBlock>>("cargo").Add(Block<IMyCargoContainer>(Fleet.Program.Me.CubeGrid,sink.Inventory));return sink;
}
public double LastProgress => Field<Dictionary<string,double>>("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);
}
}
}
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<IMyInventory> Inventory(double volume, double capacity, double mass, List<MyInventoryItem> items)
{
return new Stub<IMyInventory>().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<MyInventoryItem>)a[0]).AddRange(items); return null; });
}
static IMyTerminalBlock Block<T>(IMyCubeGrid grid, Stub<IMyInventory> inventory) where T : class, IMyTerminalBlock
{
return new Stub<T>().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<MyInventoryItem> CargoItems = new List<MyInventoryItem>();
public readonly Stub<IMyInventory> 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<IMyCargoContainer>(Program.Me.CubeGrid, Cargo));
Resources = new P.Resources(Program, Hardware);
}
public Stub<IMyInventory> Add<T>(double volume, double capacity, double mass, params MyInventoryItem[] items) where T : class, IMyTerminalBlock
{
var inventory = Inventory(volume, capacity, mass, new List<MyInventoryItem>(items));
Hardware.InventoryBlocks.Add(Block<T>(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<IMyReactor>(100, 100, 200, Item("Ingot", "Uranium", 200));
r.Add<IMyGasGenerator>(100, 100, 300, Item("Ore", "Ice", 300));
r.Add<IMyRefinery>(100, 100, 400, Item("Ore", "Iron", 400));
r.Add<IMyShipWelder>(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<IMyGasGenerator>(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<MyInventoryItem> { Item("Ore", "Stone", 20) };
var port = new List<MyInventoryItem> { Item("Ore", "Uranium", 10) };
var sorter = new List<MyInventoryItem> { Item("Ore", "Cobalt", 40) };
r.Hardware.InventoryBlocks.Add(Block<IMyShipDrill>(r.Program.Me.CubeGrid, Inventory(1, 100, 20, drill)));
r.Hardware.InventoryBlocks.Add(Block<IMyShipConnector>(r.Program.Me.CubeGrid, Inventory(1, 100, 10, port)));
r.Hardware.InventoryBlocks.Add(Block<IMyConveyorSorter>(r.Program.Me.CubeGrid, Inventory(1, 100, 40, sorter)));
r.Add<IMyGasGenerator>(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<MyInventoryItem> {Item("Ore","Stone",1729.732172)};
var inventory=Inventory(.639999,1,1729.732172,contents);
r.Hardware.InventoryBlocks.Add(Block<IMyConveyorSorter>(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<IMyCubeGrid>().Set("EntityId", 10L).Set("Min", Vector3I.Zero).Set("Max", Vector3I.Zero);
var body = new Stub<IMySlimBlock>().Set("CubeGrid", grid.Value).Set("Position", Vector3I.Zero).Set("Mass", 1000f);
grid.Method("GetCubeBlock", a => body.Value);
TestRig.SetBase(r.Program, "Me", new Stub<IMyProgrammableBlock>().Set("CubeGrid", grid.Value).Value);
TestRig.SetBase(r.Program, "World", new Stub<IMyGridProgramWorldInfo>().Set("InventoryMultiplier", multiplier).Value);
r.Hardware.Connector = TestRig.Connector(r.Program, true).Value;
((List<IMyShipConnector>)typeof(P.ShipHardware).GetField("connectors", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(r.Hardware)).Add(r.Hardware.Connector);
r.Hardware.InventoryBlocks.Clear();
r.Add<IMyCargoContainer>(0, 100, 100);
r.Add<IMyReactor>(100, 100, 200, Item("Ingot", "Uranium", 200));
r.Add<IMyGasGenerator>(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<IMyReactor>(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<MyInventoryItem> { Item("Ore", "Ice", 500) };
r.Hardware.InventoryBlocks.Add(Block<IMyGasGenerator>(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<IMyCubeGrid>().Set("EntityId", 1000L).Value;
var blocks = new List<IMyTerminalBlock>(); int cargoTransfers = 0, supplyReads = 0, supplyTransfers = 0;
var received = new List<MyInventoryItem>();
var destination = Inventory(0, 1000, 0, received);
((List<IMyTerminalBlock>)typeof(P.FleetController).GetField("cargo", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(r.Fleet))
.Add(Block<IMyCargoContainer>(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<MyInventoryItem> { 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<MyInventoryItem>)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<long, List<IMyTerminalBlock>>)typeof(P.FleetController).GetField("connectedBlocks", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(r.Fleet))[1000] = blocks;
else
TestRig.SetBase(r.Program, "GridTerminalSystem", new Stub<IMyGridTerminalSystem>().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<IMyInventory> Inventory;
public readonly List<MyInventoryItem> Items=new List<MyInventoryItem>();
public double Room;
public int Calls;
}
public readonly FleetStateTests.Rig Fleet=new FleetStateTests.Rig(1,true);
public readonly List<MyInventoryItem> Items=new List<MyInventoryItem> {Item("Ore","Stone",10)};
public readonly List<Sink> Sinks=new List<Sink>();
public readonly Stub<IMyInventory> 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<IMyCubeGrid>().Set("EntityId",1000L).Value;
Field<Dictionary<long,List<IMyTerminalBlock>>>("connectedBlocks")[1000]=new List<IMyTerminalBlock> {sorter?Block<IMyConveyorSorter>(grid,Source):Block<IMyShipDrill>(grid,Source)};
}
T Field<T>(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<List<IMyTerminalBlock>>("cargo").Add(Block<IMyCargoContainer>(Fleet.Program.Me.CubeGrid,sink.Inventory));return sink;
}
public double LastProgress => Field<Dictionary<string,double>>("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);
}
}
}