VERC: Making A Semi-Automatic Handgun Last edited 1 year ago2022-09-29 07:55:08 UTC

A note from the editor
This article made use of text colouring to differentiate between "existing" code and "new" code. This colouring is not possible on TWHL, so it's a good idea to refer back to the original archived article (see the end of the page) if you're not sure.
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; // added
    // 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; // added
    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; // added

    GlockFire( 0.01, 0.3, TRUE );

    TriggerReleased = FALSE; // added
}
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; // added

    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 <= 0.0))
    {
#if 0 // FIXME, need ammo on client to make this work right
        // complete the reload.
        int j = min( iMaxClip() - m_iClip, m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]);

        // Add them to the clip
        m_iClip += j;
        m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= j;
#else
        m_iClip += 10;
#endif
        m_fInReload = FALSE;
    }

    if ((m_pPlayer->pev->button & IN_ATTACK2) && (m_flNextSecondaryAttack <= 0.0))
    {
        if ( pszAmmo2() && !m_pPlayer->m_rgAmmo[SecondaryAmmoIndex()] )
        {
            m_fFireOnEmpty = TRUE;
        }

        SecondaryAttack();
        m_pPlayer->pev->button &= ~IN_ATTACK2;
    }
    else if ((m_pPlayer->pev->button & IN_ATTACK) && (m_flNextPrimaryAttack <= 0.0))
    {
        if ( (m_iClip == 0 && pszAmmo1()) || (iMaxClip() == -1 && !m_pPlayer->m_rgAmmo[PrimaryAmmoIndex()] ) )
        {
            m_fFireOnEmpty = TRUE;
        }

        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;

        // 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 < 0.0 )
        {
            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. ;)
This article was originally published on Valve Editing Resource Collective (VERC).
The archived page is available here.
TWHL only publishes archived articles from defunct websites, or with permission. For more information on TWHL's archiving efforts, please visit the TWHL Archiving Project page.

Comments

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