refactor common apps into its own package
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
title = 'Core apps and apis',
|
||||
title = 'APIs used by various programs',
|
||||
repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/core',
|
||||
description = [[Provides common files used by Opus applications. Also includes various useful applications.]],
|
||||
description = [[Provides APIs used by Opus applications.]],
|
||||
licence = 'MIT',
|
||||
}
|
||||
|
||||
@@ -1,410 +0,0 @@
|
||||
local Ansi = require('ansi')
|
||||
local SHA1 = require('sha1')
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
|
||||
local fs = _G.fs
|
||||
local http = _G.http
|
||||
local multishell = _ENV.multishell
|
||||
local os = _G.os
|
||||
local shell = _ENV.shell
|
||||
|
||||
local REGISTRY_DIR = 'usr/.registry'
|
||||
|
||||
|
||||
-- FIX SOMEDAY
|
||||
local function registerApp(app, key)
|
||||
|
||||
app.key = SHA1.sha1(key)
|
||||
Util.writeTable(fs.combine(REGISTRY_DIR, app.key), app)
|
||||
os.queueEvent('os_register_app')
|
||||
end
|
||||
|
||||
local function unregisterApp(key)
|
||||
|
||||
local filename = fs.combine(REGISTRY_DIR, SHA1.sha1(key))
|
||||
if fs.exists(filename) then
|
||||
fs.delete(filename)
|
||||
os.queueEvent('os_register_app')
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local sandboxEnv = Util.shallowCopy(_ENV)
|
||||
setmetatable(sandboxEnv, { __index = _G })
|
||||
|
||||
multishell.setTitle(multishell.getCurrent(), 'App Store')
|
||||
UI:configure('Appstore', ...)
|
||||
|
||||
local APP_DIR = 'usr/apps'
|
||||
|
||||
local sources = {
|
||||
|
||||
{ text = "STD Default",
|
||||
event = 'source',
|
||||
url = "http://pastebin.com/raw/zVws7eLq" }, --stock
|
||||
--[[
|
||||
{ text = "Discover",
|
||||
event = 'source',
|
||||
generateName = true,
|
||||
url = "http://pastebin.com/raw/9bXfCz6M" }, --owned by dannysmc95
|
||||
|
||||
{ text = "Opus",
|
||||
event = 'source',
|
||||
url = "http://pastebin.com/raw/ajQ91Rmn" },
|
||||
]]
|
||||
}
|
||||
|
||||
shell.setDir(APP_DIR)
|
||||
|
||||
local function downloadApp(app)
|
||||
local h
|
||||
|
||||
if type(app.url) == "table" then
|
||||
h = contextualGet(app.url[1])
|
||||
else
|
||||
h = http.get(app.url)
|
||||
end
|
||||
|
||||
if h then
|
||||
local contents = h.readAll()
|
||||
h:close()
|
||||
return contents
|
||||
end
|
||||
end
|
||||
|
||||
local function runApp(app, checkExists, ...)
|
||||
|
||||
local path, fn
|
||||
local args = { ... }
|
||||
|
||||
if checkExists and fs.exists(fs.combine(APP_DIR, app.name)) then
|
||||
path = fs.combine(APP_DIR, app.name)
|
||||
else
|
||||
local program = downloadApp(app)
|
||||
|
||||
fn = function()
|
||||
|
||||
if not program then
|
||||
error('Failed to download')
|
||||
end
|
||||
|
||||
local fn = loadstring(program, app.name)
|
||||
|
||||
if not fn then
|
||||
error('Failed to download')
|
||||
end
|
||||
|
||||
setfenv(fn, sandboxEnv)
|
||||
fn(unpack(args))
|
||||
end
|
||||
end
|
||||
|
||||
multishell.openTab({
|
||||
title = app.name,
|
||||
env = sandboxEnv,
|
||||
path = path,
|
||||
fn = fn,
|
||||
focused = true,
|
||||
})
|
||||
|
||||
return true, 'Running program'
|
||||
end
|
||||
|
||||
local installApp = function(app)
|
||||
|
||||
local program = downloadApp(app)
|
||||
if not program then
|
||||
return false, "Failed to download"
|
||||
end
|
||||
|
||||
local fullPath = fs.combine(APP_DIR, app.name)
|
||||
Util.writeFile(fullPath, program)
|
||||
return true, 'Installed as ' .. fullPath
|
||||
end
|
||||
|
||||
local viewApp = function(app)
|
||||
|
||||
local program = downloadApp(app)
|
||||
if not program then
|
||||
return false, "Failed to download"
|
||||
end
|
||||
|
||||
Util.writeFile('/.source', program)
|
||||
shell.openForegroundTab('edit /.source')
|
||||
fs.delete('/.source')
|
||||
return true
|
||||
end
|
||||
|
||||
local getSourceListing = function(source)
|
||||
local contents = http.get(source.url)
|
||||
if contents then
|
||||
|
||||
local fn = loadstring(contents.readAll(), source.text)
|
||||
contents.close()
|
||||
|
||||
local env = { std = { } }
|
||||
setmetatable(env, { __index = _G })
|
||||
setfenv(fn, env)
|
||||
fn()
|
||||
|
||||
if env.contextualGet then
|
||||
contextualGet = env.contextualGet
|
||||
end
|
||||
|
||||
source.storeURLs = env.std.storeURLs
|
||||
source.storeCatagoryNames = env.std.storeCatagoryNames
|
||||
|
||||
if source.storeURLs and source.storeCatagoryNames then
|
||||
for k,v in pairs(source.storeURLs) do
|
||||
if source.generateName then
|
||||
v.name = v.title:match('(%w+)')
|
||||
if not v.name or #v.name == 0 then
|
||||
v.name = tostring(k)
|
||||
else
|
||||
v.name = v.name:lower()
|
||||
end
|
||||
else
|
||||
v.name = k
|
||||
end
|
||||
v.categoryName = source.storeCatagoryNames[v.catagory]
|
||||
v.ltitle = v.title:lower()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local appPage = UI.Page {
|
||||
menuBar = UI.MenuBar {
|
||||
-- showBackButton = not pocket,
|
||||
buttons = {
|
||||
{ text = '\027', event = 'back' },
|
||||
{ text = 'Install', event = 'install' },
|
||||
{ text = 'Run', event = 'run' },
|
||||
{ text = 'View', event = 'view' },
|
||||
{ text = 'Remove', event = 'uninstall', name = 'removeButton' },
|
||||
},
|
||||
},
|
||||
container = UI.Window {
|
||||
x = 2, y = 3, ex = -2, ey = -3,
|
||||
viewport = UI.Viewport(),
|
||||
},
|
||||
notification = UI.Notification(),
|
||||
accelerators = {
|
||||
q = 'back',
|
||||
backspace = 'back',
|
||||
},
|
||||
}
|
||||
|
||||
function appPage.container.viewport:draw()
|
||||
local app = self.parent.parent.app
|
||||
local str = string.format(
|
||||
'%s \nBy: %s \nCategory: %s\nFile name: %s\n\n%s',
|
||||
Ansi.yellow .. app.title .. Ansi.reset,
|
||||
app.creator,
|
||||
app.categoryName, app.name,
|
||||
Ansi.yellow .. app.description .. Ansi.reset)
|
||||
|
||||
self:clear()
|
||||
self:setCursorPos(1, 1)
|
||||
self:print(str)
|
||||
self.ymax = self.cursorY
|
||||
|
||||
if appPage.notification.enabled then
|
||||
appPage.notification:draw()
|
||||
end
|
||||
end
|
||||
|
||||
function appPage:enable(source, app)
|
||||
self.source = source
|
||||
self.app = app
|
||||
UI.Page.enable(self)
|
||||
|
||||
self.container.viewport:setScrollPosition(0)
|
||||
if fs.exists(fs.combine(APP_DIR, app.name)) then
|
||||
self.menuBar.removeButton:enable('Remove')
|
||||
else
|
||||
self.menuBar.removeButton:disable('Remove')
|
||||
end
|
||||
end
|
||||
|
||||
function appPage:eventHandler(event)
|
||||
if event.type == 'back' then
|
||||
UI:setPreviousPage()
|
||||
|
||||
elseif event.type == 'run' then
|
||||
self.notification:info('Running program', 3)
|
||||
self:sync()
|
||||
runApp(self.app, true)
|
||||
|
||||
elseif event.type == 'view' then
|
||||
self.notification:info('Downloading program', 3)
|
||||
self:sync()
|
||||
viewApp(self.app)
|
||||
|
||||
elseif event.type == 'uninstall' then
|
||||
if self.app.runOnly then
|
||||
runApp(self.app, false, 'uninstall')
|
||||
else
|
||||
fs.delete(fs.combine(APP_DIR, self.app.name))
|
||||
self.notification:success("Uninstalled " .. self.app.name, 3)
|
||||
self:focusFirst(self)
|
||||
self.menuBar.removeButton:disable('Remove')
|
||||
self.menuBar:draw()
|
||||
|
||||
unregisterApp(self.app.creator .. '.' .. self.app.name)
|
||||
end
|
||||
|
||||
elseif event.type == 'install' then
|
||||
self.notification:info("Installing", 3)
|
||||
self:sync()
|
||||
local s, m
|
||||
if self.app.runOnly then
|
||||
s,m = runApp(self.app, false)
|
||||
else
|
||||
s,m = installApp(self.app)
|
||||
end
|
||||
if s then
|
||||
self.notification:success(m, 3)
|
||||
|
||||
if not self.app.runOnly then
|
||||
self.menuBar.removeButton:enable('Remove')
|
||||
self.menuBar:draw()
|
||||
|
||||
local category = 'Apps'
|
||||
if self.app.catagoryName == 'Game' then
|
||||
category = 'Games'
|
||||
end
|
||||
|
||||
registerApp({
|
||||
run = fs.combine(APP_DIR, self.app.name),
|
||||
title = self.app.title,
|
||||
category = category,
|
||||
icon = self.app.icon,
|
||||
}, self.app.creator .. '.' .. self.app.name)
|
||||
end
|
||||
else
|
||||
self.notification:error(m, 3)
|
||||
end
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local categoryPage = UI.Page {
|
||||
menuBar = UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = 'Catalog', dropdown = sources },
|
||||
{ text = 'Category', name = 'categoryButton', dropdown = { } },
|
||||
},
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 2, ey = -2,
|
||||
columns = {
|
||||
{ heading = 'Title', key = 'title' },
|
||||
},
|
||||
sortColumn = 'title',
|
||||
},
|
||||
statusBar = UI.StatusBar(),
|
||||
accelerators = {
|
||||
l = 'lua',
|
||||
q = 'quit',
|
||||
},
|
||||
}
|
||||
|
||||
function categoryPage:setCategory(source, name, index)
|
||||
self.grid.values = { }
|
||||
for _,v in pairs(source.storeURLs) do
|
||||
if index == 0 or index == v.catagory then
|
||||
table.insert(self.grid.values, v)
|
||||
end
|
||||
end
|
||||
self.statusBar:setStatus(string.format('%s: %s', source.text, name))
|
||||
self.grid:update()
|
||||
self.grid:setIndex(1)
|
||||
end
|
||||
|
||||
function categoryPage:setSource(source)
|
||||
|
||||
if not source.categoryMenu then
|
||||
|
||||
self.statusBar:setStatus('Loading...')
|
||||
self.statusBar:draw()
|
||||
self:sync()
|
||||
|
||||
getSourceListing(source)
|
||||
|
||||
if not source.storeURLs then
|
||||
error('Unable to download application list')
|
||||
end
|
||||
|
||||
local buttons = { }
|
||||
for k,v in Util.spairs(source.storeCatagoryNames,
|
||||
function(a, b) return a:lower() < b:lower() end) do
|
||||
|
||||
if v ~= 'Operating System' then
|
||||
table.insert(buttons, {
|
||||
text = v,
|
||||
event = 'category',
|
||||
index = k,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
source.categoryMenu = UI.DropMenu({
|
||||
buttons = buttons,
|
||||
})
|
||||
source.index, source.name = Util.first(source.storeCatagoryNames)
|
||||
|
||||
categoryPage.menuBar.categoryButton:add({
|
||||
categoryMenu = source.categoryMenu
|
||||
})
|
||||
end
|
||||
|
||||
self.source = source
|
||||
self.menuBar.categoryButton.dropmenu = source.categoryMenu
|
||||
categoryPage:setCategory(source, source.name, source.index)
|
||||
end
|
||||
|
||||
function categoryPage.grid:sortCompare(a, b)
|
||||
return a.ltitle < b.ltitle
|
||||
end
|
||||
|
||||
function categoryPage.grid:getRowTextColor(row, selected)
|
||||
if fs.exists(fs.combine(APP_DIR, row.name)) then
|
||||
return colors.orange
|
||||
end
|
||||
return UI.Grid:getRowTextColor(row, selected)
|
||||
end
|
||||
|
||||
function categoryPage:eventHandler(event)
|
||||
|
||||
if event.type == 'grid_select' or event.type == 'select' then
|
||||
UI:setPage(appPage, self.source, self.grid:getSelected())
|
||||
|
||||
elseif event.type == 'category' then
|
||||
self:setCategory(self.source, event.button.text, event.button.index)
|
||||
self:setFocus(self.grid)
|
||||
self:draw()
|
||||
|
||||
elseif event.type == 'source' then
|
||||
self:setFocus(self.grid)
|
||||
self:setSource(event.button)
|
||||
self:draw()
|
||||
|
||||
elseif event.type == 'quit' then
|
||||
UI:exitPullEvents()
|
||||
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
print("Retrieving catalog list")
|
||||
categoryPage:setSource(sources[1])
|
||||
|
||||
UI:setPage(categoryPage)
|
||||
UI:pullEvents()
|
||||
UI.term:reset()
|
||||
205
core/Devices.lua
205
core/Devices.lua
@@ -1,205 +0,0 @@
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Ansi = require('ansi')
|
||||
local Event = require('event')
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
|
||||
local colors = _G.colors
|
||||
local peripheral = _G.peripheral
|
||||
|
||||
--[[ -- PeripheralsPage -- ]] --
|
||||
local peripheralsPage = UI.Page {
|
||||
grid = UI.ScrollingGrid {
|
||||
ey = -2,
|
||||
columns = {
|
||||
{ heading = 'Type', key = 'type' },
|
||||
{ heading = 'Side', key = 'side' },
|
||||
},
|
||||
sortColumn = 'type',
|
||||
autospace = true,
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
values = 'Select peripheral',
|
||||
},
|
||||
accelerators = {
|
||||
q = 'quit',
|
||||
},
|
||||
}
|
||||
|
||||
function peripheralsPage.grid:draw()
|
||||
local sides = peripheral.getNames()
|
||||
|
||||
Util.clear(self.values)
|
||||
for _,side in pairs(sides) do
|
||||
table.insert(self.values, {
|
||||
type = peripheral.getType(side),
|
||||
side = side
|
||||
})
|
||||
end
|
||||
self:update()
|
||||
self:adjustWidth()
|
||||
UI.Grid.draw(self)
|
||||
end
|
||||
|
||||
function peripheralsPage:updatePeripherals()
|
||||
if UI:getCurrentPage() == self then
|
||||
self.grid:draw()
|
||||
self:sync()
|
||||
end
|
||||
end
|
||||
|
||||
function peripheralsPage:eventHandler(event)
|
||||
if event.type == 'quit' then
|
||||
Event.exitPullEvents()
|
||||
|
||||
elseif event.type == 'grid_select' then
|
||||
UI:setPage('methods', event.selected)
|
||||
|
||||
end
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
|
||||
--[[ -- MethodsPage -- ]] --
|
||||
local methodsPage = UI.Page {
|
||||
backgroundColor = colors.black,
|
||||
doc = UI.TextArea {
|
||||
backgroundColor = colors.black,
|
||||
x = 2, y = 2, ex = -1, ey = -7,
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
y = -6, ey = -2,
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'name', width = UI.term.width }
|
||||
},
|
||||
sortColumn = 'name',
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
status = 'q to return',
|
||||
},
|
||||
accelerators = {
|
||||
q = 'back',
|
||||
backspace = 'back',
|
||||
},
|
||||
}
|
||||
|
||||
function methodsPage:enable(p)
|
||||
|
||||
self.peripheral = p or self.peripheral
|
||||
|
||||
p = peripheral.wrap(self.peripheral.side)
|
||||
if p.getDocs then
|
||||
-- plethora
|
||||
self.grid.values = { }
|
||||
for k,v in pairs(p.getDocs()) do
|
||||
table.insert(self.grid.values, {
|
||||
name = k,
|
||||
doc = v,
|
||||
})
|
||||
end
|
||||
elseif not p.getAdvancedMethodsData then
|
||||
-- computercraft
|
||||
self.grid.values = { }
|
||||
for name in pairs(p) do
|
||||
table.insert(self.grid.values, {
|
||||
name = name,
|
||||
noext = true,
|
||||
})
|
||||
end
|
||||
else
|
||||
-- open peripherals
|
||||
self.grid.values = p.getAdvancedMethodsData()
|
||||
for name,f in pairs(self.grid.values) do
|
||||
f.name = name
|
||||
end
|
||||
end
|
||||
|
||||
self.grid:update()
|
||||
self.grid:setIndex(1)
|
||||
|
||||
self.doc:setText(self:getDocumentation())
|
||||
|
||||
self.statusBar:setStatus(self.peripheral.type)
|
||||
UI.Page.enable(self)
|
||||
|
||||
self:setFocus(self.grid)
|
||||
end
|
||||
|
||||
function methodsPage:eventHandler(event)
|
||||
if event.type == 'back' then
|
||||
UI:setPage(peripheralsPage)
|
||||
return true
|
||||
elseif event.type == 'grid_focus_row' then
|
||||
self.doc:setText(self:getDocumentation())
|
||||
end
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
|
||||
function methodsPage:getDocumentation()
|
||||
|
||||
local method = self.grid:getSelected()
|
||||
|
||||
if method.noext then -- computercraft docs
|
||||
return 'No documentation'
|
||||
end
|
||||
|
||||
if method.doc then -- plethora docs
|
||||
return Ansi.yellow .. method.doc
|
||||
end
|
||||
|
||||
-- open peripherals docs
|
||||
local sb = { }
|
||||
if method.description then
|
||||
table.insert(sb, method.description .. '\n\n')
|
||||
end
|
||||
|
||||
if method.returnTypes ~= '()' then
|
||||
table.insert(sb, Ansi.yellow .. method.returnTypes .. ' ')
|
||||
end
|
||||
table.insert(sb, Ansi.blue .. method.name .. Ansi.reset .. '(')
|
||||
|
||||
for k,arg in ipairs(method.args) do
|
||||
if arg.optional then
|
||||
table.insert(sb, Ansi.orange .. string.format('[%s]', arg.name))
|
||||
else
|
||||
table.insert(sb, Ansi.green .. arg.name)
|
||||
end
|
||||
if k < #method.args then
|
||||
table.insert(sb, ',')
|
||||
end
|
||||
end
|
||||
table.insert(sb, Ansi.reset .. ')')
|
||||
|
||||
Util.filterInplace(method.args, function(a) return #a.description > 0 end)
|
||||
if #method.args > 0 then
|
||||
table.insert(sb, '\n\n')
|
||||
for k,arg in ipairs(method.args) do
|
||||
if arg.optional then
|
||||
table.insert(sb, Ansi.orange)
|
||||
else
|
||||
table.insert(sb, Ansi.green)
|
||||
end
|
||||
table.insert(sb, arg.name .. Ansi.reset .. ': ' .. arg.description)
|
||||
if k ~= #method.args then
|
||||
table.insert(sb, '\n\n')
|
||||
end
|
||||
end
|
||||
end
|
||||
return table.concat(sb)
|
||||
end
|
||||
|
||||
Event.on('peripheral', function()
|
||||
peripheralsPage:updatePeripherals()
|
||||
end)
|
||||
|
||||
Event.on('peripheral_detach', function()
|
||||
peripheralsPage:updatePeripherals()
|
||||
end)
|
||||
|
||||
UI:setPage(peripheralsPage)
|
||||
|
||||
UI:setPages({
|
||||
methods = methodsPage,
|
||||
})
|
||||
|
||||
UI:pullEvents()
|
||||
@@ -1,222 +0,0 @@
|
||||
local Ansi = require('ansi')
|
||||
local Event = require('event')
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
|
||||
local colors = _G.colors
|
||||
local fs = _G.fs
|
||||
local peripheral = _G.peripheral
|
||||
|
||||
local drives = { }
|
||||
peripheral.find('drive', function(n, v)
|
||||
if not drives.left then
|
||||
drives.left = Util.shallowCopy(v)
|
||||
drives.left.name = n
|
||||
else
|
||||
drives.right = Util.shallowCopy(v)
|
||||
drives.right.name = n
|
||||
end
|
||||
end)
|
||||
|
||||
if not (drives.left and drives.right) then
|
||||
error('Two drives are required')
|
||||
end
|
||||
|
||||
local COPY_LEFT = 1
|
||||
local COPY_RIGHT = 2
|
||||
local directions = {
|
||||
[ COPY_LEFT ] = { text = '-->>' },
|
||||
[ COPY_RIGHT ] = { text = '<<--' },
|
||||
}
|
||||
local copyDir = COPY_LEFT
|
||||
|
||||
local page = UI.Page {
|
||||
linfo = UI.Window {
|
||||
x = 2, y = 2, ey = 5, width = 18,
|
||||
},
|
||||
rinfo = UI.Window {
|
||||
x = -19, y = 2, ey = 5, width = 18,
|
||||
},
|
||||
dir = UI.Button {
|
||||
x = 17, y = 7, width = 6,
|
||||
event = 'change_dir',
|
||||
},
|
||||
progress = UI.ProgressBar {
|
||||
x = 2, ex = -2, y = -4,
|
||||
backgroundColor = colors.black,
|
||||
},
|
||||
cloneText = UI.Text {
|
||||
x = 2, y = -2,
|
||||
value = 'Clone'
|
||||
},
|
||||
clone = UI.Checkbox {
|
||||
x = 8, y = -2,
|
||||
},
|
||||
copyButton = UI.Button {
|
||||
x = -7, y = -2,
|
||||
text = 'Copy',
|
||||
event = 'copy',
|
||||
inactive = true,
|
||||
},
|
||||
warning = UI.Text {
|
||||
x = 2, ex = -2, y = -5,
|
||||
align = 'center',
|
||||
textColor = colors.orange,
|
||||
},
|
||||
notification = UI.Notification { },
|
||||
}
|
||||
|
||||
function page:enable()
|
||||
Util.merge(self.dir, directions[copyDir])
|
||||
UI.Page.enable(self)
|
||||
end
|
||||
|
||||
local function isValid(drive)
|
||||
return drive.isDiskPresent() and drive.getMountPath()
|
||||
end
|
||||
|
||||
local function needsLabel(drive)
|
||||
return drive.isDiskPresent() and not drive.getMountPath() and not drive.getAudioTitle()
|
||||
end
|
||||
|
||||
function page:drawInfo(drive, textArea)
|
||||
local function getLabel()
|
||||
return not drive.isDiskPresent() and 'empty' or
|
||||
not drive.getMountPath() and 'invalid' or
|
||||
drive.getDiskLabel() or 'unlabeled'
|
||||
end
|
||||
|
||||
local function getUsed()
|
||||
return isValid(drive) and fs.getSize(drive.getMountPath(), true) or 0
|
||||
end
|
||||
|
||||
local function getFree()
|
||||
return isValid(drive) and fs.getFreeSpace(drive.getMountPath()) or 0
|
||||
end
|
||||
|
||||
textArea:setCursorPos(1, 1)
|
||||
textArea:print(string.format('Drive: %s%s%s\nLabel: %s%s%s\nUsed: %s%s%s\nFree: %s%s%s',
|
||||
Ansi.yellow, drive.name, Ansi.reset,
|
||||
isValid(drive) and Ansi.yellow or Ansi.orange, getLabel():sub(1, 10), Ansi.reset,
|
||||
Ansi.yellow, Util.toBytes(getUsed()), Ansi.reset,
|
||||
Ansi.yellow, Util.toBytes(getFree()), Ansi.reset))
|
||||
end
|
||||
|
||||
function page:scan()
|
||||
local showWarning = needsLabel(drives.left) or needsLabel(drives.right)
|
||||
local valid = isValid(drives.left) and isValid(drives.right)
|
||||
|
||||
self.warning.value = showWarning and 'Computers must be labeled'
|
||||
self.copyButton.inactive = not valid
|
||||
|
||||
self:draw()
|
||||
self.progress:centeredWrite(1, 'Analyzing Disks..')
|
||||
self.progress:sync()
|
||||
|
||||
self:drawInfo(drives.left, self.linfo)
|
||||
self:drawInfo(drives.right, self.rinfo)
|
||||
|
||||
self.progress:clear()
|
||||
end
|
||||
|
||||
function page:copy(sdrive, tdrive)
|
||||
local throttle = Util.throttle()
|
||||
local sourceFiles, targetFiles = { }, { }
|
||||
|
||||
local function getListing(mountPath, path, files)
|
||||
for _,f in pairs(fs.list(path)) do
|
||||
local file = fs.combine(path, f)
|
||||
if not fs.isReadOnly(file) then
|
||||
files[string.sub(file, #mountPath + 1)] = true
|
||||
if fs.isDir(file) then
|
||||
getListing(mountPath, file, files)
|
||||
end
|
||||
end
|
||||
end
|
||||
throttle()
|
||||
end
|
||||
|
||||
self.progress:centeredWrite(1, 'Computing..')
|
||||
self.progress:sync()
|
||||
|
||||
getListing(sdrive.getMountPath(), sdrive.getMountPath(), sourceFiles)
|
||||
getListing(tdrive.getMountPath(), tdrive.getMountPath(), targetFiles)
|
||||
|
||||
local copied = 0
|
||||
local totalFiles = Util.size(sourceFiles)
|
||||
|
||||
local function rawCopy(source, target)
|
||||
if fs.isDir(source) then
|
||||
copied = copied + 1
|
||||
if not fs.exists(target) then
|
||||
fs.makeDir(target)
|
||||
end
|
||||
for _,f in pairs(fs.list(source)) do
|
||||
rawCopy(fs.combine(source, f), fs.combine(target, f))
|
||||
end
|
||||
|
||||
else
|
||||
if fs.exists(target) then
|
||||
fs.delete(target)
|
||||
end
|
||||
|
||||
fs.copy(source, target)
|
||||
copied = copied + 1
|
||||
self.progress.value = copied * 100 / totalFiles
|
||||
self.progress:draw()
|
||||
self.progress:sync()
|
||||
end
|
||||
throttle()
|
||||
end
|
||||
|
||||
local function cleanup()
|
||||
for k in pairs(targetFiles) do
|
||||
if not sourceFiles[k] then
|
||||
fs.delete(fs.combine(tdrive.getMountPath(), k))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.progress:clear()
|
||||
rawCopy(sdrive.getMountPath(), tdrive.getMountPath())
|
||||
cleanup()
|
||||
self.progress:centeredWrite(1, 'Copy Complete', colors.lime, colors.black)
|
||||
self.progress:sync()
|
||||
|
||||
self.progress.value = 0
|
||||
self.progress:clear()
|
||||
|
||||
self:scan()
|
||||
end
|
||||
|
||||
function page:eventHandler(event)
|
||||
if event.type == 'change_dir' then
|
||||
copyDir = (copyDir) % 2 + 1
|
||||
Util.merge(self.dir, directions[copyDir])
|
||||
self.dir:draw()
|
||||
|
||||
elseif event.type == 'copy' then
|
||||
if copyDir == COPY_LEFT then
|
||||
self:copy(drives.left, drives.right)
|
||||
else
|
||||
self:copy(drives.right, drives.left)
|
||||
end
|
||||
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
Event.on({ 'disk', 'disk_eject' }, function()
|
||||
page:scan()
|
||||
page:sync()
|
||||
end)
|
||||
|
||||
Event.onTimeout(.2, function()
|
||||
page:scan()
|
||||
page:sync()
|
||||
end)
|
||||
|
||||
UI:setPage(page)
|
||||
UI:pullEvents()
|
||||
137
core/Events.lua
137
core/Events.lua
@@ -1,137 +0,0 @@
|
||||
local Event = require('event')
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
|
||||
local multishell = _ENV.multishell
|
||||
local os = _G.os
|
||||
|
||||
UI:configure('Events', ...)
|
||||
|
||||
local page = UI.Page {
|
||||
menuBar = UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = 'Filter', event = 'filter' },
|
||||
{ text = 'Reset', event = 'reset' },
|
||||
{ text = 'Pause ', event = 'toggle', name = 'pauseButton' },
|
||||
},
|
||||
},
|
||||
grid = UI.Grid {
|
||||
y = 2,
|
||||
columns = {
|
||||
{ key = 'event' },
|
||||
{ key = 'p1' },
|
||||
{ key = 'p2' },
|
||||
{ key = 'p3' },
|
||||
{ key = 'p4' },
|
||||
{ key = 'p5' },
|
||||
},
|
||||
autospace = true,
|
||||
disableHeader = true,
|
||||
},
|
||||
accelerators = {
|
||||
f = 'filter',
|
||||
p = 'toggle',
|
||||
r = 'reset',
|
||||
c = 'clear',
|
||||
q = 'quit',
|
||||
},
|
||||
filtered = { },
|
||||
}
|
||||
|
||||
function page:eventHandler(event)
|
||||
|
||||
if event.type == 'filter' then
|
||||
local entry = self.grid:getSelected()
|
||||
self.filtered[entry.event] = true
|
||||
|
||||
elseif event.type == 'toggle' then
|
||||
self.paused = not self.paused
|
||||
if self.paused then
|
||||
self.menuBar.pauseButton.text = 'Resume'
|
||||
else
|
||||
self.menuBar.pauseButton.text = 'Pause '
|
||||
end
|
||||
self.menuBar:draw()
|
||||
|
||||
elseif event.type == 'grid_select' then
|
||||
multishell.openTab({
|
||||
path = 'sys/apps/Lua.lua',
|
||||
args = { event.selected },
|
||||
focused = true,
|
||||
})
|
||||
|
||||
elseif event.type == 'reset' then
|
||||
self.filtered = { }
|
||||
self.grid:setValues({ })
|
||||
self.grid:draw()
|
||||
if self.paused then
|
||||
self:emit({ type = 'toggle' })
|
||||
end
|
||||
|
||||
elseif event.type == 'clear' then
|
||||
self.grid:setValues({ })
|
||||
self.grid:draw()
|
||||
|
||||
elseif event.type == 'quit' then
|
||||
UI:exitPullEvents()
|
||||
|
||||
elseif event.type == 'focus_change' then
|
||||
if event.focused == self.grid then
|
||||
if not self.paused then
|
||||
self:emit({ type = 'toggle' })
|
||||
end
|
||||
end
|
||||
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function page.grid:getDisplayValues(row)
|
||||
row = Util.shallowCopy(row)
|
||||
|
||||
local function tovalue(s)
|
||||
if type(s) == 'table' then
|
||||
return 'table'
|
||||
end
|
||||
return s
|
||||
end
|
||||
|
||||
for k,v in pairs(row) do
|
||||
row[k] = tovalue(v)
|
||||
end
|
||||
|
||||
return row
|
||||
end
|
||||
|
||||
function page.grid:draw()
|
||||
self:adjustWidth()
|
||||
UI.Grid.draw(self)
|
||||
end
|
||||
|
||||
Event.addRoutine(function()
|
||||
|
||||
while true do
|
||||
local e = { os.pullEvent() }
|
||||
if not page.paused and not page.filtered[e[1]] then
|
||||
table.insert(page.grid.values, 1, {
|
||||
event = e[1],
|
||||
p1 = e[2],
|
||||
p2 = e[3],
|
||||
p3 = e[4],
|
||||
p4 = e[5],
|
||||
p5 = e[6],
|
||||
})
|
||||
if #page.grid.values > page.grid.height then
|
||||
table.remove(page.grid.values, #page.grid.values)
|
||||
end
|
||||
page.grid:update()
|
||||
page.grid:draw()
|
||||
page:sync()
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
UI:setPage(page)
|
||||
UI:pullEvents()
|
||||
187
core/Follow.lua
187
core/Follow.lua
@@ -1,187 +0,0 @@
|
||||
local Event = require('event')
|
||||
local GPS = require('gps')
|
||||
local Point = require('point')
|
||||
local Socket = require('socket')
|
||||
local Swarm = require('core.swarm')
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
|
||||
local colors = _G.colors
|
||||
local network = _G.network
|
||||
local os = _G.os
|
||||
|
||||
local swarm = Swarm()
|
||||
local gpt = GPS.getPoint() or error('GPS not found')
|
||||
local pts, blocks
|
||||
|
||||
local page = UI.Page {
|
||||
mode = UI.Chooser {
|
||||
x = 13, y = -1,
|
||||
choices = {
|
||||
{ name = 'No breaking', value = 'digNone' },
|
||||
{ name = 'Destructive', value = 'turtleSafe' },
|
||||
},
|
||||
value = 'digNone',
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 2, ey = -2,
|
||||
columns = {
|
||||
{ heading = 'Label', key = 'label' },
|
||||
{ heading = 'Dist', key = 'distance' },
|
||||
{ heading = 'Status', key = 'status' },
|
||||
{ heading = 'Fuel', key = 'fuel' },
|
||||
},
|
||||
sortColumn = 'distance',
|
||||
autospace = true,
|
||||
},
|
||||
}
|
||||
|
||||
function page.grid:getRowTextColor(row, selected)
|
||||
if swarm.pool[row.id] then
|
||||
return colors.yellow
|
||||
end
|
||||
return UI.ScrollingGrid.getRowTextColor(self, row, selected)
|
||||
end
|
||||
|
||||
function page.grid:getDisplayValues(row)
|
||||
row = Util.shallowCopy(row)
|
||||
if row.fuel then
|
||||
row.fuel = row.fuel > 0 and Util.toBytes(row.fuel) or ''
|
||||
end
|
||||
if row.distance then
|
||||
row.distance = Util.round(row.distance, 1)
|
||||
end
|
||||
return row
|
||||
end
|
||||
|
||||
function page:enable()
|
||||
local function update()
|
||||
local t = { }
|
||||
for _,v in pairs(network) do
|
||||
if v.fuel and v.active and v.fuel > 0 and v.distance then
|
||||
table.insert(t, v)
|
||||
end
|
||||
end
|
||||
self.grid:setValues(t)
|
||||
end
|
||||
|
||||
Event.onInterval(3, function()
|
||||
update()
|
||||
self.grid:draw()
|
||||
self:sync()
|
||||
end)
|
||||
|
||||
update()
|
||||
|
||||
UI.Page.enable(self)
|
||||
end
|
||||
|
||||
local function follow(member)
|
||||
local turtle = member.turtle
|
||||
turtle.reset()
|
||||
turtle.set({
|
||||
digPolicy = page.mode.value,
|
||||
status = 'Following',
|
||||
})
|
||||
|
||||
if not turtle.enableGPS(nil, true) then
|
||||
error('turtle: No GPS found')
|
||||
end
|
||||
|
||||
member.snmp = Socket.connect(member.id, 161)
|
||||
member.snmp.co = coroutine.running()
|
||||
|
||||
local pt
|
||||
|
||||
while true do
|
||||
while pt and Point.same(gpt, pt) do
|
||||
os.sleep(.5)
|
||||
end
|
||||
pt = Point.copy(gpt)
|
||||
|
||||
local cpt = Point.closest(turtle.getPoint(), pts)
|
||||
|
||||
turtle.abort(false)
|
||||
if turtle.pathfind(cpt, { blocks = blocks }) then
|
||||
turtle.headTowards(pt)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function swarm:onRemove(member, status, message)
|
||||
if member.socket then
|
||||
pcall(function()
|
||||
member.turtle.set({ status = 'idle' })
|
||||
member.turtle.abort(true)
|
||||
end)
|
||||
end
|
||||
if member.snmp then
|
||||
member.snmp:close()
|
||||
member.snmp = nil
|
||||
end
|
||||
if not status then
|
||||
_G._debug(message)
|
||||
end
|
||||
end
|
||||
|
||||
function page:eventHandler(event)
|
||||
if event.type == 'grid_select' then
|
||||
if not swarm.pool[event.selected.id] then
|
||||
swarm:add(event.selected.id)
|
||||
swarm:run(follow)
|
||||
else
|
||||
swarm:remove(event.selected.id)
|
||||
end
|
||||
self.grid:draw()
|
||||
|
||||
elseif event.type == 'choice_change' then
|
||||
local script = string.format('turtle.set({ digPolicy = "%s"})', event.value)
|
||||
for _, member in pairs(swarm.pool) do
|
||||
member.snmp:write({ type = 'scriptEx', args = script })
|
||||
end
|
||||
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
Event.addRoutine(function()
|
||||
while true do
|
||||
local pt = GPS.getPoint()
|
||||
if not pts or (pt and not Point.same(pt, gpt)) then
|
||||
gpt = pt
|
||||
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 },
|
||||
}
|
||||
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(Point.above(pt))
|
||||
|
||||
for _, member in pairs(swarm.pool) do
|
||||
if member.snmp then
|
||||
member.snmp:write({ type = 'scriptEx', args = 'turtle.abort(true)' })
|
||||
end
|
||||
end
|
||||
end
|
||||
os.sleep(1)
|
||||
end
|
||||
end)
|
||||
|
||||
UI:setPage(page)
|
||||
UI:pullEvents()
|
||||
|
||||
swarm:stop()
|
||||
379
core/Turtles.lua
379
core/Turtles.lua
@@ -1,379 +0,0 @@
|
||||
local Config = require('config')
|
||||
local Event = require('event')
|
||||
local itemDB = require('core.itemDB')
|
||||
local Socket = require('socket')
|
||||
local Terminal = require('terminal')
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
|
||||
local colors = _G.colors
|
||||
local fs = _G.fs
|
||||
local multishell = _ENV.multishell
|
||||
local network = _G.network
|
||||
local os = _G.os
|
||||
local shell = _ENV.shell
|
||||
local term = _G.term
|
||||
|
||||
--UI.Button.defaults.focusIndicator = ' '
|
||||
UI:configure('Turtles', ...)
|
||||
|
||||
local config = { }
|
||||
Config.load('Turtles', config)
|
||||
|
||||
local options = {
|
||||
turtle = { arg = 'i', type = 'number', value = config.id or -1,
|
||||
desc = 'Turtle ID' },
|
||||
tab = { arg = 's', type = 'string', value = config.tab or 'Sel',
|
||||
desc = 'Selected tab to display' },
|
||||
help = { arg = 'h', type = 'flag', value = false,
|
||||
desc = 'Displays the options' },
|
||||
}
|
||||
|
||||
local SCRIPTS_PATH = 'packages/core/etc/scripts'
|
||||
|
||||
local nullTerm = Terminal.getNullTerm(term.current())
|
||||
local socket
|
||||
|
||||
local page = UI.Page {
|
||||
coords = UI.Window {
|
||||
backgroundColor = colors.black,
|
||||
height = 3,
|
||||
},
|
||||
tabs = UI.Tabs {
|
||||
x = 1, y = 4, ey = -2,
|
||||
scripts = UI.ScrollingGrid {
|
||||
tabTitle = 'Run',
|
||||
backgroundColor = colors.cyan,
|
||||
columns = {
|
||||
{ heading = '', key = 'label' },
|
||||
},
|
||||
disableHeader = true,
|
||||
sortColumn = 'label',
|
||||
autospace = true,
|
||||
},
|
||||
turtles = UI.ScrollingGrid {
|
||||
tabTitle = 'Select',
|
||||
backgroundColor = colors.cyan,
|
||||
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.ScrollingGrid {
|
||||
backgroundColor = colors.cyan,
|
||||
tabTitle = 'Inv',
|
||||
columns = {
|
||||
{ heading = '', key = 'index', width = 2 },
|
||||
{ heading = '', key = 'qty', width = 2 },
|
||||
{ heading = 'Inventory', key = 'id', width = UI.term.width - 7 },
|
||||
},
|
||||
disableHeader = true,
|
||||
sortColumn = 'index',
|
||||
},
|
||||
--[[
|
||||
policy = UI.ScrollingGrid {
|
||||
tabTitle = 'Mod',
|
||||
backgroundColor = UI.TabBar.defaults.selectedBackgroundColor,
|
||||
columns = {
|
||||
{ heading = 'label', key = 'label' },
|
||||
},
|
||||
values = policies,
|
||||
disableHeader = true,
|
||||
sortColumn = 'label',
|
||||
autospace = true,
|
||||
},
|
||||
]]
|
||||
action = UI.Window {
|
||||
tabTitle = 'Action',
|
||||
backgroundColor = colors.cyan,
|
||||
moveUp = UI.Button {
|
||||
x = 5, y = 2,
|
||||
text = 'up',
|
||||
fn = 'turtle.up',
|
||||
},
|
||||
moveDown = UI.Button {
|
||||
x = 5, y = 4,
|
||||
text = 'dn',
|
||||
fn = 'turtle.down',
|
||||
},
|
||||
moveForward = UI.Button {
|
||||
x = 9, y = 3,
|
||||
text = 'f',
|
||||
fn = 'turtle.forward',
|
||||
},
|
||||
moveBack = UI.Button {
|
||||
x = 2, y = 3,
|
||||
text = 'b',
|
||||
fn = 'turtle.back',
|
||||
},
|
||||
turnLeft = UI.Button {
|
||||
x = 2, y = 6,
|
||||
text = 'lt',
|
||||
fn = 'turtle.turnLeft',
|
||||
},
|
||||
turnRight = UI.Button {
|
||||
x = 8, y = 6,
|
||||
text = 'rt',
|
||||
fn = 'turtle.turnRight',
|
||||
},
|
||||
info = UI.TextArea {
|
||||
x = 15, y = 2,
|
||||
inactive = true,
|
||||
}
|
||||
},
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
values = { },
|
||||
columns = {
|
||||
{ key = 'status' },
|
||||
{ key = 'distance', width = 6 },
|
||||
{ key = 'fuel', width = 6 },
|
||||
},
|
||||
},
|
||||
notification = UI.Notification(),
|
||||
accelerators = {
|
||||
q = 'quit',
|
||||
},
|
||||
}
|
||||
|
||||
function page:enable(turtle)
|
||||
self.turtle = turtle
|
||||
UI.Page.enable(self)
|
||||
end
|
||||
|
||||
function page:runFunction(script, nowrap)
|
||||
for _ = 1, 2 do
|
||||
if not socket then
|
||||
socket = Socket.connect(self.turtle.id, 161)
|
||||
end
|
||||
|
||||
if socket then
|
||||
if not nowrap then
|
||||
script = 'turtle.run(' .. script .. ')'
|
||||
end
|
||||
if socket:write({ type = 'scriptEx', args = script }) then
|
||||
local t = socket:read(3)
|
||||
if t then
|
||||
return table.unpack(t)
|
||||
end
|
||||
return false, 'Socket timeout'
|
||||
end
|
||||
end
|
||||
socket = nil
|
||||
end
|
||||
self.notification:error('Unable to connect')
|
||||
end
|
||||
|
||||
function page:runScript(scriptName)
|
||||
if self.turtle then
|
||||
self.notification:info('Connecting')
|
||||
self:sync()
|
||||
|
||||
local cmd = string.format('Script %d %s', self.turtle.id, scriptName)
|
||||
local ot = term.redirect(nullTerm)
|
||||
pcall(function() shell.run(cmd) end)
|
||||
term.redirect(ot)
|
||||
self.notification:success('Sent')
|
||||
end
|
||||
end
|
||||
|
||||
function page.coords:draw()
|
||||
local t = self.parent.turtle
|
||||
self:clear()
|
||||
if t then
|
||||
self:setCursorPos(2, 2)
|
||||
local ind = 'GPS'
|
||||
if not t.point.gps then
|
||||
ind = 'REL'
|
||||
end
|
||||
self:print(string.format('%s : %d,%d,%d',
|
||||
ind, t.point.x, t.point.y, t.point.z))
|
||||
end
|
||||
end
|
||||
|
||||
--[[ Inventory Tab ]]--
|
||||
function page.tabs.inventory:getRowTextColor(row, selected)
|
||||
if page.turtle and row.selected then
|
||||
return colors.yellow
|
||||
end
|
||||
return UI.ScrollingGrid.getRowTextColor(self, row, selected)
|
||||
end
|
||||
|
||||
function page.tabs.inventory:draw()
|
||||
local t = page.turtle
|
||||
Util.clear(self.values)
|
||||
if t then
|
||||
for _,v in ipairs(t.inventory) do
|
||||
if v.qty > 0 then
|
||||
table.insert(self.values, v)
|
||||
if v.index == t.slotIndex then
|
||||
v.selected = true
|
||||
end
|
||||
if v.id then
|
||||
v.id = itemDB:getName(v)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
self:adjustWidth()
|
||||
self:update()
|
||||
UI.ScrollingGrid.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.ScrollingGrid.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function page.tabs.scripts:draw()
|
||||
|
||||
Util.clear(self.values)
|
||||
local files = fs.list(SCRIPTS_PATH)
|
||||
for _,path in pairs(files) do
|
||||
table.insert(self.values, { label = path, path = fs.combine(SCRIPTS_PATH, path) })
|
||||
end
|
||||
self:update()
|
||||
UI.ScrollingGrid.draw(self)
|
||||
end
|
||||
|
||||
function page.tabs.scripts:eventHandler(event)
|
||||
if event.type == 'grid_select' then
|
||||
page:runScript(event.selected.label)
|
||||
else
|
||||
return UI.ScrollingGrid.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.ScrollingGrid.draw(self)
|
||||
end
|
||||
|
||||
function page.tabs.turtles:eventHandler(event)
|
||||
if event.type == 'grid_select' then
|
||||
page.turtle = event.selected
|
||||
config.id = event.selected.id
|
||||
Config.update('Turtles', config)
|
||||
multishell.setTitle(multishell.getCurrent(), page.turtle.label)
|
||||
if socket then
|
||||
socket:close()
|
||||
socket = nil
|
||||
end
|
||||
else
|
||||
return UI.ScrollingGrid.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function page.statusBar:draw()
|
||||
local t = self.parent.turtle
|
||||
if t then
|
||||
self.values.status = t.status
|
||||
self.values.distance = Util.round(t.distance, 2)
|
||||
self.values.fuel = Util.toBytes(t.fuel)
|
||||
end
|
||||
UI.StatusBar.draw(self)
|
||||
end
|
||||
|
||||
function page:showBlocks()
|
||||
local script = [[
|
||||
local function inspect(direction)
|
||||
local s,b = turtle['inspect' .. (direction or '')]()
|
||||
if not s then
|
||||
return 'minecraft:air:0'
|
||||
end
|
||||
return string.format('%s:%d', b.name, b.metadata)
|
||||
end
|
||||
|
||||
local bu, bf, bd = inspect('Up'), inspect(), inspect('Down')
|
||||
return string.format('%s\n%s\n%s', bu, bf, bd)
|
||||
]]
|
||||
|
||||
local s, m = self:runFunction(script, true)
|
||||
self.tabs.action.info:setText(s or m)
|
||||
end
|
||||
|
||||
function page:eventHandler(event)
|
||||
if event.type == 'quit' then
|
||||
UI:exitPullEvents()
|
||||
|
||||
elseif event.type == 'tab_select' then
|
||||
config.tab = event.button.text
|
||||
Config.update('Turtles', config)
|
||||
|
||||
elseif event.type == 'button_press' then
|
||||
if event.button.fn then
|
||||
self:runFunction(event.button.fn, event.button.nowrap)
|
||||
self:showBlocks()
|
||||
elseif event.button.script then
|
||||
self:runScript(event.button.script)
|
||||
end
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function page:enable()
|
||||
UI.Page.enable(self)
|
||||
-- self.tabs:activateTab(page.tabs.turtles)
|
||||
end
|
||||
|
||||
if not Util.getOptions(options, { ... }, true) then
|
||||
return
|
||||
end
|
||||
|
||||
if options.turtle.value >= 0 then
|
||||
for _ = 1, 10 do
|
||||
page.turtle = _G.network[options.turtle.value]
|
||||
if page.turtle then
|
||||
break
|
||||
end
|
||||
os.sleep(1)
|
||||
end
|
||||
end
|
||||
|
||||
Event.onInterval(1, function()
|
||||
if page.turtle then
|
||||
local t = _G.network[page.turtle.id]
|
||||
page.turtle = t
|
||||
page:draw()
|
||||
page:sync()
|
||||
end
|
||||
end)
|
||||
|
||||
if config.tab then
|
||||
page.tabs.tabBar:selectTab(config.tab)
|
||||
end
|
||||
|
||||
UI:setPage(page)
|
||||
|
||||
UI:pullEvents()
|
||||
@@ -1,9 +0,0 @@
|
||||
if fs.exists('packages/core/lavaRefuel.lua') then fs.delete('packages/core/lavaRefuel.lua') end
|
||||
if fs.exists('packages/core/t.lua') then fs.delete('packages/core/t.lua') end
|
||||
|
||||
_ENV.shell.setCompletionFunction("packages/core/edit.lua",
|
||||
function(shell, nIndex, sText)
|
||||
if nIndex == 1 then
|
||||
return _G.fs.complete(sText, shell.dir(), true, false)
|
||||
end
|
||||
end)
|
||||
@@ -1,33 +0,0 @@
|
||||
local Util = require('util')
|
||||
|
||||
local os = _G.os
|
||||
local peripheral = _G.peripheral
|
||||
local term = _G.term
|
||||
|
||||
local args = { ... }
|
||||
local mon = args[1] and peripheral.wrap(args[1]) or
|
||||
peripheral.find('monitor') or
|
||||
error('Syntax: debug <monitor>')
|
||||
|
||||
mon.clear()
|
||||
mon.setTextScale(.5)
|
||||
mon.setCursorPos(1, 1)
|
||||
|
||||
local oldDebug = _G._debug
|
||||
|
||||
_G._debug = function(...)
|
||||
local oldTerm = term.redirect(mon)
|
||||
Util.print(...)
|
||||
term.redirect(oldTerm)
|
||||
end
|
||||
|
||||
repeat
|
||||
local e, side = os.pullEventRaw('monitor_touch')
|
||||
if e == 'monitor_touch' and side == mon.side then
|
||||
mon.clear()
|
||||
mon.setTextScale(.5)
|
||||
mon.setCursorPos(1, 1)
|
||||
end
|
||||
until e == 'terminate'
|
||||
|
||||
_G._debug = oldDebug
|
||||
1301
core/edit.lua
1301
core/edit.lua
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
||||
turtle.abort(true)
|
||||
@@ -1,7 +0,0 @@
|
||||
_G.requireInjector(_ENV)
|
||||
local config = require('config').load('gps')
|
||||
if config.home then
|
||||
if turtle.enableGPS() then
|
||||
return turtle.pathfind(config.home)
|
||||
end
|
||||
end
|
||||
@@ -1,29 +0,0 @@
|
||||
turtle.run(function()
|
||||
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local GPS = require('gps')
|
||||
local Socket = require('socket')
|
||||
|
||||
local id = {COMPUTER_ID}
|
||||
|
||||
if not turtle.enableGPS() then
|
||||
error('turtle: No GPS found')
|
||||
end
|
||||
|
||||
local socket = Socket.connect(id, 161)
|
||||
if not socket then
|
||||
error('turtle: Unable to connect to ' .. id)
|
||||
end
|
||||
|
||||
socket:write({ type = 'gps' })
|
||||
|
||||
local pt = socket:read(3)
|
||||
if not pt then
|
||||
error('turtle: No GPS response')
|
||||
end
|
||||
|
||||
if not turtle.pathfind(pt) then
|
||||
error('Unable to go to location')
|
||||
end
|
||||
end)
|
||||
@@ -1 +0,0 @@
|
||||
os.reboot()
|
||||
@@ -1,8 +0,0 @@
|
||||
_G.requireInjector(_ENV)
|
||||
local Config = require('config')
|
||||
local pt = turtle.enableGPS()
|
||||
if pt then
|
||||
local config = Config.load('gps', { })
|
||||
config.home = pt
|
||||
Config.update('gps', config)
|
||||
end
|
||||
@@ -1 +0,0 @@
|
||||
os.shutdown()
|
||||
@@ -1,74 +0,0 @@
|
||||
local function summon(id)
|
||||
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local GPS = require('gps')
|
||||
local Point = require('point')
|
||||
local Socket = require('socket')
|
||||
|
||||
turtle.setStatus('GPSing')
|
||||
turtle.setPoint({ x = 0, y = 0, z = 0, heading = 0 })
|
||||
|
||||
local pts = {
|
||||
[ 1 ] = { x = 0, z = 0, y = 0 },
|
||||
[ 2 ] = { x = 4, z = 0, y = 0 },
|
||||
[ 3 ] = { x = 2, z = -2, y = 2 },
|
||||
[ 4 ] = { x = 2, z = 2, y = 2 },
|
||||
}
|
||||
local tFixes = { }
|
||||
|
||||
local socket = Socket.connect(id, 161)
|
||||
|
||||
if not socket then
|
||||
error('turtle: Unable to connect to ' .. id)
|
||||
end
|
||||
|
||||
local function getDistance()
|
||||
socket:write({ type = 'ping' })
|
||||
local _, d = socket:read(5)
|
||||
return d
|
||||
end
|
||||
|
||||
local function doGPS()
|
||||
tFixes = { }
|
||||
for i = 1, 4 do
|
||||
if not turtle.go(pts[i]) then
|
||||
error('turtle: Unable to perform GPS maneuver')
|
||||
end
|
||||
local distance = getDistance()
|
||||
if not distance then
|
||||
error('turtle: No response from ' .. id)
|
||||
end
|
||||
table.insert(tFixes, {
|
||||
position = vector.new(turtle.point.x, turtle.point.y, turtle.point.z),
|
||||
distance = distance
|
||||
})
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
if not doGPS() then
|
||||
turtle.turnAround()
|
||||
turtle.setPoint({ x = 0, y = 0, z = 0, heading = 0})
|
||||
if not doGPS() then
|
||||
socket:close()
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
socket:close()
|
||||
|
||||
local pos = GPS.trilaterate(tFixes)
|
||||
|
||||
if pos then
|
||||
local pt = { x = pos.x, y = pos.y, z = pos.z }
|
||||
local _, h = Point.calculateMoves(turtle.getPoint(), pt)
|
||||
local hi = turtle.getHeadingInfo(h)
|
||||
turtle.setStatus('recalling')
|
||||
turtle.pathfind({ x = pt.x - hi.xd, z = pt.z - hi.zd, y = pt.y - hi.yd, heading = h })
|
||||
else
|
||||
error("turtle: Could not determine position")
|
||||
end
|
||||
end
|
||||
|
||||
turtle.run(function() summon({COMPUTER_ID}) end)
|
||||
@@ -1,459 +0,0 @@
|
||||
local Event = require('event')
|
||||
local GPS = require('gps')
|
||||
local itemDB = require('core.itemDB')
|
||||
local Point = require('point')
|
||||
local Socket = require('socket')
|
||||
local Util = require('util')
|
||||
local UI = require('ui')
|
||||
|
||||
local colors = _G.colors
|
||||
local network = _G.network
|
||||
local os = _G.os
|
||||
local peripheral = _G.peripheral
|
||||
|
||||
UI:configure('multiMiner', ...)
|
||||
|
||||
local scanner = peripheral.find('neuralInterface')
|
||||
if not scanner or not scanner.scan then
|
||||
error('Plethora scanner must be equipped')
|
||||
end
|
||||
|
||||
local canvas = scanner.canvas and scanner.canvas()
|
||||
if canvas then
|
||||
canvas.group = canvas.addGroup({ 4, 90 })
|
||||
canvas.bg = canvas.group.addRectangle(0, 0, 60, 24, 0x00000033)
|
||||
canvas.text = canvas.group.addText({ 4, 5 }, '') -- , 0x202020FF)
|
||||
canvas.text.setShadow(true)
|
||||
canvas.text.setScale(.75)
|
||||
end
|
||||
|
||||
local function locate()
|
||||
for _ = 1, 3 do
|
||||
local pt = GPS.getPoint()
|
||||
if pt then
|
||||
return pt
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local spt = GPS.getPoint() or error('GPS failure')
|
||||
local chestPoint -- location of chest
|
||||
local blockTypes = { } -- blocks types requested to mine
|
||||
local turtles = { } -- active turtles
|
||||
local pool = { } -- all turtles
|
||||
local queue = { } -- actual blocks to mine
|
||||
local abort
|
||||
|
||||
local function hijackTurtle(remoteId)
|
||||
local socket, msg = Socket.connect(remoteId, 188)
|
||||
|
||||
if not socket then
|
||||
error(msg)
|
||||
end
|
||||
|
||||
socket:write('turtle')
|
||||
local methods = socket:read()
|
||||
|
||||
local hijack = { }
|
||||
for _,method in pairs(methods) do
|
||||
hijack[method] = function(...)
|
||||
socket:write({ method, ... })
|
||||
local resp = socket:read()
|
||||
if not resp then
|
||||
error('timed out: ' .. method)
|
||||
end
|
||||
return table.unpack(resp)
|
||||
end
|
||||
end
|
||||
|
||||
return hijack, socket
|
||||
end
|
||||
|
||||
local function getNextPoint(turtle)
|
||||
local pt = Point.closest(turtle.getPoint(), queue)
|
||||
if pt then
|
||||
turtle.pt = pt
|
||||
queue[pt.pkey] = nil
|
||||
return pt
|
||||
end
|
||||
end
|
||||
|
||||
local function run(member, point)
|
||||
Event.addRoutine(function()
|
||||
local turtle, socket
|
||||
local _, m = pcall(function()
|
||||
member.active = true
|
||||
turtle, socket = hijackTurtle(member.id)
|
||||
|
||||
local function emptySlots(retain, pt)
|
||||
local slots = turtle.getFilledSlots()
|
||||
for _,slot in pairs(slots) do
|
||||
if not retain[slot.key] then
|
||||
turtle.select(slot.index)
|
||||
if pt then
|
||||
turtle.dropAt(pt, 64)
|
||||
else
|
||||
turtle.dropUp(64)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function dropOff()
|
||||
-- go to 2 above chest
|
||||
local topPoint = Point.copy(chestPoint)
|
||||
topPoint.y = topPoint.y + 2
|
||||
turtle.gotoY(topPoint.y)
|
||||
while not turtle.go(topPoint) do
|
||||
os.sleep(.5)
|
||||
end
|
||||
|
||||
-- path to chest
|
||||
local box = Point.makeBox(
|
||||
{ x = chestPoint.x - 3, y = chestPoint.y + 3, z = chestPoint.z - 3 },
|
||||
{ x = chestPoint.x + 3, y = chestPoint.y, z = chestPoint.z + 3 }
|
||||
)
|
||||
turtle.set({
|
||||
movementStrategy = 'pathing',
|
||||
pathingBox = Point.normalizeBox(box),
|
||||
digPolicy = 'digNone',
|
||||
})
|
||||
while not turtle.moveAgainst(chestPoint) do
|
||||
os.sleep(.5)
|
||||
end
|
||||
emptySlots({ }, chestPoint)
|
||||
|
||||
-- path to 3 above chest
|
||||
turtle.pathfind(Point.above(topPoint))
|
||||
turtle.set({
|
||||
movementStrategy = 'goto',
|
||||
digPolicy = 'turtleSafe',
|
||||
})
|
||||
end
|
||||
|
||||
if turtle then
|
||||
turtles[member.id] = turtle
|
||||
|
||||
turtle.reset()
|
||||
turtle.set({
|
||||
attackPolicy = 'attack',
|
||||
digPolicy = 'turtleSafe',
|
||||
movementStrategy = 'goto',
|
||||
point = point,
|
||||
})
|
||||
turtle.select(1)
|
||||
|
||||
repeat
|
||||
local pt = getNextPoint(turtle)
|
||||
if pt then
|
||||
member.status = 'digging'
|
||||
|
||||
if blockTypes[pt.key] == true then
|
||||
if turtle.moveAgainst(pt) then
|
||||
local index = turtle.selectOpenSlot()
|
||||
if turtle.digAt(pt, pt.name) then
|
||||
local slot = turtle.getSlot(index)
|
||||
if slot.count > 0 then
|
||||
blockTypes[pt.key] = slot.key
|
||||
if slot.key ~= pt.key then
|
||||
blockTypes[slot.key] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
turtle.select(1)
|
||||
else
|
||||
turtle.digAt(pt, pt.name)
|
||||
end
|
||||
|
||||
if turtle.getItemCount(15) > 0 then
|
||||
member.status = 'ejecting trash'
|
||||
emptySlots(blockTypes)
|
||||
turtle.condense()
|
||||
if turtle.getItemCount(15) > 0 then
|
||||
member.status = 'dropping off'
|
||||
if not chestPoint then
|
||||
member.abort = true
|
||||
member.status = 'full'
|
||||
else
|
||||
dropOff()
|
||||
end
|
||||
end
|
||||
turtle.select(1)
|
||||
end
|
||||
else
|
||||
member.status = 'waiting'
|
||||
os.sleep(1)
|
||||
end
|
||||
if member.fuel < 100 then
|
||||
member.status = 'out of fuel'
|
||||
break
|
||||
end
|
||||
until member.abort
|
||||
end
|
||||
|
||||
emptySlots(blockTypes)
|
||||
|
||||
if chestPoint then
|
||||
dropOff()
|
||||
while not turtle.go(Point.above(spt)) do
|
||||
os.sleep(.5)
|
||||
end
|
||||
turtle.set({ digPolicy = 'dig' })
|
||||
turtle.go(spt)
|
||||
else
|
||||
turtle.gotoY(spt.y)
|
||||
turtle.go(spt)
|
||||
end
|
||||
end)
|
||||
|
||||
turtles[member.id] = nil
|
||||
member.status = m
|
||||
member.active = false
|
||||
if socket then
|
||||
socket:close()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local blocksTab = UI.Tab {
|
||||
tabTitle = 'Blocks',
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 1,
|
||||
columns = {
|
||||
{ heading = 'Count', key = 'count', width = 6, align = 'right' },
|
||||
{ heading = 'Name', key = 'displayName' },
|
||||
},
|
||||
sortColumn = 'displayName',
|
||||
},
|
||||
}
|
||||
|
||||
local turtlesTab = UI.Tab {
|
||||
tabTitle = 'Turtles',
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 1,
|
||||
values = pool,
|
||||
columns = {
|
||||
{ heading = 'ID', key = 'id', width = 4, },
|
||||
{ heading = ' Fuel', key = 'fuel', width = 5, align = 'right' },
|
||||
{ heading = ' Dist', key = 'distance', width = 5, align = 'right' },
|
||||
{ heading = 'Status', key = 'status' },
|
||||
},
|
||||
sortColumn = 'label',
|
||||
},
|
||||
}
|
||||
|
||||
local page = UI.Page {
|
||||
menuBar = UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = 'Scan', event = 'scan' },
|
||||
{ text = 'Abort', event = 'abort' },
|
||||
},
|
||||
},
|
||||
tabs = UI.Tabs {
|
||||
y = 2, ey = -2,
|
||||
[1] = blocksTab,
|
||||
[2] = turtlesTab,
|
||||
},
|
||||
info = UI.Window {
|
||||
y = -1,
|
||||
backgroundColor = colors.blue,
|
||||
}
|
||||
}
|
||||
|
||||
function page.info:draw()
|
||||
self:clear()
|
||||
self:write(2, 1, 'Turtles: ' .. Util.size(turtles))
|
||||
if not chestPoint then
|
||||
self:write(16, 1, 'No chest')
|
||||
end
|
||||
self:write(28, 1, 'Queue: ' .. Util.size(queue))
|
||||
end
|
||||
|
||||
function turtlesTab.grid:getDisplayValues(row)
|
||||
row = Util.shallowCopy(row)
|
||||
row.distance = row.distance and Util.round(row.distance, 1)
|
||||
row.fuel = row.fuel and row.fuel > 0 and Util.toBytes(row.fuel) or ''
|
||||
return row
|
||||
end
|
||||
|
||||
function page:scan()
|
||||
local gpt = GPS.getPoint()
|
||||
if not gpt then
|
||||
return
|
||||
end
|
||||
local rawBlocks = scanner:scan()
|
||||
local candidates = { }
|
||||
|
||||
self.totals = Util.reduce(rawBlocks,
|
||||
function(acc, b)
|
||||
b.key = table.concat({ b.name, b.metadata }, ':')
|
||||
local entry = acc[b.key]
|
||||
if not entry then
|
||||
b.displayName = itemDB:getName(b.key)
|
||||
b.count = 1
|
||||
acc[b.key] = b
|
||||
else
|
||||
entry.count = entry.count + 1
|
||||
end
|
||||
|
||||
if b.name == 'computercraft:turtle_advanced' or
|
||||
b.name == 'computercraft:turtle' then
|
||||
table.insert(candidates, b)
|
||||
end
|
||||
|
||||
if b.name == 'minecraft:chest' or b.name:find('shulker') then
|
||||
chestPoint = b
|
||||
end
|
||||
|
||||
-- add relevant blocks to queue
|
||||
b.x = gpt.x + b.x
|
||||
b.y = gpt.y + b.y
|
||||
b.z = gpt.z + b.z
|
||||
b.pkey = table.concat({ b.x, b.y, b.z }, ':')
|
||||
if blockTypes[b.key] then
|
||||
if not Util.any(turtles, function(t)
|
||||
return t.pt and t.pt.pkey == b.pkey
|
||||
end) then
|
||||
queue[b.pkey] = b
|
||||
end
|
||||
else
|
||||
queue[b.pkey] = nil
|
||||
end
|
||||
return acc
|
||||
end,
|
||||
{ })
|
||||
|
||||
for _, b in pairs(candidates) do
|
||||
local v = scanner.getBlockMeta(b.x - gpt.x, b.y - gpt.y, b.z - gpt.z)
|
||||
if v and v.computer then
|
||||
local member = pool[v.computer.id]
|
||||
if not member then
|
||||
member = {
|
||||
id = v.computer.id,
|
||||
label = v.computer.label,
|
||||
}
|
||||
pool[v.computer.id] = member
|
||||
end
|
||||
|
||||
member.fuel = v.turtle.fuel
|
||||
member.distance = 0
|
||||
|
||||
if not v.computer.isOn then
|
||||
member.status = 'Powered off'
|
||||
elseif v.turtle.fuel < 100 and not member.active then
|
||||
member.status = 'Not enough fuel'
|
||||
elseif not member.active and not member.abort then
|
||||
local pt = Point.copy(b)
|
||||
pt.heading = Point.facings[v.state.facing].heading
|
||||
run(member, pt)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function blocksTab.grid:getDisplayValues(row)
|
||||
row = Util.shallowCopy(row)
|
||||
row.count = Util.toBytes(row.count) .. ' '
|
||||
return row
|
||||
end
|
||||
|
||||
function blocksTab.grid:getRowTextColor(row, selected)
|
||||
return blockTypes[row.key] and
|
||||
colors.yellow or
|
||||
UI.Grid.getRowTextColor(self, row, selected)
|
||||
end
|
||||
|
||||
function blocksTab:eventHandler(event)
|
||||
if event.type == 'grid_select' then
|
||||
local key = event.selected.key
|
||||
if blockTypes[key] then
|
||||
for k,v in pairs(queue) do
|
||||
if v.key == key then
|
||||
queue[k] = nil
|
||||
end
|
||||
end
|
||||
blockTypes[key] = nil
|
||||
else
|
||||
blockTypes[key] = true
|
||||
end
|
||||
self.grid:draw()
|
||||
end
|
||||
end
|
||||
|
||||
function page:eventHandler(event)
|
||||
if event.type == 'scan' then
|
||||
blocksTab.grid:setValues(self.totals)
|
||||
blocksTab.grid:draw()
|
||||
self.tabs:selectTab(blocksTab)
|
||||
|
||||
elseif event.type == 'abort' then
|
||||
for _, v in pairs(pool) do
|
||||
v.abort = true
|
||||
v.status = 'aborting'
|
||||
end
|
||||
spt = Point.above(locate())
|
||||
abort = true
|
||||
end
|
||||
|
||||
UI.Page.eventHandler(self, event)
|
||||
end
|
||||
|
||||
Event.onInterval(3, function()
|
||||
if not abort then
|
||||
page:scan()
|
||||
end
|
||||
end)
|
||||
|
||||
Event.onInterval(1, function()
|
||||
for id,v in pairs(network) do
|
||||
if v.fuel then
|
||||
if pool[id] then
|
||||
pool[id].fuel = v.fuel
|
||||
pool[id].distance = v.distance
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if abort and Util.size(turtles) == 0 then
|
||||
Event.exitPullEvents()
|
||||
end
|
||||
|
||||
if turtlesTab.enabled then
|
||||
turtlesTab.grid:update()
|
||||
turtlesTab.grid:draw()
|
||||
end
|
||||
|
||||
page.info:draw()
|
||||
page.info:sync()
|
||||
|
||||
if canvas then
|
||||
local text = string.format('Turtles: %s\nQueue: %s',
|
||||
Util.size(turtles), Util.size(queue))
|
||||
canvas.text.setText(text)
|
||||
end
|
||||
end)
|
||||
|
||||
Event.onTimeout(.1, function()
|
||||
page:scan()
|
||||
blocksTab.grid:setValues(page.totals)
|
||||
blocksTab.grid:draw()
|
||||
page:sync()
|
||||
end)
|
||||
|
||||
UI:setPage(page)
|
||||
|
||||
Event.onTerminate(function()
|
||||
spt = Point.above(locate())
|
||||
for _, v in pairs(pool) do
|
||||
v.status = 'aborting'
|
||||
v.abort = true
|
||||
end
|
||||
abort = true
|
||||
end)
|
||||
|
||||
Event.pullEvents()
|
||||
|
||||
if canvas then
|
||||
canvas.group.remove()
|
||||
end
|
||||
@@ -1,546 +0,0 @@
|
||||
-- +---------------------+------------+---------------------+
|
||||
-- | | | |
|
||||
-- | | 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 Util = require('util')
|
||||
|
||||
local multishell = _ENV.multishell
|
||||
local os = _G.os
|
||||
|
||||
local recTerm, oldTerm, arg, showInput, skipLast, lastDelay, curInput = {}, Util.shallowCopy(multishell.term), {...}, false, false, 2, ""
|
||||
local curBlink, oldBlink, tTerm, buffer, colourNum, xPos, yPos, oldXPos, oldYPos, tCol, bCol, xSize, ySize = false, false, {}, {}, {}, 1, 1, 1, 1, colours.white, colours.black, oldTerm.getSize()
|
||||
local greys, buttons = {["0"] = true, ["7"] = true, ["8"] = true, ["f"] = true}, {"l", "r", "m"}
|
||||
local charW, charH, chars, resp
|
||||
|
||||
local calls = { }
|
||||
local curCalls = { delay = 0 }
|
||||
local callListCount = 0
|
||||
local callCount = 0
|
||||
|
||||
local function showSyntax()
|
||||
print('Gif Recorder by Bomb Bloke\n')
|
||||
print('Syntax: recGif [-i] [-s] [-ld:<delay>] filename')
|
||||
print(' -i : show input')
|
||||
print(' -s : skip last')
|
||||
print(' -ld : last delay')
|
||||
end
|
||||
|
||||
for i = #arg, 1, -1 do
|
||||
local curArg = arg[i]:lower()
|
||||
|
||||
if curArg == "-i" then
|
||||
showInput, ySize = true, ySize + 1
|
||||
table.remove(arg, i)
|
||||
elseif curArg == "-s" then
|
||||
skipLast = true
|
||||
table.remove(arg, i)
|
||||
elseif curArg:sub(1, 4) == "-ld:" then
|
||||
curArg = tonumber(curArg:sub(5))
|
||||
if curArg then lastDelay = curArg end
|
||||
table.remove(arg, i)
|
||||
elseif curArg == '-?' then
|
||||
showSyntax()
|
||||
return
|
||||
elseif i ~= #arg then
|
||||
showSyntax()
|
||||
printError('\nInvalid argument')
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
print('Gif Recorder by Bomb Bloke\n')
|
||||
print('Press control-p to stop recording')
|
||||
|
||||
local filename = arg[#arg]
|
||||
if not filename then
|
||||
print('Enter file name:')
|
||||
filename = read()
|
||||
end
|
||||
|
||||
if #filename == 0 then
|
||||
showSyntax()
|
||||
print()
|
||||
error('Invalid file name')
|
||||
end
|
||||
|
||||
print('Initializing...')
|
||||
|
||||
-- don't pollute global env
|
||||
-- convert these to require style apis
|
||||
local function loadAPI(url, env)
|
||||
local apiEnv = Util.shallowCopy(env)
|
||||
apiEnv.shell = nil
|
||||
apiEnv.multishell = nil
|
||||
setmetatable(apiEnv, { __index = _G })
|
||||
local fn, m = Util.loadUrl(url, apiEnv)
|
||||
if not fn then
|
||||
error(m)
|
||||
end
|
||||
fn()
|
||||
return apiEnv
|
||||
end
|
||||
|
||||
bbpack = loadAPI('http://pastebin.com/raw/PdrJjb5S', getfenv(1))
|
||||
GIF = loadAPI('http://pastebin.com/raw/5uk9uRjC', getfenv(1))
|
||||
|
||||
local s, m = Util.runUrl(getfenv(1), 'http://pastebin.com/raw/cUYTGbpb', 'get', 'CnLzL5fg')
|
||||
if not s then
|
||||
error(m)
|
||||
end
|
||||
-- 'Y0eLUPtr')
|
||||
-- CnLzL5fg
|
||||
local function snooze()
|
||||
local myEvent = tostring({})
|
||||
os.queueEvent(myEvent)
|
||||
os.pullEvent(myEvent)
|
||||
end
|
||||
|
||||
local function safeString(text)
|
||||
local newText = {}
|
||||
|
||||
for i = 1, #text do
|
||||
local val = text:byte(i)
|
||||
newText[i] = (val > 31 and val < 127) and val or 63
|
||||
end
|
||||
|
||||
return string.char(unpack(newText))
|
||||
end
|
||||
|
||||
local function safeCol(text, subst)
|
||||
local newText = {}
|
||||
|
||||
for i = 1, #text do
|
||||
local val = text:sub(i, i)
|
||||
newText[i] = greys[val] and val or subst
|
||||
end
|
||||
|
||||
return table.concat(newText)
|
||||
end
|
||||
|
||||
-- Build a terminal that records stuff:
|
||||
|
||||
recTerm = multishell.term
|
||||
|
||||
for key, func in pairs(oldTerm) do
|
||||
recTerm[key] = function(...)
|
||||
local result = { func(...) }
|
||||
|
||||
if callCount == 0 then
|
||||
os.queueEvent('capture_frame')
|
||||
end
|
||||
callCount = callCount + 1
|
||||
curCalls[callCount] = { key, ... }
|
||||
return unpack(result)
|
||||
end
|
||||
end
|
||||
|
||||
local tabId = multishell.getCurrent()
|
||||
|
||||
_G.device.keyboard.addHotkey('control-p', function()
|
||||
os.queueEvent('recorder_stop')
|
||||
end)
|
||||
|
||||
local tabs = multishell.getTabs()
|
||||
for _,tab in pairs(tabs) do
|
||||
if tab.isOverview then
|
||||
multishell.hideTab(tabId)
|
||||
multishell.setFocus(tab.tabId)
|
||||
os.queueEvent('term_resize')
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local curTime = os.clock() - 1
|
||||
|
||||
while true do
|
||||
local event = { os.pullEventRaw() }
|
||||
|
||||
if event[1] == 'recorder_stop' or event[1] == 'terminate' then
|
||||
break
|
||||
end
|
||||
|
||||
if event[1] == 'capture_frame' then
|
||||
local newTime = os.clock()
|
||||
|
||||
if callListCount > 0 then
|
||||
calls[callListCount].delay = (newTime - curTime)
|
||||
end
|
||||
|
||||
curTime = newTime
|
||||
callListCount = callListCount + 1
|
||||
calls[callListCount] = curCalls
|
||||
|
||||
curCalls, callCount = { delay = 0 }, 0
|
||||
end
|
||||
end
|
||||
|
||||
_G.device.keyboard.removeHotkey('control-p')
|
||||
|
||||
for k,fn in pairs(oldTerm) do
|
||||
multishell.term[k] = fn
|
||||
end
|
||||
|
||||
multishell.unhideTab(tabId)
|
||||
multishell.setFocus(tabId)
|
||||
|
||||
if #calls[#calls] == 0 then calls[#calls] = nil end
|
||||
if skipLast and #calls > 1 then calls[#calls] = nil end
|
||||
|
||||
calls[#calls].delay = lastDelay
|
||||
|
||||
print(string.format("Encoding %d frames...", #calls))
|
||||
--Util.writeTable('tmp/raw.txt', calls)
|
||||
|
||||
-- Perform a quick re-parse of the recorded data (adding frames for when the cursor blinks):
|
||||
|
||||
do
|
||||
local callListCount, tempCalls, blink, oldBlink, curBlink, blinkDelay = 1, {}, false, false, true, 0
|
||||
|
||||
for i = 1, #calls - 1 do
|
||||
curCalls = calls[i]
|
||||
tempCalls[callListCount] = curCalls
|
||||
for j = 1, #curCalls do if curCalls[j][1] == "setCursorBlink" then blink = curCalls[j][2] end end
|
||||
|
||||
if blink then
|
||||
if blinkDelay == 0 then
|
||||
curCalls[#curCalls + 1] = {"toggleCur", curBlink}
|
||||
blinkDelay, curBlink = 0.4, not curBlink
|
||||
end
|
||||
|
||||
while tempCalls[callListCount].delay > blinkDelay do
|
||||
local remainder = tempCalls[callListCount].delay - blinkDelay
|
||||
tempCalls[callListCount].delay = blinkDelay
|
||||
callListCount = callListCount + 1
|
||||
tempCalls[callListCount] = {{"toggleCur", curBlink}, ["delay"] = remainder}
|
||||
blinkDelay, curBlink = 0.4, not curBlink
|
||||
end
|
||||
|
||||
blinkDelay = blinkDelay - tempCalls[callListCount].delay
|
||||
else
|
||||
if oldBlink then curCalls[#curCalls + 1] = {"toggleCur", false} end
|
||||
blinkDelay = (curCalls.delay - blinkDelay) % 0.4
|
||||
end
|
||||
|
||||
callListCount, oldBlink = callListCount + 1, blink
|
||||
end
|
||||
|
||||
tempCalls[callListCount] = calls[#calls]
|
||||
tempCalls[callListCount][#tempCalls[callListCount] + 1] = {"toggleCur", false}
|
||||
|
||||
calls, curCalls = tempCalls, nil
|
||||
end
|
||||
|
||||
snooze()
|
||||
|
||||
-- Load font data:
|
||||
do
|
||||
local ascii, counter = GIF.toPaintutils(GIF.flattenGIF(GIF.loadGIF("ascii.gif"))), 0
|
||||
local newFont, ybump, xbump = #ascii ~= #ascii[1], 0, 0
|
||||
charW, charH, chars = newFont and #ascii[1] / 16 or #ascii[1] * 3 / 64, #ascii / 16, {}
|
||||
|
||||
for yy = 0, newFont and 15 or 7 do
|
||||
for xx = 0, 15 do
|
||||
local newChar, length = {}, 0
|
||||
|
||||
-- Place in 2d grid of bools:
|
||||
for y = 1, charH do
|
||||
local newRow = {}
|
||||
|
||||
for x = 1, charW do
|
||||
local set = ascii[y + ybump][x + xbump] == 1
|
||||
if set and x > length then length = x end
|
||||
newRow[x] = set
|
||||
end
|
||||
|
||||
newChar[y] = newRow
|
||||
end
|
||||
|
||||
-- Center:
|
||||
if not newFont then for y = 1, charH do for x = 1, math.floor((charW - length) / 2) do table.insert(newChar[y], 1, false) end end end
|
||||
|
||||
chars[counter] = newChar
|
||||
counter, xbump = counter + 1, xbump + (newFont and charW or charH)
|
||||
end
|
||||
xbump, ybump = 0, ybump + charH
|
||||
end
|
||||
end
|
||||
|
||||
snooze()
|
||||
|
||||
-- Terminal data translation:
|
||||
|
||||
do
|
||||
local hex, counter = "0123456789abcdef", 1
|
||||
|
||||
for i = 1, 16 do
|
||||
colourNum[counter] = hex:sub(i, i)
|
||||
counter = counter * 2
|
||||
end
|
||||
end
|
||||
|
||||
for y = 1, ySize do
|
||||
buffer[y] = {}
|
||||
for x = 1, xSize do buffer[y][x] = {" ", colourNum[tCol], colourNum[bCol]} end
|
||||
end
|
||||
|
||||
if showInput then for x = 1, xSize do buffer[ySize][x][3] = colourNum[colours.lightGrey] end end
|
||||
|
||||
tTerm.blit = function(text, fgCol, bgCol)
|
||||
if xPos > xSize or xPos + #text - 1 < 1 or yPos < 1 or yPos > ySize then return end
|
||||
|
||||
if not _HOST then text = safeString(text) end
|
||||
|
||||
if not term.isColour() then
|
||||
fgCol = safeCol(fgCol, "0")
|
||||
bgCol = safeCol(bgCol, "f")
|
||||
end
|
||||
|
||||
if xPos < 1 then
|
||||
text = text:sub(2 - xPos)
|
||||
fgCol = fgCol:sub(2 - xPos)
|
||||
bgCol = bgCol:sub(2 - xPos)
|
||||
xPos = 1
|
||||
end
|
||||
|
||||
if xPos + #text - 1 > xSize then
|
||||
text = text:sub(1, xSize - xPos + 1)
|
||||
fgCol = fgCol:sub(1, xSize - xPos + 1)
|
||||
bgCol = bgCol:sub(1, xSize - xPos + 1)
|
||||
end
|
||||
|
||||
for x = 1, #text do
|
||||
buffer[yPos][xPos + x - 1][1] = text:sub(x, x)
|
||||
buffer[yPos][xPos + x - 1][2] = fgCol:sub(x, x)
|
||||
buffer[yPos][xPos + x - 1][3] = bgCol:sub(x, x)
|
||||
end
|
||||
|
||||
xPos = xPos + #text
|
||||
end
|
||||
|
||||
tTerm.write = function(text)
|
||||
text = tostring(text)
|
||||
tTerm.blit(text, string.rep(colourNum[tCol], #text), string.rep(colourNum[bCol], #text))
|
||||
end
|
||||
|
||||
tTerm.clearLine = function()
|
||||
local oldXPos = xPos
|
||||
|
||||
xPos = 1
|
||||
tTerm.write(string.rep(" ", xSize))
|
||||
|
||||
xPos = oldXPos
|
||||
end
|
||||
|
||||
tTerm.clear = function()
|
||||
local oldXPos, oldYPos = xPos, yPos
|
||||
|
||||
for y = 1, ySize do
|
||||
xPos, yPos = 1, y
|
||||
tTerm.write(string.rep(" ", xSize))
|
||||
end
|
||||
|
||||
xPos, yPos = oldXPos, oldYPos
|
||||
end
|
||||
|
||||
tTerm.setCursorPos = function(x, y)
|
||||
xPos, yPos = math.floor(x), math.floor(y)
|
||||
end
|
||||
|
||||
tTerm.setTextColour = function(col)
|
||||
tCol = col
|
||||
end
|
||||
|
||||
tTerm.setTextColor = function(col)
|
||||
tCol = col
|
||||
end
|
||||
|
||||
tTerm.setBackgroundColour = function(col)
|
||||
bCol = col
|
||||
end
|
||||
|
||||
tTerm.setBackgroundColor = function(col)
|
||||
bCol = col
|
||||
end
|
||||
|
||||
tTerm.scroll = function(lines)
|
||||
if math.abs(lines) < ySize then
|
||||
local oldXPos, oldYPos = xPos, yPos
|
||||
|
||||
for y = 1, ySize do
|
||||
if y + lines > 0 and y + lines <= ySize then
|
||||
for x = 1, xSize do
|
||||
xPos, yPos = x, y
|
||||
tTerm.blit(buffer[y + lines][x][1], buffer[y + lines][x][2], buffer[y + lines][x][3])
|
||||
end
|
||||
else
|
||||
yPos = y
|
||||
tTerm.clearLine()
|
||||
end
|
||||
end
|
||||
|
||||
xPos, yPos = oldXPos, oldYPos
|
||||
else tTerm.clear() end
|
||||
end
|
||||
|
||||
tTerm.toggleCur = function(newBlink)
|
||||
curBlink = newBlink
|
||||
end
|
||||
|
||||
tTerm.newInput = function(input)
|
||||
local oldTC, oldBC, oldX, oldY = tCol, bCol, xPos, yPos
|
||||
tCol, bCol, xPos, yPos, ySize, input = colours.grey, colours.lightGrey, 1, ySize + 1, ySize + 1, input .. " "
|
||||
|
||||
while #curInput + #input + 1 > xSize do curInput = curInput:sub(curInput:find(" ") + 1) end
|
||||
curInput = curInput .. input .. " "
|
||||
tTerm.clearLine()
|
||||
tTerm.write(curInput)
|
||||
|
||||
tCol, bCol, xPos, yPos, ySize = oldTC, oldBC, oldX, oldY, ySize - 1
|
||||
end
|
||||
|
||||
tTerm.key = function(key)
|
||||
tTerm.newInput((not keys.getName(key)) and "unknownKey" or keys.getName(key))
|
||||
end
|
||||
|
||||
tTerm.mouse_click = function(button, x, y)
|
||||
tTerm.newInput(buttons[button] .. "C@" .. tostring(x) .. "x" .. tostring(y))
|
||||
end
|
||||
|
||||
local image = {["width"] = xSize * charW, ["height"] = ySize * charH}
|
||||
|
||||
for i = 1, #calls do
|
||||
local xMin, yMin, xMax, yMax, oldBuffer, curCalls, changed = xSize + 1, ySize + 1, 0, 0, {}, calls[i], false
|
||||
calls[i] = nil
|
||||
|
||||
for y = 1, ySize do
|
||||
oldBuffer[y] = {}
|
||||
for x = 1, xSize do oldBuffer[y][x] = {buffer[y][x][1], buffer[y][x][2], buffer[y][x][3], buffer[y][x][4]} end
|
||||
end
|
||||
|
||||
snooze()
|
||||
|
||||
if showInput then ySize = ySize - 1 end
|
||||
for j = 1, #curCalls do if tTerm[curCalls[j][1]] then tTerm[curCalls[j][1]](unpack(curCalls[j], 2)) end end
|
||||
if showInput then ySize = ySize + 1 end
|
||||
|
||||
if i > 1 then
|
||||
for yy = 1, ySize do for xx = 1, xSize do if buffer[yy][xx][1] ~= oldBuffer[yy][xx][1] or (buffer[yy][xx][2] ~= oldBuffer[yy][xx][2] and buffer[yy][xx][1] ~= " ") or buffer[yy][xx][3] ~= oldBuffer[yy][xx][3] then
|
||||
changed = true
|
||||
if xx < xMin then xMin = xx end
|
||||
if xx > xMax then xMax = xx end
|
||||
if yy < yMin then yMin = yy end
|
||||
if yy > yMax then yMax = yy end
|
||||
end end end
|
||||
else xMin, yMin, xMax, yMax, changed = 1, 1, xSize, ySize, true end
|
||||
|
||||
if oldBlink and (xPos ~= oldXPos or yPos ~= oldYPos or not curBlink) and oldXPos > 0 and oldYPos > 0 and oldXPos <= xSize and oldYPos <= ySize then
|
||||
changed = true
|
||||
if oldXPos < xMin then xMin = oldXPos end
|
||||
if oldXPos > xMax then xMax = oldXPos end
|
||||
if oldYPos < yMin then yMin = oldYPos end
|
||||
if oldYPos > yMax then yMax = oldYPos end
|
||||
buffer[oldYPos][oldXPos][4] = false
|
||||
end
|
||||
|
||||
if curBlink and (xPos ~= oldXPos or yPos ~= oldYPos or not oldBlink) and xPos > 0 and yPos > 0 and xPos <= xSize and yPos <= ySize then
|
||||
changed = true
|
||||
if xPos < xMin then xMin = xPos end
|
||||
if xPos > xMax then xMax = xPos end
|
||||
if yPos < yMin then yMin = yPos end
|
||||
if yPos > yMax then yMax = yPos end
|
||||
buffer[yPos][xPos][4] = true
|
||||
end
|
||||
|
||||
oldBlink, oldXPos, oldYPos = curBlink, xPos, yPos
|
||||
|
||||
local thisFrame = {
|
||||
["xstart"] = (xMin - 1) * charW,
|
||||
["ystart"] = (yMin - 1) * charH,
|
||||
["xend"] = (xMax - xMin + 1) * charW,
|
||||
["yend"] = (yMax - yMin + 1) * charH,
|
||||
["delay"] = curCalls.delay,
|
||||
["disposal"] = 1
|
||||
}
|
||||
|
||||
for y = 1, (yMax - yMin + 1) * charH do
|
||||
local row = {}
|
||||
for x = 1, (xMax - xMin + 1) * charW do row[x] = " " end
|
||||
thisFrame[y] = row
|
||||
end
|
||||
|
||||
snooze()
|
||||
|
||||
for yy = yMin, yMax do
|
||||
local yBump = (yy - yMin) * charH
|
||||
|
||||
for xx = xMin, xMax do if buffer[yy][xx][1] ~= oldBuffer[yy][xx][1] or (buffer[yy][xx][2] ~= oldBuffer[yy][xx][2] and buffer[yy][xx][1] ~= " ") or buffer[yy][xx][3] ~= oldBuffer[yy][xx][3] or buffer[yy][xx][4] ~= oldBuffer[yy][xx][4] or i == 1 then
|
||||
local thisChar, thisT, thisB, xBump = chars[buffer[yy][xx][1]:byte()], buffer[yy][xx][2], buffer[yy][xx][3], (xx - xMin) * charW
|
||||
if thisChar then
|
||||
for y = 1, charH do
|
||||
for x = 1, charW do
|
||||
local ch = thisChar[y][x] and thisT or thisB
|
||||
thisFrame[y + yBump][x + xBump] = ch
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if buffer[yy][xx][4] then
|
||||
thisT, thisChar = colourNum[tCol], chars[95]
|
||||
for y = 1, charH do for x = 1, charW do if thisChar[y][x] then thisFrame[y + yBump][x + xBump] = thisT end end end
|
||||
end
|
||||
end end
|
||||
|
||||
for y = yBump + 1, yBump + charH do
|
||||
local skip, chars, row = 0, {}, {}
|
||||
|
||||
for x = 1, #thisFrame[y] do
|
||||
if thisFrame[y][x] == " " then
|
||||
if #chars > 0 then
|
||||
row[#row + 1] = table.concat(chars)
|
||||
chars = {}
|
||||
end
|
||||
|
||||
skip = skip + 1
|
||||
else
|
||||
if skip > 0 then
|
||||
row[#row + 1] = skip
|
||||
skip = 0
|
||||
end
|
||||
|
||||
chars[#chars + 1] = thisFrame[y][x]
|
||||
end
|
||||
end
|
||||
|
||||
if #chars > 0 then row[#row + 1] = table.concat(chars) end
|
||||
thisFrame[y] = row
|
||||
end
|
||||
|
||||
snooze()
|
||||
end
|
||||
|
||||
if changed then
|
||||
image[#image + 1] = thisFrame
|
||||
else
|
||||
image[#image].delay = image[#image].delay + curCalls.delay
|
||||
end
|
||||
end
|
||||
|
||||
buffer = nil
|
||||
|
||||
GIF.saveGIF(image, filename)
|
||||
|
||||
fs.delete('ascii.gif')
|
||||
|
||||
print("Encode complete")
|
||||
Reference in New Issue
Block a user