Advertisement
joebodo

schematic.api

Apr 19th, 2014
148
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 28.47 KB | None | 0 0
  1. --[[
  2.   Loading and manipulating a schematic
  3. --]]
  4.  
  5. local DEFLATE = Util.loadApi('deflatelua.api')
  6.  
  7. local schematicMagic = 0x0a00
  8. local gzipMagic = 0x1f8b
  9.  
  10. Schematic = class.class()
  11. function Schematic:init(args)
  12.   self.blocks = { }
  13.   self.damages = { }
  14.   self.originalBlocks = { }
  15.   self.placementChains = { }
  16.   self.x, self.y, self.z = 0, 0, 0
  17.   self.height = 0
  18.   self.index = 1
  19. end
  20.  
  21. --[[
  22.   Credit to Orwell for the schematic file reader code
  23.   http://www.computercraft.info/forums2/index.php?/topic/1949-turtle-schematic-file-builder/
  24.  
  25.   Some parts of the file reader code was modified from the original
  26. --]]
  27.  
  28. function Schematic:discardBytes(h, n, spinner)
  29.   for i = 1,n do
  30.     h:readbyte()
  31.     if (i % 1000) == 0 then
  32.       spinner:spin()
  33.     end
  34.   end
  35. end
  36.  
  37. function Schematic:readname(h)  
  38.   local n1 = h:readbyte(h)
  39.   local n2 = h:readbyte(h)
  40.  
  41.   if(n1 == nil or n2 == nil) then
  42.     return ""
  43.   end
  44.  
  45.   local n = n1*256 + n2
  46.  
  47.   local str = ""
  48.   for i=1,n do
  49.     local c = h:readbyte(h)
  50.     if c == nil then
  51.       return
  52.     end  
  53.     str = str .. string.char(c)
  54.   end
  55.   return str
  56. end
  57.  
  58. function Schematic:parse(a, h, containsName, spinner)
  59.  
  60.   if a==0 then
  61.     return
  62.   end
  63.  
  64.   local name
  65.   if containsName then
  66.     name = self:readname(h)
  67.   end
  68.  
  69.   if a==1 then
  70.     self:discardBytes(h, 1, spinner)
  71.   elseif a==2 then
  72.     local i1 = h:readbyte(h)
  73.     local i2 = h:readbyte(h)
  74.     local i = i1*256 + i2
  75.     if(name=="Height") then
  76.       --self.height = i
  77.     elseif (name=="Length") then
  78.       self.length = i
  79.     elseif (name=="Width") then
  80.       self.width = i
  81.     end
  82.     return 2
  83.   elseif a==3 then
  84.     self:discardBytes(h, 4, spinner)
  85.     return 4
  86.   elseif a==4 then
  87.     self:discardBytes(h,8, spinner)
  88.     return 8
  89.   elseif a==5 then
  90.     self:discardBytes(h,4, spinner)
  91.     return 4
  92.   elseif a==6 then
  93.     self:discardBytes(h,8, spinner)
  94.   elseif a==7 then
  95.     local i1 = h:readbyte(h)
  96.     local i2 = h:readbyte(h)
  97.     local i3 = h:readbyte(h)
  98.     local i4 = h:readbyte(h)
  99.     local i = bit.blshift(i1, 24) + bit.blshift(i2, 16) + bit.blshift(i3, 8) + i4
  100.  
  101.     if not self.length or not self.width then
  102.  
  103.       self:discardBytes(h,i, spinner)
  104.       self.twopass = true
  105.  
  106.     elseif name == "Blocks" then
  107.       for i = 1, i do
  108.         local id = h:readbyte(h)
  109.         self:assignCoord(i, id)
  110.         if (i % 1000) == 0 then
  111.           spinner:spin()
  112.         end
  113.       end
  114.     elseif name == "Data" then
  115.       for i = 1, i do
  116.         local dmg = h:readbyte(h)
  117.         if dmg > 0 then
  118.           self.damages[i] = dmg
  119.         end
  120.         if (i % 1000) == 0 then
  121.           spinner:spin()
  122.         end
  123.       end
  124.     else
  125.       self:discardBytes(h,i, spinner)
  126.     end
  127.   elseif a==8 then
  128.     local i1 = h:readbyte(h)
  129.     local i2 = h:readbyte(h)
  130.     local i = i1*256 + i2
  131.     self:discardBytes(h,i, spinner)
  132.   elseif a==9 then
  133.     local type = h:readbyte(h)
  134.     local i1 = h:readbyte(h)
  135.     local i2 = h:readbyte(h)
  136.     local i3 = h:readbyte(h)
  137.     local i4 = h:readbyte(h)
  138.     local i = bit.blshift(i1, 24) + bit.blshift(i2, 16) + bit.blshift(i3, 8) + i4
  139.  
  140.     for j=1,i do
  141.       self:parse(type, h, false, spinner)
  142.     end
  143.   elseif a > 11 then
  144.     error('invalid tag')
  145.   end
  146. end
  147. -- end http://www.computercraft.info/forums2/index.php?/topic/1949-turtle-schematic-file-builder/
  148.  
  149. function Schematic:copyBlocks(iblocks, oblocks, spinner)
  150.   Profile.start('copyBlocks')
  151.   for k,b in ipairs(iblocks) do
  152.     oblocks[k] = Util.shallowCopy(b)
  153.     if spinner then
  154.       if (k % 1000) == 0 then
  155.         spinner:spin()
  156.       end
  157.     end
  158.   end
  159.   Profile.stop('copyBlocks')
  160. end
  161.  
  162. function Schematic:reload()
  163.   self.placementChains = {}
  164.   self.blocks = { }
  165.   self:copyBlocks(self.originalBlocks, self.blocks)
  166.   --[[
  167.   self.planes = { }
  168.   for i = 0, self.height - 1 do
  169.     self.planes[i] = { }
  170.   end
  171.   for k,b in ipairs(self.blocks) do
  172.     if not self.planes[b.y].start then
  173.       self.planes[b.y].start = k
  174.     end
  175.   end
  176.   --]]
  177. end
  178.  
  179. function Schematic:getMagic(filename)
  180.   local h = fs.open(filename, "rb")
  181.  
  182.   if not h then
  183.     error('unable to open: ' .. filename)
  184.   end
  185.  
  186.   local magic = h.read() * 256 +  h.read()
  187.  
  188.   h.close()
  189.  
  190.   return magic
  191. end
  192.  
  193. function Schematic:isCompressed(filename)
  194.   return self:getMagic(filename) == gzipMagic
  195. end
  196.  
  197. function Schematic:checkFileType(filename)
  198.  
  199.   local magic = self:getMagic(filename)
  200.   if magic ~= schematicMagic then
  201.     error('Unknown file type')
  202.   end
  203. end
  204.  
  205. function Schematic:decompress(ifname, ofname, spinner)
  206.  
  207.   Profile.start('decompress')
  208.  
  209.   local ifh = fs.open(ifname, "rb")
  210.   if not ifh then
  211.     error('Unable to open ' .. ifname)
  212.   end
  213.  
  214.   local ofh = fs.open(ofname, "wb")
  215.   if not ofh then
  216.     error('Unable to open ' .. ofname)
  217.   end
  218.  
  219.   DEFLATE.gunzip({
  220.     input=function(...) spinner:spin() return ifh.read() end,
  221.     output=function(b) ofh.write(b) end,
  222.     disable_crc=true
  223.   })
  224.  
  225.   ifh.close()
  226.   ofh.close()
  227.  
  228.   spinner:stop()
  229.   Profile.stop('decompress')
  230. end
  231.  
  232. function Schematic:loadpass(filename, spinner)
  233.  
  234.   Profile.start('load')
  235.  
  236.   local h = fs.open(filename, "rb")
  237.  
  238.   if not h then
  239.     error('unable to open: ' .. filename)
  240.   end
  241.  
  242.   function h:readbyte()
  243.     return self.read()
  244.   end
  245.  
  246.   while true do
  247.     local a = h:readbyte()
  248.  
  249.     if not a then
  250.       break
  251.     end
  252.     self:parse(a, h, true, spinner)
  253.     if self.twopass and self.width and self.length then
  254.       break
  255.     end
  256.  
  257.     spinner:spin()
  258.   end
  259.  
  260.   h.close()
  261.   Profile.stop('load')
  262.  
  263.   self:assignDamages(spinner)
  264.   self.damages = nil
  265.  
  266.   self:copyBlocks(self.blocks, self.originalBlocks, spinner)
  267.   Profile.display()
  268.  
  269.   spinner:stop()
  270. end
  271.  
  272. function Schematic:load(filename)
  273.  
  274.   local cursorX, cursorY = term.getCursorPos()
  275.   local spinner = UI.Spinner({
  276.     x = UI.term.width,
  277.     y = cursorY - 1
  278.   })
  279.  
  280.   if self:isCompressed(filename) then
  281.     local originalFile = filename
  282.     filename = originalFile .. '.uncompressed'
  283.  
  284.     if not fs.exists(filename) then
  285.       print('Decompressing')
  286.       self:decompress(originalFile, filename, spinner)
  287.     end
  288.   end
  289.  
  290.   self.filename = string.match(filename, '([^/]+)$')
  291.   self:checkFileType(filename)
  292.  
  293.   --[[
  294.   local size = fs.getSize(filename)
  295.  
  296.   local buffer = {
  297.     h = h,
  298.     i = 1,
  299.     s = { },
  300.     l = size,
  301.   }
  302.  
  303.   for i = 1,size do
  304.     buffer.s[i] = h.read()
  305.   end
  306.  
  307.   function buffer:readbyte()
  308.     --return self.h.read()
  309.     local b = self.s[self.i]
  310.     self.i = self.i + 1
  311.     return b
  312.   end
  313.   ]]--
  314.  
  315.   print('Initial pass     ')
  316.   self:loadpass(filename, spinner)
  317.  
  318.   if self.twopass then
  319.     self.twopass = nil
  320.     self.blocks = { }
  321.     self.damages = { }
  322.     self.originalBlocks = { }
  323.     self.placementChains = { }
  324.     self.x, self.y, self.z = 0, 0, 0
  325.     self.height = 0
  326.     self.index = 1
  327.  
  328.     print('Second pass      ')
  329.     self:loadpass(filename, spinner)
  330.   end
  331. end
  332.  
  333. function Schematic:assignCoord(i, id)
  334.  
  335.   if id > 0 then
  336.     table.insert(self.blocks, {
  337.       id = id,
  338.       index = i,
  339.       x = self.x,
  340.       z = self.z,
  341.       y = self.y,
  342.     })
  343.   end
  344.  
  345.   self.x = self.x + 1
  346.   if self.x >= self.width then
  347.     self.x = 0
  348.     self.z = self.z + 1
  349.   end
  350.   if self.z >= self.length then
  351.     self.z = 0
  352.     self.y = self.y + 1
  353.   end
  354.   if self.y >= self.height then
  355.     self.height = self.y + 1
  356.   end
  357. end
  358.  
  359. function Schematic:assignDamages(spinner)
  360.   local i = 0
  361.  
  362.   Profile.start('assignDamages')
  363.   print('Assigning damages')
  364.  
  365.   for _,b in pairs(self.blocks) do
  366.     b.dmg = self.damages[b.index] or 0
  367.     i = i + 1
  368.     if (i % 1000) == 0 then
  369.       spinner:spin()
  370.     end
  371.   end
  372.   Profile.stop('assignDamages')
  373. end
  374.  
  375. function Schematic:findIndexAt(x, z, y)
  376.   if y < 0 then
  377.     return
  378.   end
  379.  
  380.   if not self.rowIndex then
  381.     for k,b in pairs(self.blocks) do
  382.       if b.x == x and b.z == z and b.y == y then
  383.         return k, b
  384.       end
  385.     end
  386.   else
  387.     local ri = self.rowIndex[y]
  388.     if ri then
  389.       for i = ri.s, ri.e do
  390.         local b = self.blocks[i]
  391.         if b.x == x and b.z == z and b.y == y then
  392.           return i, b
  393.         end
  394.       end
  395.     end
  396.   end
  397. end
  398.  
  399. function Schematic:findIndexOfBlock(b)
  400.   return self:findIndexAt(b.x, b.z, b.y)
  401. end
  402.  
  403. function Schematic:findBlockAtSide(b, side)
  404.   local hi = turtle.getHeadingInfo(side)
  405.   local index = self:findIndexAt(b.x + hi.xd, b.z + hi.zd, b.y + hi.yd)
  406.   if index then
  407.     return self.blocks[index]
  408.   end
  409. end
  410.  
  411. function Schematic:addPlacementChain(chain)
  412.   local t = { }
  413.   for _,v in ipairs(chain) do
  414.     local k = self:findIndexAt(v.x, v.z, v.y)
  415.     if k then
  416.       local b = self.blocks[k]
  417.       b.index = v.y * self.width * self.length + v.z * self.width + v.x + 1
  418.       table.insert(t, b)
  419.     end
  420.   end
  421.   if #t > 1 then
  422.     local keys = { }
  423.     for _,b in pairs(t) do
  424.       keys[b.index] = true
  425.     end
  426.     table.insert(self.placementChains, {
  427.       blocks = t,
  428.       keys = keys
  429.     })
  430.   end
  431. end
  432.  
  433. function Schematic:bestSide(b, ...)
  434.   local directions = { ... }
  435.   local blocks = { }
  436.  
  437.   for k,d in pairs(directions) do
  438.     local hi = turtle.getHeadingInfo(d)
  439.     local sb = self:findIndexAt(b.x - hi.xd, b.z - hi.zd, b.y)
  440.     if not sb then
  441.       b.heading = turtle.getHeadingInfo(d).heading
  442.       b.direction = d .. '-block'
  443.       return
  444.     end
  445.     blocks[k] = {
  446.       b = self.blocks[sb],
  447.       hi = hi,
  448.       d = d
  449.     }
  450.   end
  451.  
  452.   local bestBlock
  453.   for _,sb in ipairs(blocks) do
  454.     if not sb.b.direction then
  455.       bestBlock = sb
  456.       break
  457.     end
  458.   end
  459.  
  460.   if not bestBlock then
  461.     local sideDirections = {
  462.       [ 'east-block' ] = 'east',
  463.       [ 'south-block' ] = 'south',
  464.       [ 'west-block'  ] = 'west',
  465.       [ 'north-block'  ] = 'north'
  466.     }
  467.     for _,sb in ipairs(blocks) do
  468.       if not bestBlock then
  469.         bestBlock = sb
  470.       end
  471.       if not sideDirections[sb.b.direction] then
  472.         bestBlock = sb
  473.         break
  474.       end
  475.     end
  476.   end
  477.  
  478.   local hi = bestBlock.hi
  479.   b.heading = hi.heading     -- ?????????????????????????????????
  480.   b.direction = bestBlock.d .. '-block'
  481.   self:addPlacementChain({
  482.     { x = b.x,         z = b.z,         y = b.y },
  483.     { x = b.x - hi.xd, z = b.z - hi.zd, y = b.y }
  484.   })
  485. end
  486.  
  487. function Schematic:bestOfTwoSides(b, side1, side2)
  488.  
  489.   local sb
  490.   local fb = b  -- first block
  491.   local lb = b  -- last block
  492.   local od = b.direction -- original direction
  493.  
  494.   -- find the last block in the row with the same two-sided direction
  495.   while true do
  496.     sb = self:findBlockAtSide(lb, side2)
  497.     if not sb or sb.direction ~= b.direction then
  498.       break
  499.     end
  500.     lb = sb
  501.   end
  502.  
  503.   -- find the first block
  504.   while true do
  505.     sb = self:findBlockAtSide(fb, side1)
  506.     if not sb or sb.direction ~= b.direction then
  507.       break
  508.     end
  509.     fb = sb
  510.   end
  511.  
  512.   -- set the placement order to side1 -> side2
  513.   if fb ~= lb then -- only 1 block
  514.  
  515.     local pc = { }  -- placementChain
  516.     b = fb
  517.  
  518.     while true do
  519.  
  520.       table.insert(pc, { x = b.x,   z = b.z,   y = b.y })
  521.  
  522.       b.direction = side1 .. '-block'
  523.       b.heading = turtle.getHeadingInfo(side1).heading
  524.  
  525.       if b == lb then
  526.         break
  527.       end
  528.  
  529.       b = self:findBlockAtSide(b, side2)
  530.     end
  531.  
  532.     self:addPlacementChain(pc)
  533.   end
  534.  
  535.   -- can we place the first block from the side (instead of using piston) ?
  536.   sb = self:findBlockAtSide(fb, side1)
  537.   if not sb then
  538.     local ub = self:findBlockAtSide(fb, 'down')
  539.     if not ub then
  540.       fb.direction = side1 .. '-block'
  541.       fb.heading = turtle.getHeadingInfo(side1).heading
  542.     else
  543.       fb.direction = od
  544.     end
  545.   else  -- really should use placement chain
  546.     fb.direction = od
  547.   end
  548.  
  549.   -- can we place the last block from the side (instead of using piston) ?
  550.   sb = self:findBlockAtSide(lb, side2)
  551.   if not sb then
  552.     local ub = self:findBlockAtSide(lb, 'down')
  553.     if not ub then
  554.       lb.direction = side1 .. '-block'
  555.       lb.heading = turtle.getHeadingInfo(side1).heading
  556.     else
  557.       fb.direction = od
  558.     end
  559.   else
  560.     lb.direction = od
  561.   end
  562.  
  563. end
  564.  
  565. -- Determine the best way to place each block
  566. function Schematic:determineBlockPlacement(row)
  567.  
  568.   -- NOTE: blocks are evaluated top to bottom
  569.  
  570.   local spinner = UI.Spinner({
  571.     x = 1,
  572.     spinSymbols = { 'o.....', '.o....', '..o...', '...o..', '....o.', '.....o' }
  573.   })
  574.   local stairDownDirections = {
  575.     [ 'north-down' ] = 'north',
  576.     [ 'south-down' ] = 'south',
  577.     [ 'east-down'  ] = 'east',
  578.     [ 'west-down'  ] = 'west'
  579.   }
  580.   local stairUpDirections = {
  581.     [ 'east-up' ] = { 'east', 'east-block', 1, 0, 'west-block' },
  582.     [ 'west-up' ] = { 'west', 'west-block', -1, 0, 'east-block' },
  583.     [ 'north-up' ] = { 'north', 'north-block', 0, -1, 'south-block' },
  584.     [ 'south-up' ] = { 'south', 'south-block', 0, 1, 'north-block' }
  585.   }
  586.   local twoSideDirections = {
  587.     [ 'east-west-block'   ] = true,
  588.     [ 'north-south-block' ] = true,
  589.   }
  590.   local directions = {
  591.     [ 'north' ] = 'north',
  592.     [ 'south' ] = 'south',
  593.     [ 'east'  ] = 'east',
  594.     [ 'west'  ] = 'west',
  595.   }
  596.   local blockDirections = {
  597.     [ 'east-block' ] = 'east',
  598.     [ 'south-block' ] = 'south',
  599.     [ 'west-block'  ] = 'west',
  600.     [ 'north-block'  ] = 'north',
  601.   }
  602.   local doorDirections = {
  603.     [ 'east-door' ] = 'east',
  604.     [ 'south-door' ] = 'south',
  605.     [ 'west-door'  ] = 'west',
  606.     [ 'north-door'  ] = 'north',
  607.   }
  608.   local vineDirections = {
  609.     [ 'east-block-vine' ] = 'east-block',
  610.     [ 'south-block-vine' ] = 'south-block',
  611.     [ 'west-block-vine'  ] = 'west-block',
  612.     [ 'north-block-vine'  ] = 'north-block'
  613.   }
  614.  
  615.   local dirtyBlocks = {}
  616.   local dirtyBlocks2 = {}
  617.   self.rowIndex = { }
  618.   for k,b in ipairs(self.blocks) do
  619.     local ri = self.rowIndex[b.y]
  620.     if not ri then
  621.       self.rowIndex[b.y] = { s = k, e = k }
  622.     else
  623.       ri.e = k
  624.     end
  625.     if b.direction then
  626.       if twoSideDirections[b.direction] then
  627.         table.insert(dirtyBlocks2, b)
  628.       else
  629.         table.insert(dirtyBlocks, b)
  630.       end
  631.     end
  632.   end
  633.  
  634.   -- remove unnecessary blocks
  635.   for _,b in ipairs(dirtyBlocks) do
  636.     local d = b.direction
  637.  
  638.     if vineDirections[d] then
  639.       local _, aboveBlock = self:findIndexAt(b.x, b.z, b.y+1)
  640.       if aboveBlock and aboveBlock.id == b.id and aboveBlock.dmg == b.dmg and aboveBlock.direction == d then
  641.         -- only need to place top vine
  642.         b.id = 'minecraft:air'
  643.         b.dmg = 0
  644.         b.direction = nil
  645.       else
  646.         b.direction = vineDirections[d]
  647.       end
  648.     end
  649.   end
  650.  
  651.   Util.filterInplace(dirtyBlocks, function(b) return b.id ~= 'minecraft:air' end)
  652.  
  653.   -- iterate through the directional blocks setting the placement strategy
  654.   while #dirtyBlocks > 0 do
  655.     local b = table.remove(dirtyBlocks)
  656.     local d = b.direction
  657.  
  658.     spinner:spin(#dirtyBlocks + #dirtyBlocks2 .. ' blocks remaining ')
  659.  
  660.     if directions[d] then
  661.       b.heading = turtle.getHeadingInfo(directions[d]).heading
  662.  
  663.     elseif b.direction == 'top' then
  664.       -- slab occupying top of voxel
  665.       -- can be placed from the top if there is no block below
  666.       local belowBlock = self:findIndexAt(b.x, b.z, b.y-1)
  667.       if not belowBlock then
  668.         b.direction = nil
  669.       end
  670.     end
  671.  
  672.     if doorDirections[d] then
  673.  
  674.       -- remove bottom of door
  675.       --local index = self:findIndexOf(b)
  676.       --table.remove(self.blocks, index)
  677.  
  678.       -- find the door top, replace with door bottom
  679.       local doorTop = self:findIndexAt(b.x, b.z, b.y+1)
  680.       if doorTop then
  681.         self.blocks[doorTop].direction = d
  682.         self.blocks[doorTop].id = b.id
  683.         self.blocks[doorTop].dmg = b.dmg
  684.         self.blocks[doorTop].heading = turtle.getHeadingInfo(doorDirections[d]).heading
  685.  
  686.         b.id = 'minecraft:air'
  687.         b.dmg = 0
  688.         b = self.blocks[doorTop]
  689.       end
  690.  
  691.       if d == 'north-door' then
  692.         self:addPlacementChain({
  693.           { x = b.x, z = b.z, y = b.y },
  694.           { x = b.x, z = b.z+1, y = b.y-1 },
  695.           { x = b.x, z = b.z+1, y = b.y },
  696.         })
  697.       elseif d == 'south-door' then
  698.         self:addPlacementChain({
  699.           { x = b.x, z = b.z, y = b.y },
  700.           { x = b.x, z = b.z-1, y = b.y-1 },
  701.           { x = b.x, z = b.z-1, y = b.y },
  702.         })
  703.       elseif d == 'west-door' then
  704.         self:addPlacementChain({
  705.           { x = b.x,   z = b.z, y = b.y },
  706.           { x = b.x+1, z = b.z, y = b.y-1 },
  707.           { x = b.x+1, z = b.z, y = b.y },
  708.         })
  709.       elseif d == 'east-door' then
  710.         self:addPlacementChain({
  711.           { x = b.x,   z = b.z, y = b.y },
  712.           { x = b.x-1, z = b.z, y = b.y-1 },
  713.           { x = b.x-1, z = b.z, y = b.y },
  714.         })
  715.       end
  716.     end
  717.  
  718.     if stairDownDirections[d] then
  719.       if not self:findIndexAt(b.x, b.z, b.y-1) then
  720.         b.direction = stairDownDirections[b.direction]
  721.         b.heading = turtle.getHeadingInfo(b.direction).heading
  722.       else
  723.         b.heading = turtle.getHeadingInfo(stairDownDirections[b.direction]).heading
  724.       end
  725.     end
  726.  
  727.     if d == 'bottom' then
  728.       -- slab occupying bottom of voxel
  729.       -- can be placed from top if a block is below
  730.       -- otherwise, needs to be placed from side
  731.  
  732.       -- except... if the block below is a slab :(
  733.         local _,db = self:findIndexAt(b.x, b.z, b.y-1)
  734.         if not db then
  735.           -- no block below, place from side
  736.           self:bestSide(b, 'east', 'south', 'west', 'north')
  737.         elseif not db.direction or db.direction ~= 'bottom' then
  738.           -- not a slab below, ok to place from above
  739.           b.direction = nil
  740.         end
  741.         -- otherwise, builder will piston it in from above
  742.  
  743.     elseif stairUpDirections[d] then
  744.       -- a directional stair
  745.       -- turtle can place correctly from above if there is a block below
  746.       -- otherwise, the turtle must place the block from the same plane
  747.       -- against another block
  748.       -- if no block to place against (from side) then the turtle must place from
  749.       -- the other side
  750.       --
  751.       -- Stair bug in 1.7 - placing a stair southward doesn't respect the turtle's direction
  752.       -- all other directions are fine
  753.       -- any stair southwards that can't be placed against another block must be pistoned
  754.       local sd = stairUpDirections[d]
  755.  
  756.       if self:findIndexAt(b.x, b.z, b.y-1) then
  757.         -- there's a block below
  758.         b.direction = sd[1]
  759.         b.heading = turtle.getHeadingInfo(b.direction).heading
  760.       else
  761.         local _,pb = self:findIndexAt(b.x + sd[3], b.z + sd[4], b.y)
  762.         if pb and pb.direction ~= sd[5] then
  763.           -- place stair against another block (that's not relying on this block to be down first)
  764.           d = sd[2]  -- fall through to the blockDirections code below
  765.           b.direction = sd[2]
  766.         else
  767.           b.heading = (turtle.getHeadingInfo(sd[1]).heading + 2) % 4
  768.         end
  769.       end
  770.     end
  771.  
  772.     if blockDirections[d] then
  773.       -- placing a block from the side
  774.       local hi = turtle.getHeadingInfo(blockDirections[d])
  775.       b.heading = hi.heading
  776.       self:addPlacementChain({
  777.         { x = b.x + hi.xd, z = b.z + hi.zd, y = b.y },  -- block we are placing against
  778.         { x = b.x,         z = b.z,         y = b.y },  -- the block (or torch, etc)
  779.         { x = b.x - hi.xd, z = b.z - hi.zd, y = b.y }   -- room for the turtle
  780.       })
  781.     end
  782.   end
  783.  
  784.   -- pass 2
  785.   while #dirtyBlocks2 > 0 do
  786.     local b = table.remove(dirtyBlocks2)
  787.     local d = b.direction
  788.     if d == 'east-west-block' then
  789.       self:bestOfTwoSides(b, 'east', 'west')
  790.     elseif d == 'north-south-block' then
  791.       self:bestOfTwoSides(b, 'north', 'south')
  792.     end
  793.   end
  794.  
  795.   spinner:stop()
  796.   term.clearLine()
  797. end
  798.  
  799. -- set the order for block dependencies
  800. function Schematic:setPlacementOrder()
  801.   local cursorX, cursorY = term.getCursorPos()
  802.   local spinner = UI.Spinner({
  803.     x = 1
  804.   })
  805.  
  806.   Profile.start('overlapping')
  807.   -- optimize for overlapping check
  808.   for _,chain in pairs(self.placementChains) do
  809.     for index,_ in pairs(chain.keys) do
  810.       if not chain.startRow or (index < chain.startRow) then
  811.         chain.startRow = index
  812.       end
  813.       if not chain.endRow or (index > chain.endRow) then
  814.         chain.endRow = index
  815.       end
  816.     end
  817.   end
  818.  
  819.   local function groupOverlappingChains(t, groupedChain, chain, spinner)
  820.     local found = true
  821.  
  822.     local function overlaps(chain1, chain2)
  823.       if chain1.startRow > chain2.endRow or
  824.          chain2.startRow > chain1.endRow then
  825.         return false
  826.       end
  827.       for k,_ in pairs(chain1.keys) do
  828.         if chain2.keys[k] then
  829.           return true
  830.         end
  831.       end
  832.     end
  833.  
  834.     while found do
  835.       found = false
  836.       for k, v in pairs(t) do
  837.         local o = overlaps(chain, v)
  838.         if o then
  839.           table.remove(t, k)
  840.           table.insert(groupedChain, v)
  841.           groupOverlappingChains(t, groupedChain, v, spinner)
  842.           spinner:spin()
  843.           found = true
  844.           break
  845.         end
  846.       end
  847.     end
  848.   end
  849.  
  850.   -- group together any placement chains with overlapping blocks
  851.   local groupedChains = {}
  852.   while #self.placementChains > 0 do
  853.     local groupedChain = {}
  854.     local chain = table.remove(self.placementChains)
  855.     table.insert(groupedChain, chain)
  856.     table.insert(groupedChains, groupedChain)
  857.     groupOverlappingChains(self.placementChains, groupedChain, chain, spinner)
  858.     spinner:spin('chains: ' .. #groupedChains .. '  ' .. #self.placementChains .. '  ')
  859.   end
  860.   Profile.stop('overlapping')
  861.  
  862.   --Logger.log('schematic', 'groups: ' .. #groupedChains)
  863.   --Logger.setFileLogging('chains')
  864.  
  865.   local function mergeChains(chains)
  866.  
  867.     --[[
  868.     Logger.debug('---------------')
  869.     Logger.log('schematic', 'mergeChains: ' .. #chains)
  870.     for _,chain in ipairs(chains) do
  871.       Logger.log('schematic', chain)
  872.       for _,e in ipairs(chain) do
  873.         Logger.log('schematic', string.format('%d:%d:%d %s %d:%d',
  874.           e.block.x, e.block.z, e.block.y, tostring(e.block.direction), e.block.id, e.block.dmg))
  875.       end
  876.     end
  877.     ]]--
  878.  
  879.     local masterChain = table.remove(chains)
  880.  
  881.     --[[ it's something like this:
  882.  
  883.        A chain     B chain           result
  884.           1                            1
  885.           2 --------  2                2
  886.                       3                3
  887.           4                            4
  888.                       5                5
  889.           6 --------  6                6
  890.                       7                7
  891.     --]]
  892.     local function splice(chain1, chain2)
  893.       for k,v in ipairs(chain1) do
  894.         for k2,v2 in ipairs(chain2) do
  895.           if v == v2 then
  896.             local index = k
  897.             local dupe
  898.             for i = k2-1, 1, -1 do
  899.               dupe = false
  900.               -- traverse back through the first chain aligning on matches
  901.               for j = index-1, 1, -1 do
  902.                 if chain1[j] == chain2[i] then
  903.                  index = j
  904.                  dupe = true
  905.                  break
  906.                 end
  907.               end
  908.               if not dupe then
  909.                 table.insert(chain1, index, chain2[i])
  910.               end
  911.             end
  912.             index = k+1
  913.             for i = k2+1, #chain2, 1 do
  914.               dupe = false
  915.               for j = index, #chain1, 1 do
  916.                 if chain1[j] == chain2[i] then
  917.                  index = j
  918.                  dupe = true
  919.                  break
  920.                 end
  921.               end
  922.               if not dupe then
  923.                 table.insert(chain1, index, chain2[i])
  924.               end
  925.               index = index + 1
  926.             end
  927.             return true
  928.           end
  929.         end
  930.       end
  931.     end
  932.  
  933.     while #chains > 0 do
  934.       for k,chain in pairs(chains) do
  935.         if splice(masterChain.blocks, chain.blocks) then
  936.           table.remove(chains, k)
  937.           break
  938.         end
  939.       end
  940.     end
  941.  
  942.     --[[
  943.     Logger.log('schematic', 'master chain: ')
  944.     Logger.log('schematic', masterChain)
  945.     Logger.log('schematic', '---------------')
  946.     for _,e in ipairs(masterChain.blocks) do
  947.       Logger.log('schematic', string.format('%d:%d:%d %s %s:%d',
  948.         e.x, e.z, e.y, tostring(e.direction), e.id, e.dmg))
  949.     end
  950.     --]]
  951.  
  952.     return masterChain
  953.   end
  954.  
  955.   -- combine the individual overlapping placement chains into 1 long master chain
  956.   Profile.start('masterchains')
  957.   local masterChains = {}
  958.   for _,group in pairs(groupedChains) do
  959.     spinner:spin('chains: ' .. #masterChains)
  960.     table.insert(masterChains, mergeChains(group))
  961.   end
  962.   Profile.stop('masterchains')
  963.  
  964.   local function removeDuplicates(chain)
  965.     for k,v in ipairs(chain) do
  966.       for i = #chain, k+1, -1 do
  967.         if v == chain[i] then
  968. v.info = 'Unplaceable'
  969.           table.remove(chain, i)
  970.         end
  971.       end
  972.     end
  973.   end
  974.  
  975.   -- any chains with duplicates cannot be placed correctly
  976.   -- there are some cases where a turtle cannot place blocks the same as a player
  977.   Profile.start('duplicates')
  978.   for _,chain in pairs(masterChains) do
  979.     removeDuplicates(chain.blocks)
  980.     spinner:spin('chains: ' .. #masterChains)
  981.  
  982.     --[[
  983.     Logger.log('schematic', "MASTER CHAIN")
  984.     for _,e in ipairs(chain) do
  985.       Logger.log('schematic', string.format('%d:%d:%d %s %d:%d',
  986.         e.block.x, e.block.z, e.block.y, tostring(e.block.direction), e.block.id, e.block.dmg))
  987.     end
  988.     --]]
  989.  
  990.   end
  991.   Profile.stop('duplicates')
  992.   term.clearLine()
  993.  
  994.   -- adjust row indices as blocks are being moved
  995.   Profile.start('reordering')
  996.   for k,chain in pairs(masterChains) do
  997.     spinner:spin('chains: ' .. #masterChains - k)
  998.  
  999.     local startBlock = table.remove(chain.blocks, 1)
  1000.     startBlock.movedBlocks = chain.blocks
  1001.  
  1002.     for _,b in pairs(chain.blocks) do
  1003.       b.moved = true
  1004.     end
  1005.   end
  1006.  
  1007.   local t = { }
  1008.   for k,b in ipairs(self.blocks) do
  1009.     spinner:spin('blocks: ' .. #self.blocks - k)
  1010.     if not b.moved then
  1011.       table.insert(t, b)
  1012.     end
  1013.     if b.movedBlocks then
  1014.       for _,mb in ipairs(b.movedBlocks) do
  1015.         table.insert(t, mb)
  1016.       end
  1017.     end
  1018.   end
  1019.  
  1020.   self.blocks = t
  1021.  
  1022.   Profile.stop('reordering')
  1023.  
  1024.   --Logger.setWirelessLogging()
  1025.  
  1026.   term.clearLine()
  1027.   spinner:stop()
  1028. end
  1029.  
  1030. function Schematic:optimizeRoute()
  1031.  
  1032.   local function getNearestNeighbor(p, pt, maxDistance)
  1033.     local key, block, heading
  1034.     local moves = maxDistance
  1035.  
  1036.     local function getMoves(b, k)
  1037.       local distance = math.abs(pt.x - b.x) + math.abs(pt.z - b.z)
  1038.  
  1039.       if distance < moves then
  1040.         -- this operation is expensive - only run if distance is close
  1041.         local c, h = Point.calculateMoves(pt, b, distance)
  1042.         if c < moves then
  1043.           block = b
  1044.           key = k
  1045.           moves = c
  1046.           heading = h
  1047.         end
  1048.       end
  1049.     end
  1050.  
  1051.     local mid = pt.index
  1052.     local forward = mid + 1
  1053.     local backward = mid - 1
  1054.     while forward <= #p or backward > 0 do
  1055.       if forward <= #p then
  1056.         local b = p[forward]
  1057.         if not b.u then
  1058.           getMoves(b, forward)
  1059.           if moves <= 1 then
  1060.             break
  1061.           end
  1062.           if moves < maxDistance and math.abs(b.z - pt.z) > moves and pt.index > 0 then
  1063.             forward = #p
  1064.           end
  1065.         end
  1066.         forward = forward + 1
  1067.       end
  1068.       if backward > 0 then
  1069.         local b = p[backward]
  1070.         if not b.u then
  1071.           getMoves(b, backward)
  1072.           if moves <= 1 then
  1073.             break
  1074.           end
  1075.           if moves < maxDistance and math.abs(pt.z - b.z) > moves then
  1076.             backward = 0
  1077.           end
  1078.         end
  1079.         backward = backward - 1
  1080.       end
  1081.     end
  1082.     pt.x = block.x
  1083.     pt.z = block.z
  1084.     pt.y = block.y
  1085.     pt.heading = heading
  1086.     pt.index = key
  1087.     block.u = true
  1088.     return block
  1089.   end
  1090.  
  1091.   local pt = { x = -1, z = -1, y = 0, heading = 0 }
  1092.   local t = {}
  1093.   local cursorX, cursorY = term.getCursorPos()
  1094.   local spinner = UI.Spinner({
  1095.     x = 0,
  1096.     y = cursorY
  1097.   })
  1098.  
  1099.   local function extractPlane(y)
  1100.     local t = {}
  1101.     for _, b in pairs(self.blocks) do
  1102.       if b.y == y then
  1103.         table.insert(t, b)
  1104.       end
  1105.     end
  1106.     return t
  1107.   end
  1108.  
  1109.   local maxDistance = self.width*self.length
  1110.   Profile.start('optimize')
  1111.   for y = 0, self.height do
  1112.     local percent = math.floor(y * 100 / self.height) .. '%'
  1113.     spinner:spin(percent)
  1114.     local plane = extractPlane(y)
  1115.     pt.index = 0
  1116.     for i = 1, #plane do
  1117.       local b = getNearestNeighbor(plane, pt, maxDistance)
  1118.       table.insert(t, b)
  1119.       spinner:spin(percent .. ' ' .. #plane - i .. '    ')
  1120.     end
  1121.   end
  1122.   Profile.stop('optimize')
  1123.   self.blocks = t
  1124.   spinner:stop('    ')
  1125. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement