Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --Import
- -- From http://lua-users.org/wiki/SimpleLuaClasses
- -- class.lua
- -- Compatible with Lua 5.1 (not 5.0).
- _G.class = { }
- function class.class(base, init)
- local c = {} -- a new class instance
- if not init and type(base) == 'function' then
- init = base
- base = nil
- elseif type(base) == 'table' then
- -- our new class is a shallow copy of the base class!
- for i,v in pairs(base) do
- c[i] = v
- end
- c._base = base
- end
- -- the class will be the metatable for all its objects,
- -- and they will look up their methods in it.
- c.__index = c
- -- expose a constructor which can be called by <classname>(<args>)
- local mt = {}
- mt.__call =
- function(class_tbl, ...)
- local obj = {}
- setmetatable(obj,c)
- --if init then
- -- init(obj,...)
- if class_tbl.init then
- class_tbl.init(obj, ...)
- else
- -- make sure that any stuff from the base class is initialized!
- if base and base.init then
- base.init(obj, ...)
- end
- end
- return obj
- end
- c.init = init
- c.is_a =
- function(self, klass)
- local m = getmetatable(self)
- while m do
- if m == klass then return true end
- m = m._base
- end
- return false
- end
- setmetatable(c, mt)
- return c
- end
- --Import
- _G.Logger = { }
- local debugMon
- local logServerId
- local logFile
- local filteredEvents = {}
- local function nopLogger(text)
- end
- local function monitorLogger(text)
- debugMon.write(text)
- debugMon.scroll(-1)
- debugMon.setCursorPos(1, 1)
- end
- local function screenLogger(text)
- local x, y = term.getCursorPos()
- if x ~= 1 then
- local sx, sy = term.getSize()
- term.setCursorPos(1, sy)
- --term.scroll(1)
- end
- print(text)
- end
- local logger = screenLogger
- local function wirelessLogger(text)
- if logServerId then
- rednet.send(logServerId, {
- type = 'log',
- contents = text
- })
- end
- end
- local function fileLogger(text)
- local mode = 'w'
- if fs.exists(logFile) then
- mode = 'a'
- end
- local file = io.open(logFile, mode)
- if file then
- file:write(text)
- file:write('\n')
- file:close()
- end
- end
- local function setLogger(ilogger)
- logger = ilogger
- end
- function Logger.disable()
- setLogger(nopLogger)
- end
- function Logger.setMonitorLogging(logServer)
- debugMon = Util.wrap('monitor')
- debugMon.setTextScale(.5)
- debugMon.clear()
- debugMon.setCursorPos(1, 1)
- setLogger(monitorLogger)
- end
- function Logger.setScreenLogging()
- setLogger(screenLogger)
- end
- function Logger.setWirelessLogging(id)
- if id then
- logServerId = id
- end
- setLogger(wirelessLogger)
- end
- function Logger.setFileLogging(fileName)
- logFile = fileName
- fs.delete(fileName)
- setLogger(fileLogger)
- end
- function Logger.log(category, value, ...)
- if filteredEvents[category] then
- return
- end
- if type(value) == 'table' then
- local str
- for k,v in pairs(value) do
- if not str then
- str = '{ '
- else
- str = str .. ', '
- end
- str = str .. k .. '=' .. tostring(v)
- end
- value = str .. ' }'
- elseif type(value) == 'string' then
- local args = { ... }
- if #args > 0 then
- value = string.format(value, unpack(args))
- end
- else
- value = tostring(value)
- end
- logger(category .. ': ' .. value)
- end
- function Logger.debug(value, ...)
- Logger.log('debug', value, ...)
- end
- function Logger.logNestedTable(t, indent)
- for _,v in ipairs(t) do
- if type(v) == 'table' then
- log('table')
- logNestedTable(v) --, indent+1)
- else
- log(v)
- end
- end
- end
- function Logger.filter( ...)
- local events = { ... }
- for _,event in pairs(events) do
- filteredEvents[event] = true
- end
- end
- --Import
- _G.Profile = { }
- Profile.start = function() end
- Profile.stop = function() end
- Profile.display = function() end
- Profile.methods = { }
- local function Profile_display()
- Logger.log('profile', 'Profiling results')
- for k,v in pairs(Profile.methods) do
- Logger.log('profile', '%s: %f %d %f',
- k, Util.round(v.elapsed, 2), v.count, Util.round(v.elapsed/v.count, 2))
- end
- Profile.methods = { }
- end
- local function Profile_start(name)
- local p = Profile.methods[name]
- if not p then
- p = { }
- p.elapsed = 0
- p.count = 0
- Profile.methods[name] = p
- end
- p.clock = os.clock()
- return p
- end
- local function Profile_stop(name)
- local p = Profile.methods[name]
- p.elapsed = p.elapsed + (os.clock() - p.clock)
- p.count = p.count + 1
- end
- function Profile.enable()
- Logger.log('profile', 'Profiling enabled')
- Profile.start = Profile_start
- Profile.stop = Profile_stop
- Profile.display = Profile_display
- end
- function Profile.disable()
- Profile.start = function() end
- Profile.stop = function() end
- Profile.display = function() end
- end
- --Import
- _G.Util = { }
- -- _G.String = { }
- math.randomseed(os.time())
- function _G.printf(format, ...)
- print(string.format(format, ...))
- end
- function Util.tryTimed(timeout, f, ...)
- local c = os.clock()
- repeat
- local ret = f(...)
- if ret then
- return ret
- end
- until os.clock()-c >= timeout
- end
- function Util.tryTimes(attempts, f, ...)
- local c = os.clock()
- for i = 1, attempts do
- local ret = f(...)
- if ret then
- return ret
- end
- end
- end
- function Util.print(value)
- if type(value) == 'table' then
- for k,v in pairs(value) do
- print(k .. '=' .. tostring(v))
- end
- else
- print(tostring(value))
- end
- end
- function Util.round(num)
- if num >= 0 then return math.floor(num+.5)
- else return math.ceil(num-.5) end
- end
- function Util.clear(t)
- local keys = Util.keys(t)
- for _,k in pairs(keys) do
- t[k] = nil
- end
- end
- function Util.empty(t)
- return Util.size(t) == 0
- end
- function Util.key(t, value)
- for k,v in pairs(t) do
- if v == value then
- return k
- end
- end
- end
- function Util.keys(t)
- local keys = {}
- for k in pairs(t) do
- keys[#keys+1] = k
- end
- return keys
- end
- function Util.find(t, name, value)
- for k,v in pairs(t) do
- if v[name] == value then
- return v, k
- end
- end
- end
- function Util.findAll(t, name, value)
- local rt = { }
- for k,v in pairs(t) do
- if v[name] == value then
- table.insert(rt, v)
- end
- end
- return rt
- end
- --http://lua-users.org/wiki/TableUtils
- function table.val_to_str ( v )
- if "string" == type( v ) then
- v = string.gsub( v, "\n", "\\n" )
- if string.match( string.gsub(v,"[^'\"]",""), '^"+$' ) then
- return "'" .. v .. "'"
- end
- return '"' .. string.gsub(v,'"', '\\"' ) .. '"'
- else
- return "table" == type( v ) and table.tostring( v ) or
- tostring( v )
- end
- end
- function table.key_to_str ( k )
- if "string" == type( k ) and string.match( k, "^[_%a][_%a%d]*$" ) then
- return k
- else
- return "[" .. table.val_to_str( k ) .. "]"
- end
- end
- function table.tostring( tbl )
- local result, done = {}, {}
- for k, v in ipairs( tbl ) do
- table.insert( result, table.val_to_str( v ) )
- done[ k ] = true
- end
- for k, v in pairs( tbl ) do
- if not done[ k ] then
- table.insert( result,
- table.key_to_str( k ) .. "=" .. table.val_to_str( v ) )
- end
- end
- return "{" .. table.concat( result, "," ) .. "}"
- end
- --end http://lua-users.org/wiki/TableUtils
- --https://github.com/jtarchie/underscore-lua
- function Util.size(list, ...)
- local args = {...}
- if Util.isArray(list) then
- return #list
- elseif Util.isObject(list) then
- local length = 0
- Util.each(list, function() length = length + 1 end)
- return length
- end
- return 0
- end
- function Util.each(list, func)
- local pairing = pairs
- if Util.isArray(list) then pairing = ipairs end
- for index, value in pairing(list) do
- func(value, index, list)
- end
- end
- function Util.isObject(value)
- return type(value) == "table"
- end
- function Util.isArray(value)
- return type(value) == "table" and (value[1] or next(value) == nil)
- end
- -- end https://github.com/jtarchie/underscore-lua
- function Util.random(max, min)
- min = min or 0
- return math.random(0, max-min) + min
- end
- function Util.readFile(fname)
- local f = fs.open(fname, "r")
- if f then
- local t = f.readAll()
- f.close()
- return t
- end
- end
- function Util.readTable(fname)
- local t = Util.readFile(fname)
- if t then
- return textutils.unserialize(t)
- end
- end
- function Util.writeTable(fname, data)
- Util.writeFile(fname, textutils.serialize(data))
- end
- function Util.writeFile(fname, data)
- local file = io.open(fname, "w")
- if not file then
- error('Unable to open ' .. fname, 2)
- end
- file:write(data)
- file:close()
- end
- function Util.shallowCopy(t)
- local t2 = {}
- for k,v in pairs(t) do
- t2[k] = v
- end
- return t2
- end
- function Util.split(str)
- local t = {}
- local function helper(line) table.insert(t, line) return "" end
- helper((str:gsub("(.-)\n", helper)))
- return t
- end
- -- http://snippets.luacode.org/?p=snippets/Check_string_ends_with_other_string_74
- -- Author: David Manura
- --String.endswith = function(s, send)
- --return #s >= #send and s:find(send, #s-#send+1, true) and true or false
- --end
- -- end http://snippets.luacode.org/?p=snippets/Check_string_ends_with_other_string_74
- string.lpad = function(str, len, char)
- if char == nil then char = ' ' end
- return str .. string.rep(char, len - #str)
- end
- -- http://stackoverflow.com/questions/15706270/sort-a-table-in-lua
- function Util.spairs(t, order)
- if not t then
- error('spairs: nil passed')
- end
- -- collect the keys
- local keys = {}
- for k in pairs(t) do keys[#keys+1] = k end
- -- if order function given, sort by it by passing the table and keys a, b,
- -- otherwise just sort the keys
- if order then
- table.sort(keys, function(a,b) return order(t[a], t[b]) end)
- else
- table.sort(keys)
- end
- -- return the iterator function
- local i = 0
- return function()
- i = i + 1
- if keys[i] then
- return keys[i], t[keys[i]]
- end
- end
- end
- function Util.first(t, order)
- -- collect the keys
- local keys = {}
- for k in pairs(t) do keys[#keys+1] = k end
- -- if order function given, sort by it by passing the table and keys a, b,
- -- otherwise just sort the keys
- if order then
- table.sort(keys, function(a,b) return order(t[a], t[b]) end)
- else
- table.sort(keys)
- end
- return keys[1], t[keys[1]]
- end
- --[[
- pbInfo - Libs/lib.WordWrap.lua
- v0.41
- by p.b. a.k.a. novayuna
- released under the Creative Commons License By-Nc-Sa: http://creativecommons.org/licenses/by-nc-sa/3.0/
- original code by Tomi H.: http://shadow.vs-hs.org/library/index.php?page=2&id=48
- ]]
- function Util.WordWrap(strText, intMaxLength)
- local tblOutput = {};
- local intIndex;
- local strBuffer = "";
- local tblLines = Util.Explode(strText, "\n");
- for k, strLine in pairs(tblLines) do
- local tblWords = Util.Explode(strLine, " ");
- if (#tblWords > 0) then
- intIndex = 1;
- while tblWords[intIndex] do
- local strWord = " " .. tblWords[intIndex];
- if (strBuffer:len() >= intMaxLength) then
- table.insert(tblOutput, strBuffer:sub(1, intMaxLength));
- strBuffer = strBuffer:sub(intMaxLength + 1);
- else
- if (strWord:len() > intMaxLength) then
- strBuffer = strBuffer .. strWord;
- elseif (strBuffer:len() + strWord:len() >= intMaxLength) then
- table.insert(tblOutput, strBuffer);
- strBuffer = ""
- else
- if (strBuffer == "") then
- strBuffer = strWord:sub(2);
- else
- strBuffer = strBuffer .. strWord;
- end;
- intIndex = intIndex + 1;
- end;
- end;
- end;
- if strBuffer ~= "" then
- table.insert(tblOutput, strBuffer);
- strBuffer = ""
- end;
- end;
- end;
- return tblOutput;
- end
- function Util.Explode(strText, strDelimiter)
- local strTemp = "";
- local tblOutput = {};
- for intIndex = 1, strText:len(), 1 do
- if (strText:sub(intIndex, intIndex + strDelimiter:len() - 1) == strDelimiter) then
- table.insert(tblOutput, strTemp);
- strTemp = "";
- else
- strTemp = strTemp .. strText:sub(intIndex, intIndex);
- end;
- end;
- if (strTemp ~= "") then
- table.insert(tblOutput, strTemp)
- end;
- return tblOutput;
- end
- -- http://lua-users.org/wiki/AlternativeGetOpt
- 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
- end
- y = y + 1
- end
- end
- end
- return tab
- end
- -- end http://lua-users.org/wiki/AlternativeGetOpt
- function Util.showOptions(options)
- print('Arguments: ')
- for k, v in pairs(options) do
- print(string.format('-%s %s', v.arg, v.desc))
- end
- end
- function Util.getOptions(options, args, syntaxMessage)
- local argLetters = ''
- for _,o in pairs(options) do
- if o.type ~= 'flag' then
- argLetters = argLetters .. o.arg
- end
- end
- local rawOptions = getopt(args, argLetters)
- for k,ro in pairs(rawOptions) do
- local found = false
- for _,o in pairs(options) do
- if o.arg == k then
- found = true
- if o.type == 'number' then
- o.value = tonumber(ro)
- elseif o.type == 'help' then
- Util.showOptions(options)
- return false
- else
- o.value = ro
- end
- end
- end
- if not found then
- print('Invalid argument')
- Util.showOptions(options)
- return false
- end
- end
- return true
- end
- --Import
- _G.Peripheral = { }
- function Peripheral.getAll()
- local aliasDB = {
- obsidian = 'chest',
- diamond = 'chest',
- container_chest = 'chest'
- }
- local t = { }
- for _,side in pairs(peripheral.getNames()) do
- local pType = peripheral.getType(side)
- if pType == 'modem' then
- if peripheral.call(side, 'isWireless') then
- t[side] = {
- type = 'wireless_modem',
- side = side
- }
- else
- t[side] = {
- type = 'wired_modem',
- side = side
- }
- end
- else
- t[side] = {
- type = pType,
- side = side,
- alias = aliasDB[pType]
- }
- end
- end
- return t
- end
- function Peripheral.getBySide(pList, sideName)
- return pList[sideName]
- end
- function Peripheral.getByType(pList, typeName)
- return Util.find(pList, 'type', typeName)
- end
- function Peripheral.getByAlias(pList, aliasName)
- return Util.find(pList, 'alias', aliasName)
- end
- function Peripheral.hasMethod(p, methodName)
- local methods = peripheral.getMethods(p.side)
- return Util.key(methods, methodName)
- end
- function Peripheral.getByMethod(pList, methodName)
- for _,p in pairs(pList) do
- if Peripheral.hasMethod(p, methodName) then
- return p
- end
- end
- end
- -- peripheral must match all arguments
- function Peripheral.isAllPresent(args)
- local t = Peripheral.getAll()
- local p
- if args.side then
- p = Peripheral.getBySide(t, args.side)
- if not p then
- return
- end
- end
- if args.type then
- if p then
- if p.type ~= args.type then
- return
- end
- else
- p = Peripheral.getByType(t, args.type)
- if not p then
- return
- end
- end
- end
- if args.method then
- if p then
- if not Peripheral.hasMethod(p, args.method) then
- return
- end
- else
- p = Peripheral.getByMethod(t, args.method)
- if p then
- return
- end
- end
- end
- return p
- end
- function Peripheral.isPresent(args)
- if type(args) == 'string' then
- args = { type = args }
- end
- local t = Peripheral.getAll()
- args = args or { type = pType }
- if args.type then
- local p = Peripheral.getByType(t, args.type)
- if p then
- return p
- end
- end
- if args.alias then
- local p = Peripheral.getByAlias(t, args.alias)
- if p then
- return p
- end
- end
- if args.method then
- local p = Peripheral.getByMethod(t, args.method)
- if p then
- return p
- end
- end
- if args.side then
- local p = Peripheral.getBySide(t, args.side)
- if p then
- return p
- end
- end
- end
- local function _wrap(p)
- Logger.log('peripheral', 'Wrapping ' .. p.type)
- if p.type == 'wired_modem' or p.type == 'wireless_modem' then
- rednet.open(p.side)
- return p
- end
- return peripheral.wrap(p.side)
- end
- function Peripheral.wrap(args)
- if type(args) == 'string' then
- args = { type = args }
- end
- local p = Peripheral.isPresent(args)
- if p then
- return _wrap(p)
- end
- error('Peripheral ' .. table.tostring(args, '?') .. ' is not connected', 2)
- end
- function Peripheral.wrapAll()
- local t = Peripheral.getAll()
- Util.each(t, function(p) p.wrapper = _wrap(p) end)
- return t
- end
- function Peripheral.wrapSide(sideName)
- if peripheral.isPresent(sideName) then
- return peripheral.wrap(sideName)
- end
- error('No Peripheral on side ' .. sideName, 2)
- end
- --Import
- _G.Event = { }
- local eventHandlers = {
- namedTimers = {}
- }
- local enableQueue = {}
- local removeQueue = {}
- local function deleteHandler(h)
- for k,v in pairs(eventHandlers[h.event].handlers) do
- if v == h then
- table.remove(eventHandlers[h.event].handlers, k)
- break
- end
- end
- --table.remove(eventHandlers[h.event].handlers, h.key)
- end
- function Event.addHandler(type, f)
- local event = eventHandlers[type]
- if not event then
- event = {}
- event.handlers = {}
- eventHandlers[type] = event
- end
- local handler = {}
- handler.event = type
- handler.f = f
- handler.enabled = true
- table.insert(event.handlers, handler)
- -- any way to retrieve key here for removeHandler ?
- return handler
- end
- function Event.removeHandler(h)
- h.deleted = true
- h.enabled = false
- table.insert(removeQueue, h)
- end
- function Event.queueTimedEvent(name, timeout, event, args)
- Event.addNamedTimer(name, timeout, false,
- function()
- os.queueEvent(event, args)
- end
- )
- end
- function Event.addNamedTimer(name, interval, recurring, f)
- Event.cancelNamedTimer(name)
- eventHandlers.namedTimers[name] = Event.addTimer(interval, recurring, f)
- end
- function Event.getNamedTimer(name)
- return eventHandlers.namedTimers[name]
- end
- function Event.cancelNamedTimer(name)
- local timer = Event.getNamedTimer(name)
- if timer then
- timer.enabled = false
- timer.recurring = false
- end
- end
- function Event.isTimerActive(timer)
- return timer.enabled and
- os.clock() < timer.start + timer.interval
- end
- function Event.addTimer(interval, recurring, f)
- local timer = Event.addHandler('timer',
- function(t, id)
- if t.timerId ~= id then
- return
- end
- if t.enabled then
- t.fired = true
- t.cf(t, id)
- end
- if t.recurring then
- t.fired = false
- t.start = os.clock()
- t.timerId = os.startTimer(t.interval)
- else
- Event.removeHandler(t)
- end
- end
- )
- timer.cf = f
- timer.interval = interval
- timer.recurring = recurring
- timer.start = os.clock()
- timer.timerId = os.startTimer(interval)
- return timer
- end
- function Event.removeTimer(h)
- Event.removeHandler(h)
- end
- function Event.blockUntilEvent(event, timeout)
- return Event.waitForEvent(event, timeout, os.pullEvent)
- end
- function Event.waitForEvent(event, timeout, pullEvent)
- pullEvent = pullEvent or Event.pullEvent
- local timerId = os.startTimer(timeout)
- repeat
- local e, p1, p2, p3, p4 = pullEvent()
- if e == event then
- return e, p1, p2, p3, p4
- end
- until e == 'timer' and id == timerId
- end
- function Event.heartbeat(timeout)
- local function heart()
- while true do
- os.sleep(timeout)
- os.queueEvent('heartbeat')
- end
- end
- parallel.waitForAny(Event.pullEvents, heart)
- end
- function Event.pullEvents()
- while true do
- local e = Event.pullEvent()
- if e == 'exitPullEvents' then
- break
- end
- end
- end
- function Event.exitPullEvents()
- os.queueEvent('exitPullEvents')
- end
- function Event.enableHandler(h)
- table.insert(enableQueue, h)
- end
- function Event.backgroundPullEvents()
- local function allEvents()
- while true do
- local e = { os.pullEvent() }
- if e[1] == 'exitPullEvents' then
- break
- end
- if e[1] ~= 'rednet_message' and e[1] ~= 'timer' then
- Event.processEvent(e)
- end
- end
- end
- local function timerEvents()
- while true do
- local e = { os.pullEvent('timer') }
- Event.processEvent(e)
- end
- end
- local function rednetEvents()
- while true do
- local e = { os.pullEvent('rednet_message') }
- Event.processEvent(e)
- end
- end
- parallel.waitForAny(allEvents, rednetEvents, timerEvents)
- end
- function Event.pullEvent(eventType)
- local e = { os.pullEvent(eventType) }
- return Event.processEvent(e)
- end
- function Event.processEvent(pe)
- Logger.log('event', pe)
- local e, p1, p2, p3, p4, p5 = unpack(pe)
- local event = eventHandlers[e]
- if event then
- for k,v in pairs(event.handlers) do
- if v.enabled then
- v.f(v, p1, p2, p3, p4, p5)
- end
- end
- while #enableQueue > 0 do
- table.remove(handlerQueue).enabled = true
- end
- while #removeQueue > 0 do
- deleteHandler(table.remove(removeQueue))
- end
- end
- return e, p1, p2, p3, p4, p5
- end
- --Import
- _G.Message = { }
- local messageHandlers = {}
- function Message.addHandler(type, f)
- table.insert(messageHandlers, {
- type = type,
- f = f,
- enabled = true
- })
- end
- function Message.removeHandler(h)
- for k,v in pairs(messageHandlers) do
- if v == h then
- messageHandlers[k] = nil
- break
- end
- end
- end
- Event.addHandler('rednet_message',
- function(event, id, msg, distance)
- if msg and msg.type then -- filter out messages from other systems
- Logger.log('rednet_receive', { id, msg.type })
- for k,h in pairs(messageHandlers) do
- if h.type == msg.type then
- -- should provide msg.contents instead of message - type is already known
- h.f(h, id, msg, distance)
- end
- end
- end
- end
- )
- function Message.send(id, msgType, contents)
- if id then
- Logger.log('rednet_send', { tostring(id), msgType })
- rednet.send(id, { type = msgType, contents = contents })
- else
- Logger.log('rednet_send', { 'broadcast', msgType })
- rednet.broadcast({ type = msgType, contents = contents })
- end
- end
- function Message.broadcast(t, contents)
- Logger.log('rednet_send', { 'broadcast', t })
- rednet.broadcast({ type = t, contents = contents })
- end
- function Message.waitForMessage(msgType, timeout, fromId)
- local timerId = os.startTimer(timeout)
- repeat
- local e, id, msg, distance = os.pullEvent()
- if e == 'rednet_message' then
- if msg and msg.type and msg.type == msgType then
- if not fromId or id == fromId then
- return e, id, msg, distance
- end
- end
- end
- until e == 'timer' and id == timerId
- end
- function Message.enableWirelessLogging()
- local modem = Peripheral.isPresent('wireless_modem')
- if modem then
- if not rednet.isOpen(modem.side) then
- Logger.log('message', 'enableWirelessLogging: opening modem')
- rednet.open(modem.side)
- end
- Message.broadcast('logClient')
- local _, id = Message.waitForMessage('logServer', 1)
- if not id then
- return false
- end
- Logger.log('message', 'enableWirelessLogging: Logging to ' .. id)
- Logger.setWirelessLogging(id)
- return true
- end
- end
- --Import
- _G.Relay = { }
- Relay.stationId = nil
- function Relay.find(msgType, stationType)
- while true do
- Logger.log('relay', 'locating relay station')
- Message.broadcast('getRelayStation', os.getComputerLabel())
- local _, id = Message.waitForMessage('relayStation', 2)
- if id then
- Relay.stationId = id
- return id
- end
- end
- end
- function Relay.register(...)
- local types = { ... }
- Message.send(Relay.stationId, 'listen', types)
- end
- function Relay.send(type, contents, toId)
- local relayMessage = {
- type = type,
- contents = contents,
- fromId = os.getComputerID()
- }
- if toId then
- relayMessage.toId = toId
- end
- Message.send(Relay.stationId, 'relay', relayMessage)
- end
- --Import
- _G.UI = { }
- local function widthify(s, len)
- if not s then
- s = ' '
- end
- return string.lpad(string.sub(s, 1, len) , len, ' ')
- end
- function UI.setProperties(obj, args)
- if args then
- for k,v in pairs(args) do
- obj[k] = v
- end
- end
- end
- function UI.setDefaultDevice(device)
- UI.defaultDevice = device
- end
- function UI.bestDefaultDevice(...)
- local termList = { ... }
- for _,name in ipairs(termList) do
- if name == 'monitor' then
- if Util.hasDevice('monitor') then
- UI.defaultDevice = UI.Device({ deviceType = 'monitor' })
- return UI.defaultDevice
- end
- end
- end
- return UI.term
- end
- --[[-- Glasses device --]]--
- UI.Glasses = class.class()
- function UI.Glasses:init(args)
- local defaults = {
- backgroundColor = colors.black,
- textColor = colors.white,
- textScale = .5,
- backgroundOpacity = .5
- }
- defaults.width, defaults.height = term.getSize()
- UI.setProperties(defaults, args)
- UI.setProperties(self, defaults)
- self.bridge = Peripheral.wrap({
- type = 'openperipheral_glassesbridge',
- method = 'addBox'
- })
- self.bridge.clear()
- self.setBackgroundColor = function(...) end
- self.setTextColor = function(...) end
- self.t = { }
- for i = 1, self.height do
- self.t[i] = {
- text = self.bridge.addText(0, 40+i*4, string.rep(' ', self.width+1), 0xffffff),
- bg = { }
- }
- self.t[i].text.setScale(self.textScale)
- self.t[i].text.setZ(1)
- end
- end
- function UI.Glasses:setBackgroundBox(boxes, ax, bx, y, bgColor)
- local colors = {
- [ colors.black ] = 0x000000,
- [ colors.brown ] = 0x7F664C,
- [ colors.blue ] = 0x253192,
- [ colors.gray ] = 0x272727,
- [ colors.lime ] = 0x426A0D,
- [ colors.green ] = 0x2D5628,
- [ colors.white ] = 0xFFFFFF
- }
- local function overlap(box, ax, bx)
- if bx < box.ax or ax > box.bx then
- return false
- end
- return true
- end
- for _,box in pairs(boxes) do
- if overlap(box, ax, bx) then
- if box.bgColor == bgColor then
- ax = math.min(ax, box.ax)
- bx = math.max(bx, box.bx)
- box.ax = box.bx + 1
- elseif ax == box.ax then
- box.ax = bx + 1
- elseif ax > box.ax then
- if bx < box.bx then
- table.insert(boxes, { -- split
- ax = bx + 1,
- bx = box.bx,
- bgColor = box.bgColor
- })
- box.bx = ax - 1
- break
- else
- box.ax = box.bx + 1
- end
- elseif ax < box.ax then
- if bx > box.bx then
- box.ax = box.bx + 1 -- delete
- else
- box.ax = bx + 1
- end
- end
- end
- end
- if bgColor ~= colors.black then
- table.insert(boxes, {
- ax = ax,
- bx = bx,
- bgColor = bgColor
- })
- end
- local deleted
- repeat
- deleted = false
- for k,box in pairs(boxes) do
- if box.ax > box.bx then
- if box.box then
- box.box.delete()
- end
- table.remove(boxes, k)
- deleted = true
- break
- end
- if not box.box then
- box.box = self.bridge.addBox(
- math.floor((box.ax - 1) * 2.6665),
- 40 + y * 4,
- math.ceil((box.bx - box.ax + 1) * 2.6665),
- 4,
- colors[bgColor],
- self.backgroundOpacity)
- else
- box.box.setX(math.floor((box.ax - 1) * 2.6665))
- box.box.setWidth(math.ceil((box.bx - box.ax + 1) * 2.6665))
- end
- end
- until not deleted
- end
- function UI.Glasses:write(x, y, text, bg)
- if x < 1 then
- error(' less ', 6)
- end
- if y <= #self.t then
- local line = self.t[y]
- local str = line.text.getText()
- str = str:sub(1, x-1) .. text .. str:sub(x + 1 + #text)
- line.text.setText(str)
- self:setBackgroundBox(line.bg, x, x + #text - 1, y, bg)
- UI.term:write(x, y, text, bg)
- end
- end
- function UI.Glasses:clear(bg)
- for i = 1, self.height do
- self.t[i].text.setText('')
- end
- end
- --[[-- Basic drawable area --]]--
- UI.Window = class.class()
- function UI.Window:init(args)
- local defaults = {
- UIElement = 'Window',
- x = 1,
- y = 1,
- cursorX = 1,
- cursorY = 1,
- isUIElement = true
- }
- UI.setProperties(self, defaults)
- UI.setProperties(self, args)
- if self.parent then
- self:setParent()
- end
- end
- function UI.Window:setParent()
- if not self.width then
- self.width = self.parent.width - self.x + 1
- end
- if not self.height then
- self.height = self.parent.height - self.y + 1
- end
- local children = self.children
- for k,child in pairs(self) do
- if type(child) == 'table' and child.isUIElement and not child.parent then
- if not children then
- children = { }
- end
- --self.children[k] = child
- table.insert(children, child)
- end
- end
- if children then
- for _,child in pairs(children) do
- if not child.parent then
- child.parent = self
- child:setParent()
- end
- end
- end
- self.children = children
- end
- function UI.Window:add(children)
- UI.setProperties(self, children)
- for k,child in pairs(children) do
- if type(child) == 'table' and child.isUIElement and not child.parent then
- if not self.children then
- self.children = { }
- end
- self.children[k] = child
- --table.insert(self.children, child)
- child.parent = self
- child:setParent()
- end
- end
- end
- function UI.Window:getCursorPos()
- return self.cursorX, self.cursorY
- end
- function UI.Window:setCursorPos(x, y)
- self.cursorX = x
- self.cursorY = y
- self.parent:setCursorPos(self.x + x - 1, self.y + y - 1)
- end
- function UI.Window:setCursorBlink(blink)
- self.parent:setCursorBlink(blink)
- end
- function UI.Window:setBackgroundColor(bgColor)
- self.backgroundColor = bgColor
- end
- function UI.Window:draw()
- self:clear(self.backgroundColor)
- if self.children then
- for k,child in pairs(self.children) do
- child:draw()
- end
- end
- end
- function UI.Window:setTextScale(textScale)
- self.textScale = textScale
- self.parent:setTextScale(textScale)
- end
- function UI.Window:reset(bg)
- self:clear(bg)
- self:setCursorPos(1, 1)
- end
- function UI.Window:clear(bg)
- self:clearArea(1, 1, self.width, self.height, bg)
- end
- function UI.Window:clearLine(y, bg)
- local filler = string.rep(' ', self.width)
- self:write(1, y, filler, bg)
- end
- function UI.Window:clearArea(x, y, width, height, bg)
- local filler = string.rep(' ', width)
- for i = 0, height-1 do
- self:write(x, y+i, filler, bg)
- end
- end
- function UI.Window:write(x, y, text, bg, tc)
- bg = bg or self.backgroundColor
- tc = tc or self.textColor
- if y < self.height+1 then
- self.parent:write(
- self.x + x - 1, self.y + y - 1, tostring(text), bg, tc)
- end
- end
- function UI.Window:centeredWrite(y, text, bg)
- if #text >= self.width then
- self:write(1, y, text, bg)
- else
- local space = math.floor((self.width-#text) / 2)
- local filler = string.rep(' ', space + 1)
- local str = filler:sub(1, space) .. text
- str = str .. filler:sub(self.width - #str + 1)
- self:write(1, y, str, bg)
- end
- end
- function UI.Window:wrappedWrite(x, y, text, len, bg)
- for k,v in pairs(Util.WordWrap(text, len)) do
- self:write(x, y, v, bg)
- y = y + 1
- end
- return y
- end
- function UI.Window:print(text, indent, len, bg)
- indent = indent or 0
- len = len or self.width - indent
- if #text + self.cursorX > self.width then
- for k,v in pairs(Util.WordWrap(text, len+1)) do
- self:write(self.cursorX, self.cursorY, v, bg)
- self.cursorY = self.cursorY + 1
- self.cursorX = 1 + indent
- end
- else
- self:write(self.cursorX, self.cursorY, text, bg)
- self.cursorY = self.cursorY + 1
- self.cursorX = 1
- end
- end
- function UI.Window:emit(event)
- local parent = self
- Logger.log('ui', self.UIElement .. ' emitting ' .. event.type)
- while parent do
- if parent.eventHandler then
- if parent:eventHandler(event) then
- return true
- end
- end
- parent = parent.parent
- end
- end
- function UI.Window:eventHandler(event)
- return false
- end
- function UI.Window:setFocus(focus)
- if self.parent then
- self.parent:setFocus(focus)
- end
- end
- function UI.Window:getPreviousFocus(focused)
- if self.children then
- local k = Util.key(self.children, focused)
- for k = k-1, 1, -1 do
- if self.children[k].focus then
- return self.children[k]
- end
- end
- end
- end
- function UI.Window:getNextFocus(focused)
- if self.children then
- local k = Util.key(self.children, focused)
- for k = k+1, #self.children do
- if self.children[k].focus then
- return self.children[k]
- end
- end
- end
- end
- --[[-- Terminal for computer / advanced computer / monitor --]]--
- UI.Device = class.class(UI.Window)
- function UI.Device:init(args)
- local defaults = {
- UIElement = 'Device',
- isUIElement = false,
- backgroundColor = colors.black,
- textColor = colors.white,
- textScale = 1,
- device = term
- }
- UI.setProperties(defaults, args)
- if defaults.deviceType then
- defaults.device = Peripheral.wrap({ type = defaults.deviceType })
- end
- if not defaults.device.setTextScale then
- defaults.device.setTextScale = function(...) end
- end
- defaults.device.setTextScale(defaults.textScale)
- defaults.width, defaults.height = defaults.device.getSize()
- UI.Window.init(self, defaults)
- self.UIElement = nil
- self.isColor = self.device.isColor()
- if not self.isColor then
- self.device.setBackgroundColor = function(...) end
- self.device.setTextColor = function(...) end
- end
- end
- function UI.Device:setCursorPos(x, y)
- self.cursorX = x
- self.cursorY = y
- self.device.setCursorPos(x, y)
- end
- function UI.Device:setCursorBlink(blink)
- self.device.setCursorBlink(blink)
- end
- function UI.Device:write(x, y, text, bg, tc)
- bg = bg or self.backgroundColor
- tc = tc or self.textColor
- self.device.setCursorPos(x, y)
- self.device.setTextColor(tc)
- self.device.setBackgroundColor(bg)
- self.device.write(text)
- end
- function UI.Device:setTextScale(textScale)
- self.textScale = textScale
- self.device.setTextScale(self.textScale)
- end
- function UI.Device:clear(bg)
- bg = bg or self.backgroundColor
- self.device.setBackgroundColor(bg)
- self.device.clear()
- end
- UI.term = UI.Device({ device = term })
- UI.defaultDevice = UI.term
- --[[-- StringBuffer --]]--
- UI.StringBuffer = class.class()
- function UI.StringBuffer:init(bufSize)
- self.bufSize = bufSize
- self.buffer = {}
- end
- function UI.StringBuffer:insert(s, index)
- table.insert(self.buffer, { index = index, str = s })
- end
- function UI.StringBuffer:append(s)
- local str = self:get()
- self:insert(s, #str)
- end
- -- pi -> getBeeParents -> Demonic -> special conditions
- function UI.StringBuffer:get()
- local str = ''
- for k,v in Util.spairs(self.buffer, function(a, b) return a.index < b.index end) do
- str = str .. string.rep(' ', math.max(v.index - string.len(str), 0)) .. v.str
- end
- local len = string.len(str)
- if len < self.bufSize then
- str = str .. string.rep(' ', self.bufSize - len)
- end
- return str
- end
- function UI.StringBuffer:clear()
- self.buffer = {}
- end
- --[[-- Pager --]]--
- UI.Pager = class.class()
- function UI.Pager:init(args)
- local defaults = {
- pages = { }
- }
- UI.setProperties(defaults, args)
- UI.setProperties(self, defaults)
- Event.addHandler('mouse_scroll', function(h, direction, x, y)
- local event = self:pointToChild(self.currentPage, x, y)
- local directions = {
- [ -1 ] = 'up',
- [ 1 ] = 'down'
- }
- event.type = 'key'
- event.key = directions[direction]
- event.UIElement:emit(event)
- end)
- Event.addHandler('monitor_touch', function(h, button, x, y)
- self:click(1, x, y)
- end)
- Event.addHandler('mouse_click', function(h, button, x, y)
- self:click(button, x, y)
- end)
- Event.addHandler('char', function(h, ch)
- self:emitEvent({ type = 'key', key = ch })
- end)
- Event.addHandler('key', function(h, code)
- local ch = keys.getName(code)
- -- filter out a through z as they will be get picked up
- -- as char events
- if ch and #ch > 1 then
- if self.currentPage then
- self:emitEvent({ type = 'key', key = ch })
- if ch == 'f10' then
- UI.displayTable(_G, 'Global Memory')
- elseif ch == 'f9' then
- UI.displayTable(getfenv(4), 'Local memory')
- end
- end
- end
- end)
- end
- function UI.Pager:emitEvent(event)
- if self.currentPage and self.currentPage.focused then
- return self.currentPage.focused:emit(event)
- end
- end
- function UI.Pager:pointToChild(parent, x, y)
- if parent.children then
- for _,child in pairs(parent.children) do
- if x >= child.x and x < child.x + child.width and
- y >= child.y and y < child.y + child.height then
- local c = self:pointToChild(child, x - child.x + 1, y - child.y + 1)
- if c then
- return c
- end
- end
- end
- end
- return {
- UIElement = parent,
- x = x,
- y = y
- }
- end
- function UI.Pager:click(button, x, y)
- if self.currentPage then
- local function getClicked(parent, button, x, y)
- if parent.children then
- for _,child in pairs(parent.children) do
- if x >= child.x and x < child.x + child.width and
- y >= child.y and y < child.y + child.height and
- not child.isShadow then
- local c = getClicked(child, button, x - child.x + 1, y - child.y + 1)
- if c then
- return c
- end
- end
- end
- end
- local events = { 'mouse_click', 'mouse_rightclick' }
- return {
- UIElement = parent,
- type = events[button],
- button = button,
- x = x,
- y = y
- }
- end
- local clickEvent = getClicked(self.currentPage, button,
- x - self.currentPage.x + 1,
- y - self.currentPage.y + 1)
- if clickEvent.UIElement.focus then
- self.currentPage:setFocus(clickEvent.UIElement)
- end
- clickEvent.UIElement:emit(clickEvent)
- end
- end
- function UI.Pager:addPage(name, page)
- self.pages[name] = page
- end
- function UI.Pager:setPages(pages)
- self.pages = pages
- end
- function UI.Pager:getPage(pageName)
- local page = self.pages[pageName]
- if not page then
- error('Pager:getPage: Invalid page: ' .. tostring(pageName), 2)
- end
- return page
- end
- function UI.Pager:setPage(pageOrName)
- local page = pageOrName
- if type(pageOrName) == 'string' then
- page = self.pages[pageOrName]
- end
- if page == self.currentPage then
- page:draw()
- else
- if self.currentPage then
- if self.currentPage.focused then
- self.currentPage.focused.focused = false
- self.currentPage.focused:focus()
- end
- self.currentPage:disable()
- self.currentPage.enabled = false
- page.previousPage = self.currentPage
- end
- self.currentPage = page
- self.currentPage:reset(page.backgroundColor)
- page.enabled = true
- page:enable()
- page:draw()
- if self.currentPage.focused then
- self.currentPage.focused.focused = true
- self.currentPage.focused:focus()
- end
- end
- end
- function UI.Pager:getCurrentPage()
- return self.currentPage
- end
- function UI.Pager:setPreviousPage()
- if self.currentPage.previousPage then
- local previousPage = self.currentPage.previousPage.previousPage
- self:setPage(self.currentPage.previousPage)
- self.currentPage.previousPage = previousPage
- end
- end
- UI.pager = UI.Pager()
- --[[-- Page --]]--
- UI.Page = class.class(UI.Window)
- function UI.Page:init(args)
- local defaults = {
- UIElement = 'Page',
- parent = UI.defaultDevice,
- accelerators = { }
- }
- --if not args or not args.parent then
- --self.parent = UI.defaultDevice
- --end
- UI.setProperties(defaults, args)
- UI.Window.init(self, defaults)
- self.focused = self:findFirstFocus(self)
- --if self.focused then
- --self.focused.focused = true
- --end
- end
- function UI.Page:enable()
- end
- function UI.Page:disable()
- end
- function UI.Page:draw()
- UI.Window.draw(self)
- --if self.focused then
- --self:setFocus(self.focused)
- --end
- end
- function UI.Page:findFirstFocus(parent)
- if not parent.children then
- return
- end
- for _,child in ipairs(parent.children) do
- if child.children then
- local c = self:findFirstFocus(child)
- if c then
- return c
- end
- end
- if child.focus then
- return child
- end
- end
- end
- function UI.Page:getFocused()
- return self.focused
- end
- function UI.Page:focusFirst()
- local focused = self:findFirstFocus(self)
- if focused then
- self:setFocus(focused)
- end
- end
- function UI.Page:focusPrevious()
- local parent = self.focused.parent
- local child = self.focused
- local focused
- while parent do
- focused = parent:getPreviousFocus(child)
- if focused then
- break
- end
- child = parent
- parent = parent.parent
- if not parent.getPreviousFocused then
- break
- end
- end
- if focused then
- self:setFocus(focused)
- end
- end
- function UI.Page:focusNext()
- local parent = self.focused.parent
- local child = self.focused
- local focused
- while parent do
- focused = parent:getNextFocus(child)
- if focused then
- break
- end
- child = parent
- parent = parent.parent
- if not parent.getNextFocused then
- break
- end
- end
- if focused then
- self:setFocus(focused)
- end
- end
- function UI.Page:setFocus(child)
- if not child.focus then
- return
- --error('cannot focus child ' .. child.UIElement, 2)
- end
- if self.focused and self.focused ~= child then
- self.focused.focused = false
- self.focused:focus()
- self.focused = child
- end
- if not child.focused then
- child.focused = true
- self:emit({ type = 'focus_change', focused = child })
- end
- child:focus()
- end
- function UI.Page:eventHandler(event)
- if self.focused then
- if event.type == 'key' then
- local acc = self.accelerators[event.key]
- if acc then
- if self:eventHandler({ type = acc }) then
- return true
- end
- end
- local ch = event.key
- if ch == 'down' or ch == 'enter' or ch == 'k' or ch == 'tab' then
- self:focusNext()
- return true
- elseif ch == 'tab' then
- --self:focusNextGroup()
- elseif ch == 'up' or ch == 'j' then
- self:focusPrevious()
- return true
- end
- end
- end
- end
- --[[-- GridLayout --]]--
- UI.GridLayout = class.class(UI.Window)
- function UI.GridLayout:init(args)
- local defaults = {
- UIElement = 'GridLayout',
- x = 1,
- y = 1,
- textColor = colors.white,
- backgroundColor = colors.black,
- values = {},
- columns = {}
- }
- UI.setProperties(defaults, args)
- UI.Window.init(self, defaults)
- end
- function UI.GridLayout:setParent()
- UI.Window.setParent(self)
- self:adjustWidth()
- end
- function UI.GridLayout:adjustWidth()
- if not self.width then
- self.width = self:calculateWidth()
- end
- if self.autospace then
- local width
- for _,col in pairs(self.columns) do
- width = 1
- for _,row in pairs(self.t) do
- local value = row[col[2]]
- if value then
- value = tostring(value)
- if #value > width then
- width = #value
- end
- end
- end
- col[3] = width
- end
- local colswidth = 0
- for _,c in pairs(self.columns) do
- colswidth = colswidth + c[3] + 1
- end
- local spacing = (self.width - colswidth - 1)
- if spacing > 0 then
- spacing = math.floor(spacing / (#self.columns - 1) )
- for _,c in pairs(self.columns) do
- c[3] = c[3] + spacing
- end
- end
- --[[
- width = 1
- for _,c in pairs(self.columns) do
- width = c[3] + width + 1
- end
- if width < self.width then
- local spacing = self.width - width
- spacing = spacing / #self.columns
- for i = 1, #self.columns do
- local col = self.columns[i]
- col[3] = col[3] + spacing
- end
- elseif width > self.width then
- end
- --]]
- end
- end
- function UI.GridLayout:calculateWidth()
- -- gutters on each side
- local width = 2
- for _,col in pairs(self.columns) do
- width = width + col[3] + 1
- end
- return width - 1
- end
- function UI.GridLayout:drawRow(row, y)
- local sb = UI.StringBuffer(self.width)
- local x = 1
- for _,col in pairs(self.columns) do
- local value = row[col[2]]
- if value then
- sb:insert(string.sub(value, 1, col[3]), x)
- end
- x = x + col[3] + 1
- end
- local selected = index == self.index and self.selectable
- if selected then
- self:setSelected(row)
- end
- self.parent:write(
- self.x, y, sb:get(), self.backgroundColor, self.textColor)
- end
- function UI.GridLayout:draw()
- local size = #self.values
- local startRow = self:getStartRow()
- local endRow = startRow + self.height - 1
- if endRow > size then
- endRow = size
- end
- for i = startRow, endRow do
- self:drawRow(self.values[i], self.y + i - 1)
- end
- if endRow - startRow < self.height - 1 then
- self.parent:clearArea(
- self.x, self.y + endRow, self.width, self.height - endRow, self.backgroundColor)
- end
- end
- function UI.GridLayout:getStartRow()
- return 1
- end
- --[[-- Grid --]]--
- UI.Grid = class.class(UI.Window)
- function UI.Grid:init(args)
- local defaults = {
- UIElement = 'Grid',
- x = 1,
- y = 1,
- pageNo = 1,
- index = 1,
- inverseSort = false,
- disableHeader = false,
- selectable = true,
- textColor = colors.white,
- textSelectedColor = colors.white,
- backgroundColor = colors.black,
- backgroundSelectedColor = colors.green,
- t = {},
- columns = {}
- }
- UI.setProperties(defaults, args)
- UI.Window.init(self, defaults)
- end
- function UI.Grid:setParent()
- UI.Window.setParent(self)
- self:adjustWidth()
- if not self.pageSize then
- if self.disableHeader then
- self.pageSize = self.height
- else
- self.pageSize = self.height - 1
- end
- end
- end
- function UI.Grid:adjustWidth()
- if self.autospace then
- for _,col in pairs(self.columns) do
- col[3] = #col[1]
- end
- for _,col in pairs(self.columns) do
- for _,row in pairs(self.t) do
- local value = row[col[2]]
- if value then
- value = tostring(value)
- if #value > col[3] then
- col[3] = #value
- end
- end
- end
- end
- local colswidth = 1
- for _,c in pairs(self.columns) do
- colswidth = colswidth + c[3] + 1
- end
- local function round(num)
- if num >= 0 then return math.floor(num+.5)
- else return math.ceil(num-.5) end
- end
- local spacing = (self.width - colswidth)
- if spacing > 0 then
- spacing = spacing / (#self.columns - 1)
- local space = 0
- for k,c in pairs(self.columns) do
- space = space + spacing
- c[3] = c[3] + math.floor(round(space) / k)
- end
- end
- end
- end
- function UI.Grid:setPosition(x, y)
- self.x = x
- self.y = y
- end
- function UI.Grid:setPageSize(pageSize)
- self.pageSize = pageSize
- end
- function UI.Grid:setColumns(columns)
- self.columns = columns
- end
- function UI.Grid:getTable()
- return self.t
- end
- function UI.Grid:setTable(t)
- self.t = t
- end
- function UI.Grid:setInverseSort(inverseSort)
- self.inverseSort = inverseSort
- self:drawRows()
- end
- function UI.Grid:setSortColumn(column)
- self.sortColumn = column
- for _,col in pairs(self.columns) do
- if col[2] == column then
- return
- end
- end
- error('Grid:setSortColumn: invalid column', 2)
- end
- function UI.Grid:setSelected(row)
- self.selected = row
- self:emit({ type = 'grid_focus_row', selected = self.selected })
- end
- function UI.Grid:getSelected()
- return self.selected
- end
- function UI.Grid:focus()
- self:draw()
- end
- function UI.Grid:draw()
- if not self.disableHeader then
- self:drawHeadings()
- end
- self:drawRows()
- end
- function UI.Grid:drawHeadings()
- local sb = UI.StringBuffer(self.width)
- local x = 1
- local indx
- for k,col in ipairs(self.columns) do
- local width = col[3] + 1
- if self.inverseSort then
- if col[2] == self.sortColumn then
- indx = x + #col[1] + 1
- end
- end
- sb:insert(col[1], x)
- x = x + width
- end
- self:write(1, 1, sb:get(), colors.blue)
- if indx then
- self:write(indx, 1, '^', colors.blue, colors.gray)
- end
- end
- function UI.Grid:calculateWidth()
- -- gutters on each side
- local width = 2
- for _,col in pairs(self.columns) do
- width = width + col[3] + 1
- end
- return width - 1
- end
- function UI.Grid:drawRows()
- local function sortM(a, b)
- a = a[self.sortColumn]
- b = b[self.sortColumn]
- if not a then
- return false
- elseif not b then
- return true
- end
- return a < b
- end
- local function inverseSortM(a, b)
- a = a[self.sortColumn]
- b = b[self.sortColumn]
- if not a then
- return true
- elseif not b then
- return false
- end
- return a > b
- end
- local sortMethod
- if self.sortColumn then
- sortMethod = sortM
- if self.inverseSort then
- sortMethod = inverseSortM
- end
- end
- if self.index > Util.size(self.t) then
- local newIndex = Util.size(self.t)
- if newIndex <= 0 then
- newIndex = 1
- end
- self:setIndex(newIndex)
- if Util.size(self.t) == 0 then
- y = 1
- if not self.disableHeader then
- y = y + 1
- end
- self:clearArea(1, y, self.width, self.pageSize, self.backgroundColor)
- end
- return
- end
- local startRow = self:getStartRow()
- local y = self.y
- local rowCount = 0
- local sb = UI.StringBuffer(self.width)
- if not self.disableHeader then
- y = y + 1
- end
- local index = 1
- for _,row in Util.spairs(self.t, sortMethod) do
- if index >= startRow then
- sb:clear()
- if index >= startRow + self.pageSize then
- break
- end
- if self.focused then
- if index == self.index and self.selectable then
- sb:insert('>', 0)
- end
- end
- local x = 1
- for _,col in pairs(self.columns) do
- local value = row[col[2]]
- if value then
- sb:insert(string.sub(value, 1, col[3]), x)
- end
- x = x + col[3] + 1
- end
- local selected = index == self.index and self.selectable
- if selected then
- self:setSelected(row)
- end
- self.parent:write(self.x, y, sb:get(),
- self:getRowBackgroundColor(row, selected),
- self:getRowTextColor(row, selected))
- y = y + 1
- rowCount = rowCount + 1
- end
- index = index + 1
- end
- if rowCount < self.pageSize then
- self.parent:clearArea(self.x, y, self.width, self.pageSize-rowCount, self.backgroundColor)
- end
- term.setTextColor(colors.white)
- end
- function UI.Grid:getRowTextColor(row, selected)
- if selected then
- return self.textSelectedColor
- end
- return self.textColor
- end
- function UI.Grid:getRowBackgroundColor(row, selected)
- if selected then
- if self.focused then
- return self.backgroundSelectedColor
- else
- return colors.lightGray
- end
- end
- return self.backgroundColor
- end
- function UI.Grid:getIndex(index)
- return self.index
- end
- function UI.Grid:setIndex(index)
- if self.index ~= index then
- if index < 1 then
- index = 1
- end
- self.index = index
- self:drawRows()
- end
- end
- function UI.Grid:getStartRow()
- return math.floor((self.index - 1)/ self.pageSize) * self.pageSize + 1
- end
- function UI.Grid:getPage()
- return math.floor(self.index / self.pageSize) + 1
- end
- function UI.Grid:getPageCount()
- local tableSize = Util.size(self.t)
- local pc = math.floor(tableSize / self.pageSize)
- if tableSize % self.pageSize > 0 then
- pc = pc + 1
- end
- return pc
- end
- function UI.Grid:nextPage()
- self:setPage(self:getPage() + 1)
- end
- function UI.Grid:previousPage()
- self:setPage(self:getPage() - 1)
- end
- function UI.Grid:setPage(pageNo)
- -- 1 based paging
- self:setIndex((pageNo-1) * self.pageSize + 1)
- end
- function UI.Grid:eventHandler(event)
- if event.type == 'mouse_click' then
- if not self.disableHeader then
- if event.y == 1 then
- local col = 2
- for _,c in ipairs(self.columns) do
- if event.x < col + c[3] then
- if self.sortColumn == c[2] then
- self:setInverseSort(not self.inverseSort)
- else
- self.sortColumn = c[2]
- self:setInverseSort(false)
- end
- self:draw()
- break
- end
- col = col + c[3] + 1
- end
- return true
- end
- end
- local row = self:getStartRow() + event.y - 1
- if not self.disableHeader then
- row = row - 1
- end
- if row > 0 and row <= Util.size(self.t) then
- self:setIndex(row)
- self:emit({ type = 'key', key = 'enter' })
- return true
- end
- elseif event.type == 'key' then
- if event.key == 'enter' then
- self:emit({ type = 'grid_select', selected = self.selected })
- return false
- elseif event.key == 'j' or event.key == 'down' then
- self:setIndex(self.index + 1)
- elseif event.key == 'k' or event.key == 'up' then
- self:setIndex(self.index - 1)
- elseif event.key == 'h' then
- self:setIndex(self.index - self.pageSize)
- elseif event.key == 'l' then
- self:setIndex(self.index + self.pageSize)
- elseif event.key == 'home' then
- self:setIndex(1)
- elseif event.key == 'end' then
- self:setIndex(Util.size(self.t))
- else
- return false
- end
- return true
- end
- return false
- end
- --[[-- ScrollingGrid --]]--
- UI.ScrollingGrid = class.class(UI.Grid)
- function UI.ScrollingGrid:init(args)
- local defaults = {
- UIElement = 'ScrollingGrid',
- scrollOffset = 1
- }
- UI.setProperties(self, defaults)
- UI.Grid.init(self, args)
- end
- function UI.ScrollingGrid:drawRows()
- UI.Grid.drawRows(self)
- self:drawScrollbar()
- end
- function UI.ScrollingGrid:drawScrollbar()
- local ts = Util.size(self.t)
- if ts > self.pageSize then
- term.setBackgroundColor(self.backgroundColor)
- local sbSize = self.pageSize - 2
- local sa = ts -- - self.pageSize
- sa = self.pageSize / sa
- sa = math.floor(sbSize * sa)
- if sa < 1 then
- sa = 1
- end
- if sa > sbSize then
- sa = sbSize
- end
- local sp = ts-self.pageSize
- sp = self.scrollOffset / sp
- sp = math.floor(sp * (sbSize-sa + 0.5))
- local x = self.x + self.width-1
- if self.scrollOffset > 1 then
- self.parent:write(x, self.y + 1, '^')
- else
- self.parent:write(x, self.y + 1, ' ')
- end
- local row = 0
- for i = 0, sp - 1 do
- self.parent:write(x, self.y + row+2, '|')
- row = row + 1
- end
- for i = 1, sa do
- self.parent:write(x, self.y + row+2, '#')
- row = row + 1
- end
- for i = row, sbSize do
- self.parent:write(x, self.y + row+2, '|')
- row = row + 1
- end
- if self.scrollOffset + self.pageSize - 1 < Util.size(self.t) then
- self.parent:write(x, self.y + self.pageSize, 'v')
- else
- self.parent:write(x, self.y + self.pageSize, ' ')
- end
- end
- end
- function UI.ScrollingGrid:getStartRow()
- local ts = Util.size(self.t)
- if ts < self.pageSize then
- self.scrollOffset = 1
- end
- return self.scrollOffset
- end
- function UI.ScrollingGrid:setIndex(index)
- if index < self.scrollOffset then
- self.scrollOffset = index
- elseif index - (self.scrollOffset - 1) > self.pageSize then
- self.scrollOffset = index - self.pageSize + 1
- end
- if self.scrollOffset < 1 then
- self.scrollOffset = 1
- else
- local ts = Util.size(self.t)
- if self.pageSize + self.scrollOffset > ts then
- self.scrollOffset = ts - self.pageSize + 1
- end
- end
- UI.Grid.setIndex(self, index)
- end
- --[[-- Menu --]]--
- UI.Menu = class.class(UI.Grid)
- function UI.Menu:init(args)
- local defaults = {
- UIElement = 'Menu',
- disableHeader = true,
- columns = { { 'Prompt', 'prompt', 20 } },
- t = args['menuItems']
- }
- UI.setProperties(defaults, args)
- UI.Grid.init(self, defaults)
- self.pageSize = #args.menuItems
- end
- function UI.Menu:setParent()
- UI.Grid.setParent(self)
- self.itemWidth = 1
- for _,v in pairs(self.t) do
- if #v.prompt > self.itemWidth then
- self.itemWidth = #v.prompt
- end
- end
- self.columns[1][3] = self.itemWidth
- if self.centered then
- self:center()
- else
- self.width = self.itemWidth + 2
- end
- end
- function UI.Menu:center()
- self.x = (self.width - self.itemWidth + 2) / 2
- self.width = self.itemWidth + 2
- end
- function UI.Menu:eventHandler(event)
- if event.type == 'key' then
- if event.key and self.menuItems[tonumber(event.key)] then
- local selected = self.menuItems[tonumber(event.key)]
- self:emit({
- type = selected.event or 'menu_select',
- selected = selected
- })
- return true
- elseif event.key == 'enter' then
- local selected = self.menuItems[self.index]
- self:emit({
- type = selected.event or 'menu_select',
- selected = selected
- })
- return true
- end
- elseif event.type == 'mouse_click' then
- if event.y <= #self.menuItems then
- UI.Grid.setIndex(self, event.y)
- local selected = self.menuItems[self.index]
- self:emit({
- type = selected.event or 'menu_select',
- selected = selected
- })
- return true
- end
- end
- return UI.Grid.eventHandler(self, event)
- end
- --[[-- ViewportWindow --]]--
- UI.ViewportWindow = class.class(UI.Window)
- function UI.ViewportWindow:init(args)
- local defaults = {
- UIElement = 'ViewportWindow',
- x = 1,
- y = 1,
- --width = console.width,
- --height = console.height,
- offset = 0
- }
- UI.setProperties(self, defaults)
- UI.Window.init(self, args)
- self.vpHeight = self.height
- end
- function UI.ViewportWindow:clear(bg)
- self:clearArea(1, 1+self.offset, self.width, self.height+self.offset, bg)
- end
- function UI.ViewportWindow:write(x, y, text, bg)
- local y = y - self.offset
- if y > self.vpHeight then
- self.vpHeight = y
- end
- if y <= self.height and y > 0 then
- UI.Window.write(self, x, y, text, bg)
- end
- end
- function UI.ViewportWindow:setPage(pageNo)
- self:setOffset((pageNo-1) * self.vpHeight + 1)
- end
- function UI.ViewportWindow:setOffset(offset)
- local newOffset = math.max(0, math.min(math.max(0, offset), self.vpHeight-self.height))
- if newOffset ~= self.offset then
- self.offset = newOffset
- self:clear()
- self:draw()
- return true
- end
- return false
- end
- function UI.ViewportWindow:eventHandler(event)
- if ch == 'j' or ch == 'down' then
- return self:setOffset(self.offset + 1)
- elseif ch == 'k' or ch == 'up' then
- return self:setOffset(self.offset - 1)
- elseif ch == 'home' then
- self:setOffset(0)
- elseif ch == 'end' then
- return self:setOffset(self.height-self.vpHeight)
- elseif ch == 'h' then
- return self:setPage(
- math.floor((self.offset - self.vpHeight) / self.vpHeight))
- elseif ch == 'l' then
- return self:setPage(
- math.floor((self.offset + self.vpHeight) / self.vpHeight) + 1)
- else
- return false
- end
- return true
- end
- --[[-- ScrollingText --]]--
- UI.ScrollingText = class.class(UI.Window)
- function UI.ScrollingText:init(args)
- local defaults = {
- UIElement = 'ScrollingText',
- x = 1,
- y = 1,
- backgroundColor = colors.black,
- --height = console.height,
- --width = console.width,
- buffer = { }
- }
- UI.setProperties(defaults, args)
- UI.Window.init(self, defaults)
- end
- function UI.ScrollingText:write(text)
- if #self.buffer+1 >= self.height then
- table.remove(self.buffer, 1)
- end
- table.insert(self.buffer, text)
- self:draw()
- end
- function UI.ScrollingText:clear()
- self.buffer = { }
- self.parent:clearArea(self.x, self.y, self.width, self.height, self.backgroundColor)
- end
- function UI.ScrollingText:draw()
- for k,text in ipairs(self.buffer) do
- self.parent:write(self.x, self.y + k - 1, widthify(text, self.width), self.backgroundColor)
- end
- end
- --[[-- TitleBar --]]--
- UI.TitleBar = class.class(UI.Window)
- function UI.TitleBar:init(args)
- local defaults = {
- UIElement = 'TitleBar',
- height = 1,
- backgroundColor = colors.brown,
- title = ''
- }
- UI.setProperties(defaults, args)
- UI.Window.init(self, defaults)
- end
- function UI.TitleBar:draw()
- self:clearArea(1, 1, self.width, 1, self.backgroundColor)
- local centered = math.ceil((self.width - #self.title) / 2)
- self:write(1 + centered, 1, self.title, self.backgroundColor)
- if self.previousPage then
- self:write(self.width, 1, '*', self.backgroundColor, colors.black)
- end
- --self:write(self.width-1, 1, '?', self.backgroundColor)
- end
- function UI.TitleBar:eventHandler(event)
- if event.type == 'mouse_click' then
- if self.previousPage and event.x == self.width then
- if type(self.previousPage) == 'string' or
- type(self.previousPage) == 'table' then
- UI.pager:setPage(self.previousPage)
- else
- UI.pager:setPreviousPage()
- end
- return true
- end
- end
- end
- --[[-- MenuBar --]]--
- UI.MenuBar = class.class(UI.Window)
- function UI.MenuBar:init(args)
- local defaults = {
- UIElement = 'MenuBar',
- buttons = { },
- height = 1,
- backgroundColor = colors.lightBlue,
- title = ''
- }
- UI.setProperties(defaults, args)
- UI.Window.init(self, defaults)
- if not self.children then
- self.children = { }
- end
- local x = 1
- for _,button in pairs(self.buttons) do
- local buttonProperties = {
- x = x,
- width = #button.text + 2,
- backgroundColor = colors.lightBlue,
- textColor = colors.black
- }
- x = x + buttonProperties.width
- UI.setProperties(buttonProperties, button)
- local child = UI.Button(buttonProperties)
- child.parent = self
- table.insert(self.children, child)
- end
- end
- --[[-- StatusBar --]]--
- UI.StatusBar = class.class(UI.GridLayout)
- function UI.StatusBar:init(args)
- local defaults = {
- UIElement = 'StatusBar',
- backgroundColor = colors.gray,
- columns = {
- { '', 'status', 10 },
- },
- values = { },
- status = { status = '' }
- }
- UI.setProperties(defaults, args)
- UI.GridLayout.init(self, defaults)
- self:setStatus(self.status)
- end
- function UI.StatusBar:setParent()
- UI.GridLayout.setParent(self)
- self.y = self.height
- if #self.columns == 1 then
- self.columns[1][3] = self.width
- end
- end
- function UI.StatusBar:setStatus(status)
- if type(status) == 'string' then
- self.values[1] = { status = status }
- else
- self.values[1] = status
- end
- end
- function UI.StatusBar:setValue(name, value)
- self.status[name] = value
- end
- function UI.StatusBar:getValue(name)
- return self.status[name]
- end
- function UI.StatusBar:timedStatus(status, timeout)
- timeout = timeout or 3
- self:write(2, 1, widthify(status, self.width-2), self.backgroundColor)
- Event.addNamedTimer('statusTimer', timeout, false, function()
- -- fix someday
- if self.parent.enabled then
- self:draw()
- end
- end)
- end
- function UI.StatusBar:getColumnWidth(name)
- for _,v in pairs(self.columns) do
- if v[2] == name then
- return v[3]
- end
- end
- end
- function UI.StatusBar:setColumnWidth(name, width)
- for _,v in pairs(self.columns) do
- if v[2] == name then
- v[3] = width
- break
- end
- end
- end
- --[[-- ProgressBar --]]--
- UI.ProgressBar = class.class(UI.Window)
- function UI.ProgressBar:init(args)
- local defaults = {
- UIElement = 'ProgressBar',
- progressColor = colors.lime,
- backgroundColor = colors.gray,
- height = 1,
- progress = 0
- }
- UI.setProperties(self, defaults)
- UI.Window.init(self, args)
- end
- function UI.ProgressBar:draw()
- local progressWidth = math.ceil(self.progress / 100 * self.width)
- if progressWidth > 0 then
- self.parent:write(self.x, self.y, string.rep(' ', progressWidth), self.progressColor)
- end
- local x = self.x + progressWidth
- progressWidth = self.width - progressWidth
- if progressWidth > 0 then
- self.parent:write(x, self.y, string.rep(' ', progressWidth), self.backgroundColor)
- end
- end
- function UI.ProgressBar:setProgress(progress)
- self.progress = progress
- end
- --[[-- VerticalMeter --]]--
- UI.VerticalMeter = class.class(UI.Window)
- function UI.VerticalMeter:init(args)
- local defaults = {
- UIElement = 'VerticalMeter',
- meterColor = colors.lime,
- height = 1,
- percent = 0
- }
- UI.setProperties(defaults, args)
- UI.Window.init(self, defaults)
- end
- function UI.VerticalMeter:draw()
- local height = self.height - math.ceil(self.percent / 100 * self.height)
- local filler = string.rep(' ', self.width)
- for i = 1, height do
- self:write(1, i, filler, self.backgroundColor)
- end
- for i = height+1, self.height do
- self:write(1, i, filler, self.meterColor)
- end
- end
- function UI.VerticalMeter:setPercent(percent)
- self.percent = percent
- end
- --[[-- Button --]]--
- UI.Button = class.class(UI.Window)
- function UI.Button:init(args)
- local defaults = {
- UIElement = 'Button',
- text = 'button',
- focused = false,
- backgroundColor = colors.gray,
- backgroundFocusColor = colors.green,
- height = 1,
- width = 8,
- event = 'button_press'
- }
- UI.setProperties(defaults, args)
- UI.Window.init(self, defaults)
- end
- function UI.Button:draw()
- local bg = self.backgroundColor
- local ind = ' '
- if self.focused then
- bg = self.backgroundFocusColor
- ind = '>'
- end
- self:clear(bg)
- local text = ind .. self.text .. ' '
- self:centeredWrite(1 + math.floor(self.height / 2), text, bg)
- end
- function UI.Button:focus()
- self:draw()
- end
- function UI.Button:eventHandler(event)
- if (event.type == 'key' and event.key == 'enter') or
- event.type == 'mouse_click' then
- self:emit({ type = self.event, button = self })
- return true
- end
- return false
- end
- --[[-- TextEntry --]]--
- UI.TextEntry = class.class(UI.Window)
- function UI.TextEntry:init(args)
- local defaults = {
- UIElement = 'TextEntry',
- value = '',
- type = 'string',
- focused = false,
- backgroundColor = colors.lightGray,
- backgroundFocusColor = colors.green,
- height = 1,
- width = 8,
- limit = 6
- }
- UI.setProperties(defaults, args)
- UI.Window.init(self, defaults)
- self.value = tostring(self.value)
- end
- function UI.TextEntry:setParent()
- UI.Window.setParent(self)
- if self.limit + 2 > self.width then
- self.limit = self.width - 2
- end
- end
- function UI.TextEntry:draw()
- local bg = self.backgroundColor
- if self.focused then
- bg = self.backgroundFocusColor
- end
- self:write(1, 1, ' ' .. widthify(self.value, self.limit) .. ' ', bg)
- if self.focused then
- self:updateCursor()
- end
- end
- function UI.TextEntry:updateCursor()
- if not self.pos then
- self.pos = #tostring(self.value)
- elseif self.pos > #tostring(self.value) then
- self.pos = #tostring(self.value)
- end
- self:setCursorPos(self.pos+2, 1)
- end
- function UI.TextEntry:focus()
- self:draw()
- if self.focused then
- self:setCursorBlink(true)
- else
- self:setCursorBlink(false)
- end
- end
- --[[
- A few lines below from theoriginalbit
- http://www.computercraft.info/forums2/index.php?/topic/16070-read-and-limit-length-of-the-input-field/
- --]]
- function UI.TextEntry:eventHandler(event)
- if event.type == 'key' then
- local ch = event.key
- if ch == 'left' then
- if self.pos > 0 then
- self.pos = math.max(self.pos-1, 0)
- self:updateCursor()
- end
- elseif ch == 'right' then
- local input = tostring(self.value)
- if self.pos < #input then
- self.pos = math.min(self.pos+1, #input)
- self:updateCursor()
- end
- elseif ch == 'home' then
- self.pos = 0
- self:updateCursor()
- elseif ch == 'end' then
- self.pos = #tostring(self.value)
- self:updateCursor()
- elseif ch == 'backspace' then
- if self.pos > 0 then
- local input = tostring(self.value)
- self.value = input:sub(1, self.pos-1) .. input:sub(self.pos+1)
- self.pos = self.pos - 1
- self:draw()
- self:updateCursor()
- self:emit({ type = 'text_change', text = self.value })
- end
- elseif ch == 'delete' then
- local input = tostring(self.value)
- if self.pos < #input then
- self.value = input:sub(1, self.pos) .. input:sub(self.pos+2)
- self:draw()
- self:updateCursor()
- self:emit({ type = 'text_change', text = self.value })
- end
- elseif #ch == 1 then
- local input = tostring(self.value)
- if #input < self.limit then
- self.value = input:sub(1, self.pos) .. ch .. input:sub(self.pos+1)
- self.pos = self.pos + 1
- self:draw()
- self:updateCursor()
- self:emit({ type = 'text_change', text = self.value })
- end
- else
- return false
- end
- return true
- end
- return false
- end
- --[[-- Chooser --]]--
- UI.Chooser = class.class(UI.Window)
- function UI.Chooser:init(args)
- local defaults = {
- UIElement = 'Chooser',
- choices = { },
- nochoice = 'Select',
- backgroundColor = colors.lightGray,
- backgroundFocusColor = colors.green,
- height = 1
- }
- UI.setProperties(defaults, args)
- UI.Window.init(self, defaults)
- end
- function UI.Chooser:setParent()
- if not self.width then
- self.width = 1
- for _,v in pairs(self.choices) do
- if #v.name > self.width then
- self.width = #v.name
- end
- end
- self.width = self.width + 4
- end
- UI.Window.setParent(self)
- end
- function UI.Chooser:draw()
- local bg = self.backgroundColor
- if self.focused then
- bg = self.backgroundFocusColor
- end
- local choice = Util.find(self.choices, 'value', self.value)
- local value = self.nochoice
- if choice then
- value = choice.name
- end
- self:write(1, 1, '<', bg, colors.black)
- self:write(2, 1, ' ' .. widthify(value, self.width-4) .. ' ', bg)
- self:write(self.width, 1, '>', bg, colors.black)
- end
- function UI.Chooser:focus()
- self:draw()
- end
- function UI.Chooser:eventHandler(event)
- if event.type == 'key' then
- if event.key == 'right' or event.key == 'space' then
- local choice,k = Util.find(self.choices, 'value', self.value)
- if k and k < #self.choices then
- self.value = self.choices[k+1].value
- else
- self.value = self.choices[1].value
- end
- self:emit({ type = 'choice_change', value = self.value })
- self:draw()
- return true
- elseif event.key == 'left' then
- local choice,k = Util.find(self.choices, 'value', self.value)
- if k and k > 1 then
- self.value = self.choices[k-1].value
- else
- self.value = self.choices[#self.choices].value
- end
- self:emit({ type = 'choice_change', value = self.value })
- self:draw()
- return true
- end
- elseif event.type == 'mouse_click' then
- if event.x == 1 then
- self:emit({ type = 'key', key = 'left' })
- return true
- elseif event.x == self.width then
- self:emit({ type = 'key', key = 'right' })
- return true
- end
- end
- end
- --[[-- Text --]]--
- UI.Text = class.class(UI.Window)
- function UI.Text:init(args)
- local defaults = {
- UIElement = 'Text',
- value = '',
- height = 1
- }
- UI.setProperties(defaults, args)
- UI.Window.init(self, defaults)
- end
- function UI.Text:setParent()
- if not self.width then
- self.width = #self.value
- end
- UI.Window.setParent(self)
- end
- function UI.Text:draw()
- local value = self.value or ''
- self:write(1, 1, widthify(value, self.width), self.backgroundColor)
- end
- --[[-- Form --]]--
- UI.Form = class.class(UI.Window)
- UI.Form.D = { -- display
- static = UI.Text,
- entry = UI.TextEntry,
- chooser = UI.Chooser,
- button = UI.Button
- }
- UI.Form.V = { -- validation
- number = function(value)
- return type(value) == 'number'
- end
- }
- UI.Form.T = { -- data types
- number = function(value)
- return tonumber(value)
- end
- }
- function UI.Form:init(args)
- local defaults = {
- UIElement = 'Form',
- values = {},
- fields = {},
- columns = {
- { 'Name', 'name', 20 },
- { 'Values', 'value', 20 }
- },
- x = 1,
- y = 1,
- labelWidth = 20,
- accept = function() end,
- cancel = function() end
- }
- UI.setProperties(defaults, args)
- UI.Window.init(self, defaults)
- self:initChildren(self.values)
- end
- function UI.Form:setValues(values)
- self.values = values
- for k,child in pairs(self.children) do
- if child.key then
- child.value = self.values[child.key]
- if not child.value then
- child.value = ''
- end
- end
- end
- end
- function UI.Form:initChildren(values)
- self.values = values
- if not self.children then
- self.children = { }
- end
- for k,field in pairs(self.fields) do
- if field.label then
- self['label_' .. k] = UI.Text({
- x = 1,
- y = k,
- width = #field.label,
- value = field.label
- })
- end
- local value
- if field.key then
- value = self.values[field.key]
- end
- if not value then
- value = ''
- end
- value = tostring(value)
- local width = #value
- if field.limit then
- width = field.limit + 2
- end
- local fieldProperties = {
- x = self.labelWidth + 2,
- y = k,
- width = width,
- value = value
- }
- UI.setProperties(fieldProperties, field)
- local child = field.display(fieldProperties)
- child.parent = self
- table.insert(self.children, child)
- end
- end
- function UI.Form:eventHandler(event)
- if event.type == 'accept' then
- for _,child in pairs(self.children) do
- if child.key then
- self.values[child.key] = child.value
- end
- end
- return false
- end
- return false
- end
- --[[-- Dialog --]]--
- UI.Dialog = class.class(UI.Page)
- function UI.Dialog:init(args)
- local defaults = {
- x = 7,
- y = 4,
- width = UI.term.width-11,
- height = 7,
- backgroundColor = colors.lightBlue,
- titleBar = UI.TitleBar({ previousPage = true }),
- acceptButton = UI.Button({
- text = 'Accept',
- event = 'accept',
- x = 5,
- y = 5
- }),
- cancelButton = UI.Button({
- text = 'Cancel',
- event = 'cancel',
- x = 17,
- y = 5
- }),
- statusBar = UI.StatusBar(),
- }
- UI.setProperties(defaults, args)
- UI.Page.init(self, defaults)
- end
- function UI.Dialog:eventHandler(event)
- if event.type == 'cancel' then
- UI.pager:setPreviousPage()
- end
- return UI.Page.eventHandler(self, event)
- end
- --[[-- Spinner --]]--
- UI.Spinner = class.class()
- function UI.Spinner:init(args)
- local defaults = {
- UIElement = 'Spinner',
- timeout = .095,
- x = 1,
- y = 1,
- c = os.clock(),
- spinIndex = 0,
- spinSymbols = { '-', '/', '|', '\\' }
- }
- defaults.x, defaults.y = term.getCursorPos()
- defaults.startX = defaults.x
- defaults.startY = defaults.y
- UI.setProperties(self, defaults)
- UI.setProperties(self, args)
- end
- function UI.Spinner:spin(text)
- local cc = os.clock()
- if cc > self.c + self.timeout then
- term.setCursorPos(self.x, self.y)
- local str = self.spinSymbols[self.spinIndex % #self.spinSymbols + 1]
- if text then
- str = str .. ' ' .. text
- end
- term.write(str)
- self.spinIndex = self.spinIndex + 1
- self.c = cc
- os.sleep(0)
- end
- end
- function UI.Spinner:stop(text)
- term.setCursorPos(self.x, self.y)
- local str = string.rep(' ', #self.spinSymbols)
- if text then
- str = str .. ' ' .. text
- end
- term.write(str)
- term.setCursorPos(self.startX, self.startY)
- end
- --[[-- Table Viewer --]]--
- function UI.displayTable(t, title)
- local resultPath = { }
- local resultsPage = UI.Page({
- parent = UI.term,
- titleBar = UI.TitleBar(),
- grid = UI.ScrollingGrid({
- columns = {
- { 'Name', 'name', 10 },
- { 'Value', 'value', 10 }
- },
- sortColumn = 'name',
- pageSize = UI.term.height - 2,
- y = 2,
- width = UI.term.width,
- height = UI.term.height - 3,
- autospace = true
- }),
- })
- function resultsPage:setResult(result, title)
- local t = { }
- if type(result) == 'table' then
- for k,v in pairs(result) do
- local entry = {
- name = k,
- value = tostring(v)
- }
- if type(v) == 'table' then
- if Util.size(v) == 0 then
- entry.value = 'table: (empty)'
- else
- entry.value = 'table'
- entry.table = v
- end
- end
- table.insert(t, entry)
- end
- else
- table.insert(t, {
- name = 'result',
- value = tostring(result)
- })
- end
- self.grid.sortColumn = 'Name'
- self.grid.columns = {
- { 'Name', 'name', 10 },
- { 'Value', 'value', 10 }
- }
- self.grid.t = t
- self.grid:adjustWidth()
- if title then
- self.titleBar.title = title
- end
- end
- function resultsPage.grid:flatten()
- self.columns = { }
- local _,first = next(self.t)
- for k in pairs(first.table) do
- table.insert(self.columns, {
- k, k, 1
- })
- end
- local t = { }
- for k,v in pairs(self.t) do
- v.table.__key = v.name
- t[v.name] = v.table
- end
- self.t = t
- self.sortColumn = '__key'
- self:adjustWidth()
- self:draw()
- end
- function resultsPage.grid:eventHandler(event)
- if event.type == 'key' then
- local ch = event.key
- if ch == 'enter' or ch == 'l' then
- local nameValue = self:getSelected()
- if nameValue.table then
- if Util.size(nameValue.table) > 0 then
- table.insert(resultPath, self.t)
- resultsPage:setResult(nameValue.table)
- self:setIndex(1)
- self:draw()
- end
- end
- return true
- elseif ch == 'f' then
- self:flatten()
- elseif ch == 'h' then
- if #resultPath > 0 then
- self.t = table.remove(resultPath)
- self.columns = {
- { 'Name', 'name', 10 },
- { 'Value', 'value', 10 }
- }
- self.sortColumn = 'Name'
- self:adjustWidth()
- self:draw()
- else
- UI.pager:setPreviousPage()
- end
- return true
- elseif ch == 'q' then
- UI.pager:setPreviousPage()
- return true
- end
- end
- return UI.Grid.eventHandler(self, event)
- end
- resultsPage:setResult(t, title or 'Table Viewer')
- UI.pager:setPage(resultsPage)
- end
- --Import
- _G.TableDB = class.class()
- function TableDB:init(args)
- local defaults = {
- fileName = '',
- dirty = false,
- data = { },
- tabledef = { },
- }
- UI.setProperties(defaults, args)
- UI.setProperties(self, defaults)
- end
- function TableDB:load()
- local table = Util.readTable(self.fileName)
- if table then
- self.data = table.data
- self.tabledef = table.tabledef
- end
- end
- function TableDB:add(key, entry)
- if type(key) == 'table' then
- key = table.concat(key, ':')
- end
- self.data[key] = entry
- self.dirty = true
- end
- function TableDB:get(key)
- if type(key) == 'table' then
- key = table.concat(key, ':')
- end
- return self.data[key]
- end
- function TableDB:remove(key)
- self.data[key] = nil
- self.dirty = true
- end
- function TableDB:flush()
- if self.dirty then
- Util.writeTable(self.fileName, {
- tabledef = self.tabledef,
- data = self.data,
- })
- self.dirty = false
- end
- end
- if turtle then
- --Import
- --[[
- Modes:
- normal - oob functionality
- pathfind - goes around blocks/mobs
- destructive - destroys blocks
- friendly - destructive and creates a 2 block walking passage (not implemented)
- Dig strategies:
- none - does not dig or kill mobs
- normal - digs and kills mobs
- wasteful - digs and drops mined blocks and kills mobs
- cautious - digs and kills mobs but will not destroy other turtles
- --]]
- _G.TL2 = { }
- TL2.actions = {
- up = {
- detect = turtle.detectUp,
- dig = turtle.digUp,
- move = turtle.up,
- attack = turtle.attackUp,
- place = turtle.placeUp,
- suck = turtle.suckUp,
- compare = turtle.compareUp,
- side = 'top'
- },
- down = {
- detect = turtle.detectDown,
- dig = turtle.digDown,
- move = turtle.down,
- attack = turtle.attackDown,
- place = turtle.placeDown,
- suck = turtle.suckDown,
- compare = turtle.compareDown,
- side = 'bottom'
- },
- forward = {
- detect = turtle.detect,
- dig = turtle.dig,
- move = turtle.forward,
- attack = turtle.attack,
- place = turtle.place,
- suck = turtle.suck,
- compare = turtle.compare,
- side = 'front'
- }
- }
- TL2.headings = {
- [ 0 ] = { xd = 1, yd = 0, zd = 0, heading = 0 }, -- east
- [ 1 ] = { xd = 0, yd = 1, zd = 0, heading = 1 }, -- south
- [ 2 ] = { xd = -1, yd = 0, zd = 0, heading = 2 }, -- west
- [ 3 ] = { xd = 0, yd = -1, zd = 0, heading = 3 }, -- north
- [ 4 ] = { xd = 0, yd = 0, zd = 1, heading = 4 }, -- up
- [ 5 ] = { xd = 0, yd = 0, zd = -1, heading = 5 } -- down
- }
- TL2.namedHeadings = {
- east = 0,
- south = 1,
- west = 2,
- north = 3,
- up = 4,
- down = 5
- }
- _G.Point = { }
- -- used mainly to extract point specific info from a table
- function Point.create(pt)
- return { x = pt.x, y = pt.y, z = pt.z }
- end
- function Point.subtract(a, b)
- a.x = a.x - b.x
- a.y = a.y - b.y
- a.z = a.z - b.z
- end
- function Point.tostring(pt)
- local str = string.format('x:%d y:%d z:%d', pt.x, pt.y, pt.z)
- if pt.heading then
- str = str .. ' heading:' .. pt.heading
- end
- return str
- end
- function Point.calculateDistance(a, b)
- if b.z then
- return math.max(a.x, b.x) - math.min(a.x, b.x) +
- math.max(a.y, b.y) - math.min(a.y, b.y) +
- math.max(a.z, b.z) - math.min(a.z, b.z)
- else
- return math.max(a.x, b.x) - math.min(a.x, b.x) +
- math.max(a.y, b.y) - math.min(a.y, b.y)
- end
- end
- -- a better distance calculation - returns # moves + turns
- -- calculate distance to location including turns
- -- also returns the resuling heading
- function Point.calculateMoves(pta, ptb, distance)
- local heading = pta.heading
- local moves = distance or Point.calculateDistance(pta, ptb)
- if (pta.heading % 2) == 0 and pta.y ~= ptb.y then
- moves = moves + 1
- if ptb.heading and (ptb.heading % 2 == 1) then
- heading = ptb.heading
- elseif ptb.y > pta.y then
- heading = 1
- else
- heading = 3
- end
- elseif (pta.heading % 2) == 1 and pta.x ~= ptb.x then
- moves = moves + 1
- if ptb.heading and (ptb.heading % 2 == 0) then
- heading = ptb.heading
- elseif ptb.x > pta.x then
- heading = 0
- else
- heading = 2
- end
- end
- if ptb.heading then
- if heading ~= ptb.heading then
- moves = moves + TL2.calculateTurns(heading, ptb.heading)
- --(math.max(heading, ptb.heading) + 4 - math.min(heading, ptb.heading)) % 4
- heading = ptb.heading
- end
- end
- return moves, heading
- end
- -- deprecated
- function TL2.createPoint(pt)
- return Point.create(pt)
- end
- function TL2.pointToBox(pt, width, length, height)
- return { ax = pt.x,
- ay = pt.y,
- az = pt.z,
- bx = pt.x + width - 1,
- by = pt.y + length - 1,
- bz = pt.z + height - 1
- }
- end
- function TL2.pointInBox(pt, box)
- return pt.x >= box.ax and
- pt.y >= box.ay and
- pt.x <= box.bx and
- pt.y <= box.by
- end
- function TL2.boxContain(boundingBox, containedBox)
- local shiftX = boundingBox.ax - containedBox.ax
- if shiftX > 0 then
- containedBox.ax = containedBox.ax + shiftX
- containedBox.bx = containedBox.bx + shiftX
- end
- local shiftY = boundingBox.ay - containedBox.ay
- if shiftY > 0 then
- containedBox.ay = containedBox.ay + shiftY
- containedBox.by = containedBox.by + shiftY
- end
- shiftX = boundingBox.bx - containedBox.bx
- if shiftX < 0 then
- containedBox.ax = containedBox.ax + shiftX
- containedBox.bx = containedBox.bx + shiftX
- end
- shiftY = boundingBox.by - containedBox.by
- if shiftY < 0 then
- containedBox.ay = containedBox.ay + shiftY
- containedBox.by = containedBox.by + shiftY
- end
- end
- function TL2.calculateTurns(ih, oh)
- if ih == oh then
- return 0
- end
- if (ih % 2) == (oh % 2) then
- return 2
- end
- return 1
- end
- -- deprecated
- function TL2.calculateDistance(a, b)
- return Point.calculateDistance(a, b)
- end
- function TL2.calculateHeadingTowards(startPt, endPt, heading)
- local xo = endPt.x - startPt.x
- local yo = endPt.y - startPt.y
- if heading == 0 and xo > 0 or
- heading == 2 and xo < 0 or
- heading == 1 and yo > 0 or
- heading == 3 and yo < 0 then
- -- maintain current heading
- return heading
- elseif heading == 0 and yo > 0 or
- heading == 2 and yo < 0 or
- heading == 1 and xo < 0 or
- heading == 3 and xo > 0 then
- -- right
- return (heading + 1) % 4
- elseif heading == 0 and yo < 0 or
- heading == 2 and yo > 0 or
- heading == 1 and xo < 0 or
- heading == 3 and xo > 0 then
- -- left
- return (heading + 1) % 4
- elseif yo == 0 and xo ~= 0 or
- xo == 0 and yo ~= 0 then
- -- behind
- return (heading + 2) % 4
- elseif endPt.z > startPt.z then
- -- up
- return 4
- elseif endPt.z < startPt.z then
- -- down
- return 5
- end
- return heading
- end
- local function _attack(action)
- if action.attack() then
- Util.tryTimed(4,
- function()
- -- keep trying until attack fails
- return not action.attack()
- end)
- return true
- end
- return false
- end
- local modes = {
- normal = function(action)
- return action.move()
- end,
- destructive = function(action, digStrategy)
- while not action.move() do
- if not _attack(action) then
- if not digStrategy(action) then
- return false
- end
- end
- Logger.log('turtle', 'destructive move retry: ')
- end
- return true
- end,
- }
- TL2.digStrategies = {
- none = function(action)
- return false
- end,
- normal = function(action)
- if action.dig() then
- return true
- end
- if not action.attack() then
- return false
- end
- for i = 1, 50 do
- if not action.attack() then
- break
- end
- end
- return true
- end,
- cautious = function(action)
- if not TL2.isTurtleAt(action.side) then
- return action.dig()
- end
- os.sleep(.5)
- return not action.detect()
- end,
- wasteful = function(action)
- -- why is there no method to get current slot ?
- local slots = TL2.getSlots()
- -- code below should be done -- only reconcile if no empty slot
- -- taking the cautious approach for now
- --if not selectOpenSlot() then
- --return false
- --end
- if action.detect() and action.dig() then
- TL2.reconcileSlots(slots)
- return true
- end
- TL2.reconcileSlots(slots)
- return false
- end
- }
- local state = {
- x = 0,
- y = 0,
- z = 0,
- slot = 1, -- must track slot since there is no method to determine current slot
- -- not relying on this for now (but will track)
- heading = 0,
- maxRetries = 100,
- status = 'idle',
- mode = modes.normal,
- dig = TL2.digStrategies.normal
- }
- local memory = {
- locations = {},
- blocks = {}
- }
- function TL2.getState()
- return state
- end
- function TL2.getStatus()
- return state.status
- end
- function TL2.setStatus(status)
- state.status = status
- end
- function TL2.getMemory()
- return memory
- end
- function TL2.select(slot)
- state.slot = slot
- turtle.select(slot)
- end
- function TL2.forward()
- if not state.mode(TL2.actions.forward, state.dig) then
- return false
- end
- state.x = state.x + TL2.headings[state.heading].xd
- state.y = state.y + TL2.headings[state.heading].yd
- return true
- end
- function TL2.up()
- if state.mode(TL2.actions.up, state.dig) then
- state.z = state.z + 1
- return true
- end
- return false
- end
- function TL2.down()
- if not state.mode(TL2.actions.down, state.dig) then
- return false
- end
- state.z = state.z - 1
- return true
- end
- function TL2.back()
- if not turtle.back() then
- return false
- end
- state.x = state.x - TL2.headings[state.heading].xd
- state.y = state.y - TL2.headings[state.heading].yd
- return true
- end
- function TL2.dig()
- return state.dig(TL2.actions.forward)
- end
- function TL2.digUp()
- return state.dig(TL2.actions.up)
- end
- function TL2.digDown()
- return state.dig(TL2.actions.down)
- end
- function TL2.attack()
- _attack(TL2.actions.forward)
- end
- function TL2.attackUp()
- _attack(TL2.actions.up)
- end
- function TL2.attackDown()
- _attack(TL2.actions.down)
- end
- local complexActions = {
- up = {
- detect = turtle.detectUp,
- dig = TL2.digUp,
- move = TL2.up,
- attack = TL2.attackUp,
- place = turtle.placeUp,
- side = 'top'
- },
- down = {
- detect = turtle.detectDown,
- dig = TL2.digDown,
- move = TL2.down,
- attack = TL2.attackDown,
- place = turtle.placeDown,
- side = 'bottom'
- },
- forward = {
- detect = turtle.detect,
- dig = TL2.dig,
- move = TL2.forward,
- attack = TL2.attack,
- place = turtle.place,
- side = 'front'
- }
- }
- local function _place(action, slot)
- return Util.tryTimed(5,
- function()
- if action.detect() then
- action.dig()
- end
- if slot then
- TL2.select(slot)
- end
- if action.place() then
- return true
- end
- _attack(action)
- end)
- end
- function TL2.place(slot)
- return _place(complexActions.forward, slot)
- end
- function TL2.placeUp(slot)
- return _place(complexActions.up, slot)
- end
- function TL2.placeDown(slot)
- return _place(complexActions.down, slot)
- end
- function TL2.getModes()
- return modes
- end
- function TL2.getMode()
- return state.mode
- end
- function TL2.setMode(mode)
- if not modes[mode] then
- error('TL2.setMode: invalid mode', 2)
- end
- state.mode = modes[mode]
- end
- function TL2.setDigStrategy(digStrategy)
- if not TL2.digStrategies[digStrategy] then
- error('TL2.setDigStrategy: invalid strategy', 2)
- end
- state.dig = TL2.digStrategies[digStrategy]
- end
- function TL2.reset()
- state.gxOff = state.gxOff - state.x
- state.gyOff = state.gyOff - state.y
- state.gzOff = state.gzOff - state.z
- state.x = 0
- state.y = 0
- state.z = 0
- end
- function TL2.isTurtleAt(side)
- local sideType = peripheral.getType(side)
- return sideType and sideType == 'turtle'
- end
- function TL2.saveLocation()
- return Util.shallowCopy(state)
- end
- function TL2.getNamedLocation(name)
- return memory.locations[name]
- end
- function TL2.gotoNamedLocation(name)
- local nl = memory.locations[name]
- if nl then
- return TL2.goto(nl.x, nl.y, nl.z, nl.heading)
- end
- end
- function TL2.getStoredPoint(name)
- return Util.readTable(name .. '.pt')
- end
- function TL2.gotoStoredPoint(name)
- local pt = TL2.getStoredPoint(name)
- if pt then
- return TL2.gotoPoint(pt)
- end
- end
- function TL2.storePoint(name, pt)
- Util.writeTable(name .. '.pt', pt)
- end
- function TL2.storeCurrentPoint(name)
- local ray = TL2.getPoint()
- TL2.storePoint(name, ray)
- end
- function TL2.saveNamedLocation(name, x, y, z, heading)
- if x then
- memory.locations[name] = {
- x = x,
- y = y,
- z = z,
- heading = heading
- }
- else
- memory.locations[name] = {
- x = state.x,
- y = state.y,
- z = state.z,
- heading = state.heading
- }
- end
- end
- function TL2.normalizeCoords(x, y, z, heading)
- return
- x - state.gxOff,
- y - state.gyOff,
- z - state.gzOff,
- heading
- end
- function TL2.gotoLocation(nloc)
- if nloc.gps then
- return TL2.goto(
- nloc.x - state.gxOff,
- nloc.y - state.gyOff,
- nloc.z - state.gzOff,
- nloc.heading)
- else
- return TL2.goto(nloc.x, nloc.y, nloc.z, nloc.heading)
- end
- end
- function TL2.turnRight()
- TL2.setHeading(state.heading + 1)
- end
- function TL2.turnLeft()
- TL2.setHeading(state.heading - 1)
- end
- function TL2.turnAround()
- TL2.setHeading(state.heading + 2)
- end
- function TL2.getHeadingInfo(heading)
- if heading and type(heading) == 'string' then
- heading = TL2.namedHeadings[heading]
- end
- heading = heading or state.heading
- return TL2.headings[heading]
- end
- function TL2.setNamedHeading(headingName)
- local heading = TL2.namedHeadings[headingName]
- if heading then
- TL2.setHeading(heading)
- end
- return false
- end
- function TL2.getHeading()
- return state.heading
- end
- function TL2.setHeading(heading)
- if not heading then
- return
- end
- if heading ~= state.heading then
- while heading < state.heading do
- heading = heading + 4
- end
- if heading - state.heading == 3 then
- turtle.turnLeft()
- state.heading = state.heading - 1
- else
- turns = heading - state.heading
- while turns > 0 do
- turns = turns - 1
- state.heading = state.heading + 1
- turtle.turnRight()
- end
- end
- if state.heading > 3 then
- state.heading = state.heading - 4
- elseif state.heading < 0 then
- state.heading = state.heading + 4
- end
- end
- end
- function TL2.gotoPoint(pt)
- return TL2.goto(pt.x, pt.y, pt.z, pt.heading)
- end
- function TL2.gotoX2(dx)
- local direction = dx - state.x
- local move
- if direction > 0 and state.heading == 0 or
- direction < 0 and state.heading == 2 then
- move = TL2.forward
- else
- move = TL2.back
- end
- repeat
- if not move() then
- return false
- end
- until state.x == dx
- return true
- end
- function TL2.gotoY2(dy)
- local direction = dy - state.y
- local move
- if direction > 0 and state.heading == 1 or
- direction < 0 and state.heading == 3 then
- move = TL2.forward
- else
- move = TL2.back
- end
- repeat
- if not move() then
- return false
- end
- until state.y == dy
- return true
- end
- -- go backwards - turning around if necessary to fight mobs
- function TL2.goback()
- local hi = TL2.headings[state.heading]
- return TL2.goto(state.x - hi.xd, state.y - hi.yd, state.z, state.heading)
- end
- function TL2.gotoZlast(pt)
- if TL2.goto(pt.x, pt.y, nil, pt.heading) then
- if TL2.gotoZ(pt.z) then
- TL2.setHeading(pt.heading)
- end
- end
- end
- function TL2.goto(dx, dy, dz, dh)
- if not TL2.goto2(dx, dy, dz, dh) then
- if not TL2.goto3(dx, dy, dz) then
- return false
- end
- end
- TL2.setHeading(dh)
- return true
- end
- -- 1 turn goto (going backwards if possible)
- function TL2.goto2(dx, dy, dz, dh)
- dz = dz or state.z
- local function gx()
- if state.x ~= dx then
- TL2.gotoX2(dx)
- end
- if state.y ~= dy then
- if dh and dh % 2 == 1 then
- TL2.setHeading(dh)
- else
- TL2.headTowardsY(dy)
- end
- end
- end
- local function gy()
- if state.y ~= dy then
- TL2.gotoY2(dy)
- end
- if state.x ~= dx then
- if dh and dh % 2 == 0 then
- TL2.setHeading(dh)
- else
- TL2.headTowardsX(dx)
- end
- end
- end
- repeat
- local x, y
- local z = state.z
- repeat
- x, y = state.x, state.y
- if state.heading % 2 == 0 then
- gx()
- gy()
- else
- gy()
- gx()
- end
- until x == state.x and y == state.y
- if state.z ~= dz then
- TL2.gotoZ(dz)
- end
- if state.x == dx and state.y == dy and state.z == dz then
- return true
- end
- until x == state.x and y == state.y and z == state.z
- return false
- end
- -- fallback goto - will turn around if was previously moving backwards
- function TL2.goto3(dx, dy, dz)
- if TL2.gotoEx(dx, dy, dz) then
- return true
- end
- local moved
- repeat
- local x, y, z = state.x, state.y, state.z
- -- try going the other way
- if (state.heading % 2) == 1 then
- TL2.headTowardsX(dx)
- else
- TL2.headTowardsY(dy)
- end
- if TL2.gotoEx(dx, dy, dz) then
- return true
- end
- if dz then
- TL2.gotoZ(dz)
- end
- moved = x ~= state.x or y ~= state.y or z ~= state.z
- until not moved
- return false
- end
- function TL2.gotoEx(dx, dy, dz)
- -- determine the heading to ensure the least amount of turns
- -- first check is 1 turn needed - remaining require 2 turns
- if state.heading == 0 and state.x <= dx or
- state.heading == 2 and state.x >= dx or
- state.heading == 1 and state.y <= dy or
- state.heading == 3 and state.y >= dy then
- -- maintain current heading
- -- nop
- elseif dy > state.y and state.heading == 0 or
- dy < state.y and state.heading == 2 or
- dx < state.x and state.heading == 1 or
- dx > state.x and state.heading == 3 then
- TL2.turnRight()
- else
- TL2.turnLeft()
- end
- if (state.heading % 2) == 1 then
- if not TL2.gotoY(dy) then return false end
- if not TL2.gotoX(dx) then return false end
- else
- if not TL2.gotoX(dx) then return false end
- if not TL2.gotoY(dy) then return false end
- end
- if dz then
- if not TL2.gotoZ(dz) then return false end
- end
- return true
- end
- function TL2.headTowardsX(dx)
- if state.x ~= dx then
- if state.x > dx then
- TL2.setHeading(2)
- else
- TL2.setHeading(0)
- end
- end
- end
- function TL2.headTowardsY(dy)
- if state.y ~= dy then
- if state.y > dy then
- TL2.setHeading(3)
- else
- TL2.setHeading(1)
- end
- end
- end
- function TL2.headTowards(pt)
- if state.x ~= pt.x then
- TL2.headTowardsX(pt.x)
- else
- TL2.headTowardsY(pt.y)
- end
- end
- function TL2.gotoX(dx)
- TL2.headTowardsX(dx)
- while state.x ~= dx do
- if not TL2.forward() then
- return false
- end
- end
- return true
- end
- function TL2.gotoY(dy)
- TL2.headTowardsY(dy)
- while state.y ~= dy do
- if not TL2.forward() then
- return false
- end
- end
- return true
- end
- function TL2.gotoZ(dz)
- while state.z > dz do
- if not TL2.down() then
- return false
- end
- end
- while state.z < dz do
- if not TL2.up() then
- return false
- end
- end
- return true
- end
- function TL2.emptySlots(dropAction)
- dropAction = dropAction or turtle.drop
- for i = 1, 16 do
- TL2.emptySlot(i, dropAction)
- end
- end
- function TL2.emptySlot(slot, dropAction)
- dropAction = dropAction or turtle.drop
- local count = turtle.getItemCount(slot)
- if count > 0 then
- TL2.select(slot)
- dropAction(count)
- end
- end
- function TL2.getSlots(slots)
- slots = slots or { }
- for i = 1, 16 do
- slots[i] = {
- qty = turtle.getItemCount(i),
- index = i
- }
- end
- return slots
- end
- function TL2.getFilledSlots(startSlot)
- startSlot = startSlot or 1
- local slots = { }
- for i = startSlot, 16 do
- local count = turtle.getItemCount(i)
- if count > 0 then
- slots[i] = {
- qty = turtle.getItemCount(i),
- index = i
- }
- end
- end
- return slots
- end
- function TL2.reconcileSlots(slots, dropAction)
- dropAction = dropAction or turtle.drop
- for _,s in pairs(slots) do
- local qty = turtle.getItemCount(s.index)
- if qty > s.qty then
- TL2.select(s.index)
- dropAction(qty-s.qty, s)
- end
- end
- end
- function TL2.selectSlotWithItems(startSlot)
- startSlot = startSlot or 1
- for i = startSlot, 16 do
- if turtle.getItemCount(i) > 0 then
- TL2.select(i)
- return i
- end
- end
- end
- function TL2.selectOpenSlot(startSlot)
- return TL2.selectSlotWithQuantity(0, startSlot)
- end
- function TL2.selectSlotWithQuantity(qty, startSlot)
- startSlot = startSlot or 1
- for i = startSlot, 16 do
- if turtle.getItemCount(i) == qty then
- TL2.select(i)
- return i
- end
- end
- end
- function TL2.getPoint()
- return { x = state.x, y = state.y, z = state.z, heading = state.heading }
- end
- function TL2.setPoint(pt)
- state.x = pt.x
- state.y = pt.y
- state.z = pt.z
- if pt.heading then
- state.heading = pt.heading
- end
- end
- _G.GPS = { }
- function GPS.locate()
- local pt = { }
- pt.x, pt.z, pt.y = gps.locate(10)
- if pt.x then
- return pt
- end
- end
- function GPS.isAvailable()
- return Util.hasDevice("modem") and GPS.locate()
- end
- function GPS.initialize()
- --[[
- TL2.getState().gps = GPS.getPoint()
- --]]
- end
- function GPS.gotoPoint(pt)
- --[[
- local heading
- if pt.heading then
- heading = (pt.heading + state.gps.heading) % 4
- end
- return TL2.goto(
- pt.x - state.gps.x,
- pt.y - state.gps.y,
- pt.z - state.gps.z,
- heading)
- --]]
- end
- function GPS.getPoint()
- local ray = TL2.getPoint()
- local apt = GPS.locate()
- if not apt then
- error("GPS.getPoint: GPS not available")
- end
- while not TL2.forward() do
- TL2.turnRight()
- if TL2.getHeading() == ray.heading then
- error('GPS.getPoint: Unable to move forward')
- end
- end
- local bpt = GPS.locate()
- if not apt then
- error("No GPS")
- end
- if not TL2.back() then
- error("GPS.getPoint: Unable to move back")
- end
- if apt.x < bpt.x then
- apt.heading = 0
- elseif apt.y < bpt.y then
- apt.heading = 1
- elseif apt.x > bpt.x then
- apt.heading = 2
- else
- apt.heading = 3
- end
- return apt
- end
- function GPS.storeCurrentPoint(name)
- local ray = GPS.getPoint()
- TL2.storePoint(name, ray)
- end
- --[[
- All pathfinding related follows
- b = block
- a = adjacent block
- bb = bounding box
- c = coordinates
- --]]
- local function addAdjacentBlock(blocks, b, dir, bb, a)
- local key = a.x .. ':' .. a.y .. ':' .. a.z
- if b.adj[dir] then
- a = b.adj[dir]
- else
- local _a = blocks[key]
- if _a then
- a = _a
- else
- blocks[key] = a
- end
- end
- local revDir = { 2, 3, 0, 1, 5, 4 }
- b.adj[dir] = a
- a.adj[revDir[dir+1]] = b
- --[[
- -- too much time...
- if dir == 4 and turtle.detectUp() then
- a.blocked = true
- elseif dir == 5 and turtle.detectDown() then
- a.blocked = true
- elseif dir == state.heading and turtle.detect() then
- a.blocked = true
- --]]
- if a.x < bb.ax or a.x > bb.bx or
- a.y < bb.ay or a.y > bb.by or
- a.z < bb.az or a.z > bb.bz then
- a.blocked = true
- end
- end
- local function addAdjacentBlocks(blocks, b, bb)
- if not b.setAdj then
- addAdjacentBlock(blocks, b, 0, bb,
- { x = state.x+1, y = state.y , z = state.z , adj = {} })
- addAdjacentBlock(blocks, b, 1, bb,
- { x = state.x , y = state.y+1, z = state.z , adj = {} })
- addAdjacentBlock(blocks, b, 2, bb,
- { x = state.x-1, y = state.y , z = state.z , adj = {} })
- addAdjacentBlock(blocks, b, 3, bb,
- { x = state.x , y = state.y-1, z = state.z , adj = {} })
- addAdjacentBlock(blocks, b, 4, bb,
- { x = state.x , y = state.y , z = state.z + 1, adj = {} })
- addAdjacentBlock(blocks, b, 5, bb,
- { x = state.x , y = state.y , z = state.z - 1, adj = {} })
- end
- b.setAdj = true
- end
- local function getMovesTo(x, y, z)
- local dest = { x = x, y = y, z = z }
- return calculateMoves(state, dest, state.heading)
- end
- local function calculateAdjacentBlockDistances(b, dest)
- for k,a in pairs(b.adj) do
- if not a.blocked then
- --a.distance = calculateMoves(a, dest, k)
- a.distance = TL2.calculateDistance(a, dest)
- else
- a.distance = 9000
- end
- --print(string.format('%d: %f %d,%d,%d, %s', k, a.distance, a.x, a.y, a.z, tostring(a.blocked)))
- --read()
- end
- -- read()
- end
- local function blockedIn(b)
- for _,a in pairs(b.adj) do
- if not a.blocked then
- return false
- end
- end
- return true
- end
- local function pathfindMove(b)
- for _,a in Util.spairs(b.adj, function(a, b) return a.distance < b.distance end) do
- --print('shortest: ' .. pc(a) .. ' distance: ' .. a.distance)
- if not a.blocked then
- local success = TL2.moveTowardsPoint(a)
- if success then
- return a
- end
- a.blocked = true
- end
- end
- end
- local function rpath(blocks, block, dest, boundingBox)
- addAdjacentBlocks(blocks, block, boundingBox)
- calculateAdjacentBlockDistances(block, dest)
- block.blocked = true
- repeat
- local newBlock = pathfindMove(block)
- if newBlock then
- if state.x == dest.x and state.y == dest.y and state.z == dest.z then
- block.blocked = false
- return true
- end
- --[[
- if goto then return true end
- block = getCurrentBlock() (gets or creates block)
- but this might - will - move us into a block we marked as blockedin
- if block.blocked then
- goto newBlock
- block = getCurrentBlock() (gets or creates block)
- end
- maybe check if we have a clear line of sight to the destination
- ie. keep traveling towards destination building up blocked blocks
- as we encounter them (instead of adding all blocks on the path)
- --]]
- if rpath(blocks, newBlock, dest, boundingBox) then
- block.blocked = false
- return true
- end
- if not TL2.moveTowardsPoint(block) then
- return false
- end
- end
- until blockedIn(block)
- return false
- end
- --[[
- goto will traverse towards the destination until it is blocked
- by something on the x, y, or z coordinate of the destination
- pathfinding will attempt to find a way around the blockage
- goto example:
- . . >-X B D stuck behind block
- . . | . B .
- . . | . . .
- S >-^B. . .
- pathfind example:
- . . >-v B D when goto fails, pathfinding kicks in
- . . | |-B-|
- . . | >---^
- S >-^B. . .
- --]]
- function TL2.pathtofreely(dx, dy, dz, dh)
- local boundingBox = {
- ax = -9000,
- ay = -9000,
- az = -9000,
- bx = 9000,
- by = 9000,
- bz = 9000
- }
- return TL2.pathto(dx, dy, dz, dh, boundingBox)
- end
- local function pathfind(dx, dy, dz, dh, boundingBox)
- local blocks = { } -- memory.blocks
- local dest = { x = dx, y = dy, z = dz }
- local block = { x = state.x, y = state.y, z = state.z, adj = {} }
- local key = block.x .. ':' .. block.y .. ':' .. block.z
- blocks[key] = block
- if rpath(blocks, block, dest, boundingBox) then
- TL2.setHeading(dh)
- return true
- end
- return false
- end
- function TL2.pathto(dx, dy, dz, dh, boundingBox)
- local start = { x = state.x, y = state.y, z = state.z }
- if TL2.goto(dx, dy, dz, dh) then
- return true
- end
- if not dz then
- dz = state.z
- end
- if not boundingBox then
- boundingBox = {
- ax = math.min(dx, start.x),
- ay = math.min(dy, start.y),
- az = math.min(dz, start.z),
- bx = math.max(dx, start.x),
- by = math.max(dy, start.y),
- bz = math.max(dz, start.z),
- }
- end
- return pathfind(dx, dy, dz, dh, boundingBox)
- end
- function TL2.moveTowardsPoint(c)
- if c.z > state.z then
- return TL2.up()
- elseif c.z < state.z then
- return TL2.down()
- end
- TL2.headTowards(c)
- return TL2.forward()
- end
- --Import
- --[[
- turtle action chains and communications
- --]]
- _G.TLC = { }
- local registeredActions = {
- setBoss = function(args, action)
- TL2.getState().boss = action.requestor
- printf('setting boss to %d', action.requestor)
- return true
- end,
- abort = function(args, action)
- TL2.getState().abort = true
- return true
- end,
- run = function(args, action)
- printf('do file: %s', args.filename)
- return dofile(args.filename)
- end,
- setState = function(args)
- for k,v in pairs(args) do
- TL2.getState()[k] = v
- end
- return true
- end,
- recall = function(args, action)
- local pt = TLC.tracker(action.requestor, action.distance)
- if pt then
- TL2.getState().z = pt.z
- if pt.z > 0 then
- TL2.gotoZ(1)
- else
- TL2.gotoZ(-1)
- end
- end
- return true
- end,
- goto = function(args)
- if args.mode then
- TL2.setMode(args.mode)
- end
- if args.digStrategy then
- TL2.setDigStrategy(args.digStrategy)
- end
- for i = 1, 3 do
- if TL2.goto(args.x, args.y, args.z, args.heading) then
- return true
- end
- os.sleep(.5)
- end
- end,
- gotoZ = function(args)
- if args.mode then
- TL2.setMode(args.mode)
- end
- if args.digStrategy then
- TL2.setDigStrategy(args.digStrategy)
- end
- return TL2.gotoZ(args.z)
- end,
- move = function(args)
- local result = false
- if args.mode then
- TL2.setMode(args.mode)
- end
- if args.digStrategy then
- TL2.setDigStrategy(args.digStrategy)
- end
- for i = 1, args.moves do
- if args.subaction == "u" then
- result = TL2.up()
- elseif args.subaction == "d" then
- result = TL2.down()
- elseif args.subaction == "f" then
- result = TL2.forward()
- elseif args.subaction == "r" then
- result = TL2.turnRight()
- elseif args.subaction == "l" then
- result = TL2.turnLeft()
- elseif args.subaction == "b" then
- result = TL2.back()
- end
- if not result then
- return false
- end
- end
- return result
- end,
- reset = function(args)
- TL2.getState().x = 0
- TL2.getState().y = 0
- TL2.getState().z = 0
- return true
- end,
- shutdown = function(args)
- os.shutdown()
- end,
- reboot = function(args)
- os.reboot()
- end,
- gotoNamedLocation = function(args)
- return TL2.gotoNamedLocation(args.name)
- end,
- setLocation = function(args)
- TL2.getState().x = args.x
- TL2.getState().y = args.y
- TL2.getState().z = args.z
- if args.heading then
- TL2.getState().heading = args.heading
- end
- return true
- end,
- status = function(args, action)
- TLC.sendStatus(action.requestor)
- return true
- end
- }
- function TLC.sendStatus(requestor, lastState)
- local state = TL2.getState()
- requestor = requestor or state.boss
- if lastState then
- if state.x == lastState.x and
- state.y == lastState.y and
- state.z == lastState.z and
- state.status == lastState.status and
- state.fuel == lastState.fuel then
- return
- end
- end
- Message.send(requestor, 'isAlive', {
- label = os.getComputerLabel(),
- fuel = turtle.getFuelLevel(),
- status = state.status,
- extStatus = state.extStatus,
- --role = state.role,
- x = state.x,
- y = state.y,
- z = state.z
- })
- TL2.getState().lastStatus = os.clock()
- Logger.log('turtle', '>> Status to ' .. tostring(requestor) .. ' of ' .. state.status)
- end
- function TLC.registerAction(action, f)
- registeredActions[action] = f
- end
- function TLC.performActions(actions)
- local function performSingleAction(action)
- local actionName = action.action
- local actionFunction = registeredActions[actionName]
- if not actionFunction then
- Logger.log('turtle', 'Unknown turtle action: ' .. tostring(actionName))
- Logger.log('turtle', action)
- error('Unknown turtle action: ' .. tostring(actionName))
- end
- -- perform action
- Logger.log('turtle', '<< Action: ' .. actionName)
- --if actionName == 'status' then
- --return actionFunction(action.args, action)
- --end
- local state = TL2.getState()
- local previousStatus = state.status
- result = actionFunction(action.args, action)
- return result
- end
- for _,action in ipairs(actions) do
- if not action.result then
- action.result = performSingleAction(action)
- if not action.result then
- Logger.log('turtle', action)
- Logger.log('turtle', '**Action failed')
- if not action.retryCount then
- action.retryCount = 1
- else
- action.retryCount = action.retryCount + 1
- end
- if action.retryCount < 3 then
- -- queue action chain for another attempt
- TL2.getState().status = 'busy'
- os.queueEvent('turtleActions', actions)
- end
- return false
- end
- end
- end
- return true
- end
- function TLC.performAction(actionName, actionArgs)
- return TLC.performActions({{ action = actionName, args = actionArgs }})
- end
- function TLC.sendAction(id, actionName, actionArgs)
- local action = {
- action = actionName,
- args = actionArgs
- }
- Logger.log('turtle', '>>Sending to ' .. tostring(id) .. ' ' .. actionName)
- Message.send(id, 'turtle', { action })
- end
- function TLC.sendActions(id, actions)
- local str = ''
- for _,v in pairs(actions) do
- str = str .. ' ' .. v.action
- end
- Logger.log('turtle', '>>Sending to ' .. id .. str)
- Message.send(id, 'turtle', actions)
- end
- function TLC.requestState(id)
- Logger.log('turtle', '>>Requesting state from ' .. id)
- Message.send(id, 'turtleState')
- end
- function TLC.setRole(role)
- TL2.getState().role = role
- end
- function TLC.queueAction(actionName, actionArgs)
- TLC.queueActions({{
- action = actionName,
- args = actionArgs
- }})
- end
- function TLC.queueActions(actions)
- table.insert(TL2.getState().actionQueue, actions)
- os.queueEvent('turtleActions', actions)
- end
- function TLC.run(command)
- TLC.queueAction('run', { filename = command })
- end
- function TLC.saveState()
- end
- function TLC.clearState()
- end
- function TLC.pullEvents(role, isworker, filename)
- TL2.getState().status = 'idle'
- TL2.getState().role = role
- TL2.getState().actionQueue = { }
- local function heartbeat()
- local lastState
- while not TL2.getState().boss do
- os.sleep(3)
- if TL2.getStatus() == 'idle' then
- Message.broadcast('unassignedTurtle', {
- label = os.getComputerLabel(),
- fuel = turtle.getFuelLevel(),
- status = TL2.getState().status,
- role = TL2.getState().role,
- })
- end
- end
- while true do
- if (not TL2.getState().lastStatus) or
- (os.clock() - TL2.getState().lastStatus > 30) then
- TLC.sendStatus(TL2.getState().boss)
- else
- TLC.sendStatus(TL2.getState().boss, lastState)
- end
- lastState = Util.shallowCopy(TL2.getState())
- os.sleep(1)
- end
- end
- Message.addHandler('turtle',
- function(h, id, msg, distance)
- local actions = msg.contents
- for _,action in ipairs(actions) do
- action.distance = distance
- action.requestor = id
- end
- TLC.queueActions(actions)
- end
- )
- Event.addHandler('turtleActions',
- function(h, actions)
- local state = TL2.getState()
- if state.status == 'idle' then
- TLC.clearState()
- state.status = 'busy'
- while #state.actionQueue > 0 do
- local actions = table.remove(state.actionQueue, 1)
- TLC.performActions(actions)
- end
- state.status = 'idle'
- state.abort = nil
- TLC.saveState()
- if state.boss then
- TLC.sendStatus(state.boss)
- end
- end
- end
- )
- Message.addHandler('alive',
- function(e, id, msg)
- TLC.sendStatus(id)
- end
- )
- if isworker then
- parallel.waitForAny(heartbeat, Event.backgroundPullEvents)
- else
- parallel.waitForAny(Event.backgroundPullEvents)
- end
- end
- --[[
- function TLC.condence(slot)
- local iQty = turtle.getItemCount(slot)
- for i = 1, 16 do
- if i ~= slot then
- local qty = turtle.getItemCount(i)
- if qty > 0 then
- turtle.select(i)
- if turtle.compareTo(slot) then
- turtle.transferTo(slot, qty)
- iQty = iQty + qty
- if iQty >= 64 then
- break
- end
- end
- end
- end
- end
- end
- --]]
- -- from stock gps API
- local function trilaterate( A, B, C )
- local a2b = B.position - A.position
- local a2c = C.position - A.position
- if math.abs( a2b:normalize():dot( a2c:normalize() ) ) > 0.999 then
- return nil
- end
- local d = a2b:length()
- local ex = a2b:normalize( )
- local i = ex:dot( a2c )
- local ey = (a2c - (ex * i)):normalize()
- local j = ey:dot( a2c )
- local ez = ex:cross( ey )
- local r1 = A.distance
- local r2 = B.distance
- local r3 = C.distance
- local x = (r1*r1 - r2*r2 + d*d) / (2*d)
- local y = (r1*r1 - r3*r3 - x*x + (x-i)*(x-i) + j*j) / (2*j)
- local result = A.position + (ex * x) + (ey * y)
- local zSquared = r1*r1 - x*x - y*y
- if zSquared > 0 then
- local z = math.sqrt( zSquared )
- local result1 = result + (ez * z)
- local result2 = result - (ez * z)
- local rounded1, rounded2 = result1:round(), result2:round()
- if rounded1.x ~= rounded2.x or rounded1.y ~= rounded2.y or rounded1.z ~= rounded2.z then
- return rounded1, rounded2
- else
- return rounded1
- end
- end
- return result:round()
- end
- local function narrow( p1, p2, fix )
- local dist1 = math.abs( (p1 - fix.position):length() - fix.distance )
- local dist2 = math.abs( (p2 - fix.position):length() - fix.distance )
- if math.abs(dist1 - dist2) < 0.05 then
- return p1, p2
- elseif dist1 < dist2 then
- return p1:round()
- else
- return p2:round()
- end
- end
- -- end stock gps api
- TLC.tFixes = {}
- Message.addHandler('position', function(h, id, msg, distance)
- if TL2.getStatus() ~= 'idle' then
- return
- end
- local tFix = {
- position = vector.new(msg.contents.x, msg.contents.z, msg.contents.y),
- distance = distance
- }
- table.insert(TLC.tFixes, tFix)
- if #TLC.tFixes == 4 then
- Logger.log('turtle', 'trilaterating')
- local pos1, pos2 = trilaterate(TLC.tFixes[1], TLC.tFixes[2], TLC.tFixes[3])
- pos1, pos2 = narrow(pos1, pos2, TLC.tFixes[3])
- if pos2 then
- pos1, pos2 = narrow(pos1, pos2, TLC.tFixes[4])
- end
- if pos1 and pos2 then
- print("Ambiguous position")
- print("Could be "..pos1.x..","..pos1.y..","..pos1.z.." or "..pos2.x..","..pos2.y..","..pos2.z )
- elseif pos1 then
- Logger.log('turtle', "Position is "..pos1.x..","..pos1.y..","..pos1.z)
- TLC.performAction('setLocation', {
- x = pos1.x,
- y = pos1.z,
- z = pos1.y
- })
- else
- print("Could not determine position")
- end
- end
- end)
- function TLC.getDistance(id)
- for i = 1, 10 do
- rednet.send(id, { type = 'alive' })
- local _, _, _, distance = Message.waitForMessage('isAlive', 1, id)
- if distance then
- Logger.log('turtle', 'distance: ' .. distance)
- return distance
- end
- end
- end
- local function locate(id, d1, boundingBox)
- local function checkBB(boundingBox)
- if boundingBox then
- local heading = TL2.headings[TL2.getState().heading]
- local x = TL2.getState().x + heading.xd
- local y = TL2.getState().y + heading.yd
- if x < boundingBox.ax or x > boundingBox.bx or
- y < boundingBox.ay or y > boundingBox.by then
- return true
- end
- end
- return false
- end
- if checkBB(boundingBox) then
- TL2.turnAround()
- end
- if d1 == 1 then
- return d1
- end
- TL2.forward()
- local d2 = TLC.getDistance(id)
- if not d2 then return end
- if d2 == 1 then return d2 end
- if d2 > d1 then
- TL2.turnAround()
- end
- d1 = d2
- while true do
- if checkBB(boundingBox) then
- break
- end
- TL2.forward()
- d2 = TLC.getDistance(id)
- if not d2 then return end
- if d2 == 1 then return d2 end
- if d2 > d1 then
- TL2.back()
- return d1
- end
- d1 = d2
- end
- return d2
- end
- function TLC.tracker(id, d1, nozcheck, boundingBox)
- d1 = locate(id, d1, boundingBox)
- if not d1 then return end
- TL2.turnRight()
- d1 = locate(id, d1, boundingBox)
- if not d1 then return end
- if math.floor(d1) == d1 then
- local z = d1
- if not nozcheck then
- TL2.up()
- d2 = TLC.getDistance(id)
- if not d2 then return end
- TL2.down()
- z = TL2.getState().z + math.floor(d1)
- if d1 < d2 then
- z = TL2.getState().z - math.floor(d1)
- end
- end
- return { x = TL2.getState().x, y = TL2.getState().y, z = -z }
- end
- end
- end
Add Comment
Please, Sign In to add comment