Dual Pistols (CSS Style)

This article will explain how to implement dual pistols in both singleplayer and multiplayer Half-Life 2 mods (using Counter-Strike: Source as a basis).
== Getting Started ==
Firstly, you will need to make a copy of the file "weapon_pistol.cpp", renaming the copy to "weapon_dualies.cpp". You can find the file in either the <src\game_shared\hl2mp> directory (for 2006 multiplayer), <src\game\shared\hl2mp> (for 2007 multiplayer) or <src\game\server\hl2> (for singleplayer).
Now open the newly created file in your IDE of choice and do a search for the word "Pistol", replacing all instances with "Dualies" instead. Unfortunately, there is one part of the code we didn't actually want changed in this way - so find the section that looks like the snippet below and change all instances of the word "Dualies" back to "PISTOL" (as shown).
#ifndef CLIENT_DLL
acttable_t CWeaponDualies::m_acttable[] = 
{
	{ ACT_HL2MP_IDLE,			ACT_HL2MP_IDLE_PISTOL,				false },
	{ ACT_HL2MP_RUN,			ACT_HL2MP_RUN_PISTOL,				false },
	{ ACT_HL2MP_IDLE_CROUCH,		ACT_HL2MP_IDLE_CROUCH_PISTOL,			false },
	{ ACT_HL2MP_WALK_CROUCH,		ACT_HL2MP_WALK_CROUCH_PISTOL,			false },
	{ ACT_HL2MP_GESTURE_RANGE_ATTACK,	ACT_HL2MP_GESTURE_RANGE_ATTACK_PISTOL,		false },
	{ ACT_HL2MP_GESTURE_RELOAD,		ACT_HL2MP_GESTURE_RELOAD_PISTOL,		false },
	{ ACT_HL2MP_JUMP,			ACT_HL2MP_JUMP_PISTOL,				false },
	{ ACT_RANGE_ATTACK1,			ACT_RANGE_ATTACK_PISTOL,			false },
};
One further thing to make sure of when modding multiplayer is that you include "weapon_dualies.cpp" in both the client and server projects of your solution. The correct location is <Client HL2MP\Source Files\HL2MP\Weapons> (replacing "Client" with "Server" for the server project location).
== The Stub and the Flip ==
Now we need to add it as a stub. So now, go to "c_weapon__stubs_hl2.cpp" in the Client project and add:
STUB_WEAPON_CLASS( weapon_dualies, WeaponDualies, C_BaseHLCombatWeapon );
Where the pistol is located. 
Now the gun is its own gun and works in game.
Now we must code the flip.
Add this method after CWeaponDualies::PrimaryAttack():
//-----------------------------------------------------------------------------
// Purpose: Dualies Firing
//-----------------------------------------------------------------------------
void CWeaponDualies::LeftGunAttack()
{
	// If my clip is empty (and I use clips) start reload
	if ( UsesClipsForAmmo1() && !m_iClip1 ) 
	{
		Reload();
		return;
	}
	// Only the player fires this way so we can cast
	CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
	if (!pPlayer)
	{
		return;
	}
	// MUST call sound before removing a round from the clip of a CMachineGun
	WeaponSound(SINGLE);
	pPlayer->DoMuzzleFlash();
	SendWeaponAnim( GetSecondaryAttackActivity() );
	// player "shoot" animation
	pPlayer->SetAnimation( PLAYER_ATTACK1 );
	FireBulletsInfo_t info;
	info.m_vecSrc	 = pPlayer->Weapon_ShootPosition( );
	
	info.m_vecDirShooting = pPlayer->GetAutoaimVector( AUTOAIM_SCALE_DEFAULT );
	// To make the firing framerate independent, we may have to fire more than one bullet here on low-framerate systems, 
	// especially if the weapon we're firing has a really fast rate of fire.
	info.m_iShots = 0;
	float fireRate = GetFireRate();
	while ( m_flNextPrimaryAttack <= gpGlobals->curtime )
	{
		m_flNextPrimaryAttack = m_flNextPrimaryAttack + fireRate;
		info.m_iShots++;
		if ( !fireRate )
			break;
	}
	// Make sure we don't fire more than the amount in the clip
	if ( UsesClipsForAmmo1() )
	{
		info.m_iShots = min( info.m_iShots, m_iClip1 );
		m_iClip1 -= info.m_iShots;
	}
	else
	{
		info.m_iShots = min( info.m_iShots, pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) );
		pPlayer->RemoveAmmo( info.m_iShots, m_iPrimaryAmmoType );
	}
	info.m_flDistance = MAX_TRACE_LENGTH;
	info.m_iAmmoType = m_iPrimaryAmmoType;
	info.m_iTracerFreq = 2;
