Adding Ironsights: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
m (improved the understandability of the proxy code)
m (updated wording of the article)
Line 1: Line 1:
{{pov}}
This is a tutorial how to add procedural ironsighting and is an alternative to [[Ironsights|Jorg's code]].


This is a tutorial how to add Ironsights and is an alternative to [[Ironsights|Jorg's code]].
This approach is networked and allows you to control the toggling server-side, thus making it possible to affect game logic (e.g. add different bullet spread) easily. It also has working angles and fov-change.
 
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 might require some small adjustments to work in multiplayer.


__TOC__
__TOC__
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 ==
== Weaponscript ==
At first, we will add some custom script-variables to be able to adjust the ironsight-offset via the weapon-script.
To begin, add script-variables to be able to adjust the ironsight-offset via the [[Weapon_Script|weapon-script]].


=== weapon_parse.h ===
=== weapon_parse.h ===
Line 66: Line 55:


== Getting the offsets ==
== Getting the offsets ==
Now we will add simple functions to get the information we parsed from the weapon-scripts.
Now 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.
Additionally add [[ConVar|ConVars]] to overwrite the parsed info so we can easily make and adjust new ironsight-offsets via the [[Developer_Console|console]].


=== basecombatweapon_shared.h ===
=== basecombatweapon_shared.h ===
Line 75: Line 64:
QAngle GetIronsightAngleOffset( void ) const;
QAngle GetIronsightAngleOffset( void ) const;
float GetIronsightFOVOffset( void ) const;
float GetIronsightFOVOffset( void ) const;
</source>
=== basecombatweapon_shared.cpp ===
And then you add the function definitions:
<source lang="cpp">
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;
}
</source>
</source>


Line 94: Line 108:
</source>
</source>


And then you add the function definitions:
And implement the callbacks.
<source lang="cpp">
<source lang="cpp">
Vector CBaseCombatWeapon::GetIronsightPositionOffset( void ) const
void vm_adjust_enable_callback( IConVar *pConVar, char const *pOldString, float flOldValue )
{
{
if( viewmodel_adjust_enabled.GetBool() )
ConVarRef sv_cheats( "sv_cheats" );
return Vector( viewmodel_adjust_forward.GetFloat(), viewmodel_adjust_right.GetFloat(), viewmodel_adjust_up.GetFloat() );
if( !sv_cheats.IsValid() || sv_cheats.GetBool() )
return GetWpnData().vecIronsightPosOffset;
return;
 
ConVarRef var( pConVar );
 
if( var.GetBool() )
var.SetValue( "0" );
}
}


QAngle CBaseCombatWeapon::GetIronsightAngleOffset( void ) const
void vm_adjust_fov_callback( IConVar *pConVar, char const *pOldString, float flOldValue )
{
{
if( viewmodel_adjust_enabled.GetBool() )
if( !viewmodel_adjust_enabled.GetBool() )
return QAngle( viewmodel_adjust_pitch.GetFloat(), viewmodel_adjust_yaw.GetFloat(), viewmodel_adjust_roll.GetFloat() );
return;
return GetWpnData().angIronsightAngOffset;
 
}
ConVarRef var( pConVar );
 
CBasePlayer *pPlayer =
#ifdef GAME_DLL
UTIL_GetCommandClient();
#else
C_BasePlayer::GetLocalPlayer();
#endif
if( !pPlayer )
return;


float CBaseCombatWeapon::GetIronsightFOVOffset( void ) const
if( !pPlayer->SetFOV( pPlayer, pPlayer->GetDefaultFOV() + var.GetFloat(), 0.1f ) )
{
{
if( viewmodel_adjust_enabled.GetBool() )
Warning( "Could not set FOV\n" );
return viewmodel_adjust_fov.GetFloat();
var.SetValue( "0" );
return GetWpnData().flIronsightFOVOffset;
}
}
}
</source>
</source>
Line 129: Line 157:


=== basecombatweapon_shared.cpp ===
=== basecombatweapon_shared.cpp ===
Network them, let them be predicted and give 'em default values.
Network them, let them be predicted and give them default values.


Constructor:
Constructor:
Line 152: Line 180:


So, here's the code for the proxy:
So, here's the code for the proxy:
(Search for "BEGIN_PREDICTION_DATA( CBaseCombatWeapon )" and put it right about it.)
<source lang="cpp">
<source lang="cpp">
void RecvProxy_ToggleSights( const CRecvProxyData* pData, void* pStruct, void* pOut )
void RecvProxy_ToggleSights( const CRecvProxyData* pData, void* pStruct, void* pOut )
{
{
CBaseCombatWeapon *pWeapon = (CBaseCombatWeapon*)pStruct;
CBaseCombatWeapon *pWeapon = (CBaseCombatWeapon*)pStruct;
pData->m_Value.m_Int ? pWeapon->EnableIronsights() : pWeapon->DisableIronsights();
if( pData->m_Value.m_Int )
pWeapon->EnableIronsights();
else
pWeapon->DisableIronsights();
}
}
</source>
</source>
Line 175: Line 205:


=== basecombatweapon_shared.h ===
=== basecombatweapon_shared.h ===
Now that we have the variables, we also want accessors:
Now add accessors for the ironsight-variables:
<source lang="cpp">
<source lang="cpp">
virtual bool HasIronsights( void ) { return true; } //default yes; override and return false for weapons with no ironsights (like weapon_crowbar)
virtual bool HasIronsights( void ) { return true; } //default yes; override and return false for weapons with no ironsights (like weapon_crowbar)
bool IsIronsighted( void );
bool IsIronsighted( void );
void ToggleIronsights( void ) { m_bIsIronsighted ? DisableIronsights() : EnableIronsights(); }
void ToggleIronsights( void );
void EnableIronsights( void );
void EnableIronsights( void );
void DisableIronsights( void );
void DisableIronsights( void );
Line 191: Line 221:
return ( m_bIsIronsighted || viewmodel_adjust_enabled.GetBool() );
return ( m_bIsIronsighted || viewmodel_adjust_enabled.GetBool() );
}
}
void CBaseCombarWeapon::ToggleIronsights( void )
{
if( m_bIsIronsighted )
EnableIronsights();
else
DisableIronsights();


void CBaseCombatWeapon::EnableIronsights( void )
void CBaseCombatWeapon::EnableIronsights( void )
Line 202: Line 239:
return;
return;


if( pOwner->SetFOV( this, pOwner->GetDefaultFOV()+GetIronsightFOVOffset(), 1.0f ) ) //modify the last value to adjust how fast the fov is applied
if( pOwner->SetFOV( this, pOwner->GetDefaultFOV() + GetIronsightFOVOffset(), 1.0f ) ) //modify the last value to adjust how fast the fov is applied
{
{
m_bIsIronsighted = true;
m_bIsIronsighted = true;
Line 223: Line 260:
m_bIsIronsighted = false;
m_bIsIronsighted = false;
m_flIronsightedTime = gpGlobals->curtime;
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" );
}
void vm_adjust_fov_callback( IConVar *pConVar, char const *pOldString, float flOldValue )
{
if( !viewmodel_adjust_enabled.GetBool() )
return;
ConVarRef var( pConVar );
CBasePlayer *pPlayer =
#ifdef GAME_DLL
UTIL_GetLocalPlayer();
#else
C_BasePlayer::GetLocalPlayer();
#endif
if( !pPlayer )
return;
if( !pPlayer->SetFOV( pPlayer, pPlayer->GetDefaultFOV()+var.GetFloat(), 0.1f ) )
{
Warning( "Could not set FOV\n" );
var.SetValue( "0" );
}
}
}
}
Line 264: Line 266:
=== Toggle-command ===
=== Toggle-command ===
==== ConCommand ====
==== ConCommand ====
You probably want a command to switch between normal and sighted mode. It doesn't really matter where you put it, as long as you include the headers for CBasePlayer and CBaseCombatWeapon and it's client-side.
You probably want a [[ConCommand|command]] to switch between normal and sighted mode. It doesn't really matter where you put it, as long as you include the headers for CBasePlayer and CBaseCombatWeapon and it's client-side.
<source lang="cpp">
<source lang="cpp">
void CC_ToggleIronSights( void )
void CC_ToggleIronSights( void )
{
{
CBasePlayer* pPlayer = C_BasePlayer::GetLocalPlayer();
CBasePlayer* pPlayer = C_BasePlayer::GetLocalPlayer();
if ( pPlayer == NULL )
if( pPlayer == NULL )
return;
return;


Line 310: Line 312:
== Adjust the viewmodel ==
== Adjust the viewmodel ==
Ok, now for the last step we want to move the viewmodel according to the offsets.
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 [[User:Z33ky|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.
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 ===
=== baseviewmodel_shared.cpp ===
Line 325: Line 325:


//get delta time for interpolation
//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 delta = ( gpGlobals->curtime - pWeapon->m_flIronsightedTime ) * 2.5f; //modify this value to adjust how fast the interpolation is
float exp = ( pWeapon->IsIronsighted() ) ?  
float exp = ( pWeapon->IsIronsighted() ) ?  
( delta > 1.0f ) ? 1.0f : delta : //normal blending
( delta > 1.0f ) ? 1.0f : delta : //normal blending
Line 406: Line 406:


== Episode 1-engine fix ==
== Episode 1-engine fix ==
In the Episode 1-engine, SetFOV is server-side only. A simple fix would be to set the FOV on the server only, but I ([[User:Z33ky|z33ky]]) created a halfway decent clientside SetFOV-function:
In the Episode 1-engine, SetFOV is server-side only. A simple fix would be to set the FOV on the server only, but here's a client-side implementation, partially derived from OB code.


=== m_hZoomOwner ===
=== m_hZoomOwner ===
Line 446: Line 446:


=== ConVarRef ===
=== ConVarRef ===
ConVarRef does not exist in the Episode 1-engine. You can either use the OB tier1-library (should probably work; didn't test it) or instead of ConVarRef( "sv_cheats" ), put extern ConVar sv_cheats; at the top of cbasecombatweapon_shared.cpp and use the member by pointer-operator (->) instead of the member-operator (.).
ConVarRef does not exist in the Episode 1-engine. You can either use the OB tier1-library (note: untested; might not work) or instead of ConVarRef( "sv_cheats" ), put extern ConVar sv_cheats; at the top of cbasecombatweapon_shared.cpp and use the member by pointer-operator (->) instead of the member-operator (.).


For the ConVars in the callbacks, simply use the ConVars directly or cast the IConVar to a ConVar.
For the ConVars in the callbacks, simply use the ConVars directly or cast the IConVar to a ConVar.


== Adjusting the bullet spread ==  
== 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.
Open the CPP of the weapon you want to be affected by ironsights. I used the SMG for this.
Line 458: Line 456:
=== weapon_smg1.cpp ===
=== weapon_smg1.cpp ===


Once again, use the dropdown or simply Ctrl + F for ''virtual const Vector& GetBulletSpread( void )''. You'll see the following:
Search for the GetBulletSpread-function. It'll look something like this:
 
<source lang="cpp">
<source lang="cpp">
virtual const Vector& GetBulletSpread( void )
virtual const Vector& GetBulletSpread( void )
Line 468: Line 465:
</source>
</source>


Replace it with the following:
You can, for example, replace it with the following:


<source lang="cpp">
<source lang="cpp">
Line 486: Line 483:
</source>
</source>


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.
Here's an example picture with a 5 degree cone on the left, and a 1 degree cone on the right.
 
<source lang="cpp">
#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 )
</source>
 
An example with 5 degree cone on the left, and 1 degree cone on the right.


[http://i132.photobucket.com/albums/q15/spaghettoid_vermicellus/parcour0000.jpg Bullet Spread Example]
[http://i132.photobucket.com/albums/q15/spaghettoid_vermicellus/parcour0000.jpg Bullet Spread Example]
Line 517: Line 496:
And don't forget to add this #MOD_Toggle_Ironsight to your resource/MOD_english.txt (and the other lanuages).
And don't forget to add this #MOD_Toggle_Ironsight to your resource/MOD_english.txt (and the other lanuages).


== Notes ==
== Ironsight Sounds ==
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, [[User:Z33ky|z33ky]], if you use this code (modified or unmodified) - but there is no need to.
 
Also, if you fix the issues, post how to!
 
== Ironsight Sounds: Potteh's Adjustment ==
If like me, you wanted IronSight sounds for deploy/undeploy; Here's how to do it:
 
=== basecombatweapon_shared.cpp ===
=== basecombatweapon_shared.cpp ===
Underneath
<source lang="cpp">
pData->m_Value.m_Int ? pWeapon->EnableIronsights() : pWeapon->DisableIronsights();
</source>
Add
Add
<source land="cpp">pPlayer->EmitSound( "WWIPlayer.IronSightIn" );</source>
to EnableIronsights and
<source lang="cpp">pPlayer->EmitSound( "WWIPlayer.IronSightOut" );</source>
to DisableIronsights.


<source lang="cpp">
Change the strings WWIPlayer.IronSightin and WWIPlayer.IronSightOut to your own sounds and precache them.
const char *IronSightSound = pWeapon->IsIronsighted() ?  "WWIPlayer.IronSightIn" : "WWIPlayer.IronSightOut";
pPlayer->EmitSound( IronSightSound );
</source>
 
 
Of course changing WWIPlayer.IronSightin & WWIPlayer.IronSightOut to your own sounds which need to be precached.
Compile, run, enjoy.
 
-[[User:Potteh|Potteh]]
 


[[Category:Weapons programming]]
[[Category:Weapons programming]]

Revision as of 09:47, 30 July 2011

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

This approach is networked and allows you to control the toggling server-side, thus making it possible to affect game logic (e.g. add different bullet spread) easily. It also has working angles and fov-change.

Weaponscript

To begin, add 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
	{
		//note: you can set a bool here if you'd like to disable ironsights for weapons with no IronSight-key
		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 add simple functions to get the information we parsed from the weapon-scripts. Additionally add ConVars to overwrite the parsed info so we can easily make and adjust new ironsight-offsets via the console.

basecombatweapon_shared.h

This goes to the public functions:

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

basecombatweapon_shared.cpp

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

basecombatweapon_shared.cpp

These ConVars usually go after the includes:

//forward declarations of callbacks used by viewmodel_adjust_enable and viewmodel_adjust_fov
void vm_adjust_enable_callback( IConVar *pConVar, char const *pOldString, float flOldValue );
void vm_adjust_fov_callback( IConVar *pConVar, const char *pOldString, float flOldValue );

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, "Note: this feature is not available during any kind of zoom", vm_adjust_fov_callback );
ConVar viewmodel_adjust_enabled( "viewmodel_adjust_enabled", "0", FCVAR_REPLICATED|FCVAR_CHEAT, "enabled viewmodel adjusting", vm_adjust_enable_callback );

And implement the callbacks.

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

void vm_adjust_fov_callback( IConVar *pConVar, char const *pOldString, float flOldValue )
{
	if( !viewmodel_adjust_enabled.GetBool() )
		return;

	ConVarRef var( pConVar );

	CBasePlayer *pPlayer = 
#ifdef GAME_DLL
		UTIL_GetCommandClient();
#else
		C_BasePlayer::GetLocalPlayer();
#endif
	if( !pPlayer )
		return;

	if( !pPlayer->SetFOV( pPlayer, pPlayer->GetDefaultFOV() + var.GetFloat(), 0.1f ) )
	{
		Warning( "Could not set FOV\n" );
		var.SetValue( "0" );
	}
}

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, let them be predicted and give them default values.

Constructor:

	m_bIsIronsighted = false;
	m_flIronsightedTime = 0.0f;


Network-table (DT_BaseCombatWeapon):

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

and

	RecvPropInt( RECVINFO( m_bIsIronsighted ), 0, RecvProxy_ToggleSights ), //note: RecvPropBool is actually RecvPropInt (see its implementation), but we need a proxy
	RecvPropFloat( RECVINFO( m_flIronsightedTime ) ),

As mentioned, we need a RecvProxy on that variable. We don't just want the boolean to update, we also want the ironsights to toggle if it was changed only on the server.

So, here's the code for the proxy:

void RecvProxy_ToggleSights( const CRecvProxyData* pData, void* pStruct, void* pOut )
{
	CBaseCombatWeapon *pWeapon = (CBaseCombatWeapon*)pStruct;
	if( pData->m_Value.m_Int )
		pWeapon->EnableIronsights();
	else
		pWeapon->DisableIronsights();
}

Prediction-table (CBaseCombatWeapon):

	DEFINE_PRED_FIELD( m_bIsIronsighted, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
	DEFINE_PRED_FIELD( m_flIronsightedTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),


If you want the variables to be saved in a save-game, also add them in the data-description:

	DEFINE_FIELD( m_bIsIronsighted, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_flTronsightedTime, FIELD_FLOAT ),

basecombatweapon_shared.h

Now add accessors for the ironsight-variables:

	virtual bool				HasIronsights( void ) { return true; } //default yes; override and return false for weapons with no ironsights (like weapon_crowbar)
	bool					IsIronsighted( void );
	void					ToggleIronsights( void );
	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 CBaseCombarWeapon::ToggleIronsights( void )
{
	if( m_bIsIronsighted )
		EnableIronsights();
	else
		DisableIronsights();

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 ) ) //modify the last value 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 ) ) //modify the last value to adjust how fast the fov is applied
	{
		m_bIsIronsighted = false;
		m_flIronsightedTime = gpGlobals->curtime;
	}
}

Toggle-command

ConCommand

You probably want a command to switch between normal and sighted mode. It doesn't really matter where you put it, as long as you include the headers for CBasePlayer and CBaseCombatWeapon and it's client-side.

void CC_ToggleIronSights( void )
{
	CBasePlayer* pPlayer = C_BasePlayer::GetLocalPlayer();
	if( pPlayer == NULL )
		return;

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

	pWeapon->ToggleIronsights();

	engine->ServerCmd( "toggle_ironsight" ); //forward to server
}

static ConCommand toggle_ironsight("toggle_ironsight", CC_ToggleIronSights);

player.cpp

Then in CBasePlayer::ClientCommand, add this before returning false:

else if( stricmp( cmd, "toggle_ironsight" ) == 0 )
{
	CBaseCombatWeapon *pWeapon = GetActiveWeapon();
	if( pWeapon != NULL )
		pWeapon->ToggleIronsights();

	return true;
}

Automatic ironsight-toggle

basecombatweapon_shared.cpp

Add

DisableIronsights();

to the following places:

  • bool CBaseCombatWeapon::Holster( CBaseCombatWeapon *pSwitchingTo ) for switching/holstering weapons
  • bool CBaseCombatWeapon::Reload( void ) for reloading weapons
  • void CBaseCombatWeapon::Drop( const Vector &vecVelocity ) for weapon dropping

Adjust the viewmodel

Ok, now for the last step we want to move the viewmodel according to the offsets.

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.

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

Episode 1-engine fix

In the Episode 1-engine, SetFOV is server-side only. A simple fix would be to set the FOV on the server only, but here's a client-side implementation, partially derived from OB code.

m_hZoomOwner

Set m_hZoomOwner up for networking in player.cpp/.h by using a CNetworkHandle and adding it to the send-table.

In c_baseplayer.cpp/.h, add the m_hZoomOwner to the class-declaration, so it can be received. Set it to NULL in the constructor and add it to the recv- and the prediction-table.

Also add a SetFOV-function to C_BasePlayer and define it like this:

bool C_BasePlayer::SetFOV( C_BaseEntity *pRequester, int FOV, float zoomRate )
{
	//NOTENOTE: You MUST specify who is requesting the zoom change
	assert( pRequester != NULL );
	if ( pRequester == NULL )
		return false;

	if( ( m_hZoomOwner.Get() != NULL ) && ( m_hZoomOwner.Get() != pRequester ) )
		return false;
	else
	{
		//FIXME: Maybe do this is as an accessor instead
		if ( FOV == 0 )
		{
			m_hZoomOwner = NULL;
		}
		else
		{
			m_hZoomOwner = pRequester;
		}
	}

	m_iFOV = FOV;

	m_Local.m_flFOVRate	= zoomRate;

	return true;
}

ConVarRef

ConVarRef does not exist in the Episode 1-engine. You can either use the OB tier1-library (note: untested; might not work) or instead of ConVarRef( "sv_cheats" ), put extern ConVar sv_cheats; at the top of cbasecombatweapon_shared.cpp and use the member by pointer-operator (->) instead of the member-operator (.).

For the ConVars in the callbacks, simply use the ConVars directly or cast the IConVar to a ConVar.

Adjusting the bullet spread

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

weapon_smg1.cpp

Search for the GetBulletSpread-function. It'll look something like this:

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

You can, for example, 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;
		}
	}

Here's an example picture with a 5 degree cone on the left, and a 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).

Ironsight Sounds

basecombatweapon_shared.cpp

Add

pPlayer->EmitSound( "WWIPlayer.IronSightIn" );

to EnableIronsights and

pPlayer->EmitSound( "WWIPlayer.IronSightOut" );

to DisableIronsights.

Change the strings WWIPlayer.IronSightin and WWIPlayer.IronSightOut to your own sounds and precache them.