Simple Projectile Bullets: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
No edit summary
No edit summary
Line 179: Line 179:


And that's it! You now have simple projectile bullets. This can be used for both player weapons AND NPC's.
And that's it! You now have simple projectile bullets. This can be used for both player weapons AND NPC's.
[[Category:Free source code]]
[[Category:Weapons programming]]

Revision as of 15:24, 21 November 2021

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)
	{
		Error("ERROR: Firing an actual bullet without an attacker specified. This will crash the game.");
		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)
{
	if (GetOwnerEntity() && GetOwnerEntity()->IsPlayer())
	{
		CBasePlayer *pPlayer = dynamic_cast<CBasePlayer*>(GetOwnerEntity());
	}
	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 == nullptr)
	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.