opus/apps/builder.lua

2065 lines
50 KiB
Lua

if not turtle then
error('Must be run on a turtle')
end
require = requireInjector(getfenv(1))
local class = require('class')
local Event = require('event')
local Message = require('message')
local Logger = require('logger')
local UI = require('ui')
local Schematic = require('schematic')
local Profile = require('profile')
local TableDB = require('tableDB')
local ChestProvider = require('chestProvider')
local MEProvider = require('meProvider')
local Blocks = require('blocks')
local Point = require('point')
Logger.filter('modem_send', 'event', 'ui')
if device.wireless_modem then
Logger.setWirelessLogging()
else
Logger.setDaemonLogging()
end
local BUILDER_DIR = '.builder'
local schematic = Schematic()
local blocks = Blocks({ dir = BUILDER_DIR })
local Builder = {
version = '1.70',
ccVersion = nil,
slots = { },
index = 1,
mode = 'build',
fuelItem = { id = 'minecraft:coal', dmg = 0 },
resourceSlots = 15,
facing = 'south',
}
-- these wrenches work relative to the turtle
local GoodWrenches = {
[ 'appliedEnergistics2:item.ToolCertusQuartzWrench' ] = true,
[ 'appliedEnergistics2:item.ToolNetherQuartzWrench' ] = true,
[ 'EnderIO:itemYetaWrench' ] = true,
}
--[[
-- these wrenches work but take more hits to turn a piston
local BadButUsableWrenches = {
[ 'ThermalExpansion:wrench' ] = true,
[ 'MineFactoryReloaded:hammer' ] = true,
[ 'ImmersiveEngineering:tool' ] = true,
}
--]]
--[[-- SubDB --]]--
subDB = TableDB({
fileName = fs.combine(BUILDER_DIR, 'sub.db'),
tabledef = {
autokeys = false,
columns = {
{ name = 'Key', type = 'key', length = 8 },
{ name = 'id', type = 'number', length = 5 },
{ name = 'dmg', type = 'number', length = 2 },
{ name = 'refid', type = 'number', length = 5 },
{ name = 'refdmg', type = 'number', length = 2 },
}
}
})
function subDB:load()
if fs.exists(self.fileName) then
TableDB.load(self)
else
self:seedDB()
end
end
function subDB:seedDB()
self.data = {
[ "minecraft:redstone_wire:0" ] = {
sdmg = 0,
sid = "minecraft:redstone",
dmg = 0,
id = "minecraft:redstone_wire",
},
[ "minecraft:wall_sign:0" ] = {
sdmg = 0,
sid = "minecraft:sign",
dmg = 0,
id = "minecraft:wall_sign",
},
[ "minecraft:standing_sign:0" ] = {
sdmg = 0,
sid = "minecraft:sign",
dmg = 0,
id = "minecraft:standing_sign",
},
[ "minecraft:potatoes:0" ] = {
sdmg = 0,
sid = "minecraft:potato",
dmg = 0,
id = "minecraft:potatoes",
},
[ "minecraft:dirt:1" ] = {
sdmg = 0,
sid = "minecraft:dirt",
dmg = 1,
id = "minecraft:dirt",
},
[ "minecraft:unlit_redstone_torch:0" ] = {
sdmg = 0,
sid = "minecraft:redstone",
dmg = 0,
id = "minecraft:unlit_redstone_torch",
},
[ "minecraft:powered_repeater:0" ] = {
sdmg = 0,
sid = "minecraft:repeater",
dmg = 0,
id = "minecraft:powered_repeater",
},
[ "minecraft:unpowered_repeater:0" ] = {
sdmg = 0,
sid = "minecraft:repeater",
dmg = 0,
id = "minecraft:unpowered_repeater",
},
[ "minecraft:carrots:0" ] = {
sdmg = 0,
sid = "minecraft:carrot",
dmg = 0,
id = "minecraft:carrots",
},
[ "minecraft:cocoa:0" ] = {
sdmg = 3,
sid = "minecraft:dye",
dmg = 0,
id = "minecraft:cocoa",
},
[ "minecraft:unpowered_comparator:0" ] = {
sdmg = 0,
sid = "minecraft:comparator",
dmg = 0,
id = "minecraft:unpowered_comparator",
},
[ "minecraft:piston_head:0" ] = {
sdmg = 0,
sid = "minecraft:air",
dmg = 0,
id = "minecraft:piston_head",
},
[ "minecraft:double_wooden_slab:0" ] = {
sdmg = 0,
sid = "minecraft:planks",
dmg = 0,
id = "minecraft:double_wooden_slab",
},
[ "minecraft:double_wooden_slab:1" ] = {
sdmg = 1,
sid = "minecraft:planks",
dmg = 1,
id = "minecraft:double_wooden_slab",
},
[ "minecraft:double_wooden_slab:2" ] = {
sdmg = 2,
sid = "minecraft:planks",
dmg = 2,
id = "minecraft:double_wooden_slab",
},
[ "minecraft:double_wooden_slab:3" ] = {
sdmg = 3,
sid = "minecraft:planks",
dmg = 3,
id = "minecraft:double_wooden_slab",
},
[ "minecraft:double_wooden_slab:4" ] = {
sdmg = 4,
sid = "minecraft:planks",
dmg = 4,
id = "minecraft:double_wooden_slab",
},
[ "minecraft:lit_redstone_lamp:0" ] = {
sdmg = 0,
sid = "minecraft:redstone_lamp",
dmg = 0,
id = "minecraft:lit_redstone_lamp",
},
[ "minecraft:double_stone_slab:1" ] = {
sdmg = 0,
sid = "minecraft:sandstone",
dmg = 1,
id = "minecraft:double_stone_slab",
},
[ "minecraft:double_stone_slab:3" ] = {
sdmg = 0,
sid = "minecraft:cobblestone",
dmg = 3,
id = "minecraft:double_stone_slab",
},
[ "minecraft:double_stone_slab:5" ] = {
sdmg = 0,
sid = "minecraft:stonebrick",
dmg = 5,
id = "minecraft:double_stone_slab",
},
[ "minecraft:double_stone_slab:6" ] = {
sdmg = 0,
sid = "minecraft:nether_brick",
dmg = 6,
id = "minecraft:double_stone_slab",
},
[ "minecraft:double_stone_slab:7" ] = {
sdmg = 0,
sid = "minecraft:quartz_block",
dmg = 7,
id = "minecraft:double_stone_slab",
},
[ "minecraft:double_stone_slab:9" ] = {
sdmg = 2,
sid = "minecraft:sandstone",
dmg = 9,
id = "minecraft:double_stone_slab",
},
[ "minecraft:double_stone_slab2:0" ] = {
sdmg = 0,
sid = "minecraft:sandstone",
dmg = 0,
id = "minecraft:double_stone_slab2",
},
[ "minecraft:stone_slab:2" ] = {
sdmg = 0,
sid = "minecraft:wooden_slab",
dmg = 2,
id = "minecraft:stone_slab",
},
[ "minecraft:wheat:0" ] = {
sdmg = 0,
sid = "minecraft:wheat_seeds",
dmg = 0,
id = "minecraft:wheat",
},
[ "minecraft:flowing_water:0" ] = {
sdmg = 0,
sid = "minecraft:air",
dmg = 0,
id = "minecraft:flowing_water",
},
[ "minecraft:lit_furnace:0" ] = {
sdmg = 0,
sid = "minecraft:furnace",
dmg = 0,
id = "minecraft:lit_furnace",
},
}
self.dirty = true
self:flush()
end
function subDB:add(s)
TableDB.add(self, { s.id, s.dmg}, s)
self:flush()
end
function subDB:remove(s)
-- TODO: tableDB.remove should take table key
TableDB.remove(self, s.id .. ':' .. s.dmg)
self:flush()
end
function subDB:getSubstitutedItem(id, dmg)
local sub = TableDB.get(self, { id, dmg })
if sub then
return { id = sub.sid, dmg = sub.sdmg }
end
return { id = id, dmg = dmg }
end
function subDB:lookupBlocksForSub(sid, sdmg)
local t = { }
for k,v in pairs(self.data) do
if v.sid == sid and v.sdmg == sdmg then
t[k] = v
end
end
return t
end
--[[-- maxStackDB --]]--
maxStackDB = TableDB({
fileName = fs.combine(BUILDER_DIR, 'maxstack.db'),
tabledef = {
autokeys = false,
type = 'simple',
columns = {
{ label = 'Key', type = 'key', length = 8 },
{ label = 'Quantity', type = 'number', length = 2 },
}
}
})
function maxStackDB:get(id, dmg)
return self.data[id .. ':' .. dmg] or 64
end
--[[-- Spinner --]]--
UI.Spinner = class()
function UI.Spinner:init(args)
local defaults = {
UIElement = 'Spinner',
timeout = .095,
x = 1,
y = 1,
c = os.clock(),
spinIndex = 0,
spinSymbols = { '-', '/', '|', '\\' }
}
defaults.x, defaults.y = term.getCursorPos()
defaults.startX = defaults.x
defaults.startY = defaults.y
UI.setProperties(self, defaults)
UI.setProperties(self, args)
end
function UI.Spinner:spin(text)
local cc = os.clock()
if cc > self.c + self.timeout then
term.setCursorPos(self.x, self.y)
local str = self.spinSymbols[self.spinIndex % #self.spinSymbols + 1]
if text then
str = str .. ' ' .. text
end
term.write(str)
self.spinIndex = self.spinIndex + 1
self.c = cc
os.sleep(0)
end
end
function UI.Spinner:stop(text)
term.setCursorPos(self.x, self.y)
local str = string.rep(' ', #self.spinSymbols)
if text then
str = str .. ' ' .. text
end
term.write(str)
term.setCursorPos(self.startX, self.startY)
end
--[[-- Builder --]]--
function Builder:getBlockCounts()
local blocks = { }
-- add a couple essential items to the supply list to allow replacements
local wrench = subDB:getSubstitutedItem('ThermalExpansion:wrench', 0)
wrench.qty = 0
wrench.need = 1
blocks[wrench.id .. ':' .. wrench.dmg] = wrench
local fuel = subDB:getSubstitutedItem(Builder.fuelItem.id, Builder.fuelItem.dmg)
fuel.qty = 0
fuel.need = 1
blocks[fuel.id .. ':' .. fuel.dmg] = fuel
blocks['minecraft:piston:0'] = {
id = 'minecraft:piston',
dmg = 0,
qty = 0,
need = 1,
}
for k,b in ipairs(schematic.blocks) do
if k >= self.index then
local key = tostring(b.id) .. ':' .. b.dmg
local block = blocks[key]
if not block then
block = Util.shallowCopy(b)
block.qty = 0
block.need = 0
blocks[key] = block
end
blocks[key].need = blocks[key].need + 1
end
end
return blocks
end
function Builder:selectItem(id, dmg)
for k,s in ipairs(self.slots) do
if s.qty > 0 and s.id == id and s.dmg == dmg then
-- check to see if someone pulled items from inventory
-- or we passed over a hopper
if turtle.getItemCount(s.index) > 0 then
if k > 1 and s.qty > 1 then
table.remove(self.slots, k)
table.insert(self.slots, 1, s)
end
turtle.select(s.index)
return s
end
end
end
end
function Builder:getAirResupplyList(blockIndex)
local slots = { }
if self.mode == 'destroy' then
for i = 1, self.resourceSlots do
slots[i] = {
qty = 0,
need = 0,
index = i
}
end
else
slots, _ = self:getGenericSupplyList(blockIndex)
end
local fuel = subDB:getSubstitutedItem(Builder.fuelItem.id, Builder.fuelItem.dmg)
slots[15] = {
id = 'minecraft:chest',
dmg = 0,
qty = 0,
need = 1,
index = 15,
}
slots[16] = {
id = fuel.id,
dmg = fuel.dmg,
wrench = true,
qty = 0,
need = 64,
index = 16,
}
return slots
end
function Builder:getSupplyList(blockIndex)
local slots, lastBlock = self:getGenericSupplyList(blockIndex)
slots[15] = {
id = 'minecraft:piston',
dmg = 0,
qty = 0,
need = 1,
index = 15,
}
local wrench = subDB:getSubstitutedItem('ThermalExpansion:wrench', 0)
slots[16] = {
id = wrench.id,
dmg = wrench.dmg,
wrench = true,
qty = 0,
need = 1,
index = 16,
}
self.slots = slots
return lastBlock
end
function Builder:getGenericSupplyList(blockIndex)
local slots = { }
for i = 1, self.resourceSlots do
slots[i] = {
qty = 0,
need = 0,
index = i
}
end
local function getSlot(id, dmg)
-- find matching slot
local maxStack = maxStackDB:get(id, dmg)
for _, s in ipairs(slots) do
if s.id == id and s.dmg == dmg and s.need < maxStack then
return s
end
end
-- return first available slot
for _, s in ipairs(slots) do
if not s.id then
s.key = id .. ':' .. dmg
s.id = id
s.dmg = dmg
return s
end
end
end
local lastBlock = blockIndex
for k = blockIndex, #schematic.blocks do
lastBlock = k
local b = schematic.blocks[k]
if b.id ~= 'minecraft:air' then
local slot = getSlot(b.id, b.dmg)
if not slot then
break
end
slot.need = slot.need + 1
end
end
for _,s in pairs(slots) do
if s.id then
s.name = blocks.blockDB:getName(s.id, s.dmg)
end
end
return slots, lastBlock
end
function Builder:substituteBlocks()
local spinner = UI.Spinner({
spinSymbols = { '' }
})
for _,b in pairs(schematic.blocks) do
-- replace schematic block type with substitution
local pb = blocks:getRealBlock(b.id, b.dmg)
b.id = pb.id
b.dmg = pb.dmg
b.direction = pb.direction
local sub = subDB:get({ b.id, b.dmg })
if sub then
b.id = sub.sid
b.dmg = sub.sdmg
end
spinner:spin()
end
end
function Builder:dumpInventory()
local success = true
for i = 1, 16 do
local qty = turtle.getItemCount(i)
if qty > 0 then
self.itemProvider:insert(i, qty)
end
if turtle.getItemCount(i) ~= 0 then
success = false
end
end
turtle.select(1)
return success
end
function Builder:dumpInventoryWithCheck()
while not self:dumpInventory() do
Logger.log('builder', 'Unable to dump inventory')
print('Provider is full or missing - make space or replace')
print('Press enter to continue')
turtle.setHeading(0)
read()
end
end
function Builder:autocraft(supplies)
local t = { }
for i,s in pairs(supplies) do
local key = s.id .. ':' .. s.dmg
local item = t[key]
if not item then
item = {
id = s.id,
dmg = s.dmg,
qty = 0,
}
t[key] = item
end
item.qty = item.qty + (s.need - s.qty)
end
Builder.itemProvider:craftItems(t)
end
function Builder:getSupplies()
self.itemProvider:refresh()
local t = { }
for _,s in ipairs(self.slots) do
if s.need > 0 then
local item = self.itemProvider:getItemInfo(s.id, s.dmg)
if item then
if item.name then
s.name = item.name
end
local qty = math.min(s.need - s.qty, item.qty)
if qty + s.qty > item.max_size then
maxStackDB:add({ s.id, s.dmg }, item.max_size)
maxStackDB.dirty = true
maxStackDB:flush()
qty = item.max_size
s.need = qty
end
if qty > 0 then
self.itemProvider:provide(item, qty, s.index)
s.qty = turtle.getItemCount(s.index)
end
end
end
if s.qty < s.need then
table.insert(t, s)
local name = s.name or s.id .. ':' .. s.dmg
Logger.log('builder', 'Need %d %s', s.need - s.qty, name)
end
end
return t
end
Event.addHandler('build', function()
Builder:build()
end)
function Builder:refuel()
while turtle.getFuelLevel() < 4000 and self.fuelItem do
Logger.log('builder', 'Refueling')
turtle.select(1)
local fuel = subDB:getSubstitutedItem(self.fuelItem.id, self.fuelItem.dmg)
self.itemProvider:provide(fuel, 64, 1)
if turtle.getItemCount(1) == 0 then
Logger.log('builder', 'Out of fuel, add fuel to chest/ME system')
print('Out of fuel, add fuel to chest/ME system')
turtle.setHeading(0)
turtle.status = 'waiting'
os.sleep(5)
else
turtle.refuel(64)
end
end
end
function Builder:inAirDropoff()
if not device.wireless_modem then
return false
end
self:log('Requesting air supply drop for supply #: ' .. 1)
while true do
Message.broadcast('needSupplies', { point = turtle.getPoint(), uid = 1 })
local _, id, msg, _ = Message.waitForMessage('gotSupplies', 1)
if not msg or not msg.contents then
Message.broadcast('supplyList', { uid = 1, slots = self:getAirResupplyList() })
return false
end
turtle.status = 'waiting'
if msg.contents.point then
local pt = msg.contents.point
self:log('Received supply location')
os.sleep(0)
turtle.goto(pt.x, pt.z, pt.y)
os.sleep(.1) -- random computer is not connected error
local chestProvider = ChestProvider({ direction = 'down', wrapSide = 'top' })
if not chestProvider:isValid() then
self:log('Chests above is not valid')
return false
end
local oldProvider = self.itemProvider
self.itemProvider = chestProvider
if not self:dumpInventory() then
self:log('Unable to dump inventory')
self.itemProvider = oldProvider
return false
end
self.itemProvider = oldProvider
Message.broadcast('thanks', { })
for i = 1,12 do -- wait til supplier is idle before sending next request
if turtle.detectUp() then
os.sleep(.25)
end
end
os.sleep(.1)
Message.broadcast('supplyList', { uid = 1, slots = self:getAirResupplyList() })
return true
end
end
end
function Builder:inAirResupply()
if not device.wireless_modem then
return false
end
local oldProvider = self.itemProvider
self:log('Requesting air supply drop for supply #: ' .. self.slotUid)
while true do
Message.broadcast('needSupplies', { point = turtle.getPoint(), uid = Builder.slotUid })
local _, id, msg, _ = Message.waitForMessage('gotSupplies', 1)
if not msg or not msg.contents then
self.itemProvider = oldProvider
return false
end
turtle.status = 'waiting'
if msg.contents.point then
local pt = msg.contents.point
self:log('Received supply location')
os.sleep(0)
turtle.goto(pt.x, pt.z, pt.y)
os.sleep(.1) -- random computer is not connected error
local chestProvider = ChestProvider({ direction = 'down', wrapSide = 'top' })
if not chestProvider:isValid() then
Util.print('not valid')
read()
end
self.itemProvider = chestProvider
if not self:dumpInventory() then
self.itemProvider = oldProvider
return false
end
self:refuel()
local lastBlock = self:getSupplyList(self.index)
local supplies = self:getSupplies()
Message.broadcast('thanks', { })
self.itemProvider = oldProvider
if #supplies == 0 then
for i = 1,12 do -- wait til supplier is idle before sending next request
if turtle.detectUp() then
os.sleep(.25)
end
end
os.sleep(.1)
if lastBlock < #schematic.blocks then
self:sendSupplyRequest(lastBlock)
else
Message.broadcast('finished')
end
return true
end
self:log('Missing supplies - manually resupplying')
return false
end
end
end
function Builder:sendSupplyRequest(lastBlock)
local slots = self:getAirResupplyList(lastBlock)
self.slotUid = os.clock()
Message.broadcast('supplyList', { uid = self.slotUid, slots = slots })
end
function Builder:resupply()
if self.slotUid and self:inAirResupply() then
os.queueEvent('build')
return
end
turtle.status = 'resupplying'
self:log('Resupplying')
turtle.gotoYlast(turtle.getLocation('supplies'))
os.sleep(.1) -- random 'Computer is not connected' error...
self:dumpInventoryWithCheck()
self:refuel()
local lastBlock = self:getSupplyList(self.index)
if lastBlock < #schematic.blocks then
self:sendSupplyRequest(lastBlock)
elseif device.wireless_modem then
Message.broadcast('finished')
end
os.sleep(1)
local supplies = self:getSupplies()
if #supplies == 0 then
os.queueEvent('build')
else
turtle.setHeading(0)
self:autocraft(supplies)
Logger.log('builder', 'Waiting for supplies')
supplyPage.grid:setValues(supplies)
UI:setPage('supply')
end
end
function Builder:placeDown(slot)
return turtle.placeDown(slot.index)
end
function Builder:placeUp(slot)
return turtle.placeUp(slot.index)
end
function Builder:place(slot)
return turtle.place(slot.index)
end
function Builder:getWrenchSlot()
local wrench = subDB:getSubstitutedItem('ThermalExpansion:wrench', 0)
return Builder:selectItem(wrench.id, wrench.dmg)
end
function Builder:wrenchBlock(side, count)
local s = Builder:getWrenchSlot()
if not s then
b.needResupply = true
return
end
turtle.select(s.index)
for i = 1,count do
turtle.getAction(side).place()
end
return true
end
-- place piston, wrench piston to face downward, extend, remove piston
function Builder:placePiston(b)
local ps = Builder:selectItem('minecraft:piston', 0)
local ws = Builder:getWrenchSlot()
if not ps or not ws then
b.needResupply = true
-- a hopper may have eaten the piston
return
end
if not turtle.place(ps.index) then
return false
end
local wrenchCount = 5
if GoodWrenches[ws.id] then
wrenchCount = 2
end
local success = self:wrenchBlock('forward', wrenchCount) --wrench piston to point downwards
rs.setOutput('front', true)
os.sleep(.25)
rs.setOutput('front', false)
os.sleep(.25)
turtle.select(ps.index)
turtle.dig()
return true
end
function Builder:goto(x, z, y, heading)
if not turtle.goto(x, z, y, heading) then
Logger.log('builder', 'stuck')
print('stuck')
print('Press enter to continue')
os.sleep(1)
turtle.status = 'stuck'
read()
end
end
-- goto used when turtle could be below travel plane
-- if the distance is no more than 1 block, there's no need to pop back to the travel plane
function Builder:gotoEx(x, z, y, h, travelPlane)
local distance = math.abs(turtle.getPoint().x - x) + math.abs(turtle.getPoint().z - z)
-- following code could be better
if distance == 0 then
turtle.gotoY(y)
elseif distance == 1 then
if turtle.point.y < y then
turtle.gotoY(y)
end
elseif distance > 1 then
self:gotoTravelPlane(travelPlane)
end
self:goto(x, z, y, h)
end
function Builder:placeDirectionalBlock(b, slot, travelPlane)
local d = b.direction
local function getAdjacentPoint(pt, direction)
local hi = turtle.getHeadingInfo(direction)
return { x = pt.x + hi.xd, z = pt.z + hi.zd, y = pt.y + hi.yd, heading = (hi.heading + 2) % 4 }
end
local directions = {
[ 'north' ] = 'north',
[ 'south' ] = 'south',
[ 'east' ] = 'east',
[ 'west' ] = 'west',
}
if directions[d] then
self:gotoEx(b.x, b.z, b.y, turtle.getHeadingInfo(directions[d]).heading, travelPlane)
b.placed = self:placeDown(slot)
end
if d == 'top' then
self:gotoEx(b.x, b.z, b.y+1, nil, travelPlane)
if self:placeDown(slot) then
turtle.goback()
b.placed = self:placePiston(b)
end
end
if d == 'bottom' then
local t = {
[1] = getAdjacentPoint(b, 'east'),
[2] = getAdjacentPoint(b, 'south'),
[3] = getAdjacentPoint(b, 'west'),
[4] = getAdjacentPoint(b, 'north'),
}
local c = Point.closest(turtle.getPoint(), t)
self:gotoEx(c.x, c.z, c.y, c.heading, travelPlane)
if self:place(slot) then
turtle.up()
b.placed = self:placePiston(b)
end
end
local stairDownDirections = {
[ 'north-down' ] = 'north',
[ 'south-down' ] = 'south',
[ 'east-down' ] = 'east',
[ 'west-down' ] = 'west'
}
if stairDownDirections[d] then
self:gotoEx(b.x, b.z, b.y+1, turtle.getHeadingInfo(stairDownDirections[d]).heading, travelPlane)
if self:placeDown(slot) then
turtle.goback()
b.placed = self:placePiston(b)
end
end
local stairUpDirections = {
[ 'north-up' ] = 'south',
[ 'south-up' ] = 'north',
[ 'east-up' ] = 'west',
[ 'west-up' ] = 'east'
}
if stairUpDirections[d] then
local isSouth = (turtle.getHeadingInfo(Builder.facing).heading +
turtle.getHeadingInfo(stairUpDirections[d]).heading) % 4 == 1
if isSouth then
-- for some reason, the south facing stair doesn't place correctly
-- jump through some hoops to place it
self:gotoEx(b.x, b.z, b.y, (turtle.getHeadingInfo(stairUpDirections[d]).heading + 2) % 4, travelPlane)
if self:placeUp(slot) then
turtle.goback()
turtle.gotoY(turtle.point.y + 2)
b.placed = self:placePiston(b)
turtle.down()
b.placed = self:placePiston(b)
b.heading = turtle.point.heading -- stop debug message below since we are pointing in wrong direction
end
else
local hi = turtle.getHeadingInfo(stairUpDirections[d])
self:gotoEx(b.x - hi.xd, b.z - hi.zd, b.y, hi.heading, travelPlane)
if self:place(slot) then
turtle.up()
b.placed = self:placePiston(b)
end
end
end
local horizontalDirections = {
[ 'east-west-block' ] = { 'east', 'west' },
[ 'north-south-block' ] = { 'north', 'south' },
}
if horizontalDirections[d] then
local t = {
[1] = getAdjacentPoint(b, horizontalDirections[d][1]),
[2] = getAdjacentPoint(b, horizontalDirections[d][2]),
}
local c = Point.closest(turtle.getPoint(), t)
self:gotoEx(c.x, c.z, c.y, c.heading, travelPlane)
if self:place(slot) then
turtle.up()
b.placed = self:placePiston(b)
end
end
local pistonDirections = {
[ 'piston-north' ] = 'north',
[ 'piston-south' ] = 'south',
[ 'piston-west' ] = 'west',
[ 'piston-east' ] = 'east',
[ 'piston-down' ] = 'down',
}
if pistonDirections[d] then
-- why are pistons so broke in cc 1.7 ??????????????????????
local ws = Builder:getWrenchSlot()
if not ws then
b.needResupply = true
-- a hopper may have eaten the piston
return false
end
if GoodWrenches[ws.id] then
-- piston turns relative to turtle position :)
local rotatedPistonDirections = {
[ 'piston-east' ] = 'south',
[ 'piston-south' ] = 'west',
[ 'piston-west' ] = 'north',
[ 'piston-north' ] = 'east',
[ 'piston-down' ] = 'down',
}
local wrenchCount
if d == 'piston-down' then
self:gotoEx(b.x -1, b.z, b.y, 0, travelPlane)
wrenchCount = 2
else
local hi = turtle.getHeadingInfo(rotatedPistonDirections[d])
self:gotoEx(b.x + hi.xd, b.z + hi.zd, b.y, (hi.heading + 2) % 4, travelPlane)
wrenchCount = 1
end
if self:place(slot) then
self:wrenchBlock('forward', wrenchCount)
turtle.up()
b.placed = self:placePiston(b)
end
else -- cresent wrench
-- piston turns relative to the world :(
local wrenchCounts = {
[ 1 ] = 4, -- east
[ 2 ] = 2, -- south
[ 3 ] = 3, -- west
[ 4 ] = 1, -- north
}
self:goto(b.x, b.z, b.y, nil, travelPlane)
local wrenchCount = 5
if d ~= 'piston-down' then
local offsetDirection = (turtle.getHeadingInfo(Builder.facing).heading +
turtle.getHeadingInfo(pistonDirections[d]).heading) % 4
wrenchCount = wrenchCounts[offsetDirection + 1]
end
if self:placeDown(slot) then
b.placed = self:wrenchBlock('down', wrenchCount)
end
end
end
local doorDirections = {
[ 'east-door' ] = 'east',
[ 'south-door' ] = 'south',
[ 'west-door' ] = 'west',
[ 'north-door' ] = 'north',
}
if doorDirections[d] then
local hi = turtle.getHeadingInfo(doorDirections[d])
self:gotoEx(b.x - hi.xd, b.z - hi.zd, b.y - 2, hi.heading, travelPlane)
-- if not turtle.detectDown() then
-- if turtle.down() then
-- if not turtle.detectDown() then
-- if turtle.down() then
b.placed = self:place(slot)
-- end
-- end
-- end
-- end
end
local blockDirections = {
[ 'north-block' ] = 'north',
[ 'south-block' ] = 'south',
[ 'east-block' ] = 'east',
[ 'west-block' ] = 'west',
}
if blockDirections[d] then
local hi = turtle.getHeadingInfo(blockDirections[d])
self:gotoEx(b.x - hi.xd, b.z - hi.zd, b.y-1, hi.heading, travelPlane)
b.placed = self:place(slot)
end
-- debug
if d ~= 'top' and d ~= 'bottom' and not horizontalDirections[d] and not pistonDirections[d] then
if not b.heading or turtle.getHeading() ~= b.heading then
self:log(d .. ' - ' .. turtle.getHeading() .. ' - ' .. (b.heading or 'nil'))
--read()
end
end
return b.placed
end
function Builder:reloadSchematic()
schematic:reload()
self:substituteBlocks()
end
function Builder:log(...)
Logger.log('builder', ...)
Util.print(...)
end
function Builder:logBlock(index, b)
local bdir = b.direction or ''
local logText = string.format('%d %s:%d (x:%d,z:%d:y:%d) %s',
index, b.id, b.dmg, b.x, b.z, b.y, bdir)
self:log(logText)
-- self:log(b.index) -- unique identifier of block
if device.wireless_modem then
Message.broadcast('builder', { x = b.x, y = b.y, z = b.z, heading = b.heading })
end
if b.info then
Logger.debug(b.info)
end
end
function Builder:saveProgress(index)
Util.writeTable(
fs.combine(BUILDER_DIR, schematic.filename .. '.progress'),
{ index = index, facing = Builder.facing }
)
end
function Builder:loadProgress(filename)
local progress = Util.readTable(fs.combine(BUILDER_DIR, filename))
if progress then
Builder.index = progress.index
if Builder.index > #schematic.blocks then
Builder.index = 1
end
Builder.facing = progress.facing or 'south'
end
end
-- find the highest y in the last 2 planes
function Builder:findTravelPlane(index)
local travelPlane
for i = index, 1, -1 do
local b = schematic.blocks[i]
if not travelPlane or b.y > travelPlane then
-- if not b.twoHigh then
travelPlane = b.y
Logger.log('builder', 'adjusting travelPlane')
Logger.log('builder', b)
--read()
-- end
elseif travelPlane and travelPlane - b.y > 2 then
break
end
end
return travelPlane or 0
end
function Builder:gotoTravelPlane(travelPlane)
if travelPlane > turtle.getPoint().y then
turtle.gotoY(travelPlane)
end
end
function Builder:build()
local direction = 1
local last = #schematic.blocks
local travelPlane = 0
local minFuel = schematic.height + schematic.width + schematic.length + 100
if self.mode == 'destroy' then
direction = -1
last = 1
turtle.status = 'destroying'
else
travelPlane = self:findTravelPlane(self.index)
turtle.status = 'building'
end
UI:setPage('blank')
for i = self.index, last, direction do
self.index = i
local b = schematic.blocks[i]
if b.id ~= 'minecraft:air' then
if self.mode == 'destroy' then
b.heading = nil -- don't make the supplier follow the block heading
self:logBlock(self.index, b)
if b.y ~= turtle.getPoint().y then
turtle.gotoY(b.y)
end
self:goto(b.x, b.z, b.y)
turtle.digDown()
-- if no supplier, then should fill all slots
if turtle.getItemCount(self.resourceSlots) > 0 or turtle.getFuelLevel() < minFuel then
Logger.log('builder', 'Dropping off inventory')
if turtle.getFuelLevel() < minFuel or not self:inAirDropoff() then
turtle.gotoLocation('supplies')
os.sleep(.1) -- random 'Computer is not connected' error...
self:dumpInventoryWithCheck()
self:refuel()
end
turtle.status = 'destroying'
end
else -- Build mode
local slot = Builder:selectItem(b.id, b.dmg)
if not slot or turtle.getFuelLevel() < minFuel then
if turtle.getPoint().x > -1 or turtle.getPoint().z > -1 then
self:gotoTravelPlane(travelPlane)
end
self:resupply()
return
end
if b.y > travelPlane then
travelPlane = b.y
end
self:logBlock(self.index, b)
if b.direction then
b.needResupply = false
self:placeDirectionalBlock(b, slot, travelPlane)
if b.needResupply then -- lost our piston in a hopper probably
self:gotoTravelPlane(travelPlane)
self:resupply()
return
end
else
self:gotoTravelPlane(travelPlane)
self:goto(b.x, b.z, b.y)
b.placed = self:placeDown(slot)
end
if b.placed then
slot.qty = slot.qty - 1
else
Logger.log('builder', 'failed to place block')
print('failed to place block')
end
end
end
self:saveProgress(self.index+1)
if turtle.abort then
turtle.status = 'aborting'
self:gotoTravelPlane(travelPlane)
turtle.gotoLocation('supplies')
turtle.setHeading(0)
Builder:dumpInventory()
Event.exitPullEvents()
UI.term:reset()
print('Aborted')
return
end
end
Message.broadcast('finished')
self:gotoTravelPlane(travelPlane)
turtle.gotoLocation('supplies')
turtle.setHeading(0)
Builder:dumpInventory()
for i = 1, 4 do
turtle.turnRight()
end
--self.index = 1
--os.queueEvent('build')
Event.exitPullEvents()
UI.term:reset()
fs.delete(schematic.filename .. '.progress')
Logger.log('builder', 'Finished')
print('Finished')
end
--[[-- blankPage --]]--
blankPage = UI.Page()
function blankPage:draw()
self:clear()
self:setCursorPos(1, 1)
end
--[[-- selectSubstitutionPage --]]--
selectSubstitutionPage = UI.Page({
titleBar = UI.TitleBar({
title = 'Select a substitution',
previousPage = 'listing'
}),
grid = UI.ScrollingGrid({
columns = {
{ heading = 'id', key = 'id' },
{ heading = 'dmg', key = 'dmg' },
},
sortColumn = 'odmg',
height = UI.term.height-1,
autospace = true,
y = 2,
}),
})
function selectSubstitutionPage:enable()
self.grid:adjustWidth()
self.grid:setIndex(1)
UI.Page.enable(self)
end
function selectSubstitutionPage:eventHandler(event)
if event.type == 'grid_select' then
substitutionPage.sub = event.selected
UI:setPage(substitutionPage)
elseif event.type == 'key' and event.key == 'q' then
UI:setPreviousPage()
else
return UI.Page.eventHandler(self, event)
end
return true
end
--[[-- substitutionPage --]]--
substitutionPage = UI.Page({
backgroundColor = colors.gray,
titleBar = UI.TitleBar({
previousPage = true,
title = 'Substitute a block'
}),
menuBar = UI.MenuBar({
y = 2,
buttons = {
{ text = 'Accept', event = 'accept', help = 'Accept' },
{ text = 'Revert', event = 'revert', help = 'Restore to original' },
{ text = 'Air', event = 'air', help = 'Air' },
},
}),
inName = UI.Text({ y = 4, width = UI.term.width }),
outName = UI.Text({ y = 5, width = UI.term.width }),
grid = UI.ScrollingGrid({
columns = {
{ heading = 'Name', key = 'name', width = UI.term.width-9 },
{ heading = 'Qty', key = 'fQty', width = 5 },
},
sortColumn = 'name',
height = UI.term.height-7,
y = 7,
}),
statusBar = UI.StatusBar()
})
substitutionPage.menuBar:add({
filterLabel = UI.Text({
value = 'Search',
x = UI.term.width-14,
textColor = colors.black,
}),
filter = UI.TextEntry({
x = UI.term.width-7,
width = 7,
})
})
function substitutionPage:draw()
local inName = blocks.blockDB:getName(self.sub.id, self.sub.dmg)
self.inName.value = ' Replace ' .. inName
self.outName.value = ''
if self.sub.sid then
local outName = blocks.blockDB:getName(self.sub.sid, self.sub.sdmg)
self.outName.value = ' With ' .. outName
end
--self.grid:adjustWidth()
UI.Page.draw(self)
end
function substitutionPage:enable()
self.allItems = Builder.itemProvider:refresh()
self.grid.values = self.allItems
for _,item in pairs(self.grid.values) do
item.key = item.id .. ':' .. item.dmg
item.lname = string.lower(item.name)
item.fQty = Util.toBytes(item.qty)
end
self.grid:update()
self.menuBar.filter.value = ''
self.menuBar.filter.pos = 1
self:setFocus(self.menuBar.filter)
UI.Page.enable(self)
end
--function substitutionPage:focusFirst()
-- self.menuBar.filter:focus()
--end
function substitutionPage:applySubstitute(id, dmg)
self.sub.sid = id
self.sub.sdmg = dmg
end
function substitutionPage:eventHandler(event)
if event.type == 'grid_focus_row' then
local s = string.format('%s:%d',
event.selected.id,
event.selected.dmg)
self.statusBar:setStatus(s)
self.statusBar:draw()
elseif event.type == 'grid_select' then
if not blocks.blockDB:lookupName(event.selected.id, event.selected.dmg) then
blocks.blockDB:add(event.selected.id, event.selected.dmg, event.selected.name, event.selected.id)
blocks.blockDB:flush()
end
self:applySubstitute(event.selected.id, event.selected.dmg)
self:draw()
elseif event.type == 'text_change' then
local text = event.text
if #text == 0 then
self.grid.values = self.allItems
else
self.grid.values = { }
for _,item in pairs(self.allItems) do
if string.find(item.lname, text) then
table.insert(self.grid.values, item)
end
end
end
--self.grid:adjustWidth()
self.grid:update()
self.grid:setIndex(1)
self.grid:draw()
elseif event.type == 'accept' or event.type == 'air' or event.type == 'revert' then
self.statusBar:setStatus('Saving changes...')
self.statusBar:draw()
if event.type == 'air' then
self:applySubstitute('minecraft:air', 0)
end
if event.type == 'revert' then
subDB:remove(self.sub)
elseif not self.sub.sid then
self.statusBar:setStatus('Select a substition')
self.statusBar:draw()
return UI.Page.eventHandler(self, event)
else
subDB:add(self.sub)
end
Builder:reloadSchematic()
UI:setPage('listing')
elseif event.type == 'cancel' then
UI:setPreviousPage()
end
return UI.Page.eventHandler(self, event)
end
--[[-- SupplyPage --]]--
supplyPage = UI.Page({
titleBar = UI.TitleBar({
title = 'Waiting for supplies',
previousPage = 'start'
}),
menuBar = UI.MenuBar({
y = 2,
buttons = {
--{ text = 'Refresh', event = 'refresh', help = 'Refresh inventory' },
{ text = 'Continue', event = 'build', help = 'Continue building' },
{ text = 'Menu', event = 'menu', help = 'Return to main menu' },
{ text = 'Force Craft', event = 'craft', help = 'Request crafting (again)' },
}
}),
grid = UI.Grid({
columns = {
{ heading = 'Slot', key = 'index', width = 4 },
{ heading = 'Name', key = 'name', width = UI.term.width-12 },
{ heading = 'Need', key = 'need', width = 4 },
},
sortColumn = 'index',
y = 3,
width = UI.term.width,
height = UI.term.height - 3
}),
statusBar = UI.StatusBar({
columns = {
{ 'Help', 'help', UI.term.width - 13 },
{ 'Fuel', 'fuel', 11 }
}
}),
accelerators = {
c = 'craft',
r = 'refresh',
b = 'build',
m = 'menu',
},
})
function supplyPage:eventHandler(event)
if event.type == 'craft' then
local s = self.grid:getSelected()
if Builder.itemProvider:craft(s.id, s.dmg, s.need-s.qty) then
local name = s.name or ''
self.statusBar:timedStatus('Requested ' .. s.need-s.qty .. ' ' .. name, 3)
else
self.statusBar:timedStatus('Unable to craft')
end
elseif event.type == 'refresh' then
self:refresh()
elseif event.type == 'build' then
Builder:build()
elseif event.type == 'menu' then
Builder:dumpInventory()
--Builder.status = 'idle'
UI:setPage('start')
turtle.status = 'idle'
elseif event.type == 'grid_focus_row' then
self.statusBar:setValue('help', event.selected.id .. ':' .. event.selected.dmg)
self.statusBar:draw()
elseif event.type == 'focus_change' then
self.statusBar:timedStatus(event.focused.help, 3)
end
return UI.Page.eventHandler(self, event)
end
function supplyPage:enable()
self.grid:setIndex(1)
self.statusBar:setValue('fuel',
string.format('Fuel: %dk', math.floor(turtle.getFuelLevel() / 1024)))
-- self.statusBar:setValue('block',
-- string.format('Block: %d', Builder.index))
Event.addNamedTimer('supplyRefresh', 6, true, function()
if self.enabled then
Builder:autocraft(Builder:getSupplies())
self:refresh()
self.statusBar:timedStatus('Refreshed ', 2)
self:sync()
end
end)
UI.Page.enable(self)
end
function supplyPage:disable()
Event.cancelNamedTimer('supplyRefresh')
end
function supplyPage:refresh()
self.statusBar:timedStatus('Refreshed ', 3)
local t = Builder:getSupplies()
if #t == 0 then
Builder:build()
else
self.grid:setValues(t)
self.grid:draw()
end
end
--[[-- ListingPage --]]--
listingPage = UI.Page({
titleBar = UI.TitleBar({
title = 'Supply List',
previousPage = 'start'
}),
menuBar = UI.MenuBar({
y = 2,
buttons = {
{ text = 'Craft', event = 'craft', help = 'Request crafting' },
{ text = 'Refresh', event = 'refresh', help = 'Refresh inventory' },
{ text = 'Toggle', event = 'toggle', help = 'Toggles needed blocks' },
{ text = 'Substitute', event = 'edit', help = 'Substitute a block' },
}
}),
grid = UI.ScrollingGrid({
columns = {
{ heading = 'Name', key = 'name', width = UI.term.width - 14 },
{ heading = 'Need', key = 'fNeed', width = 5 },
{ heading = 'Have', key = 'fQty', width = 5 },
},
sortColumn = 'name',
y = 3,
height = UI.term.height-3,
help = 'Set a block type or pick a substitute block'
}),
accelerators = {
q = 'menu',
c = 'craft',
r = 'refresh',
t = 'toggle',
},
statusBar = UI.StatusBar(),
fullList = true
})
function listingPage:enable()
listingPage:refresh()
UI.Page.enable(self)
end
function listingPage:eventHandler(event)
if event.type == 'craft' then
local s = self.grid:getSelected()
local item = Builder.itemProvider:getItemInfo(s.id, s.dmg)
if item and item.is_craftable then
local qty = math.max(0, s.need - item.qty)
if item then
Builder.itemProvider:craft(s.id, s.dmg, qty)
local name = s.name or s.key
self.statusBar:timedStatus('Requested ' .. qty .. ' ' .. name, 3)
end
else
self.statusBar:timedStatus('Unable to craft')
end
elseif event.type == 'grid_focus_row' then
self.statusBar:setStatus(event.selected.id .. ':' .. event.selected.dmg)
self.statusBar:draw()
elseif event.type == 'refresh' then
self:refresh()
self:draw()
self.statusBar:timedStatus('Refreshed ', 3)
elseif event.type == 'toggle' then
self.fullList = not self.fullList
self:refresh()
self:draw()
elseif event.type == 'menu' then
UI:setPage('start')
elseif event.type == 'edit' or event.type == 'grid_select' then
self:manageBlock(self.grid:getSelected())
elseif event.type == 'focus_change' then
if event.focused.help then
self.statusBar:timedStatus(event.focused.help, 3)
end
end
return UI.Page.eventHandler(self, event)
end
function listingPage.grid:getRowTextColor(row, selected)
if row.is_craftable then
return colors.yellow
end
return UI.Grid:getRowTextColor(row, selected)
end
function listingPage:refresh()
local supplyList = Builder:getBlockCounts()
Builder.itemProvider:refresh()
for _,b in pairs(supplyList) do
if b.need > 0 then
local item = Builder.itemProvider:getItemInfo(b.id, b.dmg)
if item then
local block = blocks.blockDB:lookup(b.id, b.dmg)
if not block then
blocks.blockDB:add(b.id, b.dmg, item.name)
elseif not blocks.name and item.name then
blocks.blockDB:add(b.id, b.dmg, item.name)
end
b.qty = item.qty
b.is_craftable = item.is_craftable
elseif not b.name then
b.name = blocks.blockDB:getName(b.id, b.dmg)
end
end
end
blocks.blockDB:flush()
local t = {}
for _,b in pairs(supplyList) do
local block = blocks.blockDB:lookup(b.id, b.dmg)
if block then
b.name = block.name
end
if self.fullList or b.qty < b.need then
table.insert(t, b)
end
b.fNeed = Util.toBytes(b.need)
b.fQty = Util.toBytes(b.qty)
end
self.grid:setValues(t)
self.grid:setIndex(1)
end
function listingPage:manageBlock(selected)
local substitutes = subDB:lookupBlocksForSub(selected.id, selected.dmg)
if Util.empty(substitutes) then
substitutionPage.sub = { id = selected.id, dmg = selected.dmg }
UI:setPage(substitutionPage)
elseif Util.size(substitutes) == 1 then
local _,sub = next(substitutes)
substitutionPage.sub = sub
UI:setPage(substitutionPage)
else
selectSubstitutionPage.selected = selected
selectSubstitutionPage.grid:setValues(substitutes)
UI:setPage(selectSubstitutionPage)
end
end
--[[-- startPage --]]--
local startPage = UI.Page({
-- titleBar = UI.TitleBar({ title = 'Builder v' .. Builder.version }),
window = UI.Window({
x = UI.term.width-16,
y = 2,
width = 16,
height = UI.term.height-2,
backgroundColor = colors.gray,
grid = UI.Grid({
columns = {
{ heading = 'Name', key = 'name', width = 6 },
{ heading = 'Value', key = 'value', width = 7 },
},
disableHeader = true,
--y = UI.term.height-1,
x = 1,
y = 2,
width = 16,
height = 9,
--autospace = true,
selectable = false,
backgroundColor = colors.gray
}),
}),
menu = UI.Menu({
x = 2,
y = 4,
menuItems = {
{ prompt = 'Set starting level', event = 'startLevel' },
{ prompt = 'Set starting block', event = 'startBlock' },
{ prompt = 'Supply list', event = 'assignBlocks' },
{ prompt = 'Toggle mode', event = 'toggleMode' },
{ prompt = 'Toggle facing', event = 'toggleFacing' },
{ prompt = 'Begin', event = 'begin' },
{ prompt = 'Quit', event = 'quit' }
}
}),
accelerators = {
x = 'test',
q = 'quit'
}
})
function startPage:draw()
local fuel = turtle.getFuelLevel()
if fuel > 9999 then
fuel = string.format('%dk', math.floor(fuel/1024))
end
local t = {
{ name = 'mode', value = Builder.mode },
{ name = 'start', value = Builder.index },
{ name = 'blocks', value = #schematic.blocks },
{ name = 'fuel', value = fuel },
{ name = 'facing', value = Builder.facing },
{ name = 'length', value = schematic.length },
{ name = 'width', value = schematic.width },
{ name = 'height', value = schematic.height },
}
self.window.grid:setValues(t)
UI.Page.draw(self)
end
function startPage:enable()
self:setFocus(self.menu)
UI.Page.enable(self)
end
function startPage:eventHandler(event)
if event.type == 'startLevel' then
local dialog = UI.Dialog({
text = UI.Text({ x = 5, y = 3, value = '0 - ' .. schematic.height }),
textEntry = UI.TextEntry({ x = 15, y = 3, '0 - 11' })
})
dialog.eventHandler = function(self, event)
if event.type == 'accept' then
local l = tonumber(self.textEntry.value)
if l and l < schematic.height and l >= 0 then
for k,v in pairs(schematic.blocks) do
if v.y >= l then
Builder.index = k
Builder:saveProgress(Builder.index)
UI:setPreviousPage()
break
end
end
else
self.statusBar:timedStatus('Invalid Level', 3)
end
return true
end
return UI.Dialog.eventHandler(self, event)
end
dialog.titleBar.title = 'Enter Starting Level'
dialog:setFocus(dialog.textEntry)
UI:setPage(dialog)
elseif event.type == 'startBlock' then
local dialog = UI.Dialog({
text = UI.Text({ x = 5, y = 3, value = '1 - ' .. #schematic.blocks }),
textEntry = UI.TextEntry({ x = 15, y = 3, value = tostring(Builder.index) })
})
dialog.eventHandler = function(self, event)
if event.type == 'accept' then
local bn = tonumber(self.textEntry.value)
if bn and bn < #schematic.blocks and bn >= 0 then
Builder.index = bn
Builder:saveProgress(Builder.index)
UI:setPreviousPage()
else
self.statusBar:timedStatus('Invalid Block', 3)
end
return true
end
return UI.Dialog.eventHandler(self, event)
end
dialog.titleBar.title = 'Enter Block Number'
dialog:setFocus(dialog.textEntry)
UI:setPage(dialog)
elseif event.type == 'assignBlocks' then
Builder:dumpInventory()
UI:setPage('listing')
elseif event.type == 'toggleMode' then
if Builder.mode == 'build' then
if Builder.index == 1 then
Builder.index = #schematic.blocks
end
Builder.mode = 'destroy'
else
if Builder.index == #schematic.blocks then
Builder.index = 1
end
Builder.mode = 'build'
end
self:draw()
elseif event.type == 'toggleFacing' then
local directions = {
[ 'north' ] = 'east',
[ 'east' ] = 'south',
[ 'south' ] = 'west',
[ 'west' ] = 'north',
}
Builder.facing = directions[Builder.facing]
Builder:saveProgress(Builder.index)
self:draw()
elseif event.type == 'begin' then
UI:setPage('blank')
--Builder.status = 'building'
turtle.status = 'thinking'
print('Reloading schematic')
Builder:reloadSchematic()
print('Determining block placement')
schematic:determineBlockPlacement()
print('Optimizing route (' .. #schematic.blocks .. ' blocks)')
schematic:optimizeRoute()
print('Adjusting route')
schematic:setPlacementOrder()
Builder:dumpInventory()
Builder:refuel()
if Builder.mode == 'destroy' then
if device.wireless_modem then
Message.broadcast('supplyList', { uid = 1, slots = Builder:getAirResupplyList() })
end
print('Beginning destruction')
else
print('Starting build')
end
Builder:build()
Profile.display()
elseif event.type == 'quit' then
Event.exitPullEvents()
end
return UI.Page.eventHandler(self, event)
end
--[[-- startup logic --]]--
local args = {...}
if #args < 1 then
error('supply file name')
end
--if #args > 1 then
Profile.enable()
--end
if os.version() == 'CraftOS 1.7' then
Builder.ccVersion = 1.7
Builder.resourceSlots = 14
else
error('Unsupported ComputerCraft version')
end
Builder.itemProvider = MEProvider()
if not Builder.itemProvider:isValid() then
Builder.itemProvider = ChestProvider()
if not Builder.itemProvider:isValid() then
error('A chest or ME interface must be below turtle')
end
end
multishell.setTitle(multishell.getCurrent(), 'Builder v' .. Builder.version)
maxStackDB:load()
subDB:load()
UI.term:reset()
turtle.status = 'reading'
print('Loading ' .. args[1])
schematic:load(args[1])
print('Substituting blocks')
Builder:substituteBlocks()
if not fs.exists(BUILDER_DIR) then
fs.makeDir(BUILDER_DIR)
end
Builder:loadProgress(schematic.filename .. '.progress')
UI:setPages({
listing = listingPage,
start = startPage,
supply = supplyPage,
blank = blankPage
})
UI:setPage('start')
turtle.setPolicy(turtle.policies.digAttack)
turtle.setPoint({ x = -1, z = -1, y = 0, heading = 0 })
turtle.saveLocation('supplies')
turtle.status = 'idle'
turtle.abort = false
Event.pullEvents()
UI.term:reset()
turtle.status = 'idle'