TUTORIAL 19 - Verwundbare rockets

von Lancer


Danke an Chris Hilton für sein Q2 Tutorial Vulnerable Rockets, zu finden auf QDevels, auf welchem ein Grossteil dieses Tutorials basiert.

Dieses Tutorial erklärt, wie man rockets oder grenades bzw. jedes beliebige Projektil verwundbar und zerstörbar macht. Als ich mich damit beschäftigte und einige Foren durchsuchte, bemerkte ich, dass auch andere Coder Probleme damit hatten. Als ich es endlich am laufen hatte, endschied ich, meine Methode als Tutorial zu veröffentlichen. Im Prinzip ist es auch nicht sehr schwer, es sind nur wenige Änderungen nötig.

Folgende Dateien werden modifiziert:

  • g_missile.c
  • g_weapon.c

1. Die Funktionszeiger einer Entity

Zuerst schauen wir uns die gentitys_t Struktur in g_local.h an. Etwa bei Zeile 110 findet sich folgendes:

void (*think)(gentity_t *self);
void (*reached)(gentity_t *self);
        // movers call this when hitting endpoint
void (*blocked)(gentity_t *self, gentity_t *other);
void (*touch)(gentity_t *self, gentity_t *other, trace_t *trace);
void (*use)(gentity_t *self, gentity_t *other, gentity_t *activator);
void (*pain)(gentity_t *self, gentity_t *attacker, int damage);
void (*die)(gentity_t *self, gentity_t *inflictor, gentity_t *attacker,
        int damage, int mod);

Dies sind Zeiger auf Funktionen, die aufgerufen werden, wenn die Entity in bestimmte Situationen kommt. Ich werde nicht erklären, wozu diese im einzelnen da sind, ausser der letzen: die. Sie wird aufgerufen, wenn der health Wert der Entity 0 oder kleiner 0 erreicht, nachdem ihr Schaden zugefügt wurde. Schau dir die Funktion G_Damage in g_combat.c an, um die Details zu sehen.

2. Eine "die" Funktion definieren

Füge folgende Funktion in g_missile.c, direkt nach G_ExplodeMissile ein:

/*
================
G_MissileDie

Lancer - Destroy a missile
================
*/
void G_MissileDie( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) {

        if (inflictor == self)
                return;
        self->takedamage = qfalse;
        self->think = G_ExplodeMissile;
        self->nextthink = level.time + 10;
}

Diese Funktion tut nichts anderes den think Zeiger auf G_ExplodeMissile zu setzen und diese in 10 ms aufzurufen. Vorher fangen wir den Fall ab, dass der Schaden von der Entity selbst zugefügt wurde. Dieser Fall sollte nie eintreten, aber sicher ist sicher. Ausserdem verhindern wir, dass die Entity weiteren Schaden nehmen kann, damit ihre Explosion nicht verzögert wird.

Wenn du willst, dass sich die Entity in deiner Mod anders verhält, wenn sie Schaden nimmt, tausche einfach G_ExplodeMissile gegen eine andere Funktion aus.

3. Die rocket zum Leben erwecken

Jetzt wo sie sterben kann, muss die rocket nur noch Schaden nehmen können.
Obwohl diese Tutorial mit rockets arbeitet, lässt sich das Prinzip auf alle anderen Projektile übertragen.
Öffne fire_rocket in g_missile.c und füge folgende Zeilen ein(Z 630):


	bolt = G_Spawn();
	bolt->classname = "rocket";
	bolt->nextthink = level.time + 15000;
	bolt->think = G_ExplodeMissile;
	// Lancer {
	bolt->health = 5;
	bolt->takedamage = qtrue;
	bolt->die = G_MissileDie;
	bolt->r.contents = CONTENTS_BODY;
	VectorSet(bolt->r.mins, -10, -3, 0);
	VectorCopy(bolt->r.mins, bolt->r.absmin);
	VectorSet(bolt->r.maxs, 10, 3, 6);
	VectorCopy(bolt->r.maxs, bolt->r.absmax);
	// Lancer }
	bolt->s.eType = ET_MISSILE;
	bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;

