Local script not working with StreamingEnabled

hello, I have made a local script in my game which allows players to walkthrough my grass and move it to make it realistic. The local script is in StarterPlayerScripts but when StreamingEnabled is turned on the script only works when things are within a certain distance. I understand it has something to do with it being a local script but I don’t know how to fix or rewrite my script so that it would work with StreamingEnabled on. Does anyone have an idea as to what I should do, i am not sure if i have to rewrite the whole script in ServerScriptService or if its just changing a few things around in my preexisting local script.

here is my script:

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 RNG = Random.new()

local player = PS.LocalPlayer
local camera = workspace.CurrentCamera
local hitbox = RPS.Hitbox

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

local grassData = {}
local hitboxes = {}
local activeParts = {}

local playerConnections = {}
local physicsConnections = {}
local hitboxConnections = {}

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

local timeCount = 0
local updateTimeCount = 0
local timeInterval = 0.1
local updateInterval = timeInterval * 2
local activationRadius = 50 -- Radius within which wind affects grass

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

-- Define the tween function
local function tween(name, mesh, newCFrame)
    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()
    end)
end

-- Function to apply wind effect to grass rotation
local function applyWind(grassInstance, dt, windStrength, swayFactor)
    -- Check if grass is within the activation radius of the player
    local distance = (grassInstance.Position - player.Character.PrimaryPart.Position).Magnitude
    if distance <= activationRadius then
        local sway = math.sin(tick() * windStrength) * swayFactor
        local newCFrame = grassInstance.CFrame * CFrame.Angles(math.rad(sway), 0, math.rad(sway))
        grassInstance.CFrame = newCFrame
    end
end

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

    physicsConnections[hitboxInstance].Touched = hitboxInstance.Touched:Connect(function(partTouched)
        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 = ((hitboxInstance.Position - partTouched.Position) * -Vector3.new(1, 0, 1)).Unit * partTouched.Size.Y / root3
        local newLookAt = data.Top + direction
        local newPosition = data.Pivot + (newLookAt - data.Pivot).Unit * partTouched.Size.Y / 2

        local finalCFrame = CFrame.lookAt(newPosition, newLookAt, upAxis) * rotationCFrame
        tween('pivot', partTouched.Parent, finalCFrame)
    end)

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

        table.remove(activeParts, index)
        tween('pivot', partTouched.Parent, partTouched.CFrame)
    end)

    hitboxInstance.BrickColor = BrickColor.new('Magenta')
end

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

    hitboxInstance.BrickColor = BrickColor.new('Really red')
end

local function activateGrass(grassInstance)
    grassInstance.Hitbox.CanTouch = true
    grassInstance.BrickColor = BrickColor.new('Camo')
end

local function deactivateGrass(grassInstance)
    grassInstance.Hitbox.CanTouch = false

    if grassInstance:FindFirstChild('pivot') then
        grassInstance.pivot:Cancel()
        grassInstance.CFrame = grassInstance.Hitbox.CFrame
    end

    local index = table.find(activeParts, grassInstance.Hitbox)
    if index then
        table.remove(activeParts, index)
        grassInstance.CFrame = grassInstance.Hitbox.CFrame
    end

    grassInstance.BrickColor = BrickColor.new('New Yeller')
end

local function setupGrass(grassInstance)
    local hasHitbox = grassInstance:FindFirstChild('Hitbox')
    local grassHitbox = hasHitbox or Instance.new('Part')

    -- Assign random wind strength and sway factor
    local windStrength = RNG:NextNumber(3, 5) -- Adjust the range as needed
    local swayFactor = RNG:NextNumber(0.1, 0.3) -- Adjust the range as needed

    grassData[grassHitbox] = {
        Top = grassInstance.Position + Vector3.new(0, grassInstance.Size.Y / 2, 0),
        Pivot = grassInstance.Position - Vector3.new(0, grassInstance.Size.Y / 2, 0),
        IsEligible = true,
        ActivationUpdated = false,
        WindStrength = windStrength,
        SwayFactor = swayFactor
    }

    if not hasHitbox then
        grassInstance.CFrame = CFrame.lookAt(grassInstance.Position, grassData[grassHitbox].Top, upAxis) * rotationCFrame
        grassHitbox.CFrame = grassInstance.CFrame
        grassHitbox.Size = grassInstance.Size
        grassHitbox.CanCollide = false
        grassHitbox.CanQuery = false
        grassHitbox.Anchored = true
        grassHitbox.Massless = true
        grassHitbox.CastShadow = false
        grassHitbox.Transparency = 1
        grassHitbox.Name = 'Hitbox'
        grassHitbox.Parent = grassInstance
    end
end

-- Rest of the script remains unchanged



local function removeGrass(grassInstance)
	deactivateGrass(grassInstance)

	if grassData[grassInstance.Hitbox] then
		grassData[grassInstance.Hitbox] = nil
	end

	grassInstance.Hitbox:Destroy()
end

local function setupHitbox(hitboxInstance)
	if not hitboxInstance:IsDescendantOf(workspace) then
		return
	end
	hitboxes[hitboxInstance] = {IsEligible = true, ActivationUpdated = false}

	activateHitbox(hitboxInstance)
end

local function removeHitbox(hitboxInstance)
	deactivateHitbox(hitboxInstance)

	if hitboxes[hitboxInstance] then
		hitboxes[hitboxInstance] = nil
	end

	if hitboxConnections[hitboxInstance] then
		hitboxConnections[hitboxInstance]:Disconnect()
		hitboxConnections[hitboxInstance] = nil
	end
end

local function cameraCFrameChanged(hitboxOnly)
	local controlVector = lastCFrame.LookVector
	local threshold = camera.FieldOfView + 2

	if not hitboxOnly then
		for hitbox, data in pairs(grassData) do
			local directionVector = (hitbox.Position - lastCFrame.Position)
			local angle = math.floor(math.deg(directionVector.Unit:Angle(controlVector)))

			if angle <= threshold then
				local directionVector2 = player.Character and player.Character.PrimaryPart and (hitbox.Position - player.Character.PrimaryPart.Position)
				local xzDistance = directionVector2 and (Vector3.new(directionVector2.X, 0, directionVector2.Z)).Magnitude

				if xzDistance and xzDistance <= activationRadius then
					if not data.IsEligible then
						eligibilityChanged = true
						data.IsEligible = true
						data.ActivationUpdated = false
					end
				else
					if data.IsEligible then
						eligibilityChanged = true
						data.IsEligible = false
						data.ActivationUpdated = false
					end
				end
			else
				if data.IsEligible then
					eligibilityChanged = true
					data.IsEligible = false
					data.ActivationUpdated = false
				end
			end
		end
	end

	for hitbox, data in pairs(hitboxes) do
		local directionVector = (hitbox.Position - lastCFrame.Position)
		local angle = math.floor(math.deg(directionVector.Unit:Angle(controlVector)))

		if angle <= threshold then
			local directionVector2 = player.Character and player.Character.PrimaryPart and (hitbox.Position - player.Character.PrimaryPart.Position)
			local xzDistance = directionVector2 and (Vector3.new(directionVector2.X, 0, directionVector2.Z)).Magnitude

			if xzDistance and xzDistance <= activationRadius then
				if not data.IsEligible then
					hitboxEligibilityChanged = true
					data.IsEligible = true
					data.ActivationUpdated = false
				end
			else
				if data.IsEligible then
					hitboxEligibilityChanged = true
					data.IsEligible = false
					data.ActivationUpdated = false
				end
			end
		else
			if data.IsEligible then
				hitboxEligibilityChanged = true
				data.IsEligible = false
				data.ActivationUpdated = false
			end
		end
	end
end

local function characterAdded(character)
	local primaryPart = character:WaitForChild('HumanoidRootPart')
	local newHitbox = hitbox:Clone()

	local attachment = Instance.new('Attachment')
	attachment.Parent = newHitbox

	local alignPosition = Instance.new('AlignPosition')
	alignPosition.RigidityEnabled = true
	alignPosition.Mode = Enum.PositionAlignmentMode.OneAttachment
	alignPosition.Attachment0 = attachment
	alignPosition.Enabled = true
	alignPosition.Parent = attachment

	hitboxConnections[newHitbox] = RS.Stepped:Connect(function()
		alignPosition.Position = primaryPart.Position
	end)

	newHitbox.Position = primaryPart.Position
	newHitbox.Parent = hitboxFolder
	CLS:AddTag(newHitbox, 'Hitbox')
end

local function characterRemoving(character)
	character.Hitbox:Destroy()
end

-- Update the applyWind function call in updateCamera function
local function updateCamera(dt)
	timeCount = timeCount + dt

	if timeCount > updateInterval then
		timeCount = 0

		if (camera.CFrame.Position - lastCFrame.Position).Magnitude > 1 or (camera.CFrame.Rotation ~= lastCFrame.Rotation) then
			updateCount = 0
			lastCFrame = camera.CFrame
			task.spawn(cameraCFrameChanged)
		else
			updateCount = updateCount + 1
			if updateCount > 4 then
				updateCount = 0
				task.spawn(cameraCFrameChanged, true)
			end
		end
	end

	-- Apply wind effect on grass
	for hitbox, data in pairs(grassData) do
		applyWind(hitbox.Parent, dt, data.WindStrength, data.SwayFactor) -- Update this line
	end
end

local function updateActivation(dt)
	updateTimeCount = updateTimeCount + dt

	if updateTimeCount > updateInterval then
		updateTimeCount = 0

		if eligibilityChanged then
			eligibilityChanged = false

			for hitbox, data in pairs(grassData) do
				if data.IsEligible then
					if not data.ActivationUpdated then
						data.ActivationUpdated = true
						activateGrass(hitbox.Parent)
					end
				else
					if not data.ActivationUpdated then
						data.ActivationUpdated = true
						deactivateGrass(hitbox.Parent)
					end
				end
			end
		end

		if hitboxEligibilityChanged then
			hitboxEligibilityChanged = false

			for hitbox, data in pairs(hitboxes) do
				if data.IsEligible then
					if not data.ActivationUpdated then
						data.ActivationUpdated = true
						activateHitbox(hitbox)
					end
				else
					if not data.ActivationUpdated then
						data.ActivationUpdated = true
						deactivateHitbox(hitbox)
					end
				end
			end
		end
	end
end

PS.PlayerAdded:Connect(function(player)
	playerConnections[player.Name] = {
		CharacterAdded = player.CharacterAdded:Connect(characterAdded),
		CharacterRemoving = player.CharacterRemoving:Connect(characterRemoving)
	}

	if player.Character then
		characterAdded(player.Character)
	end
end)

PS.PlayerRemoving:Connect(function(player)
	playerConnections[player.Name].CharacterAdded:Disconnect()
	playerConnections[player.Name].CharacterRemoving:Disconnect()
	playerConnections[player.Name] = nil
end)

CLS:GetInstanceAddedSignal('Grass'):Connect(setupGrass)
CLS:GetInstanceRemovedSignal('Grass'):Connect(removeGrass)

CLS:GetInstanceAddedSignal('Hitbox'):Connect(setupHitbox)
CLS:GetInstanceRemovedSignal('Hitbox'):Connect(removeHitbox)

RS:BindToRenderStep('cameraUpdate', Enum.RenderPriority.Camera.Value, updateCamera)
RS:BindToRenderStep('activationUpdate', Enum.RenderPriority.Camera.Value + 2, updateActivation)

for _, grassInstance in ipairs(CLS:GetTagged('tallGrass')) do
	setupGrass(grassInstance)
end

for _, hitboxInstance in ipairs(CLS:GetTagged('Hitbox')) do
	setupHitbox(hitboxInstance)
end

for _, player in ipairs(PS:GetPlayers()) do
	playerConnections[player.Name] = {
		CharacterAdded = player.CharacterAdded:Connect(characterAdded),
		CharacterRemoving = player.CharacterRemoving:Connect(characterRemoving)
	}

	if player.Character then
		characterAdded(player.Character)
	end
end