
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);
else { .... }
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)
{ // .... else { 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; return; } 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; else from++; 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) continue; 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:
#define STICKY_TRIGGERDIST 175
#define STICKY_THINKTIME 1
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; return; } 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, |