Quake DeveLS - AirStrike 2

Author: Jonathan Benson (Hardware)
Difficulty: Hard + 1

OK well I like what Chris Hilton had done so much I thought I'd improve on it a bit.  So I've taken his tute and I'll change it about a bit to suit the newer version and the 3.14 source code.  I hope he doesn't mind but I thought it would be the easiest and quickest way to create this tute.

If I haven't explained something it's probably because I've chopped out comments by Chris so you might like to take a look at the Airstrike tute to brush up first.

Anyway the improvements I've made are:

Some credit should also go to Dragon (sorry I don't remember your real name) who added that last bit and the initial sound file.

Key

Original Quake II code = red color
Chris Hilton's code (Airstrike) = blue color
My code = green color

Client Variables

This part of the tute doesn't change so we simply add the variables to the end of the gclient_t struct in g_local.h as before:

        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

Again no change, so let's add our command for calling in the airstrike. To the ClientCommand() function at the end of g_cmds.c we add the following:

        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    // anything that doesn't match a command will be a chat
                Cmd_Say_f (ent, false, true);
This enables us to type "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 should be added just before ClientCommand() in g_cmds.c like so:

/*
=================
Cmd_Airstrike_f
CCH: new function to call in airstrikes
JDB: modified 5/4/98
=================
*/
void Cmd_Airstrike_f (edict_t *ent)
{
       vec3_t  start;
       vec3_t  forward;
       vec3_t world_up;
       vec3_t  end;
       trace_t tr;


        // cancel airstrike if it's already been called
        if ( ent->client->airstrike_called )
        {
                ent->client->airstrike_called = 0;
                gi.cprintf(ent, PRINT_HIGH, "The airstrike has been called off!!\n");
                gi.sound(ent, CHAN_ITEM, gi.soundindex("world/pilot1.wav"), 0.4, ATTN_NORM, 0);
                return;
        }


       // see if 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) )
       {
        // We hit something but it wasn't sky, so let's see if there is sky above it!
                VectorCopy(tr.endpos,start);
                VectorSet(world_up, 0, 0, 1);
                VectorMA(start, 8192, world_up, end);
                tr = gi.trace(start, NULL, NULL, end, ent, MASK_SHOT|CONTENTS_SLIME|CONTENTS_LAVA);
                if ( tr.surface && !(tr.surface->flags & SURF_SKY))  // No sky above it either!!
                {
                        gi.cprintf(ent, PRINT_HIGH, "Airstrikes have to come from the sky!!!\n");
                        gi.sound(ent, CHAN_ITEM, gi.soundindex("world/pilot1.wav"), 0.4, ATTN_NORM, 0);
                        return;
                }
        }

       // set up for the airstrike
       VectorCopy(tr.endpos, ent->client->airstrike_entry);
       ent->client->airstrike_called = 1;
       ent->client->airstrike_time = level.time + 14;
       gi.cprintf(ent, PRINT_HIGH, "Airstrike on it's way! Light on the target. ETA 15 seconds.\n");
       gi.sound(ent, CHAN_ITEM, gi.soundindex("world/pilot3.wav"), 0.8, ATTN_NORM, 0);
}


 /*
 =================
 ClientCommand
 =================
 */
What's changed?

If the surface hit by the original trace is not sky, then we check directly above whatever we hit by tracing a line 8192 units (which is probably a little excessive, but it's a magic number so I stuck with it) up.  That way if there is sky directly above the target then the airstrike will come from there!   If you followed how the last trace worked (see the Airstrike tute) then this one shouldn't be a problem.  The only part that may be confusing is that we set it's start position to be the end of the last trace, ie whatever we hit.

I've also added some sound and reduced the inbound time from 30 to 15 seconds.  The sounds are all played on the ITEM channel (why not?) with varying volumes depending on their importance and annoyance factor!  :)  OK I guess I had better give a brief explanation of the gi.sound function so here goes:
This is taken straight from a reference of gi (game interface) functions found at http://www.quake2.com/dll/doc/index.html

Audio Commands

gi.soundindex (sound_filename)

during spawning it caches the sound, after that it simply returns the index which
refers to that sound

gi.sound (entity, channel, sound_index, volume, attenuation, time_ofs)
 
generates sound centered on given entity
channel specifies the sort of sound - a second sound of the same sort will overwrite the first
sound to play is given by sound_index, which is given by gi.soundindex
volume is between 0 and 1
attenuation gives how far away sound can be heard
time_ofs is time before playing sound? (not yet found anything other than 0)
 

Again no change so just modify the ClientThink() function in p_client.c as before:

                        client->weapon_thunk = true;
                        Think_Weapon (ent);
                }
        }
 
        // 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

