Ever find yourself in a cozy observing spot, watching the enemy stand
around like lemmings, and wishing you could call in an airstrike? Now
you can!

First, we'll need to keep track of some new information for the player.
This data won't need to be persistant across levels or anything, so we
simply add the variables to the end of the gclient_t structure at
approx. line 829 of g_local.h like so ('+' signs indicate lines added)

 	float		pickup_msg_time;
 
 	float		respawn_time;		// can respawn when time > this
+
+	// CCH: new variables for airstrikes
+	int		airstrike_called;
+	vec3_t		airstrike_entry;
+	float		airstrike_time;
+
 } gclient_t;
 
Now we can keep track of when an airstrike has been called, where it
should enter, and what time it should arrive.

'Where it should enter?' you might be thinking. You can't have
airstrikes just anywhere, they have to come from the sky. So when an
airstrike is called in, we want to make sure there's a bit of sky for
it to enter through. We'll talk about this more in just a minute.

Let's add our command for killing in the airstrike. To the
ClientCommand() function in g_cmds.c we add the following

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

This enables us to type "cmd airstrike" at the console to call in an
airstrike (you'll probably want to bind this command to a key). Using
this command calls the function Cmd_Airstrike_f(), which I've added
just before ClientCommand() in g_cmds.c like so

+/*
+=================
+Cmd_Airstrike_f
+CCH: new function to call in airstrikes
+=================
+*/
+void Cmd_Airstrike_f (edict_t *ent)
+{
+	vec3_t	start;
+	vec3_t	forward;
+	vec3_t	end;
+	trace_t	tr;
+
+	// make sure an airstrike hasn't already been called
+	if ( ent->client->airstrike_called )
+	{
+		gi.cprintf(ent, PRINT_HIGH, "The airstrike is already on its way.\n");
+		return;
+	}
+
+	// make sure we're pointed at the sky
+	VectorCopy(ent->s.origin, start);
+	start[2] += ent->viewheight;
+	AngleVectors(ent->client->v_angle, forward, NULL, NULL);
+	VectorMA(start, 8192, forward, end);
+	tr = gi.trace(start, NULL, NULL, end, ent, MASK_SHOT|CONTENTS_SLIME|CONTENTS_LAVA);
+	if ( tr.surface && !(tr.surface->flags & SURF_SKY) )
+	{
+		gi.cprintf(ent, PRINT_HIGH, "Airstrikes have to come through the sky!\n");
+		return;
+	}
+
+	// set up for the airstrike
+	VectorCopy(tr.endpos, ent->client->airstrike_entry);
+	ent->client->airstrike_called = 1;
+	ent->client->airstrike_time = level.time + 30;
+	gi.cprintf(ent, PRINT_HIGH, "Airstrike en route, ETA 30 seconds. Light up target.\n");
+}
+
 /*
 =================
 ClientCommand
 =================
 */

In this function, we first make sure that an airstrike hasn't already
been called. If one has, we let the player know and return.

Next, we make sure the player is pointed at the sky. This will be the
entry point of the airstrike into the game world. To do this, we'll
trace a line from the player's head to a point 8192 units ahead of it
(this should be more than long enough to find a surface to run into)
and check to see what we find. We use the vector start to define the
start point of this line, which is the player's origin plus his
viewheight. Then we use the AngleVectors() function to determine the
forward vector. Using VectorMA() gives us our end point, which is 8192
units from start in the direction forward. Then we use gi.trace() to
trace the line from start to end, stopping wherever a shot would stop or
if we run into slime or lava. This will fill tr with data about what the
trace ran into, but what we're concerned about here is the surface we
ran into. If that surface is not sky, we let the player know what the
'air' in airstrike means and return.

When we do find sky, we copy the trace's endpoint into our
airstrike_entry variable, set our airstrike_called variable, and set the
airstrike_time to 30 seconds in the future. Finally, let the player know
that the airstrike is on its way, it's time to target this thing!

So, how will we know when the airstrike arrives? We'll just modify the
ClientThink() function in p_client.c, which is called every client
frame, and add a little code at the end to check if the airstrike has
arrived.
 
 		}
 	}
 
