Simulated Bullets: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
Line 8: Line 8:
==Body==
==Body==
This code is WIP by ts2do
This code is WIP by ts2do
===cl_dll===
===game_shared===
====c_bullet_manager.cpp====
<pre>#include "cbase.h"
CON_COMMAND_F(createbullet,NULL,FCVAR_CHEAT)
{
engine->ServerCmd(VarArgs("createbullet_s %f\n",gpGlobals->curtime));
}</pre>
===dlls===
====bullet_manager.h====
====bullet_manager.h====
<pre>#define MAX_BULLETS 256
<pre>#define MAX_BULLETS 256
#define BULLET_SPEED 240//inches per hundredths of a second
#define BULLET_SPEED 240//inches per hundredths of a second
#ifdef CLIENT_DLL
#define CBulletManager C_BulletManager
#endif
class CBulletManager;
class CBulletManager;
extern CBulletManager *g_pBulletManager;
extern CBulletManager *g_pBulletManager;
Line 31: Line 27:
{
{
m_Bullet = FireBulletsInfo_t();
m_Bullet = FireBulletsInfo_t();
m_vOrigin = vec3_origin;
m_vOrigin.Init();
m_flInitialBulletSpeed = m_flBulletSpeed = BULLET_SPEED;
m_flInitialBulletSpeed = m_flBulletSpeed = BULLET_SPEED;
m_flEntryDensity = m_flLagCompensation = 0.0f;
m_flEntryDensity = m_flLagCompensation = 0.0f;
Line 43: Line 39:
m_flEntryDensity = 0.0f;
m_flEntryDensity = 0.0f;
m_flLagCompensation = lagCompensation;
m_flLagCompensation = lagCompensation;
#ifndef CLIENT_DLL
m_pIgnoreList = new CTraceFilterSimpleList(COLLISION_GROUP_NONE);
m_pIgnoreList = new CTraceFilterSimpleList(COLLISION_GROUP_NONE);
m_pIgnoreList->AddEntityToIgnore(m_Bullet.m_pAttacker);
m_pIgnoreList->AddEntityToIgnore(m_Bullet.m_pAttacker);
m_pIgnoreList->AddEntityToIgnore(m_Bullet.m_pAdditionalIgnoreEnt);
m_pIgnoreList->AddEntityToIgnore(m_Bullet.m_pAdditionalIgnoreEnt);
#endif
m_pTwoEnts = new CTraceFilterSkipTwoEntities(m_Bullet.m_pAttacker,m_Bullet.m_pAdditionalIgnoreEnt,COLLISION_GROUP_NONE);
m_pTwoEnts = new CTraceFilterSkipTwoEntities(m_Bullet.m_pAttacker,m_Bullet.m_pAdditionalIgnoreEnt,COLLISION_GROUP_NONE);
}
}
void DeletePointers(void)
void DeletePointers(void)
{
{
#ifndef CLIENT_DLL
//leave the compensation considerations because they're entities
//leave the compensation considerations because they're entities
delete m_pIgnoreList;
delete m_pIgnoreList;
#endif
delete m_pTwoEnts;
delete m_pTwoEnts;
}
}
Line 61: Line 61:
float m_flLagCompensation;
float m_flLagCompensation;
float m_flEntryDensity;
float m_flEntryDensity;
bool m_bWasInWater;
#ifndef CLIENT_DLL
CUtlVector<CBaseEntity *> m_pCompensationConsiderations;//Couldn't resist
CUtlVector<CBaseEntity *> m_pCompensationConsiderations;//Couldn't resist
CTraceFilterSimpleList *m_pIgnoreList;//already hit
CTraceFilterSimpleList *m_pIgnoreList;//already hit
#endif
CTraceFilterSkipTwoEntities *m_pTwoEnts;
CTraceFilterSkipTwoEntities *m_pTwoEnts;
};
};
Line 74: Line 77:
CBulletManager();
CBulletManager();
void Spawn(void);
void Spawn(void);
#ifdef CLIENT_DLL
void ClientThink(void);
#else
void Think(void);
void Think(void);
#endif
void SimulateBullet(int index, float time=1.0f);
void SimulateBullet(int index, float time=1.0f);
void AddBullet(FireBulletsInfo_t &bullet, float lagCompensation);
void AddBullet(FireBulletsInfo_t &bullet, float lagCompensation);
Line 85: Line 92:
#include "util_shared.h"
#include "util_shared.h"
#include "bullet_manager.h"
#include "bullet_manager.h"
#include "shot_manipulator.h"
#pragma warning(disable:4800)
ConVar g_debug_bullets( "g_debug_bullets", "0", FCVAR_CHEAT );
ConVar g_debug_bullets( "g_debug_bullets", "0", FCVAR_CHEAT );
CBulletManager *g_pBulletManager;
CBulletManager *g_pBulletManager;
Line 93: Line 102:
m_iLastBullet = -1;
m_iLastBullet = -1;
}
}
#ifdef CLIENT_DLL
CON_COMMAND_F(createbullet,NULL,FCVAR_CHEAT)
{
engine->ServerCmd(VarArgs("createbullet_s %f\n",gpGlobals->curtime));
}
#else
CON_COMMAND_F(createbullet_s,NULL,FCVAR_CHEAT)
CON_COMMAND_F(createbullet_s,NULL,FCVAR_CHEAT)
{
{
Line 110: Line 125:
BulletManager()->AddBullet(info,lag);
BulletManager()->AddBullet(info,lag);
}
}
#endif
void CBulletManager::Spawn(void)
void CBulletManager::Spawn(void)
{
{
g_pBulletManager = this;
#ifdef CLIENT_DLL
ClientThink();
#else
Think();
Think();
#endif
}
}
void CBulletManager::SimulateBullet(int index, float time/*=1.0f 100ths of sec*/)
void CBulletManager::SimulateBullet(int index, float time/*=1.0f 100ths of sec*/)
Line 123: Line 144:
Vector vecNewRay = CUR.m_Bullet.m_vecDirShooting * CUR.m_flBulletSpeed * time;
Vector vecNewRay = CUR.m_Bullet.m_vecDirShooting * CUR.m_flBulletSpeed * time;
CUR.m_vOrigin += vecNewRay;//in/100th of a sec * 100th of a sec
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 vecEntryPosition;
Vector vecExitPosition;
Vector vecExitPosition;
Line 141: Line 176:
else if(trace.startsolid)//exit solid
else if(trace.startsolid)//exit solid
{
{
#ifdef CLIENT_DLL
//TODO: penetration surface impact stuff
//TODO: penetration surface impact stuff
#endif //CLIENT_DLL
vecExitPosition = trace.fractionleftsolid * vecNewRay + m_vTraceStart;
vecExitPosition = trace.fractionleftsolid * vecNewRay + m_vTraceStart;
flPenetrationDistance = vecEntryPosition.DistTo(vecExitPosition);
flPenetrationDistance = vecEntryPosition.DistTo(vecExitPosition);
Line 153: Line 190:
return;
return;
}
}
#ifdef CLIENT_DLL
//TODO: surface impact stuff
//TODO: surface impact stuff
#endif //CLIENT_DLL
CUR.m_flEntryDensity = physprops->GetSurfaceData(trace.surface.surfaceProps)->physics.density;
CUR.m_flEntryDensity = physprops->GetSurfaceData(trace.surface.surfaceProps)->physics.density;
vecEntryPosition = trace.endpos;
vecEntryPosition = trace.endpos;
#ifdef GAME_DLL
if(trace.DidHitNonWorldEntity())
if(trace.DidHitNonWorldEntity())
{
{
Line 164: Line 204:
}
}
}
}
#endif //GAME_DLL
}
}
}
}
Line 169: Line 210:
}while(trace.endpos!=CUR.m_vOrigin&&CUR.m_flBulletSpeed>0.0f);
}while(trace.endpos!=CUR.m_vOrigin&&CUR.m_flBulletSpeed>0.0f);


