--------------------[ HIT HANDLING FUNCTIONS ]----------------------------------------

function getBaseDamage(Dist)
	local startDmg = S.damageSettings.Start.Damage
	local startDist = S.damageSettings.Start.Dist
	local endDmg = S.damageSettings.End.Damage
	local endDist = S.damageSettings.End.Dist
	return (
		(
			Dist < startDist * S.bulletSettings.Range
		) and startDmg or
		(
			Dist >= startDist * S.bulletSettings.Range and
			Dist < endDist * S.bulletSettings.Range
		) and numLerp(startDmg, endDmg, Map(Dist / S.bulletSettings.Range, startDist, endDist, 0, 1)) or
		(
			Dist >= endDist * S.bulletSettings.Range
		) and endDmg
	)
end

function Damage(H, P, N, D, Dist, customIgnore)
	local hVal = S.damageSettings.Multipliers.Head
	local cVal = S.damageSettings.Multipliers.Chest
	local lVal = S.damageSettings.Multipliers.Limbs
	local baseDamage = getBaseDamage(Dist)
	if Humanoid.Health ~= 0 then
		local hitHumanoid = nil
		if H.Parent:IsA("Hat") then
			table.insert(customIgnore, H)
			local newRay = Ray.new(P - D * 0.1, D * (S.bulletSettings.Range - Dist + 0.1))
			local newH, newP, newN = workspace:FindPartOnRayWithIgnoreList(newRay, customIgnore)
			if newH then
				hitHumanoid = Damage(newH, newP, newN, D, Dist + (newP - P).magnitude, customIgnore)
			end
		else
			hitHumanoid = findFirstClass(H.Parent, "Humanoid")
			if hitHumanoid and hitHumanoid.Health > 0 and isEnemy(hitHumanoid) then
				local Tag = Instance.new("ObjectValue")
				Tag.Value = Player
				Tag.Name = "creator"
				Tag.Parent = hitHumanoid
				DS:AddItem(Tag, 0.3)
				local chosenDamage = 0
				if H.Name == "Head" then
					chosenDamage = baseDamage  * RAND(hVal, hVal + 0.1, 0.01)
				elseif H.Name == "Torso" then
					chosenDamage = baseDamage  * RAND(cVal, cVal + 0.1, 0.01)
				else
					chosenDamage = baseDamage  * RAND(lVal, lVal + 0.1, 0.01)
				end
				hitHumanoid:TakeDamage(chosenDamage)
				markHit()
			end
		end
		return hitHumanoid
	end
end

function isWallIgnored(Wall)
	return (
		Wall.Transparency >= S.penetrationSettings.transparencyThreshold or
		(S.penetrationSettings.ignoreNonCanCollide and (not Wall.CanCollide)) or
		isIgnored(Wall, S.penetrationSettings.ignoreCustom)
	)
end

function penetrateWall(Wall, hitPos, Direction, Normal, Ignore, totalPDist, totalBDist, lastDamagedHumanoid)
	local wallIgnore = isWallIgnored(Wall)
	local hitHumanoid = (Wall.Parent:IsA("Hat") and findFirstClass(Wall.Parent.Parent, "Humanoid") or findFirstClass(Wall.Parent, "Humanoid"))
	local damagedHumanoid = nil
	if hitHumanoid and hitHumanoid ~= lastDamagedHumanoid then
		lastDamagedHumanoid = hitHumanoid
		damagedHumanoid = Damage(Wall, hitPos, Normal, Direction, totalBDist, {Char, ignoreModel})
	else
		lastDamagedHumanoid = nil
	end
	local ignoreObject = hitHumanoid and (Wall.Parent:IsA("Hat") and Wall.Parent.Parent or Wall.Parent) or Wall
	table.insert(Ignore, ignoreObject)
	local rayLength = S.bulletSettings.Range - totalBDist
	local testRay = Ray.new(hitPos, Direction * (S.bulletSettings.Range - totalBDist))
	local H1, P1, N1 = workspace:FindPartOnRayWithIgnoreList(testRay, Ignore)
	local newIgnore = removeElement(Ignore, ignoreObject)
	local wallRay = Ray.new(P1 + Direction * 0.1, -Direction * (rayLength + 1))
	local H2, P2, N2 = workspace:FindPartOnRayWithIgnoreList(wallRay, Ignore)
	local newPDist = totalPDist + (wallIgnore and 0 or (getNearestPoint(P1, P2, hitPos) - hitPos).magnitude)
	local newBDist = totalBDist + (P1 - hitPos).magnitude
	local outOfRange = Round(newPDist, 0.001) > S.penetrationSettings.Dist or Round(newBDist, 0.001) > S.bulletSettings.Range
	if (not wallIgnore) then
		createBulletImpact:FireServer(Wall, hitPos, Normal, Direction, hitHumanoid, gunIgnore, S)
		if (not hitHumanoid) then
			createShockwave:FireServer(hitPos, S.shockwaveSettings.Radius, gunIgnore, S)
		end
	end
	if hitHumanoid and hitHumanoid.Health > 0 and isEnemy(hitHumanoid) and hitHumanoid == damagedHumanoid then
		createBlood:FireServer(Wall, P2, Direction, gunIgnore, S)
	end
	if outOfRange or (not H1) then
		if (not outOfRange) and (not wallIgnore) then
			createBulletImpact:FireServer(Wall, P2, N2, Direction, hitHumanoid, gunIgnore, S)
			if (not hitHumanoid) then
				createShockwave:FireServer(P2, S.shockwaveSettings.Radius, gunIgnore, S)
			end
		end
		return Wall, hitPos
	else
		if Wall == H2 and (not wallIgnore) then
			createBulletImpact:FireServer(Wall, P2, N2, Direction, hitHumanoid, gunIgnore, S)
			if (not hitHumanoid) then
				createShockwave:FireServer(P2, S.shockwaveSettings.Radius, gunIgnore, S)
			end
		end
		return penetrateWall(H1, P1, Direction, N1, Ignore, newPDist, newBDist, lastDamagedHumanoid)
	end
