Sub-Orbital Flight

This introductory tutorial uses kRPC to send some Kerbals on a sub-orbital flight, and (hopefully) returns them safely back to Kerbin. It covers the following topics:

  • Controlling a rocket (activating stages, setting the throttle)
  • Using the auto pilot to point the vessel in a specific direction
  • Using events to wait for things to happen in game
  • Tracking the amount of resources in the vessel
  • Tracking flight and orbital data (such as altitude and apoapsis altitude)

Note

For details on how to write scripts and connect to kRPC, see the Getting Started guide.

This tutorial uses the two stage rocket pictured below. The craft file for this rocket can be downloaded here.

This tutorial includes source code examples for the main client languages that kRPC supports. The entire program, for your chosen language can be downloaded from here:

C#, C++, Java, Lua, Python

../_images/SubOrbitalFlight.png

Part One: Preparing for Launch

The first thing we need to do is open a connection to the server. We can also pass a descriptive name for our script that will appear in the server window in game:

10
        var conn = new Connection ("Sub-orbital flight");
 9
10
11
  krpc::Client conn = krpc::connect("Sub-orbital flight");
  krpc::services::KRPC krpc(&conn);
  krpc::services::SpaceCenter space_center(&conn);
20
21
22
    Connection connection = Connection.newInstance("Sub-orbital flight");
    KRPC krpc = KRPC.newInstance(connection);
    SpaceCenter spaceCenter = SpaceCenter.newInstance(connection);
1
2
3
local krpc = require 'krpc'
local platform = require 'krpc.platform'
local conn = krpc.connect('Sub-orbital flight')
3
conn = krpc.connect(name='Sub-orbital flight')

Next we need to get an object representing the active vessel. It’s via this object that we will send instructions to the rocket:

12
        var vessel = conn.SpaceCenter ().ActiveVessel;
13
  auto vessel = space_center.active_vessel();
24
    SpaceCenter.Vessel vessel = spaceCenter.getActiveVessel();
5
local vessel = conn.space_center.active_vessel
5
vessel = conn.space_center.active_vessel

We then need to prepare the rocket for launch. The following code sets the throttle to maximum and instructs the auto-pilot to hold a pitch and heading of 90° (vertically upwards). It then waits for 1 second for these settings to take effect.

14
15
16
17
        vessel.AutoPilot.TargetPitchAndHeading (90, 90);
        vessel.AutoPilot.Engage ();
        vessel.Control.Throttle = 1;
        System.Threading.Thread.Sleep (1000);
15
16
17
18
  vessel.auto_pilot().target_pitch_and_heading(90, 90);
  vessel.auto_pilot().engage();
  vessel.control().set_throttle(1);
  std::this_thread::sleep_for(std::chrono::seconds(1));
26
27
28
29
    vessel.getAutoPilot().targetPitchAndHeading(90, 90);
    vessel.getAutoPilot().engage();
    vessel.getControl().setThrottle(1);
    Thread.sleep(1000);
 7
 8
 9
10
vessel.auto_pilot:target_pitch_and_heading(90, 90)
vessel.auto_pilot:engage()
vessel.control.throttle = 1
platform.sleep(1)
 7
 8
 9
10
vessel.auto_pilot.target_pitch_and_heading(90, 90)
vessel.auto_pilot.engage()
vessel.control.throttle = 1
time.sleep(1)

Part Two: Lift-off!

We’re now ready to launch by activating the first stage (equivalent to pressing the space bar):

19
20
        Console.WriteLine ("Launch!");
        vessel.Control.ActivateNextStage ();
20
21
  std::cout << "Launch!" << std::endl;
  vessel.control().activate_next_stage();
31
32
    System.out.println("Launch!");
    vessel.getControl().activateNextStage();
12
13
print('Launch!')
vessel.control:activate_next_stage()
12
13
print('Launch!')
vessel.control.activate_next_stage()

The rocket has a solid fuel stage that will quickly run out, and will need to be jettisoned. We can monitor the amount of solid fuel in the rocket using an event that is triggered when there is very little solid fuel left in the rocket. When the event is triggered, we can activate the next stage to jettison the boosters:

23
24
25
26
27
28
29
            var solidFuel = Connection.GetCall(() => vessel.Resources.Amount("SolidFuel"));
            var expr = Expression.LessThan(
                conn, Expression.Call(conn, solidFuel), Expression.ConstantFloat(conn, 0.1f));
            var evnt = conn.KRPC().AddEvent(expr);
            lock (evnt.Condition) {
                evnt.Wait();
            }
26
27
28
29
30
31
32
    auto solid_fuel = vessel.resources().amount_call("SolidFuel");
    auto expr = Expr::less_than(
      conn, Expr::call(conn, solid_fuel), Expr::constant_float(conn, 0.1));
    auto event = krpc.add_event(expr);
    event.acquire();
    event.wait();
    event.release();
34
35
36
37
38
39
40
41
42
43
44
45
46
47
    {
      ProcedureCall solidFuel = connection.getCall(vessel.getResources(), "amount", "SolidFuel");
      Expression expr = Expression.lessThan(
        connection,
        Expression.call(connection, solidFuel),
        Expression.constantFloat(connection, 0.1f));
      Event event = krpc.addEvent(expr);
      synchronized (event.getCondition()) {
        event.waitFor();
      }
    }

    System.out.println("Booster separation");
    vessel.getControl().activateNextStage();
15
16
17
18
19
while vessel.resources:amount('SolidFuel') > 0.1 do
    platform.sleep(1)
end
print('Booster separation')
vessel.control:activate_next_stage()
15
16
17
18
19
20
21
22
23
fuel_amount = conn.get_call(vessel.resources.amount, 'SolidFuel')
expr = conn.krpc.Expression.less_than(
    conn.krpc.Expression.call(fuel_amount),
    conn.krpc.Expression.constant_float(0.1))
event = conn.krpc.add_event(expr)
with event.condition:
    event.wait()
print('Booster separation')
vessel.control.activate_next_stage()

In this bit of code, vessel.resources returns a Resources object that is used to get information about the resources in the rocket. The code creates the expression vessel.resources.amount('SolidFuel') < 0.1 on the server, using the expression API. This expression is then used to drive an event, which is triggered when the expression returns true.

Part Three: Reaching Apoapsis

Next we will execute a gravity turn when the rocket reaches a sufficiently high altitude. The following uses an event to wait until the altitude of the rocket reaches 10km:

36
37
38
39
40
41
42
            var meanAltitude = Connection.GetCall(() => vessel.Flight(null).MeanAltitude);
            var expr = Expression.GreaterThan(
                conn, Expression.Call(conn, meanAltitude), Expression.ConstantDouble(conn, 10000));
            var evnt = conn.KRPC().AddEvent(expr);
            lock (evnt.Condition) {
                evnt.Wait();
            }
39
40
41
42
43
44
45
    auto mean_altitude = vessel.flight().mean_altitude_call();
    auto expr = Expr::greater_than(
      conn, Expr::call(conn, mean_altitude), Expr::constant_double(conn, 10000));
    auto event = krpc.add_event(expr);
    event.acquire();
    event.wait();
    event.release();
50
51
52
53
54
55
56
57
58
      ProcedureCall meanAltitude = connection.getCall(vessel.flight(null), "getMeanAltitude");
      Expression expr = Expression.greaterThan(
        connection,
        Expression.call(connection, meanAltitude),
        Expression.constantDouble(connection, 10000));
      Event event = krpc.addEvent(expr);
      synchronized (event.getCondition()) {
        event.waitFor();
      }
21
22
23
while vessel:flight().mean_altitude < 10000 do
    platform.sleep(1)
end
25
26
27
28
29
30
31
mean_altitude = conn.get_call(getattr, vessel.flight(), 'mean_altitude')
expr = conn.krpc.Expression.greater_than(
    conn.krpc.Expression.call(mean_altitude),
    conn.krpc.Expression.constant_double(10000))
event = conn.krpc.add_event(expr)
with event.condition:
    event.wait()

In this bit of code, calling vessel.flight() returns a Flight object that is used to get all sorts of information about the rocket, such as the direction it is pointing in and its velocity.

Now we need to angle the rocket over to a pitch of 60° and maintain a heading of 90° (west). To do this, we simply reconfigure the auto-pilot:

45
46
        Console.WriteLine ("Gravity turn");
        vessel.AutoPilot.TargetPitchAndHeading (60, 90);
48
49
  std::cout << "Gravity turn" << std::endl;
  vessel.auto_pilot().target_pitch_and_heading(60, 90);
61
62
    System.out.println("Gravity turn");
    vessel.getAutoPilot().targetPitchAndHeading(60, 90);
25
26
print('Gravity turn')
vessel.auto_pilot:target_pitch_and_heading(60, 90)
33
34
print('Gravity turn')
vessel.auto_pilot.target_pitch_and_heading(60, 90)

Now we wait until the apoapsis reaches 100km (again, using an event), then reduce the throttle to zero, jettison the launch stage and turn off the auto-pilot:

32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
        {
            var apoapsisAltitude = Connection.GetCall(() => vessel.Orbit.ApoapsisAltitude);
            var expr = Expression.GreaterThan(
                conn, Expression.Call(conn, apoapsisAltitude), Expression.ConstantDouble(conn, 100000));
            var evnt = conn.KRPC().AddEvent(expr);
            lock (evnt.Condition) {
                evnt.Wait();
            }
        }

        Console.WriteLine ("Launch stage separation");
        vessel.Control.Throttle = 0;
        System.Threading.Thread.Sleep (1000);
        vessel.Control.ActivateNextStage ();
        vessel.AutoPilot.Disengage ();
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
  {
    auto apoapsis_altitude = vessel.orbit().apoapsis_altitude_call();
    auto expr = Expr::greater_than(
      conn, Expr::call(conn, apoapsis_altitude), Expr::constant_double(conn, 100000));
    auto event = krpc.add_event(expr);
    event.acquire();
    event.wait();
    event.release();
  }

  std::cout << "Launch stage separation" << std::endl;
  vessel.control().set_throttle(0);
  std::this_thread::sleep_for(std::chrono::seconds(1));
  vessel.control().activate_next_stage();
  vessel.auto_pilot().disengage();
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
    {
      ProcedureCall apoapsisAltitude = connection.getCall(
        vessel.getOrbit(), "getApoapsisAltitude");
      Expression expr = Expression.greaterThan(
        connection,
        Expression.call(connection, apoapsisAltitude),
        Expression.constantDouble(connection, 100000));
      Event event = krpc.addEvent(expr);
      synchronized (event.getCondition()) {
        event.waitFor();
      }
    }

    System.out.println("Launch stage separation");
    vessel.getControl().setThrottle(0);
    Thread.sleep(1000);
    vessel.getControl().activateNextStage();
    vessel.getAutoPilot().disengage();
28
29
30
31
32
33
34
35
while vessel.orbit.apoapsis_altitude < 100000 do
    platform.sleep(1)
end
print('Launch stage separation')
vessel.control.throttle = 0
platform.sleep(1)
vessel.control:activate_next_stage()
vessel.auto_pilot:disengage()
36
37
38
39
40
41
42
43
44
45
46
47
48
apoapsis_altitude = conn.get_call(getattr, vessel.orbit, 'apoapsis_altitude')
expr = conn.krpc.Expression.greater_than(
    conn.krpc.Expression.call(apoapsis_altitude),
    conn.krpc.Expression.constant_double(100000))
event = conn.krpc.add_event(expr)
with event.condition:
    event.wait()

print('Launch stage separation')
vessel.control.throttle = 0
time.sleep(1)
vessel.control.activate_next_stage()
vessel.auto_pilot.disengage()

In this bit of code, vessel.orbit returns an Orbit object that contains all the information about the orbit of the rocket.

Part Four: Returning Safely to Kerbin

Our Kerbals are now heading on a sub-orbital trajectory and are on a collision course with the surface. All that remains to do is wait until they fall to 1km altitude above the surface, and then deploy the parachutes. If you like, you can use time acceleration to skip ahead to just before this happens - the script will continue to work.

