
Quake DeveLS - Sword Of The Highlander Revisited
Author: Dan Eisner (DanE)
Continuation of code tutorial by: Patrick Wagstrom(Pridkett)
Difficulty: EASE/MEDIUM
This section assums you
have performed Pridkett's tutorial already.
Basically, you should now have a pretty decent sword function, thanks to
Pridkett, but we still have to do a bit more work before we can call it a full
weapon patch.
Lets say we would like to
do the following things:
Note:
Text in red
= Quake II's code
Text in
yellow = Pridkett's code
Text in
green =my code
Let's handle those step in the order they are above.
1. Number toggle weapons
When
you press a number key in order to switch weapons, all that you are really
doing is signaling an alias for the console command "use
<weapon>". ie, when you press "2" it is the same as typing
in "use Shotgun."
So, to change what happens
when you press a number, you have to change what happens when you type
"Use." That is done in the function "Cmd_Use_f" in the file
"g_cmds.c". It should be line 301 or so.
When you find it, change
the function so it looks like this:
/*==================Cmd_Use_f Use an inventory item==================*/void Cmd_Use_f (edict_t *ent){ int index; gitem_t *it; char *s; s = gi.args(); it = FindItem (s); if (!it) { gi.cprintf (ent, PRINT_HIGH, "unknown item: %s\n", s); return; } index = ITEM_INDEX(it); if (!ent->client->pers.inventory[index]) { gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s); return;}
//added 1-13-98 by Dan Eisner
//to allow multiple weapons from one keypress else if (!Q_stricmp(s, ent->client->pers.weapon->pickup_name)) { if (!Q_stricmp(s, "Blaster")) { it = FindItem ("Sword"); } else if (!Q_stricmp(s, "Shotgun")) { it = FindItem ("SuperShotgun"); } } //end added portion
it->use (ent, it);
}
What is this code doing?
Well, actually, its pretty simple. The original code, up to the new addition
first "it" to be the item you would like to use. It then checks first
to see if "it" is an item defined in the game, and then to see if you
have any of "it."
Normally, this would be
enough, and quake would proceed to use "it." However, we have added
an additional check. If "it" is already the weapon currectly being
used, then check to see if there is a special case for it. We have defined
special cases for the "Blaster" and the "Shotgun."
With this code, pressing "1" twice will arm the sword, and pressing
"2" twice will produce the Super Shotgun (assuming you have it). More
double-ups (or even triple-ups) can easily be created by adding additional if
statements.
2. Sound Effects
In order for Quake 2 to
play a sound, it must have that sound precached. This is done towards the end
of "g_items.c". Find the code which you modified earlier (it should
be around line 1350), and change the last line of weapon_sword.
/*QUAKED
weapon_bfg (.3 .3 1) (-16 -16 -16) (16 16 16)*/
{
"weapon_bfg",
Pickup_Weapon,
Use_Weapon,
Drop_Weapon,
Weapon_BFG,
"misc/w_pkup.wav",
"models/weapons/g_bfg/tris.md2", EF_ROTATE,
"models/weapons/v_bfg/tris.md2",
/* icon */
"w_bfg",
/* pickup */ "BFG10K",
0,
50,
"Cells",
IT_WEAPON,
NULL,
0,
/* precache */ "sprites/s_bfg1.sp2 sprites/s_bfg2.sp2 sprites/s_bfg3.sp2
weapons/bfg__f1y.wav weapons/bfg__l1a.wav weapons/bfg__x1b.wav
weapons/bfg_hum.wav"
},
//::Pridkett
/* weapon_sword
always owned, never in the world
*/
{
"weapon_sword",
NULL,
Use_Weapon,
//How to use
NULL,
Weapon_Sword,
//What the function is
"misc/w_pkup.wav",
NULL,
0,
"models/weapons/v_blast/tris.md2",
//The models stuff
"w_blaster",
//Icon to be used
"Sword",
//Pickup name
0,
0,
NULL,
IT_WEAPON,
NULL,
0,
"weapons/hgrenlb1b.wav
misc/fhit3.wav"
// The sound of the grenade bouncing. This is precached
// New
Sword impact sounds -- DanE
},
//!Pridkett
You can find sounds which are included in the Quake 2 .pak file (use you're
favorite .pak viewer to check them out), or you can create your own. I decided
to use ones which cam with Quake 2 so no extra downloading is neccessary.
Now that the sound files are accessable, lets go to where
we'll call them. That will be in the "sword.h" file, in the function
"fire_sword." Change this part to look like this (around line 64):
if (!(tr.fraction <1.0)) //I can only assume this has something to do //with the progress of the trace { vectoangles(aimdir,dir); AngleVectors(dir,forward,right,up); //possibly sets some of the angle vectors //as standards? VectorMA (start, 8192, forward, end); //This does some extension of the vector... //note how short I have this attack going } //The fire_lead had an awful lot of stuff in here dealing with the effect of the shot //upon water and whatnot, but a sword doesn't make you worry about that sort of stuff //thats why highlanders are so damn cool. if (!((tr.surface) && (tr.surface->flags & SURF_SKY)))
{
if (tr.fraction <1.0) { if (tr.ent->takedamage)
{
//This tells us to damage the thing that in our path...hehe
T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, 0);
gi.sound (self, CHAN_AUTO,
gi.soundindex("misc/fhit3.wav") , 1, ATTN_NORM, 0);
// Sound if hit player/monster -- added
1-13-98 by DanE
}
else
{
if (strncmp (tr.surface->name, "sky", 3) != 0)
{
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_GUNSHOT);
gi.WritePosition (tr.endpos);
gi.WriteDir (tr.plane.normal);
gi.multicast (tr.endpos, MULTICAST_PVS);
/*if (self->client)
PlayerNoise(self, tr.endpos, PNOISE_IMPACT);*/
gi.sound (self, CHAN_AUTO, gi.soundindex("weapons/grenlb1b.wav") , 1, ATTN_NORM, 0);
//Sound if hit wall -- added 1-13-98 by DanE
}
}
}
}
return;
}
I'm not entirely sure how the "PlayerNoise" section which is commented out works, although I do understand that it makes the machinegun impact noise at random (??) impacts with the wall. Since we want a different sound to be made ALL the time, I replaced it with the function "gi.sound". This means that hitting walls with the sword will not alert monsters around the corner the way shooting the walls would (In theory), but that hardly matters.>
And that wraps up the sound effects.
3. Change the impact graaphics
When the sword hits a wall, we don't want it
to look like a bullet. Instead, lets change it to sparks. This change may be
easier than you suspect. Simply find the following code in the same function as
above and make it look like this:
if (strncmp (tr.surface->name,
"sky", 3) != 0)
{
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_SPARKS); //Changed 1-13-98 by
DanE to make impact look like sparks
gi.WritePosition (tr.endpos);
gi.WriteDir (tr.plane.normal);
gi.multicast (tr.endpos, MULTICAST_PVS);
/*if (self->client)
PlayerNoise(self, tr.endpos, PNOISE_IMPACT);*/
And that's it!
4. Adjust the range appropriately
This is the hardest part, which is why its last. In order
to do this, we must understand the code step-by-step. The function which
effects the range is "fire_sword," which is also the function we were
working with above. Lets take a look at it in detail.
Here is the original function with explanitory comments
/*=============fire_sword attacks with the beloved sword of the highlanderedict_t *self - entity producing it, yourselfvec3_t start - The place you arevec3_t aimdir - Where you are looking at in this caseint damage - the damage the sword inflictsint kick - how much you want that bitch to be thrown back=============*/ void fire_sword ( edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick){ //You may recognize a lot of this from the fire lead command, which //is the one that I understood best what the hell was going on trace_t tr; //Not entirely sure what this is, I know that it is used //to trace out the route of the weapon being used...gotta limit it// Used to figure out what is in front of you--ie, what your aiming at
vec3_t dir; //Another point I am unclear about vec3_t forward; //maybe someday I will know a little bit vec3_t right; //better about what these are // the vector pointing right from dir
vec3_t up; //The vector pointing up from dir
vec3_t end; /* Most of the above vectors are used in fire_lead to calculate the
* horozontal and vertical spread associated with the machine-guns.
* Since we don't use spread with the sword, we don't need many of
* those vectors. */
tr = gi.trace (self->s.origin, NULL, NULL, start, self, MASK_SHOT);// Checks to see what is in front of the player (self->s.origin),
// up till the point "self" In other words, this trace only has a
// range of 0
if (!(tr.fraction < 1.0)) //I can only assume this has something to do//with the progress of the trace
// if tr.fraction == 1.0 then the trace completed without hitting anything,
// otherwise, it hit something and stopped
{ vectoangles(aimdir,dir); AngleVectors(dir,forward,right,up); //possibly sets some of the angle vectors//as standards? //sets the vectors relative to dir
VectorMA (start, 8192, forward, end); //This does some extension of the vector...//note how short I have this attack going //adds the dispersal to the bullets
}/* The previous section applies only to machine-gun fire, and has no effect on our
* Highlander Sword, since it doesn't fire bullets. Therefore, we can eliminate
* it altogether. */
//The fire_lead had an awful lot of stuff in here dealing with the effect of the shot //upon water and whatnot, but a sword doesn't make you worry about that sort of stuff //thats why highlanders are so damn cool. if (!((tr.surface) && (tr.surface->flags & SURF_SKY))) // make sure we didn't hit the sky
{if (tr.fraction < 1.0) // make sure we didn't get out of range
{ if (tr.ent->takedamage) { //This tells us to damage the thing that in our path...hehe T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, 0); } else {if (strncmp (tr.surface->name, "sky", 3) != 0)//check (AGAIN!) if we hit the sky
{ gi.WriteByte (svc_temp_entity); gi.WriteByte (TE_GUNSHOT); gi.WritePosition (tr.endpos); gi.WriteDir (tr.plane.normal); gi.multicast (tr.endpos, MULTICAST_PVS); if (self->client) PlayerNoise(self, tr.endpos, PNOISE_IMPACT); } } } } return;}
As you can see, there are a bunch of superflous things in
this function. Lets clean it up a bit, and put in the impact graphic and sound
effect modification we made earlier.
You will have to add "#define SWORD_RANGE 35"
with the other #define's like so (these are values I liked. You can play around
with them):
#define SWORD_NORMAL_DAMAGE 35#define SWORD_DEATHMATCH_DAMAGE 45#define SWORD_KICK 200
#define SWORD_RANGE 35
Now, here is the complete new function, for your
convenience:
/*=============fire_sword attacks with the beloved sword of the highlander(or use the blaster as a bludgeon) edict_t *self - entity producing it, yourself vec3_t start - The place you arevec3_t aimdir - Where you are looking at in this caseint damage - the damage the sword inflictsint kick - how much you want that bitch to be thrown backModified by DanE on 1-13-98=============*/
void fire_sword ( edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick){
trace_t tr; //detect whats in front of you up to range "vec3_t end"
vec3_t end;
// Figure out what we hit, if anything:
VectorMA (start, SWORD_RANGE, aimdir, end); //calculates the range vector tr = gi.trace (self->s.origin, NULL, NULL, end, self, MASK_SHOT); // figuers out what in front of the player up till "end" // Figure out what to do about what we hit, if anything
if (!((tr.surface) && (tr.surface->flags & SURF_SKY)))
{ if (tr.fraction < 1.0) { if (tr.ent->takedamage) { //This tells us to damage the thing that in our path...hehe T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, 0);gi.sound (self, CHAN_AUTO, gi.soundindex("misc/fhit3.wav") , 1, ATTN_NORM, 0);
}
else { gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_SPARKS);gi.WritePosition (tr.endpos);
gi.WriteDir (tr.plane.normal);gi.multicast (tr.endpos, MULTICAST_PVS);
gi.sound (self, CHAN_AUTO, gi.soundindex("weapons/grenlb1b.wav") , 1, ATTN_NORM, 0); }
} } return;} // 1-13-98 DanE
The most important new component is the VectorMA line. It
computes the "end" vector for the trace as SWORD_RANGE units in the
direction the player is currectly facing (aimdir). When the trace stops, the
weapon can no longer hit anything, so that is our range limitor.
If you replace this function, add the new #Define, and make
the other 2 changes listed, you are all set.
---------------------------
Of course, to make this a really cool patch, you'd have to
create a new mesh and your own sound effects for when the sword misses, but as
my old high school teacher used to say, that is beyond the scope of this course.
8-)
Author: Dan Eisner (DanE)