TUTORIAL: Adding Reloading To Your Mod


 

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