Realistic Simulated Bullets

From Valve Developer Community
Jump to: navigation, search

This article based on Simulated Bullets.

Note:Compile errors on SP MOD - OB - reported by djjonastybe on 07/01/2010
Changes
Modified algorithm of penetration
Added impacts of water and walls
Added bubbles and tracers
Added entity hit (EntityImpact() function is now complete)
Added ricochet
Bullet can lose speed
Speed changes now affects damage and impact force (but not for NPC or Player)
Removed unused stuff
Still to do
Multiplayer
Shotguns
Invalid tracer position
Tracer types
Bullet mass
Wind affection
Note:Although much of the code for the bullet_manager entity is shared client-side and server-side, the entity itself is not networked, but instead made into a server-side entity and client-side entity to run separately. The only thing being networked is the message to make the bullets.
Note:Not done for HL2MP

Procedure

game_shared\gamerules.cpp

Add
#include "bullet_manager.h"
After
#include "KeyValues.h"

Add
g_pBulletManager = (CBulletManager*)CBaseEntity::Create( "bullet_manager", vec3_origin, vec3_angle );
After
g_pPlayerResource = (CPlayerResource*)CBaseEntity::Create( "player_manager", vec3_origin, vec3_angle );

game_shared\ammodef.h

Change the Ammo_t structure to the following:

struct Ammo_t 
{
	char 					*pName;
	int					nDamageType;
	int					eTracerType;
	float					physicsForceImpulse;
	float					bulletSpeed;
	int					nMinSplashSize;
	int					nMaxSplashSize;

	int					nFlags;

	// Values for player/NPC damage and carrying capability
	// If the integers are set, they override the CVars
	int					pPlrDmg;		// CVar for player damage amount
	int					pNPCDmg;		// CVar for NPC damage amount
	int					pMaxCarry;		// CVar for maximum number can carry
	const ConVar*				pPlrDmgCVar;		// CVar for player damage amount
	const ConVar*				pNPCDmgCVar;		// CVar for NPC damage amount
	const ConVar*				pMaxCarryCVar;		// CVar for maximum number can carry
};

In the CAmmoDef class, change the first two AddAmmoType function prototypes to the following:

	void				AddAmmoType(char const* name, int damageType, int tracerType,
							int plr_dmg, int npc_dmg, int carry,
							float bulletSpeed, float physicsForceImpulse,
							int nFlags, int minSplashSize = 4,
							int maxSplashSize = 8 );

	void				AddAmmoType(char const* name, int damageType, int tracerType,
							char const* plr_cvar, char const* npc_var,
							char const* carry_cvar, float bulletSpeed,
							float physicsForceImpulse, int nFlags,
							int minSplashSize = 4, int maxSplashSize = 8 );

game_shared\ammodef.cpp

Add
float bulletSpeed,
Before both
float physicsForceImpulse,

Add
m_AmmoType[m_nAmmoIndex].bulletSpeed = bulletSpeed;
Before both
m_AmmoType[m_nAmmoIndex].physicsForceImpulse = physicsForceImpulse;

game_shared\hl2\hl2_gamerules.cpp

Add
#define BULLET_SPEED(ftpersec) 0.12*ftpersec //inches per centisecond
Before
CAmmoDef *GetAmmoDef()

Change GetAmmoDef to the following:

CAmmoDef *GetAmmoDef()
{
	static CAmmoDef def;
	static bool bInitted = false;
	
	if ( !bInitted )
	{
		bInitted = true;
		//		Name		Damage				Tracer			PlrDmg	NPCDmg	MaxCarry	Bulletspeed		Physics Force Impulse		Flags
		def.AddAmmoType("AR2",		DMG_BULLET,			TRACER_LINE_AND_WHIZ,	0,	0,	60,		BULLET_SPEED(1225),	BULLET_IMPULSE(200, 1225),	0 );
		def.AddAmmoType("AR2AltFire",	DMG_DISSOLVE,			TRACER_NONE,		0,	0,	3,		0,			0,				0 );
		def.AddAmmoType("Pistol",	DMG_BULLET,			TRACER_LINE_AND_WHIZ,	0,	0,	150,		BULLET_SPEED(1225),	BULLET_IMPULSE(200, 1225),	0 );
		def.AddAmmoType("SMG1",		DMG_BULLET,			TRACER_LINE_AND_WHIZ,	0,	0,	225,		BULLET_SPEED(1225),	BULLET_IMPULSE(200, 1225),	0 );
		def.AddAmmoType("357",		DMG_BULLET,			TRACER_LINE_AND_WHIZ,	0,	0,	12,		BULLET_SPEED(5000),	BULLET_IMPULSE(800, 5000),	0 );
		def.AddAmmoType("XBowBolt",	DMG_BULLET,			TRACER_LINE,		0,	0,	10,		BULLET_SPEED(8000),	BULLET_IMPULSE(800, 8000),	0 );
		def.AddAmmoType("Buckshot",	DMG_BULLET | DMG_BUCKSHOT,	TRACER_LINE,		0,	0,	30,		BULLET_SPEED(1200),	BULLET_IMPULSE(400, 1200),	0 );
		def.AddAmmoType("RPG_Round",	DMG_BURN,			TRACER_NONE,		0,	0,	3,		0,			0,				0 );
		def.AddAmmoType("SMG1_Grenade",	DMG_BURN,			TRACER_NONE,		0,	0,	3,		0,			0,				0 );
		def.AddAmmoType("Grenade",	DMG_BURN,			TRACER_NONE,		0,	0,	5,		0,			0,				0 );
		def.AddAmmoType("slam",		DMG_BURN,			TRACER_NONE,		0,	0,	5,		0,			0,				0 );
	}

	return &def;
}

As you can see, the bullet speed (start speed) is derived from the ftpersec of the bullet impulses.

game_shared\bullet_manager.cpp

Create and add this file to server and client projects

// (More) Reallistic simulated bullets
//
// This code is originally based from article on Valve Developer Community
// The original code you can find by this link:
// http://developer.valvesoftware.com/wiki/Simulated_Bullets

// NOTENOTE: Tested only on localhost. There maybe a latency errors and others
// NOTENOTE: The simulation might be strange.

#include "cbase.h"
#include "util_shared.h"
#include "bullet_manager.h"
#include "effect_dispatch_data.h"
#include "tier0/vprof.h"
#include "decals.h"


CBulletManager *g_pBulletManager;
CUtlLinkedList<CSimulatedBullet*> g_Bullets;

#ifdef CLIENT_DLL//-------------------------------------------------
#include "engine/ivdebugoverlay.h"
#include "c_te_effect_dispatch.h"
ConVar g_debug_client_bullets( "g_debug_client_bullets", "0", FCVAR_CHEAT );
extern void FX_PlayerTracer( Vector& start, Vector& end);
#else//-------------------------------------------------------------
#include "te_effect_dispatch.h"
#include "soundent.h"
#include "player_pickup.h"
#include "ilagcompensationmanager.h"
ConVar g_debug_bullets( "g_debug_bullets", "0", FCVAR_CHEAT, "Debug of bullet simulation\nThe white line shows the bullet trail.\nThe red line shows not passed penetration test.\nThe green line shows passed penetration test. Turn developer mode for more information." );
#endif//------------------------------------------------------------

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

#define MAX_RICO_DOT_ANGLE 0.15f  //Maximum dot allowed for any ricochet
#define MIN_RICO_SPEED_PERC 0.55f //Minimum speed percent allowed for any ricochet


static void BulletSpeedModifierCallback(ConVar *var, const char *pOldString)
{
	if(var->GetFloat()==0.0f) //To avoid math exception
		var->Revert();
}
ConVar sv_bullet_speed_modifier( "sv_bullet_speed_modifier", "700.000000", (FCVAR_ARCHIVE | FCVAR_REPLICATED),
								"Density/(This Value) * (Distance Penetrated) = (Change in Speed)",
								BulletSpeedModifierCallback );

static void UnrealRicochetCallback(ConVar *var, const char *pOldString)
{
	if(gpGlobals->maxClients > 1)
	{
		var->Revert();
		Msg("Cannot use unreal ricochet in multiplayer game\n");
	}

	if(var->GetBool()) //To avoid math exception
		Warning("\nWarning! Enabling unreal ricochet may cause the game crash.\n\n");
}
ConVar sv_bullet_unrealricochet( "sv_bullet_unrealricochet", "0", FCVAR_CHEAT | FCVAR_REPLICATED, "Unreal ricochet",UnrealRicochetCallback);



static void BulletStopSpeedCallback(ConVar *var, const char *pOldString)
{
	int val = var->GetInt();
	if(val<ONE_HIT_MODE)
		var->Revert();
	else if(BulletManager())
		BulletManager()->UpdateBulletStopSpeed();
}
ConVar sv_bullet_stop_speed( "sv_bullet_stop_speed", "40", (FCVAR_ARCHIVE | FCVAR_REPLICATED),
							"Integral speed at which to remove the bullet from the bullet queue\n-1 is butter mode\n-2 is 1 hit mode",
							BulletStopSpeedCallback );



LINK_ENTITY_TO_CLASS( bullet_manager, CBulletManager );



//==================================================
// Purpose:	Constructor
//==================================================
CSimulatedBullet::CSimulatedBullet()
{
	m_vecOrigin.Init();
	m_vecDirShooting.Init();
	m_flInitialBulletSpeed = m_flBulletSpeed = 0;
	m_flEntryDensity = 0.0f;
	bStuckInWall = false;

	m_iDamageType = 2;
}

//==================================================
// Purpose:	Constructor
//==================================================
CSimulatedBullet::CSimulatedBullet( FireBulletsInfo_t info,Vector newdir,CBaseEntity *pInfictor, CBaseEntity *pAdditionalIgnoreEnt,
						bool bTraceHull
#ifndef CLIENT_DLL
						, CBaseEntity *pCaller
#endif
)
{
	// Input validation
	Assert( pInfictor );
#ifndef CLIENT_DLL
	Assert( pCaller );
#endif

	bulletinfo = info;			// Setup Fire bullets information here

	p_eInfictor = pInfictor;	// Setup inflictor

	// Create a list of entities with which this bullet does not collide.
	m_pIgnoreList = new CTraceFilterSimpleList(COLLISION_GROUP_NONE);
	Assert( m_pIgnoreList );

	bStuckInWall = false;

	// Don't collide with the entity firing the bullet.
	m_pIgnoreList->AddEntityToIgnore(p_eInfictor);

	// Don't collide with some optionally-specified entity.
	if(pAdditionalIgnoreEnt!=NULL)
		m_pIgnoreList->AddEntityToIgnore(pAdditionalIgnoreEnt);

	m_iDamageType = GetAmmoDef()->DamageType( bulletinfo.m_iAmmoType );


	//m_szTracerName = (char*)p_eInfictor->GetTracerType(); 

	// Basic information about the bullet
	m_flInitialBulletSpeed = m_flBulletSpeed = GetAmmoDef()->GetAmmoOfIndex(bulletinfo.m_iAmmoType)->bulletSpeed;
	m_vecDirShooting = newdir;
	m_vecOrigin = bulletinfo.m_vecSrc;
	m_bTraceHull = bTraceHull;
#ifndef CLIENT_DLL
	m_hCaller = pCaller;
#endif
	m_flEntryDensity = 0.0f;
	m_vecTraceRay = m_vecOrigin + m_vecDirShooting * m_flBulletSpeed;
	m_flRayLength = m_flInitialBulletSpeed;
}



//==================================================
// Purpose:	Deconstructor
//==================================================
CSimulatedBullet::~CSimulatedBullet()
{
	delete m_pIgnoreList;
}
//==================================================
// Purpose:	Simulates a bullet through a ray of its bullet speed
//==================================================
bool CSimulatedBullet::SimulateBullet(void)
{
	VPROF( "C_SimulatedBullet::SimulateBullet" );

	if(!IsFinite(m_flBulletSpeed))
		return false;		 //prevent a weird crash

	trace_t trace;

	if(m_flBulletSpeed <= 0) //Avoid errors;
		return false;

	if(!p_eInfictor)
	{
		p_eInfictor = bulletinfo.m_pAttacker;

		if(!p_eInfictor)
			return false;
	}

	m_flRayLength = m_flBulletSpeed;

	m_flBulletSpeed += 0.8f * m_vecDirShooting.z; //TODO: Bullet mass
	m_vecTraceRay = m_vecOrigin + m_vecDirShooting * m_flBulletSpeed; 

	m_vecDirShooting.z -= 0.1 / m_flBulletSpeed;

#ifdef GAME_DLL
	if(bulletinfo.m_flLatency!= 0 )
	{
		m_vecTraceRay *= bulletinfo.m_flLatency * 100;
	}
#endif

	if(!IsInWorld())
	{
		return false;
	}

	if(bStuckInWall)
		return false;

	if(!m_bWasInWater)
	{
		WaterHit(m_vecOrigin,m_vecTraceRay);
	}

	m_bWasInWater = (UTIL_PointContents(m_vecOrigin)& MASK_WATER )?true:false;

	if(m_bWasInWater)
	{
		m_flBulletSpeed -= m_flBulletSpeed * 0.6;
#ifdef GAME_DLL
		//This is a server stuff
		UTIL_BubbleTrail(m_vecOrigin, m_vecTraceRay, 5 );
#endif
	}

#ifndef CLIENT_DLL
	if(m_bWasInWater)
	{
		CEffectData tracerData;
		tracerData.m_vStart = m_vecOrigin;
		tracerData.m_vOrigin = m_vecTraceRay;

		tracerData.m_fFlags = TRACER_TYPE_WATERBULLET;

		DispatchEffect( "TracerSound", tracerData );
	}
#endif


	bool bulletSpeedCheck;
	bulletSpeedCheck = false;


	if(m_bTraceHull)
		UTIL_TraceHull( m_vecOrigin, m_vecTraceRay, Vector(-3, -3, -3), Vector(3, 3, 3), MASK_SHOT, m_pIgnoreList, &trace );
	else
		UTIL_TraceLine( m_vecOrigin, m_vecTraceRay, MASK_SHOT, m_pIgnoreList, &trace );

	if(!m_bWasInWater)
	{
		UTIL_Tracer( m_vecOrigin, trace.endpos, p_eInfictor->entindex(), TRACER_DONT_USE_ATTACHMENT, 0, true,p_eInfictor->GetTracerType());
	}

#ifdef CLIENT_DLL
	if(g_debug_client_bullets.GetBool())
	{
		debugoverlay->AddLineOverlay( trace.startpos, trace.endpos, 255, 0, 0, true, 10.0f );
	}
#else
	if(g_debug_bullets.GetBool())
	{
		NDebugOverlay::Line( trace.startpos, trace.endpos, 255, 255, 255, true, 10.0f );
	}

	// Now hit all triggers along the ray that respond to shots...
	// Clip the ray to the first collided solid returned from traceline
	CTakeDamageInfo triggerInfo( p_eInfictor, bulletinfo.m_pAttacker, bulletinfo.m_iDamage, GetDamageType() );
	CalculateBulletDamageForce( &triggerInfo, bulletinfo.m_iAmmoType, m_vecDirShooting, trace.endpos );
	triggerInfo.ScaleDamageForce( bulletinfo.m_flDamageForceScale );
	triggerInfo.SetAmmoType( bulletinfo.m_iAmmoType );
	BulletManager()->SendTraceAttackToTriggers( triggerInfo, trace.startpos, trace.endpos, m_vecDirShooting );
#endif

	if (trace.fraction == 1.0f)
	{
		m_vecOrigin += m_vecDirShooting * m_flBulletSpeed; //Do the WAY

		CEffectData data;
		data.m_vStart = trace.startpos;
		data.m_vOrigin = trace.endpos;
		data.m_nDamageType = GetDamageType();
	
		DispatchEffect( "RagdollImpact", data );

		return true;
	}
	else
	{
		EntityImpact(trace);

		if (trace.m_pEnt == p_eInfictor) //HACK: Remove bullet if we hit self (for frag grenades)
			return false;

		if(!(trace.surface.flags&SURF_SKY))
		{
			if(trace.allsolid)//in solid
			{
				if(!AllSolid(trace))
					return false;

				m_vecOrigin += m_vecDirShooting * m_flBulletSpeed; //Do the WAY

				bulletSpeedCheck = true;
			}
			else if(trace.fraction!=1.0f)//hit solid
			{
				if(!EndSolid(trace))
					return false;

				bulletSpeedCheck = true;
			}
			else if(trace.startsolid)//exit solid
			{
				if(!StartSolid(trace))
					return false;
				
				m_vecOrigin += m_vecDirShooting * m_flBulletSpeed; //Do the WAY

				bulletSpeedCheck = true;
			}
			else
			{
				//don't do a bullet speed check for not touching anything
			}
		}
		else
		{
			return false; //Through sky? No.
		}
	}

	if(sv_bullet_unrealricochet.GetBool()) //Fun bullet ricochet fix
	{
		delete m_pIgnoreList; //Prevent causing of memory leak
		m_pIgnoreList = new CTraceFilterSimpleList(COLLISION_GROUP_NONE);
	}

	if(bulletSpeedCheck)
	{
		if(m_flBulletSpeed<=BulletManager()->BulletStopSpeed())
		{
			return false;
		}
	}

	return true;
}


//==================================================
// Purpose:	Simulates when a solid is exited
//==================================================
bool CSimulatedBullet::StartSolid(trace_t &ptr)
{
	switch(BulletManager()->BulletStopSpeed())
	{
		case BUTTER_MODE:
		{
			//Do nothing to bullet speed
			return true;
		}
		case ONE_HIT_MODE:
		{
			return false;
		}
		default:
		{
			float flPenetrationDistance = VectorLength(AbsEntry - AbsExit);

			m_flBulletSpeed -= flPenetrationDistance * m_flEntryDensity / sv_bullet_speed_modifier.GetFloat();
			return true;
		}
	}
	return true;
}


