Dokumentation GOTHIC
Die Skriptsprache "DAEDALUS"
Autor: Ulf Wohlers Version: 12.Juli 2001


Entworfen von:
  • Dieter Hildebrandt
  • Bert Speckels
  • Ulf Wohlers


    Inhalt:
    1. Einführung
    2. Beschreibung der Syntax
    3. Kommentare
    4. Anweisungen
    5. Ausdrücke
      • Operatoren
      • Ausdruck
    6. Typen, Variablen und Konstanten
      • Elementare Typen
      • C++ und DAEDALUS
      • Variablen und Konstanten
      • Bezugsrahmen: global, lokal
    7. Zuweisungen
    8. Funktionen
      • Definition
      • Parameterübergabe
      • Funktionsaufrufe
    9. Klassen, Schablonen und Instanzen
      • Klassen
      • Prototypen
      • Instanzen
    10. Kontrollstrukturen
    11. Dynamische Variablen
    12. Wichtige Unterschiede zu C++
    Zum Index

    1. Einführung

    Die von uns entworfene Skript-Sprache wird im folgenden Text mit DAEDALUS bezeichnet. Das eigentliche Spielprogramm, das den Skript-Code interpretiert wird in der Regel mit "C++"-Code bezeichnet.

    Dieses Kapitel beschreibt die Skriptsprache DAEDALUS. Beim Design inspirierten uns vor allem die Programmiersprache "C", die Skript-Sprache "Quake-C" und zum Teil Pascal. DAEDALUS weicht jedoch in manchen Bereichen weit von diesen ab.

    Einsatzgebiete von DAEDALUS

    • Dialoge / Informationen
    • NSC - AI
    • Aufträge
    • Text-Pool
    • NPCs (Prototypen und Instanzen)
    • Gegenstände (Prototypen und Instanzen)
    • Implementation von Konzept-Regeln
    • "Event-Programme" (Realisierung komplexer logischer Rätsel)
    • Deklaration von Sound-Resourcen
    • Deklaration von Grafik-Resourcen (auch mit Ani-Information)

    2. Beschreibung der Syntax

    Es gibt 5 Arten von sogenannten Token: Bezeichner (identifier), Schlüsselwörter (keywords), Literale (literals), Operatoren (operators) und andere Trennzeichen. Aus diesen Token ist ein Skript zusammengesetzt. Leerzeichen, Zeilenumbrüche, Kommentare etc. werden ignoriert. Die Länge von Bezeichnern ist nicht beschränkt.

    Bezeichner sind Namen für Variablen, Konstanten, Instanzen, Prototypen, Klassen und Funktionen. Ein Bezeichner ist eine Folge von Buchstaben und Ziffern. Das erste Zeichen muß ein Buchstabe sein. Danach sind Buchstaben, die Ziffern 0 bis 9, sowie der Unterstrich erlaubt.

    Schlüsselwörter sind:

    class       const       else        float      func         if
    instance    int         other       prototype  return       self
    string      this        var         void       while
    

    Literale sind Zeichenketten ("Hallo") und konstante Werte (453). Operatoren werden weiter unten eingeführt.

    In dieser Referenz beschreibe ich die Skriptsprache formal in Anlehnung an das Buch "Die C++ Programmiersprache" von Bjarne Stroustrup. Syntaktische Bezeichner werden in Kursivschrift dargestellt.

    Die Skriptsprache ist nicht case-sensitive (anders als in C).

    Wir empfehlen aber folgende Konventionen für die Namensgebung von Bezeichnern: (Sei "dng" die Kurzkennung für ein Modul "dungeon.D")

    • Funktion: Dng_MoveLift();
    • Variable: dng_buttonsPressed;
    • Konstante: DNG_NUM_TRIES;

    3. Kommentare

    Die Zeichenfolge /* beginnt einen Kommentar, der mit der Zeichenfolge */ beendet wird. Die Zeichenfolge // beginnt einen Kommentar, der bis zum Ende der Zeile geht. Innerhalb eines Kommentars haben die Zeichenfolgen // und /*, sowie */ nach einem Zeilenkommentar keine weitere Bedeutung und werden wie andere Zeichen behandelt.

    Das Kommentarkonzept wurde aus C++ übernommen

    4. Anweisungen

    Anweisungen (statements) sind Deklarationen, Befehle oder auch ein Block von Anweisungen:

    statement
      vardecl
      constdecl
    assignment	
      if-statement
      return-statement
    statement-block
    
    Beispiel: 
    statement:        door_open();
    statement-list:   door_open();  opened = TRUE;
    statement-block:  { door_open(); opened = TRUE; }
    

    5. Ausdrücke

    Operatoren

    Ein Operator "berechnet" aus einem oder zwei Werten den Ergebnis-Wert. Ein Vergleichsoperator sowie ein boolscher Operator liefert den Integer-Wert 1, falls der Ausdruck wahr ist, sonst 0. Bit-Operatoren manipulieren Variablen auf Bit-Ebene. Die Operatorenpriorität wurde von C++ übernommen.

    operator:
      calc-op
      cmp-op
      bool-op
      bit-op
    
    a) Operatoren
    
    calc-op: 
      +   Addition
      -   Subtraktion
      *   Multiplikation
      /   Division
      %   Restdivision (Modulo)
    
    
    c) Vergleichsoperatoren
    
    cmp-op: 
      <   kleiner
      <=  kleiner gleich
      >   größer
      >=  größer gleich
      ==  Gleichheit
      !=  Ungleichheit
    
    d) Boolesche Operatoren
    
    bool-op:
      !   nicht
      &&  und
      ||  oder
    
    
    e) Bitweise Operatoren
    
    bit-op:
      &   and
      |   or
    
    f) Vorzeichen
    
    vorzeichen:
      +   positiv
      -   negativ
    

    Ausdruck

    Ausdrücke werden mit den oben dargestellten Operatoren wie in C üblich gebildet. Hier werden nur Beispiele von Ausdrücken gezeigt.

    expression:
      literal
      calc-expressoin
      cmp-expression
      bool-expression
      bit-expression
    
    a) Ausdruck
    
    expression:
      -x1 + x2
      x1 * (x2 + x3)
      (x2 % 2) * x3
    
    b) Vergleiche (compares)
    
    cmp- expression: 
      x1 < x2
      x1 == x2
    
    c) Boolesche Ausdrücke
    
    bool- expression:
      x1 && x2
      x1 || x2
        (x0)
    
    Ein numerischer Wert gilt als wahr, falls er nicht gleich Null ist.
    
    d) Bitweise Manipulationen
    
    bit- expression:
      x1 | 5;
      x1 & 4;
    

    6. Typen, Variablen und Konstanten

    Es existieren zwei Arten von Typen: elementare Typen und Klassen. Es können keine weiteren Typen definiert werden, wie in C/C++ üblich. Klassen haben eine direkte Entsprechung im C-Code der Engine. Variablen einer Klasse sind die sogenannten Instanzen.

    Elementare Typen

    float
    int
    string
    

    Entsprechen den Typen in C/C++

    Weiterhin können int- und string-arrays gebildet werden:

    VAR INT attribute[MAX_ATTRIBUTES];
    VAR STRING names[MAX_NAMES];
    

    Es können nur eindimensionale Arrays erstellt werden. Die einzelnen Elemente der Felder werden wie in C++ gewohnt angesprochen, es können dafür aber nur Konstanten als Index benutzt werden:

    attribute[1] = 0;
    

    Das erste Element beginnt mit dem Index Null.

    C++ und DAEDALUS

    Funktionen, Variablen und Konstanten, auf die sowohl im C++-Code als auch in DAEDALUS zugegriffen werden muß, werden mindestens in DAEDALUS deklariert. Dazu dient das Schlüsselwort extern. Variablen und Konstanten werden zusätzlich auch definiert, d.h. ihnen werden Werte zugewiesen.

    Variablen und Konstanten

    Eine Variablendeklaration muß durch das Schlüsselwort var eingeleitet werden. Dies gilt für jede einzelne Deklaration, nicht, wie in PASCAL, für einen ganzen Block. Auflistungen von Variablen aber (wie in C) möglich:

    vardecl:
      var  type  identifier [,identifier]opt  [...]opt;
    
    Beispiel:
    
    korrekt:
      var int wert1, wert2, wert3;
      var string frage, antwort;
      var int wert;
    
    falsch:
      int value;
    

    Eine Konstantendefinition muß durch das Schlüsselwort const eingeleitet werden:

    constsdef:
      const type  identifier = expression;
    
    Beispiel:
      const type identifier[x] = { expression, expression, expression };
    

    Bezugsrahmen: global, lokal

    Es existieren zwei verschiedene Bezugsrahmen für Variablen und Konstanten:

    • Eine außerhalb jedes Blockes deklarierte Variable oder Konstante ist global verfügbar: Sie ist nach ihrer Deklaration im gesamten folgenden Skriptteil gültig.
    • Eine innerhalb eines Blockes deklarierte Variable/Konstante ist lokal im Bezug auf den äußersten Block.
    Beispiele:
    
    var int count;
    
    func void Test()
    { var int x; var int y; }
    

    Die variable count ist im Skript global verfügbar. Die Variablen x und y haben wie in C/C++ - den gleichen lokalen Bezugsrahmen: die Funktion Test().

    7. Zuweisungen

    assignment:
      identifier = expression; // einfache Zuweisung
    
    Beispiel:
      var int x1;
      x1 = 40;	
      x1 = x1 / 2;
      x1 = x1* 3;	
    

    8. Funktionen

    Definition

    Funktionsdefinitionen werden mit dem Schlüsselwort func eingeleitet.

    func-def:
      func type identifier ( vardecl1opt , ... , vardecl8opt ) statement-block
    
    Beispiel:
    
      func int UsingSchild(var int x1, var string s1)
      {
        [...]
      };
    

    Parameterübergabe

    Die Länge der Parameterliste ist unbegrenzt, sollte allerdings aus Speicherplatzgründen möglichst gering gehalten werden. Parameter werden call-by-value übergeben, liefern also keinen Wert zurück. Arrays sind als Übergabeparameter nicht erlaubt.

    Funktionsaufrufe

    Funktionen werden wie in C++ üblich aufgerufen. Also mit ihrem Bezeichner sowie einer zwingend notwendigen Parameterklammer.

    9. Klassen, Schablonen und Instanzen

    Klassen

    Die Klassendeklarationen beschreiben exakt die Datenstrukturen der Engine. Sie sind also nicht beliebig im Skript erweiterbar, sondern direkt mit der Engine verknüpft.

    classdecl:
      class  classname  (base-classname)opt  declaration-block
    
    Beispiel:
    
    class Creature (Vob)
    {
      // attributes
      var string  name;
      var int     hitpoints;
      var int     hp_max;
      // actions
      var funcref birth;
      var funcref death;
    };
    

    Die Attribute erhalten Standardwerte:

    • Variablen vom Typ int enthalten den Wert 0
    • Strings den leeren String ""
    • Pointertypen referenzieren NULL.

    Prototypen

    Mit dem Schlüsselwort prototype ist es möglich, sogenannte Prototypen zu erzeugen, die andere Standardwerte haben:

    prototype-def:
    	prototype class-identifier  identifier  statement-block
    

    Ein Prototyp kann man als eine "abgeleitete" Klasse ansehen, bei der NUR die Standardwerte geändert wurden. Oder als eine Instanz der Klasse, die nur als Vorlage für weitere Instanzen dient. Die Definition der Standardwerte findet im statement-block satt.

    Es findet eine Trennung zwischen Klassendeklaration (class), welche die exakte Struktur der Engine-internen Klasse widerspiegelt, und der Klassendefinition (prototype)statt.

    Instanzen

    Instanzen von Klassen oder Prototypen stellen deren konkrete Repräsentationen dar. Die Instanz einer Klasse Material ist ein bestimmtes Material mit all seinen Eigenschaften.

    instance-def:
    	instance class-identifier  identifier  statement-block
    	instance instance-identifier prototype-identifier statement-block
    

    Der statement-block einer Instanz-Definition dient zur Definition der Variablen, die zu der Klasse gehören. Dabei behalten nicht definierte Attribute oder Aktionen ihren Standardwert. Es sind aber auch alle weiteren Anweisungen erlaubt, soweit sie in irgendeiner Weise Sinn machen.

    Kleines (fiktives) Beispiel für Item/Schild/Holzschild
    
    class Item(Vob)
    {
      // attributes
      var int damage;
      var int attack;
      var string description;
      // actions
      var funcref use;
    };
      
    
    prototype SchildProtoType (Item)
    {
      damage = 0;
      attack = 0;
      descriptions = "";
      // actions
      use = UsingSchild();
    };
    
    instance HolzSchild1 (SchildProtoType)
    {
      // attributes
      description = "Ein recht erbärmliches Holzschild";
    };
    

    10. Kontrollstrukturen

    Verzweigung: if-then-else

    Die if-Abfrage wurde aus C++ übernommen. Zu beachten sind nur die eingeschränkten Möglichkeiten von Ausdrücken. Außerdem wird immer ein statement-block erwartet.

    if-statement:
      if ( expression ) statement-block
      if ( expression ) statement-block else statement-block
    
    falsch:   if (x<4) SoundPlay(ID_sound_roar);
    richtig:  if (x<4) { SoundPlay(ID_sound_roar); };
    

    Funktionswert-Rückgabe: return

    In Funktionen, die einen Wert zurückliefern, wird wie in C++ die return-Anweisung verwendet:

    return-statement:
        return ( expression );
    

    11. Dynamische Variablen

    Einige Variablen werden beim Aufruf einer Funktion dynamisch gesetzt und verweisen dann z.B. in einem Dialog auf die Instanz des NPCs (self) und seinen Gesprächspartner(other). Weiterhin wird es Build-In-Funktionen geben, die einen Zugriff auf andere VOBs ermöglicht.

    Zur Unterstützung dieses Konzeptes werden einige globale Variablen deklariert und im Spielverlauf dynamisch gesetzt, so daß diese in Funktionen abgefragt werden können. Die Variablen sind momentan folgende:

    VAR C_NPC self;
    VAR C_NPC other;
    VAR C_NPC victim;
    VAR C_NPC hero;
    VAR C_NPC item;
    

    12. Wichtige Unterschiede zu C++

    Hier sollen nur in Stichworten einige Fallstricke aufgezeigt werden, die aus den Unterschieden zu C++ entstehen. Wo C++ auch einzelne Anweisungen erlaubt, muß in DAEDALUS ein Anweisungsblock stehen. Dies betrifft if-Statement.

    Beispiel:
    
    if (x<4) { SoundPlay(); };