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