made it better. instructions print to console when you press copy
local SES = game:GetService("ScriptEditorService")
local toolbar = plugin:CreateToolbar("Plugin Name")
local pluginButton = toolbar:CreateButton(
"Terrain Copy Paste", --Text that will appear below button
"Copy and paste terrain", --Text that will appear if you hover your mouse on button
"rbxassetid://8740888472") --Button icon
local info = DockWidgetPluginGuiInfo.new(
Enum.InitialDockState.Right, --From what side gui appears
false, --Widget will be initially enabled
false, --Don't overdrive previouse enabled state
200, --default weight
300, --default height
150, --minimum weight (optional)
150 --minimum height (optional)
)
-- Construct UI
local widget = plugin:CreateDockWidgetPluginGui("TestWidget", info)
widget.Title = "TestWidget" --Giving title to our widget gui
local CopyBtn = Instance.new("TextButton")
CopyBtn.BorderSizePixel = 0
CopyBtn.AnchorPoint = Vector2.new(0.5,0)
CopyBtn.Size = UDim2.fromScale(.6, .4)
CopyBtn.Position = UDim2.fromScale(.5, 0.05)
CopyBtn.SizeConstraint = Enum.SizeConstraint.RelativeYY
CopyBtn.Text = "Copy"
CopyBtn.TextScaled = true
CopyBtn.Parent = widget
local PasteBtn = Instance.new("TextButton")
PasteBtn.BorderSizePixel = 0
PasteBtn.AnchorPoint = Vector2.new(0.5,1)
PasteBtn.Size = UDim2.fromScale(.6, .4)
PasteBtn.Position = UDim2.fromScale(.5, 0.95)
PasteBtn.SizeConstraint = Enum.SizeConstraint.RelativeYY
PasteBtn.Text = "Paste"
PasteBtn.TextScaled = true
PasteBtn.Parent = widget
pluginButton.Click:Connect(function()
widget.Enabled = not widget.Enabled
end)
local function findVolume()
if not game.Workspace:FindFirstChild("VolumePart") then
error("You need to create a part named \"VolumePart\" parented to the workspace which represents the area to be copied from or pasted into")
end
return game.Workspace:FindFirstChild("VolumePart")
end
--[[local function toString(voxeldata)
task.wait(.0005)
local answer = "{\n"
for key, value in pairs(voxeldata) do
if key == "Size" then
answer ..= " Size = Vector3.new(" .. tostring(value) .. "),\n"
else
answer ..= ` [{key}]` .. " = {\n"
for k, v in pairs(value) do
answer ..= ` [{k}]` .. " = {\n"
for kk, vv in pairs(v) do
answer ..= ` [{kk}]` .. " = " .. tostring(vv) .. ",\n"
end
answer ..= " },\n"
end
answer ..= "},\n"
end
end
return answer .. "}"
end]]
local http = game:GetService("HttpService")
local function toString(voxelData)
for x, row in pairs(voxelData) do
if x ~= "Size" then
for y, column in pairs (row) do
for z, entry in pairs(column) do
if typeof(entry) == "EnumItem" then
column[z] = entry.Name
end
end
end
end
end
return http:JSONEncode(voxelData)
end
local function createSaveForVolume(Part)
local w = Part.Size/2
task.wait(0.02)
local Materials, Occupancies = game.Workspace.Terrain:ReadVoxels(Region3.new(Part.Position - w, Part.Position + w), 4)
local SaveScript = Instance.new("ModuleScript")
SaveScript.Name = "TerrainData"
local Materials = toString(Materials)
local Occupancies = toString(Occupancies)
local Source = "local TerrainData = {}"
.. "\nTerrainData.Materials = " .. "`" .. Materials .. "`"
.. `\nTerrainData.Occupancies = "{Occupancies}"`
.. "\nreturn TerrainData"
SES:UpdateSourceAsync(SaveScript, function(oldContent)
return Source
end)
SaveScript.Parent = Part
end
CopyBtn.Activated:Connect(function()
if not game.Workspace:FindFirstChild("TerrainCopyVolumes") then
warn("You need to set up your Workspace"
.."\n1. Create a folder in the Workspace named \"TerrainCopyVolumes\". This has now been created for you"
.."\n2. Create Parts and parent those Parts to TerrainCopyVolumes. These parts represent the space(s) being copied."
.."\n Leave the parts in their original orientation. This does not support rotated parts."
.."\n Be careful not to make them too big! Trying to copy massive spaces can lag out your machine or even crash Studio."
.."\n If you're experimenting with large volumes, save your file first!"
.."\n If you need to copy a massive area, divide the area into multiple parts parented to TerrainCopyVolumes"
.."\n3. Move the parts (still within the folder) to wherever you want to paste the copied terrain to"
.."\n To paste the terrain in a different experience, copy the TerrainCopyVolumes folder, paste it to the new experience, and then use the Paste button in the plugin"
.."\n To paste to a different area in the same map, just move the parts and click paste"
.."\n4. Peace and love on planet earth"
)
local F = Instance.new("Folder")
F.Name = "TerrainCopyVolumes"
F.Parent = game.Workspace
print("Folder: ", F)
return
end
warn("Creating save data . . . Please wait until done!")
local count = #game.Workspace.TerrainCopyVolumes:GetChildren()
if count == 0 then
warn("No parts were found..."
.."\n- Did you make sure the parts you created are parented to TerrainCopyVolumes?"
.."\n- If so, do you accidentally have multiple folders named TerrainCopyVolumes?"
)
return
end
for i, c in pairs(game.Workspace.TerrainCopyVolumes:GetChildren()) do
createSaveForVolume(c)
print(`... finished {i}/{count}`)
end
warn("Done! Peace and love on planet earth")
end)
local function loadSaveDataOnPart(Part)
local w = Part.Size/2
local SaveData = require(Part:FindFirstChild("TerrainData"))
-- Read existing voxels
local Materials, Occupancies = game.Workspace.Terrain:ReadVoxels(Region3.new(Part.Position - w, Part.Position + w), 4)
local SaveMaterials = http:JSONDecode(SaveData.Materials)
local SaveOccupancies = http:JSONDecode(SaveData.Occupancies)
SaveMaterials.Size = Materials.Size
SaveOccupancies.Size = Occupancies.Size
-- Ensure we have enough material and occupancies data
local newSize = Materials.Size
for x = 1, newSize.X do
local row = SaveMaterials[x]
if row then
for y = 1, newSize.Y do
local col = SaveMaterials[x][y]
if col then
for y = 1, newSize.Z do
if col[y] then
col[y] = Enum.Material[col[y]]
else
col[y] = Enum.Material.Air
end
end
else
-- No SaveMaterials[x][y]
SaveMaterials[x][y] = table.create(newSize.Z, Enum.Material.Air)
end
end
else
-- No SaveMaterials[x]
SaveMaterials[x] = {}
for y = 1, newSize.Y do
SaveMaterials[x][y] = table.create(newSize.Z, Enum.Material.Air)
end
end
end
local newSize = Occupancies.Size
for x = 1, newSize.X do
local row = SaveOccupancies[x]
if row then
for y = 1, newSize.Y do
local col = SaveOccupancies[x][y]
if col then
for y = 1, newSize.Z do
if not col[y] then
col[y] = 0
end
end
else
-- No SaveMaterials[x][y]
SaveOccupancies[x][y] = table.create(newSize.Z, 0)
end
end
else
-- No SaveMaterials[x]
SaveOccupancies[x] = {}
for y = 1, newSize.Y do
SaveOccupancies[x][y] = table.create(newSize.Z, 0)
end
end
end
-- Ensure we don't have too many data
if #SaveMaterials > newSize.X then
for x = #SaveMaterials, newSize.X+1, -1 do
table.remove(SaveMaterials, x)
end
end
for x = 1, #SaveMaterials do
local row = SaveMaterials[x]
if #row > newSize.Y then
for y = #row, newSize.Y + 1, -1 do
table.remove(row, y)
end
end
for y = 1, #row do
local column = row[y]
if #column > newSize.Z then
for z = #column, newSize.Z + 1, -1 do
table.remove(column, z)
end
end
end
end
if #SaveOccupancies > newSize.X then
for x = #SaveOccupancies, newSize.X+1, -1 do
table.remove(SaveOccupancies, x)
end
end
for x = 1, #SaveOccupancies do
local row = SaveOccupancies[x]
if #row > newSize.Y then
for y = #row, newSize.Y + 1, -1 do
table.remove(row, y)
end
end
for y = 1, #row do
local column = row[y]
if #column > newSize.Z then
for z = #column, newSize.Z + 1, -1 do
table.remove(column, z)
end
end
end
end
game.Workspace.Terrain:WriteVoxels(Region3.new(Part.Position - w, Part.Position + w), 4, SaveMaterials, SaveOccupancies)
end
PasteBtn.Activated:Connect(function()
for i, c in pairs(game.Workspace.TerrainCopyVolumes:GetChildren()) do
loadSaveDataOnPart(c)
end
end)