Half-Life Weapons: MP5 Example

Intro

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.

MP5 Code (mp5.cpp)


//=================================================
//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