Quake DeveLS - Burst Fire MG

Author: Bruce Knepper
Difficulty: Easy/Medium

For all of you machine gun lovers out there, here's a way to make it so that you can fire in burst mode, i.e. for one pull of the trigger you get 3 consecutive bullets.

To start out, we'll define some variables. First we'll define a variable called fire_mode in the persistent data section of client structure. This variable will tell us if we are currently in burst fire (BF) mode or fully automatic (FA). In g_local.h, head on down to line 743 and modify it to look like this:

        int             max_slugs;
        gitem_t         *weapon;

+       qboolean        fire_mode;      // Muce:  0 for FA and 1 for BF

} client_presistant_t
Were also going to need a integer to keep track of where we are in the firing sequence when using BF. Now go to line 809 and add this variable:
        int     machinegun_shots;               // for weapon rasing
+       int     burstfire_count;                // Muce:  to keep track of BF
And of course we will initialize fire_mode in p_client.c. Go to line 256 and add this:
        client->pers.max_slugs  = 50;
+       client->pers.fire_mode  = 0;    // Muce:  initialize to FA
} 
Good. Now to make the function to switch between FA and BF. Open up g_cmds.c and goto line 588. You want to insert this function after Cmd_Wave_f (); and before ClientCommand(). This is the function:
+/*
+=================
+Cmd_FireMode_f
+MUCE: new function for adjusting firing mode
+=================
+*/
+void Cmd_FireMode_f (edict_t *ent)
+       {
+       int i;
+       i=ent->client->pers.fire_mode;
+       switch (i)
+               {
+               case 0:
+                       ent->client->pers.fire_mode=1;
+                       gi.cprintf(ent,PRINT_HIGH,"Burst Fire Mode\n");
+                       break;
+               case 1:
+               default:
+                       ent->client->burstfire_count=0;
+                       ent->client->pers.fire_mode=0;
+                       gi.cprintf(ent,PRINT_HIGH,"Fully Automatic Mode\n");
+                       break;
+               }
+       }
This function is set up to toggle fire_mode every time it is called. You'll also notice that I set burstfire_count to zero when I switch to FA, the reason for this will be explained later. We also need to add this function to the list of commands in ClientCommand(); so head on down to line 660 and modify it to look like this:
        else if (Q_stricmp (cmd, "wave") == 0)
                Cmd_Wave_f (ent);
        
+       // MUCE:  added to call function for changeing fireing modes
+
+       else if (Q_stricmp (cmd, "firemode") == 0)
+               Cmd_FireMode_f (ent);
This way all we have to do is type "cmd firemode" in the console and we will toggle between FA and BF.

Before we dive into the code for firing the machine gun, lets come up with a little strategy. We will use burstfire_count to keep track of how many shots were already fired in our burst and also to keep track of how long until we can fire another burst. We will increment burstfire_count every frame. When burstfire_count is 0,1,2 we will fire, and as long as we still are holding the fire button, burstfire_count will keep getting incremented to 3,4,5,6 but during theses frames we won't be firing. I do this to keep BF from looking like FA when we hold the fire button down constantly.

Ok, so lets look at the code now. Open up p_weapon.c and go to line 816:

if (!(ent->client->buttons & BUTTON_ATTACK))
{
        ent->client->machinegun_shots=0;
        ent->client->ps.gunframe++;
        return;
}
When the Machinegun_Fire() function is called (which I believe is every frame) this first function checks to see whether or not the attack button is pressed. If it isn't it returns out of the function. For our new duel mode machine gun, we must change it to this:
if (!(ent->client->buttons & BUTTON_ATTACK) && 
( (ent->client->burstfire_count > 2) ||
   (!ent->client->burstfire_count ) ) )
{
        ent->client->machinegun_shots=0;
        ent->client->burstfire_count=0;
        ent->client->ps.gunframe++;
        return;
}
What this says is that we will not end the firing sequence unless the attack button is not depressed and either one of two things: 1. burstfire_count > 2 (we have fired 3 shots) or 2. burstfire_count = 0; (we are idle or in FA mode).

