-- Properties

local OptimalEffects = true
local RenderDistance = 400
local ScreenCullingEnabled = true
local ScreenCullingRadius = 16
FastCast.DebugLogging = false
FastCast.VisualizeCasts = false
FastCast.RayExit = true -- Only applies for Wall Penetration.

local Beam = Instance.new("Beam")
Beam.TextureSpeed = 0
Beam.LightEmission = 0
Beam.LightInfluence = 1
Beam.Transparency = NumberSequence.new(0)

local Caster = FastCast.new()

local function MakeImpactFX(Hit, Position, Normal, Material, ParentToPart, HitEffectData, BulletHoleData, ClientData)
	if Hit ~= nil and Position ~= nil and Normal ~= nil then
		local SurfaceCF = CFrame.new(Position, Position + Normal)
			
   		if HitEffectData[1] then		
			local Attachment = Instance.new("Attachment")
			Attachment.CFrame = SurfaceCF
	    	Attachment.Parent = Workspace.Terrain
			local Sound
			
			local function Spawner(material)
				if HitEffectData[6][material.Name]:FindFirstChild("MaterialSounds") then
		            local tracks = HitEffectData[6][material.Name].MaterialSounds:GetChildren()
		            local rn = math.random(1, #tracks)
		            local track = tracks[rn]
		            if track ~= nil then
						Sound = track:Clone()
						if track:FindFirstChild("Pitch") then
							Sound.PlaybackSpeed = Random.new():NextNumber(track.Pitch.Min.Value, track.Pitch.Max.Value)
						else
							Sound.PlaybackSpeed = Random.new():NextNumber(HitEffectData[3], HitEffectData[4])
						end
						if track:FindFirstChild("Volume") then
							Sound.Volume = Random.new():NextNumber(track.Volume.Min.Value, track.Volume.Max.Value)
						else
							Sound.Volume = HitEffectData[5]
						end
						Sound.Parent = Attachment
		            end
				else
					Sound = Instance.new("Sound",Attachment)
					Sound.SoundId = "rbxassetid://"..HitEffectData[2][math.random(1, #HitEffectData[2])]
					Sound.PlaybackSpeed = Random.new():NextNumber(HitEffectData[3], HitEffectData[4])
					Sound.Volume = HitEffectData[5]
				end
				for i, v in pairs(HitEffectData[6][material.Name]:GetChildren()) do
					if v.ClassName == "ParticleEmitter" then
						local Count = 1
						local Particle = v:Clone()
						Particle.Parent = Attachment
						if Particle:FindFirstChild("EmitCount") then
							Count = Particle.EmitCount.Value
						end
						if Particle.PartColor.Value then
							local HitPartColor = Hit and Hit.Color or Color3.fromRGB(255, 255, 255)
							if Hit and Hit:IsA("Terrain") then
								HitPartColor = Workspace.Terrain:GetMaterialColor(Material or Enum.Material.Sand)
							end
							Particle.Color = ColorSequence.new(HitPartColor, HitPartColor)
						end
						Thread:Delay(0.01, function()
							if OptimalEffects then
								local QualityLevel = UserSettings().GameSettings.SavedQualityLevel
								if QualityLevel == Enum.SavedQualitySetting.Automatic then
									local Compressor = 1 / 2
									Particle:Emit(Count * Compressor)
								else
									local Compressor = QualityLevel.Value / 21
									Particle:Emit(Count * Compressor)
								end
							else
								Particle:Emit(Count)
							end
							Debris:AddItem(Particle, Particle.Lifetime.Max)
						end)					
					end
				end
	        	Sound:Play()
			    if BulletHoleData[1] then
					local Hole = Instance.new("Attachment")
					Hole.Parent = ParentToPart and Hit or Workspace.Terrain
					Hole.WorldCFrame = SurfaceCF * CFrame.Angles(math.rad(90), math.rad(180), 0)
					if ParentToPart then
						local Scale = BulletHoleData[2]
						if HitEffectData[6][material.Name]:FindFirstChild("MaterialHoleSize") then
							Scale = HitEffectData[6][material.Name].MaterialHoleSize.Value
						end
						local A0 = Instance.new("Attachment")
						local A1 = Instance.new("Attachment")
						local BeamClone = Beam:Clone()
						BeamClone.Width0 = Scale
						BeamClone.Width1 = Scale
						if HitEffectData[6][material.Name]:FindFirstChild("MaterialDecals") then
				            local Decals = HitEffectData[6][material.Name].MaterialDecals:GetChildren()
							local Chosen = math.random(1, #Decals)
							local Decal = Decals[Chosen]
							if Decal ~= nil then
								BeamClone.Texture = "rbxassetid://"..Decal.Value
								if Decal.PartColor.Value then
									local HitPartColor = Hit and Hit.Color or Color3.fromRGB(255, 255, 255)
									if Hit and Hit:IsA("Terrain") then
										HitPartColor = Workspace.Terrain:GetMaterialColor(Material or Enum.Material.Sand)
									end
									BeamClone.Color = ColorSequence.new({ColorSequenceKeypoint.new(0, HitPartColor), ColorSequenceKeypoint.new(1, HitPartColor)})
								end
				            end
						else
							BeamClone.Texture = "rbxassetid://"..BulletHoleData[3][math.random(1, #BulletHoleData[3])]
						    if BulletHoleData[4] then
								local HitPartColor = Hit and Hit.Color or Color3.fromRGB(255, 255, 255)
								if Hit and Hit:IsA("Terrain") then
									HitPartColor = Workspace.Terrain:GetMaterialColor(Material or Enum.Material.Sand)
								end
								BeamClone.Color = ColorSequence.new({ColorSequenceKeypoint.new(0, HitPartColor), ColorSequenceKeypoint.new(1, HitPartColor)})
							end
						end
						BeamClone.Attachment0 = A0
						BeamClone.Attachment1 = A1
						A0.Parent = Hit
						A1.Parent = Hit
						A0.WorldCFrame = Hole.WorldCFrame * CFrame.new(Scale / 2, -0.01, 0) * CFrame.Angles(math.rad(90), 0, 0)
						A1.WorldCFrame = Hole.WorldCFrame * CFrame.new(-Scale / 2, -0.01, 0) * CFrame.Angles(math.rad(90), math.rad(180), 0)
						BeamClone.Parent = Workspace.Terrain
						Thread:Delay(BulletHoleData[5], function()
							if BulletHoleData[5] > 0 then
								if OptimalEffects then
									if ClientData then
										local t0 = os.clock()
										while Hole ~= nil do
											local Alpha = math.min((os.clock() - t0) / BulletHoleData[6], 1)
											if BeamClone then BeamClone.Transparency = NumberSequence.new(Math.Lerp(0, 1, Alpha)) end
										    if Alpha == 1 then break end
									      	Thread:Wait()
										end
										if A0 then A0:Destroy() end
										if A1 then A1:Destroy() end
										if BeamClone then BeamClone:Destroy() end
									    if Hole then Hole:Destroy() end
									else
										if A0 then A0:Destroy() end
										if A1 then A1:Destroy() end
										if BeamClone then BeamClone:Destroy() end
									    if Hole then Hole:Destroy() end
									end
								else
									local t0 = os.clock()
									while Hole ~= nil do
										local Alpha = math.min((os.clock() - t0) / BulletHoleData[6], 1)
										if BeamClone then BeamClone.Transparency = NumberSequence.new(Math.Lerp(0, 1, Alpha)) end
									    if Alpha == 1 then break end
									    Thread:Wait()
									end
									if A0 then A0:Destroy() end
									if A1 then A1:Destroy() end
									if BeamClone then BeamClone:Destroy() end
									if Hole then Hole:Destroy() end
								end
							else
								if A0 then A0:Destroy() end
								if A1 then A1:Destroy() end
								if BeamClone then BeamClone:Destroy() end
							    if Hole then Hole:Destroy() end
						    end
						end)
					else
						Debris:AddItem(Hole, 5)
					end
			    end
			end
		
			if not HitEffectData[7] then
				if HitEffectData[6]:FindFirstChild(Hit.Material) then
					Spawner(Hit.Material)
				else
					Spawner(HitEffectData[6].Custom)
				end
        	else
				Spawner(HitEffectData[6].Custom)
        	end

			Debris:AddItem(Attachment, 10)				
		end
	end
end

local function MakeBloodFX(Hit, Position, Normal, Material, ParentToPart, BloodEffectData, ClientData)
	if Hit ~= nil and Position ~= nil and Normal ~= nil then
		local SurfaceCF = CFrame.new(Position, Position + Normal)
		
	    if BloodEffectData[1] then
			local Attachment = Instance.new("Attachment")
			Attachment.CFrame = SurfaceCF
			Attachment.Parent = Workspace.Terrain
			
			local Sound = Instance.new("Sound",Attachment)
			Sound.SoundId = "rbxassetid://"..BloodEffectData[2][math.random(1, #BloodEffectData[2])]
			Sound.PlaybackSpeed = Random.new():NextNumber(BloodEffectData[3], BloodEffectData[4])
			Sound.Volume = BloodEffectData[5]
		
			local function Spawner()
				local C = BloodEffectData[6]:GetChildren()
				for i, v in pairs(BloodEffectData[6]:GetChildren()) do
					if v.ClassName == "ParticleEmitter" then
						local Count = 1
						local Particle = C[i]:Clone()
						Particle.Parent = Attachment
						if Particle:FindFirstChild("EmitCount") then
							Count = Particle.EmitCount.Value
						end
						Thread:Delay(0.01, function()
							if OptimalEffects then
								local QualityLevel = UserSettings().GameSettings.SavedQualityLevel
								if QualityLevel == Enum.SavedQualitySetting.Automatic then
									local Compressor = 1 / 2
									Particle:Emit(Count * Compressor)
								else
									local Compressor = QualityLevel.Value / 21
									Particle:Emit(Count * Compressor)
								end
							else
								Particle:Emit(Count)
							end
							Debris:AddItem(Particle, Particle.Lifetime.Max)
						end)
					end
				end
		        Sound:Play()
			end
			
			Thread:Spawn(Spawner)
		    Debris:AddItem(Attachment, 10)
		end
		
		if BloodEffectData[7][1] then
			local Hole = Instance.new("Attachment")
			Hole.Parent = ParentToPart and Hit or Workspace.Terrain
			Hole.WorldCFrame = SurfaceCF * CFrame.Angles(math.rad(90), math.rad(180), 0)
			if ParentToPart then
				local Scale = BloodEffectData[7][2]
				local A0 = Instance.new("Attachment")
				local A1 = Instance.new("Attachment")
				local BeamClone = Beam:Clone()
				BeamClone.Width0 = Scale
				BeamClone.Width1 = Scale
				BeamClone.Texture = "rbxassetid://"..BloodEffectData[7][3][math.random(1, #BloodEffectData[7][3])]
				BeamClone.Color = ColorSequence.new({ColorSequenceKeypoint.new(0, BloodEffectData[7][4]), ColorSequenceKeypoint.new(1, BloodEffectData[7][4])})
				if BloodEffectData[7][7] then
					local HitPartColor = Hit and Hit.Color or Color3.fromRGB(255, 255, 255)
					if Hit and Hit:IsA("Terrain") then
						HitPartColor = Workspace.Terrain:GetMaterialColor(Material or Enum.Material.Sand)
					end
					BeamClone.Color = ColorSequence.new({ColorSequenceKeypoint.new(0, HitPartColor), ColorSequenceKeypoint.new(1, HitPartColor)})
				end				
				BeamClone.Attachment0 = A0
				BeamClone.Attachment1 = A1
				A0.Parent = Hit
				A1.Parent = Hit
				A0.WorldCFrame = Hole.WorldCFrame * CFrame.new(Scale / 2, -0.01, 0) * CFrame.Angles(math.rad(90), 0, 0)
				A1.WorldCFrame = Hole.WorldCFrame * CFrame.new(-Scale / 2, -0.01, 0) * CFrame.Angles(math.rad(90), math.rad(180), 0)
				BeamClone.Parent = Workspace.Terrain
				Thread:Delay(BloodEffectData[7][5], function()
					if BloodEffectData[7][5] > 0 then
						if OptimalEffects then
							if ClientData then
								local t0 = os.clock()
								while Hole ~= nil do
									local Alpha = math.min((os.clock() - t0) / BloodEffectData[7][6], 1)
									if BeamClone then BeamClone.Transparency = NumberSequence.new(Math.Lerp(0, 1, Alpha)) end
								    if Alpha == 1 then break end
							      	Thread:Wait()
								end
								if A0 then A0:Destroy() end
								if A1 then A1:Destroy() end
								if BeamClone then BeamClone:Destroy() end
							    if Hole then Hole:Destroy() end
							else
								if A0 then A0:Destroy() end
								if A1 then A1:Destroy() end
								if BeamClone then BeamClone:Destroy() end
							    if Hole then Hole:Destroy() end
							end
						else
							local t0 = os.clock()
							while Hole ~= nil do
								local Alpha = math.min((os.clock() - t0) / BloodEffectData[7][6], 1)
								if BeamClone then BeamClone.Transparency = NumberSequence.new(Math.Lerp(0, 1, Alpha)) end
							    if Alpha == 1 then break end
							    Thread:Wait()
							end
							if A0 then A0:Destroy() end
							if A1 then A1:Destroy() end
							if BeamClone then BeamClone:Destroy() end
							if Hole then Hole:Destroy() end
						end
					else
						if A0 then A0:Destroy() end
						if A1 then A1:Destroy() end
						if BeamClone then BeamClone:Destroy() end
					    if Hole then Hole:Destroy() end
				    end
				end)
			else
				Debris:AddItem(Hole, 5)
			end
		end
	end	
end

local function OnRayHit(origin, direction, hitPart, hitPoint, normal, material, segmentVelocity, cosmeticBulletObject, hitData)
	local ShowEffects = ScreenCullingEnabled and (ScreenCulling(hitPoint, ScreenCullingRadius) and (hitPoint - Camera.CFrame.p).Magnitude <= RenderDistance) or (hitPoint - Camera.CFrame.p).Magnitude <= RenderDistance
	cosmeticBulletObject.Transparency = 1
	for i, v in pairs(cosmeticBulletObject:GetDescendants()) do
		if (v:IsA("BasePart") or v:IsA("Decal") or v:IsA("Texture")) then
			v.Transparency = 1
		elseif v:IsA("ParticleEmitter") then
			v.Enabled = false
		elseif v:IsA("Sound") then
			v:Stop()
		elseif v:IsA("PointLight") then
			v.Enabled = false
		--[[elseif v:IsA("Trail") then -- There is a case that trail is barely visible when projectile hits (especially at high speed). So I marked it as comment.
			v.Enabled = false]]
		elseif v:IsA("Beam") then
			v.Enabled = false
		end
	end
	Debris:AddItem(cosmeticBulletObject, 5) -- 10
	if not hitData[1][1] then
	    if hitPart ~= nil and hitPart.Parent ~= nil then
			if hitData[6] ~= nil and hitPart.Name == "_glass" then
				ShatterGlass:FireServer(hitPart, hitPoint, direction)
			else
				local Distance = (hitPoint - origin).Magnitude
				local Target = hitPart:FindFirstAncestorOfClass("Model")
				local TargetHumanoid = Target and Target:FindFirstChildOfClass("Humanoid")
				local TargetTorso = Target and (Target:FindFirstChild("HumanoidRootPart") or Target:FindFirstChild("Head"))
				local Tagger = Players:GetPlayerFromCharacter(hitData[5].Parent)
				if TargetHumanoid and TargetHumanoid.Health > 0 and TargetTorso then
					if ShowEffects then MakeBloodFX(hitPart, hitPoint, normal, material, true, hitData[4], hitData[6]) end
					if hitData[6] ~= nil then
						if TargetHumanoid.Health > 0 then
							Thread:Spawn(function()
								InflictTarget:InvokeServer(hitData[7], hitData[5],
			                    	Tagger,
			                       	TargetHumanoid,
					                TargetTorso,
									{hitData[6][1][1], hitData[6][1][2], hitData[6][1][3],Distance, hitData[6][1][4], hitData[6][1][5], hitData[6][1][6], hitData[6][1][7]},
					                {hitData[6][2][1], hitData[6][2][2], hitData[6][2][3], hitData[6][2][4], hitData[6][2][5]},
					                {hitData[6][3][1], hitData[6][3][2], hitData[6][3][3]},
					                hitPart,
					                {hitData[6][4][1], hitData[6][4][2], hitData[6][4][3], hitData[6][4][4], hitData[6][4][5], hitData[6][4][6], hitData[6][4][7]})
							end)
							if hitData[5].GunScript_Local:FindFirstChild("MarkerEvent") then
								hitData[5].GunScript_Local.MarkerEvent:Fire(hitPart.Name == "Head" and hitData[6][1][3])
							end
						end
					end
		   		else
					if ShowEffects then
						MakeImpactFX(hitPart, hitPoint, normal, material, true, hitData[2], hitData[3], hitData[6])
					end
		   		end
			end
		end
    else
	    if hitData[1][3] then
			local Attachment = Instance.new("Attachment")
			Attachment.CFrame = CFrame.new(hitPoint)
			Attachment.Parent = Workspace.Terrain
			local Sound = Instance.new("Sound")
		    Sound.SoundId = "rbxassetid://"..hitData[1][4][math.random(1, #hitData[1][4])]
		    Sound.PlaybackSpeed = Random.new():NextNumber(hitData[1][5], hitData[1][6])
		    Sound.Volume = hitData[1][7]
		    Sound.Parent = Attachment
		    Sound:Play()
			Debris:AddItem(Attachment, 10)		
		end
		
		local Explosion = Instance.new("Explosion")
		Explosion.BlastRadius = hitData[1][2]
		Explosion.BlastPressure = 0
		Explosion.ExplosionType = Enum.ExplosionType.NoCraters
	    Explosion.Position = hitPoint ~= nil and hitPoint or cosmeticBulletObject.Position 
		Explosion.Parent = Camera
		
		local SurfaceCF = hitPart ~= nil and hitPoint ~= nil and normal ~= nil and CFrame.new(hitPoint, hitPoint + normal) or cosmeticBulletObject.CFrame
		
		if ShowEffects and hitPart ~= nil and hitPoint ~= nil and normal ~= nil then
			if hitData[1][11] then
				local Target = hitPart:FindFirstAncestorOfClass("Model")
				if hitPart and not hitPart.Parent or not (Target and Target:FindFirstChildOfClass("Humanoid")) then
					local ParentToPart = true
					local Hole = Instance.new("Attachment")
					Hole.Parent = ParentToPart and hitPart or Workspace.Terrain
					Hole.WorldCFrame = SurfaceCF * CFrame.Angles(math.rad(90), math.rad(180), 0)
					if ParentToPart then
						local Scale = hitData[1][12]
						local A0 = Instance.new("Attachment")
						local A1 = Instance.new("Attachment")
						local BeamClone = Beam:Clone()
						BeamClone.Width0 = Scale
						BeamClone.Width1 = Scale
						BeamClone.Texture = "rbxassetid://"..hitData[1][13][math.random(1, #hitData[1][13])]
				    	if hitData[1][14] then
							local HitPartColor = hitPart and hitPart.Color or Color3.fromRGB(255, 255, 255)
							if hitPart and hitPart:IsA("Terrain") then
								HitPartColor = Workspace.Terrain:GetMaterialColor(material or Enum.Material.Sand)
							end
							BeamClone.Color = ColorSequence.new({ColorSequenceKeypoint.new(0, HitPartColor), ColorSequenceKeypoint.new(1, HitPartColor)})
						end			
						BeamClone.Attachment0 = A0
						BeamClone.Attachment1 = A1
						A0.Parent = hitPart
						A1.Parent = hitPart
						A0.WorldCFrame = Hole.WorldCFrame * CFrame.new(Scale / 2, -0.01, 0) * CFrame.Angles(math.rad(90), 0, 0)
						A1.WorldCFrame = Hole.WorldCFrame * CFrame.new(-Scale / 2, -0.01, 0) * CFrame.Angles(math.rad(90), math.rad(180), 0)
						BeamClone.Parent = workspace.Terrain
						Thread:Delay(hitData[1][15], function()
							if hitData[1][15] > 0 then
								if OptimalEffects then
									if hitData[6] then
										local t0 = os.clock()
										while Hole ~= nil do
											local Alpha = math.min((os.clock() - t0) / hitData[1][16], 1)
											if BeamClone then BeamClone.Transparency = NumberSequence.new(Math.Lerp(0, 1, Alpha)) end
										    if Alpha == 1 then break end
									      	Thread:Wait()
										end
										if A0 then A0:Destroy() end
										if A1 then A1:Destroy() end
										if BeamClone then BeamClone:Destroy() end
									    if Hole then Hole:Destroy() end
									else
										if A0 then A0:Destroy() end
										if A1 then A1:Destroy() end
										if BeamClone then BeamClone:Destroy() end
									    if Hole then Hole:Destroy() end
									end
								else
									local t0 = os.clock()
									while Hole ~= nil do
										local Alpha = math.min((os.clock() - t0) / hitData[1][16], 1)
										if BeamClone then BeamClone.Transparency = NumberSequence.new(Math.Lerp(0, 1, Alpha)) end
									    if Alpha == 1 then break end
									    Thread:Wait()
									end
									if A0 then A0:Destroy() end
									if A1 then A1:Destroy() end
									if BeamClone then BeamClone:Destroy() end
									if Hole then Hole:Destroy() end
								end
							else
								if A0 then A0:Destroy() end
								if A1 then A1:Destroy() end
								if BeamClone then BeamClone:Destroy() end
							    if Hole then Hole:Destroy() end
						    end
						end)
					else
						Debris:AddItem(Hole, 5)
					end
				end
			end
		end
		
		if hitData[1][8] then
			Explosion.Visible = false
			
			if ShowEffects then
			    local Attachment = Instance.new("Attachment")
			    Attachment.CFrame = SurfaceCF
		        Attachment.Parent = Workspace.Terrain
		
				local function Spawner()
					for i, v in pairs(hitData[1][9]:GetChildren()) do
						if v.ClassName == "ParticleEmitter" then
							local Count = 1
							local Particle = v:Clone()
							Particle.Parent = Attachment
							if Particle:FindFirstChild("EmitCount") then
								Count = Particle.EmitCount.Value
							end
							Thread:Delay(0.01, function()
								if OptimalEffects then
									local QualityLevel = UserSettings().GameSettings.SavedQualityLevel
									if QualityLevel == Enum.SavedQualitySetting.Automatic then
										local Compressor = 1 / 2
										Particle:Emit(Count * Compressor)
									else
										local Compressor = QualityLevel.Value / 21
										Particle:Emit(Count * Compressor)
									end
								else
									Particle:Emit(Count)
								end
								Debris:AddItem(Particle, Particle.Lifetime.Max)
							end)
						end
					end
			    end
			
				Thread:Spawn(Spawner)
		        Debris:AddItem(Attachment, 10)
			end
		end	
		
		local HitHumanoids = {}
		
		Explosion.Hit:Connect(function(HitPart, HitDist)
			if HitPart and hitData[6] ~= nil then
				if HitPart.Parent and HitPart.Name == "HumanoidRootPart" or HitPart.Name == "Head" then
					local Distance = (hitPoint - origin).Magnitude
					local Target = HitPart:FindFirstAncestorOfClass("Model")
					local TargetHumanoid = Target and Target:FindFirstChildOfClass("Humanoid")
					local TargetTorso = Target and (Target:FindFirstChild("HumanoidRootPart") or Target:FindFirstChild("Head"))
					local Tagger = Players:GetPlayerFromCharacter(hitData[5].Parent)
					if TargetHumanoid then
						if TargetHumanoid.Health > 0 then
							if not HitHumanoids[TargetHumanoid] then
								if hitData[1][17] then
									local Multipler = hitData[1][20]
									local DistanceFactor = HitDist / hitData[1][2]
									DistanceFactor = 1 - DistanceFactor
									local VelocityMod = (TargetTorso.Position - Explosion.Position).Unit * hitData[1][18] --* DistanceFactor
									local AirVelocity = TargetTorso.Velocity - Vector3.new(0, TargetTorso.Velocity.y, 0) + Vector3.new(VelocityMod.X, 0, VelocityMod.Z)
									if DamageModule.CanDamage(Target, hitData[5].Parent, hitData[6][1][7]) then
										local TorsoFly = Instance.new("BodyVelocity")
										TorsoFly.MaxForce = Vector3.new(math.huge, 0, math.huge)
										TorsoFly.Velocity = AirVelocity
										TorsoFly.Parent = TargetTorso
										TargetTorso.Velocity = TargetTorso.Velocity + Vector3.new(0, VelocityMod.Y * Multipler, 0)
										Debris:AddItem(TorsoFly, 0.25)	
									else
										if TargetHumanoid.Parent.Name == Tagger.Name then
											Multipler = hitData[1][19]
											local TorsoFly = Instance.new("BodyVelocity")
											TorsoFly.MaxForce = Vector3.new(math.huge, 0, math.huge)
											TorsoFly.Velocity = AirVelocity
											TorsoFly.Parent = TargetTorso
											TargetTorso.Velocity = TargetTorso.Velocity + Vector3.new(0, VelocityMod.Y * Multipler, 0)
											Debris:AddItem(TorsoFly, 0.25)
										end
									end							
								end
								Thread:Spawn(function()
									InflictTarget:InvokeServer(hitData[7], hitData[5],
										Tagger,
										TargetHumanoid,
										TargetTorso,
										{hitData[6][1][1], hitData[6][1][2], hitData[6][1][3], Distance, hitData[6][1][4], hitData[6][1][5], hitData[6][1][6], hitData[6][1][7]},
										{hitData[6][2][1], hitData[6][2][2], hitData[6][2][3], hitData[6][2][4], hitData[6][2][5]},
										{hitData[6][3][1], hitData[6][3][2], hitData[6][3][3]},
										HitPart,
										{hitData[6][4][1], hitData[6][4][2], hitData[6][4][3], hitData[6][4][4], hitData[6][4][5], hitData[6][4][6], hitData[6][4][7]},
										{hitData[1][1], hitData[1][2], HitDist, hitData[1][10], hitData[1][21], hitData[1][22], hitData[1][23]})
								end)
								if hitData[5].GunScript_Local:FindFirstChild("MarkerEvent") then
									hitData[5].GunScript_Local.MarkerEvent:Fire(HitPart.Name == "Head" and hitData[6][1][3])
								end
								HitHumanoids[TargetHumanoid] = true
							end   	
						end
					end
				elseif HitPart.Name == "_glass" then
					ShatterGlass:FireServer(HitPart, HitPart.Position, direction)
		    	end
			end
	    end)
	end
end

local function CanRayPenetrate(origin, direction, hitPart, hitPoint, normal, material, segmentVelocity, hitData)
	-- This function shows off the penetrating feature. Pass this function as the last argument (after bulletAcceleration) and it will run this every time the ray runs into an object
	local ShowEffects = ScreenCullingEnabled and (ScreenCulling(hitPoint, ScreenCullingRadius) and (hitPoint - Camera.CFrame.p).Magnitude <= RenderDistance) or (hitPoint - Camera.CFrame.p).Magnitude <= RenderDistance
	if hitPart ~= nil and hitPart.Parent ~= nil then
		if hitData[6] ~= nil and hitPart.Name == "_glass" then
			ShatterGlass:FireServer(hitPart, hitPoint, direction)
			return "penetratedObject" -- Penetrate Object
		else
			local Distance = (hitPoint - origin).Magnitude
			local Target = hitPart:FindFirstAncestorOfClass("Model")
			local TargetHumanoid = Target and Target:FindFirstChildOfClass("Humanoid")
			local TargetTorso = Target and (Target:FindFirstChild("HumanoidRootPart") or Target:FindFirstChild("Head"))
			local Tagger = Players:GetPlayerFromCharacter(hitData[5].Parent)
			if TargetHumanoid and TargetHumanoid.Health > 0 and TargetTorso then
				if ShowEffects then MakeBloodFX(hitPart, hitPoint, normal, material, true, hitData[4], hitData[6]) end
				if hitData[6] ~= nil then
					if TargetHumanoid.Health > 0 then
						Thread:Spawn(function()
							InflictTarget:InvokeServer(hitData[7], hitData[5],
		                    	Tagger,
		                      	TargetHumanoid,
				               	TargetTorso,
								{hitData[6][1][1], hitData[6][1][2], hitData[6][1][3], Distance, hitData[6][1][4], hitData[6][1][5], hitData[6][1][6], hitData[6][1][7]},
				               	{hitData[6][2][1], hitData[6][2][2], hitData[6][2][3], hitData[6][2][4], hitData[6][2][5]},
				               	{hitData[6][3][1], hitData[6][3][2], hitData[6][3][3]},
				               	hitPart,
				               	{hitData[6][4][1], hitData[6][4][2], hitData[6][4][3], hitData[6][4][4], hitData[6][4][5], hitData[6][4][6], hitData[6][4][7]})
						end)
						if hitData[5].GunScript_Local:FindFirstChild("MarkerEvent") then
							hitData[5].GunScript_Local.MarkerEvent:Fire(hitPart.Name == "Head" and hitData[6][1][3])
						end  
					end
				end
				return "penetratedHumanoid" -- Penetrate Humanoid
		  	else
		   		if ShowEffects then MakeImpactFX(hitPart, hitPoint, normal, material, true, hitData[2], hitData[3], hitData[6]) end
				return "penetratedObject" -- Penetrate Object
		   	end
		end
	end
end

local function CanRayPenetrateHumanoid(origin, direction, hitPart, hitPoint, normal, material, segmentVelocity, penetrationAmount, hitData)
	-- This function shows off the penetrating feature. Pass this function as the last argument (after bulletAcceleration) and it will run this every time the ray runs into an object
	local ShowEffects = ScreenCullingEnabled and (ScreenCulling(hitPoint, ScreenCullingRadius) and (hitPoint - Camera.CFrame.p).Magnitude <= RenderDistance) or (hitPoint - Camera.CFrame.p).Magnitude <= RenderDistance
	if hitPart ~= nil and hitPart.Parent ~= nil then
		if (hitPart.Transparency > 0.75
			or hitPart.Name == "Handle"
			or hitPart.Name == "Effect"
			or hitPart.Name == "Bullet"
			or hitPart.Name == "Laser"
			or string.lower(hitPart.Name) == "water"
			or hitPart.Name == "Rail"
			or hitPart.Name == "Arrow"
			or (hitPart.Parent:FindFirstChild("Humanoid") and hitPart.Parent.Humanoid.Health == 0)) then
			return true -- Penetrate Blacklisted Object
		else
			local Distance = (hitPoint - origin).Magnitude
			local Target = hitPart:FindFirstAncestorOfClass("Model")
			local TargetHumanoid = Target and Target:FindFirstChildOfClass("Humanoid")
			local TargetTorso = Target and (Target:FindFirstChild("HumanoidRootPart") or Target:FindFirstChild("Head"))
			local Tagger = Players:GetPlayerFromCharacter(hitData[5].Parent)
			if TargetHumanoid and TargetHumanoid.Health > 0 and TargetTorso then
				if penetrationAmount > 0 then
					if ShowEffects then MakeBloodFX(hitPart, hitPoint, normal, material, true, hitData[4], hitData[6]) end
					if hitData[6] ~= nil then
						if TargetHumanoid.Health > 0 then
							Thread:Spawn(function()
								InflictTarget:InvokeServer(hitData[7], hitData[5],
			                   		Tagger,
			                  		TargetHumanoid,
				                	TargetTorso,
									{hitData[6][1][1], hitData[6][1][2], hitData[6][1][3], Distance, hitData[6][1][4], hitData[6][1][5], hitData[6][1][6], hitData[6][1][7]},
				                	{hitData[6][2][1], hitData[6][2][2], hitData[6][2][3], hitData[6][2][4], hitData[6][2][5]},
				                	{hitData[6][3][1], hitData[6][3][2], hitData[6][3][3]},
				                	hitPart,
				                	{hitData[6][4][1], hitData[6][4][2], hitData[6][4][3], hitData[6][4][4], hitData[6][4][5], hitData[6][4][6], hitData[6][4][7]})
							end)
							if hitData[5].GunScript_Local:FindFirstChild("MarkerEvent") then
								hitData[5].GunScript_Local.MarkerEvent:Fire(hitPart.Name == "Head" and hitData[6][1][3])
							end  
						end
					end
				end
				return true -- Penetrate Humanoid
			--[[else
				-- We must check if penetrationAmount reachs 0
				if penetrationAmount <= 0 then
			   		MakeImpactFX(hitPart, hitPoint, normal, material, true, hitData[2], hitData[3], hitData[6])
				end]]
			end
		end
	end
	return false -- Can't penetrate
end

local function OnRayExited(origin, direction, hitPart, hitPoint, normal, material, segmentVelocity, hitData)
	local ShowEffects = ScreenCullingEnabled and (ScreenCulling(hitPoint, ScreenCullingRadius) and (hitPoint - Camera.CFrame.p).Magnitude <= RenderDistance) or (hitPoint - Camera.CFrame.p).Magnitude <= RenderDistance
	if hitPart.Name == "_glass" then
		return
	else
		local Target = hitPart:FindFirstAncestorOfClass("Model")
		local TargetHumanoid = Target and Target:FindFirstChildOfClass("Humanoid")
		local TargetTorso = Target and (Target:FindFirstChild("HumanoidRootPart") or Target:FindFirstChild("Head"))
		if TargetHumanoid and TargetHumanoid.Health > 0 and TargetTorso then
			if ShowEffects then MakeBloodFX(hitPart, hitPoint, normal, material, true, hitData[4], hitData[6]) end
		else
		   	if ShowEffects then MakeImpactFX(hitPart, hitPoint, normal, material, true, hitData[2], hitData[3], hitData[6]) end
		end
	end
end

local function OnRayUpdated(castOrigin, segmentOrigin, segmentDirection, length, segmentVelocity, cosmeticBulletObject, bulletData, whizData, hitData)
	-- Whenever the caster steps forward by one unit, this function is called
	-- The bullet argument is the same object passed into the fire function
	local bulletLength = cosmeticBulletObject.Size.Z / 2 -- This is used to move the bullet to the right spot based on a CFrame offset
	local baseCFrame = CFrame.new(segmentOrigin, segmentOrigin + segmentDirection)
	if bulletData[1] then
		cosmeticBulletObject.CFrame = baseCFrame * CFrame.new(0, 0, -(length - bulletLength)) * CFrame.Angles(math.rad(-360 * (os.clock() * bulletData[2] - math.floor(os.clock() * bulletData[2]))), math.rad(-360 * (os.clock() * bulletData[3] - math.floor(os.clock() * bulletData[3]))), math.rad(-360 * (os.clock() * bulletData[4] - math.floor(os.clock() * bulletData[4]))))
	else
		cosmeticBulletObject.CFrame = baseCFrame * CFrame.new(0, 0, -(length - bulletLength))
	end
	if hitData[6] == nil and whizData[1] and cosmeticBulletObject.Parent and cosmeticBulletObject:FindFirstChild("Whizzed") then
		local Whizzed = cosmeticBulletObject:FindFirstChild("Whizzed")
		if not Whizzed.Value then	
	   		local Mag = (Camera.CFrame.p - cosmeticBulletObject.Position).Magnitude		
			if Mag < whizData[5] then
				--local Loudness = 1 - (mag / whizData[5])
				local WhizSound = Instance.new("Sound")
				WhizSound.SoundId = "rbxassetid://"..whizData[2][math.random(1, #whizData[2])]
				WhizSound.Volume = whizData[6] --Loudness
				WhizSound.PlaybackSpeed = Random.new():NextNumber(whizData[3], whizData[4])
				WhizSound.Name = "WhizSound"
				WhizSound.Parent = SoundService				
				WhizSound:Play()
				Debris:AddItem(WhizSound, WhizSound.TimeLength / WhizSound.PlaybackSpeed)
				Whizzed.Value = true
			end
		end
	end
end

function ProjectileHandler:VisualizeHitEffect(Type, Replicate, Hit, Position, Normal, Material, ...)
	if Replicate then 
		VisualizeHitEffect:FireServer(Type, nil, Hit, Position, Normal, Material, ...)
	end
	local ShowEffects = ScreenCullingEnabled and (ScreenCulling(Position, ScreenCullingRadius) and (Position - Camera.CFrame.p).Magnitude <= RenderDistance) or (Position - Camera.CFrame.p).Magnitude <= RenderDistance
	if ShowEffects then
		if Type == "Normal" then
			MakeImpactFX(Hit, Position, Normal, Material, ...)
		elseif Type == "Blood" then
			MakeBloodFX(Hit, Position, Normal, Material, ...)
		end
	end
end

function ProjectileHandler:SimulateProjectile(Module, Tool, Handle, Directions, FirePointObject, HitEffectData, BloodEffectData, BulletHoleData, ExplosiveData, BulletData, WhizData, ClientData)
	if Module and Tool and Handle then
		if ClientData ~= nil then 
			VisualizeBullet:FireServer(Module, Tool, Handle, Directions, FirePointObject, HitEffectData, BloodEffectData, BulletHoleData, ExplosiveData, BulletData, WhizData, nil)
		end
		for _, Direction in pairs(Directions) do
			local Origin, Dir = FirePointObject.WorldPosition, Direction

            if FirePointObject ~= nil then
                local TipCFrame = FirePointObject.WorldCFrame
				local TipPos = TipCFrame.Position
				local TipDir = TipCFrame.LookVector
				local AmountToCheatBack = math.abs((Tool.Parent:FindFirstChild("HumanoidRootPart").Position - TipPos):Dot(TipDir)) + 1
				local gunRay = Ray.new(TipPos - TipDir.Unit * AmountToCheatBack, TipDir.Unit * AmountToCheatBack)
                local HitPart, HitPoint = Workspace:FindPartOnRayWithIgnoreList(gunRay, {Handle, Tool.Parent, Camera}, false, true)
				if HitPart and math.abs((TipPos - HitPoint).Magnitude) > 0 then
					Origin = HitPoint - TipDir.Unit * 0.1
                    --Dir = TipDir.Unit
                end
            end

			local HumanoidRootPart = Tool.Parent:WaitForChild("HumanoidRootPart", 1)
			local MyMovementSpeed = HumanoidRootPart.Velocity
			local ModifiedBulletSpeed = (Dir * BulletData[2]) -- + MyMovementSpeed
	
			local Bullet = Projectiles[BulletData[1]]:Clone()
			Bullet.CFrame = CFrame.new(Origin, Origin + Dir)
			Bullet.Parent = Camera

			local Whizzed = Instance.new("BoolValue")
			Whizzed.Name = "Whizzed"
			Whizzed.Value = false
			Whizzed.Parent = Bullet

			for _, v in pairs(Bullet:GetDescendants()) do
				if v:IsA("ParticleEmitter") then
					v.Enabled = true
				elseif v:IsA("Sound") then
					v:Play()
				elseif v:IsA("PointLight") then
					v.Enabled = true
				elseif v:IsA("Trail") then
					v.Enabled = true
				elseif v:IsA("Beam") then
					v.Enabled = true
				end
			end
	
			if ClientData ~= nil then 
				if (BulletData[10] > 0 or BulletData[11] > 0) and not ExplosiveData[1] then
					Caster:FireWithBlacklist(Origin, Dir * BulletData[4], ModifiedBulletSpeed, {Handle, Tool.Parent, Camera}, Bullet, true, BulletData[3], {BulletData[6], BulletData[7], BulletData[8], BulletData[9]}, WhizData,
						{{ExplosiveData[1], ExplosiveData[2], ExplosiveData[3], ExplosiveData[4], ExplosiveData[5], ExplosiveData[6], ExplosiveData[7], ExplosiveData[8], ExplosiveData[9], ExplosiveData[10], ExplosiveData[11], ExplosiveData[12], ExplosiveData[13], ExplosiveData[14], ExplosiveData[15], ExplosiveData[16], ExplosiveData[17], ExplosiveData[18], ExplosiveData[19], ExplosiveData[20], ExplosiveData[21], ExplosiveData[22], ExplosiveData[23]},
	                	{HitEffectData[1], HitEffectData[2], HitEffectData[3], HitEffectData[4], HitEffectData[5], HitEffectData[6], HitEffectData[7], HitEffectData[8]},
	                	{BulletHoleData[1], BulletHoleData[2], BulletHoleData[3], BulletHoleData[4], BulletHoleData[5], BulletHoleData[6]},
	                	{BloodEffectData[1], BloodEffectData[2], BloodEffectData[3], BloodEffectData[4], BloodEffectData[5], BloodEffectData[6], BloodEffectData[7]},
						Tool,
						{{ClientData[1][1], ClientData[1][2], ClientData[1][3], ClientData[1][4], ClientData[1][5], ClientData[1][6], ClientData[1][7]},
	                	{ClientData[2][1], ClientData[2][2], ClientData[2][3], ClientData[2][4], ClientData[2][5], ClientData[2][6], ClientData[2][7], ClientData[2][8]},
	                	{ClientData[3][1], ClientData[3][2], ClientData[3][3]},
	                	{ClientData[4][1], ClientData[4][2], ClientData[4][3], ClientData[4][4], ClientData[4][5], ClientData[4][6], ClientData[4][7]}},
						Module}, 
						{penetrationType = BulletData[5], penetrationDepth = BulletData[10], canPenetrateFunction = CanRayPenetrate, penetrationAmount = BulletData[11], canPenetrateHumanoidFunction = CanRayPenetrateHumanoid})
				else
					Caster:FireWithBlacklist(Origin, Dir * BulletData[4], ModifiedBulletSpeed, {Handle, Tool.Parent, Camera}, Bullet, true, BulletData[3], {BulletData[6], BulletData[7], BulletData[8], BulletData[9]}, WhizData,
						{{ExplosiveData[1], ExplosiveData[2], ExplosiveData[3], ExplosiveData[4], ExplosiveData[5], ExplosiveData[6], ExplosiveData[7], ExplosiveData[8], ExplosiveData[9], ExplosiveData[10], ExplosiveData[11], ExplosiveData[12], ExplosiveData[13], ExplosiveData[14], ExplosiveData[15], ExplosiveData[16], ExplosiveData[17], ExplosiveData[18], ExplosiveData[19], ExplosiveData[20], ExplosiveData[21], ExplosiveData[22], ExplosiveData[23]},
	                	{HitEffectData[1], HitEffectData[2], HitEffectData[3], HitEffectData[4], HitEffectData[5], HitEffectData[6], HitEffectData[7], HitEffectData[8]},
	                	{BulletHoleData[1], BulletHoleData[2], BulletHoleData[3], BulletHoleData[4], BulletHoleData[5], BulletHoleData[6]},
	                	{BloodEffectData[1], BloodEffectData[2], BloodEffectData[3], BloodEffectData[4], BloodEffectData[5], BloodEffectData[6], BloodEffectData[7]},
						Tool,
						{{ClientData[1][1], ClientData[1][2], ClientData[1][3], ClientData[1][4], ClientData[1][5], ClientData[1][6], ClientData[1][7]},
	                	{ClientData[2][1], ClientData[2][2], ClientData[2][3], ClientData[2][4], ClientData[2][5], ClientData[2][6], ClientData[2][7], ClientData[2][8]},
	                	{ClientData[3][1], ClientData[3][2], ClientData[3][3]},
	                	{ClientData[4][1], ClientData[4][2], ClientData[4][3], ClientData[4][4], ClientData[4][5], ClientData[4][6], ClientData[4][7]}},
						Module},
						nil)
				end
			else
				if (BulletData[10] > 0 or BulletData[11] > 0) and not ExplosiveData[1] then
					Caster:FireWithBlacklist(Origin, Dir * BulletData[4], ModifiedBulletSpeed, {Handle, Tool.Parent, Camera}, Bullet, true, BulletData[3], {BulletData[6], BulletData[7], BulletData[8], BulletData[9]}, WhizData,
						{{ExplosiveData[1], ExplosiveData[2], ExplosiveData[3], ExplosiveData[4], ExplosiveData[5], ExplosiveData[6], ExplosiveData[7], ExplosiveData[8], ExplosiveData[9], ExplosiveData[10], ExplosiveData[11], ExplosiveData[12], ExplosiveData[13], ExplosiveData[14], ExplosiveData[15], ExplosiveData[16], ExplosiveData[17], ExplosiveData[18], ExplosiveData[19], ExplosiveData[20], ExplosiveData[21], ExplosiveData[22], ExplosiveData[23]},
	                	{HitEffectData[1], HitEffectData[2], HitEffectData[3], HitEffectData[4], HitEffectData[5], HitEffectData[6], HitEffectData[7], HitEffectData[8]},
	                	{BulletHoleData[1], BulletHoleData[2], BulletHoleData[3], BulletHoleData[4], BulletHoleData[5], BulletHoleData[6]},
	                	{BloodEffectData[1], BloodEffectData[2], BloodEffectData[3], BloodEffectData[4], BloodEffectData[5], BloodEffectData[6], BloodEffectData[7]},
						Tool, nil, Module},
						{penetrationType = BulletData[5], penetrationDepth = BulletData[10], canPenetrateFunction = CanRayPenetrate, penetrationAmount = BulletData[11], canPenetrateHumanoidFunction = CanRayPenetrateHumanoid})
				else
					Caster:FireWithBlacklist(Origin, Dir * BulletData[4], ModifiedBulletSpeed, {Handle, Tool.Parent, Camera}, Bullet, true, BulletData[3], {BulletData[6], BulletData[7], BulletData[8], BulletData[9]}, WhizData,
						{{ExplosiveData[1], ExplosiveData[2], ExplosiveData[3], ExplosiveData[4], ExplosiveData[5], ExplosiveData[6], ExplosiveData[7], ExplosiveData[8], ExplosiveData[9], ExplosiveData[10], ExplosiveData[11], ExplosiveData[12], ExplosiveData[13], ExplosiveData[14], ExplosiveData[15], ExplosiveData[16], ExplosiveData[17], ExplosiveData[18], ExplosiveData[19], ExplosiveData[20], ExplosiveData[21], ExplosiveData[22], ExplosiveData[23]},
	                	{HitEffectData[1], HitEffectData[2], HitEffectData[3], HitEffectData[4], HitEffectData[5], HitEffectData[6], HitEffectData[7], HitEffectData[8]},
	                	{BulletHoleData[1], BulletHoleData[2], BulletHoleData[3], BulletHoleData[4], BulletHoleData[5], BulletHoleData[6]},
	                	{BloodEffectData[1], BloodEffectData[2], BloodEffectData[3], BloodEffectData[4], BloodEffectData[5], BloodEffectData[6], BloodEffectData[7]},
						Tool, nil, Module},
						nil)
				end
			end
		end
	end
end

Caster.LengthChanged:Connect(OnRayUpdated)
Caster.RayHit:Connect(OnRayHit)
Caster.RayExited:Connect(OnRayExited)

return ProjectileHandler
