Vulnerable Missiles

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!