mirror of
https://github.com/kepler155c/opus
synced 2025-01-12 16:51:05 +00:00
treefarm + turtle improvements + cleanup
This commit is contained in:
parent
e50e6da700
commit
9aca96cc3e
@ -1,5 +1,3 @@
|
||||
local Util = require('util')
|
||||
|
||||
local Event = {
|
||||
uid = 1, -- unique id for handlers
|
||||
routines = { }, -- coroutines
|
||||
@ -26,7 +24,7 @@ end
|
||||
function Routine:resume(event, ...)
|
||||
|
||||
if not self.co then
|
||||
error('Cannot resume a dead routine\n' .. Util.tostring(self))
|
||||
error('Cannot resume a dead routine')
|
||||
end
|
||||
|
||||
if not self.filter or self.filter == event or event == "terminate" then
|
||||
@ -41,7 +39,7 @@ function Routine:resume(event, ...)
|
||||
end
|
||||
|
||||
if not s and event ~= 'terminate' then
|
||||
error('\n' .. (m or 'Error processing event') .. '\n' .. Util.tostring(self))
|
||||
error('\n' .. (m or 'Error processing event'))
|
||||
end
|
||||
|
||||
return s, m
|
||||
|
@ -1,3 +1,5 @@
|
||||
local Util = require('util')
|
||||
|
||||
local Point = { }
|
||||
|
||||
function Point.copy(pt)
|
||||
@ -10,6 +12,14 @@ function Point.same(pta, ptb)
|
||||
pta.z == ptb.z
|
||||
end
|
||||
|
||||
function Point.above(pt)
|
||||
return { x = pt.x, y = pt.y + 1, z = pt.z, heading = pt.heading }
|
||||
end
|
||||
|
||||
function Point.below(pt)
|
||||
return { x = pt.x, y = pt.y - 1, z = pt.z, heading = pt.heading }
|
||||
end
|
||||
|
||||
function Point.subtract(a, b)
|
||||
a.x = a.x - b.x
|
||||
a.y = a.y - b.y
|
||||
@ -123,30 +133,17 @@ function Point.closest(reference, pts)
|
||||
return lpt
|
||||
end
|
||||
|
||||
-- find the closest block
|
||||
-- * favor same plane
|
||||
-- * going backwards only if the dest is above or below
|
||||
function Point.closest2(reference, pts)
|
||||
local lpt, lm -- lowest
|
||||
for _,pt in pairs(pts) do
|
||||
local m = Point.turtleDistance(reference, pt)
|
||||
local h = Point.calculateHeading(reference, pt)
|
||||
local t = Point.calculateTurns(reference.heading, h)
|
||||
if pt.y ~= reference.y then -- try and stay on same plane
|
||||
m = m + .01
|
||||
end
|
||||
if t ~= 2 or pt.y == reference.y then
|
||||
m = m + t
|
||||
if t > 0 then
|
||||
m = m + .01
|
||||
end
|
||||
end
|
||||
if not lm or m < lm then
|
||||
lpt = pt
|
||||
lm = m
|
||||
function Point.eachClosest(spt, ipts, fn)
|
||||
|
||||
local pts = Util.shallowCopy(ipts)
|
||||
while #pts > 0 do
|
||||
local pt = Point.closest(spt, pts)
|
||||
local r = fn(pt)
|
||||
if r then
|
||||
return r
|
||||
end
|
||||
Util.removeByValue(pts, pt)
|
||||
end
|
||||
return lpt
|
||||
end
|
||||
|
||||
function Point.adjacentPoints(pt)
|
||||
@ -159,26 +156,28 @@ function Point.adjacentPoints(pt)
|
||||
return pts
|
||||
end
|
||||
|
||||
return Point
|
||||
|
||||
--[[
|
||||
function Point.toBox(pt, width, length, height)
|
||||
return { ax = pt.x,
|
||||
ay = pt.y,
|
||||
az = pt.z,
|
||||
bx = pt.x + width - 1,
|
||||
by = pt.y + height - 1,
|
||||
bz = pt.z + length - 1
|
||||
}
|
||||
function Point.normalizeBox(box)
|
||||
return {
|
||||
x = math.min(box.x, box.ex),
|
||||
y = math.min(box.y, box.ey),
|
||||
z = math.min(box.z, box.ez),
|
||||
ex = math.max(box.x, box.ex),
|
||||
ey = math.max(box.y, box.ey),
|
||||
ez = math.max(box.z, box.ez),
|
||||
}
|
||||
end
|
||||
|
||||
function Point.inBox(pt, box)
|
||||
return pt.x >= box.ax and
|
||||
pt.z >= box.az and
|
||||
pt.x <= box.bx and
|
||||
pt.z <= box.bz
|
||||
return pt.x >= box.x and
|
||||
pt.y >= box.y and
|
||||
pt.z >= box.z and
|
||||
pt.x <= box.ex and
|
||||
pt.z <= box.ez
|
||||
end
|
||||
|
||||
return Point
|
||||
|
||||
--[[
|
||||
Box = { }
|
||||
|
||||
function Box.contain(boundingBox, containedBox)
|
||||
|
@ -1,121 +0,0 @@
|
||||
local Util = require('util')
|
||||
|
||||
local Process = { }
|
||||
|
||||
function Process:init(args)
|
||||
self.args = { }
|
||||
self.uid = 0
|
||||
self.threads = { }
|
||||
Util.merge(self, args)
|
||||
self.name = self.name or 'Thread:' .. self.uid
|
||||
end
|
||||
|
||||
function Process:isDead()
|
||||
return coroutine.status(self.co) == 'dead'
|
||||
end
|
||||
|
||||
function Process:terminate()
|
||||
print('terminating ' .. self.name)
|
||||
self:resume('terminate')
|
||||
end
|
||||
|
||||
function Process:threadEvent(...)
|
||||
|
||||
for _,key in pairs(Util.keys(self.threads)) do
|
||||
local thread = self.threads[key]
|
||||
if thread then
|
||||
thread:resume(...)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Process:addThread(fn, ...)
|
||||
return self:newThread(nil, fn, ...)
|
||||
end
|
||||
|
||||
-- deprecated
|
||||
function Process:newThread(name, fn, ...)
|
||||
|
||||
self.uid = self.uid + 1
|
||||
|
||||
local thread = { }
|
||||
setmetatable(thread, { __index = Process })
|
||||
thread:init({
|
||||
fn = fn,
|
||||
name = name,
|
||||
uid = self.uid,
|
||||
})
|
||||
|
||||
local args = { ... }
|
||||
thread.co = coroutine.create(function()
|
||||
|
||||
local s, m = pcall(function() fn(unpack(args)) end)
|
||||
if not s and m then
|
||||
if m == 'Terminated' then
|
||||
--printError(thread.name .. ' terminated')
|
||||
else
|
||||
printError(m)
|
||||
end
|
||||
end
|
||||
|
||||
--print('thread died ' .. thread.name)
|
||||
self.threads[thread.uid] = nil
|
||||
|
||||
thread:threadEvent('terminate')
|
||||
|
||||
return s, m
|
||||
end)
|
||||
|
||||
self.threads[thread.uid] = thread
|
||||
|
||||
thread:resume()
|
||||
|
||||
return thread
|
||||
end
|
||||
|
||||
function Process:resume(event, ...)
|
||||
|
||||
-- threads get a chance to process the event regardless of the main process filter
|
||||
self:threadEvent(event, ...)
|
||||
|
||||
if not self.filter or self.filter == event or event == "terminate" then
|
||||
local ok, result = coroutine.resume(self.co, event, ...)
|
||||
if ok then
|
||||
self.filter = result
|
||||
end
|
||||
return ok, result
|
||||
end
|
||||
|
||||
return true, self.filter
|
||||
end
|
||||
|
||||
-- confusing...
|
||||
|
||||
-- pull either one event if no filter or until event matches filter
|
||||
-- or until terminated (regardless of filter)
|
||||
function Process:pullEvent(filter)
|
||||
while true do
|
||||
local e = { os.pullEventRaw() }
|
||||
self:threadEvent(unpack(e))
|
||||
|
||||
if not filter or e[1] == filter or e[1] == 'terminate' then
|
||||
return unpack(e)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- pull events until either the filter is matched or terminated
|
||||
function Process:pullEvents(filter)
|
||||
while true do
|
||||
local e = { os.pullEventRaw() }
|
||||
self:threadEvent(unpack(e))
|
||||
if (filter and e[1] == filter) or e[1] == 'terminate' then
|
||||
return unpack(e)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local process = { }
|
||||
setmetatable(process, { __index = Process })
|
||||
process:init({ name = 'Main', co = coroutine.running() })
|
||||
return process
|
@ -1,58 +0,0 @@
|
||||
local function resolveFile(filename, dir, lua_path)
|
||||
|
||||
local ch = string.sub(filename, 1, 1)
|
||||
if ch == "/" then
|
||||
return filename
|
||||
end
|
||||
|
||||
if dir then
|
||||
local path = fs.combine(dir, filename)
|
||||
if fs.exists(path) and not fs.isDir(path) then
|
||||
return path
|
||||
end
|
||||
end
|
||||
|
||||
if lua_path then
|
||||
for dir in string.gmatch(lua_path, "[^:]+") do
|
||||
local path = fs.combine(dir, filename)
|
||||
if fs.exists(path) and not fs.isDir(path) then
|
||||
return path
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local modules = { }
|
||||
|
||||
return function(filename)
|
||||
|
||||
local dir = DIR
|
||||
if not dir and shell and type(shell.dir) == 'function' then
|
||||
dir = shell.dir()
|
||||
end
|
||||
|
||||
local fname = resolveFile(filename:gsub('%.', '/') .. '.lua',
|
||||
dir or '', LUA_PATH or '/sys/apis')
|
||||
|
||||
if not fname or not fs.exists(fname) then
|
||||
error('Unable to load: ' .. filename, 2)
|
||||
end
|
||||
|
||||
local rname = fname:gsub('%/', '.'):gsub('%.lua', '')
|
||||
|
||||
local module = modules[rname]
|
||||
if not module then
|
||||
|
||||
local f, err = loadfile(fname)
|
||||
if not f then
|
||||
error(err)
|
||||
end
|
||||
setfenv(f, getfenv(1))
|
||||
|
||||
module = f(rname)
|
||||
|
||||
modules[rname] = module
|
||||
end
|
||||
|
||||
return module
|
||||
end
|
166
sys/apis/turtle/craft.lua
Normal file
166
sys/apis/turtle/craft.lua
Normal file
@ -0,0 +1,166 @@
|
||||
local itemDB = require('itemDB')
|
||||
local Util = require('util')
|
||||
|
||||
local Craft = { }
|
||||
|
||||
local function clearGrid(chestProvider)
|
||||
for i = 1, 16 do
|
||||
local count = turtle.getItemCount(i)
|
||||
if count > 0 then
|
||||
chestProvider:insert(i, count)
|
||||
if turtle.getItemCount(i) ~= 0 then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local function splitKey(key)
|
||||
local t = Util.split(key, '(.-):')
|
||||
local item = { }
|
||||
if #t[#t] > 2 then
|
||||
item.nbtHash = table.remove(t)
|
||||
end
|
||||
item.damage = tonumber(table.remove(t))
|
||||
item.name = table.concat(t, ':')
|
||||
return item
|
||||
end
|
||||
|
||||
local function getItemCount(items, key)
|
||||
local item = splitKey(key)
|
||||
for _,v in pairs(items) do
|
||||
if v.name == item.name and
|
||||
v.damage == item.damage and
|
||||
v.nbtHash == item.nbtHash then
|
||||
return v.count
|
||||
end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
local function turtleCraft(recipe, qty, chestProvider)
|
||||
|
||||
clearGrid(chestProvider)
|
||||
|
||||
for k,v in pairs(recipe.ingredients) do
|
||||
local item = splitKey(v)
|
||||
chestProvider:provide({ id = item.name, dmg = item.damage, nbt_hash = item.nbtHash }, qty, k)
|
||||
if turtle.getItemCount(k) == 0 then -- ~= qty then
|
||||
-- FIX: ingredients cannot be stacked
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return turtle.craft()
|
||||
end
|
||||
|
||||
function Craft.craftRecipe(recipe, count, chestProvider)
|
||||
|
||||
local items = chestProvider:listItems()
|
||||
|
||||
local function sumItems(items)
|
||||
-- produces { ['minecraft:planks:0'] = 8 }
|
||||
local t = {}
|
||||
for _,item in pairs(items) do
|
||||
t[item] = (t[item] or 0) + 1
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
count = math.ceil(count / recipe.count)
|
||||
|
||||
local maxCount = recipe.maxCount or math.floor(64 / recipe.count)
|
||||
local summedItems = sumItems(recipe.ingredients)
|
||||
|
||||
for key,icount in pairs(summedItems) do
|
||||
local itemCount = getItemCount(items, key)
|
||||
if itemCount < icount * count then
|
||||
local irecipe = Craft.recipes[key]
|
||||
if irecipe then
|
||||
Util.print('Crafting %d %s', icount * count - itemCount, key)
|
||||
if not Craft.craftRecipe(irecipe,
|
||||
icount * count - itemCount,
|
||||
chestProvider) then
|
||||
turtle.select(1)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
repeat
|
||||
if not turtleCraft(recipe, math.min(count, maxCount), chestProvider) then
|
||||
turtle.select(1)
|
||||
return false
|
||||
end
|
||||
count = count - maxCount
|
||||
until count <= 0
|
||||
|
||||
turtle.select(1)
|
||||
return true
|
||||
end
|
||||
|
||||
-- given a certain quantity, return how many of those can be crafted
|
||||
function Craft.getCraftableAmount(recipe, count, items)
|
||||
|
||||
local function sumItems(recipe, items, summedItems, count)
|
||||
|
||||
local canCraft = 0
|
||||
|
||||
for i = 1, count do
|
||||
for _,item in pairs(recipe.ingredients) do
|
||||
local summedItem = summedItems[item] or getItemCount(items, item)
|
||||
|
||||
local irecipe = Craft.recipes[item]
|
||||
if irecipe and summedItem <= 0 then
|
||||
summedItem = summedItem + sumItems(irecipe, items, summedItems, 1)
|
||||
end
|
||||
if summedItem <= 0 then
|
||||
return canCraft
|
||||
end
|
||||
summedItems[item] = summedItem - 1
|
||||
end
|
||||
canCraft = canCraft + recipe.count
|
||||
end
|
||||
|
||||
return canCraft
|
||||
end
|
||||
|
||||
return sumItems(recipe, items, { }, math.ceil(count / recipe.count))
|
||||
end
|
||||
|
||||
function Craft.canCraft(item, count, items)
|
||||
return Craft.getCraftableAmount(Craft.recipes[item], count, items) == count
|
||||
end
|
||||
|
||||
function Craft.setRecipes(recipes)
|
||||
Craft.recipes = recipes
|
||||
end
|
||||
|
||||
function Craft.getCraftableAmountTest()
|
||||
local results = { }
|
||||
Craft.setRecipes(Util.readTable('sys/etc/recipes.db'))
|
||||
|
||||
local items = {
|
||||
{ name = 'minecraft:planks', damage = 0, count = 5 },
|
||||
{ name = 'minecraft:log', damage = 0, count = 2 },
|
||||
}
|
||||
results[1] = { item = 'chest', expected = 1, got = Craft.getCraftableAmount(Craft.recipes['minecraft:chest:0'], 2, items) }
|
||||
|
||||
items = {
|
||||
{ name = 'minecraft:log', damage = 0, count = 1 },
|
||||
{ name = 'minecraft:coal', damage = 1, count = 1 },
|
||||
}
|
||||
results[2] = { item = 'torch', expected = 4, got = Craft.getCraftableAmount(Craft.recipes['minecraft:torch:0'], 4, items) }
|
||||
|
||||
return results
|
||||
end
|
||||
|
||||
function Craft.craftRecipeTest(name, count)
|
||||
local ChestProvider = require('chestProvider18')
|
||||
local chestProvider = ChestProvider({ wrapSide = 'top', direction = 'down' })
|
||||
Craft.setRecipes(Util.readTable('usr/etc/recipes.db'))
|
||||
return { Craft.craftRecipe(Craft.recipes[name], count, chestProvider) }
|
||||
end
|
||||
|
||||
return Craft
|
160
sys/apis/turtle/level.lua
Normal file
160
sys/apis/turtle/level.lua
Normal file
@ -0,0 +1,160 @@
|
||||
local Point = require('point')
|
||||
local Util = require('util')
|
||||
|
||||
local checkedNodes = { }
|
||||
local nodes = { }
|
||||
local box = { }
|
||||
local oldCallback
|
||||
|
||||
local function toKey(pt)
|
||||
return table.concat({ pt.x, pt.y, pt.z }, ':')
|
||||
end
|
||||
|
||||
local function addNode(node)
|
||||
|
||||
for i = 0, 5 do
|
||||
local hi = turtle.getHeadingInfo(i)
|
||||
local testNode = { x = node.x + hi.xd, y = node.y + hi.yd, z = node.z + hi.zd }
|
||||
|
||||
if Point.inBox(testNode, box) then
|
||||
local key = toKey(testNode)
|
||||
if not checkedNodes[key] then
|
||||
nodes[key] = testNode
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function dig(action)
|
||||
|
||||
local directions = {
|
||||
top = 'up',
|
||||
bottom = 'down',
|
||||
}
|
||||
|
||||
-- convert to up, down, north, south, east, west
|
||||
local direction = directions[action.side] or
|
||||
turtle.getHeadingInfo(turtle.point.heading).direction
|
||||
|
||||
local hi = turtle.getHeadingInfo(direction)
|
||||
local node = { x = turtle.point.x + hi.xd, y = turtle.point.y + hi.yd, z = turtle.point.z + hi.zd }
|
||||
if Point.inBox(node, box) then
|
||||
|
||||
local key = toKey(node)
|
||||
checkedNodes[key] = true
|
||||
nodes[key] = nil
|
||||
|
||||
if action.dig() then
|
||||
addNode(node)
|
||||
repeat until not action.dig() -- sand, etc
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function move(action)
|
||||
if action == 'turn' then
|
||||
dig(turtle.getAction('forward'))
|
||||
elseif action == 'up' then
|
||||
dig(turtle.getAction('up'))
|
||||
elseif action == 'down' then
|
||||
dig(turtle.getAction('down'))
|
||||
elseif action == 'back' then
|
||||
dig(turtle.getAction('up'))
|
||||
dig(turtle.getAction('down'))
|
||||
end
|
||||
|
||||
if oldCallback then
|
||||
oldCallback(action)
|
||||
end
|
||||
end
|
||||
|
||||
-- find the closest block
|
||||
-- * favor same plane
|
||||
-- * going backwards only if the dest is above or below
|
||||
function closestPoint(reference, pts)
|
||||
local lpt, lm -- lowest
|
||||
for _,pt in pairs(pts) do
|
||||
local m = Point.turtleDistance(reference, pt)
|
||||
local h = Point.calculateHeading(reference, pt)
|
||||
local t = Point.calculateTurns(reference.heading, h)
|
||||
if pt.y ~= reference.y then -- try and stay on same plane
|
||||
m = m + .01
|
||||
end
|
||||
if t ~= 2 or pt.y == reference.y then
|
||||
m = m + t
|
||||
if t > 0 then
|
||||
m = m + .01
|
||||
end
|
||||
end
|
||||
if not lm or m < lm then
|
||||
lpt = pt
|
||||
lm = m
|
||||
end
|
||||
end
|
||||
return lpt
|
||||
end
|
||||
|
||||
local function getAdjacentPoint(pt)
|
||||
local t = { }
|
||||
table.insert(t, pt)
|
||||
for i = 0, 5 do
|
||||
local hi = turtle.getHeadingInfo(i)
|
||||
local heading
|
||||
if i < 4 then
|
||||
heading = (hi.heading + 2) % 4
|
||||
end
|
||||
table.insert(t, { x = pt.x + hi.xd, z = pt.z + hi.zd, y = pt.y + hi.yd, heading = heading })
|
||||
end
|
||||
|
||||
return closestPoint(turtle.getPoint(), t)
|
||||
end
|
||||
|
||||
return function(startPt, endPt, firstPt, verbose)
|
||||
|
||||
checkedNodes = { }
|
||||
nodes = { }
|
||||
box = { }
|
||||
|
||||
box.x = math.min(startPt.x, endPt.x)
|
||||
box.y = math.min(startPt.y, endPt.y)
|
||||
box.z = math.min(startPt.z, endPt.z)
|
||||
box.ex = math.max(startPt.x, endPt.x)
|
||||
box.ey = math.max(startPt.y, endPt.y)
|
||||
box.ez = math.max(startPt.z, endPt.z)
|
||||
|
||||
turtle.pathfind(firstPt)
|
||||
|
||||
turtle.setPolicy("attack", { dig = dig }, "assuredMove")
|
||||
|
||||
oldCallback = turtle.getMoveCallback()
|
||||
turtle.setMoveCallback(move)
|
||||
|
||||
repeat
|
||||
local key = toKey(turtle.point)
|
||||
|
||||
checkedNodes[key] = true
|
||||
nodes[key] = nil
|
||||
|
||||
dig(turtle.getAction('down'))
|
||||
dig(turtle.getAction('up'))
|
||||
dig(turtle.getAction('forward'))
|
||||
|
||||
if verbose then
|
||||
print(string.format('%d nodes remaining', Util.size(nodes)))
|
||||
end
|
||||
|
||||
if Util.size(nodes) == 0 then
|
||||
break
|
||||
end
|
||||
|
||||
local node = closestPoint(turtle.point, nodes)
|
||||
node = getAdjacentPoint(node)
|
||||
if not turtle.gotoPoint(node) then
|
||||
break
|
||||
end
|
||||
until turtle.abort
|
||||
|
||||
turtle.resetState()
|
||||
turtle.setMoveCallback(oldCallback)
|
||||
end
|
@ -1,22 +1,19 @@
|
||||
if not turtle or turtle.pathfind then
|
||||
return
|
||||
end
|
||||
|
||||
requireInjector(getfenv(1))
|
||||
|
||||
local Grid = require ("jumper.grid")
|
||||
local Pathfinder = require ("jumper.pathfinder")
|
||||
local Point = require('point')
|
||||
local Util = require('util')
|
||||
|
||||
local WALKABLE = 0
|
||||
|
||||
local function createMap(dim)
|
||||
local map = { }
|
||||
for z = 0, dim.ez do
|
||||
for z = 1, dim.ez do
|
||||
local row = {}
|
||||
for x = 0, dim.ex do
|
||||
for x = 1, dim.ex do
|
||||
local col = { }
|
||||
for y = 0, dim.ey do
|
||||
for y = 1, dim.ey do
|
||||
table.insert(col, WALKABLE)
|
||||
end
|
||||
table.insert(row, col)
|
||||
@ -65,12 +62,21 @@ local function mapDimensions(dest, blocks, boundingBox)
|
||||
end
|
||||
|
||||
-- expand one block out in all directions
|
||||
sx = math.max(sx - 1, boundingBox.sx)
|
||||
sz = math.max(sz - 1, boundingBox.sz)
|
||||
sy = math.max(sy - 1, boundingBox.sy)
|
||||
ex = math.min(ex + 1, boundingBox.ex)
|
||||
ez = math.min(ez + 1, boundingBox.ez)
|
||||
ey = math.min(ey + 1, boundingBox.ey)
|
||||
if boundingBox then
|
||||
sx = math.max(sx - 1, boundingBox.x)
|
||||
sz = math.max(sz - 1, boundingBox.z)
|
||||
sy = math.max(sy - 1, boundingBox.y)
|
||||
ex = math.min(ex + 1, boundingBox.ex)
|
||||
ez = math.min(ez + 1, boundingBox.ez)
|
||||
ey = math.min(ey + 1, boundingBox.ey)
|
||||
else
|
||||
sx = sx - 1
|
||||
sz = sz - 1
|
||||
sy = sy - 1
|
||||
ex = ex + 1
|
||||
ez = ez + 1
|
||||
ey = ey + 1
|
||||
end
|
||||
|
||||
return {
|
||||
ex = ex - sx + 1,
|
||||
@ -138,22 +144,14 @@ local function addSensorBlocks(blocks, sblocks)
|
||||
end
|
||||
end
|
||||
|
||||
local function pathTo(dest, blocks, maxRadius)
|
||||
local function pathTo(dest, options)
|
||||
|
||||
blocks = blocks or { }
|
||||
maxRadius = maxRadius or 1000000
|
||||
local blocks = options.blocks or { }
|
||||
local allDests = options.dest or { } -- support alternative destinations
|
||||
|
||||
local lastDim = nil
|
||||
local map = nil
|
||||
local grid = nil
|
||||
local boundingBox = {
|
||||
sx = math.min(turtle.point.x, dest.x) - maxRadius,
|
||||
sy = math.min(turtle.point.y, dest.y) - maxRadius,
|
||||
sz = math.min(turtle.point.z, dest.z) - maxRadius,
|
||||
ex = math.max(turtle.point.x, dest.x) + maxRadius,
|
||||
ey = math.max(turtle.point.y, dest.y) + maxRadius,
|
||||
ez = math.max(turtle.point.z, dest.z) + maxRadius,
|
||||
}
|
||||
|
||||
-- Creates a pathfinder object
|
||||
local myFinder = Pathfinder(grid, 'ASTAR', walkable)
|
||||
@ -164,7 +162,7 @@ local function pathTo(dest, blocks, maxRadius)
|
||||
while turtle.point.x ~= dest.x or turtle.point.z ~= dest.z or turtle.point.y ~= dest.y do
|
||||
|
||||
-- map expands as we encounter obstacles
|
||||
local dim = mapDimensions(dest, blocks, boundingBox)
|
||||
local dim = mapDimensions(dest, blocks, options.box)
|
||||
|
||||
-- reuse map if possible
|
||||
if not lastDim or not dimsAreEqual(dim, lastDim) then
|
||||
@ -189,23 +187,33 @@ local function pathTo(dest, blocks, maxRadius)
|
||||
local path = myFinder:getPath(startPt.x, startPt.y, startPt.z, turtle.point.heading, endPt.x, endPt.y, endPt.z, dest.heading)
|
||||
|
||||
if not path then
|
||||
return false, 'failed to recalculate'
|
||||
end
|
||||
Util.removeByValue(allDests, dest)
|
||||
dest = Point.closest(turtle.point, allDests)
|
||||
|
||||
for node, count in path:nodes() do
|
||||
local pt = nodeToPoint(dim, node)
|
||||
|
||||
if turtle.abort then
|
||||
return false, 'aborted'
|
||||
if not dest then
|
||||
return false, 'failed to recalculate'
|
||||
end
|
||||
else
|
||||
|
||||
-- use single turn method so the turtle doesn't turn around when encountering obstacles
|
||||
if not turtle.gotoSingleTurn(pt.x, pt.z, pt.y) then
|
||||
for node, count in path:nodes() do
|
||||
local pt = nodeToPoint(dim, node)
|
||||
|
||||
if turtle.abort then
|
||||
return false, 'aborted'
|
||||
end
|
||||
|
||||
-- use single turn method so the turtle doesn't turn around
|
||||
-- when encountering obstacles -- IS THIS RIGHT ??
|
||||
if not turtle.gotoSingleTurn(pt.x, pt.z, pt.y, node.heading) then
|
||||
table.insert(blocks, pt)
|
||||
if #allDests > 0 then
|
||||
dest = Point.closest(turtle.point, allDests)
|
||||
end
|
||||
--if device.turtlesensorenvironment then
|
||||
-- addSensorBlocks(blocks, device.turtlesensorenvironment.sonicScan())
|
||||
--end
|
||||
break
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -213,12 +221,13 @@ local function pathTo(dest, blocks, maxRadius)
|
||||
if dest.heading then
|
||||
turtle.setHeading(dest.heading)
|
||||
end
|
||||
return true
|
||||
return dest
|
||||
end
|
||||
|
||||
turtle.pathfind = function(dest, blocks, maxRadius)
|
||||
if not blocks and turtle.gotoPoint(dest) then
|
||||
return true
|
||||
return function(dest, options)
|
||||
options = options or { }
|
||||
if not options.blocks and turtle.gotoPoint(dest) then
|
||||
return dest
|
||||
end
|
||||
return pathTo(dest, blocks, maxRadius)
|
||||
return pathTo(dest, options)
|
||||
end
|
@ -343,7 +343,7 @@ function Manager:click(button, x, y)
|
||||
|
||||
if button == 1 then
|
||||
local c = os.clock()
|
||||
if self.doubleClickTimer and (c - self.doubleClickTimer < 1.5) and
|
||||
if self.doubleClickTimer and (c - self.doubleClickTimer < 1.9) and
|
||||
self.doubleClickX == x and self.doubleClickY == y and
|
||||
self.doubleClickElement == clickEvent.element then
|
||||
button = 3
|
||||
|
@ -243,6 +243,15 @@ function Util.size(list)
|
||||
return 0
|
||||
end
|
||||
|
||||
function Util.removeByValue(t, e)
|
||||
for k,v in pairs(t) do
|
||||
if v == e then
|
||||
table.remove(t, k)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Util.each(list, func)
|
||||
for index, value in pairs(list) do
|
||||
func(value, index, list)
|
||||
|
107
sys/apps/Pim.lua
107
sys/apps/Pim.lua
@ -1,107 +0,0 @@
|
||||
requireInjector(getfenv(1))
|
||||
|
||||
local Config = require('config')
|
||||
local Event = require('event')
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
|
||||
multishell.setTitle(multishell.getCurrent(), 'PIM')
|
||||
|
||||
local inventory = { }
|
||||
local mode = 'sync'
|
||||
|
||||
if not device.pim then
|
||||
error('PIM not attached')
|
||||
end
|
||||
|
||||
local page = UI.Page({
|
||||
menu = UI.Menu({
|
||||
centered = true,
|
||||
y = 2,
|
||||
menuItems = {
|
||||
{ prompt = 'Learn', event = 'learn', help = '' },
|
||||
},
|
||||
}),
|
||||
statusBar = UI.StatusBar({
|
||||
columns = {
|
||||
{ 'Status', 'status', UI.term.width - 7 },
|
||||
{ 'Mode', 'mode', 7 }
|
||||
}
|
||||
}),
|
||||
accelerators = {
|
||||
q = 'quit',
|
||||
},
|
||||
})
|
||||
|
||||
local function learn()
|
||||
if device.pim.getInventorySize() > 0 then
|
||||
local stacks = device.pim.getAllStacks(false)
|
||||
Config.update('pim', stacks)
|
||||
mode = 'sync'
|
||||
page.statusBar:setValue('status', 'Learned inventory')
|
||||
end
|
||||
page.statusBar:setValue('mode', mode)
|
||||
page.statusBar:draw()
|
||||
end
|
||||
|
||||
function page:eventHandler(event)
|
||||
|
||||
if event.type == 'learn' then
|
||||
mode = 'learn'
|
||||
learn()
|
||||
elseif event.type == 'quit' then
|
||||
Event.exitPullEvents()
|
||||
end
|
||||
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
|
||||
local function inInventory(s)
|
||||
for _,i in pairs(inventory) do
|
||||
if i.id == s.id then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function pimWatcher()
|
||||
local playerOn = false
|
||||
|
||||
while true do
|
||||
if device.pim.getInventorySize() > 0 and not playerOn then
|
||||
playerOn = true
|
||||
|
||||
if mode == 'learn' then
|
||||
learn()
|
||||
|
||||
else
|
||||
local stacks = device.pim.getAllStacks(false)
|
||||
for k,stack in pairs(stacks) do
|
||||
if not inInventory(stack) then
|
||||
device.pim.pushItem('down', k, stack.qty)
|
||||
end
|
||||
end
|
||||
page.statusBar:setValue('status', 'Synchronized')
|
||||
page.statusBar:draw()
|
||||
end
|
||||
|
||||
elseif device.pim.getInventorySize() == 0 and playerOn then
|
||||
page.statusBar:setValue('status', 'No player')
|
||||
page.statusBar:draw()
|
||||
playerOn = false
|
||||
end
|
||||
os.sleep(1)
|
||||
end
|
||||
end
|
||||
|
||||
Config.load('pim', inventory)
|
||||
|
||||
if Util.empty(inventory) then
|
||||
mode = 'learn'
|
||||
end
|
||||
page.statusBar:setValue('mode', mode)
|
||||
|
||||
UI:setPage(page)
|
||||
|
||||
Event.pullEvents(pimWatcher)
|
||||
UI.term:reset()
|
@ -2,6 +2,7 @@ requireInjector(getfenv(1))
|
||||
|
||||
local ChestProvider = require('chestProvider18')
|
||||
local Config = require('config')
|
||||
local Craft = require('turtle.craft')
|
||||
local Event = require('event')
|
||||
local itemDB = require('itemDB')
|
||||
local Peripheral = require('peripheral')
|
||||
@ -34,13 +35,15 @@ local chestProvider = ChestProvider({ direction = 'west', wrapSide = 'back' })
|
||||
local turtleChestProvider = ChestProvider({ direction = 'up', wrapSide = 'bottom' })
|
||||
|
||||
local RESOURCE_FILE = 'usr/etc/resources.db'
|
||||
local RECIPES_FILE = 'usr/etc/recipes.db'
|
||||
local RECIPES_FILE = 'sys/etc/recipes.db'
|
||||
|
||||
local jobListGrid
|
||||
local craftingPaused = false
|
||||
local recipes = Util.readTable(RECIPES_FILE) or { }
|
||||
local resources = Util.readTable(RESOURCE_FILE) or { }
|
||||
|
||||
Craft.setRecipes(recipes)
|
||||
|
||||
for _,r in pairs(resources) do
|
||||
r.maxDamage = nil
|
||||
r.displayName = nil
|
||||
@ -70,6 +73,17 @@ local function getItem(items, inItem, ignoreDamage)
|
||||
end
|
||||
end
|
||||
|
||||
local function splitKey(key)
|
||||
local t = Util.split(key, '(.-):')
|
||||
local item = { }
|
||||
if #t[#t] > 2 then
|
||||
item.nbtHash = table.remove(t)
|
||||
end
|
||||
item.damage = tonumber(table.remove(t))
|
||||
item.name = table.concat(t, ':')
|
||||
return item
|
||||
end
|
||||
|
||||
local function getItemQuantity(items, item)
|
||||
item = getItem(items, item)
|
||||
if item then
|
||||
@ -114,7 +128,8 @@ local function mergeResources(t)
|
||||
end
|
||||
end
|
||||
|
||||
for _,v in pairs(recipes) do
|
||||
for k in pairs(recipes) do
|
||||
local v = splitKey(k)
|
||||
local item = getItem(t, v)
|
||||
if not item then
|
||||
item = Util.shallowCopy(v)
|
||||
@ -149,8 +164,8 @@ end
|
||||
local function sumItems3(ingredients, items, summedItems, count)
|
||||
|
||||
local canCraft = 0
|
||||
for _,item in pairs(ingredients) do
|
||||
local key = uniqueKey(item)
|
||||
for _,key in pairs(ingredients) do
|
||||
local item = splitKey(key)
|
||||
local summedItem = summedItems[key]
|
||||
if not summedItem then
|
||||
summedItem = Util.shallowCopy(item)
|
||||
@ -167,52 +182,6 @@ local function sumItems3(ingredients, items, summedItems, count)
|
||||
end
|
||||
end
|
||||
|
||||
local function sumItems2(ingredients, items, summedItems, count)
|
||||
|
||||
local canCraft = 0
|
||||
|
||||
for i = 1, count do
|
||||
for _,item in pairs(ingredients) do
|
||||
local key = uniqueKey(item)
|
||||
local summedItem = summedItems[key]
|
||||
if not summedItem then
|
||||
summedItem = Util.shallowCopy(item)
|
||||
summedItem.recipe = recipes[key]
|
||||
summedItem.count = getItemQuantity(items, summedItem)
|
||||
summedItems[key] = summedItem
|
||||
end
|
||||
if summedItem.recipe and summedItem.count <= 0 then
|
||||
summedItem.count = sumItems2(summedItem.recipe.ingredients, items, summedItems, 1)
|
||||
end
|
||||
if summedItem.count <= 0 then
|
||||
return canCraft
|
||||
end
|
||||
summedItem.count = summedItem.count - item.count
|
||||
end
|
||||
canCraft = canCraft + 1
|
||||
end
|
||||
|
||||
return canCraft
|
||||
end
|
||||
|
||||
local function sumItems(items)
|
||||
local t = {}
|
||||
|
||||
for _,item in pairs(items) do
|
||||
local key = uniqueKey(item)
|
||||
local summedItem = t[key]
|
||||
if summedItem then
|
||||
summedItem.count = summedItem.count + item.count
|
||||
else
|
||||
summedItem = Util.shallowCopy(item)
|
||||
summedItem.recipe = recipes[key]
|
||||
t[key] = summedItem
|
||||
end
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
local function isGridClear()
|
||||
for i = 1, 16 do
|
||||
if turtle.getItemCount(i) ~= 0 then
|
||||
@ -235,32 +204,6 @@ local function clearGrid()
|
||||
return true
|
||||
end
|
||||
|
||||
local function turtleCraft(recipe, originalItem, qty)
|
||||
|
||||
for k,v in pairs(recipe.ingredients) do
|
||||
|
||||
chestProvider:provide({ id = v.name, dmg = v.damage, nbt_hash = v.nbtHash }, v.count * qty, k)
|
||||
if turtle.getItemCount(k) ~= v.count * qty then
|
||||
clearGrid()
|
||||
originalItem.status = v.name .. ' (extract failed)'
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if not turtle.craft() then
|
||||
clearGrid()
|
||||
return false
|
||||
end
|
||||
|
||||
--for k,ingredient in pairs(recipe.ingredients) do
|
||||
-- local item = getItem(items, ingredient)
|
||||
-- item.count = item.count - ingredient.count
|
||||
--end
|
||||
|
||||
clearGrid()
|
||||
return true
|
||||
end
|
||||
|
||||
local function addCraftingRequest(item, craftList, count)
|
||||
local key = uniqueKey(item)
|
||||
local request = craftList[key]
|
||||
@ -272,64 +215,30 @@ local function addCraftingRequest(item, craftList, count)
|
||||
request.count = request.count + count
|
||||
end
|
||||
|
||||
local function craftRecipe(recipe, items, originalItem, count)
|
||||
|
||||
local maxCount = recipe.maxCount
|
||||
|
||||
if not maxCount then -- temporary
|
||||
local cItem = itemDB:get(itemDB:makeKey(recipe))
|
||||
if cItem then
|
||||
maxCount = cItem.maxCount
|
||||
else
|
||||
maxCount = 1
|
||||
end
|
||||
end
|
||||
|
||||
local summedItems = sumItems(recipe.ingredients)
|
||||
for key,ingredient in pairs(summedItems) do
|
||||
local details = getItemDetails(items, ingredient)
|
||||
maxCount = math.min(details.maxCount, maxCount)
|
||||
if details.count < ingredient.count * count then
|
||||
if ingredient.recipe then
|
||||
if not craftRecipe(ingredient.recipe, items, originalItem, ingredient.count * count - details.count) then
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
repeat
|
||||
if not turtleCraft(recipe, originalItem, math.min(count, maxCount)) then
|
||||
return false
|
||||
end
|
||||
count = count - maxCount
|
||||
until count < 0
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function craftItem(recipe, items, originalItem, craftList, count)
|
||||
|
||||
if craftingPaused or not device.workbench or not isGridClear() then
|
||||
return
|
||||
end
|
||||
|
||||
count = math.ceil(count / recipe.count)
|
||||
|
||||
local toCraft = sumItems2(recipe.ingredients, items, { }, count)
|
||||
local toCraft = Craft.getCraftableAmount(recipe, count, items)
|
||||
|
||||
if toCraft > 0 then
|
||||
craftRecipe(recipe, items, originalItem, toCraft)
|
||||
Craft.craftRecipe(recipe, toCraft, chestProvider)
|
||||
clearGrid()
|
||||
items = chestProvider:listItems()
|
||||
end
|
||||
|
||||
count = count - toCraft
|
||||
|
||||
local summedItems = { }
|
||||
sumItems3(recipe.ingredients, items, summedItems, count)
|
||||
if count > 0 then
|
||||
local summedItems = { }
|
||||
sumItems3(recipe.ingredients, items, summedItems, math.ceil(count / recipe.count))
|
||||
|
||||
for key,ingredient in pairs(summedItems) do
|
||||
if not ingredient.recipe and ingredient.count < 0 then
|
||||
addCraftingRequest(ingredient, craftList, -ingredient.count)
|
||||
for key,ingredient in pairs(summedItems) do
|
||||
if not ingredient.recipe and ingredient.count < 0 then
|
||||
addCraftingRequest(ingredient, craftList, -ingredient.count)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,535 +0,0 @@
|
||||
requireInjector(getfenv(1))
|
||||
|
||||
local Peripheral = require('peripheral')
|
||||
local RefinedProvider = require('refinedProvider')
|
||||
local Terminal = require('terminal')
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
|
||||
local controller = RefinedProvider()
|
||||
if not controller:isValid() then
|
||||
error('Refined storage controller not found')
|
||||
end
|
||||
|
||||
multishell.setTitle(multishell.getCurrent(), 'Storage Manager')
|
||||
|
||||
function getItem(items, inItem, ignoreDamage)
|
||||
for _,item in pairs(items) do
|
||||
if item.name == inItem.name then
|
||||
if ignoreDamage then
|
||||
return item
|
||||
elseif item.damage == inItem.damage and item.nbtHash == inItem.nbtHash then
|
||||
return item
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function uniqueKey(item)
|
||||
return table.concat({ item.name, item.damage, item.nbtHash }, ':')
|
||||
end
|
||||
|
||||
function mergeResources(t)
|
||||
local resources = Util.readTable('resource.limits') or { }
|
||||
|
||||
for _,v in pairs(resources) do
|
||||
v.low = tonumber(v.low) -- backwards compatibility
|
||||
local item = getItem(t, v)
|
||||
if item then
|
||||
item.low = v.low
|
||||
item.auto = v.auto
|
||||
item.ignoreDamage = v.ignoreDamage
|
||||
item.rsControl = v.rsControl
|
||||
item.rsDevice = v.rsDevice
|
||||
item.rsSide = v.rsSide
|
||||
else
|
||||
v.count = 0
|
||||
table.insert(t, v)
|
||||
end
|
||||
end
|
||||
|
||||
for _,v in pairs(t) do
|
||||
v.lname = v.displayName:lower()
|
||||
end
|
||||
end
|
||||
|
||||
function filterItems(t, filter)
|
||||
if filter then
|
||||
local r = {}
|
||||
filter = filter:lower()
|
||||
for k,v in pairs(t) do
|
||||
if string.find(v.lname, filter) then
|
||||
table.insert(r, v)
|
||||
end
|
||||
end
|
||||
return r
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
function craftItems(itemList, allItems)
|
||||
|
||||
for _,item in pairs(itemList) do
|
||||
local cItem = getItem(allItems, item)
|
||||
|
||||
if controller:isCrafting(item) then
|
||||
item.status = '(crafting)'
|
||||
elseif item.rsControl then
|
||||
item.status = 'Activated'
|
||||
elseif not cItem then
|
||||
item.status = '(no recipe)'
|
||||
else
|
||||
|
||||
local count = item.count
|
||||
while count >= 1 do -- try to request smaller quantities until successful
|
||||
local s, m = pcall(function()
|
||||
item.status = '(no recipe)'
|
||||
if not controller:craft(cItem, count) then
|
||||
item.status = '(missing ingredients)'
|
||||
error('failed')
|
||||
end
|
||||
item.status = '(crafting)'
|
||||
end)
|
||||
if s then
|
||||
break -- successfully requested crafting
|
||||
end
|
||||
count = math.floor(count / 2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function getAutocraftItems()
|
||||
local t = Util.readTable('resource.limits') or { }
|
||||
local itemList = { }
|
||||
|
||||
for _,res in pairs(t) do
|
||||
|
||||
if res.auto then
|
||||
res.count = 4 -- this could be higher to increase autocrafting speed
|
||||
table.insert(itemList, res)
|
||||
end
|
||||
end
|
||||
return itemList
|
||||
end
|
||||
|
||||
local function getItemWithQty(items, res, ignoreDamage)
|
||||
|
||||
local item = getItem(items, res, ignoreDamage)
|
||||
|
||||
if item then
|
||||
|
||||
if ignoreDamage then
|
||||
local count = 0
|
||||
|
||||
for _,v in pairs(items) do
|
||||
if item.name == v.name and item.nbtHash == v.nbtHash then
|
||||
if item.maxDamage > 0 or item.damage == v.damage then
|
||||
count = count + v.count
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
item.count = count
|
||||
end
|
||||
end
|
||||
|
||||
return item
|
||||
end
|
||||
|
||||
function watchResources(items)
|
||||
|
||||
local itemList = { }
|
||||
|
||||
local t = Util.readTable('resource.limits') or { }
|
||||
for k, res in pairs(t) do
|
||||
res.low = tonumber(res.low) -- backwards compatibility
|
||||
local item = getItemWithQty(items, res, res.ignoreDamage)
|
||||
if not item then
|
||||
item = {
|
||||
damage = res.damage,
|
||||
nbtHash = res.nbtHash,
|
||||
name = res.name,
|
||||
displayName = res.displayName,
|
||||
count = 0
|
||||
}
|
||||
end
|
||||
|
||||
if res.low and item.count < res.low then
|
||||
if res.ignoreDamage then
|
||||
item.damage = 0
|
||||
end
|
||||
table.insert(itemList, {
|
||||
damage = item.damage,
|
||||
nbtHash = item.nbtHash,
|
||||
count = res.low - item.count,
|
||||
name = item.name,
|
||||
displayName = item.displayName,
|
||||
status = '',
|
||||
rsControl = res.rsControl,
|
||||
})
|
||||
end
|
||||
|
||||
if res.rsControl and res.rsDevice and res.rsSide then
|
||||
pcall(function()
|
||||
device[res.rsDevice].setOutput(res.rsSide, item.count < res.low)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
return itemList
|
||||
end
|
||||
|
||||
itemPage = UI.Page {
|
||||
backgroundColor = colors.lightGray,
|
||||
titleBar = UI.TitleBar {
|
||||
title = 'Limit Resource',
|
||||
previousPage = true,
|
||||
event = 'form_cancel',
|
||||
backgroundColor = colors.green
|
||||
},
|
||||
displayName = UI.Window {
|
||||
x = 5, y = 2, width = UI.term.width - 10, height = 3,
|
||||
},
|
||||
form = UI.Form {
|
||||
x = 4, y = 4, height = 10, rex = -4,
|
||||
[1] = UI.TextEntry {
|
||||
width = 7,
|
||||
backgroundColor = colors.gray,
|
||||
backgroundFocusColor = colors.gray,
|
||||
formLabel = 'Min', formKey = 'low', help = 'Craft if below min'
|
||||
},
|
||||
[2] = UI.Chooser {
|
||||
width = 7,
|
||||
formLabel = 'Autocraft', formKey = 'auto',
|
||||
nochoice = 'No',
|
||||
choices = {
|
||||
{ name = 'Yes', value = true },
|
||||
{ name = 'No', value = false },
|
||||
},
|
||||
help = 'Craft until out of ingredients'
|
||||
},
|
||||
[3] = UI.Chooser {
|
||||
width = 7,
|
||||
formLabel = 'Ignore Dmg', formKey = 'ignoreDamage',
|
||||
nochoice = 'No',
|
||||
choices = {
|
||||
{ name = 'Yes', value = true },
|
||||
{ name = 'No', value = false },
|
||||
},
|
||||
help = 'Ignore damage of item'
|
||||
},
|
||||
[4] = UI.Chooser {
|
||||
width = 7,
|
||||
formLabel = 'RS Control', formKey = 'rsControl',
|
||||
nochoice = 'No',
|
||||
choices = {
|
||||
{ name = 'Yes', value = true },
|
||||
{ name = 'No', value = false },
|
||||
},
|
||||
help = 'Control via redstone'
|
||||
},
|
||||
[5] = UI.Chooser {
|
||||
width = 25,
|
||||
formLabel = 'RS Device', formKey = 'rsDevice',
|
||||
--choices = devices,
|
||||
help = 'Redstone Device'
|
||||
},
|
||||
[6] = UI.Chooser {
|
||||
width = 10,
|
||||
formLabel = 'RS Side', formKey = 'rsSide',
|
||||
--nochoice = 'No',
|
||||
choices = {
|
||||
{ name = 'up', value = 'up' },
|
||||
{ name = 'down', value = 'down' },
|
||||
{ name = 'east', value = 'east' },
|
||||
{ name = 'north', value = 'north' },
|
||||
{ name = 'west', value = 'west' },
|
||||
{ name = 'south', value = 'south' },
|
||||
},
|
||||
help = 'Output side'
|
||||
},
|
||||
},
|
||||
statusBar = UI.StatusBar { }
|
||||
}
|
||||
|
||||
function itemPage.displayName:draw()
|
||||
local item = self.parent.item
|
||||
local str = string.format('Name: %s\nDamage: %d', item.displayName, item.damage)
|
||||
if item.nbtHash then
|
||||
str = str .. string.format('\nNBT: %s\n', item.nbtHash)
|
||||
end
|
||||
self:setCursorPos(1, 1)
|
||||
self:print(str)
|
||||
end
|
||||
|
||||
function itemPage:enable(item)
|
||||
self.item = item
|
||||
|
||||
self.form:setValues(item)
|
||||
self.titleBar.title = item.name
|
||||
|
||||
local devices = self.form[5].choices
|
||||
Util.clear(devices)
|
||||
for _,device in pairs(device) do
|
||||
if device.setOutput then
|
||||
table.insert(devices, { name = device.name, value = device.name })
|
||||
end
|
||||
end
|
||||
|
||||
if Util.size(devices) == 0 then
|
||||
table.insert(devices, { name = 'None found', values = '' })
|
||||
end
|
||||
|
||||
UI.Page.enable(self)
|
||||
self:focusFirst()
|
||||
end
|
||||
|
||||
function itemPage:eventHandler(event)
|
||||
if event.type == 'form_cancel' then
|
||||
UI:setPreviousPage()
|
||||
|
||||
elseif event.type == 'focus_change' then
|
||||
self.statusBar:setStatus(event.focused.help)
|
||||
self.statusBar:draw()
|
||||
|
||||
elseif event.type == 'form_complete' then
|
||||
local values = self.form.values
|
||||
local t = Util.readTable('resource.limits') or { }
|
||||
local keys = { 'name', 'displayName', 'auto', 'low', 'damage',
|
||||
'maxDamage', 'nbtHash', 'ignoreDamage',
|
||||
'rsControl', 'rsDevice', 'rsSide', }
|
||||
|
||||
local filtered = { }
|
||||
for _,key in pairs(keys) do
|
||||
filtered[key] = values[key]
|
||||
end
|
||||
filtered.low = tonumber(filtered.low)
|
||||
|
||||
filtered.ignoreDamage = filtered.ignoreDamage == true
|
||||
filtered.auto = filtered.auto == true
|
||||
filtered.rsControl = filtered.rsControl == true
|
||||
|
||||
if filtered.ignoreDamage then
|
||||
filtered.damage = 0
|
||||
end
|
||||
|
||||
t[uniqueKey(filtered)] = filtered
|
||||
Util.writeTable('resource.limits', t)
|
||||
|
||||
UI:setPreviousPage()
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
listingPage = UI.Page {
|
||||
menuBar = UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = 'Forget', event = 'forget' },
|
||||
},
|
||||
},
|
||||
grid = UI.Grid {
|
||||
y = 2, height = UI.term.height - 2,
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'displayName', width = UI.term.width - 14 },
|
||||
{ heading = 'Qty', key = 'count', width = 5 },
|
||||
{ heading = 'Min', key = 'low', width = 4 },
|
||||
},
|
||||
sortColumn = 'lname',
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
backgroundColor = colors.gray,
|
||||
width = UI.term.width,
|
||||
filterText = UI.Text {
|
||||
x = 2, width = 6,
|
||||
value = 'Filter',
|
||||
},
|
||||
filter = UI.TextEntry {
|
||||
x = 9, rex = -12,
|
||||
limit = 50,
|
||||
},
|
||||
refresh = UI.Button {
|
||||
rx = -9, width = 8,
|
||||
text = 'Refresh',
|
||||
event = 'refresh',
|
||||
},
|
||||
},
|
||||
accelerators = {
|
||||
r = 'refresh',
|
||||
q = 'quit',
|
||||
}
|
||||
}
|
||||
|
||||
function listingPage.grid:getRowTextColor(row, selected)
|
||||
if row.is_craftable then -- not implemented
|
||||
return colors.yellow
|
||||
end
|
||||
return UI.Grid:getRowTextColor(row, selected)
|
||||
end
|
||||
|
||||
function listingPage.grid:getDisplayValues(row)
|
||||
row = Util.shallowCopy(row)
|
||||
row.count = Util.toBytes(row.count)
|
||||
if row.low then
|
||||
row.low = Util.toBytes(row.low)
|
||||
end
|
||||
return row
|
||||
end
|
||||
|
||||
function listingPage.statusBar:draw()
|
||||
return UI.Window.draw(self)
|
||||
end
|
||||
|
||||
function listingPage.statusBar.filter:eventHandler(event)
|
||||
if event.type == 'mouse_rightclick' then
|
||||
self.value = ''
|
||||
self:draw()
|
||||
local page = UI:getCurrentPage()
|
||||
page.filter = nil
|
||||
page:applyFilter()
|
||||
page.grid:draw()
|
||||
page:setFocus(self)
|
||||
end
|
||||
return UI.TextEntry.eventHandler(self, event)
|
||||
end
|
||||
|
||||
function listingPage:eventHandler(event)
|
||||
if event.type == 'quit' then
|
||||
UI:exitPullEvents()
|
||||
|
||||
elseif event.type == 'grid_select' then
|
||||
local selected = event.selected
|
||||
UI:setPage('item', selected)
|
||||
|
||||
elseif event.type == 'refresh' then
|
||||
self:refresh()
|
||||
self.grid:draw()
|
||||
self.statusBar.filter:focus()
|
||||
|
||||
elseif event.type == 'forget' then
|
||||
local item = self.grid:getSelected()
|
||||
if item then
|
||||
|
||||
local resources = Util.readTable('resource.limits') or { }
|
||||
resources[uniqueKey(item)] = nil
|
||||
Util.writeTable('resource.limits', resources)
|
||||
|
||||
self.statusBar:timedStatus('Forgot: ' .. item.name, 3)
|
||||
self:refresh()
|
||||
self.grid:draw()
|
||||
end
|
||||
|
||||
elseif event.type == 'text_change' then
|
||||
self.filter = event.text
|
||||
if #self.filter == 0 then
|
||||
self.filter = nil
|
||||
end
|
||||
self:applyFilter()
|
||||
self.grid:draw()
|
||||
self.statusBar.filter:focus()
|
||||
|
||||
else
|
||||
UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function listingPage:enable()
|
||||
self:refresh()
|
||||
self:setFocus(self.statusBar.filter)
|
||||
UI.Page.enable(self)
|
||||
end
|
||||
|
||||
function listingPage:refresh()
|
||||
self.allItems = controller:listItems()
|
||||
mergeResources(self.allItems)
|
||||
self:applyFilter()
|
||||
end
|
||||
|
||||
function listingPage:applyFilter()
|
||||
local t = filterItems(self.allItems, self.filter)
|
||||
self.grid:setValues(t)
|
||||
end
|
||||
|
||||
local function jobMonitor(jobList)
|
||||
|
||||
local mon = Peripheral.getByType('monitor')
|
||||
|
||||
if mon then
|
||||
mon = UI.Device({
|
||||
device = device.monitor,
|
||||
textScale = .5,
|
||||
})
|
||||
else
|
||||
mon = UI.Device({
|
||||
device = Terminal.getNullTerm(term.current())
|
||||
})
|
||||
end
|
||||
|
||||
jobListGrid = UI.Grid {
|
||||
parent = mon,
|
||||
sortColumn = 'displayName',
|
||||
columns = {
|
||||
{ heading = 'Qty', key = 'count', width = 6 },
|
||||
{ heading = 'Crafting', key = 'displayName', width = mon.width / 2 - 10 },
|
||||
{ heading = 'Status', key = 'status', width = mon.width - 10 },
|
||||
},
|
||||
}
|
||||
|
||||
return jobListGrid
|
||||
end
|
||||
|
||||
UI:setPages({
|
||||
listing = listingPage,
|
||||
item = itemPage,
|
||||
})
|
||||
|
||||
UI:setPage(listingPage)
|
||||
listingPage:setFocus(listingPage.statusBar.filter)
|
||||
|
||||
local jobListGrid = jobMonitor()
|
||||
jobListGrid:draw()
|
||||
jobListGrid:sync()
|
||||
|
||||
function craftingThread()
|
||||
|
||||
while true do
|
||||
os.sleep(5)
|
||||
|
||||
--pcall(function()
|
||||
|
||||
local items = controller:listItems()
|
||||
|
||||
if not controller:isOnline() then
|
||||
jobListGrid.parent:clear()
|
||||
jobListGrid.parent:centeredWrite(math.ceil(jobListGrid.parent.height/2), 'Power failure')
|
||||
jobListGrid:sync()
|
||||
|
||||
elseif Util.size(items) == 0 then
|
||||
jobListGrid.parent:clear()
|
||||
jobListGrid.parent:centeredWrite(math.ceil(jobListGrid.parent.height/2), 'No items in system')
|
||||
jobListGrid:sync()
|
||||
|
||||
else
|
||||
local itemList = watchResources(items)
|
||||
jobListGrid:setValues(itemList)
|
||||
jobListGrid:draw()
|
||||
jobListGrid:sync()
|
||||
craftItems(itemList, items)
|
||||
--jobListGrid:update()
|
||||
jobListGrid:draw()
|
||||
jobListGrid:sync()
|
||||
|
||||
itemList = getAutocraftItems() -- autocrafted items don't show on job monitor
|
||||
craftItems(itemList, items)
|
||||
end
|
||||
--end)
|
||||
end
|
||||
end
|
||||
|
||||
UI:pullEvents(craftingThread)
|
||||
|
||||
UI.term:reset()
|
||||
jobListGrid.parent:reset()
|
708
sys/apps/treefarm.lua
Normal file
708
sys/apps/treefarm.lua
Normal file
@ -0,0 +1,708 @@
|
||||
requireInjector(getfenv(1))
|
||||
|
||||
--[[
|
||||
Requirements:
|
||||
Place turtle against an oak tree or oak sapling
|
||||
Area around turtle must be flat and can only be dirt or grass
|
||||
(9 blocks in each direction from turtle)
|
||||
Turtle must have: crafting table, chest
|
||||
Turtle must have a pick equipped on the left side
|
||||
|
||||
Optional:
|
||||
Add additional sapling types that can grow with a single sapling
|
||||
|
||||
Notes:
|
||||
If the turtle does not get any saplings from the initial tree, place
|
||||
down another sapling in front of the turtle.
|
||||
|
||||
The program will be able to survive server restarts as long as it has
|
||||
created the cobble line. If the program is stopped before that time,
|
||||
place the turtle in the original position before restarting the program.
|
||||
]]--
|
||||
|
||||
local ChestProvider = require('chestProvider18')
|
||||
local Craft = require('turtle.craft')
|
||||
local Level = require('turtle.level')
|
||||
local Point = require('point')
|
||||
local Util = require('util')
|
||||
|
||||
local FUEL_BASE = 0
|
||||
local FUEL_DIRE = FUEL_BASE + 10
|
||||
local FUEL_GOOD = FUEL_BASE + 2000
|
||||
|
||||
local MIN_CHARCOAL = 24
|
||||
local MAX_SAPLINGS = 32
|
||||
|
||||
local GRID_WIDTH = 8
|
||||
local GRID_LENGTH = 10
|
||||
local GRID = {
|
||||
TL = { x = 8, y = 0, z = -8 },
|
||||
TR = { x = 8, y = 0, z = 8 },
|
||||
BL = { x = -10, y = 0, z = -8 },
|
||||
BR = { x = -10, y = 0, z = 8 },
|
||||
}
|
||||
|
||||
local HOME_PT = { x = 0, y = 0, z = 0, heading = 0 }
|
||||
|
||||
local DIG_BLACKLIST = {
|
||||
[ 'minecraft:furnace' ] = true,
|
||||
[ 'minecraft:lit_furnace' ] = true,
|
||||
[ 'minecraft:chest' ] = true,
|
||||
}
|
||||
|
||||
local COBBLESTONE = 'minecraft:cobblestone:0'
|
||||
local CHARCOAL = 'minecraft:coal:1'
|
||||
local OAK_LOG = 'minecraft:log:0'
|
||||
local OAK_PLANK = 'minecraft:planks:0'
|
||||
local CHEST = 'minecraft:chest:0'
|
||||
local FURNACE = 'minecraft:furnace:0'
|
||||
local SAPLING = 'minecraft:sapling:0'
|
||||
local STONE = 'minecraft:stone:0'
|
||||
local TORCH = 'minecraft:torch:0'
|
||||
local DIRT = 'minecraft:dirt:0'
|
||||
local APPLE = 'minecraft:apple:0'
|
||||
local STICK = 'minecraft:stick:0'
|
||||
|
||||
local ALL_SAPLINGS = {
|
||||
SAPLING
|
||||
}
|
||||
|
||||
local state = Util.readTable('usr/config/treefarm') or {
|
||||
trees = {
|
||||
{ x = 1, y = 0, z = 0 }
|
||||
}
|
||||
}
|
||||
|
||||
local clock = os.clock()
|
||||
local recipes = Util.readTable('sys/etc/recipes.db') or { }
|
||||
|
||||
Craft.setRecipes(recipes)
|
||||
|
||||
local function inspect(fn)
|
||||
local s, item = fn()
|
||||
if s and item then
|
||||
return item.name .. ':' .. item.metadata
|
||||
end
|
||||
return 'minecraft:air:0'
|
||||
end
|
||||
|
||||
local function setState(key, value)
|
||||
state[key] = value
|
||||
Util.writeTable('usr/config/treefarm', state)
|
||||
end
|
||||
|
||||
local function refuel()
|
||||
if turtle.getFuelLevel() < FUEL_GOOD then
|
||||
local charcoal = turtle.getItemCount(CHARCOAL)
|
||||
if charcoal > 1 then
|
||||
turtle.refuel(CHARCOAL, math.min(charcoal - 1, MIN_CHARCOAL / 2))
|
||||
print('fuel: ' .. turtle.getFuelLevel())
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local function safePlaceBlock(item)
|
||||
|
||||
if turtle.placeUp(item) then
|
||||
return true
|
||||
end
|
||||
|
||||
local s, m = turtle.inspectUp()
|
||||
if s and not DIG_BLACKLIST[m.name] then
|
||||
turtle.digUp()
|
||||
return turtle.placeUp(item)
|
||||
end
|
||||
|
||||
turtle.forward()
|
||||
return turtle.placeUp(item)
|
||||
end
|
||||
|
||||
local function craftItem(item, qty)
|
||||
|
||||
local success
|
||||
|
||||
if safePlaceBlock(CHEST) then
|
||||
|
||||
if turtle.equip('left', 'minecraft:crafting_table') then
|
||||
|
||||
local chestProvider = ChestProvider({
|
||||
wrapSide = 'top',
|
||||
direction = 'down',
|
||||
})
|
||||
if not chestProvider:isValid() then
|
||||
print('invalid chestProvider')
|
||||
read()
|
||||
end
|
||||
-- turtle.emptyInventory(turtle.dropUp)
|
||||
|
||||
Util.print('Crafting %d %s', (qty or 1), item)
|
||||
success = Craft.craftRecipe(recipes[item], qty or 1, chestProvider)
|
||||
|
||||
repeat until not turtle.suckUp()
|
||||
end
|
||||
turtle.equip('left', 'minecraft:diamond_pickaxe')
|
||||
turtle.digUp()
|
||||
end
|
||||
|
||||
return success
|
||||
end
|
||||
|
||||
local function cook(item, count, result, fuel, fuelCount)
|
||||
|
||||
setState('cooking', true)
|
||||
|
||||
fuel = fuel or CHARCOAL
|
||||
fuelCount = fuelCount or math.ceil(count / 8)
|
||||
Util.print('Making %d %s', count, result)
|
||||
|
||||
turtle.dropForwardAt(state.furnace, fuel, fuelCount)
|
||||
turtle.dropDownAt(state.furnace, item, count)
|
||||
|
||||
count = count + turtle.getItemCount(result)
|
||||
turtle.select(1)
|
||||
turtle.pathfind(Point.below(state.furnace))
|
||||
repeat
|
||||
os.sleep(1)
|
||||
turtle.suckUp()
|
||||
until turtle.getItemCount(result) >= count
|
||||
|
||||
setState('cooking')
|
||||
end
|
||||
|
||||
local function makeSingleCharcoal()
|
||||
|
||||
local slots = turtle.getSummedInventory()
|
||||
|
||||
if not state.furnace or
|
||||
slots[CHARCOAL] or
|
||||
not slots[OAK_LOG] or
|
||||
slots[OAK_LOG].count < 2 then
|
||||
return true
|
||||
end
|
||||
|
||||
turtle.faceAgainst(state.furnace)
|
||||
if craftItem(OAK_PLANK) then
|
||||
cook(OAK_LOG, 1, CHARCOAL, OAK_PLANK, 1)
|
||||
turtle.refuel(OAK_PLANK)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function makeCharcoal()
|
||||
|
||||
local slots = turtle.getSummedInventory()
|
||||
|
||||
if not state.furnace or
|
||||
not slots[CHARCOAL] or
|
||||
slots[CHARCOAL].count >= MIN_CHARCOAL then
|
||||
return true
|
||||
end
|
||||
|
||||
local function getLogSlot(slots)
|
||||
local maxslot = { count = 0 }
|
||||
for k,slot in pairs(slots) do
|
||||
if string.match(k, 'minecraft:log') then
|
||||
if slot.count > maxslot.count then
|
||||
maxslot = slot
|
||||
end
|
||||
end
|
||||
end
|
||||
return maxslot
|
||||
end
|
||||
|
||||
repeat
|
||||
local slots = turtle.getSummedInventory()
|
||||
local charcoal = slots[CHARCOAL].count
|
||||
local slot = getLogSlot(slots)
|
||||
|
||||
if slot.count < 8 then
|
||||
break
|
||||
end
|
||||
|
||||
local toCook = math.min(charcoal, math.floor(slot.count / 8))
|
||||
toCook = math.min(toCook, math.floor((MIN_CHARCOAL + 8 - charcoal) / 8))
|
||||
toCook = toCook * 8
|
||||
|
||||
cook(slot.key, toCook, CHARCOAL)
|
||||
|
||||
until charcoal + toCook >= MIN_CHARCOAL
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function emptyFurnace()
|
||||
if state.cooking then
|
||||
|
||||
print('Emptying furnace')
|
||||
|
||||
turtle.suckDownAt(state.furnace)
|
||||
turtle.suckForwardAt(state.furnace)
|
||||
turtle.suckUpAt(state.furnace)
|
||||
setState('cooking')
|
||||
end
|
||||
end
|
||||
|
||||
local function getCobblestone(count)
|
||||
|
||||
local slots = turtle.getSummedInventory()
|
||||
|
||||
if not slots[COBBLESTONE] or slots[COBBLESTONE].count < count then
|
||||
|
||||
print('Collecting cobblestone')
|
||||
|
||||
slots[COBBLESTONE] = true
|
||||
slots[DIRT] = true
|
||||
|
||||
local pt = Point.copy(GRID.BR)
|
||||
pt.x = GRID.BR.x + 2
|
||||
pt.z = GRID.BR.z - 2
|
||||
|
||||
turtle.pathfind(pt)
|
||||
|
||||
repeat
|
||||
turtle.select(1)
|
||||
turtle.digDown()
|
||||
turtle.down()
|
||||
for i = 1, 3 do
|
||||
if inspect(turtle.inspect) == STONE then
|
||||
turtle.dig()
|
||||
end
|
||||
turtle.turnRight()
|
||||
end
|
||||
|
||||
for item in pairs(turtle.getSummedInventory()) do
|
||||
if not slots[item] then
|
||||
turtle.drop(item)
|
||||
end
|
||||
end
|
||||
|
||||
until turtle.getItemCount(COBBLESTONE) >= count
|
||||
|
||||
turtle.gotoPoint(pt)
|
||||
turtle.placeDown(DIRT)
|
||||
|
||||
turtle.drop(DIRT)
|
||||
end
|
||||
end
|
||||
|
||||
local function createFurnace()
|
||||
|
||||
if not state.furnace then
|
||||
if turtle.getFuelLevel() < FUEL_BASE + 100 then
|
||||
return true -- try again later
|
||||
end
|
||||
print('Adding a furnace')
|
||||
getCobblestone(8)
|
||||
|
||||
if craftItem(FURNACE) then
|
||||
turtle.drop(COBBLESTONE)
|
||||
local furnacePt = { x = GRID.BL.x + 2, y = 1, z = GRID.BL.z + 2 }
|
||||
turtle.placeAt(furnacePt, FURNACE)
|
||||
setState('furnace', furnacePt)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function createPerimeter()
|
||||
|
||||
if not state.perimeter then
|
||||
if not state.furnace or
|
||||
turtle.getFuelLevel() < FUEL_BASE + 500 or
|
||||
turtle.getItemCount(OAK_LOG) == 0 or
|
||||
not craftItem(OAK_PLANK, 2) then
|
||||
return true
|
||||
end
|
||||
|
||||
print('Creating a perimeter')
|
||||
|
||||
getCobblestone(GRID_WIDTH * 2 + 1)
|
||||
cook(COBBLESTONE, 2, STONE, OAK_PLANK, 2)
|
||||
turtle.refuel(OAK_PLANK)
|
||||
|
||||
turtle.pathfind(GRID.BL)
|
||||
turtle.digDown()
|
||||
turtle.placeDown(STONE)
|
||||
|
||||
turtle.setMoveCallback(function()
|
||||
local target = COBBLESTONE
|
||||
if math.abs(turtle.point.x) == GRID_LENGTH and
|
||||
math.abs(turtle.point.z) == GRID_WIDTH then
|
||||
target = STONE
|
||||
end
|
||||
|
||||
if inspect(turtle.inspectDown) ~= target then
|
||||
turtle.digDown()
|
||||
turtle.placeDown(target)
|
||||
end
|
||||
end)
|
||||
|
||||
turtle.pathfind(GRID.BR)
|
||||
|
||||
turtle.clearMoveCallback()
|
||||
turtle.drop(COBBLESTONE)
|
||||
turtle.drop(DIRT)
|
||||
|
||||
setState('perimeter', true)
|
||||
end
|
||||
end
|
||||
|
||||
local function createChests()
|
||||
if state.chest_1 then
|
||||
return false
|
||||
end
|
||||
if state.perimeter and
|
||||
turtle.getFuelLevel() > FUEL_BASE + 100 and
|
||||
Craft.canCraft(CHEST, 4, turtle.getSummedInventory()) then
|
||||
|
||||
print('Adding storage')
|
||||
if craftItem(CHEST, 4) then
|
||||
|
||||
local pt = Point.copy(GRID.BL)
|
||||
pt.x = pt.x + 1
|
||||
pt.y = pt.y - 1
|
||||
|
||||
for i = 1, 2 do
|
||||
pt.z = pt.z + 1
|
||||
|
||||
turtle.digDownAt(pt)
|
||||
turtle.placeDown(CHEST)
|
||||
|
||||
pt.z = pt.z + 1
|
||||
|
||||
turtle.digDownAt(pt)
|
||||
turtle.placeDown(CHEST)
|
||||
|
||||
setState('chest_' .. i, Util.shallowCopy(pt))
|
||||
|
||||
pt.z = pt.z + 1
|
||||
end
|
||||
turtle.drop(DIRT)
|
||||
turtle.refuel(OAK_PLANK)
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local function dropOffItems()
|
||||
|
||||
if state.chest_1 then
|
||||
local slots = turtle.getSummedInventory()
|
||||
|
||||
if state.chest_1 and
|
||||
slots[CHARCOAL] and
|
||||
slots[CHARCOAL].count >= MIN_CHARCOAL and
|
||||
(turtle.getItemCount('minecraft:log') > 0 or
|
||||
turtle.getItemCount('minecraft:log2') > 0) then
|
||||
|
||||
print('Storing logs')
|
||||
turtle.pathfind(state.chest_1)
|
||||
turtle.dropDown('minecraft:log')
|
||||
turtle.dropDown('minecraft:log2')
|
||||
end
|
||||
|
||||
if slots[APPLE] then
|
||||
print('Storing apples')
|
||||
turtle.dropDownAt(state.chest_2, APPLE)
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function eatSaplings()
|
||||
|
||||
local slots = turtle.getSummedInventory()
|
||||
|
||||
for _, sapling in pairs(ALL_SAPLINGS) do
|
||||
if slots[sapling] and slots[sapling].count > MAX_SAPLINGS then
|
||||
turtle.refuel(sapling, slots[sapling].count - MAX_SAPLINGS)
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local function placeTorches()
|
||||
if state.torches then
|
||||
return
|
||||
end
|
||||
|
||||
if Craft.canCraft(TORCH, 4, turtle.getSummedInventory()) and
|
||||
turtle.getFuelLevel() > 100 then
|
||||
|
||||
print('Placing torches')
|
||||
|
||||
if craftItem(TORCH, 4) then
|
||||
local pts = { }
|
||||
for x = -4, 4, 8 do
|
||||
for z = -4, 4, 8 do
|
||||
table.insert(pts, { x = x, y = 0, z = z })
|
||||
end
|
||||
end
|
||||
Point.eachClosest(turtle.point, pts, function(pt)
|
||||
turtle.placeAt(pt, TORCH)
|
||||
end)
|
||||
turtle.refuel(STICK)
|
||||
turtle.refuel(OAK_PLANK)
|
||||
setState('torches', true)
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function randomSapling()
|
||||
|
||||
local sapling = SAPLING
|
||||
|
||||
if #state.trees > 1 then
|
||||
ALL_SAPLINGS = { }
|
||||
|
||||
local slots = turtle.getFilledSlots()
|
||||
for _, slot in pairs(slots) do
|
||||
if slot.name == 'minecraft:sapling' then
|
||||
table.insert(ALL_SAPLINGS, slot.key)
|
||||
end
|
||||
end
|
||||
sapling = ALL_SAPLINGS[math.random(1, #ALL_SAPLINGS)]
|
||||
end
|
||||
|
||||
return sapling
|
||||
end
|
||||
|
||||
local function fellTree(pt)
|
||||
|
||||
local function desparateRefuel(min)
|
||||
if turtle.getFuelLevel() < min then
|
||||
local logs = turtle.getItemCount(OAK_LOG)
|
||||
if logs > 0 then
|
||||
if craftItem(OAK_PLANK, math.min(8, logs * 4)) then
|
||||
turtle.refuel(OAK_PLANK)
|
||||
print('fuel: ' .. turtle.getFuelLevel())
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
turtle.setMoveCallback(function() desparateRefuel(FUEL_DIRE) end)
|
||||
|
||||
desparateRefuel(FUEL_DIRE)
|
||||
|
||||
if turtle.digUpAt(Point.above(pt)) then
|
||||
Level(
|
||||
{ x = GRID_WIDTH-1, y = 1, z = GRID_WIDTH-1 },
|
||||
{ x = -(GRID_WIDTH-1), y = 50, z = -(GRID_WIDTH-1) },
|
||||
Point.above(pt))
|
||||
end
|
||||
|
||||
desparateRefuel(FUEL_BASE + 100)
|
||||
turtle.clearMoveCallback()
|
||||
turtle.setPolicy("attack")
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function fell()
|
||||
|
||||
local pts = Util.shallowCopy(state.trees)
|
||||
|
||||
local pt = table.remove(pts, math.random(1, #pts))
|
||||
if not turtle.faceAgainst(pt) or
|
||||
not string.match(inspect(turtle.inspect), 'minecraft:log') then
|
||||
return true
|
||||
end
|
||||
|
||||
print('Chopping')
|
||||
|
||||
local fuel = turtle.getFuelLevel()
|
||||
table.insert(pts, 1, pt)
|
||||
|
||||
Point.eachClosest(turtle.point, pts, function(pt)
|
||||
if turtle.faceAgainst(pt) and
|
||||
string.match(inspect(turtle.inspect), 'minecraft:log') then
|
||||
turtle.dig()
|
||||
fellTree(pt)
|
||||
end
|
||||
turtle.placeAt(pt, randomSapling())
|
||||
end)
|
||||
|
||||
print('Used ' .. (fuel - turtle.getFuelLevel()) .. ' fuel')
|
||||
return true
|
||||
end
|
||||
|
||||
local function moreTrees()
|
||||
|
||||
if #state.trees > 1 then
|
||||
return
|
||||
end
|
||||
|
||||
if not state.chest_1 or turtle.getItemCount(SAPLING) < 9 then
|
||||
return true
|
||||
end
|
||||
|
||||
print('Adding more trees')
|
||||
|
||||
local singleTree = state.trees[1]
|
||||
|
||||
state.trees = { }
|
||||
for x = -2, 2, 2 do
|
||||
for z = -2, 2, 2 do
|
||||
table.insert(state.trees, { x = x, y = 0, z = z })
|
||||
end
|
||||
end
|
||||
|
||||
turtle.digAt(singleTree)
|
||||
fellTree(singleTree)
|
||||
|
||||
setState('trees', state.trees)
|
||||
|
||||
Point.eachClosest(turtle.point, state.trees, function(pt)
|
||||
turtle.placeDownAt(pt, randomSapling())
|
||||
end)
|
||||
end
|
||||
|
||||
function getTurtleFacing(block)
|
||||
local directions = {
|
||||
[5] = 2,
|
||||
[3] = 3,
|
||||
[4] = 0,
|
||||
[2] = 1,
|
||||
}
|
||||
|
||||
if not safePlaceBlock(block) then
|
||||
error('unable to place chest above')
|
||||
end
|
||||
local _, bi = turtle.inspectUp()
|
||||
turtle.digUp()
|
||||
return directions[bi.metadata]
|
||||
end
|
||||
|
||||
function saveTurtleFacing()
|
||||
if not state.facing then
|
||||
setState('facing', getTurtleFacing(CHEST))
|
||||
end
|
||||
end
|
||||
|
||||
local function findGround()
|
||||
print('Locating ground level')
|
||||
turtle.setPoint(HOME_PT)
|
||||
|
||||
while true do
|
||||
local s, block = turtle.inspectDown()
|
||||
|
||||
if not s then block = { name = 'minecraft:air', metadata = 0 } end
|
||||
b = block.name .. ':' .. block.metadata
|
||||
|
||||
if b == 'minecraft:dirt:0' or
|
||||
b == 'minecraft:grass:0' or
|
||||
block.name == 'minecraft:chest' then
|
||||
break
|
||||
end
|
||||
|
||||
if b == COBBLESTONE or b == STONE then
|
||||
error('lost')
|
||||
end
|
||||
|
||||
if b == TORCH or b == FURNACE then
|
||||
turtle.forward()
|
||||
else
|
||||
turtle.digDown()
|
||||
turtle.down()
|
||||
end
|
||||
|
||||
if turtle.point.y < -20 then
|
||||
error('lost')
|
||||
end
|
||||
end
|
||||
turtle.setPoint(HOME_PT)
|
||||
end
|
||||
|
||||
local function findHome()
|
||||
|
||||
if not state.perimeter then
|
||||
return
|
||||
end
|
||||
|
||||
print('Determining location')
|
||||
|
||||
turtle.point.heading = getTurtleFacing(CHEST)
|
||||
turtle.setHeading(state.facing)
|
||||
turtle.point.heading = 0
|
||||
|
||||
local pt = Point.copy(turtle.point)
|
||||
|
||||
while inspect(turtle.inspectDown) ~= COBBLESTONE do
|
||||
pt.x = pt.x - 1
|
||||
turtle.pathfind(pt)
|
||||
if pt.x < -16 then
|
||||
error('lost')
|
||||
end
|
||||
end
|
||||
while inspect(turtle.inspectDown) == COBBLESTONE do
|
||||
pt.z = pt.z - 1
|
||||
turtle.pathfind(pt)
|
||||
if pt.z < -16 then
|
||||
error('lost')
|
||||
end
|
||||
end
|
||||
|
||||
turtle.setPoint({
|
||||
x = -(GRID_LENGTH),
|
||||
y = 0,
|
||||
z = -GRID_WIDTH,
|
||||
heading = turtle.point.heading
|
||||
})
|
||||
end
|
||||
|
||||
local function updateClock()
|
||||
|
||||
local ONE_HOUR = 50
|
||||
|
||||
if os.clock() - clock > ONE_HOUR then
|
||||
clock = os.clock()
|
||||
else
|
||||
print('sleeping for ' .. math.floor(ONE_HOUR - (os.clock() - clock)))
|
||||
os.sleep(ONE_HOUR - (os.clock() - clock))
|
||||
clock = os.clock()
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local tasks = {
|
||||
{ desc = 'Finding ground', fn = findGround },
|
||||
{ desc = 'Determine facing', fn = saveTurtleFacing },
|
||||
{ desc = 'Finding home', fn = findHome },
|
||||
{ desc = 'Adding trees', fn = moreTrees },
|
||||
{ desc = 'Chopping', fn = fell },
|
||||
{ desc = 'Snacking', fn = eatSaplings },
|
||||
{ desc = 'Creating chest', fn = createChests },
|
||||
{ desc = 'Creating furnace', fn = createFurnace },
|
||||
{ desc = 'Emptying furnace', fn = emptyFurnace },
|
||||
{ desc = 'Making charcoal', fn = makeSingleCharcoal },
|
||||
{ desc = 'Making charcoal', fn = makeCharcoal },
|
||||
{ desc = 'Creating perimeter', fn = createPerimeter },
|
||||
{ desc = 'Placing torches', fn = placeTorches },
|
||||
{ desc = 'Refueling', fn = refuel },
|
||||
{ desc = 'Dropping off items', fn = dropOffItems },
|
||||
{ desc = 'Condensing', fn = turtle.condense },
|
||||
{ desc = 'Sleeping', fn = updateClock },
|
||||
}
|
||||
|
||||
turtle.run(function()
|
||||
|
||||
turtle.setPolicy("attack")
|
||||
|
||||
while not turtle.abort do
|
||||
print('fuel: ' .. turtle.getFuelLevel())
|
||||
for _,task in ipairs(Util.shallowCopy(tasks)) do
|
||||
--print(task.desc)
|
||||
turtle.status = task.desc
|
||||
turtle.select(1)
|
||||
if not task.fn() then
|
||||
Util.filterInplace(tasks, function(v) return v.fn ~= task.fn end)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
@ -1,17 +1,16 @@
|
||||
if turtle and device.wireless_modem then
|
||||
|
||||
local s, m = turtle.run(function()
|
||||
local homePt = turtle.loadLocation('gpsHome')
|
||||
|
||||
if homePt then
|
||||
requireInjector(getfenv(1))
|
||||
|
||||
requireInjector(getfenv(1))
|
||||
local Config = require('config')
|
||||
local config = {
|
||||
destructive = false,
|
||||
}
|
||||
Config.load('gps', config)
|
||||
|
||||
local Config = require('config')
|
||||
local config = {
|
||||
destructive = false,
|
||||
}
|
||||
Config.load('gps', config)
|
||||
if config.home then
|
||||
|
||||
local s = turtle.enableGPS(2)
|
||||
if not s then
|
||||
@ -30,7 +29,7 @@ if turtle and device.wireless_modem then
|
||||
turtle.setPolicy('turtleSafe')
|
||||
end
|
||||
|
||||
if not turtle.pathfind(homePt) then
|
||||
if not turtle.pathfind(config.home) then
|
||||
error('Failed to return home')
|
||||
end
|
||||
end
|
||||
|
2146
sys/etc/recipes.db
Normal file
2146
sys/etc/recipes.db
Normal file
File diff suppressed because it is too large
Load Diff
@ -46,7 +46,7 @@ local function follow(id)
|
||||
addBlocks(pt)
|
||||
addBlocks({ x = pt.x, z = pt.z, y = pt.y + 1 })
|
||||
|
||||
if turtle.pathfind(cpt, blocks) then
|
||||
if turtle.pathfind(cpt, { blocks = blocks }) then
|
||||
turtle.headTowards(pt)
|
||||
end
|
||||
following = false
|
||||
|
@ -23,7 +23,7 @@ turtle.run(function()
|
||||
error('turtle: No GPS response')
|
||||
end
|
||||
|
||||
if not turtle.pathfind(pt, nil, 64) then
|
||||
if not turtle.pathfind(pt) then
|
||||
error('Unable to go to location')
|
||||
end
|
||||
end)
|
||||
|
@ -1,86 +0,0 @@
|
||||
if not turtle or turtle.abortAction then
|
||||
return
|
||||
end
|
||||
|
||||
requireInjector(getfenv(1))
|
||||
|
||||
local Util = require('util')
|
||||
|
||||
local Scheduler = {
|
||||
uid = 0,
|
||||
queue = { },
|
||||
idle = true,
|
||||
}
|
||||
|
||||
function turtle.abortAction()
|
||||
if turtle.status ~= 'idle' then
|
||||
turtle.abort = true
|
||||
os.queueEvent('turtle_abort')
|
||||
end
|
||||
Util.clear(Scheduler.queue)
|
||||
os.queueEvent('turtle_ticket', 0, true)
|
||||
end
|
||||
|
||||
local function getTicket(fn, ...)
|
||||
Scheduler.uid = Scheduler.uid + 1
|
||||
|
||||
if Scheduler.idle then
|
||||
Scheduler.idle = false
|
||||
turtle.status = 'busy'
|
||||
os.queueEvent('turtle_ticket', Scheduler.uid)
|
||||
else
|
||||
table.insert(Scheduler.queue, Scheduler.uid)
|
||||
end
|
||||
|
||||
return Scheduler.uid
|
||||
end
|
||||
|
||||
local function releaseTicket(id)
|
||||
for k,v in ipairs(Scheduler.queue) do
|
||||
if v == id then
|
||||
table.remove(Scheduler.queue, k)
|
||||
return
|
||||
end
|
||||
end
|
||||
local id = table.remove(Scheduler.queue, 1)
|
||||
if id then
|
||||
os.queueEvent('turtle_ticket', id)
|
||||
else
|
||||
Scheduler.idle = true
|
||||
turtle.status = 'idle'
|
||||
end
|
||||
end
|
||||
|
||||
function turtle.run(fn, ...)
|
||||
local ticketId = getTicket()
|
||||
|
||||
if type(fn) == 'string' then
|
||||
fn = turtle[fn]
|
||||
end
|
||||
while true do
|
||||
local e, id, abort = os.pullEventRaw('turtle_ticket')
|
||||
if e == 'terminate' then
|
||||
releaseTicket(ticketId)
|
||||
os.queueEvent('turtle_response')
|
||||
error('Terminated')
|
||||
end
|
||||
if abort then
|
||||
-- the function was queued, but the queue was cleared
|
||||
os.queueEvent('turtle_response')
|
||||
return false, 'aborted'
|
||||
end
|
||||
if id == ticketId then
|
||||
turtle.abort = false
|
||||
turtle.resetState()
|
||||
local args = { ... }
|
||||
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
|
||||
return s, m
|
||||
end
|
||||
end
|
||||
end
|
@ -3,7 +3,9 @@ if not turtle or turtle.enableGPS then
|
||||
end
|
||||
|
||||
requireInjector(getfenv(1))
|
||||
local GPS = require('gps')
|
||||
|
||||
local GPS = require('gps')
|
||||
local Config = require('config')
|
||||
|
||||
function turtle.enableGPS(timeout)
|
||||
if turtle.point.gps then
|
||||
@ -18,17 +20,23 @@ function turtle.enableGPS(timeout)
|
||||
end
|
||||
|
||||
function turtle.gotoGPSHome()
|
||||
local homePt = turtle.loadLocation('gpsHome')
|
||||
if homePt then
|
||||
local config = { }
|
||||
Config.load('gps', config)
|
||||
|
||||
if config.home then
|
||||
if turtle.enableGPS() then
|
||||
turtle.pathfind(homePt)
|
||||
turtle.pathfind(config.home)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function turtle.setGPSHome()
|
||||
local config = { }
|
||||
Config.load('gps', config)
|
||||
|
||||
if turtle.enableGPS() then
|
||||
turtle.storeLocation('gpsHome', turtle.point)
|
||||
config.home = turtle.point
|
||||
Config.update('gps', config)
|
||||
turtle.gotoPoint(turtle.point)
|
||||
end
|
||||
end
|
||||
|
@ -4,7 +4,10 @@ end
|
||||
|
||||
requireInjector(getfenv(1))
|
||||
|
||||
local Util = require('util')
|
||||
local Point = require('point')
|
||||
local synchronized = require('sync')
|
||||
local Util = require('util')
|
||||
turtle.pathfind = require('turtle.pathfind')
|
||||
|
||||
local function noop() end
|
||||
|
||||
@ -147,6 +150,29 @@ function turtle.getHeadingInfo(heading)
|
||||
end
|
||||
|
||||
-- [[ Basic turtle actions ]] --
|
||||
local function inventoryAction(fn, name, qty)
|
||||
local slots = turtle.getFilledSlots()
|
||||
local s
|
||||
for _,slot in pairs(slots) do
|
||||
if slot.key == name or slot.name == name then
|
||||
turtle.native.select(slot.index)
|
||||
if not qty then
|
||||
s = fn()
|
||||
else
|
||||
s = fn(math.min(qty, slot.count))
|
||||
qty = qty - slot.count
|
||||
if qty < 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if not s then
|
||||
return false, 'No items found'
|
||||
end
|
||||
return s
|
||||
end
|
||||
|
||||
local function _attack(action)
|
||||
if action.attack() then
|
||||
repeat until not action.attack()
|
||||
@ -193,25 +219,24 @@ function turtle.place(slot) return _place(actions.forward, slot) end
|
||||
function turtle.placeUp(slot) return _place(actions.up, slot) end
|
||||
function turtle.placeDown(slot) return _place(actions.down, slot) end
|
||||
|
||||
local function _drop(action, count, indexOrId)
|
||||
|
||||
if indexOrId then
|
||||
local slot = turtle.getSlot(indexOrId)
|
||||
if not slot or slot.qty == 0 then
|
||||
return false, 'No items to drop'
|
||||
end
|
||||
turtle.select(slot.index)
|
||||
local function _drop(action, qtyOrName, qty)
|
||||
if not qtyOrName or type(qtyOrName) == 'number' then
|
||||
return action.drop(qtyOrName)
|
||||
end
|
||||
if not count then
|
||||
return action.drop() -- wtf
|
||||
end
|
||||
return action.drop(count)
|
||||
return inventoryAction(action.drop, qtyOrName, qty)
|
||||
end
|
||||
|
||||
function turtle.drop(count, slot) return _drop(actions.forward, count, slot) end
|
||||
function turtle.dropUp(count, slot) return _drop(actions.up, count, slot) end
|
||||
function turtle.dropDown(count, slot) return _drop(actions.down, count, slot) end
|
||||
|
||||
function turtle.refuel(qtyOrName, qty)
|
||||
if not qtyOrName or type(qtyOrName) == 'number' then
|
||||
return turtle.native.refuel(qtyOrName)
|
||||
end
|
||||
return inventoryAction(turtle.native.refuel, qtyOrName, qty)
|
||||
end
|
||||
|
||||
--[[
|
||||
function turtle.dig() return state.dig(actions.forward) end
|
||||
function turtle.digUp() return state.dig(actions.up) end
|
||||
@ -277,7 +302,7 @@ turtle.movePolicies = {
|
||||
return false
|
||||
end
|
||||
local oldStatus = turtle.status
|
||||
print('stuck')
|
||||
print('assured move: stuck')
|
||||
turtle.status = 'stuck'
|
||||
repeat
|
||||
os.sleep(1)
|
||||
@ -328,6 +353,7 @@ function turtle.setDigPolicy(policy) state.digPolicy = policy end
|
||||
function turtle.setAttackPolicy(policy) state.attackPolicy = policy end
|
||||
function turtle.setMoveCallback(cb) state.moveCallback = cb end
|
||||
function turtle.clearMoveCallback() state.moveCallback = noop end
|
||||
function turtle.getMoveCallback() return state.moveCallback end
|
||||
|
||||
-- [[ Locations ]] --
|
||||
function turtle.getLocation(name)
|
||||
@ -751,31 +777,47 @@ function turtle.getSlot(indexOrId, slots)
|
||||
local detail = turtle.getItemDetail(indexOrId)
|
||||
if detail then
|
||||
return {
|
||||
name = detail.name,
|
||||
damage = detail.damage,
|
||||
count = detail.count,
|
||||
key = detail.name .. ':' .. detail.damage,
|
||||
|
||||
index = indexOrId,
|
||||
|
||||
-- deprecate
|
||||
qty = detail.count,
|
||||
dmg = detail.damage,
|
||||
id = detail.name,
|
||||
iddmg = detail.name .. ':' .. detail.damage,
|
||||
index = indexOrId,
|
||||
}
|
||||
end
|
||||
|
||||
return {
|
||||
qty = 0,
|
||||
qty = 0, -- deprecate
|
||||
count = 0,
|
||||
index = indexOrId,
|
||||
}
|
||||
end
|
||||
|
||||
function turtle.selectSlot(indexOrId)
|
||||
function turtle.select(indexOrId)
|
||||
|
||||
if type(indexOrId) == 'number' then
|
||||
return turtle.native.select(indexOrId)
|
||||
end
|
||||
|
||||
local s = turtle.getSlot(indexOrId)
|
||||
if s then
|
||||
turtle.select(s.index)
|
||||
turtle.native.select(s.index)
|
||||
return s
|
||||
end
|
||||
|
||||
return false, 'Inventory does not contain item'
|
||||
end
|
||||
|
||||
function turtle.selectSlot(indexOrId) -- deprecated
|
||||
return turtle.select(indexOrId)
|
||||
end
|
||||
|
||||
function turtle.getInventory(slots)
|
||||
slots = slots or { }
|
||||
for i = 1, 16 do
|
||||
@ -784,11 +826,38 @@ function turtle.getInventory(slots)
|
||||
return slots
|
||||
end
|
||||
|
||||
function turtle.getSummedInventory()
|
||||
local slots = turtle.getFilledSlots()
|
||||
local t = { }
|
||||
for _,slot in pairs(slots) do
|
||||
local entry = t[slot.iddmg]
|
||||
if not entry then
|
||||
entry = {
|
||||
count = 0,
|
||||
damage = slot.damage,
|
||||
name = slot.name,
|
||||
key = slot.key,
|
||||
|
||||
-- deprecate
|
||||
qty = 0,
|
||||
dmg = slot.dmg,
|
||||
id = slot.id,
|
||||
iddmg = slot.iddmg,
|
||||
}
|
||||
t[slot.iddmg] = entry
|
||||
end
|
||||
entry.qty = entry.qty + slot.qty
|
||||
entry.count = entry.qty
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
function turtle.emptyInventory(dropAction)
|
||||
dropAction = dropAction or turtle.drop
|
||||
for i = 1, 16 do
|
||||
turtle.emptySlot(i, dropAction)
|
||||
end
|
||||
turtle.select(1)
|
||||
end
|
||||
|
||||
function turtle.emptySlot(slot, dropAction)
|
||||
@ -857,28 +926,243 @@ function turtle.selectSlotWithQuantity(qty, startSlot)
|
||||
end
|
||||
end
|
||||
|
||||
function turtle.condense(startSlot)
|
||||
startSlot = startSlot or 1
|
||||
local aslots = turtle.getInventory()
|
||||
|
||||
for _,slot in ipairs(aslots) do
|
||||
if slot.qty < 64 then
|
||||
for i = slot.index + 1, 16 do
|
||||
local fslot = aslots[i]
|
||||
if fslot.qty > 0 then
|
||||
if slot.qty == 0 or slot.iddmg == fslot.iddmg then
|
||||
turtle.select(fslot.index)
|
||||
turtle.transferTo(slot.index, 64)
|
||||
local transferred = turtle.getItemCount(slot.index) - slot.qty
|
||||
slot.qty = slot.qty + transferred
|
||||
fslot.qty = fslot.qty - transferred
|
||||
slot.iddmg = fslot.iddmg
|
||||
if slot.qty == 64 then
|
||||
break
|
||||
end
|
||||
function turtle.condense()
|
||||
local slots = turtle.getInventory()
|
||||
|
||||
for i = 16, 1, -1 do
|
||||
if slots[i].count > 0 then
|
||||
for j = 1, i - 1 do
|
||||
if slots[j].count == 0 or slots[i].key == slots[j].key then
|
||||
turtle.select(i)
|
||||
turtle.transferTo(j, 64)
|
||||
local transferred = slots[i].qty - turtle.getItemCount(i)
|
||||
slots[j].count = slots[j].count + transferred
|
||||
slots[i].count = slots[i].count - transferred
|
||||
slots[j].key = slots[i].key
|
||||
if slots[i].count == 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function turtle.getItemCount(idOrName)
|
||||
if type(idOrName) == 'number' then
|
||||
return turtle.native.getItemCount(idOrName)
|
||||
end
|
||||
local slots = turtle.getFilledSlots()
|
||||
local count = 0
|
||||
for _,slot in pairs(slots) do
|
||||
if slot.iddmg == idOrName or slot.name == idOrName then
|
||||
count = count + slot.qty
|
||||
end
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
function turtle.equip(side, item)
|
||||
|
||||
if item then
|
||||
if not turtle.select(item) then
|
||||
return false, 'Unable to equip ' .. item
|
||||
end
|
||||
end
|
||||
|
||||
if side == 'left' then
|
||||
return turtle.equipLeft()
|
||||
end
|
||||
return turtle.equipRight()
|
||||
end
|
||||
|
||||
function turtle.run(fn, ...)
|
||||
local args = { ... }
|
||||
local s, m
|
||||
|
||||
if type(fn) == 'string' then
|
||||
fn = turtle[fn]
|
||||
end
|
||||
|
||||
synchronized(turtle, function()
|
||||
turtle.abort = false
|
||||
turtle.status = 'busy'
|
||||
turtle.resetState()
|
||||
s, m = pcall(function() fn(unpack(args)) end)
|
||||
turtle.abort = false
|
||||
turtle.status = 'idle'
|
||||
if not s and m then
|
||||
printError(m)
|
||||
end
|
||||
end)
|
||||
|
||||
return s, m
|
||||
end
|
||||
|
||||
function turtle.abortAction()
|
||||
if turtle.status ~= 'idle' then
|
||||
turtle.abort = true
|
||||
os.queueEvent('turtle_abort')
|
||||
end
|
||||
end
|
||||
|
||||
-- [[ Pathing ]] --
|
||||
function turtle.faceAgainst(pt) -- 4 sided
|
||||
|
||||
local pts = { }
|
||||
|
||||
for i = 0, 3 do
|
||||
local hi = turtle.getHeadingInfo(i)
|
||||
|
||||
table.insert(pts, {
|
||||
x = pt.x + hi.xd,
|
||||
z = pt.z + hi.zd,
|
||||
y = pt.y + hi.yd,
|
||||
heading = (hi.heading + 2) % 4,
|
||||
})
|
||||
end
|
||||
|
||||
return turtle.pathfind(Point.closest(turtle.point, pts), { dest = pts })
|
||||
end
|
||||
|
||||
function turtle.moveAgainst(pt) -- 6 sided
|
||||
|
||||
local pts = { }
|
||||
|
||||
for i = 0, 5 do
|
||||
local hi = turtle.getHeadingInfo(i)
|
||||
local heading, direction
|
||||
if i < 4 then
|
||||
heading = (hi.heading + 2) % 4
|
||||
direction = 'forward'
|
||||
elseif i == 4 then
|
||||
direction = 'down'
|
||||
elseif i == 5 then
|
||||
direction = 'up'
|
||||
end
|
||||
|
||||
table.insert(pts, {
|
||||
x = pt.x + hi.xd,
|
||||
z = pt.z + hi.zd,
|
||||
y = pt.y + hi.yd,
|
||||
direction = direction,
|
||||
heading = heading,
|
||||
})
|
||||
end
|
||||
|
||||
return turtle.pathfind(Point.closest(turtle.point, pts), { dest = pts })
|
||||
end
|
||||
|
||||
local actionsAt = {
|
||||
detect = {
|
||||
up = turtle.detectUp,
|
||||
down = turtle.detectDown,
|
||||
forward = turtle.detect,
|
||||
},
|
||||
dig = {
|
||||
up = turtle.digUp,
|
||||
down = turtle.digDown,
|
||||
forward = turtle.dig,
|
||||
},
|
||||
move = {
|
||||
up = turtle.moveUp,
|
||||
down = turtle.moveDown,
|
||||
forward = turtle.move,
|
||||
},
|
||||
attack = {
|
||||
up = turtle.attackUp,
|
||||
down = turtle.attackDown,
|
||||
forward = turtle.attack,
|
||||
},
|
||||
place = {
|
||||
up = turtle.placeUp,
|
||||
down = turtle.placeDown,
|
||||
forward = turtle.place,
|
||||
},
|
||||
drop = {
|
||||
up = turtle.dropUp,
|
||||
down = turtle.dropDown,
|
||||
forward = turtle.drop,
|
||||
},
|
||||
suck = {
|
||||
up = turtle.suckUp,
|
||||
down = turtle.suckDown,
|
||||
forward = turtle.suck,
|
||||
},
|
||||
compare = {
|
||||
up = turtle.compareUp,
|
||||
down = turtle.compareDown,
|
||||
forward = turtle.compare,
|
||||
},
|
||||
inspect = {
|
||||
up = turtle.inspectUp,
|
||||
down = turtle.inspectDown,
|
||||
forward = turtle.inspect,
|
||||
},
|
||||
}
|
||||
|
||||
local function _actionAt(action, pt, ...)
|
||||
local pt = turtle.moveAgainst(pt)
|
||||
if pt then
|
||||
return action[pt.direction](...)
|
||||
end
|
||||
end
|
||||
|
||||
function _actionDownAt(action, pt, ...)
|
||||
if turtle.pathfind(Point.above(pt)) then
|
||||
return action.down(...)
|
||||
end
|
||||
end
|
||||
|
||||
function _actionForwardAt(action, pt, ...)
|
||||
if turtle.faceAgainst(pt) then
|
||||
return action.forward(...)
|
||||
end
|
||||
end
|
||||
|
||||
function _actionUpAt(action, pt, ...)
|
||||
if turtle.pathfind(Point.below(pt)) then
|
||||
return action.up(...)
|
||||
end
|
||||
end
|
||||
|
||||
function turtle.detectAt(pt) return _actionAt(actionsAt.detect, pt) end
|
||||
function turtle.detectDownAt(pt) return _actionDownAt(actionsAt.detect, pt) end
|
||||
function turtle.detectForwardAt(pt) return _actionForwardAt(actionsAt.detect, pt) end
|
||||
function turtle.detectUpAt(pt) return _actionUpAt(actionsAt.detect, pt) end
|
||||
|
||||
function turtle.digAt(pt) return _actionAt(actionsAt.dig, pt) end
|
||||
function turtle.digDownAt(pt) return _actionDownAt(actionsAt.dig, pt) end
|
||||
function turtle.digForwardAt(pt) return _actionForwardAt(actionsAt.dig, pt) end
|
||||
function turtle.digUpAt(pt) return _actionUpAt(actionsAt.dig, pt) end
|
||||
|
||||
function turtle.attackAt(pt) return _actionAt(actionsAt.attack, pt) end
|
||||
function turtle.attackDownAt(pt) return _actionDownAt(actionsAt.attack, pt) end
|
||||
function turtle.attackForwardAt(pt) return _actionForwardAt(actionsAt.attack, pt) end
|
||||
function turtle.attackUpAt(pt) return _actionUpAt(actionsAt.attack, pt) end
|
||||
|
||||
function turtle.placeAt(pt, arg) return _actionAt(actionsAt.place, pt, arg) end
|
||||
function turtle.placeDownAt(pt, arg) return _actionDownAt(actionsAt.place, pt, arg) end
|
||||
function turtle.placeForwardAt(pt, arg) return _actionForwardAt(actionsAt.place, pt, arg) end
|
||||
function turtle.placeUpAt(pt, arg) return _actionUpAt(actionsAt.place, pt, arg) end
|
||||
|
||||
function turtle.dropAt(pt, ...) return _actionAt(actionsAt.drop, pt, ...) end
|
||||
function turtle.dropDownAt(pt, ...) return _actionDownAt(actionsAt.drop, pt, ...) end
|
||||
function turtle.dropForwardAt(pt, ...) return _actionForwardAt(actionsAt.drop, pt, ...) end
|
||||
function turtle.dropUpAt(pt, ...) return _actionUpAt(actionsAt.drop, pt, ...) end
|
||||
|
||||
function turtle.suckAt(pt, qty) return _actionAt(actionsAt.suck, pt, qty) end
|
||||
function turtle.suckDownAt(pt, qty) return _actionDownAt(actionsAt.suck, pt, qty) end
|
||||
function turtle.suckForwardAt(pt, qty) return _actionForwardAt(actionsAt.suck, pt, qty) end
|
||||
function turtle.suckUpAt(pt, qty) return _actionUpAt(actionsAt.suck, pt, qty) end
|
||||
|
||||
function turtle.compareAt(pt) return _actionAt(actionsAt.compare, pt) end
|
||||
function turtle.compareDownAt(pt) return _actionDownAt(actionsAt.compare, pt) end
|
||||
function turtle.compareForwardAt(pt) return _actionForwardAt(actionsAt.compare, pt) end
|
||||
function turtle.compareUpAt(pt) return _actionUpAt(actionsAt.compare, pt) end
|
||||
|
||||
function turtle.inspectAt(pt) return _actionAt(actionsAt.inspect, pt) end
|
||||
function turtle.inspectDownAt(pt) return _actionDownAt(actionsAt.inspect, pt) end
|
||||
function turtle.inspectForwardAt(pt) return _actionForwardAt(actionsAt.inspect, pt) end
|
||||
function turtle.inspectUpAt(pt) return _actionUpAt(actionsAt.inspect, pt) end
|
||||
|
@ -2,41 +2,15 @@ if device.wireless_modem then
|
||||
|
||||
requireInjector(getfenv(1))
|
||||
local Config = require('config')
|
||||
local config = {
|
||||
host = false,
|
||||
auto = false,
|
||||
x = 0,
|
||||
y = 0,
|
||||
z = 0,
|
||||
}
|
||||
|
||||
local config = { }
|
||||
Config.load('gps', config)
|
||||
|
||||
if config.host then
|
||||
|
||||
multishell.setTitle(multishell.getCurrent(), 'GPS Daemon')
|
||||
|
||||
if config.auto then
|
||||
local GPS = require('gps')
|
||||
local pt
|
||||
|
||||
for i = 1, 3 do
|
||||
pt = GPS.getPoint(10, true)
|
||||
if pt then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not pt then
|
||||
error('Unable to get GPS coordinates')
|
||||
end
|
||||
|
||||
config.x = pt.x
|
||||
config.y = pt.y
|
||||
config.z = pt.z
|
||||
end
|
||||
|
||||
os.run(getfenv(1), '/rom/programs/gps', 'host', config.x, config.y, config.z)
|
||||
os.run(getfenv(1), '/rom/programs/gps', 'host', config.host.x, config.host.y, config.host.z)
|
||||
|
||||
print('GPS daemon stopped')
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user