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:
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:
Now we can keep track of when an airstrike has been called, where it should enter, and what time it should arrive.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;
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:
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).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);
Using this command calls the function Cmd_Airstrike_f(), which should be added just before ClientCommand() in g_cmds.c like so:
What's changed?/* ================= 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 ================= */
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 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.
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.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);
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):
|
|