Simple Projectile Bullets

From Valve Developer Community
Jump to: navigation, search
Wikipedia - Letter.png
This article has multiple issues. Please help improve it or discuss these issues on the talk page. (Learn how and when to remove these template messages)
Underlinked - Logo.png
This article needs more links to other articles to help integrate it into the encyclopedia. Please help improve this article by adding links that are relevant to the context within the existing text.
January 2024


Adding simple projectile bullets is easier than you'd think. If you've looked around the site, you might have found Realistic Simulated Bullets.

It's a great article, but there's many additional features that you might not want, and it overrides the existing hitscan method of firing bullets. This can be problematic, because you may want to keep the existing hitscan methods in-tact.

That's where this comes in. This is a very simple method of adding projectile bullets that utilizes parts of the existing bullet methods without overriding them. It fires an invisible bullet and uses tracers as a visual stand-in for the bullet.

Note.pngNote:This has only been tested in SDK 2013 SP and won't work in MP without modifications. Feel free to edit the article to make the necessary changes to make it functional in MP.

The Code

Add both of the following files to your Server project.

actual_bullet.h

#ifndef ACTUALBULLET_H
#define ACTUALBULLET_H

#include "cbase.h"
#include "baseentity.h"
#include "baseentity_shared.h"
#include "baseanimating.h"

class CActualBullet : public CBaseAnimating
{
	DECLARE_CLASS(CActualBullet, CBaseAnimating);
	DECLARE_DATADESC();

public:
	void Start(void);
	void Think(void);
	void Stop(void);

	Vector m_vecDir;
	int m_Speed;
	FireBulletsInfo_t info;
};

///so this is the actual bullet creation function.
inline void FireActualBullet(FireBulletsInfo_t &info, int iSpeed, const char* tracertype)
{
	if (!info.m_pAttacker)
	{
		Warning("ERROR: Firing an actual bullet without an attacker specified. This will crash the game without it. Cancelling.\n");
		return;
	}
	int iShots = info.m_iShots;

	for (int i = 0; i < iShots; i++)
	{
		Vector vecSpreadSrc = info.m_vecSpread;
		Vector vecSpread = Vector(RandomFloat(-vecSpreadSrc[0], vecSpreadSrc[0]), RandomFloat(-vecSpreadSrc[1], vecSpreadSrc[1]), RandomFloat(-vecSpreadSrc[2], vecSpreadSrc[2]));
		Vector vecShot = info.m_vecDirShooting + vecSpread;
		Vector vecShotDir = vecShot.Normalized();
		trace_t tr;
		UTIL_TraceLine(info.m_vecSrc, info.m_vecSrc + (vecShotDir * MAX_TRACE_LENGTH), MASK_SHOT, info.m_pAttacker, COLLISION_GROUP_NONE, &tr);
		CActualBullet *pBullet = (CActualBullet*)CBaseEntity::Create("actual_bullet", info.m_vecSrc, vec3_angle, info.m_pAttacker);
		pBullet->m_vecDir = vecShotDir;
		pBullet->m_Speed = iSpeed;
		pBullet->SetOwnerEntity(info.m_pAttacker);
		pBullet->SetAbsOrigin(info.m_vecSrc);
		pBullet->info = info;
		pBullet->Start();
		UTIL_Tracer(info.m_vecSrc, tr.endpos, info.m_pAttacker->entindex(), -1, (float)iSpeed, false, tracertype, 0);
	}
}

#endif //ACTUALBULLET_H

actual_bullet.cpp

#include "cbase.h"
#include "actual_bullet.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

ConVar debug_actual_bullet("debug_actual_bullet", "0", FCVAR_GAMEDLL);

LINK_ENTITY_TO_CLASS(actual_bullet, CActualBullet);

BEGIN_DATADESC(CActualBullet)
END_DATADESC()

void CActualBullet::Start(void)
{
	SetThink(&CActualBullet::Think);
	SetNextThink(gpGlobals->curtime);
	SetOwnerEntity(info.m_pAttacker);
}
void CActualBullet::Think(void)
{
	SetNextThink(gpGlobals->curtime + 0.05f);
	Vector vecStart;
	Vector vecEnd;
	float flInterval;

	flInterval = gpGlobals->curtime - GetLastThink();
	vecStart = GetAbsOrigin();
	vecEnd = vecStart + (m_vecDir * (m_Speed * flInterval));
	float flDist = (vecStart - vecEnd).Length();

	trace_t tr;
	UTIL_TraceLine(vecStart, vecEnd, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr);
	if (debug_actual_bullet.GetBool() == true)
		DebugDrawLine(vecStart, vecEnd, 0, 128, 255, false, 0.05f);

	if (tr.fraction != 1.0)
	{
		FireBulletsInfo_t info2;
		info2.m_iShots = 1;
		info2.m_vecSrc = vecStart;
		info2.m_vecSpread = vec3_origin;
		info2.m_vecDirShooting = m_vecDir;
		info2.m_flDistance = flDist;
		info2.m_iAmmoType = info.m_iAmmoType;
		info2.m_iTracerFreq = 0;
		GetOwnerEntity()->FireBullets(info2);
		Stop();
	}
	else
	{
		SetAbsOrigin(vecEnd);
	}
}

void CActualBullet::Stop(void)
{
	SetThink(NULL);
	UTIL_Remove(this);
}

baseentity_shared.cpp

Now, find the following around line 1596:

void CBaseEntity::FireBullets( const FireBulletsInfo_t &info )

Right before this:

int rumbleEffect = pPlayer->GetActiveWeapon()->GetRumbleEffect();

Add this:

CBaseCombatWeapon *pWeapon = pPlayer->GetActiveWeapon();
if (!pWeapon)
	return;

Usage

To use the projectile bullets, add the following to the top of your weapon file:

#include "actual_bullet.h"

In your PrimaryAttack() function, set up your FireBulletsInfo_t struct like you normally would. Example:

	FireBulletsInfo_t info;
	info.m_iAmmoType = m_iPrimaryAmmoType;
	info.m_iShots = 1;
	info.m_vecSrc = pPlayer->Weapon_ShootPosition();
	info.m_vecDirShooting = pPlayer->GetAutoaimVector(AUTOAIM_SCALE_DEFAULT);
	info.m_vecSpread = GetBulletSpread();
	info.m_pAttacker = GetOwnerEntity();

But instead of doing FireBullets(), do this:

FireActualBullet(info, 12000, GetTracerType());

Change the 12000 to whatever speed you wish, or if you want, you can set it to a ConVar to fine tune it in game.

And that's it! You now have simple projectile bullets. This can be used for both player weapons AND NPC's.

You can see the effect in action here.