Check out Half-Life Re-imagined competition results!
Check out Skewing textures in Hammer, our newest tutorial!
Say hello to saviorofearth, our newest member!

logo

Site Stuff

Reference

Maps

Community

ShoutBOX

Poll

Feeling Blue

What's your favourite shade of blue?

Azure

13

Cobalt

32

Turquoise

9

Cyan

10

Royal

8

Teal

3

Onliners

13 mins

Solokiller

19 mins

The Mad Carrot

23 mins

Dr. Orange

24 mins

UrbaNebula

30 mins

JeffMOD

39 mins

Instant Mix

42 mins

Jessie

Affiliates

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

Creating a Sniper Rifle in the 2.2 SDK

By Robert Prouse

In the last weapons tutorial we used the SecondaryFire method in the weapon class to switch between full- auto and semi-auto. In this tutorial, we are going to redo the same weapon, but this time, we are going to use the secondary fire mode to switch between zoomed in and zoomed out, like a sniper rifle. To keep it simple, we are going to pretend that this is an M16 with a sniper scope on it so that you can compare this tutorial with the last. You will notice that much of this tutorial is the same as the last, this should give you a good idea what stays the same for all weapons and what changes.

I am assuming that you have the 2.2 Half-life SDK installed and you can compile both the client dll and the server dll. If not, then you better head out and read a few other tutorials first. And now for a few conventions:

Filenames are in green,
Original code is in blue,
Added code is in red.

Please note though that you cannot just drop this weapon directly into your mod, you will need to add models and sounds, or modify the code to use your models and sounds. I also may have forgotten some of the includes at the start of some of the files. Our mod is fairly different, so I had to improvise here and there. If this is the case, it should be pretty easy for you to figure out what is missing.

I will also only comment on the changes to the code between this and the last tutorial.

Okay, let's get at it...

Server Side Modifications

We will start working on the server dll, so open up the hl project and begin by adding a .cpp and .h file to the project for your weapon. These files should be in the dlls directory of the source, for this project, we will call them m16.cpp and m16.h. Open up m16.h and add the following code which will define the CM16 class;

dlls\m16.h

#ifndef TOD_M16_H
#define TOD_M16_H

class CM16 : public CBasePlayerWeapon
{
public:
    virtual void Spawn( void );
    virtual void Precache( void );
    virtual int  iItemSlot( void ) { return M16_SLOT; }
    virtual int  GetItemInfo(ItemInfo *p);
    virtual int  AddToPlayer( CBasePlayer *pPlayer );
    
    virtual void PrimaryAttack( void );
    virtual void SecondaryAttack( void );  // switch between scope and iron sights
    virtual BOOL Deploy( void );
    virtual void Holster( int skiplocal = 0 );
    virtual void Reload( void );
    virtual void WeaponIdle( void );
    
    virtual BOOL UseDecrement( void ) { return TRUE; }
    virtual float Accuracy(float acc);

private:
    int  m_iShell;
    BOOL InZoom() { return m_pPlayer->pev->fov != 0; }
    unsigned short m_event;
    unsigned short m_event_z;
};

class CM16AmmoClip : public CBasePlayerAmmo
{
    virtual void Spawn( void );
    virtual void Precache( void );
    virtual BOOL AddAmmo( CBaseEntity *pOther ) ;
};
#endif

I am using the SecondaryAttack method to zoom in on like on a sniper rifle. The only change here is that I have replaced the m_bSingleFire variable with a InZoom() variable and added another event variable for the zoomed in event. Also notice that we are overriding the Holster method from the base class, you will see why later.

Now open m16.h and add the following code.

dlls\m16.cpp

#include "extdll.h"
#include "util.h"
#include "cbase.h"
#include "monsters.h"
#include "weapons.h"
#include "nodes.h"
#include "player.h"
#include "soundent.h"
#include "gamerules.h"

#include "m16.h"

enum m16a1_e
{
    M16_IDLE_SIL = 0,
    M16_IDLE,
    M16_FIRE1,
    M16_FIRE2,
    M16_FIRE3,
    M16_RELOAD,
    M16_DEPLOY,
};

void CM16::Spawn( )
{
    pev->classname = MAKE_STRING("weapon_m16"); // hack to allow for old names
    Precache( );
    SET_MODEL(ENT(pev), M16_MODEL_WORLD);
    m_iId          = WEAPON_M16;    
    m_iDefaultAmmo = M16_DEFAULT_AMMO;
    FallInit();// get ready to fall down.
}

