SPAS-12 semi-auto/pump-action for HL2

Hello, I was asked to make a tutorial in which I show you how to create SPAS-12 with ability to change firing modes (semi-automatic and pump-action).
I made it in Visual Studio 2013 and used source-sdk-2013 source code for singleplayer. I also used tools like Crowbar and Blender to make some animations that I needed.
BTW it's my first tutorial so I apologise if I made any mistakes.
== First Step ==
First of all you will need Half-Life 2 Source Code.
You'll also need Visual Studio to compile it, I recommend Visual Studio 2013.
== Editing Shotgun Code ==
When you have your Visual Studio and Source Code ready, search for "weapon_shotgun.cpp" file and open it.
I'll show and explain everything I added for that code, but if you're not interested in it just copy all the code I put in the last paragraph.
Around line 37 we three bools. Let's add two more.
	bool	m_Autoloading;		// For semi-auto fire mode <--
	bool	m_bNeedChamber;		// When emptied completely <--
	bool	m_bNeedPump;		// Now just pump in pump-action mode.
	bool	m_bDelayedFire1;	// Fire primary when finished reloading
	bool	m_bDelayedFire2;	// Fire secondary when finished reloading
Around line 82 we have some voids, we'll need two more.
	void SwitchFiremode( void );
	void ShotgunFire(int PelletsNum, int PelletDamage, bool UsePump); 
And now around line 100 we have DATADESC for shotgun, we need to add two lines for that bools we added.
	DEFINE_FIELD( m_Autoloading, FIELD_BOOLEAN),
	DEFINE_FIELD( m_bNeedChamber, FIELD_BOOLEAN),
Around line 160 we have Precache for shotgun, we need to precache two sounds. These are sounds we're going to add in "game_sounds.txt" file in hl2/scripts folder.
	PrecacheScriptSound("Weapon_Shotgun.Chamber");
	PrecacheScriptSound("Weapon_Shotgun.Pump");
Around line 300 we have "bool CWeaponShotgun::StartReload(void)" and now we need to remove or comment out that:
	/*
	if (m_iClip1 <= 0)
	{
	 m_bNeedPump = true; // We don't need that anymore
	}
	*/
We want different animations for "pump" in semi-auto and pump-action modes and add that sounds we precached.
We need to go to "void CWeaponShotgun::Pump( void )" around line 415 and well... just remove everything and add that
	CBaseCombatCharacter *pOwner = GetOwner();
	if ( pOwner == NULL )
		return;
	
	m_bNeedPump = false;
	
	if (m_Autoloading)
	{
		EmitSound("Weapon_Shotgun.Chamber");
		SendWeaponAnim(ACT_SHOTGUN_CHAMBER); // I added new activity for new animation, you must add it "activitylist.cpp" and in "ai_activity.cpp" and "ai_activity.h" 
						 // just search for ACT_SHOTGUN_PUMP in entire solution and add ACT_SHOTGUN_CHAMBER under it in these files
						 // or just change it to ACT_SHOTGUN_PUMP if you don't want different animation.
						 // Here is example for "activitylist.cpp"
						 // REGISTER_SHARED_ACTIVITY( ACT_SHOTGUN_PUMP);
						 // REGISTER_SHARED_ACTIVITY( ACT_SHOTGUN_CHAMBER);
	}
	else										
	{
		EmitSound("Weapon_Shotgun.Pump");
		SendWeaponAnim(ACT_SHOTGUN_PUMP);
	}
	pOwner->m_flNextAttack	= gpGlobals->curtime + SequenceDuration();
	m_flNextPrimaryAttack	= gpGlobals->curtime + SequenceDuration();
Around line 460 we have "void CWeaponShotgun::PrimaryAttack(void)" and now just remove everything in it and add this:
	// Only the player fires this way so we can cast
	CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
	if (!pPlayer)
	{
		return;
	}
	if (GetOwner()->GetWaterLevel() == 3) //don't fire under water
	{
		WeaponSound(EMPTY);
		m_flNextPrimaryAttack = gpGlobals->curtime + 0.2;
		return;
	}
	if (!m_bInReload)
	{
		m_bNeedChamber = false; 
	}
	if (m_Autoloading)
	{
		ShotgunFire(11, 6, false); //Here we can set (number of pellets, damage per pellet, pump)
	}
	else
	{
		ShotgunFire(11, 8, true); //The same as above
	}
Under this we have "void CWeaponShotgun::SecondaryAttack( void )" and just like above we remove everything and add this:
	CBasePlayer *pPlayer = ToBasePlayer(GetOwner());
	if (!pPlayer)
	{
		return;
	}
	pPlayer->m_nButtons &= ~IN_ATTACK2;
	
	m_flNextPrimaryAttack = gpGlobals->curtime + 0.3;
	m_flNextSecondaryAttack = gpGlobals->curtime + 0.3;
	
	SwitchFiremode();
Now we need to add void we referenced earlier. It's basically our primary attack for shotgun. And here we can for example change fire rate.
void CWeaponShotgun::ShotgunFire(int PelletsNum, int PelletDamage, bool UsePump)
{
	CBasePlayer *pPlayer = ToBasePlayer(GetOwner());
	if (!pPlayer)
	{
		return;
	}
	WeaponSound(SINGLE);
	pPlayer->DoMuzzleFlash();
	
	pPlayer->SetAnimation(PLAYER_ATTACK1);
	if (m_Autoloading)
	{
		SendWeaponAnim(ACT_VM_SECONDARYATTACK);			 // autoloading needs different animation because it ejects shell
		m_flNextPrimaryAttack = gpGlobals->curtime + 0.28f; // Rate of fire in autoloading mode (default 214 RPM)
	}
	else
	{
		SendWeaponAnim(ACT_VM_PRIMARYATTACK);
		m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); //When there is SequenceDuration() this has to be under SendWeaponAnim !
	}
	m_iClip1 -= 1;
	Vector	vecSrc = pPlayer->Weapon_ShootPosition();
	Vector	vecAiming = pPlayer->GetAutoaimVector(AUTOAIM_SCALE_DEFAULT);
	pPlayer->SetMuzzleFlashTime(gpGlobals->curtime + 1.0);
	pPlayer->FireBullets(PelletsNum, vecSrc, vecAiming, GetBulletSpread(), MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 0, -1, -1, PelletDamage, NULL, true, true); // weapon stats
	pPlayer->ViewPunch(QAngle(random->RandomFloat(-2, -1), random->RandomFloat(-2, 2), 0));
	CSoundEnt::InsertSound(SOUND_COMBAT, GetAbsOrigin(), SOUNDENT_VOLUME_SHOTGUN, 0.2, GetOwner());
	if (!m_iClip1 && pPlayer->GetAmmoCount(m_iPrimaryAmmoType) <= 0)
	{
		// HEV suit - indicate out of ammo condition
		pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0);
	}
	if (m_iClip1)
	{
		// pump so long as some rounds are left.
		m_bNeedPump = UsePump;						// we want false in autoloading mode
	}
	m_iPrimaryAttacks++;
	gamestats->Event_WeaponFired(pPlayer, true, GetClassname());
}
Just like above we add another void for switching firing mode.
void CWeaponShotgun::SwitchFiremode(void)
{
	CBasePlayer *pPlayer = ToBasePlayer(GetOwner());
	if (!pPlayer)
	{
		return;
	}
	if (m_Autoloading)
	{
		WeaponSound(EMPTY);
		SendWeaponAnim(ACT_VM_FIDGET); //We can add animation for switch
		ClientPrint(pPlayer, HUD_PRINTCENTER, "Switched to pump-action");
		m_Autoloading = false;
	}
	else
	{
		WeaponSound(EMPTY);
		SendWeaponAnim(ACT_VM_FIDGET);
		ClientPrint(pPlayer, HUD_PRINTCENTER, "Switched to semi-automatic");
		m_Autoloading = true;
	}
}
There are a few things we need to change in "void CWeaponShotgun::ItemPostFrame( void )" it should be around 588 line now
First thing is here:
		// If I'm primary firing and have one round stop reloading and fire
		if ((pOwner->m_nButtons & IN_ATTACK) && (m_iClip1 >= 1) && !m_bNeedChamber)// <-- we need to add && !m_bNeedChamber because we can't fire when there is no shell in chamber
Now remove or comment out that because we don't use secondary attack anymore
		/*
		// If I'm secondary firing and have one round stop reloading and fire
		else if ((pOwner->m_nButtons & IN_ATTACK2 ) && (m_iClip1 >=2))//
		{
			m_bInReload		= false;
			m_bNeedPump		= false;
			m_bDelayedFire2 = true;
		}
		*/
Now we have "else if (m_flNextPrimaryAttack <= gpGlobals->curtime)". We have to add two new conditions.
			if (m_bNeedChamber)
			{
				Pump();
				m_bNeedChamber = false;
				return;
			}
			if (m_iClip1 <= 0)
			{
				Reload();
				m_bNeedChamber = true;
				return;
			}
Now search for that: In my code it's 668 line. Just change PrimaryAttack(); to SecondaryAttack(); just like that:
			// If only one shell is left, do a single shot instead	
			if ( m_iClip1 == 1 )
			{
				SecondaryAttack();
			}
Finally we have the last thing to edit "CWeaponShotgun::CWeaponShotgun( void )" in my code it's line 776.
We add : "m_bNeedChamber = false;" and "m_bFiresUnderwater = true;" 
It looks like that:
CWeaponShotgun::CWeaponShotgun( void )
{
	m_bReloadsSingly = true;
	m_bNeedPump	= false;
	m_bNeedChamber = false;
	m_bDelayedFire1 = false;
	m_bDelayedFire2 = false;
	m_fMinRange1		= 0.0;
	m_fMaxRange1		= 500;
	m_fMinRange2		= 0.0;
	m_fMaxRange2		= 200;
	m_bFiresUnderwater = true; // We want secondary attack to work under water
}
That's all, coding is done. Now just save all files and rebuild the solution. When compiling is done copy client and server dlls to bin folder.
== Adjusting Shotgun Model ==
Now we need to use Crowbar to decompile and open shotgun model. 
Open "v_shotgun.qc" file and in $sequence "altfire" change animation path to "v_shotgun_anims\fire01.smd" and add { event 6001 3 "2" } because we want shell eject in automatic mode.
You should also add new sequence for alternate "pump" animation that we added in source code.
Mine looks like that:
$sequence "chamber" {
	"v_shotgun_anims\chamber.smd"
	activity "ACT_SHOTGUN_CHAMBER" 1
	fadein 0.2
	fadeout 0.2
	node "0"
	fps 22
}
Here I used that animation I made in Blender.
Anyway save that file compile your model and put it into models folder.
== Adjusting Shotgun Sounds ==
Just open "game_sounds.txt" file and add this:
"Weapon_Shotgun.Chamber"
{
	"channel"		"CHAN_ITEM"
	"volume"		"0.7"
	"soundlevel"	"SNDLVL_NORM"
	"wave"		"weapons/shotgun/shotgun_cock.wav"
}
"Weapon_Shotgun.Pump"
{
	"channel"		"CHAN_ITEM"
	"volume"		"0.7"
	"soundlevel"	"SNDLVL_NORM"
	"wave"		"weapons/shotgun/shotgun_cock.wav"
}
== Entire Shotgun Code ==
If you don't want to make changes in code manually, just copy and paste that code.
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: A shotgun.
//
//			Primary attack: single barrel shot or semi-auto.
//			Secondary attack: change firing mode.
//
//=============================================================================//
#include "cbase.h"
#include "npcevent.h"
#include "basehlcombatweapon_shared.h"
#include "basecombatcharacter.h"
#include "ai_basenpc.h"
#include "player.h"
#include "gamerules.h"		// For g_pGameRules
#include "in_buttons.h"
#include "soundent.h"
#include "vstdlib/random.h"
#include "gamestats.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
extern ConVar sk_auto_reload_time;
extern ConVar sk_plr_num_shotgun_pellets;
class CWeaponShotgun : public CBaseHLCombatWeapon
{
	DECLARE_DATADESC();
public:
	DECLARE_CLASS( CWeaponShotgun, CBaseHLCombatWeapon );
	DECLARE_SERVERCLASS();
private:
	bool	m_Autoloading;		// For semi-auto fire mode
	bool	m_bNeedChamber;		// When emptied completely
	bool	m_bNeedPump;		// Now just pump in pump-action mode.
	bool	m_bDelayedFire1;	// Fire primary when finished reloading
	bool	m_bDelayedFire2;	// Fire secondary when finished reloading
public:
	void	Precache( void );
	int CapabilitiesGet( void ) { return bits_CAP_WEAPON_RANGE_ATTACK1; }
	virtual const Vector& GetBulletSpread( void )
	{
		static Vector vitalAllyCone = VECTOR_CONE_3DEGREES;
		static Vector cone = VECTOR_CONE_10DEGREES;
		if( GetOwner() && (GetOwner()->Classify() == CLASS_PLAYER_ALLY_VITAL) )
		{
			// Give Alyx's shotgun blasts more a more directed punch. She needs
			// to be at least as deadly as she would be with her pistol to stay interesting (sjb)
			return vitalAllyCone;
		}
		return cone;
	}
	virtual int				GetMinBurst() { return 1; }
	virtual int				GetMaxBurst() { return 3; }
	virtual float			GetMinRestTime();
	virtual float			GetMaxRestTime();
	virtual float			GetFireRate( void );
	bool StartReload( void );
	bool Reload( void );
	void FillClip( void );
	void FinishReload( void );
	void CheckHolsterReload( void );
	void Pump( void );
//	void WeaponIdle( void );
	void ItemHolsterFrame( void );
	void ItemPostFrame( void );
	void PrimaryAttack( void );
	void SecondaryAttack( void );
	void DryFire( void );
	void SwitchFiremode( void );
	void ShotgunFire(int PelletsNum, int PelletDamage, bool UsePump); 
	void FireNPCPrimaryAttack( CBaseCombatCharacter *pOperator, bool bUseWeaponAngles );
	void Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary );
	void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator );
	DECLARE_ACTTABLE();
	CWeaponShotgun(void);
};
IMPLEMENT_SERVERCLASS_ST(CWeaponShotgun, DT_WeaponShotgun)
END_SEND_TABLE()
LINK_ENTITY_TO_CLASS( weapon_shotgun, CWeaponShotgun );
PRECACHE_WEAPON_REGISTER(weapon_shotgun);
BEGIN_DATADESC( CWeaponShotgun )
	DEFINE_FIELD( m_Autoloading, FIELD_BOOLEAN),
	DEFINE_FIELD( m_bNeedChamber, FIELD_BOOLEAN),
	DEFINE_FIELD( m_bNeedPump, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_bDelayedFire1, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_bDelayedFire2, FIELD_BOOLEAN ),
