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

From Valve Developer Community
Jump to: navigation, search


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 );
	}
}
*/