Ressourcen-Icon
Vorwort

Dieses Tutorial ist aus einem interaktiven Scriptkurs im Scharesoft-Portal entstanden. Auch wenn dieser vorzeitig eingestellt werden musste, möchte ich euch hier die Ergebnisse präsentieren.

Dieses Tutorial ist so aufgebaut, dass es zunächst eine Lektion zu einem bestimmten Bereich des Scriptings gibt. Danach folgen einige Aufgaben zu diesem Thema, die ihr selber ausprobieren könnt und solltet. Am Ende des Tutorials befinden sich Lösungsvorschläge für die Aufgaben.

An dieser Stelle möchte ich auch IvanDaVile für seine Mitarbeit im Scriptkurs danken.

Killfetzer


Literatur

Wie in der Uni üblich gebe ich zum Beginn eines Kurses einige Hinweise zur tiefergehenden Lektüre. Das ist natürlich DAS Standardwerk zu Morrowind Script:
Wem dieses zweihundertseitige, engliche Werk zu viel oder zu unverständlich ist, kann ich noch die deutschen "Alternativen" empfehlen:

Wichtiger Hinweis

Legt vor euren Scriptversuchen einen Sicherungsspielstand an. Es wäre doch ziemlich ärgerlich, wenn ihr euch euren Spielstand zerschießt, weil ihr irgendwelche seltsamen Änderungen abspeichert.


Einführung

Warum sollte ich überhaupt Scripten können?

Das Spiel The Elder Scrolls III: Morrowind benutzt eine eigene Scriptsprache um interaktive Elemente des Spiels zu steuern. Diese ist unter dem Namen Morrowind Script oder TES Script bekannt. Da Oblivion ebenfalls eine, komplett andere, Scriptsprache verwendet, halte ich den Begriff Morrowind Script für weniger missverständlich.
Für Modder sind Scripte ein sehr mächtiges Werkzeug. Mit ihnen lassen sich viele sonst unmögliche Dinge in Plugins realisieren. Zum Beispiel lassen sich Gegenstände bewegen, Aktionen des Spielers detektieren und andere Objekte dynamisch auf diese Aktion reagieren lassen. Allgemein kann man mit Scripten die Interaktivität des Spiel stark erhöhen.
Morrowind Script hat den großen Vorteil, dass es nur über sehr wenige grundlegende Befehle verfügt und somit leicht erlernbar ist. Der Nachteil liegt in der teilweise sehr beschränkten Funktionalität der Scriptsprache. Es gibt viele Bereiche in der Spielmechanik können von Scripten nicht oder nur unzureichend beeinflusst werden. Sobald man also über die Standardfunktionen hinausgehen will, ist zwangsläufig Kreativität gefragt.

Wo treffen wir beim Modden eigentlich auf Scripte oder Scriptfunktionen? Da gibt es mehrere Möglichkeiten:
  • Scripte: Natürlich trifft man Scriptfunktionen in den Scripten selbst an. Ein Großteil des Kurses wird sich um diese Art des Scriptens drehen. Scripte können mit dem im Construction Set eingebauten Scripteditor erstellt oder bearbeitet werden. Scripte kann man noch weiter in lokale und globale Scripte unterteilen, aber dazu später mehr.
  • Resultboxen von Dialogen: Auch in den Resultboxen von Dialogen lassen sich Scriptfunktionen ausführen. Es gibt sogar gewisse Funktionen, die nur hier funktionieren. Allgemein liegt die Funktionalität aber weit unter der eines Scripts.
  • Konsole: Die letzte Möglichkeit Scriptfunktionen zu verwendet, stellt die Konsole im Spiel selbst dar. Hier lassen sich vor allem Debuggingfunktionen aufrufen. Allerdings kann sie auch für weitreichende Spielmanipulationen verwendet werden, was den Konsolenfunktionen ungerechter weise den Ruf von Cheats eingebracht hat. In diesem Kurs werdet ihr hoffentlich die Funktionalität der Konsole zu schätzen lernen. Die Funktionalität der Konsole liegt noch einmal unter der der Resultboxen. Hier lässt sich immer nur ein einziger Befehl gleichzeitig ausführen.
Bedienung des Construction Set

Wie kann man nun also Scripte erstellen? Dazu nun eine kurze Einführung in die verschiedenen Optionen für den Scripteditor, die Dialogresultbox und die Konsole:


Das Menü des TES III: Construction Sets

Im obigen Bild sieht man das Menü und die Symbolleiste des TES III: Construction Sets.
Mit dem Symbol 1 kann der Scripteditor geöffnet werden.
Das Menü Gameplay (2) enthält viele für das Scripting wichtige Funktionen. Hier kann man ebenfalls den Scripteditor öffnen (Edit Scripts...), man kann Startscripte definieren (Edit StartScripts...), man kann die Übersicht der globalen Variablen aufrufen (Globals... ) und man erhält Zugriff auf die Sounds und Gamesettings (Settings...).


Der Scripteditor

Der Scripteditor ist ein Werkzeug zur Bearbeitung von Scripts. Er bietet nur sehr grundlegende Funktionen, aber reichen sie normalerweise. Es gibt auch externe Scripteditoren für Morrowind-Script, auf diese werde ich aber nicht eingehen.
In der Titelleiste des Editors (1) steht in eckigen Klammern der Name des gerade geladenen Scripts. Wenn das geladene Script ungespeicherte Änderungen enthält, wird am Ende des Namens ein * angezeigt.
Darunter befindet sich das Menü des Editors (2). Dieses Menü enthält die folgenden Punkte:
  • Script
    Dieses Menü wird eigentlich nur für das Erstellen eines neuen Scripts benötigt. Alle anderen Funktionen sind in der Symbolleiste enthalten.
    • New...
      Öffnet ein neues, leeres Script.

    • Open...
      Öffnet ein Fenster Script Pick mit allen Scripten in alphabetischer Reihenfolge. Doppelklick auf das gewünschte Script öffnet dieses. Die Namen von Scripten, die in der aktiven ESP geändert wurden, werden mit einem zusätzlichen * am Ende gekennzeichnet. Wenn das geladene Script nicht gespeicherte Änderungen enthält, wird gefragt, ob die Änderungen gespeichert werden sollen.

    • Previous Script
      Öffnet das Script, das alphabetisch eins vor dem gerade geladenen Script in der Liste steht.

    • Next Script
      Öffnet das Script, das alphabetisch eins nach dem gerade geladenen Script in der Liste steht.

    • Save
      Kompiliert das geladene Script und gibt Fehlermeldungen aus. Danach wird das aktuelle Script unter dem Namen, der hinter dem begin angegeben ist gespeichert. Vorhandene Scripte werden nicht überschrieben. Im Fall doppelter Namen wird das Script unter dem gleichen Namen gespeichert. Dies verursacht erst Probleme beim erneuten Laden der ESP!

      Was ist kompilieren?
      Ein Computer versteht die Sprache der Menschen nicht und wir können mit der Maschinensprache nichts anfangen. Eine Programmiersprache ist ein Zwischending zwischen diesen beiden. Sie besitzt sehr strenge Regeln, wie man bestimmte (für den Menschen verständliche) Wörter (eben die Befehle und Funktionen) aneinander reihen kann. Trotzdem kann das ein Computer nicht verstehen.
      Allerdings gibt es für jede Programmiersprache ein Programm, den Compiler, der den Quelltext für den Computer in Maschienensprache übersetzt. Dazu versucht er den vorhandenen Quelltext nach bestimmten Regeln in Maschienensprache zu übersetzen. Kann er das nicht, gibt er eine Fehlermeldung zurück. Diesen gesamten Vorgang nennt man kompilieren.
      Anmerkung: Die Ausgabe des Compilers ist für uns in Morrowind Script nicht sichtbar. Diese ist irgendwo tief in der ESP versteckt. Wir sehen auch nach dem Kompilieren nur den normalen Quelltext (zum Glück).

    • Recompile All
      Alle Scripte werden neu kompiliert. Fehlermeldungen aus allen Scripten werden ausgegeben.

    • Delete
      Öffnet ein Fenster Script Pick mit allen Scripten in alphabetischer Reihenfolge. Doppelklick auf das gewünschte Script markiert dieses zum Löschen. Dies ist am Eintrag DELETED zu erkennen. Tatsächlich gelöscht wird das Script erst beim Schließen (bzw. Neuladen) der ESP.

    • Exit
      Schließt den Scripteditor. Wenn das geladene Script nicht gespeicherte Änderungen enthält, wird gefragt, ob die Änderungen gespeichert werden sollen.
  • Edit
    • Undo
      Macht alle Änderungen bis zum letzten Speichern rückgängig. Die Tastenkombination Strg+Z hat die gleiche Wirkung.

    • Redo
      Hebt das Rückgängigmachen wieder auf. Die Tastenkombination Strg+Y hat die gleiche Wirkung.
  • Help
    • Contents
      Öffnet die Hilfe.

    • Commands
      Öffnet die Script Commands Seite der Hilfe. Da es nur sehr wenige Befehle gibt, sollte diese nach den ersten paar Scripten überflüssig sein, weil man dann alle auswendig kennt.

    • Functions
      Öffnet die Script Functions Seite der Hilfe. Diese führt viele der Scriptfunktionen thematisch sortiert auf. Leider nicht alle, viele leider ohne Erklärung und dazu gibt es noch einige Fehler auf der Seite. Allerdings ist dies durchaus für einen schnellen Blick geeignet, wenn man sich zum Beispiel die Reihenfolge der Parameter einer bestimmten Funktion ansehen möchte. Trotzdem ist eine externe Liste meistens besser geeignet.
Unter dem Menü befindet sich die Symbolleiste (3). Die einzelnen Symbole entsprechen den Befehlen im Menü Script. Von links nach rechts haben die Schaltflächen folgende Funktionen: Open, Save, Previous Script, Next Script, Recompile All, Delete, Exit. Darunter befindet sich das Editorfenster (4), dass sich wie der normale Editor in Windows benimmt (Notepad.exe). In der Fußleiste (5) wird schließlich noch angegeben in welcher Zeile sich der Cursor gerade befindet und wie viele Zeilen das Script insgesamt besitzt.

Der Nachteil dieses Editors ist, dass er weder eine Suchen- noch eine Ersetzen-Funktion besitzt. Dies kann bei längeren Scripten teilweise ärgerlich sein. In diesem Fall empfiehlt es sich das Script in einen externen Editor zu kopieren und dort zu bearbeiten.


Das Dialogfenster

In Dialogen können Scriptfunktionen in der Resultbox (1) der Antworten aufgerufen werden. Diese wird einmal ausgeführt, wenn die entsprechende Antwort im Spiel gegeben wird. Die Befehle werden dabei so gewertet, als würden sie von einem lokalen Script des antwortenden NPCs aufgerufen. In den Resultboxen muss weder ein begin noch ein end gesetzt werden, auch funktionieren nicht alle Funktionen in den Results, mehr dazu aber in der entsprechenden Lektion.

Mit dem Button Error Check Results (2) werden alle Resultboxen kompiliert und auf Fehler geprüft. Da gefundene Fehler zu dauerhaften Problemen führen können, sollte vor dem Benutzen dieser Option die ESP gespeichert werden und danach neu geladen (die Fehler kann man sich ja aufschreiben).


Die Konsole im Spiel

In Morrowind selber kann man Scriptfunktionen in der Konsole aufrufen. Diese lässt sich mit ^ öffnen. Sobald die Konsole geöffnet wird, wird das Spiel pausiert und der Mauszeiger verwandelt sich in den Inventarpfeil (1). Fährt man mit dem über Objekte, wird die ID der Objekte angezeigt. Klickt man ein Objekt an, wird es in der Konsole ausgewählt. Dies kann man in der Titelleiste der Konsole sehen (2). Klickt man das gleiche Objekt noch einmal an, wird es wieder deselektiert.

Man kann in der Konsole einzelne Scriptfunktionen aufrufen (3). Die Funktion wird dabei so behandelt, als würde sie von einem lokalen Script des ausgewählten Objekts aufgerufen (wenn kein Objekt ausgewählt ist, werden die Funktionen global ausgeführt). Auch in der Konsole können nur einzeilige Scriptfunktionen aufgerufen werden. Mit Enter wird die Funktion ausgeführt. Die Konsole liefert darauf meistens eine Ausgabe, mit Fehlern oder den ausgeführten Aktionen. Mit Pfeil auf und Pfeil ab, kann man die zuletzt eingegebenen Funktionen durchscrollen. Das erspart einem einiges an Tipparbeit.

Klickt man bei geöffneter Konsole mit der rechten Maustaste, wird die Pause beendet und man kann sich wieder bewegen, die Konsole bleibt allerdings angezeigt. Dies ist manchmal nützlich, da in der Konsole Statusinformationen angezeigt werden können.

So viel ersteinmal zu den Möglichkeiten Scriptfunktionen einzugeben. Kommen wir nun dazu wie ein Script eigentlich allgemein aufgebaut wird.

Aufbau eines Scripts

Im Prinzip ist jedes Script in Morrowind gleich aufgebaut. Das Script beginnt mit dem Befehl begin, danach wird der gewünschte Scriptname angegeben. Dann folgt die Deklaration der Variablen und der eigentliche Scriptcode. Zum Schluss wird das Script mit end beendet.

Was ist die Deklaration einer Variablen?

Eine Variable ist eine bestimmte Buchstabenfolge, die einen Zahlenwert darstellt (Wird da die Erinnerung an Mathe wach?). In einer Programmiersprache sind Variablen sehr wichtig, da nur so eine Interaktivität erzeugt werden kann. Sonst würde jedes Programm ja immer das selbe machen.
Damit eine Programmiersprache aber weiß, welche Buchstaben denn eine Variable sein sollen und welche Art von Variable (es ist für einen Computer schon ein ziemlicher Unterschied, ob er eine 2 oder die Zahl 1234,5678 speichern soll) diese Buchstaben denn darstellen. In der Deklaration sagt man genau das einem Programm. Normalerweise muss eine Variable auch noch initialisiert werden, das heißt ihr muss ein Startwert zugewiesen werden, aber das erledigt Morrowind Script automatisch für uns.

Als Scriptname kann jede ASCII-Zeichenkette (also zum Beispiel keine Umlaute) gewählt werden. Sie muss nur einmalig in Gesamtmorrowind und allen geladenen ESP-Datein sein. Diese Eindeutigkeit beschränkt sich dabei nicht nur auf die Scriptnamen sondern auf alle IDs im Spiel. Außerdem darf ein Scriptname kein Leerzeichen enthalten. Am einfachsten ist dies zu erreichen, indem man vor jedes Script ein eigenes Kürzel setzt und immer mit _script beendet. Mehr dazu bei den Scriptkonventionen.

Für die Variablendeklaration in Morrowind Script gibt man einfach den Typ der Variable an (es gibt short, long und float), danach ein Leerzeichen und dann den gewünschten Namen. Dieser Name muss mit einem Buchstaben oder einem Unterstrich beginnen. Er darf keine Leerzeichen enthalten und darf nicht gleich einem Scriptbefehl oder einer Scriptfunktion sein. Auch muss er in diesem Script einmalig sein (man kann also nicht eine short- und eine float-Variable mit dem Namen test deklarieren). Bei der Wahl der Namen sollte man sich möglichst sinnvolle Namen ausdenken, die den Inhalt der Variablen beschreiben (xpos ist zum Beispiel recht verständlich für die Position eines Objekts auf der X-Achse).
Ein Script muss keine Variablen deklarieren.

Nach der Variablendeklaration beginnt der eigentliche Code des Scripts. Hier werden die Scriptfunktionen aufgerufen. Dies geschieht meistens in bestimmten Blöcken. In Morrowind Script sind dazu die Befehle if...endif und while...endwhile verfügbar. Zunächst beschäftigen wir uns nur mit dem ersten. Ein Script könnte also zum Beispiel so aussehen:

Code:
begin kf_beispiel1_script   ;hier beginne ich das Script mit dem Namen kf_beispiel1_script
                                                 ;mit einem Semikolon leite ich Kommentare ein, diese werden vom Compiler ignoriert

short test   ;hier deklariere ich die Variable test als eine short-Variable
                    ;sie wird automatisch den Wert 0 haben

if ( test == 0 )   ;hier lasse ich Morrowind prüfen, ob test den Wert 0 hat
                            ;wenn die Bedingung in Klammern erfüllt ist, wird dieser Teil ausgeführt

   ;mache irgendwas mit dem Script

endif   ;wenn die Bedingung nach dem if nicht erfüllt ist, läuft das Script ab hier weiter

   ;mache irgendwas mit dem Script

   ;in der nächsten Zeile beende ich das Script
end

Soviel zunächst zur Theorie. Schreiben wir doch mal ein kleines Script.

Das erste Script

Um die ganzen Informationen auszuprobieren, schreiben wir nun das erste Script. Natürlich kann dies nur das bekannte "Hello, World!" sein. Beginnen wir ganz vorne:

Ladet die Morrowind.esm im Construction Set (wenn ihr nicht wisst wie das geht, solltet ihr euch ersteinmal andere Tutorials anschauen).
Öffnet den Scripteditor und erstellt ein neues Script. Tragt dort diesen Code ein:

Code:
begin kf_hello_world_script

if ( OnActivate == 1 )
   MessageBox, "Hallo, Welt!"
endif

end

Speichert das Script ab. Was die einzelnen Funktionen bewirken, werdet ihr gleich sehen. Zunächst müsst ihr das Script aber noch einem Gegenstand zuweisen.
Öffnet den Activator Ex_balmora_roadmarker_01 und ändert seine ID auf kf_act_hello_world. Weist ihm als Script das eben erstellte Script kf_hello_world_script zu. Speichert das ab. Ihr werdet gefragt, ob ihr ein neues Objekt erzeugen wollt. Bestätigt das.
Ladet nun die Zelle Balmora (-3,-3) (oder einen Ort eurer Wahl) und platziert den Activator auf der Mitte des Platzes.
Speichert die Änderung nun unter dem Namen Scriptkurs.esp und ladet das Plugin in Morrowind.
Besucht den Obelisk (wo auch immer ihr ihn hingestellt habt) und aktiviert ihn mit der Leertaste (oder was sonst eure Aktivieren-Taste ist). Herzlichen Glückwunsch ihr habt euer erstes Script erzeugt.

Nachdem ihr nun einmal selbst ein Script benutzt habt, sollten wir uns noch auf einige Konventionen einigen.

Scriptkonventionen

Ein lediges, aber nötiges Thema: Wie genau sollte man in Morrowind Script seinen Code schreiben, damit der Compiler und andere Modder (und man selbst, wenn man sich das Script später noch einmal anschaut) das Script verstehen?

Ich selbst halte mich dazu an folgende Regeln (die eigentlich in den meisten Programmiersprachen so benutzt werden):
  • Kennzeichne die Herkunft deiner Scripte!
    Wie bereits erwähnt muss der Name eines Scripts in allen geladenen Plugins eindeutig sein. Da man nicht weiß, was andere Modder machen, sollte man also selbst dafür sorgen, dass seine Scriptnamen eindeutig sind. Es hat sich eingebürgert, dass man seine Scripts mit einem eigenen Kürzel (ich verwende kf_ für Killfetzer) beginnt. Danach sollte man noch ein weiteres Kürzel für das aktuelle Projekt angeben. Ein Beispiel: In meiner Modifikation Skill Mastery beginnt jede ID (nicht nur Scripte) mit kf_skm_. Mehr als 2 oder 3 Buchstaben sollten eure Kürzel aber auch nicht besitzen.
    Achtung: my_ oder _ sind ganz schlechte Ideen als eigenes Kürzel. Diese werden von vielen Moddern benutzt. Eure Kürzel sollten wirklich personalisiert sein.
  • Kennzeichne deine Scripte als Scripte!
    Es gibt viele verschiedene Objektarten in Morrowind, alle benötigen IDs. Die meisten eurer Scripte laufen auf von euch erschaffenen Objekten. Da ist es sehr wahrscheinlich (und konsistent) wenn das Objekt und das Script gleiche oder sehr ähnliche IDs haben. Damit man aber immer auseinander halten kann was Script und was Objekt ist, beende ich den Namen jedes Scripts mit _script. Wenn euch das zu lang ist, könnt ihr euch auch ein anderes Kürzel einfallen lassen.
  • Groß- und Kleinschreibung:
    Morrowind Script unterscheidet nicht zwischen Groß- und Kleinschreibung, deswegen wäre es eigentlich egal, wie ihr schreibt. Trotzdem solltet ihr immer die gleiche Form wählen. In der Community hat sich folgende Form verbreitet:
    Scriptbefehle (begin, end, if, ...) und Variablen werden klein geschrieben.
    Scriptfunktionen (OnActivate, MessageBox, ...) werden groß geschrieben. Und zwar wird jeweils der erste Buchstabe jedes Wortes (im englischen Sinne) großgeschrieben.
  • Variablennamen:
    Variablennamen sollten möglichst sinnig gewählt werden. Man sollte aus dem Namen einer Variablen bereits ihren Inhalt ableiten können. Einige Variablennamen sind quasi Standard geworden: doonce (short-Variable, die immer verwendet wird, wenn etwas nur einmal gemacht werden soll), state (short-Variable, wird immer verwendet, wenn ein Script mehrere verschiedene Stadien durchläuft) und timer (float-Variable, zur Speicherung des Wertes eines Timers).
  • Leerzeilen:
    Zwischen allen Sinnblöcken sollten Leerzeilen gesetzt werden. Also zum Beispiel eine nach der begin Zeile, eine nach den Variablendeklarationen, eine nach dem Ende jedes Blockes (if oder while) oder nach jedem Sinnabschnitt.
  • Einrückungen:
    Alle Befehle der nächsttieferen Ebene werden einen Tabstop weiter eingerückt (wer keine Tabstops verwenden möchte, kann auch 2 oder 3 Leerzeichen verwenden). Ein Beispiel: Alle Zeilen zwischen einem if und einem else oder endif werden eingerückt. Das else oder endif ist die erste Zeile, die wieder ausgerückt wird.
  • Leerzeichen, Anführungszeichen und Kommata:
    Morrowind Script ist auch in dieser Hinsicht sehr vergebungsfreudig. Aber um Fehlern vorzubeugen und die Lesbarkeit zu verbessern, sollte man die formalen Regelen von Morrowind Script benutzen:
    Vor und nach jeder Klammer und jedem Rechen- und Vergleichsoperator wird ein Leerzeichen gesetzt.
    Jede ID wird in Anführungszeichen gesetzt. Dies ist eigentlich nur für IDs mit Leerzeichen erforderlich, sollte aber der Einheitlichkeit halber immer gemacht werden. Werden sie weggelassen kann, es manchmal absolut unvorhersagbar Probleme geben, also lieber gleich auf Nummer Sicher gehen.
    Einzelne Parameter von Funktionen und die Funktion selbst werden mit Kommata getrennt. Dies scheint bis auf zwei mir bekannte Ausnahme rein optional für das Verständnis des Compilers zu sein, trotzdem sollte es gemacht werden.
Dies hört sich sicher erst einmal sehr kompliziert an. Aber bei längeren Scripten werdet ihr den Sinn dieser Konventionen schnell verstehen. Wenn ihr einzelne Punkte aus den Konventionen nicht verstanden habt, werdet ihr sie in den nächsten Scriptbeispielen sicherlich verstehen.

Dies war die Einführung zum Scriptkurs. Weiter geht es mit einem Abschnitt über die beiden im Beispielscript verwendeten Funktionen OnActivate und MessageBox und einige verwandte Funktionen. Ihr werdet lernen, wie man das Tagebuch verwendet, wie man dem Spieler Auswahlmöglichkeiten gibt und wie man eine Tür so verschließt, dass er Spieler sie erst nach dem Erfüllen einer bestimmten Quest öffnen kann.


Befehle und Operatoren von Morrowind Script

Was sind Befehle und wie unterscheiden sie sich von Funktionen?

In Morrowind Script (eigentlich in jeder Programmiersprache, die ich kenne) gibt es einen Unterschied zwischen Befehlen und Funktionen.

Befehle sind meistens ein sehr kleiner Satz von Wörtern, die ganz bestimmte, grundlegende Dinge ermöglichen. Befehle verwenden normalerweise sehr kurze und verständliche Wörter als Aufruf (zB. if). Befehle machen immer das gleiche.

Funktionen hingegen sind kompliziertere Gebilde. Sie selbst wurden von irgendjemand aus den zu Grunde liegenden Befehlen programmiert. Funktionen können im Gegensatz zu Befehlen bestimmte Zahlenwerte zurückgeben. Die Rückgabewerte hängen vom Spielverlauf ab.

Diese Unterscheidung mag ein wenig schwammig wirken, aber ich bin leider kein Informatiker und kann den Unterschied nicht präziser erklären. Ich kann euch nur versichern, dass der Unterschied wirklich ziemlich scharf und verständlich ist, sobald man ihn verstanden hat. Dieses Verstehen ist aber nicht unbedingt erforderlich. Erforderlich ist, dass ihr die Befehle von Morrowind Script versteht ;-)

Liste der Befehle

Morrowind Script besitzt einen extrem kleinen Satz an Befehlen. Es sind genau 15. Von diesen benötigt man (also zumindest ich) nur 12 regelmäßig. Also alles sehr überschaubar.

Die Befehle lassen sich in mehrere Gruppen einteilen:
  • Scriptstart und -ende
    • begin: Jedes Script muss mit dem Wort begin beginnen. Es markiert den Anfang eines Scripts. Hinter diesem Befehl wird der Name bzw. die ID des Scripts erwartet. Dazu wird die gewünschte ID einfach nach einem Leerzeichen hingeschrieben. Der Scriptname darf keine Leer- oder Sonderzeichen enthalten.
    • end: Jedes Script muss mit dem Wort end aufhören. Es markiert das Ende eines Scripts. Alles was hinter dem end steht, wird nicht mehr beachtet.
  • Variablendeklaration
    In Morrowind Script gibt es drei Arten von Variablen. Jede kann deklariert werden, indem man den entsprechenden Variabelntyp (das ist der Befehl) und nach einem Leerzeichen den gewünschten Namen der Variablen schreibt. Der Name muss eindeutig sein. Es darf keine andere Variable im gleichen Script (lokal) oder globale Variable geben, die den gleichen Namen beseitzt, auch wenn sich der Typ unterscheidet. Der Variablenname darf keine Leer- oder Sonderzeichen enthalten.
    Variablen müssen in Morrowind Script immer direkt hinter der begin-Zeile deklariert werden. Im späteren Script kann man diese Befehle nicht mehr verwenden.
    • short: Dieser Befehl erzeugt eine kurze, ganze Zahl (auch bekannt als Integer). Dies ist das Äquivalent von shortint in anderen Programmiersprachen. Eine short-Variable kann Werte zwischen -32.768 und 32.767 speichern. Wird ein größerer oder kleinerer Wert gespeichert, kommt es zu einem Overflow. Das heißt, dass die Variable wieder auf der anderen Seite anfängt zu zählen. 32768 wird also als -32768 gespeichert oder 32800 als -32736.
    • long: Dieser Befehl erzeugt eine lange, ganze Zahl (auch bekannt als Integer). Dies ist das Äquivalent von longint in anderen Programmiersprachen. Eine long-Variable kann Werte zwischen -2.147.483.648 und 2.147.483.647 speichern. Wird ein größerer oder kleinerer Wert gespeichert, kommt es zu einem Overflow.
    • float: Dieser Befehl erzeugt eine Gleitkommazahl. Für das Verständnis dieses Kurses reicht es aus zu wissen, dass dies eine Kommazahl ist und dass Morrowind diese standardmäßig mit 6 Nachkommastellen darstellt (zB. 1,234567). Allerdings muss auch daran gedacht werden, dass auf Grund der Speichermethode kleine Fehler auftreten können. Normalerweise sind die nur in den hintersten Nachkommastellen zu bemerken, aber in einigen Ausnahmefällen (bei aufwendigeren Rechnungen), können sie schon größere Fehler erzeugen. Die größte, speicherbare Zahl ist ±3.4*exp[±38].
  • if-Konstrukte
    Mit den Befehlen dieser Kategorie kann man bestimmte Bedingungen überprüfen und abhängig vom Ausgang dieser Überprüfung (also entweder ist sie wahr oder falsch) Quellcode ausführen oder überspringen. Somit kann man die Wirkung von Scripten abhängig von Situationen machen.
    • if: Dieser Befehl ist so aufgebaut:
      Code:
      if ( a == b )
      Man kann ihn so lesen: Wenn a gleich b ist, führe alle Zeilen bis zum nächsten elseif, else oder endif aus. Wenn a ungleich b ist, springe bis zum nächsten elseif, else oder endif.
    • elseif: Dieser Befehl ist so aufgebaut:
      Code:
      elseif ( a == c )
      Man kann ihn so lesen (zusammen mit den if-Befehl von oben): Wenn a gleich b war (oben), springe bis zum nächsten elseif, else oder endif. Wenn a ungleich b war, aber a gleich c ist, führe alle Zeilen bis zum nächsten elseif, else oder endif aus. Wenn a ungleich b war und a ungleich c ist, springe bis zum nächsten elseif, else oder endif.
    • else: Man kann diesen Befehl so lesen: Wenn keine der vorherigen Abfragen (hier a == b und a == c wahr war oder anders, wenn alle bisherigen Abfragen falsch waren, führe alle Zeilen bis zum nächsten endif aus. Wenn eine der Abfragen drüber wahr war, springe bis zum endif.
    • end: Dieser Befehl schließt einen if-Block ab.

    Jeder if-Block besitzt immer genau ein if und genau ein endif. Zusätzlich können beliebig viele elseifs und/oder genau ein else hinzukommen.

    Hinter jedem if und elseif steht eine Bedingung beziehungsweise ein Vergleich in Klammern. In diesem Beispiel war dafür ( a == b ) gewählt. Für a und b können jede Zahl, jede lokale oder globale Variable oder jede Funktion mit einem Rückgabewert stehen. Statt dem == kann auch jeder andere Vergleichsoperator (siehe weiter unten) stehen, zum Beispiel:

    Code:
    if ( a == 3) ;wahr, wenn die Variable a gleich 3 ist
    
    if ( a < b ) ;wahr, wenn die Variable a kleiner als die Variable b ist
    
    if ( OnActivate == 1 ) ;wahr, wenn die Funktion OnActivate eine 1 zurückliefert
    
    if ( GetButtonPressed != c ) ;wahr, wenn die Funktion GetButtonPressed einen anderen Wert zurückliefert, als der, der in der Variable c gespeichert ist

  • verschachtelte if-Blöcke
    Es ist auch möglich einen if-Block in einem anderen if-Block zu starten. Hierbei muss man beachten, dass alle elseif und else dem zweiten (inneren) if zugeordnet werden, bis dieses wieder mit einem endif abgeschlossen wird.
    Code:
    if ( a == b )
        if ( c == d )
           ;mache irgendwas
        else (a != c ) ;auch wenn dieses else auf den ersten Blick, wegen seiner Setzung, so aussieht, als ob es zum oberen der beiden ifs gehört, gehört es zu dem unteren if, da diese noch nicht mit einem endif beendet wurde
           ;mache irgendwas
        endif
    else ;dieses else gehört nun zum oberen if, weil das untere mit einem endif abgeschlossen wurde
        ;mache irgendwas
    endif
    Wie das obere Beispiel sehr gut zeigt, ist es wichtig, seine Scripte ordentlich zu setzen, da man sonst selber den Überblick verliert, was seine Scripte tun.
  • while-Konstrukte
    Mit den while-Befehlen kann man Schleifen in Morrowind Script bauen.
    • while: Startet eine while-Schleife. while erwartet, wie if eine Bedingung in Klammern. Solange dieser Vergleich wahr ist, wird der Code innerhalb der while-Schleife ausgeführt.
    • endwhile: Beendet eine while-Schleife.
    Eine while-Schleife ist so aufgebaut:

    Code:
    while ( a < 10 )
        set a to a + 1
        ;mache irgendwas
    endwhile
    Man kann den Code so lesen: Wiederhole alle Zeilen zwischen while und endwhile solange, bis der Vergleich hinter dem while nicht mehr wahr ist. Wenn man falsche Abfragen wählt, kann man somit natürlich wunderbare Endlosschleifen bauen, die das Spiel einfrieren lassen (können).
  • Sonstiges
    • set ... to: Mit diesem Befehl lassen sich Variablen Werte zuweisen. zwischen set und to (anstelle der Pünktchen) muss der Name einer lokalen oder globalen Variable geschrieben werden. Hinter dem to wird ein Wert erwartet, der der Variablen zugewiesen werden soll. Es gibt vier Möglichkeiten, was hinter dem to stehen kann:
      • eine Zahl (zB. set a to 1)
      • eine andere Variable (zB. set a to b)
        Wird einer short- oder long- Variablen (also einer ganzen Zahl) ein float-Wert (also eine Kommazahl) zugewiesen, werden die Nachkommastellen einfach abgeschnitten. Wird einer float-Variablen eine ganze Zahl zugewiesen, werden alle Nachkommastellen mit Nullen aufgefüllt.
      • eine Funktion mit Rückgabewert (zB. set a to GetButtonPressed)
      • eine Rechnung: In diesem Fall wird das Ergebnis der Rechnung der Variablen zugewiesen. Diese Rechnung kann Zahlen und Variablen enthalten. Funktionen können Probleme bereiten, deswegen sollte man sie nicht in Rechnungen benutzen. Um die Zahlen und Variablen zu verbinden stehen die vier Grundrechenarten zur Verfügung. Mehrere Rechnungen können mit Klammern hintereinander ausgeführt werden. Es scheint unklar zu sein, ob Morrowind Script Punkt vor Strich befolgt, somit sollte im Zweifel immer geklammert werden.
        Code:
        set a to 1 + 3
        
        set a to b - 3 + c
        
        set a to ( b + 3 ) * c
        
        set a to ( b / 2 ) - 1
    • return
      Dieser Befehl lässt das Script zurück zum begin springen.
    • -> (Funktionsaufruf)
      Mit diesem Befehl kann man eine Funktion so aufrufen, als ob das Script auf einem anderen Objekt liegen würde. Dieses Objekt muss dazu Reference Persistent sein. Für weitere Informationen siehe Lektion 3.
      Code:
      "Object_ID"->Funktionsname
      
      "ex_gg_portcullis_01"->MoveWorld, Z, 70
    • . (Variablenaufruf)
      Mit diesem Befehl kann man auf die lokalen Variablen eines anderen Scripts zugreifen. Auch hierzu mehr in Lektion 3.
      Code:
      set a to "Object_ID".state
      
      set a to "Global_Script_ID".state
Operatoren

Morrowind Script kennt zwei Arten von Operatoren: Vergleichsoperatoren und Rechenoperatoren. Die ersten werden für die Vergleiche in if, elseif und while-Befehlen benutzt. Die Rechenoperatoren werden für Rechnungen im set ... to Befehl benutzt.
  • Vergleichsoperatoren
    • == (gleich)
    • != (ungleich)
    • < (kleiner)
    • > (größer)
    • <= (kleiner oder gleich)
    • >= (größer oder gleich)
  • Rechenoperatoren
    Morrowind Script kennt nur die Grundrechenarten, die mit den Operatoren +, -, * (mal) und / (geteilt).

Lektion 1: Einfache Spielerinteraktion über Messageboxen und das Tagebuch

In der Einführung wurde euch ein Script vorgestellt:
Code:
begin kf_hello_world_script

if ( OnActivate == 1 )
   MessageBox, "Hallo, Welt!"
endif

end

Dieses Script veranlasste den Activator, sobald er aktiviert wurde, eine Messagebox mit dem Text "Hallo, Welt!" zu öffnen. Das hat immer wieder funktioniert. Wie genau funktioniert dieses Script nun?

Bedingungen abfragen: if-Blöcke

In diesem Script wird zuerst eine if-Bedingung abgefragt. Das if ist einer der grundlegenden Befehle in Morrowind Script. Seine Wirkung ist dabei so zu formulieren: Wenn eine Bedingung erfüllt ist, dann mache irgendwas. Wenn die Bedingung nicht erfüllt ist, überspringe diesen Teil. Im Beispielscript ist die einfachste Variante eines if zu sehen. Es gibt auch noch kompliziertere. Die allgemeinste Form eines if-Blocks ist:
Code:
if ( a == 1 )
   ;mache irgendwas
elseif ( a == 2 )
   ;mache irgendwas anderes
else
   ;mache nochmal was anderes
endif

Diesen Block kann man in etwas so lesen:
Wenn a gleich 1 ist, führe alles aus bis zum elseif aus. Wenn a nicht 1 ist, aber a gleich 2 ist, führe alles bis zum else aus. Wenn a weder 1 noch 2 ist, führe alles bis zum endif aus.

Jeder if-Block muss genau ein if und genau ein endif enhalten. Zusätzlich kann jeder Block beliebig viele elseif, aber nur ein else enhalten. Das else steht dabei immer als letzter Teil vor dem endif.

Hinter jedem if und elseif steht eine Bedingung beziehungsweise ein Vergleich in Klammern. Ist dieser Vergleich wahr, wird ausgeführt, was sich unter dem Vergleich befindet. Wenn der Vergleich falsch ist, wird zum nächsten Vorkommen eines else(if) oder endif gesprungen.
Vergleiche bestehen aus zwei Zahlen (ob diese Zahlen nun als tatsächliche Zahen, als Variablen oder als Rückgabewert einer Funktion dort stehen, ist ersteinmal egal) und einem Vergleichsoperator. Einige Beispiele dazu:
Code:
if ( a == 1 ) ;wenn a gleich 1

if ( a > 1 ) ;wenn a größer als 1

if ( a < 1 ) ;wenn a kleiner als 1

if ( a >= 1 ) ;wenn a größer oder gleich 1

if ( a <= 1 ) ;wenn a kleiner oder gleich 1

if ( a != 1 ) ;wenn a ungleich (also größer oder kleiner) 1

Statt der Variablen a könnte man hier auch viele Funktionen hinschreiben, zum Beispiel OnActivate.

Eine vereinfachte Form der if-Abfrage ist es den Vergleich einfach wegzulassen:
Code:
if ( OnActivate )
;mache irgendwas
endif

Bei diesem Beispiel wird ausgenutzt, dass das Script normalerweise erst den Vergleich auswertet. Ist er wahr ersetzt das Script ihn mit einer 1, ist er falsch ersetzt er ihn mit einer 0. Das Script führt den Teil nach dem if immer aus, wenn in der Klammer letztendlich keine 0 steht (0 steht in der Informatik normalerweise für falsch).

if ( OnActivate ) wird vom Script genauso verstanden wie if ( OnActivate != 0 ).

Viele verwenden diese Kurzformen, wenn eine Variable oder Funktion normalerweise nur die Werte 0 und 1 annimmt. Ich persönlich mag sie nicht besonders, aber das ist größtenteils Geschmackssache. Mein Argument gegen diese Schreibweise ist, dass zumindest Variablen vielleicht doch mal einen anderen Wert als 0 oder 1 haben könnten. In diesem Fall ist mir eine Gleichheit lieber, als eine Ungleichheit.

Scriptfunktionen

Neben den wenigen Befehlen (begin, end, if, while, ...) verfügt Morrowind Script über eine Vielzahl von Funktionen. Diese Funktionen machen die verschiedensten Dinge. Grundsätzlich kann man die Funktionen in zwei Arten einteilen. Einmal gibt es die Funktionen, die irgendeinen Wert des Spiels abfragen (und dabei nichts am Spiel verändern) und es gibt die Funktionen, die irgendwas am Spiel ändern. In dem Beispielscript gab es von jeder Sorte eine dieser Funktionen.

OnActivate

Diese Funktion ist eine Funktion die einen Wert abfragt. Sie liefert normalerweise eine 0 (beziehungsweise falsch). Nur in dem Moment, in dem jemand (normalerweise der Spieler) das Objekt auf dem dieses Script liegt, aktiviert, liefert die Funktion eine 1 (also wahr).
So kann man mit dieser Funktion herausbekommen, wenn der Spieler versucht ein Objekt zu aktivieren. Versucht deshalb, weil das Objekt sich nicht aktivieren lässt, wenn auf ihm ein Script mit OnActivate liegt. Das ist eigentlich eine sehr interessante Möglichkeite für Scripte. Man kann nun (mit Hilfe weiterer ifs) verhindern, dass der Spieler bestimmte Objekte zu einem bestimmten Zeitpunkt aktivieren kann. Ein Beispiel:
Code:
if ( OnActivate == 1 )
PlaySound, "LockedDoor"
endif

Legt man dieses Script auf eine Tür, wird sie dem Spieler so vorkommen, als wäre sie verschlossen (Sie spielt beim Aktivieren den Sound einer verschlossenen Tür ab.). Aber das Schloss kann er niemals knacken, da die Tür eigentlich gar kein Schloss besitzt.

Natürlich gibt es auch Situationen, in denen man zwar ein Aktivieren feststellen möchte, der Spieler das Objekt aber trotzdem Aktivieren können soll. Dazu kann man folgende Konstruktion verwenden:
Code:
if ( OnActivate == 1 )
;mache irgendwas

Activate
endif

Das Activate sorgt dafür, dass ein Objekt trotzdem aktiviert wird. Activate hat zwar noch mehr Einsatzmöglichkeiten, aber dies ist die wichtigste.

Noch eine kleine Randbemerkung zu OnActivate: Versucht man eine Aktivierung bei einem Activator (der Objektklasse) festzustellen, muss dieser einen Namen besitzen!

MessageBox

Die andere im Beispielscript verwendete Funktion ist MessageBox. Diese Funktion gibt, wie wir ja schon wissen, eine Nachricht im Spiel aus. Aber die Funktion kann noch viel mehr. Tatsächlich ist dies eine der vielseitigsten Funktionen in Morrowind Script.

Damit die Funktion ihre komplette Funktionalität entfaltet, müssen der Funktion gewisse Parameter übergeben werden. Parameter heißen bestimmte Zahlen, Objekt-IDs oder Variablen, die hinter einem Funktionsnamen geschrieben werden müssen. Die gesamte MessageBox-Funktion sieht so aus:
Code:
MessageBox, "Anzuzeigender Text"[, var1, var2, ...][, "Antwort 1", ..., "Antwort 9"]

Die Messagebox blendet eine Nachricht ein und kann zusätzlich bis zu 9 Buttons erzeugen (Antwort 1 – Antwort 9). Wenn Antwortmöglichkeiten gegeben sind, schließt sich das Fenster erst nach klicken auf eine der Antworten. Welche Antwort geklickt wurde lässt sich mit GetButtonPressed ausgeben. Ist keine Antwortmöglichkeit gegeben, bleibt das Fenster abhängig von der Zeichenanzahl der Nachricht eingeblendet. Die Dauer pro Zeichen wird im GameSetting fMessageTimePerChar festgelegt und ist standardmäßig auf 0,1 Sekunden gestellt.
Es ist auch möglich Werte von Variablen im Text anzuzeigen. Im Text muss dafür ein Platzhalter verwendet werden: %f oder %.yf. In der ersten Version wird jede Zahl mit 6 Nachkommastellen angegeben. In der zweiten Version wird die Zahl mit der in y festgelegten Anzahl von Nachkommastellen ausgegeben. Somit sollte für Variablen vom Typ short oder long der Platzhalter %.0f verwendet werden. In Messageboxen lassen sich auch einige Stringkonstanten ausgeben. Diese müssen mit einem ^ (Zirkumflex) angeführt werden.
Wird eine Messagebox während eines Dialoges aufgerufen, wird der Inhalt der Messagebox als weiße Schrift in einen neuen Absatz geschrieben. Dies funktioniert allerdings nur mit Messageboxen ohne Antworten.

Hier einige Beispiele, die die verschiedenen Möglichkeiten der Messagebox zeigen:
Code:
MessageBox, "Ganz einfache Messagebox."
---
MessageBox, "Der Name des Spielers ist ^PCName."
---
long gold
set gold to "Player"->GetItemCount, "Gold_001"
MessageBox, "Ihr besitzt %.0f Draken.", gold
---
MessageBox, "Es ist der %.0f. %.0f. im Jahr 3Ä %.0f.", Day, Month, Year
---
MessageBox, "Diese Nachricht verschwindet erst, wenn Ihr OK anklickt.", "OK"
---
MessageBox, "Es ist schon nach %.0f Uhr. Ihr solltet schlafen gehen.", GameHour, "OK"

GameHour, Day, Month und Year in den obigen Beispielen sind globale Variablen. Das sind Variablen, die von jedem Script aufgerufen oder verändert werden können. Man muss sie nicht im Script deklarieren, sondern kann dies im Construction Set unter Gameplay->Globals tun.

RTEmagicC_3918b7c8c5.jpg.jpg

Verwaltung der globalen Variablen

In der Beschreibung zur Messagebox habe ich auch verschiedene Antworten erwähnt und dass man über GetButtonPressed heraus bekommt, welcher Button gedrückt wurde:

GetButtonPressed

Die Funktion liefert die Nummer des Messagebox-Buttons zurück, den der Spieler angeklickt hat. Der erste Button liefert eine 0, der zweite eine 1 und so weiter. Hat der Spieler noch keinen Button geklickt, liefert die Funktion -1 zurück. Eine Konstruktion kann zum Beispiel so aussehen:
Code:
begin kf_beispiel2_script

short state
short button

if ( state == 0 )
   if ( OnActivate == 1 )
       set state to 1
      MessageBox, "Was ist eure Lieblingszahl?", "1", "2", "3"
   endif
else
   set button to GetButtonPressed
   if ( button == 0 )
      set state 0
     MessageBox, "Eure Lieblingszahl ist 1."
   elseif ( button == 1 )
      set state 0
      MessageBox, "Eure Lieblingszahl ist 2."
   elseif ( button == 2 )
      set state 0
      MessageBox, "Eure Lieblingszahl ist 3."
   endif
endif

end

In diesem Script wird auch zum ersten Mal einer Variable ein Wert zugewiesen. Mit dem Befehl set ... to ... lässt sich das erreichen. Zwischen set und to muss der Name der zu verändernden Variable stehen. Hinter dem to kann eine Zahl, eine andere Variable, eine Funktion oder eine Rechnung stehen. Einige Beispiele:
Code:
short test1
short test2

set test1 to 3

set test2 to test1

set test1 to GetButtonPressed

set test2 to test1 - 5

set test2 to ( test1 - 5 ) * 6

Damit wären die beiden Funktionen aus dem Beispielscript erklärt. Um nun noch etwas Neues einzuführen, werde ich noch die Funktion des Tagebuchs erklären:

Das Tagebuch

Das Tagebuch des Spielers ist ein einfacher Weg die Fortschritte von Quests festzuhalten. Man kann Tagebucheinträge genauso wie normale Dialoge erstellen. Allerdings gibt es die Besonderheit, dass man als einzige Bedingung einen Index angeben kann. Zusammen mit dem Namen des Tagebuchthemas bildet der Index die Möglichkeit jeden Tagebucheintrag eindeutig anzugeben.

Ihr habt nun also ein Tagebuchthema mit verschiedenen Indizes erstellt. Wie könnt ihr diese nun in das Tagebuch des Spielers eintragen oder (noch wichtiger) wie bekommt ihr heraus, ob sich ein bestimmter Eintrag schon im Tagebuch des Spielers befindet?

Dafür gibt es zwei Funktionen:

Journal

Diese Funktion kann in Scripten aufgerufen werden, wird aber auch sehr oft in Dialogresultboxen verwendet (und in der Konsole ist sie natürlich auch sehr praktisch um Quests abzukürzen).
Code:
Journal, "JournalTopic_ID", index

Journal, "C3_DestroyDagoth", 50

Diese Funktion fügt dem Tagebuch des Spielers einfach den mit Journalthema und Journalindex bestimmten Eintrag hinzu.

GetJournalIndex

Mit dieser Funktion ist es möglich den Wert des höchsten vorhandenen Indexs zu einem bestimmten Thema, der sich bereits im Tagebuch des Spielers befindet, zu bekommen.

Code:
GetJournalIndex, "JournalTopic_ID"

Eine übliche Anwendung ist es, den Wert eines Tagebucheintrags abzufragen und nur bei einem bestimmten vorhandenen Eintrag eine Aktion auszuführen. Ein Beispiel ist folgendes Script, dass beim Aktivieren eines Activators dem Spieler eine Nachricht ins Tagebuch einfügt. Aber nur wenn dieser Eintrag nicht schon vorhanden ist (Wer braucht schon doppelte Tagebucheinträge?):

Code:
if ( OnActivate == 1 )
   if ( GetJournalIndex, "C3_DestroyDagoth" < 50 )
      Journal, "C3_DestroyDagoth", 50
   endif
endif

Aufgaben

Wie angekündigt, gibt es am Ende jedes Teils einige Aufgaben, die ihr versuchen sollt zu lösen. Lösungsvorschläge findet ihr am Ende des Tutorials. Natürlich gibt es für die meisten Aufgaben verschiedene Lösungen, aber ihr könnt sie normalerweise einfach ausprobieren, indem ihr das Script schreibt und in einer Modifikation selber testet.
  1. Verändert das "Hallo, Welt"-Script aus der Einführung so, dass ihr die Beispiele zur MessageBox-Funktion ausprobieren könnt.
  2. Probiert das zweite Beispielscript aus.
  3. Verändert das "Hallo, Welt"-Script so, dass es zunächst fragt, ob der Spieler ein Mann oder eine Frau ist und danach mit "Hallo Herr y" oder "Hallo Frau x" begrüßt.
  4. Erschafft einen Activator, der sich wie eine Digitaluhr verhält. Sie soll euch mit Namen begrüßen und euch die Zeit (in Stunden und Minuten, nicht nur in Stundenbruchteilen) und das Datum angeben. Ihr sollt die Anzeige bestätigen müssen, bevor ihr weiterspielen könnt.
  5. Erschafft eine Tür, die sich erst öffnet, wenn der Spieler einen bestimmten Tagebucheintrag hat. Hat er ihn noch nicht, soll er eine entsprechende Nachricht bekommen, hat er ihn, soll die Tür beim ersten Betreten einen neuen Tagebucheintrag hinzufügen.
  6. Versucht einen Scriptteil zu schreiben, der die Rechenoperation div für die short-Variablen a und b ausführt. Div bezeichnet die Rechnung, die angibt, wie viele Vielfache einer Zahl in einer anderen stecken und dabei den Rest vernachlässigt.
    10 div 5 = 2
    11 div 5 = 2
    12 div 5 = 2
    15 div 5 = 3
  7. Schreibt einen Scriptteil, der die Modulo-Operation für die short-Variablen a und b durchführt. Modulo bezeichnet den Rest einer Division.
    6 mod 3 = 0
    7 mod 3 = 1
    8 mod 3 = 2
    9 mod 3 = 0
  8. Versucht euch an einem Scriptteil, der nur ausgeführt wird, wenn zwei Vergleiche wahr sind (könnte man auch als Bool'sches AND bezeichnen).
  9. Schreibt einen Scriptteil, der ausgeführt wird, wenn mindestens einer von zwei Vergleichen wahr ist (könnte man auch als Bool'sches OR bezeichnen).
  10. Schreibt ein Quiz mit drei Fragen (mit Messageboxen). Der Spieler soll jeweils mehrere Antwortmöglichkeiten haben, von denen nur eine richtig ist. Der Spieler soll sich eine falsche Antwort erlauben können. Beantwortet er eine zweite Frage falsch, soll er verlieren.
Für die Aufgaben 4, 6 und 7 benötigt ihr jeweils set ... to Befehle mit Rechnungen. Für die Aufgaben 5, 8, 9 und 10 benötigt ihr verschachtelte if-Abfragen. Außerdem benötigt ihr für die letzte Variablen um die Antworten des Spielers zu speichern. Wenn ihr die zehnte Aufgabe hinbekommt (ihr könnt sie ja jederzeit im Spiel testen!), solltet ihr verschachtelte if-Konstrukte wirklich verstanden haben.


Lektion 2: Inventar-Scripte

Im letzten Teil haben wir die Funktion OnActivate kennengelernt. So wie man mit OnActivate viele Interaktionen des Spielers mit seiner Umwelt feststellen kann, gibt es auch Möglichkeiten verschiedene Interaktionen mit dem Inventar zu erkennen. In vielen fällen ist das zwar sehr viel komplizierter als man sich vielleicht vorstellt, in anderen aber auch sehr leicht. Was OnActivate für die Umgebung des Spielers ist, ist in gewisser Weise die Funktion OnPCEquip für das Inventar.

Um OnPCEquip verwenden zu können, muss man es zuvor als lokale Variable short OnPCEquip deklarieren. Die Wirkung von OnPCEquip hängt vom Typ des Gegenstands ab, auf dem ein Script liegt.

Kategorie 1: ausrüstbare Gegenstände wie Rüstungen, Waffen, Kleidung, tragbare Lichter, Dietriche, Sonden

OnPCEquip wird auf 1 gesetzt, wenn der Spieler den Gegenstand ausrüstet (auf sein Charakterbild im Inventar zieht oder per Hotkey benutzt). Die Variable wird beim Ablegen automatisch auf 0 zurückgesetzt, kann aber auch von Hand zurückgesetzt werden, via set OnPCEquip to 0. Ansonsten bleibt ihr Wert 1.

Code:
begin IdV_EquipTest_equipable

; Armor, Weapons, Clothing, Lights, Picks, Probes

short OnPCEquip
short IsEquiped

if ( OnPCEquip )
    if ( IsEquiped == 0 )
        MessageBox, "Equiped"
        set IsEquiped to 1
    endif
endif

if ( IsEquiped )
    if ( OnPCEquip == 0 )
        MessageBox, "Un-Equiped"
        set IsEquiped to 0
    endif
endif

end

Kategorie 2: alle Misc-Items außer geladenen Seelensteinen

OnPCEquip wird auf 1 gesetzt, wenn der Spieler den Gegenstand ausrüstet und wird nicht automatisch zurückgesetzt, sondern bleibt 1. Man muss dies also von Hand tun oder alles im OnPCEquip-Block wird jeden Frame ausgeführt.

Code:
begin IdV_EquipTest_misc

short OnPCEquip

if ( OnPCEquip )
    MessageBox, "Equiped"
    set OnPCEquip to 0
endif

end

Bei geladenen Seelensteinen bleibt OnPCEquip auch beim Ausrüsten 0, es aber eine andere Funktion mit der man das Benutzen eines Seelensteins erkennen kann.

Kategorie 3: allen anderen Inventargegenständen: Zutaten, Tränke, Alchemie-Apparaturen, Reparturwerkzeuge, Bücher, Schriftrollen

OnPCEquip funktioniert nur in Verbindung mit PCSkipEquip.

short PCSkipEquip muss ebenfalls als lokale Variable deklatiert werden. Und kann dann auf 1 gesetzt werden.
Wenn PCSkipEquip gleich 1 ist, dann verhält sich ein Gegenstand wie ein Misc-Item.

Code:
begin IdV_EquipTest_else

short OnPCEquip
short PCSkipEquip

set PCSkipEquip to 1

if ( OnPCEquip )
    MessageBox, "Equiped"
    set OnPCEquip to 0
endif

end

Das lässt zwar die OnPCEquip-Funktion korrekt auslösen macht den Gegenstand aber nutzlos, denn er kann so nicht mehr benutzt oder verbraucht werden. Das lässt sich zwar auch wieder umgehen, benötigt aber ein weiteres (globales) Script und ist uns im Moment noch zu kompliziert. Funktioniert nicht bei geladenen Seelensteinen.

Ein weiteres Problem ist, dass Gegenstände, die ein Script tragen sich nicht mehr im Inventar stapeln lassen, was sehr hässlich aussehen kann.

Man kann PCSkipEquip natürlich auch ohne OnPCEquip verwenden und somit verhindern dass der Spieler einen bestimmten Gegenstand verwendet.

Eine besonderheit von PCSkipEquip ist, dass es bei Büchern beim Ausrüsten (= Lesen) auf 1 gesetzt wird (was bei allen anderen Gegenständen scheinbar nicht passiert).

Code:
begin IdV_BookActivate

short PCSkipEquip

if ( PCSkipEquip )
    MessageBox, "Gelesen"
    set PCSkipEquip to 0
endif

end

Dies gilt nicht für Bücher die aus der Welt heraus gelesen werden, was sich aber durch OnActivate feststellen lässt. Bei Büchern kann man mit OnActivate drei verschiedene Aktionen festellen: Den Versuch das Buch mit der Maus ins geöffnete Inventar zu ziehen, Lesen (Benutzen) in der Welt und das Anklicken von 'nehmen' beim Lesen. Die Activate-Funktion hingegen erzwingt immer Lesen. Dies bedeutet man muss mit OnActivate auf Büchern SEHR vorsichtig sein, da man sonst einen Gegenstand erschafft den man nicht mehr aufheben kann falls man ihn einmal ablegt. Hier ist zum Beispiel ein Script das verhindert, dass man ein Buch aufheben kann, es wurde von Dave Humphrey entdeckt:

Code:
begin DontPickMeUp_Simple

if ( OnActivate )
    Activate
endif

end

Es gilt also festzustellen ob das Buch bereits gelesen wird oder jemand nur versucht es in das Inventar zu ziehen. Um diese Ereignisse abzufangen, brauchen wir folgende Funktionen:

MenuMode

Gibt 1 zurück, wenn irgendein Menü geöffnet ist. Dies betrifft das Spielmenü (Esc-Taste), Inventar, Dialoge, Messageboxen, Wartezeiten beim Schlafen und Reisen, Bücher lesen sowie Alchemie-Menüs etc. Es gilt aber nicht für eine auf dem HUD fixierte Karte.

Eine häufige Verwendung ist in Verbindung mit return. Wenn man return aufruft, wird jeglicher Code unterhalb davon für den momentanen Frame ignoriert. Bereits durchlaufener Code wird nicht rückgängig gemacht.

Code:
if ( Menumode )
    return
endif

Aufgaben
  1. Probiert die Beispiel-Scripte aus, weist sie unpassenden Objekten zu und seht euch an was passiert.
  2. Erschafft eine tragbare Uhr, die dem Spieler beim Ausrüsten die Zeit anzeigt.
  3. Erschafft ein Buch, das beim ersten Lesen einen Tagebucheintrag erstellt.
  4. Erschafft ein Schwert, das der Spieler nur tragen kann, wenn er einen bestimmten Tagebucheintrag hat, zum Beispiel den von oben.
Tipp: Am anspruchsvollsten ist Aufgabe 3. Es gilt hier aber eigentlich nur den Inhalt der Beispielscripte in eine "sinnvolle" Reihenfolge zu bringen (und euer Wissen aus den vorherige Lektionen anzuwenden).


Lektion 3: Objekte verschwinden und wieder erscheinen lassen

Enable/Disable

Für diese Aufgaben stellt Morrowind Script zwei einfache Funktionen zur Verfügung: Enable und Disable.

Mit dem Aufruf von Disable wird das aufrufende Objekt unsichtbar. Es wird im Spiel nicht mehr gesehen, kann nicht mehr aktiviert werden und auch die AI (künstliche Intelligenz) von NPCs oder Kreaturen wird pausiert. Allerdings laufen Scripte auf disableden Objekten nach wie vor weiter! In Innenzellen wird gleichzeitig die Kollisionsabfrage des Objekts deaktiviert. In Außenzellen bleibt die Kollisionsbox des Objekts bis zum nächsten Neuladen der Zelle (normalerweise bis zum nächsten Betreten) erhalten! Auch für dieses Problem gibt es Workarounds, allerdings würde das jetzt zu weit führen. Vielleicht später einmal.

Enable erreicht genau das Gegenteil. Ein zuvor disabeldes Objekt wird dadruch wieder sichtbar, die AI läuft weiter und die Kollisionsabfrage wird wieder aktiviert (auch hier gibt es in Außenzellen wieder das Problem, dass zunächst neu geladen werden muss).

GetDisabled

Diese Funktion liefert eine 1 zurück, wenn das aufrufende Objekt disabled ist. Dies ist also eine Möglichkeit herauszufinden, ob man ein Objekt noch deaktivieren muss oder ob es das bereits ist.

Ein Beispiel für ein Disable-Script findet man auf dem Geisterwall:

Code:
begin GhostfenceScript

if ( GetDisabled == 1 )
Return
endif          

;this journal entry gets set when heart is wacked and akul falls apart
if ( GetJournalIndex, "C3_DestroyDagoth" >= 20 )
Disable
[...]
endif

end

Die erste if-Abfrage überprüft, ob der Wall bereits disabled wurde. Wenn ja, wird das Script wieder an den Anfang zurück gesetzt (das ist die Wirkung von return) und somit nicht weiter ausgeführt.

Danach wird in der zweiten Abfrage überprüft, ob ein bestimmter Tagebucheintrag vorhanden ist. Wenn dieser vorhanden ist, wird der Wall disabled. Der restliche Teil des Scripts beschäftigt sich mit dem Sound, den der Geisterwall abspielt, deswegen wurde dieser Teil ausgeklammert.

Aufgaben
  1. Statte ein Objekt mit einem Script aus, dass es nach dem Aktivieren verschwinden lässt.
  2. Bastele einen Activator, der anfangs disabled ist und erst nach einem bestimmten Tagebucheintrag erscheint. (Hinweis: Ihr könnt einen Gegenstand nicht im CS als disabled einstellen. Der Gegenstand ist eigentlich enabled, wenn ihr das Spiel ladet. Ihr müsst ein Script verwenden um ihn zu deaktivieren.)
  3. Erschaffe einen (freundlichen?) Geist, der nur nachts erscheint.

Lektion 4: Bewegung von Objekten - Teil 1

In dieser Lektion beschäftigen wir uns zum ersten Mal mit der Bewegung von Objekten. Dies ist natürlich ein riesiges Thema, deswegen gibt es hier nur den ersten Teil.

Man kann mit der Bewegung von Gegenständen eine größere Interaktivität in Modifikationen bringen. Statt einer Teleporttür kann man auch ein sich bewegendes Fallgitter oder statt einer Treppe einen Lift benutzen. Von genau diesen Dingen handelt diese Lektion.

Tatsächlicher Ablauf von Scripten

Zunächst einmal müssen wir ein wenig Theorie über Scripte in Morrowind machen. Für die bisherigen Scripte war es nicht wichtig, wie genau Morrowind Scripte behandelt, jetzt wird dies mit eine entscheidende Bedeutung bekommen.

Morrowind lässt jedes Script, dass sich auf irgendeinem Gegenstand in der aktuellen Zelle befindet in jedem Frame (fps = Frames per Second, also (Halb-)Bilder pro Sekunde) einmal durchlaufen. Auf den meisten Rechnern wird also jedes Script einige Dutzend Male pro Sekunde ausgeführt!

Was ziehen wir daraus für Schlussfolgerungen? Zum Ersten muss ein Script nicht erst aktiviert werden, es läuft automatisch, sobald man die Zelle des entsprechenden Objekts betritt. Das ist normalerweise sehr praktisch, wie wir gleich sehen werden. Zum Zweiten muss man aber auch sicherstellen, dass Aktionen nur einmal ausgeführt werden und nicht in jedem Frame erneut beziehungsweise das genaue Gegenteil, nämlich eine Funktion jeden Frame aufzurufen.

Bewegung von Gegenständen

Nur Gegenstände erscheinen und verschwinden zu lassen, wird natürlich schnell langweilig, man möchte sie auch bewegen. Wie funktioniert Bewegung in Morrowind? Genau genommen gibt es keine echte Bewegung. Objekte werden in jedem Frame an eine leicht veränderte Position gesetzt. Da das menschliche Auge aber nur etwa 20 Bilder pro Sekunde sehen kann (und wir hoffentlich eine viel höhere Framerate haben), nehmen wir diese vielen kleinen Sprünge als kontinuierliche Bewegung wahr.

Da Morrowind seine Scripte sowieso jeden Frame ausführt, nutzen wir dies bei Bewegungen, wir lassen die Bewegungsfunktion einfach in jedem Durchlauf des Scripts eine kleine Bewegung machen. Allerdings gibt es auch immer Situationen, in denen man keine Bewegung haben möchte. Zum Beispiel pausieren die normalen Animationen, wenn man sich im Inventar befindet, Scripte nicht. Also müssen wir dieses Pausieren per Hand erzeugen.

MenuMode

Dazu gibt es die äußerst praktische Funktion MenuMode. Diese liefert immer dann eine 1, wenn irgendein Menü geöffnet ist. Ob dies nun das Spielmenü (ESC), das Inventar, ein Dialog oder eine Messagebox ist, macht keinen Unterschied. Mit folgender Konstruktion kann man die Ausführung eines Scripts unterbrechen, wenn ein Menü geöffnet wird:

Code:
if ( MenuMode == 1 )
return
endif

Dies ist einer der wichtigsten Codebrocken in Morrowind Script. In vielen Scripten ist dies der erste Block, der nach der Variablendeklaration steht.

Move/MoveWorld

Die Bewegung in Morrowind findet in einem dreidimensionalen, kartesischen Koordinatensystem statt (ja, das ist Mathe). Die Achsen dieses Koordinatensystems werden mit X, Y und Z bezeichnet. X läuft dabei von West nach Ost (in Außenzellen), Y von Süd nach Nord (in Außenzellen) und Z von unten nach oben.

Außerdem unterscheidet Morrowind zwischen einem absoluten und einem relativen Koordinatensystem. Das absolute Koordinatensystem ist das der Welt mit den Eigenschaften, die oben beschrieben sind. Für jedes Objekt existiert aber auch ein eigenes relatives Koordinatensystem. In diesem sind die Achsen entsprechend der Drehung des Objekts in der Welt mitgedreht.



Vergleich des absoulten Welt-Koordinatensystems (schwarz) und des relativen Koordinatensystems eines Gegenstands (rot).
Das relative Koordinatensystem mit X' und Y' ist aus einer Drehung des Gegenstandes um die Z-Achse um 45° entstanden.

Wenn das Objekt nicht gedreht ist (also alle Winkel gleich 0 sind), sind das absolute und das relative Koordinatensystem gleich.

Die beiden Funktionen Move und MoveWorld werden so aufgerufen:

Code:
Move, axis, value
MoveWorld, axis, value

---

Move, Z, 100
MoveWorld, X, -200

Diese Funktionen bewegen das aufrufende Objekt mit der in value angegebenen Geschwindigkeit bezüglich der in axis angegebenen Achse. Für axis werden die Buchstaben X, Y und Z akzeptiert. value verarbeitet float-Werte, es können aber auch beliebige andere Werte angegeben werden. Die Werte werden als Editoreinheiten pro Sekunde interpretiert. Tatsächlich wird bei jedem Aufruf nur der entsprechende Bruchteil der Bewegung ausgeführt, die der Zeit seit dem letzten Frame entspricht. Um eine kontinuierliche Bewegung zu erreichen, muss die Funktion also jeden Frame aufgerufen werden. value akzeptiert (leider) keine Variablen!
Die Funktionen haben keine Auswirkungen auf NPCs, Kreaturen oder den Spieler.

Move bewegt das Objekt entlang der relativen Achse. Diese Funktion kann zum Beispiel dafür benutzt werden, dass sich ein Pfeil immer in die Richtung seiner Spitze bewegt, egal in welche Richtung er gedreht ist.

MoveWorld bewegt das Objekt entlang der absoluten Achse. Diese Funktion wird weitaus häufiger benutzt werden und sorgt zum Beispiel dafür, dass sich ein Objekt immer nach oben bewegt, unabhängig davon wie es gedreht ist.

Im obigen Beispiel ist das Objekt um 45° um die Z-Achse gedreht worden. Ein Move, X, 100 würde das Objekt entlang der rotmarkierten X'-Achse bewegen. Die Funktion MoveWorld, X, 100 hingegen bewegt das Objekt entlang der schwarzen X-Achse.

Um ein Gefühl für die Geschwindigkeit zu bekommen, muss man wissen, dass eine Editoreinheit 1,42 cm entspricht. Also entsprechen etwa 20 Editoreinheiten pro Sekunde einem km/h. Geschwindigkeiten über 400 Editoreinheiten pro Sekunde (20 km/h) sollten vermieden werden, weil sie meistens abgehackt aussehen, da die Abstände zwischen den einzelnen Sprungpositionen zu groß werden.

Nun können wir also eine Bewegung starten, wir können sie sogar pausieren, wenn ein Menü geöffnet wird, aber so wird das Objekt eine endlose Bewegung ausführen und irgendwann im Nichts verschwinden. Wie können wir die Bewegung also aufhalten?

Dazu stehen uns grundsätzlich zwei Optionen zur Verfügung. Entweder stoppen wir die Bewegung, wenn das Objekt einen bestimmten Punkt erreicht hat oder wir lassen die Bewegung nur eine bestimmte Zeit lang laufen.

GetPos

Mit dieser Funktion kann man die Position des aufrufenden Objekts bezüglich der angegebenen Achse abfragen:

Code:
float xpos

set xpos to GetPos, X

Statt dem X kann man natürlich auch wieder Y und Z abfragen. Der Rückgabewert der Funktion ist eine float-Größe (also eine Dezimalkomma-Zahl). Über eine if-Abfrage kann man nun prüfen, ob das Objekt bereits an seinem Zielpunkt angelangt ist (natürlich funktioniert hier auch ein while, aber das will ich hier nicht auch noch einführen). Hier ein Beispielscript:

Code:
begin kf_sk_bewegung_bsp1_script

short state
float zpos

if ( MenuMode == 1 )
   return
endif

if ( state == 0 )
   if ( OnActivate == 1 )
      MessageBox, "Nach oben!"

      set zpos to GetPos, Z ;berechne eine obere Schranke für die Bewegung
      set zpos to zpos + 300

      set state to 1
   endif
elseif ( state == 1 )
   if ( GetPos, Z <= zpos )
      MoveWorld, Z, 100
   else
       set state to 2 ;sorgt dafür, dass man nur einmal nach oben fahren kann
   endif
endif

end

Statt der Konstruktion mit zpos könnte man natürlich auch direkt die Koordinaten aus dem CS einsetzen, die man sich als Zielpunkte ausgesucht hat. Diese Formulierung ist aber universell anwendbar, da ich ja nicht weiß, wo in der Spielwelt ihr das Script ausprobieren wollt.

GetSecondsPassed

Die zweite Möglichkeit, um Bewegung zu begrenzen, liegt in der Zeitmessung. Bisher kennen wir dafür die globale Variable GameHour, hiermit ist es aber nicht möglich wirklich kurze Zeitspannen genau zu messen. Dafür gibt es die Funktion GetSecondsPassed. Diese liefert die vergangenen Sekunden(-bruchteile) seit dem letzten Frame zurück. Dies stellt die einzige Scriptmöglichkeit dar, kurze Zeitspannen zu messen. Hier noch einmal ein Script mit der gleichen Funktion wie oben, diesmal mit GetSecondsPassed implementiert:

Code:
begin kf_sk_bewegung_bsp2_script

short state
float timer

if ( MenuMode == 1 )
   return
endif

if ( state == 0 )
    if ( OnActivate == 1 )
      MessageBox, "Nach oben!"
      set state to 1
   endif
elseif ( state == 1 )
   set timer to timer + GetSecondsPassed
   MoveWorld, Z, 100
   if ( timer >= 3 ) ;3 Sekunden mal 100 Einheiten pro Sekunde = 300 Einheiten nach oben
       set state to 2 ;sorgt dafür, dass man nur einmal nach oben fahren kann
   endif
endif

end

Aufgaben
  1. Probiert die Beispielscripte aus und versteht, wie sie funktionieren.
  2. Erstellt einen einfachen Aufzug, der ständig hoch und runter fährt und an jedem Ende jeweils kurz anhält.
  3. Probiert, was passiert, wenn ihr die MenuMode-Abfrage weglasst und auf dem Aufzug das Inventar öffnet.
  4. Warum muss timer im zweiten Beispielscript eine float-Variable sein?
  5. Erstellt einen Activator, der euch beim Aktivieren die aktuelle Framerate (fps) ausgibt.
  6. Erstellt einen Aufzug, der anfangs still steht. Wenn er aktiviert wird, bewegt sich der Aufzug nach oben, wartet dort einen Moment und fährt wieder runter.
  7. Erstellt einen Aufzug, der nach Aktivieren nach oben beziehungsweise unten fährt und dort bleibt, bis er erneut aktiviert wird. Danach fährt er wieder in seine andere Position und wartet dort.

Lektion 5: Bewegung und Rotation von Objekten - Teil 2

In dieser Lektion geht es zunächst um die Rotation von Gegenständen. Dies ist wenn man so will noch mal eine Wiederholung der Translation (also Bewegung) aus der letzten Lektion. Die Befehle sind genau gleich strukturiert. Da dies nur ein sehr kleiner Abschnitt ist, beschäftigen wir uns danach noch mit der Fernsteuerung von Objekten. Bisher musste ein Script immer auf dem Objekt liegen, um es zu bewegen. Tatsächlich ist es oft viel einfacher, das Script auf einem anderen Gegenstand laufen zu lassen, der den zu bewegenden Gegenstand gewissermaßen fernsteuert.

Rotation von Gegenständen

Neben der Bewegung von Gegenständen entlang einer Achse, kann man sie auch um eine Achse rotieren lassen.



Vergleich des absoluten Welt-Koordinatensystems (schwarz) und des relativen Koordinatensystems eines Gegenstands (rot).
Das relative Koordinatensystem mit X' und Y' ist aus einer Drehung des Gegenstandes um die Z-Achse um 45° entstanden.

Die Befehle für die Rotation sind (analog zur Bewegung):

Rotate/RotateWorld/GetAngle

Wieder bezeichnet hier Rotate eine Rotation um die relative Achse und RotateWorld um die absolute Achse. Als Zahl wird diesmal die Winkelgeschwindigkeit in Grad pro Sekunde angegeben. Im obigen Bild bedeutet ein RotateWorld, X, dass sich das Objekt um die schwarze mit X bezeichnete Achse dreht. Ein Rotate, X lässt das Objekt aber um die rote mit X' bezeichnete Achse rotieren.

Ein Beispiel für Rotationsscripte sind die Tresenklappen:

Code:
begin BarDoor

float timer
short on
short open

if ( MenuMode == 0 )
   if ( OnActivate == 1 )
       if ( on == 0 )
          set timer to 0
          set on to 1
          PlaySound3D, "Door Creaky Open"
      endif
   endif

   if ( on == 1 )
      set timer to timer + GetSecondsPassed

      if ( open == 0 )
         if ( timer < 1 )
            Rotate, X, 110
         elseif ( timer >= 1 )
            set on to 0
            set open to 1
         endif
     elseif ( open == 1 )
         if (timer < 1)
             Rotate, X, -110
         elseif ( timer >= 1 )
              SetatStart ;nicht zwingend nötig, beseitigt in diesem Fall kleine Abweichungen, der Position, die durch die Bewegung entstehen können
              set on to 0
              set open to 0
        endif
     endif
   endif
endif

end

Um noch ein Beispiel für die Abfrage des Winkels zu liefern: Im obigen Bild mit den Koordinatensystemen würde GetAngle, Z den Wert 45 zurückliefern.

Bewegung von Gegenständen aus der Ferne

Bisher wurden immer nur die Gegenstände, auf denen das Script selbst lag, bewegt. Viel öfter möchte man aber ein anderes Script (zum Beispiel ein globales) einen Gegenstand bewegen (oder disablen/enablen) lassen. Deswegen ist es möglich in einem Script Funktionen so aufzurufen, dass sie andere Gegenstände beeinflussen.

Damit dies funktioniert, muss dieser andere Gegenstand aber einen Haken bei reference persistent haben. Was bedeutet das? Normalerweise werden in Morrowind nur die Gegenstände geladen, die sich in der aktuellen Zelle befinden. Ein Gegenstand der als persistent markiert ist, wird beim Spielstart geladen und bleibt immer geladen. Da dies natürlich das Spiel verlangsamt, sollte man möglichst sparsam mit dem Häkchen bei reference persistent umgehen. Bei NPCs und Kreaturen muss corpse persistent ausgewählt werden.
Zusätzlich muss ein Objekt, dass ferngesteuert werden soll, einmalig sein. Das heißt, es muss genau einmal existieren.


Der Activator aus dem ersten Script wurde hier als persistent markiert

Wenn man nun also solch ein Objekt hat, kann man es aus einem anderen Script heraus manipulieren. Dies kann zum Beispiel ein Schalter sein, der ein Tor steuert. Die Struktur sieht wie folgt aus:

Code:
"Object_ID"->Disable
set "Object_ID".state to 1

---

"ex_gg_portcullis_01"->MoveWorld, Z, 70
set "skinkintreesshade".soulAshGhoul to 1

Der Pfeil (->) dient hierbei zum Aufruf einer Funktion. Das Objekt vor dem Pfeil ist das, was ich immer als aufrufendens Objekt bezeichne. Wird dort kein Objekt angegeben (wie wir bisher immer), wird dort das Objekt eingesetzt, auf dem das Script liegt. Ein Beispiel hierfür ist das Script GG_OpenGate1, dass auf den Schaltern der Geisterpforte liegt und das (persistent markierte) Gitter der Geisterpforte bewegt.

Mit dem Punkt (.) kann man auf eine lokale Variable eines Scripts auf dem angegebenen Gegenstand zugreifen und diese verändern oder abfragen. Diese Variable muss dazu auch existieren, weiterhin muss der Träger des Scriptes in den letzten 72 Stunden Spielzeit tatsächlich geladen worden sein! Ein Beispiel hierfür ist das Script SkinkSoul1.

Aufgaben
  1. Warum wird im Beispiel der Tresenklappe die Funktion Rotate und nicht RotateWorld benutzt?
  2. Erstellt eine Tür aus einem Activator. Diese soll sich wie eine normale Tür verhalten.
  3. Erstellt einen Aufzug, der nach Betätigen eines Rufknopfs nach oben beziehungsweise unten fährt und dort bleibt, bis ein Rufknopf erneut betätigt wird.
  4. Bonusaufgabe: Erstellt einen Aufzug, der 5 verschiedene Haltepunkte besitzt. an jedem dieser Haltepunkte soll es einen Rufknopf geben, mit dem man den Aufzug auf diese Ebene rufen kann. Der Aufzug soll eine Bedientafel besitzen, an der man jede der Zieletagen auswählen kann. Der Aufzug soll von jeder Ebene zu jeder fahren können.
Lösungen

Hier findet ihr Lösungsvorschläge für die Aufgaben der einzelnen Lektionen. Ich betone, dass es sich nur um Lösungsvorschläge handelt, da viele der Aufgaben auf mehreren Wegen zu lösen sind.

Aufgaben Lektion 1
  1. Verändert das "Hallo, Welt"-Script aus der Einführung so, dass ihr die Beispiele zur MessageBox-Funktion ausprobieren könnt.
    Ersetzt einfach den MessageBox-Aufruf mit den angegebenen anderen Aufrufen.

  2. Probiert das zweite Beispielscript aus.
    Ja, probiert das Script aus.

  3. Verändert das "Hallo, Welt"-Script so, dass es zunächst fragt, ob der Spieler ein Mann oder eine Frau ist und danach mit "Hallo Herr y" oder "Hallo Frau x" begrüßt.
    Hier lag die Aufgabe darin, zwei Messageboxen nacheinander darzustellen. Die erste sollte dem Spieler eine Auswahl von zwei Antwortmöglichkeiten bieten und der Inhalt der zweiten sollte von der Antwort abhängen. Das hier gewählte Beispiel ist zwar eher sinnlos, aber die Problemstellung mit anderen Texten ist recht häufig.

    Code:
    begin kf_sk_act_geschlecht_script 
    
    short state 
    short button 
    
    if ( state == 0 )
         if ( OnActivate == 1 )
             set state to 1
             MessageBox, "Seid Ihr ein Mann oder eine Frau?", "Mann", "Frau"
         endif
    else
         set button to GetButtonPressed
    
         if ( button == 0 )
             set state to 0
             MessageBox, "Seid gegrüßt, Herr ^PCName"
         elseif ( button == 1 )
             set state to 0
             MessageBox, "Seid gegrüßt, Frau ^PCName"
         endif
    endif
    end
    In Zeile 1 starte ich das Script. In Zeile 3 und 4 definiere ich die beiden short-Variablen state und button. Erstere steuert, dass man das Script immer wieder neu aktivieren kann. Die zweite speichert die Entscheidung des Spielers.

    In Zeile 6 überprüfe ich, ob sich das Script im Ausgangszustand (state == 0) befindet. Wenn dem so ist, wird in die nächste Ebene (Zeile 7) gegangen. Hier wird überprüft, ob der Spieler das Objekt, auf dem das Script liegt, aktiviert. Sobald er dies tut, ist die Bedingung erfüllt und das Script kann auch an dieser if-Bedingung vorbei.

    In Zeile 8 setze ich, für den Fall, dass das Script im Ausgangszustand ist und der Spieler das Objekt aktiviert, den Status des Scripts auf 1. Somit kann dieser Teil des Scripts nun nicht noch einmal durchlaufen werden, bis man ihm wieder sagt, dass es okay ist.

    In Zeile 9 wird die erste Messagebox mit den beiden Antwortmöglichkeiten des Spielers ausgegeben.

    Danach gelangt das Script zu einem endif und springt weiter. Beim else springt das Script ebenfalls weiter, da die Bedingung zu Beginn des Scriptes ja erfüllt war. So gelangt das Script zum abschließenden endif (Zeile 21) und zum end (Zeile 23).

    Nun läuft das Script wieder von vorne los. Die Variablen sind bereits deklariert, also geht es direkt zu Zeile 6. Diesmal ist state aber 1, somit ist die Bedingung nicht erfüllt und das Script springt zum else in Zeile 11. Hier kann das Script nun weiterarbeiten.

    In Zeile 12 wird der Variable button die Nummer des Buttons zugewiesen, den der Spieler gedrückt hat. Am Anfang hat der Spieler noch keinen Button gedrückt (er hat ja eine gewisse Reaktionszeit). Deswegen nimmt button den Wert -1 an. Die folgende if-Abfrage (Zeile 14) ist also nicht erfüllt (-1 = 0 -> falsch). Also springt das Script zum elseif in Zeile 17. Auch dieses ist nicht erfüllt (-1 = 1 -> falsch). Somit springt das Script zum endif in Zeile 20 und von dort aus über das letzte endif zum end.

    Das Script startet wieder von vorne und läuft bis Zeile 12 wieder genauso ab. Jetzt hat der Spieler aber einen der beiden Buttons gedrückt. Sagen wir er hat den ersten Button ("Mann") gedrückt. In dem Fall bekommt button nun den Wert 0. Somit ist die if-Abfrage in Zeile 14 nun wahr und das Script läuft in Zeile 15 weiter. Zuerst wird state wieder auf 0 gesetzt. Das heißt, dass das Script nach diesem Durchlauf wieder neu gestartet werden kann. In Zeile 16 wird dann die zweite Messagebox ausgegeben.

    In Zeile 17 kommt erneut das elseif. Da bereits das if erfüllt war, springt das Script zum endif. Dann erneut endif und end. Jetzt ist das Script komplett durchgelaufen und man kann den Text wieder von vorne anfangen zu lesen.

  4. Erschafft einen Activator, der sich wie eine Digitaluhr verhält. Sie soll euch mit Namen begrüßen und euch die Zeit (in Stunden und Minuten, nicht nur in Stundenbruchteilen) und das Datum angeben. Ihr sollt die Anzeige bestätigen müssen, bevor ihr weiterspielen könnt.
    Die Schwierigkeit dieses Scripts lag darin, die Uhrzeit, die man nur als Kommazahl der Stundenbruchteile abrufen kann, in das uns bekannte Stunden-Minuten-Format zu konvertieren. Dazu war es nötig mit Variablen zu rechnen.

    Code:
    begin kf_sk_act_uhr_script
    
    short hour
    short min
    float time
    
    if ( OnActivate == 1 )
       
        set time to GameHour
        set hour to time
        set min to ( time - hour ) * 60
    
        if ( min < 10 )
            MessageBox, "Seid gegrüßt, ^PCName. Es ist %.0f:0%.0f Uhr am %.0f. Tag des %.0f. Monats im %.0f. Jahr der 3. Ära.", hour, min, Day, Month, Year, "OK"
        else
            MessageBox, "Seid gegrüßt, ^PCName. Es ist %.0f:%.0f Uhr am %.0f. Tag des %.0f. Monats im %.0f. Jahr der 3. Ära.", hour, min, Day, Month, Year, "OK"
        endif
    
    endif
    
    end
    • Zeile 7: Hier warte ich darauf, dass der Spieler den Gegenstand aktiviert.
    • Zeile 9: Nachdem der Gegenstand aktiviert wurde, setze ich meine lokale float-Variable time auf den Wert der globalen float-Variablen GameHour. In der wird dann zum Beispiel der Wert 16.123456 gespeichert.
    • Zeile 10: Ich weise meiner lokalen short-Variable den Wert meiner float-Variable zu. Dabei werden einfach die Nachkommastellen abgeschnitten. Die Variabel hat dann also den Inhalt 16.
    • Zeile 11: Hier berechne ich die Minuten. Die Nachkommastellen geben den Bruchteil einer ganzen Stunde an, die bereits vergangen ist, also nehme ich den mit 60 mal, damit ich den Wert der Minuten bekomme. Dazu muss ich natürlich ersteinmal die Nachkommastellen herausbekommen. Dazu ziehe ich die vollen Stunden von der aktuellen Zeit ab:
      Code:
      set min to ( time - hour ) * 60
      set min to ( 16.123456 - 16 ) * 60
      set min to 0.123456 * 60
      set min to 7.40736

      Da min wieder einer short-Variable ist, werden die Nachkommastellen wieder abgeschnitten und die Zahl 7 in min gespeichert.
    • Zeile 13: Hier prüfe ich, ob die Minuten einstellig sind. In dem Fall muss ich noch eine führende Null in der MessageBox einfügen, sonst würde die Zeit nicht als 16:07 sondern als 16:7 dargestellt.
  5. Erschafft eine Tür, die sich erst öffnet, wenn der Spieler einen bestimmten Tagebucheintrag hat. Hat er ihn noch nicht, soll er eine entsprechende Nachricht bekommen, hat er ihn, soll die Tür beim ersten Betreten einen neuen Tagebucheintrag hinzufügen.
    Hier musste man eine Aktivierung mit einer Abfrage nach einem Tagebucheintrag kombinieren und in einem der beiden Fälle noch eine weitere optionale Funktion einbauen. Normalerweise könnte man ein solches Script natürlich noch toll gestalten mit Sounds und so weiter, aber diese Funktionen hatten wir noch nicht, deswegen kommen sie in meiner Lösung auch nicht vor.

    Code:
    begin kf_sk_door_journalblock_script
    
    if ( OnActivate == 1 )
        if ( GetJournalIndex, "kf_testthema" >= 10 )
            if ( GetJournalIndex, "kf_testthema" < 20 )
                Journal, "kf_testthema", 20
            endif
            Activate
        endif
    endif
    
    end

    Dieses Script kommt ohne Variablen aus. Zunächst wird wieder darauf gewartet, dass der Spieler die Tür aktiviert. Sobald er dies tut, wird überprüft, ob er einen bestimmten Tagebucheintrag schon besitzt. Hier wird explizit überprüft, ob der Spieler einen bestimmten Tagebucheintrag oder einen höheren Eintrag dieses Themas besitzt. Dies ist normalerweise gewollt, da sich die Tür ja auch noch öffnen soll, wenn der Spieler einen weiteren Tagebucheintrag erhält. Soll sich die Tür nur einmal öffnen und danach nicht mehr aktivieren lassen, sollte hier statt dem >= ein == gewählt werden.

    Danach wird in Zeile 5 überprüft, ob der Spieler noch keinen Tagebucheintrag besitzt der größer oder gleich dem 20er Eintrag ist. Wenn das wahr ist, wird dem Spieler ein neuer Tagebucheintrag (eben der Index 20) hinzugefügt. Danach wird die Tür aktiviert (sie öffnet sich also oder teleportiert den Spieler).

    Hat der Spieler noch nicht den Tagebucheintrag mit Index 10 (oder einen höheren), läuft das Script bis zum Ende durch ohne irgendetwas zu machen. Aber mit der Tür passiert auch nichts. Für den Spieler ist es so, als hätte er die Tür überhaupt nicht aktiviert.

    Wenn der Spieler die Tür nach dem ersten erfolgreichen Betreten erneut aktiviert, ist die zweite if-Abfrage falsch (er hat ja den 20er Eintrag bereits beim ersten Durchgehen bekommen). Somit wird dieser Teil nicht ausgeführt und die Tür öffnet sich einfach. Würde diese zweite if-Abfrage weggelassen, würde der Spieler bei jedem Benutzen der Tür den 20er Tagebucheintrag erneut in sein Tagebuch geschrieben bekommen.

  6. Versucht einen Scriptteil zu schreiben, der die Rechenoperation div für die short-Variablen a und b ausführt.
    Code:
    short div
    
    set div to a / b

    Hier mache ich mir die Eigenschaft von Morrowind Script zu Nutze, dass Kommazahlen einfach abgeschnitten werden, wenn man sie einer short-Variablen zuweist.

  7. Schreibt einen Scriptteil, der die Modulo-Operation für die short-Variablen a und b durchführt. Modulo bezeichnet den Rest einer Division.
    Code:
    short mod
    short div
    
    set div to a / b
    set mod to a - ( div * b )

    Auch hier verwende ich wieder die besondere Eigenschaft der Zuweisung von Kommazahlen auf short-Variablen. Da in div * b das größte Vielfache von b steht, das kleiner ist als a, muss die Differenz von den beiden natürlich den Rest der Division ergeben.

  8. Versucht euch an einem Scriptteil, der nur ausgeführt wird, wenn zwei Vergleiche wahr sind (könnte man auch als Bool'sches AND bezeichnen).
    Code:
    begin kf_sk_and_script
    
    short a
    short b
    
    if ( a == 1 )
        if ( b == 1 )
            ;mache irgendwas
        endif
    endif
    
    end
  9. Schreibt einen Scriptteil, der ausgeführt wird, wenn mindestens einer von zwei Vergleichen wahr ist (könnte man auch als Bool'sches OR bezeichnen).
    Code:
    begin kf_sk_or_script
    
    short a
    short b
    short c ;Kontrollvariable für das ODER
    
    if ( a == 1 )
    set c to 1
    elseif ( b == 1 )
    set c to 1
    endif
    
    if ( c == 1 )
    set c to 0
    ;mache irgendwas
    endif
    
    end
  10. Schreibt ein Quiz mit drei Fragen (mit Messageboxen). Der Spieler soll jeweils mehrere Antwortmöglichkeiten haben, von denen nur eine richtig ist. Der Spieler soll sich eine falsche Antwort erlauben können. Beantwortet er eine zweite Frage falsch, soll er verlieren.
    Code:
    begin kf_sk_quiz_script
    
    short state        ;speichert den Status des Scripts
    short button        ;speichert die Antwort des Spielers auf die aktuelle Frage
    short answer    ;speichert die richtige Antwort auf die aktuelle Frage
    short false        ;speichert die Anzahl der falschen Antworten
    
    if ( state == 0 )
         if ( OnActivate == 1 )
             set state to 1
             MessageBox, "Willkommen beim TES-Quiz. Ihr müsst drei Fragen beantworten. Wenn Ihr zwei falsch beantwortet, habt Ihr verloren.", "In Ordnung", "Nein, danke"
         endif
    elseif ( state == 1 )
         set button to GetButtonPressed
         if ( button == 0 )
             set state to 2
             MessageBox, "Wie hieß der erste Teil der TES-Reihe?", "Arena", "Daggerfall", "Morrowind", "Oblivion"
             set answer to 0
             return
         elseif ( button == 1 )
             MessageBox, "Kommt später wieder."
             set state to 0
         endif
    elseif ( state == 2 )
         set button to GetButtonPressed
         if ( button == answer )
             set state to 3
             MessageBox, "Richtig. Nächste Frage: Wie hieß der zweite Teil der TES-Reihe?", "Arena", "Daggerfall", "Morrowind", "Oblivion"
             set answer to 1
             return
         elseif ( button > -1 )
             set state to 3
             MessageBox, "Falsch. Nächste Frage: Wie hieß der zweite Teil der TES-Reihe?", "Arena", "Daggerfall", "Morrowind", "Oblivion"
             set answer to 1
             set false to 1
             return
         endif
    elseif ( state == 3 )
         set button to GetButtonPressed
         if ( button == answer )
             set state to 4
             MessageBox, "Richtig. Nächste Frage: Wie hieß der dritte Teil der TES-Reihe?", "Arena", "Daggerfall", "Morrowind", "Oblivion"
             set answer to 2
             return
         elseif ( button > -1 )
             if ( false == 0 )
                 set state to 4
                 MessageBox, "Falsch. Nächste Frage: Wie hieß der dritte Teil der TES-Reihe?", "Arena", "Daggerfall", "Morrowind", "Oblivion"
                 set answer to 2
                 set false to 1
                 return
             else
                 MessageBox, "Ihr habt die zweite Frage falsch beantwortet. Ihr habt verloren."
                 set false to 0
                 set state to 0
             endif
                 endif elseif ( state == 4 )
         set button to GetButtonPressed
         if ( button == answer )
             MessageBox, "Richtig. Ihr habt gewonnen"
             set false to 0
             set state to 0
             return
         elseif ( button > -1 )
             if ( false == 0 )
                 MessageBox, "Falsch. Ihr habt trotzdem gewonnen."
                 set false to 0
                 set state to 0
             else
                 MessageBox, "Ihr habt die zweite Frage falsch beantwortet. Ihr habt verloren."
                 set false to 0
                 set state to 0
             endif
                 endif
    endif
    end
Aufgaben Lektion 2
  1. Probiert die Beispiel-Scripte aus, weist sie unpassenden Objekten zu und seht euch an was passiert.
    Probiert es aus.

  2. Erschafft eine tragbare Uhr, die dem Spieler beim Ausrüsten die Zeit anzeigt.
    Code:
    begin kf_sk_watch_script
    
    short OnPCEquip
    short min
    short hour
    float time
    
    if ( OnPCEquip == 1 )
    
        set time to GameHour
        set hour to time
        set min to ( time - hour ) * 60
    
        if ( min < 10 )
            MessageBox, "Es ist %.0f:0%.0f Uhr.", hour, min, "OK"
        else
            MessageBox, "Es ist %.0f:%.0f Uhr.", hour, min, "OK"
        endif   
    
        set OnPCEquip to 0
    endif
    
    end
  3. Erschafft ein Buch, das beim ersten Lesen einen Tagebucheintrag erstellt.
    Code:
    begin kf_sk_book_script
    
    short PCSkipEquip
    
    if ( PCSkipEquip == 1 )                ;wird ausgelöst, wenn das Buch im Inventar gelesen wird
        if ( GetJournalIndex, "kf_sk_testthema" < 10 )
            Journal, "kf_sk_testthema", 10
        endif
    endif
    
    if ( OnActivate == 1 )                ;wird ausgelöst, wenn das Buch außerhalb des Inventars gelesen wird
        if ( GetJournalIndex, "kf_sk_testthema" < 10 )
            Journal, "kf_sk_testthema", 10
        endif
        Activate
    endif
    
    end
  4. Erschafft ein Schwert, das der Spieler nur tragen kann, wenn er einen bestimmten Tagebucheintrag hat.
    Code:
    begin kf_sk_sword_script
    
    short PCSkipEquip
    short state
    
    if ( state == 0 )
        set PCSkipEquip to 1
        set state to 1
    elseif ( state == 1 )
        if ( GetJournalIndex, "kf_testthema" >= 10 )
            set PCSkipEquip to 0
            set state to 2
        endif
    endif
    
    end
Aufgaben Lektion 3
  1. Statte ein Objekt mit einem Script aus, dass es nach dem Aktivieren verschwinden lässt.
    Ein sehr kurzes Script. Man muss nur die Aktivierung des Objekts überprüfen und den Gegenstand im Falle der Aktivierung diablen. Da ein disableder Gegenstand nicht mehr aktiviert werden kann, hat sich das Problem damit von selbst gelöst.

    Code:
    begin kf_sk_vanish1_script
    
    if ( OnActivate == 1 )
    Disable
    endif
    
    end
  2. Bastele einen Activator, der anfangs disabled ist und erst nach einem bestimmten Tagebucheintrag erscheint.
    In diesem Script muss man den Gegenstand zunächst disablen. Dazu sorgt man dafür, dass dies beim ersten Scriptdurchlauf automatisch geschieht. Da die state-Variable mit 0 initialisiert wird, ist die erste if-Abfrage auf jeden Fall erfüllt. Dadurch wird der Gegenstand direkt im ersten Frame nach dem Laden der Zelle disabled. Danach wird dieser Teil des Scripts nie mehr aufgesucht, da state nun 1 ist. In diesem Zustand kann bequem auf den richtigen Tagebucheintrag gewartet werden.

    Code:
    begin kf_sk_vanish2_script
    
    short state
    
    if ( state == 0 )
       Disable
       set state to 1
    elseif ( state == 1 )
       if ( GetJournalIndex, "kf_test_journal" == 100 )
         Enable
         set state to 2
       endif
    endif
    
    end

  3. Erschaffe einen Geist, der nur nachts erscheint.
    Dieses Script ist ein wenig schwieriger. Da wir nicht wissen, wann der Spieler die Zelle mit dem Geist betritt, müssen wir jede mögliche Uhrzeit abdecken. wenn das Script zum ersten Mal läuft, ist der Geist auf jeden Fall enabled. Also prüfen wir zunächst auf einer Bedingung, wann er verschwinden soll. Das ist jede Uhrzeit zwischen 6 und 20 Uhr. Sobald dies eredigt ist, springt das Script in den zweiten Teil und wartet auf eine der Bedingungen, wann der Geist wieder erscheinen soll. Dies ist der Fall wenn entweder die Uhrzeit größer als 20 Uhr oder kleiner als 6 Uhr ist.

    Code:
    begin kf_sk_vanish1_script
    
    if ( GetDisabled == 0 )
       if ( GameHour > 6 )
         if ( GameHour < 20 )
           Disable
         endif
       endif
    else
       if ( GameHour < 6 )
         Enable
       elseif ( GameHour > 20 )
         Enable
       endif
    endif
    
    end
Aufgaben Lektion 4
  1. Probiert die Beispielscripte aus und versteht, wie sie funktionieren.
    Probiert sie aus.

  2. Erstellt einen einfachen Aufzug, der ständig hoch und runter fährt und an jedem Ende jeweils kurz anhält.
    Code:
    begin kf_sk_aufzug1_script
    
    short state
    float timer
    
    if ( MenuMode == 1 )
       return
    endif
    
    if ( state == 0 )                ;Aufzug fährt hoch
    
        if ( GetPos, Z < 300 )
            MoveWorld, Z, 50
        else
            set state to 1
        endif
    
    elseif ( state == 1 )        ;Aufzug hält oben
    
        set timer to timer + GetSecondsPassed
        if ( timer > 1 )
            set timer to 0
            set state to 2
        endif
    
    elseif ( state == 2 )        ;Aufzug fährt runter
    
        if ( GetPos, Z > 0 )
            MoveWorld, Z, -50
        else
            set state to 3
        endif
    
    else                            ;Aufzug hält unten
    
        set timer to timer + GetSecondsPassed
        if ( timer > 1 )
            set timer to 0
            set state to 0
        endif
    
    endif
    
    end
  3. Probiert, was passiert, wenn ihr die MenuMode-Abfrage weglasst und auf dem Aufzug das Inventar öffnet.
    Probiert es aus.

  4. Warum muss timer im zweiten Beispielscript eine float-Variable sein?
    Da die Funktion GetSecondsPassed die Zeit seit dem letzten Frame in Sekunden angibt, liegt dieser Wert normalerweise deutlich unter 1 (bei 30 FPS wäre der Wert 0,03). Somit würde sich der Wert von timer nie ändern und immer auf 0 bleiben, wenn man in einer short-Variable nur die Vorkommastellen speichert.

  5. Erstellt einen Activator, der euch beim Aktivieren die aktuelle Framerate (fps) ausgibt.
    Code:
    begin kf_sk_fps_script
    
    float fps
    
    if ( OnActivate == 1 )
        set fps to 1 / GetSecondsPassed
        MessageBox, "Die aktuelle Bildrate pro Sekunde (FPS) ist %.0f", fps
    endif
    
    end

    Wie euch vielleicht beim Test aufgefallen ist, kostet die eingebaute Funktion zum Anzeigen der Framerate (tdt) einen etwa 15 FPS. Somit kann dieses Script zu Testzwecken von Modifikationen (ob diese zu viel Leistung fressen) durchaus nützlich sein, da es die Geschwindigkeit von Morrowind nicht beeinflusst. Alternativ kann man natürlich auch den entsprechenden Ini-Eintrag ändern und sich die FPS immer anzeigen lassen, was die Framerate im Gegensatz zu tdt nicht beeinflusst.

  6. Erstellt einen Aufzug, der anfangs still steht. Wenn er aktiviert wird, bewegt sich der Aufzug nach oben, wartet dort einen Moment und fährt wieder runter.
    Code:
    begin kf_sk_aufzug2_script
    
    short state
    float timer
    
    if ( MenuMode == 1 )
    return
    endif
    
    if ( state == 0 )                ;Aufzug wartet auf Aktivierung
    
        if ( OnActivate == 1 )
            set state to 1
        endif
    
    elseif ( state == 1 )        ;Aufzug fährt hoch
    
        if ( GetPos, Z < 300 )
            MoveWorld, Z, 50
        else
            set state to 2
        endif
    
    elseif ( state == 2 )        ;Aufzug hält oben
    
        set timer to timer + GetSecondsPassed
        if ( timer > 1 )
            set timer to 0
            set state to 3
        endif
    
    elseif ( state == 3 )        ;Aufzug fährt runter
    
        if ( GetPos, Z > 0 )
            MoveWorld, Z, -50
        else
            set state to 0
        endif
    
    endif
    
    end
  7. Erstellt einen Aufzug, der nach Aktivieren nach oben beziehungsweise unten fährt und dort bleibt, bis er erneut aktiviert wird. Danach fährt er wieder in seine andere Position und wartet dort.
    Code:
    begin kf_sk_aufzug3_script
    
    short state
    float timer
    
    if ( MenuMode == 1 )
    return
    endif
    
    if ( state == 0 )                ;Aufzug wartet auf Aktivierung
    
        if ( OnActivate == 1 )
            set state to 1
        endif
    
    elseif ( state == 1 )        ;Aufzug fährt hoch
    
        if ( GetPos, Z < 300 )
            MoveWorld, Z, 50
        else
            set state to 2
        endif
    
    elseif ( state == 2 )        ;Aufzug hält oben und wartet auf erneute Aktivierung
    
        if ( OnActivate == 1 )
            set state to 3
        endif
    
    elseif ( state == 3 )        ;Aufzug fährt runter
    
        if ( GetPos, Z > 0 )
            MoveWorld, Z, -50
        else
            set state to 0
        endif
    
    endif
    
    end
Aufgaben Lektion 5
  1. Warum wird im Beispiel der Tresenklappe die Funktion Rotate und nicht RotateWorld benutzt?
    Da man nicht weiß, in welcher Orientierung die Klappe später im Spiel eingebaut wird, nimmt man diesen Befehl. Somit dreht sich die Klappe immer um ihre eigene Achse und zwar unabhängig davon wie verdreht auch immer sie im Spiel vorkommt.

  2. Erstellt eine Tür aus einem Activator. Diese soll sich wie eine normale Tür verhalten.
    Zunächst einmal: Warum sollte man sowas machen? Der Vorteil einer solchen Tür ist ganz klar, dass sie auf der Karte nicht als gelbes Kästchen erscheint und sich somit perfekt für eine Geheimtür eignet.

    Code:
    begin kf_sk_activator_door_script
    
    short state
    float timer
    
    if ( MenuMode == 1 )
    return
    endif
    
    if ( state == 0 )                ;Tür ist geschlossen
    
        if ( OnActivate == 1 )
            set state to 1
        endif
    
    elseif ( state == 1 )        ;Tür schwingt auf
    
        set timer to timer + GetSecondsPassed
    
        if ( timer < 1 )
            Rotate, Z, 90
        else
            set timer to 0
            set state to 2
        endif
    
    elseif ( state == 2 )        ;Tür ist offen
    
        if ( OnActivate == 1 )
            set state to 3
        endif
    
    else                            ;Tür schwingt zu
    
        set timer to timer + GetSecondsPassed
    
        if ( timer < 1 )
            Rotate, Z, -90
        else
            set timer to 0
            set state to 0
        endif
    
    endif
    
    end

  3. Erstellt einen Aufzug, der nach Betätigen eines Rufknopfs nach oben beziehungsweise unten fährt und dort bleibt, bis ein Rufknopf erneut betätigt wird.
    Hier nehme ich direkt die Lösung des 3. Aufzuges aus der 4. Lektion. Ich muss nur zwei Zeilen ändern, damit es funktioniert. Das Script liegt in dem Fall auf dem Rufknopf. Der Aufzug selbst ist ein Objekt mit reference persistent und dem Namen kf_sk_act_aufzug.

    Code:
    begin kf_sk_aufzug_rufknopf_script
    
    short state
    float timer
    
    if ( MenuMode == 1 )
    return
    endif
    
    if ( state == 0 )                ;Aufzug wartet auf Aktivierung
    
        if ( OnActivate == 1 )
            set state to 1
        endif
    
    elseif ( state == 1 )        ;Aufzug fährt hoch
    
        if ( GetPos, Z < 300 )
            "kf_sk_aufzug"->MoveWorld, Z, 50
        else
            set state to 2
        endif
    
    elseif ( state == 2 )        ;Aufzug hält oben und wartet auf erneute Aktivierung
    
        if ( OnActivate == 1 )
            set state to 3
        endif
    
    elseif ( state == 3 )        ;Aufzug fährt runter
    
        if ( GetPos, Z > 0 )
            "kf_sk_aufzug"->MoveWorld, Z, -50
        else
            set state to 0
        endif
    
    endif
    
    end
  4. Bonusaufgabe: Erstellt einen Aufzug, der 5 verschiedene Haltepunkte besitzt. an jedem dieser Haltepunkte soll es einen Rufknopf geben, mit dem man den Aufzug auf diese Ebene rufen kann. Der Aufzug soll eine Bedientafel besitzen, an der man jede der Zieletagen auswählen kann. Der Aufzug soll von jeder Ebene zu jeder fahren können.
    Zunächst einige Hinweise:
    • Ihr benötigt für meine Version der Lösung 2 Scripte und eine globale Variable.
    • Das eine Script liegt auf dem Aufzug. Das andere liegt auf allen 5 Rufknöpfen.
    Diese Aufgabe benötigt eine etwas umfangreichere Lösung. Zunächst erkläre ich, wie der Aufzug allgemein funktioniert. Danach zeige ich die Scripts für meine Version, die man natürlich beliebig abändern kann, damit sie der eigenen Situation nutzen.

    Natürlich kann man einen solchen Aufzug nicht mehr mit einfachen verschachtelten if-Konstruktionen bauen. Es gibt einfach viel zu viele mögliche Kombinationen. Meine Idee war gewesen, dass der Aufzug weiß in welchem Stockwerk er sich befindet und man ihm nur von außen vorgibt, in welches Stockwerk er fahren soll. Dann habe ich mir überlegt, dass es eigentlich besser ist, wenn man gar nicht einschränkt ob es Stockwerke (mit gleicher Höhe) oder voneinander unabhängige Haltepunkte sind. Deshalb lege ich eine globale float-Variable an, die einfach die gewünschte Zielposition des Aufzugs speichert. Die Rufknöpfe machen nichts anderes als diese globale Variable auf ihre eigene Z-Position (GetPos, Z) zu setzen.

    Das Script des Aufzugs besteht aus 3 Teilen. Der erste Teil wird nur einmal ausgeführt und setzt die globale Variable auf die Startposition des Aufzuges. Sonst würde er sofort irgendwohin fahren. Der zweite Teil ist der Dialog, mit dem der Spieler das Zielstockwerk wählen kann. Auch dieser Dialog macht nichts anderes als die globale Variable auf einen neuen Zielwert zu setzen. Der wichtigste Teil ist der dritte. Hier überprüft der haltende Aufzug, ob ihm ein neues Ziel gegeben wurde. Wenn ja, fährt er zur neuen Zielposition. Sobald er diese Zielposition erreicht hat, wartet er wieder auf eine neue Zielposition.

    In meinem Beispiel heißt die globale Variable kf_sk_dwr_aufzug_global und sie ist vom float-Typ. Ihr Startwert ist egal.

    Dieses Script liegt auf den Rufknöpfen:

    Code:
    begin kf_sk_dwr_rufknof_script
    
    float zpos
    
    if ( OnActivate == 1 )
        set zpos to GetPos, Z
        set zpos to zpos - 208 ;bereinigt die Zielhöhe um die Höhe des Schalters über dem Fußboden
        set kf_sk_dwr_aufzug_global to zpos
    endif
    
    end

    Dieses Script liegt auf dem Aufzug:

    Code:
    begin kf_sk_dwr_aufzug_script
    
    short question
    short doonce
    short button
    short state
    float ziel
    float zpos
    
    if ( doonce == 0 )
        set doonce to 1
        set zpos to GetPos, Z
        set kf_sk_dwr_aufzug_global to zpos
    endif
    
    if ( MenuMode == 1 )
        return
    endif
    
    if ( question == 0 )
        if ( OnActivate == 1 )
            set question to 1
            MessageBox, "In welches Stockwerk wollt Ihr fahren?", "1", "2", "3", "4", "5", "Abbrechen"
        endif
    else
        set button to GetButtonPressed
    
        if ( button == 5 )
            set question to 0
            return
        elseif ( button >= 0 )
            set question to 0
            set zpos to button * 384
            set zpos to zpos + 14000
            set kf_sk_dwr_aufzug_global to zpos
            return
        endif
    endif   
    
    if ( state == 0 )                                        ;Aufzug wartet auf neues Ziel
        if ( ziel < kf_sk_dwr_aufzug_global )
            set state to 1
            set ziel to kf_sk_dwr_aufzug_global
        elseif ( ziel > kf_sk_dwr_aufzug_global )
            set state to 2
            set ziel to kf_sk_dwr_aufzug_global
        endif
    elseif ( state == 1 )                                ;Aufzug fährt nach oben
        if ( GetPos, Z < ziel )
            MoveWorld, Z, 100
        else
            SetPos, Z, ziel
            set state to 0
        endif
    elseif ( state == 2 )                                ;Aufzug fährt nach unten
        if ( GetPos, Z > ziel )
            MoveWorld, Z, -100
        else
            SetPos, Z, ziel
            set state to 0
        endif
    endif
    
    end
Autor
Killfetzer
Aufrufe
3.062
Erstellt am
Letzte Bearbeitung
Bewertung
0,00 Stern(e) 0 Bewertung(en)

Weitere Ressourcen von Killfetzer