Dual Pistols (CSS Style)

For help, see the VDC Editing Help and Wikipedia cleanup process. Also, remember to check for any notes left by the tagger at this article's talk page.
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 multiplayer) or <sp directory here> (for singleplayer).
- Note: Feel free to name your dual pistols something else ("dualies" is just an example).
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).
- Note: The below is a representation of multiplayer code. Singleplayer modders can still make use of it - they just can't copy and paste it directly into their work.
#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 is that you include "weapon_dualies.cpp" in both the client and server projects of your solution. For multiplayer, the correct location is "Client HL2MP\Source Files\HL2MP\Weapons" (replacing "Client" with "Server" for the server project location). For singleplayer, the correction location is <sp directory here>.
The Stub and the Flip
So, as I said, we need to add it as a stub. So now, go to "c_weapon__stubs_hl2.cpp" 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.
Go into "basecombatweapon_shared.cpp" which is in "game_shared" and add this new function after "CBaseCombatWeapon::PrimaryAttack"
//-----------------------------------------------------------------------------
// Purpose: Dualies Firing
//-----------------------------------------------------------------------------
void CBaseCombatWeapon::RightDualiesAttack()
{
// 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();
}
Since that's done, go back to "weapon_dualies.cpp" and go down to:
class CWeaponDualies : public CBaseHL2MPCombatWeapon
and find:
void Precache();
void ItemPostFrame();
void ItemPreFrame();
void ItemBusyFrame();
void PrimaryAttack();
void AddViewKick();
void DryFire();
and change that to:
void Precache();
void ItemPostFrame();
void ItemPreFrame();
void ItemBusyFrame();
void PrimaryAttack();
void AddViewKick();
void DryFire();
void PostFireStuff();
then go to:
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 Flip;
Now we need to define our new RightDualiesAttack function in basecombatweapon_shared.h, which in your source is located in: src\game_shared\basecombatweapon_shared.h
Scroll down until you reach
// Weapon firing
virtual void PrimaryAttack(); // do "+ATTACK"
virtual void SecondaryAttack() { return; } // do "+ATTACK2"
And change that to
// Weapon firing
virtual void PrimaryAttack(); // do "+ATTACK"
virtual void RightDualiesAttack(); // do right gun shot"
virtual void SecondaryAttack() { return; } // do "+ATTACK2"
Now you have everything pretty well set up. Finally, go down to:
void CWeaponDualies::PrimaryAttack()
And make the function 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 ( Flip == true )
{
BaseClass::PrimaryAttack();
Flip = false;
PostFireStuff();
}
else
{
BaseClass::RightDualiesAttack();
Flip = true;
PostFireStuff();
}
}
//Post Fire things.
void CWeaponDualies::PostFireStuff()
{
CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
if( pOwner )
{
pOwner->ViewPunchReset();
}
// Add an accuracy penalty which can move past our maximum penalty time if we're really spastic
m_flAccuracyPenalty += DUALIES_ACCURACY_SHOT_PENALTY_TIME;
}
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.
Now open the newly created file in your text editor of choice and make the appropriate modifications to set a new bucket position, apply new models, etc..
Conclusion
Now when you next play your mod you should have a working pair of dual pistols!