-- Simulate a hitscan 

local function SimulateHitscan(cast: ActiveCast, origin: Vector3, direction: Vector3, initialVelocity: Vector3, distance: number)
	local targetWorldRoot = cast.RayInfo.WorldRoot
	local resultOfCast = targetWorldRoot:Raycast(origin, direction * distance, cast.RayInfo.Parameters)
	local point = origin + direction * distance
	local part: Instance? = nil
	local material = Enum.Material.Air
	local normal = Vector3.new()
	if resultOfCast then
		point = resultOfCast.Position
		part = resultOfCast.Instance
		material = resultOfCast.Material
		normal = resultOfCast.Normal
	end
	table.insert(cast.StateInfo.TweenTable, {
		origin = origin,
		direction = CFrame.new(origin, point),
		lastPoint = origin,
		s = 0,
		ready = false,
		l = (origin - point).Magnitude
	})	
	if part and part ~= cast.RayInfo.CosmeticBulletObject then
		if cast.RayInfo.CanPenetrateCallback == nil or (cast.RayInfo.CanPenetrateCallback ~= nil and cast.RayInfo.CanPenetrateCallback(cast, origin, direction, resultOfCast, initialVelocity, cast.RayInfo.CosmeticBulletObject) == false) then
			PrintDebug("Penetrating function is nil or it returned FALSE to not penetrate this hit.")

			if cast.RayInfo.CanHitCallback == nil or (cast.RayInfo.CanHitCallback ~= nil and cast.RayInfo.CanHitCallback(cast, origin, direction, resultOfCast, initialVelocity, cast.RayInfo.CosmeticBulletObject) == false) then
				PrintDebug("Hit was successful. Terminating.")
				SendRayFinalHit(cast, origin, direction, resultOfCast, initialVelocity, cast.RayInfo.CosmeticBulletObject)
				DbgVisualizeHit(CFrame.new(point), false)
			else
				SendRayHit(cast, origin, direction, resultOfCast, initialVelocity, cast.RayInfo.CosmeticBulletObject) -- This may result in CancelHighResCast being set to true.
				local vis = DbgVisualizeHit(CFrame.new(point), true)
				if (vis ~= nil) then vis.Color3 = Color3.new(1, 0.113725, 0.588235) end
				SimulateHitscan(cast, point, cast.RayInfo.ModifiedDirection, initialVelocity, distance)
			end
		else			
			PrintDebug("Penetrating function returned TRUE to penetrate this part.")
			DbgVisualizeHit(CFrame.new(point), true)

			local params = cast.RayInfo.Parameters
			local alteredParts = {}
			local currentPenetrationTestCount = 0
			local originalFilter = params.FilterDescendantsInstances
			local brokeFromSolidObject = false

			while true do
				-- So now what I need to do is redo this entire cast, just with the new filter list

				-- Catch case: Is it terrain?
				if resultOfCast.Instance:IsA("Terrain") then
					if material == Enum.Material.Water then
						-- Special case: Penetrated on water?
						cast:Terminate()
						error("Do not add Water as a piercable material. If you need to penetrate water, set cast.RayInfo.Parameters.IgnoreWater = true instead", 0)
					end
					warn("WARNING: The penetrate callback for this cast returned TRUE on Terrain! This can cause severely adverse effects.")
				end

				if params.FilterType == Enum.RaycastFilterType.Blacklist then
					-- blacklist
					-- DO NOT DIRECTLY TABLE.INSERT ON THE PROPERTY
					local filter = params.FilterDescendantsInstances
					table.insert(filter, resultOfCast.Instance)
					table.insert(alteredParts, resultOfCast.Instance)
					params.FilterDescendantsInstances = filter
				else
					-- whitelist
					-- method implemeneted by custom table system
					-- DO NOT DIRECTLY TABLE.REMOVEOBJECT ON THE PROPERTY
					local filter = params.FilterDescendantsInstances
					table.removeObject(filter, resultOfCast.Instance)
					table.insert(alteredParts, resultOfCast.Instance)
					params.FilterDescendantsInstances = filter
				end

				-- List has been updated, so let's cast again.					
				resultOfCast = targetWorldRoot:Raycast(origin, direction, params)

				-- No hit? No simulation. Break.
				if resultOfCast == nil then
					break
				end

				if currentPenetrationTestCount >= MAX_PENETRATION_TEST_COUNT then
					warn("WARNING: Exceeded maximum penetrate test budget for a single ray segment (attempted to test the same segment " .. MAX_PENETRATION_TEST_COUNT .. " times!)")
					break
				end
				currentPenetrationTestCount = currentPenetrationTestCount + 1;

				if cast.RayInfo.CanPenetrateCallback(cast, origin, direction, resultOfCast, initialVelocity, cast.RayInfo.CosmeticBulletObject) == false then
					brokeFromSolidObject = true
					break
				end
			end

			-- Restore the filter to its default state.
			cast.RayInfo.Parameters.FilterDescendantsInstances = originalFilter

			if brokeFromSolidObject then
				if cast.RayInfo.CanHitCallback == nil or (cast.RayInfo.CanHitCallback ~= nil and cast.RayInfo.CanHitCallback(cast, origin, direction, resultOfCast, initialVelocity, cast.RayInfo.CosmeticBulletObject) == false) then
					PrintDebug("Broke because the ray hit something solid (" .. tostring(resultOfCast.Instance) .. ") while testing for a penetration. Terminating the cast.")
					SendRayFinalHit(cast, origin, direction, resultOfCast, initialVelocity, cast.RayInfo.CosmeticBulletObject)
					DbgVisualizeHit(CFrame.new(resultOfCast.Position), false)
				else
					-- Recalculating hit something hittable instead.
					SendRayHit(cast, origin, direction, resultOfCast, initialVelocity, cast.RayInfo.CosmeticBulletObject) -- This may result in CancelHighResCast being set to true.
					local vis = DbgVisualizeHit(CFrame.new(point), true)
					if (vis ~= nil) then vis.Color3 = Color3.new(1, 0.113725, 0.588235) end
					SimulateHitscan(cast, point, cast.RayInfo.ModifiedDirection, initialVelocity, distance)
				end
			else
				SimulateHitscan(cast, point + (direction * 0.01), direction, initialVelocity, distance - ((origin - point).Magnitude))
			end
			
			-- And exit the function here too.
		end
	end
end
