Quake DeveLS - Vulnerable Rockets

Author: Chris Hilton
Difficulty: Medium

I've written a couple of mods making rockets and grenades deadlier. But if you're on the receiving end of one of these beauties, you want to take some defensive action. You want to shoot that homing missile down. You want to blow up those proximity mines and detpipes BEFORE you step on them. Here's how.

Grenades...

Let's start with grenades. Open up g_weapon.c and make these changes to fire_grenade() ('+' signs indicate lines added)

 	grenade->dmg_radius = damage_radius;
 	grenade->classname = "grenade";
 
+	// CCH: a few more attributes to let the grenade 'die'
+	VectorSet(grenade->mins, -3, -3, 0);
+	VectorSet(grenade->maxs, 3, 3, 6);
+	grenade->mass = 2;
+	grenade->health = 10;
+	grenade->die = Grenade_Die;
+	grenade->takedamage = DAMAGE_YES;
+	grenade->monsterinfo.aiflags = AI_NOSTEP;
+
 	gi.linkentity (grenade);
 }
These extra attributes have been copied out of the SP_misc_explobox() function that creates exploding barrels. First we use VectorSet to define the limits of the bounding box. Then we assign several new attributes to this grenade, including it's health value, a 'die' function to call when the health is depleted, and a takedamage value indicating this edict can take damage. Mass and monsterinfo.aiflags may not be necessary, but I've copied them from the other function to be sure.

Similar changes should be made to the fire_grenade2() function.

 	grenade->spawnflags = 1;
 	grenade->s.sound = gi.soundindex("weapons/hgrenc1b.wav");
 
+	// CCH: a few more attributes to let the grenade 'die'
+	VectorSet(grenade->mins, -3, -3, 0);
+	VectorSet(grenade->maxs, 3, 3, 6);
+	grenade->mass = 2;
+	grenade->health = 10;
+	grenade->die = Grenade_Die;
+	grenade->takedamage = DAMAGE_YES;
+	grenade->monsterinfo.aiflags = AI_NOSTEP;
+
 	if (timer <= 0.0)
 		Grenade_Explode (grenade);
 	else
So, what is this new Grenade_Die() function? Here you go. It's based on a similar function barrel_delay() used for exploding barrels. I've added this just before the fire_grenade() function.
+// CCH: When a grenade 'dies', it blows up next frame
+static void Grenade_Die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
+{
+	self->takedamage = DAMAGE_NO;
+	self->nextthink = level.time + .1;
+	self->think = Grenade_Explode;
+}
+
 void fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius)
 {
 	edict_t	*grenade;
You might be wondering why we have a separate die function, instead of calling Grenade_Explode() directly. The simple answer is they have conflicting parameters. The reason we modify the think attribute rather than calling Grenade_Explode() directly is that, well, Grenade_Explode() frees the edict, and apparently Quake 2 expects that edict to still exist when returning from the die function. So don't do this. It locks Quake 2 up, trust me.

Rockets...

That's it for grenades, now for rockets. Let's modify fire_rocket() like we did for grenades.

 	rocket->radius_dmg = radius_damage;
 	rocket->dmg_radius = damage_radius;
 	rocket->s.sound = gi.soundindex ("weapons/rockfly.wav");
+
+	// CCH: a few more attributes to let the rocket 'die'
+	VectorSet(rocket->mins, -10, -3, 0);
+	VectorSet(rocket->maxs, 10, 3, 6);
+	rocket->mass = 10;
+	rocket->health = 10;
+	rocket->die = Rocket_Die;
+	rocket->takedamage = DAMAGE_YES;
+	rocket->monsterinfo.aiflags = AI_NOSTEP;
 
 	if (self->client)
 		check_dodge (self, rocket->s.origin, dir, speed);
The only adjustment here is that the bounding box is slightly slarger and the mass is heavier.

Now, here's the other new rocket functions.

+// CCH: Explode rocket without touching anything
+static void Rocket_Explode (edict_t *ent)
+{
+	vec3_t		origin;
+
+	if (ent->owner->client)
+		PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT);
+
+	// calculate position for the explosion entity
+	VectorMA (ent->s.origin, -0.02, ent->velocity, origin);
+
+	T_RadiusDamage(ent, ent->owner, ent->radius_dmg, NULL, ent->dmg_radius);
+
+	gi.WriteByte (svc_temp_entity);
+	if (ent->waterlevel)
+		gi.WriteByte (TE_ROCKET_EXPLOSION_WATER);
+	else
+		gi.WriteByte (TE_ROCKET_EXPLOSION);
+	gi.WritePosition (origin);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	G_FreeEdict (ent);
+}
+
+// CCH: When a rocket 'dies', it blows up next frame
+static void Rocket_Die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
+{
+	self->takedamage = DAMAGE_NO;
+	self->nextthink = level.time + .1;
+	self->think = Rocket_Explode;
+}
+
 void fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage)
 {
 	edict_t	*rocket;
The Rocket_Die() is similar to Grenade_Die(), and we've added a Rocket_Explode() function because rockets usually only explode on touch. So, we've just taken the rocket_touch() function and cut out some extraneous code having to do with the object touched. When we explode, we just deal the damage, play the appropriate sound and get out.

I don't have a model viewer, so I've just guessed at the bounding box sizes used. Feel free to correct them. I haven't had any access to multiplayer games and it's kind of hard to get the monsters to shoot down my shots, but I've definitely had success blowing up grenades before their time. Let me know how it goes for you.

Full source and patch file at http://www.jump.net/~dctank.

Chris Hilton .

This site, and all content and graphics displayed on it,
are ©opyrighted to the Quake DeveLS team. All rights received.
Got a suggestion? Comment? Question? Hate mail? Send it to us!
Oh yeah, this site is best viewed in 16 Bit or higher, with the resolution on 800*600.
Thanks to Planet Quake for their great help and support with hosting.
Best viewed with Netscape 4