Zuerst spendieren wir der rocket etwas health. 5 sind so wenig, dass jeder Treffer tödlich sein wird. Dann setzen wir takedamage auf true damit die rocket Schaden nehmen kann und setzen den Zeiger der die Funktion auf G_MissileDie. Damit sind die Voraussetzungen für G_Damage erfüllt, so dass diese der rocket Schaden zufügen kann, health abziehen und anschliessend die die Funktion aufrufen kann.
Die nächsten fünf Zeilen dienen trap_trace, der Funktion zur Kollisionserkennung. Die erste Zeile gibt dem Objekt einen soliden Körper, CONTENTS_BODY lässt das Objekt auf jede Art von Projektil reagieren. Alternativ hätten wir auch CONTENTS_CORPSE wählen können, wodurch das Objekt den Körper seines Erschaffers durchdringen könnte.
Um den Unterschied zu sehen, erschaffe beispielsweise eine grenade mit CONTENTS_BODY und laufe über sie, sie wird sich wie Huckel anfühlen.
Die nächsten vier Zeilen definieren die Box, mit deren Hilfe trap_trace Treffer feststellen kann, zwischen den Ortsvektoren mins und maxs wird sie aufgespannt. Sicherheitshalber werden die Vektoren nach absmin/absmax kopiert.
Testhalber solltest du die Geschwindigkeit der rocket verringern, um sie zu einem einfachen Ziel zu machen, ändere folgende Zeile am Ende von fire_rocket:

		  
		  VectorScale( dir, 100, bolt->s.pos.trDelta );
		  

4. Ist das alles ?

Das ist alles was für das Funktionieren der mod nötig ist. Wenn du allerdings compilierst und testest, wird dir auffallen, dass du deine rockets nicht mit einem direkten Treffer zerstören kannst. Mit splash damage wird es gehen und auch von Bots geschossene rockets wirst du zerstören können. Im Prinzip funktioniert der Code also, aber warum können wir unsere rockets nicht selbst treffen ?
Öffne in g_weapons.c die Funktion Bullet_Fire und siehe dir folgende Zeilen an (Z 160):

		  
	passent = ent->s.number;
	for (i = 0; i < 10; i++) {

		trap_Trace (&tr, muzzle, NULL, NULL, end, passent, MASK_SHOT);
		if ( tr.surfaceFlags & SURF_NOIMPACT ) {
			return;
		}

Die Funktion trap_trace verfolgt eine Strecke eine gewisse Distanz weit und liefert die erste Entity zurück, auf die sie trifft. Beachte den vorletzen Parameter passent, der zuvor mit ent->s.nummer initialisiert wird. Dieser Parameter gibt diejenige Entity an, die bei der Trefferprüfung ignoriert wird. Ändere diesen Parameter wie folgt:

		  
		  trap_Trace (&tr, muzzle, NULL, NULL, end, ENTITYNUM_NONE, MASK_SHOT);

Von nun an sind deine rockets (und auch du) gültige Ziele, allerdings nur für machinegun Projektile. Wenn du deine rockets auch durch andere Waffen verwundbar machen willst, musst du auch die trap_traces in den entsprechenden Funktionen ändern.

5. Ein paar Kleinigkeiten

Beim Testen ist dir eventuell aufgefallen, dass der "hit" Sound ertönt, wenn rockets an Wänden explodieren. Der Grund ist, dass die rocket explodiert, bevor sie gelöscht wird. Ihr eigener splash damage trifft die rocket, dadurch entsteht der "hit" Sound.
Das lässt sich leicht ändern, öffne G_MissileImpact in g_missile.c und füge folgende Zeile ein (Z 280)


	// check for bounce
	if ( !other->takedamage &&
		( ent->s.eFlags & ( EF_BOUNCE | EF_BOUNCE_HALF ) ) ) {
		G_BounceMissile( ent, trace );
		G_AddEvent( ent, EV_GRENADE_BOUNCE, 0 );
		return;
	}	
		// Lancer
        ent->takedamage = qfalse;
	

Diese Funktion wird aufgerufen, wann immer eine rocket irgendwo einschlägt, egal ob Wand, Himmel oder Noob.
Da wir hier takedamage deaktivieren, kann der "hit" Sound nur noch durch einen getroffenen Spieler ausgelöst werden.

Beachte, dass abprallende Objekte (grenades) die Funktion zuvor verlassen, so das diese nicht unzerstörbar werden, wenn sie an eine Wand prallen.
Abschliessend füge folgende Zeile in G_ExplodeMissile ein(Z 50):


void G_ExplodeMissile( gentity_t *ent ) {
	vec3_t		dir;
	vec3_t		origin;

// Lancer
	ent->takedamage = qfalse;
	BG_EvaluateTrajectory( &ent->s.pos, level.time, origin );

Auch hierdurch wird der Rückkopplungseffekt vermieden.
Damit sind wir endgültig fertig.
Endlich müssen wir uns nichts mehr gefallen lassen: strike back !

<< Tutorial 18 | | Tutorial 20 >>