Simulated Bullets: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
Line 130: Line 130:
#include "bullet_manager.h"
#include "bullet_manager.h"
#include "shot_manipulator.h"
#include "shot_manipulator.h"
#include "tier0/vprof.h"
#pragma warning(disable:4800)
#pragma warning(disable:4800)


Line 141: Line 142:
#include "soundent.h"
#include "soundent.h"
#include "player_pickup.h"
#include "player_pickup.h"
#include "ilagcompensationmanager.h"
ConVar g_debug_bullets( "g_debug_bullets", "0", FCVAR_CHEAT );
ConVar g_debug_bullets( "g_debug_bullets", "0", FCVAR_CHEAT );
CBulletManager *g_pBulletManager;
CBulletManager *g_pBulletManager;
Line 158: Line 160:
static void BulletStopSpeedCallback(ConVar *var, const char *pOldString)
static void BulletStopSpeedCallback(ConVar *var, const char *pOldString)
{
{
float val = var->GetFloat();
int val = var->GetInt();
if(val==0.0f||(val<0.0f&&(val!=BUTTER_MODE||val!=ONE_HIT_MODE)))
if(val<ONE_HIT_MODE)
var->Revert();
var->Revert();
else if(BulletManager())
BulletManager()->UpdateBulletStopSpeed();
}
}
ConVar sv_bullet_stop_speed( "sv_bullet_stop_speed", "40.000000", (FCVAR_ARCHIVE | FCVAR_REPLICATED),
ConVar sv_bullet_stop_speed( "sv_bullet_stop_speed", "40", (FCVAR_ARCHIVE | FCVAR_REPLICATED),
"Speed at which to remove the bullet from the bullet queue\n-1 is butter mode\n-2 is 1 hit mode",
"Integral speed at which to remove the bullet from the bullet queue\n-1 is butter mode\n-2 is 1 hit mode",
BulletStopSpeedCallback );
BulletStopSpeedCallback );


