Secondary Fire Functions

This tutorial, will be less accessable to the cut and paste mob, simply because it really only gets you started; You still have a fair amount to do after putting this code in, namely adding the scondary fire functions themselves. This tut only tells you how to access them. Thanks to Warzone for the basic idea (someone asked how they could do this, Warzone suggested button_use

This tut revolves around weapon_generic and involves a rewrite of quite a bit of it. All the calls will need to be changed, or it won't work.


The first thing to do is to add the possiblitly of a secondary fire state for the weapon. weapon state is handled by an enumeration type in g_local.h. Find the typedef for weaponstate_t and add

WEAPON_SFIRING

to the end of it, just after WEAPON_FIRING and before the }. Add a comma (,) after WEAPON_FIRING

This creates another state the weapon can be in, thankfully the code is done properly and references are only made to the defined states, not the numbers lying behind them.


The parameters of Weapon_Generic are going to need changed a bit. The unmodified line is shown below

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

We need to add in the possiblility of a secondary fire function, what frames it will fire on and whether it should animate. A modified function prottype is shown below

void Weapon_Generic (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent),int *sfire_frames, void (*sfire)(edict_t *ent),qboolean sfireanim)

qboolean sfireanim tells the function whether the weapon should animate in secondary fire. void (*sfire)(edict_t *ent) holds the secondary fire function. int *sfire_frames holds the array containing the list of frames the weapon fires on.

Change the function declaration in p_weapon.c to read as above. To make all the calls to it still work, add ,NULL,NULL,false to the end of each call. i.e. change

Weapon_Generic (ent, 5, 16, 59, 64, pause_frames, fire_frames, weapon_grenadelauncher_fire);

to

Weapon_Generic (ent, 5, 16, 59, 64, pause_frames, fire_frames, weapon_grenadelauncher_fire,NULL,NULL,false);


As is the code will now compile. It just won't do anything new. We now need to add the checks to see if the secondary button is being pressed. For some reason id added the button_use button to the code. It isn't used anywhere that I can see (I grepped the code to check) So button_use is our seconadry fire button. Bind the key you want to use for it to +use. mouse2 works well :)#

The main problem I hit when writing this was placement of { and } 's. I'll try to make this work properly. If i havn't I'm sorry.

The first change is in the statement:
if ((ent->client->newweapon) && (ent->client->weaponstate != WEAPON_FIRING))
you need to make it check if the secondary fire function is in effect as well. So you need to make it read
if ((ent->client->newweapon) && ((ent->client->weaponstate != WEAPON_FIRING)||(ent->client->weaponstate != WEAPON_SFIRING)))

Now the big change replace the code :

if ( ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) )
               {
                       ent->client->latched_buttons &= ~BUTTON_ATTACK;
                       if ((!ent->client->ammo_index) || 
                               ( ent->client->pers.inventory[ent->client->ammo_index] >= ent->client->pers.weapon->quantity))
                       {
                               ent->client->ps.gunframe = FRAME_FIRE_FIRST;
                               ent->client->weaponstate = WEAPON_FIRING;
 
                               // start the animation
                               ent->client->anim_priority = ANIM_ATTACK;
                               if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
                               {
                                      ent->s.frame = FRAME_crattak1-1;
                                      ent->client->anim_end = FRAME_crattak9;
                               }
                               else
                               {
                                      ent->s.frame = FRAME_attack1-1;
                                      ent->client->anim_end = FRAME_attack8;
                               }
                       }
                       else
                       {
                               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;
                               }
                               NoAmmoWeaponChange (ent);
                       }
               }
               else
               {
                       if (ent->client->ps.gunframe == FRAME_IDLE_LAST)
                       {
                               ent->client->ps.gunframe = FRAME_IDLE_FIRST;
                               return;
                       }
 
                       if (pause_frames)
                       {
                               for (n = 0; pause_frames[n]; n++)
                               {
                                      if (ent->client->ps.gunframe == pause_frames[n])
                                      {
                                              if (rand()&15)
                                                     return;
                                      }
                               }
                       }
 
                       ent->client->ps.gunframe++;
                       return;
               }

with :

if ((ent->client->buttons|ent->client->buttons) & (BUTTON_ATTACK|BUTTON_USE))
               {
                       if ((!ent->client->ammo_index) || 
                               ( ent->client->pers.inventory[ent->client->ammo_index] >= ent->client->pers.weapon->quantity))
                       {
                               if ((ent->client->buttons|ent->client->buttons) & BUTTON_ATTACK)
                               {       
                                      ent->client->ps.gunframe = FRAME_FIRE_FIRST;
                                      ent->client->weaponstate = WEAPON_FIRING;
                                      ent->client->anim_priority = ANIM_ATTACK;
                               if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
                               {
                                      ent->s.frame = FRAME_crattak1-1;
                                      ent->client->anim_end = FRAME_crattak9;
 
                               }
                               else
                               {
                                      ent->s.frame = FRAME_attack1-1;
                                      ent->client->anim_end = FRAME_attack8;
                               }
                               }
                               if (((ent->client->buttons|ent->client->buttons) & BUTTON_USE)&&(sfire!=NULL)){
                               ent->client->weaponstate = WEAPON_SFIRING;
                               if (sfireanim){
                               ent->client->ps.gunframe = FRAME_FIRE_FIRST;
                               ent->client->anim_priority = ANIM_ATTACK;
                               if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
                               {
                                      ent->s.frame = FRAME_crattak1-1;
                                      ent->client->anim_end = FRAME_crattak9;
 
                               }
                               else
                               {
                                      ent->s.frame = FRAME_attack1-1;
                                      ent->client->anim_end = FRAME_attack8;
                               }
                               }
                               }
                               
                       }
                       else
                       {
                               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;
                               }
                               NoAmmoWeaponChange (ent);
                       }
               }
               else
               {
                       if (ent->client->ps.gunframe == FRAME_IDLE_LAST)
                       {
                               ent->client->ps.gunframe = FRAME_IDLE_FIRST;
                               return;
                       }
                       
                       if (pause_frames)
                       {
                               for (n = 0; pause_frames[n]; n++)
                               {
                                      if (ent->client->ps.gunframe == pause_frames[n])
                                      {
                                              if (rand()&15)
                                                     return;
                                      }
                               }
                       }
                       
                       ent->client->ps.gunframe++;
                       return;
               }