There are a number of changes here so pay attention.  This function goes just after Think_Weapon() in p_weapon.c:

/*
=================
Think_Airstrike
CCH: This will bring the airstrike ordinance into existence in the game
JDB: Modified 5/4/98
Called by ClientThink
=================
*/

void Think_Airstrike (edict_t *ent)

{

       vec3_t  start;
       vec3_t  forward;
       vec3_t  end;
       vec3_t  targetdir;
       trace_t tr;
       trace_t tr_2;

       // 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 we have a clear line of fire
        tr_2 = gi.trace(start, NULL, NULL, tr.endpos, ent, MASK_SHOT|CONTENTS_SLIME|CONTENTS_LAVA);

        // check to make sure we're not materializing in a solid
       if ( gi.pointcontents(start) == CONTENTS_SOLID || tr_2.fraction < 1.0 )
       {
               gi.cprintf(ent, PRINT_HIGH, "Airstrike intercepted en route.\n");
               gi.sound(ent, CHAN_ITEM, gi.soundindex("world/pilot1.wav"), 0.8, ATTN_NORM, 0);
               return;
       }

       gi.sound(ent, CHAN_ITEM, gi.soundindex("world/pilot2.wav"), 0.9, ATTN_NORM, 0);
       gi.sound(ent, CHAN_AUTO, gi.soundindex("world/flyby1.wav"), 0.7, ATTN_NORM, 0);
       // fire away!
       fire_rocket(ent, start, targetdir, 700, 250, 300, 450);
       fire_rocket(ent, start, targetdir, 600, 450, 200, 430);
       fire_rocket(ent, start, targetdir, 400, 150, 400, 500);
       fire_rocket(ent, start, targetdir, 600, 210, 250, 500);
       fire_rocket(ent, start, targetdir, 300, 430, 200, 450);
       fire_rocket(ent, start, targetdir, 600, 240, 320, 480);
       gi.cprintf(ent, PRINT_HIGH, "Airstrike has arrived.\n");
}
The changes should be obvious: The check for a clear shot is a quick trace between the start point and what we are aiming at.  If the trace completes then it has reached whatever we are aiming at without hitting anything and tr_2.fraction will be equal to 1.0

The added sound effect for the flyby could possibly do with a little tweaking.  See if you can figure out what is wrong with it?  If not I hint at the problem at the end of the tute as the final suggested improvement.

Note the varying speeds, etc. of the new rockets.  This is so they don't all hit at exactly the same time, etc.

Odds and Ends

Again no changes so just as before go to g_local.h at approx. line 522 we need to add our Think_Airstrike() function prototype.

 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);
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.
                        gi.sound (self, CHAN_VOICE, gi.soundindex(va("*death%i.wav", (rand()%4)+1)), 1, ATTN_NORM, 0);
                }
        }

        // CCH: call off strike
        self->client->airstrike_called = 0;

        self->deadflag = DEAD_DEAD;
Once again, that's it for now.  :)

Actually the original suggestions by Chris for improvement still stand (with one slight variation):

Who knows I may actually get around to doing some of those, but don't hold your breath, use a rebreather!  :)

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