MQ!Engine SDK-Dokumentation

MQ!Engine 1.0

The ultimate real-life gaming experience

SDK Dokumentation

Willkommen beim MQ!Engine Software Development Kit (SDK). Diese Anleitung soll einige grundlegende Konzepte der MQ!Engine erläutern sowie als kleine Anleitung zur Erstellung eigener Spiele dienen, welche die Engine verwenden.

Das SDK ist in folgende Bereiche unterteilt:

  • Grundlagen. Hier lernen Sie die ersten Schritte im Umgang mit der MQ!Engine. Die Engine wird aus Entwicklersicht beschrieben und eine wichtige Konzepte werden erläutert.
  • Erweiterte Merkmale. Wenn Sie die volle Funktionsvielfalt der MQ!Engine einsetzen möchten, dann gibt es einige Techniken zu erlernen. Die wichtigsten davon werden hier beschrieben.
  • Workshops. Hier finden Sie Schritt-für-Schritt-Anleitungen und Beispiele zu häufig vorkommenden Aufgabenstellungen.
  • Referenzen. In diesem Teil befinden sich einige Übersichten, die bei der Arbeit sehr nützlich sein können.

Das MQ!Engine-Team wünscht viel Spaß beim Lesen & Spiele-designen!


Komponentenüberblick

Ein MQ!Engine-Spiel besteht aus mehreren Komponenten. Die folgende Grafik gibt einen groben Überblick darüber, welche Teile hier zu unterscheiden sind.

Als Informationen werden die allgemeinen Angaben zum Spiel bezeichnet, z.B. der Titel, die Spielart, die Farben, die verfügbaren Kommandos usw.

Klassendefinitionen bestimmen, welche Arten von Kreaturen, Gegenständen usw. es im Spiel geben kann. Sie legen auch ihre Stärke und ihr Aussehen fest.

Medien wie Grafiken, Bilder, Sounds und Musik werden an vielen Stellen im Spiel benötigt, aber sie werden trotzdem zentral gespeichert.

Skripts sind sehr wichtige Hilfsmittel, um das Spiel anzupassen. Man kann eine sehr große Kontrolle über den Spielablauf erhalten, z.B. wann ein Spieler das Spiel gewonnen hat. Außerdem werden Aktionen wie Gespräche mit anderen Personen mit Skripts programmiert.

Schließlich gibt es noch die Karten, in denen die Spielwelt modelliert wird. Hier können auch schon die meisten Objekte und Kreaturen in der Welt platziert werden.

Es gibt noch eine Reihe weiterer kleiner Komponenten, aber die oben genannten sind die wichtigsten und genügen bereits für ein einfaches Spiel.


Ein neues Spiel anlegen

Anlegen eines neuen Spiels

Ein „Spiel“ oder „Spiele-Projekt“ ist im Prinzip einfach ein Ordner, der ein paar essentielle Informationen enthält, die zum Ausführen des Spiels benötigt werden, und eine ausführbare Datei der MQ!Engine. Alle weiteren Informationen bestimmen schon den Inhalt des Spiels.

Die grundlegendsten Informationen werden in der Datei mqengine.inf gespeichert, auch Engine-Informationsdatei genannt. Diese Datei muss im gleichen Verzeichnis liegen wie die ausführbare Engine-Datei MQEngine.exe. Dann müssen noch die Pakete definiert werden. Schließlich können Sie die allgemeinen Parameter des Spiels durch Dateien im Paket GameData anpassen.

Format der Engine-Informationsdatei

Diese Datei mit dem Dateinamen mqengine.inf wird von der Engine immer zuerst gesucht und eingelesen. Sie besitzt das folgende Textformat:

[Engine1.0]

AppKey=[your application key]

AppTitle=[game title]

Packages=[package list file name]

[Vars]

Count=[number]

Var[number]Name=[variable name]

Var[number]Val=[variable value]

AppKey definiert einen Schlüssel für das Spiel, eine Art Kennung. Sie darf nur aus max. 255 Buchstaben und Zahlen bestehen. Mit Packages kann eine alternative Paketdatei spezifiziert werden. Schließlich dürfen noch beliebige Verzeichnisvariablen deklariert werden, die u.a. in den Paketdateien referenziert werden können.

Beispieldatei:

[Engine1.0]

AppKey=Paga2

AppTitle=Paganitzu II

Packages=%app%\packages.inf

[Vars]

Count=2

Var1Name=%app%

Var1Val=C:\Games\Paga2

Var2Name=%shared%

Var2Val=C:\MQEngine\Shared

Die Zeile für Packages wäre in diesem Fall überflüssig, da sie die Standard-Vorgaben verwendet.

Beachten Sie, dass Sie für Variablen auch statt des hartcodierten Pfades auch die vordefinierten Variablen $app$ (für das Spielverzeichnis) und $shared$ (für das Verzeichnis mit gemeinsam genutzten Engine-Ressourcen) angeben können.


Pakete

Ein Spiel verwendet viele Arten von Dateien, z.B. Grafiken, Sounds und Skripte. Diese kann wieder unterteilen, z.B. in Icon-Grafiken, Textur-Grafiken, Charakter-Portraits usw. Um die verschiedenen Arten von Dateien besser verwalten zu können, werden diese in Pakete eingeteilt. Pakete enthalten also zusammengehörige Dateien. Praktisch jede Datei des Spiels ist in einem Paket enthalten.

Pakete können auch wiederum Pakete enthalten, um die Dateien nochmals abzugrenzen. Während jedoch die Pakete erster Ebene fest vorgegeben sind und nicht geändert werden können, so ist die Verwendung weiterer Ebenen von Paketen frei möglich. Hier ist dann auch nicht mehr unbedingt die Art der Dateien das entscheidende Kriterium zur Trennung, sondern z.B. der Speicherort.

So bietet die MQ!Engine bereits eine Reihe vordefinierter Klassen an, die als „Shared Data“ bezeichnet werden. Um diese Klassen aufzunehmen, müssen nicht die einzelnen Dateien kopiert werden, sondern es genügt, das ebenfalls vordefinierte Shared-Paket zu integrieren (es gibt für jeden Klassentyp ein eigenes Paket).

Die Pakete erster Ebene werden in einer Datei mit dem Namen packages.inf im Spiele-Verzeichnis festgelegt. Die Datei besitzt das Standard INI-Format und hat folgenden Aufbau:

[Paketname]
Count=Anzahl der Pakete zweiter Ebene

P1=Dateiname des ersten Pakets
P2=Dateiname des zweiten Pakets
...

Auch wenn keine zweite Paketebene verwendet wird, so befinden sich die Dateien selbst nicht im Paket erster Ebene, sondern in einem einzigen Paket zweiter Ebene.

Bei der Angabe der Dateinamen können (sollten) Pfadvariablen verwendet werden. Diese Variablen werden in der Engine-Informationsdatei definiert.

Alle Pakete zweiter und tieferer Ebenen werden in Dateien mit der Endung .pck festgelegt. Im Gegensatz zu Paketen erster Ebene können diese auch die eigentlichen Dateien enthalten.

Jede Datei in einem Paket besitzt eine (innerhalb des Pakets) eindeutige ID. Diese ID kann explizit angegeben werden, andernfalls wird der Dateiname verwendet. Ist der Dateiname numerisch, so ist die ID eine Zahl. Beispiel: Dateiname = „0015.bmp“ à ID = 15.

In den Paketdateien enthält jede Zeile ein „Kommando“. Es gibt folgende Arten von Kommandos:

  • package
    Dies ist die erste Zeile in jeder Paket-Datei, die sie als solche kennzeichnet.
  • Folder=Pfad
    Gibt den Ordner an, in dem sich alle nachfolgenden Dateien befinden. Diese Zeile muss auftauchen, bevor Kommandos mit Dateibezügen auftreten. Folder-Zeilen können auch mehrmals auftauchen.
  • *=Suchmuster
    Nimmt alle Dateien mit einem bestimmten Muster auf, z.B. *.inf. Es wird für jede Datei die Standard-ID verwendet. Um auch alle Unterverzeichnisse des zuletzt angegebenen Ordners zu durchsuchen, muss an das Suchmuster noch ein + angehängt werden.
  • ID=Dateiname
    Nimmt eine einzelne Datei auf und weist ihr die ID zu.
  • Include=Paketdatei
    Gibt eine untergeordnete Paketdatei an. Alle Dateien dieses Pakets werden ebenfalls aufgenommen.

Während der Entwicklungsphase sind die Dateien nur in logischen Paketen enthalten, d.h. sie liegen weiterhin unverändert und in loser Form auf dem Datenträger vor. Wenn das Spiel publiziert werden soll, ist diese Form jedoch ungeeignet. Mit Hilfe des Paket-Assistenten für MQ!Engine können die Dateien dann tatsächlich verpackt werden, wobei für jedes Paket erster Ebene genau eine Datei erstellt wird. Die Datei packages.inf ist in diesem Fall nicht mehr notwendig. [Anm.: Der Paket-Assistent liegt im Augenblick noch nicht vor.]

Arten von Paketen