There are no changes in this section.


void CM16::Precache( void )
{
    PRECACHE_MODEL(M16_MODEL_1STPERSON);
    PRECACHE_MODEL(M16_MODEL_3RDPERSON);
    PRECACHE_MODEL(M16_MODEL_WORLD);
    
    m_iShell = PRECACHE_MODEL ("models/shell.mdl");// brass shell
           
    PRECACHE_SOUND (M16_SOUND_FIRE1);
    PRECACHE_SOUND (M16_SOUND_FIRE2);   
    
    m_event   = PRECACHE_EVENT( 1, "events/m16.sc" );
    m_event_z = PRECACHE_EVENT( 1, "events/m16z.sc" );
}

I have added the second event for the zoomed in fire, make sure you create the empty m16z.sc file in the events directory.


int CM16::GetItemInfo(ItemInfo *p)
{
    p->pszName   = STRING(pev->classname);
    p->pszAmmo1  = "ammo_m16";              // The type of ammo it uses
    p->iMaxAmmo1 = M16_MAX_AMMO;            // Max ammo the player can carry
    p->pszAmmo2  = NULL;                    // No secondary ammo
    p->iMaxAmmo2 = -1;
    p->iMaxClip  = M16_DEFAULT_AMMO;        // The clip size
    p->iSlot     = M16_SLOT - 1;            // The number in the HUD
    p->iPosition = M16_POSITION;            // The position in a HUD slot
    p->iFlags    = ITEM_FLAG_NOAUTOSWITCHEMPTY | ITEM_FLAG_SELECTONEMPTY;
    p->iId       = m_iId = WEAPON_M16;      // The weapon id
    p->iWeight   = M16_WEIGHT;              // for autoswitching
    
    return 1;
}

int CM16::AddToPlayer( CBasePlayer *pPlayer )
{
    if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) )
    {
          MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev );
          WRITE_BYTE( m_iId );
          MESSAGE_END();
          return TRUE;
    }
    return FALSE;
}

BOOL CM16::Deploy( )
{
    return DefaultDeploy( M16_MODEL_1STPERSON, M16_MODEL_3RDPERSON, 
                          M16_DEPLOY, "mp5" );
}

No changes in here, let's look at the primary attack since that is where most of our changes are.


