• Valve's Code on Projectiles - A how-to on making entities come out of the barrel!
    5 replies, posted
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. [b]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.[/b] [b]There are screen shots below which depict the snippet's effects.[/b] [i]The following code can be also found within the latest revision of [url=http://code.google.com/p/swep-bases/]The SWEP Bases Project.[/url][/i] [i]swep_frag[/i] [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] [b]Positioning equation:[/b] [lua] local vecEye = pPlayer:EyePos(); local vForward, vRight; vForward = pPlayer:GetForward(); vRight = pPlayer:GetRight(); local vecSrc = vecEye + vForward * 18.0 + vRight * 8.0; [/lua] [b]Explanation:[/b] 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. [b]Media:[/b] [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] [b]Positioning equation:[/b] [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] [b]Explanation:[/b] 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. [b]Media:[/b] [media]http://img.photobucket.com/albums/v308/flaming-gummy-bear/gm_construct0005-1.jpg[/media] [i]swep_rpg[/i] [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] [b]Positioning equation:[/b] [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] [b]Explanation:[/b] 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
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.
[code] 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 ) ... [/code] 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!
Sorry, you need to Log In to post a reply to this thread.