Merge branch 'develop'

This commit is contained in:
kepler155c@gmail.com 2016-12-27 16:01:30 -05:00
commit 482c52b36f
27 changed files with 2014 additions and 753 deletions

View File

@ -35,26 +35,32 @@ local Browser = UI.Page {
},
fileMenu = UI.DropMenu {
buttons = {
{ text = 'Run', event = 'run' },
{ text = 'Edit e', event = 'edit' },
{ text = 'Shell s', event = 'shell' },
{ text = 'Quit q', event = 'quit' },
{ 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 = 'Mark m', event = 'mark' },
{ text = 'Cut ^x', event = 'cut' },
{ text = 'Copy ^c', event = 'copy' },
{ text = 'Paste ^v', event = 'paste' },
{ text = 'Delete del', event = 'delete' },
{ text = 'Unmark all u', event = 'unmark' },
{ 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 = 'Refresh r', event = 'refresh' },
{ text = 'Hidden ^h', event = 'toggle_hidden' },
UI.Text { },
}
},
grid = UI.ScrollingGrid {
@ -69,8 +75,8 @@ local Browser = UI.Page {
},
statusBar = UI.StatusBar {
columns = {
{ '', 'status', UI.term.width - 19 },
{ '', 'info', 10 },
{ '', 'status', UI.term.width - 8 },
--{ '', 'info', 10 },
{ 'Size: ', 'totalSize', 8 },
},
},

View File

@ -1,4 +1,5 @@
require = requireInjector(getfenv(1))
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')
@ -6,7 +7,7 @@ local History = require('history')
local sandboxEnv = Util.shallowCopy(getfenv(1))
sandboxEnv.exit = function() Event.exitPullEvents() end
sandboxEnv.require = requireInjector(sandboxEnv)
sandboxEnv.require = injector(sandboxEnv)
setmetatable(sandboxEnv, { __index = _G })
multishell.setTitle(multishell.getCurrent(), 'Lua')
@ -15,12 +16,12 @@ UI:configure('Lua', ...)
local command = ''
local history = History.load('.lua_history', 25)
local resultsPage = UI.Page({
local page = UI.Page({
menuBar = UI.MenuBar({
buttons = {
{ text = 'Local', event = 'local' },
{ text = 'Global', event = 'global' },
{ text = 'Device', event = 'device' },
{ text = 'Device', event = 'device', name = 'Device' },
},
}),
prompt = UI.TextEntry({
@ -29,10 +30,11 @@ local resultsPage = UI.Page({
backgroundFocusColor = colors.black,
limit = 256,
accelerators = {
enter = 'command_enter',
up = 'history_back',
down = 'history_forward',
mouse_rightclick = 'clear_prompt',
enter = 'command_enter',
up = 'history_back',
down = 'history_forward',
mouse_rightclick = 'clear_prompt',
-- [ 'control-space' ] = 'autocomplete',
},
}),
grid = UI.ScrollingGrid({
@ -47,7 +49,7 @@ local resultsPage = UI.Page({
notification = UI.Notification(),
})
function resultsPage:setPrompt(value, focus)
function page:setPrompt(value, focus)
self.prompt:setValue(value)
self.prompt.scroll = 0
self.prompt:setPosition(#value)
@ -59,29 +61,77 @@ function resultsPage:setPrompt(value, focus)
self.prompt:draw()
if focus then
resultsPage:setFocus(self.prompt)
page:setFocus(self.prompt)
end
end
function resultsPage:enable()
function page:enable()
self:setFocus(self.prompt)
UI.Page.enable(self)
if not device then
self.menuBar.Device:disable()
end
end
function resultsPage:eventHandler(event)
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
resultsPage:setPrompt('', true)
self:setPrompt('', true)
self:executeStatement('getfenv(0)')
command = nil
elseif event.type == 'local' then
resultsPage:setPrompt('', true)
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
resultsPage:setPrompt('device', true)
self:setPrompt('device', true)
self:executeStatement('device')
elseif event.type == 'history_back' then
@ -128,7 +178,7 @@ function resultsPage:eventHandler(event)
return true
end
function resultsPage:setResult(result)
function page:setResult(result)
local t = { }
local function safeValue(v)
@ -169,7 +219,7 @@ function resultsPage:setResult(result)
self:draw()
end
function resultsPage.grid:eventHandler(event)
function page.grid:eventHandler(event)
local entry = self:getSelected()
@ -199,18 +249,18 @@ function resultsPage.grid:eventHandler(event)
if event.type == 'grid_focus_row' then
if self.focused then
resultsPage:setPrompt(commandAppend())
page:setPrompt(commandAppend())
end
elseif event.type == 'grid_select' then
resultsPage:setPrompt(commandAppend(), true)
resultsPage:executeStatement(commandAppend())
page:setPrompt(commandAppend(), true)
page:executeStatement(commandAppend())
else
return UI.Grid.eventHandler(self, event)
end
return true
end
function resultsPage:rawExecute(s)
function page:rawExecute(s)
local fn, m = loadstring("return (" .. s .. ')', 'lua')
if not fn then
@ -225,7 +275,7 @@ function resultsPage:rawExecute(s)
return fn, m
end
function resultsPage:executeStatement(statement)
function page:executeStatement(statement)
command = statement
@ -233,6 +283,8 @@ function resultsPage:executeStatement(statement)
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()
@ -242,6 +294,13 @@ function resultsPage:executeStatement(statement)
end
end
UI:setPage(resultsPage)
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()

View File

@ -7,37 +7,37 @@ multishell.setTitle(multishell.getCurrent(), 'Network')
UI:configure('Network', ...)
local gridColumns = {
{ heading = 'Label', key = 'label' },
{ heading = 'Dist', key = 'distance' },
{ heading = 'Status', key = 'status' },
{ 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 = 'Fuel', key = 'fuel' })
table.insert(gridColumns, { heading = 'Uptime', key = 'uptime' })
end
local page = UI.Page({
menuBar = UI.MenuBar({
local page = UI.Page {
menuBar = UI.MenuBar {
buttons = {
{ text = 'Telnet', event = 'telnet' },
{ text = 'VNC', event = 'vnc' },
{ text = 'Reboot', event = 'reboot' },
},
}),
grid = UI.ScrollingGrid({
},
grid = UI.ScrollingGrid {
y = 2,
values = network,
columns = gridColumns,
sortColumn = 'label',
autospace = true,
}),
notification = UI.Notification(),
},
notification = UI.Notification { },
accelerators = {
q = 'quit',
c = 'clear',
},
})
}
function sendCommand(host, command)
@ -60,7 +60,7 @@ function sendCommand(host, command)
end
function page:eventHandler(event)
local t = self.grid.selected
local t = self.grid:getSelected()
if t then
if event.type == 'telnet' or event.type == 'grid_select' then
multishell.openTab({
@ -113,22 +113,14 @@ function page.grid:getDisplayValues(row)
return row
end
function page.grid:draw()
self:adjustWidth()
UI.Grid.draw(self)
if page.notification.enabled then
page.notification:draw()
end
end
function updateComputers()
Event.addThread(function()
while true do
page.grid:update()
page.grid:draw()
page:sync()
os.sleep(1)
end
end
end)
Event.addHandler('device_attach', function(h, deviceName)
if deviceName == 'wireless_modem' then
@ -149,5 +141,5 @@ if not device.wireless_modem then
end
UI:setPage(page)
Event.pullEvents(updateComputers)
Event.pullEvents()
UI.term:reset()

View File

@ -6,6 +6,7 @@ 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', ...)
@ -190,8 +191,9 @@ function page.container:setCategory(categoryName)
-- reposition all children
for k,child in ipairs(self.children) do
child.x = col
child.y = row
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
@ -203,6 +205,25 @@ function page.container:setCategory(categoryName)
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()
@ -223,6 +244,8 @@ function page:eventHandler(event)
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)
@ -263,9 +286,9 @@ function page:eventHandler(event)
elseif event.type == 'tab_change' then
if event.current > event.last then
self.container:setTransition('left')
--self.container:setTransition(UI.effect.slideLeft)
else
self.container:setTransition('right')
--self.container:setTransition(UI.effect.slideRight)
end
elseif event.type == 'refresh' then
@ -308,61 +331,53 @@ function page:eventHandler(event)
end
local formWidth = math.max(UI.term.width - 14, 26)
local gutter = math.floor((UI.term.width - formWidth) / 2) + 1
local editor = UI.Page({
backgroundColor = colors.blue,
form = UI.Form({
fields = {
{ label = 'Title', key = 'title', width = 15, limit = 11, display = UI.Form.D.entry,
help = 'Application title' },
{ label = 'Run', key = 'run', width = formWidth - 11, limit = 100, display = UI.Form.D.entry,
help = 'Full path to application' },
{ label = 'Category', key = 'category', width = 15, limit = 11, display = UI.Form.D.entry,
help = 'Category of application' },
{ text = 'Accept', event = 'accept', display = UI.Form.D.button,
x = 1, y = 9, width = 10 },
{ text = 'Cancel', event = 'cancel', display = UI.Form.D.button,
x = formWidth - 11, y = 9, width = 10 },
},
labelWidth = 8,
x = gutter + 1,
y = math.max(2, math.floor((UI.term.height - 9) / 2)),
local editor = UI.Dialog {
height = 11,
width = formWidth,
title = 'Edit application',
form = UI.Form {
y = 2,
height = 9,
width = UI.term.width - (gutter * 2),
image = UI.NftImage({
y = 5,
x = 1,
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,
}),
button = UI.Button({
x = 10,
y = 6,
text = 'Load icon',
width = 11,
event = 'loadIcon',
}),
}),
},
},
statusBar = UI.StatusBar(),
notification = UI.Notification(),
iconFile = '',
})
}
function editor:enable(app)
if app then
self.original = app
self.form:setValues(Util.shallowCopy(app))
self.form:setValues(app)
local icon
if app.icon then
icon = parseIcon(app.icon)
end
self.form.image:setImage(icon)
self:setFocus(self.form.children[1])
end
UI.Page.enable(self)
self:focusFirst()
end
function editor.form.image:draw()
@ -370,11 +385,11 @@ function editor.form.image:draw()
UI.NftImage.draw(self)
end
function editor:updateApplications(app, original)
if original.run then
local _,k = Util.find(applications, 'run', original.run)
if k then
function editor:updateApplications(app)
for k,v in pairs(applications) do
if v == app then
applications[k] = nil
break
end
end
table.insert(applications, app)
@ -383,7 +398,7 @@ end
function editor:eventHandler(event)
if event.type == 'cancel' then
if event.type == 'form_cancel' or event.type == 'cancel' then
UI:setPreviousPage()
elseif event.type == 'focus_change' then
@ -391,7 +406,15 @@ function editor:eventHandler(event)
self.statusBar:draw()
elseif event.type == 'loadIcon' then
UI:setPage(FileUI(), fs.getDir(self.iconFile), function(fileName)
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()
@ -408,23 +431,21 @@ function editor:eventHandler(event)
self.form.image:draw()
end)
if not s and m then
self.notification:error(m:gsub('.*: (.*)', '%1'))
local msg = m:gsub('.*: (.*)', '%1')
page.notification:error(msg)
end
end
end)
elseif event.type == 'accept' then
elseif event.type == 'form_invalid' then
page.notification:error(event.message)
elseif event.type == 'form_complete' then
local values = self.form.values
if #values.run > 0 and #values.title > 0 and #values.category > 0 then
UI:setPreviousPage()
self:updateApplications(values, self.original)
page:refresh()
page:draw()
else
self.notification:error('Require fields missing')
--self.statusBar:setStatus('Require fields missing')
--self.statusBar:draw()
end
UI:setPreviousPage()
self:updateApplications(values)
page:refresh()
page:draw()
else
return UI.Page.eventHandler(self, event)
end

View File

@ -1,4 +1,5 @@
require = requireInjector(getfenv(1))
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')

View File

@ -8,7 +8,7 @@ local GROUPS_PATH = '/apps/groups'
local SCRIPTS_PATH = '/apps/scripts'
multishell.setTitle(multishell.getCurrent(), 'Script')
UI:configure('Script', ...)
UI:configure('script', ...)
local config = {
showGroups = false,
@ -17,7 +17,7 @@ local config = {
}]],
}
Config.load('Script', config)
Config.load('script', config)
local width = math.floor(UI.term.width / 2) - 1
if UI.term.width % 2 ~= 0 then
@ -453,7 +453,7 @@ function mainPage:eventHandler(event)
-- self.statusBar.toggleButton.text = text
self:draw()
Config.update('Script', config)
Config.update('script', config)
elseif event.type == 'grid_focus_row' then
local computer = self.computers:getSelected()

View File

@ -75,7 +75,7 @@ local systemPage = UI.Page {
value = 'Label'
},
label = UI.TextEntry {
x = 9, y = 2, rex = -12,
x = 9, y = 2, rex = -4,
limit = 32,
value = os.getComputerLabel(),
backgroundFocusColor = colors.black,

View File

@ -62,11 +62,6 @@ function page.grid:getDisplayValues(row)
return row
end
function page.grid:draw()
self:adjustWidth()
UI.Grid.draw(self)
end
Event.addTimer(1, true, function()
page.grid:update()
page.grid:draw()

View File

@ -33,6 +33,12 @@ local clipboard = { size, internal }
local searchPattern
local undo = { chain = { }, pointer = 0 }
if _G.__CLIPBOARD then
clipboard = _G.__CLIPBOARD
else
_G.__CLIPBOARD = clipboard
end
local color = {
textColor = '0',
keywordColor = '4',
@ -66,7 +72,7 @@ local keyMapping = {
pageUp = 'pageUp',
[ 'control-b' ] = 'pageUp',
pageDown = 'pageDown',
[ 'control-f' ] = 'pageDown',
-- [ 'control-f' ] = 'pageDown',
home = 'home',
[ 'end' ] = 'toend',
[ 'control-home' ] = 'top',
@ -101,6 +107,7 @@ local keyMapping = {
paste = 'paste',
tab = 'tab',
[ 'control-z' ] = 'undo',
[ 'control-space' ] = 'autocomplete',
-- copy/paste
[ 'control-x' ] = 'cut',
@ -114,6 +121,7 @@ local keyMapping = {
[ 'control-enter' ] = 'run',
-- search
[ 'control-f' ] = 'find_prompt',
[ 'control-slash' ] = 'find_prompt',
[ 'control-n' ] = 'find_next',
@ -476,6 +484,41 @@ local __actions = {
end
end,
autocomplete = function()
local sLine = tLines[y]:sub(1, x - 1)
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)
if #results == 0 then
setError('No completions available')
elseif #results == 1 then
actions.insertText(x, y, results[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
actions.insertText(x, y, prefix)
else
setStatus('Too many results')
end
end
end
end,
refresh = function()
actions.dirty_all()
mark.continue = mark.active
@ -528,7 +571,7 @@ local __actions = {
find_prompt = function()
local text = actions.input('/')
if #text > 0 then
searchPattern = text
searchPattern = text:lower()
if searchPattern then
actions.unmark()
actions.find(searchPattern, x)

View File

@ -14,12 +14,17 @@ local version = "Version 1.1.6"
-- Original code by Bomb Bloke
-- Modified to integrate with opus os
local calls, recTerm, oldTerm, arg, showInput, skipLast, lastDelay, curInput, callCount, callListCount = {{["delay"] = 0}}, {}, Util.shallowCopy(multishell.term), {...}, false, false, 2, "", 1, 2
local curBlink, oldBlink, curCalls, tTerm, buffer, colourNum, xPos, yPos, oldXPos, oldYPos, tCol, bCol, xSize, ySize = false, false, calls[1], {}, {}, {}, 1, 1, 1, 1, colours.white, colours.black, term.getSize()
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')
@ -123,13 +128,14 @@ recTerm = multishell.term
for key, func in pairs(oldTerm) do
recTerm[key] = function(...)
local result = {pcall(func, ...)}
local result = { func(...) }
if result[1] then
curCalls[callCount] = {key, ...}
callCount = callCount + 1
return unpack(result, 2)
else error(result[2], 2) end
if callCount == 0 then
os.queueEvent('capture_frame')
end
callCount = callCount + 1
curCalls[callCount] = { key, ... }
return unpack(result)
end
end
@ -149,36 +155,27 @@ for _,tab in pairs(tabs) do
end
end
do
local curTime = os.clock() - 1
local curTime = os.clock() - 1
while true do
local event = { os.pullEventRaw() }
while true do
local event = { os.pullEventRaw() }
if event[1] == 'recorder_stop' or event[1] == 'terminate' then
break
end
if event[1] == 'recorder_stop' or event[1] == 'terminate' then
break
end
if event[1] == 'capture_frame' then
local newTime = os.clock()
if newTime ~= curTime then
local delay = curCalls.delay + (newTime - curTime)
curTime = newTime
if callCount > 1 then
curCalls.delay = curCalls.delay + delay
curCalls, callCount = {["delay"] = 0}, 1
calls[callListCount] = curCalls
callListCount = callListCount + 1
elseif callListCount > 2 then
calls[callListCount - 2].delay = calls[callListCount - 2].delay + delay
end
if callListCount > 0 then
calls[callListCount].delay = (newTime - curTime)
end
if showInput and (event[1] == "key" or event[1] == "mouse_click") then
curCalls[callCount] = {unpack(event)}
callCount = callCount + 1
end
curTime = newTime
callListCount = callListCount + 1
calls[callListCount] = curCalls
curCalls, callCount = { delay = 0 }, 0
end
end
@ -196,8 +193,6 @@ if skipLast and #calls > 1 then calls[#calls] = nil end
calls[#calls].delay = lastDelay
-- Recording done, bug user as to whether to encode it:
print(string.format("Encoding %d frames...", #calls))
--Util.writeTable('tmp/raw.txt', calls)
@ -463,7 +458,14 @@ for i = 1, #calls do
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}
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 = {}
@ -515,7 +517,11 @@ for i = 1, #calls do
snooze()
end
if changed then image[#image + 1] = thisFrame else image[#image].delay = image[#image].delay + curCalls.delay end
if changed then
image[#image + 1] = thisFrame
else
image[#image].delay = image[#image].delay + curCalls.delay
end
end
buffer = nil

View File

@ -212,7 +212,7 @@ function enderChestUnload()
turtle.select(1)
turtle.drop(64)
turtle.digDown()
turtle.digDown()
end
end
@ -246,8 +246,9 @@ 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 block.name ~= 'minecraft:cobblestone' then
if block.name ~= 'minecraft:chest' then
if r and not turtle.isTurtleAtSide('top') then
if block.name ~= 'minecraft:cobblestone' and
block.name ~= 'minecraft:chest' then
turtle.digUp()
end
end
@ -483,7 +484,10 @@ function boreCommand()
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

View File

@ -452,51 +452,54 @@ function watchResources(items)
return itemList
end
itemPage = UI.Page({
itemPage = UI.Page {
backgroundColor = colors.lightGray,
titleBar = UI.TitleBar({
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({
fields = {
{ label = 'Min', key = 'low', width = 7, display = UI.Form.D.entry,
help = 'Craft if below min' },
{ label = 'Max', key = 'limit', width = 7, display = UI.Form.D.entry,
validation = UI.Form.V.number, dataType = UI.Form.T.number,
help = 'Eject if above max' },
{ label = 'Autocraft', key = 'auto', width = 7, display = UI.Form.D.chooser,
nochoice = 'No',
choices = {
{ name = 'Yes', value = 'yes' },
{ name = 'No', value = 'no' },
},
help = 'Craft until out of ingredients' },
{ label = 'Ignore Dmg', key = 'ignore_dmg', width = 7, display = UI.Form.D.chooser,
nochoice = 'No',
choices = {
{ name = 'Yes', value = 'yes' },
{ name = 'No', value = 'no' },
},
help = 'Ignore damage of item' },
{ text = 'Accept', event = 'accept', display = UI.Form.D.button,
x = 1, y = 6, width = 10 },
{ text = 'Cancel', event = 'cancel', display = UI.Form.D.button,
x = 21, y = 6, width = 10 },
},
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'
},
labelWidth = 10,
x = 5,
y = 5,
height = 6
}),
statusBar = UI.StatusBar()
})
[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)
@ -504,12 +507,14 @@ function itemPage:enable()
end
function itemPage:eventHandler(event)
if event.type == 'cancel' then
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 == 'accept' then
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
@ -527,55 +532,52 @@ function itemPage:eventHandler(event)
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({
listingPage = UI.Page {
menuBar = UI.MenuBar {
buttons = {
{ text = 'Learn', event = 'learn' },
{ text = 'Forget', event = 'forget' },
},
}),
grid = UI.Grid({
},
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 },
},
y = 2,
sortColumn = 'name',
height = UI.term.height-2,
}),
statusBar = UI.StatusBar({
},
statusBar = UI.StatusBar {
backgroundColor = colors.gray,
width = UI.term.width,
filterText = UI.Text({
filterText = UI.Text {
x = 2, width = 6,
value = 'Filter',
x = 2,
width = 6,
}),
filter = UI.TextEntry({
width = 19,
},
filter = UI.TextEntry {
x = 9, width = 19,
limit = 50,
x = 9,
}),
refresh = UI.Button({
},
refresh = UI.Button {
x = 31, width = 8,
text = 'Refresh',
event = 'refresh',
x = 31,
width = 8
}),
}),
},
},
accelerators = {
r = 'refresh',
q = 'quit',
}
})
}
function listingPage.grid:getRowTextColor(row, selected)
if row.is_craftable then
@ -622,24 +624,26 @@ 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:getPage('craft').form:setValues( { ignore_dmg = 'no' } )
UI:setPage('craft')
end
elseif event.type == 'forget' then
elseif event.type == 'forget' then
local item = self.grid:getSelected()
if item then
local recipes = Util.readTable('recipes') or { }
@ -673,6 +677,7 @@ function listingPage:eventHandler(event)
self:applyFilter()
self.grid:draw()
self.statusBar.filter:focus()
else
UI.Page.eventHandler(self, event)
end
@ -747,7 +752,7 @@ local function filter(t, filter)
end
end
local function learnRecipe(page, ignore_dmg)
local function learnRecipe(page)
local t = Util.readTable('recipes') or { }
local recipe = { }
local ingredients = duckAntenna.getAllStacks(false) -- getTurtleInventory()
@ -773,7 +778,7 @@ local function learnRecipe(page, ignore_dmg)
end
end
recipe.ingredients = ingredients
recipe.ignore_dmg = 'no' -- ignore_dmg
recipe.ignore_dmg = 'no'
t[key] = recipe
@ -794,69 +799,52 @@ local function learnRecipe(page, ignore_dmg)
end
end
craftPage = UI.Page({
x = 4,
y = math.floor((UI.term.height - 8) / 2) + 1,
height = 7,
width = UI.term.width - 6,
craftPage = UI.Dialog {
height = 7, width = UI.term.width - 6,
backgroundColor = colors.lightGray,
titleBar = UI.TitleBar({
titleBar = UI.TitleBar {
title = 'Learn Recipe',
previousPage = true,
}),
idField = UI.Text({
},
idField = UI.Text {
x = 5,
y = 3,
width = UI.term.width - 10,
value = 'Place recipe in turtle'
}),
form = UI.Form({
fields = {
--[[
{ label = 'Ignore Damage', key = 'ignore_dmg', width = 7, display = UI.Form.D.chooser,
nochoice = 'No',
choices = {
{ name = 'Yes', value = 'yes' },
{ name = 'No', value = 'no' },
},
help = 'Ignore damage of ingredients' },
--]]
{ text = 'Accept', event = 'accept', display = UI.Form.D.button,
x = 1, y = 1, width = 10 },
{ text = 'Cancel', event = 'cancel', display = UI.Form.D.button,
x = 16, y = 1, width = 10 },
},
labelWidth = 13,
x = 5,
y = 5,
height = 2
}),
statusBar = UI.StatusBar({
},
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.Page.enable(self)
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
local values = self.form.values
if learnRecipe(self, values.ignore_dmg) then
if learnRecipe(self) then
UI:setPreviousPage()
end
else
return UI.Page.eventHandler(self, event)
return UI.Dialog.eventHandler(self, event)
end
return true
end

View File

@ -36,6 +36,7 @@ if turtle and device.wireless_modem then
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')

View File

@ -1,4 +1,5 @@
local Util = require('util')
local Process = require('process')
local Event = {
uid = 1, -- unique id for handlers
@ -120,13 +121,9 @@ end
local exitPullEvents = false
local function _pullEvents()
--exitPullEvents = false
while true do
local e = Event.pullEvent()
if exitPullEvents or e == 'terminate' then
break
end
local e = { os.pullEvent() }
Event.processEvent(e)
end
end
@ -137,12 +134,23 @@ function Event.sleep(t)
until event == 'timer' and id == timerId
end
function Event.addThread(fn)
return Process:addThread(fn)
end
function Event.pullEvents(...)
Process:addThread(_pullEvents)
local routines = { ... }
if #routines > 0 then
parallel.waitForAny(_pullEvents, ...)
else
_pullEvents()
for _, routine in ipairs(routines) do
Process:addThread(routine)
end
end
while true do
local e = Process:pullEvent()
if exitPullEvents or e == 'terminate' then
break
end
end
end

View File

@ -1,6 +1,6 @@
local UI = require('ui')
return function()
return function(args)
local columns = {
{ heading = 'Name', key = 'name', width = UI.term.width - 9 },
@ -13,18 +13,19 @@ return function()
)
end
local selectFile = UI.Page({
x = 3,
y = 2,
rex = -3,
rey = -3,
args = args or { }
local selectFile = UI.Dialog {
x = args.x or 3,
y = args.y or 2,
z = args.z or 2,
-- rex = args.rex or -3,
-- rey = args.rey or -3,
height = args.height,
width = args.width,
backgroundColor = colors.brown,
titleBar = UI.TitleBar({
title = 'Select file',
previousPage = true,
event = 'cancel',
}),
grid = UI.ScrollingGrid({
title = 'Select file',
grid = UI.ScrollingGrid {
x = 2,
y = 2,
rex = -2,
@ -32,8 +33,8 @@ return function()
path = '',
sortColumn = 'name',
columns = columns,
}),
path = UI.TextEntry({
},
path = UI.TextEntry {
x = 2,
ry = -1,
rex = -11,
@ -41,14 +42,14 @@ return function()
accelerators = {
enter = 'path_enter',
}
}),
cancel = UI.Button({
},
cancel = UI.Button {
text = 'Cancel',
rx = -8,
ry = -1,
event = 'cancel',
}),
})
},
}
function selectFile:enable(path, fn)
self:setPath(path)

View File

@ -46,13 +46,13 @@ function urlfs.open(node, fn, fl)
synchronized(node.url, function()
c = Util.download(node.url)
end)
if c and #c > 0 then
if c then
node.cache = c
node.size = #c
end
end
if not c or #c == 0 then
if not c then
return
end

View File

@ -1,68 +1,164 @@
local resolver, loader
local DEFAULT_UPATH = 'https://raw.githubusercontent.com/kepler155c/opus/master/sys/apis'
local PASTEBIN_URL = 'http://pastebin.com/raw'
local GIT_URL = 'https://raw.githubusercontent.com'
local function resolveFile(filename, dir, lua_path)
if filename:sub(1, 1) == "/" then
if not fs.exists(filename) then
error('Unable to load: ' .. filename, 2)
local function standardSearcher(modname, env, shell)
if package.loaded[modname] then
return function()
return package.loaded[modname]
end
return filename
end
end
if dir then
local path = fs.combine(dir, filename)
local function shellSearcher(modname, env, shell)
local fname = modname:gsub('%.', '/') .. '.lua'
if shell and type(shell.dir) == 'function' then
local path = shell.resolve(fname)
if fs.exists(path) and not fs.isDir(path) then
return path
return loadfile(path, env)
end
end
end
if lua_path then
for dir in string.gmatch(lua_path, "[^:]+") do
local path = fs.combine(dir, filename)
if fs.exists(path) and not fs.isDir(path) then
return path
local function pathSearcher(modname, env, shell)
local fname = modname:gsub('%.', '/') .. '.lua'
for dir in string.gmatch(package.path, "[^:]+") do
local path = fs.combine(dir, fname)
if fs.exists(path) and not fs.isDir(path) then
return loadfile(path, env)
end
end
end
-- fix broken http get
local syncLocks = { }
local function sync(obj, fn)
local key = tostring(obj)
if syncLocks[key] then
local cos = tostring(coroutine.running())
table.insert(syncLocks[key], cos)
repeat
local _, co = os.pullEvent('sync_lock')
until co == cos
else
syncLocks[key] = { }
end
local s, m = pcall(fn)
local co = table.remove(syncLocks[key], 1)
if co then
os.queueEvent('sync_lock', co)
else
syncLocks[key] = nil
end
if not s then
error(m)
end
end
local function loadUrl(url)
local c
sync(url, function()
local h = http.get(url)
if h then
c = h.readAll()
h.close()
end
end)
if c and #c > 0 then
return c
end
end
-- require('BniCQPVf')
local function pastebinSearcher(modname, env, shell)
if #modname == 8 and not modname:match('%W') then
local url = PASTEBIN_URL .. '/' .. modname
local c = loadUrl(url)
if c then
return load(c, modname, nil, env)
end
end
end
-- require('kepler155c.opus.master.sys.apis.util')
local function gitSearcher(modname, env, shell)
local fname = modname:gsub('%.', '/') .. '.lua'
local _, count = fname:gsub("/", "")
if count >= 3 then
local url = GIT_URL .. '/' .. fname
local c = loadUrl(url)
if c then
return load(c, modname, nil, env)
end
end
end
local function urlSearcher(modname, env, shell)
local fname = modname:gsub('%.', '/') .. '.lua'
if fname:sub(1, 1) ~= '/' then
for entry in string.gmatch(package.upath, "[^;]+") do
local url = entry .. '/' .. fname
local c = loadUrl(url)
if c then
return load(c, modname, nil, env)
end
end
end
error('Unable to load: ' .. filename, 2)
end
_G.package = {
path = LUA_PATH or 'sys/apis',
upath = LUA_UPATH or DEFAULT_UPATH,
config = '/\n:\n?\n!\n-',
loaded = {
math = math,
string = string,
table = table,
io = io,
os = os,
},
loaders = {
standardSearcher,
shellSearcher,
pathSearcher,
pastebinSearcher,
gitSearcher,
urlSearcher,
}
}
local function requireWrapper(env)
local modules = { }
local loaded = { }
return function(filename)
return function(modname)
local dir = DIR
if not dir and shell and type(shell.dir) == 'function' then
dir = shell.dir()
if loaded[modname] then
return loaded[modname]
end
local fname = resolver(filename:gsub('%.', '/') .. '.lua',
dir or '', LUA_PATH or '/sys/apis')
local rname = fname:gsub('%/', '.'):gsub('%.lua', '')
local module = modules[rname]
if not module then
local f, err = loader(fname, env)
if not f then
error(err)
end
module = f(rname)
modules[rname] = module
for _,searcher in ipairs(package.loaders) do
local fn, msg = searcher(modname, env, shell)
if fn then
local module, msg = fn(modname, env)
if not module then
error(msg)
end
loaded[modname] = module
return module
end
if msg then
error(msg, 2)
end
end
return module
error('Unable to find module ' .. modname)
end
end
local args = { ... }
resolver = args[1] or resolveFile
loader = args[2] or loadfile
return function(env)
setfenv(requireWrapper, env)
return requireWrapper(env)

View File

@ -27,6 +27,11 @@ function Process:threadEvent(...)
end
end
function Process:addThread(fn, ...)
return self:newThread(nil, fn, ...)
end
-- deprecated
function Process:newThread(name, fn, ...)
self.uid = self.uid + 1
@ -45,7 +50,7 @@ function Process:newThread(name, fn, ...)
local s, m = pcall(function() fn(unpack(args)) end)
if not s and m then
if m == 'Terminated' then
printError(thread.name .. ' terminated')
--printError(thread.name .. ' terminated')
else
printError(m)
end
@ -82,8 +87,11 @@ function Process:resume(event, ...)
return true, self.filter
end
function Process:pullEvent(filter)
-- confusing...
-- pull either one event if no filter or until event matches filter
-- or until terminated (regardless of filter)
function Process:pullEvent(filter)
while true do
local e = { os.pullEventRaw() }
self:threadEvent(unpack(e))
@ -94,12 +102,12 @@ function Process:pullEvent(filter)
end
end
-- pull events until either the filter is matched or terminated
function Process:pullEvents(filter)
while true do
local e = { os.pullEventRaw(filter) }
local e = { os.pullEventRaw() }
self:threadEvent(unpack(e))
if e[1] == 'terminate' then
if (filter and e[1] == filter) or e[1] == 'terminate' then
return unpack(e)
end
end

358
sys/apis/region.lua Normal file
View File

@ -0,0 +1,358 @@
-------------------------------------------------------------------------------
--
-- tek.lib.region
-- Written by Timm S. Mueller <tmueller at schulze-mueller.de>
--
-- Copyright 2008 - 2016 by the authors and contributors:
--
-- * Timm S. Muller <tmueller at schulze-mueller.de>
-- * Franciska Schulze <fschulze at schulze-mueller.de>
-- * Tobias Schwinger <tschwinger at isonews2.com>
--
-- Permission is hereby granted, free of charge, to any person obtaining
-- a copy of this software and associated documentation files (the
-- "Software"), to deal in the Software without restriction, including
-- without limitation the rights to use, copy, modify, merge, publish,
-- distribute, sublicense, and/or sell copies of the Software, and to
-- permit persons to whom the Software is furnished to do so, subject to
-- the following conditions:
--
-- The above copyright notice and this permission notice shall be
-- included in all copies or substantial portions of the Software.
--
-- === Disclaimer ===
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--
-- OVERVIEW::
-- This library implements the management of regions, which are
-- collections of non-overlapping rectangles.
--
-- FUNCTIONS::
-- - Region:andRect() - ''And''s a rectangle to a region
-- - Region:andRegion() - ''And''s a region to a region
-- - Region:checkIntersect() - Checks if a rectangle intersects a region
-- - Region:forEach() - Calls a function for each rectangle in a region
-- - Region:get() - Get region's min/max extents
-- - Region.intersect() - Returns the intersection of two rectangles
-- - Region:isEmpty() - Checks if a Region is empty
-- - Region.new() - Creates a new Region
-- - Region:orRect() - ''Or''s a rectangle to a region
-- - Region:orRegion() - ''Or''s a region to a region
-- - Region:setRect() - Resets a region to the given rectangle
-- - Region:shift() - Displaces a region
-- - Region:subRect() - Subtracts a rectangle from a region
-- - Region:subRegion() - Subtracts a region from a region
-- - Region:xorRect() - ''Exclusive Or''s a rectangle to a region
--
-------------------------------------------------------------------------------
local insert = table.insert
local ipairs = ipairs
local max = math.max
local min = math.min
local setmetatable = setmetatable
local unpack = unpack or table.unpack
local Region = { }
Region._VERSION = "Region 11.3"
Region.__index = Region
-------------------------------------------------------------------------------
-- x0, y0, x1, y1 = Region.intersect(d1, d2, d3, d4, s1, s2, s3, s4):
-- Returns the coordinates of a rectangle where a rectangle specified by
-- the coordinates s1, s2, s3, s4 overlaps with the rectangle specified
-- by the coordinates d1, d2, d3, d4. The return value is '''nil''' if
-- the rectangles do not overlap.
-------------------------------------------------------------------------------
function Region.intersect(d1, d2, d3, d4, s1, s2, s3, s4)
if s3 >= d1 and s1 <= d3 and s4 >= d2 and s2 <= d4 then
return max(s1, d1), max(s2, d2), min(s3, d3), min(s4, d4)
end
end
-------------------------------------------------------------------------------
-- insertrect: insert rect to table, merging with an existing one if possible
-------------------------------------------------------------------------------
local function insertrect(d, s1, s2, s3, s4)
for i = 1, min(4, #d) do
local a = d[i]
local a1, a2, a3, a4 = a[1], a[2], a[3], a[4]
if a2 == s2 and a4 == s4 then
if a3 + 1 == s1 then
a[3] = s3
return
elseif a1 == s3 + 1 then
a[1] = s1
return
end
elseif a1 == s1 and a3 == s3 then
if a4 + 1 == s2 then
a[4] = s4
return
elseif a2 == s4 + 1 then
a[2] = s2
return
end
end
end
insert(d, 1, { s1, s2, s3, s4 })
end
-------------------------------------------------------------------------------
-- cutrect: cut rect d into table of new rects, using rect s as a punch
-------------------------------------------------------------------------------
local function cutrect(d1, d2, d3, d4, s1, s2, s3, s4)
if not Region.intersect(d1, d2, d3, d4, s1, s2, s3, s4) then
return { { d1, d2, d3, d4 } }
end
local r = { }
if d1 < s1 then
insertrect(r, d1, d2, s1 - 1, d4)
d1 = s1
end
if d2 < s2 then
insertrect(r, d1, d2, d3, s2 - 1)
d2 = s2
end
if d3 > s3 then
insertrect(r, s3 + 1, d2, d3, d4)
d3 = s3
end
if d4 > s4 then
insertrect(r, d1, s4 + 1, d3, d4)
end
return r
end
-------------------------------------------------------------------------------
-- cutregion: cut region d, using s as a punch
-------------------------------------------------------------------------------
local function cutregion(d, s1, s2, s3, s4)
local r = { }
for _, dr in ipairs(d) do
local d1, d2, d3, d4 = dr[1], dr[2], dr[3], dr[4]
for _, t in ipairs(cutrect(d1, d2, d3, d4, s1, s2, s3, s4)) do
insertrect(r, t[1], t[2], t[3], t[4])
end
end
return r
end
-------------------------------------------------------------------------------
-- region = Region.new(r1, r2, r3, r4): Creates a new region from the given
-- coordinates.
-------------------------------------------------------------------------------
function Region.new(r1, r2, r3, r4)
if r1 then
return setmetatable({ region = { { r1, r2, r3, r4 } } }, Region)
end
return setmetatable({ region = { } }, Region)
end
-------------------------------------------------------------------------------
-- self = region:setRect(r1, r2, r3, r4): Resets an existing region
-- to the specified rectangle.
-------------------------------------------------------------------------------
function Region:setRect(r1, r2, r3, r4)
self.region = { { r1, r2, r3, r4 } }
return self
end
-------------------------------------------------------------------------------
-- region:orRect(r1, r2, r3, r4): Logical ''or''s a rectangle to a region
-------------------------------------------------------------------------------
function Region:orRect(s1, s2, s3, s4)
self.region = cutregion(self.region, s1, s2, s3, s4)
insertrect(self.region, s1, s2, s3, s4)
end
-------------------------------------------------------------------------------
-- region:orRegion(region): Logical ''or''s another region to a region
-------------------------------------------------------------------------------
function Region:orRegion(s)
for _, r in ipairs(s) do
self:orRect(r[1], r[2], r[3], r[4])
end
end
-------------------------------------------------------------------------------
-- region:andRect(r1, r2, r3, r4): Logical ''and''s a rectange to a region
-------------------------------------------------------------------------------
function Region:andRect(s1, s2, s3, s4)
local r = { }
for _, d in ipairs(self.region) do
local t1, t2, t3, t4 =
Region.intersect(d[1], d[2], d[3], d[4], s1, s2, s3, s4)
if t1 then
insertrect(r, t1, t2, t3, t4)
end
end
self.region = r
end
-------------------------------------------------------------------------------
-- region:xorRect(r1, r2, r3, r4): Logical ''xor''s a rectange to a region
-------------------------------------------------------------------------------
function Region:xorRect(s1, s2, s3, s4)
local r1 = { }
local r2 = { { s1, s2, s3, s4 } }
for _, d in ipairs(self.region) do
local d1, d2, d3, d4 = d[1], d[2], d[3], d[4]
for _, t in ipairs(cutrect(d1, d2, d3, d4, s1, s2, s3, s4)) do
insertrect(r1, t[1], t[2], t[3], t[4])
end
r2 = cutregion(r2, d1, d2, d3, d4)
end
self.region = r1
self:orRegion(r2)
end
-------------------------------------------------------------------------------
-- self = region:subRect(r1, r2, r3, r4): Subtracts a rectangle from a region
-------------------------------------------------------------------------------
function Region:subRect(s1, s2, s3, s4)
local r1 = { }
for _, d in ipairs(self.region) do
local d1, d2, d3, d4 = d[1], d[2], d[3], d[4]
for _, t in ipairs(cutrect(d1, d2, d3, d4, s1, s2, s3, s4)) do
insertrect(r1, t[1], t[2], t[3], t[4])
end
end
self.region = r1
return self
end
-------------------------------------------------------------------------------
-- region:getRect - gets an iterator on the rectangles in a region [internal]
-------------------------------------------------------------------------------
function Region:getRects()
local index = 0
return function(object)
index = index + 1
if object[index] then
return unpack(object[index])
end
end, self.region
end
-------------------------------------------------------------------------------
-- success = region:checkIntersect(x0, y0, x1, y1): Returns a boolean
-- indicating whether a rectangle specified by its coordinates overlaps
-- with a region.
-------------------------------------------------------------------------------
function Region:checkIntersect(s1, s2, s3, s4)
for _, d in ipairs(self.region) do
if Region.intersect(d[1], d[2], d[3], d[4], s1, s2, s3, s4) then
return true
end
end
return false
end
-------------------------------------------------------------------------------
-- region:subRegion(region2): Subtracts {{region2}} from {{region}}.
-------------------------------------------------------------------------------
function Region:subRegion(region)
if region then
for r1, r2, r3, r4 in region:getRects() do
self:subRect(r1, r2, r3, r4)
end
end
end
-------------------------------------------------------------------------------
-- region:andRegion(r): Logically ''and''s a region to a region
-------------------------------------------------------------------------------
function Region:andRegion(s)
local r = { }
for _, s in ipairs(s.region) do
for _, d in ipairs(self.region) do
local t1, t2, t3, t4 =
Region.intersect(d[1], d[2], d[3], d[4],
s[1], s[2], s[3], s[4])
if t1 then
insertrect(r, t1, t2, t3, t4)
end
end
end
self.region = r
end
-------------------------------------------------------------------------------
-- region:forEach(func, obj, ...): For each rectangle in a region, calls the
-- specified function according the following scheme:
-- func(obj, x0, y0, x1, y1, ...)
-- Extra arguments are passed through to the function.
-------------------------------------------------------------------------------
function Region:forEach(func, obj, ...)
for x0, y0, x1, y1 in self:getRects() do
func(obj, x0, y0, x1, y1, ...)
end
end
-------------------------------------------------------------------------------
-- region:shift(dx, dy): Shifts a region by delta x and y.
-------------------------------------------------------------------------------
function Region:shift(dx, dy)
for _, r in ipairs(self.region) do
r[1] = r[1] + dx
r[2] = r[2] + dy
r[3] = r[3] + dx
r[4] = r[4] + dy
end
end
-------------------------------------------------------------------------------
-- region:isEmpty(): Returns '''true''' if a region is empty.
-------------------------------------------------------------------------------
function Region:isEmpty()
return #self.region == 0
end
-------------------------------------------------------------------------------
-- minx, miny, maxx, maxy = region:get(): Get region's min/max extents
-------------------------------------------------------------------------------
function Region:get()
if #self.region > 0 then
local minx = 1000000 -- ui.HUGE
local miny = 1000000
local maxx = 0
local maxy = 0
for _, r in ipairs(self.region) do
minx = min(minx, r[1])
miny = min(miny, r[2])
maxx = max(maxx, r[3])
maxy = max(maxy, r[4])
end
return minx, miny, maxx, maxy
end
end
return Region

View File

@ -120,6 +120,18 @@ function Terminal.toGrayscale(ct)
end
end
function Terminal.getNullTerm(ct)
local nt = Terminal.copy(ct)
local methods = { 'blit', 'clear', 'clearLine', 'scroll',
'setCursorBlink', 'setCursorPos', 'write' }
for _,v in pairs(methods) do
nt[v] = function() end
end
return nt
end
function Terminal.copy(ot)
local ct = { }
for k,v in pairs(ot) do

367
sys/apis/tween.lua Normal file
View File

@ -0,0 +1,367 @@
local tween = {
_VERSION = 'tween 2.1.1',
_DESCRIPTION = 'tweening for lua',
_URL = 'https://github.com/kikito/tween.lua',
_LICENSE = [[
MIT LICENSE
Copyright (c) 2014 Enrique García Cota, Yuichi Tateno, Emmanuel Oga
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
}
-- easing
-- Adapted from https://github.com/EmmanuelOga/easing. See LICENSE.txt for credits.
-- For all easing functions:
-- t = time == how much time has to pass for the tweening to complete
-- b = begin == starting property value
-- c = change == ending - beginning
-- d = duration == running time. How much time has passed *right now*
local pow, sin, cos, pi, sqrt, abs, asin = math.pow, math.sin, math.cos, math.pi, math.sqrt, math.abs, math.asin
-- linear
local function linear(t, b, c, d) return c * t / d + b end
-- quad
local function inQuad(t, b, c, d) return c * pow(t / d, 2) + b end
local function outQuad(t, b, c, d)
t = t / d
return -c * t * (t - 2) + b
end
local function inOutQuad(t, b, c, d)
t = t / d * 2
if t < 1 then return c / 2 * pow(t, 2) + b end
return -c / 2 * ((t - 1) * (t - 3) - 1) + b
end
local function outInQuad(t, b, c, d)
if t < d / 2 then return outQuad(t * 2, b, c / 2, d) end
return inQuad((t * 2) - d, b + c / 2, c / 2, d)
end
-- cubic
local function inCubic (t, b, c, d) return c * pow(t / d, 3) + b end
local function outCubic(t, b, c, d) return c * (pow(t / d - 1, 3) + 1) + b end
local function inOutCubic(t, b, c, d)
t = t / d * 2
if t < 1 then return c / 2 * t * t * t + b end
t = t - 2
return c / 2 * (t * t * t + 2) + b
end
local function outInCubic(t, b, c, d)
if t < d / 2 then return outCubic(t * 2, b, c / 2, d) end
return inCubic((t * 2) - d, b + c / 2, c / 2, d)
end
-- quart
local function inQuart(t, b, c, d) return c * pow(t / d, 4) + b end
local function outQuart(t, b, c, d) return -c * (pow(t / d - 1, 4) - 1) + b end
local function inOutQuart(t, b, c, d)
t = t / d * 2
if t < 1 then return c / 2 * pow(t, 4) + b end
return -c / 2 * (pow(t - 2, 4) - 2) + b
end
local function outInQuart(t, b, c, d)
if t < d / 2 then return outQuart(t * 2, b, c / 2, d) end
return inQuart((t * 2) - d, b + c / 2, c / 2, d)
end
-- quint
local function inQuint(t, b, c, d) return c * pow(t / d, 5) + b end
local function outQuint(t, b, c, d) return c * (pow(t / d - 1, 5) + 1) + b end
local function inOutQuint(t, b, c, d)
t = t / d * 2
if t < 1 then return c / 2 * pow(t, 5) + b end
return c / 2 * (pow(t - 2, 5) + 2) + b
end
local function outInQuint(t, b, c, d)
if t < d / 2 then return outQuint(t * 2, b, c / 2, d) end
return inQuint((t * 2) - d, b + c / 2, c / 2, d)
end
-- sine
local function inSine(t, b, c, d) return -c * cos(t / d * (pi / 2)) + c + b end
local function outSine(t, b, c, d) return c * sin(t / d * (pi / 2)) + b end
local function inOutSine(t, b, c, d) return -c / 2 * (cos(pi * t / d) - 1) + b end
local function outInSine(t, b, c, d)
if t < d / 2 then return outSine(t * 2, b, c / 2, d) end
return inSine((t * 2) -d, b + c / 2, c / 2, d)
end
-- expo
local function inExpo(t, b, c, d)
if t == 0 then return b end
return c * pow(2, 10 * (t / d - 1)) + b - c * 0.001
end
local function outExpo(t, b, c, d)
if t == d then return b + c end
return c * 1.001 * (-pow(2, -10 * t / d) + 1) + b
end
local function inOutExpo(t, b, c, d)
if t == 0 then return b end
if t == d then return b + c end
t = t / d * 2
if t < 1 then return c / 2 * pow(2, 10 * (t - 1)) + b - c * 0.0005 end
return c / 2 * 1.0005 * (-pow(2, -10 * (t - 1)) + 2) + b
end
local function outInExpo(t, b, c, d)
if t < d / 2 then return outExpo(t * 2, b, c / 2, d) end
return inExpo((t * 2) - d, b + c / 2, c / 2, d)
end
-- circ
local function inCirc(t, b, c, d) return(-c * (sqrt(1 - pow(t / d, 2)) - 1) + b) end
local function outCirc(t, b, c, d) return(c * sqrt(1 - pow(t / d - 1, 2)) + b) end
local function inOutCirc(t, b, c, d)
t = t / d * 2
if t < 1 then return -c / 2 * (sqrt(1 - t * t) - 1) + b end
t = t - 2
return c / 2 * (sqrt(1 - t * t) + 1) + b
end
local function outInCirc(t, b, c, d)
if t < d / 2 then return outCirc(t * 2, b, c / 2, d) end
return inCirc((t * 2) - d, b + c / 2, c / 2, d)
end
-- elastic
local function calculatePAS(p,a,c,d)
p, a = p or d * 0.3, a or 0
if a < abs(c) then return p, c, p / 4 end -- p, a, s
return p, a, p / (2 * pi) * asin(c/a) -- p,a,s
end
local function inElastic(t, b, c, d, a, p)
local s
if t == 0 then return b end
t = t / d
if t == 1 then return b + c end
p,a,s = calculatePAS(p,a,c,d)
t = t - 1
return -(a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b
end
local function outElastic(t, b, c, d, a, p)
local s
if t == 0 then return b end
t = t / d
if t == 1 then return b + c end
p,a,s = calculatePAS(p,a,c,d)
return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p) + c + b
end
local function inOutElastic(t, b, c, d, a, p)
local s
if t == 0 then return b end
t = t / d * 2
if t == 2 then return b + c end
p,a,s = calculatePAS(p,a,c,d)
t = t - 1
if t < 0 then return -0.5 * (a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b end
return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p ) * 0.5 + c + b
end
local function outInElastic(t, b, c, d, a, p)
if t < d / 2 then return outElastic(t * 2, b, c / 2, d, a, p) end
return inElastic((t * 2) - d, b + c / 2, c / 2, d, a, p)
end
-- back
local function inBack(t, b, c, d, s)
s = s or 1.70158
t = t / d
return c * t * t * ((s + 1) * t - s) + b
end
local function outBack(t, b, c, d, s)
s = s or 1.70158
t = t / d - 1
return c * (t * t * ((s + 1) * t + s) + 1) + b
end
local function inOutBack(t, b, c, d, s)
s = (s or 1.70158) * 1.525
t = t / d * 2
if t < 1 then return c / 2 * (t * t * ((s + 1) * t - s)) + b end
t = t - 2
return c / 2 * (t * t * ((s + 1) * t + s) + 2) + b
end
local function outInBack(t, b, c, d, s)
if t < d / 2 then return outBack(t * 2, b, c / 2, d, s) end
return inBack((t * 2) - d, b + c / 2, c / 2, d, s)
end
-- bounce
local function outBounce(t, b, c, d)
t = t / d
if t < 1 / 2.75 then return c * (7.5625 * t * t) + b end
if t < 2 / 2.75 then
t = t - (1.5 / 2.75)
return c * (7.5625 * t * t + 0.75) + b
elseif t < 2.5 / 2.75 then
t = t - (2.25 / 2.75)
return c * (7.5625 * t * t + 0.9375) + b
end
t = t - (2.625 / 2.75)
return c * (7.5625 * t * t + 0.984375) + b
end
local function inBounce(t, b, c, d) return c - outBounce(d - t, 0, c, d) + b end
local function inOutBounce(t, b, c, d)
if t < d / 2 then return inBounce(t * 2, 0, c, d) * 0.5 + b end
return outBounce(t * 2 - d, 0, c, d) * 0.5 + c * .5 + b
end
local function outInBounce(t, b, c, d)
if t < d / 2 then return outBounce(t * 2, b, c / 2, d) end
return inBounce((t * 2) - d, b + c / 2, c / 2, d)
end
tween.easing = {
linear = linear,
inQuad = inQuad, outQuad = outQuad, inOutQuad = inOutQuad, outInQuad = outInQuad,
inCubic = inCubic, outCubic = outCubic, inOutCubic = inOutCubic, outInCubic = outInCubic,
inQuart = inQuart, outQuart = outQuart, inOutQuart = inOutQuart, outInQuart = outInQuart,
inQuint = inQuint, outQuint = outQuint, inOutQuint = inOutQuint, outInQuint = outInQuint,
inSine = inSine, outSine = outSine, inOutSine = inOutSine, outInSine = outInSine,
inExpo = inExpo, outExpo = outExpo, inOutExpo = inOutExpo, outInExpo = outInExpo,
inCirc = inCirc, outCirc = outCirc, inOutCirc = inOutCirc, outInCirc = outInCirc,
inElastic = inElastic, outElastic = outElastic, inOutElastic = inOutElastic, outInElastic = outInElastic,
inBack = inBack, outBack = outBack, inOutBack = inOutBack, outInBack = outInBack,
inBounce = inBounce, outBounce = outBounce, inOutBounce = inOutBounce, outInBounce = outInBounce
}
-- private stuff
local function copyTables(destination, keysTable, valuesTable)
valuesTable = valuesTable or keysTable
local mt = getmetatable(keysTable)
if mt and getmetatable(destination) == nil then
setmetatable(destination, mt)
end
for k,v in pairs(keysTable) do
if type(v) == 'table' then
destination[k] = copyTables({}, v, valuesTable[k])
else
destination[k] = valuesTable[k]
end
end
return destination
end
local function checkSubjectAndTargetRecursively(subject, target, path)
path = path or {}
local targetType, newPath
for k,targetValue in pairs(target) do
targetType, newPath = type(targetValue), copyTables({}, path)
table.insert(newPath, tostring(k))
if targetType == 'number' then
assert(type(subject[k]) == 'number', "Parameter '" .. table.concat(newPath,'/') .. "' is missing from subject or isn't a number")
elseif targetType == 'table' then
checkSubjectAndTargetRecursively(subject[k], targetValue, newPath)
else
assert(targetType == 'number', "Parameter '" .. table.concat(newPath,'/') .. "' must be a number or table of numbers")
end
end
end
local function checkNewParams(duration, subject, target, easing)
assert(type(duration) == 'number' and duration > 0, "duration must be a positive number. Was " .. tostring(duration))
local tsubject = type(subject)
assert(tsubject == 'table' or tsubject == 'userdata', "subject must be a table or userdata. Was " .. tostring(subject))
assert(type(target)== 'table', "target must be a table. Was " .. tostring(target))
assert(type(easing)=='function', "easing must be a function. Was " .. tostring(easing))
checkSubjectAndTargetRecursively(subject, target)
end
local function getEasingFunction(easing)
easing = easing or "linear"
if type(easing) == 'string' then
local name = easing
easing = tween.easing[name]
if type(easing) ~= 'function' then
error("The easing function name '" .. name .. "' is invalid")
end
end
return easing
end
local function performEasingOnSubject(subject, target, initial, clock, duration, easing)
local t,b,c,d
for k,v in pairs(target) do
if type(v) == 'table' then
performEasingOnSubject(subject[k], v, initial[k], clock, duration, easing)
else
t,b,c,d = clock, initial[k], v - initial[k], duration
subject[k] = easing(t,b,c,d)
end
end
end
-- Tween methods
local Tween = {}
local Tween_mt = {__index = Tween}
function Tween:set(clock)
assert(type(clock) == 'number', "clock must be a positive number or 0")
self.initial = self.initial or copyTables({}, self.target, self.subject)
self.clock = clock
if self.clock <= 0 then
self.clock = 0
copyTables(self.subject, self.initial)
elseif self.clock >= self.duration then -- the tween has expired
self.clock = self.duration
copyTables(self.subject, self.target)
else
performEasingOnSubject(self.subject, self.target, self.initial, self.clock, self.duration, self.easing)
end
return self.clock >= self.duration
end
function Tween:reset()
return self:set(0)
end
function Tween:update(dt)
assert(type(dt) == 'number', "dt must be a number")
return self:set(self.clock + dt)
end
-- Public interface
function tween.new(duration, subject, target, easing)
easing = getEasingFunction(easing)
checkNewParams(duration, subject, target, easing)
return setmetatable({
duration = duration,
subject = subject,
target = target,
easing = easing,
clock = 0
}, Tween_mt)
end
return tween

File diff suppressed because it is too large Load Diff

View File

@ -365,6 +365,10 @@ function Util.toBytes(n)
return tostring(n)
end
function Util.insertString(os, is, pos)
return os:sub(1, pos - 1) .. is .. os:sub(pos)
end
function Util.split(str, pattern)
pattern = pattern or "(.-)\n"
local t = {}
@ -457,28 +461,30 @@ end
local function getopt( arg, options )
local tab = {}
for k, v in ipairs(arg) do
if string.sub( v, 1, 2) == "--" then
local x = string.find( v, "=", 1, true )
if x then tab[ string.sub( v, 3, x-1 ) ] = string.sub( v, x+1 )
else tab[ string.sub( v, 3 ) ] = true
end
elseif string.sub( v, 1, 1 ) == "-" then
local y = 2
local l = string.len(v)
local jopt
while ( y <= l ) do
jopt = string.sub( v, y, y )
if string.find( options, jopt, 1, true ) then
if y < l then
tab[ jopt ] = string.sub( v, y+1 )
y = l
else
tab[ jopt ] = arg[ k + 1 ]
end
else
tab[ jopt ] = true
if type(v) == 'string' then
if string.sub( v, 1, 2) == "--" then
local x = string.find( v, "=", 1, true )
if x then tab[ string.sub( v, 3, x-1 ) ] = string.sub( v, x+1 )
else tab[ string.sub( v, 3 ) ] = true
end
elseif string.sub( v, 1, 1 ) == "-" then
local y = 2
local l = string.len(v)
local jopt
while ( y <= l ) do
jopt = string.sub( v, y, y )
if string.find( options, jopt, 1, true ) then
if y < l then
tab[ jopt ] = string.sub( v, y+1 )
y = l
else
tab[ jopt ] = arg[ k + 1 ]
end
else
tab[ jopt ] = true
end
y = y + 1
end
y = y + 1
end
end
end

View File

@ -4,8 +4,8 @@ LUA_PATH = '/sys/apis'
math.randomseed(os.clock())
_G.debug = function() end
_G.Util = dofile('/sys/apis/util.lua')
_G.debug = function(...) Util.print(...) end
_G.requireInjector = dofile('/sys/apis/injector.lua')
os.run(Util.shallowCopy(getfenv(1)), '/sys/extensions/device.lua')
@ -21,6 +21,7 @@ local mounts = Util.readFile('config/fstab')
if mounts then
for _,l in ipairs(Util.split(mounts)) do
if l:sub(1, 1) ~= '#' then
print('mounting ' .. l)
fs.mount(unpack(Util.matches(l)))
end
end

View File

@ -57,10 +57,12 @@ function turtle.run(fn, ...)
local e, id, abort = os.pullEventRaw('turtle_ticket')
if e == 'terminate' then
releaseTicket(ticketId)
os.queueEvent('turtle_response')
error('Terminated')
end
if abort then
-- the function was queued, but the queue was cleared
os.queueEvent('turtle_response')
return false, 'aborted'
end
if id == ticketId then
@ -72,6 +74,7 @@ function turtle.run(fn, ...)
if not s and m then
printError(m)
end
os.queueEvent('turtle_response')
return s, m
end
end

View File

@ -17,6 +17,7 @@ local state = {
moveDig = noop,
moveCallback = noop,
locations = {},
coordSystem = 'relative', -- type of coordinate system being used
}
function turtle.getState()
@ -44,6 +45,7 @@ function turtle.reset()
state.moveDig = noop
state.moveCallback = noop
state.locations = {}
state.coordSystem = 'relative'
return true
end
@ -231,7 +233,6 @@ turtle.digPolicies = {
if not turtle.isTurtleAtSide(action.side) then
return action.dig()
end
return Util.tryTimes(6, function()
-- if not turtle.isTurtleAtSide(action.side) then
-- return true --action.dig()

View File

@ -117,15 +117,19 @@ process:newThread('discovery_server', function()
end
end)
local info = {
id = os.getComputerID()
}
local function sendInfo()
local info = {
id = os.getComputerID(),
label = os.getComputerLabel(),
uptime = math.floor(os.clock()),
}
info.label = os.getComputerLabel()
info.uptime = math.floor(os.clock())
if turtle then
info.fuel = turtle.getFuelLevel()
info.status = turtle.status
info.point = turtle.point
info.inventory = turtle.getInventory()
info.coordSystem = turtle.getState().coordSystem
end
device.wireless_modem.transmit(999, os.getComputerID(), info)
end
@ -152,13 +156,11 @@ end)
if os.isTurtle() then
process:newThread('turtle_heartbeat', function()
local lastUpdate = os.clock()
os.sleep(1)
while true do
os.pullEvent('turtle_response')
if os.clock() - lastUpdate >= 1 then
lastUpdate = os.clock()
if turtle.status ~= info.status or
turtle.fuel ~= info.fuel then
sendInfo()
end
end