Projectile based Weapons

: For general information on projectiles, see Projectiles and Authoring a weapon entity.
This article explains some of the possibilities for projectile weapons like the crossbow or RPG, instead of the standard guns that hit or miss instantly. All tweaks in this article are based on the crossbow code. The relevant code can all be found in the server side part of the crossbow code. This is found under hl -> source files -> HL2 DLL -> weapon_crossbow.cpp in the solution explorer
== Adding spread ==
Spread can be added rather quickly. The GetAutoAimVector used in other weapons appears to do no good here, so another approach is needed. I basically use the aiming vector converted to angles to change these angles, and then convert it back to the aiming vector. The relevant code is found in the CWeaponCrossbow::FireBolt method, the lines that need to be changed are: 
594 - 595
	QAngle angAiming;
	VectorAngles( vecAiming, angAiming );
these can be exchanged with:
	QAngle angAiming;
	VectorAngles( vecAiming, angAiming );
	angAiming.x += ((rand() % 250) / 100.0) * (rand() % 2 == 1 ? -1 : 1);
	angAiming.y += ((rand() % 250) / 100.0) * (rand() % 2 == 1 ? -1 : 1);
		
	AngleVectors(angAiming, &vecAiming);
This changes the x and y angles with up to 2.5 degrees in either direction. This actually results in a square spread instead of a cone, but is a good quick-and-dirty solution. To make a conical spread, take the cosine and sine of the x and y coordinates respectively. If anybody knows of a better approach, I would be happy to hear about it.
Here is an example of conical spread:
	QAngle angAiming;
	VectorAngles( vecAiming, angAiming );
	float RandomAngle = (rand() % 360);
	float RandMagnitudeX = ((rand() % 175) / 100.0);
	float RandMagnitudeY = ((rand() % 175) / 100.0);
	angAiming.x += (RandMagnitudeX)*cos(RandAngle);
	angAiming.y += (RandMagnitudeY)*sin(RandAngle);
	AngleVectors(angAiming, &vecAiming);
The RandMagnitude floats can have their limits changed to make the spread wider/narrower.
Giving X and Y different limits causes an ellipses shaped spread.
In order to simplify debugging, you may want to change line 608
	m_iClip1--;" 
to
	//m_iClip1--;"
and line 640
	m_flNextPrimaryAttack = m_flNextSecondaryAttack	= gpGlobals->curtime + 0.75;
to
	m_flNextPrimaryAttack = m_flNextSecondaryAttack	= gpGlobals->curtime + 0.15;
This gives you unlimited ammo and a quicker "reload" time.
== Obeying gravity ==
Crossbow bolts do not obey the sv_gravity server variable, which can be changed with the console, or I believe even on a per map basis by the mapper. In order to make this work properly, find the CCrossbowBolt::Spawn method and edit line no 163 (unmodified file):
	SetGravity( 0.05f );
change it to:
	SetGravity( sv_gravity.GetFloat() / 600);
Since the original value was 0.05, and gravity usually is set to 600 or 800, this means that the new gravity value will be larger, and the bolt will fly in a more pronounced arc. You will have to tweak the value as you see fit, but the bolt should now obey any server side changes in gravity.
In order to make this compile you will need to add
#include "movevars_shared.h"
to the includes in the top of the file, otherwise the sv_gravity variable is not accessible.
== Firing multiple bolts (not working yet) ==
In order to create a shotgun-like effect, it may be desirable to fire multiple randomized bolts at once. The following is an example:
	for (int i = -10; i < 11; i += 2)
	{
		Vector	vecAiming	= pOwner->GetAutoaimVector( 0 );	
		Vector vecSrc		= pOwner->Weapon_ShootPosition();
		QAngle angAiming;
		VectorAngles( vecAiming, angAiming );
		angAiming.x += ((rand() % 250) / 100.0) * (rand() % 2 == 1 ? -1 : 1);
		vecSrc.x += i;
		angAiming.y += ((rand() % 250) / 100.0) * (rand() % 2 == 1 ? -1 : 1);
		vecSrc.y += i;
		/*angAiming.z += ((rand() % 350) / 100.0) * (rand() % 2 == 1 ? -1 : 1);
		vecSrc.z += i;*/
		
		AngleVectors(angAiming, &vecAiming);
		CCrossbowBolt *pBolt = CCrossbowBolt::BoltCreate( vecSrc, angAiming, pOwner );
		if ( pOwner->GetWaterLevel() == 3 )
		{
			pBolt->SetAbsVelocity(vecAiming * BOLT_WATER_VELOCITY );
		}
		else
		{
			pBolt->SetAbsVelocity(vecAiming * BOLT_AIR_VELOCITY );
		}
	}
Changing the vecSrc.x and vecSrc.y based on i results in a neat line of crossbow bolts in the instant that the trigger is pressed. However, just a moment later all but the last (or first?) in the line disappear, and only the remaining bolt continues on its trajectory, and it is only this bolt that glows. Changing the the vecSrc.x and vecSrc.y based on random numbers creates a more randomized spawn position for each bolt, and when moving around the crossbow actually shoots 3-5 bolts, but still not all 10 of them, and not all of the time. For now, the solution for this is unknown.
Modding the crossbow so that it has multiple spawn points may help with getting the code to function.
== Modifying the RPG rocket to obey gravity ==
This section will show you how to make a rocket obey gravity.
This section also needs to add the code to turn off the RPG Guidance system but for now you may figure out how to do that using the Toggling RPG Guidance tutorial.
To start, open up weapon_rpg.cpp
In the CMissile::Spawn( void ) method,
void CMissile::Spawn( void )
{
 Precache();
 SetSolid( SOLID_BBOX );
 SetModel("models/weapons/w_missile_launch.mdl");
 UTIL_SetSize( this, -Vector(4,4,4), Vector(4,4,4) );
 SetTouch( &CMissile::MissileTouch );
 SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE );
 SetThink( &CMissile::IgniteThink );
 
 SetNextThink( gpGlobals->curtime + 0.3f );
 SetDamage( EXPLOSION_DAMAGE );
 m_takedamage = DAMAGE_YES;
 m_iHealth = m_iMaxHealth = 100;
 m_bloodColor = DONT_BLEED;
 m_flGracePeriodEndsAt = 0;
 AddFlag( FL_OBJECT );
}
add these lines
 // Test out grav.
 // We should definately have the rocket be modified by gravity.
 SetGravity( 1.0f );
between
SetDamage( EXPLOSION_DAMAGE );
and
m_takedamage = DAMAGE_YES;
Looking at this code, the rocket is spawned with MOVETYPE_FLYGRAVITY. And this is all well and good, but once the CMissile::IgniteThink( void ) method is called, the method changes the movetype to ignore gravity with MOVETYPE_FLY! (which is what you would want if you are still controlling the rocket with the laser).
So what we want to do is change the movetype back to MOVETYPE_FLYGRAVITY. Go ahead and change the CMissile::IgniteThink( void ) function to look like the one below.
Also below you will notice the Think function has been changed out to use &CMissile::DumbThink. This function isn't currently declared in the header OR the DATADESC section (which it needs to be for the rest of the code to know it exists and use it). But for now just change out the code to make the next think method &CMissile::DumbThink and forget about it until we implement its declaration in a below section.
void CMissile::IgniteThink( void )
{
 // If it's MOVETYPE_FLY then gravity won't affect it.
 // SetMoveType( MOVETYPE_FLY );
 SetMoveType( MOVETYPE_FLYGRAVITY );
 SetModel("models/weapons/w_missile.mdl");
 UTIL_SetSize( this, vec3_origin, vec3_origin );
 RemoveSolidFlags( FSOLID_NOT_SOLID );
 //TODO: Play opening sound
 Vector vecForward;
 EmitSound( "Missile.Ignite" );
 AngleVectors( GetLocalAngles(), &vecForward );
 SetAbsVelocity( vecForward * RPG_SPEED );
 // Changed out &CMissile::SeekThink for &CMissile::DumbThink.
 //SetThink( &CMissile::SeekThink );
 SetThink( &CMissile::DumbThink );
 SetNextThink( gpGlobals->curtime );
 if ( m_hOwner && m_hOwner->GetOwner() )
 {
 CBasePlayer *pPlayer = ToBasePlayer( m_hOwner->GetOwner() );
 if ( pPlayer )
 {
 color32 white = { 255,225,205,64 };
 UTIL_ScreenFade( pPlayer, white, 0.1f, 0.0f, FFADE_IN );
 }
 }
 CreateSmokeTrail();
}
Below is the new method CMissile::DumbThink( void ). This method is different than CMissile::DumbFire( void ) which you should not use if you want to make your rocket be affected by gravity.
The following function can be copied directly into the weapon_rpg.cpp class. A good place to put this would be after the CMissile::SeekThink( void ) method.
The first part of this Think function
 // If we have a grace period, go solid when it ends
 if ( m_flGracePeriodEndsAt )
 {
 if ( m_flGracePeriodEndsAt < gpGlobals->curtime )
 {
 RemoveSolidFlags( FSOLID_NOT_SOLID );
 m_flGracePeriodEndsAt = 0;
 }
 }
will check the Grace Period and remove the FSOLID_NOT_SOLID flags so the rocket may collide with surfaces and objects.
The next part of the function
 // Correct the face of the rocket.
 QAngle angNewAngles;
 VectorAngles( GetAbsVelocity(), angNewAngles );
 SetAbsAngles( angNewAngles );
