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