Simulated Bullets: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
 
(107 intermediate revisions by 6 users not shown)
Line 1: Line 1:
[[Category:Programming]]
With simulated bullets, bullets are handled in batches instead of individually. Bullet simulation using individual entities is not the way to go because they can fill up the entity list and are just unconventional. This article details the changes to the HL2MP SDK that will achieve this effect. It will also attempt to create a system that works for maxplayers 1 in the HL2MP SDK for those mods that want to be bothAnd for the singleplayer only mods, just try doing this with entities instead (you'll have way more control).
==Preface==
Basically, with simulated bullets, the aspects of physics are going to tried to be captured by simulating them in batch simulation codeSo far the code is all server-side and is later expected to be client-side simulated with similar code.


{{note|This code is incomplete. Feel free to make helpful changes.}}


{{note|This code is a work in progress. Feel free to make helpful changes.}}
{{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.}}


==Alteration==
This article was the basis for [[Realistic Simulated Bullets]].
The code creates two [[ConVar]]s which allow the host to change the penetration allowed and the maximum speed at which a bullet is removed.


==Body==
==Procedure==
This code is WIP by ts2do
===game_shared===
====bullet_manager.h====
<pre>#include "ammodef.h"
#define BULLET_SPEED 240//inches per hundredths of a second
const int BUTTER_MODE = -1; // special value indicating that bullets pass through objects without slowing
const int ONE_HIT_MODE = -2; // special value indicating that bullets don't pass through objects at all


#ifdef CLIENT_DLL
===game_shared\bullet_manager.h===
Create this file and add it to both projects of the solution.
 
<source lang=cpp>
#include "ammodef.h"
#define BUTTER_MODE -1
#define ONE_HIT_MODE -2
 
#ifdef CLIENT_DLL//-----------------------
class C_BulletManager;
class C_BulletManager;
extern C_BulletManager *g_pBulletManager;
extern C_BulletManager *g_pBulletManager;
#define CBulletManager C_BulletManager
#define CBulletManager C_BulletManager
 
#define CSimulatedBullet C_SimulatedBullet
 
#else//-----------------------------------
#else //!CLIENT_DLL
class CBulletManager;
class CBulletManager;
extern CBulletManager *g_pBulletManager;
extern CBulletManager *g_pBulletManager;
#endif //CLIENT_DLL
#endif//----------------------------------


inline CBulletManager *BulletManager()
inline CBulletManager *BulletManager()
Line 40: Line 38:
CSimulatedBullet();
CSimulatedBullet();
CSimulatedBullet( CBaseEntity *pAttacker, CBaseEntity *pAdditionalIgnoreEnt,
CSimulatedBullet( CBaseEntity *pAttacker, CBaseEntity *pAdditionalIgnoreEnt,
int ammoType, int nFlags, Vector &vecDirShooting,
int ammoType, Vector &vecDirShooting, Vector &vecOrigin,
Vector &vecOrigin, int damageType
bool bTraceHull
#ifndef CLIENT_DLL
#ifndef CLIENT_DLL
, CBaseEntity *pCaller, int iDamage, float flLagCompensation
, int nFlags, CBaseEntity *pCaller, int iDamage
#endif
#endif
);
);
~CSimulatedBullet();
~CSimulatedBullet();
inline float BulletSpeedRatio(void) const
inline float BulletSpeedRatio(void)
{
{
return m_flBulletSpeed/m_flInitialBulletSpeed;
return m_flBulletSpeed/m_flInitialBulletSpeed;
Line 61: Line 59:
return true;
return true;
}
}
bool StartSolid(trace_t &ptr, Vector &vecNewRay);
bool AllSolid(trace_t &ptr);
bool EndSolid(trace_t &ptr);
#ifdef CLIENT_DLL
#ifdef CLIENT_DLL
void SimulateBullet(int index);
bool SimulateBullet(void);
#else
#else
void EntityImpact(CBaseEntity *pHit);
void EntityImpact(trace_t &ptr);
inline float LagCompensation(void) const
bool SimulateBullet(float flLagCompensation=-1);
{
return m_flLagCompensation;
}
void SimulateBullet(int index,float flTime=1.0f);
#endif
#endif
inline int AmmoIndex(void) const
inline int AmmoIndex(void) const
Line 76: Line 73:
}
}
private:
private:
bool m_bTraceHull;
bool m_bWasInWater;
bool m_bWasInWater;


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


EHANDLE m_hCaller;
EHANDLE m_hCaller;
EHANDLE m_hLastHit;


FireBulletsFlags_t m_nFlags;


float m_flBulletSpeed;
float m_flBulletSpeed;
float m_flEntryDensity;
float m_flEntryDensity;
float m_flInitialBulletSpeed;
float m_flInitialBulletSpeed;
#ifndef CLIENT_DLL
float m_flRayLength;
float m_flLagCompensation;
#endif


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


Vector m_vecDirShooting;
Vector m_vecDirShooting;
Vector m_vecOrigin;
Vector m_vecOrigin;
 
Vector m_vecEntryPosition;
// Prevent copies
Vector m_vecTraceRay;
private:
CSimulatedBullet( const CSimulatedBullet& other );
CSimulatedBullet& operator=( const CSimulatedBullet& other );
};
};
extern CUtlLinkedList<CSimulatedBullet*> g_Bullets;
class CBulletManager : public CBaseEntity
class CBulletManager : public CBaseEntity
{
{
DECLARE_CLASS( CBulletManager, CBaseEntity );
DECLARE_CLASS( CBulletManager, CBaseEntity );
public:
public:
~CBulletManager()
{
g_Bullets.PurgeAndDeleteElements();
}
#ifdef CLIENT_DLL
#ifdef CLIENT_DLL
void ClientThink(void);
void ClientThink(void);
int AddBullet(CSimulatedBullet *pBullet);
#else
#else
void Spawn(void);
void Think(void);
void Think(void);
int AddBullet(CSimulatedBullet *pBullet, float flLatency=0.0f);
#endif
#endif
void AddBullet(CSimulatedBullet *pBullet);
unsigned short RemoveBullet(int index);
void RemoveBullet(int index);
void UpdateBulletStopSpeed(void);
int BulletStopSpeed(void)
{
return m_iBulletStopSpeed;
}
private:
private:
CUtlVector<CSimulatedBullet*> m_pBullets;
int m_iBulletStopSpeed;
};</pre>
};
</source>
 
===game_shared\bullet_manager.cpp===
Create this file and add it to both projects of the solution.


====bullet_manager.cpp====
<div style="max-height:50em; overflow:auto;">
<pre>#include "cbase.h"
<source lang=cpp>
#include "cbase.h"
#include "util_shared.h"
#include "util_shared.h"
#include "bullet_manager.h"
#include "bullet_manager.h"
#include "shot_manipulator.h"
#include "shot_manipulator.h"
#pragma warning(disable:4800)
#include "tier0/vprof.h"
 
CBulletManager *g_pBulletManager;
CUtlLinkedList<CSimulatedBullet*> g_Bullets;


