My script is giving me "table index nil" error and I am not sure why

I am working on a interactive grass system for my game so that the part moves when touched everything was going well until my original script stopped working when the gamed had StreamingMeshEnabled on so i had to rewrite the script. my new issue is I’m running into this error when writing the script and i am struggling to fix the issue. anyone have any ideas?

The error is on line 146

local PS = game:GetService('Players')
local CLS = game:GetService('CollectionService')
local RPS = game:GetService('ReplicatedStorage')
local TWS = game:GetService('TweenService')
local RS = game:GetService('RunService')

local hitboxFolder : Folder = Instance.new('Folder')
hitboxFolder.Name = 'HitboxFolder'
hitboxFolder.Parent = workspace

local player : Player = PS.LocalPlayer
local camera : Camera = workspace.CurrentCamera
local hitbox : BasePart = RPS.Hitbox

local grassData : {[BasePart] : {Top : Vector3, Pivot : Vector3, IsEligible : boolean, ActivationUpdated : boolean}} = {}
local hitboxes : {[BasePart] : {IsEligible : boolean, ActivationUpdated : boolean}} = {}
local activeParts : {BasePart} = {}

local playerConnections : {[string] : {CharacterAdded : RBXScriptConnection, CharacterRemoving : RBXScriptConnection}} = {}
local physicsConnections : {[BasePart] : {Touched : RBXScriptConnection?, TouchEnded : RBXScriptConnection}} = {}  
local hitboxConnections : {[BasePart] : RBXScriptConnection} = {}

local rotationCFrame : CFrame = CFrame.Angles(0, math.rad(-90), math.rad(90))
local upAxis : Vector3 = Vector3.FromNormalId(Enum.NormalId.Back)
local tweenInfo : TweenInfo = TweenInfo.new(3.5, Enum.EasingStyle.Elastic, Enum.EasingDirection.Out, 0, false, 0)
local root3 : number = math.sqrt(3)

local timeCount : number = 0
local updateTimeCount : number = 0
local timeInterval : number = 0.1
local updateInterval : number = timeInterval * 2
local updateCount : number = 0
local activationRadius : number = 200

local eligibilityChanged : boolean = false
local hitboxEligibilityChanged : boolean = false
local lastCFrame : CFrame = camera.CFrame

local function tween(name : string, mesh : MeshPart, newCFrame : CFrame)
	if mesh:FindFirstChild(name) and mesh[name]:IsA('Tween') then
		mesh[name]:Destroy()
	end

	local tween = TWS:Create(mesh, tweenInfo, {CFrame = newCFrame})
	tween.Name = name
	tween.Parent = mesh
	tween:Play()
	tween.Completed:Connect(function()
		tween:Destroy()
		tween = nil
	end)
end

local function activateHitbox(hitboxInstance : BasePart)
	physicsConnections[hitboxInstance] = {}

	physicsConnections[hitboxInstance].Touched = hitboxInstance.Touched:Connect(function(partTouched : BasePart)
		if not (grassData[partTouched]) or not (grassData[partTouched].IsEligible) then return end
		if (table.find(activeParts, partTouched)) then return end

		activeParts[#activeParts + 1] = partTouched

		local data = grassData[partTouched]

		local direction : Vector3 = ((hitboxInstance.Position - partTouched.Position) * -Vector3.new(1, 0, 1)).Unit * partTouched.Size.Y/root3
		local newLookAt : Vector3 = (data.Top + direction)
		local newPosition : Vector3 = data.Pivot + ((newLookAt - data.Pivot).Unit * partTouched.Size.Y/root3)

		if not (partTouched:FindFirstChild('TouchedFloor')) then
			partTouched.TouchedFloor = true

			local clone = RPS.TouchedFloor:Clone()
			clone.Position = newPosition
			clone.Parent = workspace
			clone.Anchored = true

			tween('TouchedFloor', clone, CFrame.new(clone.Position) * rotationCFrame)
			tween('Color', clone, Color3.new(0, 1, 0))
		end

		local lookVector : Vector3 = ((newLookAt - partTouched.Position).Unit * partTouched.Size.Y/root3)
		local rollAngle : number = math.rad(-90)
		local newCFrame : CFrame = CFrame.new(partTouched.Position, partTouched.Position + lookVector) * CFrame.Angles(rollAngle, 0, 0)
		local invertedCFrame : CFrame = newCFrame:Inverse()
		local ray : Ray = Ray.new(invertedCFrame:PointToWorldSpace(newPosition), invertedCFrame:VectorToWorldSpace(-upAxis * 100))
		local part, position = workspace:FindPartOnRay(ray, player.Character)

		if part and part == partTouched then
			tween('Activation', partTouched, newCFrame)
			data.ActivationUpdated = true
		else
			local partTouchedSizeY : number = partTouched.Size.Y
			local partSizeY : number = part.Size.Y
			local ratio : number = partTouchedSizeY/partSizeY

			lookVector = lookVector * ratio

			newCFrame = CFrame.new(part.Position, part.Position + lookVector) * CFrame.Angles(rollAngle, 0, 0)

			local partTouchedSizeYHalf : number = partTouchedSizeY/2
			local partSizeYHalf : number = partSizeY/2

			position = part.Position + ((partTouchedSizeYHalf - partSizeYHalf) * lookVector)

			local pivot : Vector3 = data.Pivot
			local height = ((newCFrame * CFrame.new(0, -partTouchedSizeYHalf, 0)).Position - pivot).Magnitude
			local angle = (partTouched.Size.Y/part.Size.Y * height)/(part.Position - pivot).Magnitude

			tween('Activation', part, newCFrame * CFrame.Angles(angle, 0, 0))
			data.ActivationUpdated = true
		end
	end)

	physicsConnections[hitboxInstance].TouchEnded = hitboxInstance.TouchEnded:Connect(function(partTouched : BasePart)
		if not (grassData[partTouched]) or not (grassData[partTouched].IsEligible) then return end

		if table.find(activeParts, partTouched) then
			activeParts[table.find(activeParts, partTouched)] = nil
		end

		if partTouched:FindFirstChild('TouchedFloor') then
			tween('Color', partTouched.TouchedFloor, Color3.new(0.5, 0.5, 0.5))
			tween('Position', partTouched.TouchedFloor, partTouched.TouchedFloor.Position - Vector3.new(0, 10, 0))
			wait(0.5)
			partTouched.TouchedFloor:Destroy()
		end

		if grassData[partTouched].ActivationUpdated then
			local newCFrame : CFrame = CFrame.new(partTouched.Position) * rotationCFrame
			tween('Activation', partTouched, newCFrame)
			grassData[partTouched].ActivationUpdated = false
		end
	end)
end

local function deactivateHitbox(hitboxInstance : BasePart)
	if physicsConnections[hitboxInstance] then
		physicsConnections[hitboxInstance].Touched:Disconnect()
		physicsConnections[hitboxInstance].TouchEnded:Disconnect()
		physicsConnections[hitboxInstance] = nil
	end
end

local function handleHitboxEligibility(part : BasePart)
	if not hitboxes[part] then
		hitboxes[part] = {IsEligible = false, ActivationUpdated = false}
	end
end


	local eligibleParts : {BasePart} = CLS:GetTagged('tallGrass')

for _, eligiblePart in ipairs(eligibleParts) do
	if eligiblePart:IsDescendantOf(workspace) and eligiblePart:IsA('BasePart') then
		if not hitboxes[eligiblePart] then
			hitboxes[eligiblePart] = {}
		end
		if not hitboxes[eligiblePart].IsEligible then
			hitboxes[eligiblePart].IsEligible = true
			activateHitbox(eligiblePart)
			hitboxEligibilityChanged = true
		end
	end
end


	for partTouched, _ in pairs(hitboxes) do
		if partTouched:IsDescendantOf(workspace) and partTouched:IsA('BasePart') and partTouched ~= part then
			if not table.find(eligibleParts, partTouched) then
				hitboxes[partTouched].IsEligible = false
				deactivateHitbox(partTouched)
				hitboxEligibilityChanged = true
			end
		end
	end

local function handleEligibility(part : BasePart)
	if not grassData[part] then
		grassData[part] = {Top = Vector3.new(), Pivot = Vector3.new(), IsEligible = false, ActivationUpdated = false}
	end

	local activationEnabled : boolean = (part.Position - camera.CFrame.Position).Magnitude <= activationRadius

	if not grassData[part].IsEligible and activationEnabled then
		grassData[part].IsEligible = true
		hitboxEligibilityChanged = true
	elseif grassData[part].IsEligible and not activationEnabled then
		grassData[part].IsEligible = false
		hitboxEligibilityChanged = true
	end
end

local function updateGrassData()
	local parts : {BasePart} = CLS:GetTagged('tallGrass')

	for _, part in ipairs(parts) do
		if part:IsDescendantOf(workspace) and part:IsA('BasePart') then
			handleEligibility(part)

			if grassData[part].IsEligible then
				local position : Vector3 = part.Position
				local size : Vector3 = part.Size

				local top : Vector3 = Vector3.new(position.X, position.Y + (size.Y/root3), position.Z)
				local pivot : Vector3 = position + ((top - position)/2)

				grassData[part].Top = top
				grassData[part].Pivot = pivot
			end
		end
	end
end

local function handleActivation()
	local currentCFrame : CFrame = camera.CFrame
	local rotationAngle : number = (currentCFrame.Position - lastCFrame.Position).Y

	for partTouched, data in pairs(grassData) do
		if partTouched:IsDescendantOf(workspace) and partTouched:IsA('BasePart') and data.ActivationUpdated then
			local newCFrame : CFrame = partTouched.CFrame * CFrame.Angles(0, math.rad(rotationAngle), 0)
			tween('Activation', partTouched, newCFrame)
			data.ActivationUpdated = false
		end
	end

	lastCFrame = currentCFrame
end

local function main()
	while RS:IsRunning() do
		if timeCount >= timeInterval then
			updateGrassData()
			timeCount = 0
		end

		if updateTimeCount >= updateInterval then
			handleActivation()

			if hitboxEligibilityChanged then
				handleHitboxEligibility()
				hitboxEligibilityChanged = false
			end

			updateTimeCount = 0
			updateCount = updateCount + 1
		end

		timeCount = timeCount + RS.Heartbeat:Wait()
		updateTimeCount = updateTimeCount + RS.Heartbeat:Wait()
	end
end

main()```

On line 240 you call handleHitboxEligibility without passing any parameters. So the argument part is nil.

1 Like

That’s something he should’ve caught himself because he used type annotations! I guess not because the basic checking is lenient. OP should turn on strict mode with --!strict at the top of the script.

1 Like