Randomly spawning objects

Nope. Seems to be working just fine.

There is one thing tho. If the server removes a tree from the folder, then the remote gets fired. Could there be a way to clean up the local list too?

Could you send me all the code related to the tree loading again?

Ok.

Server

local event = game:GetService("ReplicatedStorage").Events.positionTrees

game:GetService("Players").PlayerAdded:Connect(function(plr)
	
	wait(15)
	event:FireClient(plr)
	
end)

game:GetService("Workspace").Trees.ChildAdded:Connect(function()
	
	event:FireAllClients()
	
end)

game:GetService("Workspace").Trees.ChildRemoved:Connect(function()

	event:FireAllClients()

end)

Client

if not game.Loaded then game.Loaded:Wait() end
local event = game:GetService("ReplicatedStorage").Events.positionTrees
local trees = {}

event.OnClientEvent:Connect(function()
	
	for i, v in pairs(game:GetService("Workspace").Trees:GetChildren()) do
		
		if v and v.Name == "Tree" and not (table.find(trees, v)) then
			
			table.insert(trees, v)
			local pos = v.Position
			
			--                  [RAYCAST STUFF]							--
			local rayOrigin = pos
			local rayDirection = Vector3.new(0, -300, 0)
			
			local raycastParams = RaycastParams.new()
			raycastParams.FilterDescendantsInstances = {v, game.Players.LocalPlayer.Character}
			raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
			
			local raycastResult = workspace:Raycast(rayOrigin, rayDirection, raycastParams)
			
			if raycastResult then
				
				local targetMaterial = raycastResult.Material
				local targetPos = raycastResult.Position
				local targetInstance = raycastResult.Instance
				
				if targetInstance == game:GetService("Workspace").Terrain and targetMaterial ~= Enum.Material.Water then
					
					v.Position = targetPos + Vector3.new(0, 7, 0)
					
				end
				
				task.wait(.5)
				
			end
			--		--			--			--				--			--
			
		end
		
	end
	
end)

So what are the problems with it now?

If you look at the code, you will definetly see that the list will not remove trees deleted by the server.

How could that work?

Maybe have an ChildRemoved event on the client to update the table.

1 Like

Thank You, this game might even work!

Great! If you have any more questions then let me know. I’d also love to see the finished thing if you complete it!

1 Like

Hey the way i would do it is the localscript that is created by the plugin i would convert into a modulescript

in order to get acces to the GetHeight function

because terrain has a pixel size of 4x4 studs the GetHeight function also requires you to divide your postion into 4

-- Position where the tree will spawn
local positionX = 489534.345
local positionZ = 23445.345
-- We dived by 4 and floor to get the terrain position
local terrainX = math.floor(positionX / 4)
local terrainZ = math.floor(positionZ / 4)
-- We use the terrain module to get the height
local height = module.GetHeight(terrainX, terrainZ)

then on the server side i would crate random invisable parts scartered around the world

then when the client enters the game they loop all invisable parts and place tree models in there place but using the height data to make sure the tree does not spawn under the terrain
this could also be done in a way so that the client only loads tree there standing close to if you want it to be more optmized but will be harder to code or you could use Content Streaming | Roblox Creator Documentation so that only invisable parts the player is standing close to is loaded but then you will need to use Instance.ChildAdded to detect when a new part was streamed to the client

the server will also give these invisable parts attributes

https://developer.roblox.com/en-us/api-reference/function/Instance/SetAttribute

for example
state = 0 (tree does not exist no model will be spawned)
state = 1 (tree is spawned)
state = 2 (tree has fallen over)

then the clients will listen for attribute changed using

https://developer.roblox.com/en-us/api-reference/event/Instance/AttributeChanged

then as soon and the server updates the attribute all clients would update the model

then when a client hits the tree the client will send a event to the server saying i hit this invisable part then the server will change the state of the part from 1 to 2 so it falls down for all clients to see

instead of using state you could also use health as a attribute if you wanted trees to have multiple hits before they fall


Off Topic

please remember that hackers can make and edit there own local scripts
also remeber that the client has ownership of there own humanoid

so what this means is hackers can send events to a server when ever they want with what ever infomation they want
also hackers can move there character around and teleport where ever they want

so how can we prevent hackers from cuting trees
if we simple check the distance from the tree we cant trust this infomation as a hacker could simply teleport there character to a tree and send the event

so we need a loop on the server side and this loop needs to keep track of every clients character position then we need to do a Magnitude check and if the hacker moved to fast we know not to let them cut the tree

2 Likes

Well That did not work in my case

script.Parent.launched.Event:Connect(function()
	
	wait(12.2)
	local module = script.ModuleScript
	
	-- Position where the tree will spawn
	local positionX = script.Parent.Position.X
	local positionZ = script.Parent.Position.Z
	-- We dived by 4 and floor to get the terrain position
	local terrainX = math.floor(positionX / 4)
	local terrainZ = math.floor(positionZ / 4)
	-- We use the terrain module to get the height
	local height = module.GetHeight(terrainX, terrainZ)
	script.Parent.CFrame.Position.Y = height
end)

It tell me
GetHeight is not a valid member of ModuleScript “Workspace.rockey.Launcher.force.timer.ModuleScript”

1 Like

You need to use the Terrain Module, generated, by the plugin.

If you mean turning the local script into a module script ,Yes Ive done that

Hey so let me show you how to use ModuleScript | Roblox Creator Documentation

so when you create a empty modulescript
you first see this

local module = {}

return module

so what this is doing is creating a table and returning it to who ever requires the module
so what ever you put inside this table other scripts can have access to it

we can put functions inside this table so other scripts can call this same function over and over

and here you can see what it looks like to add a function to the module

local module = {}

module.MyModuleFunction = function()
	print("Hello World")
end

return module

and this is what the plugin script should look like when its a module

local module = {}

local dataChild = nil
local heightChild = nil
local materialChild = nil
for i, child in ipairs(script:GetChildren()) do
	if child.ClassName == "ModuleScript" then
		local data = child:GetAttribute("Data")
		if data == "Terrain" then
			dataChild = child
		end
	elseif child.ClassName == "Folder" then
		local data = child:GetAttribute("Data")
		if data == "Height" then
			heightChild = child
		elseif data == "Material" then
			materialChild = child
		end
	end
end
if dataChild == nil then return end

local distance = 16
local chunkSize = 16
local positionX = math.huge
local positionZ = math.huge
local heightData = {}
local materialData = {}
local loaded = {}

local data = require(dataChild)

if heightChild then
	for i, child in ipairs(heightChild:GetDescendants()) do
		if child.ClassName ~= "ModuleScript" then continue end
		local data = require(child)
		local position = child:GetAttribute("Position")
		for i = 1, #data, 2 do
			local x = position.X + data[i]
			local zData = data[i + 1]
			if heightData[x] == nil then heightData[x] = {} end
			for j = 1, #zData, 2 do
				local z = position.Y + zData[j]
				local height = zData[j + 1]
				heightData[x][z] = height
			end
		end
	end
end

if materialChild then
	for i, child in ipairs(materialChild:GetDescendants()) do
		if child.ClassName ~= "ModuleScript" then continue end
		local data = require(child)
		local position = child:GetAttribute("Position")
		for i = 1, #data, 2 do
			local x = position.X + data[i]
			local zData = data[i + 1]
			if materialData[x] == nil then materialData[x] = {} end
			for j = 1, #zData, 2 do
				local z = position.Y + zData[j]
				local material = zData[j + 1]
				materialData[x][z] = material
			end
		end
	end
end


module.GetHeight = function(x, z)
	if heightData[x] == nil then heightData[x] = {} end
	if heightData[x][z] ~= nil then return heightData[x][z] end	
	local height = 0
	for i, data in ipairs(data.noises) do
		local noise = math.noise(x * data[3], data[1], z * data[3])
		height += math.clamp(noise, data[4], data[5]) * data[2]
	end
	height += data.shift
	height = math.clamp(height, data.minimumHeight, data.maximumHeight)
	heightData[x][z] = height
	return height
end