Line 177: Line 181:
m_vecOrigin.Init();
m_vecOrigin.Init();
m_vecDirShooting.Init();
m_vecDirShooting.Init();
m_flInitialBulletSpeed = m_flBulletSpeed = BULLET_SPEED;
m_flInitialBulletSpeed = m_flBulletSpeed = 0;
#ifndef CLIENT_DLL
#ifndef CLIENT_DLL
m_flLagCompensation =
m_flLagCompensation =
Line 200: Line 204:
m_vecOrigin = vecOrigin;
m_vecOrigin = vecOrigin;
m_vecDirShooting = vecDirShooting;
m_vecDirShooting = vecDirShooting;
m_flInitialBulletSpeed = m_flBulletSpeed = GetAmmoDef()->GetAmmoOfIndex(ammoType)->flFeetPerSecond * 0.12;
m_flInitialBulletSpeed = m_flBulletSpeed = GetAmmoDef()->GetAmmoOfIndex(ammoType)->bulletSpeed;
//m_flInitialBulletSpeed = m_flBulletSpeed = BULLET_SPEED;
//m_flInitialBulletSpeed = m_flBulletSpeed = BULLET_SPEED;
m_flEntryDensity = 0.0f;
m_flEntryDensity = 0.0f;
Line 220: Line 224:
delete m_pTwoEnts;
delete m_pTwoEnts;
}
}
#define REMOVE_BULLET_AND_END(x) BulletManager()->RemoveBullet(x);return
#ifdef CLIENT_DLL
#ifdef CLIENT_DLL
void CSimulatedBullet::SimulateBullet(int index)
bool FX_AffectRagdolls(Vector vecOrigin, Vector vecStart, int iDamageType);
bool C_SimulatedBullet::SimulateBullet(void)
{
VPROF( "C_SimulatedBullet::SimulateBullet" );
#else
#else
void CSimulatedBullet::SimulateBullet(int index, float flTime/*=1.00f (1/100 sec)*/)
bool CSimulatedBullet::SimulateBullet(float flLagCompensation/*=-1 (seconds)*/)
{
VPROF( "CSimulatedBullet::SimulateBullet" );
#endif
#endif
{
trace_t trace;//, trace_back;
Vector vecEntryPosition;
Vector vecExitPosition;
float flPenetrationDistance = 0.0f;
trace_t trace, trace_back;
Vector vecOldOrigin(m_vecOrigin);
Vector vecTraceStart(m_vecOrigin);
Vector vecTraceStart(m_vecOrigin);
#ifdef CLIENT_DLL
Vector vecNewRay = m_vecDirShooting * m_flBulletSpeed;
Vector vecNewRay = m_vecDirShooting * m_flBulletSpeed;
#else
#ifdef GAME_DLL
Vector vecNewRay = m_vecDirShooting * m_flBulletSpeed * flTime;
if(flLagCompensation!=-1)
{
//TODO: call backtracking first
vecNewRay *= flLagCompensation * 100;
}
#endif
#endif


DevMsg("Bullet %i Speed %f\n",index,m_flBulletSpeed);
m_vecOrigin += vecNewRay;
m_vecOrigin += vecNewRay;//in/100th of a sec * 100th of a sec
bool bInWater = UTIL_PointContents(m_vecOrigin)&MASK_SPLITAREAPORTAL;
bool bInWater = UTIL_PointContents(m_vecOrigin)&MASK_SPLITAREAPORTAL;
if(!IsInWorld())
if(!IsInWorld())
{
{
REMOVE_BULLET_AND_END(index);
return false;
}
}
#ifdef CLIENT_DLL
#ifdef CLIENT_DLL
FX_PlayerTracer( vecOldOrigin, m_vecOrigin );
FX_PlayerTracer( vecTraceStart, m_vecOrigin );
#endif
#endif
if(m_bWasInWater!=bInWater)
if(m_bWasInWater!=bInWater)
Line 268: Line 272:
if(g_cl_debug_bullets.GetBool())
if(g_cl_debug_bullets.GetBool())
{
{
debugoverlay->AddLineOverlay( vecOldOrigin, m_vecOrigin, 255, 0, 0, true, 10.0f );
debugoverlay->AddLineOverlay( vecTraceStart, m_vecOrigin, 255, 0, 0, true, 10.0f );
}
}
#else
#else
if(g_debug_bullets.GetBool())
if(g_debug_bullets.GetBool())
{
{
NDebugOverlay::Line( vecOldOrigin, m_vecOrigin, 255, 255, 255, true, 10.0f );
NDebugOverlay::Line( vecTraceStart, m_vecOrigin, 255, 255, 255, true, 10.0f );
}
}
#endif
#endif
m_bWasInWater = bInWater;
m_bWasInWater = bInWater;
float flBulletStopSpeed = sv_bullet_stop_speed.GetFloat();
bool bulletSpeedCheck;
int iBulletStopSpeedCase = (int)flBulletStopSpeed;
do
do
{
{
bulletSpeedCheck = false;
UTIL_TraceLine( vecTraceStart, m_vecOrigin, MASK_SHOT, m_pIgnoreList, &trace );
UTIL_TraceLine( vecTraceStart, m_vecOrigin, MASK_SHOT, m_pIgnoreList, &trace );
if(!(trace.surface.flags&SURF_SKY))
if(!(trace.surface.flags&SURF_SKY))
Line 286: Line 290:
if(trace.allsolid)//in solid
if(trace.allsolid)//in solid
{
{
trace.endpos = m_vecOrigin;
if(!AllSolid(trace))
trace.fraction = 1.0f;
return false;
switch(iBulletStopSpeedCase)
bulletSpeedCheck = true;
{
case BUTTER_MODE:
{
//Do nothing to bullet speed
break;
}
case ONE_HIT_MODE:
{
REMOVE_BULLET_AND_END(index);
}
default:
{
m_flBulletSpeed -= m_flBulletSpeed * m_flEntryDensity / sv_bullet_speed_modifier.GetFloat();
break;
}
}
break;
}
}
else if(trace.startsolid)//exit solid
else if(trace.startsolid)//exit solid
{
{
#ifdef CLIENT_DLL
if(!StartSolid(trace, vecNewRay))
//TODO: penetration surface impact stuff
return false;
#endif //CLIENT_DLL
bulletSpeedCheck = true;
vecExitPosition = trace.fractionleftsolid * vecNewRay + vecTraceStart;
flPenetrationDistance = vecEntryPosition.DistTo(vecExitPosition);
switch(iBulletStopSpeedCase)
{
case BUTTER_MODE:
{
//Do nothing to bullet speed
break;
}
case ONE_HIT_MODE:
{
REMOVE_BULLET_AND_END(index);
}
default:
{
m_flBulletSpeed -= flPenetrationDistance * m_flEntryDensity / sv_bullet_speed_modifier.GetFloat();
break;
}
}
}
}
else if(trace.fraction!=1.0f)//hit solid
else if(trace.fraction!=1.0f)//hit solid
{
{
vecEntryPosition = trace.endpos;
if(!EndSolid(trace))
return false;
bulletSpeedCheck = true;
}
else
{
//don't do a bullet speed check for not touching anything
break;
}
}
if(bulletSpeedCheck)
{
if(m_flBulletSpeed<=BulletManager()->BulletStopSpeed())
{
return false;
}
}
vecTraceStart = trace.endpos + m_vecDirShooting;
}while(trace.fraction!=1.0f);
return true;
}
bool CSimulatedBullet::StartSolid(trace_t &ptr, Vector &vecNewRay)
{
#ifdef CLIENT_DLL
//TODO: penetration surface impact stuff
#endif //CLIENT_DLL
Vector vecExitPosition = ptr.fractionleftsolid * vecNewRay + ptr.startpos;
float flPenetrationDistance = m_vecEntryPosition.DistTo(vecExitPosition);
switch(BulletManager()->BulletStopSpeed())
{
case BUTTER_MODE:
{
//Do nothing to bullet speed
break;
}
case ONE_HIT_MODE:
{
return false;
}
default:
{
m_flBulletSpeed -= flPenetrationDistance * m_flEntryDensity / sv_bullet_speed_modifier.GetFloat();
break;
}
}
return true;
}
bool CSimulatedBullet::AllSolid(trace_t &ptr)
{
switch(BulletManager()->BulletStopSpeed())
{
case BUTTER_MODE:
{
//Do nothing to bullet speed
break;
}
case ONE_HIT_MODE:
{
return false;
}
default:
{
m_flBulletSpeed -= m_flBulletSpeed * m_flEntryDensity / sv_bullet_speed_modifier.GetFloat();
break;
}
}
return true;
}
bool CSimulatedBullet::EndSolid(trace_t &ptr)
{
m_vecEntryPosition = ptr.endpos;
#ifndef CLIENT_DLL
#ifndef CLIENT_DLL
int soundEntChannel = ( m_nFlags&FIRE_BULLETS_TEMPORARY_DANGER_SOUND ) ? SOUNDENT_CHANNEL_BULLET_IMPACT : SOUNDENT_CHANNEL_UNSPECIFIED;
int soundEntChannel = ( m_nFlags&FIRE_BULLETS_TEMPORARY_DANGER_SOUND ) ? SOUNDENT_CHANNEL_BULLET_IMPACT : SOUNDENT_CHANNEL_UNSPECIFIED;


CSoundEnt::InsertSound( SOUND_BULLET_IMPACT, vecEntryPosition, 200, 0.5, m_hCaller, soundEntChannel );
CSoundEnt::InsertSound( SOUND_BULLET_IMPACT, m_vecEntryPosition, 200, 0.5, m_hCaller, soundEntChannel );
#endif
#endif
if(FStrEq(trace.surface.name,"tools/toolsblockbullets"))
if(FStrEq(ptr.surface.name,"tools/toolsblockbullets"))
{
{
REMOVE_BULLET_AND_END(index);
return false;
}
}
#ifdef CLIENT_DLL
#ifdef CLIENT_DLL
CEffectData data;
FX_AffectRagdolls(ptr.startpos, ptr.endpos, m_nDamageType);
data.m_vStart = trace.startpos;
//TODO: surface impact stuff
data.m_vOrigin = trace.endpos;
data.m_nDamageType = m_nDamageType;
//Clientside ragdolls' locations cannot be determined by the server
DispatchEffect( "RagdollImpact", data );
//TODO: surface impact stuff
#endif //CLIENT_DLL
#endif //CLIENT_DLL
m_flEntryDensity = physprops->GetSurfaceData(trace.surface.surfaceProps)->physics.density;
m_flEntryDensity = physprops->GetSurfaceData(ptr.surface.surfaceProps)->physics.density;
if(trace.DidHitNonWorldEntity())
if(ptr.DidHitNonWorldEntity())
{
{
if(m_pIgnoreList->ShouldHitEntity(trace.m_pEnt,MASK_SHOT))
if(m_pIgnoreList->ShouldHitEntity(ptr.m_pEnt,MASK_SHOT))
{
{
m_pIgnoreList->AddEntityToIgnore(trace.m_pEnt);
m_pIgnoreList->AddEntityToIgnore(ptr.m_pEnt);
DevMsg("Hit: %s\n",ptr.m_pEnt->GetClassname());
#ifndef CLIENT_DLL
#ifndef CLIENT_DLL
EntityImpact(trace.m_pEnt);
EntityImpact(ptr);
#endif
#endif
}
}
if(iBulletStopSpeedCase==ONE_HIT_MODE)
{
REMOVE_BULLET_AND_END(index);
}
}
}
}
vecTraceStart = trace.endpos + m_vecDirShooting;
}
}while(trace.endpos!=m_vecOrigin&&m_flBulletSpeed>flBulletStopSpeed);
if(BulletManager()->BulletStopSpeed()==ONE_HIT_MODE)
 
 
if(m_flBulletSpeed<=flBulletStopSpeed)
{
{
REMOVE_BULLET_AND_END(index);
return false;
}
}
return true;
}
}
#ifndef CLIENT_DLL
#ifndef CLIENT_DLL
void CSimulatedBullet::EntityImpact(CBaseEntity *pHit)
void CSimulatedBullet::EntityImpact(trace_t &ptr)
{
{
//TODO: entity impact stuff
//TODO: entity impact stuff
/*CTakeDamageInfo dmgInfo( this, pAttacker, flActualDamage, nActualDamageType );
CalculateBulletDamageForce( &dmgInfo, info.m_iAmmoType, vecDir, tr.endpos );
dmgInfo.ScaleDamageForce( info.m_flDamageForceScale );
dmgInfo.SetAmmoType( info.m_iAmmoType );
tr.m_pEnt->DispatchTraceAttack( dmgInfo, vecDir, &tr );*/
if ( GetAmmoDef()->Flags(m_iAmmoType) & AMMO_FORCE_DROP_IF_CARRIED )
if ( GetAmmoDef()->Flags(m_iAmmoType) & AMMO_FORCE_DROP_IF_CARRIED )
{
{
// Make sure if the player is holding this, he drops it
// Make sure if the player is holding this, he drops it
Pickup_ForcePlayerToDropThisObject( pHit );
Pickup_ForcePlayerToDropThisObject( ptr.m_pEnt );
}
}
}
}
Line 394: Line 424:
#endif
#endif
{
{
for(int x=0;x<m_pBullets.Count();x++)
int x = m_Bullets.Head();
while(m_Bullets.IsValidIndex(x))
{
{
m_pBullets[x]->SimulateBullet(x);
if(m_Bullets[x]->SimulateBullet())
x = m_Bullets.Next(x);
else
x = RemoveBullet(x);
}
}
#ifdef CLIENT_DLL
#ifdef CLIENT_DLL
Line 404: Line 438:
#endif
#endif
}
}
void CBulletManager::AddBullet(CSimulatedBullet *pBullet)
#ifdef CLIENT_DLL
void BulletShotCallback( const CEffectData &data )
{
Vector vecDir(data.m_vNormal), vecOrigin(data.m_vOrigin);
CSimulatedBullet *pBullet = new CSimulatedBullet(cl_entitylist->GetBaseEntity(data.m_nEntIndex),
cl_entitylist->GetBaseEntity(data.m_nAttachmentIndex), data.m_nMaterial,
data.m_fFlags, vecDir, vecOrigin, data.m_nDamageType);
BulletManager()->AddBullet(pBullet);
}
 
DECLARE_CLIENT_EFFECT( "bulletshot", BulletShotCallback );
#endif
void CBulletManager::UpdateBulletStopSpeed(void)
{
m_iBulletStopSpeed = sv_bullet_stop_speed.GetInt();
}
int CBulletManager::AddBullet(CSimulatedBullet *pBullet, float flLatency)
{
{
if (pBullet->AmmoIndex() == -1)
if (pBullet->AmmoIndex() == -1)
{
{
DevMsg("ERROR: Undefined ammo type!\n");
DevMsg("ERROR: Undefined ammo type!\n");
return;
return -1;
}
}
int index = m_pBullets.AddToTail(pBullet);
int index = m_Bullets.AddToTail(pBullet);
#ifdef CLIENT_DLL
#ifdef CLIENT_DLL
DevMsg( "Client Bullet Created (%i)\n", index);
DevMsg( "Client Bullet Created (%i)\n", index);
#else
#else
DevMsg( "Bullet Created (%i) LagCompensation %f\n", index, pBullet->LagCompensation() );
DevMsg( "Bullet Created (%i) LagCompensation %f\n", index, flLatency );
if(m_pBullets[index]->LagCompensation()!=0.0f)
if(flLatency!=0.0f)
m_pBullets[index]->SimulateBullet(index, pBullet->LagCompensation());
m_Bullets[index]->SimulateBullet(flLatency);
#endif
#endif
return index;
}
}
void CBulletManager::RemoveBullet(int index)
int CBulletManager::RemoveBullet(int index)
{
{
int retVal = m_Bullets.Next(index);
#ifdef CLIENT_DLL
#ifdef CLIENT_DLL
DevMsg("Client ");
DevMsg("Client ");
#endif
#endif
DevMsg( "Bullet Removed (%i)\n", index );
DevMsg( "Bullet Removed (%i)\n", index );
m_pBullets.Remove(index);
m_Bullets.Remove(index);
return retVal;
}</pre>
}</pre>
==Implementation==
==Implementation==
===game_shared===
===game_shared===

Revision as of 19:44, 25 November 2005

Preface

Basically, with simulated bullets, the aspects of physics are going to tried to be captured by simulating them in batch simulation code. So far the code is all server-side and is later expected to be client-side simulated with similar code. Bullet simulation using individual entities is not the way to go because they can fill up the entity list and are just unconventional.


Note.pngNote:This code is a work in progress. Feel free to make helpful changes.

Alteration

The code creates two ConVars which allow the host to change the penetration allowed and the maximum speed at which a bullet is removed.

Body

This code is WIP by ts2do

game_shared

bullet_manager.h

#include "ammodef.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( CBaseEntity *pAttacker, CBaseEntity *pAdditionalIgnoreEnt,
						int ammoType, int nFlags, Vector &vecDirShooting,
						Vector &vecOrigin, int damageType
#ifndef CLIENT_DLL
						, CBaseEntity *pCaller, int iDamage, float flLagCompensation
#endif
						);
	~CSimulatedBullet();
	inline float BulletSpeedRatio(void)
	{
		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, Vector &vecNewRay);
	bool AllSolid(trace_t &ptr);
	bool EndSolid(trace_t &ptr);
#ifdef CLIENT_DLL
	bool SimulateBullet(void);
#else
	void EntityImpact(trace_t &ptr);
	bool SimulateBullet(float flLagCompensation=-1);
