treefarm + turtle improvements + cleanup

This commit is contained in:
kepler155c@gmail.com
2017-09-12 23:04:44 -04:00
parent e50e6da700
commit 9aca96cc3e
21 changed files with 3652 additions and 1190 deletions

View File

@@ -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()

View File

@@ -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

View File

@@ -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
View 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)