Adding Ironsights

From Valve Developer Community
Jump to navigation Jump to search

This is a tutorial how to add Ironsights and is an alternative to Jorg's code.

The new approach allows you to control the toggling server-side and thus you can add different bullet spread and such easily. It also has working angles and fov-change. However, the code still has to be edited to work in multiplayer and also only has been tested with a singleplayer OB-Mod.

New Approach

Note: There are no global variables or functions used in my code, so everything goes into the corresponding class-definition.

UPDATE March 5, '09

I've added automatic toggling off whenever the weapon is switched. I noticed that if you simply used this code, and tried to holster a weapon that was ironsighted, the weapon you switched to would have the same viewmodel offset as the previous weapon, and you can turn it off only if you switched back to your original weapon and toggle the ironsights off. In other words, say I had an ironsighted SMG deployed. If I switched to pistol, the viewmodel offset would be the one for the SMG, and if you clicked your ironsight bind while having that pistol, nothing would happen. I reversed engineered the technique from weapon_crossbow.cpp, and it works well.

I've also showed how to manipulate the bullet spread if you use this code. It is a very minor and simple manipulation, and really it should be quite straight forward, but I've added it nonetheless in case some non-coder needs to do this. -vcool

Weaponscript

At first, we will add some custom script-variables to be able to adjust the ironsight-offset via the weapon-script.

weapon_parse.h

Add this to the member-variables of FileWeaponInfo_t:

	Vector					vecIronsightPosOffset;
	QAngle					angIronsightAngOffset;
	float					flIronsightFOVOffset;

weapon_parse.cpp

This goes to FileWeaponInfo_t::Parse:

	KeyValues *pSights = pKeyValuesData->FindKey( "IronSight" );
	if (pSights)
	{
		vecIronsightPosOffset.x		= pSights->GetFloat( "forward", 0.0f );
		vecIronsightPosOffset.y		= pSights->GetFloat( "right", 0.0f );
		vecIronsightPosOffset.z		= pSights->GetFloat( "up", 0.0f );

		angIronsightAngOffset[PITCH]	= pSights->GetFloat( "pitch", 0.0f );
		angIronsightAngOffset[YAW]		= pSights->GetFloat( "yaw", 0.0f );
		angIronsightAngOffset[ROLL]		= pSights->GetFloat( "roll", 0.0f );

		flIronsightFOVOffset		= pSights->GetFloat( "fov", 0.0f );
	}
	else
	{
		vecIronsightPosOffset = vec3_origin;
		angIronsightAngOffset.Init();
		flIronsightFOVOffset = 0.0f;
	}

weapon_smg1.txt

This is an example of how to add the ironsight-offsets to your script:

	IronSight
	{
		"forward"	"-10"
		"right"		"-6.91"
		"up"		"0.185"
		"roll"		"-20"
		"fov"		"-20"
	}

Getting the offsets

Now we will add simple functions to get the information we parsed from the weapon-scripts. But we also want to have console-variables to overwrite the parsed info so we can easily make new ironsight-offsets.

basecombatweapon_shared.h

This goes to the public functions:

	Vector					GetIronsightPositionOffset( void ) const;
	QAngle					GetIronsightAngleOffset( void ) const;
	float					GetIronsightFOVOffset( void ) const;

basecombatweapon_shared.cpp

These ConVars usually go after the includes:

ConVar viewmodel_adjust_forward( "viewmodel_adjust_forward", "0", FCVAR_REPLICATED );
ConVar viewmodel_adjust_right( "viewmodel_adjust_right", "0", FCVAR_REPLICATED );
ConVar viewmodel_adjust_up( "viewmodel_adjust_up", "0", FCVAR_REPLICATED );
ConVar viewmodel_adjust_pitch( "viewmodel_adjust_pitch", "0", FCVAR_REPLICATED );
ConVar viewmodel_adjust_yaw( "viewmodel_adjust_yaw", "0", FCVAR_REPLICATED );
ConVar viewmodel_adjust_roll( "viewmodel_adjust_roll", "0", FCVAR_REPLICATED );
ConVar viewmodel_adjust_fov( "viewmodel_adjust_fov", "0", FCVAR_REPLICATED );
ConVar viewmodel_adjust_enabled( "viewmodel_adjust_enabled", "0", FCVAR_REPLICATED|FCVAR_CHEAT, "enabled viewmodel adjusting", vm_adjust_enable_callback );

And then you add the function definitions:

Vector CBaseCombatWeapon::GetIronsightPositionOffset( void ) const
{
	if( viewmodel_adjust_enabled.GetBool() )
		return Vector( viewmodel_adjust_forward.GetFloat(), viewmodel_adjust_right.GetFloat(), viewmodel_adjust_up.GetFloat() );
	return GetWpnData().vecIronsightPosOffset;
}

QAngle CBaseCombatWeapon::GetIronsightAngleOffset( void ) const
{
	if( viewmodel_adjust_enabled.GetBool() )
		return QAngle( viewmodel_adjust_pitch.GetFloat(), viewmodel_adjust_yaw.GetFloat(), viewmodel_adjust_roll.GetFloat() );
	return GetWpnData().angIronsightAngOffset;
}

float CBaseCombatWeapon::GetIronsightFOVOffset( void ) const
{
	if( viewmodel_adjust_enabled.GetBool() )
		return viewmodel_adjust_fov.GetFloat();
	return GetWpnData().flIronsightFOVOffset;
}

Adding toggle-functions

Of course you also want to be able to use the sights.

basecombatweapon_shared.h

Add these two networked variables:

	CNetworkVar( bool, m_bIsIronsighted );
	CNetworkVar( float, m_flIronsightedTime );

basecombatweapon_shared.cpp

Network them and give 'em default values.

Constructor:

	m_bIsIronsighted = false;
	m_flIronsightedTime = 0.0f;

Network-table (DT_BaseCombatWeapon):

	SendPropBool( SENDINFO( m_bIsIronsighted ) ),
	SendPropFloat( SENDINFO( m_flIronsightedTime ) ),

and

	RecvPropBool( RECVINFO( m_bIsIronsighted ) ),
	RecvPropFloat( RECVINFO( m_flIronsightedTime ) ),

basecombatweapon_shared.h

Now that we have the variables, we also want accessors:

	virtual bool				HasIronsights( void ) { return true; } //default yes
	bool					IsIronsighted( void );
	void					ToggleIronsights( void ) { m_bIsIronsighted ? DisableIronsights() : EnableIronsights(); }
	void					EnableIronsights( void );
	void					DisableIronsights( void );

basecombatweapon_shared.cpp

And of course define them:

bool CBaseCombatWeapon::IsIronsighted( void )
{
	return ( m_bIsIronsighted || viewmodel_adjust_enabled.GetBool() );
}

void CBaseCombatWeapon::EnableIronsights( void )
{
	if( !HasIronsights() || m_bIsIronsighted )
		return;

	CBasePlayer *pOwner = ToBasePlayer( GetOwner() );

	if( !pOwner )
		return;

	if( pOwner->SetFOV( this, pOwner->GetDefaultFOV()+GetIronsightFOVOffset(), 1.0f, 0.3f ) ) //modify these values to adjust how fast the fov is applied
	{
		m_bIsIronsighted = true;
		m_flIronsightedTime = gpGlobals->curtime;
	}
}

void CBaseCombatWeapon::DisableIronsights( void )
{
	if( !HasIronsights() || !m_bIsIronsighted )
		return;

	CBasePlayer *pOwner = ToBasePlayer( GetOwner() );

	if( !pOwner )
		return;

	if( pOwner->SetFOV( this, 0, 0.4f, 0.1f ) ) //modify these values to adjust how fast the fov is applied
	{
		m_bIsIronsighted = false;
		m_flIronsightedTime = gpGlobals->curtime;
	}
}

void vm_adjust_enable_callback( IConVar *pConVar, char const *pOldString, float flOldValue )
{
	ConVarRef sv_cheats( "sv_cheats" );
	if( !sv_cheats.IsValid() || sv_cheats.GetBool() )
		return;

	ConVarRef var( pConVar );

	if( var.GetBool() )
		var.SetValue( "0" );
}

hl2_player.cpp

You probably want a command to switch between normal and sighted mode. I've put them in hl2_player.cpp.