64
65
66
67
68
69
70
71
72
73
74
        {
            var srfAltitude = Connection.GetCall(() => vessel.Flight(null).SurfaceAltitude);
            var expr = Expression.LessThan(
                conn, Expression.Call(conn, srfAltitude), Expression.ConstantDouble(conn, 1000));
            var evnt = conn.KRPC().AddEvent(expr);
            lock (evnt.Condition) {
                evnt.Wait();
            }
        }

        vessel.Control.ActivateNextStage ();
67
68
69
70
71
72
73
74
75
76
77
  {
    auto srf_altitude = vessel.flight().surface_altitude_call();
    auto expr = Expr::less_than(
      conn, Expr::call(conn, srf_altitude), Expr::constant_double(conn, 1000));
    auto event = krpc.add_event(expr);
    event.acquire();
    event.wait();
    event.release();
  }

  vessel.control().activate_next_stage();
83
84
85
86
87
88
89
90
91
92
93
94
95
96
    {
      ProcedureCall srfAltitude = connection.getCall(
        vessel.flight(null), "getSurfaceAltitude");
      Expression expr = Expression.lessThan(
        connection,
        Expression.call(connection, srfAltitude),
        Expression.constantDouble(connection, 1000));
      Event event = krpc.addEvent(expr);
      synchronized (event.getCondition()) {
        event.waitFor();
      }
    }

    vessel.getControl().activateNextStage();
37
38
39
40
while vessel:flight().surface_altitude > 1000 do
    platform.sleep(1)
end
vessel.control:activate_next_stage()
50
51
52
53
54
55
56
57
58
srf_altitude = conn.get_call(getattr, vessel.flight(), 'surface_altitude')
expr = conn.krpc.Expression.less_than(
    conn.krpc.Expression.call(srf_altitude),
    conn.krpc.Expression.constant_double(1000))
event = conn.krpc.add_event(expr)
with event.condition:
    event.wait()

vessel.control.activate_next_stage()

The parachutes should have now been deployed. The next bit of code will repeatedly print out the altitude of the capsule until its speed reaches zero – which will happen when it lands:

76
77
78
79
80
81
        while (vessel.Flight (vessel.Orbit.Body.ReferenceFrame).VerticalSpeed < -0.1) {
            Console.WriteLine ("Altitude = {0:F1} meters", vessel.Flight ().SurfaceAltitude);
            System.Threading.Thread.Sleep (1000);
        }
        Console.WriteLine ("Landed!");
        conn.Dispose();
79
80
81
82
83
  while (vessel.flight(vessel.orbit().body().reference_frame()).vertical_speed() < -0.1) {
    std::cout << "Altitude = " << vessel.flight().surface_altitude() << " meters" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
  }
  std::cout << "Landed!" << std::endl;
 98
 99
100
101
102
103
    while (vessel.flight(vessel.getOrbit().getBody().getReferenceFrame()).getVerticalSpeed() < -0.1) {
      System.out.printf("Altitude = %.1f meters\n", vessel.flight(null).getSurfaceAltitude());
      Thread.sleep(1000);
    }
    System.out.println("Landed!");
    connection.close();
42
43
44
45
46
47
while vessel:flight(vessel.orbit.body.reference_frame).vertical_speed < -0.1 do
    print(string.format('Altitude = %.1f meters',
                        vessel:flight().surface_altitude))
    platform.sleep(1)
end
print('Landed!')
60
61
62
63
while vessel.flight(vessel.orbit.body.reference_frame).vertical_speed < -0.1:
    print('Altitude = %.1f meters' % vessel.flight().surface_altitude)
    time.sleep(1)
print('Landed!')

This bit of code uses the vessel.flight() function, as before, but this time it is passed a ReferenceFrame parameter. We want to get the vertical speed of the capsule relative to the surface of Kerbin, so the values returned by the flight object need to be relative to the surface of Kerbin. We therefore pass vessel.orbit.body.reference_frame to vessel.flight() as this reference frame has its origin at the center of Kerbin and it rotates with the planet. For more information, check out the tutorial on Reference Frames.

Your Kerbals should now have safely landed back on the surface.