Autocrafting improvements

This commit is contained in:
kepler155c
2017-12-23 01:48:36 -05:00
parent 863b7ff600
commit 85c5f542a8
10 changed files with 1059 additions and 232 deletions

View File

@@ -86,13 +86,12 @@ function ChestAdapter:listItems(throttle)
if not entry then if not entry then
self.cache[key] = v self.cache[key] = v
local ikey = { v.name, v.damage, v.nbtHash } if not itemDB:get(v) then
if not itemDB:get(ikey) then
local t = { } local t = { }
for _,k in pairs(keys) do for _,k in pairs(keys) do
t[k] = v[k] t[k] = v[k]
end end
itemDB:add(ikey, t) itemDB:add(t)
end end
else else
entry.count = entry.count + v.count entry.count = entry.count + v.count

View File

@@ -51,17 +51,20 @@ function ChestAdapter:isValid()
end end
function ChestAdapter:getCachedItemDetails(item, k) function ChestAdapter:getCachedItemDetails(item, k)
local key = { item.name, item.damage, item.nbtHash } local detail = itemDB:get(item)
local detail = itemDB:get(key)
if not detail then if not detail then
pcall(function() detail = self.getItemMeta(k) end) pcall(function() detail = self.getItemMeta(k) end)
if not detail then if not detail then
debug(item)
debug('no details')
-- error('Inventory has changed') -- error('Inventory has changed')
return return
end end
-- NOT SUFFICIENT -- NOT SUFFICIENT
if detail.name ~= item.name then if detail.name ~= item.name then
debug('name change ?')
debug(item)
debug(detail)
-- error('Inventory has changed') -- error('Inventory has changed')
return return
end end
@@ -72,7 +75,8 @@ function ChestAdapter:getCachedItemDetails(item, k)
end end
end end
itemDB:add(key, detail) debug('adding')
itemDB:add(detail)
end end
if detail then if detail then
return Util.shallowCopy(detail) return Util.shallowCopy(detail)
@@ -91,25 +95,29 @@ function ChestAdapter:listItems(throttle)
throttle = throttle or Util.throttle() throttle = throttle or Util.throttle()
for k,v in pairs(self.list()) do for k,v in pairs(self.list()) do
local key = table.concat({ v.name, v.damage, v.nbtHash }, ':') if v.count > 0 then
local key = table.concat({ v.name, v.damage, v.nbtHash }, ':')
local entry = self.cache[key] local entry = self.cache[key]
if not entry then
entry = self:getCachedItemDetails(v, k)
if not entry then if not entry then
return -- Inventory has changed entry = self:getCachedItemDetails(v, k)
if not entry then
debug(key)
debug('inv changed')
return -- Inventory has changed
end
entry.count = 0
self.cache[key] = entry
table.insert(items, entry)
end end
entry.count = 0
self.cache[key] = entry
table.insert(items, entry)
end
if entry then if entry then
entry.count = entry.count + v.count entry.count = entry.count + v.count
end
throttle()
end end
throttle()
end end
--read()
itemDB:flush() itemDB:flush()
return items return items

View File

@@ -31,8 +31,8 @@ local function safeString(text)
return text return text
end end
function itemDB:makeKey(item) local function makeKey(item)
return { item.name, item.damage, item.nbtHash } return { item.name, item.damage or '*', item.nbtHash }
end end
function itemDB:splitKey(key, item) function itemDB:splitKey(key, item)
@@ -52,26 +52,55 @@ function itemDB:splitKey(key, item)
end end
function itemDB:get(key) function itemDB:get(key)
if type(key) == 'string' then if type(key) == 'string' then
key = self:makeKey(self:splitKey(key)) key = self:splitKey(key)
end end
local item = TableDB.get(self, key) local item = TableDB.get(self, makeKey(key))
if item then if item then
return item return item
end end
if not key[2] or key[2] ~= 0 then -- try finding an item that has damage values
item = TableDB.get(self, { key[1], 0, key[3] }) if type(key.damage) == 'number' then
if item and item.maxDamage > 0 then item = TableDB.get(self, makeKey({ name = key.name, nbtHash = key.nbtHash }))
if item and item.maxDamage then
item = Util.shallowCopy(item) item = Util.shallowCopy(item)
item.damage = key[2] item.damage = key.damage
item.displayName = string.format('%s (damage: %s)', item.displayName, (item.damage or '*')) if item.maxDamage > 0 and type(item.damage) == 'number' and item.damage > 0 then
item.displayName = string.format('%s (damage: %s)', item.displayName, item.damage)
end
return item return item
end end
end end
if key.nbtHash then
item = self:get({ name = key.name, damage = key.damage })
if item and (item.maxDamage > 0 or item.damage == key.damage) then
item.nbtHash = key.nbtHash
return item
end
local damage = tonumber(key.damage)
for _,item in pairs(self.data) do
if item.name == key.name and
((not damage or item.maxDamage > 0) or damage == item.damage) and
item.nbtHash then
item = Util.shallowCopy(item)
item.damage = damage or item.damage
if item.maxDamage > 0 and item.damage and item.damage > 0 then
item.displayName = string.format('%s (damage: %s)', item.displayName, item.damage)
end
item.nbtHash = key.nbtHash
return item
end
end
end
--debug('miss: ' .. table.concat(makeKey(key), ':'))
--[[
if not key[3] then if not key[3] then
for _,item in pairs(self.data) do for _,item in pairs(self.data) do
if item.name == key[1] and if item.name == key[1] and
@@ -83,11 +112,13 @@ function itemDB:get(key)
end end
end end
end end
--]]
end end
function itemDB:add(key, item) function itemDB:add(item)
local key = makeKey(item)
if item.maxDamage > 0 then if item.maxDamage > 0 then
key = { key[1], 0, key[3] } key = makeKey({ name = item.name, damage = '*', nbtHash = item.nbtHash })
end end
item.displayName = safeString(item.displayName) item.displayName = safeString(item.displayName)
TableDB.add(self, key, item) TableDB.add(self, key, item)
@@ -99,7 +130,7 @@ function itemDB:getName(item)
item = self:splitKey(item) item = self:splitKey(item)
end end
local detail = self:get(self:makeKey(item)) local detail = self:get(item)
if detail then if detail then
return detail.displayName return detail.displayName
end end
@@ -113,7 +144,7 @@ function itemDB:getMaxCount(item)
item = self:splitKey(item) item = self:splitKey(item)
end end
local detail = self:get(self:makeKey(item)) local detail = self:get(item)
if detail then if detail then
return detail.maxCount return detail.maxCount
end end

View File

@@ -86,13 +86,12 @@ function MEAdapter:refresh()
Util.merge(v, v.item) Util.merge(v, v.item)
convertItem(v) convertItem(v)
local key = { v.name, v.damage, v.nbtHash } if not itemDB:get(v) then
if not itemDB:get(key) then
local t = { } local t = { }
for _,k in pairs(keys) do for _,k in pairs(keys) do
t[k] = v[k] t[k] = v[k]
end end
itemDB:add(key, t) itemDB:add(t)
end end
end end
itemDB:flush() itemDB:flush()

View File

