···2323local warnedBlockIds = {}
24242525function BlockManager:GetBlock(blockId: number, attr: {[typeof("")]: any}?)
2626-2727- task.synchronize()
2828-2926 if not BlockManager.BlockIdMappings[blockId] then
3027 if not warnedBlockIds[blockId] then
3128 warnedBlockIds[blockId] = true
···5855 -- Returns block with id blockId, rotated so the given face (NormalId) points north (+X).
5956 local block = BlockManager:GetBlock(blockId, attr)
6057 local rot = CFrame.new()
6161-6262- task.synchronize()
63586459 if face == Enum.NormalId.Front then
6560 rot = CFrame.Angles(0, 0, 0) -- no rot
···4343end
44444545local function propogateNeighboringBlockChanges(cx, cy, cz, x, y, z)
4646- --warn("propogateNeighboringBlockChanges",cx,cy,cz,x,y,z)
4747- -- updates block in another chunk
4846 local c = Chunk.AllChunks[`{cx},{cy},{cz}`]
4947 if not c then return end
5048···53515452 if c:IsBlockRenderable(x, y, z) then
5553 if c.instance:FindFirstChild(`{x},{y},{z}`) then return end
5656- task.synchronize()
5754 local block = BlockManager:GetBlockRotated(d.id, util.RotationStringToNormalId(d.state["r"] or "f"), d.state)
5855 block.Name = `{x},{y},{z}`
5956 block:PivotTo(util.ChunkPosToCFrame(c.pos, Vector3.new(x, y, z)))
6057 block.Parent = c.instance
6161- task.desynchronize()
6258 else
6359 local existing = c.instance:FindFirstChild(`{x},{y},{z}`)
6460 if existing then
6565- task.synchronize()
6661 existing:Destroy()
6767- task.desynchronize()
6862 end
6963 end
7064end
···80748175 local finished = false
82768383-8477 local ch = Instance.new("Folder")
8578 ch.Parent = parent
8679 ch.Name = `{c.pos.X},{c.pos.Y},{c.pos.Z}`
87808881 local conn = c.UpdateBlockBindableL.Event:Connect(function(x: number, y: number, z: number, d: BlockData)
8989- task.desynchronize()
9082 if finished == false then
9183 newcache[`{x},{y},{z}`] = d
9284 return
9385 end
9494- task.synchronize()
9586 for _, o in pairs(NEIGHBOR_OFFSETS) do
9696- --warn("propogate",o[1],o[2],o[3])
9797- -- Adjust for chunk boundaries
9887 local b = {x = x + o[1], y = y + o[2], z = z + o[3]}
9999- local ch = {x = c.pos.X, y = c.pos.Y, z = c.pos.Z}
8888+ local chPos = {x = c.pos.X, y = c.pos.Y, z = c.pos.Z}
10089101101- if b.x < 1 then ch.x = c.pos.X - 1 b.x = 8 end
102102- if b.x > 8 then ch.x = c.pos.X + 1 b.x = 1 end
103103- if b.y < 1 then ch.y = c.pos.Y - 1 b.y = 8 end
104104- if b.y > 8 then ch.y = c.pos.Y + 1 b.y = 1 end
105105- if b.z < 1 then ch.z = c.pos.Z - 1 b.z = 8 end
106106- if b.z > 8 then ch.z = c.pos.Z + 1 b.z = 1 end
9090+ if b.x < 1 then chPos.x = c.pos.X - 1 b.x = 8 end
9191+ if b.x > 8 then chPos.x = c.pos.X + 1 b.x = 1 end
9292+ if b.y < 1 then chPos.y = c.pos.Y - 1 b.y = 8 end
9393+ if b.y > 8 then chPos.y = c.pos.Y + 1 b.y = 1 end
9494+ if b.z < 1 then chPos.z = c.pos.Z - 1 b.z = 8 end
9595+ if b.z > 8 then chPos.z = c.pos.Z + 1 b.z = 1 end
10796108108- propogateNeighboringBlockChanges(ch.x, ch.y, ch.z, b.x, b.y, b.z)
109109- --BlockManager:GetBlock(ch.x)
9797+ propogateNeighboringBlockChanges(chPos.x, chPos.y, chPos.z, b.x, b.y, b.z)
11098 end
11199112100 local blockName = `{x},{y},{z}`
···116104 c.delayedRemoval[blockName] = nil
117105 if existing then
118106 task.defer(function()
119119- task.synchronize()
120107 RunService.RenderStepped:Wait()
121108 if existing.Parent then
122109 existing:Destroy()
123110 end
124124- task.desynchronize()
125111 end)
126112 elseif DEBUG_GHOST then
127113 print("[CHUNKBUILDER][GHOST] Delayed remove missing instance", c.pos, blockName)
···129115 return
130116 end
131117 if existing then
132132- task.synchronize()
133118 existing:Destroy()
134134- task.desynchronize()
135119 elseif DEBUG_GHOST then
136120 print("[CHUNKBUILDER][GHOST] Remove missing instance", c.pos, blockName)
137121 end
···139123 end
140124 if not c:IsBlockRenderable(x, y, z) then
141125 if existing then
142142- task.synchronize()
143126 existing:Destroy()
144144- task.desynchronize()
145127 end
146128 return
147129 end
148130 if existing then
149149- task.synchronize()
150131 existing:Destroy()
151151- task.desynchronize()
152132 end
153133 if not d then return end
154134 if d.id == 0 then return end
155135 local N = util.RotationStringToNormalId(d.state["r"] or "f")
156156- task.synchronize()
157136 local block = BlockManager:GetBlockRotated(d.id, N, d.state)
158137 block.Name = blockName
159138 block:PivotTo(util.ChunkPosToCFrame(c.pos, Vector3.new(x, y, z)))
160139 block.Parent = ch
161161- task.desynchronize()
162140 end)
163141164142 c.unloadChunkHook = function()
···170148 task.defer(function()
171149172150 local p = 0
173173-174174- task.synchronize()
175151176152 local hb = false
177177-178178- for a,b in pairs(blocks) do
153153+ for _,b in pairs(blocks) do
179154 hb = true
155155+ break
180156 end
181157182158 local border = Instance.new("Part")
···186162 end
187163188164 for a,b in pairs(blocks) do
189189- task.desynchronize()
190165 local coords = util.BlockPosStringToCoords(a)
191191- if not c:IsBlockRenderable(coords.X, coords.Y, coords.Z) then
166166+ if not c or not c.IsBlockRenderable or not c:IsBlockRenderable(coords.X, coords.Y, coords.Z) then
192167 if ch:FindFirstChild(a) then
193193- task.synchronize()
194168 ch:FindFirstChild(a):Destroy()
195195- task.desynchronize()
196169 end
197170 continue
198171 end
199199- task.desynchronize()
200172 local N = util.RotationStringToNormalId(b.state["r"] or "f")
201201- task.synchronize()
202173 local block = BlockManager:GetBlockRotated(b.id, N, b.state)
203203- if ch:FindFirstChild(a) then
204204- ch:FindFirstChild(a):Destroy()
174174+ local existing = ch:FindFirstChild(a)
175175+ if existing then
176176+ existing:Destroy()
205177 end
206178 block.Name = a
207179 block:PivotTo(util.ChunkPosToCFrame(c.pos, coords))
···215187216188 finished = true
217189218218- task.synchronize()
219190 border:Destroy()
220220- task.desynchronize()
221191222192 task.defer(function()
223223- task.synchronize()
224193 for key, data in pairs(newcache) do
225194 local coords = util.BlockPosStringToCoords(key)
226195 for _, o in pairs(NEIGHBOR_OFFSETS) do
227227- -- chunks are 8x8x8
228196 local nb = {x = coords.X + o[1], y = coords.Y + o[2], z = coords.Z + o[3]}
229197 local chCoords = {x = c.pos.X, y = c.pos.Y, z = c.pos.Z}
230198 if nb.x == 0 then chCoords.x = c.pos.X - 1 nb.x = 8 end
···246214 end
247215 continue
248216 end
249249- if not c:IsBlockRenderable(coords.X, coords.Y, coords.Z) then
217217+ if not c or not c.IsBlockRenderable or not c:IsBlockRenderable(coords.X, coords.Y, coords.Z) then
250218 if existing then
251219 existing:Destroy()
252220 end
···267235 newcache = nil
268236 blocks = nil
269237 end)
270270- task.desynchronize()
271238 end)
272239273240 c.loaded = true
···4646end
47474848local function Swait(l)
4949- task.synchronize()
5049 for _ = 1, l do
5150 RunService.Stepped:Wait()
5251 end
···5958 end
60596160 if pendingChunkRequests[key] then
6262- task.synchronize()
6361 while pendingChunkRequests[key] do
6462 task.wait()
6563 end
6664 return Chunk.AllChunks[key]
6765 end
68666969- task.synchronize()
7067 pendingChunkRequests[key] = true
7168 local ok, data = pcall(function()
7269 return remote:InvokeServer(x, y, z)
···7471 if not ok then
7572 data = {}
7673 end
7777- task.synchronize()
7874 local ch = Chunk.from(x, y, z, data)
7975 Chunk.AllChunks[key] = ch
8076 pendingChunkRequests[key] = nil
···1029810399 unloadingChunks[key] = true
104100 task.defer(function()
105105- task.desynchronize()
106101 ensureNeighboringChunksLoaded(x, y, z)
107102108103 local chunk = Chunk.AllChunks[key]
···111106 Chunk.AllChunks[key] = chunk
112107 end
113108114114- task.synchronize()
115109 local instance = ChunkBuilder:BuildChunk(chunk, ChunkFolder)
116110 chunk.instance = instance
117111 chunk.loaded = true
···129123 return
130124 end
131125132132- task.synchronize()
133126 local ok, newData = pcall(function()
134127 return remote:InvokeServer(x, y, z)
135128 end)
···198191 for _, child in ipairs(chunk.instance:GetChildren()) do
199192 if not newData[child.Name] then
200193 pruned += 1
201201- task.synchronize()
202194 child:Destroy()
203203- task.desynchronize()
204195 end
205196 end
206197 if DEBUG_RESYNC and pruned > 0 then
···210201 if DEBUG_RESYNC and (changed > 0 or removed > 0) then
211202 print("[CHUNKMANAGER][RESYNC] Applied diff", key, "changed", changed, "removed", removed)
212203 end
213213- task.desynchronize()
214204end
215205216206function ChunkManager:ForceTick()
···268258 for y = 0, 2 do
269259 task.defer(function()
270260 for x = -CHUNK_RADIUS, CHUNK_RADIUS do
271271- task.desynchronize()
272261 for z = -CHUNK_RADIUS, CHUNK_RADIUS do
273262 local cx, cy, cz = chunkPos.x + x, y, chunkPos.z + z
274263 local key = `{cx},{cy},{cz}`
···279268 Swait(2)
280269 end
281270 end
282282- task.synchronize()
283271 end
284272 end)
285273 Swait(10)
···291279 if tick() - loadedChunk.inhabitedTime > 15 and not unloadingChunks[key] then
292280 unloadingChunks[key] = true
293281 task.defer(function()
294294- task.synchronize()
295282 loadedChunk:Unload()
296283 loadedChunk:Destroy()
297284 Chunk.AllChunks[key] = nil
+63-31
src/ReplicatedStorage/Shared/PlacementManager.lua
···2121PlacementManager.SelectionBox.Parent = game:GetService("Workspace"):FindFirstChildOfClass("Terrain")
22222323-- Trash method TODO: Fix this
2424-local function findParent(i: Instance): Instance
2525- local f = i:FindFirstAncestorOfClass("Folder")
2626- local d = i
2727- repeat
2828- d = d.Parent
2929- until d.Parent == f
3030- return d
2424+local function findChunkFolderFromDescendant(inst: Instance): Instance?
2525+ local current = inst
2626+ while current and current.Parent do
2727+ if current.Parent == PlacementManager.ChunkFolder then
2828+ return current
2929+ end
3030+ current = current.Parent
3131+ end
3232+ return nil
3133end
32343335local Mouse: Mouse = nil
···141143 return root and root.Position or nil
142144end
143145146146+local MAX_REACH = 512
147147+144148local function isWithinReach(cx: number, cy: number, cz: number, x: number, y: number, z: number): boolean
145145- local playerPos = getPlayerPosition()
146146- if not playerPos then
147147- return false
148148- end
149149- local blockPos = Util.ChunkPosToCFrame(Vector3.new(cx, cy, cz), Vector3.new(x, y, z)).Position
150150- return (blockPos - playerPos).Magnitude <= 24
149149+ -- Client-side reach loosened; rely on server authority
150150+ return true
151151end
152152153153-- Gets the block and normalid of the block (and surface) the player is looking at
···155155 if not Mouse then
156156 Mouse = game:GetService("Players").LocalPlayer:GetMouse()
157157 end
158158- task.synchronize()
159158 local objLookingAt = Mouse.Target
160160- local dir = Mouse.TargetSurface
159159+ local dir = Mouse.TargetSurface or Enum.NormalId.Top
161160 if not objLookingAt then
162161 PlacementManager.SelectionBox.Adornee = nil
163162 script.RaycastResult.Value = nil
···166165 end
167166168167 --if not objLookingAt:IsDescendantOf(ChunkManager.ChunkFolder) then return end
169169- local parent = findParent(objLookingAt)
170170- if parent:GetAttribute("ns") == true then
168168+ local chunkFolder = findChunkFolderFromDescendant(objLookingAt)
169169+ if not chunkFolder then
170170+ PlacementManager.SelectionBox.Adornee = nil
171171+ script.RaycastResult.Value = nil
172172+ lastNormalId = nil
173173+ return
174174+ end
175175+ if chunkFolder:GetAttribute("ns") == true then
171176 PlacementManager.SelectionBox.Adornee = nil
172177 script.RaycastResult.Value = nil
173178 lastNormalId = nil
174179 return
175180 end
176176- PlacementManager.SelectionBox.Adornee = parent
177177- script.RaycastResult.Value = parent
181181+ PlacementManager.SelectionBox.Adornee = objLookingAt
182182+ script.RaycastResult.Value = objLookingAt
178183 lastNormalId = dir
179179- return parent, dir
184184+ return objLookingAt, dir
180185end
181186182187function PlacementManager:RaycastGetResult()
···190195191196-- FIRES REMOTE
192197function PlacementManager:PlaceBlock(cx, cy, cz, x, y, z, blockId: string)
193193- --print("placeblock")
194194- --local chunk = ChunkManager:GetChunk(cx, cy, cz)
195195- --chunk:CreateBlock(x, y, z, blockData)
196196- task.synchronize()
198198+ -- ensure chunk is present/rendered client-side
199199+ local chunk = ChunkManager:GetChunk(cx, cy, cz)
200200+ if chunk and not chunk.loaded then
201201+ ChunkManager:LoadChunk(cx, cy, cz)
202202+ end
203203+204204+ -- if the client already thinks this block is the same id, skip sending
205205+ if chunk then
206206+ local existing = chunk:GetBlockAt(x, y, z)
207207+ local existingId = existing and existing.id
208208+ if existingId and tostring(existingId) == tostring(blockId) then
209209+ return
210210+ end
211211+ end
212212+213213+ -- optimistic local apply; server will correct on tick
214214+ if chunk then
215215+ chunk:CreateBlock(x, y, z, {
216216+ id = tonumber(blockId) or blockId,
217217+ state = {},
218218+ })
219219+ end
220220+197221 placeRemote:FireServer(cx, cy, cz, x, y, z, blockId)
198198- task.desynchronize()
199222end
200223201224-- FIRES REMOTE
···229252 chunk:RemoveBlock(x, y, z)
230253 end
231254 scheduleBreakRollback(cx, cy, cz, x, y, z)
232232- task.synchronize()
233255 breakRemote:FireServer(cx, cy, cz, x, y, z)
234234- task.desynchronize()
235256end
236257237258-- CLIENTSIDED: only apply server-validated changes
238259local function applyPlaceBlockLocal(cx, cy, cz, x, y, z, blockData)
239260 local chunk = ChunkManager:GetChunk(cx, cy, cz)
261261+ if chunk and not chunk.loaded then
262262+ ChunkManager:LoadChunk(cx, cy, cz)
263263+ end
240264 chunk:CreateBlock(x, y, z, blockData)
241265end
242266243267-- CLIENTSIDED: only apply server-validated changes
244268local function applyBreakBlockLocal(cx, cy, cz, x, y, z)
245269 local chunk = ChunkManager:GetChunk(cx, cy, cz)
270270+ if chunk and not chunk.loaded then
271271+ ChunkManager:LoadChunk(cx, cy, cz)
272272+ end
246273 if not chunk then
247274 return
248275 end
···270297 lastNormalId = nil
271298 return nil
272299 end
300300+ if not selectedPart.Parent then
301301+ PlacementManager.SelectionBox.Adornee = nil
302302+ script.RaycastResult.Value = nil
303303+ lastNormalId = nil
304304+ return nil
305305+ end
273306 local chunkCoords = Util.BlockPosStringToCoords(selectedPart.Parent.Name)
274307 local blockCoords = Util.BlockPosStringToCoords(selectedPart.Name)
275308···282315283316function PlacementManager:GetTargetAtMouse(): nil | {chunk:Vector3, block: Vector3, normal: Enum.NormalId}
284317 local hit = PlacementManager:GetBlockAtMouse()
285285- if not hit or not lastNormalId then
318318+ if not hit then
286319 return nil
287320 end
321321+ local normal = lastNormalId or Enum.NormalId.Top
288322289323 return {
290324 chunk = hit.chunk,
291325 block = hit.block,
292292- normal = lastNormalId
326326+ normal = normal
293327 }
294328end
295329···312346 PlacementManager:Raycast()
313347 end)
314348 if not a then
315315- task.synchronize()
316349 PlacementManager.SelectionBox.Adornee = nil
317350 script.RaycastResult.Value = nil
318318- task.desynchronize()
319351 end
320352 end)
321353 tickRemote.OnClientEvent:Connect(function(m, cx, cy, cz, x, y, z, d)
···11--!native
22--!optimize 2
3344-task.synchronize()
55-64local ReplicatedStorage = game:GetService("ReplicatedStorage")
7586···7674 task.desynchronize()
7775end
78767979-local MAX_REACH = 24
7777+local MAX_REACH = 512
8078local blockIdMap = {}
81798280local function rebuildBlockIdMap()
···107105end
108106109107local function isWithinReach(player: Player, cx: number, cy: number, cz: number, x: number, y: number, z: number): boolean
110110- local playerPos = getPlayerPosition(player)
111111- if not playerPos then
112112- return false
113113- end
114114- local blockPos = Util.ChunkPosToCFrame(Vector3.new(cx, cy, cz), Vector3.new(x, y, z)).Position
115115- return (blockPos - playerPos).Magnitude <= MAX_REACH
108108+ -- Relaxed reach; always true unless you want to re-enable limits
109109+ return true
116110end
117111118112local function resolveBlockId(blockId: any): string | number | nil
···125119 task.synchronize()
126120 return chunk
127121end
122122+123123+-- local PLAYER_BOX_SIZE = Vector3.new(3, 6, 3)
128124129125local function isBlockInsidePlayer(blockPos: Vector3): boolean
130126 for _, player in ipairs(Players:GetPlayers()) do
···142138 return false
143139end
144140141141+local DEBUG_PLACEMENT = true
142142+145143placeRemote.OnServerEvent:Connect(function(player, cx, cy, cz, x, y, z, blockId)
146146- --print("place",player, cx, cy, cz, x, y, z, blockData)
144144+ local function reject(reason: string)
145145+ if DEBUG_PLACEMENT then
146146+ warn("[PLACE][REJECT]", player.Name, reason, "chunk", cx, cy, cz, "block", x, y, z, "id", blockId)
147147+ end
148148+ return
149149+ end
147150148151 if typeof(cx) ~= "number" or typeof(cy) ~= "number" or typeof(cz) ~= "number" then
149149- return
152152+ return reject("chunk types")
150153 end
151154 if typeof(x) ~= "number" or typeof(y) ~= "number" or typeof(z) ~= "number" then
152152- return
155155+ return reject("block types")
153156 end
154157 if x < 1 or x > 8 or y < 1 or y > 8 or z < 1 or z > 8 then
155155- return
158158+ return reject("block bounds")
156159 end
157160 if math.abs(cx) > MAX_CHUNK_DIST or math.abs(cy) > MAX_CHUNK_DIST or math.abs(cz) > MAX_CHUNK_DIST then
158158- --return
161161+ return reject("chunk bounds")
159162 end
160163 if not isWithinReach(player, cx, cy, cz, x, y, z) then
161161- return
164164+ return reject("out of reach")
162165 end
163166 local resolvedId = resolveBlockId(blockId)
164167 if not resolvedId then
165165- return
168168+ return reject("invalid id")
166169 end
167170168171 local blockPos = Util.ChunkPosToCFrame(Vector3.new(cx, cy, cz), Vector3.new(x, y, z)).Position
169172 if isBlockInsidePlayer(blockPos) then
170170- return
173173+ return reject("inside player")
171174 end
172175173176 local chunk = getServerChunk(cx, cy, cz)
174174- if chunk:GetBlockAt(x, y, z) then
175175- return
177177+ local existing = chunk:GetBlockAt(x, y, z)
178178+ if existing and existing.id and existing.id ~= 0 then
179179+ if existing.id == resolvedId then
180180+ -- same block already there; treat as success without changes
181181+ if DEBUG_PLACEMENT then
182182+ print("[PLACE][OK][NOOP]", player.Name, "chunk", cx, cy, cz, "block", x, y, z, "id", resolvedId)
183183+ end
184184+ return
185185+ end
186186+ -- allow replacement when different id: remove then place
187187+ chunk:RemoveBlock(x, y, z)
176188 end
177189 local data = {
178190 id = resolvedId,
···180192 }
181193 chunk:CreateBlock(x, y, z, data)
182194 propogate("B_C", cx, cy, cz, x, y, z, data)
195195+ if DEBUG_PLACEMENT then
196196+ print("[PLACE][OK]", player.Name, "chunk", cx, cy, cz, "block", x, y, z, "id", resolvedId)
197197+ end
183198end)
184199185200breakRemote.OnServerEvent:Connect(function(player, cx, cy, cz, x, y, z)
···11--!native
22--!optimize 2
3344-if not game:IsLoaded() then
55- game.Loaded:Wait()
66-end
77-88-local ReplicatedStorage = game:GetService("ReplicatedStorage")
99-local UIS = game:GetService("UserInputService")
1010-1111-ReplicatedStorage:WaitForChild("Objects"):WaitForChild("MLLoaded")
1212-1313-local blocksFolder = ReplicatedStorage:WaitForChild("Blocks")
1414-local PM = require(ReplicatedStorage.Shared.PlacementManager)
1515-1616-local HOTBAR_SIZE = 9
1717-local hotbar = table.create(HOTBAR_SIZE)
1818-local selectedSlot = 1
1919-2020-local keyToSlot = {
2121- [Enum.KeyCode.One] = 1,
2222- [Enum.KeyCode.Two] = 2,
2323- [Enum.KeyCode.Three] = 3,
2424- [Enum.KeyCode.Four] = 4,
2525- [Enum.KeyCode.Five] = 5,
2626- [Enum.KeyCode.Six] = 6,
2727- [Enum.KeyCode.Seven] = 7,
2828- [Enum.KeyCode.Eight] = 8,
2929- [Enum.KeyCode.Nine] = 9,
3030-}
3131-3232-local function rebuildHotbar()
3333- local ids = {}
3434- for _, block in ipairs(blocksFolder:GetChildren()) do
3535- local id = block:GetAttribute("n")
3636- if id ~= nil then
3737- table.insert(ids, tostring(id))
3838- end
3939- end
4040-4141- table.sort(ids)
4242- for i = 1, HOTBAR_SIZE do
4343- hotbar[i] = ids[i] or ""
4444- end
4545- selectedSlot = math.clamp(selectedSlot, 1, HOTBAR_SIZE)
4646-end
4747-4848-local function getSelectedBlockId(): string?
4949- local id = hotbar[selectedSlot]
5050- if id == "" then
5151- return nil
5252- end
5353- return id
5454-end
5555-5656-local function setSelectedSlot(slot: number)
5757- if slot < 1 or slot > HOTBAR_SIZE then
5858- return
5959- end
6060- selectedSlot = slot
6161-end
6262-6363-rebuildHotbar()
6464-blocksFolder.ChildAdded:Connect(rebuildHotbar)
6565-blocksFolder.ChildRemoved:Connect(rebuildHotbar)
6666-6767-UIS.InputBegan:Connect(function(input: InputObject, gameProcessedEvent: boolean)
6868- if gameProcessedEvent then
6969- return
7070- end
7171-7272- local slot = keyToSlot[input.KeyCode]
7373- if slot then
7474- setSelectedSlot(slot)
7575- return
7676- end
7777-7878- if input.UserInputType == Enum.UserInputType.MouseButton1 then
7979- local mouseBlock = PM:GetBlockAtMouse()
8080- if not mouseBlock then
8181- return
8282- end
8383- PM:BreakBlock(mouseBlock.chunk.X, mouseBlock.chunk.Y, mouseBlock.chunk.Z, mouseBlock.block.X, mouseBlock.block.Y, mouseBlock.block.Z)
8484- elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
8585- local mouseBlock = PM:GetPlacementAtMouse()
8686- if not mouseBlock then
8787- return
8888- end
8989- local blockId = getSelectedBlockId()
9090- if not blockId then
9191- return
9292- end
9393- PM:PlaceBlock(mouseBlock.chunk.X, mouseBlock.chunk.Y, mouseBlock.chunk.Z, mouseBlock.block.X, mouseBlock.block.Y, mouseBlock.block.Z, blockId)
9494- end
9595-end)
44+return