Flame Thrower


 

Posted by JACKofAllTrades (24.93.247.*) on March 09, 1999 at 21:47:48:



Title: New flame thrower weapon for Quake2

Difficulty: Easy to moderate

By: JACKofAllTrades

Email: servoguy@hotmail.com

Date: 11-26-98

Note: Please give credit where credit is due. 

---------------------------------------------------------------

This tutorial will show you how to add a new weapon and ammo
items using the built in weapon handling framework of Quake2.
I will also attempt to explain (in some detail) how the guts
of weapon handling actually works.  Oh, yeah, you'll also get
a cool new flame thrower for your trouble.

 

What it does:
The flame thrower launches animated glowing fireballs that, when
they come in contact with an enemy, will "attach" themselves to
said enemy and engulf them in an animated pulsing fireball.  The
fireball will continue to burn for 10 seconds (continuously causing
damage) or until the victim dives into water or dies.  Jumping
into water will extinguish the fireball accompanied by an
appropriate "sizzle" sound.

 

How it does it:
I coded this new weapon using id's existing weapon handling
framework.  While delving into the underlying weapons/items
code seems like a daunting task at first (it did to me) it
is well worth the effort.  By adding weapons and items using
id's techniques you basically get a whole lot of functionality
for free:  new items show up in the inventory menu and are
selectable; pick up and drop items; out of ammo auto weapon
change; cheat using the "give" command; cycle through weapons
with "weapnext" and "weapprev" all work properly on the new
items with no added code!  Also map makers can put these new
items into maps and they will be spawned and behave properly,
again with no added code, just a side benefit of following the
"rules".

 

What you need:
Since we're dealing with a new weapon and ammo item you're
going to need a few model and sound files.  For my models I
just put a new skin on the BFG (thought it looked the most
like a flame thrower).  For the new ammo I used a model that
I found in pak0.pak that isn't used in the game and put a new
skin on it.  You can just substitute the BFG model and icon
etc. files to try out the flame thrower without any new files.
If you want, drop me a line and I'll send you a zip with all
the stuff I used.

 

OK, enough babbling, I'll try to explain the things that I think
are important throughout the tutorial.

Let's start in g_items.c, the heart of item/weapon handling:

 

=============== begin g_items.c modifications =================

Find the gitem_t itemlist[] array and just after the entry for
the BFG add our new flame thrower.  Note that the order that the
weapons appear in the itemlist[] declaration controls the order
which they appear in the inventory menu and the order used for
weapnext and weapprev.  If you want the flame thrower to be
elsewhere in the list, just insert the code at the appropriate
point.

---------------------------------------------------------------

/*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|IT_STAY_COOP,
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"
},

///////////////// begin new code /////////////////
{
/* this is the classname our new ent will have when spawned */

"weapon_flamethrower",

/* use id's generic weapon "touch" function */

Pickup_Weapon,

/* use id's generic weapon "use" function */

Use_Weapon,

/* use id's generic weapon "drop" function */

Drop_Weapon,

/*
this is the "weapon think" function for our new weapon
(we have to write this one ourselves).  This function
will get called every server frame when the flame thrower
is your current weapon.
*/

Weapon_Flamethrower,

/* sound played when weapon is picked up */

"misc/w_pkup.wav",

/*
the model to use for the world view.
note that if you don't have my new models
and just want to try out the flamethrower just
substitute "models/weapons/g_bfg/tris.md2"
*/

"models/weapons/g_flame/tris.md2",

/* attribute that makes the world model "sit 'n spin" */

EF_ROTATE,

/*
player view model.
note that if you don't have my new models
and just want to try out the flamethrower just
substitute "models/weapons/v_bfg/tris.md2"
*/

"models/weapons/v_flame/tris.md2",

/*
icon displayed when item is picked up or selected
in inventory.  Again, if you don't have my files
substitute "w_bfg"
*/

"w_flame",

/*
name displayed when item is picked up or displayed
in inventory, also the name used with the "use",
"give", or "drop" commands
*/

"Flamethrower",

0, /* N/A for weapons (only applies for ammo items) */
5, /* how much ammo this weapon uses per shot */

/* item name of the ammo that this weapon uses */

"Napalm",

/* flag that says this item is a weapon */

IT_WEAPON|IT_STAY_COOP,

NULL, /* ??? */
0,    /* N/A for weapons (only applies for ammo items) */

/*
auxilliary files that the game will
automatically precache for us
*/

"weapons/flame.wav weapons/douse.wav"
},
////////////// end new code //////////////////

---------------------------------------------------------------

Further down in the itemlist[] array find the entry for "ammo_slugs"
and add our new ammo item after it.

---------------------------------------------------------------

/*QUAKED ammo_slugs (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{
"ammo_slugs",
Pickup_Ammo,
NULL,
Drop_Ammo,
NULL,
"misc/am_pkup.wav",
"models/items/ammo/slugs/medium/tris.md2", 0,
NULL,
/* icon */ "a_slugs",
/* pickup */ "Slugs",
/* width */ 3,
10,
NULL,
IT_AMMO,
NULL,
AMMO_SLUGS,
/* precache */ ""
},


///////////////// begin new code /////////////////
{
/* item classname */

"ammo_napalm",

/* use id's generic ammo "touch" function */

Pickup_Ammo,

/* no "use" function for ammo items */

NULL,

/* use id's generic ammo "drop" function */

Drop_Ammo,

/* no "think" function for ammo items */

NULL,

/* sound played when ammo is picked up */

"misc/am_pkup.wav",

/*
the model to use for the world view.
note that if you don't have my new models
and just want to try out the flamethrower just
substitute any existing ammo model you want, for
example: "models/items/ammo/slugs/medium/tris.md2"
*/

"models/items/ammo/napalm/tris.md2",

/* ammo items don't need any "effects" */

0,

/* no player view model for ammo items */

NULL,

/*
icon displayed when item is picked up or selected
in inventory.  Again, if you don't have my files
substitute some other ammo icon.  "a_slugs" would
work fine.
*/

"a_napalm",

/*
name displayed when item is picked up or displayed
in inventory, also the name used with the "give"
or "drop" commands
*/

"Napalm",

/*
number of digits to display on the HUD when your
current weapon uses this type of ammo
*/

3,

/* number of ammo units you get when you pick up one of these */

30,

NULL, /* N/A for ammo (only applies for weapon items) */

IT_AMMO, /* flag that says this is an ammo item */

NULL, /* ??? */

AMMO_NAPALM,

"" /* no extra precache files required */
},
////////////// end new code //////////////////

---------------------------------------------------------------

While we're still in g_items.c scroll up and find the
Pickup_Bandolier() function and make the addition shown below.
Note that this change is optional, it's up to you whether you
want the bandolier to increase your Napalm carrying capacity.
 
---------------------------------------------------------------

if (other->client->pers.max_slugs < 75)
  other->client->pers.max_slugs = 75;
///////////////// begin new code /////////////////
if (other->client->pers.max_napalm < 180)
  other->client->pers.max_napalm = 180;
///////////////// end new code //////////////////

---------------------------------------------------------------

Now find the Pickup_Pack() function and make the additions shown
below.  Again, this change is optional, it's up to you whether you
want the ammo pack to increase your Napalm carrying capacity.  The
first section of code just increases the amount of Napalm you can
carry, the second section actually gives you some.
 
---------------------------------------------------------------

if (other->client->pers.max_slugs < 100)
  other->client->pers.max_slugs = 100;
///////////////// begin new code /////////////////
if (other->client->pers.max_napalm < 240)
  other->client->pers.max_napalm = 240;
///////////////// end new code //////////////////


item = FindItem("Slugs");
if (item)
  {
  index = ITEM_INDEX(item);
  other->client->pers.inventory[index] += item->quantity;
  if (other->client->pers.inventory[index] > other->client->pers.max_slugs)
    other->client->pers.inventory[index] = other->client->pers.max_slugs;
  }

///////////////// begin new code /////////////////
item = FindItem("Napalm");
if (item)
  {
  index = ITEM_INDEX(item);
  other->client->pers.inventory[index] += item->quantity;
  if (other->client->pers.inventory[index] > other->client->pers.max_napalm)
    other->client->pers.inventory[index] = other->client->pers.max_napalm;
  }
///////////////// end new code //////////////////


---------------------------------------------------------------

Next find the Add_Ammo() function and make the addition shown
below.  This change just makes sure you're limited on how much
Napalm you can carry.
 
---------------------------------------------------------------

else if (item->tag == AMMO_SLUGS)
  max = ent->client->pers.max_slugs;
///////////////// begin new code /////////////////
else if (item->tag == AMMO_NAPALM)
  max = ent->client->pers.max_napalm;
///////////////// end new code //////////////////
else
  return false;


---------------------------------------------------------------

The last thing you need to do in g_items.c is to prototype our
new Weapon_Flamethrower "think" function.  Near the top of the
file find this spot and add the indicated code.  This will keep
the compiler from raising hell when it processes the itemlist[]
array since we are forewarning it about the existance of our
new function.
---------------------------------------------------------------

void Weapon_BFG (edict_t *ent);
///////////////// begin new code /////////////////
void Weapon_Flamethrower (edict_t *ent);
///////////////// end new code ///////////////////

 

=============== end g_items.c modifications ===================

Now let's turn our attention toward p_weapon.c.

=============== begin p_weapon.c modifications ================

I chose the BFG model to use for the flame thrower because, out
of all the Q2 weapons, I thought it looked the most like a flame
thrower (I've never seen a *real* flame thrower so I could be
full of crap here).  Making a new model was out of the question
as my artistic skills are about nil.  I had one problem with the
BFG model; the firing animations didn't suit my new weapon at
all.  What to do?  Well, being a much better programmer than an
artist I decided to "fix it with code".

Making Weapon_Generic More Generic
----------------------------------

If you peruse down through p_weapon.c you'll find the "think"
function for each weapon defined in the itemlist[] array
(Weapon_Blaster, Weapon_Shotgun, etc.).  Each of these functions
does one thing: calls Weapon_Generic() with a big list of
parameters.  What does Weapon_Generic() do?  Glad you asked.
Weapon_Generic handles cycling through the animation frames for
the weapon view models and calls the weapon "fire" function on the
appropriate animation frame(s) of the "firing sequence".  The weapon
model files each contain four animation sequences: "activating"
(pulling up the weapon), "firing", "idle" (fidgit sequences),
and "deactivating" (putting the weapon away).  All the original
weapon models have the sequences stored in this order, i.e., the
first frame of the "activating" sequence is frame 0; the first
"firing" frame immediately follows the last "activating" frame; the
first "idle" frame immediately follows the last "firing" frame, etc.
The second, third, fourth, and fifth parameters passed to
Weapon_Generic() tell it where the divisions between animation
sequences occur by specifying the number of the last frame in
each sequence.  This is where I ran into trouble.  The "firing"
sequence of the BFG consists of a long charge up period followed
by a huge kick of the gun.  Just simply no good for a flame thrower.
I found that if I could skip a few frames at the beginning and
end of the "firing" sequence a much more realistic effect could
be obtained.  My first thought was to make a special, hacked-up
version of Weapon_Generic just for the flame thrower.  However,
no matter how I looked at it, it would be just that, a hack.
Then it dawned on me.  I'll just add a few more parameters to
Weapon_Generic so that I could tell it the ending AND starting
frames of each sequence.  That means I'd have to go back and fix
up all the places where Weapon_Generic was called, but, so be it;
it's definitely a cleaner solution.

Let's get on with it, shall we?  Open up p_weapon.c and make the
following changes.

---------------------------------------------------------------

Find Weapon_Generic() and comment out the #define statements just
before it.  Then replace the function header with the one shown
below.

---------------------------------------------------------------

