Simulated Bullets: Difference between revisions
Jump to navigation
Jump to search
Note:This code is a work in progress. Feel free to make helpful changes.
(→bullet_manager.h: Disallowed unsafe copying. Other minor good-practice changes. These changes untested.) |
|||
Line 15: | Line 15: | ||
<pre>#include "ammodef.h" | <pre>#include "ammodef.h" | ||
#define BULLET_SPEED 240//inches per hundredths of a second | #define BULLET_SPEED 240//inches per hundredths of a second | ||
const int BUTTER_MODE = -1; // special value indicating that bullets pass through objects without slowing | |||
const int ONE_HIT_MODE = -2; // special value indicating that bullets don't pass through objects at all | |||
#ifdef CLIENT_DLL | #ifdef CLIENT_DLL | ||
Line 47: | Line 47: | ||
); | ); | ||
~CSimulatedBullet(); | ~CSimulatedBullet(); | ||
inline float BulletSpeedRatio(void) | inline float BulletSpeedRatio(void) const | ||
{ | { | ||
return m_flBulletSpeed/m_flInitialBulletSpeed; | return m_flBulletSpeed/m_flInitialBulletSpeed; | ||
} | } | ||
inline bool IsInWorld(void) | inline bool IsInWorld(void) const | ||
{ | { | ||
if (m_vecOrigin.x >= MAX_COORD_INTEGER) return false; | if (m_vecOrigin.x >= MAX_COORD_INTEGER) return false; | ||
Line 65: | Line 65: | ||
#else | #else | ||
void EntityImpact(CBaseEntity *pHit); | void EntityImpact(CBaseEntity *pHit); | ||
inline float LagCompensation(void) | inline float LagCompensation(void) const | ||
{ | { | ||
return m_flLagCompensation; | return m_flLagCompensation; | ||
Line 71: | Line 71: | ||
void SimulateBullet(int index,float flTime=1.0f); | void SimulateBullet(int index,float flTime=1.0f); | ||
#endif | #endif | ||
inline int AmmoIndex(void) | inline int AmmoIndex(void) const | ||
{ | { | ||
return m_iAmmoType; | return m_iAmmoType; | ||
Line 103: | Line 103: | ||
Vector m_vecDirShooting; | Vector m_vecDirShooting; | ||
Vector m_vecOrigin; | Vector m_vecOrigin; | ||
// Prevent copies | |||
private: | |||
CSimulatedBullet( const CSimulatedBullet& other ); | |||
CSimulatedBullet& operator=( const CSimulatedBullet& other ); | |||
}; | }; | ||
class CBulletManager : public CBaseEntity | class CBulletManager : public CBaseEntity |
Revision as of 14:07, 23 November 2005
Preface
Basically, with simulated bullets, the aspects of physics are going to tried to be captured by simulating them in batch simulation code. So far the code is all server-side and is later expected to be client-side simulated with similar code.

Alteration
The code creates two ConVars which allow the host to change the penetration allowed and the maximum speed at which a bullet is removed.
Body
This code is WIP by ts2do
bullet_manager.h
#include "ammodef.h" #define BULLET_SPEED 240//inches per hundredths of a second const int BUTTER_MODE = -1; // special value indicating that bullets pass through objects without slowing const int ONE_HIT_MODE = -2; // special value indicating that bullets don't pass through objects at all #ifdef CLIENT_DLL class C_BulletManager; extern C_BulletManager *g_pBulletManager; #define CBulletManager C_BulletManager #else //!CLIENT_DLL class CBulletManager; extern CBulletManager *g_pBulletManager; #endif //CLIENT_DLL inline CBulletManager *BulletManager() { return g_pBulletManager; } extern ConVar g_debug_bullets; class CSimulatedBullet { public: CSimulatedBullet(); CSimulatedBullet( CBaseEntity *pAttacker, CBaseEntity *pAdditionalIgnoreEnt, int ammoType, int nFlags, Vector &vecDirShooting, Vector &vecOrigin, int damageType #ifndef CLIENT_DLL , CBaseEntity *pCaller, int iDamage, float flLagCompensation #endif ); ~CSimulatedBullet(); inline float BulletSpeedRatio(void) const { 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; } #ifdef CLIENT_DLL void SimulateBullet(int index); #else void EntityImpact(CBaseEntity *pHit); inline float LagCompensation(void) const { return m_flLagCompensation; } void SimulateBullet(int index,float flTime=1.0f); #endif inline int AmmoIndex(void) const { return m_iAmmoType; } private: bool m_bWasInWater; CTraceFilterSkipTwoEntities *m_pTwoEnts; CTraceFilterSimpleList *m_pIgnoreList;//already hit #ifndef CLIENT_DLL CUtlVector<CBaseEntity *> m_pCompensationConsiderations;//Couldn't resist #endif EHANDLE m_hCaller; FireBulletsFlags_t m_nFlags; float m_flBulletSpeed; float m_flEntryDensity; float m_flInitialBulletSpeed; #ifndef CLIENT_DLL float m_flLagCompensation; #endif int m_iAmmoType; #ifndef CLIENT_DLL int m_iDamage; #endif int m_nDamageType; Vector m_vecDirShooting; Vector m_vecOrigin; // Prevent copies private: CSimulatedBullet( const CSimulatedBullet& other ); CSimulatedBullet& operator=( const CSimulatedBullet& other ); }; class CBulletManager : public CBaseEntity { DECLARE_CLASS( CBulletManager, CBaseEntity ); public: #ifdef CLIENT_DLL void ClientThink(void); #else void Spawn(void); void Think(void); #endif void AddBullet(CSimulatedBullet *pBullet); void RemoveBullet(int index); private: CUtlVector<CSimulatedBullet*> m_pBullets; };
bullet_manager.cpp
#include "cbase.h" #include "util_shared.h" #include "bullet_manager.h" #include "shot_manipulator.h" #pragma warning(disable:4800) #ifdef CLIENT_DLL//------------------------------------------------- #include "c_te_effect_dispatch.h" #include "engine/ivdebugoverlay.h" ConVar g_cl_debug_bullets( "g_cl_debug_bullets", "0", FCVAR_CHEAT ); C_BulletManager *g_pBulletManager = (C_BulletManager *)CreateEntityByName("bullet_manager"); extern void FX_PlayerTracer( Vector& start, Vector& end); #else//------------------------------------------------------------- #include "soundent.h" #include "player_pickup.h" ConVar g_debug_bullets( "g_debug_bullets", "0", FCVAR_CHEAT ); CBulletManager *g_pBulletManager; #endif//------------------------------------------------------------ // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" static void BulletSpeedModifierCallback(ConVar *var, const char *pOldString) { if(var->GetFloat()==0.0f) var->Revert(); } ConVar sv_bullet_speed_modifier( "sv_bullet_speed_modifier", "700.000000", (FCVAR_ARCHIVE | FCVAR_REPLICATED), "Density/(This Value) * (Distance Penetrated) = (Change in Speed)", BulletSpeedModifierCallback ); static void BulletStopSpeedCallback(ConVar *var, const char *pOldString) { float val = var->GetFloat(); if(val==0.0f||(val<0.0f&&(val!=BUTTER_MODE||val!=ONE_HIT_MODE))) var->Revert(); } ConVar sv_bullet_stop_speed( "sv_bullet_stop_speed", "40.000000", (FCVAR_ARCHIVE | FCVAR_REPLICATED), "Speed at which to remove the bullet from the bullet queue\n-1 is butter mode\n-2 is 1 hit mode", BulletStopSpeedCallback ); LINK_ENTITY_TO_CLASS( bullet_manager, CBulletManager ); #ifndef CLIENT_DLL void CBulletManager::Spawn(void) { Think(); } #endif CSimulatedBullet::CSimulatedBullet() { m_vecOrigin.Init(); m_vecDirShooting.Init(); m_flInitialBulletSpeed = m_flBulletSpeed = BULLET_SPEED; #ifndef CLIENT_DLL m_flLagCompensation = #endif m_flEntryDensity = 0.0f; m_nFlags = (FireBulletsFlags_t)0; #ifndef CLIENT_DLL m_iDamage = m_nDamageType = #endif m_iAmmoType = 0; } CSimulatedBullet::CSimulatedBullet( CBaseEntity *pAttacker, CBaseEntity *pAdditionalIgnoreEnt, int ammoType, int nFlags, Vector &vecDirShooting, Vector &vecOrigin, int damageType #ifndef CLIENT_DLL , CBaseEntity *pCaller, int iDamage, float flLagCompensation #endif ) { m_nFlags = (FireBulletsFlags_t)nFlags; m_iAmmoType = ammoType; m_vecOrigin = vecOrigin; m_vecDirShooting = vecDirShooting; m_flInitialBulletSpeed = m_flBulletSpeed = GetAmmoDef()->GetAmmoOfIndex(ammoType)->flFeetPerSecond * 0.12; //m_flInitialBulletSpeed = m_flBulletSpeed = BULLET_SPEED; m_flEntryDensity = 0.0f; #ifndef CLIENT_DLL m_flLagCompensation = 100*flLagCompensation; m_iDamage = iDamage; m_hCaller = pCaller; #endif m_nDamageType = damageType; m_pIgnoreList = new CTraceFilterSimpleList(COLLISION_GROUP_NONE); m_pIgnoreList->AddEntityToIgnore(pAttacker); if(pAdditionalIgnoreEnt!=NULL) m_pIgnoreList->AddEntityToIgnore(pAdditionalIgnoreEnt); m_pTwoEnts = new CTraceFilterSkipTwoEntities(pAttacker,pAdditionalIgnoreEnt,COLLISION_GROUP_NONE); } CSimulatedBullet::~CSimulatedBullet() { delete m_pIgnoreList; delete m_pTwoEnts; } #define REMOVE_BULLET_AND_END(x) BulletManager()->RemoveBullet(x);return #ifdef CLIENT_DLL void CSimulatedBullet::SimulateBullet(int index) #else void CSimulatedBullet::SimulateBullet(int index, float flTime/*=1.00f (1/100 sec)*/) #endif { Vector vecEntryPosition; Vector vecExitPosition; float flPenetrationDistance = 0.0f; trace_t trace, trace_back; Vector vecOldOrigin(m_vecOrigin); Vector vecTraceStart(m_vecOrigin); #ifdef CLIENT_DLL Vector vecNewRay = m_vecDirShooting * m_flBulletSpeed; #else Vector vecNewRay = m_vecDirShooting * m_flBulletSpeed * flTime; #endif DevMsg("Bullet %i Speed %f\n",index,m_flBulletSpeed); m_vecOrigin += vecNewRay;//in/100th of a sec * 100th of a sec bool bInWater = UTIL_PointContents(m_vecOrigin)&MASK_SPLITAREAPORTAL; if(!IsInWorld()) { REMOVE_BULLET_AND_END(index); } #ifdef CLIENT_DLL FX_PlayerTracer( vecOldOrigin, m_vecOrigin ); #endif if(m_bWasInWater!=bInWater) { #ifdef CLIENT_DLL //TODO: water impact effect //CBaseEntity::HandleShotImpactingWater #endif //CLIENT_DLL } if(bInWater) { #ifdef CLIENT_DLL //TODO: 1 bubble clientside #endif //CLIENT_DLL } #ifdef CLIENT_DLL if(g_cl_debug_bullets.GetBool()) { debugoverlay->AddLineOverlay( vecOldOrigin, m_vecOrigin, 255, 0, 0, true, 10.0f ); } #else if(g_debug_bullets.GetBool()) { NDebugOverlay::Line( vecOldOrigin, m_vecOrigin, 255, 255, 255, true, 10.0f ); } #endif m_bWasInWater = bInWater; float flBulletStopSpeed = sv_bullet_stop_speed.GetFloat(); int iBulletStopSpeedCase = (int)flBulletStopSpeed; do { UTIL_TraceLine( vecTraceStart, m_vecOrigin, MASK_SHOT, m_pIgnoreList, &trace ); if(!(trace.surface.flags&SURF_SKY)) { if(trace.allsolid)//in solid { trace.endpos = m_vecOrigin; trace.fraction = 1.0f; switch(iBulletStopSpeedCase) { case BUTTER_MODE: { //Do nothing to bullet speed break; } case ONE_HIT_MODE: { REMOVE_BULLET_AND_END(index); } default: { m_flBulletSpeed -= m_flBulletSpeed * m_flEntryDensity / sv_bullet_speed_modifier.GetFloat(); break; } } break; } else if(trace.startsolid)//exit solid { #ifdef CLIENT_DLL //TODO: penetration surface impact stuff #endif //CLIENT_DLL vecExitPosition = trace.fractionleftsolid * vecNewRay + vecTraceStart; flPenetrationDistance = vecEntryPosition.DistTo(vecExitPosition); switch(iBulletStopSpeedCase) { case BUTTER_MODE: { //Do nothing to bullet speed break; } case ONE_HIT_MODE: { REMOVE_BULLET_AND_END(index); } default: { m_flBulletSpeed -= flPenetrationDistance * m_flEntryDensity / sv_bullet_speed_modifier.GetFloat(); break; } } } else if(trace.fraction!=1.0f)//hit solid { vecEntryPosition = trace.endpos; #ifndef CLIENT_DLL int soundEntChannel = ( m_nFlags&FIRE_BULLETS_TEMPORARY_DANGER_SOUND ) ? SOUNDENT_CHANNEL_BULLET_IMPACT : SOUNDENT_CHANNEL_UNSPECIFIED; CSoundEnt::InsertSound( SOUND_BULLET_IMPACT, vecEntryPosition, 200, 0.5, m_hCaller, soundEntChannel ); #endif if(FStrEq(trace.surface.name,"tools/toolsblockbullets")) { REMOVE_BULLET_AND_END(index); } #ifdef CLIENT_DLL CEffectData data; data.m_vStart = trace.startpos; data.m_vOrigin = trace.endpos; data.m_nDamageType = m_nDamageType; //Clientside ragdolls' locations cannot be determined by the server DispatchEffect( "RagdollImpact", data ); //TODO: surface impact stuff #endif //CLIENT_DLL m_flEntryDensity = physprops->GetSurfaceData(trace.surface.surfaceProps)->physics.density; if(trace.DidHitNonWorldEntity()) { if(m_pIgnoreList->ShouldHitEntity(trace.m_pEnt,MASK_SHOT)) { m_pIgnoreList->AddEntityToIgnore(trace.m_pEnt); #ifndef CLIENT_DLL EntityImpact(trace.m_pEnt); #endif } } if(iBulletStopSpeedCase==ONE_HIT_MODE) { REMOVE_BULLET_AND_END(index); } } } vecTraceStart = trace.endpos + m_vecDirShooting; }while(trace.endpos!=m_vecOrigin&&m_flBulletSpeed>flBulletStopSpeed); if(m_flBulletSpeed<=flBulletStopSpeed) { REMOVE_BULLET_AND_END(index); } } #ifndef CLIENT_DLL void CSimulatedBullet::EntityImpact(CBaseEntity *pHit) { //TODO: entity impact stuff if ( GetAmmoDef()->Flags(m_iAmmoType) & AMMO_FORCE_DROP_IF_CARRIED ) { // Make sure if the player is holding this, he drops it Pickup_ForcePlayerToDropThisObject( pHit ); } } void CBulletManager::Think(void) #else void CBulletManager::ClientThink(void) #endif { for(int x=0;x<m_pBullets.Count();x++) { m_pBullets[x]->SimulateBullet(x); } #ifdef CLIENT_DLL SetNextClientThink( gpGlobals->curtime + 0.01f ); #else SetNextThink( gpGlobals->curtime + 0.01f ); #endif } void CBulletManager::AddBullet(CSimulatedBullet *pBullet) { if (pBullet->AmmoIndex() == -1) { DevMsg("ERROR: Undefined ammo type!\n"); return; } int index = m_pBullets.AddToTail(pBullet); #ifdef CLIENT_DLL DevMsg( "Client Bullet Created (%i)\n", index); #else DevMsg( "Bullet Created (%i) LagCompensation %f\n", index, pBullet->LagCompensation() ); if(m_pBullets[index]->LagCompensation()!=0.0f) m_pBullets[index]->SimulateBullet(index, pBullet->LagCompensation()); #endif } void CBulletManager::RemoveBullet(int index) { #ifdef CLIENT_DLL DevMsg("Client "); #endif DevMsg( "Bullet Removed (%i)\n", index ); m_pBullets.Remove(index); }
hl2mp_gamerules.cpp
Change GetAmmoDef
to the following:
CAmmoDef *GetAmmoDef() { static CAmmoDef def; static bool bInitted = false; if ( !bInitted ) { bInitted = true; // Name Damage Tracer PlrDmg NPCDmg MaxCarry ft/sec Physics Force Impulse Flags def.AddAmmoType("AR2", DMG_BULLET, TRACER_LINE_AND_WHIZ, 0, 0, 60, 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, 1225, BULLET_IMPULSE(200, 1225), 0 ); def.AddAmmoType("SMG1", DMG_BULLET, TRACER_LINE_AND_WHIZ, 0, 0, 225, 1225, BULLET_IMPULSE(200, 1225), 0 ); def.AddAmmoType("357", DMG_BULLET, TRACER_LINE_AND_WHIZ, 0, 0, 12, 5000, BULLET_IMPULSE(800, 5000), 0 ); def.AddAmmoType("XBowBolt", DMG_BULLET, TRACER_LINE, 0, 0, 10, 8000, BULLET_IMPULSE(800, 8000), 0 ); def.AddAmmoType("Buckshot", DMG_BULLET | DMG_BUCKSHOT, TRACER_LINE, 0, 0, 30, 1200, BULLET_IMPULSE(400, 1200), 0 ); def.AddAmmoType("RPG_Round", DMG_BURN, TRACER_NONE, 0, 0, 3, 0, 0, 0 ); def.AddAmmoType("SMG1_Grenade", DMG_BURN, TRACER_NONE, 0, 0, 3, 0, 0, 0 ); def.AddAmmoType("Grenade", DMG_BURN, TRACER_NONE, 0, 0, 5, 0, 0, 0 ); def.AddAmmoType("slam", DMG_BURN, TRACER_NONE, 0, 0, 5, 0, 0, 0 ); } return &def; }
As you can see, the bullet speed is derived from the second argument of the bullet impulses.
ammodef.h
Change the Ammo_t
structure to the following:
struct Ammo_t { char *pName; int nDamageType; int eTracerType; float physicsForceImpulse; float flFeetPerSecond; int nMinSplashSize; int nMaxSplashSize; int nFlags; // Values for player/NPC damage and carrying capability // If the integers are set, they override the CVars int pPlrDmg; // CVar for player damage amount int pNPCDmg; // CVar for NPC damage amount int pMaxCarry; // CVar for maximum number can carry const ConVar* pPlrDmgCVar; // CVar for player damage amount const ConVar* pNPCDmgCVar; // CVar for NPC damage amount const ConVar* pMaxCarryCVar; // CVar for maximum number can carry };
Change the first two AddAmmoType
function prototypes to the following:
void AddAmmoType(char const* name, int damageType, int tracerType, int plr_dmg, int npc_dmg, int carry, float ftpersec, 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 ftpersec, float physicsForceImpulse, int nFlags, int minSplashSize = 4, int maxSplashSize = 8 );
ammodef.cpp
Add | float ftpersec, |
Before both | float physicsForceImpulse, |
Add | m_AmmoType[m_nAmmoIndex].flFeetPerSecond = ftpersec; |
After both | m_AmmoType[m_nAmmoIndex].physicsForceImpulse = physicsForceImpulse; |
Implementation
gamerules.cpp
Add | #include "bullet_manager.h" |
After | #include "filesystem.h" |
Add | g_pBulletManager = (CBulletManager*)CBaseEntity::Create( "bullet_manager", vec3_origin, vec3_angle ); |
After | g_pPlayerResource = (CPlayerResource*)CBaseEntity::Create( "player_manager", vec3_origin, vec3_angle ); |