#endif
	inline int AmmoIndex(void) const
	{
		return m_iAmmoType;
	}
private:
	bool m_bWasInWater;

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

	EHANDLE m_hCaller;

	FireBulletsFlags_t m_nFlags;

	float m_flBulletSpeed;
	float m_flEntryDensity;
	float m_flInitialBulletSpeed;

	int m_iAmmoType;
#ifndef CLIENT_DLL
	int m_iDamage;
#endif
	int m_nDamageType;

	Vector m_vecDirShooting;
	Vector m_vecOrigin;
	Vector m_vecEntryPosition;
	// Prevent copies 
private: 
	CSimulatedBullet( const CSimulatedBullet& other ); 
	CSimulatedBullet& operator=( const CSimulatedBullet& other ); 
};
class CBulletManager : public CBaseEntity
{
	DECLARE_CLASS( CBulletManager, CBaseEntity );
public:
#ifdef CLIENT_DLL
	void ClientThink(void);
#else
	void Spawn(void);
	void Think(void);
#endif
	int AddBullet(CSimulatedBullet *pBullet, float flLatency);
	int RemoveBullet(int index);
	void UpdateBulletStopSpeed(void);
	int BulletStopSpeed(void)
	{
		return m_iBulletStopSpeed;
	}
private:
	int m_iBulletStopSpeed;
	CUtlLinkedList<CSimulatedBullet*,int> m_Bullets;
};

bullet_manager.cpp

#include "cbase.h"
#include "util_shared.h"
#include "bullet_manager.h"
#include "shot_manipulator.h"
#include "tier0/vprof.h"
#pragma warning(disable:4800)

#ifdef CLIENT_DLL//-------------------------------------------------
#include "c_te_effect_dispatch.h"
#include "engine/ivdebugoverlay.h"
ConVar g_cl_debug_bullets( "g_cl_debug_bullets", "0", FCVAR_CHEAT );
C_BulletManager *g_pBulletManager = (C_BulletManager *)CreateEntityByName("bullet_manager");
extern void FX_PlayerTracer( Vector& start, Vector& end);
#else//-------------------------------------------------------------
#include "soundent.h"
#include "player_pickup.h"
#include "ilagcompensationmanager.h"
ConVar g_debug_bullets( "g_debug_bullets", "0", FCVAR_CHEAT );
CBulletManager *g_pBulletManager;
#endif//------------------------------------------------------------

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
static void BulletSpeedModifierCallback(ConVar *var, const char *pOldString)
{
	if(var->GetFloat()==0.0f)
		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 );
#ifndef CLIENT_DLL
void CBulletManager::Spawn(void)
{
	Think();
}
#endif
CSimulatedBullet::CSimulatedBullet()
{
	m_vecOrigin.Init();
	m_vecDirShooting.Init();
	m_flInitialBulletSpeed = m_flBulletSpeed = 0;
#ifndef CLIENT_DLL
	m_flLagCompensation =
#endif
	m_flEntryDensity = 0.0f;
	m_nFlags = (FireBulletsFlags_t)0;
#ifndef CLIENT_DLL
	m_iDamage = m_nDamageType =
#endif
	m_iAmmoType = 0;
}
CSimulatedBullet::CSimulatedBullet( CBaseEntity *pAttacker, CBaseEntity *pAdditionalIgnoreEnt,
									int ammoType, int nFlags, Vector &vecDirShooting,
									Vector &vecOrigin, int damageType
#ifndef CLIENT_DLL
									, CBaseEntity *pCaller, int iDamage, float flLagCompensation
#endif
									)
{
	m_nFlags = (FireBulletsFlags_t)nFlags;
	m_iAmmoType = ammoType;
	m_vecOrigin = vecOrigin;
	m_vecDirShooting = vecDirShooting;
	m_flInitialBulletSpeed = m_flBulletSpeed = GetAmmoDef()->GetAmmoOfIndex(ammoType)->bulletSpeed;
	//m_flInitialBulletSpeed = m_flBulletSpeed = BULLET_SPEED;
	m_flEntryDensity = 0.0f;
#ifndef CLIENT_DLL
	m_flLagCompensation = 100*flLagCompensation;
	m_iDamage = iDamage;
	m_hCaller = pCaller;
#endif
	m_nDamageType = damageType;
	m_pIgnoreList = new CTraceFilterSimpleList(COLLISION_GROUP_NONE);
	m_pIgnoreList->AddEntityToIgnore(pAttacker);
	if(pAdditionalIgnoreEnt!=NULL)
		m_pIgnoreList->AddEntityToIgnore(pAdditionalIgnoreEnt);
	m_pTwoEnts = new CTraceFilterSkipTwoEntities(pAttacker,pAdditionalIgnoreEnt,COLLISION_GROUP_NONE);
}
CSimulatedBullet::~CSimulatedBullet()
{
	delete m_pIgnoreList;
	delete m_pTwoEnts;
}
#ifdef CLIENT_DLL
bool FX_AffectRagdolls(Vector vecOrigin, Vector vecStart, int iDamageType);
bool C_SimulatedBullet::SimulateBullet(void)
{
	VPROF( "C_SimulatedBullet::SimulateBullet" );
#else
bool CSimulatedBullet::SimulateBullet(float flLagCompensation/*=-1 (seconds)*/)
{
	VPROF( "CSimulatedBullet::SimulateBullet" );
#endif
	trace_t trace;//, trace_back;
	Vector vecTraceStart(m_vecOrigin);
	
	Vector vecNewRay = m_vecDirShooting * m_flBulletSpeed;
#ifdef GAME_DLL
	if(flLagCompensation!=-1)
	{
		//TODO: call backtracking first
		vecNewRay *= flLagCompensation * 100;
	}
#endif

	m_vecOrigin += vecNewRay;
	bool bInWater = UTIL_PointContents(m_vecOrigin)&MASK_SPLITAREAPORTAL;
	if(!IsInWorld())
	{
		return false;
	}
#ifdef CLIENT_DLL
	FX_PlayerTracer( vecTraceStart, m_vecOrigin );
#endif
	if(m_bWasInWater!=bInWater)
	{
#ifdef CLIENT_DLL
		//TODO: water impact effect
		//CBaseEntity::HandleShotImpactingWater
#endif //CLIENT_DLL
	}
	if(bInWater)
	{
#ifdef CLIENT_DLL
		//TODO: 1 bubble clientside
#endif //CLIENT_DLL
	}
	
#ifdef CLIENT_DLL
	if(g_cl_debug_bullets.GetBool())
	{
		debugoverlay->AddLineOverlay( vecTraceStart, m_vecOrigin, 255, 0, 0, true, 10.0f );
	}
#else
	if(g_debug_bullets.GetBool())
	{
		NDebugOverlay::Line( vecTraceStart, m_vecOrigin, 255, 255, 255, true, 10.0f );
	}
#endif
	m_bWasInWater = bInWater;
	bool bulletSpeedCheck;
	do
	{
		bulletSpeedCheck = false;
		UTIL_TraceLine( vecTraceStart, m_vecOrigin, MASK_SHOT, m_pIgnoreList, &trace );
		if(!(trace.surface.flags&SURF_SKY))
		{
			if(trace.allsolid)//in solid
			{
				if(!AllSolid(trace))
					return false;
				bulletSpeedCheck = true;
			}
			else if(trace.startsolid)//exit solid
			{
				if(!StartSolid(trace, vecNewRay))
					return false;
				bulletSpeedCheck = true;
			}
			else if(trace.fraction!=1.0f)//hit solid
			{
				if(!EndSolid(trace))
					return false;
				bulletSpeedCheck = true;
			}
			else
			{
				//don't do a bullet speed check for not touching anything
				break;
			}
		}
		if(bulletSpeedCheck)
		{
			if(m_flBulletSpeed<=BulletManager()->BulletStopSpeed())
			{
				return false;
			}
		}
		vecTraceStart = trace.endpos + m_vecDirShooting;
	}while(trace.fraction!=1.0f);
	return true;
}
bool CSimulatedBullet::StartSolid(trace_t &ptr, Vector &vecNewRay)
{
#ifdef CLIENT_DLL
	//TODO: penetration surface impact stuff
#endif //CLIENT_DLL
	Vector vecExitPosition = ptr.fractionleftsolid * vecNewRay + ptr.startpos;
	float flPenetrationDistance = m_vecEntryPosition.DistTo(vecExitPosition);
	switch(BulletManager()->BulletStopSpeed())
	{
		case BUTTER_MODE:
		{
			//Do nothing to bullet speed
			break;
		}
		case ONE_HIT_MODE:
		{
			return false;
		}
		default:
		{
			m_flBulletSpeed -= flPenetrationDistance * m_flEntryDensity / sv_bullet_speed_modifier.GetFloat();
			break;
		}
	}
	return true;
}
bool CSimulatedBullet::AllSolid(trace_t &ptr)
{
	switch(BulletManager()->BulletStopSpeed())
	{
		case BUTTER_MODE:
		{
			//Do nothing to bullet speed
			break;
		}
		case ONE_HIT_MODE:
		{
			return false;
		}
		default:
		{
			m_flBulletSpeed -= m_flBulletSpeed * m_flEntryDensity / sv_bullet_speed_modifier.GetFloat();
			break;
		}
	}
	return true;
}
bool CSimulatedBullet::EndSolid(trace_t &ptr)
{
	m_vecEntryPosition = ptr.endpos;
#ifndef CLIENT_DLL
	int soundEntChannel = ( m_nFlags&FIRE_BULLETS_TEMPORARY_DANGER_SOUND ) ? SOUNDENT_CHANNEL_BULLET_IMPACT : SOUNDENT_CHANNEL_UNSPECIFIED;

	CSoundEnt::InsertSound( SOUND_BULLET_IMPACT, m_vecEntryPosition, 200, 0.5, m_hCaller, soundEntChannel );
#endif
	if(FStrEq(ptr.surface.name,"tools/toolsblockbullets"))
	{
		return false;
	}
#ifdef CLIENT_DLL
	FX_AffectRagdolls(ptr.startpos, ptr.endpos, m_nDamageType);
	//TODO: surface impact stuff
#endif //CLIENT_DLL
	m_flEntryDensity = physprops->GetSurfaceData(ptr.surface.surfaceProps)->physics.density;
	if(ptr.DidHitNonWorldEntity())
	{
		if(m_pIgnoreList->ShouldHitEntity(ptr.m_pEnt,MASK_SHOT))
		{
			m_pIgnoreList->AddEntityToIgnore(ptr.m_pEnt);
			DevMsg("Hit: %s\n",ptr.m_pEnt->GetClassname());
#ifndef CLIENT_DLL
			EntityImpact(ptr);
#endif
		}
	}
	if(BulletManager()->BulletStopSpeed()==ONE_HIT_MODE)
	{
		return false;
	}
	return true;
}
#ifndef CLIENT_DLL
void CSimulatedBullet::EntityImpact(trace_t &ptr)
{
	//TODO: entity impact stuff
	/*CTakeDamageInfo dmgInfo( this, pAttacker, flActualDamage, nActualDamageType );
				CalculateBulletDamageForce( &dmgInfo, info.m_iAmmoType, vecDir, tr.endpos );
				dmgInfo.ScaleDamageForce( info.m_flDamageForceScale );
				dmgInfo.SetAmmoType( info.m_iAmmoType );
				tr.m_pEnt->DispatchTraceAttack( dmgInfo, vecDir, &tr );*/
	if ( GetAmmoDef()->Flags(m_iAmmoType) & AMMO_FORCE_DROP_IF_CARRIED )
	{
		// Make sure if the player is holding this, he drops it
		Pickup_ForcePlayerToDropThisObject( ptr.m_pEnt );		
	}
}
void CBulletManager::Think(void)
#else
void CBulletManager::ClientThink(void)
#endif
{
	int x = m_Bullets.Head();
	while(m_Bullets.IsValidIndex(x))
	{
		if(m_Bullets[x]->SimulateBullet())
			x = m_Bullets.Next(x);
		else
			x = RemoveBullet(x);
	}
#ifdef CLIENT_DLL
	SetNextClientThink( gpGlobals->curtime + 0.01f );
#else
	SetNextThink( gpGlobals->curtime + 0.01f );
#endif
}
#ifdef CLIENT_DLL
void BulletShotCallback( const CEffectData &data )
{
	Vector vecDir(data.m_vNormal), vecOrigin(data.m_vOrigin);
	CSimulatedBullet *pBullet = new CSimulatedBullet(cl_entitylist->GetBaseEntity(data.m_nEntIndex),
								cl_entitylist->GetBaseEntity(data.m_nAttachmentIndex), data.m_nMaterial,
								data.m_fFlags, vecDir, vecOrigin, data.m_nDamageType);
	BulletManager()->AddBullet(pBullet);
}

DECLARE_CLIENT_EFFECT( "bulletshot", BulletShotCallback );
#endif
void CBulletManager::UpdateBulletStopSpeed(void)
{
	m_iBulletStopSpeed = sv_bullet_stop_speed.GetInt();
}
int CBulletManager::AddBullet(CSimulatedBullet *pBullet, float flLatency)
{
	if (pBullet->AmmoIndex() == -1)
	{
		DevMsg("ERROR: Undefined ammo type!\n");
		return -1;
	}
	int index = m_Bullets.AddToTail(pBullet);
	
#ifdef CLIENT_DLL
	DevMsg( "Client Bullet Created (%i)\n", index);
#else
	DevMsg( "Bullet Created (%i) LagCompensation %f\n", index, flLatency );
	if(flLatency!=0.0f)
		m_Bullets[index]->SimulateBullet(flLatency);
#endif
	return index;
}
int CBulletManager::RemoveBullet(int index)
{
	int retVal = m_Bullets.Next(index);
#ifdef CLIENT_DLL
	DevMsg("Client ");
#endif
	DevMsg( "Bullet Removed (%i)\n", index );
	m_Bullets.Remove(index);
	return retVal;
}

Implementation

game_shared

gamerules.cpp

Add
#include "bullet_manager.h"
After
#include "filesystem.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 );

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

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

ammodef.cpp

Add
float bulletSpeed,
Before both
float physicsForceImpulse,

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

hl2mp_gamerules.cpp

Change GetAmmoDef to the following:

#define BULLET_SPEED(ftpersec) 0.12*ftpersec//inches per hundredth of a second
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 is derived from the ftpersec of the bullet impulses.

baseentity_shared.cpp

In CBaseEntity::FireBullets

TODO

dlls

player_lagcompensation.cpp

Add the following function to this file:

static int GetTargetTick(CBasePlayer *player,CUserCmd *cmd)
{
	static CBasePlayer *lastPlayer;
	static int lastTarget;
	if(player==lastPlayer)
		return lastTarget;
	// 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 );
	}
	lastPlayer = player;
	lastTarget = targettick;
	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);

ilagcompensationmanager.h

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

player_command.cpp

In CPlayerMove::StartCommand

Comment out
lagcompensation->StartLagCompensation( player, cmd );

hl2mp_player.cpp

The bullets will know when they're being fired so in CHL2MP_Player::WantsLagCompensationOnEntity

Comment out
	if ( !( pCmd->buttons & IN_ATTACK ) && (pCmd->command_number - m_iLastWeaponFireUsercmd > 5) )
		return false;

In CHL2MP_Player::FireBullets

Add
lagcompensation->StartLagCompensation(this, m_pCurrentCommand);
	modinfo.m_flLatency = TICKS_TO_TIME(GetTargetTick(this,m_pCurrentCommand));
Before BaseClass::FireBullets( modinfo );
Add
lagcompensation->FinishLagCompensation(this);
After BaseClass::FireBullets( modinfo );