builder improvements

This commit is contained in:
kepler155c@gmail.com 2017-06-23 02:04:56 -04:00
parent 3f66a9397c
commit 7f99c0c69a
20 changed files with 2827 additions and 675 deletions

View File

@ -1,5 +1,39 @@
local class = require('class')
local TableDB = require('tableDB')
local JSON = require('json')
-- see https://github.com/Khroki/MCEdit-Unified/blob/master/pymclevel/minecraft.yaml
-- see https://github.com/Khroki/MCEdit-Unified/blob/master/Items/minecraft/blocks.json
local nameDB = TableDB({
fileName = 'blocknames.db'
})
function nameDB:load(dir, blockDB)
self.fileName = fs.combine(dir, self.fileName)
if fs.exists(self.fileName) then
TableDB.load(self)
end
self.blockDB = blockDB
end
function nameDB:getName(id, dmg)
return self:lookupName(id, dmg) or id .. ':' .. dmg
end
function nameDB:lookupName(id, dmg)
-- is it in the name db ?
local name = self:get({ id, dmg })
if name then
return name
end
-- is it in the block db ?
for _,v in pairs(self.blockDB.data) do
if v.strId == id and v.dmg == dmg then
return v.name
end
end
end
local blockDB = TableDB({
fileName = 'block.db',
@ -16,7 +50,7 @@ local blockDB = TableDB({
}
})
function blockDB:load(dir, sbDB, btDB)
function blockDB:load(dir)
self.fileName = fs.combine(dir, self.fileName)
if fs.exists(self.fileName) then
TableDB.load(self)
@ -27,89 +61,25 @@ end
function blockDB:seedDB(dir)
-- http://lua-users.org/wiki/LuaCsv
function ParseCSVLine (line,sep)
local res = {}
local pos = 1
sep = sep or ','
while true do
local c = string.sub(line,pos,pos)
if (c == "") then break end
if (c == '"') then
-- quoted value (ignore separator within)
local txt = ""
repeat
local startp,endp = string.find(line,'^%b""',pos)
txt = txt..string.sub(line,startp+1,endp-1)
pos = endp + 1
c = string.sub(line,pos,pos)
if (c == '"') then txt = txt..'"' end
-- check first char AFTER quoted string, if it is another
-- quoted string without separator, then append it
-- this is the way to "escape" the quote char in a quote. example:
-- value1,"blub""blip""boing",value3 will result in blub"blip"boing for the middle
until (c ~= '"')
table.insert(res,txt)
assert(c == sep or c == "")
pos = pos + 1
else
-- no quotes used, just look for the first separator
local startp,endp = string.find(line,sep,pos)
if (startp) then
table.insert(res,string.sub(line,pos,startp-1))
pos = endp + 1
else
-- no separator found -> use rest of string and terminate
table.insert(res,string.sub(line,pos))
break
end
local blocks = JSON.decodeFromFile(fs.combine('sys/etc', 'blocks.json'))
if not blocks then
error('Unable to read blocks.json')
end
for strId, block in pairs(blocks) do
strId = 'minecraft:' .. strId
if type(block.name) == 'string' then
self:add(block.id, 0, block.name, strId)
else
for nid,name in pairs(block.name) do
self:add(block.id, nid - 1, name, strId)
end
end
return res
end
local f = fs.open(fs.combine('sys/etc', 'blockIds.csv'), "r")
if not f then
error('unable to read blockIds.csv')
end
local lastID = nil
while true do
local data = f.readLine()
if not data then
break
end
local line = ParseCSVLine(data, ',')
local strId = line[3] -- string ID
local nid -- schematic ID
local id = line[3]
local dmg = 0
local name = line[1]
if not strId or #strId == 0 then
strId = lastID
end
lastID = strId
local t = { }
string.gsub(line[2], '(%d+)', function(d) table.insert(t, d) end)
nid = tonumber(t[1])
dmg = 0
if t[2] then
dmg = tonumber(t[2])
end
self:add(nid, dmg, name, strId)
end
f.close()
self.dirty = true
self:flush()
-- self:flush()
end
function blockDB:lookup(id, dmg)
@ -117,27 +87,12 @@ function blockDB:lookup(id, dmg)
if not id then
return
end
if not id or not dmg then error('blockDB:lookup: nil passed', 2) end
local key = id .. ':' .. dmg
return self.data[key]
end
function blockDB:getName(id, dmg)
return self:lookupName(id, dmg) or id .. ':' .. dmg
end
function blockDB:lookupName(id, dmg)
if not id or not dmg then error('blockDB:lookupName: nil passed', 2) end
for _,v in pairs(self.data) do
if v.strId == id and v.dmg == dmg then
return v.name
end
end
end
function blockDB:add(id, dmg, name, strId)
local key = id .. ':' .. dmg
@ -170,8 +125,8 @@ function placementDB:load(dir, sbDB, btDB)
end
-- testing
-- self.dirty = true
-- self:flush()
self.dirty = true
--self:flush()
end
function placementDB:addSubsForBlockType(id, dmg, bt)
@ -192,11 +147,12 @@ function placementDB:addSubsForBlockType(id, dmg, bt)
odmg,
sub.sid or strId,
sub.sdmg or dmg,
sub.dir)
sub.dir,
sub.extra)
end
end
function placementDB:add(id, dmg, sid, sdmg, direction)
function placementDB:add(id, dmg, sid, sdmg, direction, extra)
if not id or not dmg then error('placementDB:add: nil passed', 2) end
local key = id .. ':' .. dmg
@ -212,6 +168,7 @@ function placementDB:add(id, dmg, sid, sdmg, direction)
sid = sid, -- string ID
sdmg = sdmg, -- dmg without placement info
direction = direction,
extra = extra,
}
end
@ -286,7 +243,7 @@ function standardBlockDB:seedDB()
[ '65:0' ] = 'wallsign-ladder',
[ '66:0' ] = 'rail',
[ '67:0' ] = 'stairs',
[ '68:0' ] = 'wallsign',
[ '68:0' ] = 'wallsign-ladder',
[ '69:0' ] = 'lever',
[ '71:0' ] = 'door',
[ '75:0' ] = 'torch',
@ -295,6 +252,7 @@ function standardBlockDB:seedDB()
[ '78:0' ] = 'flatten',
[ '81:0' ] = 'flatten',
[ '83:0' ] = 'flatten',
[ '84:0' ] = 'flatten', -- jukebox
[ '86:0' ] = 'pumpkin',
[ '90:0' ] = 'air',
[ '91:0' ] = 'pumpkin',
@ -312,6 +270,7 @@ function standardBlockDB:seedDB()
[ '115:0' ] = 'flatten',
[ '117:0' ] = 'flatten',
[ '118:0' ] = 'cauldron',
[ '120:0' ] = 'flatten', -- end portal
[ '126:0' ] = 'slab',
[ '126:1' ] = 'slab',
[ '126:2' ] = 'slab',
@ -339,7 +298,7 @@ function standardBlockDB:seedDB()
[ '155:2' ] = 'quartz-pillar',
[ '156:0' ] = 'stairs',
[ '157:0' ] = 'adp-rail',
[ '158:0' ] = 'hopper',
[ '158:0' ] = 'dispenser',
[ '161:0' ] = 'leaves',
[ '161:1' ] = 'leaves',
[ '162:0' ] = 'wood',
@ -347,15 +306,15 @@ function standardBlockDB:seedDB()
[ '163:0' ] = 'stairs',
[ '164:0' ] = 'stairs',
[ '167:0' ] = 'trapdoor',
[ '170:0' ] = 'quartz-pillar', -- hay bale
[ '170:0' ] = 'hay-bale', -- hay bale
[ '175:0' ] = 'largeplant',
[ '175:1' ] = 'largeplant',
[ '175:2' ] = 'largeplant', -- double tallgrass - an alternative would be to use grass as the bottom part, bonemeal as top part
[ '175:3' ] = 'largeplant',
[ '175:4' ] = 'largeplant',
[ '175:5' ] = 'largeplant',
[ '176:0' ] = 'banner',
[ '177:0' ] = 'wall_banner',
[ '176:0' ] = 'signpost',
[ '177:0' ] = 'wallsign-ladder',
[ '178:0' ] = 'truncate',
[ '180:0' ] = 'stairs',
[ '182:0' ] = 'slab',
@ -369,7 +328,7 @@ function standardBlockDB:seedDB()
[ '195:0' ] = 'door',
[ '196:0' ] = 'door',
[ '197:0' ] = 'door',
[ '198:0' ] = 'flatten',
[ '198:0' ] = 'end_rod', -- end rod
[ '205:0' ] = 'slab',
[ '210:0' ] = 'flatten',
[ '355:0' ] = 'bed',
@ -377,7 +336,7 @@ function standardBlockDB:seedDB()
[ '404:0' ] = 'comparator',
}
self.dirty = true
self:flush()
-- self:flush()
end
--[[-- BlockTypeDB --]]--
@ -415,7 +374,8 @@ function blockTypeDB:addTemp(blockType, subs)
odmg = sub[1],
sid = sub[2],
sdmg = sub[3],
dir = sub[4]
dir = sub[4],
extra = sub[5]
})
end
self.dirty = true
@ -512,6 +472,11 @@ function blockTypeDB:seedDB()
{ 3, nil, 2, 'north-south-block' },
{ 4, nil, 2, 'east-west-block' }, -- should be east-west-block
})
blockTypeDB:addTemp('hay-bale', {
{ 0, nil, 0 },
{ 4, nil, 0, 'east-west-block' }, -- should be east-west-block
{ 8, nil, 0, 'north-south-block' },
})
blockTypeDB:addTemp('button', {
{ 1, nil, 0, 'west-block' },
{ 2, nil, 0, 'east-block' },
@ -526,14 +491,23 @@ function blockTypeDB:seedDB()
{ 3, nil, 0 },
})
blockTypeDB:addTemp('dispenser', {
{ 0, nil, 0 },
{ 1, nil, 0 },
{ 0, nil, 0, 'wrench-down' },
{ 1, nil, 0, 'wrench-up' },
{ 2, nil, 0, 'south' },
{ 3, nil, 0, 'north' },
{ 4, nil, 0, 'east' },
{ 5, nil, 0, 'west' },
{ 9, nil, 0 },
})
blockTypeDB:addTemp('end_rod', {
{ 0, nil, 0, 'wrench-down' },
{ 1, nil, 0, 'wrench-up' },
{ 2, nil, 0, 'south-block-flip' },
{ 3, nil, 0, 'north-block-flip' },
{ 4, nil, 0, 'east-block-flip' },
{ 5, nil, 0, 'west-block-flip' },
{ 9, nil, 0 },
})
blockTypeDB:addTemp('hopper', {
{ 0, nil, 0 },
{ 1, nil, 0 },
@ -541,6 +515,7 @@ function blockTypeDB:seedDB()
{ 3, nil, 0, 'north-block' },
{ 4, nil, 0, 'east-block' },
{ 5, nil, 0, 'west-block' },
{ 8, nil, 0 },
{ 9, nil, 0 },
{ 10, nil, 0 },
{ 11, nil, 0, 'south-block' },
@ -583,22 +558,22 @@ function blockTypeDB:seedDB()
{ 13, nil, 0, 'south' },
})
blockTypeDB:addTemp('signpost', {
{ 0, nil, 0, 'south' },
{ 1, nil, 0, 'south' },
{ 2, nil, 0, 'south' },
{ 3, nil, 0, 'south' },
{ 4, nil, 0, 'west' },
{ 5, nil, 0, 'west' },
{ 6, nil, 0, 'west' },
{ 7, nil, 0, 'west' },
{ 8, nil, 0, 'north' },
{ 9, nil, 0, 'north' },
{ 10, nil, 0, 'north' },
{ 11, nil, 0, 'north' },
{ 12, nil, 0, 'east' },
{ 13, nil, 0, 'east' },
{ 14, nil, 0, 'east' },
{ 15, nil, 0, 'east' },
{ 0, nil, 0, 'north' },
{ 1, nil, 0, 'north', { facing = 1 } },
{ 2, nil, 0, 'north', { facing = 2 } },
{ 3, nil, 0, 'north', { facing = 3 } },
{ 4, nil, 0, 'east' },
{ 5, nil, 0, 'east', { facing = 1 } },
{ 6, nil, 0, 'east', { facing = 2 } },
{ 7, nil, 0, 'east', { facing = 3 } },
{ 8, nil, 0, 'south' },
{ 9, nil, 0, 'south', { facing = 1 } },
{ 10, nil, 0, 'south', { facing = 2 } },
{ 11, nil, 0, 'south', { facing = 3 } },
{ 12, nil, 0, 'west' },
{ 13, nil, 0, 'west', { facing = 1 } },
{ 14, nil, 0, 'west', { facing = 2 } },
{ 15, nil, 0, 'west', { facing = 3 } },
})
blockTypeDB:addTemp('vine', {
{ 0, nil, 0 },
@ -684,18 +659,12 @@ function blockTypeDB:seedDB()
})
blockTypeDB:addTemp('wallsign-ladder', {
{ 0, nil, 0 },
{ 1, nil, 0 },
{ 2, nil, 0, 'south-block' },
{ 3, nil, 0, 'north-block' },
{ 4, nil, 0, 'east-block' },
{ 5, nil, 0, 'west-block' },
})
blockTypeDB:addTemp('wallsign', {
{ 0, nil, 0 },
{ 2, 'minecraft:sign', 0, 'south-block' },
{ 3, 'minecraft:sign', 0, 'north-block' },
{ 4, 'minecraft:sign', 0, 'east-block' },
{ 5, 'minecraft:sign', 0, 'west-block' },
})
blockTypeDB:addTemp('chest-furnace', {
{ 0, nil, 0 },
{ 2, nil, 0, 'south' },
@ -817,32 +786,6 @@ function blockTypeDB:seedDB()
{ 14,'minecraft:air', 0 },
{ 15,'minecraft:air', 0 },
})
blockTypeDB:addTemp('banner', {
{ 0, nil, 0, 'north' },
{ 1, nil, 0, 'east' },
{ 2, nil, 0, 'east' },
{ 3, nil, 0, 'east' },
{ 4, nil, 0, 'east' },
{ 5, nil, 0, 'east' },
{ 6, nil, 0, 'east' },
{ 7, nil, 0, 'east' },
{ 8, nil, 0, 'south' },
{ 9, nil, 0, 'west' },
{ 10, nil, 0, 'west' },
{ 11, nil, 0, 'west' },
{ 12, nil, 0, 'west' },
{ 13, nil, 0, 'west' },
{ 14, nil, 0, 'west' },
{ 15, nil, 0, 'west' },
})
blockTypeDB:addTemp('wall_banner', {
{ 0, nil, 0 },
{ 1, nil, 0 },
{ 2, nil, 0, 'south-block' },
{ 3, nil, 0, 'north-block' },
{ 4, nil, 0, 'east-block' },
{ 5, nil, 0, 'west-block' },
})
blockTypeDB:addTemp('cocoa', {
{ 0, nil, 0, 'south-block' },
{ 1, nil, 0, 'west-block' },
@ -858,7 +801,7 @@ function blockTypeDB:seedDB()
{ 11, nil, 0, 'east-block' },
})
self.dirty = true
self:flush()
-- self:flush()
end
local Blocks = class()
@ -866,10 +809,12 @@ function Blocks:init(args)
Util.merge(self, args)
self.blockDB = blockDB
self.nameDB = nameDB
blockDB:load(self.dir)
standardBlockDB:load(self.dir)
blockTypeDB:load(self.dir)
nameDB:load(self.dir, blockDB)
placementDB:load(self.dir, standardBlockDB, blockTypeDB)
end
@ -878,7 +823,7 @@ function Blocks:getRealBlock(id, dmg)
local p = placementDB:get({id, dmg})
if p then
return { id = p.sid, dmg = p.sdmg, direction = p.direction }
return { id = p.sid, dmg = p.sdmg, direction = p.direction, extra = p.extra }
end
local b = blockDB:get({id, dmg})
@ -886,6 +831,11 @@ function Blocks:getRealBlock(id, dmg)
return { id = b.strId, dmg = b.dmg }
end
b = blockDB:get({id, 0})
if b then
return { id = b.strId, dmg = b.dmg }
end
return { id = id, dmg = dmg }
end

View File

@ -1,65 +1,124 @@
local class = require('class')
local TableDB = require('tableDB')
local Peripheral = require('peripheral')
local ChestProvider = class()
local keys = Util.transpose({
'damage',
'displayName',
'maxCount',
'maxDamage',
'name',
'nbtHash',
})
function ChestProvider:init(args)
args = args or { }
local defaults = {
items = { },
name = 'chest',
direction = 'up',
wrapSide = 'bottom',
}
Util.merge(self, defaults)
Util.merge(self, args)
self.items = { } -- consolidated item info
self.cache = { }
self.name = 'chest'
self.direction = args.direction or 'up'
self.wrapSide = args.wrapSide or 'bottom'
self.p = peripheral.wrap(self.wrapSide)
local chest = Peripheral.getBySide(self.wrapSide)
if chest then
Util.merge(self, chest)
end
if not self.itemInfoDB then
self.itemInfoDB = TableDB({
fileName = 'items.db'
})
self.itemInfoDB:load()
end
end
function ChestProvider:isValid()
return self.p and self.p.list
return not not self.list
end
function ChestProvider:refresh()
if self.p then
self.items = { }
for k,s in pairs(self.p.list()) do
function ChestProvider:getCachedItemDetails(item, k)
local key = table.concat({ item.name, item.damage, item.nbtHash }, ':')
local key = s.name .. ':' .. s.damage
local entry = self.items[key]
if not entry then
entry = self.cache[key]
if not entry then
local meta = self.p.getItemMeta(k) -- slow method.. cache for speed
if meta then
entry = {
id = s.name,
dmg = s.damage,
name = meta.displayName,
max_size = meta.maxCount,
}
self.cache[key] = entry
end
end
if entry then
entry = Util.shallowCopy(entry)
self.items[key] = entry
entry.qty = 0
end
local detail = self.itemInfoDB:get(key)
if not detail then
pcall(function() detail = self.getItemMeta(k) end)
if not detail then
return
end
if detail.name ~= item.name then
return
end
if detail.maxDamage and detail.maxDamage > 0 and detail.damage > 0 then
detail.displayName = detail.displayName .. ' (damaged)'
end
for _,k in ipairs(Util.keys(detail)) do
if not keys[k] then
detail[k] = nil
end
end
self.itemInfoDB:add(key, detail)
end
if detail then
return Util.shallowCopy(detail)
end
end
function ChestProvider:refresh(throttle)
return self:listItems(throttle)
end
-- provide a consolidated list of items
function ChestProvider:listItems(throttle)
self.cache = { }
local items = { }
throttle = throttle or Util.throttle()
for k,v in pairs(self.list()) do
local key = table.concat({ v.name, v.damage, v.nbtHash }, ':')
local entry = self.cache[key]
if not entry then
entry = self:getCachedItemDetails(v, k)
if entry then
entry.qty = entry.qty + s.count
entry.dmg = entry.damage
entry.id = entry.name
entry.count = 0
entry.display_name = entry.displayName
entry.max_size = entry.maxCount
entry.nbt_hash = entry.nbtHash
entry.lname = entry.displayName:lower()
self.cache[key] = entry
table.insert(items, entry)
end
end
if entry then
entry.count = entry.count + v.count
entry.qty = entry.count
end
throttle()
end
return self.items
self.itemInfoDB:flush()
return items
end
function ChestProvider:getItemInfo(id, dmg)
for key,item in pairs(self.items) do
if item.id == id and item.dmg == dmg then
return item
end
function ChestProvider:getItemInfo(id, dmg, nbtHash)
if not self.cache then
self:listItems()
end
local key = table.concat({ id, dmg, nbtHash }, ':')
return self.cache[key]
end
function ChestProvider:craft(id, dmg, qty)
@ -69,31 +128,25 @@ function ChestProvider:craftItems(items)
end
function ChestProvider:provide(item, qty, slot)
if self.p then
local stacks = self.p.list()
for key,stack in pairs(stacks) do
if stack.name == item.id and stack.damage == item.dmg then
local amount = math.min(qty, stack.count)
self.p.pushItems(self.direction, key, amount, slot)
qty = qty - amount
if qty <= 0 then
break
end
local stacks = self.list()
for key,stack in pairs(stacks) do
if stack.name == item.id and stack.damage == item.dmg then
local amount = math.min(qty, stack.count)
self.pushItems(self.direction, key, amount, slot)
qty = qty - amount
if qty <= 0 then
break
end
end
end
end
function ChestProvider:extract(slot, qty)
if self.p then
self.p.pushItems(self.direction, slot, qty)
end
self.pushItems(self.direction, slot, qty)
end
function ChestProvider:insert(slot, qty)
if self.p then
self.p.pullItems(self.direction, slot, qty)
end
self.pullItems(self.direction, slot, qty)
end
return ChestProvider

View File

@ -139,17 +139,25 @@ function Event.addThread(fn)
end
function Event.pullEvents(...)
Process:addThread(_pullEvents)
local routines = { ... }
if #routines > 0 then
Process:addThread(_pullEvents)
for _, routine in ipairs(routines) do
Process:addThread(routine)
end
end
while true do
local e = Process:pullEvent()
if exitPullEvents or e == 'terminate' then
break
end
end
else
while true do
local e = Process:pullEvent()
if exitPullEvents or e == 'terminate' then
break
local e = { os.pullEvent() }
Event.processEvent(e)
if exitPullEvents or e == 'terminate' then
break
end
end
end
end

View File

@ -205,7 +205,7 @@ end
function json.decodeFromFile(path)
local file = assert(fs.open(path, "r"))
local decoded = decode(file.readAll())
local decoded = json.decode(file.readAll())
file.close()
return decoded
end

View File

@ -51,13 +51,15 @@ function Peripheral.addDevice(deviceList, side)
end
deviceList[name] = peripheral.wrap(side)
Util.merge(deviceList[name], {
name = name,
type = ptype,
side = side,
})
if deviceList[name] then
Util.merge(deviceList[name], {
name = name,
type = ptype,
side = side,
})
return deviceList[name]
return deviceList[name]
end
end
function Peripheral.getBySide(side)

View File

@ -62,13 +62,6 @@ function RefinedProvider:getCachedItemDetails(item)
end
detail.lname = detail.displayName:lower()
-- backwards capability
detail.dmg = detail.damage
detail.id = detail.name
detail.qty = detail.count
detail.display_name = detail.displayName
detail.nbtHash = item.nbtHash
local t = { }
for _,key in pairs(keys) do
t[key] = detail[key]
@ -76,8 +69,6 @@ function RefinedProvider:getCachedItemDetails(item)
detail = t
self.itemInfoDB:add(key, detail)
os.sleep(0) -- prevent timeout on large inventories
end
end
if detail then

View File

@ -148,20 +148,18 @@ function Schematic:parse(a, h, containsName, spinner)
end
-- end http://www.computercraft.info/forums2/index.php?/topic/1949-turtle-schematic-file-builder/
function Schematic:copyBlocks(iblocks, oblocks, spinner)
function Schematic:copyBlocks(iblocks, oblocks, throttle)
for k,b in ipairs(iblocks) do
oblocks[k] = Util.shallowCopy(b)
if spinner then
if (k % 1000) == 0 then
spinner:spin()
end
if (k % 1000) == 0 then
throttle()
end
end
end
function Schematic:reload()
function Schematic:reload(throttle)
self.blocks = { }
self:copyBlocks(self.originalBlocks, self.blocks)
self:copyBlocks(self.originalBlocks, self.blocks, throttle)
for _,ri in pairs(self.rowIndex) do
ri.loaded = false
@ -283,7 +281,7 @@ function Schematic:loadpass(fh, spinner)
self:assignDamages(spinner)
self.damages = nil
self:copyBlocks(self.blocks, self.originalBlocks, spinner)
self:copyBlocks(self.blocks, self.originalBlocks, function() spinner:spin() end)
spinner:stop()
end
@ -488,6 +486,37 @@ function Schematic:bestSide(b, chains, ...)
})
end
function Schematic:bestFlipSide(b, chains)
-- If there is a block to place this one against
local directions = {
[ 'east-block-flip' ] = 'east',
[ 'west-block-flip' ] = 'west',
[ 'north-block-flip' ] = 'north',
[ 'south-block-flip' ] = 'south',
}
local d = directions[b.direction]
local hi = turtle.getHeadingInfo(d)
local _, fb = self:findIndexAt(b.x + hi.xd, b.z + hi.zd, b.y)
if fb then
self:addPlacementChain(chains, {
{ 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
})
b.direction = d .. '-block'
else
self:addPlacementChain(chains, {
{ 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
{ x = b.x + hi.xd, z = b.z + hi.zd, y = b.y }, -- block we are placing against
})
b.direction = turtle.getHeadingInfo((hi.heading + 2) % 4).direction .. '-block'
end
end
function Schematic:bestOfTwoSides(b, chains, side1, side2) -- could be better
local sb
@ -617,12 +646,23 @@ function Schematic:determineBlockPlacement(y)
[ 'west-block-vine' ] = 'west-block',
[ 'north-block-vine' ] = 'north-block'
}
local flipDirections = {
[ 'east-block-flip' ] = 'east-block',
[ 'south-block-flip' ] = 'south-block',
[ 'west-block-flip' ] = 'west-block',
[ 'north-block-flip' ] = 'north-block'
}
local dirtyBlocks = {}
local dirtyBlocks2 = {}
local chains = {}
local ri = self.rowIndex[y]
if not ri then
ri = { s = -1, e = -2 }
self.rowIndex[y] = ri
end
for k = ri.s, ri.e do
local b = self.blocks[k]
local d = b.direction
@ -757,6 +797,8 @@ function Schematic:determineBlockPlacement(y)
b.heading = (turtle.getHeadingInfo(sd[1]).heading + 2) % 4
end
end
elseif flipDirections[d] then
self:bestFlipSide(b, chains)
end
if blockDirections[d] then
@ -790,6 +832,7 @@ function Schematic:determineBlockPlacement(y)
self:setPlacementOrder(spinner, chains)
local plane = self:optimizeRoute(spinner, y)
term.clearLine()
spinner:stop()
for k,b in ipairs(plane) do

View File

@ -331,7 +331,7 @@ function Manager:click(button, x, y)
if button == 1 then
local c = os.clock()
if self.doubleClickTimer and (c - self.doubleClickTimer < 1) and
if self.doubleClickTimer and (c - self.doubleClickTimer < 1.5) and
self.doubleClickX == x and self.doubleClickY == y and
self.doubleClickElement == clickEvent.element then
button = 3
@ -1076,8 +1076,8 @@ end
UI.TransitionSlideLeft = class()
UI.TransitionSlideLeft.defaults = {
UIElement = 'TransitionSlideLeft',
ticks = 12,
easing = 'outBounce',
ticks = 4,
easing = 'outQuint',
}
function UI.TransitionSlideLeft:init(args)
local defaults = UI:getDefaults(UI.TransitionSlideLeft, args)
@ -1116,11 +1116,11 @@ end
UI.TransitionSlideRight = class()
UI.TransitionSlideRight.defaults = {
UIElement = 'TransitionSlideRight',
ticks = 12,
easing = 'outBounce',
ticks = 4,
easing = 'outQuint',
}
function UI.TransitionSlideRight:init(args)
local defaults = UI:getDefaults(UI.TransitionSlideLeft, args)
local defaults = UI:getDefaults(UI.TransitionSlideRight, args)
UI.setProperties(self, defaults)
self.pos = { x = self.x }
@ -2520,6 +2520,72 @@ function UI.Notification:display(value, timeout)
end)
end
--[[-- Throttle --]]--
UI.Throttle = class(UI.Window)
UI.Throttle.defaults = {
UIElement = 'Throttle',
backgroundColor = colors.gray,
height = 6,
width = 10,
timeout = .095,
ctr = 0,
image = {
' //) (O )~@ &~&-( ?Q ',
' //) (O )- @ \-( ?) && ',
' //) (O ), @ \-(?) && ',
' //) (O ). @ \-d ) (@ '
}
}
function UI.Throttle:init(args)
local defaults = UI:getDefaults(UI.Throttle, args)
UI.Window.init(self, defaults)
end
function UI.Throttle:setParent()
self.x = math.ceil((self.parent.width - self.width) / 2)
self.y = math.ceil((self.parent.height - self.height) / 2)
UI.Window.setParent(self)
end
function UI.Throttle:enable()
self.enabled = false
end
function UI.Throttle:disable()
if self.canvas then
self.enabled = false
self.canvas:removeLayer()
self.canvas = nil
self.c = nil
end
end
function UI.Throttle:update()
local cc = os.clock()
if not self.c then
self.c = cc
elseif cc > self.c + self.timeout then
os.sleep(0)
self.c = os.clock()
self.enabled = true
if not self.canvas then
self.canvas = UI.term.canvas:addLayer(self, self.backgroundColor, colors.cyan)
self.canvas:setVisible(true)
self:clear(colors.cyan)
end
local image = self.image[self.ctr + 1]
local width = self.width - 2
for i = 0, #self.image do
self:write(2, i + 2, image:sub(width * i + 1, width * i + width), colors.black, colors.white)
end
self.ctr = (self.ctr + 1) % #self.image
self:sync()
end
end
--[[-- GridLayout --]]--
UI.GridLayout = class(UI.Window)
UI.GridLayout.defaults = {

View File

@ -27,8 +27,8 @@ function Util.throttle(fn)
return function(...)
local nts = os.clock()
if nts > ts + timeout then
ts = nts
os.sleep(0)
ts = os.clock()
if fn then
fn(...)
end

View File

@ -160,10 +160,14 @@ end
function page:runScript(scriptName)
if self.turtle then
self.notification:info('Connecting')
self:sync()
local cmd = string.format('Script %d %s', self.turtle.id, scriptName)
local ot = term.redirect(nullTerm)
pcall(function() shell.run(cmd) end)
term.redirect(ot)
self.notification:success('Sent')
end
end

View File

@ -33,13 +33,12 @@ local schematic = Schematic()
local blocks = Blocks({ dir = BUILDER_DIR })
local Builder = {
version = '1.70',
ccVersion = nil,
version = '1.71',
slots = { },
index = 1,
mode = 'build',
fuelItem = { id = 'minecraft:coal', dmg = 0 },
resourceSlots = 15,
resourceSlots = 14,
facing = 'south',
confirmFacing = false,
}
@ -49,16 +48,6 @@ local pistonFacings
--[[-- 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()
@ -71,222 +60,66 @@ 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:double_wooden_slab:5" ] = {
sdmg = 5,
sid = "minecraft:planks",
dmg = 5,
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:2" ] = {
sdmg = 0,
sid = "minecraft:planks",
dmg = 2,
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:4" ] = {
sdmg = 0,
sid = "minecraft:brick_block",
dmg = 4,
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",
},
[ "minecraft:redstone_wire:0" ] = "minecraft:redstone:0",
[ "minecraft:wall_sign:0" ] = "minecraft:sign:0",
[ "minecraft:standing_sign:0" ] = "minecraft:sign:0",
[ "minecraft:potatoes:0" ] = "minecraft:potato:0",
[ "minecraft:unlit_redstone_torch:0" ] = "minecraft:redstone_torch:0",
[ "minecraft:powered_repeater:0" ] = "minecraft:repeater:0",
[ "minecraft:unpowered_repeater:0" ] = "minecraft:repeater:0",
[ "minecraft:carrots:0" ] = "minecraft:carrot:0",
[ "minecraft:cocoa:0" ] = "minecraft:dye:3",
[ "minecraft:unpowered_comparator:0" ] = "minecraft:comparator:0",
[ "minecraft:powered_comparator:0" ] = "minecraft:comparator:0",
[ "minecraft:piston_head:0" ] = "minecraft:air:0",
[ "minecraft:double_wooden_slab:0" ] = "minecraft:planks:0",
[ "minecraft:double_wooden_slab:1" ] = "minecraft:planks:1",
[ "minecraft:double_wooden_slab:2" ] = "minecraft:planks:2",
[ "minecraft:double_wooden_slab:3" ] = "minecraft:planks:3",
[ "minecraft:double_wooden_slab:4" ] = "minecraft:planks:4",
[ "minecraft:double_wooden_slab:5" ] = "minecraft:planks:5",
[ "minecraft:lit_redstone_lamp:0" ] = "minecraft:redstone_lamp:0",
[ "minecraft:double_stone_slab:1" ] = "minecraft:sandstone:0",
[ "minecraft:double_stone_slab:2" ] = "minecraft:planks:0",
[ "minecraft:double_stone_slab:3" ] = "minecraft:cobblestone:0",
[ "minecraft:double_stone_slab:4" ] = "minecraft:brick_block:0",
[ "minecraft:double_stone_slab:5" ] = "minecraft:stonebrick:0",
[ "minecraft:double_stone_slab:6" ] = "minecraft:nether_brick:0",
[ "minecraft:double_stone_slab:7" ] = "minecraft:quartz_block:0",
[ "minecraft:double_stone_slab:9" ] = "minecraft:sandstone:2",
[ "minecraft:double_stone_slab2:0" ] = "minecraft:sandstone:0",
[ "minecraft:stone_slab:2" ] = "minecraft:wooden_slab:0",
[ "minecraft:wheat:0" ] = "minecraft:wheat_seeds:0",
[ "minecraft:flowing_water:0" ] = "minecraft:air:0",
[ "minecraft:lit_furnace:0" ] = "minecraft:furnace:0",
[ "minecraft:wall_banner:0" ] = "minecraft:banner:0",
[ "minecraft:standing_banner:0" ] = "minecraft:banner:0",
[ "minecraft:tripwire:0" ] = "minecraft:string:0",
}
self.dirty = true
self:flush()
end
function subDB:add(s)
TableDB.add(self, { s.id, s.dmg}, s)
TableDB.add(self, { s.id, s.dmg }, table.concat({ s.sid, s.sdmg }, ':'))
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:extract(s)
local id, dmg = s:match('(.+):(%d+)')
return id, tonumber(dmg)
end
function subDB:getSubstitutedItem(id, dmg)
local sub = TableDB.get(self, { id, dmg })
if sub then
return { id = sub.sid, dmg = sub.sdmg }
id, dmg = self:extract(sub)
end
return { id = id, dmg = dmg }
end
@ -294,8 +127,10 @@ 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
local id, dmg = self:extract(v)
if id == sid and dmg == sdmg then
id, dmg = self:extract(k)
t[k] = { id = id, dmg = dmg, sid = sid, sdmg = sdmg }
end
end
return t
@ -304,14 +139,6 @@ 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)
@ -348,8 +175,8 @@ function UI.Spinner:spin(text)
end
term.write(str)
self.spinIndex = self.spinIndex + 1
self.c = cc
os.sleep(0)
self.c = os.clock()
end
end
@ -395,7 +222,7 @@ function Builder:getBlockCounts()
block.need = 0
blocks[key] = block
end
blocks[key].need = blocks[key].need + 1
block.need = block.need + 1
end
end
@ -532,15 +359,14 @@ function Builder:getGenericSupplyList(blockIndex)
for _,s in pairs(slots) do
if s.id then
s.name = blocks.blockDB:getName(s.id, s.dmg)
s.name = blocks.nameDB:getName(s.id, s.dmg)
end
end
return slots, lastBlock
end
function Builder:substituteBlocks()
local throttle = Util.throttle()
function Builder:substituteBlocks(throttle)
for _,b in pairs(schematic.blocks) do
@ -550,13 +376,12 @@ function Builder:substituteBlocks()
b.id = pb.id
b.dmg = pb.dmg
b.direction = pb.direction
b.extra = pb.extra
local sub = subDB:get({ b.id, b.dmg })
if sub then
b.id = sub.sid
b.dmg = sub.sdmg
b.id, b.dmg = subDB:extract(sub)
end
throttle()
end
end
@ -619,7 +444,7 @@ function Builder:getSupplies()
if s.need > 0 then
local item = self.itemProvider:getItemInfo(s.id, s.dmg)
if item then
s.name = item.name
s.name = item.display_name
local qty = math.min(s.need - s.qty, item.qty)
@ -635,7 +460,7 @@ function Builder:getSupplies()
s.qty = turtle.getItemCount(s.index)
end
else
s.name = blocks.blockDB:getName(s.id, s.dmg)
s.name = blocks.nameDB:getName(s.id, s.dmg)
end
end
if s.qty < s.need then
@ -730,7 +555,6 @@ function Builder:inAirDropoff()
return true
end
end
end
function Builder:inAirResupply()
@ -863,10 +687,10 @@ end
function Builder:getWrenchSlot()
local wrench = subDB:getSubstitutedItem('SubstituteAWrench', 0)
return Builder:selectItem(wrench.id, wrench.dmg)
end
-- figure out our orientation in the world
function Builder:getTurtleFacing()
if os.getVersion() == 1.8 then
@ -889,7 +713,7 @@ function Builder:getTurtleFacing()
return Builder.facing
end
function Builder:wrenchBlock(side, facing)
function Builder:wrenchBlock(side, facing, cache)
local s = Builder:getWrenchSlot()
@ -899,14 +723,16 @@ function Builder:wrenchBlock(side, facing)
end
local key = turtle.point.heading .. '-' .. facing
local count = pistonFacings[side][key]
if cache then
local count = cache[side][key]
if count then
turtle.select(s.index)
for i = 1,count do
turtle.getAction(side).place()
if count then
turtle.select(s.index)
for i = 1,count do
turtle.getAction(side).place()
end
return true
end
return true
end
local directions = {
@ -928,10 +754,11 @@ function Builder:wrenchBlock(side, facing)
print('determining wrench count')
for i = 1, 6 do
local _, bi = turtle.getAction(side).inspect()
local pistonFacing = directions[bi.metadata]
if facing == pistonFacing then
pistonFacings[side][key] = count
if facing == directions[bi.metadata] then
if cache then
cache[side][key] = count
end
return true
end
count = count + 1
@ -941,6 +768,43 @@ function Builder:wrenchBlock(side, facing)
return false
end
function Builder:rotateBlock(side, facing)
local s = Builder:getWrenchSlot()
if not s then
b.needResupply = true
return false
end
for i = 1, facing do
turtle.getAction(side).place()
end
return true
--[[
local origFacing
while true do
local _, bi = turtle.getAction(side).inspect()
-- spin until it repeats
if not origFacing then
origFacing = bi.metadata
elseif bi.metadata == origFacing then
return false
end
if facing == bi.metadata then
return true
end
turtle.getAction(side).place()
end
return false
]]--
end
-- place piston, wrench piston to face downward, extend, remove piston
function Builder:placePiston(b)
@ -957,7 +821,7 @@ function Builder:placePiston(b)
return
end
local success = self:wrenchBlock('forward', 'down') --wrench piston to point downwards
local success = self:wrenchBlock('forward', 'down', pistonFacings) --wrench piston to point downwards
rs.setOutput('front', true)
os.sleep(.25)
@ -1096,7 +960,7 @@ function Builder:placeDirectionalBlock(b, slot, travelPlane)
end
local horizontalDirections = {
[ 'east-west-block' ] = { 'east', 'west' },
[ 'east-west-block' ] = { 'east', 'west' },
[ 'north-south-block' ] = { 'north', 'south' },
}
if horizontalDirections[d] then
@ -1118,10 +982,10 @@ function Builder:placeDirectionalBlock(b, slot, travelPlane)
local pistonDirections = {
[ 'piston-north' ] = 'north',
[ 'piston-south' ] = 'south',
[ 'piston-west' ] = 'west',
[ 'piston-east' ] = 'east',
[ 'piston-west' ] = 'west',
[ 'piston-east' ] = 'east',
[ 'piston-down' ] = 'down',
[ 'piston-up' ] = 'up',
[ 'piston-up' ] = 'up',
}
if pistonDirections[d] then
@ -1151,7 +1015,29 @@ function Builder:placeDirectionalBlock(b, slot, travelPlane)
end
if self:placeDown(slot) then
b.placed = self:wrenchBlock('down', pistonDirections[d])
b.placed = self:wrenchBlock('down', pistonDirections[d], pistonFacings)
end
end
local wrenchDirections = {
[ 'wrench-down' ] = 'down',
[ 'wrench-up' ] = 'up',
}
if wrenchDirections[d] then
local ws = Builder:getWrenchSlot()
if not ws then
b.needResupply = true
-- a hopper may have eaten the piston
return false
end
self:gotoEx(b.x, b.z, b.y, nil, travelPlane)
if self:placeDown(slot) then
b.placed = self:wrenchBlock('down', wrenchDirections[d])
end
end
@ -1179,6 +1065,10 @@ function Builder:placeDirectionalBlock(b, slot, travelPlane)
b.placed = self:place(slot)
end
if b.extra then
self:rotateBlock('down', b.extra.facing)
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
@ -1190,9 +1080,9 @@ end
return b.placed
end
function Builder:reloadSchematic()
schematic:reload()
self:substituteBlocks()
function Builder:reloadSchematic(throttle)
schematic:reload(throttle)
self:substituteBlocks(throttle)
end
function Builder:log(...)
@ -1424,7 +1314,7 @@ selectSubstitutionPage = UI.Page({
{ heading = 'id', key = 'id' },
{ heading = 'dmg', key = 'dmg' },
},
sortColumn = 'odmg',
sortColumn = 'id',
height = UI.term.height-1,
autospace = true,
y = 2,
@ -1451,32 +1341,33 @@ function selectSubstitutionPage:eventHandler(event)
end
--[[-- substitutionPage --]]--
substitutionPage = UI.Page({
substitutionPage = UI.Page {
backgroundColor = colors.gray,
titleBar = UI.TitleBar({
titleBar = UI.TitleBar {
previousPage = true,
title = 'Substitute a block'
}),
menuBar = UI.MenuBar({
},
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' },
},
}),
info = UI.Window({ y = 4, width = UI.term.width, height = 3 }),
grid = UI.ScrollingGrid({
},
info = UI.Window { y = 4, width = UI.term.width, height = 3 },
grid = UI.ScrollingGrid {
columns = {
{ heading = 'Name', key = 'name', width = UI.term.width-9 },
{ heading = 'Name', key = 'display_name', width = UI.term.width-9 },
{ heading = 'Qty', key = 'fQty', width = 5 },
},
sortColumn = 'name',
height = UI.term.height-7,
y = 7,
}),
statusBar = UI.StatusBar()
})
},
throttle = UI.Throttle { },
statusBar = UI.StatusBar { }
}
substitutionPage.menuBar:add({
filterLabel = UI.Text({
@ -1493,10 +1384,10 @@ substitutionPage.menuBar:add({
function substitutionPage.info:draw()
local sub = self.parent.sub
local inName = blocks.blockDB:getName(sub.id, sub.dmg)
local inName = blocks.nameDB:getName(sub.id, sub.dmg)
local outName = ''
if sub.sid then
outName = blocks.blockDB:getName(sub.sid, sub.sdmg)
outName = blocks.nameDB:getName(sub.sid, sub.sdmg)
end
self:clear()
@ -1512,7 +1403,7 @@ function substitutionPage:enable()
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.lname = string.lower(item.display_name)
item.fQty = Util.toBytes(item.qty)
end
self.grid:update()
@ -1543,9 +1434,9 @@ function substitutionPage:eventHandler(event)
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()
if not blocks.nameDB:lookupName(event.selected.id, event.selected.dmg) then
blocks.nameDB:add({event.selected.id, event.selected.dmg}, event.selected.name)
blocks.nameDB:flush()
end
self:applySubstitute(event.selected.id, event.selected.dmg)
@ -1587,7 +1478,9 @@ function substitutionPage:eventHandler(event)
subDB:add(self.sub)
end
Builder:reloadSchematic()
self.throttle:enable()
Builder:reloadSchematic(function() self.throttle:update() end)
self.throttle:disable()
UI:setPage('listing')
elseif event.type == 'cancel' then
@ -1598,12 +1491,12 @@ function substitutionPage:eventHandler(event)
end
--[[-- SupplyPage --]]--
supplyPage = UI.Page({
titleBar = UI.TitleBar({
supplyPage = UI.Page {
titleBar = UI.TitleBar {
title = 'Waiting for supplies',
previousPage = 'start'
}),
menuBar = UI.MenuBar({
},
menuBar = UI.MenuBar {
y = 2,
buttons = {
--{ text = 'Refresh', event = 'refresh', help = 'Refresh inventory' },
@ -1611,8 +1504,8 @@ supplyPage = UI.Page({
{ text = 'Menu', event = 'menu', help = 'Return to main menu' },
{ text = 'Force Craft', event = 'craft', help = 'Request crafting (again)' },
}
}),
grid = UI.Grid({
},
grid = UI.Grid {
columns = {
{ heading = 'Name', key = 'name', width = UI.term.width - 7 },
{ heading = 'Need', key = 'need', width = 4 },
@ -1621,20 +1514,20 @@ supplyPage = UI.Page({
y = 3,
width = UI.term.width,
height = UI.term.height - 3
}),
statusBar = UI.StatusBar({
},
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)
@ -1675,7 +1568,7 @@ function supplyPage:enable()
self.statusBar:setValue('fuel',
string.format('Fuel: %dk', math.floor(turtle.getFuelLevel() / 1024)))
-- self.statusBar:setValue('block',
-- string.format('Block: %d', Builder.index))
-- string.format('Block: %d', Builder.index))
Event.addNamedTimer('supplyRefresh', 6, true, function()
if self.enabled then
@ -1737,7 +1630,7 @@ listingPage = UI.Page({
}),
grid = UI.ScrollingGrid({
columns = {
{ heading = 'Name', key = 'name', width = UI.term.width - 14 },
{ heading = 'Name', key = 'name', width = UI.term.width - 14 },
{ heading = 'Need', key = 'need', width = 5 },
{ heading = 'Have', key = 'qty', width = 5 },
},
@ -1756,8 +1649,8 @@ listingPage = UI.Page({
fullList = true
})
function listingPage:enable()
listingPage:refresh()
function listingPage:enable(throttle)
listingPage:refresh(throttle)
UI.Page.enable(self)
end
@ -1821,11 +1714,11 @@ function listingPage.grid:getRowTextColor(row, selected)
return UI.Grid:getRowTextColor(row, selected)
end
function listingPage:refresh()
function listingPage:refresh(throttle)
local supplyList = Builder:getBlockCounts()
Builder.itemProvider:refresh()
Builder.itemProvider:refresh(throttle)
for _,b in pairs(supplyList) do
if b.need > 0 then
@ -1834,19 +1727,22 @@ function listingPage:refresh()
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, b.id)
elseif not block.name and item.name then
blocks.blockDB:add(b.id, b.dmg, item.name, b.id)
blocks.nameDB:add({b.id, b.dmg}, item.display_name)
elseif not block.name and item.display_name then
blocks.nameDB:add({b.id, b.dmg}, item.display_name)
end
b.name = item.name
b.name = item.display_name
b.qty = item.qty
b.is_craftable = item.is_craftable
else
b.name = blocks.blockDB:getName(b.id, b.dmg)
b.name = blocks.nameDB:getName(b.id, b.dmg)
end
end
if throttle then
throttle()
end
end
blocks.blockDB:flush()
blocks.nameDB:flush()
if self.fullList then
self.grid:setValues(supplyList)
@ -1881,15 +1777,15 @@ function listingPage:manageBlock(selected)
end
--[[-- startPage --]]--
local startPage = UI.Page({
local startPage = UI.Page {
-- titleBar = UI.TitleBar({ title = 'Builder v' .. Builder.version }),
window = UI.Window({
window = UI.Window {
x = UI.term.width-16,
y = 2,
width = 16,
height = UI.term.height-2,
backgroundColor = colors.gray,
grid = UI.Grid({
grid = UI.Grid {
columns = {
{ heading = 'Name', key = 'name', width = 6 },
{ heading = 'Value', key = 'value', width = 7 },
@ -1903,9 +1799,9 @@ local startPage = UI.Page({
--autospace = true,
selectable = false,
backgroundColor = colors.gray
}),
}),
menu = UI.Menu({
},
},
menu = UI.Menu {
x = 2,
y = 4,
menuItems = {
@ -1917,12 +1813,13 @@ local startPage = UI.Page({
{ prompt = 'Begin', event = 'begin' },
{ prompt = 'Quit', event = 'quit' }
}
}),
},
throttle = UI.Throttle { },
accelerators = {
x = 'test',
q = 'quit'
}
})
}
function startPage:draw()
local fuel = turtle.getFuelLevel()
@ -2027,7 +1924,8 @@ function startPage:eventHandler(event)
-- as the current level's route may or may not have been
-- computed
Builder:dumpInventory()
UI:setPage('listing')
UI:setPage('listing', function() self.throttle:update() end)
self.throttle:disable()
elseif event.type == 'toggleMode' then
if Builder.mode == 'build' then
@ -2061,7 +1959,7 @@ function startPage:eventHandler(event)
turtle.status = 'thinking'
print('Reloading schematic')
Builder:reloadSchematic()
Builder:reloadSchematic(Util.throttle())
Builder:dumpInventory()
Builder:refuel()
@ -2095,13 +1993,6 @@ if #args < 1 then
error('supply file name')
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()
@ -2117,10 +2008,11 @@ subDB:load()
UI.term:reset()
turtle.status = 'reading'
print('Loading ' .. args[1])
print('Loading schematic')
schematic:load(args[1])
print('Substituting blocks')
Builder:substituteBlocks()
Builder:substituteBlocks(Util.throttle())
if not fs.exists(BUILDER_DIR) then
fs.makeDir(BUILDER_DIR)
@ -2146,4 +2038,3 @@ turtle.run(function()
end)
UI.term:reset()
--turtle.status = 'idle'

View File

@ -189,10 +189,10 @@ itemPage = UI.Page {
backgroundColor = colors.green
},
displayName = UI.Window {
x = 5, y = 3, width = UI.term.width - 10, height = 3,
x = 5, y = 2, width = UI.term.width - 10, height = 3,
},
form = UI.Form {
x = 4, y = 6, height = 10, rex = -4,
x = 4, y = 4, height = 10, rex = -4,
[1] = UI.TextEntry {
width = 7,
backgroundColor = colors.gray,

350
sys/apps/shapes.lua Normal file
View File

@ -0,0 +1,350 @@
require = requireInjector(getfenv(1))
local UI = require('ui')
local GPS = require('gps')
local Socket = require('socket')
multishell.setTitle(multishell.getCurrent(), 'Shapes')
local args = { ... }
local turtleId = args[1] or error('Supply turtle ID')
turtleId = tonumber(turtleId)
local script = [[
require = requireInjector(getfenv(1))
local GPS = require('gps')
local ChestProvider = require('chestProvider18')
local Point = require('point')
local itemProvider
function dumpInventory()
for i = 1, 16 do
local qty = turtle.getItemCount(i)
if qty > 0 then
itemProvider:insert(i, qty)
end
if turtle.getItemCount(i) ~= 0 then
print('Provider is full or missing - make space or replace')
print('Press enter to continue')
read()
end
end
turtle.select(1)
end
local function refuel()
while turtle.getFuelLevel() < 4000 do
print('Refueling')
turtle.select(1)
itemProvider:provide({ id = 'minecraft:coal', dmg = 0 }, 64, 1)
if turtle.getItemCount(1) == 0 then
print('Out of fuel, add fuel to chest/ME system')
turtle.status = 'waiting'
os.sleep(5)
else
turtle.refuel(64)
end
end
end
local function goto(pt)
while not turtle.gotoPoint(pt) do
print('stuck')
os.sleep(5)
end
end
local function pathTo(pt)
while not turtle.pathfind(pt) do
print('stuck')
os.sleep(5)
end
end
local function resupply()
if data.suppliesPt then
pathTo(data.suppliesPt)
itemProvider = ChestProvider({ direction = 'up', wrapSide = 'bottom' })
dumpInventory()
refuel()
end
end
local function makePlane(y)
local pt = { x = math.min(data.startPt.x, data.endPt.x),
ex = math.max(data.startPt.x, data.endPt.x),
z = math.min(data.startPt.z, data.endPt.z),
ez = math.max(data.startPt.z, data.endPt.z) }
local blocks = { }
for z = pt.z, pt.ez do
for x = pt.x, pt.ex do
table.insert(blocks, { x = x, y = y, z = z })
end
end
return blocks
end
local function optimizeRoute(plane, ptb)
local maxDistance = 99999999
local function getNearestNeighbor(p, pt, threshold)
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 function blockReady(b)
return not b.u
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 blockReady(b) then
getMoves(b, forward)
if moves <= threshold 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 blockReady(b) then
getMoves(b, backward)
if moves <= threshold 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 throttle = Util.throttle()
local t = { }
ptb.index = 0
local threshold = 0
for i = 1, #plane do
local b = getNearestNeighbor(plane, ptb, threshold)
table.insert(t, b)
throttle()
threshold = 1
end
return t
end
local function clear()
local pt = Util.shallowCopy(data.startPt)
pt.y = math.min(data.startPt.y, data.endPt.y)
pt.heading = 0
local osy = pt.y
local sy = osy + 1
local ey = math.max(data.startPt.y, data.endPt.y)
local firstPlane = true
resupply()
while true do
if sy > ey then
sy = ey
end
local plane = makePlane(sy)
plane = optimizeRoute(plane, pt)
if firstPlane then
turtle.pathfind(plane[1])
turtle.setPolicy(turtle.policies.digAttack)
firstPlane = false
end
for _,b in ipairs(plane) do
turtle.gotoPoint(b)
if sy < ey then
turtle.digUp()
end
if sy > osy then
turtle.digDown()
end
if turtle.abort then
break
end
end
if turtle.abort then
break
end
if sy + 1 >= ey then
break
end
sy = sy + 3
end
turtle.setPolicy(turtle.policies.none)
resupply()
end
turtle.run(function()
turtle.status = 'Clearing'
if turtle.enableGPS() then
local pt = Util.shallowCopy(turtle.point)
local s, m = pcall(clear)
pathTo(pt)
if not s and m then
error(m)
read()
end
end
end)
]]
local data = Util.readTable('/usr/config/shapes') or { }
local page = UI.Page {
titleBar = UI.TitleBar { title = 'Shapes' },
info = UI.Window { x = 5, y = 3, height = 1 },
startCoord = UI.Button { x = 2, y = 6, text = 'Start ', event = 'startCoord' },
endCoord = UI.Button { x = 2, y = 8, text = 'End ', event = 'endCoord' },
supplies = UI.Button { x = 2, y = 10, text = 'Supplies', event = 'supplies' },
cancel = UI.Button { rx = 2, ry = -2, text = 'Abort', event = 'cancel' },
begin = UI.Button { rx = -7, ry = -2, text = 'Begin', event = 'begin' },
accelerators = { q = 'quit' },
notification = UI.Notification(),
statusBar = UI.StatusBar(),
}
function page.info:draw()
local function size(a, b)
return (math.abs(a.x - b.x) + 1) *
(math.abs(a.y - b.y) + 1) *
(math.abs(a.z - b.z) + 1)
end
self:clear()
if not data.startPt then
self:write(1, 1, 'Set starting corner')
elseif not data.endPt then
self:write(1, 1, 'Set ending corner')
else
self:write(1, 1, 'Blocks: ' .. size(data.startPt, data.endPt))
end
end
function page:getPoint()
local pt = GPS.getPoint()
if not pt then
self.notification:error('GPS not available')
end
return pt
end
function page:runFunction(id, script)
self.notification:info('Connecting')
local fn, msg = loadstring(script, 'script')
if not fn then
self.notification:error('Error in script')
debug(msg)
return
end
local socket = Socket.connect(id, 161)
if not socket then
self.notification:error('Unable to connect')
return
end
socket:write({ type = 'script', args = script })
socket:close()
self.notification:success('Sent')
end
function page:eventHandler(event)
if event.type == 'startCoord' then
data.startPt = self:getPoint()
if data.startPt then
self.statusBar:setStatus('starting corner set')
Util.writeTable('/usr/config/shapes', data)
end
self:draw()
elseif event.type == 'endCoord' then
data.endPt = self:getPoint()
if data.endPt then
self.statusBar:setStatus('ending corner set')
Util.writeTable('/usr/config/shapes', data)
end
self:draw()
elseif event.type == 'supplies' then
data.suppliesPt = self:getPoint()
if data.suppliesPt then
self.statusBar:setStatus('supplies location set')
Util.writeTable('/usr/config/shapes', data)
end
elseif event.type == 'begin' then
if data.startPt and data.endPt then
local s = 'local data = ' .. textutils.serialize(data) .. script
self:runFunction(turtleId, s)
else
self.notification:error('Corners not set')
end
self.statusBar:setStatus('')
elseif event.type == 'cancel' then
self:runFunction(turtleId, 'turtle.abortAction()')
self.statusBar:setStatus('')
else
return UI.Page.eventHandler(self, event)
end
return true
end
UI:setPage(page)
UI:pullEvents()
UI.term:reset()

View File

@ -4,10 +4,14 @@ local Event = require('event')
local UI = require('ui')
local RefinedProvider = require('refinedProvider')
local MEProvider = require('meProvider')
local ChestProvider = require('chestProvider18')
local storage = RefinedProvider()
if not storage:isValid() then
storage = MEProvider()
if not storage:isValid() then
storage = ChestProvider()
end
end
if not storage:isValid() then
@ -106,7 +110,7 @@ local function uniqueKey(item)
end
function changedPage:refresh()
local t = storage:listItems('all')
local t = storage:listItems()
if not t or Util.empty(t) then
self:clear()

View File

@ -31,6 +31,7 @@ Logger.filter('modem_send', 'event', 'ui')
Logger.setWirelessLogging()
local __BUILDER_ID = 6
local itemInfoDB
local Builder = {
version = '1.70',
@ -116,7 +117,7 @@ function Builder:refuel()
self.itemProvider:provide(self.fuelItem, 64, 1)
if turtle.getItemCount(1) == 0 then
Builder:log('Out of fuel, add coal to chest/ME system')
turtle.setHeading(0)
--turtle.setHeading(0)
os.sleep(5)
else
turtle.refuel(64)
@ -160,6 +161,11 @@ function Builder:getSupplies()
if s.qty < s.need then
table.insert(t, s)
local name = s.name or s.id .. ':' .. s.dmg
local item = itemInfoDB:get({ s.id, s.dmg })
if item then
name = item.displayName
end
Builder:log('Need %d %s', s.need - s.qty, name)
end
end
@ -400,6 +406,12 @@ __BUILDER_ID = tonumber(args[1])
maxStackDB:load()
itemInfoDB = TableDB({
fileName = 'items.db'
})
itemInfoDB:load()
Builder.itemProvider = MEProvider({ direction = args[2] })
if not Builder.itemProvider:isValid() then
local sides = {

View File

@ -45,6 +45,7 @@ Leaves (Spruce),18:1,,Solid Block
Leaves (Birch),18:2,,Solid Block
Leaves (Jungle),18:3,,Solid Block
Sponge,19,minecraft:sponge,Solid Block
Wet Sponge,19:1,minecraft:sponge,Solid Block
Glass,20,minecraft:glass,Solid Block
Lapis Lazuli Ore,21,minecraft:lapis_ore,Solid Block
Lapis Lazuli Block,22,minecraft:lapis_block,Solid Block

1 Air 0 minecraft:air Non-Solid Block
45 Leaves (Birch) 18:2 Solid Block
46 Leaves (Jungle) 18:3 Solid Block
47 Sponge 19 minecraft:sponge Solid Block
48 Wet Sponge 19:1 minecraft:sponge Solid Block
49 Glass 20 minecraft:glass Solid Block
50 Lapis Lazuli Ore 21 minecraft:lapis_ore Solid Block
51 Lapis Lazuli Block 22 minecraft:lapis_block Solid Block

1775
sys/etc/blocks.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -72,10 +72,10 @@ function turtle.run(fn, ...)
local s, m = pcall(function() fn(unpack(args)) end)
turtle.abort = false
releaseTicket(ticketId)
os.queueEvent('turtle_response')
if not s and m then
printError(m)
end
os.queueEvent('turtle_response')
return s, m
end
end

View File

@ -30,6 +30,6 @@ end
function turtle.setGPSHome()
if turtle.enableGPS() then
turtle.storeLocation('gpsHome', turtle.point)
turtle.gotoPoint(tturtle.point)
turtle.gotoPoint(turtle.point)
end
end

View File

@ -19,6 +19,8 @@ Event.addHandler('peripheral', function(event, side)
term.setTextColor(attachColor)
Util.print('[%s] %s attached', dev.side, dev.name)
os.queueEvent('device_attach', dev.name)
else
Util.print('[%s] attached failed', side)
end
end
end)