Load = function(x, z)
	local minimum = math.huge
	local maximum = -math.huge
	for xx = x-1, x+1 do
		for zz = z-1, z+1 do
			local height = module.GetHeight(xx, zz)
			minimum = math.min(minimum, height)
			maximum = math.max(maximum, height)
		end
	end
	local slope = maximum - minimum
	local height = heightData[x][z]
	local thickness = height - minimum + data.thickness
	local cFrame = CFrame.new(x * 4 + 2, height - thickness / 2, z * 4 + 2)
	local size = Vector3.new(4, thickness, 4)
	if materialData[x] ~= nil and materialData[x][z] ~= nil then
		workspace.Terrain:FillBlock(cFrame, size, materialData[x][z])
	else
		for i, materialData in ipairs(data.materials) do
			if height >= materialData[2] and height < materialData[3] and slope >= materialData[4] and slope < materialData[5] then
				workspace.Terrain:FillBlock(cFrame, size, materialData[1])
				break
			end
		end
	end
	if height >= data.waterHeight then return end
	thickness = data.waterHeight - height
	local cFrame = CFrame.new(x * 4 + 2, height + thickness / 2, z * 4 + 2)
	local size = Vector3.new(4, thickness, 4)
	workspace.Terrain:FillBlock(cFrame, size, Enum.Material.Water)
end

LoadChunk = function(chunkX, chunkZ)
	if loaded[chunkX] == nil then loaded[chunkX] = {} end
	if loaded[chunkX][chunkZ] ~= nil then return end
	loaded[chunkX][chunkZ] = true
	local startX = chunkX * chunkSize
	local startZ = chunkZ * chunkSize
	local endX = startX + chunkSize - 1
	local endZ = startZ + chunkSize - 1
	for x = startX, endX do
		for z = startZ, endZ do
			Load(x, z)
		end
	end
	wait()
end

LoadChunks = function(chunkX, chunkZ, hole)
	if hole == nil then hole = 0 LoadChunk(chunkX, chunkZ) end
	hole = hole * 2 + 1
	local x = chunkX + math.floor(hole/2)
	local z = chunkZ - math.floor(hole/2)
	local dx, dz = 1, 0
	local passed = hole - 1
	local length = hole
	local amount = (distance * 2 + 1) * (distance * 2 + 1) - hole * hole
	for i = 1, amount do
		x += dx z += dz
		if Vector2.new(chunkX - x, chunkZ - z).Magnitude < distance + 0.5 then LoadChunk(x, z) end
		passed += 1
		if passed == length then passed = 0 dx, dz = -dz, dx if dz == 0 then length += 1 end end
	end
end

workspace.CurrentCamera:GetPropertyChangedSignal("Focus"):Connect(function()
	local x = workspace.CurrentCamera.Focus.Position.X
	local z = workspace.CurrentCamera.Focus.Position.Z
	local magnitude = Vector2.new(positionX - x, positionZ - z).Magnitude
	if magnitude < chunkSize * 8 then return end
	positionX, positionZ = x, z
	x = math.floor(positionX / 4 / chunkSize)
	z = math.floor(positionZ / 4 / chunkSize)
	if magnitude < distance * chunkSize * 2 then
		LoadChunks(x, z, math.floor(distance / 2 - 0.5))
	else
		LoadChunks(x, z)
	end
end)

return module

notice how the

GetHeight = function(x, z)

now looks like this

module.GetHeight = function(x, z)

ok now we have the modulescript place this modulescript inside ReplicatedStorage

now this is how we use the module script in other scripts

-- get the module table into the terrainModule varable
local terrainModule = require(game.ReplicatedStorage.TerrainModuleScript)

-- Position where the tree will spawn
local positionX = 489534.345
local positionZ = 23445.345

-- We dived by 4 and floor to get the terrain position
local terrainX = math.floor(positionX / 4)
local terrainZ = math.floor(positionZ / 4)

-- We use the terrain module to get the height
local height = terrainModule.GetHeight(terrainX, terrainZ)

notice how we use require to get the module

thank you for helping,Im making an exploration game and i make a rocket but terrain always load slowly so the roket go through,But now,I can get height of the terrain at a time and just teleport the rocket there,Thank you!

Make sure you only have 1 terrain script so your not loading the terrain multiple times other then that the terrain is loading as fast as it can unfortunately it’s not a simple operation so it needs some time