Valve's Code on Projectiles - A how-to on making entities come out of the barrel!

Hi! Just thought I’d share a tidbit of information on projectile ejection. The following code is a 1:1 Lua translation of Valve’s C++ Source SDK code for the weapon_frag and weapon_rpg, respectively.

The snippets below show you how to throw and launch entities from a predesignated barrel location. This helps add an extra, although small, still realistic factor to your weapons.

There are screen shots below which depict the snippet’s effects.

The following code can be also found within the latest revision of The SWEP Bases Project.
swep_frag
[lua]//-----------------------------------------------------------------------------
// Purpose:
// Input : *pPlayer -
//-----------------------------------------------------------------------------
function SWEP:ThrowGrenade( pPlayer )

if ( self.m_bRedraw ) then
	return;
end

if ( !CLIENT ) then
local vecEye = pPlayer:EyePos();
local vForward, vRight;

vForward = pPlayer:GetForward();
vRight = pPlayer:GetRight();
local vecSrc = vecEye + vForward * 18.0 + vRight * 8.0;
self:CheckThrowPosition( pPlayer, vecEye, vecSrc );

// vForward.x = vForward.x + 0.1;
// vForward.y = vForward.y + 0.1;

local vecThrow;
vecThrow = pPlayer:GetVelocity();
vecThrow = vecThrow + vForward * 1200;
local pGrenade = ents.Create( self.Primary.AmmoType );
pGrenade:SetPos( vecSrc );
pGrenade:SetAngles( vec3_angle );
pGrenade:SetOwner( pPlayer );
pGrenade:Fire( "SetTimer", GRENADE_TIMER );
pGrenade:Spawn()
pGrenade:GetPhysicsObject():SetVelocity( vecThrow );
pGrenade:GetPhysicsObject():AddAngleVelocity( Angle(600,math.random(-1200,1200),0) );

if ( pGrenade ) then
	if ( pPlayer && !pPlayer:Alive() ) then
		vecThrow = pPlayer:GetVelocity();

		local pPhysicsObject = pGrenade:GetPhysicsObject();
		if ( pPhysicsObject ) then
			vecThrow = pPhysicsObject:SetVelocity();
		end
	end

	pGrenade.m_flDamage = self.Primary.Damage;
	pGrenade.m_DmgRadius = GRENADE_DAMAGE_RADIUS;
end

end

self.m_bRedraw = true;

self.Weapon:EmitSound( self.Primary.Sound );

// player "shoot" animation
pPlayer:SetAnimation( PLAYER_ATTACK1 );

end
[/lua]

Positioning equation:
[lua] local vecEye = pPlayer:EyePos();
local vForward, vRight;

vForward = pPlayer:GetForward();
vRight = pPlayer:GetRight();
local vecSrc = vecEye + vForward * 18.0 + vRight * 8.0;

[/lua]

Explanation:
Since normally, we shoot entities out of the GetShootPos return value, which happens to be a player’s eyes, we don’t visually see the entity fly out of the barrel. By getting the forward normalized vector of a player (the direction they’re facing), and the right normalized vector (your right-hand side), we can multiply the normalized vectors to position the entity closer to the viewmodel’s barrel, therefore making it seem as if the entity has shot out of the gun.

Media:

http://img.photobucket.com/albums/v308/flaming-gummy-bear/gm_construct0011.jpg[/media]

[lua]//-----------------------------------------------------------------------------
// Purpose:
// Input : *pPlayer -
//-----------------------------------------------------------------------------
function SWEP:LobGrenade( pPlayer )

if ( self.m_bRedraw ) then
	return;
end

if ( !CLIENT ) then
local vecEye = pPlayer:EyePos();
local vForward, vRight;

vForward = pPlayer:GetForward();
vRight = pPlayer:GetRight();
local vecSrc = vecEye + vForward * 18.0 + vRight * 8.0 + Vector( 0, 0, -8 );
self:CheckThrowPosition( pPlayer, vecEye, vecSrc );

local vecThrow;
vecThrow = pPlayer:GetVelocity();
vecThrow = vecThrow + vForward * 350 + Vector( 0, 0, 50 );
local pGrenade = ents.Create( self.Primary.AmmoType );
pGrenade:SetPos( vecSrc );
pGrenade:SetAngles( vec3_angle );
pGrenade:SetOwner( pPlayer );
pGrenade:Fire( "SetTimer", GRENADE_TIMER );
pGrenade:Spawn()
pGrenade:GetPhysicsObject():SetVelocity( vecThrow );
pGrenade:GetPhysicsObject():AddAngleVelocity( Angle(200,math.random(-600,600),0) );

if ( pGrenade ) then
	pGrenade.m_flDamage = self.Primary.Damage;
	pGrenade.m_DmgRadius = GRENADE_DAMAGE_RADIUS;
end

end

self.Weapon:EmitSound( self.Secondary.Sound );

// player "shoot" animation
pPlayer:SetAnimation( PLAYER_ATTACK1 );

self.m_bRedraw = true;

end
[/lua]

Positioning equation:
[lua] local vecEye = pPlayer:EyePos();
local vForward, vRight;

vForward = pPlayer:GetForward();
vRight = pPlayer:GetRight();
local vecSrc = vecEye + vForward * 18.0 + vRight * 8.0 + Vector( 0, 0, -8 );

