TUTORIAL 19 - Vulnerable Missiles
by Lancer
Thanks go to
Chris Hilton for his Q2 Vulnerable
Rockets Tutorial on QDevels upon which
much of this tutorial is based.
This
tutorial explains how to make rockets or grenades or any missile in
general, vulnerable to being shot and destroyed. I was trying
to do this myself, and after checking various forums noticed that a
few people had some small problems with it. Once I got it
working, I decided I might as well share what I've found. The
process is actually quite easy and only a few changes have to be
made.
Files
modified: g_missile.c
g_weapon.c
1. ENTITY FUNCTION POINTERS First off, let's look at g_local.h to
get an idea of how Q3A deals with entities.
Take a look
at the gentitys_t struct at about line 111:
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);
These
are different function types that get executed when the entity is in
a certain situation. I will not explain what all of these are
for, except that the one we are concerned with is the die function
pointer. die gets called when an entity's health goes to or
below 0 after being damaged. You can confirm this for yourself
by checking out the end of G_Damage function in the g_combat.c
file.
2. CREATING A DIE FUNCTION Add the following function in
g_missile.c just after G_ExplodeMissile.
/*
================
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;
}
All this
function does is catch when a missile has been killed and set the
missile to explode within a short period of time. We ignore
the case where the inflictor of the damage is the same missile
suffering the damage. This case should already be avoided, but
this will cover your @$$ in case something weird happens. We
also stop it from taking damage again, since it's possible that
hitting it persistently will cause it to take forever to
die.
You can
adjust the nextthink time frame to whatever you want. I simply
chose the one from the Q2 Vulnerable
Rockets tutorial by Chris
Hilton on the QDevels board, since
it seemed very reasonable.
Note that
I've also set the think function to G_ExplodeMissile. While
this is redundant in this mod, if you set the think function of your
missile to anything else (for whatever reason) in your mod, this
will ensure that the next think the missile does is to
explode.
3. GIVING LIFE TO OUR MISSILES So now we have the
means of death, let's make a missile destructible! In this
case, I'm going to look at making a rocket vulnerable, though any
other sort of missile (grenade, bfg, plasma) can be changed in the
same way. Go to fire_rocket in g_missile.c, and find this
segment of code: bolt->nextthink = level.time + 10000;
bolt->think = G_ExplodeMissile;
Now add
the following:
bolt->nextthink = level.time + 10000;
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);
The
first thing we add is some health to the rocket. 5 is enough
for just about anything in Quake to kill it. We also must
allow this entity to takedamage in the game, and then we point the
die function pointer to our G_MissileDie function. These 3
lines were added primarily for the sake of the G_Damage function, so
that it will damage the rocket, subtract health, and call the die
function once it has been killed.
The next
five lines are for the sake of the trap_Trace function which just
about every weapon in the game uses. The first line gives the
object a solid body, so-to-speak. CONTENTS_BODY will intercept
all kinds of fire. You could choose CONTENTS_CORPSE here too,
but the only real difference is that the one launching the rocket
will be able to clip through it on the CONTENTS_CORPSE
setting. CONTENTS_BODY gives it a solid feel (eg: if you were
modifying a grenade, you'd feel like you were actually stepping on
something if you walked over it (I don't recommend walking over
grenades though)).
The next
four lines define a bounding box which sets the dimensions that the
trap_Trace function will clip against. Think of it as mins
being one corner of a box, and the maxs being the opposing
corner. Again, the values I used for these vectors were taken
from the Q2 Vulnerable
Rockets tutorial by Chris
Hilton. I also copy each vector to the corresponding
absmin/absmax vectors for safety's sake.
And for
testing, let's slow down the rockets so they makes an easy
target. Change the following line near the end of fire_rocket
from: VectorScale( dir, 900, bolt->s.pos.trDelta ); to: VectorScale( dir, 100, bolt->s.pos.trDelta );
4. IS THAT ALL THERE IS? This is actually all you need for your
basic vulnerable missile. However, if you try compiling and
running it, you will notice that if you fire a rocket and try to
shoot it down with your machinegun, it won't blow up! On the
otherhand, if you either blow something else up near it (like firing
a rocket at the ground), or load up some bots to fire rockets for
you to shoot, you will find that they *do* blow up. So we know
that our code is working, but why can't we shoot them down
ourselves?
Go into
g_weapon.c and take a look on the Bullet_Fire function at this
line: trap_Trace (&tr, muzzle, NULL, NULL, end,
ent->s.number, MASK_SHOT); This system call
traces a line for a certain distance and stops at the first entity
it encounters. Notice the second last paramter
though: ent->s.number This identifies the
client number, or essentially skips the owner of the entity.
This means you're not going to be able to shoot down your own
missiles! Change the line to: trap_Trace (&tr, muzzle, NULL, NULL, end,
ENTITYNUM_NONE, MASK_SHOT); Now your rockets
become a valid target (and technically so do you), but only for your
machinegun bullets. You'd have to change the trap_Trace
function for every weapon you want to be able to shoot your own
missiles down with. But since there's really no reason to
shoot at your own missiles, let's just use the machinegun this way
to look at our handiwork.
5. FINISHING TOUCHES Now if you compile and run, you'll be
able to shoot down your own rockets like the slow slugs they
are. But without any Bots hounding us, you might notice that
whenever your rocket hits a wall the "hit" sound gets played.
This seems out of place.
If you think
about it a little, you'll realize what's happening. The only
thing that's actually getting hit is your own rocket. The way
Quake 3 Arena seems to work is that the rocket explodes before the
entity is cleared away. Therefore, even though the rocket hits
a wall and explodes, it's within its own blast-radius and attempts
to damage itself. This registers a "hit"
sound.
This can
easily be fixed. A rocket can explode in two ways, either it
hits a wall or it explodes after its nextthink time is
reached. Open up g_missile.c again, and we'll add a single
line in two places.
In
G_MissileImpact look for: // 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;
} and add in: // 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; G_MissileImpact is
what happens whenever a missile hits anything, be it floor, wall or
Quake d00d. If we turn off takedamage here, it will ensure
that the only hit registered is that of another player being
hit.
Note that
this line is inserted after a bouncing object leaves the function,
so that grenades don't become indestructible after the first
bounce.
Now look in
G_Explode, right after the vector declarations:
vec3_t dir;
vec3_t origin; and add
in: vec3_t dir;
vec3_t origin;
// Lancer
ent->takedamage = qfalse; This is the only
other place that missiles seem to explode. Therefore, turn the
takedamage boolean off so we don't register a hit because of our
exploding missile.
If you
compile and run now, no longer will you hear those "bip" sounds when
your rockets impact walls. Now you're all set to try and
either dodge those incoming missiles, or shoot 'em down! Also, feel
free to set your rocket speed to whatever you want (since 100 is way
too slow in a practical game). To make grenades or other
missile objects shootable, just copy what you've got in fire_rocket
now and stick it into the corresponding missile's fire
function. Play with the vectors a bit, compile and blow 'em
up!
Shoot
straight and have
fun! |