local v=game:GetService("\087\111\114\107\115\112\097\099\101")local m={}m.ClassName="\082\097\121\099\097\115\116\101\114"function m.new(v)local u=setmetatable({_ignoreWater=false,_maxCasts=5,_ignoreList={}},m)if v then u.Filter=v end return u end function m.Ignore(m,v)local u=m.IgnoreList if typeof(v)=="\073\110\115\116\097\110\099\101"then table.insert(u,v)return end assert(type(v)=="\116\097\098\108\101")for v,m in pairs(v)do table.insert(u,m)end end function m.FindPartOnRay(m,v)assert(typeof(v)=="\082\097\121")local u={}for v,m in pairs(rawget(m,"\095\105\103\110\111\114\101\076\105\115\116"))do u[v]=m end local q=m.MaxCasts while q>0 do local d,X=m:_tryCast(v,u)if d then return X end q=q-1 end warn("\091\082\097\121\099\097\115\116\101\114\046\070\105\110\100\080\097\114\116\079\110\082\097\121\093 - \082\097\110 \111\117\116 \111\102 \099\097\115\116\115")return nil end function m.__index(u,v)if v=="\073\103\110\111\114\101\076\105\115\116"then return rawget(u,"\095\105\103\110\111\114\101\076\105\115\116")elseif v=="\070\105\108\116\101\114"then return rawget(u,"\095\102\105\108\116\101\114")elseif v=="\073\103\110\111\114\101\087\097\116\101\114"then return rawget(u,"\095\105\103\110\111\114\101\087\097\116\101\114")elseif v=="\077\097\120\067\097\115\116\115"then return rawget(u,"\095\109\097\120\067\097\115\116\115")elseif m[v]then return m[v]else error(("\085\110\107\110\111\119\110 \105\110\100\101\120 \037\113"):format(tostring(v)))end end function m.__newindex(u,v,m)if v=="\073\103\110\111\114\101\087\097\116\101\114"then assert(type(m)=="\098\111\111\108\101\097\110")rawset(u,"\095\105\103\110\111\114\101\087\097\116\101\114",m)elseif v=="\077\097\120\067\097\115\116\115"then assert(type(m)=="\110\117\109\098\101\114")rawset(u,"\095\109\097\120\067\097\115\116\115",m)elseif v=="\070\105\108\116\101\114"then assert(type(m)=="\102\117\110\099\116\105\111\110")rawset(u,"\095\102\105\108\116\101\114",m)else error(("\085\110\107\110\111\119\110 \105\110\100\101\120 \037\113"):format(tostring(v)))end end function m._tryCast(q,m,u)local d,X,T,F=v:FindPartOnRayWithIgnoreList(m,u,false,q._ignoreWater)if not d then return true,nil end local s={Part=d,Position=X,Normal=T,Material=F}local p=q.Filter if p and p(s)then table.insert(u,d)return false,nil end return true,s end return m
--- Repeats raycasting attempts while ignoring items via a filter function
-- @classmod Raycaster

local Workspace = game:GetService("Workspace")

local Raycaster = {}
Raycaster.ClassName = "Raycaster"

function Raycaster.new(filterFunction)
	local self = setmetatable({
		_ignoreWater = false,
		_maxCasts = 5,
		_ignoreList = {},
	}, Raycaster)

	if filterFunction then
		self.Filter = filterFunction
	end

	return self
end

function Raycaster:Ignore(tableOrInstance)
	local ignoreList = self.IgnoreList
	if typeof(tableOrInstance) == "Instance" then
		table.insert(ignoreList, tableOrInstance)
		return
	end

	assert(type(tableOrInstance) == "table")
	for _, item in pairs(tableOrInstance) do
		table.insert(ignoreList, item)
	end
end

function Raycaster:FindPartOnRay(ray)
	assert(typeof(ray) == "Ray")

	local ignoreList = {}
	for key, value in pairs(rawget(self, "_ignoreList")) do
		ignoreList[key] = value
	end

	local casts = self.MaxCasts
	while casts > 0 do
		local ok, result = self:_tryCast(ray, ignoreList)
		if ok then
			return result
		end
		casts = casts - 1
	end

	warn("[Raycaster.FindPartOnRay] - Ran out of casts")
	return nil
end

function Raycaster:__index(index)
	if index == "IgnoreList" then
		return rawget(self, "_ignoreList")
	elseif index == "Filter" then
		return rawget(self, "_filter")
	elseif index == "IgnoreWater" then
		return rawget(self, "_ignoreWater")
	elseif index == "MaxCasts" then
		return rawget(self, "_maxCasts")
	elseif Raycaster[index] then
		return Raycaster[index]
	else
		error(("Unknown index %q"):format(tostring(index)))
	end
end

function Raycaster:__newindex(index, value)
	if index == "IgnoreWater" then
		assert(type(value) == "boolean")
		rawset(self, "_ignoreWater", value)
	elseif index == "MaxCasts" then
		assert(type(value) == "number")
		rawset(self, "_maxCasts", value)
	elseif index == "Filter" then
		assert(type(value) == "function")
		rawset(self, "_filter", value)
	else
		error(("Unknown index %q"):format(tostring(index)))
	end
end

function Raycaster:_tryCast(ray, ignoreList)
	local part, position, normal, material = Workspace:FindPartOnRayWithIgnoreList(
		ray, ignoreList, false, self._ignoreWater)

	if not part then
		return true, nil
	end

	local data = {
		Part = part;
		Position = position;
		Normal = normal;
		Material = material;
	}

	local filter = self.Filter
	if filter and filter(data) then
		table.insert(ignoreList, part)
		return false, nil
	end

	return true, data
end

return Raycaster
