Check out Half-Life Re-imagined competition results!
Check out Skewing textures in Hammer, our newest tutorial!
Welcome, Daedle, our newest member!

logo

Site Stuff

Reference

Maps

Community

ShoutBOX

Poll

Feeling Blue

What's your favourite shade of blue?

Azure

11

Cobalt

24

Turquoise

7

Cyan

9

Royal

5

Teal

3

Onliners

8 mins

Instant Mix

13 mins

Admer456

13 mins

Solokiller

13 mins

Screamernail

22 mins

Jessie

34 mins

Dr. Orange

37 mins

Ghost129er

Affiliates

A gaming and technology blog by TWHL admins Penguinboy and Ant. A music blog by TWHL users Ant and Hugh.

Making A Semi-Automatic Handgun

By Lo?c 'Loc' Visse

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)

weapons.h
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.

.
.
.

hl_wpn_glock.cpp
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_rgAmm

    m_iPrimaryAmmoType]);     

         // Add them to the clip
         m_iClip += j;
         m_pPlayer->m_rgAmm

      m_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_rgAmm

        SecondaryAmmoIndex()] )
             {
                  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_rgAmm

          PrimaryAmmoIndex()] ) )
               {
                    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 all
          if ( 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. ;)