Simulated Bullets

From Valve Developer Community
Revision as of 18:30, 22 November 2005 by Ts2do (talk | contribs) (→‎Body)
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.


Note.pngNote:This code is a work in progress. Feel free to make helpful changes. To test the code, you can bind a key to 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

game_shared

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

ammodef.h

ammodef.cpp

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

Making weapons shoot them