
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.