Advertisement
bootyeater69420

Untitled

Jun 28th, 2025
18
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 7.97 KB | None | 0 0
  1. -- Server Script: Fast chunk loading + progressive block exposure + block break handling
  2.  
  3. local Players = game:GetService("Players")
  4. local RunService = game:GetService("RunService")
  5. local Workspace = game:GetService("Workspace")
  6. local ReplicatedStorage = game:GetService("ReplicatedStorage")
  7.  
  8. -- Config
  9. local BLOCK_SIZE = 3
  10. local CHUNK_SIZE = 16
  11. local HEIGHT_SCALE = 7
  12. local BASE_HEIGHT = 20
  13. local DIRT_LAYERS = 3
  14. local RENDER_DISTANCE_CHUNKS = 3 -- 3 chunks radius = 7x7 chunks total
  15.  
  16. local ORE_CHANCES = {
  17. Emerald = 15,
  18. Diamond = 50,
  19. Ruby = 120,
  20. }
  21.  
  22. local CAVE_CHANCE = 0.015
  23. local RAVINE_CHANCE = 0.001
  24. local RUBY_MAX_Y = 10
  25.  
  26. local TREE_CHANCE = 0.05
  27.  
  28. -- References
  29. local Blocks = Workspace:WaitForChild("Blocks")
  30. local Trees = Workspace:WaitForChild("Trees")
  31. local TerrainFolder = Workspace:FindFirstChild("GeneratedTerrain") or Instance.new("Folder", Workspace)
  32. TerrainFolder.Name = "GeneratedTerrain"
  33.  
  34. local BreakBlockEvent = ReplicatedStorage:FindFirstChild("BreakBlockEvent")
  35. if not BreakBlockEvent then
  36. BreakBlockEvent = Instance.new("RemoteEvent")
  37. BreakBlockEvent.Name = "BreakBlockEvent"
  38. BreakBlockEvent.Parent = ReplicatedStorage
  39. end
  40.  
  41. local noise = math.noise
  42. local Random = Random.new()
  43.  
  44. -- Data store for chunks: key -> chunkData
  45. -- chunkData.blocks = {[posStr] = {blockName, rendered, part}}
  46. local Chunks = {}
  47.  
  48. -- Helpers
  49. local function posToStr(x,y,z)
  50. return x..","..y..","..z
  51. end
  52.  
  53. local function strToPos(s)
  54. local x,y,z = s:match("([^,]+),([^,]+),([^,]+)")
  55. return tonumber(x), tonumber(y), tonumber(z)
  56. end
  57.  
  58. local function getChunkCoords(x,z)
  59. return math.floor(x/CHUNK_SIZE), math.floor(z/CHUNK_SIZE)
  60. end
  61.  
  62. local function getChunkKey(cx, cz)
  63. return cx .. "," .. cz
  64. end
  65.  
  66. local function worldToChunkBlockPos(x,y,z)
  67. local cx, cz = getChunkCoords(x,z)
  68. local bx = x - cx*CHUNK_SIZE
  69. local bz = z - cz*CHUNK_SIZE
  70. return cx, cz, bx, y, bz
  71. end
  72.  
  73. local function createBlockPart(blockName, pos)
  74. local template = Blocks:FindFirstChild(blockName)
  75. if not template then return nil end
  76. local part = template:Clone()
  77. part:PivotTo(CFrame.new(pos))
  78. part.Parent = TerrainFolder
  79. return part
  80. end
  81.  
  82. -- Coroutine-friendly chunk generation (yielding every few blocks to avoid lag)
  83. local function generateChunk(cx, cz)
  84. local chunkData = {blocks = {}}
  85.  
  86. for bx = 0, CHUNK_SIZE - 1 do
  87. for bz = 0, CHUNK_SIZE - 1 do
  88. local wx = cx * CHUNK_SIZE + bx
  89. local wz = cz * CHUNK_SIZE + bz
  90. local height = math.floor(noise(wx / 25, wz / 25) * HEIGHT_SCALE + BASE_HEIGHT)
  91.  
  92. for y = 0, height do
  93. -- Cave & ravine
  94. if y < height - 2 and noise(wx / 10, y / 10, wz / 10) > 0.55 and Random:NextNumber() < CAVE_CHANCE then
  95. continue
  96. end
  97. if Random:NextNumber() < RAVINE_CHANCE and wz % 20 == 0 and y < BASE_HEIGHT then
  98. continue
  99. end
  100.  
  101. local blockName
  102. if y == height then
  103. blockName = "Grass"
  104. elseif y >= height - DIRT_LAYERS then
  105. blockName = "Dirt"
  106. else
  107. local placedOre = false
  108. for ore, chance in pairs(ORE_CHANCES) do
  109. if ore == "Ruby" and y > RUBY_MAX_Y then
  110. -- skip ruby
  111. elseif Random:NextInteger(1, chance) == 1 then
  112. blockName = ore
  113. placedOre = true
  114. break
  115. end
  116. end
  117. if not placedOre then
  118. blockName = "Stone"
  119. end
  120. end
  121.  
  122. local worldPos = Vector3.new(wx * BLOCK_SIZE, y * BLOCK_SIZE, wz * BLOCK_SIZE)
  123. local key = posToStr(wx, y, wz)
  124.  
  125. chunkData.blocks[key] = {
  126. blockName = blockName,
  127. rendered = false,
  128. part = nil
  129. }
  130.  
  131. -- Render surface blocks only at generation
  132. if y == height then
  133. local part = createBlockPart(blockName, worldPos)
  134. chunkData.blocks[key].rendered = true
  135. chunkData.blocks[key].part = part
  136. end
  137. end
  138. end
  139. -- Yield every row of bz to avoid lag
  140. coroutine.yield()
  141. end
  142.  
  143. -- Generate trees on grass surface blocks
  144. for key, data in pairs(chunkData.blocks) do
  145. if data.blockName == "Grass" and data.rendered then
  146. local x, y, z = strToPos(key)
  147. if Random:NextNumber() < TREE_CHANCE then
  148. local treeList = Trees:GetChildren()
  149. if #treeList > 0 then
  150. local tree = treeList[Random:NextInteger(1, #treeList)]:Clone()
  151. local treePos = Vector3.new(x * BLOCK_SIZE, (y + 1) * BLOCK_SIZE, z * BLOCK_SIZE)
  152. tree:PivotTo(CFrame.new(treePos))
  153. tree.Parent = TerrainFolder
  154. end
  155. end
  156. end
  157. end
  158.  
  159. Chunks[getChunkKey(cx, cz)] = chunkData
  160. end
  161.  
  162. local function renderBlock(x,y,z)
  163. local cx, cz, bx, by, bz = worldToChunkBlockPos(x,y,z)
  164. local key = getChunkKey(cx, cz)
  165. local chunk = Chunks[key]
  166. if not chunk then return end
  167.  
  168. local blockKey = posToStr(x,y,z)
  169. local blockData = chunk.blocks[blockKey]
  170. if not blockData or blockData.rendered then return end
  171.  
  172. local pos = Vector3.new(x * BLOCK_SIZE, y * BLOCK_SIZE, z * BLOCK_SIZE)
  173. local part = createBlockPart(blockData.blockName, pos)
  174. if part then
  175. blockData.rendered = true
  176. blockData.part = part
  177. end
  178. end
  179.  
  180. local function exposeBlockAndNeighbors(x,y,z)
  181. -- Expose target block
  182. renderBlock(x,y,z)
  183. -- Expose all 6 neighbors (up/down/left/right/front/back)
  184. local neighbors = {
  185. {x+1, y, z}, {x-1, y, z},
  186. {x, y+1, z}, {x, y-1, z},
  187. {x, y, z+1}, {x, y, z-1}
  188. }
  189.  
  190. for _, pos in ipairs(neighbors) do
  191. renderBlock(pos[1], pos[2], pos[3])
  192. end
  193. end
  194.  
  195. local function breakBlock(x,y,z)
  196. local cx, cz = getChunkCoords(x,z)
  197. local chunkKey = getChunkKey(cx, cz)
  198. local chunk = Chunks[chunkKey]
  199. if not chunk then return end
  200.  
  201. local blockKey = posToStr(x,y,z)
  202. local blockData = chunk.blocks[blockKey]
  203. if not blockData or not blockData.rendered then return end
  204.  
  205. -- Remove block visual
  206. if blockData.part then
  207. blockData.part:Destroy()
  208. end
  209. -- Remove from chunk data
  210. chunk.blocks[blockKey] = nil
  211.  
  212. -- Expose neighbors
  213. exposeBlockAndNeighbors(x,y,z)
  214. end
  215.  
  216. local function getPlayerChunkPos(player)
  217. local hrp = player.Character and player.Character:FindFirstChild("HumanoidRootPart")
  218. if hrp then
  219. local pos = hrp.Position
  220. local cx, cz = getChunkCoords(math.floor(pos.X / BLOCK_SIZE), math.floor(pos.Z / BLOCK_SIZE))
  221. return cx, cz
  222. end
  223. return nil, nil
  224. end
  225.  
  226. local ACTIVE_CHUNKS = {}
  227.  
  228. local function loadChunksAroundPlayer(player)
  229. local cx, cz = getPlayerChunkPos(player)
  230. if not cx or not cz then return end
  231.  
  232. local keysToKeep = {}
  233.  
  234. for dx = -RENDER_DISTANCE_CHUNKS, RENDER_DISTANCE_CHUNKS do
  235. for dz = -RENDER_DISTANCE_CHUNKS, RENDER_DISTANCE_CHUNKS do
  236. local ncx, ncz = cx + dx, cz + dz
  237. local key = getChunkKey(ncx, ncz)
  238. keysToKeep[key] = true
  239.  
  240. if not Chunks[key] then
  241. -- Generate chunk in coroutine to avoid lag
  242. local co = coroutine.create(generateChunk)
  243. local success, err = coroutine.resume(co, ncx, ncz)
  244. while coroutine.status(co) ~= "dead" do
  245. coroutine.resume(co)
  246. task.wait() -- yield to prevent blocking
  247. end
  248. end
  249.  
  250. -- Parent all rendered blocks' parts for active chunks
  251. if not ACTIVE_CHUNKS[key] then
  252. for _, blockData in pairs(Chunks[key].blocks) do
  253. if blockData.rendered and blockData.part and blockData.part.Parent ~= TerrainFolder then
  254. blockData.part.Parent = TerrainFolder
  255. end
  256. end
  257. ACTIVE_CHUNKS[key] = true
  258. end
  259. end
  260. end
  261.  
  262. -- Unload distant chunks
  263. for key in pairs(ACTIVE_CHUNKS) do
  264. if not keysToKeep[key] then
  265. local chunk = Chunks[key]
  266. if chunk then
  267. for _, blockData in pairs(chunk.blocks) do
  268. if blockData.rendered and blockData.part then
  269. blockData.part.Parent = nil -- remove from workspace but keep data
  270. end
  271. end
  272. end
  273. ACTIVE_CHUNKS[key] = nil
  274. end
  275. end
  276. end
  277.  
  278. -- Player Join Handler
  279. Players.PlayerAdded:Connect(function(player)
  280. -- Periodic chunk loading for player
  281. spawn(function()
  282. while player.Parent do
  283. loadChunksAroundPlayer(player)
  284. wait(1)
  285. end
  286. end)
  287. end)
  288.  
  289. -- Handle block breaking event
  290. BreakBlockEvent.OnServerEvent:Connect(function(player, bx, by, bz)
  291. breakBlock(bx, by, bz)
  292. end)
  293.  
  294. print("🌲 Chunk-based terrain generator (server) initialized.")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement