
Posted by Psykotik on December 23, 1998 at
23:38:02:
=========
TUTORIAL: Adding Reloading
Date: 12/23/98
Notes: Credit. You know the drill.
Difficulty: Some hard concepts, some work involved, overall Medium.
=========
Here's the basis of my
reloading code, its very easily extensible. This code is loosely based off of
GreyBear's Adding Weapons: Realism tutorial at www.quake2.com/dll, modified by
myself not to require any fancy new models or model frames.
NOTE: THIS IS NOT ONE OF
THOSE CUT-AND-PASTE-IT-INTO-MY-MOD-AND-IT-WORKS TUTORIALS! It's easy to
implement, but YOU have to implement lots of it. I just came up with it all, and
a message including ALL my code would be way too huge.
First of all, go into
G_LOCAL.H and near the top find typedef enum weaponstate_t. Add in my reloading
variables like so:
typedef enum
{
WEAPON_READY,
WEAPON_ACTIVATING,
WEAPON_DROPPING,
WEAPON_FIRING,
WEAPON_END_MAG, // new
WEAPON_START_RELOADING, // new
WEAPON_END_RELOADING, // new
WEAPON_RELOADING // new
} weaponstate_t;
These control when to keep
then from firing, play the up/down animations, and start changing the reload
timer, respectively.
Now go down into gclient_s
and add these in:
int pistol_rds; // my mod
has a pistol which uses ammo instead of a blaster
int flareg_rds; // and my mod has a flaregun
int shotgun_rds;
int sshot_rds;
int mg_rds;
int dualmg_rds; // and dual machineguns
int chaingun_rds;
int gl_rds;
int rl_rds;
int hb_rds;
int rail_rds;
int bfg_rds;
int reload_time;
Add/delete these as
necessary. The beauty of this code is that you can add to it with about 10
lines of code.
Now, go into p_weapon.c and
get to the ChangeWeapon function. This is something that I would like to fix,
but it would require lots of code so I've never gotten around to it.
Using the weaponstates has
one negative side effect, involving changing weapons. Let's say you have fired
off all of the rounds in your Railgun clip. Somebody comes up close-range
before you start reloading, so you flip over to your MG, blast him, and flip
back. Going back to the railgun set the weaponstate to WEAPON_READY, in the
changing area of Weapon_Generic. Since the game will not prevent you from
firing unless your weaponstate is WEAPON_END_MAG (see later in the email), you
can start firing again. Your rounds are currently at zero. Now, the only way
that you can get back to WEAPON_END_MAG is to run out of ammo, or somehow get
back to rail_rds == 1 (see later in the email). It is quite impossible to get
back to rail_rds == 1, as you are subtracting one for every shot (since you are
in a dm your counter is presumably at like -10 by now). So you see the reason
for the bug. If you can find a way to prevent it better than what I have done,
PLEASE tell me. Here's my stopgap solution, paste it in at the top:
// reload their clips - i
know its cheap, but it prevents a bug
ent->client->pistol_rds = 12;
ent->client->flareg_rds = 4;
ent->client->shotgun_rds = 6;
ent->client->sshot_rds = 10;
ent->client->mg_rds = 30;
ent->client->dualmg_rds = 60;
ent->client->chaingun_rds = 75;
ent->client->gl_rds = 6;
ent->client->rl_rds = 6;
ent->client->hb_rds = 40;
ent->client->rail_rds = 3;
Notice I dont reload all
guns. Since my mod has no BFG (none of the classes get it), I dont bother
reloading it here, or anywhere else for that matter.
Now go down into
Weapon_Generic. There are lots of nifty ways to do this that involve adding
parameters like FRAME_RELOAD_LAST and programming reload frame model sequences
into the guns, but I'm not a modeller, so I took the easy way out. Add this in
after the if (ent->deadflag || ent->s.modelindex != 255) if block:
if(
ent->client->weaponstate == WEAPON_RELOADING) // they've pushed reload
{
if(ent->client->reload_time < 0 || ent->client->reload_time >
50) // their reload timer is screwed somehow
ent->client->reload_time = 50; // so set it to 5 seconds remaining
else if(ent->client->reload_time > 0) // they are still reloading
{
ent->client->ps.gunindex = 0; // so make sure their gun model is still
gone
ent->client->reload_time--; // and increment the reload timer
}
else // their reload timer is working, and they arent still reloading, so lets
fill their clip
{
ent->client->ps.gunframe = 0; // set gunframe to ACTIVATE_FIRST (0)
ent->client->reload_time = 0; // clear reload time
ent->client->weaponstate = WEAPON_END_RELOADING; // set weaponstate to
play gun raise animation
if(strcmp(ent->client->pers.weapon->pickup_name, "Pistol")
== 0) // reload based upon gun type
{
if(ent->client->pers.inventory[ent->client->ammo_index] >= 12)
// if we have the ammo
ent->client->pistol_rds = 12; // completely fill the clip
else // not enough ammo
ent->client->pistol_rds = ent->client->pers.inventory[ent->client->ammo_index];
// give em what we can
}
// include else if
statements for every gun in your entire mod...
if
(!ent->client->chasetoggle && !ent->client->rocketview) //
my check for a chasecam mode, remove if you need to
ent->client->ps.gunindex =
gi.modelindex(ent->client->pers.weapon->view_model);
}
}
That handles reload timers
and actually reloading the clips.
Now move down below the if
(ent->client->weaponstate == WEAPON_DROPPING) block and add this:
if
(ent->client->weaponstate == WEAPON_START_RELOADING)
{
if (ent->client->ps.gunframe < FRAME_DEACTIVATE_FIRST)
ent->client->ps.gunframe = FRAME_DEACTIVATE_FIRST;
else if (ent->client->ps.gunframe == FRAME_DEACTIVATE_LAST)
{
ent->client->weaponstate = WEAPON_RELOADING;
return;
}
ent->client->ps.gunframe++;
return;
}
This handles the
deactivation frames played to put the gun away when you start reloading.
Right below this there are
these lines:
if
(ent->client->weaponstate == WEAPON_ACTIVATING)
{
if (ent->client->ps.gunframe == FRAME_ACTIVATE_LAST)
{
change to this:
if
(ent->client->weaponstate == WEAPON_ACTIVATING ||
ent->client->weaponstate == WEAPON_END_RELOADING)
{
if (ent->client->ps.gunframe == FRAME_ACTIVATE_LAST)
{
This handles the weapon
raise function at the end of the reload sequence.
Now go down to the bottom
of the function and add in this:
if(
ent->client->weaponstate == WEAPON_END_MAG)
{
if (ent->client->ps.gunframe < FRAME_IDLE_FIRST ||
ent->client->ps.gunframe > FRAME_IDLE_LAST)
ent->client->ps.gunframe = FRAME_IDLE_FIRST;
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;
}
This handles advancing idle
frames when you are at the end of your mag, but will NOT let you fire.
Now this is the part of the
code that requires some elbow grease and thought. I am only giving one example
of how to set the reload code into the actual weapon code, so here goes. You
will have to copy/modify this around all fire functions of all guns you want
reloaded.
Grenade Launcher is
included as an example. Replace the fire_grenade line in
weapon_grenadelauncher_fire with the following code block:
if
(ent->client->pers.inventory[ent->client->ammo_index] == 1 ||
(ent->client->gl_rds == 1)) // only one round
{
ent->client->weaponstate = WEAPON_END_MAG; // set to end mag
fire_grenade (ent, start, forward, damage, 600, 2.5, radius); // fire
ent->client->gl_rds--; // now there's 0 rounds
}
else
{
// if no reload, fire normally.
fire_grenade (ent, start, forward, damage, 600, 2.5, radius);
ent->client->gl_rds--;
// subtract rounds
}
Now, everything is set,
except for an actual command to reload the guns, so add in Cmd_Reload_f to the
g_cmds.c file:
void Cmd_Reload_f (edict_t
*ent)
{
int rds_left;
if(ent->deadflag ==
DEAD_DEAD)
{
gi.centerprintf(ent, "Cannot reload while dead.\n");
return;
}
// first, grab the current
magazine max count...
if(strcmp(ent->client->pers.weapon->pickup_name, "Pistol")
== 0)
rds_left = 12;
else if(strcmp(ent->client->pers.weapon->pickup_name, "Flare
Gun") == 0)
rds_left = 4;
else if(strcmp(ent->client->pers.weapon->pickup_name,
"Shotgun") == 0)
rds_left = 6;
else if(strcmp(ent->client->pers.weapon->pickup_name, "Super
Shotgun") == 0)
rds_left = 10;
else if(strcmp(ent->client->pers.weapon->pickup_name,
"Machinegun") == 0)
rds_left = 30;
else if(strcmp(ent->client->pers.weapon->pickup_name, "Dual
Machineguns") == 0)
rds_left = 60;
else if(strcmp(ent->client->pers.weapon->pickup_name,
"Chaingun") == 0)
rds_left = 75;
else if(strcmp(ent->client->pers.weapon->pickup_name, "Grenade
Launcher") == 0)
rds_left = 6;
else if(strcmp(ent->client->pers.weapon->pickup_name, "Rocket
Launcher") == 0)
rds_left = 6;
else if(strcmp(ent->client->pers.weapon->pickup_name,
"HyperBlaster") == 0)
rds_left = 40;
else if(strcmp(ent->client->pers.weapon->pickup_name,
"Railgun") == 0)
rds_left = 3;
else
{ // un-reloadable weapon, like hand grenades
gi.centerprintf(ent,"You can't reload that.\n");
return;
}
if(ent->client->pers.inventory[ent->client->ammo_index])
// if we have ammo for the gun
{
if((ent->client->weaponstate != WEAPON_END_MAG) &&
(ent->client->pers.inventory[ent->client->ammo_index] <
rds_left)) // if we arent on the end of our magazine (trying to reload a half
clip) and we dont have enough ammo to reload
{
gi.centerprintf(ent,"You're on your last magazine!\n");
}
else // we do have enough ammo
{
ent->client->reload_time = 50; // set the reload timer to 5 seconds,
change if you wish (should probably be lower
ent->client->weaponstate = WEAPON_START_RELOADING; // set state flag to
drop gun next frame
}
}
else // no ammo
gi.centerprintf(ent,"Cannot reload with no ammo.\n");
}
There you go! Now go out
there AND ummm... AMAZE ALL THE BASTARDS WITH YOUR ummm... NIFTY RELOADING
CAPABILITIES!!!
Psykotik