Advertisement
Tag365

Command Computer Build API

Oct 6th, 2015
479
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 14.28 KB | None | 0 0
  1. -----------------------------------
  2. -- Build API by Matthew Cenance. --
  3. -----------------------------------
  4. -- Requires a Command Computer to function.
  5. if not commands then
  6.     error("The Build API can only be used on a Command Computer.")
  7. end
  8.  
  9. local API = {}
  10.  
  11. local errorIfValueTypeIsInvalid = function(value, typesAllowed, variableNum, errLevel)
  12.     if type(typesAllowed) == "table" then
  13.         for key, validType in ipairs(typesAllowed) do
  14.             if type(value) == validType then
  15.                 return true
  16.             end
  17.         end
  18.         error("bad argument #"..variableNum..": "..table.concat(typesAllowed, " or ").." expected, got "..type(value), errLevel or 3)
  19.     end
  20.     if type(value) ~= typesAllowed then
  21.         error("bad argument #"..variableNum..": "..typesAllowed.." expected, got "..type(value), errLevel or 3)
  22.     end
  23. end
  24.  
  25. -- Metatable for the block table.
  26. local AllowAnyMixedCasing = {
  27.     __index = function(self, key)
  28.         if type(key) == "string" then
  29.             if rawget(self, key:lower()) then
  30.                 return rawget(self, key:lower())
  31.             end
  32.         end
  33.     end
  34. }
  35.  
  36. -- Blocks table, which contains strings for various types of blocks.
  37. local blocks = setmetatable({
  38.     air = "minecraft:air",
  39.     stone = "minecraft:stone",
  40.     grass = "minecraft:grass",
  41.     wool = "minecraft:wool",
  42.     stainedclay = "minecraft:stained_hardened_clay",
  43.     blockofredstone = "minecraft:redstone_block",
  44.     redstoneblock = "minecraft:redstone_block",
  45. }, AllowAnyMixedCasing)
  46.  
  47. -- Structure API. --
  48. local Structure = {}
  49. Structure.__index = Structure
  50.  
  51. -- Creates a new Structure object with the selected dimensions.
  52. -- Arguments:
  53. -- width, height, and depth are the dimensions of the Structure.
  54. function Structure.new(width, height, depth)
  55.     -- Create a new Structure object.
  56.     local self = setmetatable({}, Structure)
  57.     self.data = {{{1, 0}}}
  58.     self.numberToTilename = {"minecraft:air"}
  59.     self.tilenameToNumber = {["minecraft:air"] = 1}
  60.  
  61.     -- Create an empty data table.
  62.     for y = 1, height do
  63.         self.data[y] = {{}}
  64.         for x = 1, width do
  65.             self.data[y][x] = {{1, 0}}
  66.             for z = 1, depth do
  67.                 self.data[y][x][z] = {1, 0}
  68.             end
  69.         end
  70.     end
  71.  
  72.     -- Set the width, height, and depth of the Structure, and mark the loading as finished.
  73.     self.loadFinished = true
  74.     self.width = width
  75.     self.height = height
  76.     self.depth = depth
  77.     return self
  78. end
  79.  
  80. -- Creates a new Structure object based off of a region of the Terrain.
  81. -- Arguments:
  82. -- x1, y1, z1 is the first corner of the region.
  83. -- x2, y2, z2 is the second corner of the region.
  84. function Structure.loadFromWorld(x1, y1, z1, x2, y2, z2)
  85.     -- Error checking.
  86.     errorIfValueTypeIsInvalid(x1, "number", 1)
  87.     errorIfValueTypeIsInvalid(y1, "number", 2)
  88.     errorIfValueTypeIsInvalid(z1, "number", 3)
  89.     errorIfValueTypeIsInvalid(x2, "number", 4)
  90.     errorIfValueTypeIsInvalid(y2, "number", 5)
  91.     errorIfValueTypeIsInvalid(z2, "number", 6)
  92.  
  93.     -- Create a new Structure object.
  94.     local self = setmetatable({}, Structure)
  95.     self.data = {{{}}}
  96.     self.numberToTilename = {"minecraft:air"}
  97.     self.tilenameToNumber = {["minecraft:air"] = 1}
  98.  
  99.     -- Create a coroutine table to hold all of our coroutines.
  100.     local coroutines = {}
  101.  
  102.     -- Create an empty data table, and also create coroutines that will obtain the block info from the world Terrain.
  103.     for y = 1, y2 - y1 do
  104.         self.data[y] = {}
  105.         for x = 1, x2 - x1 do
  106.             self.data[y][x] = {}
  107.             for z = 1, z2 - z1 do
  108.                 self.data[y][x][z] = {1, 0}
  109.                 table.insert(coroutines, function()
  110.                     local value = commands.getBlockInfo(x + x1, y + y1, z + z1)
  111.                     self:BuildBlock(x, y, z, value.name, value.metadata or 0)
  112.                 end)
  113.             end
  114.         end
  115.     end
  116.  
  117.     -- Start pulling information from the world.
  118.     parallel.waitForAll(unpack(coroutines))
  119.  
  120.     -- Set the width, height, and depth of the Structure, and mark the loading as finished.
  121.     self.loadFinished = true
  122.     self.width = x2 - x1
  123.     self.height = y2 - y1
  124.     self.depth = z2 - z1
  125.     return self
  126. end
  127.  
  128. local FileIdentifier = "error('This is a Model and cannot be run.', 0)"
  129.  
  130. -- Loads a structure from a path.
  131. -- Arguments:
  132. -- path is the location of the file you want to load.
  133. function Structure.load(path)
  134.     -- Error checking.
  135.     errorIfValueTypeIsInvalid(path, "string", 1)
  136.  
  137.     -- Create a new Structure object.
  138.     local self = setmetatable({}, Structure)
  139.     self.numberToTilename = {"minecraft:air"}
  140.     self.tilenameToNumber = {["minecraft:air"] = 1}
  141.     local file = fs.open(path, "r")
  142.     local width, height, depth
  143.  
  144.     -- Attempt to load the structure from a file.
  145.     if file then
  146.         local str = file.readLine()
  147.         if str ~= FileIdentifier then
  148.             error(path.." is not a Model file.", 2)
  149.         end
  150.  
  151.         -- Load the model tilename table.
  152.         local sLine = file.readLine()
  153.         while sLine do
  154.             if sLine == "-- Start of Structure Data --" then
  155.                 break
  156.             end
  157.             local id = #self.numberToTilename + 1
  158.             self.numberToTilename[id] = sLine
  159.             self.tilenameToNumber[sLine] = id
  160.             sLine = file.readLine()
  161.         end
  162.  
  163.         -- Load the model data.
  164.         sLine = file.readLine()
  165.         local myData = {}
  166.         local curY, curX = 0, 0
  167.         while sLine do
  168.             if sLine:find("Table") then
  169.                 curY = curY + 1
  170.                 width = curX
  171.                 curX = 0
  172.                 myData[curY] = {}
  173.             else
  174.                 curX = curX + 1
  175.                 local sLine2 = file.readLine()
  176.                 myData[curY][curX] = {}
  177.                 local values1 = {}
  178.                 local values2 = {}
  179.                 for value in string.gmatch(sLine, "%d+") do
  180.                     values1[#values1 + 1] = tonumber(value)
  181.                 end
  182.                 for value in string.gmatch(sLine2, "%d+") do
  183.                     values2[#values2 + 1] = tonumber(value)
  184.                 end
  185.                 for key, value in ipairs(values1) do
  186.                     myData[curY][curX][key] = {value, values2[key]}
  187.                 end
  188.                 depth = #values1
  189.             end
  190.             sLine = file.readLine()
  191.         end
  192.         self.data = myData
  193.         height = #myData
  194.         file.close()
  195.     else
  196.         error(path.." is not a valid path or file name.", 2)
  197.     end
  198.  
  199.     -- Set the width, height, and depth of the Structure, and mark the loading as finished.
  200.     self.loadFinished = true
  201.     self.width = width
  202.     self.height = height
  203.     self.depth = depth
  204.     return self
  205. end
  206.  
  207. -- Saves the structure to a file.
  208. -- Arguments:
  209. -- path is the location of the new file.
  210. function Structure:Save(path)
  211.     local file = fs.open(path, "w")
  212.     if file then
  213.         file.writeLine(FileIdentifier)
  214.         for key = 2, #self.numberToTilename do
  215.             file.writeLine(self.numberToTilename[key])
  216.         end
  217.         file.writeLine("-- Start of Structure Data --")
  218.         for y, yTable in ipairs(self.data) do
  219.             file.writeLine("Table "..y)
  220.             for x, xTable in ipairs(yTable) do
  221.                 local str = ""
  222.                 local str2 = ""
  223.                 for z, zBlock in ipairs(xTable) do
  224.                     str = str..zBlock[1].." "
  225.                     str2 = str2..zBlock[2].." "
  226.                 end
  227.                 file.writeLine(str)
  228.                 file.writeLine(str2)
  229.             end
  230.         end
  231.         file.close()
  232.         return true
  233.     end
  234.     error(path.." is not a valid path or file name.", 2)
  235. end
  236.  
  237. -- Sets a block in the Structure to the selected tilename and data string.
  238. -- Arguments:
  239. -- x, y, z is the absolute position you want to place the block.
  240. -- tilename is the tilename of the block you want to use.
  241. -- data is the metadata number of the block.
  242. function Structure:BuildBlock(x, y, z, tilename, data)
  243.     -- Error checking.
  244.     errorIfValueTypeIsInvalid(x, "number", 1)
  245.     errorIfValueTypeIsInvalid(y, "number", 2)
  246.     errorIfValueTypeIsInvalid(z, "number", 3)
  247.     errorIfValueTypeIsInvalid(tilename, "string", 4)
  248.     if type(tonumber(data)) ~= "number" and type(data) ~= "nil" then
  249.         error("bad argument #8: number expected, got "..type(data), 2)
  250.     end
  251.     data = tonumber(data)
  252.  
  253.     -- If the tilename has not been used before, add it to the tilename table.
  254.     if not self.tilenameToNumber[tilename] then
  255.         local id = #self.numberToTilename + 1
  256.         self.numberToTilename[id] = tilename
  257.         self.tilenameToNumber[tilename] = id
  258.     end
  259.  
  260.     -- Set the block.
  261.     self.data[y][x][z] = {self.tilenameToNumber[tilename], data or 0}
  262. end
  263.  
  264. -- Fills a 3D region of the structure with the selected block.
  265. -- Arguments:
  266. -- x1, y1, z1 is the first corner of the region.
  267. -- x2, y2, z2 is the second corner of the region.
  268. -- tilename is the tilename of the block you want to use.
  269. -- data is the metadata number of the block.
  270. function Structure:BuildBlocksInRegion(x1, y1, z1, x2, y2, z2, tilename, data)
  271.     -- Error checking.
  272.     errorIfValueTypeIsInvalid(x1, "number", 1)
  273.     errorIfValueTypeIsInvalid(y1, "number", 2)
  274.     errorIfValueTypeIsInvalid(z1, "number", 3)
  275.     errorIfValueTypeIsInvalid(x2, "number", 4)
  276.     errorIfValueTypeIsInvalid(y2, "number", 5)
  277.     errorIfValueTypeIsInvalid(z2, "number", 6)
  278.     errorIfValueTypeIsInvalid(tilename, "string", 7)
  279.     if type(tonumber(data)) ~= "number" and type(data) ~= "nil" then
  280.         error("bad argument #8: number expected, got "..type(data), 2)
  281.     end
  282.     data = tonumber(data)
  283.  
  284.     -- If the tilename has not been used before, add it to the tilename table.
  285.     if not self.tilenameToNumber[tilename] then
  286.         local id = #self.numberToTilename + 1
  287.         self.numberToTilename[id] = tilename
  288.         self.tilenameToNumber[tilename] = id
  289.     end
  290.     local id = self.tilenameToNumber[tilename]
  291.     for y = y1, y2 do
  292.         for x = x1, x2 do
  293.             for z = z1, z2 do
  294.                 self.data[y][x][z] = {id, data or 0}
  295.             end
  296.         end
  297.     end
  298. end
  299.  
  300. -- Pastes another Structure to this Structure.
  301. -- Arguments:
  302. -- structure2 is the Structure with the contents you want to paste.
  303. -- x, y, z is the position you want to paste the other Structure.
  304. -- placeAirBlocks sets if Air blocks in the other Structure will overwrite and remove blocks in this Structure.
  305. function Structure:PasteOtherStructure(structure2, xP, yP, zP, placeAirBlocks)
  306.     -- Error checking.
  307.     errorIfValueTypeIsInvalid(xP, "number", 2)
  308.     errorIfValueTypeIsInvalid(yP, "number", 3)
  309.     errorIfValueTypeIsInvalid(zP, "number", 4)
  310.  
  311.     -- Paste the other structure.
  312.     local blocksChanged = 0
  313.     for y = 1, self.height do
  314.         for x = 1, self.width do
  315.             for z = 1, self.depth do
  316.                 if structure2.data[y][x][z][1] ~= 1 or placeAirBlocks then
  317.                     blocksChanged = blocksChanged + 1
  318.                     self:BuildBlock(x + xP, y + yP, z + zP, structure2.numberToTilename[structure2.data[y][x][z][1]], structure2.data[y][x][z][2])
  319.                 end
  320.             end
  321.         end
  322.     end
  323.     return blocksChanged
  324. end
  325.  
  326. -- Pastes the contents of the Structure to the world.
  327. -- Arguments:
  328. -- x, y, z is the position you want to place the Structure.
  329. -- placeAirBlocks sets if Air blocks in the Structure will overwrite and remove blocks in the world.
  330. -- Returns the number of blocks changed in the world.
  331. function Structure:PlaceInWorld(xP, yP, zP, placeAirBlocks)
  332.     -- Error checking.
  333.     errorIfValueTypeIsInvalid(xP, "number", 1)
  334.     errorIfValueTypeIsInvalid(yP, "number", 2)
  335.     errorIfValueTypeIsInvalid(zP, "number", 3)
  336.  
  337.     -- Since this will set a large amount of blocks, disable the output until this operation completes.
  338.     local v, str = commands.exec("gamerule commandBlockOutput")
  339.     str = str[1]
  340.     local commandBlockOutput = (str:sub(#("commandBlockOutput = ") + 1) == "true")
  341.     commands.exec("gamerule commandBlockOutput false")
  342.  
  343.     -- Set all blocks in the Structure.
  344.     local blocksChanged = 0
  345.     for y = 1, self.height do
  346.         for x = 1, self.width do
  347.             for z = 1, self.depth do
  348.                 if self.data[y][x][z][1] ~= 1 or placeAirBlocks then
  349.                     blocksChanged = blocksChanged + 1
  350.                     API.buildBlock(x + xP - math.floor(self.width*.5), y + yP, z + zP - math.floor(self.depth*.5), self.numberToTilename[self.data[y][x][z][1]], self.data[y][x][z][2])
  351.                 end
  352.             end
  353.         end
  354.     end
  355.  
  356.     -- Reset the commandBlockOutput if it was already set.
  357.     if commandBlockOutput then
  358.         sleep(math.ceil(blocksChanged/1000)/20)
  359.         commands.exec("say "..blocksChanged.." blocks changed.")
  360.         commands.exec("gamerule commandBlockOutput "..tostring(commandBlockOutput))
  361.     end
  362.     return blocksChanged
  363. end
  364.  
  365. -- Build API. --
  366.  
  367. -- Returns an absolute world position from a position relative to the center of the Command Computer.
  368. -- Arguments:
  369. -- x, y, z is the position relative to the center of the Command Computer.
  370. -- All three variables are optional and can be set to nil.
  371. -- Returns a position relative to the center of the world.
  372. function API.getAbsolutePosition(x, y, z)
  373.     x, y, z = x or 0, y or 0, z or 0
  374.     local myX, myY, myZ = commands.getBlockPosition()
  375.     return x + myX, y + myY, z + myZ
  376. end
  377.  
  378. -- Returns a relative position from a position that is relative to the center of the world.
  379. -- Arguments:
  380. -- x, y, z is the position relative to the center of the world.
  381. -- All three variables are optional and can be set to nil.
  382. -- Returns a position relative to the center of the Command Computer.
  383. function API.getRelativePosition(x, y, z)
  384.     x, y, z = x or 0, y or 0, z or 0
  385.     local myX, myY, myZ = commands.getBlockPosition()
  386.     return x - myX, y + myY, z + myZ
  387. end
  388.  
  389.  
  390. -- Sets a block in the world to a selected tilename and metadata value.
  391. -- Arguments:
  392. -- x, y, z is the position you want to place the block.
  393. -- tilename is the tilename of the block you want to use.
  394. -- data is the metadata number of the block.
  395. function API.buildBlock(x, y, z, tilename, data)
  396.     -- Error checking.
  397.     errorIfValueTypeIsInvalid(x, "number", 1)
  398.     errorIfValueTypeIsInvalid(y, "number", 2)
  399.     errorIfValueTypeIsInvalid(z, "number", 3)
  400.     errorIfValueTypeIsInvalid(tilename, "string", 4)
  401.  
  402.     -- Set the block.
  403.     commands.execAsync("setblock "..x.." "..y.." "..z.." "..tilename.." "..(data or ""))
  404. end
  405.  
  406. -- Fills a region of the world Terrain with the selected block.
  407. -- Arguments:
  408. -- x1, y1, z1 is the first corner of the region.
  409. -- x2, y2, z2 is the second corner of the region.
  410. -- tilename is the tilename of the block you want to use.
  411. -- data is the metadata number of the block.
  412. -- Returns the number of blocks changed in the world.
  413. function API.buildBlocksInRegion(x1, y1, z1, x2, y2, z2, tilename, data)
  414.     -- Since this will set a large amount of blocks, disable the output until this operation completes.
  415.     local v, str = commands.exec("gamerule commandBlockOutput")
  416.     str = str[1]
  417.     local commandBlockOutput = (str:sub(#("commandBlockOutput = ") + 1) == "true")
  418.     commands.exec("gamerule commandBlockOutput false")
  419.  
  420.     -- Set all blocks in the Structure.
  421.     local blocksChanged = 0
  422.     for y = y1, y2 do
  423.         for x = x1, x2 do
  424.             for z = z1, z2 do
  425.                 blocksChanged = blocksChanged + 1
  426.                 API.buildBlock(x, y, z, tilename, data)
  427.             end
  428.         end
  429.     end
  430.  
  431.     -- Reset the commandBlockOutput if it was already set.
  432.     if commandBlockOutput then
  433.         sleep(math.ceil(blocksChanged/1000)/20)
  434.         commands.exec("say "..blocksChanged.." blocks changed.")
  435.         commands.exec("gamerule commandBlockOutput "..tostring(commandBlockOutput))
  436.     end
  437.  
  438.     return blocksChanged
  439. end
  440.  
  441. _G.blocks = blocks
  442. _G.Structure = Structure
  443. _G.build = API
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement