package management

This commit is contained in:
kepler155c
2018-11-03 18:14:11 -04:00
parent aa66b1c663
commit 1f7ef4a483
124 changed files with 1274 additions and 9 deletions

9
core/.package Normal file
View File

@@ -0,0 +1,9 @@
{
required = {
'opus-develop-1.8',
},
title = 'Core apps and apis',
repository = 'kepler155c/opus-apps/develop1.8/core',
description = [[ ... ]],
licence = 'MIT',
}

412
core/Appstore.lua Normal file
View File

@@ -0,0 +1,412 @@
_G.requireInjector()
local Ansi = require('ansi')
local SHA1 = require('sha1')
local UI = require('ui')
local Util = require('util')
local fs = _G.fs
local http = _G.http
local multishell = _ENV.multishell
local os = _G.os
local shell = _ENV.shell
local REGISTRY_DIR = 'usr/.registry'
-- FIX SOMEDAY
local function registerApp(app, key)
app.key = SHA1.sha1(key)
Util.writeTable(fs.combine(REGISTRY_DIR, app.key), app)
os.queueEvent('os_register_app')
end
local function unregisterApp(key)
local filename = fs.combine(REGISTRY_DIR, SHA1.sha1(key))
if fs.exists(filename) then
fs.delete(filename)
os.queueEvent('os_register_app')
end
end
local sandboxEnv = Util.shallowCopy(_ENV)
setmetatable(sandboxEnv, { __index = _G })
multishell.setTitle(multishell.getCurrent(), 'App Store')
UI:configure('Appstore', ...)
local APP_DIR = 'usr/apps'
local sources = {
{ text = "STD Default",
event = 'source',
url = "http://pastebin.com/raw/zVws7eLq" }, --stock
--[[
{ text = "Discover",
event = 'source',
generateName = true,
url = "http://pastebin.com/raw/9bXfCz6M" }, --owned by dannysmc95
{ text = "Opus",
event = 'source',
url = "http://pastebin.com/raw/ajQ91Rmn" },
]]
}
shell.setDir(APP_DIR)
local function downloadApp(app)
local h
if type(app.url) == "table" then
h = contextualGet(app.url[1])
else
h = http.get(app.url)
end
if h then
local contents = h.readAll()
h:close()
return contents
end
end
local function runApp(app, checkExists, ...)
local path, fn
local args = { ... }
if checkExists and fs.exists(fs.combine(APP_DIR, app.name)) then
path = fs.combine(APP_DIR, app.name)
else
local program = downloadApp(app)
fn = function()
if not program then
error('Failed to download')
end
local fn = loadstring(program, app.name)
if not fn then
error('Failed to download')
end
setfenv(fn, sandboxEnv)
fn(unpack(args))
end
end
multishell.openTab({
title = app.name,
env = sandboxEnv,
path = path,
fn = fn,
focused = true,
})
return true, 'Running program'
end
local installApp = function(app)
local program = downloadApp(app)
if not program then
return false, "Failed to download"
end
local fullPath = fs.combine(APP_DIR, app.name)
Util.writeFile(fullPath, program)
return true, 'Installed as ' .. fullPath
end
local viewApp = function(app)
local program = downloadApp(app)
if not program then
return false, "Failed to download"
end
Util.writeFile('/.source', program)
shell.openForegroundTab('edit /.source')
fs.delete('/.source')
return true
end
local getSourceListing = function(source)
local contents = http.get(source.url)
if contents then
local fn = loadstring(contents.readAll(), source.text)
contents.close()
local env = { std = { } }
setmetatable(env, { __index = _G })
setfenv(fn, env)
fn()
if env.contextualGet then
contextualGet = env.contextualGet
end
source.storeURLs = env.std.storeURLs
source.storeCatagoryNames = env.std.storeCatagoryNames
if source.storeURLs and source.storeCatagoryNames then
for k,v in pairs(source.storeURLs) do
if source.generateName then
v.name = v.title:match('(%w+)')
if not v.name or #v.name == 0 then
v.name = tostring(k)
else
v.name = v.name:lower()
end
else
v.name = k
end
v.categoryName = source.storeCatagoryNames[v.catagory]
v.ltitle = v.title:lower()
end
end
end
end
local appPage = UI.Page {
menuBar = UI.MenuBar {
-- showBackButton = not pocket,
buttons = {
{ text = '\027', event = 'back' },
{ text = 'Install', event = 'install' },
{ text = 'Run', event = 'run' },
{ text = 'View', event = 'view' },
{ text = 'Remove', event = 'uninstall', name = 'removeButton' },
},
},
container = UI.Window {
x = 2, y = 3, ex = -2, ey = -3,
viewport = UI.Viewport(),
},
notification = UI.Notification(),
accelerators = {
q = 'back',
backspace = 'back',
},
}
function appPage.container.viewport:draw()
local app = self.parent.parent.app
local str = string.format(
'%s \nBy: %s \nCategory: %s\nFile name: %s\n\n%s',
Ansi.yellow .. app.title .. Ansi.reset,
app.creator,
app.categoryName, app.name,
Ansi.yellow .. app.description .. Ansi.reset)
self:clear()
self:setCursorPos(1, 1)
self:print(str)
self.ymax = self.cursorY
if appPage.notification.enabled then
appPage.notification:draw()
end
end
function appPage:enable(source, app)
self.source = source
self.app = app
UI.Page.enable(self)
self.container.viewport:setScrollPosition(0)
if fs.exists(fs.combine(APP_DIR, app.name)) then
self.menuBar.removeButton:enable('Remove')
else
self.menuBar.removeButton:disable('Remove')
end
end
function appPage:eventHandler(event)
if event.type == 'back' then
UI:setPreviousPage()
elseif event.type == 'run' then
self.notification:info('Running program', 3)
self:sync()
runApp(self.app, true)
elseif event.type == 'view' then
self.notification:info('Downloading program', 3)
self:sync()
viewApp(self.app)
elseif event.type == 'uninstall' then
if self.app.runOnly then
runApp(self.app, false, 'uninstall')
else
fs.delete(fs.combine(APP_DIR, self.app.name))
self.notification:success("Uninstalled " .. self.app.name, 3)
self:focusFirst(self)
self.menuBar.removeButton:disable('Remove')
self.menuBar:draw()
unregisterApp(self.app.creator .. '.' .. self.app.name)
end
elseif event.type == 'install' then
self.notification:info("Installing", 3)
self:sync()
local s, m
if self.app.runOnly then
s,m = runApp(self.app, false)
else
s,m = installApp(self.app)
end
if s then
self.notification:success(m, 3)
if not self.app.runOnly then
self.menuBar.removeButton:enable('Remove')
self.menuBar:draw()
local category = 'Apps'
if self.app.catagoryName == 'Game' then
category = 'Games'
end
registerApp({
run = fs.combine(APP_DIR, self.app.name),
title = self.app.title,
category = category,
icon = self.app.icon,
}, self.app.creator .. '.' .. self.app.name)
end
else
self.notification:error(m, 3)
end
else
return UI.Page.eventHandler(self, event)
end
return true
end
local categoryPage = UI.Page {
menuBar = UI.MenuBar {
buttons = {
{ text = 'Catalog', dropdown = sources },
{ text = 'Category', name = 'categoryButton', dropdown = { } },
},
},
grid = UI.ScrollingGrid {
y = 2, ey = -2,
columns = {
{ heading = 'Title', key = 'title' },
},
sortColumn = 'title',
},
statusBar = UI.StatusBar(),
accelerators = {
l = 'lua',
q = 'quit',
},
}
function categoryPage:setCategory(source, name, index)
self.grid.values = { }
for _,v in pairs(source.storeURLs) do
if index == 0 or index == v.catagory then
table.insert(self.grid.values, v)
end
end
self.statusBar:setStatus(string.format('%s: %s', source.text, name))
self.grid:update()
self.grid:setIndex(1)
end
function categoryPage:setSource(source)
if not source.categoryMenu then
self.statusBar:setStatus('Loading...')
self.statusBar:draw()
self:sync()
getSourceListing(source)
if not source.storeURLs then
error('Unable to download application list')
end
local buttons = { }
for k,v in Util.spairs(source.storeCatagoryNames,
function(a, b) return a:lower() < b:lower() end) do
if v ~= 'Operating System' then
table.insert(buttons, {
text = v,
event = 'category',
index = k,
})
end
end
source.categoryMenu = UI.DropMenu({
buttons = buttons,
})
source.index, source.name = Util.first(source.storeCatagoryNames)
categoryPage.menuBar.categoryButton:add({
categoryMenu = source.categoryMenu
})
end
self.source = source
self.menuBar.categoryButton.dropmenu = source.categoryMenu
categoryPage:setCategory(source, source.name, source.index)
end
function categoryPage.grid:sortCompare(a, b)
return a.ltitle < b.ltitle
end
function categoryPage.grid:getRowTextColor(row, selected)
if fs.exists(fs.combine(APP_DIR, row.name)) then
return colors.orange
end
return UI.Grid:getRowTextColor(row, selected)
end
function categoryPage:eventHandler(event)
if event.type == 'grid_select' or event.type == 'select' then
UI:setPage(appPage, self.source, self.grid:getSelected())
elseif event.type == 'category' then
self:setCategory(self.source, event.button.text, event.button.index)
self:setFocus(self.grid)
self:draw()
elseif event.type == 'source' then
self:setFocus(self.grid)
self:setSource(event.button)
self:draw()
elseif event.type == 'quit' then
UI:exitPullEvents()
else
return UI.Page.eventHandler(self, event)
end
return true
end
print("Retrieving catalog list")
categoryPage:setSource(sources[1])
UI:setPage(categoryPage)
UI:pullEvents()
UI.term:reset()

205
core/Devices.lua Normal file
View File

