--[[Functions]]
--
function getRaycastIgnoreList()
	local ignoreList = {};

	for _, v in next, workspace:GetDescendants() do
		if v:IsA("BasePart") and not v.CanCollide or v:FindFirstChildOfClass("Humanoid") then
			table.insert(ignoreList, v);
		end
	end
	raycastParams.FilterDescendantsInstances = ignoreList;

	workspace.DescendantAdded:Connect(function(desc)
		if desc:IsA("BasePart") and not desc.CanCollide or desc:FindFirstChildOfClass("Humanoid") then
			table.insert(ignoreList, desc);
			raycastParams.FilterDescendantsInstances = ignoreList;
		end
	end)
end
getRaycastIgnoreList();

function detectNoclip() : boolean --Returns true if there is a part between the player's current position and last recorded position
	local origin : Vector3 = lastValidCF.Position;
	local hrpPos : Vector3 = hrp.Position;
	local dist : number = (origin - hrpPos).Magnitude;

	local direction : Vector3 = (hrpPos - origin).Unit * dist;

	return workspace:Raycast(origin, direction, raycastParams) ~= nil;
end

function onDescAddedToChar(desc)
	if desc:IsA("BodyVelocity") then
		table.insert(velocityModifiers, desc);
	end
end
char.DescendantAdded:Connect(onDescAddedToChar);

function computeVelocityModifiers() : number --Returns the magnitude of all body velocities acting on the player
	local moddedVelocity = 0;

	for i, v in next, velocityModifiers do
		if v and v:IsDescendantOf(char) then
			moddedVelocity += v.Velocity.Magnitude;
		else
			table.remove(velocityModifiers, i);
		end
	end

	return moddedVelocity;
end

function detectSpeed() : boolean
	local vel : Vector3 = hrp.AssemblyLinearVelocity;
	local horizontalVel : Vector2 = Vector2.new(vel.X, vel.Z);
	local speed : number = horizontalVel.Magnitude;

	local maxAcceptableSpeed = hum.WalkSpeed + 5 + computeVelocityModifiers();
	return speed > maxAcceptableSpeed;
end

function main()
	local isNoclipping = detectNoclip();
	local isSpeeding = detectSpeed();

	--print(isNoclipping, isSpeeding);

	if isNoclipping or isSpeeding then
		hrp.AssemblyLinearVelocity = Vector3.new();
		char:SetPrimaryPartCFrame(lastValidCF * CFrame.new(0, 2 + hum.HipHeight, 0)); 
		--In my tests without this offset, the script would place anyone caught by this in the floor and infinitely
		--flag them for no-clipping.
	else
		lastValidCF = hrp.CFrame;
	end

	task.wait(0.1);
end

while true do
	main();
end