//==================================================
// Purpose:	Simulates when a solid is being passed through
//==================================================
bool CSimulatedBullet::AllSolid(trace_t &ptr)
{
	switch(BulletManager()->BulletStopSpeed())
	{
		case BUTTER_MODE:
		{
			//Do nothing to bullet speed
			return true;
		}
		case ONE_HIT_MODE:
		{
			return false;
		}
		default:
		{
			m_flBulletSpeed -= m_flBulletSpeed * m_flEntryDensity / sv_bullet_speed_modifier.GetFloat();
			return true;
		}
	}
	return true;
}


//==================================================
// Purpose:	Simulate when a surface is hit
//==================================================
bool CSimulatedBullet::EndSolid(trace_t &ptr)
{
	m_vecEntryPosition = ptr.endpos;

#ifndef CLIENT_DLL
	int soundEntChannel = ( bulletinfo.m_nFlags&FIRE_BULLETS_TEMPORARY_DANGER_SOUND ) ? SOUNDENT_CHANNEL_BULLET_IMPACT : SOUNDENT_CHANNEL_UNSPECIFIED;

	CSoundEnt::InsertSound( SOUND_BULLET_IMPACT, m_vecEntryPosition, 200, 0.5, p_eInfictor, soundEntChannel );
#endif

	if(FStrEq(ptr.surface.name,"tools/toolsblockbullets"))
	{
		return false;
	}
	m_flEntryDensity = physprops->GetSurfaceData(ptr.surface.surfaceProps)->physics.density;

	trace_t rtr;
	Vector vecEnd = m_vecEntryPosition + m_vecDirShooting * 32; //32 units
	
	//Doing now test of penetration
	UTIL_TraceLine(m_vecEntryPosition + m_vecDirShooting * 1.1, vecEnd, MASK_SHOT, m_pIgnoreList, &rtr);

	AbsEntry = m_vecEntryPosition;
	AbsExit  = rtr.startpos;

	float flPenetrationDistance = VectorLength(AbsEntry - AbsExit);

	DesiredDistance = 0.0f;

	surfacedata_t *p_penetrsurf = physprops->GetSurfaceData( ptr.surface.surfaceProps );
	switch (p_penetrsurf->game.material)
	{
		case CHAR_TEX_WOOD:
			DesiredDistance = 9.0f; // 9 units in hammer
			break;
		case CHAR_TEX_GRATE:
			DesiredDistance = 6.0f; // 6 units in hammer
			break;
		case CHAR_TEX_CONCRETE:
			DesiredDistance = 4.0f; // 4 units in hammer
			break;
		case CHAR_TEX_TILE:
			DesiredDistance = 5.0f; // 5 units in hammer
			break;
		case CHAR_TEX_COMPUTER:
			DesiredDistance = 5.0f; // 5 units in hammer
			break;
		case CHAR_TEX_GLASS:
			DesiredDistance = 8.0f; // maximum 8 units in hammer.
			break;
		case CHAR_TEX_VENT:
			DesiredDistance = 4.0f; // 4 units in hammer and no more(!)
			break;
		case CHAR_TEX_METAL:
			DesiredDistance = 5.0f; // 2 units in hammer. We cannot penetrate a really 'fat' metal wall. Corners are good.
			break;
		case CHAR_TEX_PLASTIC:
			DesiredDistance = 8.0f; // 8 units in hammer: Plastic can more
			break;
		case CHAR_TEX_BLOODYFLESH:
			DesiredDistance = 16.0f; // 16 units in hammer
			break;
		case CHAR_TEX_FLESH:
			DesiredDistance = 16.0f; // 16 units in hammer
			break;
		case CHAR_TEX_DIRT:
			DesiredDistance = 6.0f; // 6 units in hammer: >4 cm of plaster can be penetrated
			break;
	}

	Vector	reflect;
	float fldot = m_vecDirShooting.Dot( ptr.plane.normal );						//Getting angles from lasttrace

	bool bMustDoRico = (fldot > -MAX_RICO_DOT_ANGLE && GetBulletSpeedRatio() > MIN_RICO_SPEED_PERC); // We can't do richochet when bullet has lowest speed

	if (sv_bullet_unrealricochet.GetBool() && p_eInfictor->IsPlayer()) //Cheat is only for player,yet =)
		bMustDoRico = true;

	if ( bMustDoRico )
	{
		if(!sv_bullet_unrealricochet.GetBool())
		{
			if(gpGlobals->maxClients == 1) //Use more simple for multiplayer
			{
				m_flBulletSpeed *= (1.0f / -fldot) * random->RandomFloat(0.005,0.1);
			}
			else
			{
				m_flBulletSpeed *= (1.0f / -fldot) * 0.025;
			}
		}
		else
		{
			m_flBulletSpeed *= 0.9;
		}

		reflect = m_vecDirShooting + ( ptr.plane.normal * ( fldot*-2.0f ) );	//reflecting

		if(gpGlobals->maxClients == 1 && !sv_bullet_unrealricochet.GetBool()) //Use more simple for multiplayer
		{
			reflect[0] += random->RandomFloat( fldot, -fldot );
			reflect[1] += random->RandomFloat( fldot, -fldot );
			reflect[2] += random->RandomFloat( fldot, -fldot );
		}

		m_flEntryDensity *= 0.2;

		m_vecDirShooting = reflect;

		m_vecOrigin = ptr.endpos+m_vecDirShooting;//(ptr.endpos + m_vecDirShooting*1.1) + m_vecDirShooting * m_flBulletSpeed;
	}
	else
	{
		if (flPenetrationDistance > DesiredDistance || ptr.IsDispSurface()) //|| !bulletinfo.m_bMustPenetrate
		{
			bStuckInWall = true;

#ifdef GAME_DLL
			if(g_debug_bullets.GetBool())
			{
				NDebugOverlay::Line( AbsEntry, AbsExit, 255, 0, 0, true, 10.0f );

				if(!ptr.IsDispSurface())
					DevMsg("Cannot penetrate surface, The distance(%f) > %f \n",flPenetrationDistance,DesiredDistance);
				else
					DevMsg("Displacement penetration was tempolary disabled\n");
			}
#endif
		}
		else
		{
				trace_t tr;
				UTIL_TraceLine(AbsExit + m_vecDirShooting * 1.1 ,AbsEntry,MASK_SHOT,m_pIgnoreList,&tr);
				UTIL_ImpactTrace( &tr, GetDamageType() ); // On exit

				#ifdef GAME_DLL
					if(g_debug_bullets.GetBool())
					{			
						NDebugOverlay::Line( AbsEntry, AbsExit, 0, 255, 0, true, 10.0f );
					}
				#endif

				if(gpGlobals->maxClients == 1) //Use more simple for multiplayer
				{
						m_vecDirShooting[0] += (random->RandomFloat( -flPenetrationDistance, flPenetrationDistance ))*0.03;
						m_vecDirShooting[1] += (random->RandomFloat( -flPenetrationDistance, flPenetrationDistance ))*0.03;
						m_vecDirShooting[2] += (random->RandomFloat( -flPenetrationDistance, flPenetrationDistance ))*0.03;

						VectorNormalize(m_vecDirShooting);
				}

				m_vecOrigin = AbsExit+m_vecDirShooting;
		}
	}

#ifdef GAME_DLL
	//Cancel making dust underwater:
	if(!m_bWasInWater)
	{
		UTIL_ImpactTrace( &ptr, GetDamageType() );
	}
#endif

	m_flBulletSpeed -= flPenetrationDistance * m_flEntryDensity / sv_bullet_speed_modifier.GetFloat();

	if(BulletManager()->BulletStopSpeed()==ONE_HIT_MODE)
	{
		return false;
	}
	return true;
}