/*
================
Weapon_Generic

A generic function to handle the basics of weapon thinking
================
*/
//#define FRAME_FIRE_FIRST (FRAME_ACTIVATE_LAST + 1)
//#define FRAME_IDLE_FIRST (FRAME_FIRE_LAST + 1)
//#define FRAME_DEACTIVATE_FIRST (FRAME_IDLE_LAST + 1)

void Weapon_Generic (edict_t *ent,
                     int FRAME_ACTIVATE_LAST,
                     int FRAME_FIRE_FIRST,
                     int FRAME_FIRE_LAST,
                     int FRAME_IDLE_FIRST,
                     int FRAME_IDLE_LAST,
                     int FRAME_DEACTIVATE_FIRST,
                     int FRAME_DEACTIVATE_LAST,
                     int *pause_frames,
                     int *fire_frames,
                     void (*fire)(edict_t *ent))

---------------------------------------------------------------

Now go through and fix up all the places where Weapon_Generic
is called.  Just replace the original functions with the ones
shown below.

---------------------------------------------------------------

void Weapon_GrenadeLauncher (edict_t *ent)
{
static int pause_frames[] = {34, 51, 59, 0};
static int fire_frames[] = {6, 0};

Weapon_Generic (ent, 5, 6, 16, 17, 59, 60, 64, pause_frames, fire_frames, weapon_grenadelauncher_fire);
}


void Weapon_RocketLauncher (edict_t *ent)
{
static int pause_frames[] = {25, 33, 42, 50, 0};
static int fire_frames[] = {5, 0};

Weapon_Generic (ent, 4, 5, 12, 13, 50, 51, 54, pause_frames, fire_frames, Weapon_RocketLauncher_Fire);
}


void Weapon_Blaster (edict_t *ent)
{
static int pause_frames[] = {19, 32, 0};
static int fire_frames[] = {5, 0};

Weapon_Generic (ent, 4, 5, 8, 9, 52, 53, 55, pause_frames, fire_frames, Weapon_Blaster_Fire);
}


void Weapon_HyperBlaster (edict_t *ent)
{
static int pause_frames[] = {0};
static int fire_frames[] = {6, 7, 8, 9, 10, 11, 0};

Weapon_Generic (ent, 5, 6, 20, 21, 49, 50, 53, pause_frames, fire_frames, Weapon_HyperBlaster_Fire);
}


void Weapon_Machinegun (edict_t *ent)
{
static int pause_frames[] = {23, 45, 0};
static int fire_frames[] = {4, 5, 0};

Weapon_Generic (ent, 3, 4, 5, 6, 45, 46, 49, pause_frames, fire_frames, Machinegun_Fire);
}


void Weapon_Chaingun (edict_t *ent)
{
static int pause_frames[] = {38, 43, 51, 61, 0};
static int fire_frames[] = {5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 0};

Weapon_Generic (ent, 4, 5, 31, 32, 61, 62, 64, pause_frames, fire_frames, Chaingun_Fire);
}


void Weapon_Shotgun (edict_t *ent)
{
static int pause_frames[] = {22, 28, 34, 0};
static int fire_frames[] = {8, 9, 0};

Weapon_Generic (ent, 7, 8, 18, 19, 36, 37, 39, pause_frames, fire_frames, weapon_shotgun_fire);
}


void Weapon_SuperShotgun (edict_t *ent)
{
static int pause_frames[] = {29, 42, 57, 0};
static int fire_frames[] = {7, 0};

Weapon_Generic (ent, 6, 7, 17, 18, 57, 58, 61, pause_frames, fire_frames, weapon_supershotgun_fire);
}


void Weapon_Railgun (edict_t *ent)
{
static int pause_frames[] = {56, 0};
static int fire_frames[] = {4, 0};

Weapon_Generic (ent, 3, 4, 18, 19, 56, 57, 61, pause_frames, fire_frames, weapon_railgun_fire);
}


void Weapon_BFG (edict_t *ent)
{
static int pause_frames[] = {39, 45, 50, 55, 0};
static int fire_frames[] = {9, 17, 0};

Weapon_Generic (ent, 8, 9, 32, 33, 55, 56, 58, pause_frames, fire_frames, weapon_bfg_fire);
}