+	// CCH: Check to see if an airstrike has arrived
+	if ( client->airstrike_called && level.time > client->airstrike_time )
+	{
+		client->airstrike_called = 0;
+		Think_Airstrike (ent);
+	}
 
 }

When the time comes, we set airstrike_called back to off and call our
Think_Airstrike() function which will deliver the payload to the game
world.

I've added this function to just after Think_Weapon() in p_weapon.c.
 
+/*
+=================
+Think_Airstrike
+CCH: This will bring the airstrike ordinance into existence in the game
+Called by ClientThink
+=================
+*/
+void Think_Airstrike (edict_t *ent)
+{
+	vec3_t	start;
+	vec3_t	forward;
+	vec3_t	end;
+	vec3_t	targetdir;
+	trace_t	tr;
+
+	// find the target point
+	VectorCopy(ent->s.origin, start);
+	start[2] += ent->viewheight;
+	AngleVectors(ent->client->v_angle, forward, NULL, NULL);
+	VectorMA(start, 8192, forward, end);
+	tr = gi.trace(start, NULL, NULL, end, ent, MASK_SHOT|CONTENTS_SLIME|CONTENTS_LAVA);
+
+	// find the direction from the entry point to the target
+	VectorSubtract(tr.endpos, ent->client->airstrike_entry, targetdir);
+	VectorNormalize(targetdir);
+	VectorAdd(ent->client->airstrike_entry, targetdir, start);
+
+	// check to make sure we're not materializing in a solid
+	if ( gi.pointcontents(start) == CONTENTS_SOLID )
+	{
+		gi.cprintf(ent, PRINT_HIGH, "Airstrike intercepted en route.\n");
+		return;
+	}
+
+	// fire away!
+	fire_rocket(ent, start, targetdir, 600, 550, 600, 600);
+	gi.cprintf(ent, PRINT_HIGH, "Airstrike has arrived.\n");
+}

We know where the payload will enter from, but we need to know what
direction it will travel. The player will act as a spotter for targeting
the payload, so wherever the player is pointing will be the destination.
We use gi.trace() just like we did when we called in the airstrike to
find the destination point.

Next, we subtract the entry and destination points to find the direction
the payload should travel. Directions should nearly always be normalized
(reduced to 1 unit in length) and we even add this normalized vector to
the airstrike_entry point so that the payload will spawn into a point
that is just a little bit off of the surface.

Finally, we use gi.pointcontents() to check the start point and make
sure we're not going to spawn in a solid (you never know where the
player has set the target point after 30 seconds). If it's solid, we
just let the player know that the payload didn't make it. Otherwise, we
fire a rocket using the fire_rocket() function but with 5 times the
normal damage and let the player know the payload has arrived.

All that's left now is a few bits of code to wrap everything up. To
g_local.h at approx. line 522 we need to add our Think_Airstrike()
function prototype.

 void ChangeWeapon (edict_t *ent);
 void SpawnItem (edict_t *ent, gitem_t *item);
 void Think_Weapon (edict_t *ent);
+
+// CCH: new prototype for function called when airstrike arrives
+void Think_Airstrike (edict_t *ent);
+
 int ArmorIndex (edict_t *ent);
 int PowerArmorType (edict_t *ent);
 gitem_t	*GetItemByIndex (int index);

Also, we shouldn't really allow dead players to act as spotters, so
we'll add the folliwing to the player_die() function at line 219 of
p_client.c.

 		}
 	}
 
+	// CCH: Call off the airstrike
+	self->client->airstrike_called = 0;
+
 // FIXME once we have death frames
 //	self->deadflag = DEAD_DYING;
 	self->deadflag = DEAD_DEAD;

That's it for now. Some ways this might be improved would be having an
actual sighting laser, ability to select different payloads (cluster
bombs, napalm, homing missile), perhaps randomizing the airstrike
delivery (never know when those pilots will show up), and adding a flyby
sound, like the one for the Strogg ship.

Have fun. Full source and patch file at http://www.jump.net/~dctank.

Chris Hilton
chilton@scci-ad.com