Adding a new weapon 

Adding a new weapon to Quake2 is surprisingly easy.  In this tutorial I'll explain the function and importance of the various weapon functions that need to be coded in order to work in a new weapon into Quake2.  I'll also go over how to add the weapon as a pick-up item that enables you to place the weapon in a map. 

The weapon this tutorial adds is pretty useless - it's a flaregun.  All it does is light up things and shoot sparks every once in a while.  The flaregun is not meant to be used or distributed; it is merely a basic weapon used to illustrate how to add new weapons. 

You'll need to get the .zip file that contains the modified models to do the tutorial. 

Basic Setup 

The actual .md2 models used by the flaregun already exist (except for the flare itself) in the models/weapons/v_flareg and models/weapons/g_flareg.  I've modified these models a little, and therefore we're going to have to use the modified models instead of the ones in the game.  To do this, we are going to duplicate the directory structure of quake2/baseq2 in our mod directory (let's call it quake2/flaremod).  It also contains the item icon for the flaregun (used while cycling through the itemlist using [ and ]). 

The content of the tutorial .zip file (see above) has this directory structure, so just unzip the whole thing to the drive that contains the quake2 folder.  You'll end up with a flaremod directory under /quake2. 

Weapon models 

There are two distinct models you will need to make for your weapon.  The first is the model that will be used to represent the weapon in the map - its the one that spins.  This model has only one frame, and goes in the /models/weapons/g_weaponname directory.  In our case, the model goes in /models/weapons/g_flareg. 

The second model is the one that you see when firing the weapon.  The model has four animations in it - enable (unholster), fire, idle, and disable (holster).  Check out some of the model sites around for info on creating models.  This model goes in the /models/weapons/v_weaponname directory.  In our case, it goes in /models/weapons/v_flareg. 

I also made a very simple model to use as the actual flare.  It goes in the /models/objects/flare directory. 

Itemlist icon 

The itemlist icon that appears when you scroll through the itemlist is specified in the g_item structure (which we will look at later).  It is a 23x23 .pcx, and as you can see I didn't spend too much time on mine :).  It goes in the /pics directory. 
  

Coding the new weapon 

Ok, here we go.  We're going to build the functions that make a weapon work in a "ground up" manner.  There are three functions that make a weapon work, and each are interdependant to each other. Here they are, in ascending order:
 

fire_<weaponname> 
Source file: g_weapon.c 
Placement: At end of file 
 
 
/*
 * Drops a spark from the flare flying thru the air.  Checks to make
 * sure we aren't in the water.
 */
void flare_sparks(edict_t *self)
{
    vec3_t dir;
    vec3_t forward, right, up;

    // Spawn some sparks.  This isn't net-friendly at all, but will
    // be fine for single player.
    //
  gi.WriteByte (svc_temp_entity);
  gi.WriteByte (TE_BLASTER);
  gi.WritePosition (self->s.origin);

   // If we are still moving, calculate the normal to the direction
    // we are travelling.
    //
    if( VectorLength(self->velocity) > 0.0 )
    {
      vectoangles (self->velocity, dir);
     AngleVectors (dir, forward, right, up);

   gi.WriteDir (up);
    }
    // If we're stopped, just write out the origin as our normal
    //
    else
    {
   gi.WriteDir (vec3_origin);
    }
  gi.multicast (self->s.origin, MULTICAST_PVS);
}

/*
   void flare_think( edict_t *self )

   Purpose: The think function of a flare round.  It generates sparks
            on the flare using a temp entity, and kills itself after
            self->timestamp runs out.
   Parameters:
     self: A pointer to the edict_t structure representing the
           flare round.  self->timestamp is the value used to
           measure the lifespan of the round, and is set in
           fire_flaregun blow.

   Notes:
     - I'm not sure how much bandwidth is eaten by spawning a temp
       entity every FRAMETIME seconds.  It might very well turn out
       that the sparks need to go bye-bye in favor of less bandwidth
       usage.  Then again, why the hell would you use this gun on
       a DM server????

     - I haven't seen self->timestamp used anywhere else in the code,
       but I never really looked that hard.  It doesn't seem to cause
       any problems, and is aptly named, so I used it.
 */
void flare_think(edict_t *self)
{
  // self->timestamp is 15 seconds after the flare was spawned.
  //
  if( level.time > self->timestamp )
  {
    G_FreeEdict( self );
  }
  // We're still active, so lets see if we need to shoot some sparks.
  //
  else
  {
    //If we're in water, we won't spark.  Otherwise we will...
    //
    if( !(gi.pointcontents(self->s.origin) & MASK_WATER) )
    {
      // We're not in water, so proceed as normal
      //
      flare_sparks( self );

    }
    else
    {
      // We're in water, so let's not glow anymore.
      //
      self->s.effects &= ~EF_BLASTER;
    }
  }
  // We'll think again in .2 seconds
  //
  self->nextthink = level.time+0.2;
}

void flare_touch( edict_t *ent, edict_t *other,
                   cplane_t *plane, csurface_t *surf )
{
  // Flares don't weigh that much, so let's have them stop
  // the instant they whack into anything.
  //
  VectorClear( ent->velocity );
}

void fire_flaregun (edict_t *self, vec3_t start, vec3_t aimdir,
                    int damage, int speed, float timer,
                    float damage_radius)
{
 edict_t *flare;
 vec3_t dir;
 vec3_t forward, right, up;

 vectoangles (aimdir, dir);
 AngleVectors (dir, forward, right, up);

 flare = G_Spawn();
 VectorCopy (start, flare->s.origin);
 VectorScale (aimdir, speed, flare->velocity);
 VectorSet (flare->avelocity, 300, 300, 300);
 flare->movetype = MOVETYPE_BOUNCE;
 flare->clipmask = MASK_SHOT;
 flare->solid = SOLID_BBOX;
  flare->s.effects |= EF_BLASTER; //make it glow
 VectorClear (flare->mins);
 VectorClear (flare->maxs);

 flare->s.modelindex = gi.modelindex ("models/objects/flare/tris.md2");
 flare->owner = self;
 flare->touch = flare_touch;
 flare->nextthink = FRAMETIME;
 flare->think = flare_think;
 flare->radius_dmg = damage;
 flare->dmg_radius = damage_radius;
 flare->classname = "flare";
  flare->timestamp = level.time + 15.0; //live for 15 seconds
 gi.linkentity (flare);
}
 

 

fire_flaregun is the lowest of the three required weapon functions.  fire_flaregun is responsible for spawning the projectile (if any) and determining who gets hurt because the weapon was fired.  It accepts a bunch of parameters that specify the entity firing the weapon, where the weapon is aiming, the damage it will do, and other weapon-specific parameters.

Looking at the code, you see that there are a couple of support functions.  
flare_think is the think function for the actual flare itself (not the gun).  It takes care of killing the flare after 15 seconds and other things.  flare_sparks is a function called by flare_think in order to make the flare shoot sparks.  The flare_touch function is called when the flare bumps into something.
 

weapon_<weaponname>_fire
Source file: p_weapon.c
Placement: At end of file
 
 
/*
 * Forward declaration for fire_flaregun(), which is defined in
 * g_weapon.c.
 */
void fire_flaregun (edict_t *self, vec3_t start, vec3_t aimdir, int damage,
                    int speed, float timer, float damage_radius);

/*
 * weapon_flaregun_fire (edict_t *ent)
 *
 * Basically used to wrap the call to fire_flaregun(), this function
 * calculates all the parameters needed by fire_flaregun.  Calls
 * fire_flaregun and then subtracts 1 from the firing entity's
 * cell stash.
 */
void weapon_flaregun_fire(edict_t *ent)
{
 vec3_t offset;
 vec3_t forward, right;
 vec3_t start;

 // Setup the parameters used in the call to fire_flaregun()
  //
  VectorSet(offset, 8, 8, ent->viewheight-8);
 AngleVectors (ent->client->v_angle, forward, right, NULL);
 P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);

 VectorScale (forward, -2, ent->client->kick_origin);
 ent->client->kick_angles[0] = -1;

 // Make the flaregun actually shoot the flare
  //
  fire_flaregun (ent, start, forward, 0, 800, 25, 0);

 // Bump the gunframe
  //
  ent->client->ps.gunframe++;

  PlayerNoise(ent, start, PNOISE_WEAPON);

 // Subtract one cell from our inventory
  //
  ent->client->pers.inventory[ent->client->ammo_index]--;
}

 