[/lua]

Explanation:
The decrease in Z position notes that the entity will be thrown slightly lower. In this case, it matches up nicely with the hand position during the weapon’s animation.

Media:
[media]http://img.photobucket.com/albums/v308/flaming-gummy-bear/gm_construct0005-1.jpg[/media]

swep_rpg
[lua]/---------------------------------------------------------
Name: SWEP:PrimaryAttack( )
Desc: +attack1 has been pressed
---------------------------------------------------------
/
function SWEP:PrimaryAttack()

// Only the player fires this way so we can cast
local pPlayer = self.Owner;

if (!pPlayer) then
	return;
end

if (self.m_bNeedReload) then
	return;
end

if ( self:Ammo1() <= 0 ) then
	self.Weapon:EmitSound( self.Primary.Empty );
	self.Weapon:SetNextPrimaryFire( CurTime() + self.Primary.Delay );

	return
end

if ( self.m_bIsUnderwater && !self.m_bFiresUnderwater ) then
	self.Weapon:EmitSound( self.Primary.Empty );
	self.Weapon:SetNextPrimaryFire( CurTime() + 0.2 );

	return;
end

// Can't have an active missile out
if ( VERSION >= 72 ) then
	if ( self.dt.Missile != NULL ) then
		return;
	end
elseif ( self.Weapon:GetNetworkedEntity( "Missile" ) != NULL ) then
	return;
end

// Can't be reloading
if ( self.Weapon:GetActivity() == ACT_VM_RELOAD || self.m_bInReload ) then
	return;
end

local vecOrigin;
local vecForward;

self.Weapon:SetNextPrimaryFire( CurTime() + 0.5 );

local pOwner = self.Owner;

if ( pOwner == NULL ) then
	return;
end

local	vForward, vRight, vUp;

vForward = pOwner:GetForward();
vRight = pOwner:GetRight();
vUp = pOwner:GetUp();

local	muzzlePoint = pOwner:GetShootPos() + vForward * 12.0 + vRight * 6.0 + vUp * -3.0;

if ( !CLIENT ) then
local vecAngles;
vecAngles = pOwner:GetAimVector():Angle();

local pMissile = ents.Create( self.Primary.AmmoType );
pMissile:SetPos( muzzlePoint );
pMissile:SetAngles( vecAngles );
pMissile:SetOwner( self.Owner );
pMissile:Spawn();

// If the shot is clear to the player, give the missile a grace period
local	tr;
local vecEye = pOwner:EyePos();
tr = {}
tr.startpos = vecEye
tr.endpos = vecEye + vForward * 128
tr.mask = MASK_SHOT
tr.filter = self.Weapon
tr.collision = COLLISION_GROUP_NONE
tr = util.TraceLine( tr );
if ( tr.Fraction == 1.0 ) then
	pMissile.GracePeriod = 0.3;
end

pMissile.Damage = self.Primary.Damage;

if ( VERSION >= 72 ) then
	self.dt.Missile = pMissile
else
	self.Weapon:SetNetworkedEntity( "Missile", pMissile );
end
self.m_hMissile = pMissile;

end

self:DecrementAmmo( self.Owner );
self.Weapon:SendWeaponAnim( ACT_VM_PRIMARYATTACK );
if ( !self.Owner:IsNPC() ) then
	self.Weapon:EmitSound( self.Primary.Sound );
else
	self.Weapon:EmitSound( self.Primary.SoundNPC );
end

// player "shoot" animation
pPlayer:SetAnimation( PLAYER_ATTACK1 );

self.m_bNeedReload = true;

end
[/lua]

Positioning equation:
[lua] local vForward, vRight, vUp;

vForward = pOwner:GetForward();
vRight = pOwner:GetRight();
vUp = pOwner:GetUp();

local	muzzlePoint = pOwner:GetShootPos() + vForward * 12.0 + vRight * 6.0 + vUp * -3.0;

[/lua]

Explanation:
As you can see, for different weapons, you may have to retrieve all directions to properly place the entity, rather than just the player’s forward and right-hand directions. Here we also move the entity down slightly.

Media:
[media]http://img.photobucket.com/albums/v308/flaming-gummy-bear/gm_construct0001-2.jpg

I hope this thread’s information has been informative, or useful to any of you out there. Best wishes to your work, and Merry Christmas.

Looks like it works perfectly, I’ll give it a shot next time I make some kind of projectile weapon

Rated useful.

Thanks, I made a projectile weapon a week or 2 ago but I didn’t know how to position it so it was just spawning in front of your face.



local muzzle, vm, att, direction_here, rocket, ...

vm = self.Owner:GetViewModel( )
muzzle = vm:LookupAttachment( "0" )
att = vm:GetAttachment( muzzle )

...
rocket:SetPos( att.Pos )
rocket:SetAngles( direction_here )
...


direction_here would probably be a normal from a trace’s hit pos and the attachment position.

This only really works with most models, given the hand grenade model and probably some others don’t have any attachments, but most of the time this is a simpler way to do it when it’s available.

Your code is quite useful, though, and I don’t mean to say it’s not.

I figured this out myself already, but still a nice tutorial. Useful’d :buddy:

Nice tutorial!