Scriptsystem

  • Das OMSI Scriptsystem - Variablen, Operatoren, Makros, Trigger

    1 Allgemeines

    Das Scriptsystem in OMSI dient als Schnittstelle zwischen dem OMSI Spielkern (inklusive der Grafik- und Soundengine) und Szenerieobjekten oder Fahrzeugen. Die Kommunikation findet in beide Richtungen statt, sodass Szenerieobjekte oder Fahrzeuge mit individuellen Scriptroutinen ausgestattet werden können, welche Eingaben oder Informationen vom OMSI Spielkern interpretieren oder dem Spielkern Informationen übermitteln.


    2 Vorraussetzungen

    Um den eigenen Content mit Scriptfunktionen zu erweitern, werden folgende Vorraussetzungen benötigt:

    • eine (oder mehrere) Scriptdateien mit ausführbarem Code (*.osc)
    • varlist- und stringvarlist-Dateien zur Definition von lokalen Variablen
    • constfile-Dateien zur Definition von Userkonstanten und Funktionstabelle

    Alle Dateien, welche vom Fahrzeug/Szenerieobjekt gelesen werden sollen, müssen in der entsprechenden Konfigurationsdatei definiert werden. Dies gilt nicht für vordefinierte Variablen, diese können auch ohne explizite Definition in den Scriptdateien gelesen und geschrieben werden.


    3 Anwendung

    In der Konfiguration können folgende Befehle genutzt werden, um die verschiedenen Dateien zu implementieren:

    • [script]
    • [varnamelist]
    • [stringvarnamelist]
    • [constfile]

    Nach dem Befehl folgt die Anzahl der definierten Dateien, dannach folgen die Dateien mit dem entsprechenden Pfad relativ zur Konfigurationsdatei. Der Dateiname kann frei gewählt werden, Sonderzeichen wie "Ä,Ö,Ü" sollten allerdings vermieden werden, da es zu Problemen mit Systemen kommen kann, welche kein deutsches Windows installiert haben. Am besten hält man sich an das englische Alphabet - dies wird i.d.R. nativ von jedem System unterstützt.


    4 Grundlagen der Scriptsprache

    Das Scriptsystem von OMSI nutzt die umgekehrte polnische Notation (kurz: RPN). Entgegen der typischen Schreibweise einer Rechenoperation (2+5) steht der Operator nicht zwischen den Operanden, sondern dahinter (25+). Auch wenn die RPN auf den ersten Blick ungewohnt wirkt, so hat sie einige Vorteile, die wir weiter unten näher beleuchten werden. Abgesehen von der effektiveren Eingabe der Rechenoperationen) ermöglicht die RPN auch die Stapelverarbeitung, welche in OMSI eine große Bedeutung hat. Ein komplizierteres Beispiel: " (1 + 2) * (3 + 4) " entspricht 1 2 + 3 4 + *.


    Bei der Erstellung von OMSI-Textdateien und Scripts sollte auf die Groß-/Kleinschreibung geachtet werden. OMSI ist Case-Sensitive.


    5 Stack(Stapel) und Register

    Durch die Verwendung der RPN besitzt OMSI einen String-Stack (Komplexe Zeichenfolgen) und einen Gleitkomma-Stack. Im folgenden ist mit "Stack" immer der Gleitkomma-Stack gemeint, ansonsten wird von "String-Stack" gesprochen.


    Beide Stacks enthalten 8 Speicherplätze, welche von 0 bis 7 durchnummeriert sind (0-basiert). Jede Script-Operation fügt einen neuen Wert in den Stack ein (push) oder zieht Werte aus dem Stack herraus (pop/pull). Auch ein Auslesen (peek) ohne das Verändern des Stacks ist möglich. Der zuletzt gesetzte Wert nimmt dabei die erste Stelle im Stack ein, jeder zuvor gesetzte Wert rücken einen Platz "höher".


    Sollen Werte für eine spätere Verarbeitung im Script zwischengespeichert werden, ohne lokale Variablen zu nutzen, so bietet OMSI acht indizierte Speicherplätze an (0-7), welche direkt gelesen und geschrieben werden können und sich im Register befinden. Dies gilt allerdings nur für Gleitkommazahlen, für Strings gibt es kein Register.


    6 Beispiel-Operationen

    Beispiel:

    Die Operation ist 1 + 2. Der Scriptcode dafür sieht folgendermaßen aus:

    1 2 +


    Die folgende Tabelle veranschaulicht, wie sich der Stack bei o.g. Operation verhält:



    Stackplatz 0 Stackplatz 1 Stackplatz 2 Stackplatz 3 Stackplatz 4 Stackplatz 5 Stackplatz 6 Stackplatz 7
    vorher: 0 0 0 0 0 0 0 0
    "1" 1 0 0 0 0 0 0 0
    "2" 2 1 0 0 0 0 0 0
    "+" 3 0 0 0 0 0 0 0



    Im Ausgangszustand ist der Stack i.d.R. mit Nullen gefüllt. Der Befehl "1" schiebt die Eins in den obersten Stackplatz, alle bereits enthaltenen Zahlen rutschen einen Platz weiter hoch. Der Befehl "4" schiebt die Vier in den obersten Stackplatz, dementsprechend die Eins auf den Zweiten und alle weiteren Zahlen einen Platz weiter hoch. Der Befehl "+" vergleicht die ersten beiden Werte im Stack und setzt das Ergebnis auf den ersten Stackplatz. Bei der Verwendung von Operatoren werden die zu berechnenden Werte aus dem Stack entfernt, das Ergebnis steht dann in der Obersten Stelle des Stacks.


    Ein weiteres Beispiel:

    (1 + 2) * (3 + 4) muss wie oben bereits erwähnt folgendermaßen notiert werden: 1 2 + 3 4 + *


    Der Stack verhält sich somit folgendermaßen:


    Stackplatz 0 Stackplatz 1 Stackplatz 2 Stackplatz 3 Stackplatz 4 Stackplatz 5 Stackplatz 6 Stackplatz 7
    vorher: 0 0 0 0 0 0 0 0
    "1" 1 0 0 0 0 0 0 0
    "2" 2 1 0 0 0 0 0 0
    "+" 3 0 0 0 0 0 0 0
    "3" 3 3 0 0 0 0 0 0
    "4" 4 3 3 0 0 0 0 0
    "+" 7 3 0 0 0 0 0 0
    "*" 21 0 0 0 0 0 0 0



    Am Ende der Operation befindet sich das korrekte Ergebnis auf Stackplatz 0.
    Anstatt von Zahlen können auch Variablen oder Ergebnisse anderer Berechnungen als Operanden verwendet werden.


    7 Gleitkommazahlen und Strings

    OMSI arbeitet ausschließlich mit den Datentypen Gleitkommazahl und String. Diese Dateitypen haben getrennte Stack und laufen somit i.d.R. unabhängig nebeneinander. Es ist allerdings möglich, Gleitkommawerte in Strings umzuwandeln oder Strings zu Gleitkommawerten - bestimmte Funktionen greifen demnach eventuell auf beide Bereiche gleichzeitig zu.


    Boolische Variablen finden in OMSI keine Verwendung, i.d.R. werden für true und false die Zahlenwerte 0 und 1 verwendet - also das klassische An/Aus- bzw. Ja/Nein-Prinzip in der Informatik.


    8 Script-Schlüsselwörter

    8.1 Kommentare

    Kommentierungen sind nur möglich, indem in der auszukommentierenden Zeile ganz vorne ein Apostroph ' gesetzt wird:


    Code
    1. 'Ich bin ein Kommentar
    2. 'Ich bin KEIN Kommentar!
    3. _'Ich bin KEIN Kommentar!


    Auch einen eingerückter Bereich via Tabstops erkennt OMSI nicht als Kommentar - der Tabstop wird als Zeichen interpretiert. GANZ VORN bedeutet also auch GANZ VORN!


    8.2 Entry-/Exitpoints, Trigger, Macros

    Alle Befehle müssen zwischen einem Entry- und Exitpoint liegen. Entrypoints werden mit Hilfe der folgenden Schlüsselwörter gekennzeichnet:


    {frame} Scripts, die in diesem Bereich definiert sind, werden in jedem Frame einmal ausgeführt.
    {init} Scripts, die in diesem Bereich definiert sind, werden bei der Initialisierung des Scripts einmalig ausgeführt.
    {frame_ai} Scripts, die in diesem Bereich definiert sind, werden in jedem Frame einmal ausgeführt - allerdings nur, wenn das Fahrzeug nicht im Fokus des Spielers ist. Funktioniert NICHT bei Szenerieobjekten!
    {macro:''name''} Scripts, die in diesem Bereich definiert sind, werden nur ausgeführt, wenn das Macro aufgerufen wird.
    {trigger:''name''} Scripts, die in diesem Bereich definiert sind, werden nur ausgeführt, wenn der Spieler oder OMSI diesen Trigger, z.bsp. per Tastendruck, aufruft.
    {end} Universeller Ausstiegspunkt. muss für jeden der o.g. Befehle definiert werden. Am Ende eines Scripts muss es genausoviele Exitpoints wie Entrypoints geben!


    8.3 Makroaufruf

    Der Aufruf eines Makros erfolgt über (M.L.''name''). Der Aufruf eines Macros muss immer VOR dem Macro selbst erfolgen.


    8.4 Aufteilung des Scripts in mehrere Dateien

    Bei einfachen oder kurzen Scripts kann die Verwendung des Haupt {frame}-{end} Blockes (ggf. in Kombination mit einem {init}-{end} Blockes) genügen. Bei komplexen Scripten empfiehlt sich die Nutzung eines Main-Scriptes, welchen einen Haupt Script- und Init Block enthält. In diesem können dann mit der Verwendung eines {Macro:Name} Sub-Scripte Definiert werden, welche in eigenen Dateien liegen.


    Hierbei sollten die folgenden Vorgaben beachtet werden:

    • Ein Hauptscript, welches die {frame}- und {init}-Blöcke enthält, welche jedoch nur die Subscripts mit Hilfe eines Macros aufrufen
    • Jedes Subscript sollte eine eigene *.osc Datei und - je nach Bedarf - eigene Varlist und Consfiles besitzen. Tastenbefehltrigger, die zu diesem Scriptabschnitt gehören, sollten auch im entsprechenden Subscript definiert werden.
    • Makros sollten sinnvoll benannt werden, damit dritte die Scripts nachvollziehen können - beispielsweise {macro:''subsystem''_frame} und {macro:''subsystem''_init}.
    • Wir errinern uns: Der Aufruf eines Macros muss immer VOR dem Macro selbst erfolgen. Werden also Subscripts benutzt, muss das Hauptscript in der Konfigurationsdatei als erstes aufgerufen werden. Bei dateiübergreifenden Makroaufrufen muss ebenfalls auf die korrekte Reihenfolge geachtet werden.


    8.5 Trigger

    Ein {trigger:''name''}...{end}-Abschnitt kann durch verschiedene Möglichkeiten aus dem Hauptprogramm heraus aufgerufen werden. Hierzu zählen:

    • Auslösung per Tastatur.
      Wurde die Tastenkombination mit dem Namen ''Tastenkombination'' bezeichnet, wird beim Druck der Taste {trigger:Tastenkombination} aufgerufen, beim Loslassen der Taste wird {trigger:Tastenkombination_off} aufgerufen
    • Auslösung per Maus.
      Wurde ein Mesh mit der [mouseevent]-Bezeichnung ''mouse_event'' angeklickt, so wird {trigger:mouse_event} aufgerufen. Beim Gedrückthalten der Maustaste wird {trigger:mouse_event_drag} und beim Loslassen der Maustaste wird {trigger:mouse_event_off} aufgerufen. Ein Mesh kann mit [mouseevent] auch ein normaler Trigger zugewiesen werden. - Die Unterscheidung ist i.d.R. nur dort Notwendig, wo die Mausbewegung Einfluss auf die Animation oder die übermittelten Werte hat (z.B. Drehschalter).


    * Außerdem gibt es noch eine Reihe von LINK:Systemtrigger, die vom OMSI-Spielkern gesetzt werden.


    9 Operationen

    9.1 Stack-Operationen

    %stackdump% Gibt eine Dialogbox mit dem Gleitkomma-Stackinhalt aus (sollte nur zu Debug-Zwecken eingesetzt werden).
    s0, s1, ..., s7 Speichern des aktuellen Stackwertes im durch die Ziffer angegebenen Register. Der Wert verbleibt hierbei im Stack.
    l0, l1, ..., l7 Laden des entsprechenden Registerwertes und Verschiebung in den Stack. Der Wert verbleibt hierbei im Register
    d Dupliziert den obersten Stackwert; alle weiteren Stackwerte rücken nach hinten.
    $msg Schreibt den obersten String-Stack-Wert in die Debug-Zeile von OMSI - egal, ob es sich um ein Fahrzeug oder Szenerieobjekt handelt.
    $d analog zu "d" - dupliziert den obersten Stringstack-Wert.


    9.2 Logische Operationen

    Die logischen Operationen arbeiten nach dem Prinzip 0 = FALSE, alles andere ist TRUE.


    && Wenn der Wert in Stack 0 und Stack 1 identisch, gibt dieser Operator 1 aus; sonst 0.
    || Oder
    ! Verneinung (Invertiert die Variable)



    9.3 Vergleichsoperationen

    Die Vergleichsoperationen vergleichen die Werte in den beiden jeweils obersten Stackplätzen und fügen dann je nach Ergebnis eine 1 oder 0 im obersten Stackplatz ein.


    = "1", falls die obersten Stackwerte identisch sind, sonst "0".
    <
    "1", falls Stackwert 1 kleiner als Stackwert 0, sonst "0".
    > "1", falls Stackwert 1 größer als Stackwert 0, sonst "0".
    <= "1", falls Stackwert 1 kleiner oder gleich Stackwert 0, sonst "0".
    >= "1", falls Stackwert 1 größer oder gleich Stackwert 0, sonst "0".
    $= Wie "=" nur für die obersten beiden Stringstack-Plätze.
    $< $< Kleiner als (String). Die Ungleich-Operationen bei Strings prüfen auf alphabetische Reihenfolge. "A" ist also kleiner als "B".
    $> $>größer als (String). Die Ungleich-Operationen bei Strings prüfen auf alphabetische Reihenfolge. "A" ist also größer als "B".
    $<= Kleiner oder gleich (String).
    $>= Größer oder gleich (String).


    9.4 Mathematische Operationen

    + Addition
    - Subtraktion
    * Multiplikation
    / Division
    % Rest der Division (folgendermaßen erweitert für Gleitkommazahlen: Stack0 - trunc(Stack1 / Stack0) * Stack1 )
    /-/ Vorzeichenwechsel
    sin Sinus
    arcsin Umkehrfunktion zum Sinus
    arctan Umkehrfunktion zum Tangens
    min Wahl des kleineren der beiden obersten Stackwerte
    max Wahl des größeren der beiden obersten Stackwerte
    exp Exponentialfunktion zur Basis e (e^Stack0)
    sqrt Quadratwurzel
    sqr Quadrat
    sgn Rückgabe des Vorzeichens; je nachdem entweder -1, 0 oder 1
    pi Kreiszahl pi (3,14159265...)
    random ganzzahlige Zufallszahl 0 <= x < Stack0
    trunc Abrunden auf nächste ganze Zahl


    9.5 String-Operationen

    "blubb" Einfügen des Strings "blubb" auf dem obersten String-Stack-Platz
    $+ Zusammenfügen zweier Strings. "Tutti" "Frutti" $+ ergibt "TuttiFrutti"
    $* Der oberste Stack-String wird so oft wiederholt, bis die resultierende Zeichenlänge gerade noch kleiner oder gleich des obersten Stackwertes ist. Beispiel: "nom" 8 $* ergibt "nomnomno"
    $length Gibt die Anzahl der Zeichen des obersten Stack-String zurück in den Stack.
    $cutBegin Schneidet "stack0" Zeichen vorne vom obersten Stack-String ab.
    $cutEnd Schneidet ''stack0'' Zeichen hinten vom obersten Stack-String ab.
    $SetLengthR Passt die Länge des obersten Stack-Strings auf ''stack0'' rechtsbündig an, indem am Anfang Zeichen entfernt oder Leerzeichen ergänzt werden.*
    $SetLengthC Passt die Länge des obersten Stack-Strings auf ''stack0'' mittig an, indem am Anfang Zeichen entfernt oder Leerzeichen ergänzt werden.*
    SetLengthL Passt die Länge des obersten Stack-Strings auf ''stack0'' linksbündig an, indem am Anfang Zeichen entfernt oder Leerzeichen ergänzt werden.*
    $IntToStr Rundet ''stack0'' ab und wandelt die resultierende Ganzzahl um in einen String.
    $IntToStrEnh Die erweiterte Version von IntToStr. Dient zum Auffüllen des Strings mit Zeichen. Beispiel: 11 " 5" $IntToStrEnh führt zu " 11" und 123456789 "X11" $IntToStrEnh führt zu "XX123456789". Falls ein Fehler vorliegt, wird "ERROR" ausgegeben.
    $StrToFloat Wandelt den obersten Stack-String in eine Gleitkommazahl um, falls möglich. Andernfalls wird eine -1 geschrieben.
    $RemoveSpaces Entfernt sämtliche Leerzeichen vor und nach dem eigentlichen String. Aus " Leer Zeichen " wird "Leer Zeichen"


    * Achtung: Der Befehl entfernt den Float-Operator nicht vom Stack. Der Wert, auf dessen Länge der String gekürzt wurde, verbleibt also auf Stack0.


    10 Variablenzugriff

    OMSI unterscheidet zwischen Systemvariablen und lokalen Variablen. Systemvariablen können überall gelesen werden, lokale Variablen sind Fahrzeug-/Objektbezogen. Systemvariablen sind Beispielsweise die Zeit oder das Wetter, lokale Variablen sind beispielsweise eine Schalterposition im Fahrzeug oder die Temperatur der Heizung.


    Weiterhin gibt es vordefinierte lokale Variablen, die nicht vom Nutzer definiert werden. Hierzu gehört Beispielsweise die Geschwindigkeit des Busses. Lokale Variablen können vom Nutzer unbegrenzt vergeben werden. Außerdem gibt es sogenannte "on-demand" variablen, welche von OMSI zwar vordefiniert werden, bei Nutzung aber trotzdem selbst definiert werden müssen.


    (L.S.''varname'') Lädt die Systemvariable ''varname'' in den obersten Stackplatz
    (L.L.''varname'') Lädt die lokale Variable ''varname'' in den obersten Stackplatz
    (S.L.''varname'') Speichert den obersten Stackplatz in die lokale Variable ''varname''
    (L.$.''varname'') Lädt die lokale String-Variable ''varname'' in den obersten String-Stackplatz
    (S.$.''varname'') Speichert den obersten String-Stackplatz in die lokale String-Variable ''varname''


    Eine detaillierte Beschreibung der einzelnen verfügbaren System- und vordefinierten, lokalen Variablen finden Sie hier: LINK Variablen


    11 Konstanten und Funktionen


    Lokale Konstanten und stückweise definierte Funktionen können in den Konstantendateien (''constfiles'', ''~_constfile.txt'') definiert werden. Der Aufbau jeder Konstantendatei besteht aus einem Schlüsselwort , einer Variable und den entsprechenden Werten. Es können nur Gleitkommawerte definiert werden, keine Strings.


    [const] definiert eine neue Konstante und gibt ihren Wert an:


    Code
    1. [const]
    2. name
    3. wert


    Beispiel:

    Code
    1. [const]
    2. Power
    3. 200


    [newcurve] leitet die Definition einer neuen (stückweise linearen) Funktion ein:

    Code
    1. [newcurve]
    2. name


    [pnt] fügt der zuvor mit [newcurve] definierten Funktion ein neues x-y-Paar hinzu. Jede Funktion sollte normalerweise über mindestens zwei Paare verfügen. Die Reihenfolge der Paare ''muss'' in x-Richtung aufsteigend sein!

    Code
    1. [pnt]
    2. x
    3. y


    Beispiel:


    Konstanten werden über den Befehl (C.L.''Konstante'') im Script aufgerufen und in den Stack geladen.


    Funktionen werden über den Befehl (F.L.''Funktion'') aufgerufen. Der auf der Obersten Stackposition befindliche Wert wird dabei als "Input" (X-Paramenter) für die Kurve genutzt. Ausgegeben wird der Y-Wert, welcher von der Funktion an Position X definiert wurde. Bewegt sich der x-Wert außerhalb der Grenzen der durch die [pnt]-Einträge definierten Eckpunkte, wird stets der y-Wert des nächstliegenden Eckpunktes verwendet. Die Funktion wird also vor dem ersten und hinter dem letzten Eckpunkt ins Unendliche horizontal verlängert.


    12 Sound-Trigger

    Sound Trigger sind keine Trigger, die mit Tastatureingaben oder Mauseeingaben gesteuert werden können. Sound Trigger werdem vom Fahrzeug/Szeneriescript ausgelöst und diese dienen der Möglichkeit, Sounds abzuspielen. Hierzu gibt es zwei Möglichkeiten:


    (T.F.''Soundtrigger'') Spielt einen durch das Script definierten Sound einmalig ab. Hierbei wird der oberste Stackstring ausgelesen und als Dateiname verwendet, wobei der Dateipfad relativ zum Sound-Ordner des Objektes interpretiert wird.



    Beispiel:

    Die mit dem Trigger "Soundtrigger" definierte Sounddatei "Test.wav" wird abgespielt, wenn im Script (T.L.MeinTrigger) aufgerufen wird.


    "Test2.wav" (T.F.MeinTrigger) spielt die Datei Test2.wav ab. Dies ist für z.B. Haltestellenansagen notwendig, bei der durch das Fahrzeugscript die aktuelle Haltestelle berechnet wird und so mit dem gleichen Trigger bei jeder Haltestelle eine andere Sounddatei abgespielt werden kann.

    13 System-Makros

    Die bisher beleuchteten Funktionen erlauben dem Script, folgendermaßen mit OMSI zu kommunizieren:

    - Lesen von Werten über System- und vordefinierte lokale Variablen

    - Schreiben von Werten über ebendiese

    - Empfangen von Tastatur-, Maus- oder Systemtriggern


    Neben diesen Möglichkeiten gibt es noch sogenannte "System-Makros". Diese Makros sind komplexe Funktionen, die vom OMSI Script eingaben erwarten und entsprechend Ihrer Funktion Daten zurück an das Script übermitteln. Dies ist beispielsweise für das Auslesen der Hofdatei notwendig.


    Die Anwendung ist identisch mit selbstdefinierten Makros, allerdings erwartet das System-Makro Informationen, die es aus dem Stack holt und das Ergebnis wieder in den Stack schreibt.

    Eine Aufstellung aller System-Makros findet sich hier: LINK System-Makros


    14 Bedingungen und Schleifen

    Die einzige Steuerung des Programmablaufs ist zur Zeit nur mit der IF-Bedingung möglich. Schleifen und Go-To's sind mit dem OMSI Scriptsystem nur bedingt möglich.


    14.1 IF-Bedingung

    Code
    1. 'Hier muss eine Bedingung stehen:
    2. (L.L.blubb) 1 =
    3. {if}
    4. 'Dieser Abschnitt wird ausgeführt, wenn blubb = 1 ist:
    5. 2 3 +
    6. {else}
    7. 'Dieser Abschnitt wird andernfalls ausgeführt:
    8. 3 4 +
    9. {endif}
    10. (S.L.bla)


    Aufgrund der Rückwärtsnotation mit Stapelverarbeitung macht es auch sinn, dass die Bedingung zuerst definiert wird: die Werte müssen ja zuerst in den Stack, damit der Operator diese vergleichen kann. Das Ergebnis ist 0 oder 1, dementsprechend true oder false. mit diesem Wert kann dann auch die {if}-Bedingung ausgewertet werden.


    Wenn also im obigen Beispiel ''blubb'' den Wert 1 hat, wird ''bla'' auf den Wert 5 gesetzt, andernfalls auf den Wert 7.


    Verschachtelungen der IF-Bedingung dienen als Ersatz für die (nicht vorhandenen) "Else-If"-Konstruktionen:


    Hierbei ist insbesondere auf das doppelte {endif} zu achten! Für jede geöffnete {if}-Bedingung muss auch ein {endif} gesetzt werden.

Share