END_DATADESC()
acttable_t	CWeaponShotgun::m_acttable[] = 
{
	{ ACT_IDLE,						ACT_IDLE_SMG1,					true },	// FIXME: hook to shotgun unique
	{ ACT_RANGE_ATTACK1,			ACT_RANGE_ATTACK_SHOTGUN,			true },
	{ ACT_RELOAD,					ACT_RELOAD_SHOTGUN,					false },
	{ ACT_WALK,						ACT_WALK_RIFLE,						true },
	{ ACT_IDLE_ANGRY,				ACT_IDLE_ANGRY_SHOTGUN,				true },
// Readiness activities (not aiming)
	{ ACT_IDLE_RELAXED,				ACT_IDLE_SHOTGUN_RELAXED,		false },//never aims
	{ ACT_IDLE_STIMULATED,			ACT_IDLE_SHOTGUN_STIMULATED,	false },
	{ ACT_IDLE_AGITATED,			ACT_IDLE_SHOTGUN_AGITATED,		false },//always aims
	{ ACT_WALK_RELAXED,				ACT_WALK_RIFLE_RELAXED,			false },//never aims
	{ ACT_WALK_STIMULATED,			ACT_WALK_RIFLE_STIMULATED,		false },
	{ ACT_WALK_AGITATED,			ACT_WALK_AIM_RIFLE,				false },//always aims
	{ ACT_RUN_RELAXED,				ACT_RUN_RIFLE_RELAXED,			false },//never aims
	{ ACT_RUN_STIMULATED,			ACT_RUN_RIFLE_STIMULATED,		false },
	{ ACT_RUN_AGITATED,				ACT_RUN_AIM_RIFLE,				false },//always aims
// Readiness activities (aiming)
	{ ACT_IDLE_AIM_RELAXED,			ACT_IDLE_SMG1_RELAXED,			false },//never aims	
	{ ACT_IDLE_AIM_STIMULATED,		ACT_IDLE_AIM_RIFLE_STIMULATED,	false },
	{ ACT_IDLE_AIM_AGITATED,		ACT_IDLE_ANGRY_SMG1,			false },//always aims
	{ ACT_WALK_AIM_RELAXED,			ACT_WALK_RIFLE_RELAXED,			false },//never aims
	{ ACT_WALK_AIM_STIMULATED,		ACT_WALK_AIM_RIFLE_STIMULATED,	false },
	{ ACT_WALK_AIM_AGITATED,		ACT_WALK_AIM_RIFLE,				false },//always aims
	{ ACT_RUN_AIM_RELAXED,			ACT_RUN_RIFLE_RELAXED,			false },//never aims
	{ ACT_RUN_AIM_STIMULATED,		ACT_RUN_AIM_RIFLE_STIMULATED,	false },
	{ ACT_RUN_AIM_AGITATED,			ACT_RUN_AIM_RIFLE,				false },//always aims
//End readiness activities
	{ ACT_WALK_AIM,					ACT_WALK_AIM_SHOTGUN,				true },
	{ ACT_WALK_CROUCH,				ACT_WALK_CROUCH_RIFLE,				true },
	{ ACT_WALK_CROUCH_AIM,			ACT_WALK_CROUCH_AIM_RIFLE,			true },
	{ ACT_RUN,						ACT_RUN_RIFLE,						true },
	{ ACT_RUN_AIM,					ACT_RUN_AIM_SHOTGUN,				true },
	{ ACT_RUN_CROUCH,				ACT_RUN_CROUCH_RIFLE,				true },
	{ ACT_RUN_CROUCH_AIM,			ACT_RUN_CROUCH_AIM_RIFLE,			true },
	{ ACT_GESTURE_RANGE_ATTACK1,	ACT_GESTURE_RANGE_ATTACK_SHOTGUN,	true },
	{ ACT_RANGE_ATTACK1_LOW,		ACT_RANGE_ATTACK_SHOTGUN_LOW,		true },
	{ ACT_RELOAD_LOW,				ACT_RELOAD_SHOTGUN_LOW,				false },
	{ ACT_GESTURE_RELOAD,			ACT_GESTURE_RELOAD_SHOTGUN,			false },
};
IMPLEMENT_ACTTABLE(CWeaponShotgun);
void CWeaponShotgun::Precache( void )
{
	CBaseCombatWeapon::Precache();
	PrecacheScriptSound("Weapon_Shotgun.Chamber");
	PrecacheScriptSound("Weapon_Shotgun.Pump");
}
//-----------------------------------------------------------------------------
// Purpose: 
// Input : *pOperator - 
//-----------------------------------------------------------------------------
void CWeaponShotgun::FireNPCPrimaryAttack( CBaseCombatCharacter *pOperator, bool bUseWeaponAngles )
{
	Vector vecShootOrigin, vecShootDir;
	CAI_BaseNPC *npc = pOperator->MyNPCPointer();
	ASSERT( npc != NULL );
	WeaponSound( SINGLE_NPC );
	pOperator->DoMuzzleFlash();
	m_iClip1 = m_iClip1 - 1;
	if ( bUseWeaponAngles )
	{
		QAngle	angShootDir;
		GetAttachment( LookupAttachment( "muzzle" ), vecShootOrigin, angShootDir );
		AngleVectors( angShootDir, &vecShootDir );
	}
	else 
	{
		vecShootOrigin = pOperator->Weapon_ShootPosition();
		vecShootDir = npc->GetActualShootTrajectory( vecShootOrigin );
	}
	pOperator->FireBullets( 8, vecShootOrigin, vecShootDir, GetBulletSpread(), MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 0 );
}
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponShotgun::Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary )
{
	// Ensure we have enough rounds in the clip
	m_iClip1++;
	FireNPCPrimaryAttack( pOperator, true );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input :
// Output :
//-----------------------------------------------------------------------------
void CWeaponShotgun::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator )
{
	switch( pEvent->event )
	{
		case EVENT_WEAPON_SHOTGUN_FIRE:
		{
			FireNPCPrimaryAttack( pOperator, false );
		}
		break;
		default:
			CBaseCombatWeapon::Operator_HandleAnimEvent( pEvent, pOperator );
			break;
	}
}
//-----------------------------------------------------------------------------
// Purpose:	When we shipped HL2, the shotgun weapon did not override the
//			BaseCombatWeapon default rest time of 0.3 to 0.6 seconds. When
//			NPC's fight from a stationary position, their animation events
//			govern when they fire so the rate of fire is specified by the
//			animation. When NPC's move-and-shoot, the rate of fire is 
//			specifically controlled by the shot regulator, so it's imporant
//			that GetMinRestTime and GetMaxRestTime are implemented and provide
//			reasonable defaults for the weapon. To address difficulty concerns,
//			we are going to fix the combine's rate of shotgun fire in episodic.
//			This change will not affect Alyx using a shotgun in EP1. (sjb)
//-----------------------------------------------------------------------------
float CWeaponShotgun::GetMinRestTime()
{
	if( hl2_episodic.GetBool() && GetOwner() && GetOwner()->Classify() == CLASS_COMBINE )
	{
		return 1.2f;
	}
	
	return BaseClass::GetMinRestTime();
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
float CWeaponShotgun::GetMaxRestTime()
{
	if( hl2_episodic.GetBool() && GetOwner() && GetOwner()->Classify() == CLASS_COMBINE )
	{
		return 1.5f;
	}
	return BaseClass::GetMaxRestTime();
}
//-----------------------------------------------------------------------------
// Purpose: Time between successive shots in a burst. Also returned for EP2
//			with an eye to not messing up Alyx in EP1.
//-----------------------------------------------------------------------------
float CWeaponShotgun::GetFireRate()
{
	if( hl2_episodic.GetBool() && GetOwner() && GetOwner()->Classify() == CLASS_COMBINE )
	{
		return 0.8f;
	}
	return 0.7;
}
//-----------------------------------------------------------------------------
// Purpose: Override so only reload one shell at a time
// Input :
// Output :
//-----------------------------------------------------------------------------
bool CWeaponShotgun::StartReload( void )
{
	CBaseCombatCharacter *pOwner = GetOwner();
	
	if ( pOwner == NULL )
		return false;
	if (pOwner->GetAmmoCount(m_iPrimaryAmmoType) <= 0)
		return false;
	if (m_iClip1 >= GetMaxClip1())
		return false;
	// If shotgun totally emptied then a pump animation is needed // K but pump after you load the first shell
	
	//NOTENOTE: This is kinda lame because the player doesn't get strong feedback on when the reload has finished,
	//			without the pump. Technically, it's incorrect, but it's good for feedback...
	//			
	/*
	if (m_iClip1 <= 0)
	{
		m_bNeedPump = true; // We don't need that anymore
	}
	*/
	int j = MIN(1, pOwner->GetAmmoCount(m_iPrimaryAmmoType));
	if (j <= 0)
		return false;
	SendWeaponAnim( ACT_SHOTGUN_RELOAD_START );
	// Make shotgun shell visible
	SetBodygroup(1,0);
	pOwner->m_flNextAttack = gpGlobals->curtime;
	m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration();
	m_bInReload = true;
	return true;
}
//-----------------------------------------------------------------------------
// Purpose: Override so only reload one shell at a time
// Input :
// Output :
//-----------------------------------------------------------------------------
bool CWeaponShotgun::Reload( void )
{
	// Check that StartReload was called first
	if (!m_bInReload)
	{
		Warning("ERROR: Shotgun Reload called incorrectly!\n");
	}
	CBaseCombatCharacter *pOwner = GetOwner();
	
	if ( pOwner == NULL )
		return false;
	if (pOwner->GetAmmoCount(m_iPrimaryAmmoType) <= 0)
		return false;
	if (m_iClip1 >= GetMaxClip1())
		return false;
	int j = MIN(1, pOwner->GetAmmoCount(m_iPrimaryAmmoType));
	if (j <= 0)
		return false;
		FillClip();
		WeaponSound(RELOAD);
		SendWeaponAnim(ACT_VM_RELOAD);
	
	pOwner->m_flNextAttack = gpGlobals->curtime;
	m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration();
	return true;
}
//-----------------------------------------------------------------------------
// Purpose: Play finish reload anim and fill clip
// Input :
// Output :
//-----------------------------------------------------------------------------
void CWeaponShotgun::FinishReload( void )
{
	// Make shotgun shell invisible
	SetBodygroup(1,1);
	CBaseCombatCharacter *pOwner = GetOwner();
	
	if ( pOwner == NULL )
		return;
	m_bInReload = false;
	// Finish reload animation
	SendWeaponAnim( ACT_SHOTGUN_RELOAD_FINISH );
	pOwner->m_flNextAttack = gpGlobals->curtime;
	m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration();
}
//-----------------------------------------------------------------------------
// Purpose: Play finish reload anim and fill clip
// Input :
// Output :
//-----------------------------------------------------------------------------
void CWeaponShotgun::FillClip( void )
{
	CBaseCombatCharacter *pOwner = GetOwner();
	
	if ( pOwner == NULL )
		return;
	// Add them to the clip
	if ( pOwner->GetAmmoCount( m_iPrimaryAmmoType ) > 0 )
	{
		if ( Clip1() < GetMaxClip1() )
		{
			m_iClip1++;
			pOwner->RemoveAmmo( 1, m_iPrimaryAmmoType );
		}
	}
}
//-----------------------------------------------------------------------------
// Purpose: Play weapon pump anim
// Input :
// Output :
//-----------------------------------------------------------------------------
void CWeaponShotgun::Pump( void )
{
	CBaseCombatCharacter *pOwner = GetOwner();
	if ( pOwner == NULL )
		return;
	
	m_bNeedPump = false;
	
	if (m_Autoloading)
	{
		EmitSound("Weapon_Shotgun.Chamber");
		SendWeaponAnim(ACT_SHOTGUN_CHAMBER); //Hello there, change me to ACT_SHOTGUN_PUMP if you don't want different animation or give me references.
	}
	else										
	{
		EmitSound("Weapon_Shotgun.Pump");
		SendWeaponAnim(ACT_SHOTGUN_PUMP);
	}
	pOwner->m_flNextAttack	= gpGlobals->curtime + SequenceDuration();
	m_flNextPrimaryAttack	= gpGlobals->curtime + SequenceDuration();
}
//-----------------------------------------------------------------------------
// Purpose: 
//
//
//-----------------------------------------------------------------------------
void CWeaponShotgun::DryFire( void )
{
	WeaponSound(EMPTY);
	SendWeaponAnim( ACT_VM_DRYFIRE );
	
	m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration();
}
//-----------------------------------------------------------------------------
// Purpose: 
//
//
//-----------------------------------------------------------------------------
void CWeaponShotgun::PrimaryAttack( void )
{
	// Only the player fires this way so we can cast
	CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
	if (!pPlayer)
	{
		return;
	}
	if (GetOwner()->GetWaterLevel() == 3) //don't fire under water
	{
		WeaponSound(EMPTY);
		m_flNextPrimaryAttack = gpGlobals->curtime + 0.2;
		return;
	}
	if (!m_bInReload)
	{
		m_bNeedChamber = false; 
	}
	if (m_Autoloading)
	{
		ShotgunFire(11, 6, false); //Here we can set (number of pellets, damage per pellet, pump)
	}
	else
	{
		ShotgunFire(11, 8, true); //The same as above
	}
}
//-----------------------------------------------------------------------------
// Purpose: 
//
//
//-----------------------------------------------------------------------------
void CWeaponShotgun::SecondaryAttack( void )
{
	CBasePlayer *pPlayer = ToBasePlayer(GetOwner());
	if (!pPlayer)
	{
		return;
	}
	pPlayer->m_nButtons &= ~IN_ATTACK2;
	
	m_flNextPrimaryAttack = gpGlobals->curtime + 0.3;
	m_flNextSecondaryAttack = gpGlobals->curtime + 0.3;
	
	SwitchFiremode();
}
void CWeaponShotgun::ShotgunFire(int PelletsNum, int PelletDamage, bool UsePump)
{
	CBasePlayer *pPlayer = ToBasePlayer(GetOwner());
	if (!pPlayer)
	{
		return;
	}
	WeaponSound(SINGLE);
	pPlayer->DoMuzzleFlash();
	
	pPlayer->SetAnimation(PLAYER_ATTACK1);
	if (m_Autoloading)
	{
		SendWeaponAnim(ACT_VM_SECONDARYATTACK);			 // autoloading needs different animation because it ejects shell
		m_flNextPrimaryAttack = gpGlobals->curtime + 0.28f; // Rate of fire in autoloading mode (default 214 RPM)
	}
	else
	{
		SendWeaponAnim(ACT_VM_PRIMARYATTACK);
		m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); //When there is SequenceDuration() this has to be under SendWeaponAnim !
	}
	m_iClip1 -= 1;
	Vector	vecSrc = pPlayer->Weapon_ShootPosition();
	Vector	vecAiming = pPlayer->GetAutoaimVector(AUTOAIM_SCALE_DEFAULT);
	pPlayer->SetMuzzleFlashTime(gpGlobals->curtime + 1.0);
	pPlayer->FireBullets(PelletsNum, vecSrc, vecAiming, GetBulletSpread(), MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 0, -1, -1, PelletDamage, NULL, true, true); // weapon stats
	pPlayer->ViewPunch(QAngle(random->RandomFloat(-2, -1), random->RandomFloat(-2, 2), 0));
	CSoundEnt::InsertSound(SOUND_COMBAT, GetAbsOrigin(), SOUNDENT_VOLUME_SHOTGUN, 0.2, GetOwner());
	if (!m_iClip1 && pPlayer->GetAmmoCount(m_iPrimaryAmmoType) <= 0)
	{
		// HEV suit - indicate out of ammo condition
		pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0);
	}
	if (m_iClip1)
	{
		// pump so long as some rounds are left.
		m_bNeedPump = UsePump;						// we want false in autoloading mode
	}
	m_iPrimaryAttacks++;
	gamestats->Event_WeaponFired(pPlayer, true, GetClassname());
}
void CWeaponShotgun::SwitchFiremode(void)
{
	CBasePlayer *pPlayer = ToBasePlayer(GetOwner());
	if (!pPlayer)
	{
		return;
	}
	if (m_Autoloading)
	{
		WeaponSound(EMPTY);
		SendWeaponAnim(ACT_VM_FIDGET);
		ClientPrint(pPlayer, HUD_PRINTCENTER, "Switched to pump-action");
		m_Autoloading = false;
	}
	else
	{
		WeaponSound(EMPTY);
		SendWeaponAnim(ACT_VM_FIDGET);
		ClientPrint(pPlayer, HUD_PRINTCENTER, "Switched to semi-automatic");
		m_Autoloading = true;
	}
}
//-----------------------------------------------------------------------------
// Purpose: Override so shotgun can do mulitple reloads in a row
//-----------------------------------------------------------------------------
void CWeaponShotgun::ItemPostFrame( void )
{
	CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
	if (!pOwner)
	{
		return;
	}
	if (m_bInReload)
	{
		// If I'm primary firing and have one round stop reloading and fire
		if ((pOwner->m_nButtons & IN_ATTACK) && (m_iClip1 >= 1) && !m_bNeedChamber)
		{
			m_bInReload		= false;
			m_bNeedPump		= false;
			m_bDelayedFire1 = true;
		}
		/*
		// If I'm secondary firing and have one round stop reloading and fire
		else if ((pOwner->m_nButtons & IN_ATTACK2 ) && (m_iClip1 >=2))//
		{
			m_bInReload		= false;
			m_bNeedPump		= false;
			m_bDelayedFire2 = true;
		}
		*/
		else if (m_flNextPrimaryAttack <= gpGlobals->curtime)
		{
			if (m_bNeedChamber)
			{
				Pump();
				m_bNeedChamber = false;
				return;
			}
			if (m_iClip1 <= 0)
			{
				Reload();
				m_bNeedChamber = true;
				return;
			}
			// If out of ammo end reload
			if (pOwner->GetAmmoCount(m_iPrimaryAmmoType) <=0)
			{
				FinishReload();
				return;
			}
			// If clip not full reload again
			if (m_iClip1 < GetMaxClip1())
			{
				Reload();
				return;
			}
			// Clip full, stop reloading
			else
			{
				FinishReload();
				return;
			}
		}
	}
	else
	{			
		// Make shotgun shell invisible
		SetBodygroup(1,1);
	}
	if ((m_bNeedPump) && (m_flNextPrimaryAttack <= gpGlobals->curtime))
	{
		Pump();
		return;
	}
	
	// Shotgun uses same timing and ammo for secondary attack
	if ((m_bDelayedFire2 || pOwner->m_nButtons & IN_ATTACK2)&&(m_flNextPrimaryAttack <= gpGlobals->curtime))
	{
		m_bDelayedFire2 = false;
		
		if ( (m_iClip1 <= 1 && UsesClipsForAmmo1()))
		{
			// If only one shell is left, do a single shot instead	
			if ( m_iClip1 == 1 )
			{
				SecondaryAttack();
			}
			else if (!pOwner->GetAmmoCount(m_iPrimaryAmmoType))
			{
				DryFire();
			}
			else
			{
				StartReload();
			}
		}
		// Fire underwater?
		else if (GetOwner()->GetWaterLevel() == 3 && m_bFiresUnderwater == false)
		{
			WeaponSound(EMPTY);
			m_flNextPrimaryAttack = gpGlobals->curtime + 0.2;
			return;
		}
		else
		{
			// If the firing button was just pressed, reset the firing time
			if ( pOwner->m_afButtonPressed & IN_ATTACK )
			{
				 m_flNextPrimaryAttack = gpGlobals->curtime;
			}
			SecondaryAttack();
		}
	}
	else if ( (m_bDelayedFire1 || pOwner->m_nButtons & IN_ATTACK) && m_flNextPrimaryAttack <= gpGlobals->curtime)
	{
		m_bDelayedFire1 = false;
		if ( (m_iClip1 <= 0 && UsesClipsForAmmo1()) || ( !UsesClipsForAmmo1() && !pOwner->GetAmmoCount(m_iPrimaryAmmoType) ) )
		{
			if (!pOwner->GetAmmoCount(m_iPrimaryAmmoType))
			{
				DryFire();
			}
			else
			{
				StartReload();
			}
		}
		// Fire underwater?
		else if (pOwner->GetWaterLevel() == 3 && m_bFiresUnderwater == false)
		{
			WeaponSound(EMPTY);
			m_flNextPrimaryAttack = gpGlobals->curtime + 0.2;
			return;
		}
		else
		{
			// If the firing button was just pressed, reset the firing time
			CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
			if ( pPlayer && pPlayer->m_afButtonPressed & IN_ATTACK )
			{
				 m_flNextPrimaryAttack = gpGlobals->curtime;
			}
			PrimaryAttack();
		}
	}
	if ( pOwner->m_nButtons & IN_RELOAD && UsesClipsForAmmo1() && !m_bInReload ) 
	{
		// reload when reload is pressed, or if no buttons are down and weapon is empty.
		StartReload();
	}
	else 
	{
		// no fire buttons down
		m_bFireOnEmpty = false;
		if ( !HasAnyAmmo() && m_flNextPrimaryAttack < gpGlobals->curtime ) 
		{
			// weapon isn't useable, switch.
			if ( !(GetWeaponFlags() & ITEM_FLAG_NOAUTOSWITCHEMPTY) && pOwner->SwitchToNextBestWeapon( this ) )
			{
				m_flNextPrimaryAttack = gpGlobals->curtime + 0.3;
				return;
			}
		}
		else
		{
			// weapon is useable. Reload if empty and weapon has waited as long as it has to after firing
			if ( m_iClip1 <= 0 && !(GetWeaponFlags() & ITEM_FLAG_NOAUTORELOAD) && m_flNextPrimaryAttack < gpGlobals->curtime )
			{
				if (StartReload())
				{
					// if we've successfully started to reload, we're done
					return;
				}
			}
		}
		WeaponIdle( );
		return;
	}
}
//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CWeaponShotgun::CWeaponShotgun( void )
{
	m_bReloadsSingly = true;
	m_bNeedPump		= false;
	m_bNeedChamber = false;
	m_bDelayedFire1 = false;
	m_bDelayedFire2 = false;
	m_fMinRange1		= 0.0;
	m_fMaxRange1		= 500;
	m_fMinRange2		= 0.0;
	m_fMaxRange2		= 200;
	m_bFiresUnderwater = true;// We want secondary attack to work under water
}
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponShotgun::ItemHolsterFrame( void )
{
	// Must be player held
	if ( GetOwner() && GetOwner()->IsPlayer() == false )
		return;
	// We can't be active
	if ( GetOwner()->GetActiveWeapon() == this )
		return;
	// If it's been longer than three seconds, reload
	if ( ( gpGlobals->curtime - m_flHolsterTime ) > sk_auto_reload_time.GetFloat() )
	{
		// Reset the timer
		m_flHolsterTime = gpGlobals->curtime;
	
		if ( GetOwner() == NULL )
			return;
		if ( m_iClip1 == GetMaxClip1() )
			return;
		// Just load the clip with no animations
		int ammoFill = MIN( (GetMaxClip1() - m_iClip1), GetOwner()->GetAmmoCount( GetPrimaryAmmoType() ) );
		
		GetOwner()->RemoveAmmo( ammoFill, GetPrimaryAmmoType() );
		m_iClip1 += ammoFill;
	}
}
//==================================================
// Purpose: 
//==================================================
/*
void CWeaponShotgun::WeaponIdle( void )
{
	//Only the player fires this way so we can cast
	CBasePlayer *pPlayer = GetOwner()
	if ( pPlayer == NULL )
		return;
	//If we're on a target, play the new anim
	if ( pPlayer->IsOnTarget() )
	{
		SendWeaponAnim( ACT_VM_IDLE_ACTIVE );
	}
}
*/
Category:Weapons programming
Category:Tutorials