#ifdef CLIENT_DLL//-------------------------------------------------
#ifdef CLIENT_DLL//-------------------------------------------------
#include "c_te_effect_dispatch.h"
#include "engine/ivdebugoverlay.h"
#include "engine/ivdebugoverlay.h"
ConVar g_cl_debug_bullets( "g_cl_debug_bullets", "0", FCVAR_CHEAT );
ConVar g_debug_client_bullets( "g_debug_client_bullets", "0", FCVAR_CHEAT );
C_BulletManager *g_pBulletManager = (C_BulletManager *)CreateEntityByName("bullet_manager");
extern void FX_PlayerTracer( Vector& start, Vector& end);
extern void FX_PlayerTracer( Vector& start, Vector& end);
#else//-------------------------------------------------------------
#else//-------------------------------------------------------------
#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;
#endif//------------------------------------------------------------
#endif//------------------------------------------------------------


// memdbgon must be the last include file in a .cpp file!!!
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#include "tier0/memdbgon.h"
static void BulletSpeedModifierCallback(ConVar *var, const char *pOldString)
static void BulletSpeedModifierCallback(ConVar *var, const char *pOldString)
{
{
Line 155: Line 167:
"Density/(This Value) * (Distance Penetrated) = (Change in Speed)",
"Density/(This Value) * (Distance Penetrated) = (Change in Speed)",
BulletSpeedModifierCallback );
BulletSpeedModifierCallback );


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


LINK_ENTITY_TO_CLASS( bullet_manager, CBulletManager );
LINK_ENTITY_TO_CLASS( bullet_manager, CBulletManager );
#ifndef CLIENT_DLL
 
void CBulletManager::Spawn(void)
 
{
 
Think();
//==================================================
}
// Purpose: Constructor
#endif
//==================================================
CSimulatedBullet::CSimulatedBullet()
CSimulatedBullet::CSimulatedBullet()
{
{
m_vecOrigin.Init();
m_vecOrigin.Init();
m_vecDirShooting.Init();
m_vecDirShooting.Init();
m_flInitialBulletSpeed = m_flBulletSpeed = BULLET_SPEED;
m_flInitialBulletSpeed = m_flBulletSpeed = 0;
m_flEntryDensity = 0.0f;
#ifndef CLIENT_DLL
#ifndef CLIENT_DLL
m_flLagCompensation =
#endif
m_flEntryDensity = 0.0f;
m_nFlags = (FireBulletsFlags_t)0;
m_nFlags = (FireBulletsFlags_t)0;
#ifndef CLIENT_DLL
m_iDamage = 0;
m_iDamage = m_nDamageType =
#endif
#endif
m_iAmmoType = 0;
m_iAmmoType = 0;
}
}
//==================================================
// Purpose: Constructor
//==================================================
CSimulatedBullet::CSimulatedBullet( CBaseEntity *pAttacker, CBaseEntity *pAdditionalIgnoreEnt,
CSimulatedBullet::CSimulatedBullet( CBaseEntity *pAttacker, CBaseEntity *pAdditionalIgnoreEnt,
int ammoType, int nFlags, Vector &vecDirShooting,
int ammoType, Vector &vecDirShooting, Vector &vecOrigin,
Vector &vecOrigin, int damageType
bool bTraceHull
#ifndef CLIENT_DLL
#ifndef CLIENT_DLL
, CBaseEntity *pCaller, int iDamage, float flLagCompensation
, int nFlags, CBaseEntity *pCaller, int iDamage
#endif
#endif
)
)
{
{
m_nFlags = (FireBulletsFlags_t)nFlags;
// Input validation
Assert( pAttacker );
#ifndef CLIENT_DLL
Assert( pCaller );
#endif
 
// Create a list of entities with which this bullet does not collide.
m_pIgnoreList = new CTraceFilterSimpleList(COLLISION_GROUP_NONE);
Assert( m_pIgnoreList );
 
// Don't collide with the entity firing the bullet.
m_pIgnoreList->AddEntityToIgnore(pAttacker);
 
// Don't collide with some optionally-specified entity.
if(pAdditionalIgnoreEnt!=NULL)
m_pIgnoreList->AddEntityToIgnore(pAdditionalIgnoreEnt);
 
// Not useful yet... may be useful for effects later
//m_pTwoEnts = new CTraceFilterSkipTwoEntities(pAttacker,pAdditionalIgnoreEnt,COLLISION_GROUP_NONE);
 
// Basic information about the bullet
m_iAmmoType = ammoType;
m_iAmmoType = ammoType;
m_flInitialBulletSpeed = m_flBulletSpeed = GetAmmoDef()->GetAmmoOfIndex(ammoType)->bulletSpeed;
m_vecDirShooting = vecDirShooting;
m_vecOrigin = vecOrigin;
m_vecOrigin = vecOrigin;
m_vecDirShooting = vecDirShooting;
m_bTraceHull = bTraceHull;
m_flInitialBulletSpeed = m_flBulletSpeed = GetAmmoDef()->GetAmmoOfIndex(ammoType)->flFeetPerSecond * 0.12;
//m_flInitialBulletSpeed = m_flBulletSpeed = BULLET_SPEED;
m_flEntryDensity = 0.0f;
#ifndef CLIENT_DLL
#ifndef CLIENT_DLL
m_flLagCompensation = 100*flLagCompensation;
m_nFlags = (FireBulletsFlags_t)nFlags;
m_iDamage = iDamage;
m_iDamage = iDamage;
m_hCaller = pCaller;
m_hCaller = pCaller;
#endif
#endif
m_nDamageType = damageType;
m_flEntryDensity = 0.0f;
m_pIgnoreList = new CTraceFilterSimpleList(COLLISION_GROUP_NONE);
m_vecTraceRay = vecDirShooting * m_flInitialBulletSpeed;
m_pIgnoreList->AddEntityToIgnore(pAttacker);
m_flRayLength = m_flInitialBulletSpeed;
if(pAdditionalIgnoreEnt!=NULL)
m_pIgnoreList->AddEntityToIgnore(pAdditionalIgnoreEnt);
m_pTwoEnts = new CTraceFilterSkipTwoEntities(pAttacker,pAdditionalIgnoreEnt,COLLISION_GROUP_NONE);
}
}
//==================================================
// Purpose: Deconstructor
//==================================================
CSimulatedBullet::~CSimulatedBullet()
CSimulatedBullet::~CSimulatedBullet()
{
{
Line 220: Line 262:
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);
 
 
//==================================================
// Purpose: Simulates a bullet through a ray of its bullet speed
//==================================================
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
{
 
Vector vecEntryPosition;
if(!IsFinite(m_flBulletSpeed))
Vector vecExitPosition;
return false;//prevent a weird crash
float flPenetrationDistance = 0.0f;
 
trace_t trace, trace_back;
trace_t trace;
Vector vecOldOrigin(m_vecOrigin);
Vector vecTraceStart(m_vecOrigin);
Vector vecTraceStart(m_vecOrigin);
 
Vector vecTraceRay;
#ifdef CLIENT_DLL
 
Vector vecNewRay = m_vecDirShooting * m_flBulletSpeed;
if(m_flRayLength==m_flBulletSpeed)
#else
{
Vector vecNewRay = m_vecDirShooting * m_flBulletSpeed * flTime;
vecTraceRay = m_vecTraceRay;
}
else
{
vecTraceRay = m_vecTraceRay = m_vecDirShooting * m_flBulletSpeed;
m_flRayLength = m_flBulletSpeed;
}
#ifdef GAME_DLL
if(flLagCompensation!=-1)
{
vecTraceRay *= flLagCompensation * 100;
}
#endif
#endif


DevMsg("Bullet %i Speed %f\n",index,m_flBulletSpeed);
m_vecOrigin += vecTraceRay;
m_vecOrigin += vecNewRay;//in/100th of a sec * 100th of a sec
bool bInWater = (UTIL_PointContents(m_vecOrigin)&MASK_SPLITAREAPORTAL)==1;
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 266: Line 326:
#ifdef CLIENT_DLL
#ifdef CLIENT_DLL
if(g_cl_debug_bullets.GetBool())
if(g_debug_client_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
{
{
UTIL_TraceLine( vecTraceStart, m_vecOrigin, MASK_SHOT, m_pIgnoreList, &trace );
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 );
if(!(trace.surface.flags&SURF_SKY))
if(!(trace.surface.flags&SURF_SKY))
{
{
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, vecTraceRay))
//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;
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 &vecTraceRay)
{
#ifdef CLIENT_DLL
//TODO: penetration surface impact stuff
#endif //CLIENT_DLL
Vector vecExitPosition = ptr.fractionleftsolid * vecTraceRay + 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;
}
 
 
//==================================================
// 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
break;
}
case ONE_HIT_MODE:
{
return false;
}
default:
{
m_flBulletSpeed -= m_flBulletSpeed * m_flEntryDensity / sv_bullet_speed_modifier.GetFloat();
break;
}
}
return true;
}
 
 
//==================================================
// Purpose: Simulate when a surface is hit
//==================================================
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;
int iDamageType = GetAmmoDef()->DamageType( m_iAmmoType );
data.m_vStart = trace.startpos;
FX_AffectRagdolls(ptr.startpos, ptr.endpos, iDamageType);
data.m_vOrigin = trace.endpos;
UTIL_ImpactTrace( &ptr, iDamageType );
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(ptr.m_pEnt!=m_hLastHit)
{
{
m_pIgnoreList->AddEntityToIgnore(trace.m_pEnt);
if(m_pIgnoreList->ShouldHitEntity(ptr.m_pEnt,MASK_SHOT))
{
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;
else
}while(trace.endpos!=m_vecOrigin&&m_flBulletSpeed>flBulletStopSpeed);
m_pIgnoreList->AddEntityToIgnore(ptr.m_pEnt);
 
m_hLastHit = ptr.m_pEnt;
 
}
if(m_flBulletSpeed<=flBulletStopSpeed)
if(BulletManager()->BulletStopSpeed()==ONE_HIT_MODE)
{
{
REMOVE_BULLET_AND_END(index);
return false;
}
}
return true;
}
}
#ifndef CLIENT_DLL
#ifndef CLIENT_DLL
void CSimulatedBullet::EntityImpact(CBaseEntity *pHit)
 
 
 
