VERC: Making A Semi-Automatic Handgun Last edited 15 years ago2003-12-12 20:47:00 UTC by Penguinboy Penguinboy

This article was recovered from an archive and needs to be reviewed

  1. The formatting may be incorrect as it was automatically converted to WikiCode from HTML, it needs to be revised and reformatted
  2. Some information may be out of date as it was written before Half-Life was available on Steam
  3. After the article is re-formatted and updated for Steam HL, remove this notice
  4. Please do not remove the archive notice from the bottom of the article.
  5. Some archive articles are no longer useful, or they duplicate information from other tutorials and entity guides. In this case, delete the page after merging any relevant information into other pages. Contact an admin to delete a page.
My code is in red in this article. Also I assume you are doing this on the default Glock, but it could very well work on just any other weapon (except the RPG, but I will explain that later)


First, open weapons.h. We're going to have to declare a new variable to indicate whether the trigger is pressed or released. Scroll down until you find the CGlock class declaration:

class CGlock : public CBasePlayerWeapon{public:     void Spawn( void );     void Precache( void );     int iItemSlot( void ) { return 2; }     int GetItemInfo(ItemInfo *p);          void PrimaryAttack( void );     void SecondaryAttack( void );     void GlockFire( float flSpread, float flCycleTime, BOOL fUseAutoAim );     BOOL Deploy( void );     void Reload( void );     void WeaponIdle( void );     BOOL TriggerReleased; // This declares the TriggerReleased boolean....


Now open hl_wpn_glock.cpp and go to the definition of Deploy:

BOOL CGlock::Deploy( ){     TriggerReleased = FALSE;     return DefaultDeploy( "models/v_9mmhandgun.mdl", "models/p_9mmhandgun.mdl", GLOCK_DRAW, "onehanded", UseDecrement() ? 1 : 0, pev->body );     }
Since you need to press the fire key to confirm a weapon deploy, we consider that this key is down when the weapon is deployed (this is just to set a value anyway). Next, go to PrimaryAttack:

void CGlock::PrimaryAttack( void ){     if (!TriggerReleased )          return;     GlockFire( 0.01, 0.3, TRUE );     TriggerReleased = FALSE;}
Pretty simple, if the trigger has not been released, you can't shoot, and if you shoot, we can assume that the trigger is down. You can do that on the Secondary attack, too.

Now we need to set back TriggerReleased to TRUE at some moment, or the gun will never fire. This is simply done in WeaponIdle:

void CGlock::WeaponIdle( void ){     TriggerReleased = TRUE;     ResetEmptySound( );          m_pPlayer->GetAutoaimVector( AUTOAIM_10DEGREES );          if ( m_flTimeWeaponIdle > UTIL_WeaponTimeBase() )          return;...
As soon as you stop pressing the attack button, WeaponIdle() will be called and TriggerReleased set to true. Make sure you do this before the test on m_flTimeWeaponIdle though or you would need to wait for an actual idle animation before shooting again.
Note that you won't be able to shoot as fast as you press the firing key though, the delay between attacks is still here, preventing you to shoot more than one round every 0.3 seconds. If you need to, change this.
Also, don't forget to compile the client-side dll, the hl_wpn_glock.cpp and weapons.h files are part of BOTH the hl and client projects.

If you are really new to coding and don't want to know how it works then you can scrap the rest of the tutorial...

Let's take a look at the ItemPostFrame() function in weapons.cpp which handles the main weapon code (it may slightly differ from the original but nothing important)

void CBasePlayerWeapon::ItemPostFrame( void ){     if ((m_fInReload) && ( m_pPlayer->m_flNextAttack <= UTIL_WeaponTimeBase() ) )     {     // complete the reload.      int j = min( iMaxClip() - m_iClip, m_pPlayer->m_rgAmmm_iPrimaryAmmoType]);          // Add them to the clip     m_iClip += j;     m_pPlayer->m_rgAmmm_iPrimaryAmmoType] -= j;     m_pPlayer->TabulateAmmo();     m_fInReload = FALSE;     }     if ((m_pPlayer->pev->button & IN_ATTACK2) && CanAttack( m_flNextSecondaryAttack, gpGlobals->time, UseDecrement() ) )     {          if ( pszAmmo2() && !m_pPlayer->m_rgAmmSecondaryAmmoIndex()] )     {          m_fFireOnEmpty = TRUE;     }     m_pPlayer->TabulateAmmo();     SecondaryAttack();     m_pPlayer->pev->button &= ~IN_ATTACK2;     }     else if ((m_pPlayer->pev->button & IN_ATTACK) && CanAttack( m_flNextPrimaryAttack, gpGlobals->time, UseDecrement() ) ){     if ( (m_iClip == 0 && pszAmmo1()) || (iMaxClip() == -1 && !m_pPlayer->m_rgAmmPrimaryAmmoIndex()] ) )     {          m_fFireOnEmpty = TRUE;     }     m_pPlayer->TabulateAmmo();     PrimaryAttack();}else if ( m_pPlayer->pev->button & IN_RELOAD && iMaxClip() != WEAPON_NOCLIP && !m_fInReload ) {     // reload when reload is pressed, or if no buttons are down and weapon is empty.     Reload();}else if ( !(m_pPlayer->pev->button & (IN_ATTACK|IN_ATTACK2) ) ) {     // no fire buttons down     m_fFireOnEmpty = FALSE;     if ( !IsUseable() && m_flNextPrimaryAttack < ( UseDecrement() ? 0.0 : gpGlobals->time ) )      {          // weapon isn't useable, switch.          if ( !(iFlags() & ITEM_FLAG_NOAUTOSWITCHEMPTY) && g_pGameRules->GetNextBestWeapon( m_pPlayer, this ) )          {               m_flNextPrimaryAttack = ( UseDecrement() ? 0.0 : gpGlobals->time ) + 0.3;               return;          }     }     else     {          // weapon is useable. Reload if empty and weapon has waited as long as it has to after firing          if ( m_iClip == 0 && !(iFlags() & ITEM_FLAG_NOAUTORELOAD) && m_flNextPrimaryAttack < ( UseDecrement() ? 0.0 : gpGlobals->time ) )     {          Reload();          return;     }}WeaponIdle( );return;}     // catch allif ( ShouldWeaponIdle() ){     WeaponIdle();}}
You can see that the code is made in such a way that WeaponIdle() will only be activated if none of the attack buttons are pressed (by default it used to be only the primary attack button, I think), which is exactly what we needed to achieve the desired effect. If the WeaponIdle code has to be executed anyway, there is a test for ShouldWeaponIdle(). This returns false for all weapons but the RPG, which needs to update the laserspot. If for some reason you absolutely needed to execute the WeaponIdle code, then we would need to modify ItemPostFrame and store the buttons state from the previous frame. Now that's your problem. ;)
This article was originally published on the Valve Editing Resource Collective (VERC).
TWHL only archives articles from defunct websites. For more information on TWHL's archiving efforts, please visit the TWHL Archiving Project page.


You must log in to post a comment. You can login or register a new account.