weapon_flaregun_fire is in the middle of the weapon function heirarchy.  It accepts one parameter, a pointer to the edict_t that is firing the weapon.  It is responsible for calling the weapon's fire_<weaponname> function and updating the ammo inventory (if necessary).  Because of this, it is important to remember to give a forward declaration for the fire_<weaponname> function in p_weapon.c.
 

Weapon_FlareGun
Source file: p_weapon.c
Placement: At end of file
 
 
/*
 * Weapon_FlareGun (edict_t *ent)
 *
 * This is the function that is referenced in the itemlist structure
 * defined in g_items.c.  It is called every frame when our weapon is
 * active.  It calls Weapon_Generic() to handle per-frame weapon
 * handling (like animation and stuff).  Haven't delved too deeply
 * into Weapon_Generic()'s responsiblities... if someone has insight
 * drop me a line :)
 */
void Weapon_FlareGun (edict_t *ent)
{
 static int pause_frames[] = {39, 45, 50, 53, 0};
 static int fire_frames[] = {9, 17, 0};

  // Check the top of p_weapon.c for definition of Weapon_Generic
  //
  Weapon_Generic (ent, 8, 13, 49, 53,
                  pause_frames,
                  fire_frames,
                  weapon_flaregun_fire);
}

 

Weapon_FlareGun is the top-level weapon function.  This function does some interesting things... First, it declares two arrays of integers - both of which I am not sure how they are used.  It then calls Weapon_Generic, which is used once per frame to handle weapon frames and other such things.  It passes the two integer arrays and a pointer to the weapon_flaregun_fire function, which is called when the user presses the attack button.
 