If you have problems with this, then theres probably a problem with the }'s. count them, make sure they tally properly.

This should be fairly self explanetory as code goes. it check to see if you have one of the buttons pressed, if it's the attack button, then it procedes normaly, if its the seconadry fire, it checks to see that a secondary fire function exists and then starts up the secondary fire if it does. If sfireanim is false, then it doesn't start the attack up.

Not long to go now :). All you need to do now is add the handler code for the state WEAPON_SFIRING. Add the code below just after the stuff handling WEAPON_FIRING, right at the bottom of the function

if (ent->client->weaponstate == WEAPON_SFIRING)
        {
               if (sfireanim){
               for (n = 0; sfire_frames[n]; n++)
               {
                       if (ent->client->ps.gunframe == sfire_frames[n])
                       {
                               if (ent->client->quad_framenum > level.framenum)
                                      gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0);
                                      sfire (ent);
                               break;
                       }
               }
               
               if (!sfire_frames[n])
                       ent->client->ps.gunframe++;
               
               if (ent->client->ps.gunframe == FRAME_IDLE_FIRST+1)
                       ent->client->weaponstate = WEAPON_READY;}
        else
        {
               sfire(ent);
               ent->client->weaponstate = WEAPON_READY;
               ent->client->ps.gunframe = FRAME_IDLE_FIRST+1;
        }
        }

Try compiling. it should work fine. If it doesn't go though the code checking }'s as that is probably the main problem.


Adding secondary fire functions is fairly simple. You might want to add a debounce timer to keep the secondary fire function from being used too often. mail me if you don't know what I mean and need to restrict fire rate. I'll give two examples. Add these to the top of p_weapon.c, or stick them in a new file and prototype them at the top of p_weapon.c. As long as there is at least a prototype before weapon_generic.

void weaptriblast (edict_t *ent)  // yes this is the triple blaster code from qdevels, but with the blaster bolts being in a triangle, instead of side by side.
{
        int             damage;
        // STEVE
        vec3_t tempvec;
        if (deathmatch->value)
                damage = 15;
        else
                damage = 10;
        Blaster_Fire (ent, vec3_origin, damage, false, EF_BLASTER);
 
        // STEVE : add 2 new bolts below
        VectorSet(tempvec, 0, 4, -4);
        VectorAdd(tempvec, vec3_origin, tempvec);
        Blaster_Fire (ent, tempvec, damage, false, EF_BLASTER);
 
        VectorSet(tempvec, 0, -4, -4);
        VectorAdd(tempvec, vec3_origin, tempvec);
        Blaster_Fire (ent, tempvec, damage, false, EF_BLASTER);
 
        ent->client->ps.gunframe++;
}
 
 
 
void railzoom (edict_t *ent) // like a sniper scope. unfortunatly if has to zoom in all the way, before resetting to the outer zoom.
{
if (ent->client->ps.fov == 10){
 ent->client->ps.fov = 90;
return;
}
ent->client->ps.fov -= 5;
}

Each of these is added in a slightly different way. I'll start with railzoom as it is the easier of the two. Change Weapon_Generic (ent, 3, 18, 56, 61, pause_frames, fire_frames, weapon_railgun_fire,NULL,NULL,false);
to
Weapon_Generic (ent, 3, 18, 56, 61, pause_frames, fire_frames, weapon_railgun_fire,NULL,railzoom,false);

As I don't want the weapon to do anything while I zoom in, I don't need fire frames, this just gets called every 0.1 secs when I'm firing.

Now for the slightly harder one. change weapon_blaster from

void Weapon_Blaster (edict_t *ent)
{
        static int     pause_frames[] = {19, 32, 0};
        static int     fire_frames[]  = {5, 0};
 
        Weapon_Generic (ent, 4, 8, 52, 55, pause_frames, fire_frames, Weapon_Blaster_Fire,NULL,NULL,false);
}

To

void Weapon_Blaster (edict_t *ent)
  {
          static int   pause_frames[] = {19, 32, 0};
          static int   fire_frames[]  = {5, 0};
          static int sfire_frames[]={5};
          
 
          Weapon_Generic (ent, 4, 8, 52, 55, pause_frames, fire_frames, Weapon_Blaster_Fire,sfire_frames,weaptriblast,true);
  }

This will fire as often as the normal blaster in either firemode, and the seconadry mode is sick :) (I added the debounce timer I mentioned earlier) If its not clear why the call is done this way mail me and i'll explain in detail.


Skunkworks Tutorials