Tutorial 2 ‐ aircraft (Cpp) - Outerra/anteworld GitHub Wiki

Tutorial on how to make basic aircraft using C++.

image

You can find the files belonging to this tutorial and complete code in Outerra/anteworld "Code" section in Tutorials/example_models_cpp/packages/tutorial_aircraft_plugin.

You can also find more information in previous C++ tutorial ( Tutorial ‐ car (Cpp) ).

Warning: aircraft interface does not contain "simulation_step" function.

Aircraft configuration files

If you are unfamiliar with creating flight model configuration files for your aircraft, you can use Aeromatic.

For more information about using aeromatic, see JSBSim & Aeromatic.

Note: using Aeromatic is not recommended, as it doesn't provide accurate aircraft configurations, but for now, it may serve as a useful starting point for generating aircraft configurations, especially for beginners or those looking to quickly get started with aircraft in Outerra.

Mod files

For a tutorial on mod files, please refer to mod files tutorial.

Warning: mods working with models (in this case, we are working with Diamond DA40-NG model), need to have the files located under "packages" folder (example: Outerra World Sandbox\mods\example_models_cpp\packages\tutorial_aircraft_cpp)

Creating project

To learn how to create a project and set project properties, visit creating cpp project tutorial.

In .hpp file

Include necessary files.

aircraft_script.h is needed for our aircraft simulation, to provide important tools and features.

#include "ot/aircraft_script.h"

Define a constant for PI (will be used for joint rotation)

const double PI = 3.14159265358979323846;

Declare static variables, which will represent different parts and features of our aircraft.

//struct for bone members
struct bones
{
	static int propeller;
	static int wheel_front;
	static int wheel_right;
	static int wheel_left;
	static int elevator_right;
	static int elevator_left;
	static int rudder;
	static int aileron_right;
	static int aileron_left;
	static int flap_right;
	static int flap_left;
	static int rudder_pedal_right;
	static int rudder_pedal_left;
	static int brake_pedal_left;
	static int brake_pedal_right;
	static int throttle_handle;
};

//struct for mesh members
struct meshes
{
	static int prop_blur;
	static int blade_one;
	static int blade_two;
	static int blade_three;
};

//struct for sound members
struct sounds
{
	static int rumble;
	static int eng_exterior;
	static int eng_interior;
	static int prop_exterior;
	static int prop_interior;
};

//struct for sound source members
struct sources
{
	static int rumble_exterior;
	static int rumble_interior;
	static int eng_exterior;
	static int eng_interior;
	static int prop_exterior;
	static int prop_interior;
};

"Static" keyword is used, so that these variables can be shared across all instances (familiar to creating global variable), without it, the variables would be defined only for the 1. instance, because they are set in init_chassis, which is called only the first time.

Create class which inherits functionality from ot::aircraft_script.

class tutorial_aircraft_plugin : public ot::aircraft_script
{
};

In class tutorial_aircraft_plugin

You can find information about aircraft interface on Outerra/anteworld wiki in aircraft interface.

Override virtual functions from the base class "ot::aircraft_script".

These are basic functions needed for creating an aircraft.

// Function to initialize chassis parameters
ot::chassis_params init_chassis(const coid::charstr& params) override;
// Function to initialize the aircraft (called on startup or reload)
void initialize(bool reload) override;
// Function to handle functionality each frame
void update_frame(float dt) override;

Note: function "update_frame" in aircraft_script interface receives only the "dt" (delta time) parameter.

Declare additional functions.

// Utility function to clamp a value within a specified range
float clamp(float val, float minval, float maxval);
// Function to handle landing lights events
void landing_lights(float val, uint code, uint channel, int handler_id);
// Function to handle navigation lights events
void navigation_lights(float val, uint code, uint channel, int handler_id);	

By default, JSBSim actions are handled internally, allowing gameplay without direct user intervention. However, sometimes it is needed to customize or fine-tune aircraft behavior, therefore in this case, the following handlers are also handled here.