---------------------------------------------------------------

Ok, now that we've fixed all the code that got broken when we
modified Weapon_Generic() let's get on with adding our flame
thrower.

Go down to the bottom of p_weapon.c and paste in these two functions
for the flame thrower.

---------------------------------------------------------------

/*
======================================================================

Flame Thrower

======================================================================
*/

void weapon_flame_fire (edict_t *ent)
{
vec3_t  offset, start;
vec3_t  forward, right;
int            damage;

if (is_quad) damage *= 4;

AngleVectors (ent->client->v_angle, forward, right, NULL);
VectorScale (forward, -2, ent->client->kick_origin);

VectorSet(offset, 16, 8, ent->viewheight-8);
P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
fireFlameThrower (ent, start, forward);

ent->client->ps.gunframe++;

PlayerNoise(ent, start, PNOISE_WEAPON);

if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
  ent->client->pers.inventory[ent->client->ammo_index] -= 5;
}


void Weapon_Flamethrower (edict_t *ent)
{
static int pause_frames[] = {39, 45, 50, 55, 0};
static int fire_frames[] = {25, 0};

Weapon_Generic (ent, 8, 25, 32, 33, 55, 56, 58, pause_frames, fire_frames, weapon_flame_fire);
}

=============== end p_weapon.c modifications ==================

---------------------------------------------------------------

Well, Ok, what do these functions do?  You'll remember
Weapon_Flamethrower from the code we added to the itemlist array
in g_items.c.  In there we added a pointer to this function.
The variable ent->client->pers.weapon points to the item in the
itemlist[] array which is your current weapon.  We really don't
have to worry about how a value gets assigned to
ent->client->pers.weapon as the existing weapon changing
functions take care of this and are generic enough to function
correctly on any new weapon (as long as we insert everything
properly in the itemlist[] array).  So, to recap, the ent for
each player has a pointer that points to a weapon item in the
itemlist[] array; each weapon item in the array has a pointer to
a "weaponthink" function that performs the actions appropriate
for that weapon; in our case, Weapon_Flamethrower.  The unraveling
of these pointers to pointers is done by Think_Weapon() which is
called every server frame for each player (client).  Ok, we know
how we got here, what does it do?  Looking at Weapon_Flamethrower
we see that all it does is call Weapon_Generic.  The first parameter
passed to Weapon_Generic is the ent of the player we are currently
processing.  The next seven parameters define, as we saw previously,
the start and end frames of each of the weapon animation sequences.
What the heck are pause_frames and fire_frames?  These are just
initialized arrays of ints; you'll notice that they always end
with a zero; this is how Weapon_Generic knows where the end of
the array is.  Ok, but you still haven't told me what they're
for.  Let's start with fire_frames.  If you scrutinize
Weapon_Generic you'll find that it keeps track of what "state"
the weapon is in.  This basically boils down to which of the four
animation sequences is currently running.  When the weapon is in
the "firing" state it runs through the fire_frames array to see
if any of the values match the current frame number of the weapon
model.  If it finds a match it calls the "fire" function (the last
parameter that we passed to Weapon_Generic) which in our case is
weapon_flame_fire.  This is how you synchronize the firing of
projectiles with the animation of the weapon view model.  Note
that since Weapon_Generic only looks at the fire_frames array
when it is running the "firing" animation it wouldn't make much
sense to specify frame numbers that don't belong to the "firing"
sequence.  Ok, let's move on to pause_frames.  When you're not
changing or firing your weapon, Weapon_Generic continuously cycles
through the "idle" animation frames.  This would seem pretty
artificial if you always twitched your fingers, scratched your
ass, or whatever, exactly the same way every time.  So, to make
the "idle" animation seem a little less repetitive we have
pause_frames.  This works similar to fire_frames in that it checks
the current frame number against the numbers in the array; if it
finds a match it temporarily pauses the animation for a random
amount of time.  I suppose this gives the illusion that there
are more "idle" frames than there really are.  The pause_frames
array just defines a list of frames where you will allow
Weapon_Generic to insert slight random pauses in the "idle"
animation.