//==================================================
// Purpose: Entity impact procedure
//==================================================
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 );
}
}
}
}
//==================================================
// Purpose: Simulates all bullets every centisecond
//==================================================
void CBulletManager::Think(void)
void CBulletManager::Think(void)
#else
#else
Line 394: Line 517:
#endif
#endif
{
{
for(int x=0;x<m_pBullets.Count();x++)
unsigned short iNext=0;
for ( unsigned short i=g_Bullets.Head(); i != g_Bullets.InvalidIndex(); i=iNext )
{
{
m_pBullets[x]->SimulateBullet(x);
iNext = g_Bullets.Next( i );
if ( !g_Bullets[i]->SimulateBullet() )
RemoveBullet( i );
}
}
if(g_Bullets.Head()!=g_Bullets.InvalidIndex())
{
#ifdef CLIENT_DLL
#ifdef CLIENT_DLL
SetNextClientThink( gpGlobals->curtime + 0.01f );
SetNextClientThink( gpGlobals->curtime + 0.01f );
#else
#else
SetNextThink( gpGlobals->curtime + 0.01f );
SetNextThink( gpGlobals->curtime + 0.01f );
#endif
#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();
}
}
void CBulletManager::AddBullet(CSimulatedBullet *pBullet)
 
 
//==================================================
// Purpose: Add bullet to linked list
// Handle lag compensation with "prebullet simulation"
// Output: Bullet's index (-1 for error)
//==================================================
#ifdef CLIENT_DLL
int CBulletManager::AddBullet(CSimulatedBullet *pBullet)
#else
int CBulletManager::AddBullet(CSimulatedBullet *pBullet, float flLatency/*=0.0f*/)
#endif
{
{
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);
unsigned short index = g_Bullets.AddToTail(pBullet);
#ifdef CLIENT_DLL
#ifdef CLIENT_DLL
DevMsg( "Client Bullet Created (%i)\n", index);
DevMsg( "Client Bullet Created (%i)\n", index);
if(g_Bullets.Count()==1)
{
SetNextClientThink(gpGlobals->curtime + 0.01f);
}
#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());
g_Bullets[index]->SimulateBullet(flLatency);
if(g_Bullets.Count()==1)
{
SetNextThink(gpGlobals->curtime + 0.01f);
}
#endif
#endif
return index;
}
}
void CBulletManager::RemoveBullet(int index)
 
 
//==================================================
// Purpose: Remove the bullet at index from the linked list
// Output: Next index
//==================================================
unsigned short CBulletManager::RemoveBullet(int index)
{
{
unsigned short retVal = g_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);
g_Bullets.Remove(index);
}</pre>
if(g_Bullets.Count()==0)
 
{
 
#ifdef CLIENT_DLL
 
SetNextClientThink( TICK_NEVER_THINK );
#else
SetNextThink( TICK_NEVER_THINK );
#endif
}
return retVal;
}
</source>
</div>


===game_shared\gamerules.cpp===


{| style="background-color: transparent;"
| Add || <source lang=cpp>


#include "bullet_manager.h"


==Implementation==
</source>
===gamerules.cpp===
{| style="background-color: transparent;"
| Add || <pre>#include "bullet_manager.h"</pre>
|-
|-
| After || <pre>#include "filesystem.h"</pre>
| After || <source lang=cpp>#include "KeyValues.h"
</source>
|}
|}
----
----
{| style="background-color: transparent;"
{| style="background-color: transparent;"
| Add || <pre>g_pBulletManager = (CBulletManager*)CBaseEntity::Create( "bullet_manager", vec3_origin, vec3_angle );</pre>
| Add || <source lang=cpp>
 
g_pBulletManager = (CBulletManager*)CBaseEntity::Create( "bullet_manager", vec3_origin, vec3_angle );
</source>
|-
|-
| After || <pre>g_pPlayerResource = (CPlayerResource*)CBaseEntity::Create( "player_manager", vec3_origin, vec3_angle );</pre>
| After || <source lang=cpp>
 
g_pPlayerResource = (CPlayerResource*)CBaseEntity::Create( "player_manager", vec3_origin, vec3_angle );
</source>
|}
|}
====ammodef.h====
 
===game_shared\ammodef.h===
Change the <code>Ammo_t</code> structure to the following:
Change the <code>Ammo_t</code> structure to the following:
<pre>struct Ammo_t  
<source lang=cpp>struct Ammo_t  
{
{
char *pName;
char *pName;
Line 471: Line 652:
const ConVar* pNPCDmgCVar; // CVar for NPC damage amount
const ConVar* pNPCDmgCVar; // CVar for NPC damage amount
const ConVar* pMaxCarryCVar; // CVar for maximum number can carry
const ConVar* pMaxCarryCVar; // CVar for maximum number can carry
};</pre>
};</source>
----
----
Change the first two <code>AddAmmoType</code> function prototypes to the following:
In the <code>CAmmoDef</code> class, change the first two <code>AddAmmoType</code> function prototypes to the following:
<pre> void AddAmmoType(char const* name, int damageType, int tracerType,
<source lang=cpp> void AddAmmoType(char const* name, int damageType, int tracerType,
int plr_dmg, int npc_dmg, int carry,
int plr_dmg, int npc_dmg, int carry,
float bulletSpeed, float physicsForceImpulse,
float bulletSpeed, float physicsForceImpulse,
Line 484: Line 665:
char const* carry_cvar, float bulletSpeed,
char const* carry_cvar, float bulletSpeed,
float physicsForceImpulse, int nFlags,
float physicsForceImpulse, int nFlags,
int minSplashSize = 4, int maxSplashSize = 8 );</pre>
int minSplashSize = 4, int maxSplashSize = 8 );</source>
====ammodef.cpp====
 
===game_shared\ammodef.cpp===
{| style="background:transparent;"
{| style="background:transparent;"
| Add || <pre>float ftpersec,</pre>
| Add || <source lang=cpp>float bulletSpeed,</source>
|-
|-
| Before both || <pre>float physicsForceImpulse,</pre>
| Before both || <source lang=cpp>float physicsForceImpulse,</source>
|}
|}
----
----
{| style="background:transparent;"
{| style="background:transparent;"
| Add || <pre>m_AmmoType[m_nAmmoIndex].flFeetPerSecond = ftpersec;</pre>
| Add || <source lang=cpp>m_AmmoType[m_nAmmoIndex].bulletSpeed = bulletSpeed;</source>
|-
|-
| After both || <pre>m_AmmoType[m_nAmmoIndex].physicsForceImpulse = physicsForceImpulse;</pre>
| Before both || <source lang=cpp>m_AmmoType[m_nAmmoIndex].physicsForceImpulse = physicsForceImpulse;</source>
|}
|}


====hl2mp_gamerules.cpp====
===game_shared\hl2mp/hl2mp_gamerules.cpp===
{| style="background:transparent;"
| Add || <source lang=cpp>#define BULLET_SPEED(ftpersec) 0.12*ftpersec//inches per centisecond</source>
|-
| Before || <source lang=cpp>CAmmoDef *GetAmmoDef()</source>
|}
----
Change <code>GetAmmoDef</code> to the following:
Change <code>GetAmmoDef</code> to the following:
<pre>
<source lang=cpp>CAmmoDef *GetAmmoDef()
#define BULLET_SPEED(ftpersec) 0.12*ftpersec//inches per hundredth of a second
CAmmoDef *GetAmmoDef()
{
{
static CAmmoDef def;
static CAmmoDef def;
Line 525: Line 711:


return &def;
return &def;
}</pre>
}</source>
As you can see, the bullet speed is derived from the second argument of the bullet impulses.
As you can see, the bullet speed is derived from the ftpersec of the bullet impulses.
 
===game_shared\baseentity_shared.cpp===
{| style="background:transparent;"
| Add || <source lang=cpp>#include "bullet_manager.h"</source>
|-
| After || <source lang=cpp>#include "coordsize.h"</source>
|}
----
Change <code>CBaseEntity::FireBullets</code> to the following:
<source lang=cpp>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 );
 
#ifdef GAME_DLL
// bool bDoImpacts = false;
bool bDoTracers = false;
#endif
for (int iShot = 0; iShot < info.m_iShots; iShot++)
{
// bool bHitWater = false;
// bool bHitGlass = false;
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(pAttacker, info.m_pAdditionalIgnoreEnt,
info.m_iAmmoType, vecDir, vecSrc, bTraceHull
#ifndef CLIENT_DLL
, info.m_nFlags, this, info.m_iDamage
#endif
);
#ifdef CLIENT_DLL
BulletManager()->AddBullet(pBullet);
#else
BulletManager()->AddBullet(pBullet, info.m_flLatency);
#endif
iSeed++;
}
 
#ifdef GAME_DLL
TE_HL2MPFireBullets( pAttacker?pAttacker->entindex():0,
info.m_pAdditionalIgnoreEnt?info.m_pAdditionalIgnoreEnt->entindex():0,
info.m_iAmmoType, info.m_vecSrc, info.m_vecDirShooting, bDoTracers, iEffectSeed, info.m_vecSpread.x, info.m_iShots);
ApplyMultiDamage();
#endif
}</source>
 
===game_shared\shareddefs.h===
Change the FireBulletsInfo_t structure to the following:
<source lang=cpp>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;
};</source>
 
===dlls/player_lagcompensation.cpp===
Add the following function to this file:
<source lang=cpp>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;
}</source>
----
In <code>CLagCompensationManager::StartLagCompensation</code>
 
Replace
<source lang=cpp> // 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 );
}</source>
With
<source lang=cpp> int targettick = GetTargetTick(player, cmd);</source>
----
{| style="background:transparent;"
| Add || GetTargetTick();
|-
| After || VPROF_BUDGET_FLAGS( "FinishLagCompensation", "CLagCompensationManager", BUDGETFLAG_CLIENT|BUDGETFLAG_SERVER );
|}
 
===dlls/ilagcompensationmanager.h===
{| style="background: transparent;"
| Add || <source lang=cpp>int GetTargetTick(CBasePlayer *player=NULL,CUserCmd *cmd=NULL);</source>
|-
| Before || <source lang=cpp>extern ILagCompensationManager *lagcompensation;</source>
|}
 
===dlls/hl2mp_dll/hl2mp_player.cpp===
{| style="background: transparent;"
| Add || <source lang=cpp>#include "ilagcompensationmanager.h"</source>
|-
| After || <source lang=cpp>#include "SoundEmitterSystem/isoundemittersystembase.h"</source>
|}
----
In <code>CHL2MP_Player::FireBullets</code>
{| style="background: transparent;"
| Add || <source lang=cpp> if(gpGlobals->maxClients!=1)
modinfo.m_flLatency = TICKS_TO_TIME(GetTargetTick(this,m_pCurrentCommand));</source>
|-
| Before || <source lang=cpp> BaseClass::FireBullets( modinfo );</source>
|}
 
===cl_dll\clientmode_shared.cpp===
{| style="background: transparent;"
| Add || <source lang=cpp>#include "bullet_manager.h"</source>
|-
| After || <source lang=cpp>#include "c_team.h"</source>
|}
----
{| style="background:transparent;"
| Add || <source lang=cpp> g_pBulletManager = (C_BulletManager *)CreateEntityByName("bullet_manager");
ClientEntityList().AddNonNetworkableEntity(g_pBulletManager);</source>
|-
| After || <source lang=cpp> enginesound->SetPlayerDSP( filter, 0, true );</source>
|}
 
