[page=Introduction/Contents]
Welcome to my fourth tutorial!
Now, this tutorial may seem vaguely familiar to some people. When I found this tutorial, I noticed that there are LOTS of things that he didn't (but should have of) explain(ed)! This includes:
Changing the mag limit
Adding more than one weapon to the Weapon_Generic function
Notes
Now just to note.. This isn't very easy. This tutorial IS cut and paste, but there are things you must do yourself. I am taking this from a Mini-Project I am making, called ReloadQ2, so it will include the normal Quake2 names and such.
Alrighty then, if you're ready, let's get started on this!
[page=Adding the Definitions]
Alright, you're here now.
find:
float respawn_time; // can respawn when time > this
Beneath it, above the end of the struct, add
//PP - Capacities and how many rounds left
int shotg_max;
int shotg_rds;
int sshotg_max;
int sshotg_rds;
//PP end code
In the same file, find this:
typedef enum
{
WEAPON_READY,
WEAPON_ACTIVATING,
WEAPON_DROPPING,
WEAPON_FIRING
First, add a comma after WEAPON_FIRING.. so, the entire block like that would look like:
typedef enum
{
WEAPON_READY,
WEAPON_ACTIVATING,
WEAPON_DROPPING,
WEAPON_FIRING,
//PP added to animate weapon reload and last round
WEAPON_END_MAG,
WEAPON_RELOADING
//PP end add
} weaponstate_t;
Now, we're heading to p_client, and find PutClientInServer
Now, at the beginning with the definitions, add:
//PP added new declaration to give ammo
gitem_t *item;
Same code, go lower to find:
gi.linkentity (ent);
Under that, and above the:
client->newweapon = client->pers.weapon;
add:
//PP set the max clip size, then fill the current mag...
client->shotg_max = 12;
client->shotg_rds = client->shotg_max;
client->sshotg_max = 3;
client->sshotg_rds = client->sshotg_max;
//PP end add
Alright, you've finished that. Now, let's get on with the Weapons themself..
[page=Adding New Weapon Animations]
find this in p_weapon above Weapon_Generic
#define FRAME_FIRE_FIRST (FRAME_ACTIVATE_LAST + 1)
#define FRAME_IDLE_FIRST (FRAME_FIRE_LAST + 1)
#define FRAME_DEACTIVATE_FIRST (FRAME_IDLE_LAST + 1)
and add this right beneath it:
//PP - To use the reload and last round animations
#define FRAME_RELOAD_FIRST (FRAME_DEACTIVATE_LAST +1)
#define FRAME_LASTRD_FIRST (FRAME_RELOAD_LAST +1)
//PP end add
Now, the next part is BIG.. so, just replace your entire Weapon_Generic function with this:
//PP Create a local define to make changing clip size easy
#define SHOTGMAG 7
#define SSHOTGMAG 3
void Weapon_Generic (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST,
int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST,
/*PP added*/
int FRAME_RELOAD_LAST, int FRAME_LASTRD_LAST,
/*PP end add*/
int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent))
{
int n;
//PP - Added Reloading weapon, done manually via a cmd
if( ent->client->weaponstate == WEAPON_RELOADING)
{
if(ent->client->ps.gunframe < FRAME_RELOAD_FIRST || ent->client->ps.gunframe > FRAME_RELOAD_LAST)
ent->client->ps.gunframe = FRAME_RELOAD_FIRST;
else if(ent->client->ps.gunframe < FRAME_RELOAD_LAST)
{
ent->client->ps.gunframe++;
//PP - Check weapon to find out when to play reload sounds
if(stricmp(ent->client->pers.weapon->pickup_name, "Shotgun") == 0)
{
if(ent->client->ps.gunframe == 42)
gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/m4a1out.wav"), 1, ATTN_NORM, 0);
if(ent->client->ps.gunframe == 56)
gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/m4a1in.wav"), 1, ATTN_NORM, 0);
if(ent->client->ps.gunframe == 63)
gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/shotgr1b.wav"), 1, ATTN_NORM, 0);
}
else if(stricmp(ent->client->pers.weapon->pickup_name, "Super Shotgun") == 0)
{
if(ent->client->ps.gunframe == 42)
gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/m4a1out.wav"), 1, ATTN_NORM, 0);
if(ent->client->ps.gunframe == 56)
gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/m4a1in.wav"), 1, ATTN_NORM, 0);
if(ent->client->ps.gunframe == 63)
gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/shotgr1b.wav"), 1, ATTN_NORM, 0);
}
}
else
{
ent->client->ps.gunframe = FRAME_IDLE_FIRST;
ent->client->weaponstate = WEAPON_READY;
if(stricmp(ent->client->pers.weapon->pickup_name, "Shotgun") == 0)
{
if(ent->client->pers.inventory[ent->client->ammo_index] >= ent->client->shotg_max)
ent->client->shotg_rds = ent->client->shotg_max;
else
ent->client->shotg_rds = ent->client->pers.inventory[ent->client->ammo_index];
}
else if(stricmp(ent->client->pers.weapon->pickup_name, "Super Shotgun") == 0)
{
if(ent->client->pers.inventory[ent->client->ammo_index] >= ent->client->sshotg_max)
ent->client->sshotg_rds = ent->client->sshotg_max;
else
ent->client->sshotg_rds = ent->client->pers.inventory[ent->client->ammo_index];
}
}
}
//PP - Empty or unloaded weapon
if( ent->client->weaponstate == WEAPON_END_MAG)
{
if(ent->client->ps.gunframe < FRAME_LASTRD_LAST)
ent->client->ps.gunframe++;
else
ent->client->ps.gunframe = FRAME_LASTRD_LAST;
}
if (ent->client->weaponstate == WEAPON_DROPPING)
{
if (ent->client->ps.gunframe == FRAME_DEACTIVATE_LAST)
{
ChangeWeapon (ent);
return;
}
ent->client->ps.gunframe++;
return;
}
if (ent->client->weaponstate == WEAPON_ACTIVATING)
{
if (ent->client->ps.gunframe == FRAME_ACTIVATE_LAST)
{
ent->client->weaponstate = WEAPON_READY;
ent->client->ps.gunframe = FRAME_IDLE_FIRST;
return;
}
//PP - Check the current weapon to find out when to play takeout sounds
if(stricmp(ent->client->pers.weapon->pickup_name, "Shotgun") == 0)
{
if(ent->client->ps.gunframe == 2)
gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/shotgr1b.wav"), 1, ATTN_NORM, 0);
ent->client->shotg_max = SHOTGMAG; //set mag rounds ent->client->Mk23_rds = MK23MAG; //fill the mag...
}
ent->client->ps.gunframe++;
return;
}
if ((ent->client->newweapon) && (ent->client->weaponstate != WEAPON_FIRING))
{
ent->client->weaponstate = WEAPON_DROPPING;
ent->client->ps.gunframe = FRAME_DEACTIVATE_FIRST;
return;
}
if (ent->client->weaponstate == WEAPON_READY)
{
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;
}
//+BD - Disabled for manual weapon change
//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 (ent->client->weaponstate == WEAPON_FIRING)
{
for (n = 0; fire_frames[n]; n++)
{
if (ent->client->ps.gunframe == fire_frames[n])
{
if (ent->client->quad_framenum > level.framenum) gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0);
fire (ent);
break;
}
}
if (!fire_frames[n])
ent->client->ps.gunframe++;
if (ent->client->ps.gunframe == FRAME_IDLE_FIRST+1)
ent->client->weaponstate = WEAPON_READY;
}
}
Now that was big! That includes the code for both the Super Shotgun and Shotgun.
Now, in the weapons which you need reloading (in this case, Shotgun and Super Shotgun), look for this:
// 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);
or something around that, and this:
//Display the yellow muzzleflash light effect
gi.WriteByte (svc_muzzleflash);
gi.WriteShort (ent-g_edicts);
//If not silenced, play a shot sound for everyone else
gi.WriteByte (MZ_SHOTGUN | is_silenced);
gi.multicast (ent->s.origin, MULTICAST_PVS);
First, comment your firing function. Now, between both those, put this in:
//PP - Added to animate last round firing...
if (ent->client->pers.inventory[ent->client->ammo_index] == 1 || (ent->client->shotg_rds == 1))
{
//Hard coded for reload only.
ent->client->ps.gunframe=64;
ent->client->weaponstate = WEAPON_END_MAG;
fire_shotgun (ent, start, forward, damage, kick, 500, 500, DEFAULT_SHOTGUN_COUNT, MOD_SHOTGUN);
ent->client->shotg_rds--;
}
else
{
//If no reload, fire normally.
fire_shotgun (ent, start, forward, damage, kick, 500, 500, DEFAULT_SHOTGUN_COUNT, MOD_SHOTGUN);
ent->client->shotg_rds--;
}
[page=Command]
Alright! You're 70/100 way there!
Now, head over to g_cmds, and above say.. Cmd_God_f, add this:
//PP ENTIRE CODE BLOCK NEW
// Cmd_Reload_f()
// Handles weapon reload requests
void Cmd_Reload_f (edict_t *ent)
{
int rds_left; //PP - Variable to handle rounds left
//PP - If the player is dead, don't bother
if(ent->deadflag == DEAD_DEAD)
{
gi.centerprintf(ent, "I know you're not smart,\nBUT YOU'RE DEAD!!\n");
return;
}
//First, grab the current magazine max count...
if(stricmp(ent->client->pers.weapon->pickup_name, "Shotgun") == 0)
rds_left = ent->client->shotg_max;
else //We should never get here, but...
//BD 5/26 - Actually we get here quite often right now. Just exit for weaps that we
// don't want reloaded or that never reload (grenades)
{
gi.centerprintf(ent,"Where'd you train?\nYou can't reload that!\n");
return;
}
if(ent->client->pers.inventory[ent->client->ammo_index])
{
if((ent->client->weaponstate != WEAPON_END_MAG) && (ent->client->pers.inventory[ent->client->ammo_index] < rds_left))
{
gi.centerprintf(ent,"Buy a clue-\nYou're on your last magazine!\n");
}
else
//Set the weaponstate...
ent->client->weaponstate = WEAPON_RELOADING;
}
else
gi.centerprintf(ent,"Pull your head out-\nYou've got NO AMMO!\n");
}
//PP END CODE BLOCK
Now, find:
else if (Q_stricmp (cmd, "invdrop") == 0)
Cmd_InvDrop_f (ent);
add beneath:
//PP - for handling reload commands
else if (Q_stricmp (cmd, "reload") == 0)
Cmd_Reload_f (ent);
You should be good there.
[page=Frames]
This couldn't be simpler, even easier if you have models.
void Weapon_Shotgun (edict_t *ent)
{
static int pause_frames[] = {22, 28, 34, 0};
static int fire_frames[] = {8, 9, 0};
Weapon_Generic (ent, 7, 18, 36, 39, 68, 69, pause_frames, fire_frames, weapon_shotgun_fire);
}
You're adding two new numbers. One after the fourth for handling the last frame for your reload, and one after that for handling the Last Round animation. I'll explain those after the New Weapons.
[page=Adding New Weapons to the List]
This isn't too hard, but can be a bit freaky.
First, you must make definitions just the same with the other weapons. And then, look at what I did in Weapon_Generic with the names. ALWAYS, and I mean ALWAYS use Else If when adding a new weapon through Weapon_Generic, just like I did for the Super Shotgun.
Like so:
if(stricmp(ent->client->pers.weapon->pickup_name, "Shotgun") == 0)
{
if(ent->client->ps.gunframe == 42)
gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/m4a1out.wav"), 1, ATTN_NORM, 0);
if(ent->client->ps.gunframe == 56)
gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/m4a1in.wav"), 1, ATTN_NORM, 0);
if(ent->client->ps.gunframe == 63)
gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/shotgr1b.wav"), 1, ATTN_NORM, 0);
}
else if(stricmp(ent->client->pers.weapon->pickup_name, "Super Shotgun") == 0)
{
if(ent->client->ps.gunframe == 42)
gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/m4a1out.wav"), 1, ATTN_NORM, 0);
if(ent->client->ps.gunframe == 56)
gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/m4a1in.wav"), 1, ATTN_NORM, 0);
if(ent->client->ps.gunframe == 63)
gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/shotgr1b.wav"), 1, ATTN_NORM, 0);
}
It's always an Else If (but change the sounds :))
[page=Frames Explaination]
Alright, here's another thing.
Ok, here's how your model is set up (or should be):
activate01 - activatexx (Activation)
fire01 - firexx (Fire)
idle01 - idlexx (Idle frames/ready frames)
deactivate01 - deactivatexx (Putaway)
reload01 - reloadxx (Reloading)
lastrnd01 - lastrndxx (Last round)
The last round is a bit hard to understand. You should take the frames from fire, and copy + paste them into the Last Frame, but for weapons like the Mk23, you might want a signal to know that the weapon now needs reloading! Such as in Action Quake, the slider goes back, indicating you have no ammo.
Reload frames is obvious.
[page=Extra]
Now, alot of you may need help on this one. May be a problem, errors, anything.. Contact me please. This is a new tutorial, so the email isn't worn out, don't worry ;)
jonno.5000@gmail.com
OR
check at Paril101.planetquake.gamespy.com for a sample code (if available)
Thank you for listening and reading. Hopefully, you'll have some good realism in mods.
If you use this tutorial, no need to thank me. I am just someone who wishes to help at no cost to ANYONE.
Thank you.
Paril