Minecraft-like Roblox block game
rblx.games/135624152691584
roblox
roblox-game
rojo
1--!native
2--!optimize 2
3
4local ReplicatedStorage = game:GetService("ReplicatedStorage")
5local ServerStorage = game:GetService("ServerStorage")
6local ClientStateService = require(script.Parent.ClientState)
7
8local Shared = ReplicatedStorage:WaitForChild("Shared")
9local ModsFolder = ReplicatedStorage:WaitForChild("Mods")
10local BlocksFolderRS = ReplicatedStorage:FindFirstChild("Blocks") or Instance.new("Folder")
11BlocksFolderRS.Name = "Blocks"
12BlocksFolderRS.Parent = ReplicatedStorage
13local BlocksFolderSS = ServerStorage:FindFirstChild("Blocks") or Instance.new("Folder")
14BlocksFolderSS.Name = "Blocks"
15BlocksFolderSS.Parent = ServerStorage
16
17local Util = require(Shared.Util)
18local TG = require(script.TerrainGen)
19local Players = game:GetService("Players")
20
21local blockIdMap = {}
22local rebuildBlockIdMap
23
24local function syncBlocksToServerStorage()
25 BlocksFolderSS:ClearAllChildren()
26 for _, child in ipairs(BlocksFolderRS:GetChildren()) do
27 child:Clone().Parent = BlocksFolderSS
28 end
29 ClientStateService:SetBlocksFolder(BlocksFolderSS)
30 if rebuildBlockIdMap then
31 rebuildBlockIdMap()
32 end
33end
34
35BlocksFolderRS.ChildAdded:Connect(syncBlocksToServerStorage)
36BlocksFolderRS.ChildRemoved:Connect(syncBlocksToServerStorage)
37
38do
39 local workspaceModFolder = game:GetService("Workspace"):WaitForChild("mods")
40
41 for _,v in pairs(workspaceModFolder:GetChildren()) do
42 v.Parent = ModsFolder
43 end
44 workspaceModFolder:Destroy()
45end
46
47local ML = require(Shared.ModLoader)
48ML.loadModsS()
49syncBlocksToServerStorage()
50ClientStateService:Init()
51
52do
53 local bv = Instance.new("BoolValue")
54 bv.Name = "MLLoaded"
55 bv.Value = true
56 bv.Parent = ReplicatedStorage:WaitForChild("Objects")
57end
58
59local MAX_CHUNK_DIST = 200
60
61local FakeChunk = TG:GetFakeChunk(-5,-5,-5)
62
63task.synchronize()
64
65ReplicatedStorage.Tick.OnServerEvent:Connect(function(player: Player, v: string)
66 if TG.ServerChunkCache[v] then
67 pcall(function()
68 TG.ServerChunkCache[v].inhabitedTime = tick()
69 end)
70 end
71end)
72
73ReplicatedStorage.RecieveChunkPacket.OnServerInvoke = function(plr: Player, x: number, y: number, z: number)
74 -- validate xyz type and limit
75 if typeof(x) ~= "number" or typeof(y) ~= "number" or typeof(z) ~= "number" then
76 return {}
77 end
78
79 if math.abs(x) > MAX_CHUNK_DIST or math.abs(y) > MAX_CHUNK_DIST or math.abs(z) > MAX_CHUNK_DIST then
80 return FakeChunk.data
81 end
82
83 task.desynchronize()
84 local chunk = TG:GetChunk(x, y, z)
85 local chunkdata = chunk.data
86 task.synchronize()
87
88 return chunkdata
89
90end
91
92local tickRemote = ReplicatedStorage.Tick
93local remotes = ReplicatedStorage:WaitForChild("Remotes")
94local placeRemote = remotes:WaitForChild("PlaceBlock")
95local breakRemote = remotes:WaitForChild("BreakBlock")
96local blocksFolder = BlocksFolderSS
97local function propogate(a, cx, cy, cz, x, y, z, bd)
98 task.synchronize()
99 tickRemote:FireAllClients(a, cx, cy, cz, x, y, z, bd)
100 task.desynchronize()
101end
102
103local MAX_REACH = 512
104
105rebuildBlockIdMap = function()
106 table.clear(blockIdMap)
107 for _, block in ipairs(blocksFolder:GetChildren()) do
108 local id = block:GetAttribute("n")
109 if id ~= nil then
110 blockIdMap[id] = id
111 blockIdMap[tostring(id)] = id
112 end
113 end
114end
115
116rebuildBlockIdMap()
117blocksFolder.ChildAdded:Connect(rebuildBlockIdMap)
118blocksFolder.ChildRemoved:Connect(rebuildBlockIdMap)
119
120local function getPlayerPosition(player: Player): Vector3?
121 local character = player.Character
122 if not character then
123 return nil
124 end
125 local root = character:FindFirstChild("HumanoidRootPart")
126 if not root then
127 return nil
128 end
129 return root.Position
130end
131
132local function isWithinReach(player: Player, cx: number, cy: number, cz: number, x: number, y: number, z: number): boolean
133 -- Relaxed reach; always true unless you want to re-enable limits
134 return true
135end
136
137local function resolveBlockId(blockId: any): string | number | nil
138 return blockIdMap[blockId]
139end
140
141local function playerCanUseBlock(player: Player, resolvedId: any): boolean
142 if not ClientStateService:HasInInventory(player, resolvedId) then
143 return false
144 end
145 local selected = ClientStateService:GetSelectedBlockId(player)
146 if not selected then
147 return false
148 end
149 return tostring(selected) == tostring(resolvedId)
150end
151
152local function getServerChunk(cx: number, cy: number, cz: number)
153 task.desynchronize()
154 local chunk = TG:GetChunk(cx, cy, cz)
155 task.synchronize()
156 return chunk
157end
158
159-- local PLAYER_BOX_SIZE = Vector3.new(3, 6, 3)
160
161local function isBlockInsidePlayer(blockPos: Vector3): boolean
162 for _, player in ipairs(Players:GetPlayers()) do
163 local character = player.Character
164 if character then
165 local cf, size = character:GetBoundingBox()
166 local localPos = cf:PointToObjectSpace(blockPos)
167 if math.abs(localPos.X) <= size.X * 0.5
168 and math.abs(localPos.Y) <= size.Y * 0.5
169 and math.abs(localPos.Z) <= size.Z * 0.5 then
170 return true
171 end
172 end
173 end
174 return false
175end
176
177local DEBUG_PLACEMENT = false
178local function debugPlacementLog(...: any)
179 if DEBUG_PLACEMENT then
180 Util.StudioLog(...)
181 end
182end
183
184local function debugPlacementWarn(...: any)
185 if DEBUG_PLACEMENT then
186 Util.StudioWarn(...)
187 end
188end
189
190placeRemote.OnServerEvent:Connect(function(player, cx, cy, cz, x, y, z, blockId)
191 local function reject(reason: string)
192 debugPlacementWarn("[PLACE][REJECT]", player.Name, reason, "chunk", cx, cy, cz, "block", x, y, z, "id", blockId)
193 return
194 end
195
196 if typeof(cx) ~= "number" or typeof(cy) ~= "number" or typeof(cz) ~= "number" then
197 return reject("chunk types")
198 end
199 if typeof(x) ~= "number" or typeof(y) ~= "number" or typeof(z) ~= "number" then
200 return reject("block types")
201 end
202 if x < 1 or x > 8 or y < 1 or y > 8 or z < 1 or z > 8 then
203 return reject("block bounds")
204 end
205 if math.abs(cx) > MAX_CHUNK_DIST or math.abs(cy) > MAX_CHUNK_DIST or math.abs(cz) > MAX_CHUNK_DIST then
206 return reject("chunk bounds")
207 end
208 if not isWithinReach(player, cx, cy, cz, x, y, z) then
209 return reject("out of reach")
210 end
211 local resolvedId = resolveBlockId(blockId)
212 if not resolvedId then
213 return reject("invalid id")
214 end
215 if not playerCanUseBlock(player, resolvedId) then
216 return reject("not in inventory/hotbar")
217 end
218
219 local blockPos = Util.ChunkPosToCFrame(Vector3.new(cx, cy, cz), Vector3.new(x, y, z)).Position
220 if isBlockInsidePlayer(blockPos) then
221 return reject("inside player")
222 end
223
224 local chunk = getServerChunk(cx, cy, cz)
225 local existing = chunk:GetBlockAt(x, y, z)
226 if existing and existing.id and existing.id ~= 0 then
227 if existing.id == resolvedId then
228 -- same block already there; treat as success without changes
229 debugPlacementLog("[PLACE][OK][NOOP]", player.Name, "chunk", cx, cy, cz, "block", x, y, z, "id", resolvedId)
230 return
231 end
232 -- allow replacement when different id: remove then place
233 chunk:RemoveBlock(x, y, z)
234 end
235 local data = {
236 id = resolvedId,
237 state = {}
238 }
239 chunk:CreateBlock(x, y, z, data)
240 propogate("B_C", cx, cy, cz, x, y, z, data)
241 debugPlacementLog("[PLACE][OK]", player.Name, "chunk", cx, cy, cz, "block", x, y, z, "id", resolvedId)
242end)
243
244breakRemote.OnServerEvent:Connect(function(player, cx, cy, cz, x, y, z)
245 if typeof(cx) ~= "number" or typeof(cy) ~= "number" or typeof(cz) ~= "number" then
246 return
247 end
248 if typeof(x) ~= "number" or typeof(y) ~= "number" or typeof(z) ~= "number" then
249 return
250 end
251 if x < 1 or x > 8 or y < 1 or y > 8 or z < 1 or z > 8 then
252 return
253 end
254 if math.abs(cx) > MAX_CHUNK_DIST or math.abs(cy) > MAX_CHUNK_DIST or math.abs(cz) > MAX_CHUNK_DIST then
255 return
256 end
257 if not isWithinReach(player, cx, cy, cz, x, y, z) then
258 return
259 end
260
261 local chunk = getServerChunk(cx, cy, cz)
262 if not chunk:GetBlockAt(x, y, z) then
263 task.synchronize()
264 tickRemote:FireClient(player, "C_R", cx, cy, cz, 0, 0, 0, 0)
265 task.desynchronize()
266 debugPlacementLog("[BREAK][NOOP]", player.Name, "chunk", cx, cy, cz, "block", x, y, z)
267 return
268 end
269 chunk:RemoveBlock(x, y, z)
270 propogate("B_D", cx, cy, cz, x, y, z, 0)
271 debugPlacementLog("[BREAK][OK]", player.Name, "chunk", cx, cy, cz, "block", x, y, z)
272end)
273
274task.desynchronize()