void CM16::PrimaryAttack()
{
    // This weapon is single shot when zoomed in, auto otherwise
    if (!(m_pPlayer->m_afButtonPressed & IN_ATTACK) && InZoom())
          return;

    // don't fire underwater
    if (m_pPlayer->pev->waterlevel == 3)
    {
          PlayEmptySound( );
          m_flNextPrimaryAttack = 0.15;
          return;
    }
    
    // don't fire if empty
    if (m_iClip <= 0)
    {
          PlayEmptySound();
          m_flNextPrimaryAttack = 0.15;
          return;
    }

    // Weapon sound
    m_pPlayer->m_iWeaponVolume = NORMAL_GUN_VOLUME;
    m_pPlayer->m_iWeaponFlash  = NORMAL_GUN_FLASH;

    // one less round in the clip
    m_iClip--;
    
    // add a muzzle flash
    m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH;
    
    // player "shoot" animation
    m_pPlayer->SetAnimation( PLAYER_ATTACK1 );
    
    // fire off a round
    Vector vecSrc(m_pPlayer->GetGunPosition());
    Vector vecAim(m_pPlayer->GetAutoaimVector(AUTOAIM_2DEGREES));    
    // Accuracy of the weapon in degrees, zoomed in is way more accurate
    Vector vecAcc(InZoom() ? VECTOR_CONE_1DEGREES : VECTOR_CONE_3DEGREES;
    Vector vecDir(m_pPlayer->FireBulletsPlayer( 1,           // how many shots 
                                                vecSrc,      
                                                vecAim,      
                                                vecAcc,      // accuracy
                                                8192,        // max range
                                                BULLET_PLAYER_M16, // bullet type
                                                0,           // tracer frequency
                                                0,           // damage
                                                m_pPlayer->pev, 
                                                m_pPlayer->random_seed ));
    
    // Fire off the client side event
    PLAYBACK_EVENT_FULL( FEV_NOTHOST, m_pPlayer->edict(), 
                         (m_InZoom ? m_event_z :m_event), 0.0, 
                         (float *)&g_vecZero, (float *)&g_vecZero, 
                         vecDir.x, vecDir.y, 0, 0, (m_iClip ? 0 : 1), 0 );
    
    // Add a delay before the player can fire the next shot
    m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + M16_FIRE_DELAY;    
    m_flTimeWeaponIdle    = UTIL_WeaponTimeBase() + 
                            UTIL_SharedRandomFloat(m_pPlayer->random_seed, 
                                        M16_FIRE_DELAY + 1, M16_FIRE_DELAY + 2);
}

Starting at the top of this function, I have changed the code that makes the weapon single shot so that it only does it when it is zoomed in. The only other changes are in the accuracy, zoomed in is way more accurate and the PLAYBACK_EVENT_FULL chooses which event to send based on whether or not it is zoomed in.


// Zoom in/out
void CM16::SecondaryAttack( void )
{
    if ( m_pPlayer->pev->fov != 0 )
    {
        m_pPlayer->pev->fov = m_pPlayer->m_iFOV = 0; // 0 means reset to default fov
    }
    else if ( m_pPlayer->pev->fov != 20 )
    {
        m_pPlayer->pev->fov = m_pPlayer->m_iFOV = 20;
    }

    pev->nextthink = UTIL_WeaponTimeBase() + 0.1;
    m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 1.0;
}

Secondary attack is the biggest change. Here we change the field of view (fov) for the player. This simulates looking through a rifle scope. Changing m_pPlayer->pev->fov automatically gets sent to the client.


void CM16::Holster( int skiplocal /* = 0 */ )
{
    m_fInReload = FALSE;// cancel any reload in progress.
    
    if ( InZoom() )
    {
        SecondaryAttack( );
    }
    
    m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5;
}

We must over-ride the Holster method so that we can reset the field of view to normal before the weapon gets changed. If we don't do this, the next weapon selected will still have the same fov (it will look zoomed in.)


void CM16::Reload( void )
{
    DefaultReload( M16_DEFAULT_AMMO, M16_RELOAD, M16_RELOAD_TIME );
}

void CM16::WeaponIdle( void )
{
    ResetEmptySound( );
    
    m_pPlayer->GetAutoaimVector( AUTOAIM_5DEGREES );
    
    if (m_flTimeWeaponIdle > UTIL_WeaponTimeBase())
          return;
        
    SendWeaponAnim( M16_IDLE );
    
    m_flTimeWeaponIdle = UTIL_SharedRandomFloat(m_pPlayer->random_seed, 10, 15);
}

void CM16AmmoClip::Spawn( void )
{ 
    Precache( );
    SET_MODEL(ENT(pev), "models/w_9mmARclip.mdl");
    CBasePlayerAmmo::Spawn( );
}

void CM16AmmoClip::Precache( void )
{
    PRECACHE_MODEL ("models/w_9mmARclip.mdl");
    PRECACHE_SOUND("items/9mmclip1.wav");
}

BOOL CM16AmmoClip::AddAmmo( CBaseEntity *pOther ) 
{ 
    int bResult = (pOther->GiveAmmo(M16_DEFAULT_AMMO, "ammo_m16", 
                                    M16_MAX_AMMO) != -1);
    if (bResult)
    {
        EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM);
    }
    return bResult;
}

These methods round out our weapon and are the same as before. Now make the same changes as before to weapons.h. I will review them here for completeness, but make sure you look through the last tutorial if you have any questions.

dll\weapons.h


#define WEAPON_HANDGRENADE    12
#define WEAPON_TRIPMINE       13
#define WEAPON_SATCHEL        14
#define WEAPON_SNARK          15


#define WEAPON_M16            16

This must be a unique value.


// weapon weight factors (for auto-switching)   (-1 = noswitch)
#define CROWBAR_WEIGHT    0
#define GLOCK_WEIGHT     10
#define PYTHON_WEIGHT    15
#define MP5_WEIGHT       15


#define M16_WEIGHT       15

Our M16 will have the same switching weight as the MP5


#define M16_MODEL_1STPERSON "models/v_m16a1.mdl" 
#define M16_MODEL_3RDPERSON "models/p_m16a1.mdl" 
#define M16_MODEL_WORLD     "models/w_m16a1.mdl" 
#define M16_SOUND_FIRE1     "weapons/m16a1_fire-1.wav" 
#define M16_SOUND_FIRE2     "weapons/m16a1_fire-2.wav" 
#define M16_SOUND_VOLUME    0.85 
#define M16_FIRE_DELAY      0.085 // was: .15 For comparison, glock's is 0.2 
#define M16_RELOAD_TIME     2.0
#define M16_DEFAULT_AMMO    30 
#define M16_MAX_AMMO        180
#define M16_SLOT            2
#define M16_POSITION        1

Same stuff here.


// bullet types
typedef    enum
{
    BULLET_NONE = 0,
    BULLET_PLAYER_9MM, // glock
    BULLET_PLAYER_MP5, // mp5
    BULLET_PLAYER_357, // python
    BULLET_PLAYER_BUCKSHOT, // shotgun
    BULLET_PLAYER_CROWBAR, // crowbar swipe


    BULLET_PLAYER_M16,
  

    BULLET_MONSTER_9MM,
    BULLET_MONSTER_MP5,
    BULLET_MONSTER_12MM,
} Bullet;

Everything in weapons.h was the same, now let's make the same changes The other server side files;

dlls\combat.cpp

else switch(iBulletType)
{
    default:
    case BULLET_PLAYER_9MM:        
        pEntity->TraceAttack(pevAttacker, gSkillData.plrDmg9MM, 
                             vecDir, &tr, DMG_BULLET); 
        break;
        
    case BULLET_PLAYER_MP5:


    case BULLET_PLAYER_M16:
        pEntity->TraceAttack(pevAttacker, gSkillData.plrDmgMP5, 
                             vecDir, &tr, DMG_BULLET); 
        break;
dlls\func_break.cpp

const char *CBreakable::pSpawnObjects[] =
{
    NULL,                // 0
    "item_battery",        // 1
    "item_healthkit",    // 2
    "weapon_9mmhandgun",// 3
    "ammo_9mmclip",        // 4
    "weapon_9mmAR",        // 5
    "ammo_9mmAR",        // 6
    "ammo_ARgrenades",    // 7
    "weapon_shotgun",    // 8
    "ammo_buckshot",    // 9
    "weapon_crossbow",    // 10
    "ammo_crossbow",    // 11
    "weapon_357",        // 12
    "ammo_357",            // 13
    "weapon_rpg",        // 14
    "ammo_rpgclip",        // 15
    "ammo_gaussclip",    // 16
    "weapon_handgrenade",// 17
    "weapon_tripmine",    // 18
    "weapon_satchel",    // 19
    "weapon_snark",        // 20
    "weapon_hornetgun",    // 21


    "weapon_m16",
    "ammo_m16",


};
dlls\weapons.cpp

// called by worldspawn
void W_Precache(void)
{
    memset(CBasePlayerItem::ItemInfoArray, 0, 
           sizeof(CBasePlayerItem::ItemInfoArray));
    memset(CBasePlayerItem::AmmoInfoArray, 0, 
           sizeof(CBasePlayerItem::AmmoInfoArray));
    giAmmoIndex = 0;

    // custom items...


    // m16
    UTIL_PrecacheOtherWeapon( "weapon_m16" );
    UTIL_PrecacheOther( "ammo_m16" );

Now, if you want to automatically give your weapon to a player in a game, you can do something like this... Open up multiplay_gamerules.cpp and give the player the weapon when they spawn.

dlls\multiplay_gamerules.cpp

void CHalfLifeMultiplay :: PlayerSpawn( CBasePlayer *pPlayer )
{
    BOOL        addDefault;
    CBaseEntity    *pWeaponEntity = NULL;

    pPlayer->pev->weapons |= (1<Touch( pPlayer );
        addDefault = FALSE;
    }

    if ( addDefault )
    {
        pPlayer->GiveNamedItem( "weapon_crowbar" );
        pPlayer->GiveNamedItem( "weapon_9mmhandgun" );
        pPlayer->GiveAmmo( 68, "9mm", _9MM_MAX_CARRY );// 4 full reloads


        pPlayer->GiveNamedItem( "weapon_m16" );
        pPlayer->GiveAmmo( M16_MAX_AMMO, "ammo_m16", M16_MAX_AMMO ); 


    }
}

Client Side Modifications

That's it for server side changes, now lets move on to the client. The major change on the client side is that you need a second event for the zoomed in fire. All of the changes here will be towards that end.

You will also want to add a sprite and modify the hud text file for the zoomed in crosshair. We use a sniper scope sprite and added lines like the following to our HUD text file for this weapon;


crosshair     320 crosshairs 24  0  24  24
autoaim       320 crosshairs  0 72  24  24


zoom          320 ch_sniper   0  0 256 256
zoom_autoaim  320 ch_sniper   0  0 256 256
If you need more information on this, check out this tutorial on weapon HUD sprites.

Now it is time to add in the client side events. Open up ev_hldm.cpp and we will add the following lines near the top. Notice, one more event.

cl_dll\ev_hldm.cpp

void EV_TripmineFire( struct event_args_s *args  );
void EV_SnarkFire( struct event_args_s *args  );


void EV_FireM16( struct event_args_s *args  );
void EV_FireM16Zoomed( struct event_args_s *args  );

The first event is the same;


//======================
//        M16 START
//======================
void EV_FireM16( event_args_t *args )
{
    int idx;
    vec3_t origin;
    vec3_t angles;
    vec3_t velocity;

    vec3_t ShellVelocity;
    vec3_t ShellOrigin;
    int shell;
    vec3_t vecSrc, vecAiming;
    vec3_t up, right, forward;
    float flSpread = 0.01;

    idx = args->entindex;
    VectorCopy( args->origin, origin );
    VectorCopy( args->angles, angles );
    VectorCopy( args->velocity, velocity );

    AngleVectors( angles, forward, right, up );
    
    if ( EV_IsLocal( idx ) )
    {
        // Add muzzle flash to current weapon model
        EV_MuzzleFlash();
        gEngfuncs.pEventAPI->EV_WeaponAnimation(M16_FIRE1 + 
                                                gEngfuncs.pfnRandomLong(0,2), 2);

        // This gives it a bit of kick, adjust the numbers to your liking
        V_PunchAxis( 0, gEngfuncs.pfnRandomFloat( -2, 2 ) );
    }

    // Eject shells, adjust the last three numbers to move the start point that the
    // shells eject from to fit your model
    shell = gEngfuncs.pEventAPI->EV_FindModelIndex ("models/shell.mdl"); 
    EV_GetDefaultShellInfo( args, origin, velocity, ShellVelocity, 
                            ShellOrigin, forward, right, up, 12, -10, 7 );
    EV_EjectBrass(ShellOrigin, 
                  ShellVelocity, 
                  angles[ YAW ], 
                  shell, 
                  TE_BOUNCE_SHELL); 

    // Play the fire sound
    switch( gEngfuncs.pfnRandomLong( 0, 1 ) )
    {
    case 0:
        gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, 
                                       "weapons/m16a1_fire-1.wav", 
                                       1, ATTN_NORM, 0, 
                                       94 + gEngfuncs.pfnRandomLong( 0, 0xf ) );
        break;
    case 1:
        gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, 
                                       "weapons/m16a1_fire-2.wav", 
                                       1, ATTN_NORM, 0, 
                                       94 + gEngfuncs.pfnRandomLong( 0, 0xf ) );
        break;
    }

    // Fire off the bullets client side
    EV_GetGunPosition( args, vecSrc, origin );
    VectorCopy( forward, vecAiming );
    EV_HLDM_FireBullets( idx, 
                       forward, 
                       right, 
                       up, 
                       1, 
                       vecSrc, 
                       vecAiming, 
                       8192, 
                       BULLET_PLAYER_M16, 
                       0, 
                       0, 
                       args->fparam1,     // These are the accuracy passed from
                       args->fparam2 );   // PLAYBACK_EVENT_FULL on the server
}

The zoomed event is nearly the same, with only a few things left out that aren't needed.


//======================
//  M16 Zoomed START
//======================
void EV_FireM16Zoomed( event_args_t *args )
{
    vec3_t origin;
    vec3_t angles;
    
    vec3_t vecSrc, vecAiming;
    vec3_t up, right, forward;
    float flSpread = 0.01;

    int idx = args->entindex;
    VectorCopy( args->origin, origin );
    VectorCopy( args->angles, angles );

    AngleVectors( angles, forward, right, up );
    
    if ( EV_IsLocal( idx ) )
    {
        // Add muzzle flash to current weapon model
        EV_MuzzleFlash();

        // This gives it a bit of kick, adjust the numbers to your liking
        V_PunchAxis( 0, gEngfuncs.pfnRandomFloat( -2, 2 ) );
    }

    // Play the fire sound
    switch( gEngfuncs.pfnRandomLong( 0, 1 ) )
    {
    case 0:
        gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, 
                                       "weapons/m16a1_fire-1.wav", 
                                       1, ATTN_NORM, 0, 
                                       94 + gEngfuncs.pfnRandomLong( 0, 0xf ) );
        break;
    case 1:
        gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, 
                                       "weapons/m16a1_fire-2.wav", 
                                       1, ATTN_NORM, 0, 
                                       94 + gEngfuncs.pfnRandomLong( 0, 0xf ) );
        break;
    }

    // Fire off the bullets client side
    EV_GetGunPosition( args, vecSrc, origin );
    VectorCopy( forward, vecAiming );
    EV_HLDM_FireBullets( idx, 
                       forward, 
                       right, 
                       up, 
                       1, 
                       vecSrc, 
                       vecAiming, 
                       8192, 
                       BULLET_PLAYER_M16, 
                       0, 
                       0, 
                       args->fparam1,     // These are the accuracy passed from
                       args->fparam2 );   // PLAYBACK_EVENT_FULL on the server
}

The only big difference between this and the other event is that we don't bother to eject shells or do the weapon animation since the weapon is zoomed in and the player won't be able to see it anyway.

Now we need to add in the event hooks. Open up hl_events.cpp and add;

cl_dll\hl\hl_events.cpp

void EV_HornetGunFire( struct event_args_s *args );
void EV_SnarkFire( struct event_args_s *args );


void EV_FireM16( struct event_args_s *args  );
void EV_FireM16Zoomed( struct event_args_s *args  );

Then further down;


void Game_HookEvents( void )
{
    gEngfuncs.pfnHookEvent( "events/python.sc",       EV_FirePython );
    gEngfuncs.pfnHookEvent( "events/gauss.sc",        EV_FireGauss );
    gEngfuncs.pfnHookEvent( "events/gaussspin.sc",    EV_SpinGauss );
    gEngfuncs.pfnHookEvent( "events/crossbow1.sc",    EV_FireCrossbow );
    gEngfuncs.pfnHookEvent( "events/crossbow2.sc",    EV_FireCrossbow2 );
    gEngfuncs.pfnHookEvent( "events/shotgun1.sc",     EV_FireShotGunSingle );
    gEngfuncs.pfnHookEvent( "events/shotgun2.sc",     EV_FireShotGunDouble );
    gEngfuncs.pfnHookEvent( "events/egon_fire.sc",    EV_EgonFire );
    gEngfuncs.pfnHookEvent( "events/egon_stop.sc",    EV_EgonStop );
    gEngfuncs.pfnHookEvent( "events/firehornet.sc",   EV_HornetGunFire );
    gEngfuncs.pfnHookEvent( "events/snarkfire.sc",    EV_SnarkFire );
    gEngfuncs.pfnHookEvent( "events/rpg.sc",          EV_FireRpg );
    gEngfuncs.pfnHookEvent( "events/tripfire.sc",     EV_TripmineFire );


    gEngfuncs.pfnHookEvent( "events/m16.sc",          EV_FireM16 );
    gEngfuncs.pfnHookEvent( "events/m16z.sc",         EV_FireM16Zoomed );

The only diffence here is the extra event hook. The remaining changes are the same as the last tutorial.

cl_dll\hl\hl_weapons.cpp


#include "extdll.h"
#include "util.h"
#include "cbase.h"
#include "monsters.h"
#include "weapons.h"
#include "nodes.h"
#include "player.h"

#include "usercmd.h"
#include "entity_state.h"
#include "demo_api.h"
#include "pm_defs.h"
#include "event_api.h"
#include "r_efx.h"

#include "../hud_iface.h"
#include "../com_weapons.h"
#include "../demo.h"


#include "m16.h"

Then add a global for your weapon near the top with the rest of the weapons.


CTripmine g_Tripmine;
CSqueak g_Snark;


CM16        g_M16;

Add a HUD_PrepEntity for the weapon in HUD_InitClientWeapons().


HUD_PrepEntity( &g_Tripmine , &player );
HUD_PrepEntity( &g_Snark    , &player );


HUD_PrepEntity( &g_M16      , &player );

Now, add your weapon to the switch statement in HUD_WeaponsPostThink()


switch ( from->client.m_iId )
{
    case WEAPON_CROWBAR:
        pWeapon = &g_Crowbar;
        break;
  
    case WEAPON_GLOCK:
        pWeapon = &g_Glock;
        break;


    case WEAPON_M16:
        pWeapon = &g_M16;
        break;

There is also what I think is a bug in the SDK that prevents the weapon from zooming properly. Open up hud.cpp and find the method;

cl_dll\hud.cpp


int CHud::MsgFunc_SetFOV(const char *pszName, int iSize, void *pbuf) 

and comment out these two lines near the beginning,


//if ( cl_lw && cl_lw->value ) 
// return 1; 

That's it.