end

function PenetrateWall(HitPos, Direction, HitHumanoid, OriginPos, Bullet, CurrentPDist)
	local HitDist = (HitPos - OriginPos).magnitude
	local Wall, WallHitPos = nil, nil
	local Hum, HumHitPos = nil, nil
	local CustomIgnore = {unpack(Ignore)}
	for i = 1, 10 do
		local WallRay = Ray.new(HitPos - (Direction * 0.1), Direction * S.Penetration)
		local H, P = game.Workspace:FindPartOnRayWithIgnoreList(WallRay, CustomIgnore)
		if H then
			local HitHumanoid = nil
			if H.Parent.ClassName == "Hat" then
				HitHumanoid = findFirstClass(H.Parent.Parent, "Humanoid")
			else
				HitHumanoid = findFirstClass(H.Parent, "Humanoid")
			end
			if HitHumanoid and i ~= 1 then
				Hum, HumHitPos = H, P
				break
			else
				Wall, WallHitPos = H, P
				table.insert(CustomIgnore, H)
			end
		else
			break
		end
	end
	if Wall then
		if S.InstantHit then
			if Hum then
				Damage(Hum.Parent:FindFirstChild("Head"), HumHitPos)
				return HumHitPos
			else
				local HitObj2, HitPos2 = nil, nil
				if HitHumanoid then
					HitObj2, HitPos2 = AdvRayCast(WallHitPos, Direction, S.BulletRange - HitDist, {Wall, HitHumanoid.Parent, unpack(Ignore)})
				else
					HitObj2, HitPos2 = AdvRayCast(WallHitPos, Direction, S.BulletRange - HitDist, {Wall, unpack(Ignore)})
				end
				Damage(HitObj2, HitPos2)
				
				local NewPDist = CurrentPDist + (WallHitPos - HitPos).magnitude
				local NewHitPos2 = HitPos2
				if NewPDist < S.Penetration and HitObj2 then
					NewHitPos2 = PenetrateWall(HitPos2, Direction, HitHumanoid, OriginPos, Bullet, CurrentPDist + NewPDist)
				end
				return NewHitPos2
			end
		else
			local LastPos = WallHitPos
			local TotalDistTraveled = 0
			spawn(function()
				if Hum then
					Damage(Hum.Parent:FindFirstChild("Head"), HumHitPos)
					return HumHitPos
				else
					while true do
						RS.RenderStepped:wait()
						if TotalDistTraveled >= S.BulletRange - HitDist then
							Bullet:Destroy()
							break
						end
						local DistTraveled = (Bullet.Position - LastPos).magnitude
						local NewDirection = (Bullet.Position - LastPos).unit
						local TempHitObj, TempHitPos = nil, nil
						if HitHumanoid then
							TempHitObj, TempHitPos = AdvRayCast(LastPos, NewDirection, DistTraveled, {Wall, HitHumanoid.Parent, unpack(Ignore)})
						else
							TempHitObj, TempHitPos = AdvRayCast(LastPos, NewDirection, DistTraveled, {Wall, unpack(Ignore)})
						end
						if TempHitObj then
							Damage(TempHitObj, TempHitPos)
							
							local NewPDist = CurrentPDist + (WallHitPos - HitPos).magnitude
							local NewTempPos = TempHitPos
							if NewPDist < S.Penetration and TempHitObj then
								NewTempPos = PenetrateWall(TempHitPos, Direction, HitHumanoid, OriginPos, Bullet, CurrentPDist + NewPDist)
							else
								Bullet:Destroy()
							end
							return NewTempPos
						else
							LastPos = Bullet.Position
							TotalDistTraveled = TotalDistTraveled + DistTraveled
						end
					end
				end
			end)
		end
	else
		if Bullet then Bullet:Destroy() end
		return HitPos
	end
end

function isEnemy(Human)
	local Plyr = game.Players:GetPlayerFromCharacter(Human.Parent)
	if (not Plyr) then return S.CanDamageNPCs end
	return S.AllowFriendlyFire or (Plyr.TeamColor ~= Player.TeamColor or Plyr.Neutral)
end