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:
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:
krpc_connection_t conn;
krpc_open(&conn, "COM0");
krpc_connect(conn, "Sub-orbital flight");
var conn = new Connection ("Sub-orbital flight");
krpc::Client conn = krpc::connect("Sub-orbital flight");
krpc::services::KRPC krpc(&conn);
krpc::services::SpaceCenter space_center(&conn);
Connection connection = Connection.newInstance("Sub-orbital flight");
KRPC krpc = KRPC.newInstance(connection);
SpaceCenter spaceCenter = SpaceCenter.newInstance(connection);
local krpc = require 'krpc'
local platform = require 'krpc.platform'
local conn = krpc.connect('Sub-orbital flight')
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:
krpc_SpaceCenter_Vessel_t vessel;
krpc_SpaceCenter_ActiveVessel(conn, &vessel);
krpc_SpaceCenter_AutoPilot_t auto_pilot;
krpc_SpaceCenter_Vessel_AutoPilot(conn, &auto_pilot, vessel);
krpc_SpaceCenter_Control_t control;
krpc_SpaceCenter_Vessel_Control(conn, &control, vessel);
var vessel = conn.SpaceCenter ().ActiveVessel;
auto vessel = space_center.active_vessel();
SpaceCenter.Vessel vessel = spaceCenter.getActiveVessel();
local vessel = conn.space_center.active_vessel
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.
krpc_SpaceCenter_AutoPilot_TargetPitchAndHeading(conn, auto_pilot, 90, 90);
krpc_SpaceCenter_AutoPilot_set_Engaged(conn, auto_pilot, true);
krpc_SpaceCenter_Control_set_Throttle(conn, control, 1);
sleep(1);
vessel.AutoPilot.TargetPitchAndHeading (90, 90);
vessel.AutoPilot.Engaged = true;
vessel.Control.Throttle = 1;
System.Threading.Thread.Sleep (1000);
vessel.auto_pilot().target_pitch_and_heading(90, 90);
vessel.auto_pilot().set_engaged(true);
vessel.control().set_throttle(1);
std::this_thread::sleep_for(std::chrono::seconds(1));
vessel.getAutoPilot().targetPitchAndHeading(90, 90);
vessel.getAutoPilot().setEngaged(true);
vessel.getControl().setThrottle(1);
Thread.sleep(1000);
vessel.auto_pilot:target_pitch_and_heading(90, 90)
vessel.auto_pilot.engaged = true
vessel.control.throttle = 1
platform.sleep(1)
vessel.auto_pilot.target_pitch_and_heading(90, 90)
vessel.auto_pilot.engaged = True
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):
printf("Launch!\n");
krpc_SpaceCenter_Control_ActivateNextStage(conn, NULL, control);
Console.WriteLine ("Launch!");
vessel.Control.ActivateNextStage ();
std::cout << "Launch!" << std::endl;
vessel.control().activate_next_stage();
System.out.println("Launch!");
vessel.getControl().activateNextStage();
print('Launch!')
vessel.control:activate_next_stage()
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:
krpc_SpaceCenter_Resources_t resources;
krpc_SpaceCenter_Vessel_Resources(conn, &resources, vessel);
while (true) {
float solid_fuel;
krpc_SpaceCenter_Resources_Amount(conn, &solid_fuel, resources, "SolidFuel");
if (solid_fuel < 0.1)
break;
}
printf("Booster separation\n");
krpc_SpaceCenter_Control_ActivateNextStage(conn, NULL, control);
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();
}
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();
{
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();
while vessel.resources:amount('SolidFuel') > 0.1 do
platform.sleep(1)
end
print('Booster separation')
vessel.control:activate_next_stage()
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:
krpc_SpaceCenter_Flight_t flight;
krpc_SpaceCenter_Vessel_Flight(conn, &flight, vessel, KRPC_NULL);
while (true) {
double mean_altitude;
krpc_SpaceCenter_Flight_MeanAltitude(conn, &mean_altitude, flight);
if (mean_altitude > 10000)
break;
}
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();
}
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();
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();
}
while vessel:flight().mean_altitude < 10000 do
platform.sleep(1)
end
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° (east). To do this, we simply reconfigure the auto-pilot:
printf("Gravity turn\n");
krpc_SpaceCenter_AutoPilot_TargetPitchAndHeading(conn, auto_pilot, 60, 90);
Console.WriteLine ("Gravity turn");
vessel.AutoPilot.TargetPitchAndHeading (60, 90);
std::cout << "Gravity turn" << std::endl;
vessel.auto_pilot().target_pitch_and_heading(60, 90);
System.out.println("Gravity turn");
vessel.getAutoPilot().targetPitchAndHeading(60, 90);
print('Gravity turn')
vessel.auto_pilot:target_pitch_and_heading(60, 90)
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:
krpc_SpaceCenter_Orbit_t orbit;
krpc_SpaceCenter_Vessel_Orbit(conn, &orbit, vessel);
while (true) {
double apoapsis_altitude;
krpc_SpaceCenter_Orbit_ApoapsisAltitude(conn, &apoapsis_altitude, orbit);
if (apoapsis_altitude > 100000)
break;
}
printf("Launch stage separation\n");
krpc_SpaceCenter_Control_set_Throttle(conn, control, 0);
sleep(1);
krpc_SpaceCenter_Control_ActivateNextStage(conn, NULL, control);
krpc_SpaceCenter_AutoPilot_set_Engaged(conn, auto_pilot, false);
{
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.Engaged = false;
{
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().set_engaged(false);
{
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().setEngaged(false);
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.engaged = false
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.engaged = False
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.
while (true) {
double srf_altitude;
krpc_SpaceCenter_Flight_SurfaceAltitude(conn, &srf_altitude, flight);
if (srf_altitude < 1000)
break;
}
krpc_SpaceCenter_Control_ActivateNextStage(conn, NULL, control);
{
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 ();
{
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();
{
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();
while vessel:flight().surface_altitude > 1000 do
platform.sleep(1)
end
vessel.control:activate_next_stage()
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:
krpc_SpaceCenter_CelestialBody_t body;
krpc_SpaceCenter_Orbit_Body(conn, &body, orbit);
krpc_SpaceCenter_ReferenceFrame_t body_ref_frame;
krpc_SpaceCenter_CelestialBody_ReferenceFrame(conn, &body_ref_frame, body);
krpc_SpaceCenter_Flight_t body_flight;
krpc_SpaceCenter_Vessel_Flight(conn, &body_flight, vessel, body_ref_frame);
while (true) {
double vertical_speed;
krpc_SpaceCenter_Flight_VerticalSpeed(conn, &vertical_speed, body_flight);
if (vertical_speed > -0.1)
break;
double altitude;
krpc_SpaceCenter_Flight_SurfaceAltitude(conn, &altitude, flight);
printf("Altitude = %2.f meters\n", altitude);
sleep(1);
}
printf("Landed!\n");
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();
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;
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();
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!')
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.