package management
This commit is contained in:
161
core/apis/chestAdapter.lua
Normal file
161
core/apis/chestAdapter.lua
Normal file
@@ -0,0 +1,161 @@
|
||||
local class = require('class')
|
||||
local itemDB = require('itemDB')
|
||||
local Peripheral = require('peripheral')
|
||||
local Util = require('util')
|
||||
|
||||
local os = _G.os
|
||||
|
||||
local ChestAdapter = class()
|
||||
|
||||
local convertNames = {
|
||||
name = 'id',
|
||||
damage = 'dmg',
|
||||
maxCount = 'max_size',
|
||||
count = 'qty',
|
||||
displayName = 'display_name',
|
||||
maxDamage = 'max_dmg',
|
||||
nbtHash = 'nbt_hash',
|
||||
}
|
||||
|
||||
-- Strip off color prefix
|
||||
local function safeString(text)
|
||||
|
||||
local val = text:byte(1)
|
||||
|
||||
if val < 32 or val > 128 then
|
||||
|
||||
local newText = {}
|
||||
for i = 4, #text do
|
||||
val = text:byte(i)
|
||||
newText[i - 3] = (val > 31 and val < 127) and val or 63
|
||||
end
|
||||
return string.char(unpack(newText))
|
||||
end
|
||||
|
||||
return text
|
||||
end
|
||||
|
||||
local function convertItem(item)
|
||||
for k,v in pairs(convertNames) do
|
||||
item[k] = item[v]
|
||||
item[v] = nil
|
||||
end
|
||||
item.displayName = safeString(item.displayName)
|
||||
end
|
||||
|
||||
function ChestAdapter:init(args)
|
||||
local defaults = {
|
||||
name = 'chest',
|
||||
}
|
||||
Util.merge(self, defaults)
|
||||
Util.merge(self, args)
|
||||
|
||||
local chest
|
||||
if not self.side then
|
||||
chest = Peripheral.getByMethod('getAllStacks')
|
||||
else
|
||||
chest = Peripheral.getBySide(self.side)
|
||||
if chest and not chest.getAllStacks then
|
||||
chest = nil
|
||||
end
|
||||
end
|
||||
|
||||
if chest then
|
||||
Util.merge(self, chest)
|
||||
|
||||
if chest.listAvailableItems then
|
||||
self.list = chest.listAvailableItems
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ChestAdapter:isValid()
|
||||
return not not self.getAllStacks
|
||||
end
|
||||
|
||||
function ChestAdapter:refresh(throttle)
|
||||
return self:listItems(throttle)
|
||||
end
|
||||
|
||||
-- provide a consolidated list of items
|
||||
function ChestAdapter:listItems(throttle)
|
||||
local cache = { }
|
||||
local items = { }
|
||||
throttle = throttle or Util.throttle()
|
||||
|
||||
-- getAllStacks sometimes fails
|
||||
local s, m = pcall(function()
|
||||
for _,v in pairs(self.getAllStacks(false)) do
|
||||
if v.qty > 0 then
|
||||
convertItem(v)
|
||||
local key = table.concat({ v.name, v.damage, v.nbtHash }, ':')
|
||||
|
||||
local entry = cache[key]
|
||||
if not entry then
|
||||
entry = itemDB:get(v) or itemDB:add(v)
|
||||
entry = Util.shallowCopy(entry)
|
||||
entry.count = 0
|
||||
cache[key] = entry
|
||||
table.insert(items, entry)
|
||||
end
|
||||
entry.count = entry.count + v.count
|
||||
throttle()
|
||||
end
|
||||
itemDB:flush()
|
||||
end
|
||||
end)
|
||||
if s then
|
||||
if not Util.empty(items) then
|
||||
self.cache = cache
|
||||
return items
|
||||
end
|
||||
else
|
||||
_debug(m)
|
||||
end
|
||||
end
|
||||
|
||||
function ChestAdapter:getItemInfo(item)
|
||||
if not self.cache then
|
||||
self:listItems()
|
||||
end
|
||||
local key = table.concat({ item.name, item.damage, item.nbtHash }, ':')
|
||||
return self.cache[key]
|
||||
end
|
||||
|
||||
function ChestAdapter:provide(item, qty, slot, direction)
|
||||
pcall(function()
|
||||
for key,stack in Util.rpairs(self.getAllStacks(false)) do
|
||||
if stack.id == item.name and
|
||||
(not item.damage or stack.dmg == item.damage) and
|
||||
(not item.nbtHash or stack.nbt_hash == item.nbtHash) then
|
||||
local amount = math.min(qty, stack.qty)
|
||||
if amount > 0 then
|
||||
self.pushItemIntoSlot(direction or self.direction, key, amount, slot)
|
||||
end
|
||||
qty = qty - amount
|
||||
if qty <= 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function ChestAdapter:extract(slot, qty, toSlot)
|
||||
if toSlot then
|
||||
self.pushItemIntoSlot(self.direction, slot, qty, toSlot)
|
||||
else
|
||||
self.pushItem(self.direction, slot, qty)
|
||||
end
|
||||
end
|
||||
|
||||
function ChestAdapter:insert(slot, qty, toSlot)
|
||||
-- toSlot not tested ...
|
||||
local s, m = pcall(self.pullItem, self.direction, slot, qty, toSlot)
|
||||
if not s and m then
|
||||
os.sleep(1)
|
||||
pcall(self.pullItem, self.direction, slot, qty, toSlot)
|
||||
end
|
||||
end
|
||||
|
||||
return ChestAdapter
|
||||
165
core/apis/chestAdapter18.lua
Normal file
165
core/apis/chestAdapter18.lua
Normal file
@@ -0,0 +1,165 @@
|
||||
local class = require('class')
|
||||
local Util = require('util')
|
||||
local itemDB = require('itemDB')
|
||||
local Peripheral = require('peripheral')
|
||||
|
||||
local ChestAdapter = class()
|
||||
|
||||
function ChestAdapter:init(args)
|
||||
local defaults = {
|
||||
name = 'chest',
|
||||
adapter = 'ChestAdapter18'
|
||||
}
|
||||
Util.merge(self, defaults)
|
||||
Util.merge(self, args)
|
||||
|
||||
local chest
|
||||
if not self.side then
|
||||
chest = Peripheral.getByMethod('list') or
|
||||
Peripheral.getByMethod('listAvailableItems')
|
||||
else
|
||||
chest = Peripheral.getBySide(self.side)
|
||||
if chest and not chest.list and not chest.listAvailableItems then
|
||||
chest = nil
|
||||
end
|
||||
end
|
||||
|
||||
if chest then
|
||||
Util.merge(self, chest)
|
||||
|
||||
if chest.listAvailableItems then
|
||||
self.list = chest.listAvailableItems
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ChestAdapter:isValid()
|
||||
return not not self.list
|
||||
end
|
||||
|
||||
-- handle both AE/RS and generic inventory
|
||||
function ChestAdapter:getItemDetails(index, item)
|
||||
if self.getItemMeta then
|
||||
local s, detail = pcall(self.getItemMeta, index)
|
||||
if not s or not detail or detail.name ~= item.name then
|
||||
-- debug({ s, detail })
|
||||
return
|
||||
end
|
||||
return detail
|
||||
else
|
||||
local detail = self.findItems(item)
|
||||
if detail and #detail > 0 then
|
||||
return detail[1].getMetadata()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ChestAdapter:getCachedItemDetails(item, k)
|
||||
local cached = itemDB:get(item)
|
||||
if cached then
|
||||
return cached
|
||||
end
|
||||
|
||||
local detail = self:getItemDetails(k, item)
|
||||
if detail then
|
||||
return itemDB:add(detail)
|
||||
end
|
||||
end
|
||||
|
||||
function ChestAdapter:refresh(throttle)
|
||||
return self:listItems(throttle)
|
||||
end
|
||||
|
||||
-- provide a consolidated list of items
|
||||
function ChestAdapter:listItems(throttle)
|
||||
for _ = 1, 5 do
|
||||
local list = self:listItemsInternal(throttle)
|
||||
if list then
|
||||
return list
|
||||
end
|
||||
end
|
||||
error('Error accessing inventory: ' .. self.direction)
|
||||
end
|
||||
|
||||
function ChestAdapter:listItemsInternal(throttle)
|
||||
local cache = { }
|
||||
local items = { }
|
||||
throttle = throttle or Util.throttle()
|
||||
|
||||
for k,v in pairs(self.list()) do
|
||||
if v.count > 0 then
|
||||
local key = table.concat({ v.name, v.damage, v.nbtHash }, ':')
|
||||
|
||||
local entry = cache[key]
|
||||
if not entry then
|
||||
entry = self:getCachedItemDetails(v, k)
|
||||
if not entry then
|
||||
return -- Inventory has changed
|
||||
end
|
||||
entry = Util.shallowCopy(entry)
|
||||
entry.count = 0
|
||||
cache[key] = entry
|
||||
table.insert(items, entry)
|
||||
end
|
||||
|
||||
if entry then
|
||||
entry.count = entry.count + v.count
|
||||
end
|
||||
throttle()
|
||||
end
|
||||
end
|
||||
itemDB:flush()
|
||||
|
||||
self.cache = cache
|
||||
return items
|
||||
end
|
||||
|
||||
function ChestAdapter:getItemInfo(item)
|
||||
if not self.cache then
|
||||
self:listItems()
|
||||
end
|
||||
local key = table.concat({ item.name, item.damage, item.nbtHash }, ':')
|
||||
local items = self.cache or { }
|
||||
return items[key]
|
||||
end
|
||||
|
||||
function ChestAdapter:getPercentUsed()
|
||||
if self.cache and self.getDrawerCount then
|
||||
return math.floor(Util.size(self.cache) / self.getDrawerCount() * 100)
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
function ChestAdapter:provide(item, qty, slot, direction)
|
||||
local total = 0
|
||||
|
||||
local _, m = pcall(function()
|
||||
local stacks = self.list()
|
||||
for key,stack in Util.rpairs(stacks) do
|
||||
if stack.name == item.name and
|
||||
(not item.damage or stack.damage == item.damage) and
|
||||
(not item.nbtHash or stack.nbtHash == item.nbtHash) then
|
||||
local amount = math.min(qty, stack.count)
|
||||
if amount > 0 then
|
||||
amount = self.pushItems(direction or self.direction, key, amount, slot)
|
||||
end
|
||||
qty = qty - amount
|
||||
total = total + amount
|
||||
if qty <= 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
return total, m
|
||||
end
|
||||
|
||||
function ChestAdapter:extract(slot, qty, toSlot, direction)
|
||||
return self.pushItems(direction or self.direction, slot, qty, toSlot)
|
||||
end
|
||||
|
||||
function ChestAdapter:insert(slot, qty, toSlot, direction)
|
||||
return self.pullItems(direction or self.direction, slot, qty, toSlot)
|
||||
end
|
||||
|
||||
return ChestAdapter
|
||||
18
core/apis/controllerAdapter.lua
Normal file
18
core/apis/controllerAdapter.lua
Normal file
@@ -0,0 +1,18 @@
|
||||
local Adapter = { }
|
||||
|
||||
function Adapter.wrap(args)
|
||||
local adapters = {
|
||||
'refinedAdapter',
|
||||
'meAdapter',
|
||||
}
|
||||
|
||||
for _,adapterType in ipairs(adapters) do
|
||||
local adapter = require(adapterType)(args)
|
||||
|
||||
if adapter:isValid() then
|
||||
return adapter
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return Adapter
|
||||
54
core/apis/inventoryAdapter.lua
Normal file
54
core/apis/inventoryAdapter.lua
Normal file
@@ -0,0 +1,54 @@
|
||||
local Adapter = { }
|
||||
|
||||
function Adapter.wrap(args)
|
||||
local adapters = {
|
||||
'refinedAdapter',
|
||||
'meAdapter18',
|
||||
'chestAdapter18',
|
||||
|
||||
-- adapters for version 1.7
|
||||
'meAdapter',
|
||||
'chestAdapter',
|
||||
}
|
||||
|
||||
for _,adapterType in ipairs(adapters) do
|
||||
local adapter = require(adapterType)(args)
|
||||
|
||||
if adapter:isValid() then
|
||||
|
||||
-- figure out which direction to push/pull items from an inventory
|
||||
-- based on the side the inventory is attached and which way the
|
||||
-- turtle/computer is facing
|
||||
if args and args.facing and adapter.side and not adapter.direction then
|
||||
local horz = { top = 'down', bottom = 'up' }
|
||||
adapter.direction = horz[adapter.side]
|
||||
|
||||
if not adapter.direction then
|
||||
local sides = {
|
||||
front = 0,
|
||||
right = 1,
|
||||
back = 2,
|
||||
left = 3,
|
||||
}
|
||||
-- pretty sure computer/turtle have sides reversed
|
||||
local cards = {
|
||||
east = 0,
|
||||
south = 1,
|
||||
west = 2,
|
||||
north = 3,
|
||||
}
|
||||
local icards = {
|
||||
[ 0 ] = 'west',
|
||||
[ 1 ] = 'north',
|
||||
[ 2 ] = 'east',
|
||||
[ 3 ] = 'south',
|
||||
}
|
||||
adapter.direction = icards[(cards[args.facing] + sides[adapter.side]) % 4]
|
||||
end
|
||||
end
|
||||
return adapter
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return Adapter
|
||||
208
core/apis/itemDB.lua
Normal file
208
core/apis/itemDB.lua
Normal file
@@ -0,0 +1,208 @@
|
||||
local nameDB = require('nameDB')
|
||||
local TableDB = require('tableDB')
|
||||
local Util = require('util')
|
||||
|
||||
local itemDB = TableDB({ fileName = 'usr/config/items.db' })
|
||||
|
||||
local function safeString(text)
|
||||
local val = text:byte(1)
|
||||
|
||||
if val < 32 or val > 128 then
|
||||
|
||||
local newText = { }
|
||||
local skip = 0
|
||||
for i = 1, #text do
|
||||
val = text:byte(i)
|
||||
if val == 167 then
|
||||
skip = 2
|
||||
end
|
||||
if skip > 0 then
|
||||
skip = skip - 1
|
||||
else
|
||||
if val >= 32 and val <= 128 then
|
||||
newText[#newText + 1] = val
|
||||
end
|
||||
end
|
||||
end
|
||||
return string.char(unpack(newText))
|
||||
end
|
||||
|
||||
return text
|
||||
end
|
||||
|
||||
function itemDB:makeKey(item)
|
||||
return table.concat({ item.name, item.damage or '*', item.nbtHash }, ':')
|
||||
end
|
||||
|
||||
function itemDB:splitKey(key, item)
|
||||
item = item or { }
|
||||
|
||||
local t = Util.split(key, '(.-):')
|
||||
if #t[#t] > 8 then
|
||||
item.nbtHash = table.remove(t)
|
||||
end
|
||||
local damage = table.remove(t)
|
||||
if damage ~= '*' then
|
||||
item.damage = tonumber(damage)
|
||||
end
|
||||
item.name = table.concat(t, ':')
|
||||
|
||||
return item
|
||||
end
|
||||
|
||||
function itemDB:get(key)
|
||||
if type(key) == 'string' then
|
||||
key = self:splitKey(key)
|
||||
end
|
||||
|
||||
local item = TableDB.get(self, self:makeKey(key))
|
||||
if item then
|
||||
return item
|
||||
end
|
||||
|
||||
-- try finding an item that has damage values
|
||||
if type(key.damage) == 'number' then
|
||||
item = TableDB.get(self, self:makeKey({ name = key.name, nbtHash = key.nbtHash }))
|
||||
if item and item.maxDamage then
|
||||
item = Util.shallowCopy(item)
|
||||
item.damage = key.damage
|
||||
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
|
||||
end
|
||||
else
|
||||
for k,item in pairs(self.data) do
|
||||
if key.name == item.name and
|
||||
key.nbtHash == key.nbtHash and
|
||||
item.maxDamage > 0 then
|
||||
item = Util.shallowCopy(item)
|
||||
item.nbtHash = key.nbtHash
|
||||
return item
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if key.nbtHash then
|
||||
item = self:get({ name = key.name, damage = key.damage })
|
||||
|
||||
if item and item.ignoreNBT then
|
||||
item = Util.shallowCopy(item)
|
||||
item.nbtHash = key.nbtHash
|
||||
item.damage = key.damage
|
||||
return item
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
If the base item contains an NBT hash, then the NBT hash uniquely
|
||||
identifies this item.
|
||||
]]--
|
||||
function itemDB:add(baseItem)
|
||||
local nItem = {
|
||||
name = baseItem.name,
|
||||
damage = baseItem.damage,
|
||||
nbtHash = baseItem.nbtHash,
|
||||
}
|
||||
-- if detail.maxDamage > 0 then
|
||||
-- nItem.damage = '*'
|
||||
-- end
|
||||
|
||||
nItem.displayName = safeString(baseItem.displayName)
|
||||
nItem.maxCount = baseItem.maxCount
|
||||
nItem.maxDamage = baseItem.maxDamage
|
||||
|
||||
for k,item in pairs(self.data) do
|
||||
if nItem.name == item.name and
|
||||
nItem.displayName == item.displayName then
|
||||
|
||||
if nItem.nbtHash ~= item.nbtHash and nItem.damage ~= item.damage then
|
||||
nItem.damage = '*'
|
||||
nItem.nbtHash = nil
|
||||
nItem.ignoreNBT = true
|
||||
self.data[k] = nil
|
||||
break
|
||||
elseif nItem.damage ~= item.damage then
|
||||
nItem.damage = '*'
|
||||
self.data[k] = nil
|
||||
break
|
||||
elseif nItem.nbtHash ~= item.nbtHash then
|
||||
nItem.nbtHash = nil
|
||||
nItem.ignoreNBT = true
|
||||
self.data[k] = nil
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
TableDB.add(self, self:makeKey(nItem), nItem)
|
||||
nItem = Util.shallowCopy(nItem)
|
||||
nItem.damage = baseItem.damage
|
||||
nItem.nbtHash = baseItem.nbtHash
|
||||
|
||||
return nItem
|
||||
end
|
||||
|
||||
-- Accepts: "minecraft:stick:0" or { name = 'minecraft:stick', damage = 0 }
|
||||
function itemDB:getName(item)
|
||||
if type(item) == 'string' then
|
||||
item = self:splitKey(item)
|
||||
end
|
||||
|
||||
local detail = self:get(item)
|
||||
if detail then
|
||||
return detail.displayName
|
||||
end
|
||||
|
||||
-- fallback to nameDB
|
||||
local strId = self:makeKey(item)
|
||||
local name = nameDB.data[strId]
|
||||
if not name and not item.damage then
|
||||
name = nameDB.data[self:makeKey({ name = item.name, damage = 0, nbtHash = item.nbtHash })]
|
||||
end
|
||||
return name or strId
|
||||
end
|
||||
|
||||
function itemDB:getMaxCount(item)
|
||||
local detail = self:get(item)
|
||||
return detail and detail.maxCount or 64
|
||||
end
|
||||
|
||||
function itemDB:load()
|
||||
TableDB.load(self)
|
||||
|
||||
for key,item in pairs(self.data) do
|
||||
self:splitKey(key, item)
|
||||
item.maxDamage = item.maxDamage or 0
|
||||
item.maxCount = item.maxCount or 64
|
||||
end
|
||||
end
|
||||
|
||||
function itemDB:flush()
|
||||
if self.dirty then
|
||||
|
||||
local t = { }
|
||||
for k,v in pairs(self.data) do
|
||||
v = Util.shallowCopy(v)
|
||||
v.name = nil
|
||||
v.damage = nil
|
||||
v.nbtHash = nil
|
||||
v.count = nil -- wipe out previously saved counts - temporary
|
||||
if v.maxDamage == 0 then
|
||||
v.maxDamage = nil
|
||||
end
|
||||
if v.maxCount == 64 then
|
||||
v.maxCount = nil
|
||||
end
|
||||
t[k] = v
|
||||
end
|
||||
|
||||
Util.writeTable(self.fileName, t)
|
||||
self.dirty = false
|
||||
end
|
||||
end
|
||||
|
||||
itemDB:load()
|
||||
|
||||
return itemDB
|
||||
254
core/apis/meAdapter.lua
Normal file
254
core/apis/meAdapter.lua
Normal file
@@ -0,0 +1,254 @@
|
||||
local class = require('class')
|
||||
local itemDB = require('itemDB')
|
||||
local Peripheral = require('peripheral')
|
||||
local Util = require('util')
|
||||
|
||||
local os = _G.os
|
||||
|
||||
local convertNames = {
|
||||
name = 'id',
|
||||
damage = 'dmg',
|
||||
maxCount = 'max_size',
|
||||
count = 'qty',
|
||||
displayName = 'display_name',
|
||||
maxDamage = 'max_dmg',
|
||||
nbtHash = 'nbt_hash',
|
||||
}
|
||||
|
||||
-- Strip off color prefix
|
||||
local function safeString(text)
|
||||
|
||||
local val = text:byte(1)
|
||||
|
||||
if val < 32 or val > 128 then
|
||||
|
||||
local newText = {}
|
||||
for i = 4, #text do
|
||||
val = text:byte(i)
|
||||
newText[i - 3] = (val > 31 and val < 127) and val or 63
|
||||
end
|
||||
return string.char(unpack(newText))
|
||||
end
|
||||
|
||||
return text
|
||||
end
|
||||
|
||||
local function convertItem(item)
|
||||
for k,v in pairs(convertNames) do
|
||||
item[k] = item[v]
|
||||
item[v] = nil
|
||||
end
|
||||
item.displayName = safeString(item.displayName)
|
||||
end
|
||||
|
||||
local MEAdapter = class()
|
||||
|
||||
function MEAdapter:init(args)
|
||||
local defaults = {
|
||||
items = { },
|
||||
name = 'ME',
|
||||
jobList = { },
|
||||
}
|
||||
Util.merge(self, defaults)
|
||||
Util.merge(self, args)
|
||||
|
||||
local chest
|
||||
|
||||
if not self.side then
|
||||
chest = Peripheral.getByMethod('getAvailableItems')
|
||||
else
|
||||
chest = Peripheral.getBySide(self.side)
|
||||
if chest and not chest.getAvailableItems then
|
||||
chest = nil
|
||||
end
|
||||
end
|
||||
|
||||
if chest then
|
||||
Util.merge(self, chest)
|
||||
end
|
||||
end
|
||||
|
||||
function MEAdapter:isValid()
|
||||
return self.getAvailableItems and self.getAvailableItems()
|
||||
end
|
||||
|
||||
function MEAdapter:refresh()
|
||||
self.items = nil
|
||||
local hasItems, failed
|
||||
|
||||
local s, m = pcall(function()
|
||||
self.items = self.getAvailableItems('all')
|
||||
for _,v in pairs(self.items) do
|
||||
Util.merge(v, v.item)
|
||||
convertItem(v)
|
||||
|
||||
-- if power has been interrupted, the list will still be returned
|
||||
-- but all items will have a 0 quantity
|
||||
-- ensure that the list is valid
|
||||
if not hasItems then
|
||||
hasItems = v.count > 0
|
||||
end
|
||||
|
||||
if not v.fingerprint then
|
||||
failed = true
|
||||
break
|
||||
end
|
||||
|
||||
if not itemDB:get(v) then
|
||||
itemDB:add(v, v)
|
||||
end
|
||||
end
|
||||
end)
|
||||
itemDB:flush()
|
||||
|
||||
if not s and m then
|
||||
_debug(m)
|
||||
end
|
||||
|
||||
if s and not failed and hasItems and self.items and not Util.empty(self.items) then
|
||||
return self.items
|
||||
end
|
||||
self.items = nil
|
||||
end
|
||||
|
||||
function MEAdapter:listItems()
|
||||
self:refresh()
|
||||
return self.items
|
||||
end
|
||||
|
||||
function MEAdapter:getItemInfo(item)
|
||||
for _,i in pairs(self.items) do
|
||||
if item.name == i.name and
|
||||
item.damage == i.damage and
|
||||
item.nbtHash == i.nbtHash then
|
||||
return i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function MEAdapter:isCPUAvailable()
|
||||
local cpus = self.getCraftingCPUs() or { }
|
||||
local available = false
|
||||
|
||||
for cpu,v in pairs(cpus) do
|
||||
if not v.busy then
|
||||
available = true
|
||||
elseif not self.jobList[cpu] then -- something else is crafting something (don't know what)
|
||||
return false -- return false since we are in an unknown state
|
||||
end
|
||||
end
|
||||
return available
|
||||
end
|
||||
|
||||
function MEAdapter:craft(item, count)
|
||||
if not self:isCPUAvailable() then
|
||||
return false
|
||||
end
|
||||
|
||||
self:refresh()
|
||||
|
||||
item = self:getItemInfo(item)
|
||||
if item and item.is_craftable then
|
||||
|
||||
local cpus = self.getCraftingCPUs() or { }
|
||||
for cpu,v in pairs(cpus) do
|
||||
if not v.busy then
|
||||
self.requestCrafting({
|
||||
id = item.name,
|
||||
dmg = item.damage,
|
||||
nbt_hash = item.nbtHash,
|
||||
},
|
||||
count or 1,
|
||||
v.name -- CPUs must be named ! use anvil
|
||||
)
|
||||
|
||||
os.sleep(0) -- needed ?
|
||||
cpus = self.getCraftingCPUs() or { }
|
||||
|
||||
if cpus[cpu].busy then
|
||||
self.jobList[cpu] = {
|
||||
name = item.name,
|
||||
damage = item.damage,
|
||||
nbtHash = item.nbtHash,
|
||||
count = count,
|
||||
}
|
||||
return true
|
||||
end
|
||||
break -- only need to try the first available cpu
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function MEAdapter:getJobList()
|
||||
local cpus = self.getCraftingCPUs() or { }
|
||||
for cpu,v in pairs(cpus) do
|
||||
if not v.busy then
|
||||
self.jobList[cpu] = nil
|
||||
end
|
||||
end
|
||||
|
||||
return self.jobList
|
||||
end
|
||||
|
||||
function MEAdapter:isCrafting(item)
|
||||
for _,v in pairs(self:getJobList()) do
|
||||
if v.name == item.name and
|
||||
v.damage == item.damage and
|
||||
v.nbtHash == item.nbtHash then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function MEAdapter:craftItems(items)
|
||||
local cpus = self.getCraftingCPUs() or { }
|
||||
local count = 0
|
||||
|
||||
for _,cpu in pairs(cpus) do
|
||||
if cpu.busy then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
for _,item in pairs(items) do
|
||||
if count >= #cpus then
|
||||
break
|
||||
end
|
||||
if not self:isCrafting(item) then
|
||||
if self:craft(item, item.count) then
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function MEAdapter:provide(item, qty, slot, direction)
|
||||
return pcall(function()
|
||||
for _,stack in pairs(self.getAvailableItems('all')) do
|
||||
if stack.item.id == item.name and
|
||||
(not item.damage or stack.item.dmg == item.damage) and
|
||||
(not item.nbtHash or stack.item.nbt_hash == item.nbtHash) then
|
||||
local amount = math.min(qty, stack.item.qty)
|
||||
if amount > 0 then
|
||||
self.exportItem(stack.item, direction or self.direction, amount, slot)
|
||||
end
|
||||
qty = qty - amount
|
||||
if qty <= 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function MEAdapter:insert(slot, qty, toSlot)
|
||||
local s, m = pcall(self.pullItem, self.direction, slot, qty, toSlot)
|
||||
if not s and m then
|
||||
os.sleep(1)
|
||||
pcall(self.pullItem, self.direction, slot, qty, toSlot)
|
||||
end
|
||||
end
|
||||
|
||||
return MEAdapter
|
||||
90
core/apis/meAdapter18.lua
Normal file
90
core/apis/meAdapter18.lua
Normal file
@@ -0,0 +1,90 @@
|
||||
local class = require('class')
|
||||
local RSAdapter = require('refinedAdapter')
|
||||
local Peripheral = require('peripheral')
|
||||
local Util = require('util')
|
||||
|
||||
local MEAdapter = class(RSAdapter)
|
||||
|
||||
local DEVICE_TYPE = 'appliedenergistics2:interface'
|
||||
|
||||
function MEAdapter:init(args)
|
||||
local defaults = {
|
||||
name = 'appliedEnergistics',
|
||||
jobList = { },
|
||||
}
|
||||
Util.merge(self, defaults)
|
||||
Util.merge(self, args)
|
||||
|
||||
local controller
|
||||
if not self.side then
|
||||
controller = Peripheral.getByType(DEVICE_TYPE)
|
||||
else
|
||||
controller = Peripheral.getBySide(self.side)
|
||||
end
|
||||
|
||||
if controller then
|
||||
Util.merge(self, controller)
|
||||
end
|
||||
end
|
||||
|
||||
function MEAdapter:isValid()
|
||||
return self.type == DEVICE_TYPE and not not self.findItems
|
||||
end
|
||||
|
||||
function MEAdapter:clearFinished()
|
||||
for _,key in pairs(Util.keys(self.jobList)) do
|
||||
local job = self.jobList[key]
|
||||
if job.info.status() == 'finished' then
|
||||
self.jobList[key] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function MEAdapter:isCPUAvailable()
|
||||
local cpus = self.getCraftingCPUs() or { }
|
||||
local busy = 0
|
||||
|
||||
for _,cpu in pairs(cpus) do
|
||||
if cpu.busy then
|
||||
busy = busy + 1
|
||||
end
|
||||
end
|
||||
self:clearFinished()
|
||||
return busy == Util.size(self.jobList) and busy < #cpus
|
||||
end
|
||||
|
||||
function MEAdapter:craft(item, count)
|
||||
if not self:isCPUAvailable() then
|
||||
return false
|
||||
end
|
||||
|
||||
local detail = self.findItem(item)
|
||||
if detail and detail.craft then
|
||||
local info = detail.craft(count or 1)
|
||||
if info.status() == 'unknown' then
|
||||
self.jobList[info.getId()] = {
|
||||
name = item.name,
|
||||
damage = item.damage,
|
||||
nbtHash = item.nbtHash,
|
||||
info = info,
|
||||
}
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function MEAdapter:isCrafting(item)
|
||||
self:clearFinished()
|
||||
_G._p = self.jobList
|
||||
for _,job in pairs(self.jobList) do
|
||||
if job.name == item.name and
|
||||
job.damage == item.damage and
|
||||
job.nbtHash == item.nbtHash then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
return MEAdapter
|
||||
106
core/apis/message.lua
Normal file
106
core/apis/message.lua
Normal file
@@ -0,0 +1,106 @@
|
||||
local Event = require('event')
|
||||
local Logger = require('logger')
|
||||
|
||||
local Message = { }
|
||||
|
||||
local messageHandlers = {}
|
||||
|
||||
function Message.enable()
|
||||
if not device.wireless_modem.isOpen(os.getComputerID()) then
|
||||
device.wireless_modem.open(os.getComputerID())
|
||||
end
|
||||
if not device.wireless_modem.isOpen(60000) then
|
||||
device.wireless_modem.open(60000)
|
||||
end
|
||||
end
|
||||
|
||||
if device and device.wireless_modem then
|
||||
Message.enable()
|
||||
end
|
||||
|
||||
Event.on('device_attach', function(event, deviceName)
|
||||
if deviceName == 'wireless_modem' then
|
||||
Message.enable()
|
||||
end
|
||||
end)
|
||||
|
||||
function Message.addHandler(type, f)
|
||||
table.insert(messageHandlers, {
|
||||
type = type,
|
||||
f = f,
|
||||
enabled = true
|
||||
})
|
||||
end
|
||||
|
||||
function Message.removeHandler(h)
|
||||
for k,v in pairs(messageHandlers) do
|
||||
if v == h then
|
||||
messageHandlers[k] = nil
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Event.on('modem_message',
|
||||
function(event, side, sendChannel, replyChannel, msg, distance)
|
||||
if msg and msg.type then -- filter out messages from other systems
|
||||
local id = replyChannel
|
||||
Logger.log('modem_receive', { id, msg.type })
|
||||
--Logger.log('modem_receive', msg.contents)
|
||||
for k,h in pairs(messageHandlers) do
|
||||
if h.type == msg.type then
|
||||
-- should provide msg.contents instead of message - type is already known
|
||||
h.f(h, id, msg, distance)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
function Message.send(id, msgType, contents)
|
||||
if not device.wireless_modem then
|
||||
error('No modem attached', 2)
|
||||
end
|
||||
|
||||
if id then
|
||||
Logger.log('modem_send', { tostring(id), msgType })
|
||||
device.wireless_modem.transmit(id, os.getComputerID(), {
|
||||
type = msgType, contents = contents
|
||||
})
|
||||
else
|
||||
Logger.log('modem_send', { 'broadcast', msgType })
|
||||
device.wireless_modem.transmit(60000, os.getComputerID(), {
|
||||
type = msgType, contents = contents
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function Message.broadcast(t, contents)
|
||||
if not device.wireless_modem then
|
||||
error('No modem attached', 2)
|
||||
end
|
||||
|
||||
Message.send(nil, t, contents)
|
||||
-- Logger.log('rednet_send', { 'broadcast', t })
|
||||
-- rednet.broadcast({ type = t, contents = contents })
|
||||
end
|
||||
|
||||
function Message.waitForMessage(msgType, timeout, fromId)
|
||||
local timerId = os.startTimer(timeout)
|
||||
repeat
|
||||
local e, side, _id, id, msg, distance = os.pullEvent()
|
||||
if e == 'modem_message' then
|
||||
if msg and msg.type and msg.type == msgType then
|
||||
if not fromId or id == fromId then
|
||||
return e, id, msg, distance
|
||||
end
|
||||
end
|
||||
end
|
||||
until e == 'timer' and side == timerId
|
||||
end
|
||||
|
||||
function Message.enableWirelessLogging()
|
||||
Logger.setWirelessLogging()
|
||||
end
|
||||
|
||||
return Message
|
||||
42
core/apis/nameDB.lua
Normal file
42
core/apis/nameDB.lua
Normal file
@@ -0,0 +1,42 @@
|
||||
local JSON = require('json')
|
||||
local TableDB = require('tableDB')
|
||||
|
||||
local fs = _G.fs
|
||||
|
||||
local NAME_DIR = '/packages/core/etc/names'
|
||||
|
||||
local nameDB = TableDB()
|
||||
|
||||
function nameDB:load()
|
||||
|
||||
local files = fs.list(NAME_DIR)
|
||||
table.sort(files)
|
||||
|
||||
for _,file in ipairs(files) do
|
||||
local mod = file:match('(%S+).json')
|
||||
local blocks = JSON.decodeFromFile(fs.combine(NAME_DIR, file))
|
||||
|
||||
if not blocks then
|
||||
error('Unable to read ' .. fs.combine(NAME_DIR, file))
|
||||
end
|
||||
|
||||
for strId, block in pairs(blocks) do
|
||||
strId = string.format('%s:%s', mod, strId)
|
||||
if type(block.name) == 'string' then
|
||||
self.data[strId .. ':0'] = block.name
|
||||
else
|
||||
for nid,name in pairs(block.name) do
|
||||
self.data[strId .. ':' .. (nid-1)] = name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function nameDB:getName(strId)
|
||||
return self.data[strId] or strId
|
||||
end
|
||||
|
||||
nameDB:load()
|
||||
|
||||
return nameDB
|
||||
139
core/apis/refinedAdapter.lua
Normal file
139
core/apis/refinedAdapter.lua
Normal file
@@ -0,0 +1,139 @@
|
||||
local class = require('class')
|
||||
local itemDB = require('itemDB')
|
||||
local Peripheral = require('peripheral')
|
||||
local Util = require('util')
|
||||
|
||||
local RefinedAdapter = class()
|
||||
|
||||
function RefinedAdapter:init(args)
|
||||
local defaults = {
|
||||
name = 'refinedStorage',
|
||||
}
|
||||
Util.merge(self, defaults)
|
||||
Util.merge(self, args)
|
||||
|
||||
local controller
|
||||
if not self.side then
|
||||
controller = Peripheral.getByMethod('getCraftingTasks')
|
||||
else
|
||||
controller = Peripheral.getBySide(self.side)
|
||||
end
|
||||
|
||||
if controller then
|
||||
Util.merge(self, controller)
|
||||
end
|
||||
end
|
||||
|
||||
function RefinedAdapter:isValid()
|
||||
return not not self.getCraftingTasks
|
||||
end
|
||||
|
||||
function RefinedAdapter:getItemDetails(item)
|
||||
local detail = self.findItems(item)
|
||||
if detail and #detail > 0 then
|
||||
return detail[1].getMetadata()
|
||||
end
|
||||
end
|
||||
|
||||
function RefinedAdapter:getCachedItemDetails(item)
|
||||
local cached = itemDB:get(item)
|
||||
if cached then
|
||||
return cached
|
||||
end
|
||||
|
||||
local detail = self:getItemDetails(item)
|
||||
if detail then
|
||||
return itemDB:add(detail)
|
||||
end
|
||||
end
|
||||
|
||||
function RefinedAdapter:refresh(throttle)
|
||||
return self:listItems(throttle)
|
||||
end
|
||||
|
||||
function RefinedAdapter:listItems(throttle)
|
||||
local items = { }
|
||||
throttle = throttle or Util.throttle()
|
||||
|
||||
local s, m = pcall(function()
|
||||
for _,v in pairs(self.listAvailableItems()) do
|
||||
--if v.count > 0 then
|
||||
local item = self:getCachedItemDetails(v)
|
||||
if item then
|
||||
item = Util.shallowCopy(item)
|
||||
item.count = v.count
|
||||
table.insert(items, item)
|
||||
end
|
||||
--end
|
||||
throttle()
|
||||
end
|
||||
end)
|
||||
|
||||
if not s and m then
|
||||
_debug(m)
|
||||
end
|
||||
|
||||
itemDB:flush()
|
||||
if not Util.empty(items) then
|
||||
return items
|
||||
end
|
||||
end
|
||||
|
||||
function RefinedAdapter:getItemInfo(item)
|
||||
return self:getItemDetails(item)
|
||||
end
|
||||
|
||||
function RefinedAdapter:isCPUAvailable()
|
||||
return true
|
||||
end
|
||||
|
||||
function RefinedAdapter:craft(item, qty)
|
||||
local detail = self.findItem(item)
|
||||
if detail then
|
||||
return detail.craft(qty)
|
||||
end
|
||||
end
|
||||
|
||||
function RefinedAdapter:isCrafting(item)
|
||||
for _,task in pairs(self.getCraftingTasks()) do
|
||||
local output = task.getPattern().outputs[1]
|
||||
if output.name == item.name and
|
||||
output.damage == item.damage and
|
||||
output.nbtHash == item.nbtHash then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function RefinedAdapter:provide(item, qty, slot, direction)
|
||||
return pcall(function()
|
||||
for _,stack in pairs(self.listAvailableItems()) do
|
||||
if stack.name == item.name and
|
||||
(not item.damage or stack.damage == item.damage) and
|
||||
(not item.nbtHash or stack.nbtHash == item.nbtHash) then
|
||||
local amount = math.min(qty, stack.count)
|
||||
if amount > 0 then
|
||||
local detail = self.findItem(item)
|
||||
if detail then
|
||||
return detail.export(direction or self.direction, amount, slot)
|
||||
end
|
||||
end
|
||||
qty = qty - amount
|
||||
if qty <= 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function RefinedAdapter:extract(slot, qty, toSlot)
|
||||
self.pushItems(self.direction, slot, qty, toSlot)
|
||||
end
|
||||
|
||||
function RefinedAdapter:insert(slot, qty, toSlot)
|
||||
self.pullItems(self.direction, slot, qty, toSlot)
|
||||
end
|
||||
|
||||
return RefinedAdapter
|
||||
49
core/apis/tableDB.lua
Normal file
49
core/apis/tableDB.lua
Normal file
@@ -0,0 +1,49 @@
|
||||
local class = require('class')
|
||||
local Util = require('util')
|
||||
|
||||
local TableDB = class()
|
||||
function TableDB:init(args)
|
||||
local defaults = {
|
||||
fileName = '',
|
||||
dirty = false,
|
||||
data = { },
|
||||
}
|
||||
Util.merge(defaults, args)
|
||||
Util.merge(self, defaults)
|
||||
end
|
||||
|
||||
function TableDB:load()
|
||||
local t = Util.readTable(self.fileName)
|
||||
if t then
|
||||
self.data = t.data or t
|
||||
end
|
||||
end
|
||||
|
||||
function TableDB:add(key, entry)
|
||||
if type(key) == 'table' then
|
||||
key = table.concat(key, ':')
|
||||
end
|
||||
self.data[key] = entry
|
||||
self.dirty = true
|
||||
end
|
||||
|
||||
function TableDB:get(key)
|
||||
if type(key) == 'table' then
|
||||
key = table.concat(key, ':')
|
||||
end
|
||||
return self.data[key]
|
||||
end
|
||||
|
||||
function TableDB:remove(key)
|
||||
self.data[key] = nil
|
||||
self.dirty = true
|
||||
end
|
||||
|
||||
function TableDB:flush()
|
||||
if self.dirty then
|
||||
Util.writeTable(self.fileName, self.data)
|
||||
self.dirty = false
|
||||
end
|
||||
end
|
||||
|
||||
return TableDB
|
||||
319
core/apis/turtle/craft.lua
Normal file
319
core/apis/turtle/craft.lua
Normal file
@@ -0,0 +1,319 @@
|
||||
local itemDB = require('itemDB')
|
||||
local Util = require('util')
|
||||
|
||||
local fs = _G.fs
|
||||
local turtle = _G.turtle
|
||||
|
||||
local RECIPES_DIR = 'packages/core/etc/recipes'
|
||||
local USER_RECIPES = 'usr/config/recipes.db'
|
||||
|
||||
local Craft = { }
|
||||
|
||||
local function clearGrid(inventoryAdapter)
|
||||
for i = 1, 16 do
|
||||
local count = turtle.getItemCount(i)
|
||||
if count > 0 then
|
||||
inventoryAdapter:insert(i, count)
|
||||
if turtle.getItemCount(i) ~= 0 then
|
||||
-- inventory is possibly full
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local function splitKey(key)
|
||||
local t = Util.split(key, '(.-):')
|
||||
local item = { }
|
||||
if #t[#t] > 8 then
|
||||
item.nbtHash = table.remove(t)
|
||||
end
|
||||
item.damage = tonumber(table.remove(t))
|
||||
item.name = table.concat(t, ':')
|
||||
return item
|
||||
end
|
||||
|
||||
function Craft.getItemCount(items, item)
|
||||
if type(item) == 'string' then
|
||||
item = splitKey(item)
|
||||
end
|
||||
|
||||
local count = 0
|
||||
for _,v in pairs(items) do
|
||||
if v.name == item.name and
|
||||
(not item.damage or v.damage == item.damage) and
|
||||
v.nbtHash == item.nbtHash then
|
||||
if item.damage then
|
||||
return v.count
|
||||
end
|
||||
count = count + v.count
|
||||
end
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
local function turtleCraft(recipe, qty, inventoryAdapter)
|
||||
if not clearGrid(inventoryAdapter) then
|
||||
return false
|
||||
end
|
||||
|
||||
for k,v in pairs(recipe.ingredients) do
|
||||
local item = splitKey(v)
|
||||
local provideQty = qty
|
||||
--[[
|
||||
Turtles can only craft 1 item at a time when using a tool.
|
||||
|
||||
if recipe.craftingTools and recipe.craftingTools[k] then
|
||||
provideQty = 1
|
||||
end
|
||||
]]--
|
||||
inventoryAdapter:provide(item, provideQty, k)
|
||||
if turtle.getItemCount(k) == 0 then -- ~= qty then
|
||||
-- FIX: ingredients cannot be stacked
|
||||
--debug('failed ' .. v .. ' - ' .. provideQty)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return turtle.craft()
|
||||
end
|
||||
|
||||
function Craft.loadRecipes()
|
||||
Craft.recipes = { }
|
||||
|
||||
Util.merge(Craft.recipes, (Util.readTable(fs.combine(RECIPES_DIR, 'minecraft.db')) or { }).recipes)
|
||||
|
||||
local config = Util.readTable('usr/config/recipeBooks.db') or { }
|
||||
for _, book in pairs(config) do
|
||||
local recipeFile = Util.readTable(book)
|
||||
Util.merge(Craft.recipes, recipeFile.recipes)
|
||||
end
|
||||
|
||||
local recipes = Util.readTable(USER_RECIPES) or { }
|
||||
Util.merge(Craft.recipes, recipes)
|
||||
end
|
||||
|
||||
function Craft.sumIngredients(recipe)
|
||||
-- produces { ['minecraft:planks:0'] = 8 }
|
||||
local t = { }
|
||||
for _,item in pairs(recipe.ingredients) do
|
||||
t[item] = (t[item] or 0) + 1
|
||||
end
|
||||
-- need a check for crafting tool
|
||||
return t
|
||||
end
|
||||
|
||||
function Craft.craftRecipe(recipe, count, inventoryAdapter)
|
||||
if type(recipe) == 'string' then
|
||||
recipe = Craft.recipes[recipe]
|
||||
if not recipe then
|
||||
return 0, 'No recipe'
|
||||
end
|
||||
end
|
||||
|
||||
local items = inventoryAdapter:listItems()
|
||||
if not items then
|
||||
return 0, 'Inventory changed'
|
||||
end
|
||||
|
||||
count = math.ceil(count / recipe.count)
|
||||
local maxCount = recipe.maxCount or math.floor(64 / recipe.count)
|
||||
|
||||
for key,icount in pairs(Craft.sumIngredients(recipe)) do
|
||||
local itemCount = Craft.getItemCount(items, key)
|
||||
local need = icount * count
|
||||
if recipe.craftingTools and recipe.craftingTools[key] then
|
||||
need = 1
|
||||
end
|
||||
maxCount = math.min(maxCount, itemDB:getMaxCount(key))
|
||||
if itemCount < need then
|
||||
local irecipe = Craft.findRecipe(key)
|
||||
if irecipe then
|
||||
local iqty = need - itemCount
|
||||
local crafted = Craft.craftRecipe(irecipe, iqty, inventoryAdapter)
|
||||
if crafted ~= iqty then
|
||||
turtle.select(1)
|
||||
return 0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local crafted = 0
|
||||
repeat
|
||||
if not turtleCraft(recipe, math.min(count, maxCount), inventoryAdapter) then
|
||||
turtle.select(1)
|
||||
break
|
||||
end
|
||||
crafted = crafted + math.min(count, maxCount)
|
||||
count = count - maxCount
|
||||
until count <= 0
|
||||
|
||||
turtle.select(1)
|
||||
return crafted * recipe.count
|
||||
end
|
||||
|
||||
local function makeRecipeKey(item)
|
||||
if type(item) == 'string' then
|
||||
item = splitKey(item)
|
||||
end
|
||||
return table.concat({ item.name, item.damage or 0, item.nbtHash }, ':')
|
||||
end
|
||||
|
||||
function Craft.findRecipe(key)
|
||||
if type(key) ~= 'string' then
|
||||
key = itemDB:makeKey(key)
|
||||
end
|
||||
|
||||
local item = itemDB:splitKey(key)
|
||||
if item.damage then
|
||||
return Craft.recipes[makeRecipeKey(item)]
|
||||
end
|
||||
|
||||
-- handle cases where the request is like : IC2:reactorVent:*
|
||||
for rkey,recipe in pairs(Craft.recipes) do
|
||||
local r = itemDB:splitKey(rkey)
|
||||
if item.name == r.name and
|
||||
(not item.nbtHash or r.nbtHash == item.nbtHash) then
|
||||
return recipe
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- determine the full list of ingredients needed to craft
|
||||
-- a quantity of a recipe.
|
||||
function Craft.getResourceList(inRecipe, items, inCount)
|
||||
local summed = { }
|
||||
|
||||
local function sumItems(recipe, key, count)
|
||||
local item = itemDB:splitKey(key)
|
||||
local summedItem = summed[key]
|
||||
if not summedItem then
|
||||
summedItem = Util.shallowCopy(item)
|
||||
summedItem.recipe = Craft.findRecipe(key)
|
||||
summedItem.count = Craft.getItemCount(items, item)
|
||||
summedItem.displayName = itemDB:getName(item)
|
||||
summedItem.total = 0
|
||||
summedItem.need = 0
|
||||
summedItem.used = 0
|
||||
summed[key] = summedItem
|
||||
end
|
||||
local total = count
|
||||
local used = math.min(summedItem.count, total)
|
||||
local need = total - used
|
||||
|
||||
if recipe.craftingTools and recipe.craftingTools[key] then
|
||||
summedItem.total = 1
|
||||
if summedItem.count > 0 then
|
||||
summedItem.used = 1
|
||||
summedItem.need = 0
|
||||
need = 0
|
||||
elseif not summedItem.recipe then
|
||||
summedItem.need = 1
|
||||
need = 1
|
||||
else
|
||||
need = 1
|
||||
end
|
||||
else
|
||||
summedItem.total = summedItem.total + total
|
||||
summedItem.count = summedItem.count - used
|
||||
summedItem.used = summedItem.used + used
|
||||
if not summedItem.recipe then
|
||||
summedItem.need = summedItem.need + need
|
||||
end
|
||||
end
|
||||
|
||||
if need > 0 and summedItem.recipe then
|
||||
need = math.ceil(need / summedItem.recipe.count)
|
||||
for ikey,iqty in pairs(Craft.sumIngredients(summedItem.recipe)) do
|
||||
sumItems(summedItem.recipe, ikey, math.ceil(need * iqty))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
inCount = math.ceil(inCount / inRecipe.count)
|
||||
for ikey,iqty in pairs(Craft.sumIngredients(inRecipe)) do
|
||||
sumItems(inRecipe, ikey, math.ceil(inCount * iqty))
|
||||
end
|
||||
|
||||
return summed
|
||||
end
|
||||
|
||||
function Craft.getResourceList4(inRecipe, items, count)
|
||||
local summed = Craft.getResourceList(inRecipe, items, count)
|
||||
-- filter down to just raw materials
|
||||
return Util.filter(summed, function(a) return a.used > 0 or a.need > 0 end)
|
||||
end
|
||||
|
||||
-- given a certain quantity, return how many of those can be crafted
|
||||
function Craft.getCraftableAmount(inRecipe, count, items, missing)
|
||||
local function sumItems(recipe, summedItems, count)
|
||||
local canCraft = 0
|
||||
|
||||
for _ = 1, count do
|
||||
for _,item in pairs(recipe.ingredients) do
|
||||
local summedItem = summedItems[item] or Craft.getItemCount(items, item)
|
||||
|
||||
local irecipe = Craft.findRecipe(item)
|
||||
if irecipe and summedItem <= 0 then
|
||||
summedItem = summedItem + sumItems(irecipe, summedItems, 1)
|
||||
end
|
||||
if summedItem <= 0 then
|
||||
if missing and not irecipe then
|
||||
missing.name = item
|
||||
end
|
||||
return canCraft
|
||||
end
|
||||
if not recipe.craftingTools or not recipe.craftingTools[item] then
|
||||
summedItems[item] = summedItem - 1
|
||||
end
|
||||
end
|
||||
canCraft = canCraft + recipe.count
|
||||
end
|
||||
|
||||
return canCraft
|
||||
end
|
||||
|
||||
return sumItems(inRecipe, { }, math.ceil(count / inRecipe.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('usr/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 ChestAdapter = require('chestAdapter18')
|
||||
local chestAdapter = ChestAdapter({ wrapSide = 'top', direction = 'down' })
|
||||
Craft.setRecipes(Util.readTable('usr/etc/recipes.db'))
|
||||
return { Craft.craftRecipe(Craft.recipes[name], count, chestAdapter) }
|
||||
end
|
||||
|
||||
Craft.loadRecipes()
|
||||
|
||||
return Craft
|
||||
65
core/apis/turtle/crafting.lua
Normal file
65
core/apis/turtle/crafting.lua
Normal file
@@ -0,0 +1,65 @@
|
||||
local Adapter = require('inventoryAdapter')
|
||||
local Craft = require('turtle.craft')
|
||||
|
||||
local turtle = _G.turtle
|
||||
|
||||
local CRAFTING_TABLE = 'minecraft:crafting_table'
|
||||
|
||||
local function clearGrid(inventory)
|
||||
for i = 1, 16 do
|
||||
local count = turtle.getItemCount(i)
|
||||
if count > 0 then
|
||||
inventory:insert(i, count)
|
||||
if turtle.getItemCount(i) ~= 0 then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function turtle.craftItem(item, count, inventoryInfo)
|
||||
local success, msg
|
||||
|
||||
local inventory = Adapter.wrap(inventoryInfo)
|
||||
if not inventory then
|
||||
return false, 'Invalid inventory'
|
||||
end
|
||||
|
||||
local equipped, side
|
||||
if not turtle.isEquipped('workbench') then
|
||||
local modemSide = turtle.isEquipped('modem') or 'right'
|
||||
local osides = { left = 'right', right = 'left' }
|
||||
side = osides[modemSide]
|
||||
if not turtle.select(CRAFTING_TABLE) then
|
||||
clearGrid(inventory)
|
||||
if not turtle.selectOpenSlot() then
|
||||
return false, 'Inventory is full'
|
||||
end
|
||||
if not inventory:provide({ name = CRAFTING_TABLE, damage = 0 }, 1) then
|
||||
return false, 'Missing crafting table'
|
||||
end
|
||||
end
|
||||
|
||||
local slot = turtle.select(CRAFTING_TABLE)
|
||||
turtle.equip(side, CRAFTING_TABLE)
|
||||
equipped = turtle.getItemDetail(slot.index)
|
||||
end
|
||||
|
||||
clearGrid(inventory)
|
||||
success, msg = Craft.craftRecipe(item, count or 1, inventory)
|
||||
|
||||
if equipped then
|
||||
turtle.selectOpenSlot()
|
||||
inventory:provide({ name = equipped.name, damage = equipped.damage }, 1)
|
||||
turtle.equip(side, equipped.name .. ':' .. equipped.damage)
|
||||
end
|
||||
|
||||
return success, msg
|
||||
end
|
||||
|
||||
function turtle.canCraft(item, count, items)
|
||||
return Craft.canCraft(item, count, items)
|
||||
end
|
||||
|
||||
return true
|
||||
42
core/apis/turtle/home.lua
Normal file
42
core/apis/turtle/home.lua
Normal file
@@ -0,0 +1,42 @@
|
||||
local Config = require('config')
|
||||
local GPS = require('gps')
|
||||
|
||||
local turtle = _G.turtle
|
||||
|
||||
local Home = { }
|
||||
|
||||
function Home.go()
|
||||
local config = { }
|
||||
Config.load('gps', config)
|
||||
|
||||
if config.home then
|
||||
if turtle.enableGPS() then
|
||||
return turtle.pathfind(config.home)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Home.set()
|
||||
local config = { }
|
||||
Config.load('gps', config)
|
||||
|
||||
local pt = GPS.getPoint()
|
||||
if pt then
|
||||
local originalHeading = turtle.point.heading
|
||||
local heading = GPS.getHeading()
|
||||
if heading then
|
||||
local turns = (turtle.point.heading - originalHeading) % 4
|
||||
pt.heading = (heading - turns) % 4
|
||||
config.home = pt
|
||||
Config.update('gps', config)
|
||||
|
||||
pt = GPS.getPoint()
|
||||
pt.heading = heading
|
||||
turtle.setPoint(pt, true)
|
||||
turtle._goto(config.home)
|
||||
return config.home
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return Home
|
||||
170
core/apis/turtle/level.lua
Normal file
170
core/apis/turtle/level.lua
Normal file
@@ -0,0 +1,170 @@
|
||||
local Point = require('point')
|
||||
local Util = require('util')
|
||||
|
||||
local turtle = _G.turtle
|
||||
|
||||
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'))
|
||||
dig(turtle.getAction('forward'))
|
||||
elseif action == 'down' then
|
||||
dig(turtle.getAction('down'))
|
||||
dig(turtle.getAction('forward'))
|
||||
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
|
||||
local 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
|
||||
|
||||
function turtle.level(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)
|
||||
|
||||
if not Point.inBox(firstPt, box) then
|
||||
error('Starting point is not in leveling area')
|
||||
end
|
||||
|
||||
if not turtle.pathfind(firstPt) then
|
||||
error('failed to reach starting point')
|
||||
end
|
||||
|
||||
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 not next(nodes) then
|
||||
break
|
||||
end
|
||||
|
||||
local node = closestPoint(turtle.point, nodes)
|
||||
node = getAdjacentPoint(node)
|
||||
if not turtle._goto(node) then
|
||||
break
|
||||
end
|
||||
until turtle.isAborted()
|
||||
|
||||
turtle.resetState()
|
||||
turtle.setMoveCallback(oldCallback)
|
||||
end
|
||||
|
||||
return true
|
||||
Reference in New Issue
Block a user