Scripting System

From OMSIWiki

The scripting system in OMSI lets you equip sceneryobjects and in particular vehicles with individual systems, which communicate via variables and function calls with the main program and the grafics engine respectively the sound engine.

Files of the Scripting System

The scripting system contains the following files:

  • scripting files with executable code (*.osc)
  • varlist files and stringvarlist files in the OMSI\program directory for the definition of systemfiles and predefined files
  • varlist files and stringvarlist files for the definition of uservariables
  • constfile files for the definition of userconstants and function tables

Apart from the files for the definition of systemvariables all required files for each vehicle/sceneryobject (below short: object) must be registered in its Configuration File.

For this purpose, there are the commands [script], [varnamelist], [stringvarnamelist] and [constfile]. The first entry of each command is the number of files followed by the corresponding filenames (including the path relative to the configuration file). The names of the files are not relevant as long as they are correctly entered in the lists.

The function of the respective files will be discussed in the course of describing the scripting language.

Basics of the Scripting Language

The OMSI scripting system works with the Reverse Polish notation. That is, the operator stands behind the two operands; put simply it means, that the operation " 1 + 2 " is instead listed " 1 2 + ". A more complicated example: " (1 + 2) * (4 + 5) " matches " 1 2 + 4 5 + * ".

Note that OMSI textfiles and especially the scripting language is case sensitive! So always pay attention to uppercase and lowercase.

Stack and Register

When processing the scripts OMSI uses a stack for floating-point numbers. Below "stack" always refers to the stack for floating-point numbers. Otherwise it is spoken of the "string stack".

Both stacks contain 8 memory slots, which are numbered from 0 to 7. Every script operation can insert (push) one or multiple new values into one of the stacks, where the new value is set on position 0 and all of the following values "move up a place", or it can pull one or multiple values out of the stack (pop) or just read them (peek).

For the temporary drop of numbers there are additional eight memory slots, which can be written or read directly, in the so called "register" - however, there is only a register for the floating-point numbers.

Example Operations

Example: The operationn " 1 + 4 ". The code for this is:

1 4 +

The table below demonstrates how the stack behaves on this occasion:

operation:	stack:	0	1	2	3	4 ...
-----------------------------------------------------------------------------
before:			0	0	0	0	0
"1"			1	0	0	0	0
"4"			4	1	0	0	0
"+"			5	0	0	0	0

In the initial state the stack is filled only with zeros (or unknown/random values). The command "1" pushes the One into the top stack space and pushes all the other values (zeros) back. The command "4" pushes the Four on the 0. place and all the other values, so in particular the One on the respective next place. In the next step "+" the top two values are pulled out of the stack - that is 4 and 1 - and summed up. The result (5) is in turn pushed into the stack. The two original numbers are no longer present in the stack.

A second example:

(1 + 2) * (4 + 5) must, as already mentioned, be listed as follows:

1 2 + 4 5 + *

So the stack behaves like this:

operation:	stack:	0	1	2	3	4 ...
-----------------------------------------------------------------------------
before:			0	0	0	0	0
"1"			1	0	0	0	0
"2"			2	1	0	0	0
"+"			3	0	0	0	0
"4"			4	3	0	0	0
"5"			5	4	3	0	0
"+"			9	3	0	0	0
"*"			27	0	0	0	0

So in the end the correct result is on stack space 0!

Floating-point Numbers and Strings

The OMSI scripting system works exclusively with the datatypes float (simple precision with sign) and string. Both datatypes have seperated stacks. Therefore the processings of floats ad strings generally run independently side by side. However it can happen that certain functions (e.g. converting numbers to strings) access both parts at the same time.

Boolean variables have been omitted. In general the numerical values ​​0 and 1 are used for boolean values.

Script Keywords

Comments

Comments can only be done by setting an apostrophe ['] at the front of the row to be commentated:

'I am a comment line
   'I am NOT a comment line!

Entry Points and Exit Points, Macros and Trigger

All commands must be between an entry point and an exit point.

The entry point is characterized by one of the keywords listed below:

  • {frame} During each frame OMSI calls through this entry point the framewise script processing, if it is available.
  • {init} Bei der Initialisierung ruft OMSI über diesen Einstiegspunkt die Script-Initialisierung auf.
  • {init} When initializing, OMSI calls through this entry point the script initialization
  • {frame_ai} This is a variant of the {frame}-entry point, which is called with vehicles, when they are not in the focus of the user, e.g. when it is travelling as AI vehicle. If this entry point does not exist, but only {frame}, then the latter is called alternatively. With sceneryobjects this entry point is not considered.
  • {macro:name} This entry point calls a subsection (macro), which must always be defined after the call
  • {trigger:name} This entry point is called from the main program, when the user calls the key combination or the mouse event with the designation name - there are also certain trigger, which are called directly by the main program, e.g. when OMSI changes the display on AI busses

{end} is the universial exit point. It must always close the block, which is opend by one of the commands above.

Macro Call

A macro is called by (M.L.name). The macro must always be called before its definition. Otherwise OMSI will report that the macro could not be found.

Distributing the Script into Several Files

With simple scripts (e.g. for pure AI vehicles or sceneryobjects) it is generally sufficient to create a *.sco-file, which comes with a {frame}...{end}-block and possibly a {init}...{end}-block.

If it is, however, a complete vehicle script, then it is advisable to create separate files for the various subsystems. This case should be approached as follows:

  • There is a main script and the necessary subsystem files. The main file contains the {frame}-blocks and {init}-blocks, which, however, only call macros defined in the subsystem files.
  • There is a *.sco-file for each subsystem and as required an own varlist and consfile with the variables belonging to the system. Furthermore the triggers for the key commands are sorted here.
  • It is sensible to name the macros {macro:subsystem_frame} and {macro:subsystem_init}.
  • To ensure that a makro can be called by the main script, it must be called first in the list of the scripts in the object configuration file. The order of the scripts must also be chosen so that all cross-file macro calls are always before each macro definition.

The restriction that macros must always be defined after the call can indeed sometimes be a hindrance, but is a very effective protection against circular reasoning and infinite loops.

Trigger

As has been written, a {trigger: name} ... {end}-section can be called by different ways from the main program. These include:

  • Triggered via keyboard: Has the key combination been called keycombi, is {trigger:keycombi} called, if the key is pressed and {trigger:keycombi_off} if the key is released.
  • Triggered via mouse: Has a mesh with the [mouseevent]-designation mouse_ev been clicked on, {trigger:mouse_ev} is called if the mouse button is pressed, {trigger:mouse_ev_drag}, while the mouse button is held and {trigger:mouse_ev_off} id the mouse button is released.
  • There is also a number of system triggers.

Operations

Stack Operations

%stackdump% Gives out a dialog box with the floating-point stack content. Should therefore naturally be used only for debugging purposes.
s0, s1, ..., s7 Saving the current stack value in the register indicated by the number. The value remains in the stack.
l0, l1, ..., l7 Loading the corresponding register value and shifting it into the stack. The register value is retained.
d Dublicating the top stack value. All the other stack values are pushed back.
$msg Writing the top string stack value into the debug line of OMSI. Wether it is a AI vehicle, a user vehicle or a sceneryobject.
$d Dublicates the string stack value similar to "d".

Logical Operations

The logical operations operate on the principle 0 = FALSE, everything else is TRUE.

&& AND, that is, if one of the top two stack values ​​is 0, the result is 0, otherwise it is 1
|| OR
! Negation

Rational Operations

Rational operators compare the values in the two top stack spaces and insert, depending on the result, 1 or 0 into the top stack space. The "greater than"/"less than" symbols are here according to the order, in which the values were specified before. That is:

4 2 > is true for the internal operation performed is in this case "4 > 2".

= "1", if the top stack values ​​are identical, else "0".
< "1", if stack value 1 is less than stack value 0, else "0".
> ... is greater than ...
<= ... is smaller than or equal to ...
>= ... is greater than or equal to ...
$= The same as "=" only for the string stack
$< Less than (string). The inequality operations on strings check on alphabetical order. So "A" is less than "B".
$> Greater than (string).
$<= Smaller than or equal to (string).
$>= Greater than or equal to (string).

Mathematical Operations

+ Plus
- Minus (stack space 1 - stack space 0, so in principle, as in the greater/less than operations)
* Multiplication
/ Division (stack space 1 / stack space 0)
% Remainder of the division (extended as follows for floating-point numbers: stack0 - trunc(stack1 / stack0) * stack1 )
/-/ Sign change
sin Sine
arcsin Inverse function of sine (OMSI2 only)
arctan Inverse function of tangent (OMSI2 only)
min Choosing the lesser of the two top stack values
max Choosing the greater of the two top stack values
exp Exponential function to the base e (e^Stack0)
sqrt Square root
sqr Square
sgn Return of the sign; either -1, 0 or 1
pi Circle constant pi
random random integer 0 <= x < stack0
abs Return the absolute value
trunc Rounding down to nearest integer

String Operations

"bla" Insert the string bla into the top string stack space
$+ Join two strings. "Omnibus" "simulator" $+ results in "Omnibussimulator"
$* The top stack string is repeated until the resulting character length is just less than or equal to the top stack value is. Example: "nu" 6 $* results in "nununu"
$length Returns the number of characters in the top stack string back into the stack.
$cutBegin Cuts stack0 characters from the front of the top stack string.
$cutEnd Cuts stack0 characters from the back of the top stack string.
$SetLengthR Adjusts the length of the top stack string to stack0 aligned to the right by removing leading characters or by adding spaces.

Attention: The command does not remove the float operator from the stack. I.e. the value, to which the lenght of the string is truncated, remains on stack0.

$SetLengthC Adjusts the length of the top stack string to stack0 centred by removing leading and trailing characters or by adding spaces (OMSI2 only).

Attention: The command does not remove the float operator from the stack. I.e. the value, to which the lenght of the string is truncated, remains on stack0.

$SetLengthL Adjusts the length of the top stack string to stack0 aligned to the left by removing trailing characters or by adding spaces.

Attention: The command does not remove the float operator from the stack. I.e. the value, to which the lenght of the string is truncated, remains on stack0.

$IntToStr Rounds down stack0 and converts the resulting integer to a string.
$IntToStrEnh The enhanced version of IntToStr. Here, the top stack string is used to determine the format of the string conversion: The first character of the string is used to fill the missing characters. The following characters must result in a number, which indicates the digits. Example: 35 " 5" $IntToStrEnh results in " 35" and 123456789 "011" $ IntToStrEnh results in "00123456789". If an error occures, "ERROR" is output.
$StrToFloat Converts the top stack string to a floating-point number if possible. Otherwise, a -1 is written.
$RemoveSpaces Removes all spaces before and after the actual string. " Spandau Freudstr " becomes "Spandau Freudstr" (OMSI2 only).

Accessing Variables

A distinction is made between system variables and local variables. The system variables apply OMSI-wide, the local variables are stored belonging to the vehicle/object.

For local variables, there are always a number of variables which are predefined by OMSI. In addition, any number of variables can be added using the varlist files. Furthermore there is the variant in OMSI 2 that, although one must define the variable by themselves, it will still be connected to an OMSI internal state ("on-demand pre-defined variables").

(L.S.varname) Loads the system variable varname in the top stack space
(L.L.varname) Loads the local variable varname in the top stack space
(L.$.varname) Loads the local string variable varname in the top string stack space
(S.L.varname) Saves the top stack space into the local variable varname
(S.$.varname) Saves the top string stack space into the local string variable varname

Constants und Functions

Local constants and piecewise defined linear functions can be defined in the constants files (constfiles, ~_constfile.txt). The structure of each constants file is the same as the structure of the configuration files. There are only three keywords. Note that so far no strings but only floats can be defined as constants.

[const] defines a new constant and specifies its value:

[const]
name
value

Example:

[const]
power
200

[newcurve] initiates the definition of a new (piecewise linear) function:

[newcurve]
name

[pnt] adds a new x-y pair to the previously with newcurve defined function. Each function should normally have at least two pairs. The order of the pairs must be ascending in the x-direction!

[pnt]
x
y

Example:

[newcurve]
speed_torque_curve

[pnt]
50
0

[pnt]
300
400

[pnt]
1000
600

Constants are loaded into the stack on the command (C.L.constant name)

Functions are called on the command (F.L.functionname). At that the value previously stored on the top stack position is always passed as the x-parameter and the resulting y-value of the function is instead stored on the top stack space. Is the x-value outside the limits of the vertecies defined by the [pnt]-entries, the y-value of the nearest vertex is always used. Thus the function is extended horizontally to infinity before the first and after the last vertex.

Sound Trigger

Do not mix up the sound triggers with the above mentioned triggers!

They rather represent a way to directly play a desired sound. There are two options for doing this:

(T.L.soundtriggername) is a simple sound trigger. The so marked sound is played (once).

(T.F.soundtriggername) is a sound trigger with file change function. The top stack string is read and used as the file name, where the file path is interpreted as relative to the sound folder of the object.

Example:

Assuming the sound file is "this.wav" and is linked to the trigger My trigger, then

(T.L.MyTrigger)

plays the file "this.wav" and

"other.wav" (T.F.MyTrigger)

plays the file "other.wav". As an alternative to (T.L.soundtriggername) can at the command (T.F.soundtriggername) also be a "null string" in the stack (i.e. "").

System Macros

The current methods allow the scripts following communication options with OMSI:

  • Reading values ​​of the system variables and pre-defined local variables
  • Writing values ​​on them
  • Receiving keyboard, mouse, or system triggers

Finally, there is another way to communicate with the main program, the so-called system macros, sometimes imprecisely referred to as "callback functions".

In certain situations it is necessary that the script gets values ​​from OMSI, which are present in larger databases and therefore can not be transmitted via predefined variables, e.g. the contents of the hof-file. This is where the system macros come in.

They work script-sided very much like the local, self-defined macros: The call (M.V.macroname) calls the system macro ("V" originally stood for "Vehicle-Macro", but applies equally well on scenery objects) which gets values from both stacks and/or writes into them during the execution.

Conditionals and Loops

The control of the program flow is so far only possible with the IF-condition.

IF-Condition

'Here must be a condition, e.g:
    (L.L.bla) 1 =
    {if}
'This section is executed if bla = 1:
        2 3 +
    {else}
'Otherwise this section is executed:
        3 4 +
    {endif}
    (S.L.blub)
{end}

The fact that first the condition is formulated and then the keyword {if} follows, should be clear: First of all the value must be dropped on top of the stack and anly then the keyword {if}, which checks this value, follows. Here, similar to && and ||, equality with zero is equivalent to False, all other values mean True.

So if in the example above bla has the value 1, then blub is set to the value 5, else it is set to 7.

Nesting of IF-conditions is used as a substitute for the (nonexistent) "Else if"-constructions:

'Here must be a condition, e.g:
    (L.L.bla) 1 =
    {if}
'This section is executed if bla = 1:
        2 3 +
    {else}
    (L.L.bla) 5 =
    {if}
'This section is executed if bla = 5:
        3 4 +
    {else}
'Otherwise this section is executed:
        13
    {endif}
    {endif}
    (S.L.blub)
{end}

Pay particular to the double {end if}! Here the following applies:

  • bla = 1 ==> blub = 5
  • bla = 5 ==> blub = 7
  • else ==> blub = 13