Let's modify our rockets to become homing missiles.  To pay for this
behavior, we're going to take an additional 5 cells from the player.
Also, we'd like for the player to be able to turn the homing missile
behavior on and off.

I'll start with being able to turn the missile behavior on and off.
Each player should be able to toggle this behavior independently
and the state should be maintained across levels. This means we'll add
a homing_state variable to the 'client persistant data', which every
player has and is maintained across level changes. client_persistant_t
is defined at line 719 of g_local.h, and I've added a homing_state
variable below (+'s indicate new lines added).

 	int			max_slugs;
 
 	gitem_t		*weapon;
+
+	// CCH: new persistant data
+	qboolean	homing_state;	// are homing missiles activated
+
 } client_persistant_t;
 
Now that we've defined the variable, we should initialize it like a good
programmer. InitClientPersistant() is defined at line 236 of p_client.c,
to which we add

 	client->pers.max_grenades	= 50;
 	client->pers.max_cells		= 200;
 	client->pers.max_slugs		= 50;
+
+	// CCH: initialize homing_state to off
+	client->pers.homing_state	= 0;
 }

Okay, now let's provide the player a way to toggle this variable on and
off. Console command functions are located in g_cmds.c, to which we add
our new function for toggling homing_state, as below
 
+/*
+=================
+Cmd_Homing_f
+CCH: whole new function for adjusting homing missile state
+=================
+*/
+void Cmd_Homing_f (edict_t *ent)
+{
+	int		i;
+
+	i = atoi (gi.argv(1));
+
+	switch (i)
+	{
+	case 0:
+		gi.cprintf (ent, PRINT_HIGH, "Homing missiles off\n");
+		ent->client->pers.homing_state = 0;
+		break;
+	case 1:
+	default:
+		gi.cprintf (ent, PRINT_HIGH, "HOMING MISSILES ON\n");
+		ent->client->pers.homing_state = 1;
+		break;
+	}
+}

When this function is called, it gets the first argument to the command
by calling gi.argv(1) and converting it to an integer with atoi(). The
homing_state variable for this player is set appropriately and a
message is printed to the player letting them know the homing missile
state. Now, we need to add a way to call this function by editing the
ClientCommand() function, also located in g_cmds.c, like so

 		Cmd_PutAway_f (ent);
 	else if (Q_stricmp (cmd, "wave") == 0)
 		Cmd_Wave_f (ent);
+
+	// CCH: new 'homing' command
+	else if (Q_stricmp (cmd, "homing") == 0)
+		Cmd_Homing_f (ent);
+
 	else if (Q_stricmp (cmd, "gameversion") == 0)
 	{
 		gi.cprintf (ent, PRINT_HIGH, "%s : %s\n", GAMEVERSION, __DATE__);

At this point, you should be able to compile your DLL and issue the
'homing' console command and see the homing state messages. I've read
that you should be able to type the commands 'homing 0' and 'homing 1'
directly at the console, but I've always had to type 'cmd homing 0' and
'cmd homing 1'. Any info on what's up with this would be appreciated.

Finally, we're ready for the fun stuff. Here, we'll be mucking with
g_weapon.c. First, we'll add what our homing missile will be 'think'ing
every .1 seconds.

+// CCH: New think function for homing missiles
+static void homing_think (edict_t *ent)
+{
+	edict_t	*target = NULL;
+	edict_t *blip = NULL;
+	vec3_t	targetdir, blipdir;
+	vec_t	speed;
+
+	while ((blip = findradius(blip, ent->s.origin, 1000)) != NULL)
+	{
+		if (!(blip->svflags & SVF_MONSTER) && !blip->client)
+			continue;
+		if (blip == ent->owner)
+			continue;
+		if (!blip->takedamage)
+			continue;
+		if (blip->health <= 0)
+			continue;
+		if (!visible(ent, blip))
+			continue;
+		if (!infront(ent, blip))
+			continue;
+		VectorSubtract(blip->s.origin, ent->s.origin, blipdir);
+		blipdir[2] += 16;
+		if ((target == NULL) || (VectorLength(blipdir) < VectorLength(targetdir)))
+		{
+			target = blip;
+			VectorCopy(blipdir, targetdir);
+		}
+	}
+		
+	if (target != NULL)
+	{
+		// target acquired, nudge our direction toward it
+		VectorNormalize(targetdir);
+		VectorScale(targetdir, 0.2, targetdir);
+		VectorAdd(targetdir, ent->movedir, targetdir);
+		VectorNormalize(targetdir);
+		VectorCopy(targetdir, ent->movedir);
+		vectoangles(targetdir, ent->s.angles);
+		speed = VectorLength(ent->velocity);
+		VectorScale(targetdir, speed, ent->velocity);
+	}
+
+	ent->nextthink = level.time + .1;
+}

Whew. Let's break that down a bit. The while loop is used to locate
a target. We're using findradius to step through every entity (blip)
within 1000 units. Then we have a number of exclusion factors. For
instance, if the blip is not a monster or player (only players have
client defined), we're not interested in it. Also, we check that the
blip is not our owner, can take damage, has health to lose, is visible,
and is in front of us. If all this is true, we calculate blipdir, a
vector that points from the origin of our rocket to the origin of the
blip (its feet). Then we add 16 to the blipdir's Z value so that blipdir
points at some location above the feet. If a target hasn't been set yet,
this blip becomes our target. If a target has already been set, we
compare the distances and make this blip the target only if it is
closer. 

After the while loop, if we've found a target, we want to adjust our
direction (movedir) toward the target. We normalize the targetdir, which
changes its length to 1 regardless of its previous length, then scale it
down to a length of .2. We then add this small targetdir correction to
our current movement direction (which has a length of 1) and store it
back in targetdir. We want our movedir to always have a length of 1, so
targetdir is normalized again and copied into this rocket's movedir. We
then set the rocket's angles appropriate to the new direction with
vectoangles. Also, we obtain the rocket's speed and adjust the
rocket's velocity to have the same speed, but in the new movement
direction.

Finally, we set the rocket to call its think function again in one
tenth of z second.

Now that we have the think function down, we just have to set up the
rocket to use it by modifying the fire_rocket() function in g_weapon.c
like so (-'s indicate original lines that were removed)

 	rocket->s.modelindex = gi.modelindex ("models/objects/rocket/tris.md2");
 	rocket->owner = self;
 	rocket->touch = rocket_touch;
-	rocket->nextthink = level.time + 8000/speed;
-	rocket->think = G_FreeEdict;
+
+	// CCH: see if this is a player and if they have homing on
+	if (self->client && self->client->pers.homing_state)
+	{
+		// CCH: if they have 5 cells, start homing, otherwise normal rocket think
+		if (self->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] >= 5)
+		{
+			self->client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] -= 5;
+			rocket->nextthink = level.time + .1;
+			rocket->think = homing_think;
+		} else {
+			gi.cprintf(self, PRINT_HIGH, "No cells for homing missile.\n");
+			rocket->nextthink = level.time + 8000/speed;
+			rocket->think = G_FreeEdict;
+		}
+	} else {
+		rocket->nextthink = level.time + 8000/speed;
+		rocket->think = G_FreeEdict;
+	}
+
 	rocket->dmg = damage;
 	rocket->radius_dmg = radius_damage;
 	rocket->dmg_radius = damage_radius;

If they are a player and have homing_state turned on, we see if they
have the required 5 cells. If so, we remove 5 cells from the player's
inventory and set the homing_think function to be called. Otherwise, we
let the player know he/she doesn't have enough cells for a homing
missile. When not firing a homing missile, we fire a regular rocket
instead.

That's all there is to it. A possible exercise for the reader:
Modify the blip 'height adjustment' to take into account the size of
the blip.

Hope you've found this tutorial useful. Full source and patch files
available at http://www.jump.net/~dctank.

Chris Hilton
chilton@scci-ad.com