#if !defined( CLIENT_DLL )
	// Fire the bullets
	info.m_vecSpread = pPlayer->GetAttackSpread( this );
#else
	//!!!HACKHACK - what does the client want this function for? 
	info.m_vecSpread = GetActiveWeapon()->GetBulletSpread();
#endif // CLIENT_DLL
	pPlayer->FireBullets( info );
	if (!m_iClip1 && pPlayer->GetAmmoCount(m_iPrimaryAmmoType) <= 0)
	{
		// HEV suit - indicate out of ammo condition
		pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); 
	}
	//Add our view kick in
	AddViewKick();
}
After that is done, go down to:
class CWeaponDualies : public CBaseHL2MPCombatWeapon
and find:
private:
	CNetworkVar( float,	m_flSoonestPrimaryAttack );
	CNetworkVar( float,	m_flLastAttackTime );
	CNetworkVar( float,	m_flAccuracyPenalty );
	CNetworkVar( int,	m_nNumShotsFired );
	
	
and change that to:
private:
	CNetworkVar( float,	m_flSoonestPrimaryAttack );
	CNetworkVar( float,	m_flLastAttackTime );
	CNetworkVar( float,	m_flAccuracyPenalty );
	CNetworkVar( int,	m_nNumShotsFired );
	bool bFlip;
	
Now we need to define our new LeftGunAttack method in the class definition:
Under
void	PrimaryAttack( void );
add:
void	LeftGunAttack( void );
Now you have everything pretty well set up. Finally, go down to:
void CWeaponDualies::PrimaryAttack()
And make the method look like this:
void CWeaponDualies::PrimaryAttack()
{
	if ( ( gpGlobals->curtime - m_flLastAttackTime ) > 0.5f )
	{
		m_nNumShotsFired = 0;
	}
	else
	{
		m_nNumShotsFired++;
	}
	m_flLastAttackTime = gpGlobals->curtime;
	m_flSoonestPrimaryAttack = gpGlobals->curtime + DUALIES_FASTEST_REFIRE_TIME;
	//Flipping Code -Jman
	if ( !bFlip)
	{
		BaseClass::PrimaryAttack();
		bFlip= true;
	}
	else
	{
		LeftGunAttack();
		bFlip= false;
	}
}
== Weapon View Model and World Model ==
If you were to play your mod right now, and give yourself dual pistols, then it would just be a single pistol, but fire as two. To fix this, we must use new models. There may be a way to make it so there are dual HL2 pistols, and two view models, but this way is much, much easier. First go into Steam/Steamapps/Sourcemods/*Mod Name Here*/. Make a new folder called models. Make a new folder in models called weapons. Now, you have a few choices here. If you own Counter-Strike: Source, then you can just extract the Elite models. You can also just download a skin from FPS Banana. If you download one from FPS Banana then you should get permission from the author and include him in the credits of your readme file. You can also make new models. Just make sure that all of the files are in models/weapons.
Now, you will probably see that the hands are texture-less (purple and black checkerboard). The way to fix this is to get a hand model from Counter-Strike: Source. Again, you can download from FPS Banana, extract them from your CS:S directory, or make your own. Either way, you need a hands folder, with the hand materials in them. To make them work in-game, go to your mod folder in Steam/Steamapps/Sourcemods/*YOUR MOD NAME*. Make a new folder called materials. In that make a new folder called models. In that make a new folder called weapons. In that make a new folder called v_models. Extract the hands directory here.
== Weapon Script File ==
Now that the coding is complete, a script file for the new weapon needs to be created (see here for more on weapon script files).
The easy way to do this is to make a copy of the file "weapon_pistol.txt", renaming the copy to "weapon_dualies.txt" (sound familiar?). You can find the file in the "Steam\steamapps\SourceMods\<yourmod>\scripts" directory. Set the View model to "models/weapons/v_pist_elite.mdl" (whatever the model name is, and make sure you include the quotes!)
and set the player model to "models/weapons/w_pist_elite.mdl" (again, whatever the model name is and include the quotes).
== Conclusion ==
Now when you next play your mod you should have a working pair of dual pistols! However, the next logical step would be making it so that the left mouse controls the left gun, and the right button controls the right gun.
Category:Tutorials
Category:Weapons programming