A machinegun with 3 (!) fire modes!

   This tutorial will show you how to add multiple firing modes to a weapon and change the use "weapon" function so it will switch firemodes by pressing the bound key again!  No binds required!  Many thanks goes out to Muce from the Cry Havoc Dev. team for the 3 round burst and Psykotik for the Cmd_Use function reworking.  To show you how to add more than 2 firing modes to a weapon, we're going to make a machinegun that has full automatic, 3 round burst, and single shot (or semi-automatic, whichever you prefer). 

Add in the blue code and take out any pink code.

Alright, let's get started.   Open up g_local.h and go down to the gclient_s structure.  Put in this integer:

int             machinegun_shots;     // for weapon raising

int             selectfire_count; // Used for burst fire and single shot

Now go back up to the respawn structure and add in this integer:

    client_persistant_t     coop_respawn;    // what to set client->pers to on a respawn
    int             enterframe;             // level.framenum the client entered the game
    int             score;                 // frags, etc
    vec3_t        cmd_angles;             // angles sent over in the last command

    qboolean    spectator;             // client is a spectator

    int             firemode;//for select fire machinegun

 Next open up p_weapon.c.  Go down to the machinegun section and replace the whole Machinegun_Fire function with this:

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

MACHINEGUN / CHAINGUN

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

void Machinegun_Fire (edict_t *ent)
{
int i;
vec3_t start;
vec3_t forward, right;
vec3_t angles;
int damage = 8;
int kick = 2;
vec3_t offset;

if (ent->client->resp.firemode < 2)//Automatic and 3 Round Burst
    {
if (!(ent->client->buttons & BUTTON_ATTACK) && ( (ent->client->selectfire_count > 2) || (!ent->client->selectfire_count ) ) )
{
ent->client->machinegun_shots=0;
ent->client->selectfire_count=0;
ent->client->ps.gunframe++;
return;
}
if (ent->client->selectfire_count < 3)
{
if (ent->client->ps.gunframe == 5)
ent->client->ps.gunframe = 4;
else
ent->client->ps.gunframe = 5;
}
}
else if (ent->client->resp.firemode == 2)//Single Shot
{
if (!(ent->client->buttons & BUTTON_ATTACK) && ( (ent->client->selectfire_count > 0) || (!ent->client->selectfire_count ) ) )
{
ent->client->machinegun_shots=0;
ent->client->selectfire_count=0;
ent->client->ps.gunframe++;
return;
}
if (ent->client->selectfire_count < 2)
    {
ent->client->ps.gunframe = 4;
}
}

if (ent->client->pers.inventory[ent->client->ammo_index] < 1)
{
ent->client->ps.gunframe = 6;
if (level.time >= ent->pain_debounce_time)
{
gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
ent->pain_debounce_time = level.time + 1;
}
ent->client->selectfire_count=0;

NoAmmoWeaponChange (ent);
return;
}

if (is_quad)
{
damage *= 4;
kick *= 4;
}

for (i=1 ; i<3 ; i++)
{
ent->client->kick_origin[i] = crandom() * 0.35;
ent->client->kick_angles[i] = crandom() * 0.7;
}
ent->client->kick_origin[0] = crandom() * 0.35;
ent->client->kick_angles[0] = ent->client->machinegun_shots * -1.5;

// raise the gun as it is firing
if (!deathmatch->value && !ent->client->resp.firemode)
{
ent->client->machinegun_shots++;
if (ent->client->machinegun_shots > 9)
ent->client->machinegun_shots = 9;
}

// get start / end positions
VectorAdd (ent->client->v_angle, ent->client->kick_angles, angles);
AngleVectors (angles, forward, right, NULL);
VectorSet(offset, 0, 8, ent->viewheight-8);
P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);

if (ent->client->resp.firemode == 1)
    { // Fire 3 Round Burst

        ent->client->selectfire_count++;
        if (ent->client->selectfire_count < 4)
        {
fire_bullet (ent, start, forward, damage*2, kick/2, DEFAULT_BULLET_HSPREAD/2, DEFAULT_BULLET_VSPREAD/2, MOD_MACHINEGUN);

            gi.WriteByte (svc_muzzleflash);
            gi.WriteShort (ent-g_edicts);
            gi.WriteByte (MZ_MACHINEGUN | is_silenced);
            gi.multicast (ent->s.origin, MULTICAST_PVS);

PlayerNoise(ent, start, PNOISE_WEAPON);

            if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
                ent->client->pers.inventory[ent->client->ammo_index]--;
                ent->client->anim_priority = ANIM_ATTACK;
                if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
                {
    ent->s.frame = FRAME_crattak1 - (int) (random()+0.25);
                    ent->client->anim_end = FRAME_crattak9;
                }
                else
                {
    ent->s.frame = FRAME_attack1 - (int) (random()+0.25);
                    ent->client->anim_end = FRAME_attack8;
                }
            }
        else if (ent->client->selectfire_count > 6)
            ent->client->selectfire_count=0;
    }
    else if (ent->client->resp.firemode == 2)
    { // Fire Single Shot

        ent->client->selectfire_count++;
        if (ent->client->selectfire_count < 2)
        {
fire_bullet (ent, start, forward, damage*4, kick/2, DEFAULT_BULLET_HSPREAD/8, DEFAULT_BULLET_VSPREAD/8, MOD_MACHINEGUN);

            gi.WriteByte (svc_muzzleflash);
            gi.WriteShort (ent-g_edicts);
            gi.WriteByte (MZ_MACHINEGUN | is_silenced);
            gi.multicast (ent->s.origin, MULTICAST_PVS);

PlayerNoise(ent, start, PNOISE_WEAPON);

            if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
                ent->client->pers.inventory[ent->client->ammo_index]--;
            ent->client->anim_priority = ANIM_ATTACK;
            if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
                {
    ent->s.frame = FRAME_crattak1 - (int) (random()+0.25);
                    ent->client->anim_end = FRAME_crattak9;
                }
                else
                {
    ent->s.frame = FRAME_attack1 - (int) (random()+0.25);
    ent->client->anim_end = FRAME_attack8;
                }
            }
       
}
else // Fire Fully Automatic
    {

fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_MACHINEGUN);
   
        gi.WriteByte (svc_muzzleflash);
        gi.WriteShort (ent-g_edicts);
gi.WriteByte (MZ_MACHINEGUN | is_silenced);
        gi.multicast (ent->s.origin, MULTICAST_PVS);

        PlayerNoise(ent, start, PNOISE_WEAPON);

        if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
    ent->client->pers.inventory[ent->client->ammo_index]--;
            ent->client->anim_priority = ANIM_ATTACK;
        if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
{
ent->s.frame = FRAME_crattak1 - (int) (random()+0.25);
ent->client->anim_end = FRAME_crattak9;
}
else
{
ent->s.frame = FRAME_attack1 - (int) (random()+0.25);
ent->client->anim_end = FRAME_attack8;
}
}
}

Ok, that was alot.  If you look at the top of the function now it checks to see what firemode variable we are.  If we're less than 2 (a 0 or a 1) then we're going to use the top section to control the frames of the machinegun and if we're a 2 then we're in single shot mode and we only want the gun to shake for 1 frame and then stop the whole function after it shoots once.  It wouldn't look right if the gun wasn't firing but it was still shaking, right?  Next you'll see that there are 3 seperate places where fire_bullet is called.  There is one for each firemode.  The first you'll see is 3 round burst.  You only want it to fire on the selectfire_count 1, 2, and 3 (3 shots) so you can see the if statement that only calls on fire_bullet if the integer is a 1, 2, or 3.  The next section is for single shot.   It's almost the same as the 3 round burst except we only want it to fire when the integer is a one (hence the name, single shot).  Then the last section is for good old full automatic which is just the same as the original machinegun.

Next we need to modify the the Cmd_Use function in g_cmds.c so when we type "use machinegun" it will switch to the machinegun and then each successive use of the command will switch our firemodes.  So open it up and make your Cmd_Use look 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;
    }
    if (!it->use)
    {
        gi.cprintf (ent, PRINT_HIGH, "Item is not usable.\n");
        return;
    }
    index = ITEM_INDEX(it);
    if (!ent->client->pers.inventory[index])
    {
        gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s);
        return;
    }

    else if (!Q_strcasecmp(s, ent->client->pers.weapon->pickup_name))
    {

        if (!Q_strcasecmp(s, "machinegun"))
        {
            if (ent->client->resp.firemode == 0)
            {
                ent->client->resp.firemode = 1;
                gi.cprintf(ent, PRINT_HIGH, "3 Round Burst\n");
            }
            else if (ent->client->resp.firemode == 1)
            {
                ent->client->resp.firemode = 2;
                gi.cprintf(ent, PRINT_HIGH, "Single Shot\n");
            }
            else
            {
                ent->client->selectfire_count=0;
                ent->client->resp.firemode = 0;
                gi.cprintf(ent, PRINT_HIGH, "Automatic\n");
            }
        }
    }
    else if (!Q_strcasecmp(s, "machinegun"))
    {
        ent->client->resp.firemode = 0;
    }


    it->use (ent, it);
}

There ya go!  Now in the game if you bind a key to "use machinegun" it will first switch to that weapon, then if you hit that bound key again it will start to cycle between the firing modes.  Very simple and easy.  Hope ya like it!

Tutorial by Willi