TUTORIAL 11 - Vortex Grenades
by AssKicka
This tutorial will show you how to convert the grenade launcher's grenades into Vortex Grenades. Vortex Grenades ? They are simply grenades that suck players closer and when the player hits the grenade it explodes.
First we need to add a new utility function that can search the world to find a target that will be sucked into our vortex grenade. Open g_utils.c and add the bellow function to the end of the file :
/*
================
findradius
================
*/
gentity_t *findradius (gentity_t *ent, vec3_t org, float rad) {
vec3_t eorg;
int j;
if (!ent)
ent = g_entities;
else
ent++;
for (; ent < &g_entities[level.num_entities]; ent++)
{
if (!ent->inuse)
continue;
for (j=0; j<3; j++)
eorg[j] = org[j] - (ent->r.currentOrigin[j] +
(ent->r.mins[j] + ent->r.maxs[j])*0.5);
if (VectorLength(eorg) > rad)
continue;
return ent;
}
return NULL;
}
I did not code the findradius function but it has been re-done many times from the q2 days. Lets see if we can disect this function.
This function runs through all the entities in a level and checks if any entities are in the radius (rad) of the entity 'ent'. The 'ent' entity and 'rad' float are passed to this function from the calling function, you will see how this works in a second.
Now that our findradius is complete we need to add the function prototype in g_local.h. goto line 438 and add the code bellow :
gentity_t *findradius (gentity_t *ent, vec3_t org, float rad);
Now we will add our grenade think function, goto line 326 in g_missile.c and add the following function :
/*
=================
G_Suck
=================
*/
static void G_Suck( gentity_t *self ) {
gentity_t *target;
vec3_t start,dir,end;
target = NULL;
//check if there are any entity's within a radius of 500 units.
while ((target = findradius(target, self->r.currentOrigin, 500)) != NULL)
{
// target must not be vortex grenade
if (target == self)
continue;
// target must be a client
if (!target->client)
continue;
// target must not be the player who fired the vortex grenade
if (target == self->parent)
continue;
// target must be able to take damage
if (!target->takedamage)
continue;
// put target position in start
VectorCopy(target->r.currentOrigin, start);
// put grenade position in end
VectorCopy(self->r.currentOrigin, end);
// subtract start from end to get directional vector
VectorSubtract(end, start, dir);
VectorNormalize(dir);
// scale directional vector by 200 and add to the targets velocity
VectorScale(dir,200, target->client->ps.velocity);
// make targets move direction = to directional vector.
VectorCopy(dir, target->movedir);
}
self->nextthink = level.time + 20;
// check if vortext grenade is older than 20 seconds.
if (level.time > self->wait)
G_ExplodeMissile( self);
}
Now for the explanation :
The first thing we did is to create new three dimensional vectors, nl. start,dir and end, we will use these vectors when modifying the velocity of the affected entity. We also created a new entity type called 'target', we will use this to identify with the entity that will be sucked into the vortex grenade.
On line 12 you will see a while statement that checks if target is NOT NULL after target is returned from the findradius function (Rmember the findradius function we added earlier ?).
If target is NOT NULL (target has been found by the findradius function) we then do some checks on what entity the target is.
If the target is itself we continue the while statement (search for new target).
If the target is NOT a client then search for new target.
If the target is the entity (player) that fired the grenade then search for a new target.
If the target can't be damaged then search for a new target.
If NONE of the above conditions apply then we modify the velocity of the target to nudge it towards the grenade. Let's look at how we did this :
1) On line 26 we copied the target's current position into the 'start' vector.
2) On line 27 we copied the grenade's current position into the 'end' vector.
3) On line 28 we subtracted the 'start' vector from the 'end' vector to get the directional (dir) vector.
4) On line 30 we scaled the directional vector by 200 and put the result into the target velocity, this is what makes the target nudge closer to the grenade.
5) On line 31 we change the players direction to match the directional vector.
On line 35 we make sure our G_Suck function is executed in .02 seconds from now.
On line 37 we check if the grenade has been in the world for more than 20 seconds, if it has been we explode the vortext grenade, we have achived this my making the grenades self->wait = to 20000.
We will now change the fire_grenade function to use our G_Suck function as the grenades think function. Find the 'fire_grenade' function and modify the bellow code :
bolt = G_Spawn();
bolt->classname = "grenade";
bolt->nextthink = level.time + 2500;
bolt->think = G_ExplodeMissile;
to look like this
bolt = G_Spawn();
bolt->classname = "grenade";
bolt->nextthink = level.time + 1000; // call G_Suck in 1 second
bolt->think = G_Suck;
bolt->wait = level.time + 20000; // vortext grenade lifetime.
All we have done here is changed the think function of the grenade to G_Suck and set it to be called 1 second after the grenade is fired. We also set the bolt->wait to the current time + 20 seconds. This is the amount of time that will be allowed to pass before the grenade explodes, that's if a player has not been sucked into the grenade yet !
That's it, compile and enjoy ! |