Let's back up a bit and see how all of this fits into the big
picture.

Deep in the bowels of quake2.exe lies the core of the game server
code.  Every 0.1 seconds this server code decides that it's time
to update the state of the game "world".  It does this by calling
G_RunFrame() (in g_main.c).  G_RunFrame loops through all of the
entities in the game and runs their "think", "touch", etc., functions
as required.  The ents for players (clients) get treated a little
differently.  For these ents G_RunFrame calls ClientBeginServerFrame
which calls Think_Weapon (among other things). Think_Weapon looks
up the player's current weapon in the itemlist[] array and calls the
"think" function that it finds there (for the flame thrower this
would be Weapon_Flamethrower).  This function then calls
Weapon_Generic with a list of parameters appropriate for this weapon.
Weapon_Generic checks to see what "state" the weapon is in and
advances the weapon view model to the next animation frame if
necessary.  It also checks if the weapon is in the "firing" state;
if so, it looks to see if the frame number matches anything in the
fire_frames list.  If it finds a match, it calls the specified
"fire" function (for the flame thrower this would be
weapon_flame_fire).  This function will usually do some preliminary
set up work such as calculating the direction vector of the shot,
calculating the projectile start point so that shots fired appear
to come out of the gun model, etc.  Another function will then
(usually) be called (for the flame thrower this would be
fireFlameThrower) to take care of the work involved with spawning
new ents for the projectiles, i.e. setting up their "touch", 
"think", "die" functions, velocity, move_type, etc. 

We have now (finally) reached the end of the weapon code chain.

0.1 seconds later it starts all over again...

---------------------------------------------------------------

Now that we have all the basic gun stuff taken care of we need to
add the code that creates the fire balls and makes them function.

 


Follow Ups

 

Posted by JACKofAllTrades (24.93.247.*) on March 09, 1999 at 21:51:16:

In Reply to: TUTORIAL - Flame Thrower posted by JACKofAllTrades on March 09, 1999 at 21:47:48:



Open up g_weapon.c and follow me.

=============== begin g_weapon.c modifications ================

---------------------------------------------------------------

Go to the bottom of g_weapon.c and paste in the following four
functions.

The details of these functions are explained in the comments

---------------------------------------------------------------

/*
=================
fireBallBurn

This is the "think" function that takes care of making the fireball
"stick" to the enemy and causing the damage
=================
*/

void fireBallBurn (edict_t *self)
{
vec3_t start, dir;
// random angular velocity for the fireball
VectorSet (self->avelocity, 200.0 * random (), 200.0 * random (), 200.0 * random ());
// rotate the fireball model to give a better "flame" effect
VectorMA (self->s.angles, FRAMETIME, self->avelocity, self->s.angles);
// continiuously cycle thru animation frames 1 thru 3
self->s.frame += self->count;
if (self->s.frame >= 3) self->count = -1;
if (self->s.frame <= 1) self->count = 1;
self->nextthink = level.time + FRAMETIME;
(self->delay)--;
// go away when time is up
if (self->delay <= 0) self->think = G_FreeEdict;
// check for water
if (gi.pointcontents (self->s.origin) & MASK_WATER)
  {
  gi.sound (self, CHAN_VOICE, gi.soundindex ("weapons/douse.wav"), 1, ATTN_NORM, 0);
  G_FreeEdict (self);
  return;
  }
if (self->enemy)
  {
  // find the center of the enemy's bounding box and move
  // the fireball there
  VectorAdd (self->enemy->absmin, self->enemy->absmax, start);
  VectorScale (start, 0.5, start);
  // normally it's a no-no to set an ent's origin without
  // calling gi.linkentity() but the the physics code for
  // the move_type we're using will do it for us so there's
  // no need to duplicate it here
  VectorCopy (start, self->s.origin);
  VectorNormalize2 (self->enemy->velocity, dir);
  VectorNegate (dir, start);
  // since the model frames cycle 1-2-3-2-1-2-3-2... this
  // will cause damage every other "think"
  if (self->s.frame == 2) T_Damage (self->enemy, self, self->owner, dir, self->s.origin, start, self->dmg, 0, DAMAGE_NO_KNOCKBACK, MOD_FLAME_THROWER);
  // T_Damage might have caused enemy to go away
  if (!self->enemy->inuse)
    {
    G_FreeEdict (self);
    return;
    }
  // if gibbed, go away
  if (!self->enemy->takedamage) G_FreeEdict (self);
  }
}

