Dual Pistols (CSS Style)

From Valve Developer Community
Revision as of 18:22, 26 November 2010 by Crocs (talk | contribs)

Jump to: navigation, search

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).

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).

#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 },
};
Note: The above 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.


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 m_bFlip;
Note: in the singleplayer source code the four vars that are already present are not networked.

Now find the constructor and add the boolean to the initialization list:

CWeaponDualies::CWeaponDualies( void ) : bFlip(false)

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;
	}
}

Now add after the end of ItemPostFrame:

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!