@@ -0,0 +1,205 @@
_G.requireInjector(_ENV)
local Ansi = require('ansi')
local Event = require('event')
local UI = require('ui')
local Util = require('util')
local colors = _G.colors
local peripheral = _G.peripheral
--[[ -- PeripheralsPage -- ]] --
local peripheralsPage = UI.Page {
grid = UI.ScrollingGrid {
ey = -2,
columns = {
{ heading = 'Type', key = 'type' },
{ heading = 'Side', key = 'side' },
},
sortColumn = 'type',
autospace = true,
},
statusBar = UI.StatusBar {
values = 'Select peripheral',
},
accelerators = {
q = 'quit',
},
}
function peripheralsPage.grid:draw()
local sides = peripheral.getNames()
Util.clear(self.values)
for _,side in pairs(sides) do
table.insert(self.values, {
type = peripheral.getType(side),
side = side
})
end
self:update()
self:adjustWidth()
UI.Grid.draw(self)
end
function peripheralsPage:updatePeripherals()
if UI:getCurrentPage() == self then
self.grid:draw()
self:sync()
end
end
function peripheralsPage:eventHandler(event)
if event.type == 'quit' then
Event.exitPullEvents()
elseif event.type == 'grid_select' then
UI:setPage('methods', event.selected)
end
return UI.Page.eventHandler(self, event)
end
--[[ -- MethodsPage -- ]] --
local methodsPage = UI.Page {
backgroundColor = colors.black,
doc = UI.TextArea {
backgroundColor = colors.black,
x = 2, y = 2, ex = -1, ey = -7,
},
grid = UI.ScrollingGrid {
y = -6, ey = -2,
columns = {
{ heading = 'Name', key = 'name', width = UI.term.width }
},
sortColumn = 'name',
},
statusBar = UI.StatusBar {
status = 'q to return',
},
accelerators = {
q = 'back',
backspace = 'back',
},
}
function methodsPage:enable(p)
self.peripheral = p or self.peripheral
p = peripheral.wrap(self.peripheral.side)
if p.getDocs then
-- plethora
self.grid.values = { }
for k,v in pairs(p.getDocs()) do
table.insert(self.grid.values, {
name = k,
doc = v,
})
end
elseif not p.getAdvancedMethodsData then
-- computercraft
self.grid.values = { }
for name in pairs(p) do
table.insert(self.grid.values, {
name = name,
noext = true,
})
end
else
-- open peripherals
self.grid.values = p.getAdvancedMethodsData()
for name,f in pairs(self.grid.values) do
f.name = name
end
end
self.grid:update()
self.grid:setIndex(1)
self.doc:setText(self:getDocumentation())
self.statusBar:setStatus(self.peripheral.type)
UI.Page.enable(self)
self:setFocus(self.grid)
end
function methodsPage:eventHandler(event)
if event.type == 'back' then
UI:setPage(peripheralsPage)
return true
elseif event.type == 'grid_focus_row' then
self.doc:setText(self:getDocumentation())
end
return UI.Page.eventHandler(self, event)
end
function methodsPage:getDocumentation()
local method = self.grid:getSelected()
if method.noext then -- computercraft docs
return 'No documentation'
end
if method.doc then -- plethora docs
return Ansi.yellow .. method.doc
end
-- open peripherals docs
local sb = { }
if method.description then
table.insert(sb, method.description .. '\n\n')
end
if method.returnTypes ~= '()' then
table.insert(sb, Ansi.yellow .. method.returnTypes .. ' ')
end
table.insert(sb, Ansi.blue .. method.name .. Ansi.reset .. '(')
for k,arg in ipairs(method.args) do
if arg.optional then
table.insert(sb, Ansi.orange .. string.format('[%s]', arg.name))
else
table.insert(sb, Ansi.green .. arg.name)
end
if k < #method.args then
table.insert(sb, ',')
end
end
table.insert(sb, Ansi.reset .. ')')
Util.filterInplace(method.args, function(a) return #a.description > 0 end)
if #method.args > 0 then
table.insert(sb, '\n\n')
for k,arg in ipairs(method.args) do
if arg.optional then
table.insert(sb, Ansi.orange)
else
table.insert(sb, Ansi.green)
end
table.insert(sb, arg.name .. Ansi.reset .. ': ' .. arg.description)
if k ~= #method.args then
table.insert(sb, '\n\n')
end
end
end
return table.concat(sb)
end
Event.on('peripheral', function()
peripheralsPage:updatePeripherals()
end)
Event.on('peripheral_detach', function()
peripheralsPage:updatePeripherals()
end)
UI:setPage(peripheralsPage)
UI:setPages({
methods = methodsPage,
})
UI:pullEvents()

139
core/Events.lua Normal file
View File

@@ -0,0 +1,139 @@
_G.requireInjector()
local Event = require('event')
local UI = require('ui')
local Util = require('util')
local multishell = _ENV.multishell
local os = _G.os
UI:configure('Events', ...)
local page = UI.Page {
menuBar = UI.MenuBar {
buttons = {
{ text = 'Filter', event = 'filter' },
{ text = 'Reset', event = 'reset' },
{ text = 'Pause ', event = 'toggle', name = 'pauseButton' },
},
},
grid = UI.Grid {
y = 2,
columns = {
{ key = 'event' },
{ key = 'p1' },
{ key = 'p2' },
{ key = 'p3' },
{ key = 'p4' },
{ key = 'p5' },
},
autospace = true,
disableHeader = true,
},
accelerators = {
f = 'filter',
p = 'toggle',
r = 'reset',
c = 'clear',
q = 'quit',
},
filtered = { },
}
function page:eventHandler(event)
if event.type == 'filter' then
local entry = self.grid:getSelected()
self.filtered[entry.event] = true
elseif event.type == 'toggle' then
self.paused = not self.paused
if self.paused then
self.menuBar.pauseButton.text = 'Resume'
else
self.menuBar.pauseButton.text = 'Pause '
end
self.menuBar:draw()
elseif event.type == 'grid_select' then
multishell.openTab({
path = 'sys/apps/Lua.lua',
args = { event.selected },
focused = true,
})
elseif event.type == 'reset' then
self.filtered = { }
self.grid:setValues({ })
self.grid:draw()
if self.paused then
self:emit({ type = 'toggle' })
end
elseif event.type == 'clear' then
self.grid:setValues({ })
self.grid:draw()
elseif event.type == 'quit' then
UI:exitPullEvents()
elseif event.type == 'focus_change' then
if event.focused == self.grid then
if not self.paused then
self:emit({ type = 'toggle' })
end
end
else
return UI.Page.eventHandler(self, event)
end
return true
end
function page.grid:getDisplayValues(row)
row = Util.shallowCopy(row)
local function tovalue(s)
if type(s) == 'table' then
return 'table'
end
return s
end
for k,v in pairs(row) do
row[k] = tovalue(v)
end
return row
end
function page.grid:draw()
self:adjustWidth()
UI.Grid.draw(self)
end
Event.addRoutine(function()
while true do
local e = { os.pullEvent() }
if not page.paused and not page.filtered[e[1]] then
table.insert(page.grid.values, 1, {
event = e[1],
p1 = e[2],
p2 = e[3],
p3 = e[4],
p4 = e[5],
p5 = e[6],
})
if #page.grid.values > page.grid.height then
table.remove(page.grid.values, #page.grid.values)
end
page.grid:update()
page.grid:draw()
page:sync()
end
end
end)
UI:setPage(page)
UI:pullEvents()

256
core/Music.lua Normal file
View File

@@ -0,0 +1,256 @@
_G.requireInjector()
local Event = require('event')
local UI = require('ui')
local colors = _G.colors
local device = _G.device
local turtle = _G.turtle
if not turtle then
error('This program can only be run on a turtle')
end
local radio = device.drive or error('No drive attached')
if radio.side ~= 'top' and radio.side ~= 'bottom' then
error('Disk drive must be above or below turtle')
end
UI:configure('Music', ...)
UI.Button.defaults.backgroundFocusColor = colors.gray
local page = UI.Page({
volume = 15,
stationName = UI.Text({
y = 2,
x = 2,
ex = -14,
height = 3,
backgroundColor = colors.black,
}),
play = UI.Button({
y = -4,
x = 2,
height = 3,
event = 'play',
text = '> / ll',
}),
seek = UI.Button({
y = -4,
x = 12,
height = 3,
event = 'seek',
text = ' >> ',
}),
quiet = UI.Button({
y = -4,
x = -19,
event = 'quiet',
width = 3,
height = 3,
text = '-',
}),
louder = UI.Button({
y = -4,
x = -14,
width = 3,
height = 3,
event = 'louder',
text = '+',
}),
volumeDisplay = UI.Text({
y = 3,
x = -9,
width = 4,
}),
volume1 = UI.Window({
y = -2,
x = -9,
height = 1,
width = 1,
color = colors.white
}),
volume2 = UI.Window({
y = -3,
x = -8,
height = 2,
width = 1,
color = colors.white
}),
volume3 = UI.Window({
y = -4,
x = -7,
height = 3,
width = 1,
color = colors.yellow
}),
volume4 = UI.Window({
y = -5,
x = -6,
height = 4,
width = 1,
color = colors.yellow,
}),
volume5 = UI.Window({
y = -6,
x = -5,
height = 5,
width = 1,
color = colors.orange,
}),
volume6 = UI.Window({
y = -7,
x = -4,
height = 6,
width = 1,
color = colors.orange,
}),
volume7 = UI.Window({
y = -8,
x = -3,
height = 7,
width = 1,
color = colors.red,
}),
volume8 = UI.Window({
y = -9,
x = -2,
height = 8,
width = 1,
color = colors.red,
})
})
page.volumeControls = {
page.volume1, page.volume2,
page.volume3, page.volume4,
page.volume5, page.volume6,
page.volume7, page.volume8,
}
function page:eventHandler(event)
if event.type == 'play' then
self:play(not self.playing)
elseif event.type == 'seek' then
self:seek()
self:play(true)
elseif event.type == 'louder' then
if self.playing then
self:setVolume(self.volume + 1)
end
elseif event.type == 'quiet' then
if self.playing then
self:setVolume(self.volume - 1)
end
end
end
function page:setVolume(volume)
volume = math.min(volume, 15)
volume = math.max(volume, 1)
self.volume = volume
volume = math.ceil(volume / 2)
for i = 1, volume do
self.volumeControls[i].backgroundColor =
self.volumeControls[i].color
end
for i = volume + 1, #self.volumeControls do
self.volumeControls[i].backgroundColor = colors.black
end
for i = 1, #self.volumeControls do
self.volumeControls[i]:clear()
end
local percent = math.ceil(self.volume / 15 * 100)
self.volumeDisplay.value = percent .. '%'
self.volumeDisplay:draw()
end
function page:seek()
local actions = {
top = {
suck = turtle.suckUp,
drop = turtle.dropUp,
},
bottom = {
suck = turtle.suckDown,
drop = turtle.dropDown,
},
}
local slot = turtle.selectOpenSlot()
actions[radio.side].suck()
repeat
slot = slot + 1
if (slot > 16) then
slot = 1
end
until turtle.getItemCount(slot) >= 1
turtle.select(slot)
actions[radio.side].drop()
self:updateStationName()
end
function page:play(onOff)
self.playing = onOff
if self.playing then
if not radio.hasAudio() then
self:seek()
end
self:updateStationName()
radio.playAudio()
Event.addNamedTimer('songTimer', 180, false, function()
if self.playing then
self:seek()
self:play(true)
self:sync()
end
end)
else
radio.stopAudio()
end
end
function page.stationName:draw()
self:clear()
self:centeredWrite(2, self.value)
end
function page:updateStationName()
local title = radio.getAudioTitle()
if title then
self.stationName.value = title
self.stationName:draw()
end
end
Event.onInterval(1, function()
if not page.playing then
if page.stationName.value == '' then
page:updateStationName()
else
page.stationName.value = ''
page.stationName:draw()
end
page:sync()
end
end)
page:play(true)
page:setVolume(page.volume, true)
UI:setPage(page)
turtle.setStatus('Jamming')
UI:pullEvents()
turtle.setStatus('idle')
page:play(false)
UI.term:reset()

567
core/Script.lua Normal file
View File

@@ -0,0 +1,567 @@
_G.requireInjector()
local Config = require('config')
local Event = require('event')
local Socket = require('socket')
local UI = require('ui')
local Util = require('util')
local colors = _G.colors
local fs = _G.fs
local os = _G.os
local shell = _ENV.shell
local GROUPS_PATH = 'usr/groups'
local SCRIPTS_PATH = 'usr/etc/scripts'
UI:configure('script', ...)
local config = {
showGroups = false,
variables = [[{
COMPUTER_ID = os.getComputerID(),
}]],
}
Config.load('script', config)
local width = math.floor(UI.term.width / 2) - 1
if UI.term.width % 2 ~= 0 then
width = width + 1
end
local function processVariables(script)
local fn = loadstring('return ' .. config.variables)
if fn then
local variables = fn()
for k,v in pairs(variables) do
local token = string.format('{%s}', k)
script = script:gsub(token, v)
end
end
return script
end
local function invokeScript(computer, scriptName)
local script = Util.readFile(scriptName)
if not script then
print('Unable to read script file')
end
local socket = Socket.connect(computer.id, 161)
if not socket then
print('Unable to connect to ' .. computer.id)
return
end
script = processVariables(script)
Util.print('Running %s on %s', scriptName, computer.label)
socket:write({ type = 'script', args = script })
--[[
local response = socket:read(2)
if response and response.result then
if type(response.result) == 'table' then
print(textutils.serialize(response.result))
else
print(tostring(response.result))
end
else
printError('No response')
end
--]]
socket:close()
end
local function runScript(computerOrGroup, scriptName)
if computerOrGroup.id then
invokeScript(computerOrGroup, scriptName)
else
local list = computerOrGroup.list
if computerOrGroup.path then
list = Util.readTable(computerOrGroup.path)
end
if list then
for _,computer in pairs(list) do
invokeScript(computer, scriptName)
end
end
end
end
local function getActiveComputers(t)
t = t or { }
Util.clear(t)
for k,computer in pairs(_G.network) do
if computer.active then
t[k] = computer
end
end
return t
end
local function getTurtleList()
local turtles = {
label = 'Turtles',
list = { },
}
for k,computer in pairs(getActiveComputers()) do
if computer.fuel then
turtles.list[k] = computer
end
end
return turtles
end
local args = { ... }
if #args == 2 then
local key = args[1]
local script = args[2]
local target
if tonumber(key) then
target = _G.network[tonumber(key)]
elseif key == 'All' then
target = {
list = Util.shallowCopy(getActiveComputers()),
}
elseif key == 'Localhost' then
target = { id = os.getComputerID() }
elseif key == 'Turtles' then
target = getTurtleList()
else
target = Util.readTable(fs.combine(GROUPS_PATH, key))
end
if not target then
error('Syntax: Script <ID or group> <script>')
end
runScript(target, fs.combine(SCRIPTS_PATH, script))
return
end
local function getListing(t, path)
Util.clear(t)
local files = fs.list(path)
for _,f in pairs(files) do
table.insert(t, { label = f, path = fs.combine(path, f) })
end
end
local mainPage = UI.Page({
backgroundColor = colors.black,
menuBar = UI.MenuBar({
buttons = {
{ text = 'Groups', event = 'groups' },
{ text = 'Scripts', event = 'scripts' },
{ text = 'Toggle', event = 'toggle' },
},
}),
computers = UI.ScrollingGrid({
y = 2,
height = UI.term.height-2,
columns = {
{ heading = 'Label', key = 'label', width = width },
},
width = width,
sortColumn = 'label',
}),
scripts = UI.ScrollingGrid({
columns = {
{ heading = 'Name', key = 'label', width = width },
},
sortColumn = 'label',
height = UI.term.height - 2,
width = width,
x = UI.term.width - width + 1,
y = 2,
}),
statusBar = UI.StatusBar({
columns = {
{ key = 'status' },
{ key = 'fuelF', width = 5 },
{ key = 'distanceF', width = 4 },
},
autospace = true,
}),
accelerators = {
q = 'quit',
},
})
local editorPage = UI.Page({
backgroundColor = colors.black,
menuBar = UI.MenuBar({
showBackButton = true,
buttons = {
{ text = 'Save', event = 'save', help = 'Save this group' },
},
}),
grid1 = UI.ScrollingGrid({
columns = {
{ heading = 'Name', key = 'label', width = width },
},
sortColumn = 'label',
height = UI.term.height - 4,
width = width,
y = 3,
}),
right = UI.Button({
text = '>',
event = 'right',
x = width - 2,
y = 2,
width = 3,
}),
left = UI.Button({
text = '<',
event = 'left',
x = UI.term.width - width + 1,
y = 2,
width = 3,
}),
grid2 = UI.ScrollingGrid({
columns = {
{ heading = 'Name', key = 'label', width = width },
},
sortColumn = 'label',
height = UI.term.height - 4,
width = width,
x = UI.term.width - width + 1,
y = 3,
}),
statusBar = UI.StatusBar(),
accelerators = {
q = 'back',
},
})
local groupsPage = UI.Page({
menuBar = UI.MenuBar({
showBackButton = true,
buttons = {
{ text = 'Add', event = 'add' },
{ text = 'Edit', event = 'edit' },
{ text = 'Delete', event = 'delete' },
},
}),
grid = UI.ScrollingGrid({
y = 2,
height = UI.term.height-2,
columns = {
{ heading = 'Name', key = 'label' },
},
sortColumn = 'label',
autospace = true,
}),
statusBar = UI.StatusBar(),
accelerators = {
q = 'back',
},
})
local scriptsPage = UI.Page({
menuBar = UI.MenuBar({
showBackButton = true,
buttons = {
{ text = 'Add', event = 'add' },
{ text = 'Edit', event = 'edit' },
{ text = 'Delete', event = 'delete' },
},
}),
grid = UI.ScrollingGrid({
y = 2,
height = UI.term.height-2,
columns = {
{ heading = 'Name', key = 'label' },
},
sortColumn = 'label',
autospace = true,
}),
statusBar = UI.StatusBar(),
accelerators = {
a = 'add',
e = 'edit',
delete = 'delete',
q = 'back',
},
})
function editorPage:enable()
self:focusFirst()
local groupPath = fs.combine(GROUPS_PATH, self.groupName)
if fs.exists(groupPath) then
self.grid1.values = Util.readTable(groupPath)
else
Util.clear(self.grid1.values)
end
self.grid1:update()
UI.Page.enable(self)
end
function editorPage.grid2:draw()
getActiveComputers(self.values)
for k in pairs(editorPage.grid1.values) do
self.values[k] = nil
end
self:update()
UI.ScrollingGrid.draw(self)
end
function editorPage:eventHandler(event)
if event.type == 'back' then
UI:setPage(groupsPage)
elseif event.type == 'left' then
local computer = self.grid2:getSelected()
self.grid1.values[computer.id] = computer
self.grid1:update()
self.grid1:draw()
self.grid2:draw()
elseif event.type == 'right' then
local computer = self.grid1:getSelected()
self.grid1.values[computer.id] = nil
self.grid1:update()
self.grid1:draw()
self.grid2:draw()
elseif event.type == 'save' then
Util.writeTable(fs.combine(GROUPS_PATH, self.groupName), self.grid1.values)
UI:setPage(groupsPage)
end
return UI.Page.eventHandler(self, event)
end
local function nameDialog(f)
local dialog = UI.Dialog {
width = 22,
height = 6,
title = 'Enter Name',
form = UI.Form {
x = 2, ex = -2, y = 2,
textEntry = UI.TextEntry { y = 2, width = 20, limit = 20 },
},
}
function dialog:eventHandler(event)
if event.type == 'form_complete' then
local name = self.form.textEntry.value
if name then
f(name)
else
self.statusBar:timedStatus('Invalid Name', 3)
end
return true
elseif event.type == 'form_cancel' or event.type == 'cancel' then
UI:setPreviousPage()
else
return UI.Dialog.eventHandler(self, event)
end
end
dialog:setFocus(dialog.form.textEntry)
UI:setPage(dialog)
end
function groupsPage:draw()
getListing(self.grid.values, GROUPS_PATH)
self.grid:update()
UI.Page.draw(self)
end
function groupsPage:enable()
self:focusFirst()
UI.Page.enable(self)
end
function groupsPage:eventHandler(event)
if event.type == 'back' then
UI:setPage(mainPage)
elseif event.type == 'add' then
nameDialog(function(name)
editorPage.groupName = name
UI:setPage(editorPage)
end)
elseif event.type == 'delete' then
if self.grid:getSelected() then
fs.delete(fs.combine(GROUPS_PATH, self.grid:getSelected().label))
self:draw()
end
elseif event.type == 'edit' then
if self.grid:getSelected() then
editorPage.groupName = self.grid:getSelected().label
UI:setPage(editorPage)
end
end
return UI.Page.eventHandler(self, event)
end
function scriptsPage:draw()
getListing(self.grid.values, SCRIPTS_PATH)
self.grid:update()
UI.Page.draw(self)
end
function scriptsPage:enable()
self:focusFirst()
UI.Page.enable(self)
end
function scriptsPage:eventHandler(event)
if event.type == 'back' then
UI:setPreviousPage()
elseif event.type == 'add' then
nameDialog(function(name)
shell.run('edit ' .. fs.combine(SCRIPTS_PATH, name))
UI:setPreviousPage()
end)
elseif event.type == 'edit' then
local name = fs.combine(SCRIPTS_PATH, self.grid:getSelected().label)
shell.run('edit ' .. name)
self:draw()
elseif event.type == 'delete' then
local name = fs.combine(SCRIPTS_PATH, self.grid:getSelected().label)
fs.delete(name)
self:draw()
end
return UI.Page.eventHandler(self, event)
end
function mainPage:eventHandler(event)
if event.type == 'quit' then
Event.exitPullEvents()
elseif event.type == 'groups' then
UI:setPage(groupsPage)
elseif event.type == 'scripts' then
UI:setPage(scriptsPage)
elseif event.type == 'toggle' then
config.showGroups = not config.showGroups
self:draw()
Config.update('script', config)
elseif event.type == 'grid_focus_row' then
local computer = self.computers:getSelected()
self.statusBar.values = computer
self.statusBar:draw()
elseif event.type == 'grid_select' then
local script = self.scripts:getSelected()
local computer = self.computers:getSelected()
self:clear()
self:sync()
self.enabled = false
runScript(computer, script.path)
print()
print('Press any key to continue...')
while true do
local e = os.pullEvent()
if e == 'char' or e == 'key' or e == 'mouse_click' then
break
end
end
self.enabled = true
self:draw()
end
return UI.Page.eventHandler(self, event)
end
function mainPage.statusBar:draw()
if self.values then
local computer = self.values
if computer then
if computer.fuel then
computer.fuelF = string.format("%dk", math.floor(computer.fuel/1000))
end
if computer.distance then
computer.distanceF = Util.round(computer.distance, 1)
end
end
end
UI.StatusBar.draw(self)
end
function mainPage:draw()
getListing(self.scripts.values, SCRIPTS_PATH)
if config.showGroups then
getListing(self.computers.values, GROUPS_PATH)
table.insert(self.computers.values, {
label = 'All',
list = getActiveComputers(),
})
table.insert(self.computers.values, getTurtleList())
table.insert(self.computers.values, {
label = 'Localhost',
id = os.getComputerID(),
})
else
getActiveComputers(self.computers.values)
end
self.scripts:update()
self.computers:update()
UI.Page.draw(self)
end
if not fs.exists(SCRIPTS_PATH) then
fs.makeDir(SCRIPTS_PATH)
end
if not fs.exists(GROUPS_PATH) then
fs.makeDir(GROUPS_PATH)
end
Event.on('network_attach', function()
if mainPage.enabled then
mainPage:draw()
end
end)
Event.on('network_detach', function()
if mainPage.enabled then
mainPage:draw()
end
end)
Event.onInterval(1, function()
if mainPage.enabled then
local selected = mainPage.computers:getSelected()
if selected then
local computer = _G.network[selected.id]
mainPage.statusBar.values = computer
mainPage.statusBar:draw()
mainPage:sync()
end
end
end)
UI:setPage(mainPage)
UI:pullEvents()
UI.term:reset()

374
core/Turtles.lua Normal file
View File

@@ -0,0 +1,374 @@
_G.requireInjector()
local Config = require('config')
local Event = require('event')
local itemDB = require('itemDB')
local Socket = require('socket')
local Terminal = require('terminal')
local UI = require('ui')
local Util = require('util')
local colors = _G.colors
local fs = _G.fs
local multishell = _ENV.multishell
local network = _G.network
local os = _G.os
local shell = _ENV.shell
local term = _G.term
UI.Button.defaults.focusIndicator = ' '
UI:configure('Turtles', ...)
local config = { }
Config.load('Turtles', config)
local options = {
turtle = { arg = 'i', type = 'number', value = config.id or -1,
desc = 'Turtle ID' },
tab = { arg = 's', type = 'string', value = config.tab or 'Sel',
desc = 'Selected tab to display' },
help = { arg = 'h', type = 'flag', value = false,
desc = 'Displays the options' },
}
local SCRIPTS_PATH = 'usr/etc/scripts'
local nullTerm = Terminal.getNullTerm(term.current())
local socket
local page = UI.Page {
coords = UI.Window {
backgroundColor = colors.black,
height = 4,
},
tabs = UI.Tabs {
x = 1, y = 5, ey = -2,
scripts = UI.Grid {
tabTitle = 'Run',
backgroundColor = UI.TabBar.defaults.selectedBackgroundColor,
columns = {
{ heading = '', key = 'label' },
},
disableHeader = true,
sortColumn = 'label',
autospace = true,
},
turtles = UI.Grid {
tabTitle = 'Select',
backgroundColor = UI.TabBar.defaults.selectedBackgroundColor,
columns = {
{ heading = 'label', key = 'label' },
{ heading = 'Dist', key = 'distance' },
{ heading = 'Status', key = 'status' },
{ heading = 'Fuel', key = 'fuel' },
},
disableHeader = true,
sortColumn = 'label',
autospace = true,
},
inventory = UI.Grid {
backgroundColor = UI.TabBar.defaults.selectedBackgroundColor,
tabTitle = 'Inv',
columns = {
{ heading = '', key = 'index', width = 2 },
{ heading = '', key = 'qty', width = 2 },
{ heading = 'Inventory', key = 'id', width = UI.term.width - 7 },
},
disableHeader = true,
sortColumn = 'index',
},
--[[
policy = UI.Grid {
tabTitle = 'Mod',
backgroundColor = UI.TabBar.defaults.selectedBackgroundColor,
columns = {
{ heading = 'label', key = 'label' },
},
values = policies,
disableHeader = true,
sortColumn = 'label',
autospace = true,
},
]]
action = UI.Window {
tabTitle = 'Action',
backgroundColor = UI.TabBar.defaults.selectedBackgroundColor,
moveUp = UI.Button {
x = 5, y = 2,
text = 'up',
fn = 'turtle.up',
},
moveDown = UI.Button {
x = 5, y = 4,
text = 'dn',
fn = 'turtle.down',
},
moveForward = UI.Button {
x = 9, y = 3,
text = 'f',
fn = 'turtle.forward',
},
moveBack = UI.Button {
x = 2, y = 3,
text = 'b',
fn = 'turtle.back',
},
turnLeft = UI.Button {
x = 2, y = 6,
text = 'lt',
fn = 'turtle.turnLeft',
},
turnRight = UI.Button {
x = 8, y = 6,
text = 'rt',
fn = 'turtle.turnRight',
},
info = UI.TextArea {
x = 2, y = 9,
inactive = true,
}
},
},
statusBar = UI.StatusBar(),
notification = UI.Notification(),
accelerators = {
q = 'quit',
},
}
function page:enable(turtle)
self.turtle = turtle
UI.Page.enable(self)
end
function page:runFunction(script, nowrap)
for _ = 1, 2 do
if not socket then
socket = Socket.connect(self.turtle.id, 161)
end
if socket then
if not nowrap then
script = 'turtle.run(' .. script .. ')'
end
if socket:write({ type = 'scriptEx', args = script }) then
local t = socket:read(3)
if t then
return table.unpack(t)
end
return false, 'Socket timeout'
end
end
socket = nil
end
self.notification:error('Unable to connect')
end
function page:runScript(scriptName)
if self.turtle then
self.notification:info('Connecting')
self:sync()
local cmd = string.format('Script %d %s', self.turtle.id, scriptName)
local ot = term.redirect(nullTerm)
pcall(function() shell.run(cmd) end)
term.redirect(ot)
self.notification:success('Sent')
end
end
function page.coords:draw()
local t = self.parent.turtle
self:clear()
if t then
self:setCursorPos(2, 2)
local ind = 'GPS'
if not t.point.gps then
ind = 'REL'
end
self:print(string.format('%s : %d,%d,%d\n Fuel: %s\n',
ind, t.point.x, t.point.y, t.point.z, Util.toBytes(t.fuel)))
end
end
--[[ Inventory Tab ]]--
function page.tabs.inventory:getRowTextColor(row, selected)
if page.turtle and row.selected then
return colors.yellow
end
return UI.Grid.getRowTextColor(self, row, selected)
end
function page.tabs.inventory:draw()
local t = page.turtle
Util.clear(self.values)
if t then
for _,v in ipairs(t.inventory) do
if v.qty > 0 then
table.insert(self.values, v)
if v.index == t.slotIndex then
v.selected = true
end
if v.id then
v.id = itemDB:getName(v)
end
end
end
end
self:adjustWidth()
self:update()
UI.Grid.draw(self)
end
function page.tabs.inventory:eventHandler(event)
if event.type == 'grid_select' then
local fn = string.format('turtle.select(%d)', event.selected.index)
page:runFunction(fn)
else
return UI.Grid.eventHandler(self, event)
end
return true
end
function page.tabs.scripts:draw()
Util.clear(self.values)
local files = fs.list(SCRIPTS_PATH)
for _,path in pairs(files) do
table.insert(self.values, { label = path, path = fs.combine(SCRIPTS_PATH, path) })
end
self:update()
UI.Grid.draw(self)
end
function page.tabs.scripts:eventHandler(event)
if event.type == 'grid_select' then
page:runScript(event.selected.label)
else
return UI.Grid.eventHandler(self, event)
end
return true
end
function page.tabs.turtles:getDisplayValues(row)
row = Util.shallowCopy(row)
if row.fuel then
row.fuel = Util.toBytes(row.fuel)
end
if row.distance then
row.distance = Util.round(row.distance, 1)
end
return row
end
function page.tabs.turtles:draw()
Util.clear(self.values)
for _,v in pairs(network) do
if v.fuel then
table.insert(self.values, v)
end
end
self:update()
UI.Grid.draw(self)
end
function page.tabs.turtles:eventHandler(event)
if event.type == 'grid_select' then
page.turtle = event.selected
config.id = event.selected.id
Config.update('Turtles', config)
multishell.setTitle(multishell.getCurrent(), page.turtle.label)
if socket then
socket:close()
socket = nil
end
else
return UI.Grid.eventHandler(self, event)
end
return true
end
function page.statusBar:draw()
local t = self.parent.turtle
if t then
local status = string.format('%s [ %s ]', t.status, Util.round(t.distance, 2))
self:setStatus(status, true)
end
UI.StatusBar.draw(self)
end
function page:showBlocks()
local script = [[
local function inspect(direction)
local s,b = turtle['inspect' .. (direction or '')]()
if not s then
return 'minecraft:air:0'
end
return string.format('%s:%d', b.name, b.metadata)
end
local bu, bf, bd = inspect('Up'), inspect(), inspect('Down')
return string.format('%s\n%s\n%s', bu, bf, bd)
]]
local s, m = self:runFunction(script, true)
self.tabs.action.info:setText(s or m)
end
function page:eventHandler(event)
if event.type == 'quit' then
UI:exitPullEvents()
elseif event.type == 'tab_select' then
config.tab = event.button.text
Config.update('Turtles', config)
elseif event.type == 'button_press' then
if event.button.fn then
self:runFunction(event.button.fn, event.button.nowrap)
self:showBlocks()
elseif event.button.script then
self:runScript(event.button.script)
end
else
return UI.Page.eventHandler(self, event)
end
return true
end
function page:enable()
UI.Page.enable(self)
-- self.tabs:activateTab(page.tabs.turtles)
end
if not Util.getOptions(options, { ... }, true) then
return
end
if options.turtle.value >= 0 then
for _ = 1, 10 do
page.turtle = _G.network[options.turtle.value]
if page.turtle then
break
end
os.sleep(1)
end
end
Event.onInterval(1, function()
if page.turtle then
local t = _G.network[page.turtle.id]
page.turtle = t
page:draw()
page:sync()
end
end)
if config.tab then
page.tabs.tabBar:selectTab(config.tab)
end
UI:setPage(page)
UI:pullEvents()

161
core/apis/chestAdapter.lua Normal file
View 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

View 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

View 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

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

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

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

33
core/debug.lua Normal file
View File

@@ -0,0 +1,33 @@
_G.requireInjector(_ENV)
local Util = require('util')
local device = _G.device
local os = _G.os
local term = _G.term
local args = { ... }
local mon = device[args[1] or 'monitor'] or error('Syntax: debug <monitor>')
mon.clear()
mon.setTextScale(.5)
mon.setCursorPos(1, 1)
local oldDebug = _G._debug
_G._debug = function(...)
local oldTerm = term.redirect(mon)
Util.print(...)
term.redirect(oldTerm)
end
repeat
local e, side = os.pullEventRaw('monitor_touch')
if e == 'monitor_touch' and side == mon.side then
mon.clear()
mon.setTextScale(.5)
mon.setCursorPos(1, 1)
end
until e == 'terminate'
_G._debug = oldDebug

1301
core/edit.lua Normal file

File diff suppressed because it is too large Load Diff

194
core/etc/apps/opus-apps.db Normal file
View File

@@ -0,0 +1,194 @@
{
[ "90ef98d4b6fd15466f0a1f212ec1db8d9ebe018c" ] = {
title = "Turtles",
category = "Apps",
icon = " \0305 \030c \0305 \030 \
\030d \030c \0305 \030c \0308 \030d\031f\"\
\0308\031f.\030 \031 \0308\031f.\030 \031 ",
iconExt = "\030 \031f\030f\031f\128\0305\135\030c\031c\128\128\0305\031f\139\0307\149\0308\0317\143\0307\128\
\030 \031f\030c\031f\145\031c\128\030d\132\136\030c\128\0307\149\0318\143\133\
\030 \031f\030f\031f\128\0317\143\031f\128\128\0317\143\031f\128\128\128",
run = "Turtles.lua",
},
[ "381e3298b2b8f6caeb2208b57d805ada38402f0b" ] = {
title = "Scripts",
category = "Apps",
icon = "\0300\0317if\031 \0307 \
\0300\0317turt\
\0300\0317retu",
iconExt = "\0300\0317if\140\140\140\
\0300\0317\140\031fthen\
\0300\0317else\140",
run = "Script.lua",
},
[ "7ef35cac539f84722b0a988caee03b2df734c56a" ] = {
title = "AppStore",
category = "System",
icon = "\030 \0310=\0300 \030 XX\0300\031f \030 \
\030 \031f \0300 \030 \
\030 \031f \0310o \031f \0310o\031f ",
iconExt = "\031e\139\0318\151\151\151\151\151\149\
\030 \031 \0308\031f\136\136\136\136\030f\0318\135\
\030 \031 \030f\0317\130\030 \031 \030f\0317\130",
run = "Appstore.lua",
},
[ "4486006f811b88cacd5f211fd579717e29b600cd" ] = {
title = "Miner",
category = "Apps",
icon = " \0315\\\030 \031 \
\0304\031f _ \030 \031c/\0315\\\
\0304 ",
run = "simpleMiner.lua",
requires = 'turtle',
},
[ "131260cbfbb0c821f8eae5e7c3c296c7aa4d50b9" ] = {
title = "Music",
category = "Apps",
icon = "\030 \031f === \
\030 \031f | |\
\030 \031fo| o|\
",
run = "usr/apps/Music.lua",
requires = 'turtle',
},
--[[
[ "81c0d915fa6d82fd30661c5e66e204cea52bb2b5" ] = {
title = "Activity",
category = "Apps",
icon = "\0318/\030f\031 \030 \0318\\\
\030f \0308\0319o\030f\031 \
\0318\\\030f\031 \030 \0318/",
run = "storageActivity.lua",
},
[ "89307d419a2fe4fbb69af92b3d3af27b6ec14d3e" ] = {
title = "Telnet",
category = "Apps",
icon = " \0314>\0310_\
\031f)))\031 \
\0314>\0310_\031 ",
run = "telnet.lua",
},
[ "8a77613b475e46064321fd7da18d126ee35e5066" ] = {
title = "VNC",
category = "Apps",
icon = "\
\031e\\\031 \031e/\031dn\
\031e\\/\031 \0319c",
run = "vnc.lua",
},
--]]
[ "a0365977708b7387ee9ce2c13e5820e6e11732cb" ] = {
title = "Pain",
category = "Apps",
icon = "\030 \031f\0307\031f\159\030 \159\030 \
\030 \031f\0308\031f\135\0307\0318\144\140\030f\0317\159\143\031c\139\0302\135\030f\0312\157\
\030 \031f\030f\0318\143\133\0312\136\0302\031f\159\159\143\131\030f\0312\132",
run = "http://pastebin.com/raw/wJQ7jav0",
},
[ "9e092dda4f0e27d0c7686ddd00272079e678b6e6" ] = {
title = "Storage",
category = "Apps",
icon = "\0307 \
\0307 \0308\0311 \0305 \0308\031 \0307 \0308 \0301 \
\0307 ",
run = "chestManager.lua",
requires = 'turtle',
},
[ "114edfc04a1ab03541bdc80ce064f66a7cfcedbb" ] = {
title = "Recorder",
category = "Apps",
icon = "\030 \031f \031b \031foo \
\030 \031f \030e\031b \030 \031f/\
\030 \031b \030e \030 \031f\\",
iconExt = "\030 \031f\030f\031f\128\030e\143\030f\031e\144\031f\128\0304\149\0307\0314\131\131\030f\149\
\030 \031f\030e\031f\129\031e\128\128\030f\148\0304\031f\149\0307\0318\140\140\030f\0314\149\
\030 \031f\030f\031e\139\030e\128\030f\159\129\0314\130\131\131\129",
run = "recorder.lua",
},
[ "131f0b008f44298812221d120d982940609be781" ] = {
title = "Builder",
category = "Apps",
icon = "\0317_____\
\030e\031c###\0308\0317=\030e\031c#\
\030e\031c#\0307\031f.\030e\031c###",
run = "usr/apps/builder.lua",
requires = "turtle",
},
[ "d8c298dd41e4a4ec20e8307901797b64688b3b77" ] = {
title = "GPS Deploy",
category = "Apps",
run = "http://pastebin.com/raw/VXAyXqBv",
requires = "turtle",
},
[ "53a5d150062b1e03206b9e15854b81060e3c7552" ] = {
title = "Minesweeper",
category = "Games",
icon = "\030f\031f \03131\0308\031f \030f\031d2\
\030f\031f \031d2\03131\0308\031f \030f\03131\
\030f\03131\0308\031f \030f\03131\031e3",
run = "https://pastebin.com/raw/nsKrHTbN",
},
[ "a2accffe95b2c8be30e8a05e0c6ab7e8f5966f43" ] = {
title = "Strafe",
category = "Games",
icon = "\0308\031f \0300 \0308 \
\0308\031f \0300 \030f \
\0300\031f \030f ",
iconExt = "\0308\0318\128\0300\159\129\0310\128\0308\159\129\0318\128\
\0300\0318\135\0310\128\128\030f\135\0300\031f\143\159\030f\0310\144\
\0300\128\030f\159\129\138\0300\031f\143\149\030f\0310\134",
run = "https://pastebin.com/raw/jyDH7mLH",
},
[ "48d6857f6b2869d031f463b13aa34df47e18c548" ] = {
title = "Breakout",
category = "Games",
icon = "\0301\031f \0309 \030c \030b \030e \030c \0306 \
\030 \031f \
\030 \031f \0300 \0310 ",
iconExt = "\030 \031f\030f\0319\144\030d\031f\159\030b\159\030f\0311\144\031b\144\030c\031f\159\030f\0311\144\
\030 \031f\030f\0311\130\031b\129\0319\130\031e\130\0310\144\031d\129\0316\129\
\030 \031f\030f\0310\136\140\140\030 ",
run = "https://gist.github.com/LDDestroier/c7528d95bc0103545c2a/raw",
},
--[[
[ "d78f28759f255a0db76604ee560b87c4715a0da5" ] = {
title = "Sketch",
category = "Apps",
icon = " \031bskch\
\0303\031f \030d \
\030d\031f ",
iconExt = "\030 \031f\0308\031f\151\0313\140\140\140\030f\0318\148\
\030 \031f\030b\031f\149\0318\130\131\129\030f\031b\149\
\030 \031f\030f\0318\138\0308\031b\130\131\129\030f\0318\133",
run = "http://pastebin.com/raw/Mm5hd97E",
},
]]
[ "58ec8d6e36e346d9f42eb43935652e3e58e2c829" ] = {
title = "Mwm",
category = "Apps",
icon = "\030f\031f \0304 \
\030f\031dshell]\0304\0314 \
\0304\031f ",
iconExt = "\030 \031f\0305\031f\155\030f\128\031d\152\140\030d\031f\151\030f\128\128\0304\0314\128\
\030 \031f\030f\0315\152\129\030d\031f\141\030f\031d\153\030d\031f\149\030f\031d\131\148\0304\0314\128\
\030 \031f\0304\031f\131\131\131\131\131\131\131\030e\0314\131",
run = "mwm.lua usr/config/mwm",
},
[ "8d1b0a73bedc0dc492377c2f6ab880940b97ec6e" ] = {
title = "Treefarm",
icon = "\030 \031f \0305 \030 \030d \030 \
\0305\031f \030d \030 \030d \0305 \030d \
\030 \031f \030c \030 \0304 \030 \030c \030 ",
category = "Apps",
run = "treefarm.lua",
requires = "turtle",
},
[ "01c933b2a36ad8ed2d54089cb2903039046c1216" ] = {
title = "Enchat",
icon = "\030e\031f\151\030f\031e\156\0311\140\0314\140\0315\140\031d\140\031b\140\031a\132\
\030f\0314\128\030e\031f\132\030f\031e\132\0318nchat\
\030f\031e\138\141\0311\140\0314\140\0315\132\0317v\03183\031a\132",
category = "Apps",
run = "https://raw.githubusercontent.com/LDDestroier/enchat/master/enchat3.lua",
},
}

View File

@@ -0,0 +1,592 @@
{
"smooth_sky_stone_block": {
"name": "Sky Stone Block"
},
"fluix_block": {
"name": "Fluix Block"
},
"charger": {
"name": "Charger"
},
"network_tool": {
"name": "Network Tool"
},
"wireless_terminal": {
"name": [
"Wireless Terminal"
]
},
"certus_quartz_cutting_knife": {
"name": "Certus Quartz Cutting Knife"
},
"storage_cell_16k": {
"name": "16k ME Storage Cell"
},
"grindstone": {
"name": "Quartz Grindstone"
},
"material": {
"name": [
"Certus Quartz Crystal",
"Charged Certus Quartz Crystal",
"Certus Quartz Dust",
"Nether Quartz Dust",
"",
"",
"",
"Fluix Crystal",
"Fluix Dust",
"Fluix Pearl",
"Pure Certus Quartz Crystal",
"Pure Nether Quartz Crystal",
"Pure Fluix Crystal",
"Inscriber Calculation Press",
"",
"",
"Printed Calculation Circuit",
"Printed Engineering Circuit",
"Printed Logic Circuit",
"",
"Printed Silicon",
"",
"Logic Processor",
"Calculation Processor",
"Engineering Processor",
"",
"",
"",
"Advanced Card",
"",
"Acceleration Card",
"",
"2³ Spatial Component",
"16³ Spatial Component",
"128³ Spatial Component",
"1k ME Storage Component",
"4k ME Storage Component",
"16k ME Storage Component",
"64k ME Storage Component",
"ME Storage Housing",
"",
"Wireless Receiver",
"Wireless Booster",
"Formation Core",
"Annihilation Core",
"",
"",
"",
"",
"",
"",
"",
"Blank Pattern"
]
},
"controller": {
"name": "ME Controller"
},
"quartz_glass": {
"name": "Quartz Glass"
},
"quartz_growth_accelerator": {
"name": "Crystal Growth Accelerator"
},
"energy_acceptor": {
"name": "Energy Acceptor"
},
"part": {
"name": [
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"ME Glass Cable - Fluix",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"Cable Anchor",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"Quartz Fiber",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"Illuminated Panel",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"ME Storage Bus",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"ME Annihilation Plane",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"ME Pattern Terminal",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"ME Crafting Terminal",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"ME Terminal",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"ME Interface"
]
},
"dense_energy_cell": {
"name": "Dense Energy Cell"
},
"drive": {
"name": "ME Drive"
},
"crafting_unit": {
"name": "Crafting Unit"
},
"interface": {
"name": "ME Interface"
},
"energy_cell": {
"name": "Energy Cell"
},
"wireless_access_point": {
"name": "ME Wireless Access Point"
},
"condenser": {
"name": "Matter Condenser"
},
"nether_quartz_wrench": {
"name": "Nether Quartz Wrench"
},
"crank": {
"name": "Wooden Crank"
},
"molecular_assembler": {
"name": "Molecular Assembler"
},
"chest": {
"name": "ME Chest"
},
"security_station": {
"name": "ME Security Terminal"
},
"sky_stone_block": {
"name": "Sky Stone"
},
"crafting_storage_64k": {
"name": "64k Crafting Storage"
},
"inscriber": {
"name": "Inscriber"
},
"crystal_seed": {
"name": [
"Nether Quartz Seed"
]
}
}

View File

@@ -0,0 +1,20 @@
{
"peripheral": {
"name": [
"Disk Drive",
"",
"",
"",
"Advanced Monitor"
]
},
"advanced_modem": {
"name": "Ender Modem"
},
"pocket_computer": {
"name": [
"",
"Advanced Pocket Computer"
]
}
}

226
core/etc/names/enderio.json Normal file
View File

@@ -0,0 +1,226 @@
{
"blockSolarPanel": {
"name": [
"Photovoltaic Cell",
"Advanced Photovoltaic Cell",
"Vibrant Photovoltaic Cell"
]
},
"blockFusedQuartz": {
"name": [
"Fused Quartz",
"Quite Clear Glass",
"Enlightened Fused Quartz",
"",
"Dark Fused Quartz",
"Dark Clear Glass"
]
},
"blockVacuumChest": {
"name": [
"Vacuum Chest"
]
},
"itemExtractSpeedUpgrade": {
"name": "Item Conduit Speed Upgrade"
},
"blockPainter": {
"name": "Painting Machine"
},
"blockTransceiver": {
"name": "Dimensional Transceiver"
},
"darkSteel_chestplate": {
"name": "Dark Plate"
},
"blockSliceAndSplice": {
"name": "Slice'N'Splice"
},
"itemConduitFacade": {
"name": "Conduit Facade"
},
"itemBasicFilterUpgrade": {
"name": [
"Basic Item Filter",
"Advanced Item Filter",
"Counting Item Filter"
]
},
"blockExperienceObelisk": {
"name": "Experience Obelisk"
},
"itemLiquidConduit": {
"name": [
"Fluid Conduit",
"Pressurized Fluid Conduit",
"Ender Fluid Conduit"
]
},
"blockFarmStation": {
"name": "Farming Station"
},
"blockEndermanSkull": {
"name": [
"Enderman Head",
"",
"Tormented Enderman Head"
]
},
"blockAlloySmelter": {
"name": "Alloy Smelter"
},
"itemRedstoneConduit": {
"name": "Redstone Conduit"
},
"itemPowerConduit": {
"name": [
"Energy Conduit",
"Enhanced Energy Conduit",
"Ender Energy Conduit"
]
},
"itemTravelStaff": {
"name": "Staff of Traveling"
},
"blockSagMill": {
"name": [
"SAG Mill (Configured)"
]
},
"itemItemConduit": {
"name": "Item Conduit"
},
"itemConduitProbe": {
"name": "Conduit Probe"
},
"itemPowderIngot": {
"name": [
"",
"",
"",
"",
"",
"",
"Enderium Base"
]
},
"blockBuffer": {
"name": [
"Item Buffer"
]
},
"blockPoweredSpawner": {
"name": "Powered Spawner"
},
"itemBasicCapacitor": {
"name": [
"Basic Capacitor",
"Double-Layer Capacitor",
"Octadic Capacitor",
"Modified Power Holding Device"
]
},
"blockCapBank": {
"name": [
"",
"Basic Capacitor Bank",
"Capacitor Bank",
"Vibrant Capacitor Bank"
]
},
"blockTank": {
"name": [
"Fluid Tank (Configured)"
]
},
"blockKillerJoe": {
"name": [
"Killer Joe"
]
},
"blockWirelessCharger": {
"name": "Wireless Charger"
},
"darkSteel_sword": {
"name": [
"The Ender"
]
},
"blockVat": {
"name": [
"The Vat"
]
},
"itemBrokenSpawner": {
"name": "Broken Spawner"
},
"blockSoulBinder": {
"name": "Soul Binder"
},
"itemMachinePart": {
"name": [
"Machine Chassis",
"Basic Gear"
]
},
"itemYetaWrench": {
"name": "Yeta Wrench"
},
"itemAlloy": {
"name": [
"Electrical Steel",
"Energetic Alloy",
"Vibrant Alloy",
"Redstone Alloy",
"Conductive Iron",
"Pulsating Iron",
"Dark Steel",
"Soularium"
]
},
"itemXpTransfer": {
"name": "Experience Rod"
},
"itemFrankenSkull": {
"name": [
"Zombie Electrode",
"Z-Logic Controller",
"Frank'N'Zombie",
"Ender Resonator",
"Sentient Ender",
"Skeletal Contractor"
]
},
"blockReservoir": {
"name": "Reservoir"
},
"itemSoulVessel": {
"name": [
"Soul Vial",
"Soul Vial"
]
},
"itemMaterial": {
"name": [
"Silicon",
"Conduit Binder",
"Binder Composite",
"Pulsating Iron Nugget",
"Vibrant Alloy Nugget",
"Pulsating Crystal",
"Vibrant Crystal",
"",
"Ender Crystal",
"Enticing Crystal",
"",
"",
"",
"Grains of the End",
"",
"Precient Crystal",
"",
"",
"Plant clippings and trimmings"
]
}
}

View File

@@ -0,0 +1,35 @@
{
"blockDust": {
"name": "Dust"
},
"blockBarrel1": {
"name": "Stone Barrel"
},
"hammerStone": {
"name": "Stone Hammer"
},
"itemMaterial": {
"name": [
"",
"Porcelain Clay",
"Silkworm",
"Ancient Spores",
"Grass Seeds"
]
},
"blockCrucible": {
"name": [
"Unfired Crucible",
"Crucible"
]
},
"itemMesh": {
"name": [
"",
"String Mesh",
"Flint Stiffened Mesh",
"Iron Stiffened Mesh",
"Diamond Stiffened Mesh"
]
},
}

View File

@@ -0,0 +1,70 @@
{
"resonator": {
"name": "Resonator"
},
"endershard": {
"name": "Ender Shard"
},
"glasscutter": {
"name": "Glass Cutter"
},
"decorativesolid": {
"name": [
"",
"",
"Polished Stone",
"Stoneburnt"
]
},
"passivegenerator": {
"name": [
"",
"",
"",
"Water Mill",
"",
"",
"",
"Manual Mill"
]
},
"pipe": {
"name": "Transfer Pipe"
},
"user": {
"name": "Mechanical User"
},
"trashcan": {
"name": "Trash Can"
},
"ingredients": {
"name": [
"Resonating Redstone Crystal",
"Redstone Gear",
"Eye of Redstone",
"",
"",
"",
"Upgrade Speed",
"",
"",
"Upgrade Base",
"Drop of Evil"
]
},
"drum": {
"name": [
"",
"Iron Drum"
]
},
"grocket": {
"name": [
"",
"",
"Transfer Node (Fluids)",
"",
"Retrieval Node (Fluids)"
]
}
}

View File

@@ -0,0 +1,22 @@
{
"goldDiamondUpgrade": {
"name": "Gold to Diamond Chest Upgrade"
},
"BlockIronChest": {
"name": [
"Iron Chest",
"Gold Chest",
"Diamond Chest",
"",
"",
"Crystal Chest",
"Obsidian Chest"
]
},
"woodIronUpgrade": {
"name": "Wood to Iron Chest Upgrade"
},
"ironGoldUpgrade": {
"name": "Iron to Gold Chest Upgrade"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,37 @@
{
"crafter1": {
"name": "Crafter Tier 1"
},
"modular_storage": {
"name": "Modular Storage"
},
"crafter3": {
"name": "Crafter Tier 3"
},
"machine_frame": {
"name": "Machine Frame"
},
"storage_module": {
"name": [
"Storage Module Tier 1",
"Storage Module Tier 2",
"Storage Module Tier 3",
"",
"",
"",
"Remote Storage Module"
]
},
"crafter2": {
"name": "Crafter Tier 2"
},
"machine_base": {
"name": "Machine Base"
},
"remote_storage": {
"name": "Remote Storage"
},
"timer_block": {
"name": "Timer"
}
}

View File

@@ -0,0 +1,33 @@
{
"compDrawers": {
"name": "Compacting Drawer"
},
"upgradeTemplate": {
"name": "Upgrade Template"
},
"controller": {
"name": "Drawer Controller"
},
"basicDrawers": {
"name": [
"Basic Drawer",
"",
"Basic Drawers 2x2"
]
},
"upgradeVoid": {
"name": "Void Upgrade"
},
"upgradeStorage": {
"name": [
"",
"",
"",
"",
"Storage Upgrade (V)"
]
},
"controllerSlave": {
"name": "Controller Slave"
}
}

View File

@@ -0,0 +1,36 @@
{
"materials": {
"name": [
"Seared Brick",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"Necrotic Bone"
]
},
"pattern": {
"name": "Blank Pattern"
},
"cast": {
"name": "Blank Cast"
},
"casting": {
"name": "Casting Table"
},
"soil": {
"name": "Grout"
}
}

File diff suppressed because it is too large Load Diff

1
core/etc/scripts/abort Normal file
View File

@@ -0,0 +1 @@
turtle.abort(true)

116
core/etc/scripts/follow Normal file
View File

@@ -0,0 +1,116 @@
local os = _G.os
local turtle = _G.turtle
local function follow(id)
_G.requireInjector(_ENV)
local Event = require('event')
local Point = require('point')
local Socket = require('socket')
turtle.setStatus('follow ' .. id)
if not turtle.enableGPS() then
error('turtle: No GPS found')
end
local socket = Socket.connect(id, 161)
if not socket then
error('turtle: Unable to connect to ' .. id)
return
end
local lastPoint
local following = false
Event.on('turtle_follow', function(_, pt)
local pts = {
{ x = pt.x + 2, z = pt.z, y = pt.y },
{ x = pt.x - 2, z = pt.z, y = pt.y },
{ x = pt.x, z = pt.z + 2, y = pt.y },
{ x = pt.x, z = pt.z - 2, y = pt.y },
}
local cpt = Point.closest(turtle.point, pts)
local blocks = { }
local function addBlocks(tpt)
table.insert(blocks, tpt)
local apts = Point.adjacentPoints(tpt)
for _,apt in pairs(apts) do
table.insert(blocks, apt)
end
end
-- don't run into player
addBlocks(pt)
addBlocks({ x = pt.x, z = pt.z, y = pt.y + 1 })
if turtle.pathfind(cpt, { blocks = blocks }) then
turtle.headTowards(pt)
end
following = false
end)
Event.onInterval(.5, function()
local function getRemotePoint()
if not turtle.isAborted() then
if socket:write({ type = 'gps' }) then
return socket:read(3)
end
end
end
-- sometimes gps will fail if moving
local pt, d
for _ = 1, 3 do
pt, d = getRemotePoint()
if pt then
break
end
os.sleep(.5)
end
if not pt or turtle.isAborted() then
error('Did not receive GPS location')
end
if not lastPoint or (lastPoint.x ~= pt.x or lastPoint.y ~= pt.y or lastPoint.z ~= pt.z) then
if following then
turtle.getState().abort = true
while following do
os.sleep(.1)
end
turtle.getState().abort = false
end
-- check if gps is inaccurate (player moving too fast)
if d < Point.distance(turtle.point, pt) + 10 then
lastPoint = Point.copy(pt)
following = true
os.queueEvent('turtle_follow', pt)
end
end
end)
Event.on('turtle_abort', function()
Event.exitPullEvents()
end)
Event.pullEvents()
socket:close()
return true
end
local s, m = turtle.run(function() follow({COMPUTER_ID}) end)
if not s and m then
error(m)
end

3
core/etc/scripts/goHome Normal file
View File

@@ -0,0 +1,3 @@
_G.requireInjector(_ENV)
local Home = require('turtle.home')
turtle.run(Home.go)

29
core/etc/scripts/moveTo Normal file
View File

@@ -0,0 +1,29 @@
turtle.run(function()
_G.requireInjector(_ENV)
local GPS = require('gps')
local Socket = require('socket')
local id = {COMPUTER_ID}
if not turtle.enableGPS() then
error('turtle: No GPS found')
end
local socket = Socket.connect(id, 161)
if not socket then
error('turtle: Unable to connect to ' .. id)
end
socket:write({ type = 'gps' })
local pt = socket:read(3)
if not pt then
error('turtle: No GPS response')
end
if not turtle.pathfind(pt) then
error('Unable to go to location')
end
end)

108
core/etc/scripts/obsidian Normal file
View File

@@ -0,0 +1,108 @@
_G.requireInjector(_ENV)
local Point = require('point')
local Util = require('util')
local os = _G.os
local turtle = _G.turtle
local checkedNodes, nodes
local function addNode(node)
for i = 0, 3 do
local hi = turtle.getHeadingInfo(i)
local testNode = { x = node.x + hi.xd, z = node.z + hi.zd }
local key = table.concat({ testNode.x, testNode.z }, ':')
if not checkedNodes[key] then
nodes[key] = testNode
end
end
end
local function findObsidian()
repeat
local node = { x = turtle.point.x, z = turtle.point.z }
local key = table.concat({ node.x, node.z }, ':')
checkedNodes[key] = true
nodes[key] = nil
local _,b = turtle.inspectDown()
if b and (b.name == 'minecraft:lava' or b.name == 'minecraft:flowing_lava') then
if turtle.select('minecraft:water_bucket') then
while true do
if turtle.up() then
break
end
print('stuck')
end
turtle.placeDown()
os.sleep(2)
turtle.placeDown()
turtle.down()
turtle.select(1)
_, b = turtle.inspectDown()
end
end
if turtle.getItemCount(16) > 0 then
print('Inventory full')
print('Enter to continue...')
_G.read()
end
if b and b.name == 'minecraft:obsidian' then
turtle.digDown()
addNode(node)
else
turtle.digDown()
end
print(string.format('%d nodes remaining', Util.size(nodes)))
if Util.size(nodes) == 0 then
break
end
node = Point.closest(turtle.point, nodes)
if not turtle._goto(node) then
break
end
until turtle.isAborted()
end
turtle.run(function()
turtle.reset()
turtle.setPolicy(turtle.policies.digOnly)
local s, m = pcall(function()
repeat
checkedNodes = { }
nodes = { }
local _,b = turtle.inspectDown()
if not b or b.name ~= 'minecraft:obsidian' then
break
end
findObsidian()
if not turtle.select('minecraft:water_bucket') then
break
end
turtle._goto({ x = 0, z = 0 })
turtle.placeDown()
os.sleep(2)
turtle.placeDown()
turtle.down()
turtle.select(1)
until turtle.isAborted()
end)
if not s and m then
error(m)
end
turtle._goto({ x = 0, y = 0, z = 0, heading = 0 })
end)

1
core/etc/scripts/reboot Normal file
View File

@@ -0,0 +1 @@
os.reboot()

3
core/etc/scripts/setHome Normal file
View File

@@ -0,0 +1,3 @@
_G.requireInjector(_ENV)
local Home = require('turtle.home')
turtle.run(Home.set)

View File

@@ -0,0 +1 @@
os.shutdown()

74
core/etc/scripts/summon Normal file
View File

@@ -0,0 +1,74 @@
local function summon(id)
_G.requireInjector(_ENV)
local GPS = require('gps')
local Point = require('point')
local Socket = require('socket')
turtle.setStatus('GPSing')
turtle.setPoint({ x = 0, y = 0, z = 0, heading = 0 })
local pts = {
[ 1 ] = { x = 0, z = 0, y = 0 },
[ 2 ] = { x = 4, z = 0, y = 0 },
[ 3 ] = { x = 2, z = -2, y = 2 },
[ 4 ] = { x = 2, z = 2, y = 2 },
}
local tFixes = { }
local socket = Socket.connect(id, 161)
if not socket then
error('turtle: Unable to connect to ' .. id)
end
local function getDistance()
socket:write({ type = 'ping' })
local _, d = socket:read(5)
return d
end
local function doGPS()
tFixes = { }
for i = 1, 4 do
if not turtle._goto(pts[i]) then
error('turtle: Unable to perform GPS maneuver')
end
local distance = getDistance()
if not distance then
error('turtle: No response from ' .. id)
end
table.insert(tFixes, {
position = vector.new(turtle.point.x, turtle.point.y, turtle.point.z),
distance = distance
})
end
return true
end
if not doGPS() then
turtle.turnAround()
turtle.setPoint({ x = 0, y = 0, z = 0, heading = 0})
if not doGPS() then
socket:close()
return false
end
end
socket:close()
local pos = GPS.trilaterate(tFixes)
if pos then
local pt = { x = pos.x, y = pos.y, z = pos.z }
local _, h = Point.calculateMoves(turtle.getPoint(), pt)
local hi = turtle.getHeadingInfo(h)
turtle.setStatus('recalling')
turtle.pathfind({ x = pt.x - hi.xd, z = pt.z - hi.zd, y = pt.y - hi.yd, heading = h })
else
error("turtle: Could not determine position")
end
end
turtle.run(function() summon({COMPUTER_ID}) end)

126
core/logMonitor.lua Normal file
View File

@@ -0,0 +1,126 @@
requireInjector(getfenv(1))
local Event = require('event')
local Message = require('message')
local UI = require('ui')
local Util = require('util')
multishell.setTitle(multishell.getCurrent(), 'Log Monitor')
if not device.wireless_modem then
error('Wireless modem is required')
end
device.wireless_modem.open(59998)
local ids = { }
local messages = { }
local terminal = UI.term
if device.openperipheral_bridge then
UI.Glasses = require('glasses')
terminal = UI.Glasses({
x = 4,
y = 175,
height = 40,
width = 64,
textScale = .5,
backgroundOpacity = .65,
})
elseif device.monitor then
terminal = UI.Device({
deviceType = 'monitor',
textScale = .5
})
end
--[[-- ScrollingText --]]--
UI.ScrollingText = class(UI.Window)
UI.ScrollingText.defaults = {
UIElement = 'ScrollingText',
backgroundColor = colors.black,
buffer = { },
}
function UI.ScrollingText:appendLine(text)
if #self.buffer+1 >= self.height then
table.remove(self.buffer, 1)
end
table.insert(self.buffer, text)
end
function UI.ScrollingText:clear()
self.buffer = { }
UI.Window.clear(self)
end
function UI.ScrollingText:draw()
for k,text in ipairs(self.buffer) do
self:write(1, k, Util.widthify(text, self.width), self.backgroundColor)
end
end
terminal:clear()
function getClient(id)
if not ids[id] then
ids[id] = {
titleBar = UI.TitleBar({ title = 'ID: ' .. id, parent = terminal }),
scrollingText = UI.ScrollingText({ parent = terminal })
}
local clientCount = Util.size(ids)
local clientHeight = math.floor((terminal.height - clientCount) / clientCount)
terminal:clear()
local y = 1
for k,v in pairs(ids) do
v.titleBar.y = y
y = y + 1
v.scrollingText.height = clientHeight
v.scrollingText.y = y
y = y + clientHeight
v.scrollingText:clear()
v.titleBar:draw()
v.scrollingText:draw()
end
end
return ids[id]
end
Event.on('logMessage', function()
local t = { }
while #messages > 0 do
local msg = messages[1]
table.remove(messages, 1)
local client = getClient(msg.id)
client.scrollingText:appendLine(string.format('%d %s', math.floor(os.clock()), msg.text))
t[msg.id] = client
end
for _,client in pairs(t) do
client.scrollingText:draw()
end
terminal:sync()
end)
Message.addHandler('log', function(h, id, msg)
table.insert(messages, { id = id, text = msg.contents })
os.queueEvent('logMessage')
end)
Event.on('monitor_touch', function()
terminal:reset()
ids = { }
end)
Event.on('mouse_click', function()
terminal:reset()
ids = { }
end)
Event.on('char', function()
Event.exitPullEvents()
end)
Event.pullEvents(logWriter)
terminal:reset()

52
core/mirror.lua Normal file
View File

@@ -0,0 +1,52 @@
_G.requireInjector()
local Terminal = require('terminal')
local Util = require('util')
local shell = _ENV.shell
local term = _G.term
local options = {
scale = { arg = 's', type = 'flag', value = false,
desc = 'Set monitor to .5 text scaling' },
resize = { arg = 'r', type = 'flag', value = false,
desc = 'Resize terminal to monitor size' },
execute = { arg = 'e', type = 'string',
desc = 'Execute a program' },
monitor = { arg = 'm', type = 'string', value = 'monitor',
desc = 'Name of monitor' },
help = { arg = 'h', type = 'flag', value = false,
desc = 'Displays the options' },
}
local args = { ... }
if not Util.getOptions(options, args) then
return
end
local mon = _G.device[options.monitor.value]
if not mon then
error('mirror: Invalid device')
end
mon.clear()
if options.scale.value then
mon.setTextScale(.5)
end
mon.setCursorPos(1, 1)
local oterm = Terminal.copy(term.current())
Terminal.mirror(term.current(), mon)
if options.resize.value then
term.current().getSize = mon.getSize
end
if options.execute.value then
-- TODO: allow args to be passed
shell.run(options.execute.value) -- unpack(args))
Terminal.copy(oterm, term.current())
mon.setCursorBlink(false)
end

88
core/mirrorClient.lua Normal file
View File

@@ -0,0 +1,88 @@
_G.requireInjector()
local Event = require('event')
local Logger = require('logger')
local Socket = require('socket')
local Util = require('util')
local multishell = _ENV.multishell
local os = _G.os
Logger.setScreenLogging()
local remoteId
local args = { ... }
if #args == 1 then
remoteId = tonumber(args[1])
else
print('Enter host ID')
remoteId = tonumber(_G.read())
end
if not remoteId then
error('Syntax: mirrorClient <host ID>')
end
local function wrapTerm(socket)
local methods = { 'blit', 'clear', 'clearLine', 'setCursorPos', 'write',
'setTextColor', 'setTextColour', 'setBackgroundColor',
'setBackgroundColour', 'scroll', 'setCursorBlink', }
socket.term = multishell.term
socket.oldTerm = Util.shallowCopy(socket.term)
for _,k in pairs(methods) do
socket.term[k] = function(...)
if not socket.queue then
socket.queue = { }
Event.onTimeout(0, function()
if socket.queue then
socket:write(socket.queue)
socket.queue = nil
end
end)
end
table.insert(socket.queue, {
f = k,
args = { ... },
})
socket.oldTerm[k](...)
end
end
end
while true do
print('connecting...')
local socket
while true do
socket = Socket.connect(remoteId, 5901)
if socket then
break
end
os.sleep(3)
end
print('connected')
wrapTerm(socket)
os.queueEvent('term_resize')
while true do
local e = Event.pullEvent()
if e[1] == 'terminate' then
break
end
if not socket.connected then
break
end
end
for k,v in pairs(socket.oldTerm) do
socket.term[k] = v
end
socket:close()
end

52
core/mirrorHost.lua Normal file
View File

@@ -0,0 +1,52 @@
_G.requireInjector()
local Event = require('event')
local Logger = require('logger')
local Socket = require('socket')
local colors = _G.colors
local term = _G.term
Logger.setScreenLogging()
local mon = term.current()
local args = { ... }
if args[1] then
mon = _G.device[args[1]]
end
if not mon then
error('Invalid monitor')
end
mon.setBackgroundColor(colors.black)
mon.clear()
while true do
local socket = Socket.server(5901)
print('mirror: connection from ' .. socket.dhost)
Event.addRoutine(function()
while true do
local data = socket:read()
if not data then
break
end
for _,v in ipairs(data) do
mon[v.f](unpack(v.args))
end
end
end)
while true do
Event.pullEvent()
if not socket.connected then
break
end
end
print('connection lost')
socket:close()
end

538
core/mwm.lua Normal file
View File

@@ -0,0 +1,538 @@
local injector = _G.requireInjector or load(http.get('https://raw.githubusercontent.com/kepler155c/opus/develop/sys/apis/injector.lua').readAll())()
injector()
local Canvas = require('ui.canvas')
local Util = require('util')
local colors = _G.colors
local os = _G.os
local peripheral = _G.peripheral
local printError = _G.printError
local shell = _ENV.shell
local term = _G.term
local window = _G.window
local function syntax()
printError('Syntax:')
error('mwm sessionName [monitor]')
end
local args = { ... }
local UID = 0
local multishell = { }
local processes = { }
local parentTerm = term.current()
local sessionFile = args[1] or 'usr/config/mwm'
local running
local monitor
local defaultEnv = Util.shallowCopy(_ENV)
defaultEnv.multishell = multishell
if args[3] then
monitor = _G.device[args[3]]
elseif args[2] then
monitor = peripheral.wrap(args[2]) or syntax()
else
monitor = peripheral.find('monitor') or syntax()
end
monitor.setTextScale(.5)
monitor.clear()
local monDim, termDim = { }, { }
monDim.width, monDim.height = monitor.getSize()
termDim.width, termDim.height = parentTerm.getSize()
local function nextUID()
UID = UID + 1
return UID
end
local function write(win, x, y, text)
win.setCursorPos(x, y)
win.write(text)
end
local function redraw()
monitor.clear()
for k,process in ipairs(processes) do
process.container.canvas:dirty()
process:focus(k == #processes)
end
end
local function getProcessAt(x, y)
for k = #processes, 1, -1 do
local process = processes[k]
if x >= process.x and
y >= process.y and
x <= process.x + process.width - 1 and
y <= process.y + process.height - 1 then
return k, process
end
end
end
--[[ A runnable process ]]--
local Process = { }
function Process:new(args)
args.env = args.env or Util.shallowCopy(defaultEnv)
args.width = args.width or termDim.width
args.height = args.height or termDim.height
local self = setmetatable({
uid = nextUID(),
x = args.x or 1,
y = args.y or 1,
width = args.width + 2,
height = args.height + 3,
path = args.path,
args = args.args or { },
title = args.title or 'shell',
}, { __index = Process })
self:adjustDimensions()
self.container = window.create(monitor, self.x, self.y, self.width, self.height, false)
self.window = window.create(self.container, 2, 3, args.width, args.height, true)
self.terminal = self.window
Canvas.convertWindow(self.container, monitor, self.x, self.y)
self.co = coroutine.create(function()
local result, err
if args.fn then
result, err = Util.runFunction(args.env, args.fn, table.unpack(self.args))
elseif args.path then
result, err = Util.run(args.env, args.path, table.unpack(self.args))
end
if not result and err and err ~= 'Terminated' then
printError('\n' .. tostring(err))
os.pullEventRaw('terminate')
end
multishell.removeProcess(self)
end)
self:focus(false)
return self
end
function Process:focus(focused)
if focused then
self.container.setBackgroundColor(colors.yellow)
else
self.container.setBackgroundColor(colors.gray)
end
self.container.setTextColor(colors.black)
write(self.container, 2, 2, string.rep(' ', self.width - 2))
write(self.container, 3, 2, self.title)
write(self.container, self.width - 2, 2, '*')
if focused then
self.window.restoreCursor()
elseif self.showSizers then
self:drawSizers(false)
end
end
function Process:drawSizers(showSizers)
local sizeChars = {
'\135', '\139', '\141', '\142'
}
if Util.getVersion() < 1.8 then
sizeChars = {
'#', '#', '#', '#'
}
end
self.showSizers = showSizers
self.container.setBackgroundColor(colors.black)
self.container.setTextColor(colors.white)
if self.showSizers then
write(self.container, 1, 1, sizeChars[1])
write(self.container, self.width, 1, sizeChars[2])
write(self.container, 1, self.height, sizeChars[3])
write(self.container, self.width, self.height, sizeChars[4])
self.container.setTextColor(colors.yellow)
write(self.container, 1, 3, '+')
write(self.container, 1, 5, '-')
write(self.container, 3, 1, '+')
write(self.container, 5, 1, '-')
local str = string.format('%d x %d', self.width - 2, self.height - 3)
write(self.container, (self.width - #str) / 2, 1, str)
else
write(self.container, 1, 1, string.rep(' ', self.width))
write(self.container, self.width, 1, ' ')
write(self.container, 1, self.height, ' ')
write(self.container, self.width, self.height, ' ')
write(self.container, 1, 3, ' ')
write(self.container, 1, 5, ' ')
end
end
function Process:adjustDimensions()
self.width = math.min(self.width, monDim.width)
self.height = math.min(self.height, monDim.height)
self.x = math.max(1, self.x)
self.y = math.max(1, self.y)
self.x = math.min(self.x, monDim.width - self.width + 1)
self.y = math.min(self.y, monDim.height - self.height + 1)
end
function Process:reposition()
self:adjustDimensions()
self.container.reposition(self.x, self.y, self.width, self.height)
self.container.setBackgroundColor(colors.black)
self.container.clear()
self.window.reposition(2, 3, self.width - 2, self.height - 3)
redraw()
end
function Process:click(x, y)
if y == 2 then -- title bar
if x == self.width - 2 then
self:resume('terminate')
else
self:drawSizers(not self.showSizers)
end
elseif x == 1 or y == 1 then -- sizers
self:resizeClick(x, y)
elseif x > 1 and x < self.width then
if self.showSizers then
self:drawSizers(false)
end
self:resume('mouse_click', 1, x - 1, y - 2)
self:resume('mouse_up', 1, x - 1, y - 2)
end
end
function Process:resizeClick(x, y)
if x == 1 and y == 3 then
self.height = self.height + 1
elseif x == 1 and y == 5 then
self.height = self.height - 1
elseif x == 3 and y == 1 then
self.width = self.width + 1
elseif x == 5 and y == 1 then
self.width = self.width - 1
else
return
end
self:reposition()
self:resume('term_resize')
self:drawSizers(true)
multishell.saveSession(sessionFile)
end
function Process:resume(event, ...)
if coroutine.status(self.co) == 'dead' then
return
end
if not self.filter or self.filter == event or event == "terminate" then
term.redirect(self.terminal)
local previous = running
running = self -- stupid shell set title
local ok, result = coroutine.resume(self.co, event, ...)
running = previous
self.terminal = term.current()
if ok then
self.filter = result
else
printError(result)
end
return ok, result
end
end
--[[ Install a multishell manager for the monitor ]]--
function multishell.getFocus()
return processes[#processes].uid
end
function multishell.setFocus(uid)
local process = Util.find(processes, 'uid', uid)
if process then
local lastFocused = processes[#processes]
if lastFocused ~= process then
if lastFocused then
lastFocused:focus(false)
end
Util.removeByValue(processes, process)
table.insert(processes, process)
multishell.restack()
process:focus(true)
process.container.canvas:dirty()
end
return true
end
return false
end
function multishell.getTitle(uid)
local process = Util.find(processes, 'uid', uid)
if process then
return process.title
end
end
function multishell.setTitle(uid, title)
local process = Util.find(processes, 'uid', uid)
if process then
process.title = title or ''
process:focus(process == processes[#processes])
end
end
function multishell.getCurrent()
if running then
return running.uid
end
end
function multishell.getCount()
return #processes
end
function multishell.getTabs()
return processes
end
function multishell.launch(env, file, ...)
return multishell.openTab({
path = file,
env = env,
title = 'shell',
args = { ... },
})
end
function multishell.openTab(tabInfo)
local process = Process:new(tabInfo)
table.insert(processes, 1, process)
multishell.restack()
process.container.setVisible(true)
local previousTerm = term.current()
process:resume()
term.redirect(previousTerm)
multishell.saveSession(sessionFile)
return process.uid
end
function multishell.restack() -- reset the stacking order
for k,v in ipairs(processes) do
v.container.canvas.layers = { }
for l = k + 1, #processes do
table.insert(v.container.canvas.layers, processes[l].container.canvas)
end
end
end
function multishell.removeProcess(process)
Util.removeByValue(processes, process)
multishell.restack()
multishell.saveSession(sessionFile)
redraw()
end
function multishell.saveSession(filename)
local t = { }
for _,process in pairs(processes) do
if process.path and not process.isShell then
table.insert(t, {
x = process.x,
y = process.y,
width = process.width - 2,
height = process.height - 3,
path = process.path,
args = process.args,
})
end
end
Util.writeTable(filename, t)
end
function multishell.loadSession(filename)
local config = Util.readTable(filename)
if config then
for _,v in pairs(config) do
multishell.openTab(v)
end
end
end
function multishell.stop()
multishell._stop = true
end
function multishell.start()
while not multishell._stop do
local event = { os.pullEventRaw() }
if event[1] == 'terminate' then
local focused = processes[#processes]
if focused.isShell then
focused:resume('terminate')
else
break
end
elseif event[1] == 'monitor_touch' then
local x, y = event[3], event[4]
local key, process = getProcessAt(x, y)
if process then
if key ~= #processes then
multishell.setFocus(process.uid)
end
process:click(x - process.x + 1, y - process.y + 1)
else
process = processes[#processes]
if process and process.showSizers then
process.x = math.floor(x - (process.width) / 2)
process.y = y
process:reposition()
process:drawSizers(true)
multishell.saveSession(sessionFile)
end
end
elseif event[1] == 'mouse_click' or
event[1] == 'mouse_up' then
local focused = processes[#processes]
if not focused.isShell then
multishell.setFocus(1) -- shell is always 1
else
focused:resume(unpack(event))
end
elseif event[1] == 'char' or
event[1] == 'key' or
event[1] == 'key_up' or
event[1] == 'paste' then
local focused = processes[#processes]
if focused then
focused:resume(unpack(event))
end
else
for _,process in pairs(Util.shallowCopy(processes)) do
process:resume(unpack(event))
end
end
local didRedraw
for _,process in pairs(processes) do
if process.container.canvas:isDirty() then
process.container.canvas:redraw(monitor)
didRedraw = true
end
end
local focused = processes[#processes]
if didRedraw and focused then
--focused.container.canvas:dirty()
--focused.container.canvas:redraw(parentTerm)
focused.window.restoreCursor()
local cx, cy = focused.container.getCursorPos()
monitor.setCursorPos(
focused.container.canvas.x + cx - 1,
focused.container.canvas.y + cy - 1)
end
end
end
--[[ Special shell process for launching programs ]]--
local function addShell()
local process = setmetatable({
x = monDim.width,
y = monDim.height,
width = 1,
height = 1,
isShell = true,
uid = nextUID(),
title = 'Terminal',
}, { __index = Process })
function process:focus(focused)
self.window.setVisible(focused)
if focused then
self.window.restoreCursor()
else
parentTerm.clear()
parentTerm.setCursorBlink(false)
local str = 'Click screen for shell'
write(parentTerm,
math.floor((termDim.width - #str) / 2),
math.floor(termDim.height / 2),
str)
end
end
function process:click()
end
process.container = window.create(monitor, process.x, process.y+1, process.width, process.height, true)
process.window = window.create(parentTerm, 1, 1, termDim.width, termDim.height, true)
process.terminal = process.window
Canvas.convertWindow(process.container, monitor, process.x, process.y)
process.co = coroutine.create(function()
print('To run a program on the monitor, type "fg <program>"')
print('To quit, type "exit"')
os.run(Util.shallowCopy(defaultEnv), shell.resolveProgram('shell'))
multishell.stop()
end)
table.insert(processes, process)
process:focus(true)
local previousTerm = term.current()
process:resume()
term.redirect(previousTerm)
end
addShell()
multishell.loadSession(sessionFile)
multishell.start()
term.redirect(parentTerm)
parentTerm.clear()
parentTerm.setCursorPos(1, 1)

50
core/namedb.lua Normal file
View File

@@ -0,0 +1,50 @@
_G.requireInjector()
local itemDB = require('itemDB')
local json = require('json')
local Util = require('util')
local args = { ... }
local mod = args[1] or error('Syntax: namedb MOD')
--[[
"double_plant": {
"name": ["Sunflower",
"Lilac",
"Double Tallgrass",
"Large Fern",
"Rose Bush",
"Peony"],
},
--]]
local list = { }
for _,v in pairs(itemDB.data) do
local t = Util.split(v.name, '(.-):')
if t[1] == mod then
local name = t[2]
local damage = v.damage or 0
local entry = list[name]
if not entry then
entry = { }
list[name] = entry
end
if not entry.name and damage == 0 then
entry.name = v.displayName
else
if not entry.name then
entry.name = { }
elseif type(entry.name) == 'string' then
entry.name = { entry.name }
end
while #entry.name < damage do
entry.name[#entry.name + 1] = ''
end
entry.name[damage + 1] = v.displayName
end
end
end
json.encodeToFile(string.format('usr/etc/names/%s.json', mod), list)

22
core/persist.lua Normal file
View File

@@ -0,0 +1,22 @@
-- saves a virtual file to disk
_G.requireInjector(_ENV)
local Util = require('util')
local fs = _G.fs
local shell = _ENV.shell
local args = { ... }
local fileName = args[1] and
shell.resolve(args[1]) or
error('Syntax: persist <file name>')
local c = Util.readFile(fileName) or error('Unable to read file')
-- ensure it is writable - if not an error is thrown
Util.writeFile(fileName, '')
fs.delete(fileName)
Util.writeFile(fileName, c)
print('Saved')

548
core/recorder.lua Normal file
View File

@@ -0,0 +1,548 @@
-- +---------------------+------------+---------------------+
-- | | | |
-- | | RecGif | |
-- | | | |
-- +---------------------+------------+---------------------+
local version = "Version 1.1.6"
-- Records your terminal and saves the result as an animating GIF.
-- http://www.computercraft.info/forums2/index.php?/topic/24840-recgif/
-- ----------------------------------------------------------
-- Original code by Bomb Bloke
-- Modified to integrate with opus os
_G.requireInjector()
local Util = require('util')
local multishell = _ENV.multishell
local os = _G.os
local recTerm, oldTerm, arg, showInput, skipLast, lastDelay, curInput = {}, Util.shallowCopy(multishell.term), {...}, false, false, 2, ""
local curBlink, oldBlink, tTerm, buffer, colourNum, xPos, yPos, oldXPos, oldYPos, tCol, bCol, xSize, ySize = false, false, {}, {}, {}, 1, 1, 1, 1, colours.white, colours.black, oldTerm.getSize()
local greys, buttons = {["0"] = true, ["7"] = true, ["8"] = true, ["f"] = true}, {"l", "r", "m"}
local charW, charH, chars, resp
local calls = { }
local curCalls = { delay = 0 }
local callListCount = 0
local callCount = 0
local function showSyntax()
print('Gif Recorder by Bomb Bloke\n')
print('Syntax: recGif [-i] [-s] [-ld:<delay>] filename')
print(' -i : show input')
print(' -s : skip last')
print(' -ld : last delay')
end
for i = #arg, 1, -1 do
local curArg = arg[i]:lower()
if curArg == "-i" then
showInput, ySize = true, ySize + 1
table.remove(arg, i)
elseif curArg == "-s" then
skipLast = true
table.remove(arg, i)
elseif curArg:sub(1, 4) == "-ld:" then
curArg = tonumber(curArg:sub(5))
if curArg then lastDelay = curArg end
table.remove(arg, i)
elseif curArg == '-?' then
showSyntax()
return
elseif i ~= #arg then
showSyntax()
printError('\nInvalid argument')
return
end
end
print('Gif Recorder by Bomb Bloke\n')
print('Press control-p to stop recording')
local filename = arg[#arg]
if not filename then
print('Enter file name:')
filename = read()
end
if #filename == 0 then
showSyntax()
print()
error('Invalid file name')
end
print('Initializing...')
-- don't pollute global env
-- convert these to require style apis
local function loadAPI(url, env)
local apiEnv = Util.shallowCopy(env)
apiEnv.shell = nil
apiEnv.multishell = nil
setmetatable(apiEnv, { __index = _G })
local fn, m = Util.loadUrl(url, apiEnv)
if not fn then
error(m)
end
fn()
return apiEnv
end
bbpack = loadAPI('http://pastebin.com/raw/PdrJjb5S', getfenv(1))
GIF = loadAPI('http://pastebin.com/raw/5uk9uRjC', getfenv(1))
local s, m = Util.runUrl(getfenv(1), 'http://pastebin.com/raw/cUYTGbpb', 'get', 'CnLzL5fg')
if not s then
error(m)
end
-- 'Y0eLUPtr')
-- CnLzL5fg
local function snooze()
local myEvent = tostring({})
os.queueEvent(myEvent)
os.pullEvent(myEvent)
end
local function safeString(text)
local newText = {}
for i = 1, #text do
local val = text:byte(i)
newText[i] = (val > 31 and val < 127) and val or 63
end
return string.char(unpack(newText))
end
local function safeCol(text, subst)
local newText = {}
for i = 1, #text do
local val = text:sub(i, i)
newText[i] = greys[val] and val or subst
end
return table.concat(newText)
end
-- Build a terminal that records stuff:
recTerm = multishell.term
for key, func in pairs(oldTerm) do
recTerm[key] = function(...)
local result = { func(...) }
if callCount == 0 then
os.queueEvent('capture_frame')
end
callCount = callCount + 1
curCalls[callCount] = { key, ... }
return unpack(result)
end
end
local tabId = multishell.getCurrent()
_G.device.keyboard.addHotkey('control-p', function()
os.queueEvent('recorder_stop')
end)
local tabs = multishell.getTabs()
for _,tab in pairs(tabs) do
if tab.isOverview then
multishell.hideTab(tabId)
multishell.setFocus(tab.tabId)
os.queueEvent('term_resize')
break
end
end
local curTime = os.clock() - 1
while true do
local event = { os.pullEventRaw() }
if event[1] == 'recorder_stop' or event[1] == 'terminate' then
break
end
if event[1] == 'capture_frame' then
local newTime = os.clock()
if callListCount > 0 then
calls[callListCount].delay = (newTime - curTime)
end
curTime = newTime
callListCount = callListCount + 1
calls[callListCount] = curCalls
curCalls, callCount = { delay = 0 }, 0
end
end
_G.device.keyboard.removeHotkey('control-p')
for k,fn in pairs(oldTerm) do
multishell.term[k] = fn
end
multishell.unhideTab(tabId)
multishell.setFocus(tabId)
if #calls[#calls] == 0 then calls[#calls] = nil end
if skipLast and #calls > 1 then calls[#calls] = nil end
calls[#calls].delay = lastDelay
print(string.format("Encoding %d frames...", #calls))
--Util.writeTable('tmp/raw.txt', calls)
-- Perform a quick re-parse of the recorded data (adding frames for when the cursor blinks):
do
local callListCount, tempCalls, blink, oldBlink, curBlink, blinkDelay = 1, {}, false, false, true, 0
for i = 1, #calls - 1 do
curCalls = calls[i]
tempCalls[callListCount] = curCalls
for j = 1, #curCalls do if curCalls[j][1] == "setCursorBlink" then blink = curCalls[j][2] end end
if blink then
if blinkDelay == 0 then
curCalls[#curCalls + 1] = {"toggleCur", curBlink}
blinkDelay, curBlink = 0.4, not curBlink
end
while tempCalls[callListCount].delay > blinkDelay do
local remainder = tempCalls[callListCount].delay - blinkDelay
tempCalls[callListCount].delay = blinkDelay
callListCount = callListCount + 1
tempCalls[callListCount] = {{"toggleCur", curBlink}, ["delay"] = remainder}
blinkDelay, curBlink = 0.4, not curBlink
end
blinkDelay = blinkDelay - tempCalls[callListCount].delay
else
if oldBlink then curCalls[#curCalls + 1] = {"toggleCur", false} end
blinkDelay = (curCalls.delay - blinkDelay) % 0.4
end
callListCount, oldBlink = callListCount + 1, blink
end
tempCalls[callListCount] = calls[#calls]
tempCalls[callListCount][#tempCalls[callListCount] + 1] = {"toggleCur", false}
calls, curCalls = tempCalls, nil
end
snooze()
-- Load font data:
do
local ascii, counter = GIF.toPaintutils(GIF.flattenGIF(GIF.loadGIF("ascii.gif"))), 0
local newFont, ybump, xbump = #ascii ~= #ascii[1], 0, 0
charW, charH, chars = newFont and #ascii[1] / 16 or #ascii[1] * 3 / 64, #ascii / 16, {}
for yy = 0, newFont and 15 or 7 do
for xx = 0, 15 do
local newChar, length = {}, 0
-- Place in 2d grid of bools:
for y = 1, charH do
local newRow = {}
for x = 1, charW do
local set = ascii[y + ybump][x + xbump] == 1
if set and x > length then length = x end
newRow[x] = set
end
newChar[y] = newRow
end
-- Center:
if not newFont then for y = 1, charH do for x = 1, math.floor((charW - length) / 2) do table.insert(newChar[y], 1, false) end end end
chars[counter] = newChar
counter, xbump = counter + 1, xbump + (newFont and charW or charH)
end
xbump, ybump = 0, ybump + charH
end
end
snooze()
-- Terminal data translation:
do
local hex, counter = "0123456789abcdef", 1
for i = 1, 16 do
colourNum[counter] = hex:sub(i, i)
counter = counter * 2
end
end
for y = 1, ySize do
buffer[y] = {}
for x = 1, xSize do buffer[y][x] = {" ", colourNum[tCol], colourNum[bCol]} end
end
if showInput then for x = 1, xSize do buffer[ySize][x][3] = colourNum[colours.lightGrey] end end
tTerm.blit = function(text, fgCol, bgCol)
if xPos > xSize or xPos + #text - 1 < 1 or yPos < 1 or yPos > ySize then return end
if not _HOST then text = safeString(text) end
if not term.isColour() then
fgCol = safeCol(fgCol, "0")
bgCol = safeCol(bgCol, "f")
end
if xPos < 1 then
text = text:sub(2 - xPos)
fgCol = fgCol:sub(2 - xPos)
bgCol = bgCol:sub(2 - xPos)
xPos = 1
end
if xPos + #text - 1 > xSize then
text = text:sub(1, xSize - xPos + 1)
fgCol = fgCol:sub(1, xSize - xPos + 1)
bgCol = bgCol:sub(1, xSize - xPos + 1)
end
for x = 1, #text do
buffer[yPos][xPos + x - 1][1] = text:sub(x, x)
buffer[yPos][xPos + x - 1][2] = fgCol:sub(x, x)
buffer[yPos][xPos + x - 1][3] = bgCol:sub(x, x)
end
xPos = xPos + #text
end
tTerm.write = function(text)
text = tostring(text)
tTerm.blit(text, string.rep(colourNum[tCol], #text), string.rep(colourNum[bCol], #text))
end
tTerm.clearLine = function()
local oldXPos = xPos
xPos = 1
tTerm.write(string.rep(" ", xSize))
xPos = oldXPos
end
tTerm.clear = function()
local oldXPos, oldYPos = xPos, yPos
for y = 1, ySize do
xPos, yPos = 1, y
tTerm.write(string.rep(" ", xSize))
end
xPos, yPos = oldXPos, oldYPos
end
tTerm.setCursorPos = function(x, y)
xPos, yPos = math.floor(x), math.floor(y)
end
tTerm.setTextColour = function(col)
tCol = col
end
tTerm.setTextColor = function(col)
tCol = col
end
tTerm.setBackgroundColour = function(col)
bCol = col
end
tTerm.setBackgroundColor = function(col)
bCol = col
end
tTerm.scroll = function(lines)
if math.abs(lines) < ySize then
local oldXPos, oldYPos = xPos, yPos
for y = 1, ySize do
if y + lines > 0 and y + lines <= ySize then
for x = 1, xSize do
xPos, yPos = x, y
tTerm.blit(buffer[y + lines][x][1], buffer[y + lines][x][2], buffer[y + lines][x][3])
end
else
yPos = y
tTerm.clearLine()
end
end
xPos, yPos = oldXPos, oldYPos
else tTerm.clear() end
end
tTerm.toggleCur = function(newBlink)
curBlink = newBlink
end
tTerm.newInput = function(input)
local oldTC, oldBC, oldX, oldY = tCol, bCol, xPos, yPos
tCol, bCol, xPos, yPos, ySize, input = colours.grey, colours.lightGrey, 1, ySize + 1, ySize + 1, input .. " "
while #curInput + #input + 1 > xSize do curInput = curInput:sub(curInput:find(" ") + 1) end
curInput = curInput .. input .. " "
tTerm.clearLine()
tTerm.write(curInput)
tCol, bCol, xPos, yPos, ySize = oldTC, oldBC, oldX, oldY, ySize - 1
end
tTerm.key = function(key)
tTerm.newInput((not keys.getName(key)) and "unknownKey" or keys.getName(key))
end
tTerm.mouse_click = function(button, x, y)
tTerm.newInput(buttons[button] .. "C@" .. tostring(x) .. "x" .. tostring(y))
end
local image = {["width"] = xSize * charW, ["height"] = ySize * charH}
for i = 1, #calls do
local xMin, yMin, xMax, yMax, oldBuffer, curCalls, changed = xSize + 1, ySize + 1, 0, 0, {}, calls[i], false
calls[i] = nil
for y = 1, ySize do
oldBuffer[y] = {}
for x = 1, xSize do oldBuffer[y][x] = {buffer[y][x][1], buffer[y][x][2], buffer[y][x][3], buffer[y][x][4]} end
end
snooze()
if showInput then ySize = ySize - 1 end
for j = 1, #curCalls do if tTerm[curCalls[j][1]] then tTerm[curCalls[j][1]](unpack(curCalls[j], 2)) end end
if showInput then ySize = ySize + 1 end
if i > 1 then
for yy = 1, ySize do for xx = 1, xSize do if buffer[yy][xx][1] ~= oldBuffer[yy][xx][1] or (buffer[yy][xx][2] ~= oldBuffer[yy][xx][2] and buffer[yy][xx][1] ~= " ") or buffer[yy][xx][3] ~= oldBuffer[yy][xx][3] then
changed = true
if xx < xMin then xMin = xx end
if xx > xMax then xMax = xx end
if yy < yMin then yMin = yy end
if yy > yMax then yMax = yy end
end end end
else xMin, yMin, xMax, yMax, changed = 1, 1, xSize, ySize, true end
if oldBlink and (xPos ~= oldXPos or yPos ~= oldYPos or not curBlink) and oldXPos > 0 and oldYPos > 0 and oldXPos <= xSize and oldYPos <= ySize then
changed = true
if oldXPos < xMin then xMin = oldXPos end
if oldXPos > xMax then xMax = oldXPos end
if oldYPos < yMin then yMin = oldYPos end
if oldYPos > yMax then yMax = oldYPos end
buffer[oldYPos][oldXPos][4] = false
end
if curBlink and (xPos ~= oldXPos or yPos ~= oldYPos or not oldBlink) and xPos > 0 and yPos > 0 and xPos <= xSize and yPos <= ySize then
changed = true
if xPos < xMin then xMin = xPos end
if xPos > xMax then xMax = xPos end
if yPos < yMin then yMin = yPos end
if yPos > yMax then yMax = yPos end
buffer[yPos][xPos][4] = true
end
oldBlink, oldXPos, oldYPos = curBlink, xPos, yPos
local thisFrame = {
["xstart"] = (xMin - 1) * charW,
["ystart"] = (yMin - 1) * charH,
["xend"] = (xMax - xMin + 1) * charW,
["yend"] = (yMax - yMin + 1) * charH,
["delay"] = curCalls.delay,
["disposal"] = 1
}
for y = 1, (yMax - yMin + 1) * charH do
local row = {}
for x = 1, (xMax - xMin + 1) * charW do row[x] = " " end
thisFrame[y] = row
end
snooze()
for yy = yMin, yMax do
local yBump = (yy - yMin) * charH
for xx = xMin, xMax do if buffer[yy][xx][1] ~= oldBuffer[yy][xx][1] or (buffer[yy][xx][2] ~= oldBuffer[yy][xx][2] and buffer[yy][xx][1] ~= " ") or buffer[yy][xx][3] ~= oldBuffer[yy][xx][3] or buffer[yy][xx][4] ~= oldBuffer[yy][xx][4] or i == 1 then
local thisChar, thisT, thisB, xBump = chars[buffer[yy][xx][1]:byte()], buffer[yy][xx][2], buffer[yy][xx][3], (xx - xMin) * charW
if thisChar then
for y = 1, charH do
for x = 1, charW do
local ch = thisChar[y][x] and thisT or thisB
thisFrame[y + yBump][x + xBump] = ch
end
end
end
if buffer[yy][xx][4] then
thisT, thisChar = colourNum[tCol], chars[95]
for y = 1, charH do for x = 1, charW do if thisChar[y][x] then thisFrame[y + yBump][x + xBump] = thisT end end end
end
end end
for y = yBump + 1, yBump + charH do
local skip, chars, row = 0, {}, {}
for x = 1, #thisFrame[y] do
if thisFrame[y][x] == " " then
if #chars > 0 then
row[#row + 1] = table.concat(chars)
chars = {}
end
skip = skip + 1
else
if skip > 0 then
row[#row + 1] = skip
skip = 0
end
chars[#chars + 1] = thisFrame[y][x]
end
end
if #chars > 0 then row[#row + 1] = table.concat(chars) end
thisFrame[y] = row
end
snooze()
end
if changed then
image[#image + 1] = thisFrame
else
image[#image].delay = image[#image].delay + curCalls.delay
end
end
buffer = nil
GIF.saveGIF(image, filename)
fs.delete('ascii.gif')
print("Encode complete")

153
core/shapes.lua Normal file
View File

@@ -0,0 +1,153 @@
_G.requireInjector()
local GPS = require('gps')
local Socket = require('socket')
local UI = require('ui')
local Util = require('util')
local multishell = _ENV.multishell
local textutils = _G.textutils
multishell.setTitle(multishell.getCurrent(), 'Shapes')
local args = { ... }
local turtleId = args[1] or error('Supply turtle ID')
turtleId = tonumber(turtleId)
local levelScript = [[
requireInjector(getfenv(1))
local Util = require('util')
local s, m = turtle.run(function()
turtle.addFeatures('level')
turtle.setStatus('Leveling')
if turtle.enableGPS() then
local pt = Util.shallowCopy(turtle.point)
local s, m = pcall(function()
turtle.level(data.startPt, data.endPt, data.firstPt)
end)
turtle.pathfind(pt)
if not s and m then
error(m)
end
end
end)
if not s then
error(m)
end
]]
local data = Util.readTable('/usr/config/shapes') or { }
local page = UI.Page {
titleBar = UI.TitleBar { title = 'Shapes' },
info = UI.Window { x = 5, y = 3, height = 1 },
startCoord = UI.Button { x = 2, y = 6, text = 'Start ', event = 'startCoord' },
endCoord = UI.Button { x = 2, y = 8, text = 'End ', event = 'endCoord' },
supplies = UI.Button { x = 2, y = 10, text = 'Supplies', event = 'supplies' },
first = UI.Button { x = 2, y = 11, text = 'First', event = 'firstCoord' },
cancel = UI.Button { x = 2, y = -3, text = 'Abort', event = 'cancel' },
begin = UI.Button { x = -8, y = -3, text = 'Begin', event = 'begin' },
accelerators = { q = 'quit' },
notification = UI.Notification(),
statusBar = UI.StatusBar(),
}
function page.info:draw()
local function size(a, b)
return (math.abs(a.x - b.x) + 1) *
(math.abs(a.y - b.y) + 1) *
(math.abs(a.z - b.z) + 1)
end
self:clear()
if not data.startPt then
self:write(1, 1, 'Set starting corner')
elseif not data.endPt then
self:write(1, 1, 'Set ending corner')
else
self:write(1, 1, 'Blocks: ' .. size(data.startPt, data.endPt))
end
end
function page:getPoint()
local pt = GPS.getPoint()
if not pt then
self.notification:error('GPS not available')
end
return pt
end
function page:runFunction(id, script)
--Util.writeFile('script.tmp', script)
self.notification:info('Connecting')
local fn, msg = loadstring(script, 'script')
if not fn then
self.notification:error('Error in script')
--debug(msg)
return
end
local socket = Socket.connect(id, 161)
if not socket then
self.notification:error('Unable to connect')
return
end
socket:write({ type = 'script', args = script })
socket:close()
self.notification:success('Sent')
end
function page:eventHandler(event)
if event.type == 'startCoord' then
data.startPt = self:getPoint()
if data.startPt then
self.statusBar:setStatus('starting corner set')
Util.writeTable('/usr/config/shapes', data)
end
self:draw()
elseif event.type == 'endCoord' then
data.endPt = self:getPoint()
if data.endPt then
self.statusBar:setStatus('ending corner set')
Util.writeTable('/usr/config/shapes', data)
end
self:draw()
elseif event.type == 'firstCoord' then
data.firstPt = self:getPoint()
if data.firstPt then
self.statusBar:setStatus('first point set')
Util.writeTable('/usr/config/shapes', data)
end
self:draw()
elseif event.type == 'supplies' then
data.suppliesPt = self:getPoint()
if data.suppliesPt then
self.statusBar:setStatus('supplies location set')
Util.writeTable('/usr/config/shapes', data)
end
elseif event.type == 'begin' then
if data.startPt and data.endPt then
local s = 'local data = ' .. textutils.serialize(data) .. levelScript
self:runFunction(turtleId, s)
else
self.notification:error('Corners not set')
end
self.statusBar:setStatus('')
elseif event.type == 'cancel' then
self:runFunction(turtleId, 'turtle.abort(true)')
self.statusBar:setStatus('')
else
return UI.Page.eventHandler(self, event)
end
return true
end
UI:setPage(page)
UI:pullEvents()

89
core/t.lua Normal file
View File

@@ -0,0 +1,89 @@
function doCommand(command, moves)
local function format(value)
if type(value) == 'boolean' then
if value then return 'true' end
return 'false'
end
if type(value) ~= 'table' then
return value
end
local str
for k,v in pairs(value) do
if not str then
str = '{ '
else
str = str .. ', '
end
str = str .. k .. '=' .. tostring(v)
end
if str then
str = str .. ' }'
else
str = '{ }'
end
return str
end
local function runCommand(fn, arg)
local r = { fn(arg) }
if r[2] then
print(format(r[1]) .. ': ' .. format(r[2]))
elseif r[1] then
print(format(r[1]))
end
return r[1]
end
local cmds = {
[ 's' ] = turtle.select,
[ 'rf' ] = turtle.refuel,
[ 'gh' ] = function() turtle.pathfind({ x = 0, y = 0, z = 0, heading = 0}) end,
}
local repCmds = {
[ 'u' ] = turtle.up,
[ 'd' ] = turtle.down,
[ 'f' ] = turtle.forward,
[ 'r' ] = turtle.turnRight,
[ 'l' ] = turtle.turnLeft,
[ 'ta' ] = turtle.turnAround,
[ 'DD' ] = turtle.digDown,
[ 'DU' ] = turtle.digUp,
[ 'D' ] = turtle.dig,
[ 'p' ] = turtle.place,
[ 'pu' ] = turtle.placeUp,
[ 'pd' ] = turtle.placeDown,
[ 'b' ] = turtle.back,
[ 'gfl' ] = turtle.getFuelLevel,
[ 'gp' ] = turtle.getPoint,
[ 'R' ] = function() turtle.setPoint({x = 0, y = 0, z = 0, heading = 0}) return turtle.point end
}
if cmds[command] then
runCommand(cmds[command], moves)
elseif repCmds[command] then
for i = 1, moves do
if not runCommand(repCmds[command]) then
break
end
end
end
end
local args = {...}
if #args > 0 then
doCommand(args[1], args[2] or 1)
else
print('Enter command (q to quit):')
while true do
local cmd = read()
if cmd == 'q' then break
end
args = { }
cmd:gsub('%w+', function(w) table.insert(args, w) end)
doCommand(args[1], args[2] or 1)
end
end

22
core/termShare.lua Normal file
View File

@@ -0,0 +1,22 @@
local device = _G.device
local multishell = _ENV.multishell
local os = _G.os
local term = _G.term
-- list this terminal in the devices list so it's available via
-- peripheral sharing
local args = { ... }
local name = args[1] or error('Syntax: termShare [device name] <title>')
local title = args[2]
device[name] = term.current()
device[name].name = name
device[name].side = name
device[name].type = 'terminal'
if title then
multishell.setTitle(multishell.getCurrent(), title)
end
os.pullEventRaw('terminate')
os.queueEvent('peripheral_detach', name)

52
core/trace.lua Normal file
View File

@@ -0,0 +1,52 @@
local args = {...}
if not args[1] then
print("Usage:")
print(shell.getRunningProgram() .. " <program> [program arguments, ...]")
return
end
local path = shell.resolveProgram(args[1]) or shell.resolve(args[1])
-- here be dragons
if fs.exists(path) then
local eshell = setmetatable({getRunningProgram=function() return path end}, {__index = shell})
local env = setmetatable({shell=eshell}, {__index=_ENV})
local f = fs.open(path, "r")
local d = f.readAll()
f.close()
local func, e = load(d, fs.getName(path), nil, env)
if not func then
printError("Syntax error:")
printError(" " .. e)
else
table.remove(args, 1)
xpcall(function() func(unpack(args)) end, function(err)
local trace = {}
local i, hitEnd, _, e = 4, false
repeat
_, e = pcall(function() error("<tracemarker>", i) end)
i = i + 1
if e == "xpcall: <tracemarker>" then
hitEnd = true
break
end
table.insert(trace, e)
until i > 10
table.remove(trace)
if err:match("^" .. trace[1]:match("^(.-:%d+)")) then table.remove(trace, 1) end
printError("\nProgram has crashed! Stack trace:")
printError(err)
for i, v in ipairs(trace) do
printError(" at " .. v:match("^(.-:%d+)"))
end
if not hitEnd then
printError(" ...")
end
end)
end
else
printError("program not found")
end