Hier sollen in kurzer Form die Pakettypen erster Ebene aufgelistet und beschrieben werden. Hinter dem fett gedruckten Paketnamen ist das Standard-Verzeichnis angegeben. Das Verzeichnis ist zwar für das Spiel irrelevant, jedoch setzt der Karten-Editor (im Standalone-Modus) diese Standard-Benennung voraus.

  • BottomImages (\Images\BottomImages):
    Enthält Bitmaps (.bmp) der Größe 36x36 mit Grafiken für Böden und andere Untergründe. Der Dateiname stellt die Bildnummer dar. Jedes Bild kann für mehrere Bodenarten verwendet werden. Im 3D-Modus stellen diese Bilder die Bodentextur dar. Bei der Erstellung solcher Grafiken ist zu beachten, dass später eine Kachelung ohne allzu deutlich sichtbare Ränder möglich sein sollte.
  • Bottoms (\Data\Bottoms):
    Enthält Definitionsdateien (.inf) für die Bodenarten. Jeder Boden besitzt einen eindeutigen Namen. Mehrere Böden können z.B. die gleiche Bitmap-Grafik verwenden und sich aber in ihren Eigenschaften unterscheiden.
  • CClasses (\Data\CClasses):
    Enthält die Definitionen für Klasen von Kreaturen (nicht-humane Organismen) im Spiel. Dies sind sozusagen alle Arten von Lebewesen, die im Spiel vorkommen können, zusammen mit ihren gemeinsamen Eigenschaften. (Alle Eigenschaften könne per Skript aber auch nachträglich individuell verändert werden.)
    Der Dateiname kann für diese Dateien beliebig gewählt werden.
  • Ceilings (\Data\Ceilings):
    Enthält die Definitionen für Decken (analog zu den Boden-Definitionen in Bottoms).
  • Characters (\Data\Chars):
    Enthält die Definitionen für Charaktere, welche vom Spieler gesteuert werden können. Jeder Charakter besitzt eine eindeutige Nummer und ist einer HumanClass zugeordnet.
  • CharImgs (\Images\Chars):
    Enthält Großansichten der Charaktere als .bmp-Bitmap. Die numerische ID des Dateinamens muss der ID des Charakters entsprechen.
  • CreatureFaces (\Images\CreatureFaces):
    Enthält Grafikdateien für die Portraits von Kreaturen, z.B. für den Kampfmodus. Dies sind .bmp-Dateien, wobei der Dateiname die numerische ID darstellt. Die Abmessungen des Portraits sind 81x81.
  • CreatureIcons (\Images\CreatureIcons):
    Enthält Icons für Kreaturen. Die Dateien müssen im Windows-Iconformat (.ico) vorliegen und eine numerische ID besitzen, z.B. „0003.ico“. Icons sollten immer in den Abmessungen 32x32 und 16x16 vorliegen. Empfohlene Farbtiefe ist True Color. Die Icons werden u.a. im Haupt-Display verwendet, jedoch auch an vielen anderen Stellen im Spiel.
  • GameData (\Data\Game):
    Enthält verschiedene Arten von Dateien mit Daten zur Anpassung des Spiels. Die einzige Gemeinsamkeit dieser Dateien ist, dass sie alle in Textform sind. Beispiele: Credits, Farbpaletten,...
  • HClasses (\Data\HClasses):
    Analog zu den CClasses, nur für Menschen. Diese besitzen natürlich andere Eigenschaften und sind komplexer.
  • HumanFaces (\Images\HumanFaces):
    Enthält Grafikdateien für die Portraits von Menschen, z.B. für den Gesprächsmodusmodus. Das Format ist analog zu den CreatureFaces.
  • HumanIcons (\Images\HumanIcons):
    Enthält Icons für Menschen (analog zu CreatureIcons).
  • IClasses (\Data\IClasses):
    Analog zu den CClasses, nur für Gegenstände, die auch (normalerweise) ins Inventar aufgenommen werden können.
  • ItemIcons (\Images\ItemIcons):
    Enthält Icons für Gegenstände (analog zu CreatureIcons).
  • Imagemaps (\Images\Imagemaps):
    Kann zusätzliche Hotspot-Bilder enthalten, falls diese im Spiel verwendet werden.
  • Maps (\Maps):
    Enthält die Karten-Geometrie, die in .inf-Dateien gespeichert wird. Diese Dateien sollten mit einem speziellen Editor bearbeitet werden, z.B. mit dem integrierten Karten-Editor. Kartenname und Dateiname müssen sich entsprechen. Für jede Karte existiert ein Unterverzeichnis, welches wiederum Unterverzeichnisse für die auf der Karte vorkommenden Objekte enthält.
  • MiscImg (\Images\Misc):
    Enthält verschiedene Bitmaps, z.B. für Menüs im Spiel. Diese Dateien müssen i.d. Regel bestimmte Namen haben und dürfen nicht frei benannt werden.
  • Music (\Music):
    Enthält Musikstücke im .mid-Format, die hauptsächlich als Hintegrundmusik gedacht sind. Andere Musikdateien (z.B. im MP3-Format) werden nicht in Packages aufgelistet, sondern immer im Music-Unterverzeichnis abgelegt.
  • RegionPoints (\Data\RegionPoints):
    Definiert markante Orte innerhalb einer bestimmten Region in einer Welt, zu denen man ggf. auch reisen kann. Die Definition solcher Punkte ist nicht unbedingt notwendig, falls die Funktion „Reisen“ nicht benutzt wird.
  • Regions (\Data\ Regions):
    Definiert, ähnlich wie RegionPoints, Regionen. Dies ist die übergeordnete Stufe. Jede Region kann mehrere RegionPoints beinhalten.
  • SClasses (\Data\SClasses):
    Analog zu den CClasses, nur für Sprites. Sprites sind größere oder fest in der Umgebung verankerte Objekte wie Türen, Schalter, Tische, usw.
  • SpriteIcons (\Images\SpriteIcons):
    Enthält Icons für Sprites (analog zu CreatureIcons).
  • SpriteIconsBig (\Images\SpriteIcons\Big):
    Enthält ebenfalls Icons für Sprites, allerdings in der Dimension 36x36, womit sie kartenfeldfüllend sind. Der ID-Bereich wird mit den SpriteIcons geteilt, d.h. es darf keine Datei in SpriteIcons den gleichen Namen wie eine Datei in SpriteIconsBig haben.
  • Scripts (\Scripts):
    Enthält die Skriptdateien im Spiel. Skriptdateien haben die Endung .txt. Der Name der Datei bestimmt das Modul, in dem das Skript abgelegt wird. Für manche Ereignisse im Spiel wird nur in bestimmten Modulen gesucht, z.B. Ereignisse, die nur eine bestimmte Karte betreffen, werden nur im Modul mit dem Kartennamen gesucht. Außerdem gibt es ein Modul mit dem Namen Global für globalen Code, sowie Init für die Initialisierung von Klassen und Objekten.
  • Sounds (\Sound):
    Definiert die Sounds, die als Windows-Wave-Dateien (.wav), möglichst unkomprimiert, vorliegen.
  • Strings (\Data\Texts):
    Enthält .inf-Dateien, mit deren Hilfe man die Zeichenfolgen im Spiel anpassen kann, z.B. zur Lokalisierung.
  • VoiceSets (\Data\Voice):
    Enthält inf-Dateien mit numerischer ID, welche jeweils ein Voice-Set enthalten. Ein Voice-Set definiert alle Sounds, die zu einer bestimmten Person oder Kreatur gehören.
  • Worlds (\Data\Worlds):
    Definiert Welten. Dies ist die oberste Ebene von geographischer Einteilung. Jede Welt besteht aus einer oder mehreren Regionen. Viele Spiele enthalten nur eine Welt.


Spieldesign

Überlegungen zum Spieldesign

Die Spiele-Entwicklung mit der MQ!Engine umfasst immer verschiedene Schritte, die in weitgehend beliebiger Reihenfolge vorgenommen werden können. Trotzdem kann es sinnvoll sein, sich an einer bestimmten Vorgehensweise zu orientieren. Hier eine Empfehlung:

  1. Allgemeine Überlegungen:
    Genre, grobes Szenario des Spiels, Hauptfigur(en),...
  2. Projektumgebung einrichten:
    Ein neues Projektverzeichnis mit den oben angegeben Unterverzeichnissen anlegen, die Engine-Informationsdatei und Datendateien erzeugen, und die ausführbare Datei der MQ!Engine ins Stammverzeichnis kopieren.
  3. Böden und Decken erstellen:
    Die Grafiken und .inf-Dateien für Böden und Decken neu erstellen oder aus unseren Vorlagen übernehmen.
  4. Karten kreieren:
    Die wichtigsten Karten mit dem Map Editor erstellen, nur mit Sektoren, Bodengrafiken und Wänden, evtl. auch schon Sprites.
  5. Menschen, Kreaturen, Items:
    Klassen mit Eigenschaften (Werte) festlegen und die Karten bevölkern.
  6. Kartenlogik:
    Skripts für den Spielablauf schreiben, z.B. Cutscenes, Gespräche, Story-Text, usw.
  7. Sound, Musik, Videos:
    Alle weiteren Medien einbinden.
  8. Testläufe:
    Beim Beta-Test das Spiel ungefähr so durchspielen, wie es vom Spieler erwartet wird, aber auch andere Möglichkeiten ausprobieren.


Arbeiten mit Klassen

Was sind Klassen?

Bei der MQ!Engine gibt es vier Arten von Objekten, die in der Welt platziert werden können: Humans (Menschen), Creatures (Kreaturen), Items (Gegenstände) und Sprites (feste Objekte). Viele Arten von Objekten kommen mehrfach vor, z.B. Waffen, Heiltränke oder Kreaturen. Damit nicht für jeden Heiltrank das komplette Spektrum von Eigenschaften definiert werden muss, definiert man eine Klasse mit dem Namen „Heiltrank“ und gibt für das eigentliche Objekt nur an, dass es vom Typ Heiltrank sein soll. Wenn das Objekt intern erstellt wird, werden alle Eigenschaften von der Klasse übernommen.

Klassen dienen also als eine Art Vorlage für die eigentlichen Objekte (auch als Instanzen der Klasse bezeichnet). Es gibt für jeden Objekttyp auch einen Klassentyp, also HumanClasses, CreatureClasses, ItemClasses und SpriteClasses.

Eigenschaften können nicht beliebig für die Klasse oder das Objekt gesetzt werden. Die meisten Eigenschaften stehen nur entweder der Klasse oder dem Objekt zur Verfügung. Bei Menschen wird beispielsweise der Name der Person beim Objekt festgelegt, z.B.

Humans("willi").Title = "Willi Winzig"

aber bei Kreaturen für die Klasse:

CreatureClasses("giantrat").Title = "Riesenratte"

Der Grund ist in diesem Fall offensichtlich, dass Menschen fast immer verschiedene Namen haben, die Bezeichnung von Kreaturen eines Typs aber selten abweicht.

Es ist aber trotzdem auch bei Kreaturen möglich, einzelnen Klasseninstanzen andere Namen zu geben als den übrigen. Dies funktioniert, weil die Klasseneigenschaften beim Erstellen des Objekts kopiert werden. Beispiel:

Creatures("sample_rat").Class.Title = "hässliche Riesenratte"

Dies bedeutet auch, dass die Änderung einer Klasse sich nicht mehr auf bereits erstellte Objekte auswirkt.

Definition von Klassen

Für jeden Klassentyp gibt es ein eigenes Paket. Darin enthalten ist eine .inf-Datei für jede Klasse. Jede Zeile der .inf-Datei legt eine Eigenschaft fest. Notwendig ist auf jeden Fall die Zeile „Key = [Klassenname]“. Eine Übersicht der wichtigsten Eigenschaften finden Sie in der MQOM-Referenz. Sie können die Klassen auch mit Hilfe des Karten-Editors modifizieren. Dieser zeigt für einige der Eigenschaften auch eine kurze Beschreibung an.

Klassen können außerdem eine Reihe von typspezifischen Eigenschaften besitzen. Für jeden Objekttyp sind hier andere Daten relevant. Diese werden normalerweise festgelegt durch einen Aufruf der Form

Creatures("sample_rat").Class.DP.SetVal "setting", "value"

in einer der Initialisierungsprozeduren. Hier kann eine besondere Zeile in der Klassendefinitionsdatei eine effizientere Variante darstellen:

InitDP:setting=value

Dabei steht DP für DataPermanent (Objektdaten, die sich i.A. nicht ändern). Analog gilt dies für DataDynamic (DD, veränderliche Objektdaten).

Festlegen von Klassen für Objekte

Dieser Vorgang ist ganz einfach: Wenn Sie mit dem Karten-Editor ein Objekt erstellen wollen, wählen Sie im Klassen-Browser einfach die Klasse aus und ziehen Sie das Objekt auf das Spielfeld. Damit ist die Klasse festgelegt.

In der Objekt-Definitionsdatei, die zusammen mit der Karte gespeichert wird, gibt eine Zeile der Form „Class = [class key]“ an, zu welcher Klasse das Objekt gehört.


Erstellen von Karten

Grundlegendes zu Karten

Karten bilden die Grundlage für die 2D- und 3D-Weltenansichten in jedem MQ!Engine-Spiel. Sie enthalten die Spielobjekte (Personen, Gegenstände,...) und insbesondere auch den Spieler. Im Spiel kann nur jeweils eine Karte die aktuelle Karte sein. Nur auf dieser Karte können Bewegungen erfolgen.

Jede Karte besteht aus X*Y*L Kartenfeldern (map squares), wobei X das Ost-West-Ausmaß, Y das Nord-Süd-Ausmaß und L die Anzahl der „Höhenschichten“ der Karte angibt. L sollte möglichst gering gehalten werden (viele Karten kommen mit L=1 aus). L ist nützlich, wenn z.B. ein Hochhaus mit mehreren gleich dimensionierten Stockwerken modelliert werden soll. Die Standard-Einstellungen legen für das Kartenfeld eine feste logische und grafische Länge, Breite und Höhe fest (z.B. 2m*2m*2,5m bzw. 36*36*1 Pixel). Diese Einstellungen können vom Spiel überschrieben werden, aber die Engine wurde für die Standardwerte optimiert.

Ein jedes Kartenfeld kann in jeder Himmelsrichtung eine (innere) Wand von unterschiedlicher Dicke und Farbe besitzen. Lebewesen können Wände (im Normalfall) nicht überwinden. Wände sind die einfachste Methode, die Karte physikalisch zu gestalten. Wände eignen sich auch um Türen zu simulieren (die Wand wird nur im geschlossenen Zustand angezeigt und unterscheidet sich auch in ihrer Dicke von anderen Wänden). Die Alternative zu Wänden sind nicht betretbare Kartenfelder. Diese Methode kann eine Karte grafisch ansprechender aussehen lassen, jedoch ist sie auch zeitaufwendiger und macht die Karte „enger“.

Jedes Kartenfeld kann weiterhin beliebig viele Spielobjekte enthalten. Diese Objekte sind dann genau diesem Kartenfeld zugeordnet. Objekte, die sich im Inventar einer Person auf einem Kartenfeld befinden, sind diesem Kartenfeld nicht zugeordnet (befinden sich also programmiertechnisch gesehen nicht darauf).

Kartenfelder werden zu Sektoren gruppiert. Sektoren teilen sich viele Eigenschaften, z.B. den Untergrund, Wassertiefe, Sichtbarkeit usw. Es ist eine sinnvolle Praxis, auf einer Karte viele Sektoren mit aussagekräftigen Namen zu verwenden.

Das Spiel kann durch eine (fast) beliebige Kombination von Karten aufgebaut sein. Es muss mindestens eine Startkarte geben. Alle weiteren Kartenwechsel können dann von Skripten aus gesteuert werden. Dies erlaubt z.B. Karten logisch nebeneinander oder übereinander zu positionieren (durch „Teleport-Felder“). Bei jedem Kartenwechsel wird der Zustand einer Karte vollständig gespeichert und beim erneuten Betreten wiederhergestellt.

Das Erstellen von Karten erfolgt in 4 Schritten:

  1. Anlegen einer neuen Karte (Verzeichnisstruktur, Informationsdatei, Geometriedatei)
  2. Festlegen der Kartendaten
  3. Gestalten der Kartengeometrie
  4. Bevölkern der Karte

Für alle Schritte wird i.A. der MQ!Engine Karten-Editor verwendet, auch wenn man diese Schritte theoretisch auch manuell durchführen könnte.

Anlegen einer neuen Karte

Starten Sie den Karten-Editor, und wählen Sie aus dem Menü Karte den Befehl Neu.... Die Karte wird im Maps-Unterverzeichnis des Spiels angelegt (dieses Verzeichnis wird auch im folgenden Dialogfeld angezeigt). Geben Sie nun den Dateinamen der Karte an, z.B. „MyFirstMap“. Dies ist auch der Schlüssel der Karte.

Danach müssen Sie die Kartendimensionen angeben. Wichtig: Die Größe der Karte kann später nicht mehr mit dem Karten-Editor geändert werden! Eine manuelle Vergrößerung ist ebenfalls nur schwer möglich!

Sobald die Karte erstellt wurde haben Sie im Kartenverzeichnis eine neue Datei [Kartenname].inf, welche einige Daten enthält, und einen neuen Ordner mit dem Namen der Karte. Dieser Ordner enthält die Datei [Kartenname].dat mit den Geometriedaten, sowie weitere Unterordner für alle vier Objekttypen, die auf der Karte abgelegt werden können. Die Geometriedaten sollten nur mit dem Karten-Editor bearbeitet werden.

Festlegen der Kartendaten

In der .inf-Datei finden Sie alle wichtigen Daten zur Karte, z.B. der Titel (wie er auch im Journal erscheint), Hintergrundatmosphäre, etc. Sie können diese Informationen von Hand anpassen. Eine entsprechende Funktion im Karten-Editor ist geplant, aber noch nicht umgesetzt.

Außerdem können Sie die Karte hier einer Region zuordnen (genauer: einem Regionspunkt).

Gestalten der Kartengeometrie

Die Kartengeometrie, also Wände und Sektoren, kann komplett mit dem Karten-Editor bearbeitet werden.

Bevölkern der Karte

Dieser Schritt erfolgt ebenfalls mit Hilfe des Editors, obwohl es möglich (und in einzelnen Fällen auch vorteilhafter) ist, manuell in den Karten-Ordnern die .inf-Dateien zu erstellen oder zu verändern.

Im Editor können Sie Objekte hinzufügen, indem Sie diese aus dem Klassen-Browser auf das gewünschte Kartenfeld ziehen. Sie werden dann nach einer Kennung (ID) für das Objekt gefragt. Diese muss eindeutig sein. Sie brauchen nur dann einen aussagekräftigen Namen zu wählen, falls Sie das Objekt später vom Skript aus steuern wollen. Im Menü Ansicht des Karten-Editors können Sie die Sicht so umstellen, dass alle Objekte eines bestimmten Typs angezeigt werden.

Um die Eigenschaften eines Objekts zu ändern, wählen Sie das entsprechende Kartenfeld aus und klicken auf den Buchstaben des Objekttyps im Werkzeugfenster, also „H“, „C“, „I“ oder „S“, und wählen Sie aus dem daraufhin erscheinenden Menü das Objekt aus.


Graphics Guide

Verwenden von Grafiken

In diesem Abschnitt wird erklärt, wie man vorgeht, um eigene Bilder im Spiel einzusetzen. Bilder kommen an mehreren Stellen vor:

  • Als Texturen (für Böden und Decken)
  • Als kleine Symbole (Icons) für Objekte, wie sie auf der Karte und im Inventar erscheinen
  • Als Portraits (bei Menschen und Kreaturen), für den Gesprächs- und Kampfmodus)
  • Als Charakter-Darstellungen im Großformat
  • Als Rüstungssprites für die Charakter-Vorschau
  • Für sonstige Grafiken, z.B. Hintergründe, anklickbare Bildkarten, Regionskarten, Befehlsschaltflächen usw.

Die Grafiken können mit unterschiedlichen Programmen erstellt und bearbeitet werden (wir benutzen beispielsweise hauptsächlich Corel Photo-Paint für Bilder und Microangelo für Icons), wobei je nach Verwendungszweck im Spiel ggf. bestimmte Dateiformate und Benennungsregeln beachtet werden müssen. Im folgenden werden diese nach Paketen geordnet in einer Tabelle aufgelistet.

Package | Dateiformat | Dimensionen | Dateinamen
BottomImages, [CeilingImages] | BMP | 36x36 | [#].bmp
HumanFaces, CreatureFaces | BMP | 81x81 | [#].bmp
HumanIcons, CreatureIcons, ItemIcons, SpriteIcons | ICO | 32x32 und 16x16 empfohlen | [#].ico
SpriteIconsBig | ICO | 36x36 | [#].ico
MapBGs | BMP | 640x480 | [#].bmp
MiscImg | Verschiedene | Verschiedene | [Key].*
Imagemaps | BMP, JPG | Verschiedene | [Key].*
CharImgs | BMP | 640x480 max. | [#].bmp

Dateiformate:

  • BMP: Windows-Bitmap, beliebige Farbtiefe (24-bit empfohlen), unkomprimiert
  • ICO: Windows-Icon, kann mehrere Farbformate enthalten
  • JPG: JPEG-komprimierte Bitmap, Standard empfohlen (nicht-progressiv, nicht-optimiert)
  • GIF: CompuServe-Bitmap mit optionaler Farbmaske, 8-bit

In der Package "MiscImages" sind einige untergeordnete Kategorien von Grafiken, die nur durch ein Präfix im Namen zu erkennen sind. Das Präfix ist nicht in jedem Fall obligatorisch, sollte aber eingehalten werden. Dies sind:

Bezeichnung | Beschreibung | Größe | Dateinamen
Welten-Banners | werden z.B. im Modus "Reisen" angezeigt | 256x41 | WB_[Weltenname].bmp
Symbolleisten-Icons | werden für verschiedene Symbolleisten benutzt | 32x32 | Cmd_[Key].ico
Status-Icons | kommen in Statusleisten, Listen, usw. vor | 16x16 oder 32x32 | Img_[Key].ico
Aktions-Icons | werden im Kontextmenü für Aktionen angezeigt | 16x16 | Op_[Key].ico

Einige der Packages verlangen numerische Dateibenennung. Die Grafiken können dann in Datendateien durch Angabe der entsprechenden Nummer referenziert werden. Die hat den Vorteil, dass jede Datei von mehreren Klassen genutzt werden kann. Beispielsweise können zwei Gegenstände das selbe Icon haben, indem die Icon-Eigenschaft der IClass auf die gleiche Nummer gesetzt wird, welche die Icon-Datei angibt.

Bei Grafiken mit textueller Namensgebung (also beliebige Benennung) sollte trotzdem die Schlüsselsyntax eingehalten werden (d.h. max. 255 Zeichen, keine Leerzeichen, nur Buchstaben und Zahlen, keine Zahl am Anfang usw.). Diese Bilder haben als Name (im Spiel) dann den Dateinamen ohne die Endung. Pfadangaben werden im Spiel grundsätzlich ignoriert.

Anleitung: Erstellen eines Icons

Hier soll nun gezeigt werden, wie Sie mit Hilfe von Corel Photo-Paint und Microangelo Icons für MQ!Engine erstellen können. Die Prozedur lässt sich auch auf viele andere Grafikprogramme übertragen.

  1. Öffnen Sie das Bild, das als Grundlage benutzt werden soll, in Photo-Paint.
  2. Verwenden Sie die Maskenhilfsmittel, um den gewünschten Teilbereich zu markieren. Damit können z.B. störende Hintergründe weggeschnitten werden. Mit den Maskenmodi „Hinzufügen“ und „Subtrahieren“ kann die bestehende Maske modifiziert werden.
  3. Kopieren Sie den maskierten Bereich und fügen Sie ihn als neues Bild ein.
  4. Legen Sie die Papierfarbe (in manchen Programmen auch Hintergrundfarbe) auf eine Farbe fest, die sich gut in die im Spiel verwendeten Bodentexturen einpasst. Kontrastreiche Hintergründe (weiß, schwarz) und helle Farben sind zu vermeiden. 60% Grau oder Dunkelbraun eignen sich oft gut.
  5. Resampeln Sie das Bild („Bild neu erstellen“), so dass die größere Dimension (Höhe oder Breite) auf 32 Pixel eingestellt wird und die andere Dimension im korrekten Seitenverhältnis (z.B. von 459x288 auf 32x20). Die Option Anti-Alias bringt in den meisten Fällen bessere Ergebnisse.
  6. Wählen Sie den Befehl „Papierformat ändern“ und legen Sie die Größe auf 32x32 fest, wobei das alte Bild zentriert werden soll.
  7. Führen Sie den Befehl „Alle Objekte mit dem Hintergrund kombinieren“ aus.
  8. Kopieren Sie das Bild in die Zwischenablage und wechseln Sie zu Microangelo.
  9. Legen Sie ein neues Icon mit dem Bildformat 32x32 True Color an und fügen Sie das Bild ein.
  10. Nun müssen Sie noch die Randpixel entfernen, indem Sie sie in der Transparenzfarbe einfärben. Das Füllwerkzeug kann hier am Anfang hilfreich sein.
    In vielen Fällen ist es nicht eindeutig, welche Pixel gefärbt werden sollen und welche nicht. Färben Sie zu wenige, so erscheint um das Objekt ein feiner Rahmen in einer möglicherweise unpassenden Farbe. Entfernen Sie zu viele, dann wirkt das Objekt dünn. Hier entscheiden Sie am besten nach dem subjektiv besten Eindruck.
  11. Wiederholen Sie den Vorgang ab Schritt 3 für das Format 16x16 statt 32x32.
  12. Speichern Sie das Icon.


Sounds Guide

Arten von Audio

Es gibt drei Arten von Audio in der MQ!Engine:

  • Musik
  • Sound
  • Atmosphäre

Es kann nur jeweils eine Musik und eine Atmosphäre gleichzeitig gespielt werden (für beides gibt es genau einen Abspielpuffer). Sounds können (fast) beliebig viele zur gleichen Zeit abgespielt werden.

Die Treiber für die jeweiligen Audioarten können im MQ!Engine-Setup festgelegt werden. Aus technischer Sicht sind Musik und Atmosphäre praktisch gleich, außer dass sie in getrennten Puffern abgespielt werden. Deshalb wird im Folgenden auch nur die Musik exemplarisch behandelt, wenn es um technische Aspekte geht.

Audioformate

Musik kann in allen gängigen Formaten vorliegen: MIDI, MP3, Wave, MOD u.ä. Es werden alle Formate des Windows Media Player unterstützt, da dieser zur Wiedergabe benutzt werden kann.

Sound muss als Windows PCM Wave vorliegen. Wenn die Datei komprimiert ist, muss der entsprechende ACM-Dekompressionscodec installiert sein. Die Bitrate, Kanalzahl und Frequenz sind beliebig, sollten aber innerhalb des Spiels einheitlich sein. Empfohlen sind unkomprimierte Waves mit 16-bit, 44 KHz und Stereo.

Auftreten und Definition von Musik

Menü-Musiken

Musik kommt in bestimmten Menüs oder Programmbereichen vor, und wird dort unabhängig vom laufenden Spiel wiedergegeben. Es gibt folgende Menüs mit eigener Musik:

  • Hauptmenü (wird beim Starten angezeigt)
  • Ingame-Menü (wird beim Aufruf des „System“-Kommandos angezeigt)
  • Credits-Bildschirm (kann vom Hauptmenü aus angezeigt werden)

Im Allgemeinen beginnt die Musik, wenn das Menü angezeigt wird, und sie endet, wenn das Menü verschwindet.

Um die Musik festzulegen, muss ein Eintrag in einer Datei des GameData-Pakets vorgenommen werden. Die Datei heißt General.inf. Der Eintrag für das Hauptmenü heißt MainMenuMusic und befindet sich in der Sektion MainMenu. Der Eintrag für das Ingame-Menü heißt IngameMenuMusic und befindet sich in der Sektion IngameMenu. Der Eintrag wird auf die ID des zu spielenden Menüstücks gesetzt. Dieses kann auch wiederholt werden, indem der Eintrag MainMenuLoopMusic bzw. IngameMenuLoopMusic auf 1 gesetzt wird. (Es handelt sich dann um einen „Hard Loop“, der vom Audio-Treiber unterstützt werden muss.)

Ingame-Musiken

Während dem Spiel wird Musik abgespielt, wenn diese für die aktuelle Umgebung festgelegt wurde. Die Musik ist in einem Objekt vom Typ AudioEnvironment gespeichert. Dieses Objekt kann zusätzliche Informationen enthalten, z.B. Dauer der Pause nach Abspielen eines Musikstückes oder Reihenfolge der Musiken (falls mehrere definiert wurden). AudioEnvironments werden in der Package „AudioEnv“ definiert. Der Einfachheit halber wird hier davon ausgegangen, dass jede AudioEnvironment genau ein Musikstück angibt.

Eine Musik kann für eine Karte festgelegt werden. Dann wird standardmäßig für jeden Bereich auf der Karte die gleiche Musik abgespielt. Dies kann geändert werden, indem man auch für einen Sektor die Musik festlegt. Dann wird in diesem Sektor nicht die Kartenmusik gespielt, sondern die neu festgelegte. (Um nur in einem bestimmten Sektor keine Musik zu spielen, wird auf das AudioEnvironment „Null“ verwiesen.)

In AudioEnvironments werden Musikstücke durch ihre ID referenziert. Eine Datei mit der entsprechenden ID oder dem entsprechenden Namen muss in der Package Music vorhanden sein.

Musik für sonstige Spielmodi

Hat der Spieler einen anderen Spielmodus aktiviert (z.B. Pause, Kampfmodus, Shop-Modus, Journal etc.), so wird keine neue Musik gespielt, es wird aber nicht immer die Musik angehalten.

Soll in einem solchen Spielmodus eine besondere Musik gespielt werden, so genügt es, in einer geeigneten Skriptprozedur den MQ!Engine-Befehl „Music“ aufzurufen, um die gewünschte Musik abzuspielen. Zu den meisten Spielmodi gibt es eine Skriptprozedur, die bei Aktivierung des Modus ausgeführt wird (siehe Skript-Referenz).

Auftreten von Atmosphären-Audio

Atmosphären-Geräusche werden wie Musik pro Karte und ggf. zusätzliche pro Sektor festgelegt, und zwar in dem AudioEnvironment, dass der Karte/dem Sektor zugeordnet ist. Diese Sounds werden ständig wiederholt (geloopt). Empfohlenes Format ist MP3 oder Wave. Atmosphären-Sounds haben eine von der Musik getrennte Lautstärkeregelung.

Es gibt zwei Typen von Atmosphären-Geräuschen: Der Ambience-Sound (nur einer pro AudioEnvironment) wird ständig wiederholt und dauernd gespielt. Die Env-Sounds dagegen werden per Zufall aus einer Liste ausgewählt und in bestimmten Abständen wiedergegeben.

Im Gegensatz zu Musik befindet sich Atmosphären-Audio in der Package Sounds.

Auftreten von Sound

Sound wird in folgenden Kontexten abgespielt:

  • Feste Sounds
  • Im Zusammenhang mit vordefinierten Objektaktionen (Sound Sets)
  • Im Zusammenhang mit vordefinierten Lebewesen (Voice Sets)
  • Als akustische Ausgabe von Gesprächen mit Personen
  • Bewegungsgeräusche
  • Bei benutzerdefinierten Ereignissen

Sound für feste Ereignisse

In einigen Menüs oder Interfaces werden für Benutzeraktionen Sounds abgespielt, z.B. beim Anklicken einer Schaltfläche, beim Anzeigen einer Warnung u.ä. Dabei werden einfach Sounds mit einer bestimmten ID abgespielt. Um den Sound zu ändern, muss unter der betreffenden ID nur eine andere Datei abgelegt werden. Eine Liste mit Soundereignissen finden Sie im Referenzteil.

Sound für Objektaktionen

Für jede Klasse von Gegenständen und Objekten (ItemClass, SpriteClass) werden Sounds für eine Reihe von Ereignissen festgelegt, z.B. Aufheben, Fallenlassen, Anziehen, etc. Anstatt diese direkt in der Klasse zu speichern werden sie in einem Sound Set gespeichert, dessen ID dann wiederum der Klasse zugeordnet wird. Damit können ähnlich klingende Items ein gemeinsames Sound Set benutzen. MQ!Engine spielt diese Sound automatisch bei Bedarf ab. In manchen Fällen werden Standard-Sounds abgespielt, falls kein Sound Set oder Sound Set-Eintrag definiert wurde.

Sound für Lebewesen

Ganz analog zu Sound Sets für Items und Sprites gibt es Voice Sets für Humans und Creatures. Diese werden z.B. beim Zufügen von Schaden oder beim Tod des Lebewesens abgespielt.

Sound für Gesprächstexte

Diese Funktion ist noch in der Entwicklung und wurde noch nicht umgesetzt.

Sound für Bewegungen

Bei jedem Schritt, den der Spieler oder eine andere Person oder Kreatur macht, wird ein Geräusch abgespielt, falls das Ereignis in hörbarer Nähe des Spielers stattfindet. Welcher Sound dabei abgespielt wird, hängt von mehreren Faktoren ab. Dabei wird der Sound, der das Ereignis akustisch am besten treffen würde zuerst gesucht, dann der zweitbeste usw. Falls keiner der Sounds vorhanden ist wird ein Standard-Sound abgespielt.

Es gilt die folgende Reihenfolge:

Bei Menschen: Step_bottomsound_shoeid / Step_bottomsound_Bare à Step_shoeid à Step_bottomsound à Step

Bei Kreaturen: Step_creaturewalksound à Step_creaturematerialsound à Step_bottomsound_Bare à keiner

Sonstiger Sound

An jeder beliebigen Stelle im Skript kann ein Sound durch den Aufruf von „Sound“ abgespielt werden. Die Syntax finden Sie in der Skriptreferenz.

AudioEnvironments

AudioEnvironments sind .inf-Dateien, die in einer eigenen Package gespeichert sind. Jedes AudioEnvironment besitzt einen eindeutigen Schlüssel. Dieser kann dann z.B. einer Karte oder einem Sektor zugeordnet werden.

AudioEnvironments enthalten außer dem Schlüssel die folgenden Daten:

  • MusicList: Eine Liste von durch Semikolon getrennten IDs von Musikstücken für die Umgebung
  • MusicListOnce: Gibt an, ob das Stück/die Stücke nur einmal gespielt werden soll(en).
  • MusicPause: Pause zwischen zwei Musikstücken in Sekunden
  • Ambience: Name einer Sounddatei, die dauerhaft gespielt und wiederholt werden soll
  • EnvSndList: Liste von semikolon-getrennten Sounds, die (per Zufall) von Zeit zu Zeit einmal gespielt werden sollen
  • EnvSndPauseMin / EnvSndPauseMax: Minimale bzw. maximale Pause zwischen zwei Env-Sounds. Die genaue Pause wird per Zufall bestimmt.

Sound Sets

In Vorbereitung.


Grundlagen des Scripting

Scripting ist die wichtigste Technik beim Entwickeln von Spielen für die MQ!Engine. Es ermöglicht die vollständig interaktive Gestaltung des Spielablaufs mit Hilfe einer leistungsfähigen Sprache und eines umfangreichen Objektmodells (MQOM).

Skripte sind Textdateien, die normalerweise mit der Erweiterung .scp gekennzeichnet werden. Sie enthalten reinen Skriptcode in der Programmiersprache VBScript. VBScript ist von der Syntax stark an Microsoft Visual Basic angelehnt und auch von Anfängern sehr leicht zu schreiben. Bevor Sie anfangen können, MQ!Engine-Skripte zu schreiben, sollten Sie sich etwas mit VBScript vertraut machen. Die Dokumentation finden Sie auf http://msdn.microsoft.com/scripting/ .

Prozeduren

Skripte sind in Prozeduren unterteilt. Jede Prozedur besitzt einen Namen und eine Argumentliste. (Rückgabewerte werden von MQ!Engine nicht verwendet.) Eine übliche Prozedur könnte folgendermaßen deklariert sein:

Sub SomeProc(ParamOne, ParamTwo)

   ...

End Sub

Diese Prozedur mit dem Namen SomeProc nimmt zwei Argumente: ParamOne und ParamTwo. Argumente und andere Variablen müssen in VBScript nicht explizit typisiert werden. Gerade für MQ!Engine-Skripte ist dies fast nie notwendig. Der Autor des Skripts sollte sich aber bewusst sein, welche Art von Wert ein Argument annimmt.

Im Skript können beliebige Prozeduren deklariert werden, aber diese werden nur ausgeführt, wenn sie explizit von einer anderen Prozedur im Skript aufgerufen werden. Anders sind die vordefinierten Prozeduren. Sie haben feste Namen und werden von der Engine zu bestimmten Anlässen (Ereignisse, Aktionen,...) aufgerufen, mit einer Argumentliste. Welche Prozeduren die MQ!Engine anbietet erfahren Sie in der Skript-Referenz.

Zu beachten ist, dass das erste Argument von vordefinierten Prozeduren immer zusätzliche Daten zwischen Engine und Skript austauscht. Das Argument wird auch als PLD (Prozedurlokale Daten) bezeichnet und so deklariert. Weitere Informationen dazu finden Sie weiter unten.

Module

Jede Skriptdatei stellt ein eigenes Modul dar. Skripte können sich nur innerhalb eines Moduls direkt aufrufen. Außerdem sucht die Engine nach vordefinierten Skripten in manchen Fällen nur in bestimmten Modulen. Jede Karte besitzt ein eigenes Modul, das den Namen der Karte trägt. Befindet sich der Spieler auf dieser Karte, so werden die Prozeduren in diesem Modul auch zuerst durchsucht und ausgeführt. Nicht nur aus diesem Grund ist es sinnvoll, Ereignisprozeduren, die ich auf eine einzelne Karte beschränken, im Modul zu dieser Karte zu speichern. Wollen Sie also Skripte für eine Karte mit dem Schlüssel „mymap“ schreiben, so legen Sie einfach eine Textdatei mit dem Namen „mymap.scp“ im Skriptverzeichnis an.

Es gibt außerdem ein Modul „Global“, welches Prozeduren enthält, die von allen Modulen aus aufgerufen werden können, sowie ein Modul „Init“ für Initialisierungsprozeduren.

Öffentliche Funktionen der MQ!Engine

Die MQ!Engine stellt einige Funktionen und Variablen zur Verfügung, die in Skriptprozeduren aufgerufen werden können. Dazu gehören z.B. Verweise auf die aktuelle Karte (CurrentMap) und den aktuellen Spieler (ActiveHuman), oder die Liste der Klassen (HClasses, CClasses, IClasses, SClasses). Funktionen sind u.a. „Sound“ zum Abspielen von Sounds, „ChangeMap“ zum Wechseln der Karte u.v.m.

Details finden Sie in der Referenz zu den MQ-Skriptfunktionen.

Variablen

Zusätzlichen zu den durch die Sprache zur Verfügung gestellten Variablen, die prozedurlokal sind, gibt es auch noch persistente Variablen der Engine. Diese können von jeder Prozedur aus gesetzt und abgefragt werden, und sie werden auch zusammen mit Spielständen gesichert.

Die Funktion zum Festlegen von Werten lautet SetVar, die zum Abrufen selbiger GetVar. Es ist zu beachten, dass alle Werte als Zeichenfolgen (Strings) gespeichert werden. Mithilfe der Funktion Val kann dieser aber ggf. in einer Zahl umgewandelt werden.

Ein Beispiel:

SetVar "MyVar", "Hallo Welt!"

Message GetVar("MyVar")

(Die Funktion Message gibt einen Text im Meldungsfenster aus.)

Prozedurlokale Daten

Damit die MQ!Engine mit dem Skript weitere Daten austauschen kann, wurde ein zusätzliches Argument eingefügt, das ein Objekt vom Typ ProcLocal enthält. Dieses Argument steht in allen Prozedurdeklarationen immer an erster Stelle.

Funktionen des Objekts sind z.B. SetVar und GetVar. Sie verhalten sich wie die gleichnamigen globalen Funktionen, aber die Variablen sind nur dem Skript und dem Aufrufer bekannt. Dieser Mechanismus wird manchmal benutzt, um Rückgabewerte zu simulieren. Die Variablen werden als prozedurlokale Variablen bezeichnet.

Eine weitere wichtige Aufgabe der PLDs ist die Implementierung von Skriptereignissen. Einige vordefinierte Prozeduren werden bei einem bestimmten Ereignis gesucht und – falls vorhanden – ausgeführt. Ist die Prozedur aber nicht vorhanden, so wird von der Engine eine Standardaktion gestartet.

Versucht der Spieler beispielsweise, eine Kreatur anzugreifen, so wird das Skript „Creature_objectid_Attack“ gesucht. Ist das Skript nicht vorhanden (der Normalfall), so wird der Angriff durchgeführt. Wurde es aber implementiert, so kann es sowohl erwünscht sein, den Angriff danach trotzdem auszuführen, oder aber nicht. Das Standardverhalten ist, dass bei Vorhandensein des Skripts die Standardaktion entfällt. Das Skript kann dieses Verhalten ändern, indem es die Eigenschaft EventHandeled des PLD-Objekts setzt, z.B.:

Sub Human_towerguard_Attack(PLD, Self, Relationship)

   If Relationship = 7 Then

      Say , “Das ist mein Freund, den greife ich nicht an!”

      'Standardaktion (Angriff) wird nicht ausgeführt

   Else

      PLD.EventHandeled = False

      'Standardaktion (Angriff) ausführen

   End If

End Sub


Start und Ziel des Spiels

Start des Spiels

Um ein sinnvolles Spiel schreiben zu können, muss man wissen, wie der Start des Spiels von Seiten der MQ!Engine abläuft bzw. wo das Spiel Einfluss auf den Startvorgang hat. Dies beginnt bei der Auswahl der Startkarte: Der Spieler beginnt mit dem ersten vor dem Spiel gewählten Charakter (falls nur ein Charakter existiert entfällt die Auswahl) auf der Karte und Position, die dem Human-Objekt des Charakters zugeordnet ist. Dieses wiederum wird in der Charakter .inf-Datei mit „HID=humanid“ festgelegt. Beim Start wird dann die Karte gesucht, auf der der Spieler beginnt.

Beim Anlegen der Objekte auf den Karten werden für jedes Objekt die Init-Prozeduren aufgerufen. Mehr dazu finden Sie in der Skriptreferenz.

Wenn der Ladevorgang schließlich abgeschlossen ist, wird für die Startkarte das Enter-Ereignis ausgelöst. Hier sollten die letzten Vorbereitungen und eventuelle Startnachrichten eingefügt werden.

Ziel des Spiels

Es gibt in der MQ!Engine kein fest eingebautes Spielziel. Die einzige Möglichkeit, ein Spiel zu beenden, sind die Funktionen GameWin und GameLoose. Dadurch wird dem Spieler eine entsprechende Meldung angezeigt und er kehrt zum Hauptmenü zurück.

Bei manchen Spielen kann es auch sinnvoll sein, kein Ziel zu implementieren.


Kampf-Grundlagen

Der Kampf spielt eine wichtige Rolle bei MQ!Engine-Spielen. Wann immer der Spieler z.B. auf eine Kreatur trifft wird standardmäßig ein Kampf initiiert. Ein Kampf kann verschiedene Ausgänge haben: Sieg einer Partei und Tod aller Gegner, Tod des Spielers, Flucht eines Beteiligten,...

An einem Kampf sind mindestens zwei Parteien beteiligt. Es können auch drei unabhängige Parteien gegeneinander kämpfen.

Relevante Parameter für Kämpfer

(In Arbeit) à ST, Power, Agility, BPs, BPProtection, Weapon/Bare, Shield/BPDefense, Health, WeaponType,...

Schaden

(In Arbeit)

Ablauf eines Kampfes

Ein Kampf kann durch mehrere Aktionen ausgelöst werden:

  • feindliche Kreatur begegnet dem Spieler
  • Spieler wählt die Aktion "Angreifen" für ein Lebewesen aus
  • ein anderes Ereignis (durch benutzerdefiniertes Skript)

In jedem Fall erscheint dann das Kampf-Interface. Der Kampf ist rundenbasiert, d.h. es ist immer genau ein Beteiligter am Zug. Dabei kommen alle Mitglieder einer Partei hintereinander dran, wobei die angreifende Partei anfängt (es sei denn, der Angegriffene besitzt besondere Erstschlagsfähigkeiten). Jeder Kämpfer kann ein oder mehrere Male schlagen, je nach Agilität. Wenn ein Mensch z.B. gegen schnelle kleine Kreaturen kämpft, so dürfen diese oft mehrmals schlagen. War jeder Kampfteilnehmer einmal an der Reihe, beginnt der Ablauf wieder von vorne.

Nach jedem ausgeführten Schlag wird der Kampf kurz angehalten, damit der Spieler sich die Ergebnisse des Schlages ansehen kann. Wenn der Spieler am Zug ist, darf er seine Aktion bestimmen und seine Verteidigungseinstellungen überprüfen. Zu den möglichen Aktionen zählt Schlag mit der Waffe, Magie, Benutzen eines Inventargegenstandes, Abwarten (nichts tun) und Flüchten.

Der Kampf ist zu Ende, sobald der Spieler oder alle Gegner des Spielers besiegt bzw. geflüchtet sind.

Ablauf eines einzelnen Angriffs

Vor einem Angriff kann der Angreifer seine Waffe und das ungefähre Angriffsziel wählen. (Statt einem Waffenzug kann der Angreifer auch einen Schlag/Tritt durchführen.) Der Schlag wird dann folgendermaßen ausgeführt:

Berechnen der minimalen und maximalen Schadenspunkte (SP) ohne Abwehr

Zuerst wird die primäre Angriffsmethode untersucht, um den Basisschaden festzulegen. Falls ein Mensch einen Schlag oder Tritt ausführt, so hängt dies von der BodyPower, der Barehand-Fertigkeit, der BasicHitPower und der Stärke (ST) des Angreifers ab. Bei einer Kreatur dagegen wird die Strength und Gesundheit für die Berechnung des Schadens herangezogen. Ein Waffenangriff wertet dagegen BodyPower, MinDamage und MaxDamage der Waffe, die Fertigkeit in der Waffengattung und die Stärke (ST) aus.

Dadurch wird i.A. ein Schadensbereich errechnet, aus dem per Zufall eine Schadenspunktzahl ausgewählt wird. Durch ein Query-Skript („DQuery1“) können Items und Verzauberungen diesen Wert dann noch erhöhen oder verringern.

Schließlich kann der Schaden noch prozentual verändert werden. Dies kann durch die Wahl des Zielbereichs stattfinden (Treffer am Kopf erzeugen beispielsweise stärkeren Schaden) oder wieder durch Query-Skripte („DQuery2“).

Berechnen der minimalen und maximalen Absorption (AP)

Die Absorption ist der Schaden, der nicht dem Lebewesen selbst zugefügt wird, sondern auf Rüstung oder Schild umgelenkt wird, oder einfach „verschwindet“ (Basisschutz jedes Lebewesens). Die Absorption wird beim Menschen erhöht durch die Protection des Körperteils und bei Kreaturen durch die FleshProtection.

Absolute Erhöhungen der Absorption können dann noch durch Query-Skripte („AQuery1“) erfolgen. Die Absorption wird dann per Zufall noch geringfügig erhöht oder gesenkt. Zuletzt werden noch Query-Skripte zur relativen (prozentualen) Wertänderung ausgeführt („AQuery2“.).

Durch Schild und Rüstung umgelenkter Schaden wird später berücksichtigt.

Berechnen der Chancen von Angriff und Abwehr

Wenn der Gegner geschickt in der Verteidigung ist, kann der Angriff komplett fehlschlagen. Andererseits ist ein geschickter Angreifer aber auch schwerer abzuwehren. Es werden für Angriff (Attack) und Abwehr (Defense) Chancen berechnet und dann verglichen. Ist das Ergebnis ≥ 1, so ist der Angriff erfolgreich.

Der Angriffswert wird – je nach Angriffsart – durch die Fertigkeiten Attack, Magic und SelfKnowledge erhöht. Dann ist noch eine Änderung durch Query-Skripte („AttackQuery“) und durch einen Zufallswert (nur nach oben) möglich.

Bei dem Verteidigungswert ist die Defense-Fertigkeit (nur bei Menschen) entscheidend; auch hier werden noch Query-Skripte („DefenseQuery“) und Zufallsänderungen berücksichtigt.

Umgeleiteten Schaden und Ziel bestimmen

Abhängig von der Art der Abwehr wird der umgelenkte Schaden berechnet: Wenn keine Verteidigung festgelegt wurde gibt es keine Schadensumleitung. Wird ein Körperteil zur Abwehr eingesetzt, so wird der Schaden in vollem Umfang auf dieses Körperteil umgelenkt. Bei Abwehr durch ein Schild (an der richtigen Stelle) wird ein bestimmter Prozentsatz des Schadens (je nach Schild) absorbiert und auf den Schild umgelenkt (als Beschädigung).

Anwenden des Schadens

Zuerst wird das Notification-Skript „BeforeStrikeEffects“ sowohl für den Angreifer, als auch für den Verteidiger, aufgerufen. Danach wird dem getroffenen Körperteil der verbleibende Schaden (Ausgangsschaden – Absorption – Umgelenkter Schaden) zugefügt, ggf. auch als Beschädigung der Rüstung. Dann wird der umgelenkte Schaden seinem Ziel angerechnet. Die beiden letzteren Operationen haben oft Klangeffekte als Nebenwirkung. Abgeschlossen wird dies durch das Notification-Skript „AfterStrikeEffects“, in dem z.B. magische Effekte aufgerufen werden können.


Grundlagen der Magie

Magie ist ein wichtiges spieltechnisches Element in vielen Spielen, besonders Rollenspielen. Zu den magischen Elementen gehören: Zaubersprüche (Spruchrollen und Runensprüche), Tränke, Spezialangriffe, Objektmagie (Ringe oder Rüstung), Fähigkeiten (SpecialAbilities),...

Arten von magischen Wirkungen

Es gibt folgende allgemeine Arten von MagicEffects:

  • Gezielte Effekte: Jeder Effekt kann auch ein Ziel haben (ein Mensch, eine Kreatur, ein Item oder eine Sprite). (Falls der Spruch im Kampf eingesetzt wird, kann als Ziel nur einer der Kampfgegner gewählt werden.)
  • Ungezielte Soforteffekte: Haben eine ganz konkrete Wirkung, d.h. es wird genau ein Skript ausgeführt.
  • Längerfristige Effekte: Diese Effekte müssen in der globalen Modifiers-Collection registriert werden. Sie können entweder ganz bestimmte Parameter bei Aktivierung verändern (die dann nach Ablauf der Wirkung automatisch wieder zurückgesetzt werden), oder timergesteuert Skripts ablaufen lassen. Auch diese Effekte können gezielt sein, oder aber sich auf den aktuellen Sektor beziehen.

Technische Gliederung der Magie

Das folgende Schema zeigt, wie der verschiedenen Komponenten zusammenhängen:

  • An erster Stelle stehen die auslösenden Objekte. Diese haben Kontrolle über die Kosten eines Spruches, über die Dauer (und andere Abhängigkeiten), Stärke und über die Wirkung bei Fehlschlag etc.
    Im einfachsten Fall rufen Sie einfach GenerateMagicEffect() auf. Einem magischen Objekt ist i.A. genau ein MagicEffect zugeordnet. Allerdings kann der gleiche MagicEffect für mehrere magische Objekte verwendet werden (z.B. der MagicEffect „Heilung“ für den Spell „Kleine Heilung“ und „Große Heilung“ mit unterschiedlicher Stärke).
  • Danach kommen die MagicEffects, die den Kern eines Spruches ausmachen, auch wenn sie selbst meist noch nicht die eigentliche Wirkung übernehmen – sie lösen lediglich eine Reihe von Wirkungen aus, so wie es technisch am besten zu machen ist, z.B. Verzauberungen oder Modifikationen, aber auch alle anderen spontanen Wirkungen.
  • Dann schließlich kommen die eigentlichen Aktionen in Form von Enchantments, Modifikationen und sonstigen Wirkungen.

Ausführen von Sprüchen

Es gibt mehrere Möglichkeiten, die Wirkung eines magischen Elements zu entfesseln:

  • Einen Spruch „ausführen“. Das kann das Sprechen einer Schriftrolle, das Ausüben einer Spezialfähigkeit oder das Trinken eines Zaubertranks sein. Auch Kampfzauber und insbesondere die Kampfattacken von Kreaturen zählen dazu.
    Hier wird entweder die allgemeine „_Use“-Prozedur des Objekts oder aber eine spezielle Prozedur aufgerufen (z.B. „_Cast“ bei Sprüchen oder „_Drink“ bei Potions).
  • Ein magisches Objekt „aktivieren“. Hierzu zählt das Anlegen magischer Rüstung, Ringe oder das Aufnehmen magischer Waffen. Es werden hierfür einerseits Prozeduren beim An- und Ablegen aufgerufen „_Equip“ und „_UnEquip“), und darüber hinaus auch die „_Query“-Prozeduren, in denen z.B. während dem Kampf Manipulationen erfolgen können. Allerdings werden die Query-Prozeduren normalerweise nicht für die Gegenstände selbst bearbeitet, sondern von durch die Wirkungen angelegte Enchantments.

MagicEffects

Die meisten Sprüche werden in einer Skriptdatei gespeichert, z.B. der globalen „MagicEffects“. Um einen Effekt zu generieren gibt es die Shared-Funktion GenerateMagicEffect(). Sie nimmt eine Reihe von Standard-Argumenten für Sprüche, die unten näher erläutert werden. Weitere Parameter werden in einem Objekt mit dem HiType „MagicEffect“ und dem Namen des Effekts gespeichert (z.B. ob der Spruch gezielt ist, d.h. die Angabe eines Targets erfordert). Die Skriptprozedur „Effect_..._Create“ für diese Item-Klasse ist genau die erwähnte Effektprozedur der Datei „MagicEffects“. Sie erhält (wie andere Skriptereignisse auch) als Argument das MagicEffekt-Objekt selbst, in dem als DataPermanent und DataDynamic alle Daten, die GenerateMagicEffect() erhalten hat, gespeichert sind. Spruchkosten, Fehlschlag etc. werden von dieser Prozedur vollkommen ignoriert, da ja nicht bekannt ist, ob es sich um einen Spruch, eine Schriftrolle oder einen Ring handelt, der die Wirkung auslöst. Es kann auch eine vom Skript aufgerufene Wirkung sein.

Die allgemeinen Effekt-Parameter umfassen:

  • Effektname (nur Shared-Funktion): Name des Effekts, der generiert werden soll
  • Stärke: Ein Wert von 1 bis 100, welcher eine abstrakter Größe für die „Stärke“ des Spruchs angibt. Nicht alle Sprüche unterstützen dies.
  • Dauer: Kann 0 (fixed) oder 1 (automatic) sein. Im ersten Fall ist die Dauer im voraus von der Stärke bestimmt, im zweiten Fall ist sie vom Mana oder einem anderen Faktor abhängig (noch näher zu bestimmen...) Manche Sprüche ignorieren dies, z.B. alle Sofortzauber.
  • Caster: Der Sprecher des Spruchs.
  • Target: Das Zielobjekt. Wird ignoriert, falls der Spruch ungezielt ist.
  • Abhängigkeiten: Eine Liste von Dingen, von denen die Dauer des Spruches abhängig ist. Dies sind technisch gesehen Semikolon-getrennte Gleichungen oder Codes, z.B. „PlayMinutes=5“. Diese werden normalerweise einfach der erzeugten Verzauberung mitgegeben.
    è So ist es möglich, den gleichen MagicEffect für eine Trank und einen Ring zu benutzen; der Trank übergibt eine Zeitdauer, der Ring eine Objektabhängigkeit.

Wie können Sprüche wirken?

Wie bereits erwähnt wird die eigentliche Wirkung eines Spruchs von der Effekt-Prozedur eines „MagicEffect“-Items (besser gesagt einer ItemClass) generiert.

Die Effekt-Prozedur kann nun den Effekt auf mehrere Arten umsetzen:

  • Direkte Wirkung: Das Skript generiert einfach die gewünschte Wirkung, z.B. erstellt es ein neues Objekt im Inventar des Benutzers.
  • Verzögerte Wirkung: Zu einem festgelegten Zeitpunkt passiert eine bestimmte Aktion.
  • Eigenschaften manipulieren: Durch einen Modifier können die Werte eines Objekts, z.B. des Spruchanwenders, für eine festgelegte Zeitdauer geändert werden. Beispielweise könnte sich der Sprecher 30 Sekunden lang stärker machen.
  • Verhalten manipulieren: Einige Wirkungen sind nicht allein mit Eigenschaften zu erreichen, z.B. der Effekt „Auf dem Wasser wandeln“. Hierfür kann mittels eines Modifiers für die Dauer des Spruchs eine spezielle Variable gesetzt werden, z.B. „EnableWaterWalk“.
  • Ins Geschehen einklinken: Auch die zuletzt genannte Methode deckt noch nicht alle Modifikationen ab. Wenn z.B. ein Kampf stattfindet, muss ein magischer Schild beim Abblocken z.B. dafür sorgen können, dass die Resistenz gegen Feuer gleich der Anzahl der Lebenspunkte des Spielers ist. Dazu aber muss der Schild die Resistenz ändern können, sobald geblockt wird. Für solche Fällt ruft das Spiel für alle Ringe, Kleider, Waffen und Verzauberungen besondere Skripts auf, die u.a. Zugriff auf Resistenz u.ä. haben, z.B. in obigem Fall FightHit_shield1_Query(Success, Blocked, Damage, Resistance).
  • Temporäre Aktion: Ähnlich wie das Manipulieren von Eigenschaften können für die Dauer eines Spruchs auch komplexere Dinge erfolgen. So kann z.B. zu Beginn des Spruchs ein magischer Gegenstand erschaffen werden, der bei Ablauf des Zaubers automatisch wieder zerstört wird.
  • Schrittweise Veränderungen: Alle x Sekunden passiert etwas, was jeweils noch mal in einem eigenen Skript festgelegt wird, z.B. dass der Spieler alle 10 Sekunden 5 Energiepunkte verliert.

Hinweis: Oft ist es geschickter, die „Query“-Skripts nicht für Schilde etc. direkt zu implementieren, sondern im „_Equip“-Skript des Schilds ein MagicEffect-Skript aufzurufen, welches für den Spieler eine Verzauberung erstellt, die dann auf die „Query“-Prozedur reagieren kann. Natürlich muss die Verzauberung im „_Unequip“-Skript auch wieder entfernt werden, oder man macht die Verzauberung von der Position des Elements abhängig.

Modifikationen vs. Verzauberungen

Um längerfristige Sprüche zu realisieren gibt es zwei Möglichkeiten: Modifikationen und Verzauberungen. Abgesehen davon, dass sie etwas unterschiedlich gehandhabt werden, sind sie nicht gleichermaßen in allen Fällen einsetzbar. So können sich Verzauberungen immer nur auf einzelnen Kreaturen oder Objekte beziehen, aber Modifikationen können z.B. nicht auf Ereignisse reagieren.

Beispiele:

  • Um das Wetter in einem Sektor für kurze Zeit zu ändern, muss man eine Modifikation einsetzen.
  • Um den Kampfschaden des Spielers zu erhöhen, muss man eine Verzauberung verwenden.
  • Um die Stärke des Spielers für kurze Zeit zu verbessern, kann man beides benutzen.

Verzauberungen bieten auch eine etwas bessere Kontrolle über die Dauer des Effekts und sollten daher bei gezielten Sprüchen bevorzugt eingesetzt werden. Außerdem können Verzauberungen im Gegensatz zu Modifikationen auf Ereignisse reagieren (z.B. Query-Skripts), da sie Items sind. Ein zusätzlicher Vorteil von Modifikationen ist allerdings die höhere Genauigkeit (sie können auch öfter als einmal pro Spielminute abgefragt werden).

Magische Items

Gegenstand | Beschreibung | Skript-Prozedur(en) | Zweck
(Allgemein) | Verschiedene Dinge mit magischer Wirkung | _Use | Wird beim „Benutzen“ des Gegenstandes ausgeführt. Ruft GenerateMagicEffect() auf.
Tome | Ein Zauberbuch zum Lernen eines neuen Spruchs | _Use | Legt ggf. ein neues „Spell“-Objekt in der Spells-Collection des Spielers an.
Spell | Ein imaginärer Spruch, den der Spieler beherrscht | _Cast | Wird beim Sprechen aufgerufen. (Manakosten etc. wird als PermanentData-Eigenschaft der ItemClass gespeichert.) Ruft GenerateMagicEffect() auf.
MagicWand | Zauberstab zum Umwandeln von Reagenzien | _UseWith (Container) | Wird aufgerufen, wenn der Stab mit einem Container mit Reagenzien benutzt wird. Hier werden nun die Reagenzien geprüft und ggf. eine Spruchrolle (MagicScroll) generiert. Dies ist i.A. ein fester gezielter Sofortzauber.
UserPotion | Ein Trank mit verschiedenen Zutaten | _Drink | Wird beim Trinken des Tranks aufgerufen. Hier sollte der Inhalt (als DynData) geprüft und dann ggf. GenerateMagicEffect() aufgerufen werden.
SpecialAbility | Eine Spezialfähigkeit, über die der Spieler verfügt (imaginär) | _Activate | Wird beim Benutzen der Fähigkeit aufgerufen. Sollte einfach GenerateMagicEffect() benutzen, um den Effekt zu generieren. Das Level sollte aus der DynData „Level“ x 10 entnommen werden.
Weapon, Shield, Clothing, Ring | Verzauberte Waffen, Schilde, Rüstungen, etc. | _Equip, _Unequip | Üblicherweise wird beim Anlegen GenerateMagicEffect() aufgerufen, um eine Verzauberung zu erschaffen. Diese kann dann ihrerseits auf „_Query...“- oder „_Notify...“-Ereignisse reagieren. Beim Ablegen wird die Verzauberung wieder aufgehoben.
 | oder | _Notify... | Manche besondere Effekte kann man auch erreichen, indem direkt im Notify-Ereignis erst ein MagicEffect generiert wird.
SpecialAttack | Angriffstyp, der einer Kreatur zugeordnet werden kann | _Perform | Wird aufgerufen, wenn die Kreatur diesen Spezialangriff durchführt. Beim Aufruf wird normalerweise GenerateMagicEffect() benutzt. Das Item-Objekt selbst enthält Angaben dazu, wie oft die Kreatur den Angriffstyp durchführt.

Spruchkosten

Die „Kosten“ eines Spruches können entweder direkt „bezahlt“ werden, oder von der Dauer des Spruches abhängig sein (Upkeep). Das MagicEffect-Objekt ist in jedem Fall völlig unabhängig von den Spruchkosten. Nicht jeder Spruch (oder Spruchtyp) muss etwas kosten: Magie in Ringen kostet i.A. nichts.

Bei Direktbezahlung erfolgt dies in der auslösenden Prozedur, z.B. ein (vereinfachter) Befehl wie „Caster.Mana = Caster.Mana – 10“ in der Prozedur „Spell_Heal_Cast“.

Sollen die Kosten von der Dauer abhängen, so wird einfach umgekehrt die Spruchdauer abhängig von der Bezahlung gemacht, indem ein Parameter von GenerateMagicEffect() entsprechend gesetzt wird. Solche Sprüche müssen natürlich vom Beschwörer abbrechbar sein.

Stärke und Dauer von Zaubern

Zwei wichtige variable Parameter von MagicEffects sind die Stärke (Strength) und die Dauer (Duration).

Der Stärkewert kann beliebig interpretiert werden; es werden jedoch die Zahlen von 1-100 empfohlen. Hat ein Spruch immer eine feste Stärke, so kann er diesen Wert auch ignorieren.

Die Dauer wird in Spielminuten angegeben. Sofortzauber ignorieren diesen Wert einfach. Alle anderen Sprüche erhalten automatisch eine zusätzliche Abhängigkeit von der Maximaldauer, es sei denn, diese ist mit 0 angegeben. Dann dauert der Spruch unbegrenzt lange (kann aber immer noch andere Abhängigkeiten wie Erhaltungskosten haben).

Soll ein Effekt an einer festen Stelle per Skript erzeugt werden, so sollte die Stärke und Dauer konkret bei GenerateMagicEffect() angegeben werden. Im Allgemeinen, d.h. bei anderen auslösenden Prozeduren wie „Spell_..._Cast“ oder „SpecialAbility_..._Activate“, sind beide Werte aber nicht genau festgelegt. Sie hängen von bis zu zwei Daten ab:

  • Einem „Level“ desjenigen, der den Effekt auslöst (CasterLevel).
    Bei Spells ist das z.B. die IQ-Eigenschaft des Casters, bei SpecialAbilities ist es die SelfKnowledge-Fertigkeit, bei Schriftrollen oder Tränken gibt es kein CasterLevel.
  • Einem „Level“ des Objektes, das den Effekt auslöst (SpellLevel)
    Spezialfähigkeiten können z.B. im Laufe des Spiels „upgegradet“ werden, und ihre Wirkung hängt vom aktuellen Level ab. Ringe, Scrolls etc. haben meist ein festes Level.

Um von diesen Daten, CasterLevel und SpellLevel, auf die Werte Strength und Duration zu kommen, muss eine Umrechnungsformel benutzt werden. Diese Umrechnung ist vom Zauber abhängig. Daher bietet jeder MagicEffect eine Umrechnungsprozedur „Effect_..._CalcData“ an. (Details finden Sie in der Skriptreferenz.)

Abwehr gegen Magie

Ein erfolgreich gesprochener Zauber kann noch vor Eintreten der Wirkung beeinflusst werden, z.B. durch natürliche Resistenzen des Gegners oder durch Antimagie (in beiden Fällen in Form von bereits existierenden Verzauberungen). Diese Beeinflussung erfolgt, bevor Effect_..._Create aufgerufen wird.

Dazu werden, ähnlich wie beim Kampf mit dem Schaden, alle möglichen „Beeinflusser“ gefragt, ob sie ein erzeugtes, aber noch nicht aktiviertes MagicEffect-Objekt ändern möchten, analog wie beim Damage-Objekt im Kampf. (Natürlich gilt das auch im Kampf, falls dort Magie eingesetzt wird.) Das „Befragen“ erfolgt durch das übliche Verfahren mittels Query-Skripts. Da alle notwendigen Informationen als DD- und DP-Eigenschaften im MagicEffect-Objekt gespeichert sind, genügt eine Änderung dieser Werte, um den Spruch umzugestalten.

Folgende Änderungen bzw. Auswirkungen wären denkbar:

  • Annullieren des Zaubers, indem der Item-Status auf Destroyed gesetzt wird
  • Umlenken des Zaubers, indem das Ziel verändert wird
  • Verringern der Stärke des Zaubers
  • Aktivieren eines Gegenzaubers
  • Ausführen eines sonstigen beliebigen Effektes

Wichtig: Wird der ursprüngliche Zauber abgebrochen, oder soll verhindert werden, dass weitere Objekte Einfluss auf den Zauber nehmen, so muss EventHandeled von ProcLocal auf True gesetzt werden.

Typische Verteidigungszauber schützen nur gegen bestimmte Arten von Magie oder Effekten. Deshalb erhält jeder „MagicEffect“ noch eine Reihe von Klassifizierungen, die in seiner Klasse gespeichert sind. Z.B. hat ein Beschwörungszauber die Classification „Summoning“, und ein Enchantment, welches Beschwörungen verhindert, annulliert alle Zauber der Classification „Summoning“.

Verminderung der Abwehr gegen Magie, Unterbrechungszauber

Würde ein Magie-Schutzzauber (= Antimagie) in der Query-Prozedur seines Enchantments einfach einen anderen Zauber annullieren, so wäre er „total“, d.h. könnte selbst nicht verhindert werden. Dies sollte aber i.A. nicht der Fall sein, d.h. auch Antimagie kann wieder durch Magie begegnet werden. Dazu gibt es Unterbrechungszauber. Dies sind technisch gesehen MagicEffects mit der Classification „Interrupt“ und einer zusätzlichen DP-Eigenschaft InterruptChain. Sie haben keine Wirkung. Sie werden einfach nur gestartet (durch GenerateMagicEffect), um zu sehen, ob sie „ankommen“, d.h. nicht annulliert werden. Die InterruptChain-Eigenschaft erhält als Wert die Item-ID des unterbrochen Spruchs, angehängt an dessen InterruptChain (falls nichtleer).

Es bieten sich in der Praxis folgende Möglichkeiten zur Verminderung der Abwehr gegen Magie:

  • Schwächung der Abwehr durch Änderung der Abwehrbedingungen selbst, z.B. „Abwehr nur von Sprüchen, wenn der Caster ein bestimmtes Level nicht übersteigt“ o.ä.
  • Den Zauber nicht abbrechbar machen. Dies geht nur, wenn die Antimagie das Interrupt-Verfahren anwendet (s.o.). Der Interrupt-Zauber der Antimagie fragt auch den ursprünglichen MagicEffect, den sie selbst cancelt (mittels Query-Skript). Wenn dieser in der InterruptChain-Eigenschaft des Interrupt-MagicEffects seine eigene Item-ID erkennt, kann er den Interrupt-Zauber canceln und somit sich selbst fortführen.
  • Unabhängige Zauber, ausgesprochen in Form von Enchantments, können sich speziell gegen Unterbrechungszauber richten. Sie reagieren im Query-Skript auf den Interrupt-MagicEffect und lösen ihrerseits einen Interrupt-MagicEffect aus, dessen InterruptChain-Eigenschaft um ein Element erweitert wurde. Somit können sich längere Unterbrechungsketten bilden. MagicEffects in der InterruptChain und der MagicEffect selbst werden nicht in einem Query-Skript befragt, damit sich keine Endlosketten ergeben.

Abhängigkeiten von MagicEffects

Viele magische Effekte haben Abhängigkeiten. Nicht dazu gehören Spontanzauber; ihre Wirkung tritt sofort ein und ist dann beendet. Alle längerfristigen Effekte haben jedoch mindestens eine Abhängigkeit, sonst würden sie ewig anhalten.
Da das MagicEffect-Objekt selbst nach dem erfolgreichen Aussprechen eines Zaubers gleich wieder zerstört wird, kann es also nicht selbst die Wirkung des Zaubers aufheben Der MagicEffect ist jedoch das einzige Objekt, das genau „weiß“, welche Wirkungen zu einem Zauber gehören: Es erstellt sie und kann sie auch wieder rückgängig machen. Außerdem erhält die Effektprozedur (Effect_..._Create) in Form der DP-Eigenschaften des MagicEffect-Objekts eine Liste der Abhängigkeiten für den Spruch (z.B. Dauer).

Die Lösung ist, ein „Helfer-Objekt“ zu erstellen, welches die Abhängigkeitsbedingungen ständig überprüft und bei Nichterfüllung einer der Bedingungen bestimmte Objekte zerstört, die der MagicEffect vorher angibt, z.B. Verzauberungen.

Der MagicEffect selbst muss sich nicht selbst um das Erstellen dieser Dependencies kümmern. Er muss lediglich mitteilen, welche Objekte (i.A. Verzauberungen) er erstellt hat, die beim Enden des Zaubers wieder zerstört werden müssen. Dies wird über ProcLocal-Variablen der aufrufenden Prozedur (i.A. GenerateMagicEffect) mitgeteilt.


Skriptreferenz

MQ!Engine Skriptreferenz

Allgemeines

  • Skriptprozeduren haben nie Rückgabewerte.
  • Der erste Parameter einer jeden Skriptprozedur ist immer ein LocalProc-Objekt. Dieses Objekt enthält u.a. prozedurlokale Variablen, die auch als Rückgabewerte verwendet werden können. Skriptereignisse können die Eigenschaft EventHandeled des LocalProc-Objects setzen.
  • Skriptereignisse, die den aktuellen Spieler betreffen, haben normalerweise den Namen Human_humanid_name. In diesem Fall werden sie aber zusätzlich noch unter der Bezeichnung Me_name ausgeführt, sofern sie von der Objektprozedur und der Klassenprozedur noch nicht behandelt wurden.

Spezielle Skripte

(ohne Präfix)

TravelDay():

Wird während einer Reise für jeden Tag ausgeführt.

Parameter: Traveler [Human]: Die reisende Person (i.A. der Spieler)
Start [RegionPoint]: Von wo aus die Reise begonnen hat
Destination [RegionPoint]: Wohin die Reise gehen soll
Day [Long]: Der aktuelle Tag der Reise
Duration [Long]: Die Anzahl der Tage, die diese Reise dauern wird
AllowInterruptions [Bool]: Wenn False, sollten keine Unterbrechungen vom Skript generiert werden

Variablen: InterruptTravel [out]: Falls dies auf 1 gesetzt wird, so gilt die Reise als unterbrochen. Sie kann später fortgesetzt werden. (Das Skript muss vorher die korrekte „Unterbrechungskarte“ laden.)
InterruptReason [out]: Sollte einen Text enthalten, der dem Spieler als Grund für die Unterbrechung angezeigt wird.

TravelOver():

Eine Reise ist zu Ende.

Parameter: Traveler [Human]: Die gereiste Person (i.A. der Spieler)
Start [RegionPoint]: Von wo aus die Reise begonnen hat
Destination [RegionPoint]: Das Ziel der Reise (muss nicht das erreichte Ziel sein!)
Duration [Long]: Die Dauer der Reise in Tagen

Regionspunkt-Skripte

(RegionPoint_pointkey_...)

TravelFrom():

Eine Reise zu einem anderen Regionspunkt wird begonnen (aus Sicht des Regionspunktes, der verlassen wird).

Parameter: Destination [RegionPoint]: Das Ziel der Reise

TravelTo():

Eine Reise zu einem anderen Regionspunkt wird begonnen (aus Sicht der Zielregion).

Parameter: Start [RegionPoint]: Punkt, von dem die Reise ausgeht

Reached():

Eine Reise zu diesem Regionspunkt ist zu Ende.

Parameter: Start [RegionPoint]: Punkt, von dem die Reise ausgeht

Map-Skripte

(Map_mapname_...)

Enter():

Wird jedes Mal ausgeführt, wenn der Spieler die Karte betritt.

Parameter: Actor [Human]: Der Spieler, der die Karte betritt
FirstTime [Bool]: Betritt dieser Spieler die Karte zum ersten Mal?

Init():

Wird ausgeführt, wenn die Karte zum ersten Mal geladen wird (komplett mit Geometrie)

Parameter: Self [Map]: Die Karte, die geladen wurde

OnDawn():

Wird ausgeführt, wenn es abends dunkel oder morgens hell wird. Veränderungen der Karte für die Nacht (z.B. weniger Personen) sollten an dieser Stelle durchgeführt werden.

Parameter: DawnType [Int]: 0=es wird Tag, 1=es wird Nacht

Sector-Skripte

(Sector_sectorkey_...)

Enter():

Wird ausgeführt, wenn der Spieler ein neues Feld innerhalb des Sektors betritt.

Parameter: FirstTime [Bool]: Wurde der Sektor zum ersten Mal betreten?

Square-Skripte

(Square_X_Y_L_...)

Enter():

Wird ausgeführt, wenn der Spieler ein bestimmtes Kartenfeld betritt, oder wenn er zu lange auf dem Feld stehen bleibt.

Parameter: FirstTime [Bool]: Wird das Feld zum ersten Mal betreten?
ReEnter [Bool]: True, wenn das Skript ausgeführt wird, weil der Spieler zu lange auf dem Kartenfeld stehen geblieben ist

Standardobjekt-Skripte

(Human_objid_...; Creature_objid_...;
Item_objid_...;
Sprite_objid_...;
HClass_classkey_...;
CClass_classkey_...;
IClass_classkey_...;
SClass_classkey_...)

Pickup():

Wird ausgeführt, wenn ein Objekt in das Inventar des Spielers übernommen werden soll. Bei Auswählen der Aktion „Nehmen“ durch den Spieler ist das immer der Fall, bei sonstigen Transaktionen nicht zwangsläufig.

Dies ist ein Skriptereignis. Wird es als behandelt markiert, so wird das Objekt nicht aufgenommen. Möglicherweise wird der Spieler gefragt, ob er das Objekt stattdessen auf dem Boden deponieren möchte. Ist das Objekt Teil eines Containers, so kann der gesamte Container nicht aufgenommen werden.

Parameter: Self [Item]: Der Gegenstand, der aufgenommen werden soll
Actor [H/C]: Die Person, die den Gegenstand aufnimmt (i.A. der Spieler)
InsideContainer [Bool]: Falls True, so wird das Item in Zusammenhang mit einem Container aufgenommen

Drop():

Wird ausgeführt, wenn ein Objekt auf dem Kartenfeld abgelegt werden soll, wenn es vorher im Besitzt des Spielers war.

Dies ist ein Skriptereignis. Wird es als behandelt markiert, so wird das Objekt nicht abgelegt. Ist das Objekt Teil eines Containers, so kann dann der gesamte Container nicht abgelegt werden.

Parameter: Self [Item]: Der Gegenstand, der aufgenommen werden soll
Actor [H/C]: Die Person, die den Gegenstand aufnimmt (i.A. der Spieler)
InsideContainer [Bool]: Falls True, so wird das Item in Zusammenhang mit einem Container abgelegt

Meet():

Wird ausgeführt, wenn der Spieler einem anderen Menschen oder einer Kreatur begegnet, die sich auf dem gleichen Kartenfeld befindet, oder wenn ein solcher Organismus dem Spieler begegnet. In jedem Fall werden zwei Skripte ausgeführt; eins für den Spieler und eins für seinen Partner.
Per INI-Option kann festgelegt werden, dass das Skript auch ausgeführt wird, wenn sich zwei NPCs begegnen, einschließlich Kreaturen.

Dies ist ein Skriptereignis. Wird es als behandelt markiert, so werden keine weiteren Standard-Aktionen für diese Begegnung ausgeführt, wie etwa Angriffe u.ä. Außerdem wird ggf. das zweite Skriptereignis nicht ausgelöst.

Parameter: Partner [Object]: Gibt den Gegenüber an, dem der Actor begegnet ist
Self [Object]: Enthält einen Verweis auf das eigene Objekt

Die():

Wird ausgeführt, wenn ein Organismus (Mensch/Kreatur) stirbt.

Parameter: Self [Object]: Der Organismus, der stirbt