Finally, in order for Quake2 to see our new weapon, we need to add it to the item list that is built in  g_items.c.  We are going to add a new gitem_t structure in the middle of the list defined in g_items.c.  It should go right after the gitem_t structure that describes the bfg:

Source file: g_items.c
Placement: Right after the one for the BFG and before the ammo gitem_t's.
 
 
/*QUAKED weapon_flaregun (.3 .3 1) (-16 -16 -16) (16 16 16)*/
 

 { "weapon_flaregun",// class name
    Pickup_Weapon, // Function to use to pickup weapon
    Use_Weapon,  // Function to use to use weapon
    Drop_Weapon, // Function to use to drop weapon
    Weapon_FlareGun, //Function called every frame this weapon is active
    "misc/w_pkup.wav",//Sound to play when picked up
    "models/weapons/g_flareg/tris.md2",//Item model for placement on maps
    EF_ROTATE,//Flags
    "models/weapons/v_flareg/tris.md2",//Model player sees
    "w_flareg", //name of item icon in item list (minus .pcx)
    "Flare Gun", //Item name (ie use flare gun)
    0, // Count width (for timed things like quad)
    1, // Ammo per shot
    "Cells", // Type of ammo to use
    IT_WEAPON, // IT_WEAPON, IT_ARMOR, or IT_AMMO
    NULL, // userinfo? (void*)
    0, // tag
    "" //things to precache
 },

The code above is pretty self-explanatory.  It simply adds the flare gun as a new weapon type to Quake2.  Look at the gitem_s struct defined in the Quake2 source for in-depth info.

Aftermath

After cutting/pasting all this code into your own gamex86 source, compile and everything should go well.  Then start Quake2 with the +set game flaremod and let 'er rip.

I've included a map specifically built to use the flaregun, rob3.bsp, that was built by Spawn22 of Skinning for Dummies.  The skin for the flaregun was also written by Spawn22.  If you want to use the flaregun on a normal map, do a "give flare gun" and a "use flare gun" in order to use it.  You might also want to bind a key sequence to "use flare gun" so that you don't have to type it in every time you want to use it.

I hope this tutorial on adding new weapons gives you insight on how to add new weapons to Quake2.  Hopefully the new weapons you write will be more usefull than the flaregun :)  If you have any questions at all , don't hesitate to email me.

The next installment in this series will show you how to implement the lasergun I'm working on currently.  It should be pretty cool, so keep your eyes peeled to Inside3D.

-Fatty  

Inside 3D is ©opyrighted the Inside 3D team, 1997-'98. All rights reserved
Site Hosted by the one and only Telefragged The one-stop Quake page
Feedback? Suggestions? Flames? Hate mail? Send it to all of us now !
Any information found on these pages, may NOT be re-produced before mailing us.