void CC_ToggleIronSights( void )
{
	CBasePlayer* pPlayer = UTIL_GetCommandClient();
	if ( pPlayer == NULL )
		return;

	CBaseCombatWeapon *pWeapon = pPlayer->GetActiveWeapon();
	if( pWeapon == NULL )
		return;

	pWeapon->ToggleIronsights();
}

static ConCommand toggle_ironsight("toggle_ironsight", CC_ToggleIronSights);

Toggle ironsights when switching weapons

This should be pretty simple.

basecombatweapon_shared.h

Go to some private declarations somewhere in the file. I think you could simply place your own "private:" in the beginning of the file, but in my short experience with code it's best to manipulate VALVEs code as less as possible. (unless you know what you are doing, of course) I defined my function at line 514:

	void					StopIronsights( void );

basecombatweapon_shared.cpp

Find bool CBaseCombatWeapon::Holster( CBaseCombatWeapon *pSwitchingTo ). A quick way if you are using VS 05 is to use the two dropdown menus right under the tab selection. Once you find it, you'll notice that there are several outputs (apologies for using level design language) like playing holster animation, stop reloading, etc. Amongst those commands, put the following (order doesn't matter, I have mine placed after the stop reloading bit.):

// turn off ironsights
	StopIronsights();

Once you've done this, go to the place where you've placed your ironsight commands following this article. Add the following:

void CBaseCombatWeapon::StopIronsights( void )
{
	// stop ironsights
	if ( m_bIsIronsighted )
	{
		ToggleIronsights();
	}
}

Voila, you are done! now whenever you switch your weapons, your ironsights will automatically toggle off.


Adjust the viewmodel

Ok, now for the last step we want to move the viewmodel according to the offsets. That code is based off Jorg's. Thanks from z33ky.

There are still some little issues to fix, though. Viewmodel-bob is still active while iron-sighting and the viewmodel lags way too much when going out of ironsight and rotating the view. You can try adding m_flIronsightedTime to this or moving CalcIronsights in front of those bob-things, I don't know. Since I'm not working on Delusion: Source anymore I didn't try to fix those issues.

baseviewmodel_shared.cpp

void CBaseViewModel::CalcIronsights( Vector &pos, QAngle &ang )
{
	CBaseCombatWeapon *pWeapon = GetOwningWeapon();

	if ( !pWeapon )
		return;

	//get delta time for interpolation
	float delta( ( gpGlobals->curtime - pWeapon->m_flIronsightedTime ) / 0.4f ); //modify this value to adjust how fast the interpolation is
	float exp = ( pWeapon->IsIronsighted() ) ? 
		( delta > 1.0f ) ? 1.0f : delta : //normal blending
		( delta > 1.0f ) ? 0.0f : 1.0f - delta; //reverse interpolation

	if( exp == 0.0f ) //fully not ironsighted; save performance
		return;

	Vector newPos = pos;
	QAngle newAng = ang;

	Vector vForward, vRight, vUp, vOffset;
	AngleVectors( newAng, &vForward, &vRight, &vUp );
	vOffset = pWeapon->GetIronsightPositionOffset();

	newPos += vForward * vOffset.x;
	newPos += vRight * vOffset.y;
	newPos += vUp * vOffset.z;
	newAng += pWeapon->GetIronsightAngleOffset();
	//fov is handled by CBaseCombatWeapon

	pos += ( newPos - pos ) * exp;
	ang += ( newAng - ang ) * exp;
}

Make use of this code in CBaseViewModel::CalcViewModelView:

void CBaseViewModel::CalcViewModelView( CBasePlayer *owner, const Vector& eyePosition, const QAngle& eyeAngles )
{
	// UNDONE: Calc this on the server?  Disabled for now as it seems unnecessary to have this info on the server
#if defined( CLIENT_DLL )
	QAngle vmangoriginal = eyeAngles;
	QAngle vmangles = eyeAngles;
	Vector vmorigin = eyePosition;

	CBaseCombatWeapon *pWeapon = m_hWeapon.Get();
	//Allow weapon lagging
	//only if not in ironsight-mode
	if( pWeapon == NULL || !pWeapon->IsIronsighted() )
	{
		if ( pWeapon != NULL )
		{
	#if defined( CLIENT_DLL )
			if ( !prediction->InPrediction() )
	#endif
			{
				// add weapon-specific bob 
				pWeapon->AddViewmodelBob( this, vmorigin, vmangles );
			}
		}

		// Add model-specific bob even if no weapon associated (for head bob for off hand models)
		AddViewModelBob( owner, vmorigin, vmangles );

		// Add lag
		CalcViewModelLag( vmorigin, vmangles, vmangoriginal );

#if defined( CLIENT_DLL )
		if ( !prediction->InPrediction() )
		{
			// Let the viewmodel shake at about 10% of the amplitude of the player's view
			vieweffects->ApplyShake( vmorigin, vmangles, 0.1 );	
		}
#endif
	}

	CalcIronsights( vmorigin, vmangles );

	SetLocalOrigin( vmorigin );
	SetLocalAngles( vmangles );

#endif
}

baseviewmodel_shared.h

Don't forget to declare the new function:

 	void		CalcIronsights( Vector &pos, QAngle &ang );

Adjusting the bullet spread

This is optional, you can skip this if you don't want your weapons to be more accurate (or inaccurate) when ironsighted.

Open the CPP of the weapon you want to be affected by ironsights. I used the SMG for this.

weapon_smg1.cpp

Once again, use the dropdown or simply Ctrl + F for virtual const Vector& GetBulletSpread( void ). You'll see the following:

virtual const Vector& GetBulletSpread( void )
	{
			static const Vector cone = VECTOR_CONE_5DEGREES;
			return cone;
	}

Replace it with the following:

virtual const Vector& GetBulletSpread( void )
	{
		if ( m_bIsIronsighted )
		{
			static const Vector cone = VECTOR_CONE_1DEGREES;
			return cone;
		}
		else
		{
			static const Vector cone = VECTOR_CONE_5DEGREES;
			return cone;
		}
	}

I don't think an explanation is necessary, simple "if" commands are by far the easiest to understand. You've noticed I defined my ironsighted bullet spread as 1 degree. This can be anything you like, provided you choose from the basecombatweapon_shared header. To make your life easier, however, I provide the list here.

#define VECTOR_CONE_PRECALCULATED	vec3_origin
#define VECTOR_CONE_1DEGREES		Vector( 0.00873, 0.00873, 0.00873 )
#define VECTOR_CONE_2DEGREES		Vector( 0.01745, 0.01745, 0.01745 )
#define VECTOR_CONE_3DEGREES		Vector( 0.02618, 0.02618, 0.02618 )
#define VECTOR_CONE_4DEGREES		Vector( 0.03490, 0.03490, 0.03490 )
#define VECTOR_CONE_5DEGREES		Vector( 0.04362, 0.04362, 0.04362 )
#define VECTOR_CONE_6DEGREES		Vector( 0.05234, 0.05234, 0.05234 )
#define VECTOR_CONE_7DEGREES		Vector( 0.06105, 0.06105, 0.06105 )
#define VECTOR_CONE_8DEGREES		Vector( 0.06976, 0.06976, 0.06976 )
#define VECTOR_CONE_9DEGREES		Vector( 0.07846, 0.07846, 0.07846 )
#define VECTOR_CONE_10DEGREES		Vector( 0.08716, 0.08716, 0.08716 )
#define VECTOR_CONE_15DEGREES		Vector( 0.13053, 0.13053, 0.13053 )
#define VECTOR_CONE_20DEGREES		Vector( 0.17365, 0.17365, 0.17365 )

An example with 5 degree cone on the left, and 1 degree cone on the right.

Bullet Spread Example

Add keybind

Last but not least, add the key to the Options/Keyboard-menu:

kb_act.lst

Probably somewhere in "#Valve_Combat_Title":

 "toggle_ironsight"		"#MOD_Toggle_Ironsight" 

And don't forget to add this #MOD_Toggle_Ironsight to your resource/MOD_english.txt (and the other lanuages).

Notes

It would be great if someone would add the changes you need for a HL2MP-mod and a Scratch-mod and maybe for the ep1-engine (if any are needed).

I would appreciate it if you would credit me, z33ky, if you use this code (modified or unmodified) - but there is no need to.

Also, if you fix the issues, post how to!