//-----------------------------------------------------------------------------
// analog of HandleShotImpactingWater
//-----------------------------------------------------------------------------
bool CSimulatedBullet::WaterHit(const Vector &vecStart,const Vector &vecEnd)
{
	trace_t	waterTrace;
	// Trace again with water enabled
	UTIL_TraceLine( vecStart, vecEnd, (MASK_SHOT|CONTENTS_WATER|CONTENTS_SLIME), m_pIgnoreList, &waterTrace );

	// See if this is the point we entered
	if ( ( enginetrace->GetPointContents( waterTrace.endpos - Vector(0,0,0.1f) ) & (CONTENTS_WATER|CONTENTS_SLIME) ) == 0 )
		return false;

	int	nMinSplashSize = GetAmmoDef()->MinSplashSize(GetAmmoTypeIndex()) * (1.5 - GetBulletSpeedRatio());
	int	nMaxSplashSize = GetAmmoDef()->MaxSplashSize(GetAmmoTypeIndex()) * (1.5 - GetBulletSpeedRatio()); //High speed - small splash

	CEffectData	data;
	data.m_vOrigin = waterTrace.endpos;
	data.m_vNormal = waterTrace.plane.normal;
	data.m_flScale = random->RandomFloat( nMinSplashSize, nMaxSplashSize );
	if ( waterTrace.contents & CONTENTS_SLIME )
	{
		data.m_fFlags |= FX_WATER_IN_SLIME;
	}
	DispatchEffect( "gunshotsplash", data );
	return true;
}



//==================================================
// Purpose:	Entity impact procedure
//==================================================
void CSimulatedBullet::EntityImpact(trace_t &ptr)
{
	if(bStuckInWall)
		return;

	if (ptr.m_pEnt != NULL)
	{
		//Hit inflicted once to avoid perfomance errors

		if(!p_eInfictor->IsPlayer())
		{
			if (ptr.m_pEnt->IsPlayer())
			{
				if (m_pIgnoreList->ShouldHitEntity(ptr.m_pEnt,MASK_SHOT))
				{
					m_pIgnoreList->AddEntityToIgnore(ptr.m_pEnt);
				}
				else
				{
					return;
				}
			}
		}

		if(ptr.m_pEnt == m_hLastHit)
			return;

		m_hLastHit = ptr.m_pEnt;

		float flActualDamage = g_pGameRules->GetAmmoDamage( p_eInfictor, ptr.m_pEnt, bulletinfo.m_iAmmoType );
		float flActualForce  = bulletinfo.m_flDamageForceScale; 
		
		if (!ptr.m_pEnt->IsPlayer() || !ptr.m_pEnt->IsNPC())
		{
			//To make it more reallistic
			float fldot = m_vecDirShooting.Dot( ptr.plane.normal );	 
			//We affecting damage by angle. If we has lower angle of reflection, doing lower damage.
			flActualDamage *= -fldot;
			flActualForce  *= -fldot;
			//But we not doing it on the players or NPC's
		}

		flActualDamage *= GetBulletSpeedRatio(); //And also affect damage by speed modifications
		flActualForce  *= GetBulletSpeedRatio(); //Slow bullet - bad force...

		DevMsg("Hit: %s, Damage: %f, Force: %f \n",ptr.m_pEnt->GetClassname(),flActualDamage,flActualForce);

		CTakeDamageInfo dmgInfo( p_eInfictor, bulletinfo.m_pAttacker, flActualDamage, GetDamageType() );

		CalculateBulletDamageForce( &dmgInfo, bulletinfo.m_iAmmoType, m_vecDirShooting, ptr.endpos );
		dmgInfo.ScaleDamageForce( flActualForce );
		dmgInfo.SetAmmoType( bulletinfo.m_iAmmoType );

		ptr.m_pEnt->DispatchTraceAttack( dmgInfo, bulletinfo.m_vecDirShooting, &ptr );

	#ifdef GAME_DLL
		ApplyMultiDamage(); //It's requried

		if ( GetAmmoDef()->Flags(GetAmmoTypeIndex()) & AMMO_FORCE_DROP_IF_CARRIED )
		{
			// Make sure if the player is holding this, he drops it
			Pickup_ForcePlayerToDropThisObject( ptr.m_pEnt );		
		}
	#endif
	}
}


//==================================================
// Purpose:	Simulates all bullets every centisecond
//==================================================
#ifndef CLIENT_DLL
void CBulletManager::Think(void)
#else
void CBulletManager::ClientThink(void)
#endif
{
	unsigned short iNext=0;
	for ( unsigned short i=g_Bullets.Head(); i != g_Bullets.InvalidIndex(); i=iNext )
	{
		iNext = g_Bullets.Next( i );
		if ( !g_Bullets[i]->SimulateBullet() )
			RemoveBullet( i );
	}
	if(g_Bullets.Head()!=g_Bullets.InvalidIndex())
	{
#ifdef CLIENT_DLL
		SetNextClientThink( gpGlobals->curtime + 0.01f );
#else
		SetNextThink( gpGlobals->curtime + 0.01f );
#endif
	}
}


//==================================================
// Purpose:	Called by sv_bullet_stop_speed callback to keep track of resources
//==================================================
void CBulletManager::UpdateBulletStopSpeed(void)
{
	m_iBulletStopSpeed = sv_bullet_stop_speed.GetInt();
}

#ifndef CLIENT_DLL
void CBulletManager::SendTraceAttackToTriggers( const CTakeDamageInfo &info, const Vector& start, const Vector& end, const Vector& dir )
{
	TraceAttackToTriggers(info,start,end,dir);
}
#endif


//==================================================
// Purpose:	Add bullet to linked list
//			Handle lag compensation with "prebullet simulation"
// Output:	Bullet's index (-1 for error)
//==================================================
int CBulletManager::AddBullet(CSimulatedBullet *pBullet)
{
	if (pBullet->GetAmmoTypeIndex() == -1)
	{
		Msg("ERROR: Undefined ammo type!\n");
		return -1;
	}
	unsigned short index = g_Bullets.AddToTail(pBullet);
#ifdef CLIENT_DLL
	DevMsg( "Client Bullet Created (%i)\n", index);
	if(g_Bullets.Count()==1)
	{
		SetNextClientThink(gpGlobals->curtime + 0.01f);
	}
#else
	DevMsg( "Bullet Created (%i) LagCompensation %f\n", index, pBullet->bulletinfo.m_flLatency );
	if(pBullet->bulletinfo.m_flLatency !=0.0f)
		pBullet->SimulateBullet(); //Pre-simulation

	if(g_Bullets.Count()==1)
	{
		SetNextThink(gpGlobals->curtime + 0.01f);
	}
#endif
	return index;
}


//==================================================
// Purpose:	Remove the bullet at index from the linked list
// Output:	Next index
//==================================================
void CBulletManager::RemoveBullet(int index)
{
	g_Bullets.Next(index);
#ifdef CLIENT_DLL
	DevMsg("Client ");
#endif
	DevMsg( "Bullet Removed (%i)\n", index );
	g_Bullets.Remove(index);
	if(g_Bullets.Count()==0)
	{
#ifdef CLIENT_DLL
		SetNextClientThink( TICK_NEVER_THINK );
#else
		SetNextThink( TICK_NEVER_THINK );
#endif
	}
}

game_shared\bullet_manager.h

Create and add this file to server and client projects

// (More) Reallistic simulated bullets
//
// This code is originally based from article on Valve Developer Community
// The original code you can find by this link:
// http://developer.valvesoftware.com/wiki/Simulated_Bullets

#include "ammodef.h"
#include "takedamageinfo.h"

#define BUTTER_MODE -1
#define ONE_HIT_MODE -2

#ifdef CLIENT_DLL//-----------------------
class C_BulletManager;
extern C_BulletManager *g_pBulletManager;
#define CBulletManager C_BulletManager
#define CSimulatedBullet C_SimulatedBullet
#else//-----------------------------------
class CBulletManager;
extern CBulletManager *g_pBulletManager;
#endif//----------------------------------

inline CBulletManager *BulletManager()
{
	return g_pBulletManager;
}

extern ConVar g_debug_bullets;
class CSimulatedBullet
{
public:
	CSimulatedBullet();
	CSimulatedBullet( FireBulletsInfo_t info,Vector newdir,CBaseEntity *pInfictor, CBaseEntity *pAdditionalIgnoreEnt,
						bool bTraceHull
#ifndef CLIENT_DLL
						, CBaseEntity *pCaller
#endif
						);
	~CSimulatedBullet();

	inline float GetBulletSpeedRatio(void) //The percent of bullet speed 
	{
		return m_flBulletSpeed/m_flInitialBulletSpeed;
	}

	inline bool IsInWorld(void) const
	{
		if (m_vecOrigin.x >= MAX_COORD_INTEGER) return false;
		if (m_vecOrigin.y >= MAX_COORD_INTEGER) return false;
		if (m_vecOrigin.z >= MAX_COORD_INTEGER) return false;
		if (m_vecOrigin.x <= MIN_COORD_INTEGER) return false;
		if (m_vecOrigin.y <= MIN_COORD_INTEGER) return false;
		if (m_vecOrigin.z <= MIN_COORD_INTEGER) return false;
		return true;
	}


	bool StartSolid(trace_t &ptr); //Exits solid
	bool AllSolid(trace_t &ptr); //continues in solid
	bool EndSolid(trace_t &ptr); //Enters solid
	bool WaterHit(const Vector &vecStart,const Vector &vecEnd); //Tests water collision

	bool SimulateBullet(void); //Main function of bullet simulation

	void EntityImpact(trace_t &ptr);	//Impact test
	inline int GetDamageType(void) const
	{
		return m_iDamageType;
	}

	inline int GetAmmoTypeIndex(void) const
	{
		return bulletinfo.m_iAmmoType;
	}
private:
	bool m_bTraceHull;	//Trace hull?
	bool m_bWasInWater;

	CTraceFilterSimpleList *m_pIgnoreList; //already hit
#ifndef CLIENT_DLL
	CUtlVector<CBaseEntity *> m_CompensationConsiderations; //Couldn't resist
#endif

	EHANDLE m_hCaller;
	EHANDLE	m_hLastHit;		//Last hit (I think it doesn't work)


	float m_flBulletSpeed;  //The changeable bullet speed
	float m_flEntryDensity; //Sets when doing penetration test
	float m_flInitialBulletSpeed;
	float m_flRayLength;

	float DesiredDistance; //Sets when doing penetration test

	bool m_bPenetrated;

	int m_iDamageType;

	Vector m_vecDirShooting;   //The bullet direction with applied spread
	Vector m_vecOrigin;		   //Bullet origin

	Vector m_vecEntryPosition; //Sets when doing penetration test

	Vector m_vecTraceRay;

	Vector AbsEntry;
	Vector AbsExit;

	CBaseEntity *p_eInfictor;
public:
	FireBulletsInfo_t bulletinfo;
private:
	bool bStuckInWall; // Indicates bullet 
};

extern CUtlLinkedList<CSimulatedBullet*> g_Bullets; //Bullet list


class CBulletManager : public CBaseEntity
{
	DECLARE_CLASS( CBulletManager, CBaseEntity );
public:
	~CBulletManager()
	{
		g_Bullets.PurgeAndDeleteElements();
	}
	int AddBullet(CSimulatedBullet *pBullet);
#ifdef CLIENT_DLL
	void ClientThink(void);
#else
	void Think(void);
	void SendTraceAttackToTriggers( const CTakeDamageInfo &info, const Vector& start, const Vector& end, const Vector& dir );
#endif

	void RemoveBullet(int index);	//Removes bullet.
	void UpdateBulletStopSpeed(void);	//Updates bullet speed

	int BulletStopSpeed(void) //returns speed that the bullet must be removed
	{
		return m_iBulletStopSpeed;
	}

private:
	int m_iBulletStopSpeed;
};

game_shared\baseentity_shared.cpp

Replace FireBullets() function with following code:

void CBaseEntity::FireBullets( const FireBulletsInfo_t &info )
{
	// Make sure given a valid bullet type
	if (info.m_iAmmoType == -1)
	{
		DevMsg("ERROR: Undefined ammo type!\n");
		return;
	}
	static int	tracerCount;
	//trace_t		tr;
	CAmmoDef*	pAmmoDef	= GetAmmoDef();
	int			nDamageType	= pAmmoDef->DamageType(info.m_iAmmoType);
	int			nAmmoFlags	= pAmmoDef->Flags(info.m_iAmmoType);


	int iPlayerDamage = info.m_iPlayerDamage;
	if ( iPlayerDamage == 0 )
	{
		if ( nAmmoFlags & AMMO_INTERPRET_PLRDAMAGE_AS_DAMAGE_TO_PLAYER )
		{
			iPlayerDamage = pAmmoDef->PlrDamage( info.m_iAmmoType );
		}
	}

	// the default attacker is ourselves
	CBaseEntity *pAttacker = info.m_pAttacker ? info.m_pAttacker : this;

	// Make sure we don't have a dangling damage target from a recursive call
	if ( g_MultiDamage.GetTarget() != NULL )
	{
		ApplyMultiDamage();
	}
	  
	ClearMultiDamage();
	g_MultiDamage.SetDamageType( nDamageType | DMG_NEVERGIB );


	// Prediction is only usable on players
	int iSeed = 0;
	if ( IsPlayer() )
	{
		iSeed = CBaseEntity::GetPredictionRandomSeed() & 255;
	}

	//-----------------------------------------------------
	// Set up our shot manipulator.
	//-----------------------------------------------------
	CShotManipulator Manipulator( info.m_vecDirShooting );

	for (int iShot = 0; iShot < info.m_iShots; iShot++)
	{
		Vector vecDir;

		// Prediction is only usable on players
		if ( IsPlayer() )
		{
			RandomSeed( iSeed );	// init random system with this seed
		}

		// If we're firing multiple shots, and the first shot has to be bang on target, ignore spread
		if ( iShot == 0 && info.m_iShots > 1 && (info.m_nFlags & FIRE_BULLETS_FIRST_SHOT_ACCURATE) )
		{
			vecDir = Manipulator.GetShotDirection();
		}
		else
		{

			// Don't run the biasing code for the player at the moment.
			vecDir = Manipulator.ApplySpread( info.m_vecSpread );
		}

		Vector vecSrc(info.m_vecSrc);
		bool bTraceHull = ( IsPlayer() && info.m_iShots > 1 && iShot % 2 );

		CSimulatedBullet *pBullet = new CSimulatedBullet(info,vecDir,pAttacker,info.m_pAdditionalIgnoreEnt, bTraceHull
#ifndef CLIENT_DLL
			,this
#endif
			);

		BulletManager()->AddBullet(pBullet);

		iSeed++;
	}

#if defined( HL2MP ) && defined( GAME_DLL )
	if ( bDoServerEffects == false )
	{
		// For now it doesn't work
		//TE_HL2MPFireBullets( entindex(), tr.startpos, info.m_vecDirShooting, info.m_iAmmoType, iEffectSeed, info.m_iShots, info.m_vecSpread.x, bDoTracers, bDoImpacts );
	}
#endif
	ApplyMultiDamage();
}


If you get errors about undeclared identifiers in baseentity_shared.cpp:

Add
#include "bullet_manager.h"
After
#include "coordsize.h"

game_shared\shareddefs.h

Change the FireBulletsInfo_t structure to the following:

struct FireBulletsInfo_t
{
	FireBulletsInfo_t()
	{
		m_iShots = 1;
		m_vecSpread.Init( 0, 0, 0 );
		m_flDistance = 8192;
		m_iTracerFreq = 4;
		m_iDamage = 0;
		m_iPlayerDamage = 0;
		m_pAttacker = NULL;
		m_nFlags = 0;
		m_pAdditionalIgnoreEnt = NULL;
		m_flDamageForceScale = 1.0f;

#ifdef _DEBUG
		m_iAmmoType = -1;
		m_vecSrc.Init( VEC_T_NAN, VEC_T_NAN, VEC_T_NAN );
		m_vecDirShooting.Init( VEC_T_NAN, VEC_T_NAN, VEC_T_NAN );
#endif
		m_flLatency = 0.0f;
	}

	FireBulletsInfo_t( int nShots, const Vector &vecSrc, const Vector &vecDir, const Vector &vecSpread, float flDistance, int nAmmoType )
	{
		m_iShots = nShots;
		m_vecSrc = vecSrc;
		m_vecDirShooting = vecDir;
		m_vecSpread = vecSpread;
		m_flDistance = flDistance;
		m_iAmmoType = nAmmoType;
		m_iTracerFreq = 4;
		m_iDamage = 0;
		m_iPlayerDamage = 0;
		m_pAttacker = NULL;
		m_nFlags = 0;
		m_pAdditionalIgnoreEnt = NULL;
		m_flDamageForceScale = 1.0f;
		m_flLatency = 0.0f;
	}

	int m_iShots;
	Vector m_vecSrc;
	Vector m_vecDirShooting;
	Vector m_vecSpread;
	float m_flDistance;
	int m_iAmmoType;
	int m_iTracerFreq;
	int m_iDamage;
	int m_iPlayerDamage;	// Damage to be used instead of m_iDamage if we hit a player
	int m_nFlags;			// See FireBulletsFlags_t
	float m_flDamageForceScale;
	float m_flLatency;
	CBaseEntity *m_pAttacker;
	CBaseEntity *m_pAdditionalIgnoreEnt;
};

dlls/player_lagcompensation.cpp

Add the following function to this file:

int GetTargetTick(CBasePlayer *player,CUserCmd *cmd)
{
	static CBasePlayer *lastPlayer;
	static int lastTarget;
	if(player==NULL)
	{
		lastPlayer = NULL;
		lastTarget = 0;
	}
	if(player==lastPlayer)
		return lastTarget;
	// Get true latency
	lastTarget = 0;

	// correct is the amout of time we have to correct game time
	float correct = 0.0f;

	INetChannelInfo *nci = engine->GetPlayerNetInfo( player->entindex() ); 

	if ( nci )
	{
		// add network latency
		correct+= nci->GetLatency( FLOW_OUTGOING );
	}

	// calc number of view interpolation ticks - 1
	int lerpTicks = TIME_TO_TICKS( player->m_fLerpTime );

	// add view interpolation latency see C_BaseEntity::GetInterpolationAmount()
	correct += TICKS_TO_TIME( lerpTicks );
	
	// check bouns [0,sv_maxunlag]
	correct = clamp( correct, 0.0f, sv_maxunlag.GetFloat() );

	// correct tick send by player 
	int targettick = cmd->tick_count - lerpTicks;

	// calc difference between tick send by player and our latency based tick
	float deltaTime =  correct - TICKS_TO_TIME(gpGlobals->tickcount - targettick);

	if ( fabs( deltaTime ) > 0.2f )
	{
		// difference between cmd time and latency is too big > 200ms, use time correction based on latency
		// DevMsg("StartLagCompensation: delta too big (%.3f)\n", deltaTime );
		targettick = gpGlobals->tickcount - TIME_TO_TICKS( correct );
	}
	lastPlayer = player;
	lastTarget = targettick;
	timeRetrieved = gpGlobals->tickcount;
	return targettick;
}

In CLagCompensationManager::StartLagCompensation

Replace

	// Get true latency

	// correct is the amout of time we have to correct game time
	float correct = 0.0f;

	INetChannelInfo *nci = engine->GetPlayerNetInfo( player->entindex() ); 

	if ( nci )
	{
		// add network latency
		correct+= nci->GetLatency( FLOW_OUTGOING );
	}

	// calc number of view interpolation ticks - 1
	int lerpTicks = TIME_TO_TICKS( player->m_fLerpTime );

	// add view interpolation latency see C_BaseEntity::GetInterpolationAmount()
	correct += TICKS_TO_TIME( lerpTicks );
	
	// check bouns [0,sv_maxunlag]
	correct = clamp( correct, 0.0f, sv_maxunlag.GetFloat() );

	// correct tick send by player 
	int targettick = cmd->tick_count - lerpTicks;

	// calc difference between tick send by player and our latency based tick
	float deltaTime =  correct - TICKS_TO_TIME(gpGlobals->tickcount - targettick);

	if ( fabs( deltaTime ) > 0.2f )
	{
		// difference between cmd time and latency is too big > 200ms, use time correction based on latency
		// DevMsg("StartLagCompensation: delta too big (%.3f)\n", deltaTime );
		targettick = gpGlobals->tickcount - TIME_TO_TICKS( correct );
	}

With

	int targettick = GetTargetTick(player, cmd);

Add GetTargetTick();
After BUDGETFLAG_SERVER );

dlls/ilagcompensationmanager.h

Add
int GetTargetTick(CBasePlayer *player=NULL,CUserCmd *cmd=NULL);
Before
extern ILagCompensationManager *lagcompensation;

dlls/hl2mp_dll/hl2mp_player.cpp

Add
#include "ilagcompensationmanager.h"
After
#include "SoundEmitterSystem/isoundemittersystembase.h"

In CHL2MP_Player::FireBullets

Add
	if(gpGlobals->maxClients!=1)
		modinfo.m_flLatency = TICKS_TO_TIME(GetTargetTick(this,m_pCurrentCommand));
Before
	BaseClass::FireBullets( modinfo );

cl_dll\clientmode_shared.cpp

Add
#include "bullet_manager.h"
After
#include "c_team.h"

Add
	g_pBulletManager = (C_BulletManager *)CreateEntityByName("bullet_manager");
	ClientEntityList().AddNonNetworkableEntity(g_pBulletManager);
After
	enginesound->SetPlayerDSP( filter, 0, true );

cl_dll\c_te_hl2mp_shotgun_shot.cpp

It's undone and not rewritten. Please feel free to do helpful changes for it.