|
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:
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
>>
|