
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