Simulated Bullets
Jump to navigation
Jump to search
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.

createbullet
ingame and that will fire a bullet from your view with a line drawn along its path.Body
This code is WIP by ts2do
bullet_manager.h
#define MAX_BULLETS 256 #define BULLET_SPEED 240//inches per hundredths of a second #ifdef CLIENT_DLL class C_BulletManager; extern C_BulletManager *g_pBulletManager; inline C_BulletManager *ClientBulletManager() { return g_pBulletManager; } #define CBulletManager C_BulletManager #else //!CLIENT_DLL class CBulletManager; extern CBulletManager *g_pBulletManager; inline CBulletManager *BulletManager() { return g_pBulletManager; } #endif extern ConVar g_debug_bullets; struct SimulatedBullet_t { SimulatedBullet_t() { m_Bullet = FireBulletsInfo_t(); m_vOrigin.Init(); m_flInitialBulletSpeed = m_flBulletSpeed = BULLET_SPEED; m_flEntryDensity = m_flLagCompensation = 0.0f; } SimulatedBullet_t( FireBulletsInfo_t &bullet, float lagCompensation ) { m_Bullet = bullet; m_vOrigin = bullet.m_vecSrc; m_flInitialBulletSpeed = m_flBulletSpeed = BULLET_SPEED; m_flEntryDensity = 0.0f; m_flLagCompensation = lagCompensation; #ifndef CLIENT_DLL m_pIgnoreList = new CTraceFilterSimpleList(COLLISION_GROUP_NONE); m_pIgnoreList->AddEntityToIgnore(m_Bullet.m_pAttacker); m_pIgnoreList->AddEntityToIgnore(m_Bullet.m_pAdditionalIgnoreEnt); #endif m_pTwoEnts = new CTraceFilterSkipTwoEntities(m_Bullet.m_pAttacker,m_Bullet.m_pAdditionalIgnoreEnt,COLLISION_GROUP_NONE); } void DeletePointers(void) { #ifndef CLIENT_DLL //leave the compensation considerations because they're entities delete m_pIgnoreList; #endif delete m_pTwoEnts; } FireBulletsInfo_t m_Bullet; Vector m_vOrigin; float m_flBulletSpeed; float m_flInitialBulletSpeed; float m_flLagCompensation; float m_flEntryDensity; bool m_bWasInWater; #ifndef CLIENT_DLL CUtlVector<CBaseEntity *> m_pCompensationConsiderations;//Couldn't resist CTraceFilterSimpleList *m_pIgnoreList;//already hit #endif CTraceFilterSkipTwoEntities *m_pTwoEnts; }; class CBulletManager : public CBaseEntity { DECLARE_CLASS( CBulletManager, CBaseEntity ); public: SimulatedBullet_t m_rgBullets[MAX_BULLETS]; int m_iLastBullet; CBulletManager(); void Spawn(void); #ifdef CLIENT_DLL void ClientThink(void); #else void Think(void); #endif void SimulateBullet(int index, float time=1.0f); void AddBullet(FireBulletsInfo_t &bullet, float lagCompensation); void RemoveBullet(int index); inline bool IsInWorld(int index); };
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 ConVar g_cl_debug_bullets( "g_cl_debug_bullets", "0", FCVAR_CHEAT ); C_BulletManager *g_pBulletManager = new C_BulletManager(); #else ConVar g_debug_bullets( "g_debug_bullets", "0", FCVAR_CHEAT ); CBulletManager *g_pBulletManager; #endif LINK_ENTITY_TO_CLASS( bullet_manager, CBulletManager ); CBulletManager::CBulletManager() { Q_memset( m_rgBullets, 0, MAX_BULLETS ); m_iLastBullet = -1; } #ifdef CLIENT_DLL #include "engine/ivdebugoverlay.h" CON_COMMAND_F(createbullet,NULL,FCVAR_CHEAT) { Assert(ClientBulletManager()); if(!ClientBulletManager()) return; Vector dir; g_pLocalPlayer->EyeVectors(&dir); FireBulletsInfo_t info(1,g_pLocalPlayer->EyePosition(),dir,vec3_origin,0,0); info.m_pAttacker = g_pLocalPlayer; engine->ServerCmd(VarArgs("createbullet_s %f\n",gpGlobals->curtime)); ClientBulletManager()->AddBullet(info,0.0f); } #else CON_COMMAND_F(createbullet_s,NULL,FCVAR_CHEAT) { Assert(BulletManager()); if(!BulletManager()) return; CBasePlayer *pPlayer = UTIL_GetCommandClient(); if(!pPlayer) return; Vector dir; pPlayer->EyeVectors(&dir); FireBulletsInfo_t info(1,pPlayer->EyePosition(),dir,vec3_origin,0,0); info.m_pAttacker = pPlayer; float lag = gpGlobals->curtime - atof(engine->Cmd_Argv(1)); if(lag<0.0f||lag==gpGlobals->curtime) lag=0.0f; BulletManager()->AddBullet(info,lag); } #endif void CBulletManager::Spawn(void) { g_pBulletManager = this; #ifdef CLIENT_DLL ClientThink(); #else Think(); #endif } void CBulletManager::SimulateBullet(int index, float time/*=1.0f 100ths of sec*/) { #define CUR m_rgBullets[index] trace_t trace, trace_back; Vector m_vOldOrigin(CUR.m_vOrigin); Vector m_vTraceStart(CUR.m_vOrigin); DevMsg("BulletSpeed %f\n",CUR.m_flBulletSpeed); Vector vecNewRay = CUR.m_Bullet.m_vecDirShooting * CUR.m_flBulletSpeed * time; CUR.m_vOrigin += vecNewRay;//in/100th of a sec * 100th of a sec bool bInWater = UTIL_PointContents(CUR.m_vOrigin)&MASK_SPLITAREAPORTAL; if(CUR.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 } CUR.m_bWasInWater = bInWater; Vector vecEntryPosition; Vector vecExitPosition; float flPenetrationDistance = 0.0f; do { UTIL_TraceLine( m_vTraceStart, CUR.m_vOrigin, MASK_SHOT, CUR.m_pTwoEnts, &trace ); if(!(trace.surface.flags&SURF_SKY)) { if(trace.allsolid)//in solid { trace.endpos = CUR.m_vOrigin; trace.fraction = 1.0f; CUR.m_flBulletSpeed -= CUR.m_flBulletSpeed * CUR.m_flEntryDensity / 1000.0f; break; } else if(trace.startsolid)//exit solid { #ifdef CLIENT_DLL //TODO: penetration surface impact stuff #endif //CLIENT_DLL vecExitPosition = trace.fractionleftsolid * vecNewRay + m_vTraceStart; flPenetrationDistance = vecEntryPosition.DistTo(vecExitPosition); CUR.m_flBulletSpeed -= flPenetrationDistance * CUR.m_flEntryDensity / 1000.0f; } else if(trace.fraction!=1.0f)//hit solid { if(FStrEq(trace.surface.name,"tools/toolsblockbullets")) { RemoveBullet(index); return; } #ifdef CLIENT_DLL //TODO: surface impact stuff #endif //CLIENT_DLL CUR.m_flEntryDensity = physprops->GetSurfaceData(trace.surface.surfaceProps)->physics.density; vecEntryPosition = trace.endpos; #ifdef GAME_DLL if(trace.DidHitNonWorldEntity()) { if(CUR.m_pIgnoreList->ShouldHitEntity(trace.m_pEnt,MASK_SHOT)) { CUR.m_pIgnoreList->AddEntityToIgnore(trace.m_pEnt); //TODO: entity impact stuff } } #endif //GAME_DLL } } m_vTraceStart = trace.endpos + CUR.m_Bullet.m_vecDirShooting; }while(trace.endpos!=CUR.m_vOrigin&&CUR.m_flBulletSpeed>0.0f); #ifdef GAME_DLL if(g_debug_bullets.GetBool()) { NDebugOverlay::Line(m_vOldOrigin,CUR.m_vOrigin, 255, 255, 255, true, 10.0f ); } #else //!GAME_DLL if(g_cl_debug_bullets.GetBool()) { debugoverlay->AddLineOverlay(m_vTraceStart, CUR.m_vOrigin, 255, 0, 0, true, 10.0f); } #endif if(CUR.m_flBulletSpeed<=0.0f||!IsInWorld(index)) RemoveBullet(index); #undef CUR } #ifdef CLIENT_DLL void CBulletManager::ClientThink(void) #else void CBulletManager::Think(void) #endif { for(int x=0;x<=m_iLastBullet;x++) { SimulateBullet(x); } #ifdef CLIENT_DLL SetNextClientThink #else SetNextThink #endif ( gpGlobals->curtime + 0.01f ); } void CBulletManager::AddBullet(FireBulletsInfo_t &bullet,float lagCompensation) { if (bullet.m_iAmmoType == -1) { DevMsg("ERROR: Undefined ammo type!\n"); return; } if(m_iLastBullet==MAX_BULLETS-1)//256 simultaneous bullets in the air is mayhem {//let's do something reckless on bullet mayhem Assert(m_iLastBullet!=MAX_BULLETS-1); Warning("Bullet queue filled (Removing bullet #0)\n"); RemoveBullet(0); } else m_iLastBullet++; DevMsg("Bullet Created (%i) LagCompensation %f\n",m_iLastBullet,lagCompensation); CShotManipulator Manipulator( bullet.m_vecDirShooting ); bullet.m_vecDirShooting = Manipulator.ApplySpread(bullet.m_vecSpread); m_rgBullets[m_iLastBullet] = SimulatedBullet_t(bullet,lagCompensation); if(lagCompensation!=0.0f) SimulateBullet(m_iLastBullet, lagCompensation*100); } void CBulletManager::RemoveBullet(int index) { if(m_iLastBullet!=index) { m_rgBullets[index].DeletePointers(); m_rgBullets[index] = m_rgBullets[m_iLastBullet]; DevMsg("Bullet #%i Destroyed; Replacing with #%i\n",index, m_iLastBullet); } else { m_rgBullets[index].DeletePointers(); DevMsg("Bullet #%i Destroyed\n",index); } m_rgBullets[m_iLastBullet] = SimulatedBullet_t(); m_iLastBullet--; } inline bool CBulletManager::IsInWorld(int index) { if (m_rgBullets[index].m_vOrigin.x >= MAX_COORD_INTEGER) return false; if (m_rgBullets[index].m_vOrigin.y >= MAX_COORD_INTEGER) return false; if (m_rgBullets[index].m_vOrigin.z >= MAX_COORD_INTEGER) return false; if (m_rgBullets[index].m_vOrigin.x <= MIN_COORD_INTEGER) return false; if (m_rgBullets[index].m_vOrigin.y <= MIN_COORD_INTEGER) return false; if (m_rgBullets[index].m_vOrigin.z <= MIN_COORD_INTEGER) return false; return true; }
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
bullet_manager entity
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 ); |