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

AutoMiningScript

【SpaceEngineer】全自动挖矿脚本

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