rnp

This tool converts XML code developed in RNP scripting language into RPN language used by Microsoft Flight Simulator.

Aircraft instruments, model behaviors and other parts of MSFS use a scripting language based on Reverse Polish Notation (RPN), which is notoriously difficult to work with, because it uses a mathematical notation that is declared in reverse. For example, 3 + 9 is expressed as 3 9 + and 2 * 4 + 5 is expressed as 2 4 * 5 +. This makes it faster for computers to process, but much harder for humans to read and write, particularly when complexity grows beyond mere additions and multiplications.

RNP is an alternative scripting language that uses standard notation. Its name is a pun on RPN – it’s the reverse of Reverse Polish Notation.

This tool lets you use RNP scripting in instrument and model behavior XML files as if it was RPN and converts such XML files into RPN scripting language for MSFS. All you have to do is wrap RNP code inside <!--RPN--> start tag and <!--RPN END--> end tag and run your files through the tool.

RNP scripting language was originally created by FlyByWire Simulations. This version is a further development with many improvements, but remains fully compatible with code written for the original compiler.


Usage

msfs rnp [-help] [-nobackup] file
-nobackup
Skip automatic backup of the XML file before overwriting it. Backup copies are stored in Windows’ temporary folder.
file
Path to XML file containing RNP code.

Examples

Process “cubes.xml” file:

msfs rnp "cubes.xml"

Contents of “cubes.xml”:

<ModelInfo guid="{2905f7da-3367-4c5b-a459-0fc8cbbd83ee}" version="1.1">
  <LODS>
    <LOD ModelFile="cubes.gltf"/>
  </LODS>
  <Behaviors>
    <Include ModelBehaviorFile="Asobo\Generic.xml"/>

    <Component ID="cube_red" Node="cube_red">
      <UseTemplate Name="ASOBO_GT_Visibility_Code">
        <VISIBILITY_CODE>
          <!--RNP bool-->

          let is_visible = false;

          if (E:TIME OF DAY) == 0 {
            is_visible = true;
          }

          is_visible

          <!--RNP END-->
        </VISIBILITY_CODE>
      </UseTemplate>
    </Component>

    <Component ID="cube_green" Node="cube_green">
      <UseTemplate Name="ASOBO_GT_Visibility_Code">
        <VISIBILITY_CODE>
          <!--RNP bool-->

          let is_visible = (E:TIME OF DAY) == 1;
          let is_even_second = near((E:ABSOLUTE TIME, seconds)) % 2 == 0;

          is_visible and is_even_second

          <!--RNP END-->
        </VISIBILITY_CODE>
      </UseTemplate>
    </Component>

    <Component ID="cube_blue" Node="cube_blue">
      <UseTemplate Name="ASOBO_GT_Visibility_Code">
        <VISIBILITY_CODE>
          <![CDATA[
            <!--RNP bool-->

            (A:AMBIENT TEMPERATURE, celsius) < 0

            <!--RNP END-->
          ]]>
        </VISIBILITY_CODE>
      </UseTemplate>
    </Component>

  </Behaviors>
</ModelInfo>

This model configuration file belongs to a simple SimObject that has red, green and blue cubes:

  • red – visible during dawn,
  • green – visible on even seconds (blinks) during daytime,
  • blue – visible when ambient temperature falls below 0°C.

Red cube initializes is_visible local variable to false and using if block, sets it true when the time of day is dawn (value 0). The last line references is_visible without semicolon at the end to use it as the return value.

Green cube sets is_visible and is_even_second local variables inline and returns their combined value. Both have to be true for the return expression to evaluate to true.

Blue cube is similar to red cube, but checks ambient temperature and makes the cube visibile when it’s below freezing. It does not use a if-structure nor local variables. Instead, the comparison expression is directly set as return value. In addition, code for the blue cube is wrapped inside a CDATA block, which allows to use < operator without having to escape it as &lt; (see Formatting).

Tip
You can get full MSFS project with the described Simobject by downloading simobject-cubes example from Examples.

Syntax

Start and end tags

RNP code blocks must have <!--RNP--> tag at the start of the block and <!--RNP END--> at the end:

<UseTemplate Name="ASOBO_GT_Update">
  <FREQUENCY>1</FREQUENCY>
  <UPDATE_CODE>
    <![CDATA[
      <!--RNP-->

      if (E:TIME OF DAY) != 1 and (A:GENERAL ENG COMBUSTION SOUND PERCENT, percent) > 1 {
        (A:LIGHT HEADLIGHT INTENSITY, percent) = 100;
      } else {
        (A:LIGHT HEADLIGHT INTENSITY, percent) = 0;
      }

      <!--RNP END-->
    ]]>
  </UPDATE_CODE>
  </UseTemplate>

This code sets vehicle headlights to 100% when it’s not daytime and its engine is producing sound (running).

Return values

If the code block returns a value, its type must be added to the starting tag:

<!--RNP bool-->

Supported return types:

  • void – no value (default)
  • any – unspecified type
  • bool – true or false
  • number – double-precision floating point number
  • string – text string

Comments

Single line comment:

// Simple comment

Multi-line comments:

/*
This multi-line comment
spans multiple lines
*/

Local variables

Declare a local variable:

let a = 1.0;

You can add aliases to simplify working with long variable names:

alias alt = (A:PLANE ALTITUDE, feet);

if alt < 50.0 {
  // ... do something
}

Assignments

Set value to a local variable:

a = 20;

Set a simulation variable:

(A:LIGHT BEACON, bool) = true;

Expressions

Boolean true and false literals:

true;
false;

Numbers, hex and binary values:

1.0;
0xFF;
0b101010;

Strings – enclosed in single quotes:

