Ricochets.

I am trying to make a system for some sweps where if a bullet hits with enough of an oblique angle, it ricochets, otherwise it penetrates. I’ve managed to get this working horizontally, however I simply cannot work out how to measure the vertical hits properly. Can someone help me out?
Code so far:
[lua]local
ricochetmats = {}
ricochetmats[MAT_ALIENFLESH] = { 80, 80}
ricochetmats[MAT_ANTLION] = { 80, 80}
ricochetmats[MAT_BLOODYFLESH] = { 80, 80}
ricochetmats[MAT_SAND] = { 80, 80}
ricochetmats[MAT_FLESH] = { 80, 80}
ricochetmats[MAT_FOLIAGE] = { 80, 80}
ricochetmats[MAT_SLOSH] = { 80, 80}
ricochetmats[MAT_DIRT] = { 80, 80}
ricochetmats[MAT_WOOD] = { 70, 70}
ricochetmats[MAT_CLIP] = { 90, 90}
ricochetmats[MAT_COMPUTER] = { 60, 40}
ricochetmats[MAT_CONCRETE] = { 80, 30}
ricochetmats[MAT_GLASS] = { 50, 30}
ricochetmats[MAT_GRATE] = { 40, 20}
ricochetmats[MAT_METAL] = { 40, 20}
ricochetmats[MAT_PLASTIC] = { 50, 30}
ricochetmats[MAT_TILE] = { 50, 30}
ricochetmats[MAT_VENT] = { 30, 10}
ricochetmats[88 ] = { 90, 90}
local function ricochet(tr)
local ang = tr.Normal:Angle()
–print(math.NormalizeAngle(y))
ang:RotateAroundAxis(tr.HitNormal,180)
return ang:Forward() * -1
end
concommand.Add(“ricochet”,function§
local tr = p:GetEyeTrace()
for i=1,10 do
if tr.Hit then
debugoverlay.Line(tr.StartPos,tr.HitPos,10,Color(237,40,31,255),true)
print(tr.MatType)
local p = math.abs(math.abs(math.NormalizeAngle(tr.HitNormal:Angle().p-tr.Normal:Angle().p))-90)
local y = math.abs(90-(math.abs(tr.HitNormal:Angle().y-tr.Normal:Angle().y)-90))
local p = math.abs(90-(math.abs(tr.Normal:Angle().p-tr.HitNormal:Angle().p)-90))
print(y,p)
if y < ricochetmats[tr.MatType][1]
–and p < ricochetmats[tr.MatType][2]
then
debugoverlay.Cross(tr.HitPos,10,10,color_white,true)
break
end
local line = ricochet(tr)
tr = util.TraceLine({start=tr.HitPos,endpos=tr.HitPos+line*4096})
end
end
end)
[/lua]

I think garry had this code in GMDM at one stage. I don’t know if it is still in there, but perhaps you should check that out.

GMDM does indeed have ricochets, but they do not have any limiters on them, other than number of bounces.
It does, however use this code:
[lua]// Bounce vector (Don’t worry - I don’t understand the maths either :x)
local DotProduct = tr.HitNormal:Dot( tr.Normal * -1 )
local Dir = ( 2 * tr.HitNormal * DotProduct ) + tr.Normal
Dir:Normalize()[/lua]
I’m still not entirely sure what dotting a vector does. Is that code better than mine for bounces?

Dot, as far as I know/remember, is some sort of an universal angle between v1 and v2 normalized. So, the dot from v1 and v2 is 1 if the vectors are parallel and pointing in the same direction, -1 if pointing in the opposite direction, and 0 if they are perpendicular. You can work your way out from there. If I’m right, that is.

Nevec, i am almost 100% sure you are right.

Thank you Nevec, you are right and that got it.

dot does not take into consideration yaw unfortunatly.
Use this:
–Returns the difference between two angles
function AngleOffset(ang1,ang2)
return Angle((ang1.p+180-ang2.p)%360-180,(ang1.y+180-ang2.y)%360-180,(ang1.r+180-ang2.r)%360-180)
end

So how would one implement this, like as in having a specific gun fire bullets that ricochet 1 time?

