--!native --!optimize 2 local ReplicatedStorage = game:GetService("ReplicatedStorage") local ServerStorage = game:GetService("ServerStorage") local ClientStateService = require(script.Parent.ClientState) local Shared = ReplicatedStorage:WaitForChild("Shared") local ModsFolder = ReplicatedStorage:WaitForChild("Mods") local BlocksFolderRS = ReplicatedStorage:FindFirstChild("Blocks") or Instance.new("Folder") BlocksFolderRS.Name = "Blocks" BlocksFolderRS.Parent = ReplicatedStorage local BlocksFolderSS = ServerStorage:FindFirstChild("Blocks") or Instance.new("Folder") BlocksFolderSS.Name = "Blocks" BlocksFolderSS.Parent = ServerStorage local Util = require(Shared.Util) local TG = require(script.TerrainGen) local Players = game:GetService("Players") local blockIdMap = {} local rebuildBlockIdMap local function syncBlocksToServerStorage() BlocksFolderSS:ClearAllChildren() for _, child in ipairs(BlocksFolderRS:GetChildren()) do child:Clone().Parent = BlocksFolderSS end ClientStateService:SetBlocksFolder(BlocksFolderSS) if rebuildBlockIdMap then rebuildBlockIdMap() end end BlocksFolderRS.ChildAdded:Connect(syncBlocksToServerStorage) BlocksFolderRS.ChildRemoved:Connect(syncBlocksToServerStorage) do local workspaceModFolder = game:GetService("Workspace"):WaitForChild("mods") for _,v in pairs(workspaceModFolder:GetChildren()) do v.Parent = ModsFolder end workspaceModFolder:Destroy() end local ML = require(Shared.ModLoader) ML.loadModsS() syncBlocksToServerStorage() ClientStateService:Init() do local bv = Instance.new("BoolValue") bv.Name = "MLLoaded" bv.Value = true bv.Parent = ReplicatedStorage:WaitForChild("Objects") end local MAX_CHUNK_DIST = 200 local FakeChunk = TG:GetFakeChunk(-5,-5,-5) task.synchronize() ReplicatedStorage.Tick.OnServerEvent:Connect(function(player: Player, v: string) if TG.ServerChunkCache[v] then pcall(function() TG.ServerChunkCache[v].inhabitedTime = tick() end) end end) ReplicatedStorage.RecieveChunkPacket.OnServerInvoke = function(plr: Player, x: number, y: number, z: number) -- validate xyz type and limit if typeof(x) ~= "number" or typeof(y) ~= "number" or typeof(z) ~= "number" then return {} end if math.abs(x) > MAX_CHUNK_DIST or math.abs(y) > MAX_CHUNK_DIST or math.abs(z) > MAX_CHUNK_DIST then return FakeChunk.data end task.desynchronize() local chunk = TG:GetChunk(x, y, z) local chunkdata = chunk.data task.synchronize() return chunkdata end local tickRemote = ReplicatedStorage.Tick local remotes = ReplicatedStorage:WaitForChild("Remotes") local placeRemote = remotes:WaitForChild("PlaceBlock") local breakRemote = remotes:WaitForChild("BreakBlock") local blocksFolder = BlocksFolderSS local function propogate(a, cx, cy, cz, x, y, z, bd) task.synchronize() tickRemote:FireAllClients(a, cx, cy, cz, x, y, z, bd) task.desynchronize() end local MAX_REACH = 512 rebuildBlockIdMap = function() table.clear(blockIdMap) for _, block in ipairs(blocksFolder:GetChildren()) do local id = block:GetAttribute("n") if id ~= nil then blockIdMap[id] = id blockIdMap[tostring(id)] = id end end end rebuildBlockIdMap() blocksFolder.ChildAdded:Connect(rebuildBlockIdMap) blocksFolder.ChildRemoved:Connect(rebuildBlockIdMap) local function getPlayerPosition(player: Player): Vector3? local character = player.Character if not character then return nil end local root = character:FindFirstChild("HumanoidRootPart") if not root then return nil end return root.Position end local function isWithinReach(player: Player, cx: number, cy: number, cz: number, x: number, y: number, z: number): boolean -- Relaxed reach; always true unless you want to re-enable limits return true end local function resolveBlockId(blockId: any): string | number | nil return blockIdMap[blockId] end local function playerCanUseBlock(player: Player, resolvedId: any): boolean if not ClientStateService:HasInInventory(player, resolvedId) then return false end local selected = ClientStateService:GetSelectedBlockId(player) if not selected then return false end return tostring(selected) == tostring(resolvedId) end local function getServerChunk(cx: number, cy: number, cz: number) task.desynchronize() local chunk = TG:GetChunk(cx, cy, cz) task.synchronize() return chunk end -- local PLAYER_BOX_SIZE = Vector3.new(3, 6, 3) local function isBlockInsidePlayer(blockPos: Vector3): boolean for _, player in ipairs(Players:GetPlayers()) do local character = player.Character if character then local cf, size = character:GetBoundingBox() local localPos = cf:PointToObjectSpace(blockPos) if math.abs(localPos.X) <= size.X * 0.5 and math.abs(localPos.Y) <= size.Y * 0.5 and math.abs(localPos.Z) <= size.Z * 0.5 then return true end end end return false end local DEBUG_PLACEMENT = false local function debugPlacementLog(...: any) if DEBUG_PLACEMENT then Util.StudioLog(...) end end local function debugPlacementWarn(...: any) if DEBUG_PLACEMENT then Util.StudioWarn(...) end end placeRemote.OnServerEvent:Connect(function(player, cx, cy, cz, x, y, z, blockId) local function reject(reason: string) debugPlacementWarn("[PLACE][REJECT]", player.Name, reason, "chunk", cx, cy, cz, "block", x, y, z, "id", blockId) return end if typeof(cx) ~= "number" or typeof(cy) ~= "number" or typeof(cz) ~= "number" then return reject("chunk types") end if typeof(x) ~= "number" or typeof(y) ~= "number" or typeof(z) ~= "number" then return reject("block types") end if x < 1 or x > 8 or y < 1 or y > 8 or z < 1 or z > 8 then return reject("block bounds") end if math.abs(cx) > MAX_CHUNK_DIST or math.abs(cy) > MAX_CHUNK_DIST or math.abs(cz) > MAX_CHUNK_DIST then return reject("chunk bounds") end if not isWithinReach(player, cx, cy, cz, x, y, z) then return reject("out of reach") end local resolvedId = resolveBlockId(blockId) if not resolvedId then return reject("invalid id") end if not playerCanUseBlock(player, resolvedId) then return reject("not in inventory/hotbar") end local blockPos = Util.ChunkPosToCFrame(Vector3.new(cx, cy, cz), Vector3.new(x, y, z)).Position if isBlockInsidePlayer(blockPos) then return reject("inside player") end local chunk = getServerChunk(cx, cy, cz) local existing = chunk:GetBlockAt(x, y, z) if existing and existing.id and existing.id ~= 0 then if existing.id == resolvedId then -- same block already there; treat as success without changes debugPlacementLog("[PLACE][OK][NOOP]", player.Name, "chunk", cx, cy, cz, "block", x, y, z, "id", resolvedId) return end -- allow replacement when different id: remove then place chunk:RemoveBlock(x, y, z) end local data = { id = resolvedId, state = {} } chunk:CreateBlock(x, y, z, data) propogate("B_C", cx, cy, cz, x, y, z, data) debugPlacementLog("[PLACE][OK]", player.Name, "chunk", cx, cy, cz, "block", x, y, z, "id", resolvedId) end) breakRemote.OnServerEvent:Connect(function(player, cx, cy, cz, x, y, z) if typeof(cx) ~= "number" or typeof(cy) ~= "number" or typeof(cz) ~= "number" then return end if typeof(x) ~= "number" or typeof(y) ~= "number" or typeof(z) ~= "number" then return end if x < 1 or x > 8 or y < 1 or y > 8 or z < 1 or z > 8 then return end if math.abs(cx) > MAX_CHUNK_DIST or math.abs(cy) > MAX_CHUNK_DIST or math.abs(cz) > MAX_CHUNK_DIST then return end if not isWithinReach(player, cx, cy, cz, x, y, z) then return end local chunk = getServerChunk(cx, cy, cz) if not chunk:GetBlockAt(x, y, z) then task.synchronize() tickRemote:FireClient(player, "C_R", cx, cy, cz, 0, 0, 0, 0) task.desynchronize() debugPlacementLog("[BREAK][NOOP]", player.Name, "chunk", cx, cy, cz, "block", x, y, z) return end chunk:RemoveBlock(x, y, z) propogate("B_D", cx, cy, cz, x, y, z, 0) debugPlacementLog("[BREAK][OK]", player.Name, "chunk", cx, cy, cz, "block", x, y, z) end) task.desynchronize()