Realistic Simulated Bullets: Difference between revisions
No edit summary |
|||
(19 intermediate revisions by 4 users not shown) | |||
Line 1: | Line 1: | ||
[[ | This article based on [[Simulated Bullets]]. | ||
{{note|Compile errors on SP MOD - OB - reported by djjonastybe on 07/01/2010}} | |||
; Changes | |||
: Modified algorithm of penetration | |||
: Added impacts of water and walls | |||
: Added bubbles and tracers | |||
: Added entity hit (EntityImpact() function is now complete) | |||
: Added ricochet | |||
: Bullet can lose speed | |||
: Speed changes now affects damage and impact force (but not for NPC or Player) | |||
: Removed unused stuff | |||
; Still to do | |||
:: Multiplayer | |||
:: Shotguns | |||
:: Invalid tracer position | |||
:: Tracer types | |||
:: Bullet mass | |||
:: Wind affection | |||
{{note|Although much of the code for the bullet_manager entity is shared client-side and server-side, the entity itself is not networked, but instead made into a server-side entity and client-side entity to run separately. The only thing being networked is the message to make the bullets.}} | |||
{{note| | {{note|Not done for HL2MP}} | ||
{{ | |||
{{toc-right}} | |||
== Procedure == | == Procedure == | ||
===game_shared\gamerules.cpp=== | |||
{| style="background-color: transparent;" | |||
| Add || <source lang=cpp>#include "bullet_manager.h"</source> | |||
|- | |||
| After || <source lang=cpp>#include "KeyValues.h"</source> | |||
|} | |||
---- | |||
{| style="background-color: transparent;" | |||
| Add || <source lang=cpp>g_pBulletManager = (CBulletManager*)CBaseEntity::Create( "bullet_manager", vec3_origin, vec3_angle );</source> | |||
|- | |||
| After || <source lang=cpp>g_pPlayerResource = (CPlayerResource*)CBaseEntity::Create( "player_manager", vec3_origin, vec3_angle );</source> | |||
|} | |||
===game_shared\ammodef.h=== | |||
Change the <code>Ammo_t</code> structure to the following: | |||
<source lang=cpp>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 | |||
};</source> | |||
---- | |||
In the <code>CAmmoDef</code> class, change the first two <code>AddAmmoType</code> function prototypes to the following: | |||
<source lang=cpp> 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 );</source> | |||
===game_shared\ammodef.cpp=== | |||
{| style="background:transparent;" | |||
| Add || <source lang=cpp>float bulletSpeed,</source> | |||
|- | |||
| Before both || <source lang=cpp>float physicsForceImpulse,</source> | |||
|} | |||
---- | |||
{| style="background:transparent;" | |||
| Add || <source lang=cpp>m_AmmoType[m_nAmmoIndex].bulletSpeed = bulletSpeed;</source> | |||
|- | |||
| Before both || <source lang=cpp>m_AmmoType[m_nAmmoIndex].physicsForceImpulse = physicsForceImpulse;</source> | |||
|} | |||
===game_shared\hl2\hl2_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: | |||
<source lang=cpp>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; | |||
}</source> | |||
As you can see, the bullet speed (start speed) is derived from the ftpersec of the bullet impulses. | |||
===game_shared\bullet_manager.cpp=== | ===game_shared\bullet_manager.cpp=== | ||
< | Create and add this file to server and client projects | ||
<div style="max-height:50em; overflow:auto;"> | |||
<source lang=cpp>// (More) Reallistic simulated bullets | |||
// | // | ||
// This code is originally based from article on Valve Developer Community | // This code is originally based from article on Valve Developer Community | ||
// The original code you can find by this link: | // The original code you can find by this link: | ||
// http://developer.valvesoftware.com/wiki/Simulated_Bullets | // http://developer.valvesoftware.com/wiki/Simulated_Bullets | ||
// NOTENOTE: Tested only on localhost. There maybe a latency errors and others | // NOTENOTE: Tested only on localhost. There maybe a latency errors and others | ||
// NOTENOTE: The simulation might be strange. | // NOTENOTE: The simulation might be strange. | ||
Line 77: | Line 178: | ||
"Density/(This Value) * (Distance Penetrated) = (Change in Speed)", | "Density/(This Value) * (Distance Penetrated) = (Change in Speed)", | ||
BulletSpeedModifierCallback ); | BulletSpeedModifierCallback ); | ||
static void UnrealRicochetCallback(ConVar *var, const char *pOldString) | |||
{ | |||
if(gpGlobals->maxClients > 1) | |||
{ | |||
var->Revert(); | |||
Msg("Cannot use unreal ricochet in multiplayer game\n"); | |||
} | |||
if(var->GetBool()) //To avoid math exception | |||
Warning("\nWarning! Enabling unreal ricochet may cause the game crash.\n\n"); | |||
} | |||
ConVar sv_bullet_unrealricochet( "sv_bullet_unrealricochet", "0", FCVAR_CHEAT | FCVAR_REPLICATED, "Unreal ricochet",UnrealRicochetCallback); | |||
Line 128: | Line 242: | ||
#endif | #endif | ||
bulletinfo = info; // | bulletinfo = info; // Setup Fire bullets information here | ||
p_eInfictor = pInfictor; // | p_eInfictor = pInfictor; // Setup inflictor | ||
// Create a list of entities with which this bullet does not collide. | // Create a list of entities with which this bullet does not collide. | ||
Line 146: | Line 260: | ||
m_iDamageType = GetAmmoDef()->DamageType( bulletinfo.m_iAmmoType ); | m_iDamageType = GetAmmoDef()->DamageType( bulletinfo.m_iAmmoType ); | ||
//m_szTracerName = (char*)p_eInfictor->GetTracerType(); | |||
// Basic information about the bullet | // Basic information about the bullet | ||
Line 156: | Line 273: | ||
#endif | #endif | ||
m_flEntryDensity = 0.0f; | m_flEntryDensity = 0.0f; | ||
m_vecTraceRay = m_vecDirShooting * m_flBulletSpeed; | m_vecTraceRay = m_vecOrigin + m_vecDirShooting * m_flBulletSpeed; | ||
m_flRayLength = m_flInitialBulletSpeed; | m_flRayLength = m_flInitialBulletSpeed; | ||
} | } | ||
Line 177: | Line 294: | ||
if(!IsFinite(m_flBulletSpeed)) | if(!IsFinite(m_flBulletSpeed)) | ||
return false;//prevent a weird crash | return false; //prevent a weird crash | ||
trace_t trace; | trace_t trace; | ||
if(m_flBulletSpeed <= 0) //Avoid errors; | if(m_flBulletSpeed <= 0) //Avoid errors; | ||
return false; | return false; | ||
if(!p_eInfictor) | |||
{ | |||
p_eInfictor = bulletinfo.m_pAttacker; | |||
if(!p_eInfictor) | |||
return false; | |||
} | } | ||
m_flRayLength = m_flBulletSpeed; | |||
m_flBulletSpeed += 0.8f * m_vecDirShooting.z; //TODO: Bullet mass | m_flBulletSpeed += 0.8f * m_vecDirShooting.z; //TODO: Bullet mass | ||
m_vecTraceRay = m_vecOrigin + m_vecDirShooting * m_flBulletSpeed; | |||
m_vecDirShooting.z -= 0.1 / m_flBulletSpeed; | m_vecDirShooting.z -= 0.1 / m_flBulletSpeed; | ||
Line 204: | Line 319: | ||
if(bulletinfo.m_flLatency!= 0 ) | if(bulletinfo.m_flLatency!= 0 ) | ||
{ | { | ||
m_vecTraceRay *= bulletinfo.m_flLatency * 100; | |||
} | } | ||
#endif | #endif | ||
if(! | if(!IsInWorld()) | ||
{ | { | ||
return false; | |||
} | } | ||
if( | if(bStuckInWall) | ||
return false; | return false; | ||
if(!m_bWasInWater) | if(!m_bWasInWater) | ||
{ | { | ||
WaterHit( | WaterHit(m_vecOrigin,m_vecTraceRay); | ||
} | } | ||
Line 227: | Line 340: | ||
if(m_bWasInWater) | if(m_bWasInWater) | ||
{ | { | ||
m_flBulletSpeed -= m_flBulletSpeed * 0. | m_flBulletSpeed -= m_flBulletSpeed * 0.6; | ||
#ifdef GAME_DLL | #ifdef GAME_DLL | ||
//This is a server stuff | //This is a server stuff | ||
UTIL_BubbleTrail( | UTIL_BubbleTrail(m_vecOrigin, m_vecTraceRay, 5 ); | ||
#endif | #endif | ||
} | } | ||
# | #ifndef CLIENT_DLL | ||
if(m_bWasInWater) | |||
{ | |||
CEffectData tracerData; | CEffectData tracerData; | ||
tracerData.m_vStart = | tracerData.m_vStart = m_vecOrigin; | ||
tracerData.m_vOrigin = | tracerData.m_vOrigin = m_vecTraceRay; | ||
tracerData.m_fFlags = TRACER_TYPE_WATERBULLET; | |||
DispatchEffect( "TracerSound", tracerData ); | DispatchEffect( "TracerSound", tracerData ); | ||
} | |||
#endif | #endif | ||
bool bulletSpeedCheck; | bool bulletSpeedCheck; | ||
bulletSpeedCheck = false; | |||
if(m_bTraceHull) | |||
UTIL_TraceHull( m_vecOrigin, m_vecTraceRay, Vector(-3, -3, -3), Vector(3, 3, 3), MASK_SHOT, m_pIgnoreList, &trace ); | |||
else | |||
UTIL_TraceLine( m_vecOrigin, m_vecTraceRay, MASK_SHOT, m_pIgnoreList, &trace ); | |||
if(!m_bWasInWater) | |||
{ | { | ||
UTIL_Tracer( m_vecOrigin, trace.endpos, p_eInfictor->entindex(), TRACER_DONT_USE_ATTACHMENT, 0, true,p_eInfictor->GetTracerType()); | |||
} | |||
#ifdef CLIENT_DLL | #ifdef CLIENT_DLL | ||
if(g_debug_client_bullets.GetBool()) | |||
{ | |||
debugoverlay->AddLineOverlay( trace.startpos, trace.endpos, 255, 0, 0, true, 10.0f ); | |||
} | |||
#else | #else | ||
if(g_debug_bullets.GetBool()) | |||
{ | |||
NDebugOverlay::Line( trace.startpos, trace.endpos, 255, 255, 255, true, 10.0f ); | |||
} | |||
// Now hit all triggers along the ray that respond to shots... | |||
// Clip the ray to the first collided solid returned from traceline | |||
CTakeDamageInfo triggerInfo( p_eInfictor, bulletinfo.m_pAttacker, bulletinfo.m_iDamage, GetDamageType() ); | |||
CalculateBulletDamageForce( &triggerInfo, bulletinfo.m_iAmmoType, m_vecDirShooting, trace.endpos ); | |||
triggerInfo.ScaleDamageForce( bulletinfo.m_flDamageForceScale ); | |||
triggerInfo.SetAmmoType( bulletinfo.m_iAmmoType ); | |||
BulletManager()->SendTraceAttackToTriggers( triggerInfo, trace.startpos, trace.endpos, m_vecDirShooting ); | |||
#endif | |||
if (trace.fraction == 1.0f) | |||
{ | |||
m_vecOrigin += m_vecDirShooting * m_flBulletSpeed; //Do the WAY | |||
CEffectData data; | |||
data.m_vStart = trace.startpos; | |||
data.m_vOrigin = trace.endpos; | |||
data.m_nDamageType = GetDamageType(); | |||
DispatchEffect( "RagdollImpact", data ); | |||
return true; | |||
} | |||
else | |||
{ | |||
EntityImpact(trace); | EntityImpact(trace); | ||
if (trace.m_pEnt == p_eInfictor) //HACK: Remove bullet if we hit self (for frag grenades) | |||
return false; | |||
if(!(trace.surface.flags&SURF_SKY)) | if(!(trace.surface.flags&SURF_SKY)) | ||
Line 290: | Line 421: | ||
if(!AllSolid(trace)) | if(!AllSolid(trace)) | ||
return false; | return false; | ||
m_vecOrigin += m_vecDirShooting * m_flBulletSpeed; //Do the WAY | |||
bulletSpeedCheck = true; | bulletSpeedCheck = true; | ||
} | } | ||
Line 296: | Line 430: | ||
if(!EndSolid(trace)) | if(!EndSolid(trace)) | ||
return false; | return false; | ||
bulletSpeedCheck = true; | bulletSpeedCheck = true; | ||
} | } | ||
Line 302: | Line 437: | ||
if(!StartSolid(trace)) | if(!StartSolid(trace)) | ||
return false; | return false; | ||
m_vecOrigin += m_vecDirShooting * m_flBulletSpeed; //Do the WAY | |||
bulletSpeedCheck = true; | bulletSpeedCheck = true; | ||
} | } | ||
Line 307: | Line 445: | ||
{ | { | ||
//don't do a bullet speed check for not touching anything | //don't do a bullet speed check for not touching anything | ||
} | } | ||
} | } | ||
else | else | ||
{ | { | ||
return false; //Through sky? No. | |||
} | } | ||
} | |||
if(sv_bullet_unrealricochet.GetBool()) //Fun bullet ricochet fix | |||
{ | |||
delete m_pIgnoreList; //Prevent causing of memory leak | |||
m_pIgnoreList = new CTraceFilterSimpleList(COLLISION_GROUP_NONE); | |||
} | |||
if(bulletSpeedCheck) | |||
{ | |||
if(m_flBulletSpeed<=BulletManager()->BulletStopSpeed()) | |||
{ | { | ||
return false; | return false; | ||
} | } | ||
} | |||
return true; | return true; | ||
} | } | ||
Line 342: | Line 476: | ||
bool CSimulatedBullet::StartSolid(trace_t &ptr) | bool CSimulatedBullet::StartSolid(trace_t &ptr) | ||
{ | { | ||
switch(BulletManager()->BulletStopSpeed()) | switch(BulletManager()->BulletStopSpeed()) | ||
{ | { | ||
Line 359: | Line 489: | ||
default: | default: | ||
{ | { | ||
float flPenetrationDistance = VectorLength(AbsEntry - AbsExit); | |||
m_flBulletSpeed -= flPenetrationDistance * m_flEntryDensity / sv_bullet_speed_modifier.GetFloat(); | m_flBulletSpeed -= flPenetrationDistance * m_flEntryDensity / sv_bullet_speed_modifier.GetFloat(); | ||
return true; | return true; | ||
} | } | ||
Line 411: | Line 531: | ||
{ | { | ||
m_vecEntryPosition = ptr.endpos; | m_vecEntryPosition = ptr.endpos; | ||
#ifndef CLIENT_DLL | #ifndef CLIENT_DLL | ||
int soundEntChannel = ( bulletinfo.m_nFlags&FIRE_BULLETS_TEMPORARY_DANGER_SOUND ) ? SOUNDENT_CHANNEL_BULLET_IMPACT : SOUNDENT_CHANNEL_UNSPECIFIED; | int soundEntChannel = ( bulletinfo.m_nFlags&FIRE_BULLETS_TEMPORARY_DANGER_SOUND ) ? SOUNDENT_CHANNEL_BULLET_IMPACT : SOUNDENT_CHANNEL_UNSPECIFIED; | ||
Line 416: | Line 537: | ||
CSoundEnt::InsertSound( SOUND_BULLET_IMPACT, m_vecEntryPosition, 200, 0.5, p_eInfictor, soundEntChannel ); | CSoundEnt::InsertSound( SOUND_BULLET_IMPACT, m_vecEntryPosition, 200, 0.5, p_eInfictor, soundEntChannel ); | ||
#endif | #endif | ||
if(FStrEq(ptr.surface.name,"tools/toolsblockbullets")) | if(FStrEq(ptr.surface.name,"tools/toolsblockbullets")) | ||
{ | { | ||
Line 479: | Line 601: | ||
float fldot = m_vecDirShooting.Dot( ptr.plane.normal ); //Getting angles from lasttrace | float fldot = m_vecDirShooting.Dot( ptr.plane.normal ); //Getting angles from lasttrace | ||
bool bMustDoRico = (fldot > -MAX_RICO_DOT_ANGLE && GetBulletSpeedRatio() > MIN_RICO_SPEED_PERC); // We can't do richochet when bullet has lowest speed | |||
if (sv_bullet_unrealricochet.GetBool() && p_eInfictor->IsPlayer()) //Cheat is only for player,yet =) | |||
bMustDoRico = true; | |||
if ( bMustDoRico ) | |||
{ | { | ||
if( | if(!sv_bullet_unrealricochet.GetBool()) | ||
{ | { | ||
m_flBulletSpeed *= (1.0f / -fldot) * random->RandomFloat(0.005,0.1); | if(gpGlobals->maxClients == 1) //Use more simple for multiplayer | ||
{ | |||
m_flBulletSpeed *= (1.0f / -fldot) * random->RandomFloat(0.005,0.1); | |||
} | |||
else | |||
{ | |||
m_flBulletSpeed *= (1.0f / -fldot) * 0.025; | |||
} | |||
} | } | ||
else | else | ||
{ | { | ||
m_flBulletSpeed *= | m_flBulletSpeed *= 0.9; | ||
} | } | ||
reflect = m_vecDirShooting + ( ptr.plane.normal * ( fldot*-2.0f ) ); //reflecting | reflect = m_vecDirShooting + ( ptr.plane.normal * ( fldot*-2.0f ) ); //reflecting | ||
if(gpGlobals->maxClients == 1) //Use more simple for multiplayer | if(gpGlobals->maxClients == 1 && !sv_bullet_unrealricochet.GetBool()) //Use more simple for multiplayer | ||
{ | { | ||
reflect[0] += random->RandomFloat( fldot, -fldot ); | reflect[0] += random->RandomFloat( fldot, -fldot ); | ||
Line 498: | Line 632: | ||
reflect[2] += random->RandomFloat( fldot, -fldot ); | reflect[2] += random->RandomFloat( fldot, -fldot ); | ||
} | } | ||
m_flEntryDensity *= 0.2; | |||
m_vecDirShooting = reflect; | m_vecDirShooting = reflect; | ||
m_vecOrigin = (ptr.endpos + m_vecDirShooting*1.1) + m_vecDirShooting * m_flBulletSpeed; | m_vecOrigin = ptr.endpos+m_vecDirShooting;//(ptr.endpos + m_vecDirShooting*1.1) + m_vecDirShooting * m_flBulletSpeed; | ||
} | } | ||
else | else | ||
{ | { | ||
if (flPenetrationDistance > DesiredDistance || ptr.IsDispSurface()) | if (flPenetrationDistance > DesiredDistance || ptr.IsDispSurface()) //|| !bulletinfo.m_bMustPenetrate | ||
{ | { | ||
bStuckInWall = true; | bStuckInWall = true; | ||
Line 536: | Line 672: | ||
if(gpGlobals->maxClients == 1) //Use more simple for multiplayer | if(gpGlobals->maxClients == 1) //Use more simple for multiplayer | ||
{ | { | ||
m_vecDirShooting[0] += (random->RandomFloat( -flPenetrationDistance, flPenetrationDistance ))*0. | m_vecDirShooting[0] += (random->RandomFloat( -flPenetrationDistance, flPenetrationDistance ))*0.03; | ||
m_vecDirShooting[1] += (random->RandomFloat( -flPenetrationDistance, flPenetrationDistance ))*0. | m_vecDirShooting[1] += (random->RandomFloat( -flPenetrationDistance, flPenetrationDistance ))*0.03; | ||
m_vecDirShooting[2] += (random->RandomFloat( -flPenetrationDistance, flPenetrationDistance ))*0. | m_vecDirShooting[2] += (random->RandomFloat( -flPenetrationDistance, flPenetrationDistance ))*0.03; | ||
VectorNormalize(m_vecDirShooting); | |||
} | } | ||
m_vecOrigin = AbsExit + m_vecDirShooting | m_vecOrigin = AbsExit+m_vecDirShooting; | ||
} | } | ||
} | } | ||
#ifdef GAME_DLL | |||
//Cancel making dust underwater: | //Cancel making dust underwater: | ||
if(!m_bWasInWater) | if(!m_bWasInWater) | ||
Line 550: | Line 689: | ||
UTIL_ImpactTrace( &ptr, GetDamageType() ); | UTIL_ImpactTrace( &ptr, GetDamageType() ); | ||
} | } | ||
#endif | |||
m_flBulletSpeed -= flPenetrationDistance * m_flEntryDensity / sv_bullet_speed_modifier.GetFloat(); | |||
if(BulletManager()->BulletStopSpeed()==ONE_HIT_MODE) | if(BulletManager()->BulletStopSpeed()==ONE_HIT_MODE) | ||
Line 571: | Line 713: | ||
return false; | return false; | ||
int nMinSplashSize = GetAmmoDef()->MinSplashSize(GetAmmoTypeIndex()); | int nMinSplashSize = GetAmmoDef()->MinSplashSize(GetAmmoTypeIndex()) * (1.5 - GetBulletSpeedRatio()); | ||
int nMaxSplashSize = GetAmmoDef()->MaxSplashSize(GetAmmoTypeIndex()); | int nMaxSplashSize = GetAmmoDef()->MaxSplashSize(GetAmmoTypeIndex()) * (1.5 - GetBulletSpeedRatio()); //High speed - small splash | ||
CEffectData data; | CEffectData data; | ||
Line 600: | Line 742: | ||
//Hit inflicted once to avoid perfomance errors | //Hit inflicted once to avoid perfomance errors | ||
if(p_eInfictor-> | if(!p_eInfictor->IsPlayer()) | ||
{ | { | ||
if (ptr.m_pEnt->IsPlayer()) | if (ptr.m_pEnt->IsPlayer()) | ||
Line 755: | Line 897: | ||
#endif | #endif | ||
} | } | ||
}</ | }</source> | ||
</div> | |||
===game_shared\bullet_manager.h=== | ===game_shared\bullet_manager.h=== | ||
< | Create and add this file to server and client projects | ||
<div style="max-height:50em; overflow:auto;"> | |||
<source lang=cpp> | |||
// (More) Reallistic simulated bullets | // (More) Reallistic simulated bullets | ||
// | // | ||
Line 765: | Line 911: | ||
// The original code you can find by this link: | // The original code you can find by this link: | ||
// http://developer.valvesoftware.com/wiki/Simulated_Bullets | // http://developer.valvesoftware.com/wiki/Simulated_Bullets | ||
#include "ammodef.h" | #include "ammodef.h" | ||
Line 878: | Line 1,022: | ||
extern CUtlLinkedList<CSimulatedBullet*> g_Bullets; //Bullet list | extern CUtlLinkedList<CSimulatedBullet*> g_Bullets; //Bullet list | ||
Line 908: | Line 1,051: | ||
int m_iBulletStopSpeed; | int m_iBulletStopSpeed; | ||
}; | }; | ||
</ | </source> | ||
</div> | |||
===game_shared\baseentity_shared.cpp=== | ===game_shared\baseentity_shared.cpp=== | ||
< | Replace FireBullets() function with following code: | ||
<source lang=cpp> | |||
void CBaseEntity::FireBullets( const FireBulletsInfo_t &info ) | void CBaseEntity::FireBullets( const FireBulletsInfo_t &info ) | ||
{ | { | ||
Line 1,002: | Line 1,147: | ||
if ( bDoServerEffects == false ) | if ( bDoServerEffects == false ) | ||
{ | { | ||
TE_HL2MPFireBullets( entindex(), tr.startpos, info.m_vecDirShooting, info.m_iAmmoType, iEffectSeed, info.m_iShots, info.m_vecSpread.x, bDoTracers, bDoImpacts ); | // For now it doesn't work | ||
//TE_HL2MPFireBullets( entindex(), tr.startpos, info.m_vecDirShooting, info.m_iAmmoType, iEffectSeed, info.m_iShots, info.m_vecSpread.x, bDoTracers, bDoImpacts ); | |||
} | } | ||
#endif | #endif | ||
ApplyMultiDamage(); | ApplyMultiDamage(); | ||
} | } | ||
</source> | |||
If you get errors about undeclared identifiers in baseentity_shared.cpp: | |||
</ | Add <source lang=cpp>#include "bullet_manager.h"</source> | ||
After <source lang=cpp>#include "coordsize.h"</source> | |||
===game_shared\shareddefs.h=== | |||
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> | |||
|} | |||
===cl_dll\c_te_hl2mp_shotgun_shot.cpp=== | ===cl_dll\c_te_hl2mp_shotgun_shot.cpp=== | ||
It's undone and not rewritten. Please feel free to do helpful changes for it. | It's undone and not rewritten. Please feel free to do helpful changes for it. | ||
[[Category:Free source code]] | |||
[[Category:Weapons programming]] | |||
Latest revision as of 23:35, 4 October 2012
This article based on Simulated Bullets.

- Changes
- Modified algorithm of penetration
- Added impacts of water and walls
- Added bubbles and tracers
- Added entity hit (EntityImpact() function is now complete)
- Added ricochet
- Bullet can lose speed
- Speed changes now affects damage and impact force (but not for NPC or Player)
- Removed unused stuff
- Still to do
-
- Multiplayer
- Shotguns
- Invalid tracer position
- Tracer types
- Bullet mass
- Wind affection


Procedure
Add | #include "bullet_manager.h"
|
After | #include "KeyValues.h"
|
Add | g_pBulletManager = (CBulletManager*)CBaseEntity::Create( "bullet_manager", vec3_origin, vec3_angle );
|
After | g_pPlayerResource = (CPlayerResource*)CBaseEntity::Create( "player_manager", vec3_origin, vec3_angle );
|
Change the Ammo_t
structure to the following:
struct Ammo_t
{
char *pName;
int nDamageType;
int eTracerType;
float physicsForceImpulse;
float bulletSpeed;
int nMinSplashSize;
int nMaxSplashSize;
int nFlags;
// Values for player/NPC damage and carrying capability
// If the integers are set, they override the CVars
int pPlrDmg; // CVar for player damage amount
int pNPCDmg; // CVar for NPC damage amount
int pMaxCarry; // CVar for maximum number can carry
const ConVar* pPlrDmgCVar; // CVar for player damage amount
const ConVar* pNPCDmgCVar; // CVar for NPC damage amount
const ConVar* pMaxCarryCVar; // CVar for maximum number can carry
};
In the CAmmoDef
class, change the first two AddAmmoType
function prototypes to the following:
void AddAmmoType(char const* name, int damageType, int tracerType,
int plr_dmg, int npc_dmg, int carry,
float bulletSpeed, float physicsForceImpulse,
int nFlags, int minSplashSize = 4,
int maxSplashSize = 8 );
void AddAmmoType(char const* name, int damageType, int tracerType,
char const* plr_cvar, char const* npc_var,
char const* carry_cvar, float bulletSpeed,
float physicsForceImpulse, int nFlags,
int minSplashSize = 4, int maxSplashSize = 8 );
Add | float bulletSpeed,
|
Before both | float physicsForceImpulse,
|
Add | m_AmmoType[m_nAmmoIndex].bulletSpeed = bulletSpeed;
|
Before both | m_AmmoType[m_nAmmoIndex].physicsForceImpulse = physicsForceImpulse;
|
Add | #define BULLET_SPEED(ftpersec) 0.12*ftpersec //inches per centisecond
|
Before | CAmmoDef *GetAmmoDef()
|
Change GetAmmoDef
to the following:
CAmmoDef *GetAmmoDef()
{
static CAmmoDef def;
static bool bInitted = false;
if ( !bInitted )
{
bInitted = true;
// Name Damage Tracer PlrDmg NPCDmg MaxCarry Bulletspeed Physics Force Impulse Flags
def.AddAmmoType("AR2", DMG_BULLET, TRACER_LINE_AND_WHIZ, 0, 0, 60, BULLET_SPEED(1225), BULLET_IMPULSE(200, 1225), 0 );
def.AddAmmoType("AR2AltFire", DMG_DISSOLVE, TRACER_NONE, 0, 0, 3, 0, 0, 0 );
def.AddAmmoType("Pistol", DMG_BULLET, TRACER_LINE_AND_WHIZ, 0, 0, 150, BULLET_SPEED(1225), BULLET_IMPULSE(200, 1225), 0 );
def.AddAmmoType("SMG1", DMG_BULLET, TRACER_LINE_AND_WHIZ, 0, 0, 225, BULLET_SPEED(1225), BULLET_IMPULSE(200, 1225), 0 );
def.AddAmmoType("357", DMG_BULLET, TRACER_LINE_AND_WHIZ, 0, 0, 12, BULLET_SPEED(5000), BULLET_IMPULSE(800, 5000), 0 );
def.AddAmmoType("XBowBolt", DMG_BULLET, TRACER_LINE, 0, 0, 10, BULLET_SPEED(8000), BULLET_IMPULSE(800, 8000), 0 );
def.AddAmmoType("Buckshot", DMG_BULLET | DMG_BUCKSHOT, TRACER_LINE, 0, 0, 30, BULLET_SPEED(1200), BULLET_IMPULSE(400, 1200), 0 );
def.AddAmmoType("RPG_Round", DMG_BURN, TRACER_NONE, 0, 0, 3, 0, 0, 0 );
def.AddAmmoType("SMG1_Grenade", DMG_BURN, TRACER_NONE, 0, 0, 3, 0, 0, 0 );
def.AddAmmoType("Grenade", DMG_BURN, TRACER_NONE, 0, 0, 5, 0, 0, 0 );
def.AddAmmoType("slam", DMG_BURN, TRACER_NONE, 0, 0, 5, 0, 0, 0 );
}
return &def;
}
As you can see, the bullet speed (start speed) is derived from the ftpersec of the bullet impulses.
Create and add this file to server and client projects
// (More) Reallistic simulated bullets
//
// This code is originally based from article on Valve Developer Community
// The original code you can find by this link:
// http://developer.valvesoftware.com/wiki/Simulated_Bullets
// NOTENOTE: Tested only on localhost. There maybe a latency errors and others
// NOTENOTE: The simulation might be strange.
#include "cbase.h"
#include "util_shared.h"
#include "bullet_manager.h"
#include "effect_dispatch_data.h"
#include "tier0/vprof.h"
#include "decals.h"
CBulletManager *g_pBulletManager;
CUtlLinkedList<CSimulatedBullet*> g_Bullets;
#ifdef CLIENT_DLL//-------------------------------------------------
#include "engine/ivdebugoverlay.h"
#include "c_te_effect_dispatch.h"
ConVar g_debug_client_bullets( "g_debug_client_bullets", "0", FCVAR_CHEAT );
extern void FX_PlayerTracer( Vector& start, Vector& end);
#else//-------------------------------------------------------------
#include "te_effect_dispatch.h"
#include "soundent.h"
#include "player_pickup.h"
#include "ilagcompensationmanager.h"
ConVar g_debug_bullets( "g_debug_bullets", "0", FCVAR_CHEAT, "Debug of bullet simulation\nThe white line shows the bullet trail.\nThe red line shows not passed penetration test.\nThe green line shows passed penetration test. Turn developer mode for more information." );
#endif//------------------------------------------------------------
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#define MAX_RICO_DOT_ANGLE 0.15f //Maximum dot allowed for any ricochet
#define MIN_RICO_SPEED_PERC 0.55f //Minimum speed percent allowed for any ricochet
static void BulletSpeedModifierCallback(ConVar *var, const char *pOldString)
{
if(var->GetFloat()==0.0f) //To avoid math exception
var->Revert();
}
ConVar sv_bullet_speed_modifier( "sv_bullet_speed_modifier", "700.000000", (FCVAR_ARCHIVE | FCVAR_REPLICATED),
"Density/(This Value) * (Distance Penetrated) = (Change in Speed)",
BulletSpeedModifierCallback );
static void UnrealRicochetCallback(ConVar *var, const char *pOldString)
{
if(gpGlobals->maxClients > 1)
{
var->Revert();
Msg("Cannot use unreal ricochet in multiplayer game\n");
}
if(var->GetBool()) //To avoid math exception
Warning("\nWarning! Enabling unreal ricochet may cause the game crash.\n\n");
}
ConVar sv_bullet_unrealricochet( "sv_bullet_unrealricochet", "0", FCVAR_CHEAT | FCVAR_REPLICATED, "Unreal ricochet",UnrealRicochetCallback);
static void BulletStopSpeedCallback(ConVar *var, const char *pOldString)
{
int val = var->GetInt();
if(val<ONE_HIT_MODE)
var->Revert();
else if(BulletManager())
BulletManager()->UpdateBulletStopSpeed();
}
ConVar sv_bullet_stop_speed( "sv_bullet_stop_speed", "40", (FCVAR_ARCHIVE | FCVAR_REPLICATED),
"Integral speed at which to remove the bullet from the bullet queue\n-1 is butter mode\n-2 is 1 hit mode",
BulletStopSpeedCallback );
LINK_ENTITY_TO_CLASS( bullet_manager, CBulletManager );
//==================================================
// Purpose: Constructor
//==================================================
CSimulatedBullet::CSimulatedBullet()
{
m_vecOrigin.Init();
m_vecDirShooting.Init();
m_flInitialBulletSpeed = m_flBulletSpeed = 0;
m_flEntryDensity = 0.0f;
bStuckInWall = false;
m_iDamageType = 2;
}
//==================================================
// Purpose: Constructor
//==================================================
CSimulatedBullet::CSimulatedBullet( FireBulletsInfo_t info,Vector newdir,CBaseEntity *pInfictor, CBaseEntity *pAdditionalIgnoreEnt,
bool bTraceHull
#ifndef CLIENT_DLL
, CBaseEntity *pCaller
#endif
)
{
// Input validation
Assert( pInfictor );
#ifndef CLIENT_DLL
Assert( pCaller );
#endif
bulletinfo = info; // Setup Fire bullets information here
p_eInfictor = pInfictor; // Setup inflictor
// Create a list of entities with which this bullet does not collide.
m_pIgnoreList = new CTraceFilterSimpleList(COLLISION_GROUP_NONE);
Assert( m_pIgnoreList );
bStuckInWall = false;
// Don't collide with the entity firing the bullet.
m_pIgnoreList->AddEntityToIgnore(p_eInfictor);
// Don't collide with some optionally-specified entity.
if(pAdditionalIgnoreEnt!=NULL)
m_pIgnoreList->AddEntityToIgnore(pAdditionalIgnoreEnt);
m_iDamageType = GetAmmoDef()->DamageType( bulletinfo.m_iAmmoType );
//m_szTracerName = (char*)p_eInfictor->GetTracerType();
// Basic information about the bullet
m_flInitialBulletSpeed = m_flBulletSpeed = GetAmmoDef()->GetAmmoOfIndex(bulletinfo.m_iAmmoType)->bulletSpeed;
m_vecDirShooting = newdir;
m_vecOrigin = bulletinfo.m_vecSrc;
m_bTraceHull = bTraceHull;
#ifndef CLIENT_DLL
m_hCaller = pCaller;
#endif
m_flEntryDensity = 0.0f;
m_vecTraceRay = m_vecOrigin + m_vecDirShooting * m_flBulletSpeed;
m_flRayLength = m_flInitialBulletSpeed;
}
//==================================================
// Purpose: Deconstructor
//==================================================
CSimulatedBullet::~CSimulatedBullet()
{
delete m_pIgnoreList;
}
//==================================================
// Purpose: Simulates a bullet through a ray of its bullet speed
//==================================================
bool CSimulatedBullet::SimulateBullet(void)
{
VPROF( "C_SimulatedBullet::SimulateBullet" );
if(!IsFinite(m_flBulletSpeed))
return false; //prevent a weird crash
trace_t trace;
if(m_flBulletSpeed <= 0) //Avoid errors;
return false;
if(!p_eInfictor)
{
p_eInfictor = bulletinfo.m_pAttacker;
if(!p_eInfictor)
return false;
}
m_flRayLength = m_flBulletSpeed;
m_flBulletSpeed += 0.8f * m_vecDirShooting.z; //TODO: Bullet mass
m_vecTraceRay = m_vecOrigin + m_vecDirShooting * m_flBulletSpeed;
m_vecDirShooting.z -= 0.1 / m_flBulletSpeed;
#ifdef GAME_DLL
if(bulletinfo.m_flLatency!= 0 )
{
m_vecTraceRay *= bulletinfo.m_flLatency * 100;
}
#endif
if(!IsInWorld())
{
return false;
}
if(bStuckInWall)
return false;
if(!m_bWasInWater)
{
WaterHit(m_vecOrigin,m_vecTraceRay);
}
m_bWasInWater = (UTIL_PointContents(m_vecOrigin)& MASK_WATER )?true:false;
if(m_bWasInWater)
{
m_flBulletSpeed -= m_flBulletSpeed * 0.6;
#ifdef GAME_DLL
//This is a server stuff
UTIL_BubbleTrail(m_vecOrigin, m_vecTraceRay, 5 );
#endif
}
#ifndef CLIENT_DLL
if(m_bWasInWater)
{
CEffectData tracerData;
tracerData.m_vStart = m_vecOrigin;
tracerData.m_vOrigin = m_vecTraceRay;
tracerData.m_fFlags = TRACER_TYPE_WATERBULLET;
DispatchEffect( "TracerSound", tracerData );
}
#endif
bool bulletSpeedCheck;
bulletSpeedCheck = false;
if(m_bTraceHull)
UTIL_TraceHull( m_vecOrigin, m_vecTraceRay, Vector(-3, -3, -3), Vector(3, 3, 3), MASK_SHOT, m_pIgnoreList, &trace );
else
UTIL_TraceLine( m_vecOrigin, m_vecTraceRay, MASK_SHOT, m_pIgnoreList, &trace );
if(!m_bWasInWater)
{
UTIL_Tracer( m_vecOrigin, trace.endpos, p_eInfictor->entindex(), TRACER_DONT_USE_ATTACHMENT, 0, true,p_eInfictor->GetTracerType());
}
#ifdef CLIENT_DLL
if(g_debug_client_bullets.GetBool())
{
debugoverlay->AddLineOverlay( trace.startpos, trace.endpos, 255, 0, 0, true, 10.0f );
}
#else
if(g_debug_bullets.GetBool())
{
NDebugOverlay::Line( trace.startpos, trace.endpos, 255, 255, 255, true, 10.0f );
}
// Now hit all triggers along the ray that respond to shots...
// Clip the ray to the first collided solid returned from traceline
CTakeDamageInfo triggerInfo( p_eInfictor, bulletinfo.m_pAttacker, bulletinfo.m_iDamage, GetDamageType() );
CalculateBulletDamageForce( &triggerInfo, bulletinfo.m_iAmmoType, m_vecDirShooting, trace.endpos );
triggerInfo.ScaleDamageForce( bulletinfo.m_flDamageForceScale );
triggerInfo.SetAmmoType( bulletinfo.m_iAmmoType );
BulletManager()->SendTraceAttackToTriggers( triggerInfo, trace.startpos, trace.endpos, m_vecDirShooting );
#endif
if (trace.fraction == 1.0f)
{
m_vecOrigin += m_vecDirShooting * m_flBulletSpeed; //Do the WAY
CEffectData data;
data.m_vStart = trace.startpos;
data.m_vOrigin = trace.endpos;
data.m_nDamageType = GetDamageType();
DispatchEffect( "RagdollImpact", data );
return true;
}
else
{
EntityImpact(trace);
if (trace.m_pEnt == p_eInfictor) //HACK: Remove bullet if we hit self (for frag grenades)
return false;
if(!(trace.surface.flags&SURF_SKY))
{
if(trace.allsolid)//in solid
{
if(!AllSolid(trace))
return false;
m_vecOrigin += m_vecDirShooting * m_flBulletSpeed; //Do the WAY
bulletSpeedCheck = true;
}
else if(trace.fraction!=1.0f)//hit solid
{
if(!EndSolid(trace))
return false;
bulletSpeedCheck = true;
}
else if(trace.startsolid)//exit solid
{
if(!StartSolid(trace))
return false;
m_vecOrigin += m_vecDirShooting * m_flBulletSpeed; //Do the WAY
bulletSpeedCheck = true;
}
else
{
//don't do a bullet speed check for not touching anything
}
}
else
{
return false; //Through sky? No.
}
}
if(sv_bullet_unrealricochet.GetBool()) //Fun bullet ricochet fix
{
delete m_pIgnoreList; //Prevent causing of memory leak
m_pIgnoreList = new CTraceFilterSimpleList(COLLISION_GROUP_NONE);
}
if(bulletSpeedCheck)
{
if(m_flBulletSpeed<=BulletManager()->BulletStopSpeed())
{
return false;
}
}
return true;
}
//==================================================
// Purpose: Simulates when a solid is exited
//==================================================
bool CSimulatedBullet::StartSolid(trace_t &ptr)
{
switch(BulletManager()->BulletStopSpeed())
{
case BUTTER_MODE:
{
//Do nothing to bullet speed
return true;
}
case ONE_HIT_MODE:
{
return false;
}
default:
{
float flPenetrationDistance = VectorLength(AbsEntry - AbsExit);
m_flBulletSpeed -= flPenetrationDistance * m_flEntryDensity / sv_bullet_speed_modifier.GetFloat();
return true;
}
}
return true;
}
//==================================================
// Purpose: Simulates when a solid is being passed through
//==================================================
bool CSimulatedBullet::AllSolid(trace_t &ptr)
{
switch(BulletManager()->BulletStopSpeed())
{
case BUTTER_MODE:
{
//Do nothing to bullet speed
return true;
}
case ONE_HIT_MODE:
{
return false;
}
default:
{
m_flBulletSpeed -= m_flBulletSpeed * m_flEntryDensity / sv_bullet_speed_modifier.GetFloat();
return true;
}
}
return true;
}
//==================================================
// Purpose: Simulate when a surface is hit
//==================================================
bool CSimulatedBullet::EndSolid(trace_t &ptr)
{
m_vecEntryPosition = ptr.endpos;
#ifndef CLIENT_DLL
int soundEntChannel = ( bulletinfo.m_nFlags&FIRE_BULLETS_TEMPORARY_DANGER_SOUND ) ? SOUNDENT_CHANNEL_BULLET_IMPACT : SOUNDENT_CHANNEL_UNSPECIFIED;
CSoundEnt::InsertSound( SOUND_BULLET_IMPACT, m_vecEntryPosition, 200, 0.5, p_eInfictor, soundEntChannel );
#endif
if(FStrEq(ptr.surface.name,"tools/toolsblockbullets"))
{
return false;
}
m_flEntryDensity = physprops->GetSurfaceData(ptr.surface.surfaceProps)->physics.density;
trace_t rtr;
Vector vecEnd = m_vecEntryPosition + m_vecDirShooting * 32; //32 units
//Doing now test of penetration
UTIL_TraceLine(m_vecEntryPosition + m_vecDirShooting * 1.1, vecEnd, MASK_SHOT, m_pIgnoreList, &rtr);
AbsEntry = m_vecEntryPosition;
AbsExit = rtr.startpos;
float flPenetrationDistance = VectorLength(AbsEntry - AbsExit);
DesiredDistance = 0.0f;
surfacedata_t *p_penetrsurf = physprops->GetSurfaceData( ptr.surface.surfaceProps );
switch (p_penetrsurf->game.material)
{
case CHAR_TEX_WOOD:
DesiredDistance = 9.0f; // 9 units in hammer
break;
case CHAR_TEX_GRATE:
DesiredDistance = 6.0f; // 6 units in hammer
break;
case CHAR_TEX_CONCRETE:
DesiredDistance = 4.0f; // 4 units in hammer
break;
case CHAR_TEX_TILE:
DesiredDistance = 5.0f; // 5 units in hammer
break;
case CHAR_TEX_COMPUTER:
DesiredDistance = 5.0f; // 5 units in hammer
break;
case CHAR_TEX_GLASS:
DesiredDistance = 8.0f; // maximum 8 units in hammer.
break;
case CHAR_TEX_VENT:
DesiredDistance = 4.0f; // 4 units in hammer and no more(!)
break;
case CHAR_TEX_METAL:
DesiredDistance = 5.0f; // 2 units in hammer. We cannot penetrate a really 'fat' metal wall. Corners are good.
break;
case CHAR_TEX_PLASTIC:
DesiredDistance = 8.0f; // 8 units in hammer: Plastic can more
break;
case CHAR_TEX_BLOODYFLESH:
DesiredDistance = 16.0f; // 16 units in hammer
break;
case CHAR_TEX_FLESH:
DesiredDistance = 16.0f; // 16 units in hammer
break;
case CHAR_TEX_DIRT:
DesiredDistance = 6.0f; // 6 units in hammer: >4 cm of plaster can be penetrated
break;
}
Vector reflect;
float fldot = m_vecDirShooting.Dot( ptr.plane.normal ); //Getting angles from lasttrace
bool bMustDoRico = (fldot > -MAX_RICO_DOT_ANGLE && GetBulletSpeedRatio() > MIN_RICO_SPEED_PERC); // We can't do richochet when bullet has lowest speed
if (sv_bullet_unrealricochet.GetBool() && p_eInfictor->IsPlayer()) //Cheat is only for player,yet =)
bMustDoRico = true;
if ( bMustDoRico )
{
if(!sv_bullet_unrealricochet.GetBool())
{
if(gpGlobals->maxClients == 1) //Use more simple for multiplayer
{
m_flBulletSpeed *= (1.0f / -fldot) * random->RandomFloat(0.005,0.1);
}
else
{
m_flBulletSpeed *= (1.0f / -fldot) * 0.025;
}
}
else
{
m_flBulletSpeed *= 0.9;
}
reflect = m_vecDirShooting + ( ptr.plane.normal * ( fldot*-2.0f ) ); //reflecting
if(gpGlobals->maxClients == 1 && !sv_bullet_unrealricochet.GetBool()) //Use more simple for multiplayer
{
reflect[0] += random->RandomFloat( fldot, -fldot );
reflect[1] += random->RandomFloat( fldot, -fldot );
reflect[2] += random->RandomFloat( fldot, -fldot );
}
m_flEntryDensity *= 0.2;
m_vecDirShooting = reflect;
m_vecOrigin = ptr.endpos+m_vecDirShooting;//(ptr.endpos + m_vecDirShooting*1.1) + m_vecDirShooting * m_flBulletSpeed;
}
else
{
if (flPenetrationDistance > DesiredDistance || ptr.IsDispSurface()) //|| !bulletinfo.m_bMustPenetrate
{
bStuckInWall = true;
#ifdef GAME_DLL
if(g_debug_bullets.GetBool())
{
NDebugOverlay::Line( AbsEntry, AbsExit, 255, 0, 0, true, 10.0f );
if(!ptr.IsDispSurface())
DevMsg("Cannot penetrate surface, The distance(%f) > %f \n",flPenetrationDistance,DesiredDistance);
else
DevMsg("Displacement penetration was tempolary disabled\n");
}
#endif
}
else
{
trace_t tr;
UTIL_TraceLine(AbsExit + m_vecDirShooting * 1.1 ,AbsEntry,MASK_SHOT,m_pIgnoreList,&tr);
UTIL_ImpactTrace( &tr, GetDamageType() ); // On exit
#ifdef GAME_DLL
if(g_debug_bullets.GetBool())
{
NDebugOverlay::Line( AbsEntry, AbsExit, 0, 255, 0, true, 10.0f );
}
#endif
if(gpGlobals->maxClients == 1) //Use more simple for multiplayer
{
m_vecDirShooting[0] += (random->RandomFloat( -flPenetrationDistance, flPenetrationDistance ))*0.03;
m_vecDirShooting[1] += (random->RandomFloat( -flPenetrationDistance, flPenetrationDistance ))*0.03;
m_vecDirShooting[2] += (random->RandomFloat( -flPenetrationDistance, flPenetrationDistance ))*0.03;
VectorNormalize(m_vecDirShooting);
}
m_vecOrigin = AbsExit+m_vecDirShooting;
}
}
#ifdef GAME_DLL
//Cancel making dust underwater:
if(!m_bWasInWater)
{
UTIL_ImpactTrace( &ptr, GetDamageType() );
}
#endif
m_flBulletSpeed -= flPenetrationDistance * m_flEntryDensity / sv_bullet_speed_modifier.GetFloat();
if(BulletManager()->BulletStopSpeed()==ONE_HIT_MODE)
{
return false;
}
return true;
}
//-----------------------------------------------------------------------------
// analog of HandleShotImpactingWater
//-----------------------------------------------------------------------------
bool CSimulatedBullet::WaterHit(const Vector &vecStart,const Vector &vecEnd)
{
trace_t waterTrace;
// Trace again with water enabled
UTIL_TraceLine( vecStart, vecEnd, (MASK_SHOT|CONTENTS_WATER|CONTENTS_SLIME), m_pIgnoreList, &waterTrace );
// See if this is the point we entered
if ( ( enginetrace->GetPointContents( waterTrace.endpos - Vector(0,0,0.1f) ) & (CONTENTS_WATER|CONTENTS_SLIME) ) == 0 )
return false;
int nMinSplashSize = GetAmmoDef()->MinSplashSize(GetAmmoTypeIndex()) * (1.5 - GetBulletSpeedRatio());
int nMaxSplashSize = GetAmmoDef()->MaxSplashSize(GetAmmoTypeIndex()) * (1.5 - GetBulletSpeedRatio()); //High speed - small splash
CEffectData data;
data.m_vOrigin = waterTrace.endpos;
data.m_vNormal = waterTrace.plane.normal;
data.m_flScale = random->RandomFloat( nMinSplashSize, nMaxSplashSize );
if ( waterTrace.contents & CONTENTS_SLIME )
{
data.m_fFlags |= FX_WATER_IN_SLIME;
}
DispatchEffect( "gunshotsplash", data );
return true;
}
//==================================================
// Purpose: Entity impact procedure
//==================================================
void CSimulatedBullet::EntityImpact(trace_t &ptr)
{
if(bStuckInWall)
return;
if (ptr.m_pEnt != NULL)
{
//Hit inflicted once to avoid perfomance errors
if(!p_eInfictor->IsPlayer())
{
if (ptr.m_pEnt->IsPlayer())
{
if (m_pIgnoreList->ShouldHitEntity(ptr.m_pEnt,MASK_SHOT))
{
m_pIgnoreList->AddEntityToIgnore(ptr.m_pEnt);
}
else
{
return;
}
}
}
if(ptr.m_pEnt == m_hLastHit)
return;
m_hLastHit = ptr.m_pEnt;
float flActualDamage = g_pGameRules->GetAmmoDamage( p_eInfictor, ptr.m_pEnt, bulletinfo.m_iAmmoType );
float flActualForce = bulletinfo.m_flDamageForceScale;
if (!ptr.m_pEnt->IsPlayer() || !ptr.m_pEnt->IsNPC())
{
//To make it more reallistic
float fldot = m_vecDirShooting.Dot( ptr.plane.normal );
//We affecting damage by angle. If we has lower angle of reflection, doing lower damage.
flActualDamage *= -fldot;
flActualForce *= -fldot;
//But we not doing it on the players or NPC's
}
flActualDamage *= GetBulletSpeedRatio(); //And also affect damage by speed modifications
flActualForce *= GetBulletSpeedRatio(); //Slow bullet - bad force...
DevMsg("Hit: %s, Damage: %f, Force: %f \n",ptr.m_pEnt->GetClassname(),flActualDamage,flActualForce);
CTakeDamageInfo dmgInfo( p_eInfictor, bulletinfo.m_pAttacker, flActualDamage, GetDamageType() );
CalculateBulletDamageForce( &dmgInfo, bulletinfo.m_iAmmoType, m_vecDirShooting, ptr.endpos );
dmgInfo.ScaleDamageForce( flActualForce );
dmgInfo.SetAmmoType( bulletinfo.m_iAmmoType );
ptr.m_pEnt->DispatchTraceAttack( dmgInfo, bulletinfo.m_vecDirShooting, &ptr );
#ifdef GAME_DLL
ApplyMultiDamage(); //It's requried
if ( GetAmmoDef()->Flags(GetAmmoTypeIndex()) & AMMO_FORCE_DROP_IF_CARRIED )
{
// Make sure if the player is holding this, he drops it
Pickup_ForcePlayerToDropThisObject( ptr.m_pEnt );
}
#endif
}
}
//==================================================
// Purpose: Simulates all bullets every centisecond
//==================================================
#ifndef CLIENT_DLL
void CBulletManager::Think(void)
#else
void CBulletManager::ClientThink(void)
#endif
{
unsigned short iNext=0;
for ( unsigned short i=g_Bullets.Head(); i != g_Bullets.InvalidIndex(); i=iNext )
{
iNext = g_Bullets.Next( i );
if ( !g_Bullets[i]->SimulateBullet() )
RemoveBullet( i );
}
if(g_Bullets.Head()!=g_Bullets.InvalidIndex())
{
#ifdef CLIENT_DLL
SetNextClientThink( gpGlobals->curtime + 0.01f );
#else
SetNextThink( gpGlobals->curtime + 0.01f );
#endif
}
}
//==================================================
// Purpose: Called by sv_bullet_stop_speed callback to keep track of resources
//==================================================
void CBulletManager::UpdateBulletStopSpeed(void)
{
m_iBulletStopSpeed = sv_bullet_stop_speed.GetInt();
}
#ifndef CLIENT_DLL
void CBulletManager::SendTraceAttackToTriggers( const CTakeDamageInfo &info, const Vector& start, const Vector& end, const Vector& dir )
{
TraceAttackToTriggers(info,start,end,dir);
}
#endif
//==================================================
// Purpose: Add bullet to linked list
// Handle lag compensation with "prebullet simulation"
// Output: Bullet's index (-1 for error)
//==================================================
int CBulletManager::AddBullet(CSimulatedBullet *pBullet)
{
if (pBullet->GetAmmoTypeIndex() == -1)
{
Msg("ERROR: Undefined ammo type!\n");
return -1;
}
unsigned short index = g_Bullets.AddToTail(pBullet);
#ifdef CLIENT_DLL
DevMsg( "Client Bullet Created (%i)\n", index);
if(g_Bullets.Count()==1)
{
SetNextClientThink(gpGlobals->curtime + 0.01f);
}
#else
DevMsg( "Bullet Created (%i) LagCompensation %f\n", index, pBullet->bulletinfo.m_flLatency );
if(pBullet->bulletinfo.m_flLatency !=0.0f)
pBullet->SimulateBullet(); //Pre-simulation
if(g_Bullets.Count()==1)
{
SetNextThink(gpGlobals->curtime + 0.01f);
}
#endif
return index;
}
//==================================================
// Purpose: Remove the bullet at index from the linked list
// Output: Next index
//==================================================
void CBulletManager::RemoveBullet(int index)
{
g_Bullets.Next(index);
#ifdef CLIENT_DLL
DevMsg("Client ");
#endif
DevMsg( "Bullet Removed (%i)\n", index );
g_Bullets.Remove(index);
if(g_Bullets.Count()==0)
{
#ifdef CLIENT_DLL
SetNextClientThink( TICK_NEVER_THINK );
#else
SetNextThink( TICK_NEVER_THINK );
#endif
}
}
Create and add this file to server and client projects
// (More) Reallistic simulated bullets
//
// This code is originally based from article on Valve Developer Community
// The original code you can find by this link:
// http://developer.valvesoftware.com/wiki/Simulated_Bullets
#include "ammodef.h"
#include "takedamageinfo.h"
#define BUTTER_MODE -1
#define ONE_HIT_MODE -2
#ifdef CLIENT_DLL//-----------------------
class C_BulletManager;
extern C_BulletManager *g_pBulletManager;
#define CBulletManager C_BulletManager
#define CSimulatedBullet C_SimulatedBullet
#else//-----------------------------------
class CBulletManager;
extern CBulletManager *g_pBulletManager;
#endif//----------------------------------
inline CBulletManager *BulletManager()
{
return g_pBulletManager;
}
extern ConVar g_debug_bullets;
class CSimulatedBullet
{
public:
CSimulatedBullet();
CSimulatedBullet( FireBulletsInfo_t info,Vector newdir,CBaseEntity *pInfictor, CBaseEntity *pAdditionalIgnoreEnt,
bool bTraceHull
#ifndef CLIENT_DLL
, CBaseEntity *pCaller
#endif
);
~CSimulatedBullet();
inline float GetBulletSpeedRatio(void) //The percent of bullet speed
{
return m_flBulletSpeed/m_flInitialBulletSpeed;
}
inline bool IsInWorld(void) const
{
if (m_vecOrigin.x >= MAX_COORD_INTEGER) return false;
if (m_vecOrigin.y >= MAX_COORD_INTEGER) return false;
if (m_vecOrigin.z >= MAX_COORD_INTEGER) return false;
if (m_vecOrigin.x <= MIN_COORD_INTEGER) return false;
if (m_vecOrigin.y <= MIN_COORD_INTEGER) return false;
if (m_vecOrigin.z <= MIN_COORD_INTEGER) return false;
return true;
}
bool StartSolid(trace_t &ptr); //Exits solid
bool AllSolid(trace_t &ptr); //continues in solid
bool EndSolid(trace_t &ptr); //Enters solid
bool WaterHit(const Vector &vecStart,const Vector &vecEnd); //Tests water collision
bool SimulateBullet(void); //Main function of bullet simulation
void EntityImpact(trace_t &ptr); //Impact test
inline int GetDamageType(void) const
{
return m_iDamageType;
}
inline int GetAmmoTypeIndex(void) const
{
return bulletinfo.m_iAmmoType;
}
private:
bool m_bTraceHull; //Trace hull?
bool m_bWasInWater;
CTraceFilterSimpleList *m_pIgnoreList; //already hit
#ifndef CLIENT_DLL
CUtlVector<CBaseEntity *> m_CompensationConsiderations; //Couldn't resist
#endif
EHANDLE m_hCaller;
EHANDLE m_hLastHit; //Last hit (I think it doesn't work)
float m_flBulletSpeed; //The changeable bullet speed
float m_flEntryDensity; //Sets when doing penetration test
float m_flInitialBulletSpeed;
float m_flRayLength;
float DesiredDistance; //Sets when doing penetration test
bool m_bPenetrated;
int m_iDamageType;
Vector m_vecDirShooting; //The bullet direction with applied spread
Vector m_vecOrigin; //Bullet origin
Vector m_vecEntryPosition; //Sets when doing penetration test
Vector m_vecTraceRay;
Vector AbsEntry;
Vector AbsExit;
CBaseEntity *p_eInfictor;
public:
FireBulletsInfo_t bulletinfo;
private:
bool bStuckInWall; // Indicates bullet
};
extern CUtlLinkedList<CSimulatedBullet*> g_Bullets; //Bullet list
class CBulletManager : public CBaseEntity
{
DECLARE_CLASS( CBulletManager, CBaseEntity );
public:
~CBulletManager()
{
g_Bullets.PurgeAndDeleteElements();
}
int AddBullet(CSimulatedBullet *pBullet);
#ifdef CLIENT_DLL
void ClientThink(void);
#else
void Think(void);
void SendTraceAttackToTriggers( const CTakeDamageInfo &info, const Vector& start, const Vector& end, const Vector& dir );
#endif
void RemoveBullet(int index); //Removes bullet.
void UpdateBulletStopSpeed(void); //Updates bullet speed
int BulletStopSpeed(void) //returns speed that the bullet must be removed
{
return m_iBulletStopSpeed;
}
private:
int m_iBulletStopSpeed;
};
Replace FireBullets() function with following code:
void CBaseEntity::FireBullets( const FireBulletsInfo_t &info )
{
// Make sure given a valid bullet type
if (info.m_iAmmoType == -1)
{
DevMsg("ERROR: Undefined ammo type!\n");
return;
}
static int tracerCount;
//trace_t tr;
CAmmoDef* pAmmoDef = GetAmmoDef();
int nDamageType = pAmmoDef->DamageType(info.m_iAmmoType);
int nAmmoFlags = pAmmoDef->Flags(info.m_iAmmoType);
int iPlayerDamage = info.m_iPlayerDamage;
if ( iPlayerDamage == 0 )
{
if ( nAmmoFlags & AMMO_INTERPRET_PLRDAMAGE_AS_DAMAGE_TO_PLAYER )
{
iPlayerDamage = pAmmoDef->PlrDamage( info.m_iAmmoType );
}
}
// the default attacker is ourselves
CBaseEntity *pAttacker = info.m_pAttacker ? info.m_pAttacker : this;
// Make sure we don't have a dangling damage target from a recursive call
if ( g_MultiDamage.GetTarget() != NULL )
{
ApplyMultiDamage();
}
ClearMultiDamage();
g_MultiDamage.SetDamageType( nDamageType | DMG_NEVERGIB );
// Prediction is only usable on players
int iSeed = 0;
if ( IsPlayer() )
{
iSeed = CBaseEntity::GetPredictionRandomSeed() & 255;
}
//-----------------------------------------------------
// Set up our shot manipulator.
//-----------------------------------------------------
CShotManipulator Manipulator( info.m_vecDirShooting );
for (int iShot = 0; iShot < info.m_iShots; iShot++)
{
Vector vecDir;
// Prediction is only usable on players
if ( IsPlayer() )
{
RandomSeed( iSeed ); // init random system with this seed
}
// If we're firing multiple shots, and the first shot has to be bang on target, ignore spread
if ( iShot == 0 && info.m_iShots > 1 && (info.m_nFlags & FIRE_BULLETS_FIRST_SHOT_ACCURATE) )
{
vecDir = Manipulator.GetShotDirection();
}
else
{
// Don't run the biasing code for the player at the moment.
vecDir = Manipulator.ApplySpread( info.m_vecSpread );
}
Vector vecSrc(info.m_vecSrc);
bool bTraceHull = ( IsPlayer() && info.m_iShots > 1 && iShot % 2 );
CSimulatedBullet *pBullet = new CSimulatedBullet(info,vecDir,pAttacker,info.m_pAdditionalIgnoreEnt, bTraceHull
#ifndef CLIENT_DLL
,this
#endif
);
BulletManager()->AddBullet(pBullet);
iSeed++;
}
#if defined( HL2MP ) && defined( GAME_DLL )
if ( bDoServerEffects == false )
{
// For now it doesn't work
//TE_HL2MPFireBullets( entindex(), tr.startpos, info.m_vecDirShooting, info.m_iAmmoType, iEffectSeed, info.m_iShots, info.m_vecSpread.x, bDoTracers, bDoImpacts );
}
#endif
ApplyMultiDamage();
}
If you get errors about undeclared identifiers in baseentity_shared.cpp:
Add
#include "bullet_manager.h"
After
#include "coordsize.h"
Change the FireBulletsInfo_t structure to the following:
struct FireBulletsInfo_t
{
FireBulletsInfo_t()
{
m_iShots = 1;
m_vecSpread.Init( 0, 0, 0 );
m_flDistance = 8192;
m_iTracerFreq = 4;
m_iDamage = 0;
m_iPlayerDamage = 0;
m_pAttacker = NULL;
m_nFlags = 0;
m_pAdditionalIgnoreEnt = NULL;
m_flDamageForceScale = 1.0f;
#ifdef _DEBUG
m_iAmmoType = -1;
m_vecSrc.Init( VEC_T_NAN, VEC_T_NAN, VEC_T_NAN );
m_vecDirShooting.Init( VEC_T_NAN, VEC_T_NAN, VEC_T_NAN );
#endif
m_flLatency = 0.0f;
}
FireBulletsInfo_t( int nShots, const Vector &vecSrc, const Vector &vecDir, const Vector &vecSpread, float flDistance, int nAmmoType )
{
m_iShots = nShots;
m_vecSrc = vecSrc;
m_vecDirShooting = vecDir;
m_vecSpread = vecSpread;
m_flDistance = flDistance;
m_iAmmoType = nAmmoType;
m_iTracerFreq = 4;
m_iDamage = 0;
m_iPlayerDamage = 0;
m_pAttacker = NULL;
m_nFlags = 0;
m_pAdditionalIgnoreEnt = NULL;
m_flDamageForceScale = 1.0f;
m_flLatency = 0.0f;
}
int m_iShots;
Vector m_vecSrc;
Vector m_vecDirShooting;
Vector m_vecSpread;
float m_flDistance;
int m_iAmmoType;
int m_iTracerFreq;
int m_iDamage;
int m_iPlayerDamage; // Damage to be used instead of m_iDamage if we hit a player
int m_nFlags; // See FireBulletsFlags_t
float m_flDamageForceScale;
float m_flLatency;
CBaseEntity *m_pAttacker;
CBaseEntity *m_pAdditionalIgnoreEnt;
};
dlls/player_lagcompensation.cpp
Add the following function to this file:
int GetTargetTick(CBasePlayer *player,CUserCmd *cmd)
{
static CBasePlayer *lastPlayer;
static int lastTarget;
if(player==NULL)
{
lastPlayer = NULL;
lastTarget = 0;
}
if(player==lastPlayer)
return lastTarget;
// Get true latency
lastTarget = 0;
// correct is the amout of time we have to correct game time
float correct = 0.0f;
INetChannelInfo *nci = engine->GetPlayerNetInfo( player->entindex() );
if ( nci )
{
// add network latency
correct+= nci->GetLatency( FLOW_OUTGOING );
}
// calc number of view interpolation ticks - 1
int lerpTicks = TIME_TO_TICKS( player->m_fLerpTime );
// add view interpolation latency see C_BaseEntity::GetInterpolationAmount()
correct += TICKS_TO_TIME( lerpTicks );
// check bouns [0,sv_maxunlag]
correct = clamp( correct, 0.0f, sv_maxunlag.GetFloat() );
// correct tick send by player
int targettick = cmd->tick_count - lerpTicks;
// calc difference between tick send by player and our latency based tick
float deltaTime = correct - TICKS_TO_TIME(gpGlobals->tickcount - targettick);
if ( fabs( deltaTime ) > 0.2f )
{
// difference between cmd time and latency is too big > 200ms, use time correction based on latency
// DevMsg("StartLagCompensation: delta too big (%.3f)\n", deltaTime );
targettick = gpGlobals->tickcount - TIME_TO_TICKS( correct );
}
lastPlayer = player;
lastTarget = targettick;
timeRetrieved = gpGlobals->tickcount;
return targettick;
}
In CLagCompensationManager::StartLagCompensation
Replace
// Get true latency
// correct is the amout of time we have to correct game time
float correct = 0.0f;
INetChannelInfo *nci = engine->GetPlayerNetInfo( player->entindex() );
if ( nci )
{
// add network latency
correct+= nci->GetLatency( FLOW_OUTGOING );
}
// calc number of view interpolation ticks - 1
int lerpTicks = TIME_TO_TICKS( player->m_fLerpTime );
// add view interpolation latency see C_BaseEntity::GetInterpolationAmount()
correct += TICKS_TO_TIME( lerpTicks );
// check bouns [0,sv_maxunlag]
correct = clamp( correct, 0.0f, sv_maxunlag.GetFloat() );
// correct tick send by player
int targettick = cmd->tick_count - lerpTicks;
// calc difference between tick send by player and our latency based tick
float deltaTime = correct - TICKS_TO_TIME(gpGlobals->tickcount - targettick);
if ( fabs( deltaTime ) > 0.2f )
{
// difference between cmd time and latency is too big > 200ms, use time correction based on latency
// DevMsg("StartLagCompensation: delta too big (%.3f)\n", deltaTime );
targettick = gpGlobals->tickcount - TIME_TO_TICKS( correct );
}
With
int targettick = GetTargetTick(player, cmd);
Add | GetTargetTick(); |
After | BUDGETFLAG_SERVER ); |
dlls/ilagcompensationmanager.h
Add | int GetTargetTick(CBasePlayer *player=NULL,CUserCmd *cmd=NULL);
|
Before | extern ILagCompensationManager *lagcompensation;
|
dlls/hl2mp_dll/hl2mp_player.cpp
Add | #include "ilagcompensationmanager.h"
|
After | #include "SoundEmitterSystem/isoundemittersystembase.h"
|
In CHL2MP_Player::FireBullets
Add | if(gpGlobals->maxClients!=1)
modinfo.m_flLatency = TICKS_TO_TIME(GetTargetTick(this,m_pCurrentCommand));
|
Before | BaseClass::FireBullets( modinfo );
|
Add | #include "bullet_manager.h"
|
After | #include "c_team.h"
|
Add | g_pBulletManager = (C_BulletManager *)CreateEntityByName("bullet_manager");
ClientEntityList().AddNonNetworkableEntity(g_pBulletManager);
|
After | enginesound->SetPlayerDSP( filter, 0, true );
|
cl_dll\c_te_hl2mp_shotgun_shot.cpp
It's undone and not rewritten. Please feel free to do helpful changes for it.