Then next little bit of code after that if statement is used to cycle the gun frames while firing to give the look of kickback on the machine gun:

if (ent->client->ps.gunframe == 5)
                ent->client->ps.gunframe = 4;
        else
                ent->client->ps.gunframe = 5;
This is fine, except that we only want the gun to be moving when it's firing, i.e. when burstfire_count = 0, 1, 2. So we just add a few lines like so:
+if (ent->client->burstfire_count < 3)
+       {
        if (ent->client->ps.gunframe == 5)
                ent->client->ps.gunframe = 4;
        else
                ent->client->ps.gunframe = 5;
+       }
As we continue to the next little code segment we find a little routine to handle when the gun runs out of ammo. The only thing needed here is to reset burstfire_count when we run out of ammo, so just add this line at about line 838:
                        ent->pain_debounce_time = level.time + 1;
                }
+               ent->client->burstfire_count=0;
                NoAmmoWeaponChange (ent);
                return;
Good, on to the next piece of code we need to look at ( about line 858 ):
        // raise the gun as it is firing
        if (!deathmatch->value)
        {
                ent->client->machinegun_shots++;
                if (ent->client->machinegun_shots > 9)
                        ent->client->machinegun_shots = 9;
        }
This little bit of code raises the gun when your firing fully auto. Since were only firing three round bursts, we don't want the gun to raise as long as were in BF mode so we just add another equation to the if statement:
        // raise the gun as it is firing
        if (!deathmatch->value && !ent->client->pers.fire_mode)
        {
                ent->client->machinegun_shots++;
                if (ent->client->machinegun_shots > 9)
                        ent->client->machinegun_shots = 9;
        }
Now for the big change! Around line 870 we have this segment of code:
fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD);

        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);

ent->client->pers.inventory[ent->client->ammo_index] -= ent->client->pers.weapon->quantity;
This is the code that fires the gun and broadcasts it to the rest of the world. We need to change it to this:
switch (ent->client->pers.fire_mode)
        {
        // Fire Burst Fire
        case 1:
                ent->client->burstfire_count++;
                if (ent->client->burstfire_count < 4)
                {
fire_bullet (ent, start, forward, damage*2, kick/2, DEFAULT_BULLET_HSPREAD/2, DEFAULT_BULLET_VSPREAD/2);
                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);

ent->client->pers.inventory[ent->client->ammo_index] -= ent->client->pers.weapon->quantity;
                }       
                else if (ent->client->burstfire_count > 6)
                        ent->client->burstfire_count=0;
                break;

        // Fire Fully Automatic

        case 0:
        default:
fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD);
                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);

ent->client->pers.inventory[ent->client->ammo_index] -= ent->client->pers.weapon->quantity;
                break;
        }
I told you it was a big change. Here's what it does. If we are in FA mode, it simply fires the gun using the exact same code as the original did. If we are in BF mode, we first increment burstfire_count. Next we check to see if we are on the first, second, or third round of our three round burst. We do this by checking if burstfire_count < 4 ( we use 4 because we just stepped burstfire_count in the previous line), and if it is, we fire a round. Notice I've increased the damage and reduced the kick and spread while in BF. This is to make up for the fact that were firing half as often in BF mode. If burstfire_count is 3,4,5,6 (before we incremented it) then we do nothing. If burstfire_count > 6 then we reset it to zero so we can start another burst on the next run through.

And that's it... compile it and try it out. You might want to mess with the amount of damage bursts do, or how long the wait is between bursts to get it to your liking. The idea for this patch came from my friend Ted who liked using the machinegun in deathmatch, but it turns out that it's pretty fun to use in a coop game as well. One burst will easily take out those pesky machinegun guards.

If you have any questions, or would like some help modifying this patch, or if you want my original source code, please fell free to contact me at [email protected].

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