/*
=================
fireBallTouch

This is the "touch" function that gets called by the physics
code when the fireball hits something
=================
*/

void fireBallTouch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
{
if (other == ent->owner) return;
if (surf && (surf->flags & SURF_SKY))
  {
  G_FreeEdict (ent);
  return;
  }
if (ent->owner->client)         PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT);
// change the "think" function now that we have hit something
ent->think = fireBallBurn;
ent->nextthink = level.time + FRAMETIME;
ent->s.frame = 1;
ent->count = 1;
ent->touch = NULL;
ent->solid = SOLID_NOT;
ent->movetype = MOVETYPE_NOCLIP;
VectorClear (ent->velocity);
// can we damage the thing that we hit?
if (other->takedamage)
  {
  edict_t *fball = NULL;
  /* look for other fireballs whose enemy is the thing we
  just hit.  If one is found then up its damage and burn
  time and then remove ourselves.  This will limit the total
  number of fireballs existant at one time and reduce the
  net traffic */
  while (fball = G_Find (fball, FOFS(classname), "fireball"))
    {
    // if other is already burning let the original fireball do the job
    if (fball->enemy == other)
      {
      fball->delay += ent->delay;
      fball->dmg += ent->dmg;
      G_FreeEdict (ent);
      return;
      }
    }
  ent->enemy = other;
  }
else
  ent->delay = 20;
}

/*
=================
fireBallFly

This is the "think" function for when the fireball is
flying through the air.  Normally, projectiles wouldn't
have a "think" function but since we want an animated
fireball this function takes care of that
=================
*/

void fireBallFly (edict_t *self)
{
// animate the fireball by continuously cycling through
// frames 30 thru 32
self->s.frame += self->count;
if (self->s.frame >= 32) self->count = -1;
if (self->s.frame <= 30) self->count = 1;
self->nextthink = level.time + FRAMETIME;
// check for water
if (gi.pointcontents (self->s.origin) & MASK_WATER)
  {
  gi.sound (self, CHAN_VOICE, gi.soundindex ("weapons/douse.wav"), 1, ATTN_NORM, 0);
  G_FreeEdict (self);
  }
}

/*
=================
fireFlameThrower

This is the function that gets called when you
fire the flame thrower
=================
*/

void fireFlameThrower (edict_t *self, vec3_t start, vec3_t dir)
{
edict_t    *fb;
// get a new ent for the fireball
fb = G_Spawn();
VectorCopy (start, fb->s.origin);
fb->movetype = MOVETYPE_FLYMISSILE;
VectorScale (dir, 600, fb->velocity);
fb->clipmask = MASK_SHOT;
fb->solid = SOLID_BBOX;
VectorSet (fb->mins, -8, -8, -8);
VectorSet (fb->maxs, 8, 8, 8);
VectorSet (fb->avelocity, 200.0 * random (), 200.0 * random (), 200.0 * random ());
// use the explosion model for the fireball
fb->s.modelindex = gi.modelindex ("models/objects/r_explode/tris.md2");
fb->s.frame = 30;
fb->s.skinnum = 3;
fb->s.renderfx = RF_TRANSLUCENT;
// make it give off light and leave a smoke trail
fb->s.effects = EF_ROCKET;
fb->count = 1;
fb->dmg = 1;
fb->delay = 100;
fb->owner = self;
fb->classname = "fireball";
fb->s.sound = gi.soundindex ("weapons/flame.wav");

fb->think = fireBallFly;
fb->nextthink = level.time + FRAMETIME;
fb->touch = fireBallTouch;
gi.linkentity (fb);
}

