XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

AutoMiningScript

【SpaceEngineer】全自动挖矿脚本

公开
关注 0 Fork 0 Star 0
UTF-8
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);
        }
    }
}