local PlacementManager = {} local ChunkManager = require("./ChunkManager") local Util = require("./Util") PlacementManager.ChunkFolder = ChunkManager.ChunkFolder local raycastParams = RaycastParams.new() raycastParams.FilterDescendantsInstances = {PlacementManager.ChunkFolder} raycastParams.FilterType = Enum.RaycastFilterType.Include raycastParams.IgnoreWater = true if _G.SB then return nil end _G.SB = true PlacementManager.SelectionBox = script.SelectionBox:Clone() PlacementManager.SelectionBox.Name = "$SelectionBox"..(game:GetService("RunService"):IsServer() and "_SERVER" or "") PlacementManager.SelectionBox.Parent = game:GetService("Workspace"):FindFirstChildOfClass("Terrain") -- Trash method TODO: Fix this local function findParent(i: Instance): Instance local f = i:FindFirstAncestorOfClass("Folder") local d = i repeat d = d.Parent until d.Parent == f return d end local Mouse: Mouse = nil local lastNormalId: Enum.NormalId? = nil local function normalIdToOffset(normal: Enum.NormalId): Vector3 if normal == Enum.NormalId.Top then return Vector3.new(0, 1, 0) elseif normal == Enum.NormalId.Bottom then return Vector3.new(0, -1, 0) elseif normal == Enum.NormalId.Left then return Vector3.new(-1, 0, 0) elseif normal == Enum.NormalId.Right then return Vector3.new(1, 0, 0) elseif normal == Enum.NormalId.Back then return Vector3.new(0, 0, 1) elseif normal == Enum.NormalId.Front then return Vector3.new(0, 0, -1) end return Vector3.new(0, 0, 0) end local function offsetChunkBlock(chunk: Vector3, block: Vector3, offset: Vector3) local cx, cy, cz = chunk.X, chunk.Y, chunk.Z local bx, by, bz = block.X + offset.X, block.Y + offset.Y, block.Z + offset.Z if bx < 1 then bx = 8 cx -= 1 elseif bx > 8 then bx = 1 cx += 1 end if by < 1 then by = 8 cy -= 1 elseif by > 8 then by = 1 cy += 1 end if bz < 1 then bz = 8 cz -= 1 elseif bz > 8 then bz = 1 cz += 1 end return Vector3.new(cx, cy, cz), Vector3.new(bx, by, bz) end -- Gets the block and normalid of the block (and surface) the player is looking at function PlacementManager:Raycast() if not Mouse then Mouse = game:GetService("Players").LocalPlayer:GetMouse() end task.synchronize() local objLookingAt = Mouse.Target local dir = Mouse.TargetSurface if not objLookingAt then PlacementManager.SelectionBox.Adornee = nil script.RaycastResult.Value = nil lastNormalId = nil return end --if not objLookingAt:IsDescendantOf(ChunkManager.ChunkFolder) then return end local parent = findParent(objLookingAt) if parent:GetAttribute("ns") == true then PlacementManager.SelectionBox.Adornee = nil script.RaycastResult.Value = nil lastNormalId = nil return end PlacementManager.SelectionBox.Adornee = parent script.RaycastResult.Value = parent lastNormalId = dir return parent, dir end function PlacementManager:RaycastGetResult() return script.RaycastResult.Value end local remotes = game:GetService("ReplicatedStorage"):WaitForChild("Remotes") local placeRemote = remotes:WaitForChild("PlaceBlock") local breakRemote = remotes:WaitForChild("BreakBlock") local tickRemote = game:GetService("ReplicatedStorage").Tick -- FIRES REMOTE function PlacementManager:PlaceBlock(cx, cy, cz, x, y, z, blockId: string) --print("placeblock") --local chunk = ChunkManager:GetChunk(cx, cy, cz) --chunk:CreateBlock(x, y, z, blockData) placeRemote:FireServer(cx, cy, cz, x, y, z, blockId) end -- FIRES REMOTE function PlacementManager:BreakBlock(cx, cy, cz, x, y, z) --print("breakblock") --local chunk = ChunkManager:GetChunk(cx, cy, cz) --chunk:RemoveBlock(x, y, z) breakRemote:FireServer(cx, cy, cz, x, y, z) end -- CLIENTSIDED function PlacementManager:PlaceBlockLocal(cx, cy, cz, x, y, z, blockData) local chunk = ChunkManager:GetChunk(cx, cy, cz) chunk:CreateBlock(x, y, z, blockData) end -- CLIENTSIDED function PlacementManager:BreakBlockLocal(cx, cy, cz, x, y, z) local chunk = ChunkManager:GetChunk(cx, cy, cz) chunk:RemoveBlock(x, y, z) end function PlacementManager:GetBlockAtMouse(): nil | {chunk:Vector3, block: Vector3} local selectedPart = PlacementManager:RaycastGetResult() --print(selectedPart and selectedPart:GetFullName() or nil) if selectedPart == nil then PlacementManager.SelectionBox.Adornee = nil script.RaycastResult.Value = nil lastNormalId = nil return nil end if not selectedPart.Parent then PlacementManager.SelectionBox.Adornee = nil script.RaycastResult.Value = nil lastNormalId = nil return nil end local chunkCoords = Util.BlockPosStringToCoords(selectedPart.Parent.Name) local blockCoords = Util.BlockPosStringToCoords(selectedPart.Name) return { chunk = chunkCoords, block = blockCoords } end function PlacementManager:GetTargetAtMouse(): nil | {chunk:Vector3, block: Vector3, normal: Enum.NormalId} local hit = PlacementManager:GetBlockAtMouse() if not hit or not lastNormalId then return nil end return { chunk = hit.chunk, block = hit.block, normal = lastNormalId } end function PlacementManager:GetPlacementAtMouse(): nil | {chunk:Vector3, block: Vector3} local hit = PlacementManager:GetTargetAtMouse() if not hit then return nil end local offset = normalIdToOffset(hit.normal) local placeChunk, placeBlock = offsetChunkBlock(hit.chunk, hit.block, offset) return { chunk = placeChunk, block = placeBlock } end function PlacementManager:Init() game:GetService("RunService").Heartbeat:Connect(function() local a,b = pcall(function() PlacementManager:Raycast() end) if not a then task.synchronize() PlacementManager.SelectionBox.Adornee = nil script.RaycastResult.Value = nil task.desynchronize() end end) tickRemote.OnClientEvent:Connect(function(m, cx, cy, cz, x, y, z, d) --warn("PROPOGATED TICK", m, cx, cy, cz, x, y, z, d) if m == "B_C" then PlacementManager:PlaceBlockLocal(cx, cy, cz, x, y ,z, d) end if m == "B_D" then PlacementManager:BreakBlockLocal(cx, cy, cz, x, y ,z) end end) end return PlacementManager