This is a quick tutorial on the weapon code for the mp5, written by Greg Hudson.
N.B. The code has been taken from mp5.cpp, and is Copyright Valve Software.
//=================================================
//PART ONE
//=================================================
// You should know what this does anyway but if you don't..., The #include
"xxxx.h" will take the code from
// a header file and place in the source code. This saves on time because you
dont have to repeatedly retype it
#include "extdll.h"
// lots of includes and defines
#include "util.h" // more includes and extern void
's
#include "cbase.h" // defining data in all sorts of
ways, int, float, etc etc
#include "monsters.h" // very noticeable is the hit
groups for the monsters
#include "weapons.h" // place to define the weapon
properties, also has the client side glock code (well some of it)
#include "nodes.h" // I ASSUME the waypoint nodes
#include "player.h" // bit of player movement an
the auto aim vector at end of file
#include "soundent.h" // sounds!
#include "gamerules.h" // loadsa virtual void 's in
there
//=================================================
//PART TWO
//=================================================
// In this section you list all the weapon animations so that they can be called
in your code.
// Typically if you had an animation called "swing" for a weapon called
"blaster" you would put
// BLASTER_SWING . The first animation always gets an " = 0 " after it.
// The animations are fairly self explanatory
enum mp5_e
{
MP5_LONGIDLE = 0,
MP5_IDLE1,
MP5_LAUNCH,
MP5_RELOAD,
MP5_DEPLOY,
MP5_FIRE1,
MP5_FIRE2,
MP5_FIRE3,
};
//=================================================
//PART THREE
//=================================================
// Here you have your class overview. Any new sections of code need to be added
in here.
// To keep my code tidy(ish) I put any variables that i use in here (i.e.
"m_fInView" or perhaps "lamer_counter"
class CMP5 : public CBasePlayerWeapon
{
public:
void Spawn( void ); // weapon ID, world model
void Precache( void ); // VERY IMPORTANT!
external resources (wav, mdl, sc, etc files go in here!)
int iItemSlot( void ) { return 3; } // returns
the slot number of the weapon (1-5 this time though)
int GetItemInfo(ItemInfo *p); // weapon
properties go in here like where the weapon goes in the HUD select
int AddToPlayer( CBasePlayer *pPlayer ); //
message the client dll to say that player has the weapon
void PrimaryAttack( void );
// The primary attack, this is the console command
"+attack"
void SecondaryAttack( void ); // The secondary
attack, this is the console command "+attack2"
int SecondaryAmmoIndex( void ); // The MP5 used
ammo in it's secondary fire so this is it's code
BOOL Deploy( void ); // Lauch the weapon when
selected
void Reload( void ); // reload the weapon when
button pressed or clip is empty
void WeaponIdle( void ); // Play the idle weapon
animation
// SGC - Valve use a convention where all class member variables are named
with m_
// and you should use this in your code too.
float m_flNextAnimTime; // Next time to play the idle
animation
int m_iShell; // ejected shell from weapon
private:
unsigned short m_usMP5; // calls the client .sc file
};
LINK_ENTITY_TO_CLASS( weapon_mp5, CMP5 ); // Links the
weapon to an entity in the map
LINK_ENTITY_TO_CLASS( weapon_9mmAR, CMP5 ); // Same as
above
//=================================================
//PART FOUR
//=================================================
// The mp5's contact grenades.
// This asks what the secondary ammo type is (p->pszAmmo2)
// This is asked in the weapons.cpp source file for reference
int CMP5::SecondaryAmmoIndex( void )
{
return m_iSecondaryAmmoType; // does what it says above
}
// SGC - Spawn() is
called when the weapon appears on the map. It's a good place to
// set default properties of the weapon
void CMP5::Spawn( )
// Asigns a few things to the weapon...
{
//I believe this is to force a different name to appear
//in the console when you die by this weapon.
//Originally it was done by entity name
pev->classname = MAKE_STRING("weapon_9mmAR");
Precache( ); // calls the Precache( ) function
SET_MODEL(ENT(pev), "models/w_9mmAR.mdl"); // sets the
world model (weapon the lies on the floor)
m_iId = WEAPON_MP5; // This is the weapon ID
m_iDefaultAmmo = MP5_DEFAULT_GIVE; // How much ammo do you get given when the weapon spawns?
FallInit(); //
Makes the weapon fall to the ground rather than float in mid air
}
//=================================================
//PART FIVE
//=================================================
// VERY important thing to get right. You MUST precache all the things you need.
// Also make sure you don't precache stuff you aren't going to use so you keep
your memory free
// PRECACHE_MODEL = model
file (.mdl)
// PRECACHE_SOUND = sounds file (.wav)
// PRECACHE_EVENT = event script (.sc)
// As Botman
said....Precache everything!.......or something like that
void CMP5::Precache( void )
{
PRECACHE_MODEL("models/v_9mmAR.mdl");
PRECACHE_MODEL("models/w_9mmAR.mdl");
PRECACHE_MODEL("models/p_9mmAR.mdl");
m_iShell = PRECACHE_MODEL ("models/shell.mdl");// brass shell TE_MODEL
PRECACHE_MODEL("models/grenade.mdl"); // grenade
PRECACHE_MODEL("models/w_9mmARclip.mdl");
PRECACHE_SOUND("items/9mmclip1.wav");
PRECACHE_SOUND("items/clipinsert1.wav");
PRECACHE_SOUND("items/cliprelease1.wav");
// PRECACHE_SOUND("items/guncock1.wav");
PRECACHE_SOUND ("weapons/hks1.wav");//
H to the K
PRECACHE_SOUND ("weapons/hks2.wav");// H to the K
PRECACHE_SOUND ("weapons/hks3.wav");// H to the K
PRECACHE_SOUND( "weapons/glauncher.wav"
);
PRECACHE_SOUND( "weapons/glauncher2.wav" );
PRECACHE_SOUND ("weapons/357_cock1.wav");
m_usMP5 = PRECACHE_EVENT( 1,
"events/mp5.sc" );
}
//=================================================
//PART SIX
//=================================================
// This part will assign certain values to our weapon.
// here we go......
int CMP5::GetItemInfo(ItemInfo *p)
{
// calls the value from Spawn( )
p->pszName = STRING(pev->classname);
// primary ammo type for weapon
p->pszAmmo1 = "9mm";
// maximum amount of that ammo you can hold
p->iMaxAmmo1 = _9MM_MAX_CARRY;
// secondary ammo type for the weapon
p->pszAmmo2 = "ARgrenades";
// maximum amount of ammo you can hold
p->iMaxAmmo2 = M203_GRENADE_MAX_CARRY;
// the amount of ammo that can fit into one clip of the
weapon
p->iMaxClip = MP5_MAX_CLIP;
// X position on the HUD (0->4 where 0 is the crowbar)
// SGC - ie. slot position (0 = crowbar, 1 = pistols, 2 = shotgun/mp5, 3 =
gauss, rpg, 4 = grenades)
p->iSlot = 2;
// Y position on the HUD (0->? where 0 is the crowbar,
glock mp5 etc etc)
// SGC - ie. which position on each slot list so eg. if slot=3: 0=rpg, 1=gauss,
2=egon etc.)
// NB. This must go in sequence (you cant have items missing), and must be
unique (so you cant
// have two weapons that share the same slot+position).
p->iPosition = 0;
// see below for explanation....
p->iFlags = 0;
// the weapon id. use this to refer to the mp5 in your
code
p->iId = m_iId = WEAPON_MP5;
// weapon weight factors, this is to see which weapon
should be switched to when ammo runs out in weapon
p->iWeight = MP5_WEIGHT;
return 1;
}
//=================
//p->iFlags = wtf??
//=================
/*
* ok, well there has been alot of confusion with this.
* To find out what it did i simply used the
* 'Find in Files..' command from the 'Edit' menu to look
* for the string 'iFlags'. That brought up 81 different
* examples.
* ITEM_FLAG_LIMITINWORLD
* ITEM_FLAG_EXHAUSTIBLE
* ITEM_FLAG_NOAUTOSWITCHEMPTY
* ITEM_FLAG_NOAUOTRELOAD
* ITEM_FLAG_SELECTONEMPTY
*
* /////ITEM_FLAG_LIMITINWORLD\\\\\
*
* mulitplay_gamerules.cpp -
* when we are within this close to running out of entities, items
* marked with the ITEM_FLAG_LIMITINWORLD will delay their respawn
*
* /////ITEM_FLAG_EXHAUSTIBLE\\\\\
*
* If you run out of this then the HUD will not have a sprite displayed for the
weapon
* Use this for weapons like grenades, you physically wont have any so why have
the sprite?
*
* /////ITEM_FLAG_NOAUTOSWITCHEMPTY\\\\\
*
* this one is quite clever when you think about it, only used in the hornetgun,
when you have fired
* the eight hornets the weapon should actually switch because the clip is empty
but this flag
* stops that from happening, interesting
*
* /////ITEM_FLAG_NOAUTORELOAD\\\\\
*
* another one for the hornetgun, as the hornets are self generating there is no
reload function
* as you dont get a 'clip' of eight hornet, you get one back a second or so.
*
* /////ITEM_FLAG_SELECTONEMPTY\\\\\
*
* When you have one satchel left and you press fire, the counter goes down to
zero but you still
* have to detonate it so this flag keeps the weapon from swithcing. I don't know
why valve
* didn't use 'ITEM_FLAG_NOAUTOSWITCH, but doesn't matter.
*/
//=================================================
//PART SEVEN
//=================================================
// This is all about messaging the client DLL and telling it that the player has
the weapon.
int CMP5::AddToPlayer( CBasePlayer *pPlayer
)
{
if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) )
{
// start a
message telling the client they have picked up a weapon
MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev );
// this says what weapon it is, though you dont need
to put WEAPON_MP5 or anything, just m_iId
WRITE_BYTE( m_iId );
MESSAGE_END(); // ends the message
return TRUE;
}
return FALSE;
}
//=================================================
//PART EIGHT
//=================================================
BOOL CMP5::Deploy( )
{
// 3rd person model - 1st person model 1st person anim
- weapon name
return DefaultDeploy( "models/v_9mmAR.mdl", "models/p_9mmAR.mdl", MP5_DEPLOY,
"mp5" );
}
//=================================================
//PART NINE
//=================================================
// This is obvious, this is what happens when you press the fire button
("attack")
void CMP5::PrimaryAttack()
{
// Dont fire the weapon if under water, play the empty
sound though
// SGC -
waterlevel 0 - not in water, 1 - feet in water, 2 - waist in water, 3 - head in
water
if (m_pPlayer->pev->waterlevel == 3)
{
PlayEmptySound( );
m_flNextPrimaryAttack = gpGlobals->time + 0.15;
return;
}
// if the clip is less than or equal to 0 the play the
empty sound
if (m_iClip <= 0)
{
PlayEmptySound();
m_flNextPrimaryAttack = gpGlobals->time + 0.15;
return;
}
PLAYBACK_EVENT( 0, m_pPlayer->edict(), m_usMP5 ); // this calls the event from the .sc file
m_pPlayer->m_iWeaponVolume =
NORMAL_GUN_VOLUME; // how loud the gun is
m_pPlayer->m_iWeaponFlash = NORMAL_GUN_FLASH; // how
much of a flash firing the weapon gives off
m_iClip--; // everytime the weapon fires, take one bullet away
// player "shoot"
animation
// this calls the "shoot" animation note: the animation ISN'T called
PLAYER_ATTACK1
m_pPlayer->SetAnimation( PLAYER_ATTACK1 );
Vector vecSrc = m_pPlayer->GetGunPosition( );
Vector vecAiming = m_pPlayer->GetAutoaimVector( AUTOAIM_5DEGREES );
// how much autoaim
// two fire commands.
one for multuplayer, one for single. the way they are written out
// remains the
same though m_pPlayer->FireBullets( number of bullets fired, vecSrc,
// vecAiming, spread of fire, distance bullet travels, bullet tyoe, frequency
of the tracer );
// vecSrc is already defined
// vecAiming is already defined
if ( g_pGameRules->IsDeathmatch() )
// if the game mode is deathmatch...
{
// optimized multiplayer. Widened to make it easier
to hit a moving player
// see above for variable
m_pPlayer->FireBullets( 1, vecSrc, vecAiming, VECTOR_CONE_6DEGREES,
8192, BULLET_PLAYER_MP5, 2 );
}
else // if it isnt....
{
// single player spread
// see above for variable
m_pPlayer->FireBullets( 1, vecSrc, vecAiming, VECTOR_CONE_3DEGREES, 8192,
BULLET_PLAYER_MP5, 2 );
}
if (!m_iClip &&
m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0)
// HEV suit - indicate out of ammo condition
m_pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0);
// delay before the next
attack
// SGC - ie. the weapon cant fire for 0.1 seconds
m_flNextPrimaryAttack =
m_flNextPrimaryAttack + 0.1;
if (m_flNextPrimaryAttack < gpGlobals->time)
m_flNextPrimaryAttack = gpGlobals->time + 0.1;
m_flTimeWeaponIdle = gpGlobals->time +
RANDOM_FLOAT ( 10, 15 ); // time til weapon idle(random)
}
//=================================================
//PART TEN
//=================================================
// your secondary mode of fire
void CMP5::SecondaryAttack( void )
{
// don't fire underwater
// same a primary fire
if (m_pPlayer->pev->waterlevel == 3)
{
PlayEmptySound( );
m_flNextPrimaryAttack = gpGlobals->time + 0.15;
return;
}
// if no ammo,
play empty sound and reset
if (m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType] == 0)
{
PlayEmptySound( );
return;
}
m_pPlayer->m_iWeaponVolume =
NORMAL_GUN_VOLUME; // sound level
m_pPlayer->m_iWeaponFlash = BRIGHT_GUN_FLASH; // flash
level
m_pPlayer->m_iExtraSoundTypes =
bits_SOUND_DANGER;
m_pPlayer->m_flStopExtraSoundTime = gpGlobals->time + 0.2;
m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType]-- ; //
decrease the ammo amount each time fired
SendWeaponAnim( MP5_LAUNCH ); // play the fire animation
// player "shoot"
animation
m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); // plays the
fire action
if ( RANDOM_LONG(0,1) )
{
// play this sound through BODY channel so we can
hear it if player didn't stop firing MP3
EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON,
"weapons/glauncher.wav", 0.8, ATTN_NORM); // play a sound
}
else
{
// play this sound through BODY channel so we can
hear it if player didn't stop firing MP3
EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON,
"weapons/glauncher2.wav", 0.8, ATTN_NORM); // play a sound
}
// SGC - This code actually fires the grenade (see the
CGrenade class).
// UTIL_MakeVectors sets the v_forward vector to the players current forward
view, then
// CGrenade::ShootContact creates the flying grenade
UTIL_MakeVectors( m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle
);
// we don't add in
player velocity anymore.
// this is the arch that the grenade takes when you fire it. alter the
numbers to alter it (16 and 800)
CGrenade::ShootContact( m_pPlayer->pev,
m_pPlayer->pev->origin + m_pPlayer->pev->view_ofs +
gpGlobals->v_forward * 16,
gpGlobals->v_forward * 800 );
m_flNextPrimaryAttack = gpGlobals->time
+ 1; // SGC - prevent refiring bullets for 1 second
m_flNextSecondaryAttack = gpGlobals->time + 1;
// SGC - prevent refiring grenade for 1 second
m_flTimeWeaponIdle = gpGlobals->time + 5;// idle pretty
soon after shooting.
if
(!m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType])
// HEV suit - indicate out of ammo condition
m_pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0);
// SGC - this line
knocks the player back a bit to simulate kickback from the gun.
m_pPlayer->pev->punchangle.x -= 10;
}
//=================================================
//PART ELEVEN
//=================================================
//the reload funtion for your primary fire
void CMP5::Reload( void )
{
// sets your clip so full and plays the MP5_RELOAD
animation. the 1.5 is a delay
DefaultReload( MP5_MAX_CLIP, MP5_RELOAD, 1.5 );
}
//=================================================
//PART TWELVE
//=================================================
// this function is to play the weapon idle animation
void CMP5::WeaponIdle( void )
{
ResetEmptySound( ); // does exactly what it says in the
code
m_pPlayer->GetAutoaimVector( AUTOAIM_5DEGREES ); // set auto aim
// if the time
idle HAS NOT reached the time set for m_flTimeWeaponIdle, just skip this until
it has
if (m_flTimeWeaponIdle > gpGlobals->time)
return;
int iAnim; // this
is a random "animation" generator. it will randomly choose which animation to
play
switch ( RANDOM_LONG( 0, 1 ) )
{
case 0:
iAnim = MP5_LONGIDLE;
break;
default:
case 1:
iAnim = MP5_IDLE1;
break;
}
// once the animation has been choose, call the
animation
SendWeaponAnim( iAnim );
m_flTimeWeaponIdle = gpGlobals->time +
RANDOM_FLOAT ( 10, 15 );// how long till we do this again.
}
//=================================================
//END
//=================================================
This tutorial by Greg Hudson.
Edited by Steve Cook