#ifndef _DEBUG
#ifdef GAME_DLL
if(g_debug_bullets.GetBool())
if(g_debug_bullets.GetBool())
#endif
{
{
NDebugOverlay::Line(m_vOldOrigin,CUR.m_vOrigin, 255, 255, 255, true, 10.0f );
NDebugOverlay::Line(m_vOldOrigin,CUR.m_vOrigin, 255, 255, 255, true, 10.0f );
NDebugOverlay::Cross3D(CUR.m_vOrigin, 16, 255, 0, 0, true, 10.0f);
NDebugOverlay::Cross3D(CUR.m_vOrigin, 16, 255, 0, 0, true, 10.0f);
}
}
#endif //GAME_DLL


if(CUR.m_flBulletSpeed<=0.0f||!IsInWorld(index))
if(CUR.m_flBulletSpeed<=0.0f||!IsInWorld(index))
Line 181: Line 222:
#undef CUR
#undef CUR
}
}
#ifdef CLIENT_DLL
void CBulletManager::ClientThink(void)
#else
void CBulletManager::Think(void)
void CBulletManager::Think(void)
#endif
{
{
for(int x=0;x<=m_iLastBullet;x++)
for(int x=0;x<=m_iLastBullet;x++)
Line 187: Line 232:
SimulateBullet(x);
SimulateBullet(x);
}
}
SetNextThink( gpGlobals->curtime + 0.01f );
#ifdef CLIENT_DLL
SetNextClientThink
#else
SetNextThink
#endif
( gpGlobals->curtime + 0.01f );
}
}
void CBulletManager::AddBullet(FireBulletsInfo_t &bullet,float lagCompensation)
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
if(m_iLastBullet==MAX_BULLETS-1)//256 simultaneous bullets in the air is mayhem
{//let's do something reckless on bullet mayhem
{//let's do something reckless on bullet mayhem
Line 200: Line 255:
m_iLastBullet++;
m_iLastBullet++;
DevMsg("Bullet Created (%i) LagCompensation %f\n",m_iLastBullet,lagCompensation);
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);
m_rgBullets[m_iLastBullet] = SimulatedBullet_t(bullet,lagCompensation);
if(lagCompensation!=0.0f)
if(lagCompensation!=0.0f)

Revision as of 15:52, 21 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.


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
#define CBulletManager C_BulletManager
#endif
class CBulletManager;
extern CBulletManager *g_pBulletManager;
inline CBulletManager *BulletManager()
{
	return g_pBulletManager;
}
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)
ConVar g_debug_bullets( "g_debug_bullets", "0", FCVAR_CHEAT );
CBulletManager *g_pBulletManager;
LINK_ENTITY_TO_CLASS( bullet_manager, CBulletManager );
CBulletManager::CBulletManager()
{
	Q_memset( m_rgBullets, 0, MAX_BULLETS );
	m_iLastBullet = -1;
}
#ifdef CLIENT_DLL
CON_COMMAND_F(createbullet,NULL,FCVAR_CHEAT)
{
	engine->ServerCmd(VarArgs("createbullet_s %f\n",gpGlobals->curtime));
}
#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 );
		NDebugOverlay::Cross3D(CUR.m_vOrigin, 16, 255, 0, 0, true, 10.0f);
	}
#endif //GAME_DLL

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

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