Read/Write Terrain Modulescript with no :CopyRegion() or :PasteRegion()

If I recall correctly, you can’t store a TerrainRegion from game.Workspace.Terrain:CopyRegion() in something like a Datastore. So, I made a module to do so.

--Configuration
local ReadSpeed = 10 --Speed of how fast it reads the terrain and makes the data (Should be kept low like 1-15)
local InstantRead = false --Use wait()s in reading terrain if false. DO NOT SET TO TRUE IF USING LARGE REGIONS

local WriteSpeed = 250 --Speed of how fast it reads the data and makes the terrain (Can be 1-1000 with no issues)
local InstantWrite = false --Use waits()s in reading data if false. DO NOT SET TO TRUE IF USING LARGE REGIONS

--Mainscript







local Terrain = game.Workspace.Terrain



local Enums = {
	Materials = {
		["Enum.CellMaterial.Grass"] = 1,
		["Enum.CellMaterial.Sand"] = 2,
		["Enum.CellMaterial.Brick"] = 3,
		["Enum.CellMaterial.Granite"] = 4,
		["Enum.CellMaterial.Asphalt"] = 5,
		["Enum.CellMaterial.Iron"] = 6,
		["Enum.CellMaterial.Aluminum"] = 7,
		["Enum.CellMaterial.Gold"] = 8,
		["Enum.CellMaterial.WoodPlank"] = 9, 
		["Enum.CellMaterial.WoodLog"] = 10,
		["Enum.CellMaterial.Gravel"] = 11,
		["Enum.CellMaterial.CinderBlock"] = 12,
		["Enum.CellMaterial.MossyStone"] = 13, 
		["Enum.CellMaterial.Cement"] = 14,
		["Enum.CellMaterial.RedPlastic"] = 15,
		["Enum.CellMaterial.BluePlastic"] = 16,  
		["Enum.CellMaterial.Water"] = 17,

	},
	BlockTypes = {
		["Enum.CellBlock.Solid"] = 0,
		["Enum.CellBlock.VerticalWedge"] = 1, 
		["Enum.CellBlock.CornerWedge"] = 2,
		["Enum.CellBlock.InverseCornerWedge"] = 3,
		["Enum.CellBlock.HorizontalWedge"] = 4,
	},
	Orientations = {
		["Enum.CellOrientation.NegZ"] = 0,
		["Enum.CellOrientation.X"] = 1,
		["Enum.CellOrientation.Z"] = 2,
		["Enum.CellOrientation.NegX"] = 3,
	}
}




local function GetRegionSize(Region)
	return Region.Max.X - Region.Min.X,Region.Max.Y - Region.Min.Y,Region.Max.Z - Region.Min.Z
end


local Module = {}


function Module:WriteTerrainData(Region)
	local SizeX,SizeY,SizeZ = GetRegionSize(Region)
	
	
	local grid = {}
	
	local function setVal(a,b,c,x,y,z)
		local aDim = grid[a] or {}
		grid[a] = aDim
		local bDim = aDim[b] or {}
		aDim[b] = bDim
		local cDim = bDim[c] or {}
		bDim[c] = cDim
		local xDim = cDim[x] or {}
		cDim[x] = xDim
		local yDim = xDim[y] or {}
		xDim[y] = yDim
		yDim[z] = z
	end


	
	for i = 1, SizeX + 1 do
		for j = 1, SizeZ + 1 do
			for k = 1, SizeY + 1 do
				local M,B,O = Terrain:GetCell(i + Region.Min.X - 1,k + Region.Min.Y - 1,j + Region.Min.Z - 1)
				if tostring(M) ~= "Enum.CellMaterial.Empty" then
					setVal(Enums.Materials[tostring(M)],Enums.BlockTypes[tostring(B)],Enums.Orientations[tostring(O)],i - 1,k - 1,j - 1)
				end
			end
		end
		if InstantRead == false then if math.floor(i/ReadSpeed)*ReadSpeed == i then wait() end end
	end
	return {Data=grid,SizeX = SizeX,SizeY = SizeY,SizeZ = SizeZ}
end

function Module:ReadTerrainData(DataTable,Corner,Erase)
	local Data,SizeX,SizeY,SizeZ = DataTable.Data,DataTable.SizeX,DataTable.SizeY,DataTable.SizeZ
	if Erase == true then
		Terrain:SetCells(Region3int16.new(Corner,Corner + Vector3int16.new(SizeX - 1,SizeY - 1,SizeZ - 1)),0,0,0)
	end
	local i = 0
	for Material,Blocks in pairs(Data) do
		for Block,Orientations in pairs(Blocks) do
			for Orientation,Xs in pairs(Orientations) do
				for X,Ys in pairs(Xs) do
					i = i + 1
					for Y,Zs in pairs(Ys) do
						for Z,_ in pairs(Zs) do
							Terrain:SetCell(X + Corner.X,Y + Corner.Y,Z + Corner.Z,Material,Block,Orientation)
						end
					end
					if InstantWrite == false then if math.floor(i/WriteSpeed)*WriteSpeed == i then wait() end end
				end
			end
		end
	end
end

return Module
5 Likes