Quake DeveLS - Sticky Bomb

Author: Joel Skrepnek
Difficulty: Easy/Medium

Shadow Warrior??

Being a huge fan of 3d Realms, Shadow Warrior - especially deathmatch play - I thought it might be interesting to add Sticky Bombs to Quake 2. You know, the annoying little buggers that will stick to a surface, and explode when you come near them. A friend and I used to exclusively use the Stick Bombs in our deathmatch games. Anyway, the plan is to turn hand grenades into adhesive, proximity bombs. I just noticed that Chris Hilton has already submitted on tutorial on creating Proximity Mines. I thought about using his code as a base for the Sticky Bombs tutorial, just for the sake of consistency, but I decided against it - you can surely use his code as a base if you want, as they initially both do the same thing. Onward: I decided to move as much code as possible to a separate file. You don't have to do this by any means, but it makes management much easy in the future. I created a file called g_sticky.c and included it into my project (or make file). Again, it's up to you, but I decided to remove fire_grenade2 from g_weapon.c and moved it to g_sticky.c. Since that routine won't be used for anything but the sticky bombs, it made sense. Whenever a grenade is thrown (remember, we're talking about hand grenades here - the grenade launcher code remains the same), the fire_grenade2 routine is called. This initializes a grenade, and places it into the world. We have to make several modifications to this:

NOTE: + modified/added
- removed

	+1: grenade->takedamage = DAMAGE_YES;

	+2: grenade->touch = Sticky_Touch;

	+3: grenade->pain  = Sticky_Pain;

	+4: grenade->nextthink = level.time + 3;
	+5: grenade->think = Sticky_Think;
	+6: grenade->die = Sticky_Dead;

	-7: grenade->s.sound = gi.soundindex("weapons/hgrenc1b.wav");

	if (timer <= 0.0)
	+8:	Sticky_Explode (grenade);

Simple enough, ignoring the fact that we haven't coded any of these routines yet :). Quick explanation: The first line (+1) means that the grenade will be susceptible to damage when placed into the world. This will be useful, because it will allow for some interesting chain reaction explosions. The second, third, fifth and sixth (+2, +3, +5, +6) lines all add Sticky Bomb specific functions to the grenade. The third line is new, because the grenade previously could not take damage. The seventh line is removed from the function. It caused an annoying sound to play while the grenade was active, and that would spoil all the surprise of finding a bomb on the back side of a door, eh? The eighth line is modified to support the stick bombs. Everything else in that function can be left alone. One minor point: We have moved the fire_grenade2 function. Now, technically this doesn't cause a problem, because it is already prototyped within g_local.h, but it might be worth noting somewhere, for reference.

Now, lets add functions to g_sticky.c. First, Sticky_Touch. This function will be somewhat similar to grenade_touch, with one major exception: If the object has encountered a wall, then it should stop moving. Easily done:

static void Sticky_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
		// ....
			gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, -ATTN_NORM, 0);

		//Everything up to this point is exactly the same as grenade_touch (save the function //name

		// At this point, the grenade has hit a surface

		VectorClear (ent->velocity) ;
		VectorClear (ent->avelocity) ;

		// We don't want the grenade to be affected by the big G.
		ent->movetype = MOVETYPE_NONE;


	Sticky_Explode (ent);

Except for the function name, the code is exactly the same as grenade_touch up to the indicated point. At that point, we know that the grenade has struck a wall. Before, it would just bounce off ... but we want it to stick to the surface. Just for completeness, the grenades velocity is zeroed out. Finally, the movetype is changed to MOVETYPE_NONE, to prevent gravity from coming into play. This value could have been changed to MOVETYPE_FLY, though MOVETYPE_NONE seems to fit this case better. Both serve our purposes, so it's your choice. Next, let's create a pain function. Originally, when the pain function was called, I just simply called Sticky_Explode (to come). I ran into problems when 2 or more grenades where situated near each other. Think recursion: The first grenade explodes, then calls T_RadiusDamage, which in turns explodes the next grenade, which then calls the T_RadiusDamage function, and so on. Quake2 crashed out after 3 grenades, I think. The fix was simple. Instead of calling the explosion function, I just created a think function called Sticky_Die (to come), and told it to think in 0.1 seconds (ent->nextthink). This worked great:

void Sticky_Pain(edict_t *self, edict_t *other, float kick, int damage)
	self->think = Sticky_Die ;
	self->nextthink = level.time + 0.1 ;

Where the Sticky_Die function looks like this:
void Sticky_Die (edict_t *ent)
	if (ent)
		Sticky_Explode (ent) ;

Ok, lets created the brain function, appropriately labeled Sticky_Think. Actually, before we do that, lets create a utility function to search for players within a desired radius. The findradius function (in g_utils.c) works wonderfully for this, with only a few minor modifications. We'll call the new function Sticky_FindPlayer:

edict_t *Sticky_FindPlayer (edict_t *from, vec3_t org, float rad)
	vec3_t	eorg;
	int		j;

	if (!from)
		from = g_edicts;
	for ( ; from < &g_edicts[globals.num_edicts]; from++)
+		if(!from->client)
+			continue;
		for (j=0 ; j<3 ; j++)
			eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j])*0.5);
		if (VectorLength(eorg) > rad)
		return from;
	return NULL;

The two are very similar. The only change was made within the for loop (at the '+). Since we're solely looking for players, we can effectively ignore any other entities found within the given radius. Entities that are not players do not have a client structure (thus, ent->client == NULL). One more detail before the think function is made. I added two constants to g_sticky.c. STICKY_TRIGGERDIST and STICKY_THINKTIME:

These can be modified to suit your fancy. I found that 175 tended to be too great a proximity, in some cases. Now lets put this together into a think function:

static void Sticky_Think (edict_t *ent)
	edict_t *motion = NULL;	// is someone around?

	motion = Sticky_FindPlayer(motion, ent->s.origin, STICKY_TRIGGERDIST) ;

	if(motion == NULL || !visible(ent, motion))
		ent->nextthink = level.time + STICKY_THINKTIME;

	ent->think = Sticky_Die ;
	ent->nextthink = level.time + 0.1 ;

In Sticky_Think, a call is made to Sticky_FindPlayer. If a player is found (and is visible), the grenade is set to die. If not, it continues to search, in the desired amount of time.

Only one more function, Sticky_Explode, and the mod is complete. I won't bother pasting the code here, because it is exactly the same as Grenade_Explode. I did, though, create a Sticky_Explode function, and copied the code there, for the sake of consistency and future additions.

That's it. You know have fully functional Proximity Bombs, which stick to surfaces. A couple additions you may like to do:

- Create a unique model and merge it with the sticky bomb :)
- Add a warning sound to a bomb, to gives players 'some' notice (induces paranoia!).
- Allow players the ability to shoot grenades with weapons without a blast radius. I briefly looked into this, but couldn't find anything obvious.

Any comments, criticisms, or suggestions can be sent to me at boswell@bmts.com. You can find the source code (and a .dll) to g_sticky.c at my homepage (http://www.bmts.com/~boswell/home). Thanks should go out to Chris Hilton who took the time to write the first Proximity Bomb tutorial.

Tutorial by Joel Skrepnek .

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