===dlls\te_hl2mp_shotgun_shot.h===
Paste the following into this file:
<source lang=cpp>//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
 
#ifndef TE_HL2MP_SHOTGUN_SHOT_H
#define TE_HL2MP_SHOTGUN_SHOT_H
#ifdef _WIN32
#pragma once
#endif
 
 
void TE_HL2MPFireBullets(
int iAttacker,
int iAdditionalIgnoreEnt,
int iAmmoID,
const Vector &vecOrigin,
const Vector &vecDir,
bool bDoTracers,
int iSeed,
float flSpread,
int iShots );
 
 
#endif // TE_HL2MP_SHOTGUN_SHOT_H</source>
 
===dlls\te_hl2mp_shotgun_shot.cpp===
 
<source lang=cpp>
#include "cbase.h"
#include "basetempentity.h"
 
#define NUM_BULLET_SEED_BITS 8
 
//-----------------------------------------------------------------------------
// Purpose: Display's a blood sprite
//-----------------------------------------------------------------------------
class CTEHL2MPFireBullets : public CBaseTempEntity
{
public:
DECLARE_CLASS( CTEHL2MPFireBullets, CBaseTempEntity );
DECLARE_SERVERCLASS();
 
CTEHL2MPFireBullets( const char *name );
virtual ~CTEHL2MPFireBullets( void );
 
public:
CNetworkVar( int, m_iAttacker );
CNetworkVar( int, m_iAdditionalIgnoreEnt );
CNetworkVar( int, m_iAmmoID );
CNetworkVector( m_vecOrigin );
CNetworkVector( m_vecDir );
CNetworkVar( bool, m_bDoTracers );
CNetworkVar( int, m_iSeed );
CNetworkVar( float, m_flSpread );
CNetworkVar( int, m_iShots );
};
 
//-----------------------------------------------------------------------------
// Purpose:
// Input  : *name -
//-----------------------------------------------------------------------------
CTEHL2MPFireBullets::CTEHL2MPFireBullets( const char *name ) :
CBaseTempEntity( name )
{
}
 
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CTEHL2MPFireBullets::~CTEHL2MPFireBullets( void )
{
}
 
IMPLEMENT_SERVERCLASS_ST_NOBASE(CTEHL2MPFireBullets, DT_TEHL2MPFireBullets)
SendPropInt( SENDINFO( m_iAttacker ), 5, SPROP_UNSIGNED ), // max 32 players, see MAX_PLAYERS
SendPropInt( SENDINFO( m_iAdditionalIgnoreEnt ), 16, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iAmmoID ), 5, SPROP_UNSIGNED ),
SendPropVector( SENDINFO( m_vecOrigin ), -1, SPROP_COORD ),
SendPropVector( SENDINFO( m_vecDir ), -1 ),
SendPropBool( SENDINFO( m_bDoTracers ) ),
SendPropInt( SENDINFO( m_iSeed ), NUM_BULLET_SEED_BITS, SPROP_UNSIGNED ),
SendPropFloat( SENDINFO( m_flSpread ), 10, 0, 0, 1 ),
SendPropInt( SENDINFO( m_iShots ), 5, SPROP_UNSIGNED ),
END_SEND_TABLE()
 
 
// Singleton
static CTEHL2MPFireBullets g_TEHL2MPFireBullets( "Shotgun Shot" );
 
 
void TE_HL2MPFireBullets(
int iAttacker,
int iAdditionalIgnoreEnt,
int iAmmoID,
const Vector &vecOrigin,
const Vector &vecDir,
bool bDoTracers,
int iSeed,
float flSpread,
int iShots )
{
CRecipientFilter filter;
filter.AddAllPlayers();
filter.UsePredictionRules();
 
g_TEHL2MPFireBullets.m_iAttacker = iAttacker;
g_TEHL2MPFireBullets.m_iAdditionalIgnoreEnt = iAdditionalIgnoreEnt;
g_TEHL2MPFireBullets.m_iAmmoID = iAmmoID;
g_TEHL2MPFireBullets.m_vecOrigin = vecOrigin;
g_TEHL2MPFireBullets.m_vecDir = vecDir;
g_TEHL2MPFireBullets.m_bDoTracers = bDoTracers;
g_TEHL2MPFireBullets.m_iSeed = iSeed;
g_TEHL2MPFireBullets.m_flSpread = flSpread;
g_TEHL2MPFireBullets.m_iShots = iShots;
Assert( iSeed < (1 << NUM_BULLET_SEED_BITS) );
g_TEHL2MPFireBullets.Create( filter, 0 );
}</source>
 
===cl_dll\c_te_hl2mp_shotgun_shot.cpp===
 
<div style="max-height:50em; overflow:auto;">
<source lang=cpp>
#include "cbase.h"
#include "c_basetempentity.h"
#include <cliententitylist.h>
#include "ammodef.h"
#include "c_te_effect_dispatch.h"
#include "shot_manipulator.h"
#include "bullet_manager.h"
 
class C_TEHL2MPFireBullets : public C_BaseTempEntity
{
public:
DECLARE_CLASS( C_TEHL2MPFireBullets, C_BaseTempEntity );
DECLARE_CLIENTCLASS();
 
virtual void PostDataUpdate( DataUpdateType_t updateType );
 
void CreateEffects( void );
 
public:
int m_iAttacker;
int m_iAdditionalIgnoreEnt;
int m_iAmmoID;
Vector m_vecOrigin;
Vector m_vecDir;
bool m_bDoTracers;
int m_iSeed;
float m_flSpread;
int m_iShots;
};
 
class CTraceFilterSkipPlayerAndViewModelOnly : public CTraceFilter
{
public:
virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
{
C_BaseEntity *pEntity = EntityFromEntityHandle( pServerEntity );
if( pEntity &&
( dynamic_cast<C_BaseViewModel *>( pEntity ) != NULL ) ||
( dynamic_cast<C_BasePlayer *>( pEntity ) != NULL ) )
{
return false;
}
else
{
return true;
}
}
};
 
void C_TEHL2MPFireBullets::CreateEffects( void )
{
CAmmoDef* pAmmoDef = GetAmmoDef();
 
if ( pAmmoDef == NULL )
return;
 
C_BaseEntity *pEnt = ClientEntityList().GetEnt( m_iAttacker );
 
if ( pEnt )
{
C_BaseCombatCharacter *pChar = pEnt->MyCombatCharacterPointer();
 
if ( pChar && pChar->GetActiveWeapon() )
{
C_BaseCombatWeapon *pWpn = pChar->GetActiveWeapon();
C_BaseEntity *additional = ClientEntityList().GetEnt( m_iAdditionalIgnoreEnt );
 
if ( pWpn )
{
int iSeed = m_iSeed;
CShotManipulator Manipulator( m_vecDir );
 
for (int iShot = 0; iShot < m_iShots; iShot++)
{
RandomSeed( iSeed ); // init random system with this seed
 
// Don't run the biasing code for the player at the moment.
Vector vecDir = Manipulator.ApplySpread( Vector( m_flSpread, m_flSpread, m_flSpread ) );
bool bTraceHull = ( m_iShots > 1 && iShot % 2 );
C_SimulatedBullet *pBullet = new C_SimulatedBullet(pEnt, additional,
m_iAmmoID, vecDir, m_vecOrigin, bTraceHull);
BulletManager()->AddBullet(pBullet);
 
iSeed++;
}
}
}
}
 
}
 
void C_TEHL2MPFireBullets::PostDataUpdate( DataUpdateType_t updateType )
{
CreateEffects();
}
 
 
IMPLEMENT_CLIENTCLASS_EVENT( C_TEHL2MPFireBullets, DT_TEHL2MPFireBullets, CTEHL2MPFireBullets );
 
 
BEGIN_RECV_TABLE_NOBASE(C_TEHL2MPFireBullets, DT_TEHL2MPFireBullets )
RecvPropInt( RECVINFO( m_iAttacker ) ),
RecvPropInt( RECVINFO( m_iAdditionalIgnoreEnt ) ),
RecvPropInt( RECVINFO( m_iAmmoID ) ),
RecvPropVector( RECVINFO( m_vecOrigin ) ),
RecvPropVector( RECVINFO( m_vecDir ) ),
RecvPropBool( RECVINFO( m_bDoTracers ) ),
RecvPropInt( RECVINFO( m_iSeed ) ),
RecvPropFloat( RECVINFO( m_flSpread ) ),
RecvPropInt( RECVINFO( m_iShots ) ),
END_RECV_TABLE()</source>
</div>


===baseentity_shared.cpp===
[[Category:Free source code]]
[[Category:Weapons programming]]

Latest revision as of 14:36, 4 February 2012

With simulated bullets, bullets are handled in batches instead of individually. Bullet simulation using individual entities is not the way to go because they can fill up the entity list and are just unconventional. This article details the changes to the HL2MP SDK that will achieve this effect. It will also attempt to create a system that works for maxplayers 1 in the HL2MP SDK for those mods that want to be both. And for the singleplayer only mods, just try doing this with entities instead (you'll have way more control).

Note.pngNote:This code is incomplete. Feel free to make helpful changes.
Note.pngNote: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.

This article was the basis for Realistic Simulated Bullets.

Procedure

game_shared\bullet_manager.h

Create this file and add it to both projects of the solution.

#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, Vector &vecDirShooting, Vector &vecOrigin,
						bool bTraceHull
#ifndef CLIENT_DLL
						, int nFlags, CBaseEntity *pCaller, int iDamage
#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_bTraceHull;
	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;
	EHANDLE	m_hLastHit;


	float m_flBulletSpeed;
	float m_flEntryDensity;
	float m_flInitialBulletSpeed;
	float m_flRayLength;

	int m_iAmmoType;
#ifndef CLIENT_DLL
	int m_iDamage;
	FireBulletsFlags_t m_nFlags;
#endif

	Vector m_vecDirShooting;
	Vector m_vecOrigin;
	Vector m_vecEntryPosition;
	Vector m_vecTraceRay;
};
extern CUtlLinkedList<CSimulatedBullet*> g_Bullets;
class CBulletManager : public CBaseEntity
{
	DECLARE_CLASS( CBulletManager, CBaseEntity );
public:
	~CBulletManager()
	{
		g_Bullets.PurgeAndDeleteElements();
	}
#ifdef CLIENT_DLL
	void ClientThink(void);
	int AddBullet(CSimulatedBullet *pBullet);
#else
	void Think(void);
	int AddBullet(CSimulatedBullet *pBullet, float flLatency=0.0f);
#endif
	unsigned short RemoveBullet(int index);
	void UpdateBulletStopSpeed(void);
	int BulletStopSpeed(void)
	{
		return m_iBulletStopSpeed;
	}
private:
	int m_iBulletStopSpeed;
};

game_shared\bullet_manager.cpp

Create this file and add it to both projects of the solution.

#include "cbase.h"
#include "util_shared.h"
#include "bullet_manager.h"
#include "shot_manipulator.h"
#include "tier0/vprof.h"

CBulletManager *g_pBulletManager;
CUtlLinkedList<CSimulatedBullet*> g_Bullets;

#ifdef CLIENT_DLL//-------------------------------------------------
#include "engine/ivdebugoverlay.h"
ConVar g_debug_client_bullets( "g_debug_client_bullets", "0", FCVAR_CHEAT );
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 );
#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 );



//==================================================
// Purpose:	Constructor
//==================================================
CSimulatedBullet::CSimulatedBullet()
{
	m_vecOrigin.Init();
	m_vecDirShooting.Init();
	m_flInitialBulletSpeed = m_flBulletSpeed = 0;
	m_flEntryDensity = 0.0f;
#ifndef CLIENT_DLL
	m_nFlags = (FireBulletsFlags_t)0;
	m_iDamage = 0;
#endif
	m_iAmmoType = 0;
}


//==================================================
// Purpose:	Constructor
//==================================================
CSimulatedBullet::CSimulatedBullet( CBaseEntity *pAttacker, CBaseEntity *pAdditionalIgnoreEnt,
						int ammoType, Vector &vecDirShooting, Vector &vecOrigin,
						bool bTraceHull
#ifndef CLIENT_DLL
						, int nFlags, CBaseEntity *pCaller, int iDamage
#endif
						)
{
	// Input validation
	Assert( pAttacker );
#ifndef CLIENT_DLL
	Assert( pCaller );
#endif

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

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

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

	// Not useful yet... may be useful for effects later
	//m_pTwoEnts = new CTraceFilterSkipTwoEntities(pAttacker,pAdditionalIgnoreEnt,COLLISION_GROUP_NONE);

	// Basic information about the bullet
	m_iAmmoType = ammoType;
	m_flInitialBulletSpeed = m_flBulletSpeed = GetAmmoDef()->GetAmmoOfIndex(ammoType)->bulletSpeed;
	m_vecDirShooting = vecDirShooting;
	m_vecOrigin = vecOrigin;
	m_bTraceHull = bTraceHull;
#ifndef CLIENT_DLL
	m_nFlags = (FireBulletsFlags_t)nFlags;
	m_iDamage = iDamage;
	m_hCaller = pCaller;
#endif
	m_flEntryDensity = 0.0f;
	m_vecTraceRay = vecDirShooting * m_flInitialBulletSpeed;
	m_flRayLength = m_flInitialBulletSpeed;
}



//==================================================
// Purpose:	Deconstructor
//==================================================
CSimulatedBullet::~CSimulatedBullet()
{
	delete m_pIgnoreList;
	delete m_pTwoEnts;
}
#ifdef CLIENT_DLL
bool FX_AffectRagdolls(Vector vecOrigin, Vector vecStart, int iDamageType);


//==================================================
// Purpose:	Simulates a bullet through a ray of its bullet speed
//==================================================
bool C_SimulatedBullet::SimulateBullet(void)
{
	VPROF( "C_SimulatedBullet::SimulateBullet" );
#else
bool CSimulatedBullet::SimulateBullet(float flLagCompensation/*=-1 (seconds)*/)
{
	VPROF( "CSimulatedBullet::SimulateBullet" );
#endif

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

	trace_t trace;
	Vector vecTraceStart(m_vecOrigin);

	Vector vecTraceRay;

	if(m_flRayLength==m_flBulletSpeed)
	{
		vecTraceRay = m_vecTraceRay;
	}
	else
	{
		vecTraceRay = m_vecTraceRay = m_vecDirShooting * m_flBulletSpeed;
		m_flRayLength = m_flBulletSpeed;
	}
#ifdef GAME_DLL
	if(flLagCompensation!=-1)
	{
		vecTraceRay *= flLagCompensation * 100;
	}
#endif

	m_vecOrigin += vecTraceRay;
	bool bInWater = (UTIL_PointContents(m_vecOrigin)&MASK_SPLITAREAPORTAL)==1;
	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_debug_client_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;
		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 );
		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, vecTraceRay))
					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;
		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 &vecTraceRay)
{
#ifdef CLIENT_DLL
	//TODO: penetration surface impact stuff
#endif //CLIENT_DLL
	Vector vecExitPosition = ptr.fractionleftsolid * vecTraceRay + 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;
}


//==================================================
// 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
			break;
		}
		case ONE_HIT_MODE:
		{
			return false;
		}
		default:
		{
			m_flBulletSpeed -= m_flBulletSpeed * m_flEntryDensity / sv_bullet_speed_modifier.GetFloat();
			break;
		}
	}
	return true;
}


//==================================================
// Purpose:	Simulate when a surface is hit
//==================================================
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
	int iDamageType = GetAmmoDef()->DamageType( m_iAmmoType );
	FX_AffectRagdolls(ptr.startpos, ptr.endpos, iDamageType);
	UTIL_ImpactTrace( &ptr, iDamageType );
#endif //CLIENT_DLL
	m_flEntryDensity = physprops->GetSurfaceData(ptr.surface.surfaceProps)->physics.density;
	if(ptr.DidHitNonWorldEntity())
	{
		if(ptr.m_pEnt!=m_hLastHit)
		{
			if(m_pIgnoreList->ShouldHitEntity(ptr.m_pEnt,MASK_SHOT))
			{
				DevMsg("Hit: %s\n",ptr.m_pEnt->GetClassname());
#ifndef CLIENT_DLL
				EntityImpact(ptr);
#endif
			}
		}
		else
			m_pIgnoreList->AddEntityToIgnore(ptr.m_pEnt);
		m_hLastHit = ptr.m_pEnt;
	}
	if(BulletManager()->BulletStopSpeed()==ONE_HIT_MODE)
	{
		return false;
	}
	return true;
}
#ifndef CLIENT_DLL



//==================================================
// Purpose:	Entity impact procedure
//==================================================
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 );		
	}
}


//==================================================
// Purpose:	Simulates all bullets every centisecond
//==================================================
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();
}


//==================================================
// Purpose:	Add bullet to linked list
//			Handle lag compensation with "prebullet simulation"
// Output:	Bullet's index (-1 for error)
//==================================================
#ifdef CLIENT_DLL
int CBulletManager::AddBullet(CSimulatedBullet *pBullet)
#else
int CBulletManager::AddBullet(CSimulatedBullet *pBullet, float flLatency/*=0.0f*/)
#endif
{
	if (pBullet->AmmoIndex() == -1)
	{
		DevMsg("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, flLatency );
	if(flLatency!=0.0f)
		g_Bullets[index]->SimulateBullet(flLatency);
	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
//==================================================
unsigned short CBulletManager::RemoveBullet(int index)
{
	unsigned short retVal = 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
	}
	return retVal;
}

game_shared\gamerules.cpp

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

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

game_shared\ammodef.h

Change the Ammo_t structure to the following:

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

	int					nFlags;

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

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

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

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

game_shared\ammodef.cpp

Add
float bulletSpeed,
Before both
float physicsForceImpulse,

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

game_shared\hl2mp/hl2mp_gamerules.cpp

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

Change GetAmmoDef to the following:

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

	return &def;
}

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

game_shared\baseentity_shared.cpp

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

Change CBaseEntity::FireBullets to the following:

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

#ifdef GAME_DLL
//	bool bDoImpacts = false;
	bool bDoTracers = false;
#endif
	for (int iShot = 0; iShot < info.m_iShots; iShot++)
	{
//		bool bHitWater = false;
//		bool bHitGlass = false;
		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(pAttacker, info.m_pAdditionalIgnoreEnt,
								info.m_iAmmoType, vecDir, vecSrc, bTraceHull
#ifndef CLIENT_DLL
								, info.m_nFlags, this, info.m_iDamage
#endif
								);
#ifdef CLIENT_DLL
		BulletManager()->AddBullet(pBullet);
#else
		BulletManager()->AddBullet(pBullet, info.m_flLatency);
#endif
		iSeed++;
	}

#ifdef GAME_DLL
	TE_HL2MPFireBullets( pAttacker?pAttacker->entindex():0,
		info.m_pAdditionalIgnoreEnt?info.m_pAdditionalIgnoreEnt->entindex():0,
		info.m_iAmmoType, info.m_vecSrc, info.m_vecDirShooting, bDoTracers, iEffectSeed, info.m_vecSpread.x, info.m_iShots);
	ApplyMultiDamage();
#endif
}

game_shared\shareddefs.h

Change the FireBulletsInfo_t structure to the following:

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

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

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

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

dlls/player_lagcompensation.cpp

Add the following function to this file:

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

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

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

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

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

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

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

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

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

In CLagCompensationManager::StartLagCompensation

Replace

	// Get true latency

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

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

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

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

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

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

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

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

With

	int targettick = GetTargetTick(player, cmd);

Add GetTargetTick();
After BUDGETFLAG_SERVER );

dlls/ilagcompensationmanager.h

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

dlls/hl2mp_dll/hl2mp_player.cpp

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

In CHL2MP_Player::FireBullets

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

cl_dll\clientmode_shared.cpp

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

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

dlls\te_hl2mp_shotgun_shot.h

Paste the following into this file:

//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//=============================================================================//

#ifndef TE_HL2MP_SHOTGUN_SHOT_H
#define TE_HL2MP_SHOTGUN_SHOT_H
#ifdef _WIN32
#pragma once
#endif


void TE_HL2MPFireBullets( 
	int	iAttacker,
	int iAdditionalIgnoreEnt,
	int iAmmoID,
	const Vector &vecOrigin,
	const Vector &vecDir,
	bool bDoTracers,
	int iSeed,
	float flSpread,
	int iShots );


#endif // TE_HL2MP_SHOTGUN_SHOT_H

dlls\te_hl2mp_shotgun_shot.cpp

#include "cbase.h"
#include "basetempentity.h"

#define NUM_BULLET_SEED_BITS 8

//-----------------------------------------------------------------------------
// Purpose: Display's a blood sprite
//-----------------------------------------------------------------------------
class CTEHL2MPFireBullets : public CBaseTempEntity
{
public:
	DECLARE_CLASS( CTEHL2MPFireBullets, CBaseTempEntity );
	DECLARE_SERVERCLASS();

