mirror of
https://github.com/kepler155c/opus
synced 2025-10-15 07:47:40 +00:00
major directory reorganize
This commit is contained in:
@@ -68,7 +68,7 @@ function blockDB:seedDB(dir)
|
||||
return res
|
||||
end
|
||||
|
||||
local f = fs.open(fs.combine(dir, 'blockIds.csv'), "r")
|
||||
local f = fs.open(fs.combine('sys/etc', 'blockIds.csv'), "r")
|
||||
|
||||
if not f then
|
||||
error('unable to read blockIds.csv')
|
||||
|
@@ -3,10 +3,10 @@ local Util = require('util')
|
||||
local Config = { }
|
||||
|
||||
Config.load = function(fname, data)
|
||||
local filename = '/config/' .. fname
|
||||
local filename = 'usr/config/' .. fname
|
||||
|
||||
if not fs.exists('/config') then
|
||||
fs.makeDir('/config')
|
||||
if not fs.exists('usr/config') then
|
||||
fs.makeDir('usr/config')
|
||||
end
|
||||
|
||||
if not fs.exists(filename) then
|
||||
@@ -17,7 +17,7 @@ Config.load = function(fname, data)
|
||||
end
|
||||
|
||||
Config.update = function(fname, data)
|
||||
local filename = '/config/' .. fname
|
||||
local filename = 'usr/config/' .. fname
|
||||
Util.writeTable(filename, data)
|
||||
end
|
||||
|
||||
|
@@ -146,7 +146,7 @@ local function trusted(msg, port)
|
||||
return true
|
||||
end
|
||||
|
||||
local trustList = Util.readTable('.known_hosts') or { }
|
||||
local trustList = Util.readTable('usr/.known_hosts') or { }
|
||||
local pubKey = trustList[msg.shost]
|
||||
|
||||
if pubKey then
|
||||
|
@@ -202,7 +202,7 @@ function Manager:configure(appName, ...)
|
||||
textScale = { arg = 't', type = 'number',
|
||||
desc = 'Text scale' },
|
||||
}
|
||||
local defaults = Util.loadTable('/config/' .. appName) or { }
|
||||
local defaults = Util.loadTable('usr/config/' .. appName) or { }
|
||||
if not defaults.device then
|
||||
defaults.device = { }
|
||||
end
|
||||
@@ -3321,7 +3321,7 @@ function UI.NftImage:setImage(image)
|
||||
self.image = image
|
||||
end
|
||||
|
||||
UI:loadTheme('config/ui.theme')
|
||||
UI:loadTheme('usr/config/ui.theme')
|
||||
if os.getVersion() >= 1.79 then
|
||||
UI:loadTheme('sys/etc/ext.theme')
|
||||
end
|
||||
|
8
sys/apps/.overview/Appstore
Normal file
8
sys/apps/.overview/Appstore
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
icon = "\030 \0310=\0300 \030 XX\0300\031f \030 \
|
||||
\030 \031f \0300 \030 \
|
||||
\030 \031f \0310o \031f \0310o\031f ",
|
||||
category = "System",
|
||||
title = "AppStore",
|
||||
run = "Appstore.lua",
|
||||
}
|
8
sys/apps/.overview/Events
Normal file
8
sys/apps/.overview/Events
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
icon = "\0304\031f \030 \0311e\
|
||||
\030f\031f \0304 \030 \0311ee\031f \
|
||||
\030f\031f \0304 \030 \0311e\031f ",
|
||||
title = "Events",
|
||||
category = "System",
|
||||
run = "Events.lua",
|
||||
}
|
8
sys/apps/.overview/Files
Normal file
8
sys/apps/.overview/Files
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
icon = "\0300\0317==\031 \0307 \
|
||||
\0300\0317====\
|
||||
\0300\0317====",
|
||||
title = "Files",
|
||||
category = "Apps",
|
||||
run = "Files.lua",
|
||||
}
|
8
sys/apps/.overview/Help
Normal file
8
sys/apps/.overview/Help
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
icon = " \031d?\031 \
|
||||
\031d?\031 \
|
||||
\031d?",
|
||||
title = "Help",
|
||||
category = "Apps",
|
||||
run = "Help.lua",
|
||||
}
|
8
sys/apps/.overview/Lua
Normal file
8
sys/apps/.overview/Lua
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
icon = "\030f \
|
||||
\030f\0310lua>\031 \
|
||||
\030f ",
|
||||
title = "Lua",
|
||||
category = "Apps",
|
||||
run = "Lua.lua",
|
||||
}
|
9
sys/apps/.overview/Network
Normal file
9
sys/apps/.overview/Network
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
title = "Network",
|
||||
category = "Apps",
|
||||
requires = "wireless_modem",
|
||||
icon = "\0304 \030 \
|
||||
\030f \0304 \0307 \030 \031 \031f)\
|
||||
\030f \0304 \0307 \030 \031f)",
|
||||
run = "Network.lua",
|
||||
}
|
8
sys/apps/.overview/Peripherals
Normal file
8
sys/apps/.overview/Peripherals
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
icon = "\0304 \030 \
|
||||
\030f \0304 \0307 \030 \031 \031f_\
|
||||
\030f \0304 \0307 \030 \031f/",
|
||||
title = "Devices",
|
||||
category = "System",
|
||||
run = "Peripherals.lua",
|
||||
}
|
9
sys/apps/.overview/Script
Normal file
9
sys/apps/.overview/Script
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
title = "Scripts",
|
||||
category = "Apps",
|
||||
requires = "wireless_modem",
|
||||
icon = "\0300\0317if\031 \0307 \
|
||||
\0300\0317turt\
|
||||
\0300\0317retu",
|
||||
run = "Script.lua",
|
||||
}
|
8
sys/apps/.overview/System
Normal file
8
sys/apps/.overview/System
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
icon = " \0307\031f| \
|
||||
\0307\031f---o\030 \031 \
|
||||
\0307\031f| ",
|
||||
title = "System",
|
||||
category = "System",
|
||||
run = "System.lua",
|
||||
}
|
8
sys/apps/.overview/Tabs
Normal file
8
sys/apps/.overview/Tabs
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
icon = "\0307 \0303\0317__\0307\031 \
|
||||
\0303 \
|
||||
\0303 ",
|
||||
title = "Tabs",
|
||||
category = "System",
|
||||
run = "Tabs.lua",
|
||||
}
|
5
sys/apps/.overview/Turtles
Normal file
5
sys/apps/.overview/Turtles
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
category = "Apps",
|
||||
title = "Turtles",
|
||||
run = "Turtles.lua",
|
||||
}
|
8
sys/apps/.overview/adventure
Normal file
8
sys/apps/.overview/adventure
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
icon = "\030f\0310You \031 \
|
||||
\030f\0310Ther\030 \031 \
|
||||
\030f\0314?\031f \031 \030 ",
|
||||
title = "Adventure",
|
||||
category = "Games",
|
||||
run = "rom/programs/fun/adventure",
|
||||
}
|
8
sys/apps/.overview/builder
Normal file
8
sys/apps/.overview/builder
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
icon = "\0317_____\
|
||||
\030e\031c###\0308\0317=\030e\031c#\
|
||||
\030e\031c#\0307\031f.\030e\031c###",
|
||||
title = "Builder",
|
||||
category = "Apps",
|
||||
run = "builder.lua",
|
||||
}
|
8
sys/apps/.overview/dj
Normal file
8
sys/apps/.overview/dj
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
icon = " \030f \
|
||||
\030f \0307 \
|
||||
\030f \0307 \0300 ",
|
||||
title = "DJ",
|
||||
category = "Games",
|
||||
run = "/rom/programs/fun/dj",
|
||||
}
|
8
sys/apps/.overview/falling
Normal file
8
sys/apps/.overview/falling
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
icon = "\030f \0302 \
|
||||
\0309 \0302 \0301 \
|
||||
\030e \0309 \0301 ",
|
||||
title = "Falling",
|
||||
category = "Games",
|
||||
run = "rom/programs/pocket/falling",
|
||||
}
|
8
sys/apps/.overview/reboot
Normal file
8
sys/apps/.overview/reboot
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
icon = "\0304\031f \030f\0310o..\0304\031f \
|
||||
\0304\031f \030f\0310.o.\0304\031f \
|
||||
\0304\031f - ",
|
||||
title = "Reboot",
|
||||
category = "System",
|
||||
run = "rom/programs/reboot",
|
||||
}
|
8
sys/apps/.overview/recorder
Normal file
8
sys/apps/.overview/recorder
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
icon = "\030 \031f \031b \031foo \
|
||||
\030 \031f \030e\031b \030 \031f/\
|
||||
\030 \031b \030e \030 \031f\\",
|
||||
category = "Apps",
|
||||
title = "Recorder",
|
||||
run = "recorder.lua",
|
||||
}
|
8
sys/apps/.overview/redirection
Normal file
8
sys/apps/.overview/redirection
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
icon = "\0307 \0308 \0307 \
|
||||
\0308\031b> \030b\0310>\0308\0318 \
|
||||
\0307 ",
|
||||
title = "Redirection",
|
||||
category = "Games",
|
||||
run = "rom/programs/fun/advanced/redirection",
|
||||
}
|
8
sys/apps/.overview/shell
Normal file
8
sys/apps/.overview/shell
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
icon = "\0304 \030 \
|
||||
\0304 \030f\0314> \0310_\031 \
|
||||
\0304 \030f \030 ",
|
||||
title = "Shell",
|
||||
category = "Apps",
|
||||
run = "shell",
|
||||
}
|
8
sys/apps/.overview/shutdown
Normal file
8
sys/apps/.overview/shutdown
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
icon = "\0304\031f \
|
||||
\0304\031f \030f\0310zz\031 \
|
||||
\0304\031f \030f ",
|
||||
title = "Shutdown",
|
||||
category = "System",
|
||||
run = "/rom/programs/shutdown",
|
||||
}
|
8
sys/apps/.overview/simpleMiner
Normal file
8
sys/apps/.overview/simpleMiner
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
icon = " \0315\\\030 \031 \
|
||||
\0304\031f _ \030 \031c/\0315\\\
|
||||
\0304 ",
|
||||
title = "Miner",
|
||||
category = "Apps",
|
||||
run = "simpleMiner.lua",
|
||||
}
|
8
sys/apps/.overview/storageActivity
Normal file
8
sys/apps/.overview/storageActivity
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
icon = "\0318/\030f\031 \030 \0318\\\
|
||||
\030f \0308\0319o\030f\031 \
|
||||
\0318\\\030f\031 \030 \0318/",
|
||||
title = "Activity",
|
||||
category = "Apps",
|
||||
run = "storageActivity.lua",
|
||||
}
|
8
sys/apps/.overview/storageManager
Normal file
8
sys/apps/.overview/storageManager
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
icon = "\0307 \
|
||||
\0307 \0308\0311 \0305 \0308\031 \0307 \0308 \0301 \
|
||||
\0307 ",
|
||||
title = "Storage",
|
||||
category = "Apps",
|
||||
run = "storageManager.lua",
|
||||
}
|
8
sys/apps/.overview/telnet
Normal file
8
sys/apps/.overview/telnet
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
icon = " \0314>\0310_\
|
||||
\031f)))\031 \
|
||||
\0314>\0310_\031 ",
|
||||
title = "Telnet",
|
||||
category = "Apps",
|
||||
run = "telnet.lua",
|
||||
}
|
8
sys/apps/.overview/update
Normal file
8
sys/apps/.overview/update
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
icon = "\0301\03171\03180\030 \031 \
|
||||
\0301\03181\030 \031 \
|
||||
\0301\03170\03180\03171\0307\031f>",
|
||||
title = "Update",
|
||||
category = "System",
|
||||
run = "update.lua",
|
||||
}
|
8
sys/apps/.overview/vnc
Normal file
8
sys/apps/.overview/vnc
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
icon = "\
|
||||
\031e\\\031 \031e/\031dn\
|
||||
\031e\\/\031 \0319c",
|
||||
title = "VNC",
|
||||
category = "Apps",
|
||||
run = "vnc.lua",
|
||||
}
|
8
sys/apps/.overview/worm
Normal file
8
sys/apps/.overview/worm
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
icon = "\030d \030 \030e \030 \
|
||||
\030d \030 \
|
||||
\030d ",
|
||||
title = "Worm",
|
||||
category = "Games",
|
||||
run = "/rom/programs/fun/worm",
|
||||
}
|
386
sys/apps/Appstore.lua
Normal file
386
sys/apps/Appstore.lua
Normal file
@@ -0,0 +1,386 @@
|
||||
require = requireInjector(getfenv(1))
|
||||
local Util = require('util')
|
||||
local class = require('class')
|
||||
local UI = require('ui')
|
||||
local Event = require('event')
|
||||
|
||||
local sandboxEnv = Util.shallowCopy(getfenv(1))
|
||||
setmetatable(sandboxEnv, { __index = _G })
|
||||
|
||||
multishell.setTitle(multishell.getCurrent(), 'App Store')
|
||||
UI:configure('Appstore', ...)
|
||||
|
||||
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('/apps')
|
||||
|
||||
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
|
||||
|
||||
function runApp(app, checkExists, ...)
|
||||
|
||||
local path, fn
|
||||
local args = { ... }
|
||||
|
||||
if checkExists and fs.exists(fs.combine('/apps', app.name)) then
|
||||
path = fs.combine('/apps', 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('/apps', 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')
|
||||
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({
|
||||
backgroundColor = UI.ViewportWindow.defaults.backgroundColor,
|
||||
menuBar = UI.MenuBar({
|
||||
showBackButton = not os.isPocket(),
|
||||
buttons = {
|
||||
{ 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,
|
||||
height = UI.term.height - 3,
|
||||
width = UI.term.width - 2,
|
||||
viewport = UI.ViewportWindow(),
|
||||
}),
|
||||
notification = UI.Notification(),
|
||||
accelerators = {
|
||||
q = 'back',
|
||||
backspace = 'back',
|
||||
},
|
||||
})
|
||||
|
||||
function appPage.container.viewport:draw()
|
||||
local app = self.parent.parent.app
|
||||
local str = string.format(
|
||||
'By: %s\nCategory: %s\nFile name: %s\n\n%s',
|
||||
app.creator, app.categoryName, app.name, app.description)
|
||||
|
||||
self:clear()
|
||||
local y = self:wrappedWrite(1, 1, app.title, self.width, nil, colors.yellow)
|
||||
self.height = self:wrappedWrite(1, y, str, self.width)
|
||||
|
||||
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('/apps', 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
|
||||
s,m = runApp(self.app, false, 'uninstall')
|
||||
else
|
||||
fs.delete(fs.combine('/apps', self.app.name))
|
||||
self.notification:success("Uninstalled " .. self.app.name, 3)
|
||||
self:focusFirst(self)
|
||||
self.menuBar.removeButton:disable('Remove')
|
||||
self.menuBar:draw()
|
||||
|
||||
os.unregisterApp(fs.combine('/apps', 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
|
||||
|
||||
os.registerApp({
|
||||
run = fs.combine('/apps', self.app.name),
|
||||
title = self.app.title,
|
||||
category = category,
|
||||
icon = self.app.icon,
|
||||
})
|
||||
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', event = 'dropdown', dropdown = 'sourceMenu' },
|
||||
{ text = 'Category', event = 'dropdown', dropdown = 'categoryMenu' },
|
||||
},
|
||||
}),
|
||||
sourceMenu = UI.DropMenu({
|
||||
buttons = sources,
|
||||
}),
|
||||
grid = UI.ScrollingGrid({
|
||||
y = 2,
|
||||
height = UI.term.height - 2,
|
||||
columns = {
|
||||
{ heading = 'Title', key = 'title' },
|
||||
},
|
||||
sortColumn = 'title',
|
||||
autospace = true,
|
||||
}),
|
||||
statusBar = UI.StatusBar(),
|
||||
accelerators = {
|
||||
l = 'lua',
|
||||
q = 'quit',
|
||||
},
|
||||
})
|
||||
|
||||
function categoryPage:setCategory(source, name, index)
|
||||
self.grid.values = { }
|
||||
for k,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({
|
||||
y = 2,
|
||||
x = 1,
|
||||
buttons = buttons,
|
||||
})
|
||||
source.index, source.name = Util.first(source.storeCatagoryNames)
|
||||
|
||||
categoryPage:add({
|
||||
categoryMenu = source.categoryMenu
|
||||
})
|
||||
end
|
||||
|
||||
self.source = source
|
||||
self.categoryMenu = 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('/apps', 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
|
||||
Event.exitPullEvents()
|
||||
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
print("Retrieving catalog list")
|
||||
categoryPage:setSource(sources[1])
|
||||
|
||||
UI:setPage(categoryPage)
|
||||
Event.pullEvents()
|
||||
UI.term:reset()
|
118
sys/apps/Events.lua
Normal file
118
sys/apps/Events.lua
Normal file
@@ -0,0 +1,118 @@
|
||||
require = requireInjector(getfenv(1))
|
||||
local Event = require('event')
|
||||
local UI = require('ui')
|
||||
|
||||
multishell.setTitle(multishell.getCurrent(), 'Events')
|
||||
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 = {
|
||||
{ heading = 'Event', key = 'event' },
|
||||
{ key = 'p1' },
|
||||
{ key = 'p2' },
|
||||
{ key = 'p3' },
|
||||
{ key = 'p4' },
|
||||
{ key = 'p5' },
|
||||
},
|
||||
autospace = 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 == '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
|
||||
Event.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:draw()
|
||||
self:adjustWidth()
|
||||
UI.Grid.draw(self)
|
||||
end
|
||||
|
||||
function eventLoop()
|
||||
|
||||
local function tovalue(s)
|
||||
if type(s) == 'table' then
|
||||
return 'table'
|
||||
end
|
||||
return s
|
||||
end
|
||||
|
||||
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 = tovalue(e[2]),
|
||||
p2 = tovalue(e[3]),
|
||||
p3 = tovalue(e[4]),
|
||||
p4 = tovalue(e[5]),
|
||||
p5 = tovalue(e[6]),
|
||||
})
|
||||
if #page.grid.values > page.grid.height - 1 then
|
||||
table.remove(page.grid.values, #page.grid.values)
|
||||
end
|
||||
page.grid:update()
|
||||
page.grid:draw()
|
||||
page:sync()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
UI:setPage(page)
|
||||
Event.pullEvents(eventLoop)
|
||||
UI.term:reset()
|
414
sys/apps/Files.lua
Normal file
414
sys/apps/Files.lua
Normal file
@@ -0,0 +1,414 @@
|
||||
require = requireInjector(getfenv(1))
|
||||
local Util = require('util')
|
||||
local Event = require('event')
|
||||
local UI = require('ui')
|
||||
local Config = require('config')
|
||||
|
||||
local cleanEnv = Util.shallowCopy(getfenv(1))
|
||||
cleanEnv.require = nil
|
||||
|
||||
multishell.setTitle(multishell.getCurrent(), 'Files')
|
||||
UI:configure('Files', ...)
|
||||
|
||||
local config = {
|
||||
showHidden = false,
|
||||
showDirSizes = false,
|
||||
}
|
||||
|
||||
Config.load('Files', config)
|
||||
|
||||
local copied = { }
|
||||
local marked = { }
|
||||
local directories = { }
|
||||
local cutMode = false
|
||||
|
||||
function formatSize(size)
|
||||
if size >= 1000000 then
|
||||
return string.format('%dM', math.floor(size/1000000, 2))
|
||||
elseif size >= 1000 then
|
||||
return string.format('%dK', math.floor(size/1000, 2))
|
||||
end
|
||||
return size
|
||||
end
|
||||
|
||||
local Browser = UI.Page {
|
||||
menuBar = UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = '^-', event = 'updir' },
|
||||
{ text = 'File', event = 'dropdown', dropdown = 'fileMenu' },
|
||||
{ text = 'Edit', event = 'dropdown', dropdown = 'editMenu' },
|
||||
{ text = 'View', event = 'dropdown', dropdown = 'viewMenu' },
|
||||
},
|
||||
},
|
||||
fileMenu = UI.DropMenu {
|
||||
buttons = {
|
||||
{ text = 'Run', event = 'run' },
|
||||
{ text = 'Edit e', event = 'edit' },
|
||||
{ text = 'Shell s', event = 'shell' },
|
||||
UI.Text { value = ' ------------ ' },
|
||||
{ text = 'Quit q', event = 'quit' },
|
||||
UI.Text { },
|
||||
}
|
||||
},
|
||||
editMenu = UI.DropMenu {
|
||||
buttons = {
|
||||
{ text = 'Cut ^x', event = 'cut' },
|
||||
{ text = 'Copy ^c', event = 'copy' },
|
||||
{ text = 'Paste ^v', event = 'paste' },
|
||||
UI.Text { value = ' --------------- ' },
|
||||
{ text = 'Mark m', event = 'mark' },
|
||||
{ text = 'Unmark all u', event = 'unmark' },
|
||||
UI.Text { value = ' --------------- ' },
|
||||
{ text = 'Delete del', event = 'delete' },
|
||||
UI.Text { },
|
||||
}
|
||||
},
|
||||
viewMenu = UI.DropMenu {
|
||||
buttons = {
|
||||
{ text = 'Refresh r', event = 'refresh' },
|
||||
{ text = 'Hidden ^h', event = 'toggle_hidden' },
|
||||
{ text = 'Dir Size ^s', event = 'toggle_dirSize' },
|
||||
UI.Text { },
|
||||
}
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'name', width = UI.term.width-11 },
|
||||
{ key = 'flags', width = 2 },
|
||||
{ heading = 'Size', key = 'fsize', width = 6 },
|
||||
},
|
||||
sortColumn = 'name',
|
||||
y = 2,
|
||||
height = UI.term.height-2,
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
columns = {
|
||||
{ '', 'status', UI.term.width - 8 },
|
||||
--{ '', 'info', 10 },
|
||||
{ 'Size: ', 'totalSize', 8 },
|
||||
},
|
||||
},
|
||||
accelerators = {
|
||||
q = 'quit',
|
||||
e = 'edit',
|
||||
s = 'shell',
|
||||
r = 'refresh',
|
||||
space = 'mark',
|
||||
backspace = 'updir',
|
||||
m = 'move',
|
||||
u = 'unmark',
|
||||
d = 'delete',
|
||||
delete = 'delete',
|
||||
[ 'control-h' ] = 'toggle_hidden',
|
||||
[ 'control-x' ] = 'cut',
|
||||
[ 'control-c' ] = 'copy',
|
||||
paste = 'paste',
|
||||
},
|
||||
}
|
||||
|
||||
function Browser:enable()
|
||||
UI.Page.enable(self)
|
||||
self:setFocus(self.grid)
|
||||
end
|
||||
|
||||
function Browser.grid:sortCompare(a, b)
|
||||
if self.sortColumn == 'fsize' then
|
||||
return a.size < b.size
|
||||
elseif self.sortColumn == 'flags' then
|
||||
return a.flags < b.flags
|
||||
end
|
||||
if a.isDir == b.isDir then
|
||||
return a.name:lower() < b.name:lower()
|
||||
end
|
||||
return a.isDir
|
||||
end
|
||||
|
||||
function Browser.grid:getRowTextColor(file, selected)
|
||||
if file.marked then
|
||||
return colors.green
|
||||
end
|
||||
if file.isDir then
|
||||
return colors.cyan
|
||||
end
|
||||
if file.isReadOnly then
|
||||
return colors.pink
|
||||
end
|
||||
return colors.white
|
||||
end
|
||||
|
||||
function Browser.grid:getRowBackgroundColorX(file, selected)
|
||||
if selected then
|
||||
return colors.gray
|
||||
end
|
||||
return self.backgroundColor
|
||||
end
|
||||
|
||||
function Browser.grid:eventHandler(event)
|
||||
if event.type == 'copy' then -- let copy be handled by parent
|
||||
return false
|
||||
end
|
||||
return UI.Grid.eventHandler(self, event)
|
||||
end
|
||||
|
||||
function Browser.statusBar:draw()
|
||||
if self.parent.dir then
|
||||
local info = '#:' .. Util.size(self.parent.dir.files)
|
||||
local numMarked = Util.size(marked)
|
||||
if numMarked > 0 then
|
||||
info = info .. ' M:' .. numMarked
|
||||
end
|
||||
self:setValue('info', info)
|
||||
self:setValue('totalSize', formatSize(self.parent.dir.totalSize))
|
||||
UI.StatusBar.draw(self)
|
||||
end
|
||||
end
|
||||
|
||||
function Browser:setStatus(status, ...)
|
||||
self.statusBar:timedStatus(string.format(status, ...))
|
||||
end
|
||||
|
||||
function Browser:unmarkAll()
|
||||
for k,m in pairs(marked) do
|
||||
m.marked = false
|
||||
end
|
||||
Util.clear(marked)
|
||||
end
|
||||
|
||||
function Browser:getDirectory(directory)
|
||||
|
||||
local s, dir = pcall(function()
|
||||
|
||||
local dir = directories[directory]
|
||||
if not dir then
|
||||
dir = {
|
||||
name = directory,
|
||||
size = 0,
|
||||
files = { },
|
||||
totalSize = 0,
|
||||
index = 1
|
||||
}
|
||||
directories[directory] = dir
|
||||
end
|
||||
|
||||
self:updateDirectory(dir)
|
||||
|
||||
return dir
|
||||
end)
|
||||
|
||||
return s, dir
|
||||
end
|
||||
|
||||
function Browser:updateDirectory(dir)
|
||||
|
||||
dir.size = 0
|
||||
dir.totalSize = 0
|
||||
Util.clear(dir.files)
|
||||
|
||||
local files = fs.list(dir.name, true)
|
||||
if files then
|
||||
dir.size = #files
|
||||
for _, file in pairs(files) do
|
||||
file.fullName = fs.combine(dir.name, file.name)
|
||||
file.directory = directory
|
||||
file.flags = ''
|
||||
if not file.isDir then
|
||||
dir.totalSize = dir.totalSize + file.size
|
||||
file.fsize = formatSize(file.size)
|
||||
else
|
||||
if config.showDirSizes then
|
||||
file.size = fs.getSize(file.fullName, true)
|
||||
|
||||
dir.totalSize = dir.totalSize + file.size
|
||||
file.fsize = formatSize(file.size)
|
||||
end
|
||||
file.flags = 'D'
|
||||
end
|
||||
if file.isReadOnly then
|
||||
file.flags = file.flags .. 'R'
|
||||
end
|
||||
if config.showHidden or file.name:sub(1, 1) ~= '.' then
|
||||
dir.files[file.fullName] = file
|
||||
end
|
||||
end
|
||||
end
|
||||
-- self.grid:update()
|
||||
-- self.grid:setIndex(dir.index)
|
||||
self.grid:setValues(dir.files)
|
||||
end
|
||||
|
||||
function Browser:setDir(dirName, noStatus)
|
||||
|
||||
self:unmarkAll()
|
||||
|
||||
if self.dir then
|
||||
self.dir.index = self.grid:getIndex()
|
||||
end
|
||||
DIR = fs.combine('', dirName)
|
||||
shell.setDir(DIR)
|
||||
local s, dir = self:getDirectory(DIR)
|
||||
if s then
|
||||
self.dir = dir
|
||||
elseif noStatus then
|
||||
error(dir)
|
||||
else
|
||||
self:setStatus(dir)
|
||||
self:setDir('', true)
|
||||
return
|
||||
end
|
||||
|
||||
if not noStatus then
|
||||
self.statusBar:setValue('status', '/' .. self.dir.name)
|
||||
self.statusBar:draw()
|
||||
end
|
||||
self.grid:setIndex(self.dir.index)
|
||||
end
|
||||
|
||||
function Browser:run(path, ...)
|
||||
local tabId = multishell.launch(cleanEnv, path, ...)
|
||||
multishell.setFocus(tabId)
|
||||
end
|
||||
|
||||
function Browser:hasMarked()
|
||||
if Util.size(marked) == 0 then
|
||||
local file = self.grid:getSelected()
|
||||
if file then
|
||||
file.marked = true
|
||||
marked[file.fullName] = file
|
||||
self.grid:draw()
|
||||
end
|
||||
end
|
||||
return Util.size(marked) > 0
|
||||
end
|
||||
|
||||
function Browser:eventHandler(event)
|
||||
local file = self.grid:getSelected()
|
||||
|
||||
if event.type == 'quit' then
|
||||
Event.exitPullEvents()
|
||||
|
||||
elseif event.type == 'edit' and file then
|
||||
self:run('/apps/shell', 'edit', file.name)
|
||||
|
||||
elseif event.type == 'shell' then
|
||||
self:run('/apps/shell')
|
||||
|
||||
elseif event.type == 'refresh' then
|
||||
self:updateDirectory(self.dir)
|
||||
self.grid:draw()
|
||||
self:setStatus('Refreshed')
|
||||
|
||||
elseif event.type == 'toggle_hidden' then
|
||||
config.showHidden = not config.showHidden
|
||||
Config.update('Files', config)
|
||||
|
||||
self:updateDirectory(self.dir)
|
||||
self.grid:draw()
|
||||
if not config.showHidden then
|
||||
self:setStatus('Hiding hidden')
|
||||
else
|
||||
self:setStatus('Displaying hidden')
|
||||
end
|
||||
|
||||
elseif event.type == 'toggle_dirSize' then
|
||||
config.showDirSizes = not config.showDirSizes
|
||||
Config.update('Files', config)
|
||||
|
||||
self:updateDirectory(self.dir)
|
||||
self.grid:draw()
|
||||
if config.showDirSizes then
|
||||
self:setStatus('Displaying dir sizes')
|
||||
end
|
||||
|
||||
elseif event.type == 'mark' and file then
|
||||
file.marked = not file.marked
|
||||
if file.marked then
|
||||
marked[file.fullName] = file
|
||||
else
|
||||
marked[file.fullName] = nil
|
||||
end
|
||||
self.grid:draw()
|
||||
self.statusBar:draw()
|
||||
|
||||
elseif event.type == 'unmark' then
|
||||
self:unmarkAll()
|
||||
self.grid:draw()
|
||||
self:setStatus('Marked files cleared')
|
||||
|
||||
elseif event.type == 'grid_select' or event.type == 'run' then
|
||||
if file then
|
||||
if file.isDir then
|
||||
self:setDir(file.fullName)
|
||||
else
|
||||
self:run('/apps/shell', file.name)
|
||||
end
|
||||
end
|
||||
|
||||
elseif event.type == 'updir' then
|
||||
local dir = (self.dir.name:match("(.*/)"))
|
||||
self:setDir(dir or '/')
|
||||
|
||||
elseif event.type == 'delete' then
|
||||
if self:hasMarked() then
|
||||
local width = self.statusBar:getColumnWidth('status')
|
||||
self.statusBar:setColumnWidth('status', UI.term.width)
|
||||
self.statusBar:setValue('status', 'Delete marked? (y/n)')
|
||||
self.statusBar:draw()
|
||||
self.statusBar:sync()
|
||||
local _, ch = os.pullEvent('char')
|
||||
if ch == 'y' or ch == 'Y' then
|
||||
for k,m in pairs(marked) do
|
||||
pcall(function()
|
||||
fs.delete(m.fullName)
|
||||
end)
|
||||
end
|
||||
end
|
||||
marked = { }
|
||||
self.statusBar:setColumnWidth('status', width)
|
||||
self.statusBar:setValue('status', '/' .. self.dir.name)
|
||||
self:updateDirectory(self.dir)
|
||||
|
||||
self.statusBar:draw()
|
||||
self.grid:draw()
|
||||
self:setFocus(self.grid)
|
||||
end
|
||||
|
||||
elseif event.type == 'copy' or event.type == 'cut' then
|
||||
if self:hasMarked() then
|
||||
cutMode = event.type == 'cut'
|
||||
Util.clear(copied)
|
||||
Util.merge(copied, marked)
|
||||
--self:unmarkAll()
|
||||
self.grid:draw()
|
||||
self:setStatus('Copied %d file(s)', Util.size(copied))
|
||||
end
|
||||
|
||||
elseif event.type == 'paste' then
|
||||
for k,m in pairs(copied) do
|
||||
local s, m = pcall(function()
|
||||
if cutMode then
|
||||
fs.move(m.fullName, fs.combine(self.dir.name, m.name))
|
||||
else
|
||||
fs.copy(m.fullName, fs.combine(self.dir.name, m.name))
|
||||
end
|
||||
end)
|
||||
end
|
||||
self:updateDirectory(self.dir)
|
||||
self.grid:draw()
|
||||
self:setStatus('Pasted ' .. Util.size(copied) .. ' file(s)')
|
||||
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
self:setFocus(self.grid)
|
||||
return true
|
||||
end
|
||||
|
||||
--[[-- Startup logic --]]--
|
||||
local args = { ... }
|
||||
|
||||
Browser:setDir(args[1] or shell.dir())
|
||||
|
||||
UI:setPage(Browser)
|
||||
|
||||
Event.pullEvents()
|
||||
UI.term:reset()
|
84
sys/apps/Help.lua
Normal file
84
sys/apps/Help.lua
Normal file
@@ -0,0 +1,84 @@
|
||||
require = requireInjector(getfenv(1))
|
||||
local Event = require('event')
|
||||
local UI = require('ui')
|
||||
|
||||
multishell.setTitle(multishell.getCurrent(), 'Help')
|
||||
UI:configure('Help', ...)
|
||||
|
||||
local files = { }
|
||||
for _,f in pairs(fs.list('/rom/help')) do
|
||||
table.insert(files, { name = f })
|
||||
end
|
||||
|
||||
local page = UI.Page({
|
||||
labelText = UI.Text({
|
||||
y = 2,
|
||||
x = 3,
|
||||
value = 'Search',
|
||||
}),
|
||||
filter = UI.TextEntry({
|
||||
y = 2,
|
||||
x = 10,
|
||||
width = UI.term.width - 13,
|
||||
limit = 32,
|
||||
}),
|
||||
grid = UI.ScrollingGrid({
|
||||
y = 4,
|
||||
height = UI.term.height - 4,
|
||||
values = files,
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'name', width = 12 },
|
||||
},
|
||||
sortColumn = 'name',
|
||||
}),
|
||||
statusBar = UI.StatusBar(),
|
||||
accelerators = {
|
||||
q = 'quit',
|
||||
},
|
||||
})
|
||||
|
||||
local function showHelp(name)
|
||||
UI.term:reset()
|
||||
shell.run('help ' .. name)
|
||||
print('Press enter to return')
|
||||
read()
|
||||
end
|
||||
|
||||
function page:eventHandler(event)
|
||||
|
||||
if event.type == 'quit' then
|
||||
Event.exitPullEvents()
|
||||
|
||||
elseif event.type == 'key' and event.key == 'enter' then
|
||||
showHelp(self.grid:getSelected().name)
|
||||
self:setFocus(self.filter)
|
||||
self:draw()
|
||||
|
||||
elseif event.type == 'grid_select' then
|
||||
showHelp(event.selected.name)
|
||||
self:setFocus(self.filter)
|
||||
self:draw()
|
||||
|
||||
elseif event.type == 'text_change' then
|
||||
local text = event.text
|
||||
if #text == 0 then
|
||||
self.grid.values = files
|
||||
else
|
||||
self.grid.values = { }
|
||||
for _,f in pairs(files) do
|
||||
if string.find(f.name, text) then
|
||||
table.insert(self.grid.values, f)
|
||||
end
|
||||
end
|
||||
end
|
||||
self.grid:update()
|
||||
self.grid:setIndex(1)
|
||||
self.grid:draw()
|
||||
else
|
||||
UI.Page.eventHandler(self, event)
|
||||
end
|
||||
end
|
||||
|
||||
UI:setPage(page)
|
||||
Event.pullEvents()
|
||||
UI.term:reset()
|
314
sys/apps/Lua.lua
Normal file
314
sys/apps/Lua.lua
Normal file
@@ -0,0 +1,314 @@
|
||||
local injector = requireInjector or load(http.get('http://pastebin.com/raw/c0TWsScv').readAll())()
|
||||
require = injector(getfenv(1))
|
||||
local Util = require('util')
|
||||
local UI = require('ui')
|
||||
local Event = require('event')
|
||||
local History = require('history')
|
||||
|
||||
local sandboxEnv = Util.shallowCopy(getfenv(1))
|
||||
sandboxEnv.exit = function() Event.exitPullEvents() end
|
||||
sandboxEnv.require = injector(sandboxEnv)
|
||||
setmetatable(sandboxEnv, { __index = _G })
|
||||
|
||||
multishell.setTitle(multishell.getCurrent(), 'Lua')
|
||||
UI:configure('Lua', ...)
|
||||
|
||||
local command = ''
|
||||
local history = History.load('usr/.lua_history', 25)
|
||||
|
||||
local page = UI.Page({
|
||||
menuBar = UI.MenuBar({
|
||||
buttons = {
|
||||
{ text = 'Local', event = 'local' },
|
||||
{ text = 'Global', event = 'global' },
|
||||
{ text = 'Device', event = 'device', name = 'Device' },
|
||||
},
|
||||
}),
|
||||
prompt = UI.TextEntry({
|
||||
y = 2,
|
||||
shadowText = 'enter command',
|
||||
backgroundFocusColor = colors.black,
|
||||
limit = 256,
|
||||
accelerators = {
|
||||
enter = 'command_enter',
|
||||
up = 'history_back',
|
||||
down = 'history_forward',
|
||||
mouse_rightclick = 'clear_prompt',
|
||||
-- [ 'control-space' ] = 'autocomplete',
|
||||
},
|
||||
}),
|
||||
grid = UI.ScrollingGrid({
|
||||
y = 3,
|
||||
columns = {
|
||||
{ heading = 'Key', key = 'name' },
|
||||
{ heading = 'Value', key = 'value' },
|
||||
},
|
||||
sortColumn = 'name',
|
||||
autospace = true,
|
||||
}),
|
||||
notification = UI.Notification(),
|
||||
})
|
||||
|
||||
function page:setPrompt(value, focus)
|
||||
self.prompt:setValue(value)
|
||||
self.prompt.scroll = 0
|
||||
self.prompt:setPosition(#value)
|
||||
self.prompt:updateScroll()
|
||||
|
||||
if value:sub(-1) == ')' then
|
||||
self.prompt:setPosition(#value - 1)
|
||||
end
|
||||
|
||||
self.prompt:draw()
|
||||
if focus then
|
||||
page:setFocus(self.prompt)
|
||||
end
|
||||
end
|
||||
|
||||
function page:enable()
|
||||
self:setFocus(self.prompt)
|
||||
UI.Page.enable(self)
|
||||
end
|
||||
|
||||
local function autocomplete(env, oLine, x)
|
||||
|
||||
local sLine = oLine:sub(1, x)
|
||||
local nStartPos = sLine:find("[a-zA-Z0-9_%.]+$")
|
||||
if nStartPos then
|
||||
sLine = sLine:sub(nStartPos)
|
||||
end
|
||||
|
||||
if #sLine > 0 then
|
||||
local results = textutils.complete(sLine, env)
|
||||
|
||||
if #results == 0 then
|
||||
-- setError('No completions available')
|
||||
|
||||
elseif #results == 1 then
|
||||
return Util.insertString(oLine, results[1], x + 1)
|
||||
|
||||
elseif #results > 1 then
|
||||
local prefix = results[1]
|
||||
for n = 1, #results do
|
||||
local result = results[n]
|
||||
while #prefix > 0 do
|
||||
if result:find(prefix, 1, true) == 1 then
|
||||
break
|
||||
end
|
||||
prefix = prefix:sub(1, #prefix - 1)
|
||||
end
|
||||
end
|
||||
if #prefix > 0 then
|
||||
return Util.insertString(oLine, prefix, x + 1)
|
||||
else
|
||||
-- setStatus('Too many results')
|
||||
end
|
||||
end
|
||||
end
|
||||
return oLine
|
||||
end
|
||||
|
||||
function page:eventHandler(event)
|
||||
|
||||
if event.type == 'global' then
|
||||
self:setPrompt('', true)
|
||||
self:executeStatement('getfenv(0)')
|
||||
command = nil
|
||||
|
||||
elseif event.type == 'local' then
|
||||
self:setPrompt('', true)
|
||||
self:executeStatement('getfenv(1)')
|
||||
command = nil
|
||||
|
||||
elseif event.type == 'autocomplete' then
|
||||
local sz = #self.prompt.value
|
||||
local pos = self.prompt.pos
|
||||
self:setPrompt(autocomplete(sandboxEnv, self.prompt.value, self.prompt.pos))
|
||||
self.prompt:setPosition(pos + #self.prompt.value - sz)
|
||||
self.prompt:updateCursor()
|
||||
|
||||
elseif event.type == 'device' then
|
||||
if not _G.device then
|
||||
sandboxEnv.device = { }
|
||||
for _,side in pairs(peripheral.getNames()) do
|
||||
local key = string.format('%s:%s', peripheral.getType(side), side)
|
||||
sandboxEnv.device[ key ] = peripheral.wrap(side)
|
||||
end
|
||||
end
|
||||
self:setPrompt('device', true)
|
||||
self:executeStatement('device')
|
||||
|
||||
elseif event.type == 'history_back' then
|
||||
local value = history.back()
|
||||
if value then
|
||||
self:setPrompt(value)
|
||||
end
|
||||
|
||||
elseif event.type == 'history_forward' then
|
||||
self:setPrompt(history.forward() or '')
|
||||
|
||||
elseif event.type == 'clear_prompt' then
|
||||
self:setPrompt('')
|
||||
history.setPosition(#history.entries + 1)
|
||||
|
||||
elseif event.type == 'command_enter' then
|
||||
local s = tostring(self.prompt.value)
|
||||
|
||||
if #s > 0 then
|
||||
history.add(s)
|
||||
self:executeStatement(s)
|
||||
else
|
||||
local t = { }
|
||||
for k = #history.entries, 1, -1 do
|
||||
table.insert(t, {
|
||||
name = #t + 1,
|
||||
value = history.entries[k],
|
||||
isHistory = true,
|
||||
pos = k,
|
||||
})
|
||||
end
|
||||
history.setPosition(#history.entries + 1)
|
||||
command = nil
|
||||
self.grid:setValues(t)
|
||||
self.grid:setIndex(1)
|
||||
self.grid:adjustWidth()
|
||||
self:draw()
|
||||
end
|
||||
return true
|
||||
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function page:setResult(result)
|
||||
local t = { }
|
||||
|
||||
local function safeValue(v)
|
||||
local t = type(v)
|
||||
if t == 'string' or t == 'number' then
|
||||
return v
|
||||
end
|
||||
return tostring(v)
|
||||
end
|
||||
|
||||
if type(result) == 'table' then
|
||||
for k,v in pairs(result) do
|
||||
local entry = {
|
||||
name = safeValue(k),
|
||||
rawName = k,
|
||||
value = safeValue(v),
|
||||
rawValue = v,
|
||||
}
|
||||
if type(v) == 'table' then
|
||||
if Util.size(v) == 0 then
|
||||
entry.value = 'table: (empty)'
|
||||
else
|
||||
entry.value = 'table'
|
||||
end
|
||||
end
|
||||
table.insert(t, entry)
|
||||
end
|
||||
else
|
||||
table.insert(t, {
|
||||
name = type(result),
|
||||
value = tostring(result),
|
||||
rawValue = result,
|
||||
})
|
||||
end
|
||||
self.grid:setValues(t)
|
||||
self.grid:setIndex(1)
|
||||
self.grid:adjustWidth()
|
||||
self:draw()
|
||||
end
|
||||
|
||||
function page.grid:eventHandler(event)
|
||||
|
||||
local entry = self:getSelected()
|
||||
|
||||
local function commandAppend()
|
||||
if entry.isHistory then
|
||||
history.setPosition(entry.pos)
|
||||
return entry.value
|
||||
end
|
||||
if type(entry.rawValue) == 'function' then
|
||||
if command then
|
||||
return command .. '.' .. entry.name .. '()'
|
||||
end
|
||||
return entry.name .. '()'
|
||||
end
|
||||
if command then
|
||||
if type(entry.rawName) == 'number' then
|
||||
return command .. '[' .. entry.name .. ']'
|
||||
end
|
||||
if entry.name:match("%W") or
|
||||
entry.name:sub(1, 1):match("%d") then
|
||||
return command .. "['" .. tostring(entry.name) .. "']"
|
||||
end
|
||||
return command .. '.' .. entry.name
|
||||
end
|
||||
return entry.name
|
||||
end
|
||||
|
||||
if event.type == 'grid_focus_row' then
|
||||
if self.focused then
|
||||
page:setPrompt(commandAppend())
|
||||
end
|
||||
elseif event.type == 'grid_select' then
|
||||
page:setPrompt(commandAppend(), true)
|
||||
page:executeStatement(commandAppend())
|
||||
elseif event.type == 'copy' then
|
||||
if entry then
|
||||
clipboard.setData(entry.rawValue)
|
||||
end
|
||||
else
|
||||
return UI.Grid.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function page:rawExecute(s)
|
||||
|
||||
local fn, m = loadstring("return (" .. s .. ')', 'lua')
|
||||
if not fn then
|
||||
fn, m = loadstring(s, 'lua')
|
||||
end
|
||||
|
||||
if fn then
|
||||
setfenv(fn, sandboxEnv)
|
||||
fn, m = pcall(fn)
|
||||
end
|
||||
|
||||
return fn, m
|
||||
end
|
||||
|
||||
function page:executeStatement(statement)
|
||||
|
||||
command = statement
|
||||
|
||||
local s, m = self:rawExecute(command)
|
||||
|
||||
if s and m then
|
||||
self:setResult(m)
|
||||
elseif s and type(m) == 'boolean' then
|
||||
self:setResult(m)
|
||||
else
|
||||
self.grid:setValues({ })
|
||||
self.grid:draw()
|
||||
if m then
|
||||
self.notification:error(m, 5)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local args = { ... }
|
||||
if args[1] then
|
||||
command = 'args[1]'
|
||||
sandboxEnv.args = args
|
||||
page:setResult(args[1])
|
||||
end
|
||||
|
||||
UI:setPage(page)
|
||||
Event.pullEvents()
|
||||
UI.term:reset()
|
148
sys/apps/Network.lua
Normal file
148
sys/apps/Network.lua
Normal file
@@ -0,0 +1,148 @@
|
||||
require = requireInjector(getfenv(1))
|
||||
local Event = require('event')
|
||||
local UI = require('ui')
|
||||
local Socket = require('socket')
|
||||
|
||||
multishell.setTitle(multishell.getCurrent(), 'Network')
|
||||
UI:configure('Network', ...)
|
||||
|
||||
local gridColumns = {
|
||||
{ heading = 'Label', key = 'label' },
|
||||
{ heading = 'Dist', key = 'distance' },
|
||||
{ heading = 'Status', key = 'status' },
|
||||
}
|
||||
|
||||
if UI.term.width >= 30 then
|
||||
table.insert(gridColumns, { heading = 'Fuel', key = 'fuel' })
|
||||
table.insert(gridColumns, { heading = 'Uptime', key = 'uptime' })
|
||||
end
|
||||
|
||||
local page = UI.Page {
|
||||
menuBar = UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = 'Telnet', event = 'telnet' },
|
||||
{ text = 'VNC', event = 'vnc' },
|
||||
{ text = 'Trust', event = 'trust' },
|
||||
{ text = 'Reboot', event = 'reboot' },
|
||||
},
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 2,
|
||||
values = network,
|
||||
columns = gridColumns,
|
||||
sortColumn = 'label',
|
||||
autospace = true,
|
||||
},
|
||||
notification = UI.Notification { },
|
||||
accelerators = {
|
||||
q = 'quit',
|
||||
c = 'clear',
|
||||
},
|
||||
}
|
||||
|
||||
local function sendCommand(host, command)
|
||||
|
||||
if not device.wireless_modem then
|
||||
page.notification:error('Wireless modem not present')
|
||||
return
|
||||
end
|
||||
|
||||
page.notification:info('Connecting')
|
||||
page:sync()
|
||||
|
||||
local socket = Socket.connect(host, 161)
|
||||
if socket then
|
||||
socket:write({ type = command })
|
||||
socket:close()
|
||||
page.notification:success('Command sent')
|
||||
else
|
||||
page.notification:error('Failed to connect')
|
||||
end
|
||||
end
|
||||
|
||||
function page:eventHandler(event)
|
||||
local t = self.grid:getSelected()
|
||||
if t then
|
||||
if event.type == 'telnet' or event.type == 'grid_select' then
|
||||
multishell.openTab({
|
||||
path = '/apps/telnet.lua',
|
||||
focused = true,
|
||||
args = { t.id },
|
||||
title = t.label,
|
||||
})
|
||||
elseif event.type == 'vnc' then
|
||||
multishell.openTab({
|
||||
path = '/apps/vnc.lua',
|
||||
focused = true,
|
||||
args = { t.id },
|
||||
title = t.label,
|
||||
})
|
||||
elseif event.type == 'trust' then
|
||||
shell.openForegroundTab('trust ' .. t.id)
|
||||
elseif event.type == 'reboot' then
|
||||
sendCommand(t.id, 'reboot')
|
||||
elseif event.type == 'shutdown' then
|
||||
sendCommand(t.id, 'shutdown')
|
||||
end
|
||||
end
|
||||
if event.type == 'quit' then
|
||||
Event.exitPullEvents()
|
||||
end
|
||||
UI.Page.eventHandler(self, event)
|
||||
end
|
||||
|
||||
function page.grid:getRowTextColor(row, selected)
|
||||
if not row.active then
|
||||
return colors.orange
|
||||
end
|
||||
return UI.Grid.getRowTextColor(self, row, selected)
|
||||
end
|
||||
|
||||
function page.grid:getDisplayValues(row)
|
||||
row = Util.shallowCopy(row)
|
||||
if row.uptime then
|
||||
if row.uptime < 60 then
|
||||
row.uptime = string.format("%ds", math.floor(row.uptime))
|
||||
else
|
||||
row.uptime = string.format("%sm", math.floor(row.uptime/6)/10)
|
||||
end
|
||||
end
|
||||
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
|
||||
|
||||
Event.addThread(function()
|
||||
while true do
|
||||
page.grid:update()
|
||||
page.grid:draw()
|
||||
page:sync()
|
||||
os.sleep(1)
|
||||
end
|
||||
end)
|
||||
|
||||
Event.addHandler('device_attach', function(h, deviceName)
|
||||
if deviceName == 'wireless_modem' then
|
||||
page.notification:success('Modem connected')
|
||||
page:sync()
|
||||
end
|
||||
end)
|
||||
|
||||
Event.addHandler('device_detach', function(h, deviceName)
|
||||
if deviceName == 'wireless_modem' then
|
||||
page.notification:error('Wireless modem not attached')
|
||||
page:sync()
|
||||
end
|
||||
end)
|
||||
|
||||
if not device.wireless_modem then
|
||||
page.notification:error('Wireless modem not attached')
|
||||
end
|
||||
|
||||
UI:setPage(page)
|
||||
Event.pullEvents()
|
||||
UI.term:reset()
|
478
sys/apps/Overview.lua
Normal file
478
sys/apps/Overview.lua
Normal file
@@ -0,0 +1,478 @@
|
||||
require = requireInjector(getfenv(1))
|
||||
local Util = require('util')
|
||||
local Event = require('event')
|
||||
local UI = require('ui')
|
||||
local Config = require('config')
|
||||
local NFT = require('nft')
|
||||
local class = require('class')
|
||||
local FileUI = require('fileui')
|
||||
local Tween = require('tween')
|
||||
|
||||
multishell.setTitle(multishell.getCurrent(), 'Overview')
|
||||
UI:configure('Overview', ...)
|
||||
|
||||
local config = {
|
||||
Recent = { },
|
||||
currentCategory = 'Apps',
|
||||
}
|
||||
local applications = { }
|
||||
|
||||
Config.load('Overview', config)
|
||||
|
||||
local function loadApplications()
|
||||
Util.clear(applications)
|
||||
local apps = fs.list('sys/apps/.overview')
|
||||
for _,app in pairs(apps) do
|
||||
local data = Util.readTable('sys/apps/.overview/' .. app)
|
||||
if data then
|
||||
data.filename = 'sys/apps/.overview/' .. app
|
||||
table.insert(applications, data)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
loadApplications()
|
||||
|
||||
local defaultIcon = NFT.parse([[
|
||||
8071180
|
||||
8007180
|
||||
7180071]])
|
||||
|
||||
local sx, sy = term.current().getSize()
|
||||
local maxRecent = math.ceil(sx * sy / 62)
|
||||
|
||||
local function elipse(s, len)
|
||||
if #s > len then
|
||||
s = s:sub(1, len - 2) .. '..'
|
||||
end
|
||||
return s
|
||||
end
|
||||
|
||||
local buttons = { }
|
||||
local categories = { }
|
||||
table.insert(buttons, { text = 'Recent', event = 'category' })
|
||||
for _,f in pairs(applications) do
|
||||
if not categories[f.category] then
|
||||
categories[f.category] = true
|
||||
table.insert(buttons, { text = f.category, event = 'category' })
|
||||
end
|
||||
end
|
||||
table.insert(buttons, { text = '+', event = 'new' })
|
||||
|
||||
local function parseIcon(iconText)
|
||||
local icon
|
||||
|
||||
local s, m = pcall(function()
|
||||
icon = NFT.parse(iconText)
|
||||
if icon then
|
||||
if icon.height > 3 or icon.width > 8 then
|
||||
error('Invalid size')
|
||||
end
|
||||
end
|
||||
return icon
|
||||
end)
|
||||
|
||||
if s then
|
||||
return icon
|
||||
end
|
||||
|
||||
return s, m
|
||||
end
|
||||
|
||||
local page = UI.Page {
|
||||
tabBar = UI.TabBar {
|
||||
buttons = buttons,
|
||||
},
|
||||
container = UI.ViewportWindow {
|
||||
y = 2,
|
||||
},
|
||||
notification = UI.Notification(),
|
||||
accelerators = {
|
||||
r = 'refresh',
|
||||
e = 'edit',
|
||||
s = 'shell',
|
||||
l = 'lua',
|
||||
[ 'control-l' ] = 'refresh',
|
||||
[ 'control-n' ] = 'new',
|
||||
delete = 'delete',
|
||||
},
|
||||
}
|
||||
|
||||
function page:draw()
|
||||
self.tabBar:draw()
|
||||
self.container:draw()
|
||||
end
|
||||
|
||||
UI.Icon = class(UI.Window)
|
||||
function UI.Icon:init(args)
|
||||
local defaults = {
|
||||
UIElement = 'Icon',
|
||||
width = 14,
|
||||
height = 4,
|
||||
}
|
||||
UI.setProperties(defaults, args)
|
||||
UI.Window.init(self, defaults)
|
||||
end
|
||||
|
||||
function UI.Icon:eventHandler(event)
|
||||
if event.type == 'mouse_click' then
|
||||
self:setFocus(self.button)
|
||||
return true
|
||||
elseif event.type == 'mouse_doubleclick' then
|
||||
self:emit({ type = self.button.event, button = self.button })
|
||||
elseif event.type == 'mouse_rightclick' then
|
||||
self:setFocus(self.button)
|
||||
self:emit({ type = 'edit', button = self.button })
|
||||
end
|
||||
return UI.Window.eventHandler(self, event)
|
||||
end
|
||||
|
||||
function page.container:setCategory(categoryName)
|
||||
|
||||
-- reset the viewport window
|
||||
self.children = { }
|
||||
self.offy = 0
|
||||
|
||||
local function filter(it, f)
|
||||
local ot = { }
|
||||
for _,v in pairs(it) do
|
||||
if f(v) then
|
||||
table.insert(ot, v)
|
||||
end
|
||||
end
|
||||
return ot
|
||||
end
|
||||
|
||||
local filtered
|
||||
|
||||
if categoryName == 'Recent' then
|
||||
filtered = { }
|
||||
|
||||
for _,v in ipairs(config.Recent) do
|
||||
local app = Util.find(applications, 'run', v)
|
||||
if app and fs.exists(app.run) then
|
||||
table.insert(filtered, app)
|
||||
end
|
||||
end
|
||||
|
||||
else
|
||||
filtered = filter(applications, function(a)
|
||||
return a.category == categoryName -- and fs.exists(a.run)
|
||||
end)
|
||||
table.sort(filtered, function(a, b) return a.title < b.title end)
|
||||
end
|
||||
|
||||
for _,program in ipairs(filtered) do
|
||||
|
||||
local icon
|
||||
if program.icon then
|
||||
icon = parseIcon(program.icon)
|
||||
end
|
||||
if not icon then
|
||||
icon = defaultIcon
|
||||
end
|
||||
|
||||
local title = elipse(program.title, 8)
|
||||
|
||||
local width = math.max(icon.width + 2, #title + 2)
|
||||
table.insert(self.children, UI.Icon({
|
||||
width = width,
|
||||
image = UI.NftImage({
|
||||
x = math.floor((width - icon.width) / 2) + 1,
|
||||
image = icon,
|
||||
width = 5,
|
||||
height = 3,
|
||||
}),
|
||||
button = UI.Button({
|
||||
x = math.floor((width - #title - 2) / 2) + 1,
|
||||
y = 4,
|
||||
text = title,
|
||||
backgroundColor = self.backgroundColor,
|
||||
width = #title + 2,
|
||||
event = 'button',
|
||||
app = program,
|
||||
}),
|
||||
}))
|
||||
end
|
||||
|
||||
local gutter = 2
|
||||
if UI.term.width <= 26 then
|
||||
gutter = 1
|
||||
end
|
||||
local col, row = gutter, 2
|
||||
local count = #self.children
|
||||
|
||||
-- reposition all children
|
||||
for k,child in ipairs(self.children) do
|
||||
child.x = -10
|
||||
child.y = math.floor(self.height)
|
||||
child.tween = Tween.new(6, child, { x = col, y = row }, 'outSine')
|
||||
|
||||
if k < count then
|
||||
col = col + child.width
|
||||
if col + self.children[k + 1].width + gutter - 2 > UI.term.width then
|
||||
col = gutter
|
||||
row = row + 5
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self:initChildren()
|
||||
self.animate = true
|
||||
end
|
||||
|
||||
function page.container:draw()
|
||||
if self.animate then
|
||||
self.animate = false
|
||||
for i = 1, 6 do
|
||||
for _,child in ipairs(self.children) do
|
||||
child.tween:update(1)
|
||||
child.x = math.floor(child.x)
|
||||
child.y = math.floor(child.y)
|
||||
end
|
||||
UI.ViewportWindow.draw(self)
|
||||
self:sync()
|
||||
os.sleep()
|
||||
end
|
||||
else
|
||||
UI.ViewportWindow.draw(self)
|
||||
end
|
||||
end
|
||||
|
||||
function page:refresh()
|
||||
local pos = self.container.offy
|
||||
self:focusFirst(self)
|
||||
self.container:setCategory(config.currentCategory)
|
||||
self.container:setScrollPosition(pos)
|
||||
end
|
||||
|
||||
function page:resize()
|
||||
self:refresh()
|
||||
UI.Page.resize(self)
|
||||
end
|
||||
|
||||
function page:eventHandler(event)
|
||||
|
||||
if event.type == 'category' then
|
||||
self.tabBar:selectTab(event.button.text)
|
||||
self.container:setCategory(event.button.text)
|
||||
self.container:draw()
|
||||
self:sync()
|
||||
|
||||
config.currentCategory = event.button.text
|
||||
Config.update('Overview', config)
|
||||
|
||||
elseif event.type == 'button' then
|
||||
for k,v in ipairs(config.Recent) do
|
||||
if v == event.button.app.run then
|
||||
table.remove(config.Recent, k)
|
||||
break
|
||||
end
|
||||
end
|
||||
table.insert(config.Recent, 1, event.button.app.run)
|
||||
if #config.Recent > maxRecent then
|
||||
table.remove(config.Recent, maxRecent + 1)
|
||||
end
|
||||
Config.update('Overview', config)
|
||||
multishell.openTab({
|
||||
path = 'sys/apps/shell',
|
||||
args = { event.button.app.run },
|
||||
focused = true,
|
||||
})
|
||||
|
||||
elseif event.type == 'shell' then
|
||||
multishell.openTab({
|
||||
path = 'sys/apps/shell',
|
||||
focused = true,
|
||||
})
|
||||
|
||||
elseif event.type == 'lua' then
|
||||
multishell.openTab({
|
||||
path = 'sys/apps/Lua.lua',
|
||||
focused = true,
|
||||
})
|
||||
|
||||
elseif event.type == 'focus_change' then
|
||||
if event.focused.parent.UIElement == 'Icon' then
|
||||
event.focused.parent:scrollIntoView()
|
||||
end
|
||||
|
||||
elseif event.type == 'tab_change' then
|
||||
if event.current > event.last then
|
||||
--self.container:setTransition(UI.effect.slideLeft)
|
||||
else
|
||||
--self.container:setTransition(UI.effect.slideRight)
|
||||
end
|
||||
|
||||
elseif event.type == 'refresh' then
|
||||
loadApplications()
|
||||
self:refresh()
|
||||
self:draw()
|
||||
self.notification:success('Refreshed')
|
||||
|
||||
elseif event.type == 'delete' then
|
||||
local focused = page:getFocused()
|
||||
if focused.app then
|
||||
fs.delete(focused.app.filename)
|
||||
loadApplications()
|
||||
page:refresh()
|
||||
page:draw()
|
||||
self.notification:success('Removed')
|
||||
end
|
||||
|
||||
elseif event.type == 'new' then
|
||||
local category = 'Apps'
|
||||
if config.currentCategory ~= 'Recent' then
|
||||
category = config.currentCategory or 'Apps'
|
||||
end
|
||||
UI:setPage('editor', { category = category })
|
||||
|
||||
elseif event.type == 'edit' then
|
||||
local focused = page:getFocused()
|
||||
if focused.app then
|
||||
UI:setPage('editor', focused.app)
|
||||
end
|
||||
|
||||
else
|
||||
UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local formWidth = math.max(UI.term.width - 14, 26)
|
||||
|
||||
local editor = UI.Dialog {
|
||||
height = 11,
|
||||
width = formWidth,
|
||||
title = 'Edit application',
|
||||
form = UI.Form {
|
||||
y = 2,
|
||||
height = 9,
|
||||
title = UI.TextEntry {
|
||||
formLabel = 'Title', formKey = 'title', limit = 11, help = 'Application title',
|
||||
required = true,
|
||||
},
|
||||
run = UI.TextEntry {
|
||||
formLabel = 'Run', formKey = 'run', limit = 100, help = 'Full path to application',
|
||||
required = true,
|
||||
},
|
||||
category = UI.TextEntry {
|
||||
formLabel = 'Category', formKey = 'category', limit = 11, help = 'Category of application',
|
||||
required = true,
|
||||
},
|
||||
loadIcon = UI.Button {
|
||||
x = 11, y = 6,
|
||||
text = 'Icon', event = 'loadIcon', help = 'Select icon'
|
||||
},
|
||||
image = UI.NftImage {
|
||||
y = 6,
|
||||
x = 2,
|
||||
height = 3,
|
||||
width = 8,
|
||||
},
|
||||
},
|
||||
statusBar = UI.StatusBar(),
|
||||
iconFile = '',
|
||||
}
|
||||
|
||||
function editor:enable(app)
|
||||
if app then
|
||||
self.form:setValues(app)
|
||||
|
||||
local icon
|
||||
if app.icon then
|
||||
icon = parseIcon(app.icon)
|
||||
end
|
||||
self.form.image:setImage(icon)
|
||||
end
|
||||
UI.Page.enable(self)
|
||||
self:focusFirst()
|
||||
end
|
||||
|
||||
function editor.form.image:draw()
|
||||
self:clear()
|
||||
UI.NftImage.draw(self)
|
||||
end
|
||||
|
||||
function editor:updateApplications(app)
|
||||
if not app.filename then
|
||||
app.filename = 'sys/apps/.overview/' .. app.title
|
||||
end
|
||||
Util.writeTable(app.filename, app)
|
||||
loadApplications()
|
||||
end
|
||||
|
||||
function editor:eventHandler(event)
|
||||
|
||||
if event.type == 'form_cancel' or event.type == 'cancel' then
|
||||
UI:setPreviousPage()
|
||||
|
||||
elseif event.type == 'focus_change' then
|
||||
self.statusBar:setStatus(event.focused.help or '')
|
||||
self.statusBar:draw()
|
||||
|
||||
elseif event.type == 'loadIcon' then
|
||||
local fileui = FileUI({
|
||||
x = self.x,
|
||||
y = self.y,
|
||||
z = 2,
|
||||
width = self.width,
|
||||
height = self.height,
|
||||
})
|
||||
--fileui:setTransition(UI.effect.explode)
|
||||
UI:setPage(fileui, fs.getDir(self.iconFile), function(fileName)
|
||||
if fileName then
|
||||
self.iconFile = fileName
|
||||
local s, m = pcall(function()
|
||||
local iconLines = Util.readFile(fileName)
|
||||
if not iconLines then
|
||||
error('Unable to load file')
|
||||
end
|
||||
local icon, m = parseIcon(iconLines)
|
||||
if not icon then
|
||||
error(m)
|
||||
end
|
||||
self.form.values.icon = iconLines
|
||||
self.form.image:setImage(icon)
|
||||
self.form.image:draw()
|
||||
end)
|
||||
if not s and m then
|
||||
local msg = m:gsub('.*: (.*)', '%1')
|
||||
page.notification:error(msg)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
elseif event.type == 'form_invalid' then
|
||||
page.notification:error(event.message)
|
||||
|
||||
elseif event.type == 'form_complete' then
|
||||
local values = self.form.values
|
||||
UI:setPreviousPage()
|
||||
self:updateApplications(values)
|
||||
page:refresh()
|
||||
page:draw()
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
UI:setPages({
|
||||
editor = editor,
|
||||
main = page,
|
||||
})
|
||||
|
||||
Event.addHandler('os_register_app', function()
|
||||
loadApplications()
|
||||
page:refresh()
|
||||
page:draw()
|
||||
page:sync()
|
||||
end)
|
||||
|
||||
page.tabBar:selectTab(config.currentCategory or 'Apps')
|
||||
page.container:setCategory(config.currentCategory or 'Apps')
|
||||
UI:setPage(page)
|
||||
|
||||
Event.pullEvents()
|
||||
UI.term:reset()
|
219
sys/apps/Peripherals.lua
Normal file
219
sys/apps/Peripherals.lua
Normal file
@@ -0,0 +1,219 @@
|
||||
local injector = requireInjector or load(http.get('http://pastebin.com/raw/c0TWsScv').readAll())()
|
||||
require = injector(getfenv(1))
|
||||
local Util = require('util')
|
||||
local Event = require('event')
|
||||
local UI = require('ui')
|
||||
|
||||
multishell.setTitle(multishell.getCurrent(), 'Devices')
|
||||
|
||||
--[[ -- PeripheralsPage -- ]] --
|
||||
local peripheralsPage = UI.Page {
|
||||
grid = UI.ScrollingGrid {
|
||||
columns = {
|
||||
{ heading = 'Type', key = 'type' },
|
||||
{ heading = 'Side', key = 'side' },
|
||||
},
|
||||
sortColumn = 'type',
|
||||
height = UI.term.height - 1,
|
||||
autospace = true,
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
status = '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 {
|
||||
grid = UI.ScrollingGrid {
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'name', width = UI.term.width }
|
||||
},
|
||||
sortColumn = 'name',
|
||||
height = 7,
|
||||
},
|
||||
viewportConsole = UI.ViewportWindow {
|
||||
y = 8,
|
||||
height = UI.term.height - 8,
|
||||
backgroundColor = colors.brown,
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
status = 'q to return',
|
||||
},
|
||||
accelerators = {
|
||||
q = 'back',
|
||||
backspace = 'back',
|
||||
},
|
||||
}
|
||||
|
||||
function methodsPage:enable(p)
|
||||
|
||||
self.peripheral = p or self.peripheral
|
||||
|
||||
local p = peripheral.wrap(self.peripheral.side)
|
||||
if p.getDocs then
|
||||
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
|
||||
self.grid.values = { }
|
||||
for name,f in pairs(p) do
|
||||
table.insert(self.grid.values, {
|
||||
name = name,
|
||||
noext = true,
|
||||
})
|
||||
end
|
||||
else
|
||||
self.grid.values = p.getAdvancedMethodsData()
|
||||
for name,f in pairs(self.grid.values) do
|
||||
f.name = name
|
||||
end
|
||||
end
|
||||
|
||||
self.viewportConsole.offy = 0
|
||||
|
||||
self.grid:update()
|
||||
self.grid:setIndex(1)
|
||||
|
||||
self.statusBar:setStatus(self.peripheral.type)
|
||||
UI.Page.enable(self)
|
||||
end
|
||||
|
||||
function methodsPage:eventHandler(event)
|
||||
if event.type == 'back' then
|
||||
UI:setPage(peripheralsPage)
|
||||
return true
|
||||
elseif event.type == 'grid_focus_row' then
|
||||
self.viewportConsole.offy = 0
|
||||
self.viewportConsole:draw()
|
||||
end
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
|
||||
function methodsPage.viewportConsole:draw()
|
||||
local c = self
|
||||
local method = methodsPage.grid:getSelected()
|
||||
|
||||
c:clear()
|
||||
c:setCursorPos(1, 1)
|
||||
|
||||
if method.noext then
|
||||
c.cursorY = 2
|
||||
c:print('No extended Information')
|
||||
return 2
|
||||
end
|
||||
|
||||
if method.doc then
|
||||
c:print(method.doc, nil, colors.yellow)
|
||||
c.ymax = c.cursorY + 1
|
||||
return
|
||||
end
|
||||
|
||||
if method.description then
|
||||
c:print(method.description)
|
||||
end
|
||||
|
||||
c.cursorY = c.cursorY + 2
|
||||
c.cursorX = 1
|
||||
|
||||
if method.returnTypes ~= '()' then
|
||||
c:print(method.returnTypes .. ' ', nil, colors.yellow)
|
||||
end
|
||||
c:print(method.name, nil, colors.black)
|
||||
c:print('(')
|
||||
|
||||
local maxArgLen = 1
|
||||
|
||||
for k,arg in ipairs(method.args) do
|
||||
if #arg.description > 0 then
|
||||
maxArgLen = math.max(#arg.name, maxArgLen)
|
||||
end
|
||||
local argName = arg.name
|
||||
local fg = colors.green
|
||||
if arg.optional then
|
||||
argName = string.format('[%s]', arg.name)
|
||||
fg = colors.orange
|
||||
end
|
||||
c:print(argName, nil, fg)
|
||||
if k < #method.args then
|
||||
c:print(', ')
|
||||
end
|
||||
end
|
||||
c:print(')')
|
||||
|
||||
c.cursorY = c.cursorY + 1
|
||||
|
||||
if #method.args > 0 then
|
||||
for _,arg in ipairs(method.args) do
|
||||
if #arg.description > 0 then
|
||||
c.cursorY = c.cursorY + 1
|
||||
c.cursorX = 1
|
||||
local fg = colors.green
|
||||
if arg.optional then
|
||||
fg = colors.orange
|
||||
end
|
||||
c:print(arg.name .. ': ', nil, fg)
|
||||
c.cursorX = maxArgLen + 3
|
||||
c:print(arg.description, nil, nil, maxArgLen + 3)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
c.ymax = c.cursorY + 1
|
||||
end
|
||||
|
||||
Event.addHandler('peripheral', function()
|
||||
peripheralsPage:updatePeripherals()
|
||||
end)
|
||||
|
||||
Event.addHandler('peripheral_detach', function()
|
||||
peripheralsPage:updatePeripherals()
|
||||
end)
|
||||
|
||||
UI:setPage(peripheralsPage)
|
||||
|
||||
UI:setPages({
|
||||
methods = methodsPage,
|
||||
})
|
||||
|
||||
Event.pullEvents()
|
||||
UI.term:reset()
|
105
sys/apps/Pim.lua
Normal file
105
sys/apps/Pim.lua
Normal file
@@ -0,0 +1,105 @@
|
||||
require = requireInjector(getfenv(1))
|
||||
local Event = require('event')
|
||||
local UI = require('ui')
|
||||
local Config = require('config')
|
||||
|
||||
multishell.setTitle(multishell.getCurrent(), 'PIM')
|
||||
|
||||
local inventory = { }
|
||||
local mode = 'sync'
|
||||
|
||||
if not device.pim then
|
||||
error('PIM not attached')
|
||||
end
|
||||
|
||||
local page = UI.Page({
|
||||
menu = UI.Menu({
|
||||
centered = true,
|
||||
y = 2,
|
||||
menuItems = {
|
||||
{ prompt = 'Learn', event = 'learn', help = '' },
|
||||
},
|
||||
}),
|
||||
statusBar = UI.StatusBar({
|
||||
columns = {
|
||||
{ 'Status', 'status', UI.term.width - 7 },
|
||||
{ 'Mode', 'mode', 7 }
|
||||
}
|
||||
}),
|
||||
accelerators = {
|
||||
q = 'quit',
|
||||
},
|
||||
})
|
||||
|
||||
local function learn()
|
||||
if device.pim.getInventorySize() > 0 then
|
||||
local stacks = device.pim.getAllStacks(false)
|
||||
Config.update('pim', stacks)
|
||||
mode = 'sync'
|
||||
page.statusBar:setValue('status', 'Learned inventory')
|
||||
end
|
||||
page.statusBar:setValue('mode', mode)
|
||||
page.statusBar:draw()
|
||||
end
|
||||
|
||||
function page:eventHandler(event)
|
||||
|
||||
if event.type == 'learn' then
|
||||
mode = 'learn'
|
||||
learn()
|
||||
elseif event.type == 'quit' then
|
||||
Event.exitPullEvents()
|
||||
end
|
||||
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
|
||||
local function inInventory(s)
|
||||
for _,i in pairs(inventory) do
|
||||
if i.id == s.id then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function pimWatcher()
|
||||
local playerOn = false
|
||||
|
||||
while true do
|
||||
if device.pim.getInventorySize() > 0 and not playerOn then
|
||||
playerOn = true
|
||||
|
||||
if mode == 'learn' then
|
||||
learn()
|
||||
|
||||
else
|
||||
local stacks = device.pim.getAllStacks(false)
|
||||
for k,stack in pairs(stacks) do
|
||||
if not inInventory(stack) then
|
||||
device.pim.pushItem('down', k, stack.qty)
|
||||
end
|
||||
end
|
||||
page.statusBar:setValue('status', 'Synchronized')
|
||||
page.statusBar:draw()
|
||||
end
|
||||
|
||||
elseif device.pim.getInventorySize() == 0 and playerOn then
|
||||
page.statusBar:setValue('status', 'No player')
|
||||
page.statusBar:draw()
|
||||
playerOn = false
|
||||
end
|
||||
os.sleep(1)
|
||||
end
|
||||
end
|
||||
|
||||
Config.load('pim', inventory)
|
||||
|
||||
if Util.empty(inventory) then
|
||||
mode = 'learn'
|
||||
end
|
||||
page.statusBar:setValue('mode', mode)
|
||||
|
||||
UI:setPage(page)
|
||||
|
||||
Event.pullEvents(pimWatcher)
|
||||
UI.term:reset()
|
565
sys/apps/Script.lua
Normal file
565
sys/apps/Script.lua
Normal file
@@ -0,0 +1,565 @@
|
||||
require = requireInjector(getfenv(1))
|
||||
local Event = require('event')
|
||||
local UI = require('ui')
|
||||
local Socket = require('socket')
|
||||
local Config = require('config')
|
||||
|
||||
local GROUPS_PATH = 'usr/groups'
|
||||
local SCRIPTS_PATH = 'usr/scripts'
|
||||
|
||||
multishell.setTitle(multishell.getCurrent(), 'Script')
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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({
|
||||
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-3,
|
||||
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 - 3,
|
||||
width = width,
|
||||
x = UI.term.width - width + 1,
|
||||
y = 2,
|
||||
}),
|
||||
statusBar = UI.StatusBar({
|
||||
columns = {
|
||||
{ '', 'status', 4 },
|
||||
{ '', 'fuelF', 5 },
|
||||
{ '', 'distanceF', 4 },
|
||||
},
|
||||
autospace = true,
|
||||
}),
|
||||
accelerators = {
|
||||
q = 'quit',
|
||||
},
|
||||
})
|
||||
|
||||
local editorPage = UI.Page({
|
||||
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({
|
||||
-- x = (UI.term.width - 28) / 2,
|
||||
width = 22,
|
||||
title = 'Enter Name',
|
||||
form = UI.Form {
|
||||
x = 2, rex = -2, y = 2,
|
||||
textEntry = UI.TextEntry({ y = 3, width = 20, limit = 20 })
|
||||
},
|
||||
})
|
||||
|
||||
dialog.eventHandler = function(self, 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
|
||||
fs.delete(fs.combine(GROUPS_PATH, self.grid:getSelected().label))
|
||||
self:draw()
|
||||
|
||||
elseif event.type == 'edit' then
|
||||
editorPage.groupName = self.grid:getSelected().label
|
||||
UI:setPage(editorPage)
|
||||
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
|
||||
local text = 'Computers'
|
||||
if config.showGroups then
|
||||
text = 'Groups'
|
||||
end
|
||||
-- self.statusBar.toggleButton.text = text
|
||||
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()
|
||||
local computer = self.values[1]
|
||||
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
|
||||
mainPage.statusBar:adjustWidth()
|
||||
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.addHandler('network_attach', function()
|
||||
if mainPage.enabled then
|
||||
mainPage:draw()
|
||||
end
|
||||
end)
|
||||
|
||||
Event.addHandler('network_detach', function()
|
||||
if mainPage.enabled then
|
||||
mainPage:draw()
|
||||
end
|
||||
end)
|
||||
|
||||
function statusUpdate()
|
||||
while true do
|
||||
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
|
||||
os.sleep(1)
|
||||
end
|
||||
end
|
||||
|
||||
UI:setPage(mainPage)
|
||||
|
||||
Event.pullEvents(statusUpdate)
|
||||
UI.term:reset()
|
186
sys/apps/System.lua
Normal file
186
sys/apps/System.lua
Normal file
@@ -0,0 +1,186 @@
|
||||
require = requireInjector(getfenv(1))
|
||||
local Event = require('event')
|
||||
local UI = require('ui')
|
||||
local Config = require('config')
|
||||
|
||||
multishell.setTitle(multishell.getCurrent(), 'System')
|
||||
UI:configure('System', ...)
|
||||
|
||||
local env = {
|
||||
path = shell.path(),
|
||||
aliases = shell.aliases(),
|
||||
lua_path = LUA_PATH,
|
||||
}
|
||||
Config.load('multishell', env)
|
||||
|
||||
UI.TextEntry.defaults.backgroundFocusColor = colors.black
|
||||
|
||||
local systemPage = UI.Page {
|
||||
backgroundColor = colors.blue,
|
||||
tabs = UI.Tabs {
|
||||
pathTab = UI.Window {
|
||||
tabTitle = 'Path',
|
||||
entry = UI.TextEntry {
|
||||
x = 2, y = 2, rex = -2,
|
||||
limit = 256,
|
||||
value = shell.path(),
|
||||
shadowText = 'enter system path',
|
||||
accelerators = {
|
||||
enter = 'update_path',
|
||||
},
|
||||
},
|
||||
grid = UI.Grid {
|
||||
y = 4,
|
||||
values = paths,
|
||||
disableHeader = true,
|
||||
columns = { { key = 'value' } },
|
||||
autospace = true,
|
||||
},
|
||||
},
|
||||
|
||||
aliasTab = UI.Window {
|
||||
tabTitle = 'Aliases',
|
||||
alias = UI.TextEntry {
|
||||
x = 2, y = 2, rex = -2,
|
||||
limit = 32,
|
||||
shadowText = 'Alias',
|
||||
},
|
||||
path = UI.TextEntry {
|
||||
y = 3, x = 2, rex = -2,
|
||||
limit = 256,
|
||||
shadowText = 'Program path',
|
||||
accelerators = {
|
||||
enter = 'new_alias',
|
||||
},
|
||||
},
|
||||
grid = UI.Grid {
|
||||
y = 5,
|
||||
values = aliases,
|
||||
autospace = true,
|
||||
sortColumn = 'alias',
|
||||
columns = {
|
||||
{ heading = 'Alias', key = 'alias' },
|
||||
{ heading = 'Program', key = 'path' },
|
||||
},
|
||||
accelerators = {
|
||||
delete = 'delete_alias',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
infoTab = UI.Window {
|
||||
tabTitle = 'Info',
|
||||
labelText = UI.Text {
|
||||
x = 3, y = 2,
|
||||
value = 'Label'
|
||||
},
|
||||
label = UI.TextEntry {
|
||||
x = 9, y = 2, rex = -4,
|
||||
limit = 32,
|
||||
value = os.getComputerLabel(),
|
||||
backgroundFocusColor = colors.black,
|
||||
accelerators = {
|
||||
enter = 'update_label',
|
||||
},
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 4,
|
||||
values = {
|
||||
{ name = 'CC version', value = os.getVersion() },
|
||||
{ name = 'Lua version', value = _VERSION },
|
||||
{ name = 'MC version', value = _MC_VERSION or 'unknown' },
|
||||
{ name = 'Disk free', value = Util.toBytes(fs.getFreeSpace('/')) },
|
||||
{ name = 'Computer ID', value = tostring(os.getComputerID()) },
|
||||
{ name = 'Day', value = tostring(os.day()) },
|
||||
},
|
||||
selectable = false,
|
||||
backgroundColor = colors.blue,
|
||||
columns = {
|
||||
{ key = 'name', width = 12 },
|
||||
{ key = 'value', width = UI.term.width - 15 },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
notification = UI.Notification(),
|
||||
accelerators = {
|
||||
q = 'quit',
|
||||
},
|
||||
}
|
||||
|
||||
function systemPage.tabs.pathTab.grid:draw()
|
||||
self.values = { }
|
||||
for _,v in ipairs(Util.split(env.path, '(.-):')) do
|
||||
table.insert(self.values, { value = v })
|
||||
end
|
||||
self:update()
|
||||
UI.Grid.draw(self)
|
||||
end
|
||||
|
||||
function systemPage.tabs.pathTab:eventHandler(event)
|
||||
|
||||
if event.type == 'update_path' then
|
||||
env.path = self.entry.value
|
||||
self.grid:setIndex(self.grid:getIndex())
|
||||
self.grid:draw()
|
||||
Config.update('multishell', env)
|
||||
systemPage.notification:success('reboot to take effect')
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function systemPage.tabs.aliasTab.grid:draw()
|
||||
self.values = { }
|
||||
local aliases = { }
|
||||
for k,v in pairs(env.aliases) do
|
||||
table.insert(self.values, { alias = k, path = v })
|
||||
end
|
||||
self:update()
|
||||
UI.Grid.draw(self)
|
||||
end
|
||||
|
||||
function systemPage.tabs.aliasTab:eventHandler(event)
|
||||
|
||||
if event.type == 'delete_alias' then
|
||||
env.aliases[self.grid:getSelected().alias] = nil
|
||||
self.grid:setIndex(self.grid:getIndex())
|
||||
self.grid:draw()
|
||||
Config.update('multishell', env)
|
||||
systemPage.notification:success('reboot to take effect')
|
||||
return true
|
||||
|
||||
elseif event.type == 'new_alias' then
|
||||
env.aliases[self.alias.value] = self.path.value
|
||||
self.alias:reset()
|
||||
self.path:reset()
|
||||
self:draw()
|
||||
self:setFocus(self.alias)
|
||||
Config.update('multishell', env)
|
||||
systemPage.notification:success('reboot to take effect')
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function systemPage.tabs.infoTab:eventHandler(event)
|
||||
if event.type == 'update_label' then
|
||||
os.setComputerLabel(self.label.value)
|
||||
systemPage.notification:success('Label updated')
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function systemPage:eventHandler(event)
|
||||
|
||||
if event.type == 'quit' then
|
||||
Event.exitPullEvents()
|
||||
elseif event.type == 'tab_activate' then
|
||||
event.activated:focusFirst()
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
UI:setPage(systemPage)
|
||||
Event.pullEvents()
|
||||
UI.term:reset()
|
73
sys/apps/Tabs.lua
Normal file
73
sys/apps/Tabs.lua
Normal file
@@ -0,0 +1,73 @@
|
||||
require = requireInjector(getfenv(1))
|
||||
local UI = require('ui')
|
||||
local Event = require('event')
|
||||
|
||||
multishell.setTitle(multishell.getCurrent(), 'Tabs')
|
||||
UI:configure('Tabs', ...)
|
||||
|
||||
local page = UI.Page {
|
||||
menuBar = UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = 'Activate', event = 'activate' },
|
||||
{ text = 'Terminate', event = 'terminate' },
|
||||
},
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 2,
|
||||
columns = {
|
||||
{ heading = 'ID', key = 'tabId' },
|
||||
{ heading = 'Title', key = 'title' },
|
||||
{ heading = 'Status', key = 'status' },
|
||||
{ heading = 'Time', key = 'timestamp' },
|
||||
},
|
||||
values = multishell.getTabs(),
|
||||
sortColumn = 'title',
|
||||
autospace = true,
|
||||
},
|
||||
accelerators = {
|
||||
q = 'quit',
|
||||
space = 'activate',
|
||||
t = 'terminate',
|
||||
},
|
||||
}
|
||||
|
||||
function page:eventHandler(event)
|
||||
local t = self.grid:getSelected()
|
||||
if t then
|
||||
if event.type == 'activate' or event.type == 'grid_select' then
|
||||
multishell.setFocus(t.tabId)
|
||||
elseif event.type == 'terminate' then
|
||||
multishell.terminate(t.tabId)
|
||||
end
|
||||
end
|
||||
if event.type == 'quit' then
|
||||
Event.exitPullEvents()
|
||||
end
|
||||
UI.Page.eventHandler(self, event)
|
||||
end
|
||||
|
||||
function page.grid:getDisplayValues(row)
|
||||
row = Util.shallowCopy(row)
|
||||
local elapsed = os.clock()-row.timestamp
|
||||
if elapsed < 60 then
|
||||
row.timestamp = string.format("%ds", math.floor(elapsed))
|
||||
else
|
||||
row.timestamp = string.format("%fm", math.floor(elapsed/6)/10)
|
||||
end
|
||||
if row.isDead then
|
||||
row.status = 'error'
|
||||
else
|
||||
row.status = coroutine.status(row.co)
|
||||
end
|
||||
return row
|
||||
end
|
||||
|
||||
Event.addTimer(1, true, function()
|
||||
page.grid:update()
|
||||
page.grid:draw()
|
||||
page:sync()
|
||||
end)
|
||||
|
||||
UI:setPage(page)
|
||||
Event.pullEvents()
|
||||
UI.term:reset()
|
328
sys/apps/Turtles.lua
Normal file
328
sys/apps/Turtles.lua
Normal file
@@ -0,0 +1,328 @@
|
||||
require = requireInjector(getfenv(1))
|
||||
local UI = require('ui')
|
||||
local Socket = require('socket')
|
||||
local Terminal = require('terminal')
|
||||
local TableDB = require('tableDB')
|
||||
|
||||
multishell.setTitle(multishell.getCurrent(), 'Turtles')
|
||||
UI.Button.defaults.focusIndicator = ' '
|
||||
UI:configure('Turtles', ...)
|
||||
|
||||
local options = {
|
||||
turtle = { arg = 'i', type = 'number', value = -1,
|
||||
desc = 'Turtle ID' },
|
||||
tab = { arg = 's', type = 'string', value = 'inventory',
|
||||
desc = 'Selected tab to display' },
|
||||
help = { arg = 'h', type = 'flag', value = false,
|
||||
desc = 'Displays the options' },
|
||||
}
|
||||
|
||||
local SCRIPTS_PATH = '/apps/scripts'
|
||||
|
||||
local nullTerm = Terminal.getNullTerm(term.current())
|
||||
local turtles = { }
|
||||
local policies = {
|
||||
{ label = 'none' },
|
||||
{ label = 'digOnly' },
|
||||
{ label = 'attackOnly' },
|
||||
{ label = 'digAttack' },
|
||||
{ label = 'turtleSafe' },
|
||||
}
|
||||
|
||||
local itemInfoDB = TableDB({
|
||||
fileName = 'items.db'
|
||||
})
|
||||
|
||||
itemInfoDB:load()
|
||||
|
||||
local page = UI.Page {
|
||||
moveUp = UI.Button {
|
||||
x = 5, y = 2,
|
||||
text = '/\\',
|
||||
fn = 'turtle.up',
|
||||
},
|
||||
moveDown = UI.Button {
|
||||
x = 5, y = 4,
|
||||
text = '\\/',
|
||||
fn = 'turtle.down',
|
||||
},
|
||||
moveForward = UI.Button {
|
||||
x = 9, y = 3,
|
||||
text = '>',
|
||||
fn = 'turtle.forward',
|
||||
},
|
||||
moveBack = UI.Button {
|
||||
x = 2, y = 3,
|
||||
text = '<',
|
||||
fn = 'turtle.back',
|
||||
},
|
||||
turnLeft = UI.Button {
|
||||
x = 2, y = 6,
|
||||
text = '<-',
|
||||
fn = 'turtle.turnLeft',
|
||||
},
|
||||
turnRight = UI.Button {
|
||||
x = 8, y = 6,
|
||||
text = '->',
|
||||
fn = 'turtle.turnRight',
|
||||
},
|
||||
--[[
|
||||
policy = UI.Chooser {
|
||||
x = 2, y = 8,
|
||||
choices = {
|
||||
{ name = ' None ', value = 'none' },
|
||||
{ name = ' Safe ', value = 'turtleSafe' },
|
||||
},
|
||||
},
|
||||
]]
|
||||
coords = UI.Window {
|
||||
x = 14, y = 2, height = 5, rex = -2,
|
||||
},
|
||||
tabs = UI.Tabs {
|
||||
x = 1, y = 8, rey = -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 = 'Sel',
|
||||
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 = 'qty', width = 2 },
|
||||
{ heading = 'Inventory', key = 'id', width = UI.term.width - 5 },
|
||||
},
|
||||
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,
|
||||
},
|
||||
},
|
||||
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)
|
||||
|
||||
local socket = Socket.connect(self.turtle.id, 161)
|
||||
if not socket then
|
||||
self.notification:error('Unable to connect')
|
||||
return
|
||||
end
|
||||
|
||||
if not nowrap then
|
||||
script = 'turtle.run(' .. script .. ')'
|
||||
end
|
||||
socket:write({ type = 'script', args = script })
|
||||
socket:close()
|
||||
end
|
||||
|
||||
function page:runScript(scriptName)
|
||||
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)
|
||||
end
|
||||
|
||||
function page.coords:draw()
|
||||
local t = self.parent.turtle
|
||||
if t then
|
||||
self:clear()
|
||||
self:setCursorPos(1, 1)
|
||||
self:print(string.format('%s\nx: %d\ny: %d\nz: %d\nFuel: %s\n',
|
||||
t.coordSystem, 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
|
||||
local item = itemInfoDB:get({ v.id, v.dmg })
|
||||
if item then
|
||||
v.id = item.displayName
|
||||
else
|
||||
v.id = v.id:gsub('.*:(.*)', '%1')
|
||||
end
|
||||
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 _,f in pairs(files) do
|
||||
table.insert(self.values, { label = f, path = fs.combine(SCRIPTS_PATH, f) })
|
||||
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
|
||||
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:eventHandler(event)
|
||||
if event.type == 'quit' then
|
||||
UI:exitPullEvents()
|
||||
elseif event.type == 'button_press' then
|
||||
if event.button.fn then
|
||||
self:runFunction(event.button.fn, event.button.nowrap)
|
||||
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
|
||||
|
||||
local function updateThread()
|
||||
|
||||
while true do
|
||||
if page.turtle then
|
||||
local t = _G.network[page.turtle.id]
|
||||
page.turtle = t
|
||||
page:draw()
|
||||
page:sync()
|
||||
end
|
||||
|
||||
os.sleep(1)
|
||||
end
|
||||
end
|
||||
|
||||
if not Util.getOptions(options, { ... }, true) then
|
||||
return
|
||||
end
|
||||
|
||||
if options.turtle.value >= 0 then
|
||||
for i = 1, 10 do
|
||||
page.turtle = _G.network[options.turtle.value]
|
||||
if page.turtle then
|
||||
break
|
||||
end
|
||||
os.sleep(1)
|
||||
end
|
||||
end
|
||||
|
||||
UI:setPage(page)
|
||||
|
||||
page.tabs:activateTab(page.tabs[options.tab.value])
|
||||
|
||||
UI:pullEvents(updateThread)
|
||||
UI.term:reset()
|
35
sys/apps/base64dl.lua
Normal file
35
sys/apps/base64dl.lua
Normal file
@@ -0,0 +1,35 @@
|
||||
require = requireInjector(getfenv(1))
|
||||
Base64 = require('base64')
|
||||
|
||||
local args = { ... }
|
||||
|
||||
if not args[2] then
|
||||
error('Syntax: base64dl <file name> <url>')
|
||||
end
|
||||
|
||||
local c = http.get(args[2])
|
||||
|
||||
if not c then
|
||||
error('unable to open url')
|
||||
end
|
||||
|
||||
local data = c.readAll()
|
||||
c.close()
|
||||
|
||||
print('size: ' .. #data)
|
||||
local decoded = Base64.decode(data)
|
||||
print('decoded: ' .. #decoded)
|
||||
|
||||
local file = io.open(shell.resolve(args[1]), "wb")
|
||||
if not file then
|
||||
error('Unable to open ' .. args[1], 2)
|
||||
end
|
||||
for k,b in ipairs(decoded) do
|
||||
if (k % 1000) == 0 then
|
||||
os.sleep(0)
|
||||
end
|
||||
file:write(b)
|
||||
end
|
||||
|
||||
file:close()
|
||||
print('done')
|
2155
sys/apps/builder.lua
Normal file
2155
sys/apps/builder.lua
Normal file
File diff suppressed because it is too large
Load Diff
24
sys/apps/cat.lua
Normal file
24
sys/apps/cat.lua
Normal file
@@ -0,0 +1,24 @@
|
||||
local args = { ... }
|
||||
if #args < 1 then
|
||||
error('cat <filename>')
|
||||
end
|
||||
|
||||
local fileName = shell.resolve(args[1])
|
||||
if not fs.exists(fileName) then
|
||||
error('not a file: ' .. args[1])
|
||||
end
|
||||
|
||||
local file = fs.open(fileName, 'r')
|
||||
if not file then
|
||||
error('unable to open ' .. args[1])
|
||||
end
|
||||
|
||||
while true do
|
||||
local line = file.readLine()
|
||||
if not line then
|
||||
break
|
||||
end
|
||||
print(line)
|
||||
end
|
||||
|
||||
file.close()
|
1270
sys/apps/edit.lua
Normal file
1270
sys/apps/edit.lua
Normal file
File diff suppressed because it is too large
Load Diff
41
sys/apps/itemsDB.lua
Normal file
41
sys/apps/itemsDB.lua
Normal file
@@ -0,0 +1,41 @@
|
||||
local injector = requireInjector or load(http.get('http://pastebin.com/raw/c0TWsScv').readAll())()
|
||||
require = injector(getfenv(1))
|
||||
|
||||
local RefinedProvider = require('refinedProvider')
|
||||
local TableDB = require('tableDB')
|
||||
|
||||
local controller = RefinedProvider()
|
||||
if not controller:isValid() then
|
||||
error('Refined storage controller not found')
|
||||
end
|
||||
|
||||
local itemInfoDB = TableDB({
|
||||
fileName = 'items.db'
|
||||
})
|
||||
|
||||
itemInfoDB:load()
|
||||
|
||||
local items = controller:listItems()
|
||||
|
||||
local keys = {
|
||||
'fields',
|
||||
'damage',
|
||||
'displayName',
|
||||
'maxCount',
|
||||
'maxDamage',
|
||||
'name',
|
||||
'nbtHash',
|
||||
'rawName',
|
||||
}
|
||||
|
||||
for _, item in pairs(items) do
|
||||
|
||||
local t = { }
|
||||
for _,key in pairs(keys) do
|
||||
t[key] = item[key]
|
||||
end
|
||||
|
||||
itemInfoDB:add({ item.name, item.damage, item.nbtHash }, t)
|
||||
end
|
||||
|
||||
itemInfoDB:flush()
|
102
sys/apps/logMonitor.lua
Normal file
102
sys/apps/logMonitor.lua
Normal file
@@ -0,0 +1,102 @@
|
||||
require = requireInjector(getfenv(1))
|
||||
local Event = require('event')
|
||||
local Message = require('message')
|
||||
local UI = require('ui')
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
local function logWriter()
|
||||
while true do
|
||||
os.pullEvent('logMessage')
|
||||
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
|
||||
end
|
||||
|
||||
Message.addHandler('log', function(h, id, msg)
|
||||
table.insert(messages, { id = id, text = msg.contents })
|
||||
os.queueEvent('logMessage')
|
||||
end)
|
||||
|
||||
Event.addHandler('monitor_touch', function()
|
||||
terminal:reset()
|
||||
ids = { }
|
||||
end)
|
||||
|
||||
Event.addHandler('mouse_click', function()
|
||||
terminal:reset()
|
||||
ids = { }
|
||||
end)
|
||||
|
||||
Event.addHandler('char', function()
|
||||
Event.exitPullEvents()
|
||||
end)
|
||||
|
||||
Event.pullEvents(logWriter)
|
||||
terminal:reset()
|
24
sys/apps/mirror.lua
Normal file
24
sys/apps/mirror.lua
Normal file
@@ -0,0 +1,24 @@
|
||||
require = requireInjector(getfenv(1))
|
||||
local Terminal = require('terminal')
|
||||
|
||||
local args = { ... }
|
||||
local mon = device[table.remove(args, 1) or 'monitor']
|
||||
if not mon then
|
||||
error('mirror: Invalid device')
|
||||
end
|
||||
|
||||
mon.clear()
|
||||
mon.setTextScale(.5)
|
||||
mon.setCursorPos(1, 1)
|
||||
|
||||
local oterm = Terminal.copy(term.current())
|
||||
Terminal.mirror(term.current(), mon)
|
||||
|
||||
term.current().getSize = mon.getSize
|
||||
|
||||
if #args > 0 then
|
||||
shell.run(unpack(args))
|
||||
Terminal.copy(oterm, term.current())
|
||||
|
||||
mon.setCursorBlink(false)
|
||||
end
|
83
sys/apps/mirrorClient.lua
Normal file
83
sys/apps/mirrorClient.lua
Normal file
@@ -0,0 +1,83 @@
|
||||
require = requireInjector(getfenv(1))
|
||||
local Socket = require('socket')
|
||||
local Terminal = require('terminal')
|
||||
local Logger = require('logger')
|
||||
local process = require('process')
|
||||
|
||||
Logger.setScreenLogging()
|
||||
|
||||
local remoteId
|
||||
local args = { ... }
|
||||
if #args == 1 then
|
||||
remoteId = tonumber(args[1])
|
||||
else
|
||||
print('Enter host ID')
|
||||
remoteId = tonumber(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 = { }
|
||||
os.queueEvent('mirror_flush')
|
||||
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 = process:pullEvent('mirror_flush')
|
||||
if e == 'terminate' then
|
||||
break
|
||||
end
|
||||
if not socket.connected then
|
||||
break
|
||||
end
|
||||
if socket.queue then
|
||||
socket:write(socket.queue)
|
||||
socket.queue = nil
|
||||
end
|
||||
end
|
||||
|
||||
for k,v in pairs(socket.oldTerm) do
|
||||
socket.term[k] = v
|
||||
end
|
||||
|
||||
socket:close()
|
||||
|
||||
end
|
55
sys/apps/mirrorHost.lua
Normal file
55
sys/apps/mirrorHost.lua
Normal file
@@ -0,0 +1,55 @@
|
||||
require = requireInjector(getfenv(1))
|
||||
local Socket = require('socket')
|
||||
local Logger = require('logger')
|
||||
local process = require('process')
|
||||
|
||||
Logger.setScreenLogging()
|
||||
|
||||
local args = { ... }
|
||||
local mon = device[args[1] or 'monitor']
|
||||
|
||||
if not mon then
|
||||
error('Monitor not attached')
|
||||
end
|
||||
|
||||
mon.setBackgroundColor(colors.black)
|
||||
mon.clear()
|
||||
|
||||
while true do
|
||||
local socket = Socket.server(5901)
|
||||
|
||||
print('mirror: connection from ' .. socket.dhost)
|
||||
|
||||
local updateThread = process:newThread('updateThread', 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)
|
||||
|
||||
-- ensure socket is connected
|
||||
process:newThread('pinger', function()
|
||||
while true do
|
||||
os.sleep(3)
|
||||
if not socket:ping() then
|
||||
break
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
while true do
|
||||
process:pullEvent('modem_message')
|
||||
if updateThread:isDead() then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
print('connection lost')
|
||||
|
||||
socket:close()
|
||||
end
|
605
sys/apps/multishell
Normal file
605
sys/apps/multishell
Normal file
@@ -0,0 +1,605 @@
|
||||
-- Default label
|
||||
if not os.getComputerLabel() then
|
||||
local id = os.getComputerID()
|
||||
if turtle then
|
||||
os.setComputerLabel('turtle_' .. id)
|
||||
elseif pocket then
|
||||
os.setComputerLabel('pocket_' .. id)
|
||||
else
|
||||
os.setComputerLabel('computer_' .. id)
|
||||
end
|
||||
end
|
||||
|
||||
multishell.term = term.current()
|
||||
|
||||
local defaultEnv = Util.shallowCopy(getfenv(1))
|
||||
|
||||
require = requireInjector(getfenv(1))
|
||||
local Config = require('config')
|
||||
|
||||
-- Begin multishell
|
||||
local parentTerm = term.current()
|
||||
local w,h = parentTerm.getSize()
|
||||
local tabs = {}
|
||||
local currentTab
|
||||
local _tabId = 0
|
||||
local overviewTab
|
||||
local runningTab
|
||||
local tabsDirty = false
|
||||
|
||||
local config = {
|
||||
standard = {
|
||||
focusTextColor = colors.lightGray,
|
||||
focusBackgroundColor = colors.gray,
|
||||
textColor = colors.black,
|
||||
backgroundColor = colors.lightGray,
|
||||
tabBarTextColor = colors.black,
|
||||
tabBarBackgroundColor = colors.lightGray,
|
||||
},
|
||||
color = {
|
||||
focusTextColor = colors.white,
|
||||
focusBackgroundColor = colors.brown,
|
||||
textColor = colors.gray,
|
||||
backgroundColor = colors.brown,
|
||||
tabBarTextColor = colors.lightGray,
|
||||
tabBarBackgroundColor = colors.brown,
|
||||
},
|
||||
-- path = '.:/apps:' .. shell.path():sub(3),
|
||||
path = 'usr/apps:sys/apps:' .. shell.path(),
|
||||
}
|
||||
|
||||
Config.load('multishell', config)
|
||||
|
||||
shell.setPath(config.path)
|
||||
|
||||
if config.aliases then
|
||||
for k in pairs(shell.aliases()) do
|
||||
shell.clearAlias(k)
|
||||
end
|
||||
for k,v in pairs(config.aliases) do
|
||||
shell.setAlias(k, v)
|
||||
end
|
||||
end
|
||||
|
||||
local _colors = config.standard
|
||||
if parentTerm.isColor() then
|
||||
_colors = config.color
|
||||
end
|
||||
|
||||
local function redrawMenu()
|
||||
if not tabsDirty then
|
||||
os.queueEvent('multishell', 'draw')
|
||||
tabsDirty = true
|
||||
end
|
||||
end
|
||||
|
||||
-- Draw menu
|
||||
local function draw()
|
||||
tabsDirty = false
|
||||
|
||||
parentTerm.setBackgroundColor( _colors.tabBarBackgroundColor )
|
||||
if currentTab and currentTab.isOverview then
|
||||
parentTerm.setTextColor( _colors.focusTextColor )
|
||||
else
|
||||
parentTerm.setTextColor( _colors.tabBarTextColor )
|
||||
end
|
||||
parentTerm.setCursorPos( 1, 1 )
|
||||
parentTerm.clearLine()
|
||||
parentTerm.write('+')
|
||||
|
||||
local tabX = 2
|
||||
local function compareTab(a, b)
|
||||
return a.tabId < b.tabId
|
||||
end
|
||||
for _,tab in Util.spairs(tabs, compareTab) do
|
||||
|
||||
if tab.hidden and tab ~= currentTab or tab.isOverview then
|
||||
tab.sx = nil
|
||||
tab.ex = nil
|
||||
else
|
||||
tab.sx = tabX + 1
|
||||
tab.ex = tabX + #tab.title
|
||||
tabX = tabX + #tab.title + 1
|
||||
end
|
||||
end
|
||||
for _,tab in Util.spairs(tabs) do
|
||||
if tab.sx then
|
||||
if tab == currentTab then
|
||||
parentTerm.setTextColor(_colors.focusTextColor)
|
||||
parentTerm.setBackgroundColor(_colors.focusBackgroundColor)
|
||||
else
|
||||
parentTerm.setTextColor(_colors.textColor)
|
||||
parentTerm.setBackgroundColor(_colors.backgroundColor)
|
||||
end
|
||||
parentTerm.setCursorPos(tab.sx, 1)
|
||||
parentTerm.write(tab.title)
|
||||
end
|
||||
end
|
||||
if currentTab and not currentTab.isOverview then
|
||||
parentTerm.setTextColor(_colors.textColor)
|
||||
parentTerm.setBackgroundColor(_colors.backgroundColor)
|
||||
parentTerm.setCursorPos( w, 1 )
|
||||
parentTerm.write('*')
|
||||
end
|
||||
|
||||
if currentTab then
|
||||
currentTab.window.restoreCursor()
|
||||
end
|
||||
end
|
||||
|
||||
local function selectTab( tab )
|
||||
if not tab then
|
||||
for _,ftab in pairs(tabs) do
|
||||
if not ftab.hidden then
|
||||
tab = ftab
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not tab then
|
||||
tab = overviewTab
|
||||
end
|
||||
|
||||
if currentTab and currentTab ~= tab then
|
||||
currentTab.window.setVisible(false)
|
||||
if tab and not currentTab.hidden then
|
||||
tab.previousTabId = currentTab.tabId
|
||||
end
|
||||
end
|
||||
|
||||
if tab then
|
||||
currentTab = tab
|
||||
tab.window.setVisible(true)
|
||||
end
|
||||
end
|
||||
|
||||
local function resumeTab(tab, event, eventData)
|
||||
if not tab or coroutine.status(tab.co) == 'dead' then
|
||||
return
|
||||
end
|
||||
|
||||
if not tab.filter or tab.filter == event or event == "terminate" then
|
||||
eventData = eventData or { }
|
||||
term.redirect(tab.terminal)
|
||||
local previousTab = runningTab
|
||||
runningTab = tab
|
||||
local ok, result = coroutine.resume(tab.co, event, unpack(eventData))
|
||||
tab.terminal = term.current()
|
||||
if ok then
|
||||
tab.filter = result
|
||||
else
|
||||
printError(result)
|
||||
end
|
||||
|
||||
runningTab = previousTab
|
||||
|
||||
return ok, result
|
||||
end
|
||||
end
|
||||
|
||||
local function nextTabId()
|
||||
_tabId = _tabId + 1
|
||||
return _tabId
|
||||
end
|
||||
|
||||
local function launchProcess(tab)
|
||||
|
||||
tab.tabId = nextTabId()
|
||||
tab.timestamp = os.clock()
|
||||
tab.window = window.create(parentTerm, 1, 2, w, h - 1, false)
|
||||
tab.terminal = tab.window
|
||||
tab.env = Util.shallowCopy(tab.env or defaultEnv)
|
||||
|
||||
tab.co = coroutine.create(function()
|
||||
|
||||
local result, err
|
||||
|
||||
if tab.fn then
|
||||
result, err = Util.runFunction(tab.env, tab.fn, table.unpack(tab.args or { } ))
|
||||
elseif tab.path then
|
||||
result, err = os.run(tab.env, tab.path, table.unpack(tab.args or { } ))
|
||||
else
|
||||
err = 'multishell: invalid tab'
|
||||
end
|
||||
|
||||
if not result and err ~= 'Terminated' then
|
||||
if err then
|
||||
printError(tostring(err))
|
||||
end
|
||||
printError('Press enter to exit')
|
||||
tab.isDead = true
|
||||
while true do
|
||||
local e, code = os.pullEventRaw('key')
|
||||
if e == 'terminate' or e == 'key' and code == keys.enter then
|
||||
if tab.isOverview then
|
||||
os.queueEvent('multishell', 'terminate')
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
tabs[tab.tabId] = nil
|
||||
if tab == currentTab then
|
||||
local previousTab
|
||||
if tab.previousTabId then
|
||||
previousTab = tabs[tab.previousTabId]
|
||||
end
|
||||
selectTab(previousTab)
|
||||
end
|
||||
redrawMenu()
|
||||
end)
|
||||
|
||||
tabs[tab.tabId] = tab
|
||||
|
||||
resumeTab(tab)
|
||||
|
||||
return tab
|
||||
end
|
||||
|
||||
local function resizeWindows()
|
||||
local windowY = 2
|
||||
local windowHeight = h-1
|
||||
|
||||
local keys = Util.keys(tabs)
|
||||
for _,key in pairs(keys) do
|
||||
local tab = tabs[key]
|
||||
local x,y = tab.window.getCursorPos()
|
||||
if y > windowHeight then
|
||||
tab.window.scroll( y - windowHeight )
|
||||
tab.window.setCursorPos( x, windowHeight )
|
||||
end
|
||||
tab.window.reposition( 1, windowY, w, windowHeight )
|
||||
end
|
||||
|
||||
-- Pass term_resize to all processes
|
||||
local keys = Util.keys(tabs)
|
||||
for _,key in pairs(keys) do
|
||||
resumeTab(tabs[key], "term_resize")
|
||||
end
|
||||
end
|
||||
|
||||
local control
|
||||
local hotkeys = { }
|
||||
|
||||
local function processKeyEvent(event, code)
|
||||
if event == 'key_up' then
|
||||
if code == keys.leftCtrl or code == keys.rightCtrl then
|
||||
control = false
|
||||
end
|
||||
elseif event == 'char' then
|
||||
control = false
|
||||
elseif event == 'key' then
|
||||
if code == keys.leftCtrl or code == keys.rightCtrl then
|
||||
control = true
|
||||
elseif control then
|
||||
local hotkey = hotkeys[code]
|
||||
control = false
|
||||
if hotkey then
|
||||
hotkey()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function multishell.addHotkey(code, fn)
|
||||
hotkeys[code] = fn
|
||||
end
|
||||
|
||||
function multishell.removeHotkey(code)
|
||||
hotkeys[code] = nil
|
||||
end
|
||||
|
||||
function multishell.getFocus()
|
||||
return currentTab.tabId
|
||||
end
|
||||
|
||||
function multishell.setFocus(tabId)
|
||||
local tab = tabs[tabId]
|
||||
if tab then
|
||||
selectTab(tab)
|
||||
redrawMenu()
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function multishell.getTitle(tabId)
|
||||
local tab = tabs[tabId]
|
||||
if tab then
|
||||
return tab.title
|
||||
end
|
||||
end
|
||||
|
||||
function multishell.setTitle(tabId, sTitle)
|
||||
local tab = tabs[tabId]
|
||||
if tab then
|
||||
tab.title = sTitle or ''
|
||||
redrawMenu()
|
||||
end
|
||||
end
|
||||
|
||||
function multishell.getCurrent()
|
||||
if runningTab then
|
||||
return runningTab.tabId
|
||||
end
|
||||
end
|
||||
|
||||
function multishell.getTab(tabId)
|
||||
return tabs[tabId]
|
||||
end
|
||||
|
||||
function multishell.terminate(tabId)
|
||||
local tab = tabs[tabId]
|
||||
if tab and not tab.isOverview then
|
||||
if coroutine.status(tab.co) ~= 'dead' then
|
||||
--os.queueEvent('multishell', 'terminate', tab)
|
||||
resumeTab(tab, "terminate")
|
||||
else
|
||||
tabs[tabId] = nil
|
||||
if tab == currentTab then
|
||||
local previousTab
|
||||
if tab.previousTabId then
|
||||
previousTab = tabs[tab.previousTabId]
|
||||
end
|
||||
selectTab(previousTab)
|
||||
end
|
||||
redrawMenu()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function multishell.getTabs()
|
||||
return tabs
|
||||
end
|
||||
|
||||
function multishell.launch( tProgramEnv, sProgramPath, ... )
|
||||
-- backwards compatibility
|
||||
return multishell.openTab({
|
||||
env = tProgramEnv,
|
||||
path = sProgramPath,
|
||||
args = { ... },
|
||||
})
|
||||
end
|
||||
|
||||
function multishell.openTab(tab)
|
||||
|
||||
if not tab.title and tab.path then
|
||||
tab.title = fs.getName(tab.path)
|
||||
end
|
||||
|
||||
tab.title = tab.title or 'untitled'
|
||||
|
||||
local previousTerm = term.current()
|
||||
launchProcess(tab)
|
||||
term.redirect(previousTerm)
|
||||
|
||||
if tab.hidden then
|
||||
if coroutine.status(tab.co) == 'dead' or tab.isDead then
|
||||
tab.hidden = false
|
||||
end
|
||||
elseif tab.focused then
|
||||
multishell.setFocus(tab.tabId)
|
||||
else
|
||||
redrawMenu()
|
||||
end
|
||||
return tab.tabId
|
||||
end
|
||||
|
||||
function multishell.hideTab(tabId)
|
||||
local tab = tabs[tabId]
|
||||
if tab then
|
||||
tab.hidden = true
|
||||
redrawMenu()
|
||||
end
|
||||
end
|
||||
|
||||
function multishell.unhideTab(tabId)
|
||||
local tab = tabs[tabId]
|
||||
if tab then
|
||||
tab.hidden = false
|
||||
redrawMenu()
|
||||
end
|
||||
end
|
||||
|
||||
function multishell.getCount()
|
||||
local count
|
||||
for _,tab in pairs(tabs) do
|
||||
count = count + 1
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
-- control-o - overview
|
||||
multishell.addHotkey(24, function()
|
||||
multishell.setFocus(overviewTab.tabId)
|
||||
end)
|
||||
|
||||
-- control-backspace
|
||||
multishell.addHotkey(14, function()
|
||||
local tabId = multishell.getFocus()
|
||||
local tab = tabs[tabId]
|
||||
if not tab.isOverview then
|
||||
os.queueEvent('multishell', 'terminateTab', tabId)
|
||||
tab = Util.shallowCopy(tab)
|
||||
tab.isDead = false
|
||||
tab.focused = true
|
||||
multishell.openTab(tab)
|
||||
end
|
||||
end)
|
||||
|
||||
-- control-tab - next tab
|
||||
multishell.addHotkey(15, function()
|
||||
local function compareTab(a, b)
|
||||
return a.tabId < b.tabId
|
||||
end
|
||||
local visibleTabs = { }
|
||||
for _,tab in Util.spairs(tabs, compareTab) do
|
||||
if not tab.hidden then
|
||||
table.insert(visibleTabs, tab)
|
||||
end
|
||||
end
|
||||
for k,tab in ipairs(visibleTabs) do
|
||||
if tab.tabId == currentTab.tabId then
|
||||
if k < #visibleTabs then
|
||||
multishell.setFocus(visibleTabs[k + 1].tabId)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
if #visibleTabs > 0 then
|
||||
multishell.setFocus(visibleTabs[1].tabId)
|
||||
end
|
||||
end)
|
||||
|
||||
local function startup()
|
||||
local hasError
|
||||
|
||||
local function runDir(directory, desc, open)
|
||||
if not fs.exists(directory) then
|
||||
return
|
||||
end
|
||||
|
||||
local files = fs.list(directory)
|
||||
table.sort(files)
|
||||
|
||||
for _,file in ipairs(files) do
|
||||
print(desc .. file)
|
||||
os.sleep(0)
|
||||
local result, err = open(directory .. '/' .. file)
|
||||
if not result then
|
||||
printError(err)
|
||||
hasError = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
runDir('sys/extensions', '[ ext ] ', shell.run)
|
||||
|
||||
local overviewId = multishell.openTab({
|
||||
path = 'sys/apps/Overview.lua',
|
||||
focused = true,
|
||||
hidden = true,
|
||||
isOverview = true,
|
||||
})
|
||||
overviewTab = tabs[overviewId]
|
||||
|
||||
runDir('sys/services', '[ svc ] ', shell.openHiddenTab)
|
||||
runDir('sys/autorun', '[ aut ] ', shell.run)
|
||||
runDir('usr/autorun', '[ aut ] ', shell.run)
|
||||
|
||||
if hasError then
|
||||
error('An autorun program has errored')
|
||||
end
|
||||
end
|
||||
|
||||
-- Begin
|
||||
parentTerm.clear()
|
||||
|
||||
multishell.openTab({
|
||||
focused = true,
|
||||
fn = startup,
|
||||
env = defaultEnv,
|
||||
title = 'Autorun',
|
||||
})
|
||||
|
||||
if not overviewTab or coroutine.status(overviewTab.co) == 'dead' then
|
||||
--error('Overview aborted')
|
||||
end
|
||||
|
||||
if not currentTab then
|
||||
multishell.setFocus(overviewTab.tabId)
|
||||
end
|
||||
|
||||
draw()
|
||||
|
||||
while true do
|
||||
|
||||
-- Get the event
|
||||
local tEventData = { os.pullEventRaw() }
|
||||
local sEvent = table.remove(tEventData, 1)
|
||||
|
||||
if sEvent == 'key_up' then
|
||||
processKeyEvent(sEvent, tEventData[1])
|
||||
end
|
||||
|
||||
if sEvent == "term_resize" then
|
||||
-- Resize event
|
||||
w,h = parentTerm.getSize()
|
||||
resizeWindows()
|
||||
redrawMenu()
|
||||
|
||||
elseif sEvent == 'multishell' then
|
||||
local action = tEventData[1]
|
||||
|
||||
if action == 'terminate' then
|
||||
break
|
||||
elseif action == 'terminateTab' then
|
||||
multishell.terminate(tEventData[2])
|
||||
elseif action == 'draw' then
|
||||
draw()
|
||||
end
|
||||
|
||||
elseif sEvent == "char" or
|
||||
sEvent == "key" or
|
||||
sEvent == "paste" or
|
||||
sEvent == "terminate" then
|
||||
|
||||
processKeyEvent(sEvent, tEventData[1])
|
||||
|
||||
-- Keyboard event - Passthrough to current process
|
||||
resumeTab(currentTab, sEvent, tEventData)
|
||||
|
||||
elseif sEvent == "mouse_click" then
|
||||
local button, x, y = tEventData[1], tEventData[2], tEventData[3]
|
||||
if y == 1 and os.locked then
|
||||
-- ignore
|
||||
elseif y == 1 then
|
||||
-- Switch process
|
||||
local w, h = parentTerm.getSize()
|
||||
if x == 1 then
|
||||
multishell.setFocus(overviewTab.tabId)
|
||||
elseif x == w then
|
||||
if currentTab then
|
||||
multishell.terminate(currentTab.tabId)
|
||||
end
|
||||
else
|
||||
for _,tab in pairs(tabs) do
|
||||
if not tab.hidden and tab.sx then
|
||||
if x >= tab.sx and x <= tab.ex then
|
||||
multishell.setFocus(tab.tabId)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif currentTab then
|
||||
-- Passthrough to current process
|
||||
resumeTab(currentTab, sEvent, { button, x, y-1 })
|
||||
end
|
||||
|
||||
elseif sEvent == "mouse_drag" or sEvent == "mouse_scroll" then
|
||||
-- Other mouse event
|
||||
local p1, x, y = tEventData[1], tEventData[2], tEventData[3]
|
||||
if currentTab and (y ~= 1) then
|
||||
if currentTab.terminal.scrollUp then
|
||||
if p1 == -1 then
|
||||
currentTab.terminal.scrollUp()
|
||||
else
|
||||
currentTab.terminal.scrollDown()
|
||||
end
|
||||
else
|
||||
-- Passthrough to current process
|
||||
resumeTab(currentTab, sEvent, { p1, x, y-1 })
|
||||
end
|
||||
end
|
||||
|
||||
else
|
||||
-- Other event
|
||||
-- Passthrough to all processes
|
||||
local keys = Util.keys(tabs)
|
||||
for _,key in pairs(keys) do
|
||||
resumeTab(tabs[key], sEvent, tEventData)
|
||||
end
|
||||
end
|
||||
end
|
10
sys/apps/password.lua
Normal file
10
sys/apps/password.lua
Normal file
@@ -0,0 +1,10 @@
|
||||
require = requireInjector(getfenv(1))
|
||||
local SHA1 = require('sha1')
|
||||
local Terminal = require('terminal')
|
||||
|
||||
local password = Terminal.readPassword('Enter new password: ')
|
||||
|
||||
if password then
|
||||
os.updatePassword(SHA1.sha1(password))
|
||||
print('Password updated')
|
||||
end
|
343
sys/apps/pickup.lua
Normal file
343
sys/apps/pickup.lua
Normal file
@@ -0,0 +1,343 @@
|
||||
require = requireInjector(getfenv(1))
|
||||
local GPS = require('gps')
|
||||
local Socket = require('socket')
|
||||
local MEProvider = require('meProvider')
|
||||
local Logger = require('logger')
|
||||
local Point = require('point')
|
||||
local process = require('process')
|
||||
|
||||
if not device.wireless_modem then
|
||||
error('Modem is required')
|
||||
end
|
||||
|
||||
Logger.setWirelessLogging()
|
||||
|
||||
if not turtle then
|
||||
error('Can only be run on a turtle')
|
||||
end
|
||||
|
||||
turtle.clearMoveCallback()
|
||||
|
||||
local gps = GPS.getPointAndHeading()
|
||||
if not gps then
|
||||
error('could not get gps location')
|
||||
end
|
||||
turtle.setPoint(gps)
|
||||
|
||||
local blocks = { }
|
||||
local meProvider = MEProvider()
|
||||
local items = { }
|
||||
|
||||
local pickups = Util.readTable('pickup.tbl') or { }
|
||||
local cells = Util.readTable('cells.tbl') or { }
|
||||
local refills = Util.readTable('refills.tbl') or { }
|
||||
local fluids = Util.readTable('fluids.tbl') or { }
|
||||
local chestPt = turtle.loadLocation('chest')
|
||||
local chargePt = turtle.loadLocation('charge')
|
||||
|
||||
local fuel = {
|
||||
item = {
|
||||
id = 'minecraft:coal',
|
||||
dmg = 0,
|
||||
},
|
||||
qty = 64
|
||||
}
|
||||
|
||||
local slots
|
||||
|
||||
turtle.setMoveCallback(function(action, pt)
|
||||
if slots then
|
||||
for _,slot in pairs(slots) do
|
||||
if turtle.getItemCount(slot.index) ~= slot.qty then
|
||||
printError('Slots changed')
|
||||
process:terminate()
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
function refuel()
|
||||
if turtle.getFuelLevel() < 5000 then
|
||||
print('refueling')
|
||||
turtle.status = 'refueling'
|
||||
gotoPoint(chestPt, true)
|
||||
dropOff(chestPt)
|
||||
while turtle.getFuelLevel() < 5000 do
|
||||
turtle.select(1)
|
||||
meProvider:provide(fuel.item, fuel.qty, 1)
|
||||
turtle.refuel(64)
|
||||
print(turtle.getFuelLevel())
|
||||
os.sleep(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function pickUp(pt)
|
||||
turtle.status = 'picking up'
|
||||
gotoPoint(pt, true)
|
||||
while true do
|
||||
if not turtle.selectOpenSlot() then
|
||||
dropOff(chestPt)
|
||||
gotoPoint(pt, true)
|
||||
end
|
||||
turtle.select(1)
|
||||
if not turtle.suckDown(64) then
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function dropOff(pt)
|
||||
if turtle.selectSlotWithItems() then
|
||||
gotoPoint(pt, true)
|
||||
turtle.emptyInventory(turtle.dropDown)
|
||||
if pt == chestPt then
|
||||
print('refreshing items')
|
||||
items = meProvider:refresh()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function gotoPoint(pt, doDetect)
|
||||
slots = turtle.getInventory()
|
||||
while not turtle.pathfind(pt, blocks) do
|
||||
if turtle.abort then
|
||||
error('aborted')
|
||||
end
|
||||
turtle.status = 'blocked'
|
||||
os.sleep(5)
|
||||
end
|
||||
|
||||
if doDetect and not turtle.detectDown() then
|
||||
error('Missing target')
|
||||
end
|
||||
end
|
||||
|
||||
function checkCell(pt)
|
||||
if not turtle.selectOpenSlot() then
|
||||
dropOff(chestPt)
|
||||
end
|
||||
|
||||
print('checking cell')
|
||||
turtle.status = 'recharging'
|
||||
gotoPoint(pt, true)
|
||||
local c = peripheral.wrap('bottom')
|
||||
local energy = c.getMaxEnergyStored() -
|
||||
c.getEnergyStored()
|
||||
if energy > 20000 then
|
||||
print('charging cell')
|
||||
turtle.selectOpenSlot()
|
||||
turtle.digDown()
|
||||
gotoPoint(chargePt, true)
|
||||
turtle.dropDown()
|
||||
os.sleep(energy / 20000)
|
||||
turtle.suckDown()
|
||||
print('replacing cell')
|
||||
gotoPoint(pt)
|
||||
if not turtle.placeDown() then
|
||||
error('could not place down cell')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function fluid(points)
|
||||
print('checking fluid')
|
||||
turtle.status = 'fluiding'
|
||||
gotoPoint(points.source, true)
|
||||
turtle.select(1)
|
||||
turtle.digDown()
|
||||
gotoPoint(points.target)
|
||||
if not turtle.placeDown() then
|
||||
error('could not place fluid container')
|
||||
end
|
||||
os.sleep(5)
|
||||
turtle.digDown()
|
||||
gotoPoint(points.source)
|
||||
turtle.placeDown()
|
||||
end
|
||||
|
||||
function refill(entry)
|
||||
dropOff(chestPt)
|
||||
|
||||
turtle.status = 'refilling'
|
||||
gotoPoint(chestPt)
|
||||
for _,item in pairs(entry.items) do
|
||||
meProvider:provide(item, tonumber(item.qty), turtle.selectOpenSlot())
|
||||
end
|
||||
|
||||
if turtle.selectSlotWithItems() then
|
||||
if entry.point then
|
||||
dropOff(entry.point)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function oldRefill(points)
|
||||
gotoPoint(points.source)
|
||||
repeat until not turtle.suckDown(64)
|
||||
if points.target then
|
||||
dropOff(points.target)
|
||||
end
|
||||
if points.targets then
|
||||
for k,target in pairs(points.targets) do
|
||||
dropOff(target)
|
||||
end
|
||||
end
|
||||
dropOff(points.source)
|
||||
dropOff(chestPt)
|
||||
end
|
||||
|
||||
local function makeKey(pt)
|
||||
return string.format('%d:%d:%d', pt.x, pt.y, pt.z)
|
||||
end
|
||||
|
||||
local function pickupHost(socket)
|
||||
|
||||
while true do
|
||||
local data = socket:read()
|
||||
if not data then
|
||||
print('pickup: closing connection to ' .. socket.dhost)
|
||||
return
|
||||
end
|
||||
|
||||
print('command: ' .. data.type)
|
||||
|
||||
if data.type == 'pickup' then
|
||||
local key = makeKey(data.point)
|
||||
pickups[key] = data.point
|
||||
Util.writeTable('pickup.tbl', pickups)
|
||||
socket:write( { type = "response", response = 'added' })
|
||||
|
||||
elseif data.type == 'items' then
|
||||
socket:write( { type = "response", response = items })
|
||||
|
||||
elseif data.type == 'refill' then
|
||||
local key = makeKey(data.entry.point)
|
||||
refills[key] = data.entry
|
||||
Util.writeTable('refills.tbl', refills)
|
||||
socket:write( { type = "response", response = 'added' })
|
||||
|
||||
elseif data.type == 'setPickup' then
|
||||
chestPt = data.point
|
||||
turtle.storeLocation('chest', chestPt)
|
||||
socket:write( { type = "response", response = 'Location set' })
|
||||
|
||||
elseif data.type == 'setRecharge' then
|
||||
chargePt = data.point
|
||||
turtle.storeLocation('charge', chargePt)
|
||||
socket:write( { type = "response", response = 'Location set' })
|
||||
|
||||
elseif data.type == 'charge' then
|
||||
local key = makeKey(data.point)
|
||||
cells[key] = data.point
|
||||
Util.writeTable('cells.tbl', cells)
|
||||
socket:write( { type = "response", response = 'added' })
|
||||
|
||||
elseif data.type == 'fluid' then
|
||||
|
||||
elseif data.type == 'clear' then
|
||||
local key = makeKey(data.point)
|
||||
refills[key] = nil
|
||||
cells[key] = nil
|
||||
fluids[key] = nil
|
||||
pickups[key] = nil
|
||||
|
||||
Util.writeTable('refills.tbl', refills)
|
||||
Util.writeTable('cells.tbl', cells)
|
||||
Util.writeTable('fluids.tbl', fluids)
|
||||
Util.writeTable('pickup.tbl', pickups)
|
||||
|
||||
socket:write( { type = "response", response = 'cleared' })
|
||||
else
|
||||
print('unknown command')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
process:newThread('pickup', function()
|
||||
while true do
|
||||
print('waiting for connection on port 5222')
|
||||
local socket = Socket.server(5222)
|
||||
|
||||
print('pickup: connection from ' .. socket.dhost)
|
||||
|
||||
process:newThread('pickup_connection', function() pickupHost(socket) end)
|
||||
end
|
||||
end)
|
||||
|
||||
local function eachEntry(t, fn)
|
||||
|
||||
local keys = Util.keys(t)
|
||||
for _,key in pairs(keys) do
|
||||
if t[key] then
|
||||
if turtle.abort then
|
||||
return
|
||||
end
|
||||
fn(t[key])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function eachClosestEntry(t, fn)
|
||||
|
||||
local points = { }
|
||||
for k,v in pairs(t) do
|
||||
v = Util.shallowCopy(v)
|
||||
v.key = k
|
||||
table.insert(points, v)
|
||||
end
|
||||
|
||||
while not Util.empty(points) do
|
||||
local closest = Point.closest(turtle.point, points)
|
||||
if turtle.abort then
|
||||
return
|
||||
end
|
||||
if t[closest.key] then
|
||||
fn(closest)
|
||||
end
|
||||
for k,v in pairs(points) do
|
||||
if v.key == closest.key then
|
||||
table.remove(points, k)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
refuel()
|
||||
turtle.abort = false
|
||||
|
||||
local deliveryThread = process:newThread('deliveries', function()
|
||||
|
||||
while true do
|
||||
if chestPt then
|
||||
eachClosestEntry(pickups, pickUp)
|
||||
eachEntry(refills, refill)
|
||||
refuel()
|
||||
end
|
||||
eachEntry(fluids, fluid)
|
||||
if chargePt then
|
||||
eachEntry(cells, checkCell)
|
||||
end
|
||||
print('sleeping')
|
||||
turtle.status = 'sleeping'
|
||||
if turtle.abort then
|
||||
printError('aborted')
|
||||
break
|
||||
end
|
||||
os.sleep(60)
|
||||
end
|
||||
end)
|
||||
|
||||
turtle.run(function()
|
||||
|
||||
while true do
|
||||
local e = process:pullEvent()
|
||||
if e == 'terminate' or deliveryThread:isDead() then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
end)
|
||||
|
||||
process:threadEvent('terminate')
|
229
sys/apps/pickupRemote.lua
Normal file
229
sys/apps/pickupRemote.lua
Normal file
@@ -0,0 +1,229 @@
|
||||
if not device.wireless_modem then
|
||||
error('Wireless modem is required')
|
||||
end
|
||||
|
||||
require = requireInjector(getfenv(1))
|
||||
local GPS = require('gps')
|
||||
local Event = require('event')
|
||||
local UI = require('ui')
|
||||
local Socket = require('socket')
|
||||
|
||||
multishell.setTitle(multishell.getCurrent(), 'Pickup Remote')
|
||||
|
||||
local id
|
||||
|
||||
local mainPage = UI.Page({
|
||||
menu = UI.Menu({
|
||||
centered = true,
|
||||
y = 2,
|
||||
menuItems = {
|
||||
{ prompt = 'Pickup', event = 'pickup', help = 'Pickup items from this location' },
|
||||
{ prompt = 'Charge cell', event = 'charge', help = 'Recharge this cell' },
|
||||
{ prompt = 'Refill', event = 'refill', help = 'Recharge this cell' },
|
||||
{ prompt = 'Set pickup location', event = 'setPickup', help = 'Recharge this cell' },
|
||||
{ prompt = 'Set recharge location', event = 'setRecharge', help = 'Recharge this cell' },
|
||||
{ prompt = 'Clear', event = 'clear', help = 'Remove this location' },
|
||||
},
|
||||
}),
|
||||
statusBar = UI.StatusBar(),
|
||||
accelerators = {
|
||||
q = 'quit',
|
||||
},
|
||||
})
|
||||
|
||||
local refillPage = UI.Page({
|
||||
menuBar = UI.MenuBar({
|
||||
y = 1,
|
||||
buttons = {
|
||||
{ text = 'Done', event = 'done', help = 'Pickup items from this location' },
|
||||
{ text = 'Back', event = 'back', help = 'Recharge this cell' },
|
||||
},
|
||||
}),
|
||||
grid1 = UI.ScrollingGrid({
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'name', width = UI.term.width-9 },
|
||||
{ heading = 'Qty', key = 'fQty', width = 5 },
|
||||
},
|
||||
sortColumn = 'name',
|
||||
height = 8,
|
||||
y = 3,
|
||||
}),
|
||||
grid2 = UI.ScrollingGrid({
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'name', width = UI.term.width-9 },
|
||||
{ heading = 'Qty', key = 'qty', width = 5 },
|
||||
},
|
||||
sortColumn = 'name',
|
||||
height = 4,
|
||||
y = 12,
|
||||
}),
|
||||
statusBar = UI.StatusBar(),
|
||||
accelerators = {
|
||||
q = 'quit',
|
||||
},
|
||||
})
|
||||
|
||||
refillPage.menuBar:add({
|
||||
filter = UI.TextEntry({
|
||||
x = UI.term.width-10,
|
||||
width = 10,
|
||||
})
|
||||
})
|
||||
|
||||
local function sendCommand(cmd)
|
||||
local socket = Socket.connect(id, 5222)
|
||||
if not socket then
|
||||
mainPage.statusBar:timedStatus('Unable to connect', 3)
|
||||
return
|
||||
end
|
||||
|
||||
socket:write(cmd)
|
||||
local m = socket:read(3)
|
||||
socket:close()
|
||||
if m then
|
||||
return m.response
|
||||
end
|
||||
mainPage.statusBar:timedStatus('No response', 3)
|
||||
end
|
||||
|
||||
local function getPoint()
|
||||
local gpt = GPS.getPoint()
|
||||
if not gpt then
|
||||
mainPage.statusBar:timedStatus('Unable to get location', 3)
|
||||
end
|
||||
return gpt
|
||||
end
|
||||
|
||||
function refillPage:eventHandler(event)
|
||||
|
||||
if event.type == 'grid_select' then
|
||||
|
||||
local item = {
|
||||
name = event.selected.name,
|
||||
id = event.selected.id,
|
||||
dmg = event.selected.dmg,
|
||||
qty = 0,
|
||||
}
|
||||
|
||||
local dialog = UI.Dialog({
|
||||
x = 1,
|
||||
width = UI.term.width,
|
||||
text = UI.Text({ x = 3, y = 3, value = 'Quantity' }),
|
||||
textEntry = UI.TextEntry({ x = 14, y = 3 })
|
||||
})
|
||||
|
||||
dialog.eventHandler = function(self, event)
|
||||
if event.type == 'accept' then
|
||||
local l = tonumber(self.textEntry.value)
|
||||
if l and l <= 1024 and l > 0 then
|
||||
item.qty = self.textEntry.value
|
||||
table.insert(refillPage.grid2.values, item)
|
||||
refillPage.grid2:update()
|
||||
UI:setPreviousPage()
|
||||
else
|
||||
self.statusBar:timedStatus('Invalid Quantity', 3)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
return UI.Dialog.eventHandler(self, event)
|
||||
end
|
||||
|
||||
dialog.titleBar.title = item.name
|
||||
dialog:setFocus(dialog.textEntry)
|
||||
UI:setPage(dialog)
|
||||
|
||||
elseif event.type == 'text_change' then
|
||||
local text = event.text
|
||||
if #text == 0 then
|
||||
self.grid1.values = self.allItems
|
||||
else
|
||||
self.grid1.values = { }
|
||||
for _,item in pairs(self.allItems) do
|
||||
if string.find(item.lname, text) then
|
||||
table.insert(self.grid1.values, item)
|
||||
end
|
||||
end
|
||||
end
|
||||
--self.grid:adjustWidth()
|
||||
self.grid1:update()
|
||||
self.grid1:setIndex(1)
|
||||
self.grid1:draw()
|
||||
|
||||
elseif event.type == 'back' then
|
||||
UI:setPreviousPage()
|
||||
|
||||
elseif event.type == 'done' then
|
||||
UI:setPage(mainPage)
|
||||
local pt = getPoint()
|
||||
if pt then
|
||||
local response = sendCommand({ type = 'refill', entry = { point = pt, items = self.grid2.values } })
|
||||
if response then
|
||||
mainPage.statusBar:timedStatus(response, 3)
|
||||
end
|
||||
end
|
||||
|
||||
elseif event.type == 'grid_focus_row' then
|
||||
self.statusBar:setStatus(event.selected.id .. ':' .. event.selected.dmg)
|
||||
self.statusBar:draw()
|
||||
end
|
||||
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
|
||||
function refillPage:enable()
|
||||
for _,item in pairs(self.allItems) do
|
||||
item.lname = string.lower(item.name)
|
||||
item.fQty = Util.toBytes(item.qty)
|
||||
end
|
||||
|
||||
self.grid1:setValues(self.allItems)
|
||||
|
||||
self.menuBar.filter.value = ''
|
||||
self.menuBar.filter.pos = 1
|
||||
self:setFocus(self.menuBar.filter)
|
||||
UI.Page.enable(self)
|
||||
end
|
||||
|
||||
function mainPage:eventHandler(event)
|
||||
|
||||
if event.type == 'quit' then
|
||||
Event.exitPullEvents()
|
||||
|
||||
elseif event.type == 'refill' then
|
||||
local response = sendCommand({ type = 'items' })
|
||||
if response then
|
||||
refillPage.allItems = response
|
||||
refillPage.grid2:setValues({ })
|
||||
UI:setPage(refillPage)
|
||||
end
|
||||
|
||||
elseif event.type == 'pickup' or event.type == 'setPickup' or
|
||||
event.type == 'setRecharge' or event.type == 'charge' or
|
||||
event.type == 'clear' then
|
||||
local pt = getPoint()
|
||||
if pt then
|
||||
local response = sendCommand({ type = event.type, point = pt })
|
||||
if response then
|
||||
self.statusBar:timedStatus(response, 3)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
|
||||
local args = { ... }
|
||||
if #args == 1 then
|
||||
id = tonumber(args[1])
|
||||
end
|
||||
|
||||
if not id then
|
||||
error('Syntax: pickupRemote <turtle ID>')
|
||||
end
|
||||
|
||||
UI:setPage(mainPage)
|
||||
|
||||
Event.pullEvents()
|
||||
UI.term:reset()
|
539
sys/apps/recorder.lua
Normal file
539
sys/apps/recorder.lua
Normal file
@@ -0,0 +1,539 @@
|
||||
-- +---------------------+------------+---------------------+
|
||||
-- | | | |
|
||||
-- | | 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
|
||||
|
||||
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 filename
|
||||
|
||||
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('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...')
|
||||
fs.mount('.recGif', 'ramfs', 'directory')
|
||||
fs.mount('.recGif/GIF', 'urlfs', 'http://pastebin.com/raw/5uk9uRjC')
|
||||
fs.mount('.recGif/package', 'urlfs', 'http://pastebin.com/raw/cUYTGbpb')
|
||||
|
||||
-- don't pollute global env
|
||||
local function loadAPI(filename, env)
|
||||
local apiEnv = Util.shallowCopy(env)
|
||||
apiEnv.shell = nil
|
||||
apiEnv.multishell = nil
|
||||
setmetatable(apiEnv, { __index = _G })
|
||||
local fn = loadfile(filename, apiEnv)
|
||||
fn()
|
||||
return apiEnv
|
||||
end
|
||||
|
||||
package = loadAPI('.recGif/package', getfenv(1))
|
||||
GIF = loadAPI('.recGif/GIF', getfenv(1))
|
||||
|
||||
local oldDir = shell.dir()
|
||||
shell.setDir('.recGif')
|
||||
shell.run("package get Y0eLUPtr")
|
||||
shell.setDir(oldDir)
|
||||
|
||||
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()
|
||||
|
||||
multishell.addHotkey(25, 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
|
||||
|
||||
multishell.removeHotkey(25)
|
||||
|
||||
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(".recGif/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.unmount('.recGif')
|
||||
|
||||
print("Encode complete")
|
535
sys/apps/refinedManager.lua
Normal file
535
sys/apps/refinedManager.lua
Normal file
@@ -0,0 +1,535 @@
|
||||
local injector = requireInjector or load(http.get('http://pastebin.com/raw/c0TWsScv').readAll())()
|
||||
require = injector(getfenv(1))
|
||||
|
||||
local UI = require('ui')
|
||||
local RefinedProvider = require('refinedProvider')
|
||||
local Terminal = require('terminal')
|
||||
local Peripheral = require('peripheral')
|
||||
|
||||
local controller = RefinedProvider()
|
||||
if not controller:isValid() then
|
||||
error('Refined storage controller not found')
|
||||
end
|
||||
|
||||
multishell.setTitle(multishell.getCurrent(), 'Storage Manager')
|
||||
|
||||
function getItem(items, inItem, ignoreDamage)
|
||||
for _,item in pairs(items) do
|
||||
if item.name == inItem.name then
|
||||
if ignoreDamage then
|
||||
return item
|
||||
elseif item.damage == inItem.damage and item.nbtHash == inItem.nbtHash then
|
||||
return item
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function uniqueKey(item)
|
||||
return table.concat({ item.name, item.damage, item.nbtHash }, ':')
|
||||
end
|
||||
|
||||
function mergeResources(t)
|
||||
local resources = Util.readTable('resource.limits') or { }
|
||||
|
||||
for _,v in pairs(resources) do
|
||||
v.low = tonumber(v.low) -- backwards compatibility
|
||||
local item = getItem(t, v)
|
||||
if item then
|
||||
item.low = v.low
|
||||
item.auto = v.auto
|
||||
item.ignoreDamage = v.ignoreDamage
|
||||
item.rsControl = v.rsControl
|
||||
item.rsDevice = v.rsDevice
|
||||
item.rsSide = v.rsSide
|
||||
else
|
||||
v.count = 0
|
||||
table.insert(t, v)
|
||||
end
|
||||
end
|
||||
|
||||
for _,v in pairs(t) do
|
||||
v.lname = v.displayName:lower()
|
||||
end
|
||||
end
|
||||
|
||||
function filterItems(t, filter)
|
||||
if filter then
|
||||
local r = {}
|
||||
filter = filter:lower()
|
||||
for k,v in pairs(t) do
|
||||
if string.find(v.lname, filter) then
|
||||
table.insert(r, v)
|
||||
end
|
||||
end
|
||||
return r
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
function craftItems(itemList, allItems)
|
||||
|
||||
for _,item in pairs(itemList) do
|
||||
local cItem = getItem(allItems, item)
|
||||
|
||||
if controller:isCrafting(item) then
|
||||
item.status = '(crafting)'
|
||||
elseif item.rsControl then
|
||||
item.status = 'Activated'
|
||||
elseif not cItem then
|
||||
item.status = '(no recipe)'
|
||||
else
|
||||
|
||||
local count = item.count
|
||||
while count >= 1 do -- try to request smaller quantities until successful
|
||||
local s, m = pcall(function()
|
||||
item.status = '(no recipe)'
|
||||
if not controller:craft(cItem, count) then
|
||||
item.status = '(missing ingredients)'
|
||||
error('failed')
|
||||
end
|
||||
item.status = '(crafting)'
|
||||
end)
|
||||
if s then
|
||||
break -- successfully requested crafting
|
||||
end
|
||||
count = math.floor(count / 2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function getAutocraftItems()
|
||||
local t = Util.readTable('resource.limits') or { }
|
||||
local itemList = { }
|
||||
|
||||
for _,res in pairs(t) do
|
||||
|
||||
if res.auto then
|
||||
res.count = 4 -- this could be higher to increase autocrafting speed
|
||||
table.insert(itemList, res)
|
||||
end
|
||||
end
|
||||
return itemList
|
||||
end
|
||||
|
||||
local function getItemWithQty(items, res, ignoreDamage)
|
||||
|
||||
local item = getItem(items, res, ignoreDamage)
|
||||
|
||||
if item then
|
||||
|
||||
if ignoreDamage then
|
||||
local count = 0
|
||||
|
||||
for _,v in pairs(items) do
|
||||
if item.name == v.name and item.nbtHash == v.nbtHash then
|
||||
if item.maxDamage > 0 or item.damage == v.damage then
|
||||
count = count + v.count
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
item.count = count
|
||||
end
|
||||
end
|
||||
|
||||
return item
|
||||
end
|
||||
|
||||
function watchResources(items)
|
||||
|
||||
local itemList = { }
|
||||
|
||||
local t = Util.readTable('resource.limits') or { }
|
||||
for k, res in pairs(t) do
|
||||
res.low = tonumber(res.low) -- backwards compatibility
|
||||
local item = getItemWithQty(items, res, res.ignoreDamage)
|
||||
if not item then
|
||||
item = {
|
||||
damage = res.damage,
|
||||
nbtHash = res.nbtHash,
|
||||
name = res.name,
|
||||
displayName = res.displayName,
|
||||
count = 0
|
||||
}
|
||||
end
|
||||
|
||||
if res.low and item.count < res.low then
|
||||
if res.ignoreDamage then
|
||||
item.damage = 0
|
||||
end
|
||||
table.insert(itemList, {
|
||||
damage = item.damage,
|
||||
nbtHash = item.nbtHash,
|
||||
count = res.low - item.count,
|
||||
name = item.name,
|
||||
displayName = item.displayName,
|
||||
status = '',
|
||||
rsControl = res.rsControl,
|
||||
})
|
||||
end
|
||||
|
||||
if res.rsControl and res.rsDevice and res.rsSide then
|
||||
pcall(function()
|
||||
device[res.rsDevice].setOutput(res.rsSide, item.count < res.low)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
return itemList
|
||||
end
|
||||
|
||||
itemPage = UI.Page {
|
||||
backgroundColor = colors.lightGray,
|
||||
titleBar = UI.TitleBar {
|
||||
title = 'Limit Resource',
|
||||
previousPage = true,
|
||||
event = 'form_cancel',
|
||||
backgroundColor = colors.green
|
||||
},
|
||||
displayName = UI.Window {
|
||||
x = 5, y = 3, width = UI.term.width - 10, height = 3,
|
||||
},
|
||||
form = UI.Form {
|
||||
x = 4, y = 6, height = 10, rex = -4,
|
||||
[1] = UI.TextEntry {
|
||||
width = 7,
|
||||
backgroundColor = colors.gray,
|
||||
backgroundFocusColor = colors.gray,
|
||||
formLabel = 'Min', formKey = 'low', help = 'Craft if below min'
|
||||
},
|
||||
[2] = UI.Chooser {
|
||||
width = 7,
|
||||
formLabel = 'Autocraft', formKey = 'auto',
|
||||
nochoice = 'No',
|
||||
choices = {
|
||||
{ name = 'Yes', value = true },
|
||||
{ name = 'No', value = false },
|
||||
},
|
||||
help = 'Craft until out of ingredients'
|
||||
},
|
||||
[3] = UI.Chooser {
|
||||
width = 7,
|
||||
formLabel = 'Ignore Dmg', formKey = 'ignoreDamage',
|
||||
nochoice = 'No',
|
||||
choices = {
|
||||
{ name = 'Yes', value = true },
|
||||
{ name = 'No', value = false },
|
||||
},
|
||||
help = 'Ignore damage of item'
|
||||
},
|
||||
[4] = UI.Chooser {
|
||||
width = 7,
|
||||
formLabel = 'RS Control', formKey = 'rsControl',
|
||||
nochoice = 'No',
|
||||
choices = {
|
||||
{ name = 'Yes', value = true },
|
||||
{ name = 'No', value = false },
|
||||
},
|
||||
help = 'Control via redstone'
|
||||
},
|
||||
[5] = UI.Chooser {
|
||||
width = 25,
|
||||
formLabel = 'RS Device', formKey = 'rsDevice',
|
||||
--choices = devices,
|
||||
help = 'Redstone Device'
|
||||
},
|
||||
[6] = UI.Chooser {
|
||||
width = 10,
|
||||
formLabel = 'RS Side', formKey = 'rsSide',
|
||||
--nochoice = 'No',
|
||||
choices = {
|
||||
{ name = 'up', value = 'up' },
|
||||
{ name = 'down', value = 'down' },
|
||||
{ name = 'east', value = 'east' },
|
||||
{ name = 'north', value = 'north' },
|
||||
{ name = 'west', value = 'west' },
|
||||
{ name = 'south', value = 'south' },
|
||||
},
|
||||
help = 'Output side'
|
||||
},
|
||||
},
|
||||
statusBar = UI.StatusBar { }
|
||||
}
|
||||
|
||||
function itemPage.displayName:draw()
|
||||
local item = self.parent.item
|
||||
local str = string.format('Name: %s\nDamage: %d', item.displayName, item.damage)
|
||||
if item.nbtHash then
|
||||
str = str .. string.format('\nNBT: %s\n', item.nbtHash)
|
||||
end
|
||||
self:setCursorPos(1, 1)
|
||||
self:print(str)
|
||||
end
|
||||
|
||||
function itemPage:enable(item)
|
||||
self.item = item
|
||||
|
||||
self.form:setValues(item)
|
||||
self.titleBar.title = item.name
|
||||
|
||||
local devices = self.form[5].choices
|
||||
Util.clear(devices)
|
||||
for _,device in pairs(device) do
|
||||
if device.setOutput then
|
||||
table.insert(devices, { name = device.name, value = device.name })
|
||||
end
|
||||
end
|
||||
|
||||
if Util.size(devices) == 0 then
|
||||
table.insert(devices, { name = 'None found', values = '' })
|
||||
end
|
||||
|
||||
UI.Page.enable(self)
|
||||
self:focusFirst()
|
||||
end
|
||||
|
||||
function itemPage:eventHandler(event)
|
||||
if event.type == 'form_cancel' then
|
||||
UI:setPreviousPage()
|
||||
|
||||
elseif event.type == 'focus_change' then
|
||||
self.statusBar:setStatus(event.focused.help)
|
||||
self.statusBar:draw()
|
||||
|
||||
elseif event.type == 'form_complete' then
|
||||
local values = self.form.values
|
||||
local t = Util.readTable('resource.limits') or { }
|
||||
local keys = { 'name', 'displayName', 'auto', 'low', 'damage',
|
||||
'maxDamage', 'nbtHash', 'ignoreDamage',
|
||||
'rsControl', 'rsDevice', 'rsSide', }
|
||||
|
||||
local filtered = { }
|
||||
for _,key in pairs(keys) do
|
||||
filtered[key] = values[key]
|
||||
end
|
||||
filtered.low = tonumber(filtered.low)
|
||||
|
||||
filtered.ignoreDamage = filtered.ignoreDamage == true
|
||||
filtered.auto = filtered.auto == true
|
||||
filtered.rsControl = filtered.rsControl == true
|
||||
|
||||
if filtered.ignoreDamage then
|
||||
filtered.damage = 0
|
||||
end
|
||||
|
||||
t[uniqueKey(filtered)] = filtered
|
||||
Util.writeTable('resource.limits', t)
|
||||
|
||||
UI:setPreviousPage()
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
listingPage = UI.Page {
|
||||
menuBar = UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = 'Forget', event = 'forget' },
|
||||
},
|
||||
},
|
||||
grid = UI.Grid {
|
||||
y = 2, height = UI.term.height - 2,
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'displayName', width = UI.term.width - 14 },
|
||||
{ heading = 'Qty', key = 'count', width = 5 },
|
||||
{ heading = 'Min', key = 'low', width = 4 },
|
||||
},
|
||||
sortColumn = 'lname',
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
backgroundColor = colors.gray,
|
||||
width = UI.term.width,
|
||||
filterText = UI.Text {
|
||||
x = 2, width = 6,
|
||||
value = 'Filter',
|
||||
},
|
||||
filter = UI.TextEntry {
|
||||
x = 9, rex = -12,
|
||||
limit = 50,
|
||||
},
|
||||
refresh = UI.Button {
|
||||
rx = -9, width = 8,
|
||||
text = 'Refresh',
|
||||
event = 'refresh',
|
||||
},
|
||||
},
|
||||
accelerators = {
|
||||
r = 'refresh',
|
||||
q = 'quit',
|
||||
}
|
||||
}
|
||||
|
||||
function listingPage.grid:getRowTextColor(row, selected)
|
||||
if row.is_craftable then -- not implemented
|
||||
return colors.yellow
|
||||
end
|
||||
return UI.Grid:getRowTextColor(row, selected)
|
||||
end
|
||||
|
||||
function listingPage.grid:getDisplayValues(row)
|
||||
row = Util.shallowCopy(row)
|
||||
row.count = Util.toBytes(row.count)
|
||||
if row.low then
|
||||
row.low = Util.toBytes(row.low)
|
||||
end
|
||||
return row
|
||||
end
|
||||
|
||||
function listingPage.statusBar:draw()
|
||||
return UI.Window.draw(self)
|
||||
end
|
||||
|
||||
function listingPage.statusBar.filter:eventHandler(event)
|
||||
if event.type == 'mouse_rightclick' then
|
||||
self.value = ''
|
||||
self:draw()
|
||||
local page = UI:getCurrentPage()
|
||||
page.filter = nil
|
||||
page:applyFilter()
|
||||
page.grid:draw()
|
||||
page:setFocus(self)
|
||||
end
|
||||
return UI.TextEntry.eventHandler(self, event)
|
||||
end
|
||||
|
||||
function listingPage:eventHandler(event)
|
||||
if event.type == 'quit' then
|
||||
UI:exitPullEvents()
|
||||
|
||||
elseif event.type == 'grid_select' then
|
||||
local selected = event.selected
|
||||
UI:setPage('item', selected)
|
||||
|
||||
elseif event.type == 'refresh' then
|
||||
self:refresh()
|
||||
self.grid:draw()
|
||||
self.statusBar.filter:focus()
|
||||
|
||||
elseif event.type == 'forget' then
|
||||
local item = self.grid:getSelected()
|
||||
if item then
|
||||
|
||||
local resources = Util.readTable('resource.limits') or { }
|
||||
resources[uniqueKey(item)] = nil
|
||||
Util.writeTable('resource.limits', resources)
|
||||
|
||||
self.statusBar:timedStatus('Forgot: ' .. item.name, 3)
|
||||
self:refresh()
|
||||
self.grid:draw()
|
||||
end
|
||||
|
||||
elseif event.type == 'text_change' then
|
||||
self.filter = event.text
|
||||
if #self.filter == 0 then
|
||||
self.filter = nil
|
||||
end
|
||||
self:applyFilter()
|
||||
self.grid:draw()
|
||||
self.statusBar.filter:focus()
|
||||
|
||||
else
|
||||
UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function listingPage:enable()
|
||||
self:refresh()
|
||||
self:setFocus(self.statusBar.filter)
|
||||
UI.Page.enable(self)
|
||||
end
|
||||
|
||||
function listingPage:refresh()
|
||||
self.allItems = controller:listItems()
|
||||
mergeResources(self.allItems)
|
||||
self:applyFilter()
|
||||
end
|
||||
|
||||
function listingPage:applyFilter()
|
||||
local t = filterItems(self.allItems, self.filter)
|
||||
self.grid:setValues(t)
|
||||
end
|
||||
|
||||
local function jobMonitor(jobList)
|
||||
|
||||
local mon = Peripheral.getByType('monitor')
|
||||
|
||||
if mon then
|
||||
mon = UI.Device({
|
||||
device = device.monitor,
|
||||
textScale = .5,
|
||||
})
|
||||
else
|
||||
mon = UI.Device({
|
||||
device = Terminal.getNullTerm(term.current())
|
||||
})
|
||||
end
|
||||
|
||||
jobListGrid = UI.Grid {
|
||||
parent = mon,
|
||||
sortColumn = 'displayName',
|
||||
columns = {
|
||||
{ heading = 'Qty', key = 'count', width = 6 },
|
||||
{ heading = 'Crafting', key = 'displayName', width = mon.width / 2 - 10 },
|
||||
{ heading = 'Status', key = 'status', width = mon.width - 10 },
|
||||
},
|
||||
}
|
||||
|
||||
return jobListGrid
|
||||
end
|
||||
|
||||
UI:setPages({
|
||||
listing = listingPage,
|
||||
item = itemPage,
|
||||
})
|
||||
|
||||
UI:setPage(listingPage)
|
||||
listingPage:setFocus(listingPage.statusBar.filter)
|
||||
|
||||
local jobListGrid = jobMonitor()
|
||||
jobListGrid:draw()
|
||||
jobListGrid:sync()
|
||||
|
||||
function craftingThread()
|
||||
|
||||
while true do
|
||||
os.sleep(5)
|
||||
|
||||
--pcall(function()
|
||||
|
||||
local items = controller:listItems()
|
||||
|
||||
if not controller:isOnline() then
|
||||
jobListGrid.parent:clear()
|
||||
jobListGrid.parent:centeredWrite(math.ceil(jobListGrid.parent.height/2), 'Power failure')
|
||||
jobListGrid:sync()
|
||||
|
||||
elseif Util.size(items) == 0 then
|
||||
jobListGrid.parent:clear()
|
||||
jobListGrid.parent:centeredWrite(math.ceil(jobListGrid.parent.height/2), 'No items in system')
|
||||
jobListGrid:sync()
|
||||
|
||||
else
|
||||
local itemList = watchResources(items)
|
||||
jobListGrid:setValues(itemList)
|
||||
jobListGrid:draw()
|
||||
jobListGrid:sync()
|
||||
craftItems(itemList, items)
|
||||
--jobListGrid:update()
|
||||
jobListGrid:draw()
|
||||
jobListGrid:sync()
|
||||
|
||||
itemList = getAutocraftItems() -- autocrafted items don't show on job monitor
|
||||
craftItems(itemList, items)
|
||||
end
|
||||
--end)
|
||||
end
|
||||
end
|
||||
|
||||
UI:pullEvents(craftingThread)
|
||||
|
||||
UI.term:reset()
|
||||
jobListGrid.parent:reset()
|
1
sys/apps/scripts/abort
Normal file
1
sys/apps/scripts/abort
Normal file
@@ -0,0 +1 @@
|
||||
turtle.abortAction()
|
121
sys/apps/scripts/follow
Normal file
121
sys/apps/scripts/follow
Normal file
@@ -0,0 +1,121 @@
|
||||
local function follow(id)
|
||||
|
||||
require = requireInjector(getfenv(1))
|
||||
local GPS = require('gps')
|
||||
local Socket = require('socket')
|
||||
local Point = require('point')
|
||||
local process = require('process')
|
||||
|
||||
turtle.status = 'follow ' .. id
|
||||
|
||||
local pt = GPS.getPointAndHeading()
|
||||
if not pt or not pt.heading then
|
||||
error('turtle: No GPS found')
|
||||
end
|
||||
|
||||
turtle.setPoint(pt)
|
||||
|
||||
local socket = Socket.connect(id, 161)
|
||||
if not socket then
|
||||
error('turtle: Unable to connect to ' .. id)
|
||||
return
|
||||
end
|
||||
|
||||
local lastPoint
|
||||
local following = false
|
||||
|
||||
local followThread = process:newThread('follower', function()
|
||||
while true do
|
||||
|
||||
local function getRemotePoint()
|
||||
if not turtle.abort then
|
||||
if socket:write({ type = 'gps' }) then
|
||||
return socket:read(3)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- sometimes gps will fail if moving
|
||||
local pt, d
|
||||
|
||||
for i = 1, 3 do
|
||||
pt, d = getRemotePoint()
|
||||
if pt then
|
||||
break
|
||||
end
|
||||
os.sleep(.5)
|
||||
end
|
||||
|
||||
if not pt or turtle.abort 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.abort = true
|
||||
while following do
|
||||
os.sleep(.1)
|
||||
end
|
||||
turtle.abort = false
|
||||
end
|
||||
|
||||
-- check if gps is inaccurate (player moving too fast)
|
||||
if d < Point.pythagoreanDistance(turtle.point, pt) + 10 then
|
||||
lastPoint = Point.copy(pt)
|
||||
following = true
|
||||
process:newThread('turtle_follow', function()
|
||||
|
||||
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) then
|
||||
turtle.headTowards(pt)
|
||||
end
|
||||
following = false
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
os.sleep(.5)
|
||||
end
|
||||
end)
|
||||
|
||||
while true do
|
||||
local e = process:pullEvent()
|
||||
if e == 'terminate' or followThread:isDead() or e =='turtle_abort' then
|
||||
process:threadEvent('terminate')
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
socket:close()
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local s, m = turtle.run(function() follow({COMPUTER_ID}) end)
|
||||
if not s and m then
|
||||
error(m)
|
||||
end
|
||||
|
1
sys/apps/scripts/goHome
Normal file
1
sys/apps/scripts/goHome
Normal file
@@ -0,0 +1 @@
|
||||
turtle.run(turtle.gotoGPSHome)
|
30
sys/apps/scripts/moveTo
Normal file
30
sys/apps/scripts/moveTo
Normal file
@@ -0,0 +1,30 @@
|
||||
turtle.run(function()
|
||||
require = requireInjector(getfenv(1))
|
||||
local GPS = require('gps')
|
||||
local Socket = require('socket')
|
||||
|
||||
local id = {COMPUTER_ID}
|
||||
|
||||
local pt = GPS.getPointAndHeading()
|
||||
if not pt or not pt.heading then
|
||||
error('turtle: No GPS found')
|
||||
end
|
||||
|
||||
turtle.setPoint(pt)
|
||||
|
||||
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, nil, 64) then
|
||||
error('Unable to go to location')
|
||||
end
|
||||
end)
|
100
sys/apps/scripts/obsidian
Normal file
100
sys/apps/scripts/obsidian
Normal file
@@ -0,0 +1,100 @@
|
||||
require = requireInjector(getfenv(1))
|
||||
local Point = require('point')
|
||||
|
||||
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.selectSlot('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.getCount(16) > 0 then
|
||||
print('Inventory full')
|
||||
print('Enter to continue...')
|
||||
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
|
||||
|
||||
local node = Point.closest(turtle.point, nodes)
|
||||
if not turtle.gotoPoint(node) then
|
||||
break
|
||||
end
|
||||
until turtle.abort
|
||||
end
|
||||
|
||||
turtle.reset()
|
||||
turtle.setPolicy(turtle.policies.digOnly)
|
||||
local s, m = turtle.run(function()
|
||||
repeat
|
||||
|
||||
checkedNodes = { }
|
||||
nodes = { }
|
||||
|
||||
local _,b = turtle.inspectDown()
|
||||
if not b or b.name ~= 'minecraft:obsidian' then
|
||||
break
|
||||
end
|
||||
|
||||
findObsidian()
|
||||
if not turtle.selectSlot('minecraft:water_bucket') then
|
||||
break
|
||||
end
|
||||
turtle.goto(0, 0)
|
||||
turtle.placeDown()
|
||||
os.sleep(2)
|
||||
turtle.placeDown()
|
||||
turtle.down()
|
||||
turtle.select(1)
|
||||
until turtle.abort
|
||||
end)
|
||||
turtle.goto(0, 0, 0, 0)
|
||||
turtle.reset()
|
||||
if not s and m then
|
||||
error(m)
|
||||
end
|
1
sys/apps/scripts/reboot
Normal file
1
sys/apps/scripts/reboot
Normal file
@@ -0,0 +1 @@
|
||||
os.reboot()
|
1
sys/apps/scripts/setHome
Normal file
1
sys/apps/scripts/setHome
Normal file
@@ -0,0 +1 @@
|
||||
turtle.run(turtle.setGPSHome)
|
1
sys/apps/scripts/shutdown
Normal file
1
sys/apps/scripts/shutdown
Normal file
@@ -0,0 +1 @@
|
||||
os.shutdown()
|
73
sys/apps/scripts/summon
Normal file
73
sys/apps/scripts/summon
Normal file
@@ -0,0 +1,73 @@
|
||||
local function summon(id)
|
||||
|
||||
require = requireInjector(getfenv(1))
|
||||
local GPS = require('gps')
|
||||
local Socket = require('socket')
|
||||
local Point = require('point')
|
||||
|
||||
turtle.status = '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.gotoPoint(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.status = '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)
|
1
sys/apps/scripts/update
Normal file
1
sys/apps/scripts/update
Normal file
@@ -0,0 +1 @@
|
||||
shell.run('/apps/update.lua')
|
623
sys/apps/shell
Normal file
623
sys/apps/shell
Normal file
@@ -0,0 +1,623 @@
|
||||
local parentShell = shell
|
||||
|
||||
shell = { }
|
||||
local sandboxEnv = Util.shallowCopy(getfenv(1))
|
||||
setmetatable(sandboxEnv, { __index = _G })
|
||||
|
||||
local DIR = (parentShell and parentShell.dir()) or ""
|
||||
local PATH = (parentShell and parentShell.path()) or ".:/rom/programs"
|
||||
local ALIASES = (parentShell and parentShell.aliases()) or {}
|
||||
local tCompletionInfo = (parentShell and parentShell.getCompletionInfo()) or {}
|
||||
|
||||
local bExit = false
|
||||
local tProgramStack = {}
|
||||
|
||||
local function parseCommandLine( ... )
|
||||
local sLine = table.concat( { ... }, " " )
|
||||
local tWords = {}
|
||||
local bQuoted = false
|
||||
for match in string.gmatch( sLine .. "\"", "(.-)\"" ) do
|
||||
if bQuoted then
|
||||
table.insert( tWords, match )
|
||||
else
|
||||
for m in string.gmatch( match, "[^ \t]+" ) do
|
||||
table.insert( tWords, m )
|
||||
end
|
||||
end
|
||||
bQuoted = not bQuoted
|
||||
end
|
||||
|
||||
return table.remove(tWords, 1), tWords
|
||||
end
|
||||
|
||||
-- Install shell API
|
||||
function shell.run(...)
|
||||
|
||||
local path, args = parseCommandLine(...)
|
||||
path = shell.resolveProgram(path)
|
||||
if path then
|
||||
tProgramStack[#tProgramStack + 1] = path
|
||||
local oldTitle
|
||||
|
||||
if multishell and multishell.getTitle then
|
||||
oldTitle = multishell.getTitle(multishell.getCurrent())
|
||||
multishell.setTitle(multishell.getCurrent(), fs.getName(path))
|
||||
end
|
||||
|
||||
local result, err = os.run(Util.shallowCopy(sandboxEnv), path, unpack(args))
|
||||
|
||||
if multishell then
|
||||
local title = 'shell'
|
||||
if #tProgramStack > 0 then
|
||||
title = fs.getName(tProgramStack[#tProgramStack])
|
||||
end
|
||||
multishell.setTitle(multishell.getCurrent(), oldTitle or 'shell')
|
||||
end
|
||||
|
||||
return result, err
|
||||
end
|
||||
return false, 'No such program'
|
||||
end
|
||||
|
||||
function shell.exit()
|
||||
bExit = true
|
||||
end
|
||||
|
||||
function shell.dir() return DIR end
|
||||
function shell.setDir(d) DIR = d end
|
||||
function shell.path() return PATH end
|
||||
function shell.setPath(p) PATH = p end
|
||||
|
||||
function shell.resolve( _sPath )
|
||||
local sStartChar = string.sub( _sPath, 1, 1 )
|
||||
if sStartChar == "/" or sStartChar == "\\" then
|
||||
return fs.combine( "", _sPath )
|
||||
else
|
||||
return fs.combine(DIR, _sPath )
|
||||
end
|
||||
end
|
||||
|
||||
function shell.resolveProgram( _sCommand )
|
||||
|
||||
local sPath = PATH or ''
|
||||
|
||||
if ALIASES[ _sCommand ] ~= nil then
|
||||
_sCommand = ALIASES[ _sCommand ]
|
||||
end
|
||||
|
||||
local path = shell.resolve(_sCommand)
|
||||
|
||||
if fs.exists(path) and not fs.isDir(path) then
|
||||
return path
|
||||
end
|
||||
if fs.exists(path .. '.lua') then
|
||||
return path .. '.lua'
|
||||
end
|
||||
|
||||
-- If the path is a global path, use it directly
|
||||
local sStartChar = string.sub( _sCommand, 1, 1 )
|
||||
if sStartChar == "/" or sStartChar == "\\" then
|
||||
local sPath = fs.combine( "", _sCommand )
|
||||
if fs.exists( sPath ) and not fs.isDir( sPath ) then
|
||||
return sPath
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Otherwise, look on the path variable
|
||||
for sPath in string.gmatch(sPath, "[^:]+") do
|
||||
sPath = fs.combine( shell.resolve(sPath), _sCommand )
|
||||
if fs.exists( sPath ) and not fs.isDir( sPath ) then
|
||||
return sPath
|
||||
end
|
||||
if fs.exists(sPath .. '.lua') then
|
||||
return sPath .. '.lua'
|
||||
end
|
||||
end
|
||||
|
||||
-- Not found
|
||||
return nil
|
||||
end
|
||||
|
||||
function shell.programs( _bIncludeHidden )
|
||||
local tItems = {}
|
||||
|
||||
-- Add programs from the path
|
||||
for sPath in string.gmatch(PATH, "[^:]+") do
|
||||
sPath = shell.resolve(sPath)
|
||||
if fs.isDir( sPath ) then
|
||||
local tList = fs.list( sPath )
|
||||
for n,sFile in pairs( tList ) do
|
||||
if not fs.isDir( fs.combine( sPath, sFile ) ) and
|
||||
(_bIncludeHidden or string.sub( sFile, 1, 1 ) ~= ".") then
|
||||
tItems[ sFile ] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Sort and return
|
||||
local tItemList = {}
|
||||
for sItem, b in pairs( tItems ) do
|
||||
table.insert( tItemList, sItem )
|
||||
end
|
||||
table.sort( tItemList )
|
||||
return tItemList
|
||||
end
|
||||
|
||||
function shell.complete(sLine) end
|
||||
function shell.completeProgram(sProgram) end
|
||||
|
||||
function shell.setCompletionFunction(sProgram, fnComplete)
|
||||
tCompletionInfo[sProgram] = { fnComplete = fnComplete }
|
||||
end
|
||||
|
||||
function shell.getCompletionInfo()
|
||||
return tCompletionInfo
|
||||
end
|
||||
|
||||
function shell.getRunningProgram()
|
||||
return tProgramStack[#tProgramStack]
|
||||
end
|
||||
|
||||
function shell.set(name, value)
|
||||
getfenv(1)[name] = value
|
||||
end
|
||||
|
||||
function shell.get(name)
|
||||
return getfenv(1)[name]
|
||||
end
|
||||
|
||||
function shell.setAlias( _sCommand, _sProgram )
|
||||
ALIASES[ _sCommand ] = _sProgram
|
||||
end
|
||||
|
||||
function shell.clearAlias( _sCommand )
|
||||
ALIASES[ _sCommand ] = nil
|
||||
end
|
||||
|
||||
function shell.aliases()
|
||||
local tCopy = {}
|
||||
for sAlias, sCommand in pairs(ALIASES) do
|
||||
tCopy[sAlias] = sCommand
|
||||
end
|
||||
return tCopy
|
||||
end
|
||||
|
||||
function shell.newTab(tabInfo, ...)
|
||||
local path, args = parseCommandLine(...)
|
||||
path = shell.resolveProgram(path)
|
||||
|
||||
if path then
|
||||
tabInfo.path = path
|
||||
tabInfo.env = sandboxEnv
|
||||
tabInfo.args = args
|
||||
tabInfo.title = fs.getName(path)
|
||||
|
||||
return multishell.openTab(tabInfo)
|
||||
end
|
||||
return nil, 'No such program'
|
||||
end
|
||||
|
||||
function shell.openTab( ... )
|
||||
return shell.newTab({ }, ...)
|
||||
end
|
||||
|
||||
function shell.openForegroundTab( ... )
|
||||
return shell.newTab({ focused = true }, ...)
|
||||
end
|
||||
|
||||
function shell.openHiddenTab( ... )
|
||||
return shell.newTab({ hidden = true }, ...)
|
||||
end
|
||||
|
||||
function shell.switchTab(tabId)
|
||||
multishell.setFocus(tabId)
|
||||
end
|
||||
|
||||
local tArgs = { ... }
|
||||
if #tArgs > 0 then
|
||||
-- "shell x y z"
|
||||
-- Run the program specified in this new shell
|
||||
local s, m = shell.run( ... )
|
||||
if not s and m ~= 'Terminated' then
|
||||
error(m or '')
|
||||
end
|
||||
return s, m
|
||||
end
|
||||
|
||||
require = requireInjector(getfenv(1))
|
||||
local Config = require('config')
|
||||
local History = require('history')
|
||||
|
||||
local config = {
|
||||
standard = {
|
||||
textColor = colors.white,
|
||||
commandTextColor = colors.lightGray,
|
||||
directoryTextColor = colors.gray,
|
||||
directoryBackgroundColor = colors.black,
|
||||
promptTextColor = colors.gray,
|
||||
promptBackgroundColor = colors.black,
|
||||
directoryColor = colors.gray,
|
||||
},
|
||||
color = {
|
||||
textColor = colors.white,
|
||||
commandTextColor = colors.yellow,
|
||||
directoryTextColor = colors.orange,
|
||||
directoryBackgroundColor = colors.black,
|
||||
promptTextColor = colors.blue,
|
||||
promptBackgroundColor = colors.black,
|
||||
directoryColor = colors.green,
|
||||
},
|
||||
displayDirectory = true,
|
||||
}
|
||||
|
||||
--Config.load('shell', config)
|
||||
|
||||
local _colors = config.standard
|
||||
if term.isColor() then
|
||||
_colors = config.color
|
||||
end
|
||||
|
||||
local function autocompleteFile(results, words)
|
||||
|
||||
local function getBaseDir(path)
|
||||
if #path > 1 then
|
||||
if path:sub(-1) ~= '/' then
|
||||
path = fs.getDir(path)
|
||||
end
|
||||
end
|
||||
if path:sub(1, 1) == '/' then
|
||||
path = fs.combine(path, '')
|
||||
else
|
||||
path = fs.combine(shell.dir(), path)
|
||||
end
|
||||
while not fs.isDir(path) do
|
||||
path = fs.getDir(path)
|
||||
end
|
||||
return path
|
||||
end
|
||||
|
||||
local function getRawPath(path)
|
||||
local baseDir = ''
|
||||
if path:sub(1, 1) ~= '/' then
|
||||
baseDir = shell.dir()
|
||||
end
|
||||
if #path > 1 then
|
||||
if path:sub(-1) ~= '/' then
|
||||
path = fs.getDir(path)
|
||||
end
|
||||
end
|
||||
if fs.isDir(fs.combine(baseDir, path)) then
|
||||
return path
|
||||
end
|
||||
return fs.getDir(path)
|
||||
end
|
||||
|
||||
local match = words[#words] or ''
|
||||
local startDir = getBaseDir(match)
|
||||
local rawPath = getRawPath(match)
|
||||
|
||||
if fs.isDir(startDir) then
|
||||
local files = fs.list(startDir)
|
||||
debug({ rawPath, startDir })
|
||||
for _,f in pairs(files) do
|
||||
local path = fs.combine(rawPath, f)
|
||||
if fs.isDir(fs.combine(startDir, f)) then
|
||||
results[path .. '/'] = 'directory'
|
||||
else
|
||||
results[path .. ' '] = 'program'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function autocompleteProgram(results, words)
|
||||
if #words == 1 then
|
||||
local files = shell.programs(true)
|
||||
for _,f in ipairs(files) do
|
||||
results[f .. ' '] = 'program'
|
||||
end
|
||||
for f in pairs(ALIASES) do
|
||||
results[f .. ' '] = 'program'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function autocompleteArgument(results, program, words)
|
||||
local word = ''
|
||||
if #words > 1 then
|
||||
word = words[#words]
|
||||
end
|
||||
|
||||
local tInfo = tCompletionInfo[program]
|
||||
local args = tInfo.fnComplete(shell, #words - 1, word, words)
|
||||
if args then
|
||||
Util.filterInplace(args, function(f)
|
||||
return not Util.key(args, f .. '/')
|
||||
end)
|
||||
for _,arg in ipairs(args) do
|
||||
results[word .. arg] = 'argument'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function autocomplete(line, suggestions)
|
||||
local words = { }
|
||||
for word in line:gmatch("%S+") do
|
||||
table.insert(words, word)
|
||||
end
|
||||
if line:match(' $') then
|
||||
table.insert(words, '')
|
||||
end
|
||||
|
||||
local results = { }
|
||||
|
||||
if #words == 0 then
|
||||
files = autocompleteFile(results, words)
|
||||
else
|
||||
local program = shell.resolveProgram(words[1])
|
||||
if tCompletionInfo[program] then
|
||||
autocompleteArgument(results, program, words)
|
||||
else
|
||||
autocompleteProgram(results, words)
|
||||
autocompleteFile(results, words)
|
||||
end
|
||||
end
|
||||
|
||||
local match = words[#words] or ''
|
||||
local files = { }
|
||||
for f in pairs(results) do
|
||||
if f:sub(1, #match) == match then
|
||||
table.insert(files, f)
|
||||
end
|
||||
end
|
||||
|
||||
if #files == 1 then
|
||||
words[#words] = files[1]
|
||||
return table.concat(words, ' ')
|
||||
elseif #files > 1 and suggestions then
|
||||
print()
|
||||
|
||||
local word = words[#words] or ''
|
||||
local prefix = word:match("(.*/)") or ''
|
||||
if #prefix > 0 then
|
||||
for _,f in ipairs(files) do
|
||||
if f:match("^" .. prefix) ~= prefix then
|
||||
prefix = ''
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local tDirs, tFiles = { }, { }
|
||||
for _,f in ipairs(files) do
|
||||
if results[f] == 'directory' then
|
||||
f = f:gsub(prefix, '', 1)
|
||||
table.insert(tDirs, f)
|
||||
else
|
||||
f = f:gsub(prefix, '', 1)
|
||||
table.insert(tFiles, f)
|
||||
end
|
||||
end
|
||||
table.sort(tDirs)
|
||||
table.sort(tFiles)
|
||||
|
||||
if #tDirs > 0 and #tDirs < #tFiles then
|
||||
local w = term.getSize()
|
||||
local nMaxLen = w / 8
|
||||
for n, sItem in pairs(files) do
|
||||
nMaxLen = math.max(string.len(sItem) + 1, nMaxLen)
|
||||
end
|
||||
local nCols = math.floor(w / nMaxLen)
|
||||
if #tDirs < nCols then
|
||||
for i = #tDirs + 1, nCols do
|
||||
table.insert(tDirs, '')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if #tDirs > 0 then
|
||||
textutils.tabulate(_colors.directoryColor, tDirs, colors.white, tFiles)
|
||||
else
|
||||
textutils.tabulate(colors.white, tFiles)
|
||||
end
|
||||
|
||||
term.setTextColour(_colors.promptTextColor)
|
||||
term.setBackgroundColor(_colors.promptBackgroundColor)
|
||||
write("$ " )
|
||||
|
||||
term.setTextColour(_colors.commandTextColor)
|
||||
term.setBackgroundColor(colors.black)
|
||||
return line
|
||||
elseif #files > 1 then
|
||||
|
||||
-- ugly (complete as much as possible)
|
||||
local word = words[#words] or ''
|
||||
local i = #word + 1
|
||||
while true do
|
||||
local ch
|
||||
for _,f in ipairs(files) do
|
||||
if #f < i then
|
||||
words[#words] = string.sub(f, 1, i - 1)
|
||||
return table.concat(words, ' ')
|
||||
end
|
||||
if not ch then
|
||||
ch = string.sub(f, i, i)
|
||||
elseif string.sub(f, i, i) ~= ch then
|
||||
if i == #word + 1 then
|
||||
return
|
||||
end
|
||||
words[#words] = string.sub(f, 1, i - 1)
|
||||
return table.concat(words, ' ')
|
||||
end
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function shellRead(_tHistory )
|
||||
term.setCursorBlink( true )
|
||||
|
||||
local sLine = ""
|
||||
local nHistoryPos
|
||||
local nPos = 0
|
||||
local lastPattern
|
||||
|
||||
local w = term.getSize()
|
||||
local sx = term.getCursorPos()
|
||||
|
||||
local function redraw( sReplace )
|
||||
local nScroll = 0
|
||||
if sx + nPos >= w then
|
||||
nScroll = (sx + nPos) - w
|
||||
end
|
||||
|
||||
local cx,cy = term.getCursorPos()
|
||||
term.setCursorPos( sx, cy )
|
||||
if sReplace then
|
||||
term.write( string.rep( sReplace, math.max( string.len(sLine) - nScroll, 0 ) ) )
|
||||
else
|
||||
term.write( string.sub( sLine, nScroll + 1 ) )
|
||||
end
|
||||
term.setCursorPos( sx + nPos - nScroll, cy )
|
||||
end
|
||||
|
||||
while true do
|
||||
local sEvent, param, param2 = os.pullEventRaw()
|
||||
|
||||
if sEvent == "char" then
|
||||
sLine = string.sub( sLine, 1, nPos ) .. param .. string.sub( sLine, nPos + 1 )
|
||||
nPos = nPos + 1
|
||||
redraw()
|
||||
elseif sEvent == "paste" then
|
||||
sLine = string.sub( sLine, 1, nPos ) .. param .. string.sub( sLine, nPos + 1 )
|
||||
nPos = nPos + string.len( param )
|
||||
redraw()
|
||||
elseif sEvent == 'mouse_click' and param == 2 then
|
||||
redraw(string.rep(' ', #sLine))
|
||||
sLine = ''
|
||||
nPos = 0
|
||||
redraw()
|
||||
elseif sEvent == 'terminate' then
|
||||
bExit = true
|
||||
break
|
||||
elseif sEvent == "key" then
|
||||
if param == keys.enter then
|
||||
-- Enter
|
||||
break
|
||||
elseif param == keys.tab then
|
||||
if nPos == #sLine then
|
||||
local showSuggestions = lastPattern == sLine
|
||||
lastPattern = sLine
|
||||
|
||||
local cline = autocomplete(sLine, showSuggestions)
|
||||
if cline then
|
||||
sLine = cline
|
||||
nPos = #sLine
|
||||
redraw()
|
||||
end
|
||||
end
|
||||
elseif param == keys.left then
|
||||
if nPos > 0 then
|
||||
nPos = nPos - 1
|
||||
redraw()
|
||||
end
|
||||
elseif param == keys.right then
|
||||
if nPos < string.len(sLine) then
|
||||
redraw(" ")
|
||||
nPos = nPos + 1
|
||||
redraw()
|
||||
end
|
||||
elseif param == keys.up or param == keys.down then
|
||||
if _tHistory then
|
||||
redraw(" ")
|
||||
if param == keys.up then
|
||||
if nHistoryPos == nil then
|
||||
if #_tHistory > 0 then
|
||||
nHistoryPos = #_tHistory
|
||||
end
|
||||
elseif nHistoryPos > 1 then
|
||||
nHistoryPos = nHistoryPos - 1
|
||||
end
|
||||
else
|
||||
if nHistoryPos == #_tHistory then
|
||||
nHistoryPos = nil
|
||||
elseif nHistoryPos ~= nil then
|
||||
nHistoryPos = nHistoryPos + 1
|
||||
end
|
||||
end
|
||||
if nHistoryPos then
|
||||
sLine = _tHistory[nHistoryPos]
|
||||
nPos = string.len( sLine )
|
||||
else
|
||||
sLine = ""
|
||||
nPos = 0
|
||||
end
|
||||
redraw()
|
||||
end
|
||||
elseif param == keys.backspace then
|
||||
if nPos > 0 then
|
||||
redraw(" ")
|
||||
sLine = string.sub( sLine, 1, nPos - 1 ) .. string.sub( sLine, nPos + 1 )
|
||||
nPos = nPos - 1
|
||||
redraw()
|
||||
end
|
||||
elseif param == keys.home then
|
||||
redraw(" ")
|
||||
nPos = 0
|
||||
redraw()
|
||||
elseif param == keys.delete then
|
||||
if nPos < string.len(sLine) then
|
||||
redraw(" ")
|
||||
sLine = string.sub( sLine, 1, nPos ) .. string.sub( sLine, nPos + 2 )
|
||||
redraw()
|
||||
end
|
||||
elseif param == keys["end"] then
|
||||
redraw(" ")
|
||||
nPos = string.len(sLine)
|
||||
redraw()
|
||||
end
|
||||
elseif sEvent == "term_resize" then
|
||||
w = term.getSize()
|
||||
redraw()
|
||||
end
|
||||
end
|
||||
|
||||
local cx, cy = term.getCursorPos()
|
||||
term.setCursorPos( w + 1, cy )
|
||||
print()
|
||||
term.setCursorBlink( false )
|
||||
return sLine
|
||||
end
|
||||
|
||||
local history = History.load('usr/.shell_history', 25)
|
||||
|
||||
while not bExit do
|
||||
if config.displayDirectory then
|
||||
term.setTextColour(_colors.directoryTextColor)
|
||||
term.setBackgroundColor(_colors.directoryBackgroundColor)
|
||||
print('==' .. os.getComputerLabel() .. ':/' .. DIR)
|
||||
end
|
||||
term.setTextColour(_colors.promptTextColor)
|
||||
term.setBackgroundColor(_colors.promptBackgroundColor)
|
||||
write("$ " )
|
||||
term.setTextColour(_colors.commandTextColor)
|
||||
term.setBackgroundColor(colors.black)
|
||||
local sLine = shellRead(history.entries)
|
||||
if bExit then -- terminated
|
||||
break
|
||||
end
|
||||
sLine = Util.trim(sLine)
|
||||
if #sLine > 0 and sLine ~= 'exit' then
|
||||
history.add(sLine)
|
||||
end
|
||||
term.setTextColour(_colors.textColor)
|
||||
if #sLine > 0 then
|
||||
local result, err = shell.run( sLine )
|
||||
if not result then
|
||||
printError(err)
|
||||
end
|
||||
end
|
||||
end
|
631
sys/apps/simpleMiner.lua
Normal file
631
sys/apps/simpleMiner.lua
Normal file
@@ -0,0 +1,631 @@
|
||||
require = requireInjector(getfenv(1))
|
||||
local Point = require('point')
|
||||
local Logger = require('logger')
|
||||
|
||||
if device and device.wireless_modem then
|
||||
Logger.setWirelessLogging()
|
||||
end
|
||||
|
||||
local args = { ... }
|
||||
local options = {
|
||||
chunks = { arg = 'c', type = 'number', value = -1,
|
||||
desc = 'Number of chunks to mine' },
|
||||
depth = { arg = 'd', type = 'number', value = 9000,
|
||||
desc = 'Mining depth' },
|
||||
-- enderChest = { arg = 'e', type = 'flag', value = false,
|
||||
-- desc = 'Use ender chest' },
|
||||
resume = { arg = 'r', type = 'flag', value = false,
|
||||
desc = 'Resume mining' },
|
||||
fortunePick = { arg = 'p', type = 'string', value = nil,
|
||||
desc = 'Pick to use with CCTweaks toolhost' },
|
||||
setTrash = { arg = 's', type = 'flag', value = false,
|
||||
desc = 'Set trash items' },
|
||||
help = { arg = 'h', type = 'flag', value = false,
|
||||
desc = 'Displays the options' },
|
||||
}
|
||||
|
||||
local fortuneBlocks = {
|
||||
[ 'minecraft:redstone_ore' ] = true,
|
||||
[ 'minecraft:lapis_ore' ] = true,
|
||||
[ 'minecraft:coal_ore' ] = true,
|
||||
[ 'minecraft:diamond_ore' ] = true,
|
||||
[ 'minecraft:emerald_ore' ] = true,
|
||||
}
|
||||
|
||||
local MIN_FUEL = 7500
|
||||
local LOW_FUEL = 1500
|
||||
local MAX_FUEL = 100000
|
||||
|
||||
if not term.isColor() then
|
||||
MAX_FUEL = 20000
|
||||
end
|
||||
|
||||
local mining = {
|
||||
diameter = 1,
|
||||
chunkIndex = 0,
|
||||
chunks = -1,
|
||||
}
|
||||
|
||||
local trash
|
||||
local boreDirection
|
||||
|
||||
function getChunkCoordinates(diameter, index, x, z)
|
||||
local dirs = { -- circumference of grid
|
||||
{ xd = 0, zd = 1, heading = 1 }, -- south
|
||||
{ xd = -1, zd = 0, heading = 2 },
|
||||
{ xd = 0, zd = -1, heading = 3 },
|
||||
{ xd = 1, zd = 0, heading = 0 } -- east
|
||||
}
|
||||
-- always move east when entering the next diameter
|
||||
if index == 0 then
|
||||
dirs[4].x = x + 16
|
||||
dirs[4].z = z
|
||||
return dirs[4]
|
||||
end
|
||||
dir = dirs[math.floor(index / (diameter - 1)) + 1]
|
||||
dir.x = x + dir.xd * 16
|
||||
dir.z = z + dir.zd * 16
|
||||
return dir
|
||||
end
|
||||
|
||||
function getBoreLocations(x, z)
|
||||
|
||||
local locations = {}
|
||||
|
||||
while true do
|
||||
local a = math.abs(z)
|
||||
local b = math.abs(x)
|
||||
|
||||
if x > 0 and z > 0 or
|
||||
x < 0 and z < 0 then
|
||||
-- rotate coords
|
||||
a = math.abs(x)
|
||||
b = math.abs(z)
|
||||
end
|
||||
if (a % 5 == 0 and b % 5 == 0) or
|
||||
(a % 5 == 2 and b % 5 == 1) or
|
||||
(a % 5 == 4 and b % 5 == 2) or
|
||||
(a % 5 == 1 and b % 5 == 3) or
|
||||
(a % 5 == 3 and b % 5 == 4) then
|
||||
table.insert(locations, { x = x, z = z, y = 0 })
|
||||
end
|
||||
if z % 2 == 0 then -- forward dir
|
||||
if (x + 1) % 16 == 0 then
|
||||
z = z + 1
|
||||
else
|
||||
x = x + 1
|
||||
end
|
||||
else
|
||||
if (x - 1) % 16 == 15 then
|
||||
if (z + 1) % 16 == 0 then
|
||||
break
|
||||
end
|
||||
z = z + 1
|
||||
else
|
||||
x = x - 1
|
||||
end
|
||||
end
|
||||
end
|
||||
return locations
|
||||
end
|
||||
|
||||
-- get the bore location closest to the miner
|
||||
local function getClosestLocation(points, b)
|
||||
local key = 1
|
||||
local leastMoves = 9000
|
||||
for k,pt in pairs(points) do
|
||||
|
||||
local moves = Point.calculateMoves(turtle.point, pt)
|
||||
|
||||
if moves < leastMoves then
|
||||
key = k
|
||||
leastMoves = moves
|
||||
if leastMoves == 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
return table.remove(points, key)
|
||||
end
|
||||
|
||||
function getCornerOf(c)
|
||||
return math.floor(c.x / 16) * 16, math.floor(c.z / 16) * 16
|
||||
end
|
||||
|
||||
function nextChunk()
|
||||
|
||||
local x, z = getCornerOf({ x = mining.x, z = mining.z })
|
||||
local points = math.pow(mining.diameter, 2) - math.pow(mining.diameter-2, 2)
|
||||
mining.chunkIndex = mining.chunkIndex + 1
|
||||
|
||||
if mining.chunkIndex >= points then
|
||||
mining.diameter = mining.diameter + 2
|
||||
mining.chunkIndex = 0
|
||||
end
|
||||
|
||||
if mining.chunks ~= -1 then
|
||||
local chunks = math.pow(mining.diameter-2, 2) + mining.chunkIndex
|
||||
if chunks >= mining.chunks then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
local nc = getChunkCoordinates(mining.diameter, mining.chunkIndex, x, z)
|
||||
mining.locations = getBoreLocations(nc.x, nc.z)
|
||||
|
||||
-- enter next chunk
|
||||
mining.x = nc.x
|
||||
mining.z = nc.z
|
||||
|
||||
Util.writeTable('mining.progress', mining)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function addTrash()
|
||||
|
||||
if not trash then
|
||||
trash = { }
|
||||
end
|
||||
|
||||
local slots = turtle.getFilledSlots()
|
||||
|
||||
for k,slot in pairs(slots) do
|
||||
trash[slot.iddmg] = true
|
||||
end
|
||||
|
||||
trash['minecraft:bucket:0'] = nil
|
||||
Util.writeTable('mining.trash', trash)
|
||||
end
|
||||
|
||||
function log(text)
|
||||
print(text)
|
||||
Logger.log('mineWorker', text)
|
||||
end
|
||||
|
||||
function status(status)
|
||||
turtle.status = status
|
||||
log(status)
|
||||
end
|
||||
|
||||
function refuel()
|
||||
if turtle.getFuelLevel() < MIN_FUEL then
|
||||
local oldStatus = turtle.status
|
||||
status('refueling')
|
||||
|
||||
if turtle.selectSlot('minecraft:coal:0') then
|
||||
local qty = turtle.getItemCount()
|
||||
print('refueling ' .. qty)
|
||||
turtle.refuel(qty)
|
||||
end
|
||||
if turtle.getFuelLevel() < MIN_FUEL then
|
||||
log('desperate fueling')
|
||||
|
||||
turtle.eachFilledSlot(function(slot)
|
||||
if turtle.getFuelLevel() < MIN_FUEL then
|
||||
turtle.select(slot.index)
|
||||
turtle.refuel(64)
|
||||
end
|
||||
end)
|
||||
end
|
||||
log('Fuel: ' .. turtle.getFuelLevel())
|
||||
status(oldStatus)
|
||||
end
|
||||
|
||||
turtle.select(1)
|
||||
end
|
||||
|
||||
function enderChestUnload()
|
||||
log('unloading')
|
||||
turtle.select(1)
|
||||
if not Util.tryTimed(5, function()
|
||||
turtle.digDown()
|
||||
return turtle.placeDown()
|
||||
end) then
|
||||
log('placedown failed')
|
||||
else
|
||||
turtle.reconcileInventory(slots, turtle.dropDown)
|
||||
|
||||
turtle.select(1)
|
||||
turtle.drop(64)
|
||||
turtle.digDown()
|
||||
end
|
||||
end
|
||||
|
||||
function safeGoto(x, z, y, h)
|
||||
local oldStatus = turtle.status
|
||||
while not turtle.pathfind({ x = x, z = z, y = y, heading = h }) do
|
||||
--status('stuck')
|
||||
if turtle.abort then
|
||||
return false
|
||||
end
|
||||
--os.sleep(1)
|
||||
end
|
||||
turtle.status = oldStatus
|
||||
return true
|
||||
end
|
||||
|
||||
function safeGotoY(y)
|
||||
local oldStatus = turtle.status
|
||||
while not turtle.gotoY(y) do
|
||||
status('stuck')
|
||||
if turtle.abort then
|
||||
return false
|
||||
end
|
||||
os.sleep(1)
|
||||
end
|
||||
turtle.status = oldStatus
|
||||
return true
|
||||
end
|
||||
|
||||
function makeWalkableTunnel(action, tpt, pt)
|
||||
if action ~= 'turn' and not Point.compare(tpt, { x = 0, z = 0 }) then -- not at source
|
||||
if not Point.compare(tpt, pt) then -- not at dest
|
||||
local r, block = turtle.inspectUp()
|
||||
if r and not turtle.isTurtleAtSide('top') then
|
||||
if block.name ~= 'minecraft:cobblestone' and
|
||||
block.name ~= 'minecraft:chest' then
|
||||
turtle.digUp()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function normalChestUnload()
|
||||
local oldStatus = turtle.status
|
||||
status('unloading')
|
||||
local pt = Util.shallowCopy(turtle.point)
|
||||
safeGotoY(0)
|
||||
|
||||
turtle.setMoveCallback(function(action, tpt)
|
||||
makeWalkableTunnel(action, tpt, { x = pt.x, z = pt.z })
|
||||
end)
|
||||
|
||||
safeGoto(0, 0)
|
||||
if not turtle.detectUp() then
|
||||
error('no chest')
|
||||
end
|
||||
local slots = turtle.getFilledSlots()
|
||||
for _,slot in pairs(slots) do
|
||||
if not trash[slot.iddmg] and
|
||||
slot.iddmg ~= 'minecraft:bucket:0' and
|
||||
slot.id ~= 'minecraft:diamond_pickaxe' and
|
||||
slot.id ~= 'cctweaks:toolHost' then
|
||||
if slot.id ~= options.fortunePick.value then
|
||||
turtle.select(slot.index)
|
||||
turtle.dropUp(64)
|
||||
end
|
||||
end
|
||||
end
|
||||
turtle.select(1)
|
||||
safeGoto(pt.x, pt.z, 0, pt.heading)
|
||||
|
||||
turtle.clearMoveCallback()
|
||||
|
||||
safeGotoY(pt.y)
|
||||
status(oldStatus)
|
||||
end
|
||||
|
||||
function ejectTrash()
|
||||
|
||||
local cobbleSlotCount = 0
|
||||
|
||||
turtle.eachFilledSlot(function(slot)
|
||||
if slot.iddmg == 'minecraft:cobblestone:0' then
|
||||
cobbleSlotCount = cobbleSlotCount + 1
|
||||
end
|
||||
|
||||
if trash[slot.iddmg] then
|
||||
-- retain 1 slot with cobble in order to indicate active mining
|
||||
if slot.iddmg ~= 'minecraft:cobblestone:0' or cobbleSlotCount > 1 then
|
||||
turtle.select(slot.index)
|
||||
turtle.dropDown(64)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function mineable(action)
|
||||
local r, block = action.inspect()
|
||||
if not r then
|
||||
return false
|
||||
end
|
||||
|
||||
if block.name == 'minecraft:chest' then
|
||||
collectDrops(action.suck)
|
||||
end
|
||||
|
||||
if turtle.getFuelLevel() < (MAX_FUEL - 1000) then
|
||||
if block.name == 'minecraft:lava' or block.name == 'minecraft:flowing_lava' then
|
||||
if turtle.selectSlot('minecraft:bucket:0') then
|
||||
if action.place() then
|
||||
log('Lava! ' .. turtle.getFuelLevel())
|
||||
turtle.refuel()
|
||||
log(turtle.getFuelLevel())
|
||||
end
|
||||
turtle.select(1)
|
||||
end
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if action.side == 'bottom' then
|
||||
return block.name
|
||||
end
|
||||
|
||||
if trash[block.name .. ':0'] then
|
||||
return false
|
||||
end
|
||||
|
||||
return block.name
|
||||
end
|
||||
|
||||
function fortuneDig(action, blockName)
|
||||
if options.fortunePick.value and fortuneBlocks[blockName] then
|
||||
turtle.selectSlot('cctweaks:toolHost')
|
||||
turtle.equipRight()
|
||||
turtle.selectSlot(options.fortunePick.value)
|
||||
repeat until not turtle.dig()
|
||||
turtle.selectSlot('minecraft:diamond_pickaxe')
|
||||
turtle.equipRight()
|
||||
turtle.select(1)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function mine(action)
|
||||
local blockName = mineable(action)
|
||||
if blockName then
|
||||
checkSpace()
|
||||
--collectDrops(action.suck)
|
||||
if not fortuneDig(action, blockName) then
|
||||
action.dig()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function bore()
|
||||
|
||||
local loc = turtle.point
|
||||
local level = loc.y
|
||||
|
||||
turtle.select(1)
|
||||
status('boring down')
|
||||
boreDirection = 'down'
|
||||
|
||||
while true do
|
||||
if turtle.abort then
|
||||
status('aborting')
|
||||
return false
|
||||
end
|
||||
if loc.y <= -mining.depth then
|
||||
break
|
||||
end
|
||||
|
||||
if turtle.point.y < -2 then
|
||||
-- turtle.setDigPolicy(turtle.digPolicies.turtleSafe)
|
||||
end
|
||||
|
||||
mine(turtle.getAction('down'))
|
||||
if not Util.tryTimed(3, turtle.down) then
|
||||
break
|
||||
end
|
||||
|
||||
if loc.y < level - 1 then
|
||||
mine(turtle.getAction('forward'))
|
||||
turtle.turnRight()
|
||||
mine(turtle.getAction('forward'))
|
||||
end
|
||||
end
|
||||
|
||||
boreDirection = 'up'
|
||||
status('boring up')
|
||||
|
||||
turtle.turnRight()
|
||||
mine(turtle.getAction('forward'))
|
||||
|
||||
turtle.turnRight()
|
||||
mine(turtle.getAction('forward'))
|
||||
|
||||
turtle.turnLeft()
|
||||
|
||||
while true do
|
||||
if turtle.abort then
|
||||
status('aborting')
|
||||
return false
|
||||
end
|
||||
|
||||
if turtle.point.y > -2 then
|
||||
-- turtle.setDigPolicy(turtle.digPolicies.turtleSafe)
|
||||
end
|
||||
|
||||
while not Util.tryTimed(3, turtle.up) do
|
||||
status('stuck')
|
||||
end
|
||||
if turtle.status == 'stuck' then
|
||||
status('boring up')
|
||||
end
|
||||
|
||||
if loc.y >= level - 1 then
|
||||
break
|
||||
end
|
||||
|
||||
mine(turtle.getAction('forward'))
|
||||
turtle.turnLeft()
|
||||
mine(turtle.getAction('forward'))
|
||||
end
|
||||
|
||||
if turtle.getFuelLevel() < LOW_FUEL then
|
||||
refuel()
|
||||
local veryMinFuel = Point.turtleDistance(turtle.point, { x = 0, y = 0, z = 0}) + 512
|
||||
if turtle.getFuelLevel() < veryMinFuel then
|
||||
log('Not enough fuel to continue')
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function checkSpace()
|
||||
if turtle.getItemCount(16) > 0 then
|
||||
refuel()
|
||||
local oldStatus = turtle.status
|
||||
status('condensing')
|
||||
ejectTrash()
|
||||
turtle.condense()
|
||||
local lastSlot = 16
|
||||
if boreDirection == 'down' then
|
||||
lastSlot = 15
|
||||
end
|
||||
if turtle.getItemCount(lastSlot) > 0 then
|
||||
unload()
|
||||
end
|
||||
status(oldStatus)
|
||||
turtle.select(1)
|
||||
end
|
||||
end
|
||||
|
||||
function collectDrops(suckAction)
|
||||
for i = 1, 50 do
|
||||
if not suckAction() then
|
||||
break
|
||||
end
|
||||
checkSpace()
|
||||
end
|
||||
end
|
||||
|
||||
function Point.compare(pta, ptb)
|
||||
if pta.x == ptb.x and pta.z == ptb.z then
|
||||
if pta.y and ptb.y then
|
||||
return pta.y == ptb.y
|
||||
end
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function inspect(action, name)
|
||||
local r, block = action.inspect()
|
||||
if r and block.name == name then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function boreCommand()
|
||||
local pt = getClosestLocation(mining.locations, turtle.point)
|
||||
|
||||
turtle.setMoveCallback(function(action, tpt)
|
||||
makeWalkableTunnel(action, tpt, pt)
|
||||
end)
|
||||
|
||||
safeGotoY(0)
|
||||
safeGoto(pt.x, pt.z, 0)
|
||||
|
||||
turtle.clearMoveCallback()
|
||||
|
||||
-- location is either mined, currently being mined or is the
|
||||
-- dropoff point for a turtle
|
||||
if inspect(turtle.getAction('up'), 'minecraft:cobblestone') or
|
||||
inspect(turtle.getAction('up'), 'minecraft:chest') or
|
||||
inspect(turtle.getAction('down'), 'minecraft:cobblestone') then
|
||||
return true
|
||||
end
|
||||
|
||||
turtle.digUp()
|
||||
turtle.placeUp('minecraft:cobblestone:0')
|
||||
|
||||
local success = bore()
|
||||
|
||||
safeGotoY(0) -- may have aborted
|
||||
turtle.digUp()
|
||||
|
||||
if success then
|
||||
turtle.placeDown('minecraft:cobblestone:0') -- cap with cobblestone to indicate this spot was mined out
|
||||
end
|
||||
|
||||
return success
|
||||
end
|
||||
|
||||
if not Util.getOptions(options, args) then
|
||||
return
|
||||
end
|
||||
|
||||
mining.depth = options.depth.value
|
||||
mining.chunks = options.chunks.value
|
||||
|
||||
unload = normalChestUnload
|
||||
--if options.enderChest.value then
|
||||
-- unload = enderChestUnload
|
||||
--end
|
||||
|
||||
mining.x = 0
|
||||
mining.z = 0
|
||||
mining.locations = getBoreLocations(0, 0)
|
||||
trash = Util.readTable('mining.trash')
|
||||
|
||||
if options.resume.value then
|
||||
mining = Util.readTable('mining.progress')
|
||||
elseif fs.exists('mining.progress') then
|
||||
print('use -r to resume')
|
||||
read()
|
||||
end
|
||||
|
||||
if not trash or options.setTrash.value then
|
||||
print('Add trash blocks, press enter when ready')
|
||||
read()
|
||||
addTrash()
|
||||
end
|
||||
|
||||
if not turtle.getSlot('minecraft:bucket:0') or
|
||||
not turtle.getSlot('minecraft:cobblestone:0') then
|
||||
print('Add bucket and cobblestone, press enter when ready')
|
||||
read()
|
||||
end
|
||||
|
||||
if options.fortunePick.value then
|
||||
local s = turtle.getSlot(options.fortunePick.value)
|
||||
if not s then
|
||||
error('fortunePick not found: ' .. options.fortunePick.value)
|
||||
end
|
||||
if not turtle.getSlot('cctweaks:toolHost:0') then
|
||||
error('CCTweaks tool host not found')
|
||||
end
|
||||
trash[s.iddmg] = nil
|
||||
trash['minecraft:diamond_pickaxe:0'] = nil
|
||||
trash['cctweaks:toolHost:0'] = nil
|
||||
end
|
||||
|
||||
_G._p = trash
|
||||
|
||||
local function main()
|
||||
repeat
|
||||
while #mining.locations > 0 do
|
||||
status('searching')
|
||||
if not boreCommand() then
|
||||
return
|
||||
end
|
||||
Util.writeTable('mining.progress', mining)
|
||||
end
|
||||
until not nextChunk()
|
||||
end
|
||||
|
||||
turtle.run(function()
|
||||
turtle.reset()
|
||||
turtle.setPolicy(turtle.policies.digAttack)
|
||||
turtle.setDigPolicy(turtle.digPolicies.turtleSafe)
|
||||
|
||||
unload()
|
||||
status('mining')
|
||||
|
||||
local s, m = pcall(function() main() end)
|
||||
if not s and m then
|
||||
printError(m)
|
||||
end
|
||||
|
||||
safeGotoY(0)
|
||||
safeGoto(0, 0, 0, 0)
|
||||
unload()
|
||||
turtle.reset()
|
||||
end)
|
170
sys/apps/storageActivity.lua
Normal file
170
sys/apps/storageActivity.lua
Normal file
@@ -0,0 +1,170 @@
|
||||
require = requireInjector(getfenv(1))
|
||||
local Util = require('util')
|
||||
local Event = require('event')
|
||||
local UI = require('ui')
|
||||
local RefinedProvider = require('refinedProvider')
|
||||
local MEProvider = require('meProvider')
|
||||
|
||||
local storage = RefinedProvider()
|
||||
if not storage:isValid() then
|
||||
storage = MEProvider()
|
||||
end
|
||||
|
||||
if not storage:isValid() then
|
||||
error('Not connected to a storage device')
|
||||
end
|
||||
|
||||
multishell.setTitle(multishell.getCurrent(), 'Storage Activity')
|
||||
UI:configure('StorageActivity', ...)
|
||||
|
||||
local changedPage = UI.Page({
|
||||
grid = UI.Grid({
|
||||
columns = {
|
||||
{ heading = 'Qty', key = 'qty', width = 5 },
|
||||
{ heading = 'Change', key = 'change', width = 6 },
|
||||
{ heading = 'Name', key = 'display_name', width = UI.term.width - 15 },
|
||||
},
|
||||
sortColumn = 'display_name',
|
||||
rey = -6,
|
||||
}),
|
||||
buttons = UI.Window({
|
||||
ry = -4,
|
||||
height = 5,
|
||||
backgroundColor = colors.gray,
|
||||
prevButton = UI.Button({
|
||||
event = 'previous',
|
||||
backgroundColor = colors.lightGray,
|
||||
x = 2,
|
||||
y = 2,
|
||||
height = 3,
|
||||
width = 5,
|
||||
text = ' < '
|
||||
}),
|
||||
resetButton = UI.Button({
|
||||
event = 'reset',
|
||||
backgroundColor = colors.lightGray,
|
||||
x = 8,
|
||||
y = 2,
|
||||
height = 3,
|
||||
rex = -8,
|
||||
text = 'Reset'
|
||||
}),
|
||||
nextButton = UI.Button({
|
||||
event = 'next',
|
||||
backgroundColor = colors.lightGray,
|
||||
rx = -5,
|
||||
y = 2,
|
||||
height = 3,
|
||||
width = 5,
|
||||
text = ' > '
|
||||
})
|
||||
}),
|
||||
accelerators = {
|
||||
q = 'quit',
|
||||
}
|
||||
})
|
||||
|
||||
function changedPage.grid:getDisplayValues(row)
|
||||
row = Util.shallowCopy(row)
|
||||
|
||||
local ind = '+'
|
||||
if row.change < 0 then
|
||||
ind = ''
|
||||
end
|
||||
row.change = ind .. Util.toBytes(row.change)
|
||||
row.qty = Util.toBytes(row.qty)
|
||||
|
||||
return row
|
||||
end
|
||||
|
||||
function changedPage:eventHandler(event)
|
||||
|
||||
if event.type == 'reset' then
|
||||
self.lastItems = nil
|
||||
self.grid:setValues({ })
|
||||
self.grid:clear()
|
||||
self.grid:draw()
|
||||
|
||||
elseif event.type == 'next' then
|
||||
self.grid:nextPage()
|
||||
|
||||
elseif event.type == 'previous' then
|
||||
self.grid:previousPage()
|
||||
|
||||
elseif event.type == 'quit' then
|
||||
Event.exitPullEvents()
|
||||
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function uniqueKey(item)
|
||||
return table.concat({ item.name, item.damage, item.nbtHash }, ':')
|
||||
end
|
||||
|
||||
function changedPage:refresh()
|
||||
local t = storage:listItems('all')
|
||||
|
||||
if not t or Util.empty(t) then
|
||||
self:clear()
|
||||
self:centeredWrite(math.ceil(self.height/2), 'Communication failure')
|
||||
return
|
||||
end
|
||||
|
||||
for k,v in pairs(t) do
|
||||
t[k] = Util.shallowCopy(v)
|
||||
end
|
||||
|
||||
if not self.lastItems then
|
||||
self.lastItems = t
|
||||
self.grid:setValues({ })
|
||||
else
|
||||
local changedItems = {}
|
||||
for _,v in pairs(self.lastItems) do
|
||||
found = false
|
||||
for k2,v2 in pairs(t) do
|
||||
if uniqueKey(v) == uniqueKey(v2) then
|
||||
if v.qty ~= v2.qty then
|
||||
local c = Util.shallowCopy(v2)
|
||||
c.lastQty = v.qty
|
||||
table.insert(changedItems, c)
|
||||
end
|
||||
table.remove(t, k2)
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
-- New item
|
||||
if not found then
|
||||
local c = Util.shallowCopy(v)
|
||||
c.lastQty = v.qty
|
||||
c.qty = 0
|
||||
table.insert(changedItems, c)
|
||||
end
|
||||
end
|
||||
-- No items left
|
||||
for k,v in pairs(t) do
|
||||
v.lastQty = 0
|
||||
table.insert(changedItems, v)
|
||||
end
|
||||
|
||||
for k,v in pairs(changedItems) do
|
||||
v.change = v.qty - v.lastQty
|
||||
end
|
||||
|
||||
self.grid:setValues(changedItems)
|
||||
end
|
||||
self.grid:draw()
|
||||
end
|
||||
|
||||
Event.addTimer(5, true, function()
|
||||
changedPage:refresh()
|
||||
changedPage:sync()
|
||||
end)
|
||||
|
||||
UI:setPage(changedPage)
|
||||
UI:pullEvents()
|
||||
UI.term:reset()
|
905
sys/apps/storageManager.lua
Normal file
905
sys/apps/storageManager.lua
Normal file
@@ -0,0 +1,905 @@
|
||||
require = requireInjector(getfenv(1))
|
||||
local Event = require('event')
|
||||
local UI = require('ui')
|
||||
local ME = require('me')
|
||||
local Config = require('config')
|
||||
local Logger = require('logger')
|
||||
|
||||
-- Must be a crafty turtle with duck antenna !
|
||||
-- 3 wide monitor (any side of turtle)
|
||||
|
||||
-- Config location is /sys/config/storageMonitor
|
||||
-- adjust directions in that file if needed
|
||||
|
||||
local config = {
|
||||
trashDirection = 'up', -- trash /chest in relation to interface
|
||||
turtleDirection = 'down', -- turtle in relation to interface
|
||||
noCraftingStorage = 'false' -- no ME crafting (or ability to tell if powered - use with caution)
|
||||
}
|
||||
|
||||
Config.load('storageMonitor', config)
|
||||
|
||||
if not device.tileinterface then
|
||||
error('ME interface not found')
|
||||
end
|
||||
|
||||
local duckAntenna
|
||||
|
||||
if device.workbench then
|
||||
|
||||
local oppositeSide = {
|
||||
[ 'left' ] = 'right',
|
||||
[ 'right' ] = 'left'
|
||||
}
|
||||
|
||||
local duckAntennaSide = oppositeSide[device.workbench.side]
|
||||
duckAntenna = peripheral.wrap(duckAntennaSide)
|
||||
end
|
||||
--if not device.monitor then
|
||||
-- error('Monitor not found')
|
||||
--end
|
||||
|
||||
ME.setDevice(device.tileinterface)
|
||||
|
||||
local jobListGrid
|
||||
local craftingPaused = false
|
||||
|
||||
multishell.setTitle(multishell.getCurrent(), 'Storage Manager')
|
||||
|
||||
Logger.disable()
|
||||
|
||||
function getItem(items, inItem, ignore_dmg)
|
||||
for _,item in pairs(items) do
|
||||
if item.id == inItem.id then
|
||||
if ignore_dmg and ignore_dmg == 'yes' then
|
||||
return item
|
||||
elseif item.dmg == inItem.dmg and item.nbt_hash == inItem.nbt_hash then
|
||||
return item
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function uniqueKey(item)
|
||||
local key = item.id .. ':' .. item.dmg
|
||||
if item.nbt_hash then
|
||||
key = key .. ':' .. item.nbt_hash
|
||||
end
|
||||
return key
|
||||
end
|
||||
|
||||
function mergeResources(t)
|
||||
local resources = Util.readTable('resource.limits')
|
||||
resources = resources or { }
|
||||
|
||||
for _,item in pairs(t) do
|
||||
item.has_recipe = false
|
||||
end
|
||||
|
||||
for _,v in pairs(resources) do
|
||||
local item = getItem(t, v)
|
||||
if item then
|
||||
item.limit = tonumber(v.limit)
|
||||
item.low = tonumber(v.low)
|
||||
item.auto = v.auto
|
||||
item.ignore_dmg = v.ignore_dmg
|
||||
else
|
||||
v.qty = 0
|
||||
v.limit = tonumber(v.limit)
|
||||
v.low = tonumber(v.low)
|
||||
v.auto = v.auto
|
||||
v.ignore_dmg = v.ignore_dmg
|
||||
table.insert(t, v)
|
||||
end
|
||||
end
|
||||
|
||||
recipes = Util.readTable('recipes') or { }
|
||||
|
||||
for _,v in pairs(recipes) do
|
||||
local item = getItem(t, v)
|
||||
if item then
|
||||
item.has_recipe = true
|
||||
else
|
||||
v.qty = 0
|
||||
v.limit = nil
|
||||
v.low = nil
|
||||
v.has_recipe = true
|
||||
v.auto = 'no'
|
||||
v.ignore_dmg = 'no'
|
||||
v.has_recipe = 'true'
|
||||
table.insert(t, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function filterItems(t, filter)
|
||||
local r = {}
|
||||
if filter then
|
||||
filter = filter:lower()
|
||||
for k,v in pairs(t) do
|
||||
if string.find(v.lname, filter) then
|
||||
table.insert(r, v)
|
||||
end
|
||||
end
|
||||
else
|
||||
return t
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
function sumItems(items)
|
||||
local t = {}
|
||||
|
||||
for _,item in pairs(items) do
|
||||
local key = uniqueKey(item)
|
||||
local summedItem = t[key]
|
||||
if summedItem then
|
||||
summedItem.qty = summedItem.qty + item.qty
|
||||
else
|
||||
summedItem = Util.shallowCopy(item)
|
||||
t[key] = summedItem
|
||||
end
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
function isGridClear()
|
||||
for i = 1, 16 do
|
||||
if turtle.getItemCount(i) ~= 0 then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local function clearGrid()
|
||||
for i = 1, 16 do
|
||||
local count = turtle.getItemCount(i)
|
||||
if count > 0 then
|
||||
ME.insert(i, count, config.turtleDirection)
|
||||
if turtle.getItemCount(i) ~= 0 then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function turtleCraft(recipe, originalItem)
|
||||
|
||||
for k,v in pairs(recipe.ingredients) do
|
||||
|
||||
-- ugh
|
||||
local dmg = v.dmg
|
||||
|
||||
if v.max_dmg and v.max_dmg > 0 then
|
||||
local item = ME.getItemDetail({ id = v.id, nbt_hash = v.nbt_hash }, false)
|
||||
if item then
|
||||
dmg = item.dmg
|
||||
end
|
||||
end
|
||||
|
||||
if not ME.extract(v.id, dmg, v.nbt_hash, v.qty, config.turtleDirection, k) then
|
||||
clearGrid()
|
||||
originalItem.status = v.name .. ' (extract failed)'
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if not turtle.craft() then
|
||||
clearGrid()
|
||||
return false
|
||||
end
|
||||
|
||||
clearGrid()
|
||||
return true
|
||||
end
|
||||
|
||||
function craftItem(items, recipes, item, originalItem, itemList)
|
||||
|
||||
local key = uniqueKey(item)
|
||||
local recipe = recipes[key]
|
||||
|
||||
if recipe then
|
||||
|
||||
if not isGridClear() then
|
||||
return
|
||||
end
|
||||
|
||||
local summedItems = sumItems(recipe.ingredients)
|
||||
|
||||
for i = 1, math.ceil(item.qty / recipe.qty) do
|
||||
|
||||
local failed = false -- try to craft all components (use all CPUs available)
|
||||
|
||||
for _,ingredient in pairs(summedItems) do
|
||||
local ignore_dmg = 'no'
|
||||
if ingredient.max_dmg and ingredient.max_dmg > 0 then
|
||||
ignore_dmg = 'yes'
|
||||
end
|
||||
local qty = ME.getItemCount(ingredient.id, ingredient.dmg, ingredient.nbt_hash, ignore_dmg)
|
||||
if qty < ingredient.qty then
|
||||
originalItem.status = ingredient.name .. ' (crafting)'
|
||||
ingredient.qty = ingredient.qty - qty
|
||||
if not craftItem(items, recipes, ingredient, originalItem, itemList) then
|
||||
failed = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if failed then
|
||||
return false
|
||||
end
|
||||
|
||||
if not failed and not turtleCraft(recipe, originalItem) then
|
||||
Logger.debug('turtle failed to craft ' .. item.name)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
|
||||
else
|
||||
|
||||
local meItem = getItem(items, item)
|
||||
if not meItem or not meItem.is_craftable then
|
||||
|
||||
if item.id == originalItem.id and item.dmg == originalItem.dmg then
|
||||
originalItem.status = '(not craftable)'
|
||||
else
|
||||
originalItem.status = item.name .. ' (missing)'
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
if item.id == originalItem.id and item.dmg == originalItem.dmg then
|
||||
item.meCraft = true
|
||||
return false
|
||||
end
|
||||
|
||||
-- find it in the list of items to be crafted
|
||||
for _,v in pairs(itemList) do
|
||||
if v.id == item.id and v.dmg == item.dmg and v.nbt_hash == item.nbt_hash then
|
||||
v.qty = item.qty + v.qty
|
||||
return false
|
||||
end
|
||||
end
|
||||
-- add to the item list
|
||||
table.insert(itemList, {
|
||||
id = item.id,
|
||||
dmg = item.dmg,
|
||||
nbt_hash = item.nbt_hash,
|
||||
qty = item.qty,
|
||||
name = item.name,
|
||||
meCraft = true,
|
||||
status = ''
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function craftItems(itemList)
|
||||
|
||||
local recipes = Util.readTable('recipes') or { }
|
||||
local items = ME.getAvailableItems()
|
||||
|
||||
-- turtle craft anything we can, build up list for ME items
|
||||
local keys = Util.keys(itemList)
|
||||
for _,key in pairs(keys) do
|
||||
local item = itemList[key]
|
||||
craftItem(items, recipes, item, item, itemList)
|
||||
end
|
||||
|
||||
-- second pass is to request crafting from ME with aggregated items
|
||||
for _,item in pairs(itemList) do
|
||||
if item.meCraft then
|
||||
|
||||
local alreadyCrafting = false
|
||||
local jobList = ME.getJobList()
|
||||
|
||||
for _,v in pairs(jobList) do
|
||||
if v.id == item.id and v.dmg == item.dmg and v.nbt_hash == item.nbt_hash then
|
||||
alreadyCrafting = true
|
||||
end
|
||||
end
|
||||
|
||||
if alreadyCrafting then
|
||||
item.status = '(crafting)'
|
||||
elseif not ME.isCPUAvailable() then
|
||||
item.status = '(waiting)'
|
||||
else
|
||||
item.status = '(failed)'
|
||||
|
||||
local qty = item.qty
|
||||
while qty >= 1 do -- try to request smaller quantities until successful
|
||||
if ME.craft(item.id, item.dmg, item.nbt_hash, qty) then
|
||||
item.status = '(crafting)'
|
||||
break -- successfully requested crafting
|
||||
end
|
||||
qty = math.floor(qty / 2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- AE 1 (obsolete)
|
||||
function isCrafting(jobList, id, dmg)
|
||||
for _, job in pairs(jobList) do
|
||||
if job.id == id and job.dmg == dmg then
|
||||
return job
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local nullDevice = {
|
||||
setCursorPos = function(...) end,
|
||||
write = function(...) end,
|
||||
getSize = function() return 13, 20 end,
|
||||
isColor = function() return false end,
|
||||
setBackgroundColor = function(...) end,
|
||||
setTextColor = function(...) end,
|
||||
clear = function(...) end,
|
||||
}
|
||||
|
||||
local function jobMonitor(jobList)
|
||||
|
||||
local mon
|
||||
|
||||
if device.monitor then
|
||||
mon = UI.Device({
|
||||
deviceType = 'monitor',
|
||||
textScale = .5,
|
||||
})
|
||||
else
|
||||
mon = UI.Device({
|
||||
device = nullDevice
|
||||
})
|
||||
end
|
||||
|
||||
jobListGrid = UI.Grid({
|
||||
parent = mon,
|
||||
sortColumn = 'name',
|
||||
columns = {
|
||||
{ heading = 'Qty', key = 'qty', width = 6 },
|
||||
{ heading = 'Crafting', key = 'name', width = mon.width / 2 - 10 },
|
||||
{ heading = 'Status', key = 'status', width = mon.width - 10 },
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
function getAutocraftItems(items)
|
||||
local t = Util.readTable('resource.limits') or { }
|
||||
local itemList = { }
|
||||
|
||||
for _,res in pairs(t) do
|
||||
|
||||
if res.auto and res.auto == 'yes' then
|
||||
res.qty = 4 -- this could be higher to increase autocrafting speed
|
||||
table.insert(itemList, res)
|
||||
end
|
||||
end
|
||||
return itemList
|
||||
end
|
||||
|
||||
local function getItemWithQty(items, res, ignore_dmg)
|
||||
|
||||
local item = getItem(items, res, ignore_dmg)
|
||||
|
||||
if item then
|
||||
|
||||
if ignore_dmg and ignore_dmg == 'yes' then
|
||||
local qty = 0
|
||||
|
||||
for _,v in pairs(items) do
|
||||
if item.id == v.id and item.nbt_hash == v.nbt_hash then
|
||||
if item.max_dmg > 0 or item.dmg == v.dmg then
|
||||
qty = qty + v.qty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
item.qty = qty
|
||||
end
|
||||
end
|
||||
|
||||
return item
|
||||
end
|
||||
|
||||
function watchResources(items)
|
||||
|
||||
local itemList = { }
|
||||
|
||||
local t = Util.readTable('resource.limits') or { }
|
||||
for k, res in pairs(t) do
|
||||
local item = getItemWithQty(items, res, res.ignore_dmg)
|
||||
res.limit = tonumber(res.limit)
|
||||
res.low = tonumber(res.low)
|
||||
if not item then
|
||||
item = {
|
||||
id = res.id,
|
||||
dmg = res.dmg,
|
||||
nbt_hash = res.nbt_hash,
|
||||
name = res.name,
|
||||
qty = 0
|
||||
}
|
||||
end
|
||||
|
||||
if res.limit and item.qty > res.limit then
|
||||
Logger.debug("Purging " .. item.qty-res.limit .. " " .. res.name)
|
||||
if not ME.extract(item.id, item.dmg, item.nbt_hash, item.qty - res.limit, config.trashDirection) then
|
||||
Logger.debug('Failed to purge ' .. res.name)
|
||||
end
|
||||
|
||||
elseif res.low and item.qty < res.low then
|
||||
if res.ignore_dmg and res.ignore_dmg == 'yes' then
|
||||
item.dmg = 0
|
||||
end
|
||||
table.insert(itemList, {
|
||||
id = item.id,
|
||||
dmg = item.dmg,
|
||||
nbt_hash = item.nbt_hash,
|
||||
qty = res.low - item.qty,
|
||||
name = item.name,
|
||||
status = ''
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return itemList
|
||||
end
|
||||
|
||||
itemPage = UI.Page {
|
||||
backgroundColor = colors.lightGray,
|
||||
titleBar = UI.TitleBar {
|
||||
title = 'Limit Resource',
|
||||
previousPage = true,
|
||||
event = 'form_cancel',
|
||||
backgroundColor = colors.green
|
||||
},
|
||||
idField = UI.Text {
|
||||
x = 5, y = 3, width = UI.term.width - 10,
|
||||
},
|
||||
form = UI.Form {
|
||||
x = 4, y = 4, height = 8, rex = -4,
|
||||
[1] = UI.TextEntry {
|
||||
width = 7,
|
||||
backgroundColor = colors.gray,
|
||||
backgroundFocusColor = colors.gray,
|
||||
formLabel = 'Min', formKey = 'low', help = 'Craft if below min'
|
||||
},
|
||||
[2] = UI.TextEntry {
|
||||
width = 7,
|
||||
backgroundColor = colors.gray,
|
||||
backgroundFocusColor = colors.gray,
|
||||
formLabel = 'Max', formKey = 'limit', help = 'Eject if above max'
|
||||
},
|
||||
[3] = UI.Chooser {
|
||||
width = 7,
|
||||
formLabel = 'Autocraft', formKey = 'auto',
|
||||
nochoice = 'No',
|
||||
choices = {
|
||||
{ name = 'Yes', value = 'yes' },
|
||||
{ name = 'No', value = 'no' },
|
||||
},
|
||||
help = 'Craft until out of ingredients'
|
||||
},
|
||||
[4] = UI.Chooser {
|
||||
width = 7,
|
||||
formLabel = 'Ignore Dmg', formKey = 'ignore_dmg',
|
||||
nochoice = 'No',
|
||||
choices = {
|
||||
{ name = 'Yes', value = 'yes' },
|
||||
{ name = 'No', value = 'no' },
|
||||
},
|
||||
help = 'Ignore damage of item'
|
||||
},
|
||||
},
|
||||
statusBar = UI.StatusBar { }
|
||||
}
|
||||
|
||||
function itemPage:enable()
|
||||
UI.Page.enable(self)
|
||||
self:focusFirst()
|
||||
end
|
||||
|
||||
function itemPage:eventHandler(event)
|
||||
if event.type == 'form_cancel' then
|
||||
UI:setPreviousPage()
|
||||
|
||||
elseif event.type == 'focus_change' then
|
||||
self.statusBar:setStatus(event.focused.help)
|
||||
self.statusBar:draw()
|
||||
|
||||
elseif event.type == 'form_complete' then
|
||||
local values = self.form.values
|
||||
local t = Util.readTable('resource.limits') or { }
|
||||
for k,v in pairs(t) do
|
||||
if v.id == values.id and v.dmg == values.dmg then
|
||||
table.remove(t, k)
|
||||
break
|
||||
end
|
||||
end
|
||||
local keys = { 'name', 'auto', 'id', 'low', 'dmg', 'max_dmg', 'nbt_hash', 'limit', 'ignore_dmg' }
|
||||
local filtered = { }
|
||||
for _,key in pairs(keys) do
|
||||
filtered[key] = values[key]
|
||||
end
|
||||
|
||||
table.insert(t, filtered)
|
||||
Util.writeTable('resource.limits', t)
|
||||
UI:setPreviousPage()
|
||||
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
listingPage = UI.Page {
|
||||
menuBar = UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = 'Learn', event = 'learn' },
|
||||
{ text = 'Forget', event = 'forget' },
|
||||
},
|
||||
},
|
||||
grid = UI.Grid {
|
||||
y = 2, height = UI.term.height - 2,
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'name' , width = 22 },
|
||||
{ heading = 'Qty', key = 'qty' , width = 5 },
|
||||
{ heading = 'Min', key = 'low' , width = 4 },
|
||||
{ heading = 'Max', key = 'limit', width = 4 },
|
||||
},
|
||||
sortColumn = 'name',
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
backgroundColor = colors.gray,
|
||||
width = UI.term.width,
|
||||
filterText = UI.Text {
|
||||
x = 2, width = 6,
|
||||
value = 'Filter',
|
||||
},
|
||||
filter = UI.TextEntry {
|
||||
x = 9, width = 19,
|
||||
limit = 50,
|
||||
},
|
||||
refresh = UI.Button {
|
||||
x = 31, width = 8,
|
||||
text = 'Refresh',
|
||||
event = 'refresh',
|
||||
},
|
||||
},
|
||||
accelerators = {
|
||||
r = 'refresh',
|
||||
q = 'quit',
|
||||
}
|
||||
}
|
||||
|
||||
function listingPage.grid:getRowTextColor(row, selected)
|
||||
if row.is_craftable then
|
||||
return colors.yellow
|
||||
end
|
||||
if row.has_recipe then
|
||||
if selected then
|
||||
return colors.blue
|
||||
end
|
||||
return colors.lightBlue
|
||||
end
|
||||
return UI.Grid:getRowTextColor(row, selected)
|
||||
end
|
||||
|
||||
function listingPage.grid:getDisplayValues(row)
|
||||
row = Util.shallowCopy(row)
|
||||
row.qty = Util.toBytes(row.qty)
|
||||
if row.low then
|
||||
row.low = Util.toBytes(row.low)
|
||||
end
|
||||
if row.limit then
|
||||
row.limit = Util.toBytes(row.limit)
|
||||
end
|
||||
return row
|
||||
end
|
||||
|
||||
function listingPage.statusBar:draw()
|
||||
return UI.Window.draw(self)
|
||||
end
|
||||
|
||||
function listingPage.statusBar.filter:eventHandler(event)
|
||||
if event.type == 'mouse_rightclick' then
|
||||
self.value = ''
|
||||
self:draw()
|
||||
local page = UI:getCurrentPage()
|
||||
page.filter = nil
|
||||
page:applyFilter()
|
||||
page.grid:draw()
|
||||
page:setFocus(self)
|
||||
end
|
||||
return UI.TextEntry.eventHandler(self, event)
|
||||
end
|
||||
|
||||
function listingPage:eventHandler(event)
|
||||
if event.type == 'quit' then
|
||||
Event.exitPullEvents()
|
||||
|
||||
elseif event.type == 'grid_select' then
|
||||
local selected = event.selected
|
||||
itemPage.form:setValues(selected)
|
||||
itemPage.titleBar.title = selected.name
|
||||
itemPage.idField.value = selected.id
|
||||
UI:setPage('item')
|
||||
|
||||
elseif event.type == 'refresh' then
|
||||
self:refresh()
|
||||
self.grid:draw()
|
||||
|
||||
elseif event.type == 'learn' then
|
||||
if not duckAntenna then
|
||||
self.statusBar:timedStatus('Missing peripherals', 3)
|
||||
else
|
||||
UI:setPage('craft')
|
||||
end
|
||||
|
||||
elseif event.type == 'forget' then
|
||||
local item = self.grid:getSelected()
|
||||
if item then
|
||||
local recipes = Util.readTable('recipes') or { }
|
||||
local key = uniqueKey(item)
|
||||
local recipe = recipes[key]
|
||||
|
||||
if recipe then
|
||||
recipes[key] = nil
|
||||
Util.writeTable('recipes', recipes)
|
||||
end
|
||||
|
||||
local resources = Util.readTable('resource.limits') or { }
|
||||
for k,v in pairs(resources) do
|
||||
if v.id == item.id and v.dmg == item.dmg then
|
||||
table.remove(resources, k)
|
||||
Util.writeTable('resource.limits', resources)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
self.statusBar:timedStatus('Forgot: ' .. item.name, 3)
|
||||
self:refresh()
|
||||
self.grid:draw()
|
||||
end
|
||||
|
||||
elseif event.type == 'text_change' then
|
||||
self.filter = event.text
|
||||
if #self.filter == 0 then
|
||||
self.filter = nil
|
||||
end
|
||||
self:applyFilter()
|
||||
self.grid:draw()
|
||||
self.statusBar.filter:focus()
|
||||
|
||||
else
|
||||
UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function listingPage:enable()
|
||||
self:refresh()
|
||||
self:setFocus(self.statusBar.filter)
|
||||
UI.Page.enable(self)
|
||||
end
|
||||
|
||||
function listingPage:refresh()
|
||||
self.allItems = ME.getAvailableItems('all')
|
||||
|
||||
mergeResources(self.allItems)
|
||||
|
||||
Util.each(self.allItems, function(item)
|
||||
item.lname = item.name:lower()
|
||||
end)
|
||||
|
||||
self:applyFilter()
|
||||
end
|
||||
|
||||
function listingPage:applyFilter()
|
||||
local t = filterItems(self.allItems, self.filter)
|
||||
self.grid:setValues(t)
|
||||
end
|
||||
|
||||
-- without duck antenna
|
||||
local function getTurtleInventory()
|
||||
local inventory = { }
|
||||
for i = 1,16 do
|
||||
if turtle.getItemCount(i) > 0 then
|
||||
turtle.select(i)
|
||||
local item = turtle.getItemDetail()
|
||||
inventory[i] = {
|
||||
id = item.name,
|
||||
dmg = item.damage,
|
||||
qty = item.count,
|
||||
name = item.name,
|
||||
}
|
||||
end
|
||||
end
|
||||
return inventory
|
||||
end
|
||||
|
||||
-- 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
|
||||
local 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 filter(t, filter)
|
||||
local keys = Util.keys(t)
|
||||
for _,key in pairs(keys) do
|
||||
if not Util.key(filter, key) then
|
||||
t[key] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function learnRecipe(page)
|
||||
local t = Util.readTable('recipes') or { }
|
||||
local recipe = { }
|
||||
local ingredients = duckAntenna.getAllStacks(false) -- getTurtleInventory()
|
||||
if ingredients then
|
||||
turtle.select(1)
|
||||
if turtle.craft() then
|
||||
recipe = duckAntenna.getAllStacks(false) -- getTurtleInventory()
|
||||
if recipe and recipe[1] then
|
||||
recipe = recipe[1]
|
||||
local key = uniqueKey(recipe)
|
||||
|
||||
clearGrid()
|
||||
|
||||
recipe.name = safeString(recipe.display_name)
|
||||
filter(recipe, { 'name', 'id', 'dmg', 'nbt_hash', 'qty', 'max_size' })
|
||||
|
||||
for _,ingredient in pairs(ingredients) do
|
||||
ingredient.name = safeString(ingredient.display_name)
|
||||
filter(ingredient, { 'name', 'id', 'dmg', 'nbt_hash', 'qty', 'max_size', 'max_dmg' })
|
||||
|
||||
if ingredient.max_dmg > 0 then -- let's try this...
|
||||
ingredient.dmg = 0
|
||||
end
|
||||
end
|
||||
recipe.ingredients = ingredients
|
||||
recipe.ignore_dmg = 'no'
|
||||
|
||||
t[key] = recipe
|
||||
|
||||
Util.writeTable('recipes', t)
|
||||
listingPage.statusBar.filter:setValue(recipe.name)
|
||||
listingPage.statusBar:timedStatus('Learned: ' .. recipe.name, 3)
|
||||
listingPage.filter = recipe.name
|
||||
listingPage:refresh()
|
||||
listingPage.grid:draw()
|
||||
|
||||
return true
|
||||
end
|
||||
else
|
||||
page.statusBar:timedStatus('Failed to craft', 3)
|
||||
end
|
||||
else
|
||||
page.statusBar:timedStatus('No recipe defined', 3)
|
||||
end
|
||||
end
|
||||
|
||||
craftPage = UI.Dialog {
|
||||
height = 7, width = UI.term.width - 6,
|
||||
backgroundColor = colors.lightGray,
|
||||
titleBar = UI.TitleBar {
|
||||
title = 'Learn Recipe',
|
||||
previousPage = true,
|
||||
},
|
||||
idField = UI.Text {
|
||||
x = 5,
|
||||
y = 3,
|
||||
width = UI.term.width - 10,
|
||||
value = 'Place recipe in turtle'
|
||||
},
|
||||
accept = UI.Button {
|
||||
rx = -13, ry = -2,
|
||||
text = 'Ok', event = 'accept',
|
||||
},
|
||||
cancel = UI.Button {
|
||||
rx = -8, ry = -2,
|
||||
text = 'Cancel', event = 'cancel'
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
status = 'Crafting paused'
|
||||
}
|
||||
}
|
||||
|
||||
function craftPage:enable()
|
||||
craftingPaused = true
|
||||
self:focusFirst()
|
||||
UI.Dialog.enable(self)
|
||||
end
|
||||
|
||||
function craftPage:disable()
|
||||
craftingPaused = false
|
||||
UI.Dialog.disable(self)
|
||||
end
|
||||
|
||||
function craftPage:eventHandler(event)
|
||||
if event.type == 'cancel' then
|
||||
UI:setPreviousPage()
|
||||
elseif event.type == 'accept' then
|
||||
if learnRecipe(self) then
|
||||
UI:setPreviousPage()
|
||||
end
|
||||
else
|
||||
return UI.Dialog.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
UI:setPages({
|
||||
listing = listingPage,
|
||||
item = itemPage,
|
||||
craft = craftPage,
|
||||
})
|
||||
|
||||
UI:setPage(listingPage)
|
||||
listingPage:setFocus(listingPage.statusBar.filter)
|
||||
|
||||
clearGrid()
|
||||
jobMonitor()
|
||||
jobListGrid:draw()
|
||||
jobListGrid:sync()
|
||||
|
||||
function craftingThread()
|
||||
|
||||
while true do
|
||||
os.sleep(5)
|
||||
|
||||
if not craftingPaused then
|
||||
|
||||
local items = ME.getAvailableItems()
|
||||
|
||||
if Util.size(items) == 0 then
|
||||
jobListGrid.parent:clear()
|
||||
jobListGrid.parent:centeredWrite(math.ceil(jobListGrid.parent.height/2), 'No items in system')
|
||||
jobListGrid:sync()
|
||||
|
||||
elseif config.noCraftingStorage ~= 'true' and #ME.getCraftingCPUs() <= 0 then -- only way to determine if AE is online
|
||||
jobListGrid.parent:clear()
|
||||
jobListGrid.parent:centeredWrite(math.ceil(jobListGrid.parent.height/2), 'Power failure')
|
||||
jobListGrid:sync()
|
||||
|
||||
else
|
||||
local itemList = watchResources(items)
|
||||
jobListGrid:setValues(itemList)
|
||||
jobListGrid:draw()
|
||||
jobListGrid:sync()
|
||||
craftItems(itemList)
|
||||
jobListGrid:update()
|
||||
jobListGrid:draw()
|
||||
jobListGrid:sync()
|
||||
|
||||
itemList = getAutocraftItems(items) -- autocrafted items don't show on job monitor
|
||||
craftItems(itemList)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Event.pullEvents(craftingThread)
|
||||
|
||||
UI.term:reset()
|
||||
jobListGrid.parent:reset()
|
403
sys/apps/supplier.lua
Normal file
403
sys/apps/supplier.lua
Normal file
@@ -0,0 +1,403 @@
|
||||
require = requireInjector(getfenv(1))
|
||||
local Logger = require('logger')
|
||||
local Message = require('message')
|
||||
local Event = require('event')
|
||||
local Point = require('point')
|
||||
local TableDB = require('tableDB')
|
||||
local MEProvider = require('meProvider')
|
||||
|
||||
local ChestProvider = require('chestProvider')
|
||||
if os.getVersion() == 1.8 then
|
||||
ChestProvider = require('chestProvider18')
|
||||
end
|
||||
|
||||
if not device.wireless_modem then
|
||||
error('No wireless modem detected')
|
||||
end
|
||||
|
||||
Logger.filter('modem_send', 'event', 'ui')
|
||||
Logger.setWirelessLogging()
|
||||
|
||||
local __BUILDER_ID = 6
|
||||
|
||||
local Builder = {
|
||||
version = '1.70',
|
||||
ccVersion = nil,
|
||||
slots = { },
|
||||
index = 1,
|
||||
fuelItem = { id = 'minecraft:coal', dmg = 0 },
|
||||
resupplying = true,
|
||||
ready = true,
|
||||
}
|
||||
|
||||
--[[-- maxStackDB --]]--
|
||||
local maxStackDB = TableDB({
|
||||
fileName = 'maxstack.db',
|
||||
tabledef = {
|
||||
autokeys = false,
|
||||
type = 'simple',
|
||||
columns = {
|
||||
{ label = 'Key', type = 'key', length = 8 },
|
||||
{ label = 'Quantity', type = 'number', length = 2 }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function maxStackDB:get(id, dmg)
|
||||
return self.data[id .. ':' .. dmg] or 64
|
||||
end
|
||||
|
||||
function Builder:dumpInventory()
|
||||
|
||||
local success = true
|
||||
|
||||
for i = 1, 16 do
|
||||
local qty = turtle.getItemCount(i)
|
||||
if qty > 0 then
|
||||
self.itemProvider:insert(i, qty)
|
||||
end
|
||||
if turtle.getItemCount(i) ~= 0 then
|
||||
success = false
|
||||
end
|
||||
end
|
||||
turtle.select(1)
|
||||
|
||||
return success
|
||||
end
|
||||
|
||||
function Builder:dumpInventoryWithCheck()
|
||||
while not self:dumpInventory() do
|
||||
Builder:log('Unable to dump inventory')
|
||||
print('Provider is full or missing - make space or replace')
|
||||
print('Press enter to continue')
|
||||
turtle.setHeading(0)
|
||||
self.ready = false
|
||||
read()
|
||||
end
|
||||
self.ready = true
|
||||
end
|
||||
|
||||
function Builder:autocraft(supplies)
|
||||
local t = { }
|
||||
|
||||
for i,s in pairs(supplies) do
|
||||
local key = s.id .. ':' .. s.dmg
|
||||
local item = t[key]
|
||||
if not item then
|
||||
item = {
|
||||
id = s.id,
|
||||
dmg = s.dmg,
|
||||
qty = 0,
|
||||
}
|
||||
t[key] = item
|
||||
end
|
||||
item.qty = item.qty + (s.need-s.qty)
|
||||
end
|
||||
|
||||
Builder.itemProvider:craftItems(t)
|
||||
end
|
||||
|
||||
function Builder:refuel()
|
||||
while turtle.getFuelLevel() < 4000 and self.fuelItem do
|
||||
Builder:log('Refueling')
|
||||
turtle.select(1)
|
||||
self.itemProvider:provide(self.fuelItem, 64, 1)
|
||||
if turtle.getItemCount(1) == 0 then
|
||||
Builder:log('Out of fuel, add coal to chest/ME system')
|
||||
turtle.setHeading(0)
|
||||
os.sleep(5)
|
||||
else
|
||||
turtle.refuel(64)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Builder:log(...)
|
||||
Logger.log('supplier', ...)
|
||||
Util.print(...)
|
||||
end
|
||||
|
||||
function Builder:getSupplies()
|
||||
|
||||
Builder.itemProvider:refresh()
|
||||
|
||||
local t = { }
|
||||
for _,s in ipairs(self.slots) do
|
||||
if s.need > 0 then
|
||||
local item = Builder.itemProvider:getItemInfo(s.id, s.dmg)
|
||||
if item then
|
||||
if item.name then
|
||||
s.name = item.name
|
||||
end
|
||||
|
||||
local qty = math.min(s.need-s.qty, item.qty)
|
||||
|
||||
if qty + s.qty > item.max_size then
|
||||
maxStackDB:add({ s.id, s.dmg }, item.max_size)
|
||||
maxStackDB.dirty = true
|
||||
maxStackDB:flush()
|
||||
qty = item.max_size
|
||||
s.need = qty
|
||||
end
|
||||
if qty > 0 then
|
||||
self.itemProvider:provide(item, qty, s.index)
|
||||
s.qty = turtle.getItemCount(s.index)
|
||||
end
|
||||
end
|
||||
end
|
||||
if s.qty < s.need then
|
||||
table.insert(t, s)
|
||||
local name = s.name or s.id .. ':' .. s.dmg
|
||||
Builder:log('Need %d %s', s.need - s.qty, name)
|
||||
end
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
local function moveTowardsX(dx)
|
||||
|
||||
local direction = dx - turtle.point.x
|
||||
local move
|
||||
|
||||
if direction == 0 then
|
||||
return false
|
||||
end
|
||||
|
||||
if direction > 0 and turtle.point.heading == 0 or
|
||||
direction < 0 and turtle.point.heading == 2 then
|
||||
move = turtle.forward
|
||||
else
|
||||
move = turtle.back
|
||||
end
|
||||
|
||||
return move()
|
||||
end
|
||||
|
||||
local function moveTowardsZ(dz)
|
||||
|
||||
local direction = dz - turtle.point.z
|
||||
local move
|
||||
|
||||
if direction == 0 then
|
||||
return false
|
||||
end
|
||||
|
||||
if direction > 0 and turtle.point.heading == 1 or
|
||||
direction < 0 and turtle.point.heading == 3 then
|
||||
move = turtle.forward
|
||||
else
|
||||
move = turtle.back
|
||||
end
|
||||
|
||||
return move()
|
||||
end
|
||||
|
||||
function Builder:finish()
|
||||
|
||||
Builder.resupplying = true
|
||||
Builder.ready = false
|
||||
if turtle.gotoLocation('supplies') then
|
||||
turtle.setHeading(0)
|
||||
os.sleep(.1) -- random 'Computer is not connected' error...
|
||||
Builder:dumpInventory()
|
||||
Event.exitPullEvents()
|
||||
print('Finished')
|
||||
end
|
||||
end
|
||||
|
||||
function Builder:gotoBuilder()
|
||||
|
||||
if Builder.lastPoint then
|
||||
turtle.status = 'tracking'
|
||||
while true do
|
||||
local pt = Point.copy(Builder.lastPoint)
|
||||
pt.y = pt.y + 3
|
||||
if turtle.point.y ~= pt.y then
|
||||
turtle.gotoY(pt.y)
|
||||
else
|
||||
local distance = Point.turtleDistance(turtle.point, pt)
|
||||
if distance <= 3 then
|
||||
Builder:log('Synchronized')
|
||||
break
|
||||
end
|
||||
|
||||
if turtle.point.heading % 2 == 0 then
|
||||
if turtle.point.x == pt.x then
|
||||
turtle.headTowardsZ(pt.z)
|
||||
moveTowardsZ(pt.z)
|
||||
else
|
||||
moveTowardsX(pt.x)
|
||||
end
|
||||
elseif turtle.point.z ~= pt.z then
|
||||
moveTowardsZ(pt.z)
|
||||
else
|
||||
turtle.headTowardsX(pt.x)
|
||||
moveTowardsX(pt.x)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Message.addHandler('builder',
|
||||
function(h, id, msg, distance)
|
||||
if not id or id ~= __BUILDER_ID then
|
||||
return
|
||||
end
|
||||
|
||||
if not Builder.resupplying then
|
||||
local pt = msg.contents
|
||||
pt.y = pt.y + 3
|
||||
|
||||
turtle.status = 'supervising'
|
||||
turtle.gotoYfirst(pt)
|
||||
end
|
||||
end)
|
||||
|
||||
Message.addHandler('supplyList',
|
||||
function(h, id, msg, distance)
|
||||
if not id or id ~= __BUILDER_ID then
|
||||
return
|
||||
end
|
||||
|
||||
turtle.status = 'resupplying'
|
||||
Builder.resupplying = true
|
||||
Builder.slots = msg.contents.slots
|
||||
Builder.slotUid = msg.contents.uid
|
||||
|
||||
Builder:log('Received supply list ' .. Builder.slotUid)
|
||||
|
||||
os.sleep(0)
|
||||
if not turtle.gotoLocation('supplies') then
|
||||
Builder:log('Failed to go to supply location')
|
||||
self.ready = false
|
||||
Event.exitPullEvents()
|
||||
end
|
||||
os.sleep(.2) -- random 'Computer is not connected' error...
|
||||
Builder:dumpInventoryWithCheck()
|
||||
Builder:refuel()
|
||||
|
||||
while true do
|
||||
local supplies = Builder:getSupplies()
|
||||
if #supplies == 0 then
|
||||
break
|
||||
end
|
||||
turtle.setHeading(0)
|
||||
Builder:autocraft(supplies)
|
||||
turtle.status = 'waiting'
|
||||
os.sleep(5)
|
||||
end
|
||||
Builder:log('Got all supplies')
|
||||
os.sleep(0)
|
||||
Builder:gotoBuilder()
|
||||
Builder.resupplying = false
|
||||
end)
|
||||
|
||||
Message.addHandler('needSupplies',
|
||||
function(h, id, msg, distance)
|
||||
if not id or id ~= __BUILDER_ID then
|
||||
return
|
||||
end
|
||||
|
||||
if Builder.resupplying or msg.contents.uid ~= Builder.slotUid then
|
||||
|
||||
Builder:log('No supplies ready')
|
||||
|
||||
Message.send(__BUILDER_ID, 'gotSupplies')
|
||||
else
|
||||
turtle.status = 'supplying'
|
||||
Builder:log('Supplying')
|
||||
os.sleep(0)
|
||||
|
||||
local pt = msg.contents.point
|
||||
pt.y = turtle.getPoint().y
|
||||
pt.heading = nil
|
||||
if not turtle.gotoYfirst(pt) then -- location of builder
|
||||
Builder.resupplying = true
|
||||
Message.send(__BUILDER_ID, 'gotSupplies')
|
||||
os.sleep(0)
|
||||
if not turtle.gotoLocation('supplies') then
|
||||
Builder:log('failed to go to supply location')
|
||||
self.ready = false
|
||||
Event.exitPullEvents()
|
||||
end
|
||||
return
|
||||
end
|
||||
pt.y = pt.y - 2 -- location where builder should go for the chest to be above
|
||||
|
||||
turtle.select(15)
|
||||
turtle.placeDown()
|
||||
os.sleep(.1) -- random computer not connected error
|
||||
local p = ChestProvider({ direction = 'up', wrapSide = 'bottom' })
|
||||
for i = 1, 16 do
|
||||
p:insert(i, 64)
|
||||
end
|
||||
|
||||
Message.send(__BUILDER_ID, 'gotSupplies', { supplies = true, point = pt })
|
||||
|
||||
Message.waitForMessage('thanks', 5, __BUILDER_ID)
|
||||
--os.sleep(0)
|
||||
|
||||
--p.condenseItems()
|
||||
for i = 1, 16 do
|
||||
p:extract(i, 64)
|
||||
end
|
||||
turtle.digDown()
|
||||
turtle.status = 'waiting'
|
||||
end
|
||||
end)
|
||||
|
||||
Message.addHandler('finished',
|
||||
function(h, id)
|
||||
if not id or id ~= __BUILDER_ID then
|
||||
return
|
||||
end
|
||||
Builder:finish()
|
||||
end)
|
||||
|
||||
Event.addHandler('turtle_abort',
|
||||
function()
|
||||
turtle.abort = false
|
||||
turtle.status = 'aborting'
|
||||
Builder:finish()
|
||||
end)
|
||||
|
||||
local function onTheWay() -- parallel routine
|
||||
while true do
|
||||
local e, side, _id, id, msg, distance = os.pullEvent('modem_message')
|
||||
if Builder.ready then
|
||||
if id == __BUILDER_ID and msg and msg.type then
|
||||
if msg.type == 'needSupplies' then
|
||||
Message.send(__BUILDER_ID, 'gotSupplies', { supplies = true })
|
||||
elseif msg.type == 'builder' then
|
||||
Builder.lastPoint = msg.contents
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local args = {...}
|
||||
if #args < 1 then
|
||||
error('Supply id for builder')
|
||||
end
|
||||
|
||||
__BUILDER_ID = tonumber(args[1])
|
||||
|
||||
maxStackDB:load()
|
||||
|
||||
turtle.setPoint({ x = -1, z = -2, y = 0, heading = 0 })
|
||||
turtle.saveLocation('supplies')
|
||||
|
||||
Builder.itemProvider = MEProvider()
|
||||
if not Builder.itemProvider:isValid() then
|
||||
Builder.itemProvider = ChestProvider()
|
||||
if not Builder.itemProvider:isValid() then
|
||||
error('A chest or ME interface must be below turtle')
|
||||
end
|
||||
end
|
||||
|
||||
turtle.run(function()
|
||||
Event.pullEvents(onTheWay)
|
||||
end)
|
98
sys/apps/t.lua
Normal file
98
sys/apps/t.lua
Normal file
@@ -0,0 +1,98 @@
|
||||
function doCommand(command, moves)
|
||||
--[[
|
||||
if command == 'sl' then
|
||||
local pt = GPS.getPoint()
|
||||
if pt then
|
||||
turtle.storeLocation(moves, pt)
|
||||
end
|
||||
return
|
||||
end
|
||||
--]]
|
||||
|
||||
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
|
81
sys/apps/telnet.lua
Normal file
81
sys/apps/telnet.lua
Normal file
@@ -0,0 +1,81 @@
|
||||
require = requireInjector(getfenv(1))
|
||||
local process = require('process')
|
||||
local Socket = require('socket')
|
||||
local Terminal = require('terminal')
|
||||
|
||||
local remoteId
|
||||
local args = { ... }
|
||||
if #args == 1 then
|
||||
remoteId = tonumber(args[1])
|
||||
else
|
||||
print('Enter host ID')
|
||||
remoteId = tonumber(read())
|
||||
end
|
||||
|
||||
if not remoteId then
|
||||
error('Syntax: telnet <host ID>')
|
||||
end
|
||||
|
||||
print('connecting...')
|
||||
local socket = Socket.connect(remoteId, 23)
|
||||
|
||||
if not socket then
|
||||
error('Unable to connect to ' .. remoteId .. ' on port 23')
|
||||
end
|
||||
|
||||
local ct = Util.shallowCopy(term.current())
|
||||
if not ct.isColor() then
|
||||
Terminal.toGrayscale(ct)
|
||||
end
|
||||
|
||||
local w, h = ct.getSize()
|
||||
socket:write({
|
||||
type = 'termInfo',
|
||||
width = w,
|
||||
height = h,
|
||||
isColor = ct.isColor(),
|
||||
})
|
||||
|
||||
process:newThread('telnet_read', function()
|
||||
while true do
|
||||
local data = socket:read()
|
||||
if not data then
|
||||
break
|
||||
end
|
||||
for _,v in ipairs(data) do
|
||||
ct[v.f](unpack(v.args))
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
ct.clear()
|
||||
ct.setCursorPos(1, 1)
|
||||
|
||||
local filter = Util.transpose({
|
||||
'char', 'paste', 'key', 'key_up', 'mouse_scroll', 'mouse_click', 'mouse_drag',
|
||||
})
|
||||
|
||||
while true do
|
||||
local e = { process:pullEvent() }
|
||||
local event = e[1]
|
||||
|
||||
if not socket.connected then
|
||||
print()
|
||||
print('Connection lost')
|
||||
print('Press enter to exit')
|
||||
read()
|
||||
break
|
||||
end
|
||||
|
||||
if filter[event] then
|
||||
|
||||
if not socket:write({ type = 'shellRemote', event = e }) then
|
||||
socket:close()
|
||||
break
|
||||
end
|
||||
|
||||
elseif event == 'terminate' then
|
||||
socket:close()
|
||||
break
|
||||
end
|
||||
end
|
52
sys/apps/trace.lua
Normal file
52
sys/apps/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
|
48
sys/apps/trust.lua
Normal file
48
sys/apps/trust.lua
Normal file
@@ -0,0 +1,48 @@
|
||||
require = requireInjector(getfenv(1))
|
||||
local Socket = require('socket')
|
||||
local SHA1 = require('sha1')
|
||||
local Terminal = require('terminal')
|
||||
local Crypto = require('crypto')
|
||||
|
||||
local remoteId
|
||||
local args = { ... }
|
||||
|
||||
if #args == 1 then
|
||||
remoteId = tonumber(args[1])
|
||||
else
|
||||
print('Enter host ID')
|
||||
remoteId = tonumber(read())
|
||||
end
|
||||
|
||||
if not remoteId then
|
||||
error('Syntax: trust <host ID>')
|
||||
end
|
||||
|
||||
local password = Terminal.readPassword('Enter password: ')
|
||||
|
||||
if not password then
|
||||
error('Invalid password')
|
||||
end
|
||||
|
||||
print('connecting...')
|
||||
local socket = Socket.connect(remoteId, 19)
|
||||
|
||||
if not socket then
|
||||
error('Unable to connect to ' .. remoteId .. ' on port 19')
|
||||
end
|
||||
|
||||
local publicKey = os.getPublicKey()
|
||||
local password = SHA1.sha1(password)
|
||||
|
||||
socket:write(Crypto.encrypt({ pk = publicKey, dh = os.getComputerID() }, password))
|
||||
|
||||
local data = socket:read(2)
|
||||
socket:close()
|
||||
|
||||
if data and data.success then
|
||||
print(data.msg)
|
||||
elseif data then
|
||||
error(data.msg)
|
||||
else
|
||||
error('No response')
|
||||
end
|
2
sys/apps/update.lua
Normal file
2
sys/apps/update.lua
Normal file
@@ -0,0 +1,2 @@
|
||||
local options = table.concat({ ... }, ' ')
|
||||
shell.run('pastebin run sj4VMVJj ' .. options)
|
88
sys/apps/vnc.lua
Normal file
88
sys/apps/vnc.lua
Normal file
@@ -0,0 +1,88 @@
|
||||
require = requireInjector(getfenv(1))
|
||||
local process = require('process')
|
||||
local Socket = require('socket')
|
||||
local Terminal = require('terminal')
|
||||
|
||||
local remoteId
|
||||
local args = { ... }
|
||||
if #args == 1 then
|
||||
remoteId = tonumber(args[1])
|
||||
else
|
||||
print('Enter host ID')
|
||||
remoteId = tonumber(read())
|
||||
end
|
||||
|
||||
if not remoteId then
|
||||
error('Syntax: vnc <host ID>')
|
||||
end
|
||||
|
||||
multishell.setTitle(multishell.getCurrent(), 'VNC-' .. remoteId)
|
||||
|
||||
print('connecting...')
|
||||
local socket = Socket.connect(remoteId, 5900)
|
||||
|
||||
if not socket then
|
||||
error('Unable to connect to ' .. remoteId .. ' on port 5900')
|
||||
end
|
||||
|
||||
local w, h = term.getSize()
|
||||
socket:write({
|
||||
type = 'termInfo',
|
||||
width = w,
|
||||
height = h,
|
||||
isColor = term.isColor(),
|
||||
})
|
||||
|
||||
local ct = Util.shallowCopy(term.current())
|
||||
|
||||
if not ct.isColor() then
|
||||
Terminal.toGrayscale(ct)
|
||||
end
|
||||
|
||||
process:newThread('vnc_read', function()
|
||||
while true do
|
||||
local data = socket:read()
|
||||
if not data then
|
||||
break
|
||||
end
|
||||
for _,v in ipairs(data) do
|
||||
ct[v.f](unpack(v.args))
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
ct.clear()
|
||||
ct.setCursorPos(1, 1)
|
||||
|
||||
while true do
|
||||
local e = { process:pullEvent() }
|
||||
local event = e[1]
|
||||
|
||||
if not socket.connected then
|
||||
print()
|
||||
print('Connection lost')
|
||||
print('Press enter to exit')
|
||||
read()
|
||||
break
|
||||
end
|
||||
|
||||
if event == 'char' or
|
||||
event == 'paste' or
|
||||
event == 'key' or
|
||||
event == 'key_up' or
|
||||
event == 'mouse_scroll' or
|
||||
event == 'mouse_click' or
|
||||
event == 'mouse_drag' then
|
||||
|
||||
socket:write({
|
||||
type = 'shellRemote',
|
||||
event = e,
|
||||
})
|
||||
elseif event == 'terminate' then
|
||||
socket:close()
|
||||
ct.setBackgroundColor(colors.black)
|
||||
ct.clear()
|
||||
ct.setCursorPos(1, 1)
|
||||
break
|
||||
end
|
||||
end
|
52
sys/autorun/gps.lua
Normal file
52
sys/autorun/gps.lua
Normal file
@@ -0,0 +1,52 @@
|
||||
if turtle and device.wireless_modem then
|
||||
|
||||
local s, m = turtle.run(function()
|
||||
local homePt = turtle.loadLocation('gpsHome')
|
||||
|
||||
if homePt then
|
||||
|
||||
require = requireInjector(getfenv(1))
|
||||
local Config = require('config')
|
||||
local config = {
|
||||
destructive = false,
|
||||
}
|
||||
Config.load('gps', config)
|
||||
|
||||
local GPS = require('gps')
|
||||
local pt
|
||||
for i = 1, 3 do
|
||||
pt = GPS.getPointAndHeading(2)
|
||||
if pt then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not pt and config.destructive then
|
||||
turtle.setPolicy('turtleSafe')
|
||||
pt = GPS.getPointAndHeading(2)
|
||||
end
|
||||
|
||||
if not pt then
|
||||
error('Unable to get GPS position')
|
||||
end
|
||||
|
||||
if config.destructive then
|
||||
turtle.setPolicy('turtleSafe')
|
||||
end
|
||||
|
||||
Util.print('Setting turtle point to %d %d %d', pt.x, pt.y, pt.z)
|
||||
turtle.setPoint(pt)
|
||||
turtle.getState().coordSystem = 'GPS'
|
||||
|
||||
if not turtle.pathfind(homePt) then
|
||||
error('Failed to return home')
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
turtle.setPolicy('none')
|
||||
|
||||
if not s and m then
|
||||
error(m)
|
||||
end
|
||||
end
|
@@ -2,22 +2,20 @@ print('\nStarting multishell..')
|
||||
|
||||
LUA_PATH = '/sys/apis'
|
||||
|
||||
-- math.randomseed(os.clock()) -- totally broken
|
||||
|
||||
_G.Util = dofile('/sys/apis/util.lua')
|
||||
_G.Util = dofile('sys/apis/util.lua')
|
||||
_G.debug = function(...) Util.print(...) end
|
||||
_G.requireInjector = dofile('/sys/apis/injector.lua')
|
||||
_G.requireInjector = dofile('sys/apis/injector.lua')
|
||||
|
||||
os.run(Util.shallowCopy(getfenv(1)), '/sys/extensions/device.lua')
|
||||
os.run(Util.shallowCopy(getfenv(1)), 'sys/extensions/device.lua')
|
||||
|
||||
-- vfs
|
||||
local s, m = os.run(Util.shallowCopy(getfenv(1)), '/sys/extensions/vfs.lua')
|
||||
local s, m = os.run(Util.shallowCopy(getfenv(1)), 'sys/extensions/vfs.lua')
|
||||
if not s then
|
||||
error(m)
|
||||
end
|
||||
|
||||
-- process fstab
|
||||
local mounts = Util.readFile('config/fstab')
|
||||
local mounts = Util.readFile('usr/config/fstab')
|
||||
if mounts then
|
||||
for _,l in ipairs(Util.split(mounts)) do
|
||||
if l:sub(1, 1) ~= '#' then
|
||||
@@ -27,8 +25,16 @@ if mounts then
|
||||
end
|
||||
end
|
||||
|
||||
-- user environment
|
||||
if not fs.exists('usr/apps') then
|
||||
fs.makeDir('usr/apps')
|
||||
end
|
||||
if not fs.exists('usr/autorun') then
|
||||
fs.makeDir('usr/autorun')
|
||||
end
|
||||
|
||||
local env = Util.shallowCopy(getfenv(1))
|
||||
env.multishell = { }
|
||||
|
||||
local _, m = os.run(env, '/apps/shell', '/apps/multishell')
|
||||
local _, m = os.run(env, 'sys/apps/shell', 'sys/apps/multishell')
|
||||
printError(m or 'Multishell aborted')
|
||||
|
667
sys/etc/blockIds.csv
Normal file
667
sys/etc/blockIds.csv
Normal file
@@ -0,0 +1,667 @@
|
||||
Air,0,minecraft:air,Non-Solid Block
|
||||
Stone,1,minecraft:stone,Solid Block
|
||||
Granite,1:1,,Solid Block
|
||||
Polished Granite,1:2,,Solid Block
|
||||
Diorite,1:3,,Solid Block
|
||||
Polished Diorite,1:4,,Solid Block
|
||||
Andesite,1:5,,Solid Block
|
||||
Polished Andesite,1:6,,Solid Block
|
||||
Grass,2,minecraft:grass,Solid Block
|
||||
Dirt,3,minecraft:dirt,Solid Block
|
||||
Dirt (No Grass),3:1,,Solid Block
|
||||
Podzol,3:2,,Solid Block
|
||||
Cobblestone,4,minecraft:cobblestone,Solid Block
|
||||
Wooden Plank (Oak),5,minecraft:planks,Solid Block
|
||||
Wooden Plank (Spruce),5:1,,Solid Block
|
||||
Wooden Plank (Birch),5:2,,Solid Block
|
||||
Wooden Plank (Jungle),5:3,,Solid Block
|
||||
Wooden Plank (Acacia),5:4,,Solid Block
|
||||
Wooden Plank (Dark Oak),5:5,,Solid Block
|
||||
Sapling (Oak),6,minecraft:sapling,Plant
|
||||
Sapling (Spruce),6:1,,Plant
|
||||
Sapling (Birch),6:2,,Plant
|
||||
Sapling (Jungle),6:3,,Plant
|
||||
Sapling (Acacia),6:4,,Plant
|
||||
Sapling (Dark Oak),6:5,,Plant
|
||||
Bedrock,7,minecraft:bedrock,Solid Block
|
||||
Water,8,minecraft:flowing_water,Fluid
|
||||
Water (No Spread),9,minecraft:water,Fluid
|
||||
Lava,10,minecraft:flowing_lava,Fluid
|
||||
Lava (No Spread),11,minecraft:lava,Fluid
|
||||
Sand,12,minecraft:sand,Solid Block
|
||||
Red Sand,12:1,,Solid Block
|
||||
Gravel,13,minecraft:gravel,Solid Block
|
||||
Gold Ore,14,minecraft:gold_ore,Solid Block
|
||||
Iron Ore,15,minecraft:iron_ore,Solid Block
|
||||
Coal Ore,16,minecraft:coal_ore,Solid Block
|
||||
Wood (Oak),17,minecraft:log,Solid Block
|
||||
Wood (Spruce),17:1,,Solid Block
|
||||
Wood (Birch),17:2,,Solid Block
|
||||
Wood (Jungle),17:3,,Solid Block
|
||||
Wood (Oak 4),17:4,,Solid Block
|
||||
Wood (Oak 5),17:5,,Solid Block
|
||||
Leaves (Oak),18,minecraft:leaves,Solid Block
|
||||
Leaves (Spruce),18:1,,Solid Block
|
||||
Leaves (Birch),18:2,,Solid Block
|
||||
Leaves (Jungle),18:3,,Solid Block
|
||||
Sponge,19,minecraft:sponge,Solid Block
|
||||
Glass,20,minecraft:glass,Solid Block
|
||||
Lapis Lazuli Ore,21,minecraft:lapis_ore,Solid Block
|
||||
Lapis Lazuli Block,22,minecraft:lapis_block,Solid Block
|
||||
Dispenser,23,minecraft:dispenser,Block
|
||||
Sandstone,24,minecraft:sandstone,Solid Block
|
||||
Sandstone (Chiseled),24:1,,Solid Block
|
||||
Sandstone (Smooth),24:2,,Solid Block
|
||||
Note Block,25,minecraft:noteblock,Solid Block
|
||||
Bed (Block),26,minecraft:bed,Solid Block
|
||||
Rail (Powered),27,minecraft:golden_rail,Non-Solid Block
|
||||
Rail (Detector),28,minecraft:detector_rail,Non-Solid Block
|
||||
Sticky Piston,29,minecraft:sticky_piston,Solid Block
|
||||
Cobweb,30,minecraft:web,Non-Solid Block
|
||||
Dead Shrub,31,minecraft:tallgrass,Plant
|
||||
Tall Grass,31:1,,Plant
|
||||
Fern,31:2,,Plant
|
||||
Dead Shrub,32,minecraft:deadbush,Plant
|
||||
Piston,33,minecraft:piston,Solid Block
|
||||
Piston (Head),34,minecraft:piston_head,Solid Block
|
||||
Wool,35,minecraft:wool,Solid Block
|
||||
Orange Wool,35:1,,Solid Block
|
||||
Magenta Wool,35:2,,Solid Block
|
||||
Light Blue Wool,35:3,,Solid Block
|
||||
Yellow Wool,35:4,,Solid Block
|
||||
Lime Wool,35:5,,Solid Block
|
||||
Pink Wool,35:6,,Solid Block
|
||||
Gray Wool,35:7,,Solid Block
|
||||
Light Gray Wool,35:8,,Solid Block
|
||||
Cyan Wool,35:9,,Solid Block
|
||||
Purple Wool,35:10,,Solid Block
|
||||
Blue Wool,35:11,,Solid Block
|
||||
Brown Wool,35:12,,Solid Block
|
||||
Green Wool,35:13,,Solid Block
|
||||
Red Wool,35:14,,Solid Block
|
||||
Black Wool,35:15,,Solid Block
|
||||
Piston (Moving),36,minecraft:piston_extension,Solid Block
|
||||
Dandelion,37,minecraft:yellow_flower,Plant
|
||||
Popppy,38,minecraft:red_flower,Plant
|
||||
Blue Orchid,38:1,,Plant
|
||||
Allium,38:2,,Plant
|
||||
Azure Bluet,38:3,,
|
||||
Red Tulip,38:4,,Plant
|
||||
Orange Tulip,38:5,,Plant
|
||||
White Tulip,38:6,,Plant
|
||||
Pink Tulip,38:7,,Plant
|
||||
Oxeye Daisy,38:8,,Plant
|
||||
Brown Mushroom,39,minecraft:brown_mushroom,Block
|
||||
Red Mushroom,40,minecraft:red_mushroom,Block
|
||||
Block of Gold,41,minecraft:gold_block,Solid Block
|
||||
Block of Iron,42,minecraft:iron_block,Solid Block
|
||||
Stone Slab (Double),43,minecraft:double_stone_slab,Solid Block
|
||||
Sandstone Slab (Double),43:1,,Solid Block
|
||||
Wooden Slab (Double),43:2,,Solid Block
|
||||
Cobblestone Slab (Double),43:3,,Solid Block
|
||||
Brick Slab (Double),43:4,,Solid Block
|
||||
Stone Brick Slab (Double),43:5,,Solid Block
|
||||
Nether Brick Slab (Double),43:6,,Solid Block
|
||||
Quartz Slab (Double),43:7,,Solid Block
|
||||
Smooth Stone Slab (Double),43:8,,Solid Block
|
||||
Smooth Sandstone Slab (Double),43:9,,Solid Block
|
||||
Double Quartz Slab,43:15,,Solid Block
|
||||
Stone Slab,44,minecraft:stone_slab,Solid Block
|
||||
Sandstone Slab,44:1,,Solid Block
|
||||
Wooden Slab,44:2,,Solid Block
|
||||
Cobblestone Slab,44:3,,Solid Block
|
||||
Brick Slab,44:4,,Solid Block
|
||||
Stone Brick Slab,44:5,,Solid Block
|
||||
Nether Brick Slab,44:6,,Solid Block
|
||||
Quartz Slab,44:7,,Solid Block
|
||||
Brick,45,minecraft:brick_block,Solid Block
|
||||
TNT,46,minecraft:tnt,Solid Block
|
||||
Bookshelf,47,minecraft:bookshelf,Solid Block
|
||||
Moss Stone,48,minecraft:mossy_cobblestone,Solid Block
|
||||
Obsidian,49,minecraft:obsidian,Solid Block
|
||||
Torch,50,minecraft:torch,Non-Solid Block
|
||||
Fire,51,minecraft:fire,Non-Solid Block
|
||||
Mob Spawner,52,minecraft:mob_spawner,Solid Block
|
||||
Wooden Stairs (Oak),53,minecraft:oak_stairs,Solid Block
|
||||
Chest,54,minecraft:chest,Tile Entity
|
||||
Redstone Wire,55,minecraft:redstone_wire,Raw Materials
|
||||
Diamond Ore,56,minecraft:diamond_ore,Solid Block
|
||||
Block of Diamond,57,minecraft:diamond_block,Solid Block
|
||||
Crafting Table,58,minecraft:crafting_table,Solid Block
|
||||
Wheat (Crop),59,minecraft:wheat,Plant
|
||||
Farmland,60,minecraft:farmland,Solid Block
|
||||
Furnace,61,minecraft:furnace,Solid Block
|
||||
Furnace (Smelting),62,minecraft:lit_furnace,Solid Block
|
||||
Sign (Block),63,minecraft:standing_sign,Non-Solid Block
|
||||
Wood Door (Block),64,minecraft:wooden_door,Solid Block
|
||||
Ladder,65,minecraft:ladder,Solid Block
|
||||
Rail,66,minecraft:rail,Non-Solid Block
|
||||
Cobblestair Stairs,67,minecraft:stone_stairs,Solid Block
|
||||
Sign (Wall Block),68,minecraft:wall_sign,Non-Solid Block
|
||||
Lever,69,minecraft:lever,Non-Solid Block
|
||||
Stone Pressure Plate,70,minecraft:stone_pressure_plate,Non-Solid Block
|
||||
Iron Door (Block),71,minecraft:iron_door,Solid Block
|
||||
Wooden Pressure Plate,72,minecraft:wooden_pressure_plate,Non-Solid Blocks
|
||||
Redstone Ore,73,minecraft:redstone_ore,Solid Block
|
||||
Redstoen Ore (Glowing),74,minecraft:lit_redstone_ore,Solid Block
|
||||
Redstone Torch (Off),75,minecraft:unlit_redstone_torch,Non-Solid Block
|
||||
Redstone Torch,76,minecraft:redstone_torch,Non-Solid Block
|
||||
Button (Stone),77,minecraft:stone_button,Non-Solid Block
|
||||
Snow,78,minecraft:snow_layer,Block
|
||||
Ice,79,minecraft:ice,Solid Block
|
||||
Snow Block,80,minecraft:snow,Block
|
||||
Cactus,81,minecraft:cactus,Solid Block
|
||||
Clay Block,82,minecraft:clay,Solid Block
|
||||
Sugar Can (Block),83,minecraft:reeds,Non-Solid Block
|
||||
Jukebox,84,minecraft:jukebox,Solid Block
|
||||
Fence,85,minecraft:fence,Solid Block
|
||||
Pumpkin,86,minecraft:pumpkin,Solid Block
|
||||
Netherrack,87,minecraft:netherrack,Solid Block
|
||||
Soul Sand,88,minecraft:soul_sand,Solid Block
|
||||
Glowstone,89,minecraft:glowstone,Solid Block
|
||||
Portal,90,minecraft:portal,Non-Solid Block
|
||||
Jack-O-Lantern,91,minecraft:lit_pumpkin,Solid Block
|
||||
Cake (Block),92,minecraft:cake,Food
|
||||
Redstone Repeater (Block Off),93,minecraft:unpowered_repeater,Solid Block
|
||||
Redstone Repeater (Block On),94,minecraft:powered_repeater,Solid Block
|
||||
Stained Glass (White),95,minecraft:stained_glass,Solid Block
|
||||
Stained Glass (Orange),95:1,,Solid Block
|
||||
Stained Glass (Magenta),95:2,,Solid Block
|
||||
Stained Glass (Light Blue),95:3,,Solid Block
|
||||
Stained Glass (Yellow),95:4,,Solid Block
|
||||
Stained Glass (Lime),95:5,,Solid Block
|
||||
Stained Glass (Pink),95:6,,Solid Block
|
||||
Stained Glass (Gray),95:7,,Solid Block
|
||||
Stained Glass (Light Gray),95:8,,Solid Block
|
||||
Stained Glass (Cyan),95:9,,Solid Block
|
||||
Stained Glass (Purple),95:10,,Solid Block
|
||||
Stained Glass (Blue),95:11,,Solid Block
|
||||
Stained Glass (Brown),95:12,,Solid Block
|
||||
Stained Glass (Green),95:13,,Solid Block
|
||||
Stained Glass (Red),95:14,,Solid Block
|
||||
Stained Glass (Black),95:15,,Solid Block
|
||||
Trapdoor,96,minecraft:trapdoor,Solid Block
|
||||
Monster Egg (Stone),97,minecraft:monster_egg,Solid
|
||||
Monster Egg (Cobblestone),97:1,,Solid
|
||||
Monster Egg (Stone Brick),97:2,,Solid
|
||||
Monster Egg (Mossy Stone Brick),97:3,,Solid
|
||||
Monster Egg (Cracked Stone),97:4,,Solid
|
||||
Monster Egg (Chiseled Stone),97:5,,Solid
|
||||
Stone Brick,98,minecraft:stonebrick,Solid Block
|
||||
Mossy Stone Brick,98:1,,Solid Block
|
||||
Cracked Stone Brick,98:2,,Solid Block
|
||||
Chiseled Stone Brick,98:3,,Solid Block
|
||||
Brown Mushroom (Block),99,minecraft:brown_mushroom_block,Solid Block
|
||||
Red Mushroom (Block),100,minecraft:red_mushroom_block,Solid Block
|
||||
Iron Bars,101,minecraft:iron_bars,Solid Block
|
||||
Glass Pane,102,minecraft:glass_pane,Solid Block
|
||||
Melon (Block),103,minecraft:melon_block,Solid Block
|
||||
Pumpkin Vine,104,minecraft:pumpkin_stem,
|
||||
Melon Vine,105,minecraft:melon_stem,
|
||||
Vines,106,minecraft:vine,
|
||||
Fence Gate,107,minecraft:fence_gate,
|
||||
Brick Stairs,108,minecraft:brick_stairs,
|
||||
Stone Brick Stairs,109,minecraft:stone_brick_stairs,
|
||||
Mycelium,110,minecraft:mycelium,
|
||||
Lily Pad,111,minecraft:waterlily,
|
||||
Nether Brick,112,minecraft:nether_brick,
|
||||
Nether Brick Fence,113,minecraft:nether_brick_fence,
|
||||
Nether Brick Stairs,114,minecraft:nether_brick_stairs,
|
||||
Nether Wart,115,minecraft:nether_wart,
|
||||
Enchantment Table,116,minecraft:enchanting_table,
|
||||
Brewing Stand (Block),117,minecraft:brewing_stand,
|
||||
Cauldron (Block),118,minecraft:cauldron,
|
||||
End Portal,119,minecraft:end_portal,
|
||||
End Portal Frame,120,minecraft:end_portal_frame,
|
||||
End Stone,121,minecraft:end_stone,
|
||||
Dragon Egg,122,minecraft:dragon_egg,
|
||||
Redstone Lamp,123,minecraft:redstone_lamp,
|
||||
Redstone Lamp (On),124,minecraft:lit_redstone_lamp,
|
||||
Oak Wood Slab (Double),125,minecraft:double_wooden_slab,
|
||||
Spruce Wood Slab (Double),125:1,,
|
||||
Birch Wood Slab (Double),125:2,,
|
||||
Jungle Wood Slab (Double),125:3,,
|
||||
Acacia Wood Slab (Double),125:4,,
|
||||
Dark Oak Wood Slab (Double),125:5,,
|
||||
Oak Wood Slab,126,minecraft:wooden_slab,
|
||||
Spruce Wood Slab,126:1,,
|
||||
Birch Wood Slab,126:2,,
|
||||
Jungle Wood Slab,126:3,,
|
||||
Acacia Wood Slab,126:4,,
|
||||
Dark Oak Wood Slab,126:5,,
|
||||
Cocoa Plant,127,minecraft:cocoa,
|
||||
Sandstone Stairs,128,minecraft:sandstone_stairs,
|
||||
Emerald Ore,129,minecraft:emerald_ore,
|
||||
Ender Chest,130,minecraft:ender_chest,
|
||||
Tripwire Hook,131,minecraft:tripwire_hook,
|
||||
Tripewire,132,minecraft:tripwire,
|
||||
Block of Emerald,133,minecraft:emerald_block,
|
||||
Wooden Stairs (Spruce),134,minecraft:spruce_stairs,
|
||||
Wooden Stairs (Birch),135,minecraft:birch_stairs,
|
||||
Wooden Stairs (Jungle),136,minecraft:jungle_stairs,
|
||||
Command Block,137,minecraft:command_block,
|
||||
Beacon,138,minecraft:beacon,
|
||||
Cobblestone Wall,139,minecraft:cobblestone_wall,
|
||||
Mossy Cobblestone Wall,139:1,,
|
||||
Flower Pot (Block),140,minecraft:flower_pot,
|
||||
Carrot (Crop),141,minecraft:carrots,
|
||||
Potatoes (Crop),142,minecraft:potatoes,
|
||||
Button (Wood),143,minecraft:wooden_button,
|
||||
Head Block (Skeleton),144,minecraft:skull,
|
||||
Head Block (Wither),144:1,,
|
||||
Head Block (Zombie),144:2,,
|
||||
Head Block (Steve),144:3,,
|
||||
Head Block (Creeper),144:4,,
|
||||
Anvil,145,minecraft:anvil,
|
||||
Anvil (Slightly Damaged),145:1,,
|
||||
Anvil (Very Damaged),145:2,,
|
||||
Trapped Chest,146,minecraft:trapped_chest,
|
||||
Weighted Pressure Plate (Light) (Gold),147,minecraft:light_weighted_pressure_plate,
|
||||
Weighted Pressure Plate (Heavy) (Iron),148,minecraft:heavy_weighted_pressure_plate,
|
||||
Redstone Comparator (Off),149,minecraft:unpowered_comparator,
|
||||
Redstone Comparator (On),150,minecraft:powered_comparator,
|
||||
Daylight Sensor,151,minecraft:daylight_detector,
|
||||
Block of Redstone,152,minecraft:redstone_block,
|
||||
Nether Quartz Ore,153,minecraft:quartz_ore,
|
||||
Hopper,154,minecraft:hopper,
|
||||
Quartz Block,155,minecraft:quartz_block,
|
||||
Chiseled Quartz Block,155:1,,
|
||||
Pillar Quartz Block,155:2,,
|
||||
Quartz Stairs,156,minecraft:quartz_stairs,
|
||||
Rail (Activator),157,minecraft:activator_rail,
|
||||
Dropper,158,minecraft:dropper,
|
||||
Stained Clay (White),159,minecraft:stained_hardened_clay,Solid Block
|
||||
Stained Clay (Orange),159:1,,Solid Block
|
||||
Stained Clay (Magenta),159:2,,Solid Block
|
||||
Stained Clay (Light Blue),159:3,,Solid Block
|
||||
Stained Clay (Yellow),159:4,,Solid Block
|
||||
Stained Clay (Lime),159:5,,Solid Block
|
||||
Stained Clay (Pink),159:6,,Solid Block
|
||||
Stained Clay (Gray),159:7,,Solid Block
|
||||
Stained Clay (Light Gray),159:8,,Solid Block
|
||||
Stained Clay (Cyan),159:9,,Solid Block
|
||||
Stained Clay (Purple),159:10,,Solid Block
|
||||
Stained Clay (Blue),159:11,,Solid Block
|
||||
Stained Clay (Brown),159:12,,Solid Block
|
||||
Stained Clay (Green),159:13,,Solid Block
|
||||
Stained Clay (Red),159:14,,Solid Block
|
||||
Stained Clay (Black),159:15,,Solid Block
|
||||
Stained Glass Pane (White),160,minecraft:stained_glass_pane,Solid Block
|
||||
Stained Glass Pane (Orange),160:1,,Solid Block
|
||||
Stained Glass Pane (Magenta),160:2,,Solid Block
|
||||
Stained Glass Pane (Light Blue),160:3,,Solid Block
|
||||
Stained Glass Pane (Yellow),160:4,,Solid Block
|
||||
Stained Glass Pane (Lime),160:5,,Solid Block
|
||||
Stained Glass Pane (Pink),160:6,,Solid Block
|
||||
Stained Glass Pane (Gray),160:7,,Solid Block
|
||||
Stained Glass Pane (Light Gray),160:8,,Solid Block
|
||||
Stained Glass Pane (Cyan),160:9,,Solid Block
|
||||
Stained Glass Pane (Purple),160:10,,Solid Block
|
||||
Stained Glass Pane (Blue),160:11,,Solid Block
|
||||
Stained Glass Pane (Brown),160:12,,Solid Block
|
||||
Stained Glass Pane (Green),160:13,,Solid Block
|
||||
Stained Glass Pane (Red),160:14,,Solid Block
|
||||
Stained Glass Pane (Black),160:15,,Solid Block
|
||||
Acacia Leaves,161,minecraft:leaves2,Solid Block
|
||||
Dark Oak Leaves,161:1,,Solid Block
|
||||
Acacia Wood,162,minecraft:log2,Solid Block
|
||||
Dark Oak Wood,162:1,,Solid Block
|
||||
Wooden Stairs (Acacia Oak),163,minecraft:acacia_stairs,
|
||||
Wooden Stairs (Dark Oak),164,minecraft:dark_oak_stairs,
|
||||
Slime Block,165,minecraft:slime,
|
||||
Barrier,166,minecraft:barrier,
|
||||
Iron Trapdoor,167,minecraft:iron_trapdoor,
|
||||
Prismarine,168,minecraft:prismarine,
|
||||
Prismarine Bricks,168:1,,
|
||||
Dark Prismarine,168:2,,
|
||||
Sea Lantern,169,minecraft:sea_lantern,
|
||||
Hay Bale,170,minecraft:hay_block,
|
||||
Carpet (White),171,minecraft:carpet,
|
||||
Carpet (Orange),171:1,,
|
||||
Carpet (Magenta),171:2,,
|
||||
Carpet (Light Blue),171:3,,
|
||||
Carpet (Yellow),171:4,,
|
||||
Carpet (Lime),171:5,,
|
||||
Carpet (Pink),171:6,,
|
||||
Carpet (Gray),171:7,,
|
||||
Carpet (Light Gray),171:8,,
|
||||
Carpet (Cyan),171:9,,
|
||||
Carpet (Purple),171:10,,
|
||||
Carpet (Blue),171:11,,
|
||||
Carpet (Brown),171:12,,
|
||||
Carpet (Green),171:13,,
|
||||
Carpet (Red),171:14,,
|
||||
Carpet (Black),171:15,,
|
||||
Hardened Clay,172,minecraft:hardened_clay,
|
||||
Block of Coal,173,minecraft:coal_block,
|
||||
Packed Ice,174,minecraft:packed_ice,
|
||||
Sunflower,175,minecraft:double_plant,
|
||||
Lilac,175:1,,
|
||||
Double Tallgrass,175:2,,
|
||||
Large Fern,175:3,,
|
||||
Rose Bush,175:4,,
|
||||
Peony,175:5,,
|
||||
Free-standing Banner,176,minecraft:standing_banner,
|
||||
Wall-mounted Banner,177,minecraft:wall_banner,
|
||||
Inverted Daylight Sensor,178,minecraft:daylight_detector_inverted,
|
||||
Red Sandstone,179,minecraft:red_sandstone,
|
||||
Chiseled Red Sandstone,179:1,,
|
||||
Smooth Red Sandstone,179:2,,
|
||||
Red Sandstone Stairs,180,minecraft:red_sandstone_stairs,
|
||||
Double Red Sandstone Slab,181,minecraft:double_stone_slab2,
|
||||
Red Sandstone Slab,182,minecraft:stone_slab2,
|
||||
Spruce Fence Gate,183,minecraft:spruce_fence_gate,
|
||||
Birch Fence Gate,184,minecraft:birch_fence_gate,
|
||||
Jungle Fence Gate,185,minecraft:jungle_fence_gate,
|
||||
Dark Oak Fence Gate,186,minecraft:dark_oak_fence_gate,
|
||||
Acacia Fence Gate,187,minecraft:acacia_fence_gate,
|
||||
Spruce Fence,188,minecraft:spruce_fence,
|
||||
Birch Fence,189,minecraft:birch_fence,
|
||||
Jungle Fence,190,minecraft:jungle_fence,
|
||||
Dark Oak Fence,191,minecraft:dark_oak_fence,
|
||||
Acacia Fence,192,minecraft:acacia_fence,
|
||||
Spruce Door Block,193,minecraft:spruce_door,
|
||||
Birch Door Block,194,minecraft:birch_door,
|
||||
Jungle Door Block,195,minecraft:jungle_door,
|
||||
Acacia Door Block,196,minecraft:acacia_door,
|
||||
Dark Oak Door Block,197,minecraft:dark_oak_door,
|
||||
End Rod,198,minecraft:end_rod,
|
||||
Chorus Plant,199,minecraft:chorus_plant,
|
||||
Chorus Flower,200,minecraft:chorus_flower,
|
||||
Purpur Block,201,minecraft:purpur_block,
|
||||
Purpur Pillar,202,minecraft:purpur_pillar,
|
||||
Purpur Stairs,203,minecraft:purpur_stairs,
|
||||
Purpur Double Slab,204,minecraft:purpur_double_slab,
|
||||
Purpur Slab,205,minecraft:purpur_slab,
|
||||
End Stone Bricks,206,minecraft:end_bricks,
|
||||
Beetroot Block,207,minecraft:beetroots,
|
||||
Repeating Command Block,210,minecraft:repeating_command_block,
|
||||
Chain Command Block,211,minecraft:chain_command_block,
|
||||
Iron Shovel,256,minecraft:iron_shovel,
|
||||
Iron Pickaxe,257,minecraft:iron_pickaxe,
|
||||
Iron Axe,258,minecraft:iron_axe,
|
||||
Flint and Steel,259,minecraft:flint_and_steel,
|
||||
Apple,260,minecraft:apple,
|
||||
Bow,261,minecraft:bow,
|
||||
Arrow,262,minecraft:arrow,
|
||||
Coal,263,minecraft:coal,
|
||||
Charcoal,263:1,,
|
||||
Diamond,264,minecraft:diamond,
|
||||
Iron Ingot,265,minecraft:iron_ingot,
|
||||
Gold Ingot,266,minecraft:gold_ingot,
|
||||
Iron Sword,267,minecraft:iron_sword,
|
||||
Wooden Sword,268,minecraft:wooden_sword,
|
||||
Wooden Shovel,269,minecraft:wooden_shovel,
|
||||
Wooden Pickaxe,270,minecraft:wooden_pickaxe,
|
||||
Wooden Axe,271,minecraft:wooden_axe,
|
||||
Stone Sword,272,minecraft:stone_sword,
|
||||
Stone Shovel,273,minecraft:stone_shovel,
|
||||
Stone Pickaxe,274,minecraft:stone_pickaxe,
|
||||
Stone Axe,275,minecraft:stone_axe,
|
||||
Diamond Sword,276,minecraft:diamond_sword,
|
||||
Diamond Shovel,277,minecraft:diamond_shovel,
|
||||
Diamond Pickaxe,278,minecraft:diamond_pickaxe,
|
||||
Diamond Axe,279,minecraft:diamond_axe,
|
||||
Stick,280,minecraft:stick,
|
||||
Bowl,281,minecraft:bowl,
|
||||
Mushroom Stew,282,minecraft:mushroom_stew,
|
||||
Gold Sword,283,minecraft:golden_sword,
|
||||
Gold Shovel,284,minecraft:golden_shovel,
|
||||
Gold Pickaxe,285,minecraft:golden_pickaxe,
|
||||
Gold Axe,286,minecraft:golden_axe,
|
||||
String,287,minecraft:string,
|
||||
Feather,288,minecraft:feather,
|
||||
Gunpowder,289,minecraft:gunpowder,
|
||||
Wooden Hoe,290,minecraft:wooden_hoe,
|
||||
Stone Hoe,291,minecraft:stone_hoe,
|
||||
Iron Hoe,292,minecraft:iron_hoe,
|
||||
Diamond Hoe,293,minecraft:diamond_hoe,
|
||||
Gold Hoe,294,minecraft:golden_hoe,
|
||||
Wheat Seeds,295,minecraft:wheat_seeds,
|
||||
Wheat,296,minecraft:wheat,
|
||||
Bread,297,minecraft:bread,
|
||||
Leather Helment,298,minecraft:leather_helmet,
|
||||
Leather Tunic,299,minecraft:leather_chestplate,
|
||||
Leather Leggings,300,minecraft:leather_leggings,
|
||||
Leather Boots,301,minecraft:leather_boots,
|
||||
Chainmail Helment,302,minecraft:chainmail_helmet,
|
||||
Chainmail Chestplate,303,minecraft:chainmail_chestplate,
|
||||
Chainmail Leggings,304,minecraft:chainmail_leggings,
|
||||
Chainmail Boots,305,minecraft:chainmail_boots,
|
||||
Iron Helment,306,minecraft:iron_helmet,
|
||||
Iron Chestplate,307,minecraft:iron_chestplate,
|
||||
Iron Leggings,308,minecraft:iron_leggings,
|
||||
Iron Boots,309,minecraft:iron_boots,
|
||||
Diamond Helment,310,minecraft:diamond_helmet,
|
||||
Diamond Chestplate,311,minecraft:diamond_chestplate,
|
||||
Diamond Leggings,312,minecraft:diamond_leggings,
|
||||
Diamond Boots,313,minecraft:diamond_boots,
|
||||
Gold Helment,314,minecraft:golden_helmet,
|
||||
Gold Chestplate,315,minecraft:golden_chestplate,
|
||||
Gold Leggings,316,minecraft:golden_leggings,
|
||||
Gold Boots,317,minecraft:golden_boots,
|
||||
Flint,318,minecraft:flint,
|
||||
Raw Porkchop,319,minecraft:porkchop,
|
||||
Cooked Porkchop,320,minecraft:cooked_porkchop,
|
||||
Painting,321,minecraft:painting,
|
||||
Golden Apple,322,minecraft:golden_apple,
|
||||
Enchanted Golden Apple (Notch Apple),322:1,,
|
||||
Sign,323,minecraft:sign,
|
||||
Wooden Door,324,minecraft:wooden_door,
|
||||
Bucket,325,minecraft:bucket,
|
||||
Bucket (Water),326,minecraft:water_bucket,
|
||||
Bucket (Lava),327,minecraft:lava_bucket,
|
||||
Minecart,328,minecraft:minecart,
|
||||
Saddle,329,minecraft:saddle,
|
||||
Iron Door,330,minecraft:iron_door,
|
||||
Redstone Dust,331,minecraft:redstone,
|
||||
Snowball,332,minecraft:snowball,
|
||||
Boat,333,minecraft:boat,
|
||||
Leather,334,minecraft:leather,
|
||||
Bucket (Milk),335,minecraft:milk_bucket,
|
||||
Clay Brick,336,minecraft:brick,
|
||||
Clay,337,minecraft:clay_ball,
|
||||
Sugar Cane,338,minecraft:reeds,
|
||||
Paper,339,minecraft:paper,
|
||||
Book,340,minecraft:book,
|
||||
Slime Ball,341,minecraft:slime_ball,
|
||||
Minecart (Storage) (Chest),342,minecraft:chest_minecart,
|
||||
Minecart (Powered) (Furance),343,minecraft:furnace_minecart,
|
||||
Egg,344,minecraft:egg,
|
||||
Compass,345,minecraft:compass,
|
||||
Fishing Rod,346,minecraft:fishing_rod,
|
||||
Clock,347,minecraft:clock,
|
||||
Glowstone Dust,348,minecraft:glowstone_dust,
|
||||
Raw Fish,349,minecraft:fish,
|
||||
Raw Salmon,349:1,,
|
||||
Clownfish,349:2,,
|
||||
Pufferfish,349:3,,
|
||||
Cooked Fish,350,minecraft:cooked_fished,
|
||||
Cooked Salmon,350:1,,
|
||||
Clownfish,350:2,,
|
||||
Pufferfish,350:3,,
|
||||
Ink Sack,351,minecraft:dye,
|
||||
Rose Red Dye,351:1,,
|
||||
Cactus Green Dye,351:2,,
|
||||
Cocoa Bean,351:3,,
|
||||
Lapis Lazuli,351:4,,
|
||||
Purple Due,351:5,,
|
||||
Cyan Dye,351:6,,
|
||||
Light Gray Dye,351:7,,
|
||||
Gray Dye,351:8,,
|
||||
Pink Dye,351:9,,
|
||||
Lime Dye,351:10,,
|
||||
Dandelion Yellow Dye,351:11,,
|
||||
Light Blue Dye,351:12,,
|
||||
Magenta Dye,351:13,,
|
||||
Orange Dye,351:14,,
|
||||
Bone Meal,351:15,,
|
||||
Bone,352,minecraft:bone,
|
||||
Sugar,353,minecraft:sugar,
|
||||
Cake,354,minecraft:cake,
|
||||
Bed,355,minecraft:bed,
|
||||
Redstone Repeater,356,minecraft:repeater,
|
||||
Cookie,357,minecraft:cookie,
|
||||
Map,358,minecraft:filled_map,
|
||||
Shears,359,minecraft:shears,
|
||||
Melon (Slice),360,minecraft:melon,
|
||||
Pumplin Seeds,361,minecraft:pumpkin_seeds,
|
||||
Melon Seeds,362,minecraft:melon_seeds,
|
||||
Raw Beef,363,minecraft:beef,
|
||||
Steak,364,minecraft:cooked_beef,
|
||||
Raw Chicken,365,minecraft:chicken,
|
||||
Cooked Chicken,366,minecraft:cooked_chicken,
|
||||
Rotten Flesh,367,minecraft:rotten_flesh,
|
||||
Ender Pearl,368,minecraft:ender_pearl,
|
||||
Blaze Rod,369,minecraft:blaze_rod,
|
||||
Ghast Tear,370,minecraft:ghast_tear,
|
||||
Gold Nugget,371,minecraft:god_nugget,
|
||||
Nether Wart,372,minecraft:nether_wart,
|
||||
Water Bottle,373,minecraft:potion,Potion
|
||||
Awkward Potion,373:16,,Potion
|
||||
Thick Potion,373:32,,Potion
|
||||
Mundane Potion,373:64,,Potion
|
||||
Regeneration Potion (0:45),373:8193,,Potion
|
||||
Swiftness Potion (3:00),373:8194,,Potion
|
||||
Fire Resistance Potion (3:00),373:8195,,Potion
|
||||
Poison Potion (0:45),373:8196,,Potion
|
||||
Healing Potion,373:8197,,Potion
|
||||
Night Vision Potion (3:00),373:8198,,Potion
|
||||
Weakness Potion (1:30),373:8200,,Potion
|
||||
Strength Potion (3:00),373:8201,,Potion
|
||||
Slowness Potion (1:30),373:8202,,Potion
|
||||
Harming Potion,373:8204,,Potion
|
||||
Water Breathing Potion (3:00),373:8205,,Potion
|
||||
Invisibility Potion (3:00),373:8206,,Potion
|
||||
Regeneration Potion II (0:22),373:8225,,Potion
|
||||
Swiftness Potion II (1:30),373:8226,,Potion
|
||||
Poison Potion II (0:22),373:8228,,Potion
|
||||
Healing Potion II,373:8229,,Potion
|
||||
Strength Potion II (1:30),373:8233,,Potion
|
||||
Harming Potion II,373:8236,,Potion
|
||||
Regeneration Potion (2:00),373:8257,,Potion
|
||||
Swiftness Potion (8:00),373:8258,,Potion
|
||||
Fire Resistance Potion (8:00),373:8259,,Potion
|
||||
Poison Potion (2:00),373:8260,,Potion
|
||||
Night Vision Potion (8:00),373:8262,,Potion
|
||||
Weakness Potion (4:00),373:8264,,Potion
|
||||
Strength Potion (8:00),373:8265,,Potion
|
||||
Slowness Potion (4:00),373:8266,,Potion
|
||||
Water Breathing Potion (8:00),373:8269,,Potion
|
||||
Invisbility Potion (8:00),373:8270,,Potion
|
||||
Regeneration Potion II (1:00),373:8289,,Potion
|
||||
Swiftness Potion II (4:00),373:8290,,Potion
|
||||
Poison Potion II (1:00),373:8292,,Potion
|
||||
Strength Potion II (4:00),373:8297,,Potion
|
||||
Regeneration Splash (0:33),373:16385,,Potion
|
||||
Swiftness Splash (2:15),373:16386,,Potion
|
||||
Fire Resistance Splash (2:15),373:16387,,Potion
|
||||
Poison Splash (0:33),373:16288,,Potion
|
||||
Healing Splash,373:16389,,Potion
|
||||
Night Vision Splash (2:15),373:16390,,Potion
|
||||
Weakness Splash (1:07),373:16392,,Potion
|
||||
Strength Splash (2:15),373:16393,,Potion
|
||||
Slowness Splash (1:07),373:16394,,Potion
|
||||
Harming Splash,373:16396,,Potion
|
||||
Water Breathing Splash (2:15),373:16197,,Potion
|
||||
Invisbility Splash (2:15),373:16338,,Potion
|
||||
Regeneration Splash II (0:16),373:16417,,Potion
|
||||
Swiftness Splash II (1:07),373:16418,,Potion
|
||||
Poison Splash II (0:16),373:16420,,Potion
|
||||
Healing Splash II,373:16421,,Potion
|
||||
Strength Splash II (1:07),373:16425,,Potion
|
||||
Harming Splash II,373:16428,,Potion
|
||||
Regeneration Splash (1:30),373:16449,,Potion
|
||||
Swiftness Splash (6:00),373:16450,,Potion
|
||||
Fire Resistance Splash (6:00),373:16451,,Potion
|
||||
Poison Splash (1:30),373:16452,,Potion
|
||||
Night Vision Splash (6:00),373:16454,,Potion
|
||||
Weakness Splash (3:00),373:16456,,Potion
|
||||
Strength Splash (6:00),373:16457,,Potion
|
||||
Slowness Splash (3:00),373:16458,,Potion
|
||||
Water Breathing Splash (6:00),373:16461,,Potion
|
||||
Invisbility Splash (6:00),373:16462,,Potion
|
||||
Regeneration Splash II (0:45),373:16481,,Potion
|
||||
Swiftness Splash II (3:00),373:16482,,Potion
|
||||
Poison Splash II (0:45),373:16484,,Potion
|
||||
Strength Splash II (3:00),373:16489,,Potion
|
||||
Glass Bottle,374,minecraft:glass_bottle,
|
||||
Spider Eye,375,minecraft:spider_eye,
|
||||
Fermented Spider Eye,376,minecraft:fermented_spider_eye,
|
||||
Blaze Powder,377,minecraft:blaze_powder,
|
||||
Magma Cream,378,minecraft:magma_cream,
|
||||
Brewing Stand,379,minecraft:brewing_stand,
|
||||
Cauldron,380,minecraft:cauldron,
|
||||
Eye of Ender,381,minecraft:ender_eye,
|
||||
Glistering Melon,382,minecraft:speckled_melon,
|
||||
Spawn Egg (Creeper),383:50,minecraft:spawn_egg,Item
|
||||
Spawn Egg (Skeleton),383:51,,Item
|
||||
Spawn Egg (Spider),383:52,,Item
|
||||
Spawn Egg (Zombie),383:54,,Item
|
||||
Spawn Egg (Slime),383:55,,Item
|
||||
Spawn Egg (Ghast),383:56,,Item
|
||||
Spawn Egg (Zombie Pigmen),383:57,,Item
|
||||
Spawn Egg (Endermen),383:58,,Item
|
||||
Spawn Egg (Cave Spider),383:59,,Item
|
||||
Spawn Egg (Silverfish),383:60,,Item
|
||||
Spawn Egg (Blaze),383:61,,Item
|
||||
Spawn Egg (Magma Cube),383:62,,Item
|
||||
Spawn Egg (Bat),383:65,,Item
|
||||
Spawn Egg (Witch),383:66,,Item
|
||||
Spawn Egg (Pig),383:90,,Item
|
||||
Spawn Egg (Sheep),383:91,,Item
|
||||
Spawn Egg (Cow),383:92,,Item
|
||||
Spawn Egg (Chicken),383:93,,Item
|
||||
Spawn Egg (Squid),383:94,,Item
|
||||
Spawn Egg (Wolf),383:95,,Item
|
||||
Spawn Egg (Mooshroom),383:96,,Item
|
||||
Spawn Egg (Ocelot),383:98,,Item
|
||||
Spawn Egg (Horse),383:100,,Item
|
||||
Spawn Egg (Villager),383:120,,Item
|
||||
Bottle o' Enchanting,384,minecraft:experience_bottle,
|
||||
Fire Charge,385,minecraft:fire_charge,
|
||||
Book and Quill,386,minecraft:writable_book,
|
||||
Written Book,387,minecraft:written_book,
|
||||
Emerald,388,minecraft:emerald,
|
||||
Item Frame,389,minecraft:item_frame,
|
||||
Flower Pot,390,minecraft:flower_pot,
|
||||
Carrot,391,minecraft:carrot,
|
||||
Potato,392,minecraft:potato,
|
||||
Baked Potato,393,minecraft:baked_potato,
|
||||
Poisonous Potato,394,minecraft:poisonous_potato,
|
||||
Empty Map,395,minecraft:map,
|
||||
Golden Carrot,396,minecraft:golden_carrot,
|
||||
Head (Skeleton),397,minecraft:skull,
|
||||
Head (Wither),397:1,,
|
||||
Head (Zombie),397:2,,
|
||||
Head (Steve),397:3,,
|
||||
Head (Creeper),397:4,,
|
||||
Carrot on a Stick,398,minecraft:carrot_on_a_stick,
|
||||
Nether Star,399,minecraft:nether_star,
|
||||
Pumpkin Pie,400,minecraft:pumpkin_pie,
|
||||
Firework Rocket,401,minecraft:fireworks,
|
||||
Firework Star,402,minecraft:firework_charge,
|
||||
Enchanted Book,403,minecraft:enchanted_book,
|
||||
Redstone Comparator,404,minecraft:comparator,
|
||||
Nether Brick (Item),405,minecraft:netherbrick,
|
||||
Nether Quartz,406,minecraft:quartz,
|
||||
Minecart (TNT),407,minecraft:tnt_minecart,
|
||||
Minecart (Hopper),408,minecraft:hopper_minecart,
|
||||
Iron Horse Armor,417,minecraft:iron_horse_armor,
|
||||
Gold Horse Armor,418,minecraft:golden_horse_armor,
|
||||
Diamond Horse Armor,419,minecraft:diamond_horse_armor,
|
||||
Lead,420,minecraft:lead,
|
||||
Name Tag,421,minecraft:name_tag,
|
||||
Minecart (Command Block),422,minecraft:command_block_minecart,
|
||||
Music Disk (13),2256,minecraft:record_13,Decorations
|
||||
Music Disk (Cat),2257,minecraft:record_cat,Decorations
|
||||
Music Disk (Blocks),2258,minecraft:record_blocks,Decorations
|
||||
Music Disk (Chrip),2259,minecraft:record_chirp,Decorations
|
||||
Music Disk (Far),2260,minecraft:record_far,Decorations
|
||||
Music Disk (Mall),2261,minecraft:record_mall,Decorations
|
||||
Music Disk (Mellohi),2262,minecraft:record_mellohi,Decorations
|
||||
Music Disk (Stal),2263,minecraft:record_stal,Decorations
|
||||
Music Disk (Strad),2264,minecraft:record_strad,Decorations
|
||||
Music Disk (Ward),2265,minecraft:record_ward,Decorations
|
||||
Music Disk (11) (Broken),2266,minecraft:record_11,Decorations
|
||||
Music Disk (Wait),2267,minecraft:record_wait,Decorations
|
|
@@ -41,6 +41,7 @@ function nativefs.list(node, dir, full)
|
||||
end
|
||||
|
||||
if not files then
|
||||
print(dir)
|
||||
error('Not a directory')
|
||||
end
|
||||
|
||||
|
@@ -18,9 +18,9 @@ process:newThread('trust_server', function()
|
||||
else
|
||||
data = Crypto.decrypt(data, password)
|
||||
if data and data.pk and data.dh == socket.dhost then
|
||||
local trustList = Util.readTable('.known_hosts') or { }
|
||||
local trustList = Util.readTable('usr/.known_hosts') or { }
|
||||
trustList[data.dh] = data.pk
|
||||
Util.writeTable('.known_hosts', trustList)
|
||||
Util.writeTable('usr/.known_hosts', trustList)
|
||||
|
||||
socket:write({ success = true, msg = 'Trust accepted' })
|
||||
else
|
||||
|
Reference in New Issue
Block a user