// Function to handle events, when engine starts/stop
void engine(int flags, uint code, uint channel, int handler_id);
// Function to handle event, when brake button is pressed
void brakes(float val, uint code, uint channel, int handler_id);
// Function to handle ailerons 
void ailerons(float val, uint code, uint channel, int handler_id);
// Function to handle elevator
void elevator(float val, uint code, uint channel, int handler_id);

Some of these functions will be used as callback functions (their function pointer will be passed to action handlers as parameter), therefore their arguments need to align with the parameters specified in the callback function predefinition.

Examples:

event handler parameters - function(int flags, uint code, uint channel, int handler_id)

axis handler parameters - function(float val, uint code, uint channel, int handler_id)

Declare variables.

// Flag indicating whether the aircraft has started
bool started;
// Braking value
float braking;
// Offset for navigation lights
uint nav_light_offset;

// Reference to the JSBSim object
iref <ot::jsb> jsbsim = nullptr;
// Reference to the geometry object
iref<ot::geomob> geom = nullptr;
// Reference to the sound group
iref<ot::sndgrp> snd = nullptr;

Note: jsbsim, geom and sounds are smart pointers, pointing to geomob and sound groups, they are used for managing reference-counted objects.

In .cpp file

Include header file, belonging to this .cpp file.

#include "tutorial_aircraft_plugin.hpp"

Static members must be defined outside class body (better to define to -1 for debug purpose).

int bones::propeller = -1;
int bones::wheel_front = -1;
int bones::wheel_right = -1;
int bones::wheel_left = -1;
int bones::elevator_right = -1;
int bones::elevator_left = -1;
int bones::rudder = -1;
int bones::aileron_right = -1;
int bones::aileron_left = -1;
int bones::flap_right = -1;
int bones::flap_left = -1;
int bones::rudder_pedal_right = -1;
int bones::rudder_pedal_left = -1;
int bones::brake_pedal_left = -1;
int bones::brake_pedal_right = -1;
int bones::throttle_handle = -1;

// Define mesh static members
int meshes::prop_blur = -1;
int meshes::blade_one = -1;
int meshes::blade_two = -1;
int meshes::blade_three = -1;

// Define sound static members
int sounds::rumble = -1;
int sounds::eng_exterior = -1;
int sounds::eng_interior = -1;
int sounds::prop_exterior = -1;
int sounds::prop_interior = -1;

// Define sound source static members
int sources::rumble_exterior = -1;
int sources::rumble_interior = -1;
int sources::eng_exterior = -1;
int sources::eng_interior = -1;
int sources::prop_exterior = -1;
int sources::prop_interior = -1;

It is neccesary to register our class as client, for that use macro IFC_REGISTER_CLIENT

  • parameter - derived client interface class.
namespace
{
	IFC_REGISTER_CLIENT(tutorial_aircraft_plugin);
}

Namespace here is used, so that the registration is limited in scope and will be visible only within the translation unit, where the namespace is defined.

Define additional functions

"clamp" function will be used to clamp a value within a specific range.

float tutorial_aircraft_plugin::clamp(float val, float minval, float maxval)
{
	if (val < minval)
	{
		return minval;
	}
	else if (val > maxval)
	{
		return maxval;
	}
	else
	{
		return val;
	}
}

"engine" function will be used, to start/stop the engine.

void tutorial_aircraft_plugin::engine(int flags, uint code, uint channel, int handler_id)
{
	//Safety checks for jsbsim pointer to avoid crashes
	if (!this->jsbsim)
	{
		return;
	}

	// Toggle the engine state every time this function is called 
	this->started ^= 1;

	// Start or stop the engine based on the state
	if (this->started == 1)
	{
		//For JSBSim aircraft to start, the starter and magneto needs to be activated
		//Turn on engine (0 - turn off, 1 - turn on)
		this->jsbsim->operator()("propulsion/starter_cmd", 1);
		this->jsbsim->operator()("propulsion/magneto_cmd", 1);		
	}
	else
	{
		this->jsbsim->operator()("propulsion/starter_cmd", 0);
		this->jsbsim->operator()("propulsion/magneto_cmd", 0);
	}
}

"this->jsbsim->operator()" uses jsbsim overloaded operator() function, where

1.parameter - Jsbsim property, you want to get/set.

2.parameter - value to be assigned to the property (only if the property can be set).

Debug tip: when the magneto isn't activated, it results in decreased piston engine power, with the rpm probably not exceeding 1000.

For information on JSBSim, visit the JSBSim & Aeromatic page.

For a list of usable JSBSim properties, refer to the JSBSim properties page.

"brakes" function will handle braking event.

void tutorial_aircraft_plugin::brakes(float val, uint code, uint channel, int handler_id)
{
	this->braking = val;
	this->jsbsim->operator()("fcs/center-brake-cmd-norm", val);
	this->jsbsim->operator()("fcs/left-brake-cmd-norm", val);
	this->jsbsim->operator()("fcs/right-brake-cmd-norm", val);
};

"landing_lights" function will handle landing lights events.

void tutorial_aircraft_plugin::landing_lights(float val, uint code, uint channel, int handler_id)
{
	// Toggle landing lights based on the input value
	light_mask(0x3, val > 0);
};

"navigation_lights" function will handle navigation lights events.

void tutorial_aircraft_plugin::navigation_lights(float val, uint code, uint channel, int handler_id)
{
	// Toggle navigation lights based on the input value
	light_mask(0x3, val > 0, nav_light_offset);
}

"ailerons" function will handle ailerons events.

void tutorial_aircraft_plugin::ailerons(float val, uint code, uint channel, int handler_id)
{
	this->jsbsim->operator()("fcs/aileron-cmd-norm", val);
}

"elevator" function will handle elevator events.

void tutorial_aircraft_plugin::elevator(float val, uint code, uint channel, int handler_id)
{
	this->jsbsim->operator()("fcs/elevator-cmd-norm", -val);
}

In init_chassis

Define members.

To get mesh id, use function get_mesh_id.

  • parameter - mesh name
//bone members
bones::propeller = get_joint_id("propel");
bones::wheel_front = get_joint_id("front_wheel");
bones::wheel_right = get_joint_id("right_wheel");
bones::wheel_left = get_joint_id("left_wheel");
bones::elevator_right = get_joint_id("elevator_right");
bones::elevator_left = get_joint_id("elevator_left");
bones::rudder = get_joint_id("rudder");
bones::aileron_right = get_joint_id("aileron_right");
bones::aileron_left = get_joint_id("aileron_left");
bones::flap_right = get_joint_id("flap_right");
bones::flap_left = get_joint_id("flap_left");
bones::rudder_pedal_right = get_joint_id("rudder_pedal_right");
bones::rudder_pedal_left = get_joint_id("rudder_pedal_left");
bones::brake_pedal_left = get_joint_id("brake_pedal_left");
bones::brake_pedal_right = get_joint_id("brake_pedal_right");
bones::throttle_handle = get_joint_id("throttle_lever");

// mesh members
meshes::prop_blur = get_mesh_id("propel_blur");
meshes::blade_one = get_mesh_id("main_blade_01");
meshes::blade_two = get_mesh_id("main_blade_02");
meshes::blade_three = get_mesh_id("main_blade_03");

//sound members
sounds::rumble = load_sound("sounds/engine/engn1.ogg");
//Interior sounds - will be heard from inside the plane
sounds::eng_interior = load_sound("sounds/engine/engn1_inn.ogg");
sounds::prop_interior = load_sound("sounds/engine/prop1_inn.ogg");
//Exterior sounds - will be heard from outside the plane 
sounds::eng_exterior = load_sound("sounds/engine/engn1_out.ogg");
sounds::prop_exterior = load_sound("sounds/engine/prop1_out.ogg");
	
//sound emitters
//Interior emitters
sources::rumble_interior = add_sound_emitter_id(bones::propeller, -1, 0.5f);
sources::eng_interior = add_sound_emitter_id(bones::propeller, 0, 0.5f);
sources::prop_interior = add_sound_emitter_id(bones::propeller, 0, 0.5f);
//Exterior emitters
sources::rumble_exterior = add_sound_emitter_id(bones::propeller, 1, 3.0f);
sources::eng_exterior = add_sound_emitter_id(bones::propeller, 0, 3.0f);
sources::prop_exterior = add_sound_emitter_id(bones::propeller, 0, 3.0f);
}

Note: it is possible, to use one emitter for interior and exterior, like in previous tutorial.

You can find model bones in Scene editor -> Entity properties -> Skeleton.

.

"ot::light_params" is a user-defined type that encapsulates the properties or parameters associated with light source.

Set up landing lights.

ot::light_params light_parameters = { .size = 0.1f, .angle = 100.f, .edge = 0.25f, .intensity = 5.f, .color = { 1.0f, 1.0f, 1.0f, 0.0f }, .fadeout = 0.05f,  };
add_spot_light({ 4.5f, 1.08f, 0.98f }, {-0.1f, 1.f, 0.3f}, light_parameters );
add_spot_light({ -4.5f, 1.08f, 0.98f }, {0.1f, 1.f, 0.3f}, light_parameters );

Set up navigation lights.

light_parameters = { .size = 0.035f, .angle = 100.f, .edge = 1.f, .intensity = 20.f, .color = { 1.0f, 1.0f, 1.0f, 0.0f }, .range = 0.0001f, .fadeout = 0.1f, };
nav_light_offset = add_point_light({ 5.08f, 0.18f, 1.33f }, light_parameters );

light_parameters.color = { 0.f, 1.f, 0.f, 0.f };
add_point_light({-5.05f, 0.18f, 1.33f}, light_parameters );

You can find more about lights and light parameters here.

Note: class/struct members need to be defined in the same order, as they are declared. This is the case for light parameters, wheel parameters, init_chassis return parameters etc.

Register action handlers, which are called, when binded input/object changed it's state.

//Register event handler
register_event_handler("air/engines/on", &tutorial_aircraft_plugin::engine);
//Register axis handlers
register_axis_handler("air/controls/brake", &tutorial_aircraft_plugin::brakes, { .minval = 0.f, .maxval = 1.f, .cenvel = 0.5f, .vel = 1.f, .positions = 0 });
register_axis_handler("air/lights/landing_lights", &tutorial_aircraft_plugin::landing_lights, { .minval = 0.f, .maxval = 1.f, .cenvel = 0.f, .vel = 10.f });
register_axis_handler("air/lights/nav_lights", &tutorial_aircraft_plugin::navigation_lights, { .minval = 0.f, .maxval = 1.f, .cenvel = 0.f, .vel = 10.f });
register_axis_handler("air/controls/aileron", &tutorial_aircraft_plugin::ailerons, { .minval = -1.f, .maxval = 1.f, .cenvel = 0.5f, .vel = 0.5f, .positions = 0 });
register_axis_handler("air/controls/elevator", &tutorial_aircraft_plugin::elevator, { .minval = -1.f, .maxval = 1.f, .cenvel = 0.5f, .vel = 0.5f, .positions = 0 });

You can find more about action handlers here.

Return chassis parameters.

return {
	.mass = 1310,
	.com_offset = {0.0f, 0.0f, 0.2f},
};

In initialize

initialize is the same as init_vehicle in vehicle interface. It is invoked for each new instance of the aircraft (including the first one), and it's used to define per-instance parameters.

  • reload - true if object is being reloaded.
void tutorial_aircraft_plugin::initialize(bool reload)
{

}

Get interfaces.

//Get JSBSim interface
this->jsbsim = jsb();
//Get geomob interface
this->geom = get_geomob(0);
//Get sound interface
this->snd = sound();

Use "this" keyword to refer to the current instance of the class.

Set initial values and fps position.

// Set initial fps position
set_fps_camera_pos({ 0.f, 1.f, 1.4f });

// Set initial values for the aircraft
this->started = false;
this->braking = 0;

//Safety check for jsbsim pointer to avoid crash
if (!this->jsbsim)
{
	return;
}

// Set initial JSBSim properties (optional)
//Turn off engine
this->jsbsim->operator()("propulsion/starter_cmd", 0);
//Set initial right brake value to 0 
this->jsbsim->operator()("fcs/right-brake-cmd-norm", 0);
//Set initial left brake value to 0 
this->jsbsim->operator()("fcs/left-brake-cmd-norm", 0);
//Set initial throttle value to 0 
this->jsbsim->operator()("fcs/throttle-cmd-norm[0]", 0);
//Set initial mixture value to 0 
this->jsbsim->operator()("fcs/mixture-cmd-norm[0]", 0);

//Set initial pitch value for sound emitters
this->snd->set_pitch(sources::rumble_exterior, 1);
this->snd->set_pitch(sources::rumble_interior, 1);
this->snd->set_pitch(sources::eng_exterior, 1);
this->snd->set_pitch(sources::eng_interior, 1);
this->snd->set_pitch(sources::prop_exterior, 1);
this->snd->set_pitch(sources::prop_interior, 1);

//Set initial gain value for sound emitters
this->snd->set_pitch(sources::rumble_exterior, 1);
this->snd->set_pitch(sources::rumble_interior, 1);
this->snd->set_pitch(sources::eng_exterior, 1);
this->snd->set_pitch(sources::eng_interior, 1);
this->snd->set_pitch(sources::prop_exterior, 1);
this->snd->set_pitch(sources::prop_interior, 1);
}

Note: JSBSim commands are normalized.

In update_frame

Add safety check for pointers to avoid crashes.

if (!this->jsbsim || !this->geom || !this->snd)
{
	return;
}

Store JSBSim data.

float propeller_rpm = static_cast<float>(this->jsbsim->operator()("propulsion/engine[0]/propeller-rpm"));
float wheel_speed = static_cast<float>(this->jsbsim->operator()("gear/unit[0]/wheel-speed-fps"));
float elevator_pos_rad = static_cast<float>(this->jsbsim->operator()("fcs/elevator-pos-rad"));

Update the orientation of different components.

rotate_joint - incremental, bone rotates by given angle every time, this function is called (given angle is added to joint current orientation).

rotate_joint_orig - rotate to given angle (from default orientation).

this->geom->rotate_joint(bones::propeller, dt * (2 * static_cast<float>(PI)) * propeller_rpm , { 0.f, 1.f, 0.f });
this->geom->rotate_joint(bones::wheel_front, dt * static_cast<float>(PI) * (wheel_speed / 5), { -1.f, 0.f, 0.f });
this->geom->rotate_joint(bones::wheel_right, dt * static_cast<float>(PI) * (wheel_speed / 5), { -1.f, 0.f, 0.f });
this->geom->rotate_joint(bones::wheel_left, dt * static_cast<float>(PI) * (wheel_speed / 5), { -1.f, 0.f, 0.f });
this->geom->rotate_joint_orig(bones::elevator_right, elevator_pos_rad, { 1.f, 0.f, 0.f });
this->geom->rotate_joint_orig(bones::elevator_left, elevator_pos_rad, { 1.f, 0.f, 0.f });
this->geom->rotate_joint_orig(bones::aileron_right, static_cast<float>(this->jsbsim->operator()("fcs/right-aileron-pos-rad")), { -1.f, 0.f, 0.f });
this->geom->rotate_joint_orig(bones::aileron_left, static_cast<float>(this->jsbsim->operator()("fcs/left-aileron-pos-rad")), { 1.f, 0.f, 0.f });
this->geom->rotate_joint_orig(bones::flap_right, static_cast<float>(this->jsbsim->operator()("fcs/flap-pos-rad")), { 1.f, 0.f, 0.f });
this->geom->rotate_joint_orig(bones::flap_left, static_cast<float>(this->jsbsim->operator()("fcs/flap-pos-rad")), { 1.f, 0.f, 0.f });
this->geom->rotate_joint_orig(bones::rudder, static_cast<float>(this->jsbsim->operator()("fcs/rudder-pos-rad")), { 0.f, 0.f, -1.f });
this->geom->rotate_joint_orig(bones::brake_pedal_left, static_cast<float>(this->jsbsim->operator()("fcs/left-brake-cmd-norm")) * 0.7f, { -1.f, 0.f, 0.f });
this->geom->rotate_joint_orig(bones::brake_pedal_right, static_cast<float>(this->jsbsim->operator()("fcs/right-brake-cmd-norm")) * 0.7f, { -1.f, 0.f, 0.f });

   
//Rudder has 2 pedals for turning, therefore depending on the rudder position value from JSBSim, the left or right pedal will move
if (static_cast<float>(this->jsbsim->operator()("fcs/rudder-pos-rad")) > 0)
{
	this->geom->move_joint_orig(bones::rudder_pedal_left, { 0, static_cast<float>(this->jsbsim->operator()("fcs/rudder-pos-rad")) / 5, 0 });
}
else if (static_cast<float>(this->jsbsim->operator()("fcs/rudder-pos-rad")) < 0)
{
	this->geom->move_joint_orig(bones::rudder_pedal_right, { 0, -static_cast<float>(this->jsbsim->operator()("fcs/rudder-pos-rad")) / 5,0 });
}

//Move throttle handle, depending on the throttle command value 
this->geom->move_joint_orig(bones::throttle_handle, { 0, static_cast<float>(this->jsbsim->operator()("fcs/throttle-cmd-norm[0]")) / 7, 0 });

Note: in this case the pitch/roll handle will rotate without the need of coding, because it's binded with inputs in the model

Update the visibility of propeller meshes, based on the engine rpm.

We want the propeller blades to not be visible, when propeller_rpm is bigger than 300, so that only the propeller blur is visible).

this->geom->set_mesh_visible_id(meshes::prop_blur, propeller_rpm >= 200.f);
this->geom->set_mesh_visible_id(meshes::blade_one, propeller_rpm <= 300.f);
this->geom->set_mesh_visible_id(meshes::blade_two, propeller_rpm <= 300.f);
this->geom->set_mesh_visible_id(meshes::blade_three, propeller_rpm <= 300.f);

When engine runs, control sounds and sound effects, based on the camera position and engine RPM.

if (propeller_rpm > 0)
{
	//get_camera_mode returns 0, when the first person view, inside the plane is active
	if (get_camera_mode() == 0)
	{
		//Turn off the exterior emitters, when player camera is inside the plane
		this->snd->stop(sources::rumble_exterior);
		this->snd->stop(sources::eng_exterior);
		this->snd->stop(sources::prop_exterior);

		//Set pitch for interior engine emitter and clamp it between 0 and 1, in these cases, the pitch & gain values are calculated denepding on the engine rpm.
		this->snd->set_pitch(sources::eng_interior, clamp(1 + propeller_rpm / 4000.f, 1.f, 2.f));
		//Set gain for interior engine emitter and clamp it between 0 and 0.5
		this->snd->set_gain(sources::eng_interior, clamp(propeller_rpm / 5000.f, 0.f, 0.5f));

		//If there is no sound already playing on given emitter, play sound
		if (!this->snd->is_playing(sources::eng_interior))
		{
			//Play interior engine sound in a loop
			this->snd->play_loop(sources::eng_interior, sounds::eng_interior);
		}

		//Set gain for interior propeller emitter
		this->snd->set_gain(sources::prop_interior, clamp(propeller_rpm / 7000.f, 0.f, 0.5f));

		if (!this->snd->is_playing(sources::prop_interior))
		{
			//Play interior propeller sound in a loop
			this->snd->play_loop(sources::prop_interior, sounds::rumble);
		}

		//Set gain for interior rumble emitter
		this->snd->set_gain(sources::rumble_interior, clamp(propeller_rpm / 6000.f, 0.f, 1.5f));

		if (!this->snd->is_playing(sources::rumble_interior))
		{
			//Play rumble sound in a loop on interior emitter
			this->snd->play_loop(sources::rumble_interior, sounds::rumble);
		}
	}
	else
	{
		//Turn off the interior emitters, when camera is outside the plane
		this->snd->stop(sources::eng_interior);
		this->snd->stop(sources::prop_interior);
		this->snd->stop(sources::rumble_interior);

		//Set pitch for exterior engine emitter
		this->snd->set_pitch(sources::eng_exterior, clamp(1 + propeller_rpm / 1000.f, 1.f, 3.f));
		//Set gain for exterior engine emitter
		this->snd->set_gain(sources::eng_exterior, clamp(propeller_rpm / 450.f, 0.05f, 2.f));

		if (!this->snd->is_playing(sources::eng_exterior))
		{
			//Play exterior engine sound in a loop
			this->snd->play_loop(sources::eng_exterior, sounds::eng_exterior);
		}

		//Set gain for exterior propeller emitter
		this->snd->set_gain(sources::prop_exterior, clamp(propeller_rpm / 900.f, 0.f, 2.f));

		if (!this->snd->is_playing(sources::prop_exterior))
		{
			//Play exterior propeller sound in a loop
			this->snd->play_loop(sources::prop_exterior, sounds::eng_exterior);
		}

		//Set gain for exterior rumble emitter
		this->snd->set_gain(sources::rumble_exterior, clamp(propeller_rpm / 1200.f, 0.f, 2.f));

		if (!this->snd->is_playing(sources::rumble_exterior))
		{
			//Play rumble sound in a loop on exterior emitter
			this->snd->play_loop(sources::rumble_exterior, sounds::rumble);
		}
	}
}
else
{
	//Stop all sounds playing on given emitters, when engine is turned off and propeller stops rotating
	this->snd->stop(sources::eng_exterior);
	this->snd->stop(sources::eng_interior);
	this->snd->stop(sources::prop_exterior);
	this->snd->stop(sources::prop_interior);
	this->snd->stop(sources::rumble_exterior);
	this->snd->stop(sources::rumble_interior);
}

Add (temporary) parking brake

//Active when in idle state
if (!this->started && propeller_rpm < 5)
{
	this->jsbsim->operator()("fcs/center-brake-cmd-norm", 1);
	this->jsbsim->operator()("fcs/left-brake-cmd-norm", 1);
	this->jsbsim->operator()("fcs/right-brake-cmd-norm", 1);
}
else if (this->braking < 0.1)
{
	this->jsbsim->operator()("fcs/center-brake-cmd-norm", 0);
	this->jsbsim->operator()("fcs/left-brake-cmd-norm", 0);
	this->jsbsim->operator()("fcs/right-brake-cmd-norm", 0);
}

Output files

After creating your code, the next step is to compile the project to generate the necessary output files, including the .dll and .pdb files.

Move the .dll and .pdb files into plugins folder, located in your project file's bin folder.

Warning: for these plugins to work, these files need to have sufix "_plugin".

You can find example output files here

⚠️ **GitHub.com Fallback** ⚠️