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:

  1. Make it so pressing the "1" key once switches to the blaster, and pressing it again switches to the sword
  2. Change the sound effects so the user knows he's using the sword
  3. Change the impact-graphics from a bullet hit to sparks
  4. Right now, you must be touching an object to hit it. Lets extend the range to a sword-length away

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 highlander
edict_t *self - entity producing it, yourself
vec3_t start - The place you are
vec3_t aimdir - Where you are looking at in this case
int damage - the damage the sword inflicts
int 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 are
vec3_t aimdir - Where you are looking at in this case
int damage - the damage the sword inflicts
int kick - how much you want that bitch to be thrown back
Modified 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)

 

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 there great help and support with hosting.
Best viewed with Netscape 4