Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --[[
- Loading and manipulating a schematic
- --]]
- local DEFLATE = Util.loadApi('deflatelua.api')
- local schematicMagic = 0x0a00
- local gzipMagic = 0x1f8b
- Schematic = class.class()
- function Schematic:init(args)
- self.blocks = { }
- self.damages = { }
- self.originalBlocks = { }
- self.placementChains = { }
- self.x, self.y, self.z = 0, 0, 0
- self.height = 0
- self.index = 1
- end
- --[[
- Credit to Orwell for the schematic file reader code
- http://www.computercraft.info/forums2/index.php?/topic/1949-turtle-schematic-file-builder/
- Some parts of the file reader code was modified from the original
- --]]
- function Schematic:discardBytes(h, n, spinner)
- for i = 1,n do
- h:readbyte()
- if (i % 1000) == 0 then
- spinner:spin()
- end
- end
- end
- function Schematic:readname(h)
- local n1 = h:readbyte(h)
- local n2 = h:readbyte(h)
- if(n1 == nil or n2 == nil) then
- return ""
- end
- local n = n1*256 + n2
- local str = ""
- for i=1,n do
- local c = h:readbyte(h)
- if c == nil then
- return
- end
- str = str .. string.char(c)
- end
- return str
- end
- function Schematic:parse(a, h, containsName, spinner)
- if a==0 then
- return
- end
- local name
- if containsName then
- name = self:readname(h)
- end
- if a==1 then
- self:discardBytes(h, 1, spinner)
- elseif a==2 then
- local i1 = h:readbyte(h)
- local i2 = h:readbyte(h)
- local i = i1*256 + i2
- if(name=="Height") then
- --self.height = i
- elseif (name=="Length") then
- self.length = i
- elseif (name=="Width") then
- self.width = i
- end
- return 2
- elseif a==3 then
- self:discardBytes(h, 4, spinner)
- return 4
- elseif a==4 then
- self:discardBytes(h,8, spinner)
- return 8
- elseif a==5 then
- self:discardBytes(h,4, spinner)
- return 4
- elseif a==6 then
- self:discardBytes(h,8, spinner)
- elseif a==7 then
- local i1 = h:readbyte(h)
- local i2 = h:readbyte(h)
- local i3 = h:readbyte(h)
- local i4 = h:readbyte(h)
- local i = bit.blshift(i1, 24) + bit.blshift(i2, 16) + bit.blshift(i3, 8) + i4
- if not self.length or not self.width then
- self:discardBytes(h,i, spinner)
- self.twopass = true
- elseif name == "Blocks" then
- for i = 1, i do
- local id = h:readbyte(h)
- self:assignCoord(i, id)
- if (i % 1000) == 0 then
- spinner:spin()
- end
- end
- elseif name == "Data" then
- for i = 1, i do
- local dmg = h:readbyte(h)
- if dmg > 0 then
- self.damages[i] = dmg
- end
- if (i % 1000) == 0 then
- spinner:spin()
- end
- end
- else
- self:discardBytes(h,i, spinner)
- end
- elseif a==8 then
- local i1 = h:readbyte(h)
- local i2 = h:readbyte(h)
- local i = i1*256 + i2
- self:discardBytes(h,i, spinner)
- elseif a==9 then
- local type = h:readbyte(h)
- local i1 = h:readbyte(h)
- local i2 = h:readbyte(h)
- local i3 = h:readbyte(h)
- local i4 = h:readbyte(h)
- local i = bit.blshift(i1, 24) + bit.blshift(i2, 16) + bit.blshift(i3, 8) + i4
- for j=1,i do
- self:parse(type, h, false, spinner)
- end
- elseif a > 11 then
- error('invalid tag')
- end
- end
- -- end http://www.computercraft.info/forums2/index.php?/topic/1949-turtle-schematic-file-builder/
- function Schematic:copyBlocks(iblocks, oblocks, spinner)
- Profile.start('copyBlocks')
- for k,b in ipairs(iblocks) do
- oblocks[k] = Util.shallowCopy(b)
- if spinner then
- if (k % 1000) == 0 then
- spinner:spin()
- end
- end
- end
- Profile.stop('copyBlocks')
- end
- function Schematic:reload()
- self.placementChains = {}
- self.blocks = { }
- self:copyBlocks(self.originalBlocks, self.blocks)
- --[[
- self.planes = { }
- for i = 0, self.height - 1 do
- self.planes[i] = { }
- end
- for k,b in ipairs(self.blocks) do
- if not self.planes[b.y].start then
- self.planes[b.y].start = k
- end
- end
- --]]
- end
- function Schematic:getMagic(filename)
- local h = fs.open(filename, "rb")
- if not h then
- error('unable to open: ' .. filename)
- end
- local magic = h.read() * 256 + h.read()
- h.close()
- return magic
- end
- function Schematic:isCompressed(filename)
- return self:getMagic(filename) == gzipMagic
- end
- function Schematic:checkFileType(filename)
- local magic = self:getMagic(filename)
- if magic ~= schematicMagic then
- error('Unknown file type')
- end
- end
- function Schematic:decompress(ifname, ofname, spinner)
- Profile.start('decompress')
- local ifh = fs.open(ifname, "rb")
- if not ifh then
- error('Unable to open ' .. ifname)
- end
- local ofh = fs.open(ofname, "wb")
- if not ofh then
- error('Unable to open ' .. ofname)
- end
- DEFLATE.gunzip({
- input=function(...) spinner:spin() return ifh.read() end,
- output=function(b) ofh.write(b) end,
- disable_crc=true
- })
- ifh.close()
- ofh.close()
- spinner:stop()
- Profile.stop('decompress')
- end
- function Schematic:loadpass(filename, spinner)
- Profile.start('load')
- local h = fs.open(filename, "rb")
- if not h then
- error('unable to open: ' .. filename)
- end
- function h:readbyte()
- return self.read()
- end
- while true do
- local a = h:readbyte()
- if not a then
- break
- end
- self:parse(a, h, true, spinner)
- if self.twopass and self.width and self.length then
- break
- end
- spinner:spin()
- end
- h.close()
- Profile.stop('load')
- self:assignDamages(spinner)
- self.damages = nil
- self:copyBlocks(self.blocks, self.originalBlocks, spinner)
- Profile.display()
- spinner:stop()
- end
- function Schematic:load(filename)
- local cursorX, cursorY = term.getCursorPos()
- local spinner = UI.Spinner({
- x = UI.term.width,
- y = cursorY - 1
- })
- if self:isCompressed(filename) then
- local originalFile = filename
- filename = originalFile .. '.uncompressed'
- if not fs.exists(filename) then
- print('Decompressing')
- self:decompress(originalFile, filename, spinner)
- end
- end
- self.filename = string.match(filename, '([^/]+)$')
- self:checkFileType(filename)
- --[[
- local size = fs.getSize(filename)
- local buffer = {
- h = h,
- i = 1,
- s = { },
- l = size,
- }
- for i = 1,size do
- buffer.s[i] = h.read()
- end
- function buffer:readbyte()
- --return self.h.read()
- local b = self.s[self.i]
- self.i = self.i + 1
- return b
- end
- ]]--
- print('Initial pass ')
- self:loadpass(filename, spinner)
- if self.twopass then
- self.twopass = nil
- self.blocks = { }
- self.damages = { }
- self.originalBlocks = { }
- self.placementChains = { }
- self.x, self.y, self.z = 0, 0, 0
- self.height = 0
- self.index = 1
- print('Second pass ')
- self:loadpass(filename, spinner)
- end
- end
- function Schematic:assignCoord(i, id)
- if id > 0 then
- table.insert(self.blocks, {
- id = id,
- index = i,
- x = self.x,
- z = self.z,
- y = self.y,
- })
- end
- self.x = self.x + 1
- if self.x >= self.width then
- self.x = 0
- self.z = self.z + 1
- end
- if self.z >= self.length then
- self.z = 0
- self.y = self.y + 1
- end
- if self.y >= self.height then
- self.height = self.y + 1
- end
- end
- function Schematic:assignDamages(spinner)
- local i = 0
- Profile.start('assignDamages')
- print('Assigning damages')
- for _,b in pairs(self.blocks) do
- b.dmg = self.damages[b.index] or 0
- i = i + 1
- if (i % 1000) == 0 then
- spinner:spin()
- end
- end
- Profile.stop('assignDamages')
- end
- function Schematic:findIndexAt(x, z, y)
- if y < 0 then
- return
- end
- if not self.rowIndex then
- for k,b in pairs(self.blocks) do
- if b.x == x and b.z == z and b.y == y then
- return k, b
- end
- end
- else
- local ri = self.rowIndex[y]
- if ri then
- for i = ri.s, ri.e do
- local b = self.blocks[i]
- if b.x == x and b.z == z and b.y == y then
- return i, b
- end
- end
- end
- end
- end
- function Schematic:findIndexOfBlock(b)
- return self:findIndexAt(b.x, b.z, b.y)
- end
- function Schematic:findBlockAtSide(b, side)
- local hi = turtle.getHeadingInfo(side)
- local index = self:findIndexAt(b.x + hi.xd, b.z + hi.zd, b.y + hi.yd)
- if index then
- return self.blocks[index]
- end
- end
- function Schematic:addPlacementChain(chain)
- local t = { }
- for _,v in ipairs(chain) do
- local k = self:findIndexAt(v.x, v.z, v.y)
- if k then
- local b = self.blocks[k]
- b.index = v.y * self.width * self.length + v.z * self.width + v.x + 1
- table.insert(t, b)
- end
- end
- if #t > 1 then
- local keys = { }
- for _,b in pairs(t) do
- keys[b.index] = true
- end
- table.insert(self.placementChains, {
- blocks = t,
- keys = keys
- })
- end
- end
- function Schematic:bestSide(b, ...)
- local directions = { ... }
- local blocks = { }
- for k,d in pairs(directions) do
- local hi = turtle.getHeadingInfo(d)
- local sb = self:findIndexAt(b.x - hi.xd, b.z - hi.zd, b.y)
- if not sb then
- b.heading = turtle.getHeadingInfo(d).heading
- b.direction = d .. '-block'
- return
- end
- blocks[k] = {
- b = self.blocks[sb],
- hi = hi,
- d = d
- }
- end
- local bestBlock
- for _,sb in ipairs(blocks) do
- if not sb.b.direction then
- bestBlock = sb
- break
- end
- end
- if not bestBlock then
- local sideDirections = {
- [ 'east-block' ] = 'east',
- [ 'south-block' ] = 'south',
- [ 'west-block' ] = 'west',
- [ 'north-block' ] = 'north'
- }
- for _,sb in ipairs(blocks) do
- if not bestBlock then
- bestBlock = sb
- end
- if not sideDirections[sb.b.direction] then
- bestBlock = sb
- break
- end
- end
- end
- local hi = bestBlock.hi
- b.heading = hi.heading -- ?????????????????????????????????
- b.direction = bestBlock.d .. '-block'
- self:addPlacementChain({
- { x = b.x, z = b.z, y = b.y },
- { x = b.x - hi.xd, z = b.z - hi.zd, y = b.y }
- })
- end
- function Schematic:bestOfTwoSides(b, side1, side2)
- local sb
- local fb = b -- first block
- local lb = b -- last block
- local od = b.direction -- original direction
- -- find the last block in the row with the same two-sided direction
- while true do
- sb = self:findBlockAtSide(lb, side2)
- if not sb or sb.direction ~= b.direction then
- break
- end
- lb = sb
- end
- -- find the first block
- while true do
- sb = self:findBlockAtSide(fb, side1)
- if not sb or sb.direction ~= b.direction then
- break
- end
- fb = sb
- end
- -- set the placement order to side1 -> side2
- if fb ~= lb then -- only 1 block
- local pc = { } -- placementChain
- b = fb
- while true do
- table.insert(pc, { x = b.x, z = b.z, y = b.y })
- b.direction = side1 .. '-block'
- b.heading = turtle.getHeadingInfo(side1).heading
- if b == lb then
- break
- end
- b = self:findBlockAtSide(b, side2)
- end
- self:addPlacementChain(pc)
- end
- -- can we place the first block from the side (instead of using piston) ?
- sb = self:findBlockAtSide(fb, side1)
- if not sb then
- local ub = self:findBlockAtSide(fb, 'down')
- if not ub then
- fb.direction = side1 .. '-block'
- fb.heading = turtle.getHeadingInfo(side1).heading
- else
- fb.direction = od
- end
- else -- really should use placement chain
- fb.direction = od
- end
- -- can we place the last block from the side (instead of using piston) ?
- sb = self:findBlockAtSide(lb, side2)
- if not sb then
- local ub = self:findBlockAtSide(lb, 'down')
- if not ub then
- lb.direction = side1 .. '-block'
- lb.heading = turtle.getHeadingInfo(side1).heading
- else
- fb.direction = od
- end
- else
- lb.direction = od
- end
- end
- -- Determine the best way to place each block
- function Schematic:determineBlockPlacement(row)
- -- NOTE: blocks are evaluated top to bottom
- local spinner = UI.Spinner({
- x = 1,
- spinSymbols = { 'o.....', '.o....', '..o...', '...o..', '....o.', '.....o' }
- })
- local stairDownDirections = {
- [ 'north-down' ] = 'north',
- [ 'south-down' ] = 'south',
- [ 'east-down' ] = 'east',
- [ 'west-down' ] = 'west'
- }
- local stairUpDirections = {
- [ 'east-up' ] = { 'east', 'east-block', 1, 0, 'west-block' },
- [ 'west-up' ] = { 'west', 'west-block', -1, 0, 'east-block' },
- [ 'north-up' ] = { 'north', 'north-block', 0, -1, 'south-block' },
- [ 'south-up' ] = { 'south', 'south-block', 0, 1, 'north-block' }
- }
- local twoSideDirections = {
- [ 'east-west-block' ] = true,
- [ 'north-south-block' ] = true,
- }
- local directions = {
- [ 'north' ] = 'north',
- [ 'south' ] = 'south',
- [ 'east' ] = 'east',
- [ 'west' ] = 'west',
- }
- local blockDirections = {
- [ 'east-block' ] = 'east',
- [ 'south-block' ] = 'south',
- [ 'west-block' ] = 'west',
- [ 'north-block' ] = 'north',
- }
- local doorDirections = {
- [ 'east-door' ] = 'east',
- [ 'south-door' ] = 'south',
- [ 'west-door' ] = 'west',
- [ 'north-door' ] = 'north',
- }
- local vineDirections = {
- [ 'east-block-vine' ] = 'east-block',
- [ 'south-block-vine' ] = 'south-block',
- [ 'west-block-vine' ] = 'west-block',
- [ 'north-block-vine' ] = 'north-block'
- }
- local dirtyBlocks = {}
- local dirtyBlocks2 = {}
- self.rowIndex = { }
- for k,b in ipairs(self.blocks) do
- local ri = self.rowIndex[b.y]
- if not ri then
- self.rowIndex[b.y] = { s = k, e = k }
- else
- ri.e = k
- end
- if b.direction then
- if twoSideDirections[b.direction] then
- table.insert(dirtyBlocks2, b)
- else
- table.insert(dirtyBlocks, b)
- end
- end
- end
- -- remove unnecessary blocks
- for _,b in ipairs(dirtyBlocks) do
- local d = b.direction
- if vineDirections[d] then
- local _, aboveBlock = self:findIndexAt(b.x, b.z, b.y+1)
- if aboveBlock and aboveBlock.id == b.id and aboveBlock.dmg == b.dmg and aboveBlock.direction == d then
- -- only need to place top vine
- b.id = 'minecraft:air'
- b.dmg = 0
- b.direction = nil
- else
- b.direction = vineDirections[d]
- end
- end
- end
- Util.filterInplace(dirtyBlocks, function(b) return b.id ~= 'minecraft:air' end)
- -- iterate through the directional blocks setting the placement strategy
- while #dirtyBlocks > 0 do
- local b = table.remove(dirtyBlocks)
- local d = b.direction
- spinner:spin(#dirtyBlocks + #dirtyBlocks2 .. ' blocks remaining ')
- if directions[d] then
- b.heading = turtle.getHeadingInfo(directions[d]).heading
- elseif b.direction == 'top' then
- -- slab occupying top of voxel
- -- can be placed from the top if there is no block below
- local belowBlock = self:findIndexAt(b.x, b.z, b.y-1)
- if not belowBlock then
- b.direction = nil
- end
- end
- if doorDirections[d] then
- -- remove bottom of door
- --local index = self:findIndexOf(b)
- --table.remove(self.blocks, index)
- -- find the door top, replace with door bottom
- local doorTop = self:findIndexAt(b.x, b.z, b.y+1)
- if doorTop then
- self.blocks[doorTop].direction = d
- self.blocks[doorTop].id = b.id
- self.blocks[doorTop].dmg = b.dmg
- self.blocks[doorTop].heading = turtle.getHeadingInfo(doorDirections[d]).heading
- b.id = 'minecraft:air'
- b.dmg = 0
- b = self.blocks[doorTop]
- end
- if d == 'north-door' then
- self:addPlacementChain({
- { x = b.x, z = b.z, y = b.y },
- { x = b.x, z = b.z+1, y = b.y-1 },
- { x = b.x, z = b.z+1, y = b.y },
- })
- elseif d == 'south-door' then
- self:addPlacementChain({
- { x = b.x, z = b.z, y = b.y },
- { x = b.x, z = b.z-1, y = b.y-1 },
- { x = b.x, z = b.z-1, y = b.y },
- })
- elseif d == 'west-door' then
- self:addPlacementChain({
- { x = b.x, z = b.z, y = b.y },
- { x = b.x+1, z = b.z, y = b.y-1 },
- { x = b.x+1, z = b.z, y = b.y },
- })
- elseif d == 'east-door' then
- self:addPlacementChain({
- { x = b.x, z = b.z, y = b.y },
- { x = b.x-1, z = b.z, y = b.y-1 },
- { x = b.x-1, z = b.z, y = b.y },
- })
- end
- end
- if stairDownDirections[d] then
- if not self:findIndexAt(b.x, b.z, b.y-1) then
- b.direction = stairDownDirections[b.direction]
- b.heading = turtle.getHeadingInfo(b.direction).heading
- else
- b.heading = turtle.getHeadingInfo(stairDownDirections[b.direction]).heading
- end
- end
- if d == 'bottom' then
- -- slab occupying bottom of voxel
- -- can be placed from top if a block is below
- -- otherwise, needs to be placed from side
- -- except... if the block below is a slab :(
- local _,db = self:findIndexAt(b.x, b.z, b.y-1)
- if not db then
- -- no block below, place from side
- self:bestSide(b, 'east', 'south', 'west', 'north')
- elseif not db.direction or db.direction ~= 'bottom' then
- -- not a slab below, ok to place from above
- b.direction = nil
- end
- -- otherwise, builder will piston it in from above
- elseif stairUpDirections[d] then
- -- a directional stair
- -- turtle can place correctly from above if there is a block below
- -- otherwise, the turtle must place the block from the same plane
- -- against another block
- -- if no block to place against (from side) then the turtle must place from
- -- the other side
- --
- -- Stair bug in 1.7 - placing a stair southward doesn't respect the turtle's direction
- -- all other directions are fine
- -- any stair southwards that can't be placed against another block must be pistoned
- local sd = stairUpDirections[d]
- if self:findIndexAt(b.x, b.z, b.y-1) then
- -- there's a block below
- b.direction = sd[1]
- b.heading = turtle.getHeadingInfo(b.direction).heading
- else
- local _,pb = self:findIndexAt(b.x + sd[3], b.z + sd[4], b.y)
- if pb and pb.direction ~= sd[5] then
- -- place stair against another block (that's not relying on this block to be down first)
- d = sd[2] -- fall through to the blockDirections code below
- b.direction = sd[2]
- else
- b.heading = (turtle.getHeadingInfo(sd[1]).heading + 2) % 4
- end
- end
- end
- if blockDirections[d] then
- -- placing a block from the side
- local hi = turtle.getHeadingInfo(blockDirections[d])
- b.heading = hi.heading
- self:addPlacementChain({
- { x = b.x + hi.xd, z = b.z + hi.zd, y = b.y }, -- block we are placing against
- { x = b.x, z = b.z, y = b.y }, -- the block (or torch, etc)
- { x = b.x - hi.xd, z = b.z - hi.zd, y = b.y } -- room for the turtle
- })
- end
- end
- -- pass 2
- while #dirtyBlocks2 > 0 do
- local b = table.remove(dirtyBlocks2)
- local d = b.direction
- if d == 'east-west-block' then
- self:bestOfTwoSides(b, 'east', 'west')
- elseif d == 'north-south-block' then
- self:bestOfTwoSides(b, 'north', 'south')
- end
- end
- spinner:stop()
- term.clearLine()
- end
- -- set the order for block dependencies
- function Schematic:setPlacementOrder()
- local cursorX, cursorY = term.getCursorPos()
- local spinner = UI.Spinner({
- x = 1
- })
- Profile.start('overlapping')
- -- optimize for overlapping check
- for _,chain in pairs(self.placementChains) do
- for index,_ in pairs(chain.keys) do
- if not chain.startRow or (index < chain.startRow) then
- chain.startRow = index
- end
- if not chain.endRow or (index > chain.endRow) then
- chain.endRow = index
- end
- end
- end
- local function groupOverlappingChains(t, groupedChain, chain, spinner)
- local found = true
- local function overlaps(chain1, chain2)
- if chain1.startRow > chain2.endRow or
- chain2.startRow > chain1.endRow then
- return false
- end
- for k,_ in pairs(chain1.keys) do
- if chain2.keys[k] then
- return true
- end
- end
- end
- while found do
- found = false
- for k, v in pairs(t) do
- local o = overlaps(chain, v)
- if o then
- table.remove(t, k)
- table.insert(groupedChain, v)
- groupOverlappingChains(t, groupedChain, v, spinner)
- spinner:spin()
- found = true
- break
- end
- end
- end
- end
- -- group together any placement chains with overlapping blocks
- local groupedChains = {}
- while #self.placementChains > 0 do
- local groupedChain = {}
- local chain = table.remove(self.placementChains)
- table.insert(groupedChain, chain)
- table.insert(groupedChains, groupedChain)
- groupOverlappingChains(self.placementChains, groupedChain, chain, spinner)
- spinner:spin('chains: ' .. #groupedChains .. ' ' .. #self.placementChains .. ' ')
- end
- Profile.stop('overlapping')
- --Logger.log('schematic', 'groups: ' .. #groupedChains)
- --Logger.setFileLogging('chains')
- local function mergeChains(chains)
- --[[
- Logger.debug('---------------')
- Logger.log('schematic', 'mergeChains: ' .. #chains)
- for _,chain in ipairs(chains) do
- Logger.log('schematic', chain)
- for _,e in ipairs(chain) do
- Logger.log('schematic', string.format('%d:%d:%d %s %d:%d',
- e.block.x, e.block.z, e.block.y, tostring(e.block.direction), e.block.id, e.block.dmg))
- end
- end
- ]]--
- local masterChain = table.remove(chains)
- --[[ it's something like this:
- A chain B chain result
- 1 1
- 2 -------- 2 2
- 3 3
- 4 4
- 5 5
- 6 -------- 6 6
- 7 7
- --]]
- local function splice(chain1, chain2)
- for k,v in ipairs(chain1) do
- for k2,v2 in ipairs(chain2) do
- if v == v2 then
- local index = k
- local dupe
- for i = k2-1, 1, -1 do
- dupe = false
- -- traverse back through the first chain aligning on matches
- for j = index-1, 1, -1 do
- if chain1[j] == chain2[i] then
- index = j
- dupe = true
- break
- end
- end
- if not dupe then
- table.insert(chain1, index, chain2[i])
- end
- end
- index = k+1
- for i = k2+1, #chain2, 1 do
- dupe = false
- for j = index, #chain1, 1 do
- if chain1[j] == chain2[i] then
- index = j
- dupe = true
- break
- end
- end
- if not dupe then
- table.insert(chain1, index, chain2[i])
- end
- index = index + 1
- end
- return true
- end
- end
- end
- end
- while #chains > 0 do
- for k,chain in pairs(chains) do
- if splice(masterChain.blocks, chain.blocks) then
- table.remove(chains, k)
- break
- end
- end
- end
- --[[
- Logger.log('schematic', 'master chain: ')
- Logger.log('schematic', masterChain)
- Logger.log('schematic', '---------------')
- for _,e in ipairs(masterChain.blocks) do
- Logger.log('schematic', string.format('%d:%d:%d %s %s:%d',
- e.x, e.z, e.y, tostring(e.direction), e.id, e.dmg))
- end
- --]]
- return masterChain
- end
- -- combine the individual overlapping placement chains into 1 long master chain
- Profile.start('masterchains')
- local masterChains = {}
- for _,group in pairs(groupedChains) do
- spinner:spin('chains: ' .. #masterChains)
- table.insert(masterChains, mergeChains(group))
- end
- Profile.stop('masterchains')
- local function removeDuplicates(chain)
- for k,v in ipairs(chain) do
- for i = #chain, k+1, -1 do
- if v == chain[i] then
- v.info = 'Unplaceable'
- table.remove(chain, i)
- end
- end
- end
- end
- -- any chains with duplicates cannot be placed correctly
- -- there are some cases where a turtle cannot place blocks the same as a player
- Profile.start('duplicates')
- for _,chain in pairs(masterChains) do
- removeDuplicates(chain.blocks)
- spinner:spin('chains: ' .. #masterChains)
- --[[
- Logger.log('schematic', "MASTER CHAIN")
- for _,e in ipairs(chain) do
- Logger.log('schematic', string.format('%d:%d:%d %s %d:%d',
- e.block.x, e.block.z, e.block.y, tostring(e.block.direction), e.block.id, e.block.dmg))
- end
- --]]
- end
- Profile.stop('duplicates')
- term.clearLine()
- -- adjust row indices as blocks are being moved
- Profile.start('reordering')
- for k,chain in pairs(masterChains) do
- spinner:spin('chains: ' .. #masterChains - k)
- local startBlock = table.remove(chain.blocks, 1)
- startBlock.movedBlocks = chain.blocks
- for _,b in pairs(chain.blocks) do
- b.moved = true
- end
- end
- local t = { }
- for k,b in ipairs(self.blocks) do
- spinner:spin('blocks: ' .. #self.blocks - k)
- if not b.moved then
- table.insert(t, b)
- end
- if b.movedBlocks then
- for _,mb in ipairs(b.movedBlocks) do
- table.insert(t, mb)
- end
- end
- end
- self.blocks = t
- Profile.stop('reordering')
- --Logger.setWirelessLogging()
- term.clearLine()
- spinner:stop()
- end
- function Schematic:optimizeRoute()
- local function getNearestNeighbor(p, pt, maxDistance)
- local key, block, heading
- local moves = maxDistance
- local function getMoves(b, k)
- local distance = math.abs(pt.x - b.x) + math.abs(pt.z - b.z)
- if distance < moves then
- -- this operation is expensive - only run if distance is close
- local c, h = Point.calculateMoves(pt, b, distance)
- if c < moves then
- block = b
- key = k
- moves = c
- heading = h
- end
- end
- end
- local mid = pt.index
- local forward = mid + 1
- local backward = mid - 1
- while forward <= #p or backward > 0 do
- if forward <= #p then
- local b = p[forward]
- if not b.u then
- getMoves(b, forward)
- if moves <= 1 then
- break
- end
- if moves < maxDistance and math.abs(b.z - pt.z) > moves and pt.index > 0 then
- forward = #p
- end
- end
- forward = forward + 1
- end
- if backward > 0 then
- local b = p[backward]
- if not b.u then
- getMoves(b, backward)
- if moves <= 1 then
- break
- end
- if moves < maxDistance and math.abs(pt.z - b.z) > moves then
- backward = 0
- end
- end
- backward = backward - 1
- end
- end
- pt.x = block.x
- pt.z = block.z
- pt.y = block.y
- pt.heading = heading
- pt.index = key
- block.u = true
- return block
- end
- local pt = { x = -1, z = -1, y = 0, heading = 0 }
- local t = {}
- local cursorX, cursorY = term.getCursorPos()
- local spinner = UI.Spinner({
- x = 0,
- y = cursorY
- })
- local function extractPlane(y)
- local t = {}
- for _, b in pairs(self.blocks) do
- if b.y == y then
- table.insert(t, b)
- end
- end
- return t
- end
- local maxDistance = self.width*self.length
- Profile.start('optimize')
- for y = 0, self.height do
- local percent = math.floor(y * 100 / self.height) .. '%'
- spinner:spin(percent)
- local plane = extractPlane(y)
- pt.index = 0
- for i = 1, #plane do
- local b = getNearestNeighbor(plane, pt, maxDistance)
- table.insert(t, b)
- spinner:spin(percent .. ' ' .. #plane - i .. ' ')
- end
- end
- Profile.stop('optimize')
- self.blocks = t
- spinner:stop(' ')
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement