Aaron Fischer Ingenieur, Vater, Heimwerker, Problemlöser

19 Juni, 2016

QuakeC programmieren

TL;DR:

Kommen wir zum finalen Part der Quake Modding Serie. Wer die anderen beiden Teile noch nicht gesehen hat, sollte das zuvor nachholen. Wir werden ein paar Änderungen am id1 Modcode vornehmen und neue Funktionen einbauen, die wir mit dem Map-Editor nicht machen können.

Als erstes müssen wir wieder ein paar Vorbereitungen treffen, um mit dem Sourcecode arbeiten zu können. Im Spieleverzeichnis liegt bereits das id1 Verzeichnis. Das ist sozusagen die Haupt-Mod, welche von der Engine beim Starten geladen wird. Weitere Mods können in Form weiterer Verzeichnisse im Spieleverzeichnis abgelegt und im Spiel aktiviert werden (Options -> Browse Mods und dann zum aktivieren Pfeil nach rechts). Alternativ kann man Quake beim Start auch den Parameter -game <modfolder-name> mitgeben, um die Mod automatisch zu aktivieren. In unserem Fall legen wir uns hier ein neues Verzeichnis an (Name entspricht dem Modnamen). Hierin legen wir das Verzeichnis qc an, in das wir die Kopie der original QuakeC files von id1 ablegen.

Der Ladevorgang einer Mod ist analog zum Rest: Werden Dinge mit dem gleichen Namen definiert, werden sie überschrieben. In unserem Fall wird alles überschrieben. Das können wir später noch ändern, in dem wir alles was nicht benötigt wird löschen. Um sich mit dem Sourcecode vertraut zu machen ist es aber sehr praktisch, wenn man den kompletten Code zur Verfügung hat.

Die Quake Engine wird mit einem C-Derivat namens QuakeC programmiert. Dabei handelt es sich um eine interpretierte Sprache, die an C angelehnt ist. Mit ihr können so gut wie alle Elemente der Quake Engine verwendet und manipuliert werden. Um unseren Sourcecode in ein Paket für die Engine zu transformieren, benötigen wir dennoch einen Compiler. Es gibt verschiedene QuakeC Compiler, frikqcc ist zu empfehlen.

Machen wir eine kleine Änderung, um zu sehen ob alles korrekt funktioniert. Doch bevor wir anfangen, empfiehlt es sich, ein lokales Git-Repo anzulegen, um im Zweifel Änderungen wieder rückgängig machen zu können.

git init .
git add .
git commit -m "Fresh start"

Öffnen wir die Datei weapons.qc und suchen die Funktion W_FireShotgun. Hier wird die Funktion FireBullets() aufgerufen. Der erste Parameter entspricht der Anzahl der Projektile. Ändern wir die 6 in eine 50 und sehen was passiert. Führen wir den Compiler im qc Verzeichnis aus, wird der Sourcecode compiliert und eine progs.dat erstellt:

$> frikqcc
--------------- frikqcc v2.7 ----------------
defs.qc
subs.qc
fight.qc
[...]
enforcer.qc
oldone.qc

../progs.dat - 0 error(s), 0 warning(s)
press a key

Wenn wir Quake starten und die Mod aktivieren, sollte die Shotgun etwas mehr Power haben :)

Schritte

Fangen wir mit etwas einfachem an: Wenn der Spieler läuft, wollen wir Schritt-Sounds abspielen, um etwas mehr Ambiente ins Spiel zu bringen. Dazu erstellen wir zuerst vier unterschiedliche Schritt-Sounds als WAV und legen diese im Unterverzeichnis player ab. Ich habe mir hier kurzerhand ein paar eigene Schritte aufgenommen und mit Audacity etwas schneller gemacht und vier einzelne Schritte isoliert abgespeichert. Als nächstes ist der Code dran. Die WAV-Files müssen wir zuerst laden, dies machen wir am sinnvollsten im worldspawn. Dazu die Datei world.qc öffnen und in der Funktion worldspawn die WAV-Dateien laden:

precache_sound ("player/foot1.wav");    // running
precache_sound ("player/foot2.wav");    // running
precache_sound ("player/foot3.wav");    // running
precache_sound ("player/foot4.wav");    // running

Sobald der Spieler läuft, wollen wir in zufälliger Reihenfolge die vier Sound Dateien abspielen. Dazu nehmen wir in der Datei player.qc zu Beginn der Funktion player_run folgende Erweiterung vor:

if ((self.walkframe == 1 || self.walkframe == 4)
    && checkbottom(self) == TRUE
    && self.waterlevel == 0) {

  local float r;
  r = rint(random() * 3);

  if (r == 0)
    sound (self, CHAN_AUTO, "player/foot1.wav", 0.5, ATTN_NORM);
  else if (r == 1)
    sound (self, CHAN_AUTO, "player/foot2.wav", 0.5, ATTN_NORM);
  else if (r == 2)
    sound (self, CHAN_AUTO, "player/foot3.wav", 0.5, ATTN_NORM);
  else
    sound (self, CHAN_AUTO, "player/foot4.wav", 0.5, ATTN_NORM);
}

Wenn der Spieler nicht gerade im Wasser ist und nicht springt, wählen wir eine Zufallszahl von 0 bis 3 und spielen dann die WAV-Datei ab. Da Strings in QuakeC unveränderbar (immutable) sind, kommen wir leider um dieses if/else if Konstrukt nicht drumherum. Eine kurz und knackige Übersicht der Funktionen und Konstanten ist hier zu finden. Compiliert alles, können wir es direkt ingame testen.

Bluten

Als nächstes wollen wir etwas mit Sprites machen. Hier können wir uns gleich bei einer schon fertigen Funktion bedienen. Wenn ein Spieler getroffen wird, wird die Funktion SpawnBlood aufgerufen. Wir können diese Funktion auch aufrufen, wenn der Spieler nur noch wenig Gesundheit hat -- als würde er bluten.

Öffnen wir die client.qc dazu. Ganz oben müssen wir zuerst die Funktion SpawnBlood bekannt machen, damit wir sie weiter unten verwenden können.

void(vector org, vector vel, float damage) SpawnBlood;

In der Funktion PlayerPreThink können wir dann -- abhängig von self.health -- Blut erzeugen:

if (self.health <= 65) {
  local float freq;
  freq = rint(self.health / 20);

  if (rint(random() * freq) == 1
      && rint(random() * 3) == 1)
    SpawnBlood (self.origin, '0 0 0', 70);
}

Eigentlich recht simpel: Wenn self.health unter 65 sinkt, erstellen wir eine Variable freq, die abhängig von der Gesundheit ist. Bei voller Gesundheit ist sie 5, bei 10 Lebenspunkten nur noch 1. Danach nehmen wir den Zufallsgenerator, um die Wahrscheinlichkeit zu ermitteln, ob ein Blut-Sprite erstellt werden soll oder nicht. Je niedriger die Lebensenergie ist, desto mehr Blut.

bleeding

Fehlt nur noch eine Flashlight und ein paar düstere Maps und fertig ist die Horror-Mod.

Wie man sieht, ist es recht einfach, die Engine zu modden. Nachdem man sich etwas mit dem Sourcecode vertraut gemacht hat, findet man schnell die passende Stelle. Zusammen mit dem Manual kommt man hier recht schnell zum Ziel. Auf InsideQC gibt es zudem sehr viele Tutorials zu allen möglichen Themen.

Ausblick

Das war er also, der kleine Rundumschlag zur Quake 1 Engine. In Teil 1 haben wir uns das Map-Format genauer angesehen und uns etwas mit dem Netzwerkprotokoll beschäftigt. In Teil 2 haben wir uns die verschiedenen Pack-Formate (PAK und WAD) angesehen, Texturen erstellt, eine Map gebaut und compiliert. Hier haben wir uns auch die Möglichkeiten angesehen, die wir direkt im Map-Editor haben, um die Engine zu beeinflussen. Im letzten Teil haben wir uns den QuakeC Sourcecode des Originalspiels (id1) näher angesehen und ein paar Erweiterungen eingebaut.

Mit diesem Handwerkszeug steht der eigenen Mod nichts mehr im Wege. (Ein wichtiges Themengebiet haben wir allerdings noch ausgelassen: 3D-Models. Da dies ein recht umfangreiches Thema ist, habe ich es bewusst hier nicht behandelt. Wer sich damit näher beschäftigen möchte, kann sich den recht aktuellen Artikel von Philipp Nahratow ansehen.)