Modifying Aircraft State

Most plugins need to interact with the aircraft itself. Using the scripting APIs, you can query all sorts of aircraft variables, and modify them to. In this tutorial we'll create a "Gear and Flaps" monitor, to help the pilot monitor and select their gear and flap positions. Let's start with a simple UI. If you read the last tutorial you should be able to understand the code below:

def onStart()
{

}

def onGraphicsFrame()
{

}

def getFlapAngle()
{
    return 0.0; //TODO:  We need to figure out what angle the flaps are at.
}

def getGearPosition()
{
    return 0.5; //TODO: We need to figure out where our landing gear are (extended, retracted, etc)
}

def getGearLeverPosition()
{
    return 0.9; //TODO: We need to figure out where the gear lever is
}

def extendFlaps()
{
    //TODO: Raise flaps by one increment
}

def retractFlaps()
{
    //TODO: Lower flaps by one increment
}

def toggleGear()
{
    //TODO: Toggle the gear between up and down
}

def onUIFrame()
{
    auto& ui = GetUI();
    ui.startWindowSized("Gear and Flaps Monitor", ivec2(600, 315));

    //Not the best way to position text, but it's easy ;)
    ui.text("                 FLAPS                                       GEAR");

    auto windowPos = ui.getWindowPos();

    //Draw a circular indicator for flap angle
    auto flapCenter = ivec2(150, 160) + windowPos;
    ui.fillCircle(flapCenter, 100.0, vec3(0.0, 0.0, 0.0));
    ui.drawCircle(flapCenter, 100.0, vec3(1.0, 1.0, 1.0), 1.0);

    auto flapDrawAngle = -deg2rad(getFlapAngle()) + PI;
    ui.drawLine(flapCenter, flapCenter + ivec2(cos(flapDrawAngle) * 100.0, sin(flapDrawAngle) * 100.0), vec3(1.0, 1.0, 1.0), 1.0);

    //Draw a rectangular indicator for landing gear position, and landing gear flap position
    auto gearRectTop = ivec2(425, 60) + windowPos;
    auto gearRectBottom = ivec2(475, 260) + windowPos;
    ui.fillRect(gearRectTop, gearRectBottom, vec3(0.3, 0.3, 0.3));
    ui.fillRect(gearRectTop, ivec2(gearRectBottom.x, (gearRectBottom.y - gearRectTop.y) * getGearPosition() + gearRectTop.y), vec3(1.0, 1.0, 1.0));

    auto leverLineY = (gearRectBottom.y - gearRectTop.y) * getGearLeverPosition() + gearRectTop.y;
    ui.drawLine(ivec2(gearRectTop.x, leverLineY), ivec2(gearRectBottom.x, leverLineY), vec3(1.0, 0.0, 0.0), 1.0);

    //Bump our buttons down a few lines below the graphics
    for (auto i = 0; i < 13; ++i)
    {
        ui.text("");
    }

    if (ui.button("Extend Flaps"))
    {
        extendFlaps();
    }

    ui.sameLine();

    if (ui.button("Retract Flaps"))
    {
        retractFlaps();
    }

    ui.sameLine();

    if (ui.button("Toggle Landing Gear"))
    {
        toggleGear();
    }

    ui.endWindow();
}

Nothing too tricky, we're drawing a few circles, lines, and rectangles, to visualize the flaps and landing gear. It looks like this:

Flaps UI

Despite looking pretty, it doesn't actually do anything yet though. First things first, we need to find the simulation variables we want to wire into.

Open "Debugging Tools -> Variable Editor" In the "Filter:" box type Flaps. A whole bunch of variables appear, and it can be a little bit overwhelming. There's an easy way to find what we're looking for though, just extend your aircraft's flaps and watch for the variable that changes. You can extend/retract the flaps a few times until you find a variable that has the behavior you want. In this case, I decided to go with Aircraft.Surfaces.Flaps.Trailing.Left.Angle

