void driveTransferInjection(FlightCtrlState s)
        {
            if (part.vessel.orbit.eccentricity > 1)
            {
                endOperation();
                return;
            }

            if (getPredictedPostTransferPeR() != -1 && part.vessel.orbit.relativeInclination(transferTarget.orbit) < 1)
            {
                transState = TRANSState.LOWERING_PERIAPSIS;
                return;
            }

            if(part.vessel.orbit.ApR > transferTarget.orbit.PeR)
            {
                transState = TRANSState.WAITING_FOR_CORRECTION;
                return;
            }

            if ((TimeWarp.WarpMode == TimeWarp.Modes.HIGH) && (TimeWarp.CurrentRate > TimeWarp.MaxPhysicsRate)) core.warpMinimum(this);

            core.attitudeTo(Vector3d.forward, MechJebCore.AttitudeReference.ORBIT, this);

            if (core.attitudeAngleFromTarget() < 5) s.mainThrottle = 1.0F;
            else s.mainThrottle = 0.0F;
        }
        protected override void WindowGUI(int windowID)
        {
            GUILayout.BeginVertical();

            GUIStyle normal = new GUIStyle(GUI.skin.label);
            GUIStyle yellow = new GUIStyle(GUI.skin.label);
            yellow.normal.textColor = Color.yellow;

            if (part.vessel.orbit.eccentricity < 1.0)
            {
                GUILayout.Label(String.Format("Current orbit: {0:0}km x {1:0}km, inclined {2:0.0}°", part.vessel.orbit.PeA / 1000.0, part.vessel.orbit.ApA / 1000.0, part.vessel.orbit.inclination));
            }
            else
            {
                GUILayout.Label(String.Format("Current orbit: hyperbolic, Pe = {0:0}km, inclined {1:0.0}°", part.vessel.orbit.PeA / 1000.0, part.vessel.orbit.inclination));
            }

            guiTab = (Operation)GUILayout.Toolbar((int)guiTab, guiTabStrings);

            switch (guiTab)
            {
                case Operation.PERIAPSIS:
                    newPeA = ARUtils.doGUITextInput("New periapsis: ", 250.0F, newPeAString, 50.0F, "km", 30.0F,
                                                    out newPeAString, newPeA, 1000.0);

                    if (newPeA > vesselState.altitudeASL)
                    {
                        GUILayout.Label("Periapsis cannot be above current altitude.", yellow);
                    }
                    else if (GUILayout.Button(String.Format("Burn (Δv = {0:0} m/s)", deltaVToChangePeriapsis(newPeA))))
                    {
                        core.controlClaim(this);
                        currentOperation = Operation.PERIAPSIS;
                        raisingApsis = (part.vessel.orbit.PeA < newPeA);
                    }
                    break;

                case Operation.APOAPSIS:
                    newApA = ARUtils.doGUITextInput("New apoapsis: ", 250.0F, newApAString, 50.0F, "km", 30.0F,
                                                          out newApAString, newApA, 1000.0);

                    if (newApA < vesselState.altitudeASL)
                    {
                        GUILayout.Label("Apoapsis cannot be below current altitude.", yellow);
                    }
                    else if (GUILayout.Button(String.Format("Burn (Δv = {0:0} m/s)", deltaVToChangeApoapsis(newApA))))
                    {
                        core.controlClaim(this);
                        currentOperation = Operation.APOAPSIS;
                        raisingApsis = (part.vessel.orbit.ApR > 0 && part.vessel.orbit.ApA < newApA);
                    }
                    break;

                case Operation.ELLIPTICIZE:
                    newPeA = ARUtils.doGUITextInput("New periapsis: ", 250.0F, newPeAString, 50.0F, "km", 30.0F,
                                                          out newPeAString, newPeA, 1000.0);

                    newApA = ARUtils.doGUITextInput("New apoapsis: ", 250.0F, newApAString, 50.0F, "km", 30.0F,
                                                          out newApAString, newApA, 1000.0);

                    if (newPeA > vesselState.altitudeASL)
                    {
                        GUILayout.Label("Periapsis cannot be above current altitude.", yellow);
                    }
                    else if (newApA < vesselState.altitudeASL)
                    {
                        GUILayout.Label("Apoapsis cannot be below current altitude.", yellow);
                    }
                    else if (GUILayout.Button(String.Format("Burn (Δv = {0:0} m/s)", ellipticizationVelocityCorrection(newPeA, newApA).magnitude)))
                    {
                        core.controlClaim(this);
                        currentOperation = Operation.ELLIPTICIZE;
                    }
                    break;

                case Operation.CIRCULARIZE:
                    if (GUILayout.Button(String.Format("Circularize at {0:0} km (Δv = {1:0} m/s)", vesselState.altitudeASL / 1000.0, circularizationVelocityCorrection().magnitude)))
                    {
                        core.controlClaim(this);
                        currentOperation = Operation.CIRCULARIZE;
                    }
                    break;

                case Operation.TRANSFER_INJECTION:
                    if (part.vessel.mainBody.orbitingBodies.Count > 0)
                    {
                        desiredPostTransferPeA = ARUtils.doGUITextInput("Desired final periapsis:", 250.0F, desiredPostTransferPeAString, 50.0F, "km", 30.0F,
                                                                        out desiredPostTransferPeAString, desiredPostTransferPeA, 1000.0);

                        double postTransferPeR = getPredictedPostTransferPeR();
                        if (transferTarget != null && postTransferPeR != -1)
                        {
                            GUILayout.Label(String.Format("Predicted periapsis after transfer to " + transferTarget.name + ": {0:0} km", (postTransferPeR - transferTarget.Radius) / 1000.0));
                        }

                        foreach (CelestialBody body in part.vessel.mainBody.orbitingBodies)
                        {
                            double deltaV = deltaVToChangeApoapsis(body.orbit.PeA);
                            Orbit transferOrbit = ARUtils.computeOrbit(part.vessel, deltaV * vesselState.velocityVesselOrbitUnit, vesselState.time);
                            double arrivalTime = vesselState.time + transferOrbit.timeToAp;
                            Vector3d vesselArrivalPosition = transferOrbit.getAbsolutePositionAtUT(arrivalTime);
                            Orbit targetOrbit = ARUtils.computeOrbit(body.position, (FlightGlobals.RefFrameIsRotating ? -1 : 1) * body.orbit.GetVel(), part.vessel.mainBody, vesselState.time);
                            Vector3d targetArrivalPosition = targetOrbit.getAbsolutePositionAtUT(arrivalTime);
                            Vector3d targetPlaneNormal = Vector3d.Cross(body.position - part.vessel.mainBody.position, body.orbit.GetVel());
                            Vector3d vesselArrivalPositionTargetPlane = part.vessel.mainBody.position + Vector3d.Exclude(targetPlaneNormal, vesselArrivalPosition - part.vessel.mainBody.position);
                            double angleOffset = Math.Abs(Vector3d.Angle(targetArrivalPosition - part.vessel.mainBody.position, vesselArrivalPositionTargetPlane - part.vessel.mainBody.position));

                            if (
                                    Math.Abs(Vector3d.Angle(vesselState.velocityVesselOrbitUnit, targetArrivalPosition - part.vessel.mainBody.position)) <
                                    Math.Abs(Vector3d.Angle(vesselState.velocityVesselOrbitUnit * -1, targetArrivalPosition - part.vessel.mainBody.position))
                                )
                            {
                                angleOffset = 360.0 - angleOffset; //if we have passed the transfer point then give the user the time to loop back around the long way
                            }

                            angleOffset = Math.Max(angleOffset - 1.0, 0.0); //give us a 1 degree window
                            double timeOffset = Math.Abs(angleOffset) / 360 * part.vessel.orbit.period;

                            if (GUILayout.Button(String.Format("Transfer point to " + body.name + " in {1:0} s (Δv ≈ {0:0} m/s)", deltaV, MuUtils.ToSI(timeOffset, 1))))
                            {
                                core.controlClaim(this);
                                currentOperation = Operation.TRANSFER_INJECTION;
                                transState = TRANSState.WAITING_FOR_INJECTION;
                                transferTarget = body;
                            }
                        }

                        if (part.vessel.orbit.eccentricity > 1.0)
                        {
                            GUILayout.Label("Transfer injection failed. Did you start from a circular equatorial orbit?", ARUtils.labelStyle(Color.yellow));
                        }
                    }
                    else
                    {
                        GUILayout.Label("No bodies orbit " + part.vessel.mainBody.name);
                    }
                    break;

                case Operation.WARP:
                    GUILayout.BeginHorizontal();
                    GUILayout.Label("Warp to:");
                    warpPoint = (WarpPoint)GUILayout.Toolbar((int)warpPoint, warpPointStrings);
                    GUILayout.EndHorizontal();

                    GUILayout.BeginHorizontal();

                    if (warpPoint == WarpPoint.APOAPSIS || warpPoint == WarpPoint.PERIAPSIS)
                    {
                        warpTimeOffset = ARUtils.doGUITextInput("Lead time:", 150.0F, warpTimeOffsetString, 50.0F, "s", 30.0F,
                                                               out warpTimeOffsetString, warpTimeOffset);
                    }

                    if (GUILayout.Button("Warp"))
                    {
                        core.controlClaim(this);
                        FlightInputHandler.SetNeutralControls();
                        currentOperation = Operation.WARP;
                    }
                    GUILayout.EndHorizontal();
                    break;
            }

            GUILayout.BeginHorizontal();
            String statusString = "Idle";
            switch (currentOperation)
            {
                case Operation.PERIAPSIS:
                    statusString = String.Format((raisingApsis ? "Raising" : "Lowering") + " periapsis to {0:0} km", newPeA / 1000.0);
                    break;

                case Operation.APOAPSIS:
                    statusString = String.Format((raisingApsis ? "Raising" : "Lowering") + " apoapsis to {0:0} km", newApA / 1000.0);
                    break;

                case Operation.ELLIPTICIZE:
                    statusString = String.Format("Changing orbit to {0:0}km x {1:0}km", newPeA / 1000.0, newApA / 1000.0);
                    break;

                case Operation.CIRCULARIZE:
                    statusString = String.Format("Circularizing at {0:0} km", vesselState.altitudeASL / 1000.0);
                    break;

                case Operation.TRANSFER_INJECTION:
                    statusString = "Transferring to " + transferTarget.name + "\n" + transStateStrings[(int)transState];
                    break;

                case Operation.WARP:
                    String offsetString = "";
                    if (warpPoint != WarpPoint.SOI_CHANGE)
                    {
                        if (warpTimeOffset > 0) offsetString = "" + warpTimeOffset + "s before ";
                        else if (warpTimeOffset < 0) offsetString = "" + Math.Abs(warpTimeOffset) + "s after ";
                    }
                    statusString = "Warping to " + offsetString + warpPointStrings2[(int)warpPoint];
                    break;
            }
            GUILayout.Label("Status: " + statusString, GUILayout.Width(200.0F));

            GUIStyle abortStyle = (currentOperation == Operation.NONE ? ARUtils.buttonStyle(Color.gray) : ARUtils.buttonStyle(Color.red));
            if (GUILayout.Button("Abort", abortStyle))
            {
                endOperation();
            }

            showHelpWindow = GUILayout.Toggle(showHelpWindow, "?", new GUIStyle(GUI.skin.button));

            GUILayout.EndHorizontal();

            GUILayout.EndVertical();

            GUI.DragWindow();
        }
        void driveTransferWaitingForInjection(FlightCtrlState s)
        {
            if (transferTarget == null || part.vessel.orbit.eccentricity > 1.0)
            {
                endOperation();
                return;
            }

            Orbit transferOrbit = ARUtils.computeOrbit(part.vessel, deltaVToChangeApoapsis(transferTarget.orbit.PeA) * vesselState.velocityVesselOrbitUnit, vesselState.time);

            double arrivalTime = vesselState.time + transferOrbit.timeToAp;
            Vector3d vesselArrivalPosition = transferOrbit.getAbsolutePositionAtUT(arrivalTime);

            //minus sign is there because transferTarget.orbit.GetVel() seems to be mysteriously negated
            //when the ship is in the rotating reference frame of the original body.
            Orbit targetOrbit = ARUtils.computeOrbit(transferTarget.position, (FlightGlobals.RefFrameIsRotating ? -1 : 1) * transferTarget.orbit.GetVel(), part.vessel.mainBody, vesselState.time);

            Vector3d targetArrivalPosition = targetOrbit.getAbsolutePositionAtUT(arrivalTime);

            Vector3d targetPlaneNormal = Vector3d.Cross(transferTarget.position - part.vessel.mainBody.position, transferTarget.orbit.GetVel());
            Vector3d vesselArrivalPositionTargetPlane = part.vessel.mainBody.position + Vector3d.Exclude(targetPlaneNormal, vesselArrivalPosition - part.vessel.mainBody.position);

            double angleOffset = Math.Abs(Vector3d.Angle(targetArrivalPosition - part.vessel.mainBody.position,
                                                         vesselArrivalPositionTargetPlane - part.vessel.mainBody.position));

            //If within 1 degree of our angle then cut warp and set state to injecting
            if (Math.Abs(angleOffset) < 1)
            {
                transState = TRANSState.INJECTING;
                if ((TimeWarp.WarpMode == TimeWarp.Modes.HIGH) && (TimeWarp.CurrentRate > TimeWarp.MaxPhysicsRate)) core.warpMinimum(this);
            }
            else
            {
                double timeOffset = angleOffset / 360 * part.vessel.orbit.period;
                core.warpTo(this, timeOffset - 30, warpLookaheadTimes);
            }

            core.attitudeTo(Vector3d.forward, MechJebCore.AttitudeReference.ORBIT, this);

            //don't burn:
            s.mainThrottle = 0.0F;
        }
        public LuaValue proxyTransfer(LuaValue[] args)
        {
            if (args.Count() != 2) throw new Exception("transfer usage: transfer(target body, final periapsis [in meters])");

            try
            {
                foreach (CelestialBody body in part.vessel.mainBody.orbitingBodies)
                {
                    if (body.name.ToLower().Contains(args[0].ToString().ToLower()))
                    {
                        transferTarget = body;
                        break;
                    }
                }
            }
            catch (Exception)
            {
                throw new Exception("transfer: invalid target body");
            }

            try
            {
                desiredPostTransferPeA = ((LuaNumber)args[1]).Number;
            }
            catch (Exception)
            {
                throw new Exception("transfer: invalid final periapsis");
            }

            desiredPostTransferPeAString = (desiredPostTransferPeA / 1000.0).ToString();

            this.enabled = true;
            core.controlClaim(this);
            currentOperation = Operation.TRANSFER_INJECTION;
            transState = TRANSState.WAITING_FOR_INJECTION;

            guiTab = Operation.TRANSFER_INJECTION;

            return LuaNil.Nil;
        }
        void driveTransferWaitingForCorrection(FlightCtrlState s)
        {
            if(vesselState.radius < 0.5 * (part.vessel.orbit.PeR + part.vessel.orbit.ApR))
            {
                core.warpIncrease(this, false);
            }
            else
            {
                core.warpMinimum(this, false);
                transState = TRANSState.LOWERING_PERIAPSIS;
            }

            s.mainThrottle = 0.0F;
        }