					CTEHL2MPFireBullets( const char *name );
	virtual			~CTEHL2MPFireBullets( void );

public:
	CNetworkVar( int, m_iAttacker );
	CNetworkVar( int, m_iAdditionalIgnoreEnt );
	CNetworkVar( int, m_iAmmoID );
	CNetworkVector( m_vecOrigin );
	CNetworkVector( m_vecDir );
	CNetworkVar( bool, m_bDoTracers );
	CNetworkVar( int, m_iSeed );
	CNetworkVar( float, m_flSpread );
	CNetworkVar( int, m_iShots );
};

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *name - 
//-----------------------------------------------------------------------------
CTEHL2MPFireBullets::CTEHL2MPFireBullets( const char *name ) :
	CBaseTempEntity( name )
{
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CTEHL2MPFireBullets::~CTEHL2MPFireBullets( void )
{
}

IMPLEMENT_SERVERCLASS_ST_NOBASE(CTEHL2MPFireBullets, DT_TEHL2MPFireBullets)
	SendPropInt( SENDINFO( m_iAttacker ), 5, SPROP_UNSIGNED ), 	// max 32 players, see MAX_PLAYERS
	SendPropInt( SENDINFO( m_iAdditionalIgnoreEnt ), 16, SPROP_UNSIGNED ),
	SendPropInt( SENDINFO( m_iAmmoID ), 5, SPROP_UNSIGNED ),
	SendPropVector( SENDINFO( m_vecOrigin ), -1, SPROP_COORD ),
	SendPropVector( SENDINFO( m_vecDir ), -1 ),
	SendPropBool( SENDINFO( m_bDoTracers ) ),
	SendPropInt( SENDINFO( m_iSeed ), NUM_BULLET_SEED_BITS, SPROP_UNSIGNED ),
	SendPropFloat( SENDINFO( m_flSpread ), 10, 0, 0, 1 ),
	SendPropInt( SENDINFO( m_iShots ), 5, SPROP_UNSIGNED ),
END_SEND_TABLE()


// Singleton
static CTEHL2MPFireBullets g_TEHL2MPFireBullets( "Shotgun Shot" );


void TE_HL2MPFireBullets( 
	int	iAttacker,
	int iAdditionalIgnoreEnt,
	int iAmmoID,
	const Vector &vecOrigin,
	const Vector &vecDir,
	bool bDoTracers,
	int iSeed,
	float flSpread,
	int iShots )
{
	CRecipientFilter filter;
	filter.AddAllPlayers();
	filter.UsePredictionRules();

	g_TEHL2MPFireBullets.m_iAttacker = iAttacker;
	g_TEHL2MPFireBullets.m_iAdditionalIgnoreEnt = iAdditionalIgnoreEnt;
	g_TEHL2MPFireBullets.m_iAmmoID = iAmmoID;
	g_TEHL2MPFireBullets.m_vecOrigin = vecOrigin;
	g_TEHL2MPFireBullets.m_vecDir = vecDir;
	g_TEHL2MPFireBullets.m_bDoTracers = bDoTracers;
	g_TEHL2MPFireBullets.m_iSeed = iSeed;
	g_TEHL2MPFireBullets.m_flSpread = flSpread;
	g_TEHL2MPFireBullets.m_iShots = iShots;
	
	Assert( iSeed < (1 << NUM_BULLET_SEED_BITS) );
	
	g_TEHL2MPFireBullets.Create( filter, 0 );
}

cl_dll\c_te_hl2mp_shotgun_shot.cpp

#include "cbase.h"
#include "c_basetempentity.h"
#include <cliententitylist.h>
#include "ammodef.h"
#include "c_te_effect_dispatch.h"
#include "shot_manipulator.h"
#include "bullet_manager.h"

class C_TEHL2MPFireBullets : public C_BaseTempEntity
{
public:
	DECLARE_CLASS( C_TEHL2MPFireBullets, C_BaseTempEntity );
	DECLARE_CLIENTCLASS();

	virtual void	PostDataUpdate( DataUpdateType_t updateType );

	void CreateEffects( void );

public:
	int		m_iAttacker;
	int		m_iAdditionalIgnoreEnt;
	int		m_iAmmoID;
	Vector	m_vecOrigin;
	Vector	m_vecDir;
	bool	m_bDoTracers;
	int		m_iSeed;
	float	m_flSpread;
	int		m_iShots;
};

class CTraceFilterSkipPlayerAndViewModelOnly : public CTraceFilter
{
public:
	virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
	{
		C_BaseEntity *pEntity = EntityFromEntityHandle( pServerEntity );
		if( pEntity &&
			( dynamic_cast<C_BaseViewModel *>( pEntity ) != NULL ) ||
			( dynamic_cast<C_BasePlayer *>( pEntity ) != NULL ) )
		{
			return false;
		}
		else
		{
			return true;
		}
	}
};

void C_TEHL2MPFireBullets::CreateEffects( void )
{
	CAmmoDef*	pAmmoDef	= GetAmmoDef();

	if ( pAmmoDef == NULL )
		 return;

	C_BaseEntity *pEnt = ClientEntityList().GetEnt( m_iAttacker );

	if ( pEnt )
	{
		C_BaseCombatCharacter *pChar = pEnt->MyCombatCharacterPointer();

		if ( pChar && pChar->GetActiveWeapon() )
		{
			C_BaseCombatWeapon *pWpn = pChar->GetActiveWeapon();
			C_BaseEntity *additional = ClientEntityList().GetEnt( m_iAdditionalIgnoreEnt );

			if ( pWpn )
			{
				int iSeed = m_iSeed;
					
				CShotManipulator Manipulator( m_vecDir );

				for (int iShot = 0; iShot < m_iShots; iShot++)
				{
					RandomSeed( iSeed );	// init random system with this seed

					// Don't run the biasing code for the player at the moment.
					Vector vecDir = Manipulator.ApplySpread( Vector( m_flSpread, m_flSpread, m_flSpread ) );
					bool bTraceHull = ( m_iShots > 1 && iShot % 2 );
					C_SimulatedBullet *pBullet = new C_SimulatedBullet(pEnt, additional,
							m_iAmmoID, vecDir, m_vecOrigin, bTraceHull);
					BulletManager()->AddBullet(pBullet);

					iSeed++;
				}
			}
		}
	}

}

void C_TEHL2MPFireBullets::PostDataUpdate( DataUpdateType_t updateType )
{
	CreateEffects();
}


IMPLEMENT_CLIENTCLASS_EVENT( C_TEHL2MPFireBullets, DT_TEHL2MPFireBullets, CTEHL2MPFireBullets );


BEGIN_RECV_TABLE_NOBASE(C_TEHL2MPFireBullets, DT_TEHL2MPFireBullets )
	RecvPropInt( RECVINFO( m_iAttacker ) ),
	RecvPropInt( RECVINFO( m_iAdditionalIgnoreEnt ) ),
	RecvPropInt( RECVINFO( m_iAmmoID ) ),
	RecvPropVector( RECVINFO( m_vecOrigin ) ),
	RecvPropVector( RECVINFO( m_vecDir ) ),
	RecvPropBool( RECVINFO( m_bDoTracers ) ),
	RecvPropInt( RECVINFO( m_iSeed ) ),
	RecvPropFloat( RECVINFO( m_flSpread ) ),
	RecvPropInt( RECVINFO( m_iShots ) ),
END_RECV_TABLE()