Flaps Variable

Now we'll wire into the aircraft itself to read this variable. We'll implement the TODO in getFlapAngle

def getFlapAngle()
{
    auto& ac = GetUserAircraft();
    auto& vp = ac.getVariableProvider();
    return vp.getValueFloat("Aircraft.Surfaces.Flaps.Trailing.Left.Angle", "number");
}
  1. GetUserAircraft() returns a reference to the aircraft the user is inside. We need to store a reference to it (it's non-copyable), hence the ampersand.
  2. We retrieve a reference to the VariableProvider for this aircraft, which allows us to read and write aircraft variables.
  3. We ask the VariableProvider for the current value of Aircraft.Surfaces.Flaps.Trailing.Left.Angle, as a number.

Now, if you reload the script and adjust your flaps, the plugin's flap indicator should animate smoothly.

We'll keep going and do the same for gear. The variables we want are Aircraft.Wheel.Center.Extension.Current and Aircraft.Input.GearLever.Down, both of which go from 0 to 100 (you can confirm this from the Variable Editor). After integrating these we have:

def getGearPosition()
{
    auto& ac = GetUserAircraft();
    auto& vp = ac.getVariableProvider();
    return vp.getValueFloat("Aircraft.Wheel.Center.Extension.Current", "number") / 100.0;
}

def getGearLeverPosition()
{
    auto& ac = GetUserAircraft();
    auto& vp = ac.getVariableProvider();
    return vp.getValueFloat("Aircraft.Input.GearLever.Down", "number") / 100.0;
}

Reload the Gear and Flaps add-on and you'll be able to see both the gear lever and gear position indicators functioning. Now we need to get those buttons working.

To modify the landing gear position, we can just change the landing gear lever position back and forth. Read the variable, invert the value, and write the variable.

def toggleGear()
{
    auto& ac = GetUserAircraft();
    auto& vp = ac.getVariableProvider();

    //1.  Read the variable
    auto currentGearPosition = vp.getValueFloat("Aircraft.Input.GearLever.Down", "number");

    //2.  Toggle the variable
    auto targetGearPosition = 100.0 - currentGearPosition;

    //3.  Write the variable
    vp.setValueFloat("Aircraft.Input.GearLever.Down", "number", targetGearPosition);
}

The only new method here is VariableProvider.setValueFloat and it works as you'd expect.

Extending and retracting the flaps is a little bit more complex. Rather than writing a simulator variable, we need to fire an event. Pull open "Debugging Tools -> Event Editor". Search for FLAPS in the search box.

Event Editor

The two relevent events are flaps_incr and flaps_decr. Firing an event is a little bit involved.

def extendFlaps()
{
    auto& ac = GetUserAircraft();
    auto& evtManager = ac.getEventManager();
    auto& evtRaise = evtManager.getEvent("flaps_incr");
    evtRaise.fire(evtManager.GroupStartEventChain, 0);
}

We need to:

  1. Obtain a reference to the AircraftInstance
  2. Obtain a reference to the aircraft's EventManager
  3. Obtain a reference to the Event we want to fire
  4. fire the Event. For the first parameter, you can almost always use evtManager.GroupStartEventChain. In specific circumstances, using other event chains allows you to override aircraft systems functionality.

The second parameter is a number from -16383 to 16383. It doesn't do anything for flap events so we leave it at zero, but can be used with certain events (for example, to set an exact throttle position).

All that's left is to add a similar implementation to retractFlaps

def retractFlaps()
{
    auto& ac = GetUserAircraft();
    auto& evtManager = ac.getEventManager();
    auto& evtRaise = evtManager.getEvent("flaps_decr");
    evtRaise.fire(evtManager.GroupStartEventChain, 0);
}

There we go, with just 117 lines of (verbose tutorial) code, we have a UI where we can monitor and control both our flaps and landing gear. At this point you should be able to create some interesting and useful add-ons!