Realistic Simulated Bullets: Difference between revisions
Jump to navigation
Jump to search
Note:This code is incomplete. Feel free to make helpful changes.
No edit summary |
No edit summary |
||
Line 4: | Line 4: | ||
This article based on [[Simulated_Bullets]]. | This article based on [[Simulated_Bullets]]. | ||
{{note|This code is | This code in some parts is more complete version. Changes: | ||
- Modified algorithm of penetration | |||
- Added impacts of water and walls | |||
- Added bubbles | |||
- Added entity hit (EntityImpact() function is now complete) | |||
- Added ricochet | |||
- Speed changes now affects on damage and impact force (but not for NPC or Player) | |||
- Removed unused stuff | |||
Undone parts: | |||
- Tracer and it's whiz sound | |||
- Multiplayer | |||
{{note|This code is incomplete. Feel free to make helpful changes.}} | |||
== Procedure == | == Procedure == | ||
Do the similar procedure from original article (Link above), | Do the similar procedure from original article (Link above). | ||
===game_shared\bullet_manager.cpp=== | |||
Replace all code with following: | |||
<pre>// (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 | |||
// | |||
// Modified by 'SoapyMan' | |||
// TODO: Tracers | |||
// NOTENOTE: Tested only on localhost. There maybe a latency errors and others | |||
// NOTENOTE: The simulation might be strange. | |||
#include "cbase.h" | |||
#include "util_shared.h" | |||
#include "bullet_manager.h" | |||
#include "effect_dispatch_data.h" | |||
#include "tier0/vprof.h" | |||
#include "decals.h" | |||
CBulletManager *g_pBulletManager; | |||
CUtlLinkedList<CSimulatedBullet*> g_Bullets; | |||
#ifdef CLIENT_DLL//------------------------------------------------- | |||
#include "engine/ivdebugoverlay.h" | |||
#include "c_te_effect_dispatch.h" | |||
ConVar g_debug_client_bullets( "g_debug_client_bullets", "0", FCVAR_CHEAT ); | |||
extern void FX_PlayerTracer( Vector& start, Vector& end); | |||
#else//------------------------------------------------------------- | |||
#include "te_effect_dispatch.h" | |||
#include "soundent.h" | |||
#include "player_pickup.h" | |||
#include "ilagcompensationmanager.h" | |||
ConVar g_debug_bullets( "g_debug_bullets", "0", FCVAR_CHEAT, "Debug of bullet simulation\nThe white line shows the bullet trail.\nThe red line shows not passed penetration test.\nThe green line shows passed penetration test. Turn developer mode for more information." ); | |||
#endif//------------------------------------------------------------ | |||
// memdbgon must be the last include file in a .cpp file!!! | |||
#include "tier0/memdbgon.h" | |||
#define MAX_RICO_DOT_ANGLE 0.15f //Maximum dot allowed for any ricochet | |||
#define MIN_RICO_SPEED_PERC 0.55f //Minimum speed percent allowed for any ricochet | |||
static void BulletSpeedModifierCallback(ConVar *var, const char *pOldString) | |||
{ | |||
if(var->GetFloat()==0.0f) //To avoid math exception | |||
var->Revert(); | |||
} | |||
ConVar sv_bullet_speed_modifier( "sv_bullet_speed_modifier", "700.000000", (FCVAR_ARCHIVE | FCVAR_REPLICATED), | |||
"Density/(This Value) * (Distance Penetrated) = (Change in Speed)", | |||
BulletSpeedModifierCallback ); | |||
static void BulletStopSpeedCallback(ConVar *var, const char *pOldString) | |||
{ | |||
int val = var->GetInt(); | |||
if(val<ONE_HIT_MODE) | |||
var->Revert(); | |||
else if(BulletManager()) | |||
BulletManager()->UpdateBulletStopSpeed(); | |||
} | |||
ConVar sv_bullet_stop_speed( "sv_bullet_stop_speed", "40", (FCVAR_ARCHIVE | FCVAR_REPLICATED), | |||
"Integral speed at which to remove the bullet from the bullet queue\n-1 is butter mode\n-2 is 1 hit mode", | |||
BulletStopSpeedCallback ); | |||
LINK_ENTITY_TO_CLASS( bullet_manager, CBulletManager ); | |||
//================================================== | |||
// Purpose: Constructor | |||
//================================================== | |||
CSimulatedBullet::CSimulatedBullet() | |||
{ | |||
m_vecOrigin.Init(); | |||
m_vecDirShooting.Init(); | |||
m_flInitialBulletSpeed = m_flBulletSpeed = 0; | |||
m_flEntryDensity = 0.0f; | |||
bStuckInWall = false; | |||
m_iDamageType = 2; | |||
} | |||
//================================================== | |||
// Purpose: Constructor | |||
//================================================== | |||
CSimulatedBullet::CSimulatedBullet( FireBulletsInfo_t info,Vector newdir,CBaseEntity *pInfictor, CBaseEntity *pAdditionalIgnoreEnt, | |||
bool bTraceHull | |||
#ifndef CLIENT_DLL | |||
, CBaseEntity *pCaller | |||
#endif | |||
) | |||
{ | |||
// Input validation | |||
Assert( pInfictor ); | |||
#ifndef CLIENT_DLL | |||
Assert( pCaller ); | |||
#endif | |||
bulletinfo = info; // Store Fire bullets information here | |||
p_eInfictor = pInfictor; // Store inflictor | |||
// Create a list of entities with which this bullet does not collide. | |||
m_pIgnoreList = new CTraceFilterSimpleList(COLLISION_GROUP_NONE); | |||
Assert( m_pIgnoreList ); | |||
bStuckInWall = false; | |||
// Don't collide with the entity firing the bullet. | |||
m_pIgnoreList->AddEntityToIgnore(p_eInfictor); | |||
// Don't collide with some optionally-specified entity. | |||
if(pAdditionalIgnoreEnt!=NULL) | |||
m_pIgnoreList->AddEntityToIgnore(pAdditionalIgnoreEnt); | |||
m_iDamageType = GetAmmoDef()->DamageType( bulletinfo.m_iAmmoType ); | |||
// Basic information about the bullet | |||
m_flInitialBulletSpeed = m_flBulletSpeed = GetAmmoDef()->GetAmmoOfIndex(bulletinfo.m_iAmmoType)->bulletSpeed; | |||
m_vecDirShooting = newdir; | |||
m_vecOrigin = bulletinfo.m_vecSrc; | |||
m_bTraceHull = bTraceHull; | |||
#ifndef CLIENT_DLL | |||
m_hCaller = pCaller; | |||
#endif | |||
m_flEntryDensity = 0.0f; | |||
m_vecTraceRay = m_vecDirShooting * m_flBulletSpeed; | |||
m_flRayLength = m_flInitialBulletSpeed; | |||
} | |||
//================================================== | |||
// Purpose: Deconstructor | |||
//================================================== | |||
CSimulatedBullet::~CSimulatedBullet() | |||
{ | |||
delete m_pIgnoreList; | |||
} | |||
//================================================== | |||
// Purpose: Simulates a bullet through a ray of its bullet speed | |||
//================================================== | |||
bool CSimulatedBullet::SimulateBullet(void) | |||
{ | |||
VPROF( "C_SimulatedBullet::SimulateBullet" ); | |||
if(!IsFinite(m_flBulletSpeed)) | |||
return false;//prevent a weird crash | |||
trace_t trace; | |||
Vector vecTraceStart(m_vecOrigin); | |||
if(m_flBulletSpeed <= 0) //Avoid errors; | |||
return false; | |||
Vector vecTraceRay; | |||
if(m_flRayLength==m_flBulletSpeed) | |||
{ | |||
vecTraceRay = m_vecTraceRay; | |||
} | |||
else | |||
{ | |||
vecTraceRay = m_vecTraceRay = m_vecDirShooting * m_flBulletSpeed; | |||
m_flRayLength = m_flBulletSpeed; | |||
} | |||
m_flBulletSpeed += 0.8f * m_vecDirShooting.z; //TODO: Bullet mass | |||
m_vecDirShooting.z -= 0.1 / m_flBulletSpeed; | |||
#ifdef GAME_DLL | |||
if(bulletinfo.m_flLatency!= 0 ) | |||
{ | |||
vecTraceRay *= bulletinfo.m_flLatency * 100; | |||
} | |||
#endif | |||
if(!bStuckInWall) | |||
{ | |||
m_vecOrigin += vecTraceRay; | |||
} | |||
if(!IsInWorld()) | |||
{ | |||
return false; | |||
} | |||
if(!m_bWasInWater) | |||
{ | |||
WaterHit(vecTraceStart,m_vecOrigin); | |||
} | |||
m_bWasInWater = (UTIL_PointContents(m_vecOrigin)& MASK_WATER )?true:false; | |||
if(m_bWasInWater) | |||
{ | |||
m_flBulletSpeed -= m_flBulletSpeed * 0.1; | |||
#ifdef GAME_DLL | |||
//This is a server stuff | |||
UTIL_BubbleTrail(vecTraceStart, m_vecOrigin, 5 ); | |||
#endif | |||
} | |||
#ifdef CLIENT_DLL | |||
//FX_PlayerTracer( vecTraceStart, m_vecOrigin ); | |||
#else | |||
CEffectData tracerData; | |||
tracerData.m_vStart = vecTraceStart; | |||
tracerData.m_vOrigin = m_vecOrigin; | |||
if(m_bWasInWater) | |||
tracerData.m_fFlags = TRACER_TYPE_WATERBULLET; | |||
else | |||
tracerData.m_fFlags = TRACER_TYPE_DEFAULT; | |||
DispatchEffect( "TracerSound", tracerData ); | |||
#endif | |||
bool bulletSpeedCheck; | |||
do | |||
{ | |||
bulletSpeedCheck = false; | |||
if(m_bTraceHull) | |||
UTIL_TraceHull( vecTraceStart, m_vecOrigin, Vector(-3, -3, -3), Vector(3, 3, 3), MASK_SHOT, m_pIgnoreList, &trace ); | |||
else | |||
UTIL_TraceLine( vecTraceStart, m_vecOrigin, MASK_SHOT, m_pIgnoreList, &trace ); | |||
#ifdef CLIENT_DLL | |||
if(g_debug_client_bullets.GetBool()) | |||
{ | |||
debugoverlay->AddLineOverlay( trace.startpos, trace.endpos, 255, 0, 0, true, 10.0f ); | |||
} | |||
#else | |||
if(g_debug_bullets.GetBool()) | |||
{ | |||
NDebugOverlay::Line( trace.startpos, trace.endpos, 255, 255, 255, true, 10.0f ); | |||
} | |||
// Now hit all triggers along the ray that respond to shots... | |||
// Clip the ray to the first collided solid returned from traceline | |||
CTakeDamageInfo triggerInfo( p_eInfictor, bulletinfo.m_pAttacker, bulletinfo.m_iDamage, GetDamageType() ); | |||
CalculateBulletDamageForce( &triggerInfo, bulletinfo.m_iAmmoType, m_vecDirShooting, trace.endpos ); | |||
triggerInfo.ScaleDamageForce( bulletinfo.m_flDamageForceScale ); | |||
triggerInfo.SetAmmoType( bulletinfo.m_iAmmoType ); | |||
BulletManager()->SendTraceAttackToTriggers( triggerInfo, trace.startpos, trace.endpos, m_vecDirShooting ); | |||
#endif | |||
EntityImpact(trace); | |||
if(!(trace.surface.flags&SURF_SKY)) | |||
{ | |||
if(trace.allsolid)//in solid | |||
{ | |||
if(!AllSolid(trace)) | |||
return false; | |||
bulletSpeedCheck = true; | |||
} | |||
else if(trace.fraction!=1.0f)//hit solid | |||
{ | |||
if(!EndSolid(trace)) | |||
return false; | |||
bulletSpeedCheck = true; | |||
} | |||
else if(trace.startsolid)//exit solid | |||
{ | |||
if(!StartSolid(trace)) | |||
return false; | |||
bulletSpeedCheck = true; | |||
} | |||
else | |||
{ | |||
//don't do a bullet speed check for not touching anything | |||
break; | |||
} | |||
} | |||
else | |||
{ | |||
trace.fraction = 0.0f; //Done it! | |||
} | |||
if(bulletSpeedCheck) | |||
{ | |||
if(m_flBulletSpeed<=BulletManager()->BulletStopSpeed()) | |||
{ | |||
return false; | |||
} | |||
} | |||
if(bStuckInWall) | |||
{ | |||
return false; | |||
} | |||
else | |||
{ | |||
vecTraceStart = trace.endpos + m_vecDirShooting*1.1; | |||
} | |||
if(trace.fraction==0.0f) | |||
return false; | |||
}while(trace.fraction!=1.0f); | |||
return true; | |||
} | |||
//================================================== | |||
// Purpose: Simulates when a solid is exited | |||
//================================================== | |||
bool CSimulatedBullet::StartSolid(trace_t &ptr) | |||
{ | |||
//Vector vecExitPosition = ptr.fractionleftsolid * vecTraceRay + ptr.startpos; | |||
float flPenetrationDistance = VectorLength(AbsEntry - AbsExit); | |||
switch(BulletManager()->BulletStopSpeed()) | |||
{ | |||
case BUTTER_MODE: | |||
{ | |||
//Do nothing to bullet speed | |||
return true; | |||
} | |||
case ONE_HIT_MODE: | |||
{ | |||
return false; | |||
} | |||
default: | |||
{ | |||
#ifndef CLIENT_DLL | |||
if(g_debug_bullets.GetBool()) | |||
{ | |||
DevMsg("Bullet old speed is: %f\nThe new speed is %f\nThe penetration distance is: %f\n",m_flBulletSpeed,m_flBulletSpeed - flPenetrationDistance * m_flEntryDensity / sv_bullet_speed_modifier.GetFloat(),flPenetrationDistance); | |||
} | |||
#endif | |||
m_flBulletSpeed -= flPenetrationDistance * m_flEntryDensity / sv_bullet_speed_modifier.GetFloat(); | |||
#ifndef CLIENT_DLL | |||
if(g_debug_bullets.GetBool()) | |||
{ | |||
DevMsg("Bullet speed decreased by %f\n",flPenetrationDistance * m_flEntryDensity / sv_bullet_speed_modifier.GetFloat()); | |||
} | |||
#endif | |||
return true; | |||
} | |||
} | |||
return true; | |||
} | |||
//================================================== | |||
// Purpose: Simulates when a solid is being passed through | |||
//================================================== | |||
bool CSimulatedBullet::AllSolid(trace_t &ptr) | |||
{ | |||
switch(BulletManager()->BulletStopSpeed()) | |||
{ | |||
case BUTTER_MODE: | |||
{ | |||
//Do nothing to bullet speed | |||
return true; | |||
} | |||
case ONE_HIT_MODE: | |||
{ | |||
return false; | |||
} | |||
default: | |||
{ | |||
m_flBulletSpeed -= m_flBulletSpeed * m_flEntryDensity / sv_bullet_speed_modifier.GetFloat(); | |||
return true; | |||
} | |||
} | |||
return true; | |||
} | |||
//================================================== | |||
// Purpose: Simulate when a surface is hit | |||
//================================================== | |||
bool CSimulatedBullet::EndSolid(trace_t &ptr) | |||
{ | |||
m_vecEntryPosition = ptr.endpos; | |||
#ifndef CLIENT_DLL | |||
int soundEntChannel = ( bulletinfo.m_nFlags&FIRE_BULLETS_TEMPORARY_DANGER_SOUND ) ? SOUNDENT_CHANNEL_BULLET_IMPACT : SOUNDENT_CHANNEL_UNSPECIFIED; | |||
CSoundEnt::InsertSound( SOUND_BULLET_IMPACT, m_vecEntryPosition, 200, 0.5, p_eInfictor, soundEntChannel ); | |||
#endif | |||
if(FStrEq(ptr.surface.name,"tools/toolsblockbullets")) | |||
{ | |||
return false; | |||
} | |||
m_flEntryDensity = physprops->GetSurfaceData(ptr.surface.surfaceProps)->physics.density; | |||
trace_t rtr; | |||
Vector vecEnd = m_vecEntryPosition + m_vecDirShooting * 32; //32 units | |||
//Doing now test of penetration | |||
UTIL_TraceLine(m_vecEntryPosition + m_vecDirShooting * 1.1, vecEnd, MASK_SHOT, m_pIgnoreList, &rtr); | |||
AbsEntry = m_vecEntryPosition; | |||
AbsExit = rtr.startpos; | |||
float flPenetrationDistance = VectorLength(AbsEntry - AbsExit); | |||
DesiredDistance = 0.0f; | |||
surfacedata_t *p_penetrsurf = physprops->GetSurfaceData( ptr.surface.surfaceProps ); | |||
switch (p_penetrsurf->game.material) | |||
{ | |||
case CHAR_TEX_WOOD: | |||
DesiredDistance = 9.0f; // 9 units in hammer | |||
break; | |||
case CHAR_TEX_GRATE: | |||
DesiredDistance = 6.0f; // 6 units in hammer | |||
break; | |||
case CHAR_TEX_CONCRETE: | |||
DesiredDistance = 4.0f; // 4 units in hammer | |||
break; | |||
case CHAR_TEX_TILE: | |||
DesiredDistance = 5.0f; // 5 units in hammer | |||
break; | |||
case CHAR_TEX_COMPUTER: | |||
DesiredDistance = 5.0f; // 5 units in hammer | |||
break; | |||
case CHAR_TEX_GLASS: | |||
DesiredDistance = 8.0f; // maximum 8 units in hammer. | |||
break; | |||
case CHAR_TEX_VENT: | |||
DesiredDistance = 4.0f; // 4 units in hammer and no more(!) | |||
break; | |||
case CHAR_TEX_METAL: | |||
DesiredDistance = 5.0f; // 2 units in hammer. We cannot penetrate a really 'fat' metal wall. Corners are good. | |||
break; | |||
case CHAR_TEX_PLASTIC: | |||
DesiredDistance = 8.0f; // 8 units in hammer: Plastic can more | |||
break; | |||
case CHAR_TEX_BLOODYFLESH: | |||
DesiredDistance = 16.0f; // 16 units in hammer | |||
break; | |||
case CHAR_TEX_FLESH: | |||
DesiredDistance = 16.0f; // 16 units in hammer | |||
break; | |||
case CHAR_TEX_DIRT: | |||
DesiredDistance = 6.0f; // 6 units in hammer: >4 cm of plaster can be penetrated | |||
break; | |||
} | |||
Vector reflect; | |||
float fldot = m_vecDirShooting.Dot( ptr.plane.normal ); //Getting angles from lasttrace | |||
if (fldot > -MAX_RICO_DOT_ANGLE && GetBulletSpeedRatio() > MIN_RICO_SPEED_PERC) // We can't do richochet when bullet has lowest speed | |||
{ | |||
if(gpGlobals->maxClients == 1) //Use more simple for multiplayer | |||
{ | |||
m_flBulletSpeed *= (1.0f / -fldot) * random->RandomFloat(0.005,0.1); | |||
} | |||
else | |||
{ | |||
m_flBulletSpeed *= (1.0f / -fldot) * 0.025; | |||
} | |||
reflect = m_vecDirShooting + ( ptr.plane.normal * ( fldot*-2.0f ) ); //reflecting | |||
if(gpGlobals->maxClients == 1) //Use more simple for multiplayer | |||
{ | |||
reflect[0] += random->RandomFloat( fldot, -fldot ); | |||
reflect[1] += random->RandomFloat( fldot, -fldot ); | |||
reflect[2] += random->RandomFloat( fldot, -fldot ); | |||
} | |||
m_vecDirShooting = reflect; | |||
m_vecOrigin = (ptr.endpos + m_vecDirShooting*1.1) + m_vecDirShooting * m_flBulletSpeed; | |||
} | |||
else | |||
{ | |||
if (flPenetrationDistance > DesiredDistance || ptr.IsDispSurface()) | |||
{ | |||
bStuckInWall = true; | |||
#ifdef GAME_DLL | |||
if(g_debug_bullets.GetBool()) | |||
{ | |||
NDebugOverlay::Line( AbsEntry, AbsExit, 255, 0, 0, true, 10.0f ); | |||
if(!ptr.IsDispSurface()) | |||
DevMsg("Cannot penetrate surface, The distance(%f) > %f \n",flPenetrationDistance,DesiredDistance); | |||
else | |||
DevMsg("Displacement penetration was tempolary disabled\n"); | |||
} | |||
#endif | |||
} | |||
else | |||
{ | |||
trace_t tr; | |||
UTIL_TraceLine(AbsExit + m_vecDirShooting * 1.1 ,AbsEntry,MASK_SHOT,m_pIgnoreList,&tr); | |||
UTIL_ImpactTrace( &tr, GetDamageType() ); // On exit | |||
#ifdef GAME_DLL | |||
if(g_debug_bullets.GetBool()) | |||
{ | |||
NDebugOverlay::Line( AbsEntry, AbsExit, 0, 255, 0, true, 10.0f ); | |||
} | |||
#endif | |||
if(gpGlobals->maxClients == 1) //Use more simple for multiplayer | |||
{ | |||
m_vecDirShooting[0] += (random->RandomFloat( -flPenetrationDistance, flPenetrationDistance ))*0.1; | |||
m_vecDirShooting[1] += (random->RandomFloat( -flPenetrationDistance, flPenetrationDistance ))*0.1; | |||
m_vecDirShooting[2] += (random->RandomFloat( -flPenetrationDistance, flPenetrationDistance ))*0.1; | |||
} | |||
m_vecOrigin = AbsExit + m_vecDirShooting * m_flBulletSpeed; | |||
} | |||
} | |||
//Cancel making dust underwater: | |||
if(!m_bWasInWater) | |||
{ | |||
UTIL_ImpactTrace( &ptr, GetDamageType() ); | |||
} | |||
if(BulletManager()->BulletStopSpeed()==ONE_HIT_MODE) | |||
{ | |||
return false; | |||
} | |||
return true; | |||
} | |||
//----------------------------------------------------------------------------- | |||
// analog of HandleShotImpactingWater | |||
//----------------------------------------------------------------------------- | |||
bool CSimulatedBullet::WaterHit(const Vector &vecStart,const Vector &vecEnd) | |||
{ | |||
trace_t waterTrace; | |||
// Trace again with water enabled | |||
UTIL_TraceLine( vecStart, vecEnd, (MASK_SHOT|CONTENTS_WATER|CONTENTS_SLIME), m_pIgnoreList, &waterTrace ); | |||
// See if this is the point we entered | |||
if ( ( enginetrace->GetPointContents( waterTrace.endpos - Vector(0,0,0.1f) ) & (CONTENTS_WATER|CONTENTS_SLIME) ) == 0 ) | |||
return false; | |||
int nMinSplashSize = GetAmmoDef()->MinSplashSize(GetAmmoTypeIndex()); | |||
int nMaxSplashSize = GetAmmoDef()->MaxSplashSize(GetAmmoTypeIndex()); | |||
CEffectData data; | |||
data.m_vOrigin = waterTrace.endpos; | |||
data.m_vNormal = waterTrace.plane.normal; | |||
data.m_flScale = random->RandomFloat( nMinSplashSize, nMaxSplashSize ); | |||
if ( waterTrace.contents & CONTENTS_SLIME ) | |||
{ | |||
data.m_fFlags |= FX_WATER_IN_SLIME; | |||
} | |||
DispatchEffect( "gunshotsplash", data ); | |||
return true; | |||
} | |||
//================================================== | |||
// Purpose: Entity impact procedure | |||
//================================================== | |||
void CSimulatedBullet::EntityImpact(trace_t &ptr) | |||
{ | |||
if(bStuckInWall) | |||
return; | |||
if (ptr.m_pEnt != NULL) | |||
{ | |||
//Hit inflicted once to avoid perfomance errors | |||
if(p_eInfictor->IsNPC()) | |||
{ | |||
if (ptr.m_pEnt->IsPlayer()) | |||
{ | |||
if (m_pIgnoreList->ShouldHitEntity(ptr.m_pEnt,MASK_SHOT)) | |||
{ | |||
m_pIgnoreList->AddEntityToIgnore(ptr.m_pEnt); | |||
} | |||
else | |||
{ | |||
return; | |||
} | |||
} | |||
} | |||
if(ptr.m_pEnt == m_hLastHit) | |||
return; | |||
m_hLastHit = ptr.m_pEnt; | |||
float flActualDamage = g_pGameRules->GetAmmoDamage( p_eInfictor, ptr.m_pEnt, bulletinfo.m_iAmmoType ); | |||
float flActualForce = bulletinfo.m_flDamageForceScale; | |||
if (!ptr.m_pEnt->IsPlayer() || !ptr.m_pEnt->IsNPC()) | |||
{ | |||
//To make it more reallistic | |||
float fldot = m_vecDirShooting.Dot( ptr.plane.normal ); | |||
//We affecting damage by angle. If we has lower angle of reflection, doing lower damage. | |||
flActualDamage *= -fldot; | |||
flActualForce *= -fldot; | |||
//But we not doing it on the players or NPC's | |||
} | |||
flActualDamage *= GetBulletSpeedRatio(); //And also affect damage by speed modifications | |||
flActualForce *= GetBulletSpeedRatio(); //Slow bullet - bad force... | |||
DevMsg("Hit: %s, Damage: %f, Force: %f \n",ptr.m_pEnt->GetClassname(),flActualDamage,flActualForce); | |||
CTakeDamageInfo dmgInfo( p_eInfictor, bulletinfo.m_pAttacker, flActualDamage, GetDamageType() ); | |||
CalculateBulletDamageForce( &dmgInfo, bulletinfo.m_iAmmoType, m_vecDirShooting, ptr.endpos ); | |||
dmgInfo.ScaleDamageForce( flActualForce ); | |||
dmgInfo.SetAmmoType( bulletinfo.m_iAmmoType ); | |||
ptr.m_pEnt->DispatchTraceAttack( dmgInfo, bulletinfo.m_vecDirShooting, &ptr ); | |||
#ifdef GAME_DLL | |||
ApplyMultiDamage(); //It's requried | |||
if ( GetAmmoDef()->Flags(GetAmmoTypeIndex()) & AMMO_FORCE_DROP_IF_CARRIED ) | |||
{ | |||
// Make sure if the player is holding this, he drops it | |||
Pickup_ForcePlayerToDropThisObject( ptr.m_pEnt ); | |||
} | |||
#endif | |||
} | |||
} | |||
//================================================== | |||
// Purpose: Simulates all bullets every centisecond | |||
//================================================== | |||
#ifndef CLIENT_DLL | |||
void CBulletManager::Think(void) | |||
#else | |||
void CBulletManager::ClientThink(void) | |||
#endif | |||
{ | |||
unsigned short iNext=0; | |||
for ( unsigned short i=g_Bullets.Head(); i != g_Bullets.InvalidIndex(); i=iNext ) | |||
{ | |||
iNext = g_Bullets.Next( i ); | |||
if ( !g_Bullets[i]->SimulateBullet() ) | |||
RemoveBullet( i ); | |||
} | |||
if(g_Bullets.Head()!=g_Bullets.InvalidIndex()) | |||
{ | |||
#ifdef CLIENT_DLL | |||
SetNextClientThink( gpGlobals->curtime + 0.01f ); | |||
#else | |||
SetNextThink( gpGlobals->curtime + 0.01f ); | |||
#endif | |||
} | |||
} | |||
//================================================== | |||
// Purpose: Called by sv_bullet_stop_speed callback to keep track of resources | |||
//================================================== | |||
void CBulletManager::UpdateBulletStopSpeed(void) | |||
{ | |||
m_iBulletStopSpeed = sv_bullet_stop_speed.GetInt(); | |||
} | |||
#ifndef CLIENT_DLL | |||
void CBulletManager::SendTraceAttackToTriggers( const CTakeDamageInfo &info, const Vector& start, const Vector& end, const Vector& dir ) | |||
{ | |||
TraceAttackToTriggers(info,start,end,dir); | |||
} | |||
#endif | |||
//================================================== | |||
// Purpose: Add bullet to linked list | |||
// Handle lag compensation with "prebullet simulation" | |||
// Output: Bullet's index (-1 for error) | |||
//================================================== | |||
int CBulletManager::AddBullet(CSimulatedBullet *pBullet) | |||
{ | |||
if (pBullet->GetAmmoTypeIndex() == -1) | |||
{ | |||
Msg("ERROR: Undefined ammo type!\n"); | |||
return -1; | |||
} | |||
unsigned short index = g_Bullets.AddToTail(pBullet); | |||
#ifdef CLIENT_DLL | |||
DevMsg( "Client Bullet Created (%i)\n", index); | |||
if(g_Bullets.Count()==1) | |||
{ | |||
SetNextClientThink(gpGlobals->curtime + 0.01f); | |||
} | |||
#else | |||
DevMsg( "Bullet Created (%i) LagCompensation %f\n", index, pBullet->bulletinfo.m_flLatency ); | |||
if(pBullet->bulletinfo.m_flLatency !=0.0f) | |||
pBullet->SimulateBullet(); //Pre-simulation | |||
if(g_Bullets.Count()==1) | |||
{ | |||
SetNextThink(gpGlobals->curtime + 0.01f); | |||
} | |||
#endif | |||
return index; | |||
} | |||
//================================================== | |||
// Purpose: Remove the bullet at index from the linked list | |||
// Output: Next index | |||
//================================================== | |||
void CBulletManager::RemoveBullet(int index) | |||
{ | |||
g_Bullets.Next(index); | |||
#ifdef CLIENT_DLL | |||
DevMsg("Client "); | |||
#endif | |||
DevMsg( "Bullet Removed (%i)\n", index ); | |||
g_Bullets.Remove(index); | |||
if(g_Bullets.Count()==0) | |||
{ | |||
#ifdef CLIENT_DLL | |||
SetNextClientThink( TICK_NEVER_THINK ); | |||
#else | |||
SetNextThink( TICK_NEVER_THINK ); | |||
#endif | |||
} | |||
}</pre> | |||
===game_shared\bullet_manager.h=== | |||
Other replacement: | |||
<pre> | |||
// (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 | |||
// | |||
// Modified by 'SoapyMan' | |||
#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; | |||
}; | |||
</pre> | |||
===game_shared\baseentity_shared.cpp=== | |||
Replace FireBullets() with following code: | |||
<pre> | |||
void CBaseEntity::FireBullets( const FireBulletsInfo_t &info ) | |||
{ | |||
// Make sure given a valid bullet type | |||
if (info.m_iAmmoType == -1) | |||
{ | |||
DevMsg("ERROR: Undefined ammo type!\n"); | |||
return; | |||
} | |||
static int tracerCount; | |||
//trace_t tr; | |||
CAmmoDef* pAmmoDef = GetAmmoDef(); | |||
int nDamageType = pAmmoDef->DamageType(info.m_iAmmoType); | |||
int nAmmoFlags = pAmmoDef->Flags(info.m_iAmmoType); | |||
int iPlayerDamage = info.m_iPlayerDamage; | |||
if ( iPlayerDamage == 0 ) | |||
{ | |||
if ( nAmmoFlags & AMMO_INTERPRET_PLRDAMAGE_AS_DAMAGE_TO_PLAYER ) | |||
{ | |||
iPlayerDamage = pAmmoDef->PlrDamage( info.m_iAmmoType ); | |||
} | |||
} | |||
// the default attacker is ourselves | |||
CBaseEntity *pAttacker = info.m_pAttacker ? info.m_pAttacker : this; | |||
// Make sure we don't have a dangling damage target from a recursive call | |||
if ( g_MultiDamage.GetTarget() != NULL ) | |||
{ | |||
ApplyMultiDamage(); | |||
} | |||
ClearMultiDamage(); | |||
g_MultiDamage.SetDamageType( nDamageType | DMG_NEVERGIB ); | |||
// Prediction is only usable on players | |||
int iSeed = 0; | |||
if ( IsPlayer() ) | |||
{ | |||
iSeed = CBaseEntity::GetPredictionRandomSeed() & 255; | |||
} | |||
//----------------------------------------------------- | |||
// Set up our shot manipulator. | |||
//----------------------------------------------------- | |||
CShotManipulator Manipulator( info.m_vecDirShooting ); | |||
for (int iShot = 0; iShot < info.m_iShots; iShot++) | |||
{ | |||
Vector vecDir; | |||
// Prediction is only usable on players | |||
if ( IsPlayer() ) | |||
{ | |||
RandomSeed( iSeed ); // init random system with this seed | |||
} | |||
// If we're firing multiple shots, and the first shot has to be bang on target, ignore spread | |||
if ( iShot == 0 && info.m_iShots > 1 && (info.m_nFlags & FIRE_BULLETS_FIRST_SHOT_ACCURATE) ) | |||
{ | |||
vecDir = Manipulator.GetShotDirection(); | |||
} | |||
else | |||
{ | |||
// Don't run the biasing code for the player at the moment. | |||
vecDir = Manipulator.ApplySpread( info.m_vecSpread ); | |||
} | |||
Vector vecSrc(info.m_vecSrc); | |||
bool bTraceHull = ( IsPlayer() && info.m_iShots > 1 && iShot % 2 ); | |||
CSimulatedBullet *pBullet = new CSimulatedBullet(info,vecDir,pAttacker,info.m_pAdditionalIgnoreEnt, bTraceHull | |||
#ifndef CLIENT_DLL | |||
,this | |||
#endif | |||
); | |||
BulletManager()->AddBullet(pBullet); | |||
iSeed++; | |||
} | |||
#if defined( HL2MP ) && defined( GAME_DLL ) | |||
if ( bDoServerEffects == false ) | |||
{ | |||
TE_HL2MPFireBullets( entindex(), tr.startpos, info.m_vecDirShooting, info.m_iAmmoType, iEffectSeed, info.m_iShots, info.m_vecSpread.x, bDoTracers, bDoImpacts ); | |||
} | |||
#endif | |||
ApplyMultiDamage(); | |||
} | |||
</pre> | |||
===cl_dll\c_te_hl2mp_shotgun_shot.cpp=== | |||
It's undone and not rewritten. Please feel free to do helpful changes for it. | |||
PS: Sorry for my English. |
Revision as of 10:37, 14 January 2009
Preface
This article based on Simulated_Bullets.
This code in some parts is more complete version. Changes:
- Modified algorithm of penetration - Added impacts of water and walls - Added bubbles - Added entity hit (EntityImpact() function is now complete) - Added ricochet - Speed changes now affects on damage and impact force (but not for NPC or Player) - Removed unused stuff
Undone parts:
- Tracer and it's whiz sound - Multiplayer

Procedure
Do the similar procedure from original article (Link above).
Replace all code with following:
// (More) Reallistic simulated bullets // // This code is originally based from article on Valve Developer Community // The original code you can find by this link: // http://developer.valvesoftware.com/wiki/Simulated_Bullets // // Modified by 'SoapyMan' // TODO: Tracers // NOTENOTE: Tested only on localhost. There maybe a latency errors and others // NOTENOTE: The simulation might be strange. #include "cbase.h" #include "util_shared.h" #include "bullet_manager.h" #include "effect_dispatch_data.h" #include "tier0/vprof.h" #include "decals.h" CBulletManager *g_pBulletManager; CUtlLinkedList<CSimulatedBullet*> g_Bullets; #ifdef CLIENT_DLL//------------------------------------------------- #include "engine/ivdebugoverlay.h" #include "c_te_effect_dispatch.h" ConVar g_debug_client_bullets( "g_debug_client_bullets", "0", FCVAR_CHEAT ); extern void FX_PlayerTracer( Vector& start, Vector& end); #else//------------------------------------------------------------- #include "te_effect_dispatch.h" #include "soundent.h" #include "player_pickup.h" #include "ilagcompensationmanager.h" ConVar g_debug_bullets( "g_debug_bullets", "0", FCVAR_CHEAT, "Debug of bullet simulation\nThe white line shows the bullet trail.\nThe red line shows not passed penetration test.\nThe green line shows passed penetration test. Turn developer mode for more information." ); #endif//------------------------------------------------------------ // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #define MAX_RICO_DOT_ANGLE 0.15f //Maximum dot allowed for any ricochet #define MIN_RICO_SPEED_PERC 0.55f //Minimum speed percent allowed for any ricochet static void BulletSpeedModifierCallback(ConVar *var, const char *pOldString) { if(var->GetFloat()==0.0f) //To avoid math exception var->Revert(); } ConVar sv_bullet_speed_modifier( "sv_bullet_speed_modifier", "700.000000", (FCVAR_ARCHIVE | FCVAR_REPLICATED), "Density/(This Value) * (Distance Penetrated) = (Change in Speed)", BulletSpeedModifierCallback ); static void BulletStopSpeedCallback(ConVar *var, const char *pOldString) { int val = var->GetInt(); if(val<ONE_HIT_MODE) var->Revert(); else if(BulletManager()) BulletManager()->UpdateBulletStopSpeed(); } ConVar sv_bullet_stop_speed( "sv_bullet_stop_speed", "40", (FCVAR_ARCHIVE | FCVAR_REPLICATED), "Integral speed at which to remove the bullet from the bullet queue\n-1 is butter mode\n-2 is 1 hit mode", BulletStopSpeedCallback ); LINK_ENTITY_TO_CLASS( bullet_manager, CBulletManager ); //================================================== // Purpose: Constructor //================================================== CSimulatedBullet::CSimulatedBullet() { m_vecOrigin.Init(); m_vecDirShooting.Init(); m_flInitialBulletSpeed = m_flBulletSpeed = 0; m_flEntryDensity = 0.0f; bStuckInWall = false; m_iDamageType = 2; } //================================================== // Purpose: Constructor //================================================== CSimulatedBullet::CSimulatedBullet( FireBulletsInfo_t info,Vector newdir,CBaseEntity *pInfictor, CBaseEntity *pAdditionalIgnoreEnt, bool bTraceHull #ifndef CLIENT_DLL , CBaseEntity *pCaller #endif ) { // Input validation Assert( pInfictor ); #ifndef CLIENT_DLL Assert( pCaller ); #endif bulletinfo = info; // Store Fire bullets information here p_eInfictor = pInfictor; // Store inflictor // Create a list of entities with which this bullet does not collide. m_pIgnoreList = new CTraceFilterSimpleList(COLLISION_GROUP_NONE); Assert( m_pIgnoreList ); bStuckInWall = false; // Don't collide with the entity firing the bullet. m_pIgnoreList->AddEntityToIgnore(p_eInfictor); // Don't collide with some optionally-specified entity. if(pAdditionalIgnoreEnt!=NULL) m_pIgnoreList->AddEntityToIgnore(pAdditionalIgnoreEnt); m_iDamageType = GetAmmoDef()->DamageType( bulletinfo.m_iAmmoType ); // Basic information about the bullet m_flInitialBulletSpeed = m_flBulletSpeed = GetAmmoDef()->GetAmmoOfIndex(bulletinfo.m_iAmmoType)->bulletSpeed; m_vecDirShooting = newdir; m_vecOrigin = bulletinfo.m_vecSrc; m_bTraceHull = bTraceHull; #ifndef CLIENT_DLL m_hCaller = pCaller; #endif m_flEntryDensity = 0.0f; m_vecTraceRay = m_vecDirShooting * m_flBulletSpeed; m_flRayLength = m_flInitialBulletSpeed; } //================================================== // Purpose: Deconstructor //================================================== CSimulatedBullet::~CSimulatedBullet() { delete m_pIgnoreList; } //================================================== // Purpose: Simulates a bullet through a ray of its bullet speed //================================================== bool CSimulatedBullet::SimulateBullet(void) { VPROF( "C_SimulatedBullet::SimulateBullet" ); if(!IsFinite(m_flBulletSpeed)) return false;//prevent a weird crash trace_t trace; Vector vecTraceStart(m_vecOrigin); if(m_flBulletSpeed <= 0) //Avoid errors; return false; Vector vecTraceRay; if(m_flRayLength==m_flBulletSpeed) { vecTraceRay = m_vecTraceRay; } else { vecTraceRay = m_vecTraceRay = m_vecDirShooting * m_flBulletSpeed; m_flRayLength = m_flBulletSpeed; } m_flBulletSpeed += 0.8f * m_vecDirShooting.z; //TODO: Bullet mass m_vecDirShooting.z -= 0.1 / m_flBulletSpeed; #ifdef GAME_DLL if(bulletinfo.m_flLatency!= 0 ) { vecTraceRay *= bulletinfo.m_flLatency * 100; } #endif if(!bStuckInWall) { m_vecOrigin += vecTraceRay; } if(!IsInWorld()) { return false; } if(!m_bWasInWater) { WaterHit(vecTraceStart,m_vecOrigin); } m_bWasInWater = (UTIL_PointContents(m_vecOrigin)& MASK_WATER )?true:false; if(m_bWasInWater) { m_flBulletSpeed -= m_flBulletSpeed * 0.1; #ifdef GAME_DLL //This is a server stuff UTIL_BubbleTrail(vecTraceStart, m_vecOrigin, 5 ); #endif } #ifdef CLIENT_DLL //FX_PlayerTracer( vecTraceStart, m_vecOrigin ); #else CEffectData tracerData; tracerData.m_vStart = vecTraceStart; tracerData.m_vOrigin = m_vecOrigin; if(m_bWasInWater) tracerData.m_fFlags = TRACER_TYPE_WATERBULLET; else tracerData.m_fFlags = TRACER_TYPE_DEFAULT; DispatchEffect( "TracerSound", tracerData ); #endif bool bulletSpeedCheck; do { bulletSpeedCheck = false; if(m_bTraceHull) UTIL_TraceHull( vecTraceStart, m_vecOrigin, Vector(-3, -3, -3), Vector(3, 3, 3), MASK_SHOT, m_pIgnoreList, &trace ); else UTIL_TraceLine( vecTraceStart, m_vecOrigin, MASK_SHOT, m_pIgnoreList, &trace ); #ifdef CLIENT_DLL if(g_debug_client_bullets.GetBool()) { debugoverlay->AddLineOverlay( trace.startpos, trace.endpos, 255, 0, 0, true, 10.0f ); } #else if(g_debug_bullets.GetBool()) { NDebugOverlay::Line( trace.startpos, trace.endpos, 255, 255, 255, true, 10.0f ); } // Now hit all triggers along the ray that respond to shots... // Clip the ray to the first collided solid returned from traceline CTakeDamageInfo triggerInfo( p_eInfictor, bulletinfo.m_pAttacker, bulletinfo.m_iDamage, GetDamageType() ); CalculateBulletDamageForce( &triggerInfo, bulletinfo.m_iAmmoType, m_vecDirShooting, trace.endpos ); triggerInfo.ScaleDamageForce( bulletinfo.m_flDamageForceScale ); triggerInfo.SetAmmoType( bulletinfo.m_iAmmoType ); BulletManager()->SendTraceAttackToTriggers( triggerInfo, trace.startpos, trace.endpos, m_vecDirShooting ); #endif EntityImpact(trace); if(!(trace.surface.flags&SURF_SKY)) { if(trace.allsolid)//in solid { if(!AllSolid(trace)) return false; bulletSpeedCheck = true; } else if(trace.fraction!=1.0f)//hit solid { if(!EndSolid(trace)) return false; bulletSpeedCheck = true; } else if(trace.startsolid)//exit solid { if(!StartSolid(trace)) return false; bulletSpeedCheck = true; } else { //don't do a bullet speed check for not touching anything break; } } else { trace.fraction = 0.0f; //Done it! } if(bulletSpeedCheck) { if(m_flBulletSpeed<=BulletManager()->BulletStopSpeed()) { return false; } } if(bStuckInWall) { return false; } else { vecTraceStart = trace.endpos + m_vecDirShooting*1.1; } if(trace.fraction==0.0f) return false; }while(trace.fraction!=1.0f); return true; } //================================================== // Purpose: Simulates when a solid is exited //================================================== bool CSimulatedBullet::StartSolid(trace_t &ptr) { //Vector vecExitPosition = ptr.fractionleftsolid * vecTraceRay + ptr.startpos; float flPenetrationDistance = VectorLength(AbsEntry - AbsExit); switch(BulletManager()->BulletStopSpeed()) { case BUTTER_MODE: { //Do nothing to bullet speed return true; } case ONE_HIT_MODE: { return false; } default: { #ifndef CLIENT_DLL if(g_debug_bullets.GetBool()) { DevMsg("Bullet old speed is: %f\nThe new speed is %f\nThe penetration distance is: %f\n",m_flBulletSpeed,m_flBulletSpeed - flPenetrationDistance * m_flEntryDensity / sv_bullet_speed_modifier.GetFloat(),flPenetrationDistance); } #endif m_flBulletSpeed -= flPenetrationDistance * m_flEntryDensity / sv_bullet_speed_modifier.GetFloat(); #ifndef CLIENT_DLL if(g_debug_bullets.GetBool()) { DevMsg("Bullet speed decreased by %f\n",flPenetrationDistance * m_flEntryDensity / sv_bullet_speed_modifier.GetFloat()); } #endif return true; } } return true; } //================================================== // Purpose: Simulates when a solid is being passed through //================================================== bool CSimulatedBullet::AllSolid(trace_t &ptr) { switch(BulletManager()->BulletStopSpeed()) { case BUTTER_MODE: { //Do nothing to bullet speed return true; } case ONE_HIT_MODE: { return false; } default: { m_flBulletSpeed -= m_flBulletSpeed * m_flEntryDensity / sv_bullet_speed_modifier.GetFloat(); return true; } } return true; } //================================================== // Purpose: Simulate when a surface is hit //================================================== bool CSimulatedBullet::EndSolid(trace_t &ptr) { m_vecEntryPosition = ptr.endpos; #ifndef CLIENT_DLL int soundEntChannel = ( bulletinfo.m_nFlags&FIRE_BULLETS_TEMPORARY_DANGER_SOUND ) ? SOUNDENT_CHANNEL_BULLET_IMPACT : SOUNDENT_CHANNEL_UNSPECIFIED; CSoundEnt::InsertSound( SOUND_BULLET_IMPACT, m_vecEntryPosition, 200, 0.5, p_eInfictor, soundEntChannel ); #endif if(FStrEq(ptr.surface.name,"tools/toolsblockbullets")) { return false; } m_flEntryDensity = physprops->GetSurfaceData(ptr.surface.surfaceProps)->physics.density; trace_t rtr; Vector vecEnd = m_vecEntryPosition + m_vecDirShooting * 32; //32 units //Doing now test of penetration UTIL_TraceLine(m_vecEntryPosition + m_vecDirShooting * 1.1, vecEnd, MASK_SHOT, m_pIgnoreList, &rtr); AbsEntry = m_vecEntryPosition; AbsExit = rtr.startpos; float flPenetrationDistance = VectorLength(AbsEntry - AbsExit); DesiredDistance = 0.0f; surfacedata_t *p_penetrsurf = physprops->GetSurfaceData( ptr.surface.surfaceProps ); switch (p_penetrsurf->game.material) { case CHAR_TEX_WOOD: DesiredDistance = 9.0f; // 9 units in hammer break; case CHAR_TEX_GRATE: DesiredDistance = 6.0f; // 6 units in hammer break; case CHAR_TEX_CONCRETE: DesiredDistance = 4.0f; // 4 units in hammer break; case CHAR_TEX_TILE: DesiredDistance = 5.0f; // 5 units in hammer break; case CHAR_TEX_COMPUTER: DesiredDistance = 5.0f; // 5 units in hammer break; case CHAR_TEX_GLASS: DesiredDistance = 8.0f; // maximum 8 units in hammer. break; case CHAR_TEX_VENT: DesiredDistance = 4.0f; // 4 units in hammer and no more(!) break; case CHAR_TEX_METAL: DesiredDistance = 5.0f; // 2 units in hammer. We cannot penetrate a really 'fat' metal wall. Corners are good. break; case CHAR_TEX_PLASTIC: DesiredDistance = 8.0f; // 8 units in hammer: Plastic can more break; case CHAR_TEX_BLOODYFLESH: DesiredDistance = 16.0f; // 16 units in hammer break; case CHAR_TEX_FLESH: DesiredDistance = 16.0f; // 16 units in hammer break; case CHAR_TEX_DIRT: DesiredDistance = 6.0f; // 6 units in hammer: >4 cm of plaster can be penetrated break; } Vector reflect; float fldot = m_vecDirShooting.Dot( ptr.plane.normal ); //Getting angles from lasttrace if (fldot > -MAX_RICO_DOT_ANGLE && GetBulletSpeedRatio() > MIN_RICO_SPEED_PERC) // We can't do richochet when bullet has lowest speed { if(gpGlobals->maxClients == 1) //Use more simple for multiplayer { m_flBulletSpeed *= (1.0f / -fldot) * random->RandomFloat(0.005,0.1); } else { m_flBulletSpeed *= (1.0f / -fldot) * 0.025; } reflect = m_vecDirShooting + ( ptr.plane.normal * ( fldot*-2.0f ) ); //reflecting if(gpGlobals->maxClients == 1) //Use more simple for multiplayer { reflect[0] += random->RandomFloat( fldot, -fldot ); reflect[1] += random->RandomFloat( fldot, -fldot ); reflect[2] += random->RandomFloat( fldot, -fldot ); } m_vecDirShooting = reflect; m_vecOrigin = (ptr.endpos + m_vecDirShooting*1.1) + m_vecDirShooting * m_flBulletSpeed; } else { if (flPenetrationDistance > DesiredDistance || ptr.IsDispSurface()) { bStuckInWall = true; #ifdef GAME_DLL if(g_debug_bullets.GetBool()) { NDebugOverlay::Line( AbsEntry, AbsExit, 255, 0, 0, true, 10.0f ); if(!ptr.IsDispSurface()) DevMsg("Cannot penetrate surface, The distance(%f) > %f \n",flPenetrationDistance,DesiredDistance); else DevMsg("Displacement penetration was tempolary disabled\n"); } #endif } else { trace_t tr; UTIL_TraceLine(AbsExit + m_vecDirShooting * 1.1 ,AbsEntry,MASK_SHOT,m_pIgnoreList,&tr); UTIL_ImpactTrace( &tr, GetDamageType() ); // On exit #ifdef GAME_DLL if(g_debug_bullets.GetBool()) { NDebugOverlay::Line( AbsEntry, AbsExit, 0, 255, 0, true, 10.0f ); } #endif if(gpGlobals->maxClients == 1) //Use more simple for multiplayer { m_vecDirShooting[0] += (random->RandomFloat( -flPenetrationDistance, flPenetrationDistance ))*0.1; m_vecDirShooting[1] += (random->RandomFloat( -flPenetrationDistance, flPenetrationDistance ))*0.1; m_vecDirShooting[2] += (random->RandomFloat( -flPenetrationDistance, flPenetrationDistance ))*0.1; } m_vecOrigin = AbsExit + m_vecDirShooting * m_flBulletSpeed; } } //Cancel making dust underwater: if(!m_bWasInWater) { UTIL_ImpactTrace( &ptr, GetDamageType() ); } if(BulletManager()->BulletStopSpeed()==ONE_HIT_MODE) { return false; } return true; } //----------------------------------------------------------------------------- // analog of HandleShotImpactingWater //----------------------------------------------------------------------------- bool CSimulatedBullet::WaterHit(const Vector &vecStart,const Vector &vecEnd) { trace_t waterTrace; // Trace again with water enabled UTIL_TraceLine( vecStart, vecEnd, (MASK_SHOT|CONTENTS_WATER|CONTENTS_SLIME), m_pIgnoreList, &waterTrace ); // See if this is the point we entered if ( ( enginetrace->GetPointContents( waterTrace.endpos - Vector(0,0,0.1f) ) & (CONTENTS_WATER|CONTENTS_SLIME) ) == 0 ) return false; int nMinSplashSize = GetAmmoDef()->MinSplashSize(GetAmmoTypeIndex()); int nMaxSplashSize = GetAmmoDef()->MaxSplashSize(GetAmmoTypeIndex()); CEffectData data; data.m_vOrigin = waterTrace.endpos; data.m_vNormal = waterTrace.plane.normal; data.m_flScale = random->RandomFloat( nMinSplashSize, nMaxSplashSize ); if ( waterTrace.contents & CONTENTS_SLIME ) { data.m_fFlags |= FX_WATER_IN_SLIME; } DispatchEffect( "gunshotsplash", data ); return true; } //================================================== // Purpose: Entity impact procedure //================================================== void CSimulatedBullet::EntityImpact(trace_t &ptr) { if(bStuckInWall) return; if (ptr.m_pEnt != NULL) { //Hit inflicted once to avoid perfomance errors if(p_eInfictor->IsNPC()) { if (ptr.m_pEnt->IsPlayer()) { if (m_pIgnoreList->ShouldHitEntity(ptr.m_pEnt,MASK_SHOT)) { m_pIgnoreList->AddEntityToIgnore(ptr.m_pEnt); } else { return; } } } if(ptr.m_pEnt == m_hLastHit) return; m_hLastHit = ptr.m_pEnt; float flActualDamage = g_pGameRules->GetAmmoDamage( p_eInfictor, ptr.m_pEnt, bulletinfo.m_iAmmoType ); float flActualForce = bulletinfo.m_flDamageForceScale; if (!ptr.m_pEnt->IsPlayer() || !ptr.m_pEnt->IsNPC()) { //To make it more reallistic float fldot = m_vecDirShooting.Dot( ptr.plane.normal ); //We affecting damage by angle. If we has lower angle of reflection, doing lower damage. flActualDamage *= -fldot; flActualForce *= -fldot; //But we not doing it on the players or NPC's } flActualDamage *= GetBulletSpeedRatio(); //And also affect damage by speed modifications flActualForce *= GetBulletSpeedRatio(); //Slow bullet - bad force... DevMsg("Hit: %s, Damage: %f, Force: %f \n",ptr.m_pEnt->GetClassname(),flActualDamage,flActualForce); CTakeDamageInfo dmgInfo( p_eInfictor, bulletinfo.m_pAttacker, flActualDamage, GetDamageType() ); CalculateBulletDamageForce( &dmgInfo, bulletinfo.m_iAmmoType, m_vecDirShooting, ptr.endpos ); dmgInfo.ScaleDamageForce( flActualForce ); dmgInfo.SetAmmoType( bulletinfo.m_iAmmoType ); ptr.m_pEnt->DispatchTraceAttack( dmgInfo, bulletinfo.m_vecDirShooting, &ptr ); #ifdef GAME_DLL ApplyMultiDamage(); //It's requried if ( GetAmmoDef()->Flags(GetAmmoTypeIndex()) & AMMO_FORCE_DROP_IF_CARRIED ) { // Make sure if the player is holding this, he drops it Pickup_ForcePlayerToDropThisObject( ptr.m_pEnt ); } #endif } } //================================================== // Purpose: Simulates all bullets every centisecond //================================================== #ifndef CLIENT_DLL void CBulletManager::Think(void) #else void CBulletManager::ClientThink(void) #endif { unsigned short iNext=0; for ( unsigned short i=g_Bullets.Head(); i != g_Bullets.InvalidIndex(); i=iNext ) { iNext = g_Bullets.Next( i ); if ( !g_Bullets[i]->SimulateBullet() ) RemoveBullet( i ); } if(g_Bullets.Head()!=g_Bullets.InvalidIndex()) { #ifdef CLIENT_DLL SetNextClientThink( gpGlobals->curtime + 0.01f ); #else SetNextThink( gpGlobals->curtime + 0.01f ); #endif } } //================================================== // Purpose: Called by sv_bullet_stop_speed callback to keep track of resources //================================================== void CBulletManager::UpdateBulletStopSpeed(void) { m_iBulletStopSpeed = sv_bullet_stop_speed.GetInt(); } #ifndef CLIENT_DLL void CBulletManager::SendTraceAttackToTriggers( const CTakeDamageInfo &info, const Vector& start, const Vector& end, const Vector& dir ) { TraceAttackToTriggers(info,start,end,dir); } #endif //================================================== // Purpose: Add bullet to linked list // Handle lag compensation with "prebullet simulation" // Output: Bullet's index (-1 for error) //================================================== int CBulletManager::AddBullet(CSimulatedBullet *pBullet) { if (pBullet->GetAmmoTypeIndex() == -1) { Msg("ERROR: Undefined ammo type!\n"); return -1; } unsigned short index = g_Bullets.AddToTail(pBullet); #ifdef CLIENT_DLL DevMsg( "Client Bullet Created (%i)\n", index); if(g_Bullets.Count()==1) { SetNextClientThink(gpGlobals->curtime + 0.01f); } #else DevMsg( "Bullet Created (%i) LagCompensation %f\n", index, pBullet->bulletinfo.m_flLatency ); if(pBullet->bulletinfo.m_flLatency !=0.0f) pBullet->SimulateBullet(); //Pre-simulation if(g_Bullets.Count()==1) { SetNextThink(gpGlobals->curtime + 0.01f); } #endif return index; } //================================================== // Purpose: Remove the bullet at index from the linked list // Output: Next index //================================================== void CBulletManager::RemoveBullet(int index) { g_Bullets.Next(index); #ifdef CLIENT_DLL DevMsg("Client "); #endif DevMsg( "Bullet Removed (%i)\n", index ); g_Bullets.Remove(index); if(g_Bullets.Count()==0) { #ifdef CLIENT_DLL SetNextClientThink( TICK_NEVER_THINK ); #else SetNextThink( TICK_NEVER_THINK ); #endif } }
Other replacement:
// (More) Reallistic simulated bullets // // This code is originally based from article on Valve Developer Community // The original code you can find by this link: // http://developer.valvesoftware.com/wiki/Simulated_Bullets // // Modified by 'SoapyMan' #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() with following code:
void CBaseEntity::FireBullets( const FireBulletsInfo_t &info ) { // Make sure given a valid bullet type if (info.m_iAmmoType == -1) { DevMsg("ERROR: Undefined ammo type!\n"); return; } static int tracerCount; //trace_t tr; CAmmoDef* pAmmoDef = GetAmmoDef(); int nDamageType = pAmmoDef->DamageType(info.m_iAmmoType); int nAmmoFlags = pAmmoDef->Flags(info.m_iAmmoType); int iPlayerDamage = info.m_iPlayerDamage; if ( iPlayerDamage == 0 ) { if ( nAmmoFlags & AMMO_INTERPRET_PLRDAMAGE_AS_DAMAGE_TO_PLAYER ) { iPlayerDamage = pAmmoDef->PlrDamage( info.m_iAmmoType ); } } // the default attacker is ourselves CBaseEntity *pAttacker = info.m_pAttacker ? info.m_pAttacker : this; // Make sure we don't have a dangling damage target from a recursive call if ( g_MultiDamage.GetTarget() != NULL ) { ApplyMultiDamage(); } ClearMultiDamage(); g_MultiDamage.SetDamageType( nDamageType | DMG_NEVERGIB ); // Prediction is only usable on players int iSeed = 0; if ( IsPlayer() ) { iSeed = CBaseEntity::GetPredictionRandomSeed() & 255; } //----------------------------------------------------- // Set up our shot manipulator. //----------------------------------------------------- CShotManipulator Manipulator( info.m_vecDirShooting ); for (int iShot = 0; iShot < info.m_iShots; iShot++) { Vector vecDir; // Prediction is only usable on players if ( IsPlayer() ) { RandomSeed( iSeed ); // init random system with this seed } // If we're firing multiple shots, and the first shot has to be bang on target, ignore spread if ( iShot == 0 && info.m_iShots > 1 && (info.m_nFlags & FIRE_BULLETS_FIRST_SHOT_ACCURATE) ) { vecDir = Manipulator.GetShotDirection(); } else { // Don't run the biasing code for the player at the moment. vecDir = Manipulator.ApplySpread( info.m_vecSpread ); } Vector vecSrc(info.m_vecSrc); bool bTraceHull = ( IsPlayer() && info.m_iShots > 1 && iShot % 2 ); CSimulatedBullet *pBullet = new CSimulatedBullet(info,vecDir,pAttacker,info.m_pAdditionalIgnoreEnt, bTraceHull #ifndef CLIENT_DLL ,this #endif ); BulletManager()->AddBullet(pBullet); iSeed++; } #if defined( HL2MP ) && defined( GAME_DLL ) if ( bDoServerEffects == false ) { TE_HL2MPFireBullets( entindex(), tr.startpos, info.m_vecDirShooting, info.m_iAmmoType, iEffectSeed, info.m_iShots, info.m_vecSpread.x, bDoTracers, bDoImpacts ); } #endif ApplyMultiDamage(); }
cl_dll\c_te_hl2mp_shotgun_shot.cpp
It's undone and not rewritten. Please feel free to do helpful changes for it.
PS: Sorry for my English.