Try this. I’ll probably work.
[lua]local
ricochetmats = {}
ricochetmats[MAT_CLIP] =-0.1 --Clipping brushes I guess
ricochetmats[88 ] =-0.1 --skybox
ricochetmats[MAT_ALIENFLESH] = 0.2
ricochetmats[MAT_ANTLION] = 0.2
ricochetmats[MAT_BLOODYFLESH] = 0.2
ricochetmats[MAT_SAND] = 0.2
ricochetmats[MAT_FLESH] = 0.2
ricochetmats[MAT_FOLIAGE] = 0.2
ricochetmats[MAT_SLOSH] = 0.2
ricochetmats[MAT_DIRT] = 0.2
ricochetmats[MAT_WOOD] = 0.2
ricochetmats[MAT_COMPUTER] = 0.4
ricochetmats[MAT_CONCRETE] = 0.5
ricochetmats[MAT_GLASS] = 0.6
ricochetmats[MAT_PLASTIC] = 0.6
ricochetmats[MAT_TILE] = 0.6
ricochetmats[MAT_GRATE] = 0.8
ricochetmats[MAT_METAL] = 0.9
ricochetmats[MAT_VENT] = 0.9
local function bounce(tr)
local ang = tr.Normal:Angle()
ang:RotateAroundAxis(tr.HitNormal,180)
return ang:Forward() * -1
end
function SWEP:PrimaryAttack()
local bullet = {}
bullet.Num = 1 – Has to be 1, unless you wish to work out a system to save the bounceable traces. (which shouldn’t be too hard)
bullet.Src = self.Owner:GetShootPos()
bullet.Dir = self.Owner:GetAimVector()
bullet.Force = 40
bullet.Damage = 20
function bullet.Callback(attacker,trace,damageInfo)
if -(trace.Normal:Dot(trace.HitNormal)) < ricochetmats[trace.MatType] then
local bullet = {}
bullet.Num = 1
bullet.Src = trace.HitPos
bullet.Dir = bounce(trace)
bullet.Force = 40
bullet.Damage = 20
self.Owner:FireBullets(bullet)
end
end
self.Owner:FireBullets(bullet)
end[/lua]

Take a look at this.

Lexic,

Your code does work, but

  1. It only works after you aim a certain distance away from you, which is realistic in that stuff doesn’t ricochet if it has too steep an angle, but I want it to ricochet no matter what angle it has, and

  2. When you do aim far enough away, the ricochet does work, but it doesn’t show it as the bullet bouncing off the ground or whatever it hits, but it shows a second bullet being fired from the gun, aimed where the first bullet would hit if it did ricochet.

Do you have any idea how to fix this stuff?

[editline]06:52PM[/editline]

I found that what was making it only ricochet if you aim a certain distance away was the ‘if’ line you had in the Callback function, so I edited that out. But it still shows 2 bullets being fired, one where you aimed, and another straight to the place where the first bullet would have hit if it did ricochet.

If you want to see what I mean, you can look at this code that I used:

[lua]function SWEP:PrimaryAttack()

