Created By: Tim Matthews
eMail t.n.matthews@mail.utexas.edu
Difficulty Scale Relatively Easy

Intro

This is a two part tutorial that modifies the blaster to "auto-aim" at enemies (i.e. You aim somewhere close to your enemy, and your blaster automatically adjusts the firing direction to hit the enemy dead on). The first part shows how to install the auto-aim feature. The second part installs a "target indicator", that will show you which enemy your auto-aiming blaster has it's sights on.

 

General Setup 

Same as always. Go here or here for more information.

   
Tutorial Code

Step 1
Open your quake2 project (or workspace or whatever)

Step 2
Open the "p_weapon.c" file. 

Step 3

Find the function named Blaster_Fire() and modify the following code inside the function:
1. You will have to add a new vector called firevec to the declaration list at the beginning of the function
So, 
   vec3_t	forward, right;
   vec3_t	start;
   vec3_t	offset;
becomes
   vec3_t	forward, right;
   vec3_t	start;
   vec3_t	offset;
   vec3_t firevec; /*Added: Direction to fire*/
2. Find the call to the function fire_blaster. replace the line:
  fire_blaster (ent, start, forward, damage, 1000, effect);
witht the lines:
  /*Added:Get LeadOff for nearest badguy*/
  mod_GetLeadoffVec(ent, start, 5000, 1000, false, firevec);
  fire_blaster (ent, start, firevec, damage, 1000, effect);
  /*End Modification*/
3. Now, paste the following code at the top of this "p_weapon.c" file:
/*******mod_dist_point_to_line***********

* Simply calculates the distance from a point to a line
* point: the point for which we wish to calculate the distance
* linepoint: a point on the line
* linedir: the direction of the line
*****************************************/
float mod_dist_point_to_line(vec3_t point, vec3_t linepoint, vec3_t linedir)
{
	vec3_t temp, temp2;

	VectorSubtract(point, linepoint, temp);
	CrossProduct(temp, linedir, temp2);

	return VectorLength(temp2)/VectorLength(linedir);
}



/*******mod_GetLeadoffVec****************
* Author: Tim Matthews
* NOTE: This Code may be distributed and used freely as long as you give due credit to the author.
*
* Get's the direction in which to shoot at an enemy, taking into account his
* current velocity
*
*Arguments
*	self: you
*	fireorigin: the point from which the blast originates
*	rad: the radius of the bounding sphere. Only consider monsters inside this sphere
*	projspeed: the speed of the projectile being fired
*	bydist: true if judging best target by distance, otherwise test by angle
*	firedir: the direction we want to calculate
*************************************/
edict_t *mod_GetLeadoffVec(edict_t *self, vec3_t fireorigin, float rad, float projspeed, int bydist, vec3_t firedir)
{
	edict_t *other; /*any potential target besides yourself*/
	edict_t *best /*best victim so far*/;

	vec3_t viewvec;  /*your line of site*/
	vec3_t guessdir;
	vec3_t bestvec; /*the best guess for the direction of your blaster fire*/
	float bestdist, dist;  /*distance of the other guy and the shortest distance encountered*/


	vec3_t temp, temp2, otherdir;
	float d, t;
	double alpha, beta, rho;
	double a, b, c, t1, t2;

	//gi.dprintf("Checking Targets\n");

	AngleVectors (self->client->v_angle, viewvec, NULL, NULL); /*get the view direction*/

	best=NULL;
	other=findradius(NULL, fireorigin, rad); /*find something*/

	while (other) {
		if ((other->svflags & SVF_MONSTER) && other->health>0 && other!=self) { /*might have to modify these*/
			if (visible(self, other) && infront(self, other)) {
				/*player is in front and visible*/

				/*calculate lead off*/
				VectorSubtract(other->s.origin, other->s.old_origin, otherdir);
				alpha=VectorNormalize(otherdir); /*alpha = speed,  otherdir=direction vector*/

				if (alpha>.05 && projspeed>0) { /*if speed is significant, this value may have to be changed*/
					d=mod_dist_point_to_line(fireorigin, other->s.origin, otherdir); /*distance from the firepoint to the
																																						the line on which the enemy is running*/
	
					beta=projspeed; /*our projectile speed*/

					VectorSubtract(fireorigin, other->s.origin, temp);
					CrossProduct(temp, otherdir, temp2);  //temp2 now holds the normal to a plane defined by fireorigin and other->s.origin
					CrossProduct(temp2, otherdir, temp);
					VectorNormalize(temp); /*temp holds the direction from the point to the line*/

					VectorScale(temp, d, guessdir);
					VectorAdd(guessdir, fireorigin, guessdir);
					VectorSubtract(guessdir, other->s.origin, guessdir);
					rho=VectorLength(guessdir); /*the length from other->s.origin to the point where the perpendicular vector from fireorigin intersects*/

					/*now, a little quadratic equation solving...*/
					a=alpha*alpha-beta*beta; 
					b=-2*alpha*rho;
					c=rho*rho+d*d;

					t1=(-b+sqrt(b*b-4*a*c))/(2*a);
					t2=(-b-sqrt(b*b-4*a*c))/(2*a);

					t=(t1>=0)?t1:t2; /*positive solution is the correct one*/
				
					VectorScale(otherdir, t*alpha, guessdir);
					VectorAdd(other->s.origin, guessdir, guessdir);
					VectorSubtract(guessdir, fireorigin, guessdir); /*now we have our best guess*/
				} else {
					/*enemy is standing still, so just get a simple direction vector*/
				  VectorSubtract(other->s.origin, fireorigin, guessdir); 
				}


				if (bydist) {
					dist=guessdir[0]*guessdir[0]+guessdir[1]*guessdir[1]+guessdir[2]*guessdir[2];

					if (!best || dist<bestdist) {
						best=other;
						VectorCopy(guessdir, bestvec);
						bestdist=dist;
					}
				} else {
					/*choose best as the person most in front of us*/
					VectorNormalize(guessdir);
					dist=DotProduct(viewvec, guessdir);

					if (!best || dist>bestdist) {
						best=other;
						VectorCopy(guessdir, bestvec);
						bestdist=dist;
					}
				}
			}
		}
		other=findradius(other, self->s.origin, rad); /*find the next entity*/
	}
	
	if (!best) /*No targets aquired, so just fire forward as usual*/
		AngleVectors(self->client->v_angle, firedir, NULL, NULL);
	else {
		//gi.dprintf("Target %s Aquired\n", best->classname);
		VectorCopy(bestvec, firedir);
		VectorNormalize(firedir);
	}

	return best;
}
Step 4

That's it for part one. If you are going to skip part two, you can read the exlpaination of the precceding code at the

bottom of the page.
Step 5

For part two, you will need to add the following code to the top of file "p_client.c":

/*Added*/

edict_t *targetentity=NULL;

edict_t *mod_GetLeadoffVec(edict_t *self, vec3_t fireorigin, float rad, float projspeed, int bydist, vec3_t firedir);

static void mod_TargetThink(edict_t *ent)
{
vec3_t firedir;
edict_t *best=mod_GetLeadoffVec(ent->owner, ent->owner->s.origin, 5000, 1000, false, firedir);

if (!best) {
VectorCopy(ent->owner->s.origin, ent->s.origin);
ent->s.origin[2]+=40;
} else {
VectorCopy(best->s.origin, ent->s.origin);
ent->s.origin[2]+=40;
}

ent->nextthink = level.time + .1; //Need to think again in .1 seconds

gi.linkentity (ent); /*relink because the position changed*/
}

void mod_CreateTargetEntity(edict_t *owner)
{
targetentity = G_Spawn();

targetentity->owner=owner; //this holds the owner;

VectorCopy (owner->s.origin, targetentity->s.origin);
targetentity->movetype = MOVETYPE_NOCLIP;
targetentity->clipmask = MASK_PLAYERSOLID;
targetentity->solid = SOLID_NOT;
VectorClear (targetentity->mins);
VectorClear (targetentity->maxs);
targetentity->s.modelindex = gi.modelindex ("models/objects/rocket/tris.md2");
targetentity->touch = NULL;
targetentity->nextthink = level.time + .1;
targetentity->think = mod_TargetThink;

gi.linkentity (targetentity);
}

Step 6



You will need to find the procedure void ClientBegin (edict_t *ent, qboolean loadgame) in the file same file  (p_client.c)

After the first line:
int i;
add the following:
/*Added:	*/
mod_CreateTargetEntity(ent);
Step 7

That's it! Compile the code and run the game with the modified DLL. Start a new game, find a bad guy, and kill him with
your new blaster.
   
Ending

The first part of ths tutorial is pretty straight forward. We introduced the mod_GetLeadoffVec function that will calculate the direction that you should fire in order to hit the nearest enemy. Then, we just modified the Blaster_Fire() function so that it will fire the blaster in the direction predicted by mod_GetLeadoffVec. There's really not much to be said about the modifications to Blaster_Fire() they should be obvious.

The function mod_GetLeadoffVec is probably a little more complicated than the functions you are used to seeing in these tutorials. It's not really that bad, it's really just a bunch of math. Basically, all it does is cycle through all entities within the specified radius. For each entity it encounters, it checks to see if it is an enemy (who is also alive). If that enemy is visible and in front of you, then the position of the enemy where your projectile would hit him is recorded. (The function takes into consideration the enemy's velocity, as well as your projectile's velocity). After all enemies have been considered, the closest one is chosen as the target. So, this function will calculate the "lead off" that is necessary to hit the target as it is moving.

More about mod_GetLeadoffVec(): This function is probably not the most efficient at calculating the leadoff. It was thrown together relatively quickly, and will likely be modified in the future. The predicted leadoff can be inaccurate at times. (Though the predicted firing direction is deadly accurate when the enemy is slow or standing still).

Part 2: This part is a little more involved, but still pretty simple. What we did in part 2 was to create an entity that continually checks which target your blaster is aiming at. Then, it simply positions itself over that target's head.

The call to mod_CreateTargetEntity() was made from the ClientBegin() function so that the entity will be created at the beginning of every new level. Once the entity is created, it persists throughout the level.

We added the mod_CreateTargetEntity() function, which basically just spawns a new entity, sets some attributes (most importantly, the think function), and links the entity into the game.

The mod_TargetThink() function does the actual thinking for our target entity. This function calculates the same leadoff vector that your blaster would calculate if you where to fire it. It then positions itself over the enemy that your blaster would have aimed at. (If there are no enemies around, it positions itself over your head). Then, it sets the next time to think to .1 seconds from now. The last thing it does is call the gi.linkentity() function. This is necessary because the position of the target has changed. If you don't do this, the rocket will disapear when you move into different regions of a level.

 

Tutorial html coding modified by legion.


If it is created, then it is copyrighted. Quake 2 Tutorial #6 is (c)1997-98 by Tim Matthews
and the
Inside3D staff. The site is hosted by the one and only TeleFragged. Please direct any
flames, comments, or praises to the author. Any and all information found in this tutorial may
be used in any Quake modification provided that the author and the Inside3D staff are credited.