@@ -38,9 +38,7 @@ function RefinedAdapter:isOnline()
end end
function RefinedAdapter:getCachedItemDetails(item) function RefinedAdapter:getCachedItemDetails(item)
local key = { item.name, item.damage, item.nbtHash } local detail = itemDB:get(item)
local detail = itemDB:get(key)
if not detail then if not detail then
detail = self.findItem(item) detail = self.findItem(item)
if detail then if detail then
@@ -57,7 +55,7 @@ function RefinedAdapter:getCachedItemDetails(item)
end end
detail = t detail = t
itemDB:add(key, detail) itemDB:add(detail)
end end
end end
if detail then if detail then
@@ -92,10 +90,7 @@ function RefinedAdapter:listItems()
end end
function RefinedAdapter:getItemInfo(fingerprint) function RefinedAdapter:getItemInfo(fingerprint)
local item = itemDB:get(fingerprint)
local key = { fingerprint.name, fingerprint.damage, fingerprint.nbtHash }
local item = itemDB:get(key)
if not item then if not item then
return self:getCachedItemDetails(fingerprint) return self:getCachedItemDetails(fingerprint)
end end

View File

@@ -32,7 +32,7 @@ local function splitKey(key)
return item return item
end end
local function getItemCount(items, key) function Craft.getItemCount(items, key)
local item = splitKey(key) local item = splitKey(key)
local count = 0 local count = 0
for _,v in pairs(items) do for _,v in pairs(items) do
@@ -108,12 +108,15 @@ function Craft.craftRecipe(recipe, count, inventoryAdapter)
end end
local items = inventoryAdapter:listItems() local items = inventoryAdapter:listItems()
if not items then
return 0, 'Inventory changed'
end
count = math.ceil(count / recipe.count) count = math.ceil(count / recipe.count)
local maxCount = recipe.maxCount or math.floor(64 / recipe.count) local maxCount = recipe.maxCount or math.floor(64 / recipe.count)
for key,icount in pairs(Craft.sumIngredients(recipe)) do for key,icount in pairs(Craft.sumIngredients(recipe)) do
local itemCount = getItemCount(items, key) local itemCount = Craft.getItemCount(items, key)
if itemCount < icount * count then if itemCount < icount * count then
local irecipe = Craft.recipes[key] local irecipe = Craft.recipes[key]
if irecipe then if irecipe then
@@ -156,7 +159,7 @@ function Craft.getResourceList(inRecipe, items, inCount)
if not summedItem then if not summedItem then
summedItem = Util.shallowCopy(item) summedItem = Util.shallowCopy(item)
summedItem.recipe = Craft.recipes[key] summedItem.recipe = Craft.recipes[key]
summedItem.count = getItemCount(items, key) summedItem.count = Craft.getItemCount(items, key)
summed[key] = summedItem summed[key] = summedItem
end end
summedItem.count = summedItem.count - (count * iqty) summedItem.count = summedItem.count - (count * iqty)
@@ -173,6 +176,70 @@ function Craft.getResourceList(inRecipe, items, inCount)
return summed return summed
end end
function Craft.getResourceList3(inRecipe, items, inCount)
local summed = { }
local throttle = Util.throttle()
local function sumItems(recipe, count)
count = math.ceil(count / recipe.count)
local craftable = count
for key,iqty in pairs(Craft.sumIngredients(recipe)) do
throttle()
local item = splitKey(key)
local summedItem = summed[key]
if not summedItem then
summedItem = Util.shallowCopy(item)
summedItem.recipe = Craft.recipes[key]
summedItem.count = Craft.getItemCount(items, key)
summedItem.ocount = summedItem.count
summedItem.need = 0
summedItem.used = 0
summedItem.missing = 0
summedItem.craftable = 0
summed[key] = summedItem
end
local total = count * iqty -- 4 * 2
local used = math.min(summedItem.count, total) -- 5
local need = total - used -- 3
if recipe.craftingTools and recipe.craftingTools[key] then
if summedItem.count > 0 then
summedItem.used = 1
need = 0
else
summedItem.need = 1
need = 1
end
else
summedItem.count = summedItem.count - used
summedItem.used = summedItem.used + used
summedItem.need = summedItem.need + need
end
if need > 0 then
if not summedItem.recipe then
debug(summedItem)
debug({ total, used, need })
summedItem.missing = summedItem.missing + need
craftable = math.min(craftable, math.floor(used / iqty))
else
local c = sumItems(summedItem.recipe, need) -- 4
craftable = math.min(craftable, math.floor((used + c) / iqty))
summedItem.craftable = summedItem.craftable + c
end
end
end
return craftable * recipe.count
end
sumItems(inRecipe, inCount)
return summed
end
-- given a certain quantity, return how many of those can be crafted -- given a certain quantity, return how many of those can be crafted
function Craft.getCraftableAmount(recipe, count, items, missing) function Craft.getCraftableAmount(recipe, count, items, missing)
local function sumItems(recipe, summedItems, count) local function sumItems(recipe, summedItems, count)
@@ -180,7 +247,7 @@ function Craft.getCraftableAmount(recipe, count, items, missing)
for _ = 1, count do for _ = 1, count do
for _,item in pairs(recipe.ingredients) do for _,item in pairs(recipe.ingredients) do
local summedItem = summedItems[item] or getItemCount(items, item) local summedItem = summedItems[item] or Craft.getItemCount(items, item)
local irecipe = Craft.recipes[item] local irecipe = Craft.recipes[item]
if irecipe and summedItem <= 0 then if irecipe and summedItem <= 0 then

View File

@@ -84,10 +84,6 @@ local function getItem(items, inItem, ignoreDamage, ignoreNbtHash)
end end
end end
local function splitKey(key)
return itemDB:splitKey(key)
end
local function getItemQuantity(items, item) local function getItemQuantity(items, item)
local count = 0 local count = 0
for _,v in pairs(items) do for _,v in pairs(items) do
@@ -120,7 +116,7 @@ local function mergeResources(t)
end end
for k in pairs(Craft.recipes) do for k in pairs(Craft.recipes) do
local v = splitKey(k) local v = itemDB:splitKey(k)
local item = getItem(t, v) local item = getItem(t, v)
if not item then if not item then
item = Util.shallowCopy(v) item = Util.shallowCopy(v)
@@ -145,7 +141,7 @@ local function filterItems(t, filter, displayMode)
filter = filter:lower() filter = filter:lower()
end end
for _,v in pairs(t) do for _,v in pairs(t) do
if not filter or string.find(v.lname, filter) then if not filter or string.find(v.lname, filter, 1, true) then
if not displayMode or if not displayMode or
displayMode == 0 or displayMode == 0 or
displayMode == 1 and v.count > 0 or displayMode == 1 and v.count > 0 or
@@ -169,16 +165,20 @@ local function isGridClear()
end end
local function clearGrid() local function clearGrid()
for i = 1, 16 do local function clear()
local count = turtle.getItemCount(i) for i = 1, 16 do
if count > 0 then local count = turtle.getItemCount(i)
inventoryAdapter:insert(i, count) if count > 0 then
if turtle.getItemCount(i) ~= 0 then inventoryAdapter:insert(i, count)
return false if turtle.getItemCount(i) ~= 0 then
debug('insert failed')
return false
end
end end
end end
return true
end end
return true return clear() or clear()
end end
local function addCraftingRequest(item, craftList, count) local function addCraftingRequest(item, craftList, count)
@@ -194,10 +194,16 @@ end
local function craftItem(recipe, items, originalItem, craftList, count) local function craftItem(recipe, items, originalItem, craftList, count)
if craftingPaused or not canCraft or not isGridClear() then if craftingPaused or not canCraft then
return 0 return 0
end end
if not isGridClear() then
if not clearGrid() then
originalItem.status = 'Grid obstructed'
end
end
local missing = { } local missing = { }
local toCraft = Craft.getCraftableAmount(recipe, count, items, missing) local toCraft = Craft.getCraftableAmount(recipe, count, items, missing)
if missing.name then if missing.name then
@@ -211,11 +217,17 @@ local function craftItem(recipe, items, originalItem, craftList, count)
local iRecipe = Craft.recipes[key] local iRecipe = Craft.recipes[key]
if iRecipe then if iRecipe then
local need = count * qty local need = count * qty
local has = getItemQuantity(items, splitKey(key)) local has = getItemQuantity(items, itemDB:splitKey(key))
--debug({ key, need, has })
if has < need then if has < need then
debug('crafting ' .. key .. ' - ' .. need - has) --debug('crafting ' .. key .. ' - ' .. need - has)
--read()
craftItem(iRecipe, items, originalItem, { }, math.ceil((need - has) / iRecipe.count)) craftItem(iRecipe, items, originalItem, { }, math.ceil((need - has) / iRecipe.count))
items = inventoryAdapter:listItems() items = inventoryAdapter:listItems()
if not items then
error('list failed')
--return 0
end
end end
end end
end end
@@ -232,7 +244,6 @@ debug('crafting ' .. key .. ' - ' .. need - has)
if count > 0 then if count > 0 then
local ingredients = Craft.getResourceList(recipe, items, count) local ingredients = Craft.getResourceList(recipe, items, count)
_G._p = ingredients
for _,ingredient in pairs(ingredients) do for _,ingredient in pairs(ingredients) do
--if not ingredient.recipe and ingredient.count < 0 then --if not ingredient.recipe and ingredient.count < 0 then
if ingredient.count < 0 then if ingredient.count < 0 then
@@ -243,6 +254,94 @@ _G._p = ingredients
return crafted return crafted
end end
local function forceCraftItem(inRecipe, items, originalItem, craftList, inCount)
local summed = { }
local throttle = Util.throttle()
local function sumItems(recipe, count)
count = math.ceil(count / recipe.count)
local craftable = count
for key,iqty in pairs(Craft.sumIngredients(recipe)) do
throttle()
local item = itemDB:splitKey(key)
local summedItem = summed[key]
if not summedItem then
summedItem = Util.shallowCopy(item)
summedItem.recipe = Craft.recipes[key]
if summedItem.recipe then
summedItem.recipe.key = key
end
summedItem.count = Craft.getItemCount(items, key)
summedItem.ocount = summedItem.count
summedItem.need = 0
summedItem.used = 0
summedItem.missing = 0
summedItem.craftable = 0
summed[key] = summedItem
end
local total = count * iqty -- 4 * 2
local used = math.min(summedItem.count, total) -- 5
local need = total - used -- 3
if recipe.craftingTools and recipe.craftingTools[key] then
if summedItem.count > 0 then
summedItem.used = 1
need = 0
else
summedItem.need = 1
need = 1
end
else
summedItem.count = summedItem.count - used
summedItem.used = summedItem.used + used
summedItem.need = summedItem.need + need
end
if need > 0 then
if not summedItem.recipe then
summedItem.missing = summedItem.missing + need
craftable = math.min(craftable, math.floor(used / iqty))
else
local c = sumItems(summedItem.recipe, need) -- 4
debug({ 'summed', total, used, need, c })
craftable = math.min(craftable, math.floor((used + c) / iqty))
summedItem.craftable = summedItem.craftable + c
end
end
end
if craftable > 0 then
debug('crafting ' .. recipe.key)
debug({ count, craftable })
_G._p = summed
craftable = Craft.craftRecipe(recipe, craftable, inventoryAdapter)
clearGrid()
debug('got ' .. craftable)
end
return craftable * recipe.count
end
local count = sumItems(inRecipe, inCount)
debug({ 'final', inCount, count })
if count < inCount then
for _,ingredient in pairs(summed) do
--if not ingredient.recipe and ingredient.count < 0 then
if ingredient.missing > 0 then
addCraftingRequest(ingredient, craftList, ingredient.missing)
originalItem.status = string.format('%s missing', itemDB:getName(ingredient))
originalItem.statusCode = 'missing'
end
end
end
return count
end
local function craftItems(craftList, allItems) local function craftItems(craftList, allItems)
for _,key in pairs(Util.keys(craftList)) do for _,key in pairs(Util.keys(craftList)) do
@@ -251,8 +350,16 @@ local function craftItems(craftList, allItems)
if recipe then if recipe then
item.status = nil item.status = nil
item.statusCode = nil item.statusCode = nil
item.crafted = craftItem(recipe, allItems, item, craftList, item.count) if item.forceCrafting then
recipe.key = key
item.crafted = forceCraftItem(recipe, allItems, item, craftList, item.count)
else
item.crafted = craftItem(recipe, allItems, item, craftList, item.count)
end
allItems = inventoryAdapter:listItems() -- refresh counts allItems = inventoryAdapter:listItems() -- refresh counts
if not allItems then
break
end
elseif item.rsControl then elseif item.rsControl then
item.status = 'Activated' item.status = 'Activated'
end end
@@ -332,7 +439,7 @@ local function getAutocraftItems()
for _,res in pairs(resources) do for _,res in pairs(resources) do
if res.auto then if res.auto then
res.count = 64 -- this could be higher to increase autocrafting speed res.count = 256 -- this could be higher to increase autocrafting speed
local key = uniqueKey(res) local key = uniqueKey(res)
craftList[key] = res craftList[key] = res
end end
@@ -366,7 +473,7 @@ local function watchResources(items)
local outputs = { } local outputs = { }
for _,res in pairs(resources) do for _,res in pairs(resources) do
local item = getItemWithQty(items, res, res.ignoreDamage, res.ignoreDamage) local item = getItemWithQty(items, res, res.ignoreDamage, res.ignoreNbtHash)
if not item then if not item then
item = { item = {
damage = res.damage, damage = res.damage,
@@ -422,8 +529,15 @@ end
local function loadResources() local function loadResources()
resources = Util.readTable(RESOURCE_FILE) or { } resources = Util.readTable(RESOURCE_FILE) or { }
for k,v in pairs(resources) do for _,k in pairs(Util.keys(resources)) do
Util.merge(v, splitKey(k)) local v = resources[k]
Util.merge(v, itemDB:splitKey(k))
if not v.damage then
v.damage = 0
v.ignoreDamage = true
resources[k] = nil
resources[uniqueKey(v)] = v
end
end end
end end
@@ -432,10 +546,18 @@ local function saveResources()
for k,v in pairs(resources) do for k,v in pairs(resources) do
v = Util.shallowCopy(v) v = Util.shallowCopy(v)
v.name = nil local keys = Util.transpose({ 'auto', 'low', 'limit',
v.damage = nil 'ignoreDamage', 'ignoreNbtHash',
v.nbtHash = nil 'rsControl', 'rsDevice', 'rsSide' })
t[k] = v
for _,key in pairs(Util.keys(v)) do
if not keys[key] then
v[key] = nil
end
end
if not Util.empty(v) then
t[k] = v
end
end end
Util.writeTable(RESOURCE_FILE, t) Util.writeTable(RESOURCE_FILE, t)
@@ -479,33 +601,19 @@ local itemPage = UI.Page {
}, },
[5] = UI.Chooser { [5] = UI.Chooser {
width = 7, width = 7,
formLabel = 'RS Control', formKey = 'rsControl', formLabel = 'Ignore NBT', formKey = 'ignoreNbtHash',
nochoice = 'No', nochoice = 'No',
choices = { choices = {
{ name = 'Yes', value = true }, { name = 'Yes', value = true },
{ name = 'No', value = false }, { name = 'No', value = false },
}, },
help = 'Control via redstone' help = 'Ignore NBT of item'
}, },
[6] = UI.Chooser { [6] = UI.Button {
width = 25, x = 2, y = -2, width = 10,
formLabel = 'RS Device', formKey = 'rsDevice', formLabel = 'Redstone',
--choices = devices, event = 'show_rs',
help = 'Redstone Device' text = 'Configure',
},
[7] = 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'
}, },
infoButton = UI.Button { infoButton = UI.Button {
x = 2, y = -2, x = 2, y = -2,
@@ -513,6 +621,45 @@ local itemPage = UI.Page {
text = 'Info', text = 'Info',
}, },
}, },
rsControl = UI.SlideOut {
backgroundColor = colors.cyan,
titleBar = UI.TitleBar {
title = "Redstone Control",
},
form = UI.Form {
y = 2,
[1] = UI.Chooser {
width = 7,
formLabel = 'RS Control', formKey = 'rsControl',
nochoice = 'No',
choices = {
{ name = 'Yes', value = true },
{ name = 'No', value = false },
},
help = 'Control via redstone'
},
[2] = UI.Chooser {
width = 25,
formLabel = 'RS Device', formKey = 'rsDevice',
--choices = devices,
help = 'Redstone Device'
},
[3] = 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'
},
},
},
info = UI.SlideOut { info = UI.SlideOut {
titleBar = UI.TitleBar { titleBar = UI.TitleBar {
title = "Information", title = "Information",
@@ -525,18 +672,24 @@ local itemPage = UI.Page {
ex = -2, y = -2, width = 6, ex = -2, y = -2, width = 6,
text = 'Okay', text = 'Okay',
event = 'hide_info', event = 'hide_info',
} },
}, },
statusBar = UI.StatusBar { } statusBar = UI.StatusBar { }
} }
function itemPage:enable(item) function itemPage:enable(item)
self.item = item self.item = item
debug(self.item)
self.form:setValues(item) self.form:setValues(item)
self.titleBar.title = item.displayName or item.name self.titleBar.title = item.displayName or item.name
local devices = self.form[6].choices UI.Page.enable(self)
self:focusFirst()
end
function itemPage.rsControl:enable()
local devices = self.form[1].choices
Util.clear(devices) Util.clear(devices)
for _,dev in pairs(device) do for _,dev in pairs(device) do
if dev.setOutput then if dev.setOutput then
@@ -548,25 +701,50 @@ function itemPage:enable(item)
table.insert(devices, { name = 'None found', values = '' }) table.insert(devices, { name = 'None found', values = '' })
end end
UI.Page.enable(self) UI.SlideOut.enable(self)
self:focusFirst() end
function itemPage.rsControl:eventHandler(event)
if event.type == 'form_cancel' then
self:hide()
elseif event.type == 'form_complete' then
self:hide()
else
return UI.SlideOut.eventHandler(self, event)
end
return true
end end
function itemPage:eventHandler(event) function itemPage:eventHandler(event)
if event.type == 'form_cancel' then if event.type == 'form_cancel' then
UI:setPreviousPage() UI:setPreviousPage()
elseif event.type == 'show_rs' then
self.rsControl:show()
elseif event.type == 'show_info' then elseif event.type == 'show_info' then
self.info.textArea.value = local value =
string.format( string.format('%s%s%s\n%s\n',
[[%sName: %s%s Ansi.orange, self.item.displayName, Ansi.reset,
%sID: %s%s self.item.name)
%sDamage: %s%s
%sNBT: %s%s]], if self.item.nbtHash then
Ansi.yellow, Ansi.reset, self.item.displayName, value = value .. self.item.nbtHash .. '\n'
Ansi.yellow, Ansi.reset, self.item.name, end
Ansi.yellow, Ansi.reset, self.item.damage,
Ansi.yellow, Ansi.reset, self.item.nbtHash or '(none)') value = value .. string.format('\n%sDamage:%s %s',
Ansi.yellow, Ansi.reset, self.item.damage)
if self.item.maxDamage and self.item.maxDamage > 0 then
value = value .. string.format(' (max: %s)', self.item.maxDamage)
end
if self.item.maxCount then
value = value .. string.format('\n%sStack Size: %s%s',
Ansi.yellow, Ansi.reset, self.item.maxCount)
end
self.info.textArea.value = value
self.info:show() self.info:show()
elseif event.type == 'hide_info' then elseif event.type == 'hide_info' then
@@ -578,21 +756,12 @@ Ansi.yellow, Ansi.reset, self.item.nbtHash or '(none)')
elseif event.type == 'form_complete' then elseif event.type == 'form_complete' then
local values = self.form.values local values = self.form.values
local keys = { 'name', 'auto', 'low', 'limit', 'damage', local originalKey = uniqueKey(self.item)
'nbtHash',
'rsControl', 'rsDevice', 'rsSide', }
local filtered = { } local filtered = Util.shallowCopy(values)
for _,key in pairs(keys) do
filtered[key] = values[key]
end
filtered.low = tonumber(filtered.low) filtered.low = tonumber(filtered.low)
filtered.limit = tonumber(filtered.limit) filtered.limit = tonumber(filtered.limit)
--filtered.ignoreDamage = filtered.ignoreDamage == true
--filtered.auto = filtered.auto == true
--filtered.rsControl = filtered.rsControl == true
if filtered.auto ~= true then if filtered.auto ~= true then
filtered.auto = nil filtered.auto = nil
end end
@@ -603,11 +772,19 @@ Ansi.yellow, Ansi.reset, self.item.nbtHash or '(none)')
filtered.rsDevice = nil filtered.rsDevice = nil
end end
if values.ignoreDamage == true then if filtered.ignoreDamage == true then
filtered.damage = 0 filtered.damage = 0
filtered.ignoreDamage = true else
filtered.ignoreDamage = nil
end end
if filtered.ignoreNbtHash == true then
filtered.nbtHash = nil
else
filtered.ignoreNbtHash = nil
end
debug(filtered)
resources[originalKey] = nil
resources[uniqueKey(filtered)] = filtered resources[uniqueKey(filtered)] = filtered
saveResources() saveResources()
@@ -735,7 +912,7 @@ function listingPage:eventHandler(event)
if resources[key] then if resources[key] then
resources[key] = nil resources[key] = nil
Util.writeTable(RESOURCE_FILE, resources) saveResources()
end end
self.statusBar:timedStatus('Forgot: ' .. item.name, 3) self.statusBar:timedStatus('Forgot: ' .. item.name, 3)
@@ -847,7 +1024,9 @@ local function learnRecipe(page)
for _,v in pairs(results) do for _,v in pairs(results) do
if not v.craftingTool then if not v.craftingTool then
recipe = v recipe = v
recipe.maxCount = maxCount if maxCount then
recipe.maxCount = maxCount
end
break break
end end
end end
@@ -939,54 +1118,84 @@ function learnPage:eventHandler(event)
return true return true
end end
local craftPage = UI.Dialog { local craftPage = UI.Page {
height = 6, width = UI.term.width - 10, titleBar = UI.TitleBar {
title = 'Enter amount to craft', title = "Information",
count = UI.TextEntry {
x = 15,
y = 3,
width = 10,
limit = 6,
value = '1',
}, },
accept = UI.Button { wizard = UI.Wizard {
x = -8, y = -2, y = 2,
backgroundColor = colors.green, pages = {
text = '+', event = 'accept', quantity = UI.Window {
}, index = 1,
cancel = UI.Button { text = UI.Text {
x = -4, y = -2, x = 6, y = 3,
backgroundColor = colors.red, value = 'Quantity',
text = '\215', event = 'cancel' },
count = UI.TextEntry {
x = 15,
y = 3,
width = 10,
limit = 6,
value = 1,
},
},
resources = UI.Window {
index = 2,
grid = UI.Grid {
y = 2, ey = -4,
columns = {
{ heading = 'Name', key = 'displayName' },
{ heading = 'Qty', key = 'ocount' , width = 5 },
{ heading = 'Used', key = 'used' , width = 4 },
{ heading = 'Need', key = 'need' , width = 4 },
{ heading = 'Miss', key = 'missing' , width = 4 },
{ heading = 'cra', key = 'craftable' , width = 4 },
},
sortColumn = 'displayName',
},
accept = UI.Button {
x = -8, y = -2,
backgroundColor = colors.green,
text = '+', event = 'accept',
},
cancel = UI.Button {
x = -4, y = -2,
backgroundColor = colors.red,
text = '\215', event = 'cancel'
},
},
},
}, },
} }
function craftPage:draw()
UI.Dialog.draw(self)
self:write(6, 3, 'Quantity')
end
function craftPage:enable(item) function craftPage:enable(item)
self.item = item self.item = item
self:focusFirst() self:focusFirst()
UI.Dialog.enable(self) UI.Page.enable(self)
end
function craftPage:disable()
UI.Dialog.disable(self)
end end
function craftPage:eventHandler(event) function craftPage:eventHandler(event)
if event.type == 'cancel' then if event.type == 'cancel' then
UI:setPreviousPage() UI:setPreviousPage()
elseif event.type == 'enable_view' and event.view == self.wizard.pages.resources then
local items = inventoryAdapter:listItems()
local count = self.wizard.pages.quantity.count.value
local recipe = Craft.recipes[uniqueKey(self.item)]
local ingredients = Craft.getResourceList3(recipe, items, count)
self.wizard.pages.resources.grid:setValues(ingredients)
for _,v in pairs(ingredients) do
v.displayName = itemDB:getName(v)
end
elseif event.type == 'accept' then elseif event.type == 'accept' then
local key = uniqueKey(self.item) local key = uniqueKey(self.item)
demandCrafting[key] = Util.shallowCopy(self.item) demandCrafting[key] = Util.shallowCopy(self.item)
demandCrafting[key].count = tonumber(self.count.value) demandCrafting[key].count = tonumber(self.wizard.pages.quantity.count.value)
demandCrafting[key].forceCrafting = true demandCrafting[key].forceCrafting = true
UI:setPreviousPage() UI:setPreviousPage()
else else
return UI.Dialog.eventHandler(self, event) return UI.Page.eventHandler(self, event)
end end
return true return true
end end
@@ -1010,7 +1219,7 @@ Event.onInterval(5, function()
if not craftingPaused then if not craftingPaused then
local items = inventoryAdapter:listItems() local items = inventoryAdapter:listItems()
if Util.size(items) == 0 then if not items or Util.size(items) == 0 then
jobListGrid.parent:clear() jobListGrid.parent:clear()
jobListGrid.parent:centeredWrite(math.ceil(jobListGrid.parent.height/2), 'No items in system') jobListGrid.parent:centeredWrite(math.ceil(jobListGrid.parent.height/2), 'No items in system')
jobListGrid:sync() jobListGrid:sync()
@@ -1020,10 +1229,13 @@ Event.onInterval(5, function()
craftItems(craftList, items) craftItems(craftList, items)
if Util.size(demandCrafting) > 0 then if Util.size(demandCrafting) > 0 then
local list = Util.shallowCopy(demandCrafting) items = inventoryAdapter:listItems()
craftItems(list, inventoryAdapter:listItems()) if items then
for k,v in pairs(list) do local list = Util.shallowCopy(demandCrafting)
craftList[k] = v craftItems(list, items)
for k,v in pairs(list) do
craftList[k] = v
end
end end
end end
@@ -1034,9 +1246,11 @@ Event.onInterval(5, function()
for _,key in pairs(Util.keys(demandCrafting)) do for _,key in pairs(Util.keys(demandCrafting)) do
local item = demandCrafting[key] local item = demandCrafting[key]
item.count = item.count - item.crafted if item.crafted then
if item.count <= 0 then -- should check statusCode item.count = item.count - item.crafted
demandCrafting[key] = nil if item.count <= 0 then -- should check statusCode
demandCrafting[key] = nil
end
end end
end end

View File

@@ -11,6 +11,7 @@ local Util = require('util')
local colors = _G.colors local colors = _G.colors
local multishell = _ENV.multishell local multishell = _ENV.multishell
local term = _G.term
local turtle = _G.turtle local turtle = _G.turtle
multishell.setTitle(multishell.getCurrent(), 'Crafter') multishell.setTitle(multishell.getCurrent(), 'Crafter')
@@ -25,6 +26,7 @@ local inventoryAdapter = ChestAdapter(config.inventory)
local RESOURCE_FILE = 'usr/config/resources.db' local RESOURCE_FILE = 'usr/config/resources.db'
local RECIPES_FILE = 'usr/config/recipes2.db' local RECIPES_FILE = 'usr/config/recipes2.db'
local MACHINES_FILE = 'usr/config/machines.db'
local recipes = Util.readTable(RECIPES_FILE) or { } local recipes = Util.readTable(RECIPES_FILE) or { }
local resources local resources
@@ -60,14 +62,21 @@ local function getItemQuantity(items, res)
return count return count
end end
local function getItemWithQty(items, res) local function getItemWithQty(items, res, ignoreNbtHash)
for _,v in pairs(items) do for _,v in pairs(items) do
if res.name == v.name and if res.name == v.name and
((not res.damage and v.maxDamage > 0) or res.damage == v.damage) and ((not res.damage and v.maxDamage > 0) or res.damage == v.damage) and
((not res.nbtHash and v.nbtHash) or res.nbtHash == v.nbtHash) then ((ignoreNbtHash and v.nbtHash) or res.nbtHash == v.nbtHash) then
debug('found')
debug(v)
return v return v
end end
end end
debug('nope:' .. uniqueKey(res))
local item = Util.shallowCopy(res)
item.count = 0
item.maxCount = 1
return item
end end
local function mergeResources(t) local function mergeResources(t)
@@ -129,69 +138,78 @@ local function clearGrid()
end end
local function gotoMachine(machine) local function gotoMachine(machine)
for _ = 1, machine do for _ = 1, machine.index do
if not turtle.back() then if not turtle.back() then
return return
end end
end end
return true return true
end end
local function canCraft(recipe, items, count)
count = math.ceil(count / recipe.count)
local icount = Util.size(recipe.ingredients)
local maxSlots = math.floor(16 / icount)
for key,qty in pairs(recipe.ingredients) do
local item = getItem(items, itemDB:splitKey(key))
if not item then
return 0, itemDB:getName(key)
end
local x = math.min(math.floor(item.count / qty), item.maxCount * maxSlots)
count = math.min(x, count)
end
return count, ''
end
local function craftItem(recipe, recipeKey, items, cItem, count) local function craftItem(recipe, recipeKey, items, cItem, count)
repeat until not turtle.forward() repeat until not turtle.forward()
local resource = resources[recipeKey] local resource = resources[recipeKey]
if not resource or not resource.machine then if not resource or not resource.machine then
cItem.status = 'machine not selected' cItem.status = 'machine not selected'
return false return
end
local machine = Util.find(machines, 'order', resource.machine)
if not machine then
cItem.status = 'invalid machine'
return
end end
if count == 0 then if count == 0 then
cItem.status = 'missing something' for key in pairs(recipe.ingredients) do
return false local item = getItemWithQty(items, itemDB:splitKey(key), recipe.ignoreNbtHash)
if item.count == 0 then
cItem.status = 'Missing: ' .. (item.displayName or itemDB:getName(item))
return false
end
end
return
end end
local slot = 1 local slot = 1
for key,qty in pairs(recipe.ingredients) do for key,qty in pairs(recipe.ingredients) do
local item = itemDB:get(key) local item = getItemWithQty(items, itemDB:splitKey(key), recipe.ignoreNbtHash)
if not item then if item.count == 0 then
cItem.status = 'failed' debug(item)
return false cItem.status = 'Missing: ' .. (item.displayName or itemDB:getName(item))
return
end end
local c = count * qty local c = count * qty
while c > 0 do while c > 0 do
local maxCount = math.min(c, item.maxCount) local maxCount = math.min(c, item.maxCount)
inventoryAdapter:provide(item, maxCount, slot) inventoryAdapter:provide(item, maxCount, slot)
if turtle.getItemCount(slot) == 0 then -- ~= maxCount then FIXXX !!! if turtle.getItemCount(slot) ~= maxCount then -- ~= maxCount then FIXXX !!!
cItem.status = 'failed' cItem.status = 'Extract failed: ' .. (item.displayName or itemDB:getName(item))
return false return
end end
c = c - maxCount c = c - maxCount
slot = slot + 1 slot = slot + 1
end end
end end
if not gotoMachine(resource.machine) then if not gotoMachine(machine) then
cItem.status = 'failed to find machine' cItem.status = 'failed to find machine'
else else
if resource.dir == 'up' then if machine.empty then
local s, l = pcall(Peripheral.call,
turtle.getAction(machine.dir).side, 'list')
if not s then
cItem.status = l
return
elseif not Util.empty(l) then
cItem.status = 'machine busy'
return
end
end
if machine.dir == 'up' then
turtle.emptyInventory(turtle.dropUp) turtle.emptyInventory(turtle.dropUp)
else else
turtle.emptyInventory(turtle.dropDown) turtle.emptyInventory(turtle.dropDown)
@@ -212,15 +230,7 @@ local function expandList(list)
for key,qty in pairs(recipe.ingredients) do for key,qty in pairs(recipe.ingredients) do
local item = getItemWithQty(items, itemDB:splitKey(key)) local item = getItemWithQty(items, itemDB:splitKey(key), recipe.ignoreNbtHash)
if not item then
item = itemDB:get(key)
if not item then
--item = itemDB:splitKey(key)
break
end
item.count = 0
end
local need = qty * count local need = qty * count
local irecipe = recipes[key] local irecipe = recipes[key]
@@ -271,6 +281,7 @@ end
local function craftItems(craftList) local function craftItems(craftList)
expandList(craftList) expandList(craftList)
lastItems = inventoryAdapter:listItems() -- refresh counts
jobListGrid:update() jobListGrid:update()
jobListGrid:draw() jobListGrid:draw()
jobListGrid:sync() jobListGrid:sync()
@@ -315,10 +326,17 @@ local function loadResources()
resources = Util.readTable(RESOURCE_FILE) or { } resources = Util.readTable(RESOURCE_FILE) or { }
for k,v in pairs(resources) do for k,v in pairs(resources) do
Util.merge(v, itemDB:splitKey(k)) Util.merge(v, itemDB:splitKey(k))
if not v.machine then if v.dir then
local recipe = recipes[k] for _,m in pairs(machines) do
v.machine = recipe.machine if m.index == v.machine and m.dir == v.dir then
v.dir = recipe.dir or 'down' v.machine = m.order
v.dir = nil
break
end
end
if v.dir then
error('did not find')
end
end end
end end
end end
@@ -328,6 +346,7 @@ local function saveResources()
for k,v in pairs(resources) do for k,v in pairs(resources) do
v = Util.shallowCopy(v) v = Util.shallowCopy(v)
v.name = nil v.name = nil
v.damage = nil v.damage = nil
v.nbtHash = nil v.nbtHash = nil
@@ -359,6 +378,9 @@ local function findMachines()
if not machine then if not machine then
local _ local _
_, machine = turtle.getAction(dir).inspect() _, machine = turtle.getAction(dir).inspect()
if not machine or type(machine) ~= 'table' then
machine = { name = 'Unknown' }
end
end end
if machine and type(machine) == 'table' then if machine and type(machine) == 'table' then
local rawName = getName(side) or machine.name local rawName = getName(side) or machine.name
@@ -384,6 +406,16 @@ local function findMachines()
getMachine('up') getMachine('up')
index = index + 1 index = index + 1
until not turtle.back() until not turtle.back()
local mf = Util.readTable(MACHINES_FILE) or { }
for _,m in pairs(machines) do
local m2 = Util.find(mf, 'order', m.order)
if m2 then
m.name = m2.name or m.name
m.empty = m2.empty
m.ignore = m2.ignore
end
end
end end
local function jobMonitor() local function jobMonitor()
@@ -458,6 +490,10 @@ local itemPage = UI.Page {
}, },
machines = UI.SlideOut { machines = UI.SlideOut {
backgroundColor = colors.cyan, backgroundColor = colors.cyan,
titleBar = UI.TitleBar {
title = 'Select Machine',
previousPage = true,
},
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
y = 2, ey = -4, y = 2, ey = -4,
values = machines, values = machines,
@@ -476,7 +512,6 @@ local itemPage = UI.Page {
x = -9, y = -2, x = -9, y = -2,
text = 'Cancel', event = 'cancelMachine', text = 'Cancel', event = 'cancelMachine',
}, },
statusBar = UI.StatusBar(),
}, },
statusBar = UI.StatusBar { } statusBar = UI.StatusBar { }
} }
@@ -491,11 +526,7 @@ function itemPage:enable(item)
self:focusFirst() self:focusFirst()
end end
function itemPage.machines:draw() --[[
UI.Window.draw(self)
self:centeredWrite(1, 'Select machine', nil, colors.yellow)
end
function itemPage.machines:eventHandler(event) function itemPage.machines:eventHandler(event)
if event.type == 'grid_focus_row' then if event.type == 'grid_focus_row' then
self.statusBar:setStatus(string.format('%d %s', event.selected.index, event.selected.dir)) self.statusBar:setStatus(string.format('%d %s', event.selected.index, event.selected.dir))
@@ -504,6 +535,7 @@ function itemPage.machines:eventHandler(event)
end end
return true return true
end end
]]
function itemPage:eventHandler(event) function itemPage:eventHandler(event)
if event.type == 'form_cancel' then if event.type == 'form_cancel' then
@@ -513,17 +545,18 @@ function itemPage:eventHandler(event)
UI:setPage('learn', self.item) UI:setPage('learn', self.item)
elseif event.type == 'setMachine' then elseif event.type == 'setMachine' then
self.item.machine = self.machines.grid:getSelected().index self.item.machine = self.machines.grid:getSelected().order
self.item.dir = self.machines.grid:getSelected().dir
self.machines:hide() self.machines:hide()
elseif event.type == 'cancelMachine' then elseif event.type == 'cancelMachine' then
self.machines:hide() self.machines:hide()
elseif event.type == 'selectMachine' then elseif event.type == 'selectMachine' then
self.machines.grid:update() local machineCopy = Util.shallowCopy(machines)
Util.filterInplace(machineCopy, function(m) return not m.ignore end)
self.machines.grid:setValues(machineCopy)
if self.item.machine then if self.item.machine then
local _, index = Util.find(machines, 'index', self.item.machine) local _, index = Util.find(machineCopy, 'order', self.item.machine)
if index then if index then
self.machines.grid:setIndex(index) self.machines.grid:setIndex(index)
end end
@@ -544,7 +577,6 @@ function itemPage:eventHandler(event)
end end
filtered.low = tonumber(filtered.low) filtered.low = tonumber(filtered.low)
filtered.machine = self.item.machine filtered.machine = self.item.machine
filtered.dir = self.item.dir
if values.ignoreDamage == true then if values.ignoreDamage == true then
filtered.damage = 0 filtered.damage = 0
@@ -685,11 +717,113 @@ function learnPage:eventHandler(event)
return true return true
end end
local machinesPage = UI.Page {
titleBar = UI.TitleBar {
previousPage = true,
title = 'Machines',
},
grid = UI.Grid {
y = 2, ey = -2,
values = machines,
columns = {
{ heading = 'Name', key = 'name' },
{ heading = 'Side', key = 'dir', width = 5 },
{ heading = 'Index', key = 'index', width = 5 },
},
sortColumn = 'order',
},
detail = UI.SlideOut {
backgroundColor = colors.cyan,
form = UI.Form {
x = 1, y = 2, ex = -1, ey = -2,
[1] = UI.TextEntry {
formLabel = 'Name', formKey = 'name', help = '...',
limit = 64,
},
[2] = UI.Chooser {
width = 7,
formLabel = 'Hidden', formKey = 'ignore',
nochoice = 'No',
choices = {
{ name = 'Yes', value = true },
{ name = 'No', value = false },
},
help = 'Do not show this machine'
},
[3] = UI.Chooser {
width = 7,
formLabel = 'Empty', formKey = 'empty',
nochoice = 'No',
choices = {
{ name = 'Yes', value = true },
{ name = 'No', value = false },
},
help = 'Check if machine is empty before crafting'
},
},
statusBar = UI.StatusBar(),
},
statusBar = UI.StatusBar {
values = 'Select Machine',
},
accelerators = {
h = 'toggle_hidden',
}
}
function machinesPage:enable()
self.grid:update()
UI.Page.enable(self)
end
function machinesPage.detail:eventHandler(event)
if event.type == 'focus_change' then
self.statusBar:setStatus(event.focused.help)
end
return UI.SlideOut.eventHandler(self, event)
end
function machinesPage.grid:getRowTextColor(row, selected)
if row.ignore then
return colors.yellow
end
return UI.Grid:getRowTextColor(row, selected)
end
function machinesPage:eventHandler(event)
if event.type == 'grid_select' then
self.detail.form:setValues(event.selected)
self.detail:show()
elseif event.type == 'toggle_hidden' then
local selected = self.grid:getSelected()
if selected then
selected.ignore = not selected.ignore
Util.writeTable(MACHINES_FILE, machines)
self:draw()
end
elseif event.type == 'form_complete' then
self.detail.form.values.empty = self.detail.form.values.empty == true
self.detail.form.values.ignore = self.detail.form.values.ignore == true
Util.writeTable(MACHINES_FILE, machines)
self.detail:hide()
elseif event.type == 'form_cancel' then
self.detail:hide()
else
UI.Page.eventHandler(self, event)
end
return true
end
local listingPage = UI.Page { local listingPage = UI.Page {
menuBar = UI.MenuBar { menuBar = UI.MenuBar {
buttons = { buttons = {
{ text = 'Forget', event = 'forget' }, { text = 'Forget', event = 'forget' },
{ text = 'Refresh', event = 'refresh' }, { text = 'Machines', event = 'machines' },
{ text = 'Refresh', event = 'refresh', x = -9 },
}, },
}, },
grid = UI.Grid { grid = UI.Grid {
@@ -768,6 +902,9 @@ function listingPage:eventHandler(event)
self.grid:draw() self.grid:draw()
self.statusBar.filter:focus() self.statusBar.filter:focus()
elseif event.type == 'machines' then
UI:setPage('machines')
elseif event.type == 'craft' then elseif event.type == 'craft' then
UI:setPage('craft', self.grid:getSelected()) UI:setPage('craft', self.grid:getSelected())
@@ -823,8 +960,8 @@ function listingPage:applyFilter()
self.grid:setValues(t) self.grid:setValues(t)
end end
loadResources()
findMachines() findMachines()
loadResources()
repeat until not turtle.forward() repeat until not turtle.forward()
clearGrid() clearGrid()
jobMonitor() jobMonitor()
@@ -832,6 +969,7 @@ lastItems = inventoryAdapter:listItems()
UI:setPages({ UI:setPages({
listing = listingPage, listing = listingPage,
machines = machinesPage,
item = itemPage, item = itemPage,
learn = learnPage, learn = learnPage,
}) })
@@ -851,14 +989,16 @@ Event.onInterval(30, function()
turtle.refuel() turtle.refuel()
end end
lastItems = inventoryAdapter:listItems() lastItems = inventoryAdapter:listItems()
local craftList = watchResources(lastItems) if lastItems then
local craftList = watchResources(lastItems)
jobListGrid:setValues(craftList) jobListGrid:setValues(craftList)
jobListGrid:update() jobListGrid:update()
jobListGrid:draw() jobListGrid:draw()
jobListGrid:sync() jobListGrid:sync()
craftItems(craftList) craftItems(craftList)
end
end) end)
UI:pullEvents() UI:pullEvents()

371
apps/levelEmitter.lua Normal file
View File

@@ -0,0 +1,371 @@
_G.requireInjector()
local ChestAdapter = require('chestAdapter18')
local Config = require('config')
local Event = require('event')
local itemDB = require('itemDB')
local UI = require('ui')
local Util = require('util')
local colors = _G.colors
local multishell = _ENV.multishell
local rs = _G.rs
local RESOURCE_FILE = 'usr/config/levelEmitter.db'
multishell.setTitle(multishell.getCurrent(), 'Level Emitter')
local config = {
inventoryDirection = { direction = 'north', wrapSide = 'back' },
}
Config.load('levelEmitter', config)
local inventoryAdapter = ChestAdapter(config.inventoryDirection)
local resources
local function getItem(items, inItem, ignoreDamage, ignoreNbtHash)
for _,item in pairs(items) do
if item.name == inItem.name and
(ignoreDamage or item.damage == inItem.damage) and
(ignoreNbtHash or item.nbtHash == inItem.nbtHash) then
return item
end
end
end
local function uniqueKey(item)
return table.concat({ item.name, item.damage, item.nbtHash }, ':')
end
local function mergeResources(t)
for _,v in pairs(resources) do
local item = getItem(t, v)
if item then
Util.merge(item, v)
else
item = Util.shallowCopy(v)
item.count = 0
table.insert(t, item)
end
end
for _,v in pairs(t) do
if not v.displayName then
v.displayName = itemDB:getName(v)
end
v.lname = v.displayName:lower()
end
end
local function getItemWithQty(items, res, ignoreDamage, ignoreNbtHash)
local item = getItem(items, res, ignoreDamage, ignoreNbtHash)
if item and (ignoreDamage or ignoreNbtHash) then
local count = 0
for _,v in pairs(items) do
if item.name == v.name and
(ignoreDamage or item.damage == v.damage) and
(ignoreNbtHash or item.nbtHash == v.nbtHash) then
count = count + v.count
end
end
item.count = count
end
return item
end
local function loadResources()
resources = Util.readTable(RESOURCE_FILE) or { }
for k,v in pairs(resources) do
Util.merge(v, itemDB:splitKey(k))
end
end
local function saveResources()
local t = { }
for k,v in pairs(resources) do
v = Util.shallowCopy(v)
local keys = Util.transpose({ 'limit', 'low', 'ignoreDamage', 'ignoreNbtHash' })
for _,key in pairs(Util.keys(v)) do
if not keys[key] then
v[key] = nil
end
end
if not Util.empty(v) then
t[k] = v
end
end
Util.writeTable(RESOURCE_FILE, t)
end
local itemPage = UI.Page {
titleBar = UI.TitleBar {
title = 'Limit Resource',
previousPage = true,
event = 'form_cancel',
},
form = UI.Form {
x = 1, y = 2, height = 10, ex = -1,
[1] = UI.TextEntry {
width = 7,
formLabel = 'Min', formKey = 'low', help = 'Output a signal if below'
},
[2] = UI.TextEntry {
width = 7,
formLabel = 'Max', formKey = 'limit', help = 'Output a signal if above'
},
[4] = 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'
},
[5] = UI.Chooser {
width = 7,
formLabel = 'Ignore NBT', formKey = 'ignoreNbtHash',
nochoice = 'No',
choices = {
{ name = 'Yes', value = true },
{ name = 'No', value = false },
},
help = 'Ignore NBT of item'
},
},
statusBar = UI.StatusBar { }
}
function itemPage:enable(item)
self.item = item
self.form:setValues(item)
self.titleBar.title = item.displayName or item.name
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 filtered = Util.shallowCopy(self.form.values)
if filtered.ignoreDamage == true then
filtered.damage = 0
else
filtered.ignoreDamage = nil
end
if filtered.ignoreNbtHash == true then
filtered.nbtHash = nil
else
filtered.ignoreNbtHash = nil
end
local originalKey = uniqueKey(self.item)
resources[originalKey] = nil
filtered.low = tonumber(filtered.limit)
filtered.limit = tonumber(filtered.limit)
if filtered.limit or filtered.low then
resources[uniqueKey(filtered)] = filtered
else
resources[uniqueKey(filtered)] = nil
end
saveResources()
UI:setPreviousPage()
else
return UI.Page.eventHandler(self, event)
end
return true
end
local listingPage = UI.Page {
menuBar = UI.MenuBar {
buttons = {
{ text = 'Forget', event = 'forget' },
{ text = 'Refresh', event = 'refresh', x = -9 },
},
},
grid = UI.Grid {
y = 2, height = UI.term.height - 2,
columns = {
{ heading = 'Name', key = 'displayName' , width = 22 },
{ heading = 'Qty', key = 'count' , width = 5 },
{ heading = 'Max', key = 'limit' , width = 4 },
},
sortColumn = 'displayName',
},
statusBar = UI.StatusBar {
filterText = UI.Text {
x = 2,
value = 'Filter',
},
filter = UI.TextEntry {
x = 9, ex = -5,
limit = 50,
backgroundColor = colors.gray,
backgroundFocusColor = colors.gray,
},
display = UI.Button {
x = -3,
event = 'toggle_display',
value = 0,
text = 'A',
},
},
accelerators = {
r = 'refresh',
q = 'quit',
},
displayMode = 0,
}
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
if row.limit then
row.limit = Util.toBytes(row.limit)
end
return row
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 == 'toggle_display' then
local values = {
[0] = 'A',
[1] = 'I',
[2] = 'C',
}
event.button.value = (event.button.value + 1) % 3
self.displayMode = event.button.value
event.button.text = values[event.button.value]
event.button:draw()
self:applyFilter()
self.grid:draw()
elseif event.type == 'forget' then
local item = self.grid:getSelected()
if item then
local key = uniqueKey(item)
if resources[key] then
resources[key] = nil
saveResources()
end
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 = inventoryAdapter:listItems()
mergeResources(self.allItems)
self:applyFilter()
end
function listingPage:applyFilter()
local filter = self.filter
local displayMode = self.displayMode
local t = self.allItems
if filter or displayMode > 0 then
t = { }
if filter then
filter = filter:lower()
end
for _,v in pairs(self.allItems) do
if not filter or string.find(v.lname, filter, 1, true) then
if not displayMode or
displayMode == 0 or
displayMode == 1 and v.count > 0 or
displayMode == 2 and v.has_recipe then
table.insert(t, v)
end
end
end
end
self.grid:setValues(t)
end
loadResources()
UI:setPages({
listing = listingPage,
item = itemPage,
})
UI:setPage(listingPage)
listingPage:setFocus(listingPage.statusBar.filter)
Event.onInterval(5, function()
local items = inventoryAdapter:listItems()
if items and Util.size(items) > 0 then
for _,res in pairs(resources) do
local item = getItemWithQty(items, res, res.ignoreDamage, res.ignoreNbtHash)
rs.setOutput('bottom', (res.limit and
item and item.count > res.limit) or
(res.low and
(not item or item.count < res.low)) or false)
end
end
end)
UI:pullEvents()

View File

@@ -20,8 +20,9 @@ local changedPage = UI.Page {
grid = UI.Grid { grid = UI.Grid {
ey = -6, ey = -6,
columns = { columns = {
{ heading = 'Qty', key = 'count', width = 5 }, { heading = 'Qty', key = 'count', width = 5 },
{ heading = 'Change', key = 'change', width = 6 }, { heading = 'Change', key = 'change', width = 6 },
{ heading = 'Rate', key = 'rate', width = 6 },
{ heading = 'Name', key = 'displayName' }, { heading = 'Name', key = 'displayName' },
}, },
sortColumn = 'displayName', sortColumn = 'displayName',
@@ -60,6 +61,7 @@ function changedPage.grid:getDisplayValues(row)
if row.change < 0 then if row.change < 0 then
ind = '' ind = ''
end end
row.change = ind .. Util.toBytes(row.change) row.change = ind .. Util.toBytes(row.change)
row.count = Util.toBytes(row.count) row.count = Util.toBytes(row.count)
@@ -67,7 +69,6 @@ function changedPage.grid:getDisplayValues(row)
end end
function changedPage:eventHandler(event) function changedPage:eventHandler(event)
if event.type == 'reset' then if event.type == 'reset' then
self.lastItems = nil self.lastItems = nil
self.grid:setValues({ }) self.grid:setValues({ })
@@ -98,7 +99,6 @@ function changedPage:refresh()
local t = storage:listItems() local t = storage:listItems()
if not t or Util.empty(t) then if not t or Util.empty(t) then
debug('clearing')
self.grid:clear() self.grid:clear()
self.grid:centeredWrite(math.ceil(self.height/2), 'Communication failure') self.grid:centeredWrite(math.ceil(self.height/2), 'Communication failure')
return return
@@ -110,8 +110,10 @@ debug('clearing')
if not self.lastItems then if not self.lastItems then
self.lastItems = t self.lastItems = t
self.timestamp = os.clock()
self.grid:setValues({ }) self.grid:setValues({ })
else else
self.elapsed = os.clock() - self.timestamp
local changedItems = { } local changedItems = { }
local found local found
for _,v in pairs(self.lastItems) do for _,v in pairs(self.lastItems) do
@@ -144,6 +146,7 @@ debug('clearing')
for _,v in pairs(changedItems) do for _,v in pairs(changedItems) do
v.change = v.count - v.lastCount v.change = v.count - v.lastCount
v.rate = Util.round(60 / self.elapsed * v.change, 1)
end end
self.grid:setValues(changedItems) self.grid:setValues(changedItems)