Realistic Simulated Bullets: Difference between revisions
| Djjonastybe (talk | contribs) No edit summary | |||
| (One intermediate revision by one other user not shown) | |||
| Line 9: | Line 9: | ||
| : Added entity hit (EntityImpact() function is now complete) | : Added entity hit (EntityImpact() function is now complete) | ||
| : Added ricochet | : Added ricochet | ||
| : Bullet can  | : Bullet can lose speed | ||
| : Speed changes now affects  | : Speed changes now affects damage and impact force (but not for NPC or Player) | ||
| : Removed unused stuff | : Removed unused stuff | ||
| ; Still to do | ; Still to do | ||
| : Multiplayer | :: Multiplayer | ||
| : Shotguns | :: Shotguns | ||
| : Invalid tracer position | :: Invalid tracer position | ||
| : Tracer types | :: Tracer types | ||
| : Bullet mass | :: Bullet mass | ||
| : Wind affection | :: 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|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.}} | ||
| Line 1,055: | Line 1,055: | ||
| ===game_shared\baseentity_shared.cpp=== | ===game_shared\baseentity_shared.cpp=== | ||
| Replace FireBullets() function with following code: | Replace FireBullets() function with following code: | ||
| Line 1,154: | Line 1,155: | ||
| </source> | </source> | ||
| If you get errors about undeclared identifiers in baseentity_shared.cpp: | |||
| Add  <source lang=cpp>#include "bullet_manager.h"</source> | |||
| After  <source lang=cpp>#include "coordsize.h"</source> | |||
| ===game_shared\shareddefs.h=== | ===game_shared\shareddefs.h=== | ||
Latest revision as of 23:35, 4 October 2012
This article based on Simulated Bullets.
 Note:Compile errors on SP MOD - OB - reported by djjonastybe on 07/01/2010
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: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
Note:Not done for HL2MPProcedure
| 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 );
 | 
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 );
| Add | float bulletSpeed,
 | 
| Before both | float physicsForceImpulse,
 | 
| Add | m_AmmoType[m_nAmmoIndex].bulletSpeed = bulletSpeed;
 | 
| Before both | m_AmmoType[m_nAmmoIndex].physicsForceImpulse = physicsForceImpulse;
 | 
| 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.
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
	}
}
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;
};
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"
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 );
 | 
| 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.