'Remove before flight';

Simulation variables:

(A:LIGHT LANDING, bool);
(L:GPU_ATTACHED);

If you do not declare units (eg bool) for a simulation variable, then number is assumed for E:, I:, L:, O:, P:, Z: variables by default and they will be treated as holding generic floating-point numbers.


Templating

XML template substitutions:

let count = #COUNT#;
let vol = #VOLUME, number#;

If template variable is defined without unit, then it is assumed to be a floating-point number. Declared units are used only internally to check for potential errors.


Formatting

All expressions (except return values) must end with semicolon ;:

let a = 25.0;
let b = 75.0;
let c = a + b;
c

You can wrap your code inside XML CDATA sections to use special XML characters &, <, > directly without having to write them as &amp;, &lt;, &gt;:

<UseTemplate Name="ASOBO_GT_Update">
  <FREQUENCY>1</FREQUENCY>
  <UPDATE_CODE>
    <![CDATA[
      <!--RNP-->

      // ... code here

      <!--RNP END-->
    ]]>
  </UPDATE_CODE>
</UseTemplate>

During processing, rnp tool automatically unpacks CDATA sections and escapes special characters.


Arithmetic operators

Arithmetic operators are very straightforward:

let a = 2 + 5;

You can also use parenthesis:

(L:MYVARIABLE) = 1 + 2 * 3;
(L:MYVARIABLE) = (1 + 2) * 3;

The examples above result in different order of operations:

1 2 3 * + (>L:MYVARIABLE)
1 2 + 3 * (>L:MYVARIABLE)

Supported operators:

OperatorDescription
+Addition
-Subtraction
/Floating-point division (eg 3 / 2 = 1.5)
idivInteger division (eg 3 idiv 2 = 1), also available as div() function
*Multiplication
%Modulo
**Power, also available as pow() function

Bit operators

Conditional logic with bit operators:

let nav_lights = 1;
let landing_lights = 4;

if ((A:LIGHT STATES, number) & (nav_lights | landing_lights)) != 0 {
  // Nav and landing lights are on
}

Supported operators:

OperatorDescription
&Bitwise AND
|Bitwise OR
^Bitwise XOR
~Bitwise NOT
>>Shift bits to the right
<<Shift bits to the left

Comparison operators

Basic comparisons:

if (E:TIME OF DAY) >= 2 {
  // Dusk or night
} else {
  // Dawn or day
}

Supported operators:

OperatorDescription
==Left side equals right side
!=Left side does not equal right side
>Left side is larger than right side
<Right side is larger than left side
>=Left side is larger or equal to right side
<=Right side is larger or equal to left side

Conditionals

Conditional logic follows standard convention:

if val {
  // do something
}
if val1 == val2 {
  // do something
} else {
  // do something else
}
if (E:LOCAL MONTH OF YEAR) == 1 {
  // January
} else if (E:LOCAL MONTH OF YEAR) == 12 {
  // December
} else {
  // other months
}

Compact inline form:

let visible = if (E:LOCAL DAY OF WEEK) == 3 { 1 } else { 0 };

Functions

All functions listed in MSFS SDK are available in RNP:

let x = dgrd(90.0);

This converts 90 degrees to radians, resulting in x = 1.57.

Refer to the SDK for detailed descriptions and examples of each function.
The table below summarizes available functions in the same order as they are listed in the SDK.

FunctionDescription
pmod(a, b)Absolute modulo
neg(val)Negate a number
abs(val)Absolute value
int(val)Round down to nearest integer
flr(val)Round down to nearest integer
rng(min, max, val)True if val is within min .. max range
cos(rad)Cosine, input in radians
lg(val)Logarithm to base 10
min(a, b)Smallest value of a and b
sin(rad)Sine, input in radians
acos(val)Arc cosine, input in radians
ctg(rad)Cotangent, input in radians
ln(val)Natural logarithm
sqr(val)Take square
asin(val)Arc sine
eps(val)Floating-point relative accuracy
log(a, b)Logarithm of a to the base of b
pi()PI value
sqrt(val)Square root
atg2(y, x)Arc tangent with two inputs, both in radians
exp(val)Exponent e to the power of val
max(a, b)Largest value of a and b
pow(a, b)Power of a to the value of b
tg(val)Tangent, input in radians
atg(val)Arc tangent with one input, in radians
sign(val)Sign of the value: -1 when negative, 1 when zero or positive
dec(val)Decimal part of floating-point value
div(a, b)Divide a by b and round down to nearest integer
ceil(val)Round up to nearest integer
near(val)Round up or down to nearest integer
dnor(val)Normalize angle expressed in degrees to 0 .. 360 range
d360(val)Normalize angle expressed in degrees to 0 .. 360 range
rdeg(val)Normalize angle expressed in degrees to 0 .. 360 range
rddg(rad)Convert radians to degrees
dgrd(deg)Convert degrees to radians
rnor(rad)Normalize angle expressed in radians to 0 .. 2*PI range
seed(val)Set seed for rand() function
rand()Generate a random number in 0.0 .. 1.0 range
lc(str)Convert string to lowercase
uc(str)Convert string to uppercase
cap(str)Convrt string to uppercase
chr(int)Convert integer into ASCII character
ord(str)Convert ASCII character into integer
scat(str1, str2)Concatenate two strings
schr(str, char)Finds the position of the specific character in a string
scmp(str1, str2)Case sensitive string comparison
scmi(str1, str2)Case insensitive string comparison
sstr(str, sub)Find the position of substring in a string
ssub(str, start, end)Extract substring from a string
symb(str, pos)Extract a single character from a string

Stack operators

Stack operators are used internally for local variables and are not directly exposed.