will correct the face in which the rocket is pointing. You might want to comment out this section of the code to see what the rocket would look like if you don't understand fully what this code does. (But make sure you finish the rest of the tutorial before you try this!)
Finally,
 SetNextThink( gpGlobals->curtime + 0.1f );
will tell the code to execute this function again in 100 milliseconds. You need this as the new direction of the face needs to be input.
Here is the completed function.
//-----------------------------------------------------------------------------
// Purpose: To make the rocket follow gravity and set the face of it to what direction
// it's pointing.
//-----------------------------------------------------------------------------
void CMissile::DumbThink( void )
{
 
 // If we have a grace period, go solid when it ends
 if ( m_flGracePeriodEndsAt )
 {
 if ( m_flGracePeriodEndsAt < gpGlobals->curtime )
 {
 RemoveSolidFlags( FSOLID_NOT_SOLID );
 m_flGracePeriodEndsAt = 0;
 }
 }
 // Correct the face of the rocket.
 QAngle angNewAngles;
 VectorAngles( GetAbsVelocity(), angNewAngles );
 SetAbsAngles( angNewAngles );
 SetNextThink( gpGlobals->curtime + 0.1f );
}
The rest of this tutorial is to do some housekeeping so the code knows where to look for the CMissile::DumbThink( void ) method.
In the weapon_rpg.cpp, toward the top of the file is the DATADESC section. Modify the code to look as such. (You should be adding DEFINE_FUNCTION (DumbThink ), right after the code for SeekThink)
BEGIN_DATADESC( CMissile )
 DEFINE_FIELD( m_hOwner, FIELD_EHANDLE ),
 DEFINE_FIELD( m_hRocketTrail, FIELD_EHANDLE ),
 DEFINE_FIELD( m_flAugerTime, FIELD_TIME ),
 DEFINE_FIELD( m_flMarkDeadTime, FIELD_TIME ),
 DEFINE_FIELD( m_flGracePeriodEndsAt, FIELD_TIME ),
 DEFINE_FIELD( m_flDamage, FIELD_FLOAT ),
 DEFINE_FIELD( m_bCreateDangerSounds, FIELD_BOOLEAN ),
 
 // Function Pointers
 DEFINE_FUNCTION( MissileTouch ),
 DEFINE_FUNCTION( AccelerateThink ),
 DEFINE_FUNCTION( AugerThink ),
 DEFINE_FUNCTION( IgniteThink ),
 DEFINE_FUNCTION( SeekThink ),
 DEFINE_FUNCTION( DumbThink ), // Added dumb think method.
END_DATADESC()
Now you will need to go to the weapon_rpg.h file.
The below code in bold is the declaration of the DumbThink Method. This is needed so the definition of the method will know that this method is a part of the CMissile class.
Add the following code
 // Create a dumb think method. This should make it so the rpg obeys gravity.
 void DumbThink( void );
before
 void SeekThink( void );
Use this portion of the header as reference.
...
//###########################################################################
// >> CMissile (missile launcher class is below this one!)
//###########################################################################
class CMissile : public CBaseCombatCharacter
{
 DECLARE_CLASS( CMissile, CBaseCombatCharacter );
public:
 static const int EXPLOSION_RADIUS = 200;
 static const int EXPLOSION_DAMAGE = 200;
 CMissile();
 ~CMissile();
#ifdef HL1_DLL
 Class_T Classify( void ) { return CLASS_NONE; }
#else
 Class_T Classify( void ) { return CLASS_MISSILE; }
#endif
 
 void Spawn( void );
 void Precache( void );
 void MissileTouch( CBaseEntity *pOther );
 void Explode( void );
 void ShotDown( void );
 void AccelerateThink( void );
 void AugerThink( void );
 void IgniteThink( void );
 void SeekThink( void );
 
 // Create a dumb think method. This should make it so the rpg obeys gravity.
 void DumbThink( void );
 
 void DumbFire( void );
 void SetGracePeriod( float flGracePeriod );
 int OnTakeDamage_Alive( const CTakeDamageInfo &info );
 void Event_Killed( const CTakeDamageInfo &info );
 
 virtual float GetDamage() { return m_flDamage; }
 virtual void SetDamage(float flDamage) { m_flDamage = flDamage; }
 unsigned int PhysicsSolidMaskForEntity( void ) const;
 CHandle<CWeaponRPG> m_hOwner;
 static CMissile *Create( const Vector &vecOrigin, const QAngle &vecAngles, edict_t *pentOwner );
 void CreateDangerSounds( bool bState ){ m_bCreateDangerSounds = bState; }
After you've completed this part, you should be all done! Compile the build and test the changes to your mod!
From this point, to get the exact formula you want for how affected the rocket is by gravity, you should modify the RPG_SPEED value and the float value that is passed into the SetGravity( float ) function in the CMissile::Spawn( void ) method.
Category:Weapons programming
Category:Tutorials