Scripting system

  • The scripting system in LOTUS makes the vehicles and objects alive.

    1 General

    The syntax of the scripting system follows the programming language Pascal. The scripting language is not object orientated!

    2 Bugs & Weaknesses

    At this point, we would like to mention known bugs of the script language, over which we unfortunately have no control, as well as weaknesses that cannot be compensated for easily:

    • Accidental integer division: If a division is performed with "/", Pascal expects a "real" division in any case. 3/2 thus results in 0.5. However, if this is written exactly like this in Pascal Script, the two numbers are interpreted as integer values (which is ok so far), but then also an integer division is performed, i.e. what you normally do with "div", in this example the result is 1. The solution is to write 3.0/2.0, which forces the compiler to interpret these constants as singles, which in turn results in the "/" being performed properly.
    • Apostrophes in comments: There are generally problems if there are apostrophes (') in comments, because the script precompiler coughs up here (because of string recognition). Therefore apostrophes should only be used in comments for string representations (e.g. when something is commented out), i.e. especially not "single".

    3 Structure

    A script for an object may be written completely into a single main file - or any number of variables, procedures or declarations can be put into separate files that have to be included then.


    The scripting system doesn't know the usual uses-clauses, but the following compiler instruction: {$I additionalfile.pas}. This is why the main file of a script with additional files looks like this:



    The first section serves the declarations of externally triggered, so called public variables ('vars'). The second section defines the keyboard/controller/mouse events that are used within the script. Afterwards, the section concerning the pascal files to include follows. Finally three procedures occur that will be called in the following different situations:


    • SimStep is called once per frame. The duration of the last frame can be read in Timegap.
    • Initialize is called once the object is loaded on the map. Here you can set the positions of switchs, for example, that the user should see after he places the vehicle.
    • OnButton is called everytime the user clicks on an element with his mouse, or hits a key or button of his keyboard or controller that is linked to an event. It has three parameters:
      • id states, which event has been called, for example Throttle
      • value is true, if the button is pressed and false, when the button is released
      • cockpitIndex states, which cockpit the user is in. That way prevents a user to hit the button in all cockpits simultaneously.

    4 Basic rules concerning the structure

    • The only mandatory element is the end. at the end of the file. Every other element that is introduced here is optional.
    • The Precompiler instruction {$I additionalFile.pas} is not to be confused with the uses clause: It is just a Copy-Paste-Instruction. Alternatively you could copy+paste your additional script at this place. Hence the order of the Precompiler instructions among themselves and concerning the main file is quite important, since the access to methods and vars can only be achieved within the right order of declarations.
    • The PUBLIC_VARS and the PUBLIC_BUTTONS sections may only be placed in the main file! Such sections will be ignored in additional files.

    5 Description of the sections

    5.1 Public vars / PUBLIC-VARS section

    Public vars are meant for the communication of the script with its environment, for example the physical vars like wheel speed, the animsation control, the sounds, lights, particles, and so on.


    In opposite to the 'normal' local and global vars, that have to be declared in the 'var' section as usual in Pascal, those public vars have to be declared always and only within the "PUBLIC-VARS" section. If they are declared in the 'var' section in addition, the compiler fails.


    This section always has to begin with {PUBLIC_VARS. The vars inside have to be separated by semicolon. The following usual simplification is not allowed:varA, varB, varC: single;.

    5.1.1 Special vars

    There are quite a lot of special system vars, that will be linked with corresponding vars of LOTUS depending from the object type. They just need to be declared within the PUBLIC-VARS section and hence be automatically linked.


    This is a list of all system vars and further instructions: !>

    5.2 Input events / PUBLIC-BUTTONS section

    When the user hits a button of his keyboard or gamecontroller or clicks on a vehicle element that has been linked to an event ID, OnButton is called. This forwards the event ID that has been linked with the key or the vehicle element. This is the way the script programmer is able to assign a process to an event.


    In addition, all events that the vehicle should listen to, need to be listed in the PUBLIC_BUTTONS section in order to provide this list to the vehicle constructor when he edits the clickable elements of the vehicle, and to provide it to the user so he can edit his keyboard settings accordingly.


    Therefor the section PUBLIC_BUTTONS has to be used and the Input Events have to be separated by a semikolon, again.

    5.3 SimStep prozedure

    Except for very special needs, every script should contain a procedure declared as procedure SimStep;.


    The calculations of the simulation, like 3D graphics, physics, AI and vehicle system, need to be made in the highest potential frequency to grant the user an experience of a steady flow. Most of the vars change from frame to frame just a little bit - take into account that for 50 FPS ingame they change over 20 milliseconds only.


    All script processes that have to repeat frequently as well - for example the simulation of pneumatic or electrical systems, deceleration switches, flash light relays and so one - have to be called by the simulation process frequently!


    Therefore the SimStep procedure is used: It is called once per frame - at least. To provide a precise calculation of the systems and even timers, the System var Timegap: single; is given. It contains the time that has passed since the last call of SimStep.


    5.4 SimStep_RC prozedure


    Vehicles can additionally contain a procedure with the declaration procedure SimStep_RC;. It works as SimStep, but is called if the vehicle is in "Remote control mode", like when it is controlled by the AI or even by another multiplayer gamer. For performance reasons this script should be a lot simpler, since there are a lot of remote controlled vehicles on a map, but always only a single user controlled one.


    If you do not implement a SimStep_RC procedure, the vehicle remains passive.

    5.5 SimStep_LOD-Prozedur

    By default, the script of an object stops completely, if the object is not be displayed anymore due to its distance. To prevent this, you can activate the object option "Script runs even if object is not visible (too far away)". If so, the object switches to its LOD-Simstep if it is not visible.


    If the object is a traffic light or signal, you can prevent the light effects to be hidden for a given distance, even if the object won't be rendered anymore. For this purpose, set the option "Min. distance for light effects are visible".


    If you add now a SimStep_LOD procedure to you script, this will be called in this distance range, while the object is already hidden, but the light effects are still visible. To save performance, this procedure will only be called every 10th frame. If you don't include a SimStep_LOD procedure, the script will be completely inactive already in this distance.

    5.6 Initialize

    The procedure with the declaration procedure Initialize; is called only once - immediately after loading of the object and before the first call of SimStep. It can be used to initialize vars on a special value. If the battery main switch should be on when the vehicle is placed, this call would be placed like this and in this section: battery_main := true;

    5.7 InitializeAfterConstSet

    This procedure will be called just one time after loading the vehicle, but – instead of "Initialize" – just after all constants, the vehicle number and the vehicle registration are written into the corresponding script variables.

    5.8 Finalize

    On special occurencies it can be useful to do some things in the script after the whole simulation process. For this quite rare case this procedure can be used: procedure Finalize;

    5.9 OnButton

    A quite important procedure is also procedure OnButton(id: string; value: boolean; cockpitIndex: byte);. It is called everytime the user calls an event (see above).


    Besides the Event ID, which is a string, the procedure contains value, as well, since it is called on the push of the button and on the release as well! Hence value is true for "just pushed" and false for "just released".


    After all, for vehicles with more than one cockpit, the parameter cockpitIndex indicates, from which cockpit the user called the event. In case of the event being called via keyboard or controller, the simulation checks, where the player currently is. On the opposite, for mouse-clickable events the cockpit index has to be set in the properties of the object, if the vehicle has more than one.

    6 Special procedures

    Besides the explicitly listed procedures, there are additional pre-defined functions.

    6.1 Mathmatical functions

    • Min(X, Y): Returns the smaller value of X and Y (single)
    • Max(X, Y): Returns the higher value of X and Y (single)
    • IntMin(A, B): Returns the smaller value of X and Y (integer)
    • IntMax(A, B): Returns the higher value of X and Y (integer)
    • Abs(X): Absolute value of X - if X<0, the positive value is returned
    • Sign(X): Sign function: If X>0, it returns 1, if X<0 it returns -1, else 0.
    • Sqr(X): Square of X
    • Sqrt(X): Square root of X
    • Exp(X): Exponential function to basis E
    • Ln(X): Natural logarithm (to base E)
    • Power(base, exponent): Power function, returns "base to the power of exponent"
    • Sin(X): Sinus
    • Cos(X): Cosinus
    • Tan(X): Tangens
    • ArcSin: Arcus-Sinus (inverse function of Sinus)
    • ArcCos: Arcus-Cosinus (inverse function of Cosinus)
    • ArcTan: Arcus-Tangens (inverse function of Tangens)
    • Random: Random number between (including) 0 and (excluding) 1

    6.2 Strings

    • UChar(id: word): Turns a unicode value into the corresponding char. See also Text textures.
    • RemSpacesBeginEnd(text: string): Removes spaces, quotes ("), enjambments and tab stops before and after the string.
    • StrGrThan(A, B: string): Returns true if "B" is alphabetically behind "A"
    • StrSmThan(A, B: string): Returns true if "A" is alphabetically behind "B"
    • StrCutBegin(text: string; n: integer): Removes "n" chars at the beginning of the string
    • StrCutEnd(text: string; n: integer): Removes "n" chars at the end of the string
    • StrSetLenL(text: string; n: integer): Sets the length of the string left-adjusted to "n" chars. Is the string larger, it gets cut on the right side, is it smaller, spaces will be added on the right side.
    • StrSetLenC(text: string; n: integer): Sets the length of the string justified to "n" chars. Is the string larger, it gets cut on the both sides symmetrically, is it smaller, spaces will be added on the both sides.
    • StrSetLenR(text: string; n: integer): Sets the length of the string right-adjusted to "n" chars. Is the string larger, it gets cut on the left side, is it smaller, spaces will be added on the left side.
    • text[i]: Attaching square brackets containing an integer var, the i-th char of the string is returned. Attention: This function is 1-based, thus the first char of the string is returned with text[1]. And also very important: This does not work with variables declared in the "PUBLIC VARS" section!
    • RegEx(Input: string; Pattern: string): Checks, wether the string "Input" follows the scheme defined in "Pattern". The function calls the Delphi function "TRegEx.IsMatch".
    • RegExReplace(Input: string; Pattern: string; Replacement: string): Replaces all schemes defined by "Pattern" in the "Input" string with the string "Replacement". The function calls the Delphi function "TRegEx.Replace".

    6.3 Colors

    Colors are handled with as a cardinal number in which red, green, blue and alpha are coded. That is why conversion functions have been added:

    • Color(r, g, b, a): Codes a cardinal number from its components
    • Red(col), Green(col), Blue(col), Alpha(col): Decodes a color component from a cardinal number

    6.4 Type conversions

    To switch between different data types, the following type conversions are offered (list is not yet finished):

    • Trunc: Converts a single value into an integer value by cutting off the decimals. Attention: This means that the result of positive numbers is smaller than the original one, but with negative numbers it's larger!
    • Round: Converts a single value into an integer value by rounding it properly. Hence 1,55 turns 2 and 1,43 turns 1.
    • IntToStr: Converts an integer value into a string
    • StrToInt: Converts a string value into an integer value
    • FloatToStr: Converts a single value into a string
    • StrToFloat: Converts a string into a single value
    • IntToStrEnh(value: integer; n: integer; fill: string): Converts the number "value" into a string with the defined length "n". In case the number has more than "n" places, the right places will be cut and a "#" is added. In case it has less than "n" places, "0" will be added to the left. Example:
      • IntToStrEnh(12, 4, '0') results "0012"
      • IntToStrEnh(2456, 3, '0') results "24#"
    • function DecodeDateFullyGetLeap(const DateTime: single; var Year, Month, Day, DOW: Word): This function decodes a date + time information transferred by "DateTime" into the year, month, calendar day and day of the week (DOW). The function returns as a boolean whether it is a leap year or not.

    6.5 Stepwise linear functions

    See here: Stückweise lineare Funktionen in Scripts (translation pending)

    6.6 Textures

    To set textures to materials via script, you have to declare a public integer variable. In the material properties, you can set this variable now right of the normal texture selecting field. Then, LOTUS will take the texture for this material by using this integer variable as index of the internal texture list. Because this is not known public, you will have to get this index with the following function. If it is used and the texture was not loaded yet, this will be done then.

    • GetTextureIndex(userID, contentSubID: integer), you have to enter userID and contentSubID to identify the texture to be set.

    6.7 Script texture drawing commands

    Please see https://www.lotus-simulator.de…-and-drawing-on-textures/.

    6.8 Set Module

    To each vehicle and each scenery object can be fited with module slots, which can be equiped in different ways with separatley developed module objects.


    One of these ways is equipping with the following script procedure:


    SetModule(self: integer; slotindex: integer; userID, contentSubID: integer)


    With this command the module "userID:contentSubID" will be set onto the slot with the index "slotindex".


    With GetModuleSet(self: integer; slotindex: integer): boolean you can check, if the module slot is free or already used by a module.

    6.9 Force Feedback

    Das Gamecontroller-Force-Feedback wird per Script gesteuert. Hierfür gibt es drei Prozeduren, jeweils eine für jeden (unterstützten) Effekt. Mit dem Parameter axisID wird eingestellt, welche Achse gesteuert werden soll und je nach Effekt-Typ müssen ein oder bis zu drei weitere Parameter gesetzt werden – diese müssen sich im Allgemeinen in einem Bereich zwischen 0 und 1, ggf. auch -1 bis 0 bewegen.

    • procedure SetForceFeedbackCenterforce(axisID: string; coeficient, saturation, offset: single): Hiermit wird die Rückstellkraft gesteuert. Je nachdem, wie coeficient und saturation eingestellt werden, verändert sich die Charakteristik dieser Kraft. Wahlweise kann der "Mittelpunkt" auch mit offset verschoben werden (-1 = ganz links, 0 = Mitte, 1 = ganz rechts).
    • procedure SetForceFeedbackFriction(axisID: string; coeficient: single): Hiermit wird die Reibung gesteuert. Je höher coeficient eingestellt wird (maximal 1), desto schwergänger ist der Gamecontroller zu bewegen.
    • procedure SetForceFeedbackVibration(axisID: string; magnitude, period: single): Hiermit kann ein Vibrationseffekt hinzugefügt werden. magnitude steuert die Intensität, period die Frequenz der Vibration.