=============== end g_weapon.c modifications ==================

---------------------------------------------------------------

Ok, we're almost done.  All that's left are a few details that
need cleaned up.  Since fireFlameThrower() gets called from
another file (p_weapon.c) we'll need to prototype it somewhere.
In keeping with id's way of doing things we'll put the prototype
in g_local.h.

---------------------------------------------------------------

=============== begin g_local.h modifications =================

void fire_rail (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick);
void fire_bfg (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius);
///////////////// begin new code /////////////////
void fireFlameThrower (edict_t *self, vec3_t start, vec3_t dir);
///////////////// end new code ///////////////////

---------------------------------------------------------------

Scroll up and find the section where the "means of death" flags
are defined and add the new one for our flame thrower

---------------------------------------------------------------

#define MOD_HIT  32
#define MOD_TARGET_BLASTER  33
///////////////// begin new code /////////////////
#define MOD_FLAME_THROWER  34
///////////////// end new code ///////////////////
#define MOD_FRIENDLY_FIRE        0x8000000

---------------------------------------------------------------

Scroll down and find the typedef for the client_persistant_t
struct and add the new variable as shown.

---------------------------------------------------------------

int max_grenades;
int max_cells;
int max_slugs;
///////////////// begin new code /////////////////
int max_napalm;
///////////////// end new code ///////////////////

---------------------------------------------------------------

Find the ammo_t enum and make the addition shown.

---------------------------------------------------------------

typedef enum
{
AMMO_BULLETS,
AMMO_SHELLS,
AMMO_ROCKETS,
AMMO_GRENADES,
AMMO_CELLS,
AMMO_SLUGS, // add a comma !!!!
///////////////// begin new code /////////////////
AMMO_NAPALM
///////////////// end new code ///////////////////
} ammo_t;

=============== end g_local.h modifications ===================

---------------------------------------------------------------

Can't have a new weapon without a death message for it, right?
Open up p_client.c and add one in the ClientObituary function.

---------------------------------------------------------------

=============== begin p_client.c modifications ================

case MOD_HELD_GRENADE:
  message = "feels";
  message2 = "'s pain";
  break;
case MOD_TELEFRAG:
  message = "tried to invade";
  message2 = "'s personal space";
  break;
///////////////// begin new code /////////////////
case MOD_FLAME_THROWER:
  message = "got fried by";
  message2 = "'s flame thrower";
  break;
///////////////// end new code ///////////////////

---------------------------------------------------------------

Find the InitClientPersistant function and initalize our new
max_napalm variable.

---------------------------------------------------------------

client->pers.max_cells = 200;
client->pers.max_slugs = 50;
///////////////// begin new code /////////////////
client->pers.max_napalm = 120;
///////////////// end new code ///////////////////
client->pers.connected = true;

---------------------------------------------------------------

Since dead bodies can take damage, a player's body can continue
to burn after they're dead.  If someone respawns in this scenario
they will respawn on fire; probably not a good thing.  To fix it,
we'll make a little change to the CopyToBodyQue function.  Insert
the new code at the end of the function.

---------------------------------------------------------------

void CopyToBodyQue (edict_t *ent)
{
edict_t *body;
///////////////// begin new code /////////////////
edict_t    *fb;
///////////////// end new code ///////////////////

// rest of function here

body->takedamage = DAMAGE_YES;
gi.linkentity (body);
///////////////// begin new code /////////////////
fb = NULL;
// transfer any fireballs to the dead body
while ((fb = G_Find (fb, FOFS(classname), "fireball"))) if (fb->enemy == ent) fb->enemy = body;
///////////////// end new code ///////////////////
}

=============== end p_client.c modifications ==================

Well, thats it.  You'll need to bind a key to "use flamethrower"
(unless you like typing in the console during a game) to be able
to select it.  Also, unless you make a new map with the flame
thrower in it, you'll need to do a "give flamethrower" in the
console to try it out.

Now, as Maj.Bitch says, go out there and FRY ALL THOSE BASTARDS!!!


P.S. Please let me know if you find any bugs.