Realistic Simulated Bullets

From Valve Developer Community
Jump to navigation Jump to search


Preface

This is a more complete version of Simulated Bullets. 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
- Speed changes now affects on damage and impact force (but not for NPC or Player)
- Removed unused stuff

Undone parts:

- Multiplayer
- Invalid tracer position
- Tracer types
Note.pngNote:This article based on Simulated Bullets.
Note.pngNote:This code is incomplete. Feel free to make helpful changes.

Procedure

Do the similar procedure from original article (Link above). After do following:

game_shared\bullet_manager.cpp

Replace all code with following:

// (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 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;			// Store Fire bullets information here

	p_eInfictor = pInfictor;	// Store 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 );

	// 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_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;
	Vector vecTraceStart(m_vecOrigin);

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

	Vector vecTraceRay;

	if(m_flRayLength==m_flBulletSpeed)
	{
		vecTraceRay = m_vecTraceRay;
	}
	else
	{
		vecTraceRay = m_vecTraceRay = m_vecDirShooting * m_flBulletSpeed;
		m_flRayLength = m_flBulletSpeed;
	}

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

	m_vecDirShooting.z -= 0.1 / m_flBulletSpeed;

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

	if(!bStuckInWall)
	{
		m_vecOrigin += vecTraceRay;
	}

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

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

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

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

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

		tracerData.m_fFlags = TRACER_TYPE_WATERBULLET;

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

	UTIL_Tracer( vecTraceStart, m_vecOrigin, p_eInfictor->entindex(), TRACER_DONT_USE_ATTACHMENT, 0.0f, true );


	bool bulletSpeedCheck;
	do
	{
		bulletSpeedCheck = false;
		if(m_bTraceHull)
			UTIL_TraceHull( vecTraceStart, m_vecOrigin, Vector(-3, -3, -3), Vector(3, 3, 3), MASK_SHOT, m_pIgnoreList, &trace );
		else
			UTIL_TraceLine( vecTraceStart, m_vecOrigin, MASK_SHOT, m_pIgnoreList, &trace );

#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

		EntityImpact(trace);

		if(!(trace.surface.flags&SURF_SKY))
		{
			if(trace.allsolid)//in solid
			{
				if(!AllSolid(trace))
					return false;
				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;
				bulletSpeedCheck = true;
			}
			else
			{
				//don't do a bullet speed check for not touching anything
				break;
			}
		}
		else
		{
			trace.fraction = 0.0f; //Done it!
		}

		if(bulletSpeedCheck)
		{
			if(m_flBulletSpeed<=BulletManager()->BulletStopSpeed())
			{
				return false;
			}
		}
		if(bStuckInWall)
		{
			return false;
		}
		else
		{
			vecTraceStart = trace.endpos + m_vecDirShooting*1.1;
		}
		if(trace.fraction==0.0f)
			return false;
	}while(trace.fraction!=1.0f);
	return true;
}


//==================================================
// Purpose:	Simulates when a solid is exited
//==================================================
bool CSimulatedBullet::StartSolid(trace_t &ptr)
{
	//Vector vecExitPosition = ptr.fractionleftsolid * vecTraceRay + ptr.startpos;

	float flPenetrationDistance = VectorLength(AbsEntry - AbsExit);

	switch(BulletManager()->BulletStopSpeed())
	{
		case BUTTER_MODE:
		{
			//Do nothing to bullet speed
			return true;
		}
		case ONE_HIT_MODE:
		{
			return false;
		}
		default:
		{
#ifndef	CLIENT_DLL
			if(g_debug_bullets.GetBool())
			{
				DevMsg("Bullet old speed is: %f\nThe new speed is %f\nThe penetration distance is: %f\n",m_flBulletSpeed,m_flBulletSpeed - flPenetrationDistance * m_flEntryDensity / sv_bullet_speed_modifier.GetFloat(),flPenetrationDistance);
			}
#endif
			m_flBulletSpeed -= flPenetrationDistance * m_flEntryDensity / sv_bullet_speed_modifier.GetFloat();
#ifndef	CLIENT_DLL
				if(g_debug_bullets.GetBool())
				{
					DevMsg("Bullet speed decreased by %f\n",flPenetrationDistance * m_flEntryDensity / sv_bullet_speed_modifier.GetFloat());
				}
#endif
			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

	if (fldot > -MAX_RICO_DOT_ANGLE && GetBulletSpeedRatio() > MIN_RICO_SPEED_PERC) // We can't do richochet when bullet has lowest speed
	{
		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;
		}

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

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

		m_vecDirShooting = reflect;

		m_vecOrigin = (ptr.endpos + m_vecDirShooting*1.1) + m_vecDirShooting * m_flBulletSpeed;
	}
	else
	{
		if (flPenetrationDistance > DesiredDistance || ptr.IsDispSurface())
		{
			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 * m_flBulletSpeed;
		}
	}

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

	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());
	int	nMaxSplashSize = GetAmmoDef()->MaxSplashSize(GetAmmoTypeIndex());

	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->IsNPC())
		{
			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

Other replacement:

// (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 )
	{
		TE_HL2MPFireBullets( entindex(), tr.startpos, info.m_vecDirShooting, info.m_iAmmoType, iEffectSeed, info.m_iShots, info.m_vecSpread.x, bDoTracers, bDoImpacts );
	}
#endif
	ApplyMultiDamage();
}

cl_dll\c_te_hl2mp_shotgun_shot.cpp

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


PS: Sorry for my English.