Quake DeveLS - AirStrike

Author: Chris Hilton
Difficulty: Hard

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!

Client Variables

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.

Entry Point

'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.

Think_Airstrike

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.

Odds and Ends

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.

[Ed's note... there's an exercise for the reader ! If anyone would like to implement some of those improvements, send it in as a tutorial ! - SumFuka]

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

Chris Hilton .

This site, and all content and graphics displayed on it,
are ©opyrighted to the Quake DeveLS team. All rights received.
Got a suggestion? Comment? Question? Hate mail? Send it to us!
Oh yeah, this site is best viewed in 16 Bit or higher, with the resolution on 800*600.
Thanks to Planet Quake for their great help and support with hosting.
Best viewed with Netscape 4