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