PlanetQuake | Code3Arena | Tutorials | << Prev | Tutorial 18 | Next >>

TUTORIAL 18 - Vortex Grenades II
by TeknoDragon

This is a follow-up to AssKicka's Vortex Grenades Tutorial, that beautifies the grenade's effects and may speed up the algorithm in high load situations with a lot of players tossing grenades. This tutorial assumes that you have just stepped out of the Vortex Grenades Tutorial, so if you haven't done that get over there!

I'll also go over a few things that make good development practice, they are a composite of personal experience and hours in a few Comp. Sci. classes at WSU.

#### 1. Simple Tweaks

Now that you have vortex grenades there are a few issues to consider if you want to optimize this mod. First you can change the nextthink of the grenades so that they aren't calling G_Suck 50 times a second, instead maybe 10 or 20.

Go to the G_Suck function in g_missile.c and scroll down to this line:

```self->nextthink = level.time + 20;
```
You can change the 20 to something larger (say 100 -- that's 10 times per second) to make G_Suck suck up less process. This doesn't change the rate that players are sucked towards the grenade because G_Suck doesn't add the grenade's velocity to yours, it replaces it.

Looking at how vortex grenades move the player (through modifying target->client->ps.velocity), you can find what else modifies the player motion and what it does to beautify the movement. If you use MS Visual Studio to search though the source files you'll find client->ps.velocity in several functions, including G_Damage in g_combat.c seen here:

```

// set the timer so that the other client can't cancel
// out the movement immediately

if ( !targ->client->ps.pm_time ) {
int		t;
t = knockback * 2;
if ( t < 50 ) {
t = 50;
}

if ( t > 200 ) {
t = 200;
}

targ->client->ps.pm_time = t;
targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
}
```
You'll notice that immediately after the kick velocity is added to the client a special timer is set so that the movement isn't immediately negated. We'll add something like this into our G_Suck function to beautify the effect that 2 grenades have on each other. In the original Vortex Grenades tutorial says that VectorScale adds the scaled vector to the result, but if you look at the code of _VectorScale (the unoptomized version of the VectorScale #define macro -- both in g_math.c) you'll see that it doesn't! We can change this so that it does add the velocity, but in affect that means that we're accelerating towards the grenade! This feels more natural to a player since acceleration is a natural affect of forces and it will jerk them around less.

First we need to add a new variable: vec3_t kvel, since dir is reused at the end of the function we shouldn't mess with it. We should also make a few #defines so that we can change the G_Suck effects more easily.

At the beginning of G_Suck change this:

```vec3_t start,dir,end,kvel;
```
```#define GSUCK_TIMING	50			// the think time interval of G_Suck
#define GSUCK_VELOCITY	200			// the amount of kick each second gets
```
Now you can change GSUCK_TIMING to affect the times per second that G_Suck pulls a player back. Finally to correct this little bug and add in the kick timer let's change the G_Suck code:
```// scale directional vector by the kick factor and add to the targets velocity
VectorScale(dir,GSUCK_VELOCITY / GSUCK_TIMING, kvel);

// add the kick velocity to the player's velocity

// set the timer so that the other client can't cancel
// out the movement immediately
if ( !target->client->ps.pm_time ) {
targ->client->ps.pm_time = GSUCK_TIMING - 1;

/* the next G_Suck that works here will
probably be the one that worked before */

targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
}
```
Now compile and play around with it a bit, you'll notice a few changes.

#### 2. Fine Tuning It

The grenades might not seem so effective, so turn up the GSUCK_VELOCITY. I found that 2000 is just enough so that if there's only one grenade there you can still get away, but 2 will almost certainly kill you (remember they really add forces now).

The GSUCK_TIMING value is pretty important to balancing how good it looks vs. how much process it uses. If GSUCK_VELOCITY is very large and GSUCK_TIMING is very small players will be tossed back and forth across the grenade, so be careful with this extreme.

Next, you might even up the stakes and comment out these lines:

```
//	if (target == self->parent)
//		continue;```
Now you are affected too and can better tune the grenade's strength!

We see the VectorCopy call at the end of the function, but we don't see it in the G_Damage function, that's strange. I suppose you could comment out this last thing and then remove kvel from the beginning saving you about 12 bytes on G_Suck's stack, but that's not too bad in the great big scheme of things (and no, it doesn't unequivocally add up to be a per grenade penalty... only per G_Suck simultaneous calls, which don't happen that much unless you have a lot of grenades out there).

#### 3. The Big Fix

There is one big optimization that can be done. If you poke around in many of the core functions of the code you'll see stuff from syscalls.c a lot, particularly trap_Trace. This function serves to calculate impacts. There is another function that can serve us well: trap_EntitiesInBox. This function takes two vectors that define a bounding box and returns a list of entity numbers that are inside this region! We'll take the function G_KillBox as our example.
```void G_KillBox (gentity_t *ent) {
int			i, num;
int			touch[MAX_GENTITIES];
gentity_t	*hit;
vec3_t		mins, maxs;

num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );

for (i=0 ; iclient ) {
continue;
}

// nail it
G_Damage ( hit, ent, ent, NULL, NULL,
100000, DAMAGE_NO_PROTECTION, MOD_TELEFRAG);
}
}
```
100k damage, Ouch! You see the exact behavior or trap_EntitiesInBox here, so although there is little to no documentation on the syscalls you can still use them just like id does!

However we've got a circle, not a box! So we fudge the radius a bit and then double check to make sure everyone's inside. First we need some kind of a list like touch[MAX_GENTITIES] and a number. Add this line at the beginning of G_Suck:

```vec3_t start,dir,end,kvel,mins,maxs;
int targNum[MAX_GENTITIES],num;
```
Next add this define near the others:
```#define GSUCK_RADIUS 500
```
Now take out this line:
```while ((target = findradius(target, self->r.currentOrigin, 500)) != NULL) {
```
In it's place, put this:
```mins[0] = -GSUCK_RADIUS * 1.42;

num = trap_EntitiesInBox(mins,maxs,targNum,MAX_GENTITIES);
for(num--; num > 0; num--) {    // count from num-1 down to 0
target = &g_entities[targNum[num]];
```
```// target must be able to take damage
if (!target->takedamage)
continue;

// target must actually be in GSUCK_RADIUS
if ( Distance(self->r.currentOrigin,targ->r.currentOrigin) > GSUCK_RADIUS )
continue;
```
We multiply GSUCK_RADIUS by 1.42 because it describes the far corner, not just the closest edge of the square. Since distance is = sqrt( height^2 + width^2 ) the distance between a unit square's corners as well as the ratio of its width to its diagonal distance is sqrt( 2 ) or about 1.42!

Compile away! If you have less than the minimal requirements for Quake 3 then you will probably see some performance increase when there are a lot of people and a lot of vortex grenades.

#### 4. A Nasty Surprize!

One last thing that we can do is do another trap_EntitiesInBox to see if someone is right over the top of our grenade and then explode it on them! We'll reuse targNum and make another define:

```#define GSUCK_TRIGGER	32
```
At the very end of the function add these lines:
```mins[0] = -GSUCK_TRIGGER * 1.42;
mins[1] = -GSUCK_TRIGGER * 1.42;
mins[2] = -GSUCK_TRIGGER * 1.42;
maxs[0] = GSUCK_TRIGGER * 1.42;
maxs[1] = GSUCK_TRIGGER * 1.42;
maxs[2] = GSUCK_TRIGGER * 1.42;

num = trap_EntitiesInBox(mins,maxs,targNum,MAX_GENTITIES);
for(num--; num > 0; num--) {    // count from num-1 down to 0
target = &g_entities[targNum[num]];

// target must be a client
if (!target->client)
continue;

// target must not be the player who fired the vortex grenade
if (target == self->parent)		// makes sense here
continue;

// target must be able to take damage
if (!target->takedamage)
continue;

G_ExplodeMissile( self)			// EXPLODE goes the weasel!
}
```
I don't check the radius again here since it's so small, but you certainly can! However, it involves some heavy multiplication that's not necessarily optimized.

Now that we've done the same thing twice you could take the code that seems repeated and put it in another function. This will reduce average memory usage since you can reduce the time large variables like targNum are in memory. I'll leave the implementation up to you.

One final thing to consider: MAX_GENTITIES is 1<<10, i.e. 1024, that's 4k on the stack each time though the function. I changed mine to 64 instead, which is only 256. The tradeoff here is that if you get more than 64 entities (including rockets, players, plasma, and just about everything that moves) within GSUCK_RADIUS some of them will be tagged for suckage and the younger entities probably won't AFAIK (entity management is a bit of a mystery to me yet).

#### 5. Testing and Balancing

The absolutely best way to test and balance your mod is to get together with some Quake 3 fans on a LAN and get everyone's opinions on your work. These opinions are in fact more valuable than your own in the development process, because they hopefully represent the kinds of people that will spend time playing your mod.

You can go back and change the four #defines: GSUCK_TIMING, GSUCK_VELOCITY, GSUCK_RADIUS, and GSUCK_TRIGGER to tune up your mod in-between games when you're testing it. You'll also want to add another #define to easily change grenade damage. Add this line with your other #defines:

```#define GRENADE_DAMAGE	100		// bolt->damage for grenade
```bolt->damage = GRENADE_DAMAGE;