local bullet = {}  
bullet.Num      = 1 -- Has to be 1, unless you wish to work out a system to save the bounceable traces. (which shouldn't be too hard)  
bullet.Src      = self.Owner:GetShootPos()  
bullet.Dir      = self.Owner:GetAimVector()  
bullet.Tracer	= 1
bullet.TracerName = "punch_tracer"
bullet.Force    = 40  
bullet.Damage   = 20  
function bullet.Callback(attacker,trace,damageInfo)  
    --if  -(trace.Normal:Dot(trace.HitNormal)) &lt; ricochetmats[trace.MatType] then  
        local bullet = {}  
        bullet.Num      = 1  
        bullet.Src      = trace.HitPos  
        bullet.Dir      = bounce(trace)  
        bullet.Force    = 40  
        bullet.Damage   = 20  
		bullet.Tracer	= 1
		bullet.TracerName = "punch_tracer"
        self.Owner:FireBullets(bullet)  
    --end  
end  
self.Owner:FireBullets(bullet)  

end[/lua]

I left all your other code the same.

I think maybe what we could do is fire another bullet from where the first bullet hit at the angle that’s specified. But I don’t know how to do that currently D:

[editline]06:55PM[/editline]

It looks like it should fire from the point the first bullet hits, by this line

bullet.Src = trace.HitPos

but it’s not, the ricochet’ed bullet’s source is the end of the gun. I have no idea why it’s like this . Help?

[editline]07:04PM[/editline]

Also what’s weird is, the bullet that goes where the original bullet would have ricochet’ed has the tracer effect on it, but the bullet that goes where you shoot is just a regular pistol-type bullet.

So I guess the problem is, the second bullet is firing from the gun, not the place where the first bullet hit.

EDIT: Oops, why didn’t it automerge this?

I was working with that code, and I can confirm it works fine, and I can confirm that the tracer is fucked up

You could try to make a cosmetic bullet (Make the actual damaging bullet have no tracer, and fire a cosmetic one WITH tracer and 0 damage from a created entity at the hitpos)

How’d you come to that conclusion? The code, as you can see, fires a bullet with a tracer from the hitpos. But I guess you could make it create a temporary entity, set the parent to the player, fire a bullet from it that had a tracer and 0 damage, then destroy the entity, although that seems a lot of work for this, as it should work in the first place! Can some more people look at this and see what they can find?

Don’t worry, I finished it, it works perfectly fine,
@OP: tell me who to credit and I’ll post it

Wow, you don’t have to post a SWEP, just tell us how you got it to not screw up.

EDIT: Also, I got it to work, but the first bullet doesn’t have a tracer if the callback function is there… Please tell us all how you did it. No need to assign credit, it’s obvious, Lexic thought of the main code and you fixed a problem, wow.

EDIT: Now neither bullets have tracers. Wow. Hurry up and post the fix please.

Credit what? I made up that particular system for turning the bullets around myself so me? The dotty part was nevec though.

So Vax, you can tell us now D:

[lua]
/---------------------------------------------------------
ShootBullet
Major credit for full original code and ricochet in general goes to Lexic
Minor credit for dot stuff goes to Nevec
Fixing the code, adding chances and some minor edits go to GreatVax
---------------------------------------------------------
/

local
ricochetmats = {}

–Feel free to edit these numbers

ricochetmats[MAT_CLIP] =-0.1 --Clipping brushes I guess
ricochetmats[88 ] =-0.1 --skybox
ricochetmats[MAT_ALIENFLESH] = 0.2
ricochetmats[MAT_ANTLION] = 0.2
ricochetmats[MAT_BLOODYFLESH] = 0.2
ricochetmats[MAT_SAND] = 0.2
ricochetmats[MAT_FLESH] = 0.2
ricochetmats[MAT_FOLIAGE] = 0.2
ricochetmats[MAT_SLOSH] = 0.2
ricochetmats[MAT_DIRT] = 0.2
ricochetmats[MAT_WOOD] = 0.2
ricochetmats[MAT_COMPUTER] = 0.3
ricochetmats[MAT_CONCRETE] = 0.3
ricochetmats[MAT_GLASS] = 0.0
ricochetmats[MAT_PLASTIC] = 0.3
ricochetmats[MAT_TILE] = 0.5
ricochetmats[MAT_GRATE] = 0.8
ricochetmats[MAT_METAL] = 0.9
ricochetmats[MAT_VENT] = 0.9

local function bounce(tr)
local ang = tr.Normal:Angle()
ang:RotateAroundAxis(tr.HitNormal,180)
return ang:Forward() * -1
end

function SWEP:ShootBullet(dmg, recoil, numbul, cone)
local bullet = {}

bullet.Num  = numbul
bullet.Src = self.Owner:GetShootPos() 
bullet.Dir = self.Owner:GetAimVector() 
bullet.Spread = Vector(cone, cone, 0) 
bullet.Tracer = 0 --I have this on 0 because it just looks better, you can use a tracer ofc, it'll work
bullet.Force = dmg*0.1
bullet.Damage = dmg

self.Owner:FireBullets(bullet)		
self.Owner:MuzzleFlash()        	
self.Owner:SetAnimation(PLAYER_ATTACK1)

function bullet.Callback(attacker,trace,damageInfo)
	local dist = (trace.HitPos - self.Owner:GetShootPos() ):Length()
	local delay = dist/8000
	
	local Rangle = (math.Round((trace.Normal:Dot(trace.HitNormal)*-100))/100) / (ricochetmats[trace.MatType]*1)--0.4)
	
	local Minchance = -50 --Yeah this is kinda stupid, Feel free to adjust these numbers
	local Maxchance = 90
	
	if math.random(0,Maxchance) &lt; Rangle*100-Minchance then return end --gives you a lower chance the higher the angle
	timer.Simple( delay, function()		
		--if	-(trace.Normal:Dot(trace.HitNormal)) &lt; ricochetmats[trace.MatType]*0.4 then
		
			Effect = EffectData() --Make a ricochet effect
			Effect:SetOrigin(trace.HitPos)
			Effect:SetNormal(trace.HitNormal)
			Effect:SetScale(1)
			Effect:SetRadius(10)
			Effect:SetMagnitude(0.1)
			util.Effect("hitsmoke",Effect)
			util.Effect("inflator_magic",Effect) --Feel free to change these
		
			local bulletE = {}
				bulletE.Num 		= numbul
				bulletE.Src 		= trace.HitPos
				bulletE.Dir			= bounce(trace)
				bulletE.Spread 		= Vector(cone, cone, 0)
				bulletE.Tracer 		= 0
				bulletE.Force 		= dmg*0.1
				bulletE.Damage 		= dmg
				self.Owner:FireBullets(bulletE)
				
			local bulletE1 = {}
				bulletE1.Num 		= numbul
				bulletE1.Src 		= trace.HitPos
				bulletE1.Dir		= bounce(trace)
				bulletE1.Spread 	= Vector(cone, cone, 0)
				bulletE1.Tracer 	= 1
				bulletE1.Force 		= 0
				bulletE1.Damage 	= 0
				local Soundemitter = ents.Create( "light_dynamic" ) --Light_dynamic was just a random entity I had in my clipboard atm. Feel free to change this
				Soundemitter:SetPos(trace.HitPos)
				Soundemitter:FireBullets(bulletE1)
				Soundemitter:EmitSound("weapons/fx/rics/ric"..math.random(1,5)..".wav")
				Soundemitter:Remove()
		--end
	end)
end

self.Owner:FireBullets(bullet)

end[/lua]

I’m pretty sure it’ll work like this, I’m probably gonna edit this later on, but I’m using that code and it works fine.
Be sure to use rather shallow angles, trust me, 45° looks extremely stupid :stuck_out_tongue:

Thanks.

This is a bouncing lasergun, so the minimum angle doesn’t matter for me :stuck_out_tongue: