ftibo

apis.lua

Jul 24th, 2016
109
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 120.83 KB | None | 0 0
  1. --Import
  2. -- From http://lua-users.org/wiki/SimpleLuaClasses
  3.  
  4. -- class.lua
  5. -- Compatible with Lua 5.1 (not 5.0).
  6. _G.class = { }
  7. function class.class(base, init)
  8. local c = {} -- a new class instance
  9. if not init and type(base) == 'function' then
  10. init = base
  11. base = nil
  12. elseif type(base) == 'table' then
  13. -- our new class is a shallow copy of the base class!
  14. for i,v in pairs(base) do
  15. c[i] = v
  16. end
  17. c._base = base
  18. end
  19. -- the class will be the metatable for all its objects,
  20. -- and they will look up their methods in it.
  21. c.__index = c
  22.  
  23. -- expose a constructor which can be called by <classname>(<args>)
  24. local mt = {}
  25. mt.__call =
  26. function(class_tbl, ...)
  27. local obj = {}
  28. setmetatable(obj,c)
  29. --if init then
  30. -- init(obj,...)
  31. if class_tbl.init then
  32. class_tbl.init(obj, ...)
  33. else
  34. -- make sure that any stuff from the base class is initialized!
  35. if base and base.init then
  36. base.init(obj, ...)
  37. end
  38. end
  39. return obj
  40. end
  41.  
  42. c.init = init
  43. c.is_a =
  44. function(self, klass)
  45. local m = getmetatable(self)
  46. while m do
  47. if m == klass then return true end
  48. m = m._base
  49. end
  50. return false
  51. end
  52. setmetatable(c, mt)
  53. return c
  54. end
  55.  
  56.  
  57. --Import
  58. _G.Logger = { }
  59.  
  60. local debugMon
  61. local logServerId
  62. local logFile
  63. local filteredEvents = {}
  64.  
  65. local function nopLogger(text)
  66. end
  67.  
  68. local function monitorLogger(text)
  69. debugMon.write(text)
  70. debugMon.scroll(-1)
  71. debugMon.setCursorPos(1, 1)
  72. end
  73.  
  74. local function screenLogger(text)
  75. local x, y = term.getCursorPos()
  76. if x ~= 1 then
  77. local sx, sy = term.getSize()
  78. term.setCursorPos(1, sy)
  79. --term.scroll(1)
  80. end
  81. print(text)
  82. end
  83.  
  84. local logger = screenLogger
  85.  
  86. local function wirelessLogger(text)
  87. if logServerId then
  88. rednet.send(logServerId, {
  89. type = 'log',
  90. contents = text
  91. })
  92. end
  93. end
  94.  
  95. local function fileLogger(text)
  96. local mode = 'w'
  97. if fs.exists(logFile) then
  98. mode = 'a'
  99. end
  100. local file = io.open(logFile, mode)
  101. if file then
  102. file:write(text)
  103. file:write('\n')
  104. file:close()
  105. end
  106. end
  107.  
  108. local function setLogger(ilogger)
  109. logger = ilogger
  110. end
  111.  
  112. function Logger.disable()
  113. setLogger(nopLogger)
  114. end
  115.  
  116. function Logger.setMonitorLogging(logServer)
  117. debugMon = Util.wrap('monitor')
  118. debugMon.setTextScale(.5)
  119. debugMon.clear()
  120. debugMon.setCursorPos(1, 1)
  121. setLogger(monitorLogger)
  122. end
  123.  
  124. function Logger.setScreenLogging()
  125. setLogger(screenLogger)
  126. end
  127.  
  128. function Logger.setWirelessLogging(id)
  129. if id then
  130. logServerId = id
  131. end
  132. setLogger(wirelessLogger)
  133. end
  134.  
  135. function Logger.setFileLogging(fileName)
  136. logFile = fileName
  137. fs.delete(fileName)
  138. setLogger(fileLogger)
  139. end
  140.  
  141. function Logger.log(category, value, ...)
  142. if filteredEvents[category] then
  143. return
  144. end
  145.  
  146. if type(value) == 'table' then
  147. local str
  148. for k,v in pairs(value) do
  149. if not str then
  150. str = '{ '
  151. else
  152. str = str .. ', '
  153. end
  154. str = str .. k .. '=' .. tostring(v)
  155. end
  156. value = str .. ' }'
  157. elseif type(value) == 'string' then
  158. local args = { ... }
  159. if #args > 0 then
  160. value = string.format(value, unpack(args))
  161. end
  162. else
  163. value = tostring(value)
  164. end
  165. logger(category .. ': ' .. value)
  166. end
  167.  
  168. function Logger.debug(value, ...)
  169. Logger.log('debug', value, ...)
  170. end
  171.  
  172. function Logger.logNestedTable(t, indent)
  173. for _,v in ipairs(t) do
  174. if type(v) == 'table' then
  175. log('table')
  176. logNestedTable(v) --, indent+1)
  177. else
  178. log(v)
  179. end
  180. end
  181. end
  182.  
  183. function Logger.filter( ...)
  184. local events = { ... }
  185. for _,event in pairs(events) do
  186. filteredEvents[event] = true
  187. end
  188. end
  189.  
  190. --Import
  191. _G.Profile = { }
  192.  
  193. Profile.start = function() end
  194. Profile.stop = function() end
  195. Profile.display = function() end
  196. Profile.methods = { }
  197.  
  198. local function Profile_display()
  199. Logger.log('profile', 'Profiling results')
  200. for k,v in pairs(Profile.methods) do
  201. Logger.log('profile', '%s: %f %d %f',
  202. k, Util.round(v.elapsed, 2), v.count, Util.round(v.elapsed/v.count, 2))
  203. end
  204. Profile.methods = { }
  205. end
  206.  
  207. local function Profile_start(name)
  208. local p = Profile.methods[name]
  209. if not p then
  210. p = { }
  211. p.elapsed = 0
  212. p.count = 0
  213. Profile.methods[name] = p
  214. end
  215. p.clock = os.clock()
  216. return p
  217. end
  218.  
  219. local function Profile_stop(name)
  220. local p = Profile.methods[name]
  221. p.elapsed = p.elapsed + (os.clock() - p.clock)
  222. p.count = p.count + 1
  223. end
  224.  
  225. function Profile.enable()
  226. Logger.log('profile', 'Profiling enabled')
  227. Profile.start = Profile_start
  228. Profile.stop = Profile_stop
  229. Profile.display = Profile_display
  230. end
  231.  
  232. function Profile.disable()
  233. Profile.start = function() end
  234. Profile.stop = function() end
  235. Profile.display = function() end
  236. end
  237.  
  238. --Import
  239. _G.Util = { }
  240. -- _G.String = { }
  241.  
  242. math.randomseed(os.time())
  243.  
  244. function _G.printf(format, ...)
  245. print(string.format(format, ...))
  246. end
  247.  
  248. function Util.tryTimed(timeout, f, ...)
  249. local c = os.clock()
  250. repeat
  251. local ret = f(...)
  252. if ret then
  253. return ret
  254. end
  255. until os.clock()-c >= timeout
  256. end
  257.  
  258. function Util.tryTimes(attempts, f, ...)
  259. local c = os.clock()
  260. for i = 1, attempts do
  261. local ret = f(...)
  262. if ret then
  263. return ret
  264. end
  265. end
  266. end
  267.  
  268. function Util.print(value)
  269. if type(value) == 'table' then
  270. for k,v in pairs(value) do
  271. print(k .. '=' .. tostring(v))
  272. end
  273. else
  274. print(tostring(value))
  275. end
  276. end
  277.  
  278. function Util.round(num)
  279. if num >= 0 then return math.floor(num+.5)
  280. else return math.ceil(num-.5) end
  281. end
  282.  
  283. function Util.clear(t)
  284. local keys = Util.keys(t)
  285. for _,k in pairs(keys) do
  286. t[k] = nil
  287. end
  288. end
  289.  
  290. function Util.empty(t)
  291. return Util.size(t) == 0
  292. end
  293.  
  294. function Util.key(t, value)
  295. for k,v in pairs(t) do
  296. if v == value then
  297. return k
  298. end
  299. end
  300. end
  301.  
  302. function Util.keys(t)
  303. local keys = {}
  304. for k in pairs(t) do
  305. keys[#keys+1] = k
  306. end
  307. return keys
  308. end
  309.  
  310. function Util.find(t, name, value)
  311. for k,v in pairs(t) do
  312. if v[name] == value then
  313. return v, k
  314. end
  315. end
  316. end
  317.  
  318. function Util.findAll(t, name, value)
  319. local rt = { }
  320. for k,v in pairs(t) do
  321. if v[name] == value then
  322. table.insert(rt, v)
  323. end
  324. end
  325. return rt
  326. end
  327.  
  328. --http://lua-users.org/wiki/TableUtils
  329. function table.val_to_str ( v )
  330. if "string" == type( v ) then
  331. v = string.gsub( v, "\n", "\\n" )
  332. if string.match( string.gsub(v,"[^'\"]",""), '^"+$' ) then
  333. return "'" .. v .. "'"
  334. end
  335. return '"' .. string.gsub(v,'"', '\\"' ) .. '"'
  336. else
  337. return "table" == type( v ) and table.tostring( v ) or
  338. tostring( v )
  339. end
  340. end
  341.  
  342. function table.key_to_str ( k )
  343. if "string" == type( k ) and string.match( k, "^[_%a][_%a%d]*$" ) then
  344. return k
  345. else
  346. return "[" .. table.val_to_str( k ) .. "]"
  347. end
  348. end
  349.  
  350. function table.tostring( tbl )
  351. local result, done = {}, {}
  352. for k, v in ipairs( tbl ) do
  353. table.insert( result, table.val_to_str( v ) )
  354. done[ k ] = true
  355. end
  356. for k, v in pairs( tbl ) do
  357. if not done[ k ] then
  358. table.insert( result,
  359. table.key_to_str( k ) .. "=" .. table.val_to_str( v ) )
  360. end
  361. end
  362. return "{" .. table.concat( result, "," ) .. "}"
  363. end
  364. --end http://lua-users.org/wiki/TableUtils
  365.  
  366. --https://github.com/jtarchie/underscore-lua
  367. function Util.size(list, ...)
  368. local args = {...}
  369.  
  370. if Util.isArray(list) then
  371. return #list
  372. elseif Util.isObject(list) then
  373. local length = 0
  374. Util.each(list, function() length = length + 1 end)
  375. return length
  376. end
  377.  
  378. return 0
  379. end
  380.  
  381. function Util.each(list, func)
  382. local pairing = pairs
  383. if Util.isArray(list) then pairing = ipairs end
  384.  
  385. for index, value in pairing(list) do
  386. func(value, index, list)
  387. end
  388. end
  389.  
  390. function Util.isObject(value)
  391. return type(value) == "table"
  392. end
  393.  
  394. function Util.isArray(value)
  395. return type(value) == "table" and (value[1] or next(value) == nil)
  396. end
  397. -- end https://github.com/jtarchie/underscore-lua
  398.  
  399. function Util.random(max, min)
  400. min = min or 0
  401. return math.random(0, max-min) + min
  402. end
  403.  
  404. function Util.readFile(fname)
  405. local f = fs.open(fname, "r")
  406. if f then
  407. local t = f.readAll()
  408. f.close()
  409. return t
  410. end
  411. end
  412.  
  413. function Util.readTable(fname)
  414. local t = Util.readFile(fname)
  415. if t then
  416. return textutils.unserialize(t)
  417. end
  418. end
  419.  
  420. function Util.writeTable(fname, data)
  421. Util.writeFile(fname, textutils.serialize(data))
  422. end
  423.  
  424. function Util.writeFile(fname, data)
  425. local file = io.open(fname, "w")
  426. if not file then
  427. error('Unable to open ' .. fname, 2)
  428. end
  429. file:write(data)
  430. file:close()
  431. end
  432.  
  433. function Util.shallowCopy(t)
  434. local t2 = {}
  435. for k,v in pairs(t) do
  436. t2[k] = v
  437. end
  438. return t2
  439. end
  440.  
  441. function Util.split(str)
  442. local t = {}
  443. local function helper(line) table.insert(t, line) return "" end
  444. helper((str:gsub("(.-)\n", helper)))
  445. return t
  446. end
  447.  
  448. -- http://snippets.luacode.org/?p=snippets/Check_string_ends_with_other_string_74
  449. -- Author: David Manura
  450. --String.endswith = function(s, send)
  451. --return #s >= #send and s:find(send, #s-#send+1, true) and true or false
  452. --end
  453. -- end http://snippets.luacode.org/?p=snippets/Check_string_ends_with_other_string_74
  454.  
  455. string.lpad = function(str, len, char)
  456. if char == nil then char = ' ' end
  457. return str .. string.rep(char, len - #str)
  458. end
  459.  
  460. -- http://stackoverflow.com/questions/15706270/sort-a-table-in-lua
  461. function Util.spairs(t, order)
  462. if not t then
  463. error('spairs: nil passed')
  464. end
  465.  
  466. -- collect the keys
  467. local keys = {}
  468. for k in pairs(t) do keys[#keys+1] = k end
  469.  
  470. -- if order function given, sort by it by passing the table and keys a, b,
  471. -- otherwise just sort the keys
  472. if order then
  473. table.sort(keys, function(a,b) return order(t[a], t[b]) end)
  474. else
  475. table.sort(keys)
  476. end
  477.  
  478. -- return the iterator function
  479. local i = 0
  480. return function()
  481. i = i + 1
  482. if keys[i] then
  483. return keys[i], t[keys[i]]
  484. end
  485. end
  486. end
  487.  
  488. function Util.first(t, order)
  489. -- collect the keys
  490. local keys = {}
  491. for k in pairs(t) do keys[#keys+1] = k end
  492.  
  493. -- if order function given, sort by it by passing the table and keys a, b,
  494. -- otherwise just sort the keys
  495. if order then
  496. table.sort(keys, function(a,b) return order(t[a], t[b]) end)
  497. else
  498. table.sort(keys)
  499. end
  500. return keys[1], t[keys[1]]
  501. end
  502.  
  503. --[[
  504. pbInfo - Libs/lib.WordWrap.lua
  505. v0.41
  506. by p.b. a.k.a. novayuna
  507. released under the Creative Commons License By-Nc-Sa: http://creativecommons.org/licenses/by-nc-sa/3.0/
  508.  
  509. original code by Tomi H.: http://shadow.vs-hs.org/library/index.php?page=2&id=48
  510. ]]
  511. function Util.WordWrap(strText, intMaxLength)
  512. local tblOutput = {};
  513. local intIndex;
  514. local strBuffer = "";
  515. local tblLines = Util.Explode(strText, "\n");
  516. for k, strLine in pairs(tblLines) do
  517. local tblWords = Util.Explode(strLine, " ");
  518. if (#tblWords > 0) then
  519. intIndex = 1;
  520. while tblWords[intIndex] do
  521. local strWord = " " .. tblWords[intIndex];
  522. if (strBuffer:len() >= intMaxLength) then
  523. table.insert(tblOutput, strBuffer:sub(1, intMaxLength));
  524. strBuffer = strBuffer:sub(intMaxLength + 1);
  525. else
  526. if (strWord:len() > intMaxLength) then
  527. strBuffer = strBuffer .. strWord;
  528. elseif (strBuffer:len() + strWord:len() >= intMaxLength) then
  529. table.insert(tblOutput, strBuffer);
  530. strBuffer = ""
  531. else
  532. if (strBuffer == "") then
  533. strBuffer = strWord:sub(2);
  534. else
  535. strBuffer = strBuffer .. strWord;
  536. end;
  537. intIndex = intIndex + 1;
  538. end;
  539. end;
  540. end;
  541. if strBuffer ~= "" then
  542. table.insert(tblOutput, strBuffer);
  543. strBuffer = ""
  544. end;
  545. end;
  546. end;
  547. return tblOutput;
  548. end
  549.  
  550. function Util.Explode(strText, strDelimiter)
  551. local strTemp = "";
  552. local tblOutput = {};
  553. for intIndex = 1, strText:len(), 1 do
  554. if (strText:sub(intIndex, intIndex + strDelimiter:len() - 1) == strDelimiter) then
  555. table.insert(tblOutput, strTemp);
  556. strTemp = "";
  557. else
  558. strTemp = strTemp .. strText:sub(intIndex, intIndex);
  559. end;
  560. end;
  561. if (strTemp ~= "") then
  562. table.insert(tblOutput, strTemp)
  563. end;
  564. return tblOutput;
  565. end
  566.  
  567. -- http://lua-users.org/wiki/AlternativeGetOpt
  568. local function getopt( arg, options )
  569. local tab = {}
  570. for k, v in ipairs(arg) do
  571. if string.sub( v, 1, 2) == "--" then
  572. local x = string.find( v, "=", 1, true )
  573. if x then tab[ string.sub( v, 3, x-1 ) ] = string.sub( v, x+1 )
  574. else tab[ string.sub( v, 3 ) ] = true
  575. end
  576. elseif string.sub( v, 1, 1 ) == "-" then
  577. local y = 2
  578. local l = string.len(v)
  579. local jopt
  580. while ( y <= l ) do
  581. jopt = string.sub( v, y, y )
  582. if string.find( options, jopt, 1, true ) then
  583. if y < l then
  584. tab[ jopt ] = string.sub( v, y+1 )
  585. y = l
  586. else
  587. tab[ jopt ] = arg[ k + 1 ]
  588. end
  589. else
  590. tab[ jopt ] = true
  591. end
  592. y = y + 1
  593. end
  594. end
  595. end
  596. return tab
  597. end
  598. -- end http://lua-users.org/wiki/AlternativeGetOpt
  599.  
  600. function Util.showOptions(options)
  601. print('Arguments: ')
  602. for k, v in pairs(options) do
  603. print(string.format('-%s %s', v.arg, v.desc))
  604. end
  605. end
  606.  
  607. function Util.getOptions(options, args, syntaxMessage)
  608. local argLetters = ''
  609. for _,o in pairs(options) do
  610. if o.type ~= 'flag' then
  611. argLetters = argLetters .. o.arg
  612. end
  613. end
  614. local rawOptions = getopt(args, argLetters)
  615.  
  616. for k,ro in pairs(rawOptions) do
  617. local found = false
  618. for _,o in pairs(options) do
  619. if o.arg == k then
  620. found = true
  621. if o.type == 'number' then
  622. o.value = tonumber(ro)
  623. elseif o.type == 'help' then
  624. Util.showOptions(options)
  625. return false
  626. else
  627. o.value = ro
  628. end
  629. end
  630. end
  631. if not found then
  632. print('Invalid argument')
  633. Util.showOptions(options)
  634. return false
  635. end
  636. end
  637.  
  638. return true
  639.  
  640. end
  641.  
  642. --Import
  643. _G.Peripheral = { }
  644.  
  645. function Peripheral.getAll()
  646. local aliasDB = {
  647. obsidian = 'chest',
  648. diamond = 'chest',
  649. container_chest = 'chest'
  650. }
  651.  
  652. local t = { }
  653. for _,side in pairs(peripheral.getNames()) do
  654.  
  655. local pType = peripheral.getType(side)
  656.  
  657. if pType == 'modem' then
  658. if peripheral.call(side, 'isWireless') then
  659. t[side] = {
  660. type = 'wireless_modem',
  661. side = side
  662. }
  663. else
  664. t[side] = {
  665. type = 'wired_modem',
  666. side = side
  667. }
  668. end
  669. else
  670. t[side] = {
  671. type = pType,
  672. side = side,
  673. alias = aliasDB[pType]
  674. }
  675. end
  676. end
  677. return t
  678. end
  679.  
  680. function Peripheral.getBySide(pList, sideName)
  681. return pList[sideName]
  682. end
  683.  
  684. function Peripheral.getByType(pList, typeName)
  685. return Util.find(pList, 'type', typeName)
  686. end
  687.  
  688. function Peripheral.getByAlias(pList, aliasName)
  689. return Util.find(pList, 'alias', aliasName)
  690. end
  691.  
  692. function Peripheral.hasMethod(p, methodName)
  693. local methods = peripheral.getMethods(p.side)
  694. return Util.key(methods, methodName)
  695. end
  696.  
  697. function Peripheral.getByMethod(pList, methodName)
  698. for _,p in pairs(pList) do
  699. if Peripheral.hasMethod(p, methodName) then
  700. return p
  701. end
  702. end
  703. end
  704.  
  705. -- peripheral must match all arguments
  706. function Peripheral.isAllPresent(args)
  707. local t = Peripheral.getAll()
  708. local p
  709.  
  710. if args.side then
  711. p = Peripheral.getBySide(t, args.side)
  712. if not p then
  713. return
  714. end
  715. end
  716.  
  717. if args.type then
  718. if p then
  719. if p.type ~= args.type then
  720. return
  721. end
  722. else
  723. p = Peripheral.getByType(t, args.type)
  724. if not p then
  725. return
  726. end
  727. end
  728. end
  729.  
  730. if args.method then
  731. if p then
  732. if not Peripheral.hasMethod(p, args.method) then
  733. return
  734. end
  735. else
  736. p = Peripheral.getByMethod(t, args.method)
  737. if p then
  738. return
  739. end
  740. end
  741. end
  742.  
  743. return p
  744. end
  745.  
  746. function Peripheral.isPresent(args)
  747.  
  748. if type(args) == 'string' then
  749. args = { type = args }
  750. end
  751.  
  752. local t = Peripheral.getAll()
  753. args = args or { type = pType }
  754.  
  755. if args.type then
  756. local p = Peripheral.getByType(t, args.type)
  757. if p then
  758. return p
  759. end
  760. end
  761.  
  762. if args.alias then
  763. local p = Peripheral.getByAlias(t, args.alias)
  764. if p then
  765. return p
  766. end
  767. end
  768.  
  769. if args.method then
  770. local p = Peripheral.getByMethod(t, args.method)
  771. if p then
  772. return p
  773. end
  774. end
  775.  
  776. if args.side then
  777. local p = Peripheral.getBySide(t, args.side)
  778. if p then
  779. return p
  780. end
  781. end
  782. end
  783.  
  784. local function _wrap(p)
  785. Logger.log('peripheral', 'Wrapping ' .. p.type)
  786. if p.type == 'wired_modem' or p.type == 'wireless_modem' then
  787. rednet.open(p.side)
  788. return p
  789. end
  790. return peripheral.wrap(p.side)
  791. end
  792.  
  793. function Peripheral.wrap(args)
  794.  
  795. if type(args) == 'string' then
  796. args = { type = args }
  797. end
  798.  
  799. local p = Peripheral.isPresent(args)
  800. if p then
  801. return _wrap(p)
  802. end
  803.  
  804. error('Peripheral ' .. table.tostring(args, '?') .. ' is not connected', 2)
  805. end
  806.  
  807. function Peripheral.wrapAll()
  808. local t = Peripheral.getAll()
  809. Util.each(t, function(p) p.wrapper = _wrap(p) end)
  810. return t
  811. end
  812.  
  813. function Peripheral.wrapSide(sideName)
  814. if peripheral.isPresent(sideName) then
  815. return peripheral.wrap(sideName)
  816. end
  817. error('No Peripheral on side ' .. sideName, 2)
  818. end
  819.  
  820. --Import
  821. _G.Event = { }
  822.  
  823. local eventHandlers = {
  824. namedTimers = {}
  825. }
  826. local enableQueue = {}
  827. local removeQueue = {}
  828.  
  829. local function deleteHandler(h)
  830. for k,v in pairs(eventHandlers[h.event].handlers) do
  831. if v == h then
  832. table.remove(eventHandlers[h.event].handlers, k)
  833. break
  834. end
  835. end
  836. --table.remove(eventHandlers[h.event].handlers, h.key)
  837. end
  838.  
  839. function Event.addHandler(type, f)
  840. local event = eventHandlers[type]
  841. if not event then
  842. event = {}
  843. event.handlers = {}
  844. eventHandlers[type] = event
  845. end
  846.  
  847. local handler = {}
  848. handler.event = type
  849. handler.f = f
  850. handler.enabled = true
  851. table.insert(event.handlers, handler)
  852. -- any way to retrieve key here for removeHandler ?
  853.  
  854. return handler
  855. end
  856.  
  857. function Event.removeHandler(h)
  858. h.deleted = true
  859. h.enabled = false
  860. table.insert(removeQueue, h)
  861. end
  862.  
  863. function Event.queueTimedEvent(name, timeout, event, args)
  864. Event.addNamedTimer(name, timeout, false,
  865. function()
  866. os.queueEvent(event, args)
  867. end
  868. )
  869. end
  870.  
  871. function Event.addNamedTimer(name, interval, recurring, f)
  872. Event.cancelNamedTimer(name)
  873. eventHandlers.namedTimers[name] = Event.addTimer(interval, recurring, f)
  874. end
  875.  
  876. function Event.getNamedTimer(name)
  877. return eventHandlers.namedTimers[name]
  878. end
  879.  
  880. function Event.cancelNamedTimer(name)
  881. local timer = Event.getNamedTimer(name)
  882.  
  883. if timer then
  884. timer.enabled = false
  885. timer.recurring = false
  886. end
  887. end
  888.  
  889. function Event.isTimerActive(timer)
  890. return timer.enabled and
  891. os.clock() < timer.start + timer.interval
  892. end
  893.  
  894. function Event.addTimer(interval, recurring, f)
  895. local timer = Event.addHandler('timer',
  896. function(t, id)
  897. if t.timerId ~= id then
  898. return
  899. end
  900. if t.enabled then
  901. t.fired = true
  902. t.cf(t, id)
  903. end
  904. if t.recurring then
  905. t.fired = false
  906. t.start = os.clock()
  907. t.timerId = os.startTimer(t.interval)
  908. else
  909. Event.removeHandler(t)
  910. end
  911. end
  912. )
  913. timer.cf = f
  914. timer.interval = interval
  915. timer.recurring = recurring
  916. timer.start = os.clock()
  917. timer.timerId = os.startTimer(interval)
  918.  
  919. return timer
  920. end
  921.  
  922. function Event.removeTimer(h)
  923. Event.removeHandler(h)
  924. end
  925.  
  926. function Event.blockUntilEvent(event, timeout)
  927. return Event.waitForEvent(event, timeout, os.pullEvent)
  928. end
  929.  
  930. function Event.waitForEvent(event, timeout, pullEvent)
  931. pullEvent = pullEvent or Event.pullEvent
  932.  
  933. local timerId = os.startTimer(timeout)
  934. repeat
  935. local e, p1, p2, p3, p4 = pullEvent()
  936. if e == event then
  937. return e, p1, p2, p3, p4
  938. end
  939. until e == 'timer' and id == timerId
  940. end
  941.  
  942. function Event.heartbeat(timeout)
  943. local function heart()
  944. while true do
  945. os.sleep(timeout)
  946. os.queueEvent('heartbeat')
  947. end
  948. end
  949. parallel.waitForAny(Event.pullEvents, heart)
  950. end
  951.  
  952. function Event.pullEvents()
  953.  
  954. while true do
  955. local e = Event.pullEvent()
  956. if e == 'exitPullEvents' then
  957. break
  958. end
  959. end
  960. end
  961.  
  962. function Event.exitPullEvents()
  963. os.queueEvent('exitPullEvents')
  964. end
  965.  
  966. function Event.enableHandler(h)
  967. table.insert(enableQueue, h)
  968. end
  969.  
  970. function Event.backgroundPullEvents()
  971.  
  972. local function allEvents()
  973. while true do
  974. local e = { os.pullEvent() }
  975. if e[1] == 'exitPullEvents' then
  976. break
  977. end
  978. if e[1] ~= 'rednet_message' and e[1] ~= 'timer' then
  979. Event.processEvent(e)
  980. end
  981. end
  982. end
  983.  
  984. local function timerEvents()
  985. while true do
  986. local e = { os.pullEvent('timer') }
  987. Event.processEvent(e)
  988. end
  989. end
  990.  
  991. local function rednetEvents()
  992. while true do
  993. local e = { os.pullEvent('rednet_message') }
  994. Event.processEvent(e)
  995. end
  996. end
  997.  
  998. parallel.waitForAny(allEvents, rednetEvents, timerEvents)
  999. end
  1000.  
  1001. function Event.pullEvent(eventType)
  1002. local e = { os.pullEvent(eventType) }
  1003. return Event.processEvent(e)
  1004. end
  1005.  
  1006. function Event.processEvent(pe)
  1007. Logger.log('event', pe)
  1008.  
  1009. local e, p1, p2, p3, p4, p5 = unpack(pe)
  1010.  
  1011. local event = eventHandlers[e]
  1012. if event then
  1013. for k,v in pairs(event.handlers) do
  1014. if v.enabled then
  1015. v.f(v, p1, p2, p3, p4, p5)
  1016. end
  1017. end
  1018. while #enableQueue > 0 do
  1019. table.remove(handlerQueue).enabled = true
  1020. end
  1021. while #removeQueue > 0 do
  1022. deleteHandler(table.remove(removeQueue))
  1023. end
  1024. end
  1025.  
  1026. return e, p1, p2, p3, p4, p5
  1027. end
  1028.  
  1029. --Import
  1030. _G.Message = { }
  1031.  
  1032. local messageHandlers = {}
  1033.  
  1034. function Message.addHandler(type, f)
  1035. table.insert(messageHandlers, {
  1036. type = type,
  1037. f = f,
  1038. enabled = true
  1039. })
  1040. end
  1041.  
  1042. function Message.removeHandler(h)
  1043. for k,v in pairs(messageHandlers) do
  1044. if v == h then
  1045. messageHandlers[k] = nil
  1046. break
  1047. end
  1048. end
  1049. end
  1050.  
  1051. Event.addHandler('rednet_message',
  1052. function(event, id, msg, distance)
  1053. if msg and msg.type then -- filter out messages from other systems
  1054. Logger.log('rednet_receive', { id, msg.type })
  1055. for k,h in pairs(messageHandlers) do
  1056. if h.type == msg.type then
  1057. -- should provide msg.contents instead of message - type is already known
  1058. h.f(h, id, msg, distance)
  1059. end
  1060. end
  1061. end
  1062. end
  1063. )
  1064.  
  1065. function Message.send(id, msgType, contents)
  1066. if id then
  1067. Logger.log('rednet_send', { tostring(id), msgType })
  1068. rednet.send(id, { type = msgType, contents = contents })
  1069. else
  1070. Logger.log('rednet_send', { 'broadcast', msgType })
  1071. rednet.broadcast({ type = msgType, contents = contents })
  1072. end
  1073. end
  1074.  
  1075. function Message.broadcast(t, contents)
  1076. Logger.log('rednet_send', { 'broadcast', t })
  1077. rednet.broadcast({ type = t, contents = contents })
  1078. end
  1079.  
  1080. function Message.waitForMessage(msgType, timeout, fromId)
  1081. local timerId = os.startTimer(timeout)
  1082. repeat
  1083. local e, id, msg, distance = os.pullEvent()
  1084.  
  1085. if e == 'rednet_message' then
  1086. if msg and msg.type and msg.type == msgType then
  1087. if not fromId or id == fromId then
  1088. return e, id, msg, distance
  1089. end
  1090. end
  1091. end
  1092. until e == 'timer' and id == timerId
  1093. end
  1094.  
  1095. function Message.enableWirelessLogging()
  1096. local modem = Peripheral.isPresent('wireless_modem')
  1097. if modem then
  1098. if not rednet.isOpen(modem.side) then
  1099. Logger.log('message', 'enableWirelessLogging: opening modem')
  1100. rednet.open(modem.side)
  1101. end
  1102. Message.broadcast('logClient')
  1103. local _, id = Message.waitForMessage('logServer', 1)
  1104. if not id then
  1105. return false
  1106. end
  1107. Logger.log('message', 'enableWirelessLogging: Logging to ' .. id)
  1108. Logger.setWirelessLogging(id)
  1109. return true
  1110. end
  1111. end
  1112.  
  1113. --Import
  1114. _G.Relay = { }
  1115.  
  1116. Relay.stationId = nil
  1117.  
  1118. function Relay.find(msgType, stationType)
  1119. while true do
  1120. Logger.log('relay', 'locating relay station')
  1121. Message.broadcast('getRelayStation', os.getComputerLabel())
  1122. local _, id = Message.waitForMessage('relayStation', 2)
  1123. if id then
  1124. Relay.stationId = id
  1125. return id
  1126. end
  1127. end
  1128. end
  1129.  
  1130. function Relay.register(...)
  1131. local types = { ... }
  1132. Message.send(Relay.stationId, 'listen', types)
  1133. end
  1134.  
  1135. function Relay.send(type, contents, toId)
  1136. local relayMessage = {
  1137. type = type,
  1138. contents = contents,
  1139. fromId = os.getComputerID()
  1140. }
  1141. if toId then
  1142. relayMessage.toId = toId
  1143. end
  1144. Message.send(Relay.stationId, 'relay', relayMessage)
  1145. end
  1146.  
  1147. --Import
  1148. _G.UI = { }
  1149.  
  1150. local function widthify(s, len)
  1151. if not s then
  1152. s = ' '
  1153. end
  1154. return string.lpad(string.sub(s, 1, len) , len, ' ')
  1155. end
  1156.  
  1157. function UI.setProperties(obj, args)
  1158. if args then
  1159. for k,v in pairs(args) do
  1160. obj[k] = v
  1161. end
  1162. end
  1163. end
  1164.  
  1165. function UI.setDefaultDevice(device)
  1166. UI.defaultDevice = device
  1167. end
  1168.  
  1169. function UI.bestDefaultDevice(...)
  1170. local termList = { ... }
  1171. for _,name in ipairs(termList) do
  1172. if name == 'monitor' then
  1173. if Util.hasDevice('monitor') then
  1174. UI.defaultDevice = UI.Device({ deviceType = 'monitor' })
  1175. return UI.defaultDevice
  1176. end
  1177. end
  1178. end
  1179. return UI.term
  1180. end
  1181.  
  1182. --[[-- Glasses device --]]--
  1183. UI.Glasses = class.class()
  1184. function UI.Glasses:init(args)
  1185.  
  1186. local defaults = {
  1187. backgroundColor = colors.black,
  1188. textColor = colors.white,
  1189. textScale = .5,
  1190. backgroundOpacity = .5
  1191. }
  1192. defaults.width, defaults.height = term.getSize()
  1193.  
  1194. UI.setProperties(defaults, args)
  1195. UI.setProperties(self, defaults)
  1196.  
  1197. self.bridge = Peripheral.wrap({
  1198. type = 'openperipheral_glassesbridge',
  1199. method = 'addBox'
  1200. })
  1201. self.bridge.clear()
  1202.  
  1203. self.setBackgroundColor = function(...) end
  1204. self.setTextColor = function(...) end
  1205.  
  1206. self.t = { }
  1207. for i = 1, self.height do
  1208. self.t[i] = {
  1209. text = self.bridge.addText(0, 40+i*4, string.rep(' ', self.width+1), 0xffffff),
  1210. bg = { }
  1211. }
  1212. self.t[i].text.setScale(self.textScale)
  1213. self.t[i].text.setZ(1)
  1214. end
  1215. end
  1216.  
  1217. function UI.Glasses:setBackgroundBox(boxes, ax, bx, y, bgColor)
  1218. local colors = {
  1219. [ colors.black ] = 0x000000,
  1220. [ colors.brown ] = 0x7F664C,
  1221. [ colors.blue ] = 0x253192,
  1222. [ colors.gray ] = 0x272727,
  1223. [ colors.lime ] = 0x426A0D,
  1224. [ colors.green ] = 0x2D5628,
  1225. [ colors.white ] = 0xFFFFFF
  1226. }
  1227.  
  1228. local function overlap(box, ax, bx)
  1229. if bx < box.ax or ax > box.bx then
  1230. return false
  1231. end
  1232. return true
  1233. end
  1234.  
  1235. for _,box in pairs(boxes) do
  1236. if overlap(box, ax, bx) then
  1237. if box.bgColor == bgColor then
  1238. ax = math.min(ax, box.ax)
  1239. bx = math.max(bx, box.bx)
  1240. box.ax = box.bx + 1
  1241. elseif ax == box.ax then
  1242. box.ax = bx + 1
  1243. elseif ax > box.ax then
  1244. if bx < box.bx then
  1245. table.insert(boxes, { -- split
  1246. ax = bx + 1,
  1247. bx = box.bx,
  1248. bgColor = box.bgColor
  1249. })
  1250. box.bx = ax - 1
  1251. break
  1252. else
  1253. box.ax = box.bx + 1
  1254. end
  1255. elseif ax < box.ax then
  1256. if bx > box.bx then
  1257. box.ax = box.bx + 1 -- delete
  1258. else
  1259. box.ax = bx + 1
  1260. end
  1261. end
  1262. end
  1263. end
  1264. if bgColor ~= colors.black then
  1265. table.insert(boxes, {
  1266. ax = ax,
  1267. bx = bx,
  1268. bgColor = bgColor
  1269. })
  1270. end
  1271.  
  1272. local deleted
  1273. repeat
  1274. deleted = false
  1275. for k,box in pairs(boxes) do
  1276. if box.ax > box.bx then
  1277. if box.box then
  1278. box.box.delete()
  1279. end
  1280. table.remove(boxes, k)
  1281. deleted = true
  1282. break
  1283. end
  1284. if not box.box then
  1285. box.box = self.bridge.addBox(
  1286. math.floor((box.ax - 1) * 2.6665),
  1287. 40 + y * 4,
  1288. math.ceil((box.bx - box.ax + 1) * 2.6665),
  1289. 4,
  1290. colors[bgColor],
  1291. self.backgroundOpacity)
  1292. else
  1293. box.box.setX(math.floor((box.ax - 1) * 2.6665))
  1294. box.box.setWidth(math.ceil((box.bx - box.ax + 1) * 2.6665))
  1295. end
  1296. end
  1297. until not deleted
  1298. end
  1299.  
  1300. function UI.Glasses:write(x, y, text, bg)
  1301.  
  1302. if x < 1 then
  1303. error(' less ', 6)
  1304. end
  1305. if y <= #self.t then
  1306. local line = self.t[y]
  1307. local str = line.text.getText()
  1308. str = str:sub(1, x-1) .. text .. str:sub(x + 1 + #text)
  1309. line.text.setText(str)
  1310. self:setBackgroundBox(line.bg, x, x + #text - 1, y, bg)
  1311. UI.term:write(x, y, text, bg)
  1312. end
  1313. end
  1314.  
  1315. function UI.Glasses:clear(bg)
  1316. for i = 1, self.height do
  1317. self.t[i].text.setText('')
  1318. end
  1319. end
  1320.  
  1321. --[[-- Basic drawable area --]]--
  1322. UI.Window = class.class()
  1323. function UI.Window:init(args)
  1324. local defaults = {
  1325. UIElement = 'Window',
  1326. x = 1,
  1327. y = 1,
  1328. cursorX = 1,
  1329. cursorY = 1,
  1330. isUIElement = true
  1331. }
  1332.  
  1333. UI.setProperties(self, defaults)
  1334. UI.setProperties(self, args)
  1335.  
  1336. if self.parent then
  1337. self:setParent()
  1338. end
  1339. end
  1340.  
  1341. function UI.Window:setParent()
  1342. if not self.width then
  1343. self.width = self.parent.width - self.x + 1
  1344. end
  1345. if not self.height then
  1346. self.height = self.parent.height - self.y + 1
  1347. end
  1348.  
  1349. local children = self.children
  1350. for k,child in pairs(self) do
  1351. if type(child) == 'table' and child.isUIElement and not child.parent then
  1352. if not children then
  1353. children = { }
  1354. end
  1355. --self.children[k] = child
  1356. table.insert(children, child)
  1357. end
  1358. end
  1359. if children then
  1360. for _,child in pairs(children) do
  1361. if not child.parent then
  1362. child.parent = self
  1363. child:setParent()
  1364. end
  1365. end
  1366. end
  1367. self.children = children
  1368. end
  1369.  
  1370. function UI.Window:add(children)
  1371. UI.setProperties(self, children)
  1372. for k,child in pairs(children) do
  1373. if type(child) == 'table' and child.isUIElement and not child.parent then
  1374. if not self.children then
  1375. self.children = { }
  1376. end
  1377. self.children[k] = child
  1378. --table.insert(self.children, child)
  1379. child.parent = self
  1380. child:setParent()
  1381. end
  1382. end
  1383. end
  1384.  
  1385. function UI.Window:getCursorPos()
  1386. return self.cursorX, self.cursorY
  1387. end
  1388.  
  1389. function UI.Window:setCursorPos(x, y)
  1390. self.cursorX = x
  1391. self.cursorY = y
  1392. self.parent:setCursorPos(self.x + x - 1, self.y + y - 1)
  1393. end
  1394.  
  1395. function UI.Window:setCursorBlink(blink)
  1396. self.parent:setCursorBlink(blink)
  1397. end
  1398.  
  1399. function UI.Window:setBackgroundColor(bgColor)
  1400. self.backgroundColor = bgColor
  1401. end
  1402.  
  1403. function UI.Window:draw()
  1404. self:clear(self.backgroundColor)
  1405. if self.children then
  1406. for k,child in pairs(self.children) do
  1407. child:draw()
  1408. end
  1409. end
  1410. end
  1411.  
  1412. function UI.Window:setTextScale(textScale)
  1413. self.textScale = textScale
  1414. self.parent:setTextScale(textScale)
  1415. end
  1416.  
  1417. function UI.Window:reset(bg)
  1418. self:clear(bg)
  1419. self:setCursorPos(1, 1)
  1420. end
  1421.  
  1422. function UI.Window:clear(bg)
  1423. self:clearArea(1, 1, self.width, self.height, bg)
  1424. end
  1425.  
  1426. function UI.Window:clearLine(y, bg)
  1427. local filler = string.rep(' ', self.width)
  1428. self:write(1, y, filler, bg)
  1429. end
  1430.  
  1431. function UI.Window:clearArea(x, y, width, height, bg)
  1432. local filler = string.rep(' ', width)
  1433. for i = 0, height-1 do
  1434. self:write(x, y+i, filler, bg)
  1435. end
  1436. end
  1437.  
  1438. function UI.Window:write(x, y, text, bg, tc)
  1439. bg = bg or self.backgroundColor
  1440. tc = tc or self.textColor
  1441. if y < self.height+1 then
  1442. self.parent:write(
  1443. self.x + x - 1, self.y + y - 1, tostring(text), bg, tc)
  1444. end
  1445. end
  1446.  
  1447. function UI.Window:centeredWrite(y, text, bg)
  1448. if #text >= self.width then
  1449. self:write(1, y, text, bg)
  1450. else
  1451. local space = math.floor((self.width-#text) / 2)
  1452. local filler = string.rep(' ', space + 1)
  1453. local str = filler:sub(1, space) .. text
  1454. str = str .. filler:sub(self.width - #str + 1)
  1455. self:write(1, y, str, bg)
  1456. end
  1457. end
  1458.  
  1459. function UI.Window:wrappedWrite(x, y, text, len, bg)
  1460. for k,v in pairs(Util.WordWrap(text, len)) do
  1461. self:write(x, y, v, bg)
  1462. y = y + 1
  1463. end
  1464. return y
  1465. end
  1466.  
  1467. function UI.Window:print(text, indent, len, bg)
  1468. indent = indent or 0
  1469. len = len or self.width - indent
  1470. if #text + self.cursorX > self.width then
  1471. for k,v in pairs(Util.WordWrap(text, len+1)) do
  1472. self:write(self.cursorX, self.cursorY, v, bg)
  1473. self.cursorY = self.cursorY + 1
  1474. self.cursorX = 1 + indent
  1475. end
  1476. else
  1477. self:write(self.cursorX, self.cursorY, text, bg)
  1478. self.cursorY = self.cursorY + 1
  1479. self.cursorX = 1
  1480. end
  1481. end
  1482.  
  1483. function UI.Window:emit(event)
  1484. local parent = self
  1485. Logger.log('ui', self.UIElement .. ' emitting ' .. event.type)
  1486. while parent do
  1487. if parent.eventHandler then
  1488. if parent:eventHandler(event) then
  1489. return true
  1490. end
  1491. end
  1492. parent = parent.parent
  1493. end
  1494. end
  1495.  
  1496. function UI.Window:eventHandler(event)
  1497. return false
  1498. end
  1499.  
  1500. function UI.Window:setFocus(focus)
  1501. if self.parent then
  1502. self.parent:setFocus(focus)
  1503. end
  1504. end
  1505.  
  1506. function UI.Window:getPreviousFocus(focused)
  1507. if self.children then
  1508. local k = Util.key(self.children, focused)
  1509. for k = k-1, 1, -1 do
  1510. if self.children[k].focus then
  1511. return self.children[k]
  1512. end
  1513. end
  1514. end
  1515. end
  1516.  
  1517. function UI.Window:getNextFocus(focused)
  1518. if self.children then
  1519. local k = Util.key(self.children, focused)
  1520. for k = k+1, #self.children do
  1521. if self.children[k].focus then
  1522. return self.children[k]
  1523. end
  1524. end
  1525. end
  1526. end
  1527.  
  1528. --[[-- Terminal for computer / advanced computer / monitor --]]--
  1529. UI.Device = class.class(UI.Window)
  1530.  
  1531. function UI.Device:init(args)
  1532.  
  1533. local defaults = {
  1534. UIElement = 'Device',
  1535. isUIElement = false,
  1536. backgroundColor = colors.black,
  1537. textColor = colors.white,
  1538. textScale = 1,
  1539. device = term
  1540. }
  1541.  
  1542. UI.setProperties(defaults, args)
  1543.  
  1544. if defaults.deviceType then
  1545. defaults.device = Peripheral.wrap({ type = defaults.deviceType })
  1546. end
  1547.  
  1548. if not defaults.device.setTextScale then
  1549. defaults.device.setTextScale = function(...) end
  1550. end
  1551.  
  1552. defaults.device.setTextScale(defaults.textScale)
  1553. defaults.width, defaults.height = defaults.device.getSize()
  1554.  
  1555. UI.Window.init(self, defaults)
  1556.  
  1557. self.UIElement = nil
  1558.  
  1559. self.isColor = self.device.isColor()
  1560. if not self.isColor then
  1561. self.device.setBackgroundColor = function(...) end
  1562. self.device.setTextColor = function(...) end
  1563. end
  1564. end
  1565.  
  1566. function UI.Device:setCursorPos(x, y)
  1567. self.cursorX = x
  1568. self.cursorY = y
  1569. self.device.setCursorPos(x, y)
  1570. end
  1571.  
  1572. function UI.Device:setCursorBlink(blink)
  1573. self.device.setCursorBlink(blink)
  1574. end
  1575.  
  1576. function UI.Device:write(x, y, text, bg, tc)
  1577. bg = bg or self.backgroundColor
  1578. tc = tc or self.textColor
  1579.  
  1580. self.device.setCursorPos(x, y)
  1581. self.device.setTextColor(tc)
  1582. self.device.setBackgroundColor(bg)
  1583. self.device.write(text)
  1584. end
  1585.  
  1586. function UI.Device:setTextScale(textScale)
  1587. self.textScale = textScale
  1588. self.device.setTextScale(self.textScale)
  1589. end
  1590.  
  1591. function UI.Device:clear(bg)
  1592. bg = bg or self.backgroundColor
  1593. self.device.setBackgroundColor(bg)
  1594. self.device.clear()
  1595. end
  1596.  
  1597. UI.term = UI.Device({ device = term })
  1598. UI.defaultDevice = UI.term
  1599.  
  1600. --[[-- StringBuffer --]]--
  1601. UI.StringBuffer = class.class()
  1602. function UI.StringBuffer:init(bufSize)
  1603. self.bufSize = bufSize
  1604. self.buffer = {}
  1605. end
  1606.  
  1607. function UI.StringBuffer:insert(s, index)
  1608. table.insert(self.buffer, { index = index, str = s })
  1609. end
  1610.  
  1611. function UI.StringBuffer:append(s)
  1612. local str = self:get()
  1613. self:insert(s, #str)
  1614. end
  1615.  
  1616. -- pi -> getBeeParents -> Demonic -> special conditions
  1617. function UI.StringBuffer:get()
  1618. local str = ''
  1619. for k,v in Util.spairs(self.buffer, function(a, b) return a.index < b.index end) do
  1620. str = str .. string.rep(' ', math.max(v.index - string.len(str), 0)) .. v.str
  1621. end
  1622. local len = string.len(str)
  1623. if len < self.bufSize then
  1624. str = str .. string.rep(' ', self.bufSize - len)
  1625. end
  1626. return str
  1627. end
  1628.  
  1629. function UI.StringBuffer:clear()
  1630. self.buffer = {}
  1631. end
  1632.  
  1633. --[[-- Pager --]]--
  1634. UI.Pager = class.class()
  1635. function UI.Pager:init(args)
  1636. local defaults = {
  1637. pages = { }
  1638. }
  1639. UI.setProperties(defaults, args)
  1640. UI.setProperties(self, defaults)
  1641.  
  1642. Event.addHandler('mouse_scroll', function(h, direction, x, y)
  1643. local event = self:pointToChild(self.currentPage, x, y)
  1644. local directions = {
  1645. [ -1 ] = 'up',
  1646. [ 1 ] = 'down'
  1647. }
  1648. event.type = 'key'
  1649. event.key = directions[direction]
  1650. event.UIElement:emit(event)
  1651. end)
  1652.  
  1653. Event.addHandler('monitor_touch', function(h, button, x, y)
  1654. self:click(1, x, y)
  1655. end)
  1656.  
  1657. Event.addHandler('mouse_click', function(h, button, x, y)
  1658. self:click(button, x, y)
  1659. end)
  1660.  
  1661. Event.addHandler('char', function(h, ch)
  1662. self:emitEvent({ type = 'key', key = ch })
  1663. end)
  1664.  
  1665. Event.addHandler('key', function(h, code)
  1666. local ch = keys.getName(code)
  1667. -- filter out a through z as they will be get picked up
  1668. -- as char events
  1669. if ch and #ch > 1 then
  1670. if self.currentPage then
  1671. self:emitEvent({ type = 'key', key = ch })
  1672. if ch == 'f10' then
  1673. UI.displayTable(_G, 'Global Memory')
  1674. elseif ch == 'f9' then
  1675. UI.displayTable(getfenv(4), 'Local memory')
  1676. end
  1677. end
  1678. end
  1679. end)
  1680. end
  1681.  
  1682. function UI.Pager:emitEvent(event)
  1683. if self.currentPage and self.currentPage.focused then
  1684. return self.currentPage.focused:emit(event)
  1685. end
  1686. end
  1687.  
  1688. function UI.Pager:pointToChild(parent, x, y)
  1689. if parent.children then
  1690. for _,child in pairs(parent.children) do
  1691. if x >= child.x and x < child.x + child.width and
  1692. y >= child.y and y < child.y + child.height then
  1693. local c = self:pointToChild(child, x - child.x + 1, y - child.y + 1)
  1694. if c then
  1695. return c
  1696. end
  1697. end
  1698. end
  1699. end
  1700. return {
  1701. UIElement = parent,
  1702. x = x,
  1703. y = y
  1704. }
  1705. end
  1706.  
  1707. function UI.Pager:click(button, x, y)
  1708. if self.currentPage then
  1709.  
  1710. local function getClicked(parent, button, x, y)
  1711. if parent.children then
  1712. for _,child in pairs(parent.children) do
  1713. if x >= child.x and x < child.x + child.width and
  1714. y >= child.y and y < child.y + child.height and
  1715. not child.isShadow then
  1716. local c = getClicked(child, button, x - child.x + 1, y - child.y + 1)
  1717. if c then
  1718. return c
  1719. end
  1720. end
  1721. end
  1722. end
  1723. local events = { 'mouse_click', 'mouse_rightclick' }
  1724. return {
  1725. UIElement = parent,
  1726. type = events[button],
  1727. button = button,
  1728. x = x,
  1729. y = y
  1730. }
  1731. end
  1732.  
  1733. local clickEvent = getClicked(self.currentPage, button,
  1734. x - self.currentPage.x + 1,
  1735. y - self.currentPage.y + 1)
  1736.  
  1737. if clickEvent.UIElement.focus then
  1738. self.currentPage:setFocus(clickEvent.UIElement)
  1739. end
  1740. clickEvent.UIElement:emit(clickEvent)
  1741. end
  1742. end
  1743.  
  1744. function UI.Pager:addPage(name, page)
  1745. self.pages[name] = page
  1746. end
  1747.  
  1748. function UI.Pager:setPages(pages)
  1749. self.pages = pages
  1750. end
  1751.  
  1752. function UI.Pager:getPage(pageName)
  1753. local page = self.pages[pageName]
  1754.  
  1755. if not page then
  1756. error('Pager:getPage: Invalid page: ' .. tostring(pageName), 2)
  1757. end
  1758.  
  1759. return page
  1760. end
  1761.  
  1762. function UI.Pager:setPage(pageOrName)
  1763. local page = pageOrName
  1764.  
  1765. if type(pageOrName) == 'string' then
  1766. page = self.pages[pageOrName]
  1767. end
  1768.  
  1769. if page == self.currentPage then
  1770. page:draw()
  1771. else
  1772. if self.currentPage then
  1773. if self.currentPage.focused then
  1774. self.currentPage.focused.focused = false
  1775. self.currentPage.focused:focus()
  1776. end
  1777. self.currentPage:disable()
  1778. self.currentPage.enabled = false
  1779. page.previousPage = self.currentPage
  1780. end
  1781. self.currentPage = page
  1782. self.currentPage:reset(page.backgroundColor)
  1783. page.enabled = true
  1784. page:enable()
  1785. page:draw()
  1786. if self.currentPage.focused then
  1787. self.currentPage.focused.focused = true
  1788. self.currentPage.focused:focus()
  1789. end
  1790. end
  1791. end
  1792.  
  1793. function UI.Pager:getCurrentPage()
  1794. return self.currentPage
  1795. end
  1796.  
  1797. function UI.Pager:setPreviousPage()
  1798. if self.currentPage.previousPage then
  1799. local previousPage = self.currentPage.previousPage.previousPage
  1800. self:setPage(self.currentPage.previousPage)
  1801. self.currentPage.previousPage = previousPage
  1802. end
  1803. end
  1804.  
  1805. UI.pager = UI.Pager()
  1806.  
  1807. --[[-- Page --]]--
  1808. UI.Page = class.class(UI.Window)
  1809. function UI.Page:init(args)
  1810. local defaults = {
  1811. UIElement = 'Page',
  1812. parent = UI.defaultDevice,
  1813. accelerators = { }
  1814. }
  1815. --if not args or not args.parent then
  1816. --self.parent = UI.defaultDevice
  1817. --end
  1818.  
  1819. UI.setProperties(defaults, args)
  1820. UI.Window.init(self, defaults)
  1821.  
  1822. self.focused = self:findFirstFocus(self)
  1823. --if self.focused then
  1824. --self.focused.focused = true
  1825. --end
  1826. end
  1827.  
  1828. function UI.Page:enable()
  1829. end
  1830.  
  1831. function UI.Page:disable()
  1832. end
  1833.  
  1834. function UI.Page:draw()
  1835. UI.Window.draw(self)
  1836. --if self.focused then
  1837. --self:setFocus(self.focused)
  1838. --end
  1839. end
  1840.  
  1841. function UI.Page:findFirstFocus(parent)
  1842. if not parent.children then
  1843. return
  1844. end
  1845. for _,child in ipairs(parent.children) do
  1846. if child.children then
  1847. local c = self:findFirstFocus(child)
  1848. if c then
  1849. return c
  1850. end
  1851. end
  1852. if child.focus then
  1853. return child
  1854. end
  1855. end
  1856. end
  1857.  
  1858. function UI.Page:getFocused()
  1859. return self.focused
  1860. end
  1861.  
  1862. function UI.Page:focusFirst()
  1863. local focused = self:findFirstFocus(self)
  1864. if focused then
  1865. self:setFocus(focused)
  1866. end
  1867. end
  1868.  
  1869. function UI.Page:focusPrevious()
  1870.  
  1871. local parent = self.focused.parent
  1872. local child = self.focused
  1873. local focused
  1874.  
  1875. while parent do
  1876. focused = parent:getPreviousFocus(child)
  1877. if focused then
  1878. break
  1879. end
  1880. child = parent
  1881. parent = parent.parent
  1882. if not parent.getPreviousFocused then
  1883. break
  1884. end
  1885. end
  1886.  
  1887. if focused then
  1888. self:setFocus(focused)
  1889. end
  1890. end
  1891.  
  1892. function UI.Page:focusNext()
  1893.  
  1894. local parent = self.focused.parent
  1895. local child = self.focused
  1896. local focused
  1897.  
  1898. while parent do
  1899. focused = parent:getNextFocus(child)
  1900. if focused then
  1901. break
  1902. end
  1903. child = parent
  1904. parent = parent.parent
  1905. if not parent.getNextFocused then
  1906. break
  1907. end
  1908. end
  1909.  
  1910. if focused then
  1911. self:setFocus(focused)
  1912. end
  1913. end
  1914.  
  1915. function UI.Page:setFocus(child)
  1916. if not child.focus then
  1917. return
  1918. --error('cannot focus child ' .. child.UIElement, 2)
  1919. end
  1920.  
  1921. if self.focused and self.focused ~= child then
  1922. self.focused.focused = false
  1923. self.focused:focus()
  1924. self.focused = child
  1925. end
  1926.  
  1927. if not child.focused then
  1928. child.focused = true
  1929. self:emit({ type = 'focus_change', focused = child })
  1930. end
  1931.  
  1932. child:focus()
  1933. end
  1934.  
  1935. function UI.Page:eventHandler(event)
  1936.  
  1937. if self.focused then
  1938. if event.type == 'key' then
  1939. local acc = self.accelerators[event.key]
  1940. if acc then
  1941. if self:eventHandler({ type = acc }) then
  1942. return true
  1943. end
  1944. end
  1945. local ch = event.key
  1946. if ch == 'down' or ch == 'enter' or ch == 'k' or ch == 'tab' then
  1947. self:focusNext()
  1948. return true
  1949. elseif ch == 'tab' then
  1950. --self:focusNextGroup()
  1951. elseif ch == 'up' or ch == 'j' then
  1952. self:focusPrevious()
  1953. return true
  1954. end
  1955. end
  1956. end
  1957. end
  1958.  
  1959. --[[-- GridLayout --]]--
  1960. UI.GridLayout = class.class(UI.Window)
  1961. function UI.GridLayout:init(args)
  1962. local defaults = {
  1963. UIElement = 'GridLayout',
  1964. x = 1,
  1965. y = 1,
  1966. textColor = colors.white,
  1967. backgroundColor = colors.black,
  1968. values = {},
  1969. columns = {}
  1970. }
  1971. UI.setProperties(defaults, args)
  1972. UI.Window.init(self, defaults)
  1973. end
  1974.  
  1975. function UI.GridLayout:setParent()
  1976. UI.Window.setParent(self)
  1977. self:adjustWidth()
  1978. end
  1979.  
  1980. function UI.GridLayout:adjustWidth()
  1981. if not self.width then
  1982. self.width = self:calculateWidth()
  1983. end
  1984. if self.autospace then
  1985. local width
  1986. for _,col in pairs(self.columns) do
  1987. width = 1
  1988. for _,row in pairs(self.t) do
  1989. local value = row[col[2]]
  1990. if value then
  1991. value = tostring(value)
  1992. if #value > width then
  1993. width = #value
  1994. end
  1995. end
  1996. end
  1997. col[3] = width
  1998. end
  1999.  
  2000. local colswidth = 0
  2001. for _,c in pairs(self.columns) do
  2002. colswidth = colswidth + c[3] + 1
  2003. end
  2004.  
  2005. local spacing = (self.width - colswidth - 1)
  2006. if spacing > 0 then
  2007. spacing = math.floor(spacing / (#self.columns - 1) )
  2008. for _,c in pairs(self.columns) do
  2009. c[3] = c[3] + spacing
  2010. end
  2011. end
  2012.  
  2013. --[[
  2014. width = 1
  2015. for _,c in pairs(self.columns) do
  2016. width = c[3] + width + 1
  2017. end
  2018.  
  2019. if width < self.width then
  2020. local spacing = self.width - width
  2021. spacing = spacing / #self.columns
  2022. for i = 1, #self.columns do
  2023. local col = self.columns[i]
  2024. col[3] = col[3] + spacing
  2025. end
  2026. elseif width > self.width then
  2027. end
  2028. --]]
  2029. end
  2030. end
  2031.  
  2032. function UI.GridLayout:calculateWidth()
  2033. -- gutters on each side
  2034. local width = 2
  2035. for _,col in pairs(self.columns) do
  2036. width = width + col[3] + 1
  2037. end
  2038. return width - 1
  2039. end
  2040.  
  2041. function UI.GridLayout:drawRow(row, y)
  2042. local sb = UI.StringBuffer(self.width)
  2043. local x = 1
  2044. for _,col in pairs(self.columns) do
  2045.  
  2046. local value = row[col[2]]
  2047. if value then
  2048. sb:insert(string.sub(value, 1, col[3]), x)
  2049. end
  2050.  
  2051. x = x + col[3] + 1
  2052. end
  2053.  
  2054. local selected = index == self.index and self.selectable
  2055. if selected then
  2056. self:setSelected(row)
  2057. end
  2058.  
  2059. self.parent:write(
  2060. self.x, y, sb:get(), self.backgroundColor, self.textColor)
  2061. end
  2062.  
  2063. function UI.GridLayout:draw()
  2064.  
  2065. local size = #self.values
  2066. local startRow = self:getStartRow()
  2067. local endRow = startRow + self.height - 1
  2068. if endRow > size then
  2069. endRow = size
  2070. end
  2071.  
  2072. for i = startRow, endRow do
  2073. self:drawRow(self.values[i], self.y + i - 1)
  2074. end
  2075.  
  2076. if endRow - startRow < self.height - 1 then
  2077. self.parent:clearArea(
  2078. self.x, self.y + endRow, self.width, self.height - endRow, self.backgroundColor)
  2079. end
  2080. end
  2081.  
  2082. function UI.GridLayout:getStartRow()
  2083. return 1
  2084. end
  2085.  
  2086. --[[-- Grid --]]--
  2087. UI.Grid = class.class(UI.Window)
  2088. function UI.Grid:init(args)
  2089. local defaults = {
  2090. UIElement = 'Grid',
  2091. x = 1,
  2092. y = 1,
  2093. pageNo = 1,
  2094. index = 1,
  2095. inverseSort = false,
  2096. disableHeader = false,
  2097. selectable = true,
  2098. textColor = colors.white,
  2099. textSelectedColor = colors.white,
  2100. backgroundColor = colors.black,
  2101. backgroundSelectedColor = colors.green,
  2102. t = {},
  2103. columns = {}
  2104. }
  2105. UI.setProperties(defaults, args)
  2106. UI.Window.init(self, defaults)
  2107. end
  2108.  
  2109. function UI.Grid:setParent()
  2110. UI.Window.setParent(self)
  2111. self:adjustWidth()
  2112. if not self.pageSize then
  2113. if self.disableHeader then
  2114. self.pageSize = self.height
  2115. else
  2116. self.pageSize = self.height - 1
  2117. end
  2118. end
  2119. end
  2120.  
  2121. function UI.Grid:adjustWidth()
  2122. if self.autospace then
  2123. for _,col in pairs(self.columns) do
  2124. col[3] = #col[1]
  2125. end
  2126.  
  2127. for _,col in pairs(self.columns) do
  2128. for _,row in pairs(self.t) do
  2129. local value = row[col[2]]
  2130. if value then
  2131. value = tostring(value)
  2132. if #value > col[3] then
  2133. col[3] = #value
  2134. end
  2135. end
  2136. end
  2137. end
  2138.  
  2139. local colswidth = 1
  2140. for _,c in pairs(self.columns) do
  2141. colswidth = colswidth + c[3] + 1
  2142. end
  2143.  
  2144. local function round(num)
  2145. if num >= 0 then return math.floor(num+.5)
  2146. else return math.ceil(num-.5) end
  2147. end
  2148.  
  2149. local spacing = (self.width - colswidth)
  2150. if spacing > 0 then
  2151. spacing = spacing / (#self.columns - 1)
  2152. local space = 0
  2153. for k,c in pairs(self.columns) do
  2154. space = space + spacing
  2155. c[3] = c[3] + math.floor(round(space) / k)
  2156. end
  2157. end
  2158. end
  2159. end
  2160.  
  2161. function UI.Grid:setPosition(x, y)
  2162. self.x = x
  2163. self.y = y
  2164. end
  2165.  
  2166. function UI.Grid:setPageSize(pageSize)
  2167. self.pageSize = pageSize
  2168. end
  2169.  
  2170. function UI.Grid:setColumns(columns)
  2171. self.columns = columns
  2172. end
  2173.  
  2174. function UI.Grid:getTable()
  2175. return self.t
  2176. end
  2177.  
  2178. function UI.Grid:setTable(t)
  2179. self.t = t
  2180. end
  2181.  
  2182. function UI.Grid:setInverseSort(inverseSort)
  2183. self.inverseSort = inverseSort
  2184. self:drawRows()
  2185. end
  2186.  
  2187. function UI.Grid:setSortColumn(column)
  2188. self.sortColumn = column
  2189. for _,col in pairs(self.columns) do
  2190. if col[2] == column then
  2191. return
  2192. end
  2193. end
  2194. error('Grid:setSortColumn: invalid column', 2)
  2195. end
  2196.  
  2197. function UI.Grid:setSelected(row)
  2198. self.selected = row
  2199. self:emit({ type = 'grid_focus_row', selected = self.selected })
  2200. end
  2201.  
  2202. function UI.Grid:getSelected()
  2203. return self.selected
  2204. end
  2205.  
  2206. function UI.Grid:focus()
  2207. self:draw()
  2208. end
  2209.  
  2210. function UI.Grid:draw()
  2211. if not self.disableHeader then
  2212. self:drawHeadings()
  2213. end
  2214. self:drawRows()
  2215. end
  2216.  
  2217. function UI.Grid:drawHeadings()
  2218.  
  2219. local sb = UI.StringBuffer(self.width)
  2220. local x = 1
  2221. local indx
  2222. for k,col in ipairs(self.columns) do
  2223. local width = col[3] + 1
  2224. if self.inverseSort then
  2225. if col[2] == self.sortColumn then
  2226. indx = x + #col[1] + 1
  2227. end
  2228. end
  2229. sb:insert(col[1], x)
  2230. x = x + width
  2231. end
  2232. self:write(1, 1, sb:get(), colors.blue)
  2233. if indx then
  2234. self:write(indx, 1, '^', colors.blue, colors.gray)
  2235. end
  2236. end
  2237.  
  2238. function UI.Grid:calculateWidth()
  2239. -- gutters on each side
  2240. local width = 2
  2241. for _,col in pairs(self.columns) do
  2242. width = width + col[3] + 1
  2243. end
  2244. return width - 1
  2245. end
  2246.  
  2247. function UI.Grid:drawRows()
  2248.  
  2249. local function sortM(a, b)
  2250. a = a[self.sortColumn]
  2251. b = b[self.sortColumn]
  2252. if not a then
  2253. return false
  2254. elseif not b then
  2255. return true
  2256. end
  2257. return a < b
  2258. end
  2259.  
  2260. local function inverseSortM(a, b)
  2261. a = a[self.sortColumn]
  2262. b = b[self.sortColumn]
  2263. if not a then
  2264. return true
  2265. elseif not b then
  2266. return false
  2267. end
  2268. return a > b
  2269. end
  2270.  
  2271. local sortMethod
  2272. if self.sortColumn then
  2273. sortMethod = sortM
  2274. if self.inverseSort then
  2275. sortMethod = inverseSortM
  2276. end
  2277. end
  2278.  
  2279. if self.index > Util.size(self.t) then
  2280. local newIndex = Util.size(self.t)
  2281. if newIndex <= 0 then
  2282. newIndex = 1
  2283. end
  2284. self:setIndex(newIndex)
  2285. if Util.size(self.t) == 0 then
  2286. y = 1
  2287. if not self.disableHeader then
  2288. y = y + 1
  2289. end
  2290. self:clearArea(1, y, self.width, self.pageSize, self.backgroundColor)
  2291. end
  2292. return
  2293. end
  2294.  
  2295. local startRow = self:getStartRow()
  2296. local y = self.y
  2297. local rowCount = 0
  2298. local sb = UI.StringBuffer(self.width)
  2299.  
  2300. if not self.disableHeader then
  2301. y = y + 1
  2302. end
  2303.  
  2304. local index = 1
  2305. for _,row in Util.spairs(self.t, sortMethod) do
  2306. if index >= startRow then
  2307. sb:clear()
  2308. if index >= startRow + self.pageSize then
  2309. break
  2310. end
  2311.  
  2312. if self.focused then
  2313. if index == self.index and self.selectable then
  2314. sb:insert('>', 0)
  2315. end
  2316. end
  2317.  
  2318. local x = 1
  2319. for _,col in pairs(self.columns) do
  2320.  
  2321. local value = row[col[2]]
  2322. if value then
  2323. sb:insert(string.sub(value, 1, col[3]), x)
  2324. end
  2325.  
  2326. x = x + col[3] + 1
  2327. end
  2328.  
  2329. local selected = index == self.index and self.selectable
  2330. if selected then
  2331. self:setSelected(row)
  2332. end
  2333.  
  2334. self.parent:write(self.x, y, sb:get(),
  2335. self:getRowBackgroundColor(row, selected),
  2336. self:getRowTextColor(row, selected))
  2337.  
  2338. y = y + 1
  2339. rowCount = rowCount + 1
  2340. end
  2341. index = index + 1
  2342. end
  2343.  
  2344. if rowCount < self.pageSize then
  2345. self.parent:clearArea(self.x, y, self.width, self.pageSize-rowCount, self.backgroundColor)
  2346. end
  2347. term.setTextColor(colors.white)
  2348. end
  2349.  
  2350. function UI.Grid:getRowTextColor(row, selected)
  2351. if selected then
  2352. return self.textSelectedColor
  2353. end
  2354. return self.textColor
  2355. end
  2356.  
  2357. function UI.Grid:getRowBackgroundColor(row, selected)
  2358. if selected then
  2359. if self.focused then
  2360. return self.backgroundSelectedColor
  2361. else
  2362. return colors.lightGray
  2363. end
  2364. end
  2365. return self.backgroundColor
  2366. end
  2367.  
  2368. function UI.Grid:getIndex(index)
  2369. return self.index
  2370. end
  2371.  
  2372. function UI.Grid:setIndex(index)
  2373. if self.index ~= index then
  2374. if index < 1 then
  2375. index = 1
  2376. end
  2377. self.index = index
  2378. self:drawRows()
  2379. end
  2380. end
  2381.  
  2382. function UI.Grid:getStartRow()
  2383. return math.floor((self.index - 1)/ self.pageSize) * self.pageSize + 1
  2384. end
  2385.  
  2386. function UI.Grid:getPage()
  2387. return math.floor(self.index / self.pageSize) + 1
  2388. end
  2389.  
  2390. function UI.Grid:getPageCount()
  2391. local tableSize = Util.size(self.t)
  2392. local pc = math.floor(tableSize / self.pageSize)
  2393. if tableSize % self.pageSize > 0 then
  2394. pc = pc + 1
  2395. end
  2396. return pc
  2397. end
  2398.  
  2399. function UI.Grid:nextPage()
  2400. self:setPage(self:getPage() + 1)
  2401. end
  2402.  
  2403. function UI.Grid:previousPage()
  2404. self:setPage(self:getPage() - 1)
  2405. end
  2406.  
  2407. function UI.Grid:setPage(pageNo)
  2408. -- 1 based paging
  2409. self:setIndex((pageNo-1) * self.pageSize + 1)
  2410. end
  2411.  
  2412. function UI.Grid:eventHandler(event)
  2413.  
  2414. if event.type == 'mouse_click' then
  2415. if not self.disableHeader then
  2416. if event.y == 1 then
  2417. local col = 2
  2418. for _,c in ipairs(self.columns) do
  2419. if event.x < col + c[3] then
  2420. if self.sortColumn == c[2] then
  2421. self:setInverseSort(not self.inverseSort)
  2422. else
  2423. self.sortColumn = c[2]
  2424. self:setInverseSort(false)
  2425. end
  2426. self:draw()
  2427. break
  2428. end
  2429. col = col + c[3] + 1
  2430. end
  2431. return true
  2432. end
  2433. end
  2434. local row = self:getStartRow() + event.y - 1
  2435. if not self.disableHeader then
  2436. row = row - 1
  2437. end
  2438. if row > 0 and row <= Util.size(self.t) then
  2439. self:setIndex(row)
  2440. self:emit({ type = 'key', key = 'enter' })
  2441. return true
  2442. end
  2443. elseif event.type == 'key' then
  2444. if event.key == 'enter' then
  2445. self:emit({ type = 'grid_select', selected = self.selected })
  2446. return false
  2447. elseif event.key == 'j' or event.key == 'down' then
  2448. self:setIndex(self.index + 1)
  2449. elseif event.key == 'k' or event.key == 'up' then
  2450. self:setIndex(self.index - 1)
  2451. elseif event.key == 'h' then
  2452. self:setIndex(self.index - self.pageSize)
  2453. elseif event.key == 'l' then
  2454. self:setIndex(self.index + self.pageSize)
  2455. elseif event.key == 'home' then
  2456. self:setIndex(1)
  2457. elseif event.key == 'end' then
  2458. self:setIndex(Util.size(self.t))
  2459. else
  2460. return false
  2461. end
  2462. return true
  2463. end
  2464. return false
  2465. end
  2466.  
  2467. --[[-- ScrollingGrid --]]--
  2468. UI.ScrollingGrid = class.class(UI.Grid)
  2469. function UI.ScrollingGrid:init(args)
  2470. local defaults = {
  2471. UIElement = 'ScrollingGrid',
  2472. scrollOffset = 1
  2473. }
  2474. UI.setProperties(self, defaults)
  2475. UI.Grid.init(self, args)
  2476. end
  2477.  
  2478. function UI.ScrollingGrid:drawRows()
  2479. UI.Grid.drawRows(self)
  2480. self:drawScrollbar()
  2481. end
  2482.  
  2483. function UI.ScrollingGrid:drawScrollbar()
  2484. local ts = Util.size(self.t)
  2485. if ts > self.pageSize then
  2486. term.setBackgroundColor(self.backgroundColor)
  2487. local sbSize = self.pageSize - 2
  2488. local sa = ts -- - self.pageSize
  2489. sa = self.pageSize / sa
  2490. sa = math.floor(sbSize * sa)
  2491. if sa < 1 then
  2492. sa = 1
  2493. end
  2494. if sa > sbSize then
  2495. sa = sbSize
  2496. end
  2497. local sp = ts-self.pageSize
  2498. sp = self.scrollOffset / sp
  2499. sp = math.floor(sp * (sbSize-sa + 0.5))
  2500.  
  2501. local x = self.x + self.width-1
  2502. if self.scrollOffset > 1 then
  2503. self.parent:write(x, self.y + 1, '^')
  2504. else
  2505. self.parent:write(x, self.y + 1, ' ')
  2506. end
  2507. local row = 0
  2508. for i = 0, sp - 1 do
  2509. self.parent:write(x, self.y + row+2, '|')
  2510. row = row + 1
  2511. end
  2512. for i = 1, sa do
  2513. self.parent:write(x, self.y + row+2, '#')
  2514. row = row + 1
  2515. end
  2516. for i = row, sbSize do
  2517. self.parent:write(x, self.y + row+2, '|')
  2518. row = row + 1
  2519. end
  2520. if self.scrollOffset + self.pageSize - 1 < Util.size(self.t) then
  2521. self.parent:write(x, self.y + self.pageSize, 'v')
  2522. else
  2523. self.parent:write(x, self.y + self.pageSize, ' ')
  2524. end
  2525. end
  2526. end
  2527.  
  2528. function UI.ScrollingGrid:getStartRow()
  2529. local ts = Util.size(self.t)
  2530. if ts < self.pageSize then
  2531. self.scrollOffset = 1
  2532. end
  2533. return self.scrollOffset
  2534. end
  2535.  
  2536. function UI.ScrollingGrid:setIndex(index)
  2537. if index < self.scrollOffset then
  2538. self.scrollOffset = index
  2539. elseif index - (self.scrollOffset - 1) > self.pageSize then
  2540. self.scrollOffset = index - self.pageSize + 1
  2541. end
  2542.  
  2543. if self.scrollOffset < 1 then
  2544. self.scrollOffset = 1
  2545. else
  2546. local ts = Util.size(self.t)
  2547. if self.pageSize + self.scrollOffset > ts then
  2548. self.scrollOffset = ts - self.pageSize + 1
  2549. end
  2550. end
  2551. UI.Grid.setIndex(self, index)
  2552. end
  2553.  
  2554. --[[-- Menu --]]--
  2555. UI.Menu = class.class(UI.Grid)
  2556. function UI.Menu:init(args)
  2557. local defaults = {
  2558. UIElement = 'Menu',
  2559. disableHeader = true,
  2560. columns = { { 'Prompt', 'prompt', 20 } },
  2561. t = args['menuItems']
  2562. }
  2563. UI.setProperties(defaults, args)
  2564. UI.Grid.init(self, defaults)
  2565. self.pageSize = #args.menuItems
  2566. end
  2567.  
  2568. function UI.Menu:setParent()
  2569. UI.Grid.setParent(self)
  2570. self.itemWidth = 1
  2571. for _,v in pairs(self.t) do
  2572. if #v.prompt > self.itemWidth then
  2573. self.itemWidth = #v.prompt
  2574. end
  2575. end
  2576. self.columns[1][3] = self.itemWidth
  2577.  
  2578. if self.centered then
  2579. self:center()
  2580. else
  2581. self.width = self.itemWidth + 2
  2582. end
  2583. end
  2584.  
  2585. function UI.Menu:center()
  2586. self.x = (self.width - self.itemWidth + 2) / 2
  2587. self.width = self.itemWidth + 2
  2588.  
  2589. end
  2590.  
  2591. function UI.Menu:eventHandler(event)
  2592. if event.type == 'key' then
  2593. if event.key and self.menuItems[tonumber(event.key)] then
  2594. local selected = self.menuItems[tonumber(event.key)]
  2595. self:emit({
  2596. type = selected.event or 'menu_select',
  2597. selected = selected
  2598. })
  2599. return true
  2600. elseif event.key == 'enter' then
  2601. local selected = self.menuItems[self.index]
  2602. self:emit({
  2603. type = selected.event or 'menu_select',
  2604. selected = selected
  2605. })
  2606. return true
  2607. end
  2608. elseif event.type == 'mouse_click' then
  2609. if event.y <= #self.menuItems then
  2610. UI.Grid.setIndex(self, event.y)
  2611. local selected = self.menuItems[self.index]
  2612. self:emit({
  2613. type = selected.event or 'menu_select',
  2614. selected = selected
  2615. })
  2616. return true
  2617. end
  2618. end
  2619. return UI.Grid.eventHandler(self, event)
  2620. end
  2621.  
  2622. --[[-- ViewportWindow --]]--
  2623. UI.ViewportWindow = class.class(UI.Window)
  2624. function UI.ViewportWindow:init(args)
  2625. local defaults = {
  2626. UIElement = 'ViewportWindow',
  2627. x = 1,
  2628. y = 1,
  2629. --width = console.width,
  2630. --height = console.height,
  2631. offset = 0
  2632. }
  2633. UI.setProperties(self, defaults)
  2634. UI.Window.init(self, args)
  2635. self.vpHeight = self.height
  2636. end
  2637.  
  2638. function UI.ViewportWindow:clear(bg)
  2639. self:clearArea(1, 1+self.offset, self.width, self.height+self.offset, bg)
  2640. end
  2641.  
  2642. function UI.ViewportWindow:write(x, y, text, bg)
  2643. local y = y - self.offset
  2644. if y > self.vpHeight then
  2645. self.vpHeight = y
  2646. end
  2647. if y <= self.height and y > 0 then
  2648. UI.Window.write(self, x, y, text, bg)
  2649. end
  2650. end
  2651.  
  2652. function UI.ViewportWindow:setPage(pageNo)
  2653. self:setOffset((pageNo-1) * self.vpHeight + 1)
  2654. end
  2655.  
  2656. function UI.ViewportWindow:setOffset(offset)
  2657. local newOffset = math.max(0, math.min(math.max(0, offset), self.vpHeight-self.height))
  2658. if newOffset ~= self.offset then
  2659. self.offset = newOffset
  2660. self:clear()
  2661. self:draw()
  2662. return true
  2663. end
  2664. return false
  2665. end
  2666.  
  2667. function UI.ViewportWindow:eventHandler(event)
  2668.  
  2669. if ch == 'j' or ch == 'down' then
  2670. return self:setOffset(self.offset + 1)
  2671. elseif ch == 'k' or ch == 'up' then
  2672. return self:setOffset(self.offset - 1)
  2673. elseif ch == 'home' then
  2674. self:setOffset(0)
  2675. elseif ch == 'end' then
  2676. return self:setOffset(self.height-self.vpHeight)
  2677. elseif ch == 'h' then
  2678. return self:setPage(
  2679. math.floor((self.offset - self.vpHeight) / self.vpHeight))
  2680. elseif ch == 'l' then
  2681. return self:setPage(
  2682. math.floor((self.offset + self.vpHeight) / self.vpHeight) + 1)
  2683. else
  2684. return false
  2685. end
  2686. return true
  2687. end
  2688.  
  2689. --[[-- ScrollingText --]]--
  2690. UI.ScrollingText = class.class(UI.Window)
  2691. function UI.ScrollingText:init(args)
  2692. local defaults = {
  2693. UIElement = 'ScrollingText',
  2694. x = 1,
  2695. y = 1,
  2696. backgroundColor = colors.black,
  2697. --height = console.height,
  2698. --width = console.width,
  2699. buffer = { }
  2700. }
  2701. UI.setProperties(defaults, args)
  2702. UI.Window.init(self, defaults)
  2703. end
  2704.  
  2705. function UI.ScrollingText:write(text)
  2706. if #self.buffer+1 >= self.height then
  2707. table.remove(self.buffer, 1)
  2708. end
  2709. table.insert(self.buffer, text)
  2710. self:draw()
  2711. end
  2712.  
  2713. function UI.ScrollingText:clear()
  2714. self.buffer = { }
  2715. self.parent:clearArea(self.x, self.y, self.width, self.height, self.backgroundColor)
  2716. end
  2717.  
  2718. function UI.ScrollingText:draw()
  2719. for k,text in ipairs(self.buffer) do
  2720. self.parent:write(self.x, self.y + k - 1, widthify(text, self.width), self.backgroundColor)
  2721. end
  2722. end
  2723.  
  2724. --[[-- TitleBar --]]--
  2725. UI.TitleBar = class.class(UI.Window)
  2726. function UI.TitleBar:init(args)
  2727. local defaults = {
  2728. UIElement = 'TitleBar',
  2729. height = 1,
  2730. backgroundColor = colors.brown,
  2731. title = ''
  2732. }
  2733. UI.setProperties(defaults, args)
  2734. UI.Window.init(self, defaults)
  2735. end
  2736.  
  2737. function UI.TitleBar:draw()
  2738. self:clearArea(1, 1, self.width, 1, self.backgroundColor)
  2739. local centered = math.ceil((self.width - #self.title) / 2)
  2740. self:write(1 + centered, 1, self.title, self.backgroundColor)
  2741. if self.previousPage then
  2742. self:write(self.width, 1, '*', self.backgroundColor, colors.black)
  2743. end
  2744. --self:write(self.width-1, 1, '?', self.backgroundColor)
  2745. end
  2746.  
  2747. function UI.TitleBar:eventHandler(event)
  2748. if event.type == 'mouse_click' then
  2749. if self.previousPage and event.x == self.width then
  2750. if type(self.previousPage) == 'string' or
  2751. type(self.previousPage) == 'table' then
  2752. UI.pager:setPage(self.previousPage)
  2753. else
  2754. UI.pager:setPreviousPage()
  2755. end
  2756. return true
  2757. end
  2758. end
  2759. end
  2760.  
  2761. --[[-- MenuBar --]]--
  2762. UI.MenuBar = class.class(UI.Window)
  2763. function UI.MenuBar:init(args)
  2764. local defaults = {
  2765. UIElement = 'MenuBar',
  2766. buttons = { },
  2767. height = 1,
  2768. backgroundColor = colors.lightBlue,
  2769. title = ''
  2770. }
  2771. UI.setProperties(defaults, args)
  2772. UI.Window.init(self, defaults)
  2773.  
  2774. if not self.children then
  2775. self.children = { }
  2776. end
  2777. local x = 1
  2778. for _,button in pairs(self.buttons) do
  2779. local buttonProperties = {
  2780. x = x,
  2781. width = #button.text + 2,
  2782. backgroundColor = colors.lightBlue,
  2783. textColor = colors.black
  2784. }
  2785. x = x + buttonProperties.width
  2786. UI.setProperties(buttonProperties, button)
  2787. local child = UI.Button(buttonProperties)
  2788. child.parent = self
  2789. table.insert(self.children, child)
  2790. end
  2791. end
  2792.  
  2793. --[[-- StatusBar --]]--
  2794. UI.StatusBar = class.class(UI.GridLayout)
  2795. function UI.StatusBar:init(args)
  2796. local defaults = {
  2797. UIElement = 'StatusBar',
  2798. backgroundColor = colors.gray,
  2799. columns = {
  2800. { '', 'status', 10 },
  2801. },
  2802. values = { },
  2803. status = { status = '' }
  2804. }
  2805. UI.setProperties(defaults, args)
  2806. UI.GridLayout.init(self, defaults)
  2807. self:setStatus(self.status)
  2808. end
  2809.  
  2810. function UI.StatusBar:setParent()
  2811. UI.GridLayout.setParent(self)
  2812. self.y = self.height
  2813. if #self.columns == 1 then
  2814. self.columns[1][3] = self.width
  2815. end
  2816. end
  2817.  
  2818. function UI.StatusBar:setStatus(status)
  2819. if type(status) == 'string' then
  2820. self.values[1] = { status = status }
  2821. else
  2822. self.values[1] = status
  2823. end
  2824. end
  2825.  
  2826. function UI.StatusBar:setValue(name, value)
  2827. self.status[name] = value
  2828. end
  2829.  
  2830. function UI.StatusBar:getValue(name)
  2831. return self.status[name]
  2832. end
  2833.  
  2834. function UI.StatusBar:timedStatus(status, timeout)
  2835. timeout = timeout or 3
  2836. self:write(2, 1, widthify(status, self.width-2), self.backgroundColor)
  2837. Event.addNamedTimer('statusTimer', timeout, false, function()
  2838. -- fix someday
  2839. if self.parent.enabled then
  2840. self:draw()
  2841. end
  2842. end)
  2843. end
  2844.  
  2845. function UI.StatusBar:getColumnWidth(name)
  2846. for _,v in pairs(self.columns) do
  2847. if v[2] == name then
  2848. return v[3]
  2849. end
  2850. end
  2851. end
  2852.  
  2853. function UI.StatusBar:setColumnWidth(name, width)
  2854. for _,v in pairs(self.columns) do
  2855. if v[2] == name then
  2856. v[3] = width
  2857. break
  2858. end
  2859. end
  2860. end
  2861.  
  2862. --[[-- ProgressBar --]]--
  2863. UI.ProgressBar = class.class(UI.Window)
  2864. function UI.ProgressBar:init(args)
  2865. local defaults = {
  2866. UIElement = 'ProgressBar',
  2867. progressColor = colors.lime,
  2868. backgroundColor = colors.gray,
  2869. height = 1,
  2870. progress = 0
  2871. }
  2872. UI.setProperties(self, defaults)
  2873. UI.Window.init(self, args)
  2874. end
  2875.  
  2876. function UI.ProgressBar:draw()
  2877. local progressWidth = math.ceil(self.progress / 100 * self.width)
  2878. if progressWidth > 0 then
  2879. self.parent:write(self.x, self.y, string.rep(' ', progressWidth), self.progressColor)
  2880. end
  2881. local x = self.x + progressWidth
  2882. progressWidth = self.width - progressWidth
  2883. if progressWidth > 0 then
  2884. self.parent:write(x, self.y, string.rep(' ', progressWidth), self.backgroundColor)
  2885. end
  2886. end
  2887.  
  2888. function UI.ProgressBar:setProgress(progress)
  2889. self.progress = progress
  2890. end
  2891.  
  2892. --[[-- VerticalMeter --]]--
  2893. UI.VerticalMeter = class.class(UI.Window)
  2894. function UI.VerticalMeter:init(args)
  2895. local defaults = {
  2896. UIElement = 'VerticalMeter',
  2897. meterColor = colors.lime,
  2898. height = 1,
  2899. percent = 0
  2900. }
  2901. UI.setProperties(defaults, args)
  2902. UI.Window.init(self, defaults)
  2903. end
  2904.  
  2905. function UI.VerticalMeter:draw()
  2906. local height = self.height - math.ceil(self.percent / 100 * self.height)
  2907. local filler = string.rep(' ', self.width)
  2908.  
  2909. for i = 1, height do
  2910. self:write(1, i, filler, self.backgroundColor)
  2911. end
  2912.  
  2913. for i = height+1, self.height do
  2914. self:write(1, i, filler, self.meterColor)
  2915. end
  2916. end
  2917.  
  2918. function UI.VerticalMeter:setPercent(percent)
  2919. self.percent = percent
  2920. end
  2921.  
  2922. --[[-- Button --]]--
  2923. UI.Button = class.class(UI.Window)
  2924. function UI.Button:init(args)
  2925. local defaults = {
  2926. UIElement = 'Button',
  2927. text = 'button',
  2928. focused = false,
  2929. backgroundColor = colors.gray,
  2930. backgroundFocusColor = colors.green,
  2931. height = 1,
  2932. width = 8,
  2933. event = 'button_press'
  2934. }
  2935. UI.setProperties(defaults, args)
  2936. UI.Window.init(self, defaults)
  2937. end
  2938.  
  2939. function UI.Button:draw()
  2940. local bg = self.backgroundColor
  2941. local ind = ' '
  2942. if self.focused then
  2943. bg = self.backgroundFocusColor
  2944. ind = '>'
  2945. end
  2946. self:clear(bg)
  2947. local text = ind .. self.text .. ' '
  2948. self:centeredWrite(1 + math.floor(self.height / 2), text, bg)
  2949. end
  2950.  
  2951. function UI.Button:focus()
  2952. self:draw()
  2953. end
  2954.  
  2955. function UI.Button:eventHandler(event)
  2956. if (event.type == 'key' and event.key == 'enter') or
  2957. event.type == 'mouse_click' then
  2958. self:emit({ type = self.event, button = self })
  2959. return true
  2960. end
  2961. return false
  2962. end
  2963.  
  2964. --[[-- TextEntry --]]--
  2965. UI.TextEntry = class.class(UI.Window)
  2966. function UI.TextEntry:init(args)
  2967. local defaults = {
  2968. UIElement = 'TextEntry',
  2969. value = '',
  2970. type = 'string',
  2971. focused = false,
  2972. backgroundColor = colors.lightGray,
  2973. backgroundFocusColor = colors.green,
  2974. height = 1,
  2975. width = 8,
  2976. limit = 6
  2977. }
  2978. UI.setProperties(defaults, args)
  2979. UI.Window.init(self, defaults)
  2980. self.value = tostring(self.value)
  2981. end
  2982.  
  2983. function UI.TextEntry:setParent()
  2984. UI.Window.setParent(self)
  2985. if self.limit + 2 > self.width then
  2986. self.limit = self.width - 2
  2987. end
  2988. end
  2989.  
  2990. function UI.TextEntry:draw()
  2991. local bg = self.backgroundColor
  2992. if self.focused then
  2993. bg = self.backgroundFocusColor
  2994. end
  2995. self:write(1, 1, ' ' .. widthify(self.value, self.limit) .. ' ', bg)
  2996. if self.focused then
  2997. self:updateCursor()
  2998. end
  2999. end
  3000.  
  3001. function UI.TextEntry:updateCursor()
  3002. if not self.pos then
  3003. self.pos = #tostring(self.value)
  3004. elseif self.pos > #tostring(self.value) then
  3005. self.pos = #tostring(self.value)
  3006. end
  3007. self:setCursorPos(self.pos+2, 1)
  3008. end
  3009.  
  3010. function UI.TextEntry:focus()
  3011. self:draw()
  3012. if self.focused then
  3013. self:setCursorBlink(true)
  3014. else
  3015. self:setCursorBlink(false)
  3016. end
  3017. end
  3018.  
  3019. --[[
  3020. A few lines below from theoriginalbit
  3021. http://www.computercraft.info/forums2/index.php?/topic/16070-read-and-limit-length-of-the-input-field/
  3022. --]]
  3023. function UI.TextEntry:eventHandler(event)
  3024. if event.type == 'key' then
  3025. local ch = event.key
  3026. if ch == 'left' then
  3027. if self.pos > 0 then
  3028. self.pos = math.max(self.pos-1, 0)
  3029. self:updateCursor()
  3030. end
  3031. elseif ch == 'right' then
  3032. local input = tostring(self.value)
  3033. if self.pos < #input then
  3034. self.pos = math.min(self.pos+1, #input)
  3035. self:updateCursor()
  3036. end
  3037. elseif ch == 'home' then
  3038. self.pos = 0
  3039. self:updateCursor()
  3040. elseif ch == 'end' then
  3041. self.pos = #tostring(self.value)
  3042. self:updateCursor()
  3043. elseif ch == 'backspace' then
  3044. if self.pos > 0 then
  3045. local input = tostring(self.value)
  3046. self.value = input:sub(1, self.pos-1) .. input:sub(self.pos+1)
  3047. self.pos = self.pos - 1
  3048. self:draw()
  3049. self:updateCursor()
  3050. self:emit({ type = 'text_change', text = self.value })
  3051. end
  3052. elseif ch == 'delete' then
  3053. local input = tostring(self.value)
  3054. if self.pos < #input then
  3055. self.value = input:sub(1, self.pos) .. input:sub(self.pos+2)
  3056. self:draw()
  3057. self:updateCursor()
  3058. self:emit({ type = 'text_change', text = self.value })
  3059. end
  3060. elseif #ch == 1 then
  3061. local input = tostring(self.value)
  3062. if #input < self.limit then
  3063. self.value = input:sub(1, self.pos) .. ch .. input:sub(self.pos+1)
  3064. self.pos = self.pos + 1
  3065. self:draw()
  3066. self:updateCursor()
  3067. self:emit({ type = 'text_change', text = self.value })
  3068. end
  3069. else
  3070. return false
  3071. end
  3072. return true
  3073. end
  3074. return false
  3075. end
  3076.  
  3077. --[[-- Chooser --]]--
  3078. UI.Chooser = class.class(UI.Window)
  3079. function UI.Chooser:init(args)
  3080. local defaults = {
  3081. UIElement = 'Chooser',
  3082. choices = { },
  3083. nochoice = 'Select',
  3084. backgroundColor = colors.lightGray,
  3085. backgroundFocusColor = colors.green,
  3086. height = 1
  3087. }
  3088. UI.setProperties(defaults, args)
  3089. UI.Window.init(self, defaults)
  3090. end
  3091.  
  3092. function UI.Chooser:setParent()
  3093. if not self.width then
  3094. self.width = 1
  3095. for _,v in pairs(self.choices) do
  3096. if #v.name > self.width then
  3097. self.width = #v.name
  3098. end
  3099. end
  3100. self.width = self.width + 4
  3101. end
  3102. UI.Window.setParent(self)
  3103. end
  3104.  
  3105. function UI.Chooser:draw()
  3106. local bg = self.backgroundColor
  3107. if self.focused then
  3108. bg = self.backgroundFocusColor
  3109. end
  3110. local choice = Util.find(self.choices, 'value', self.value)
  3111. local value = self.nochoice
  3112. if choice then
  3113. value = choice.name
  3114. end
  3115. self:write(1, 1, '<', bg, colors.black)
  3116. self:write(2, 1, ' ' .. widthify(value, self.width-4) .. ' ', bg)
  3117. self:write(self.width, 1, '>', bg, colors.black)
  3118. end
  3119.  
  3120. function UI.Chooser:focus()
  3121. self:draw()
  3122. end
  3123.  
  3124. function UI.Chooser:eventHandler(event)
  3125. if event.type == 'key' then
  3126. if event.key == 'right' or event.key == 'space' then
  3127. local choice,k = Util.find(self.choices, 'value', self.value)
  3128. if k and k < #self.choices then
  3129. self.value = self.choices[k+1].value
  3130. else
  3131. self.value = self.choices[1].value
  3132. end
  3133. self:emit({ type = 'choice_change', value = self.value })
  3134. self:draw()
  3135. return true
  3136. elseif event.key == 'left' then
  3137. local choice,k = Util.find(self.choices, 'value', self.value)
  3138. if k and k > 1 then
  3139. self.value = self.choices[k-1].value
  3140. else
  3141. self.value = self.choices[#self.choices].value
  3142. end
  3143. self:emit({ type = 'choice_change', value = self.value })
  3144. self:draw()
  3145. return true
  3146. end
  3147. elseif event.type == 'mouse_click' then
  3148. if event.x == 1 then
  3149. self:emit({ type = 'key', key = 'left' })
  3150. return true
  3151. elseif event.x == self.width then
  3152. self:emit({ type = 'key', key = 'right' })
  3153. return true
  3154. end
  3155. end
  3156. end
  3157.  
  3158. --[[-- Text --]]--
  3159. UI.Text = class.class(UI.Window)
  3160. function UI.Text:init(args)
  3161. local defaults = {
  3162. UIElement = 'Text',
  3163. value = '',
  3164. height = 1
  3165. }
  3166. UI.setProperties(defaults, args)
  3167. UI.Window.init(self, defaults)
  3168. end
  3169.  
  3170. function UI.Text:setParent()
  3171. if not self.width then
  3172. self.width = #self.value
  3173. end
  3174. UI.Window.setParent(self)
  3175. end
  3176.  
  3177. function UI.Text:draw()
  3178. local value = self.value or ''
  3179. self:write(1, 1, widthify(value, self.width), self.backgroundColor)
  3180. end
  3181.  
  3182. --[[-- Form --]]--
  3183. UI.Form = class.class(UI.Window)
  3184. UI.Form.D = { -- display
  3185. static = UI.Text,
  3186. entry = UI.TextEntry,
  3187. chooser = UI.Chooser,
  3188. button = UI.Button
  3189. }
  3190.  
  3191. UI.Form.V = { -- validation
  3192. number = function(value)
  3193. return type(value) == 'number'
  3194. end
  3195. }
  3196.  
  3197. UI.Form.T = { -- data types
  3198. number = function(value)
  3199. return tonumber(value)
  3200. end
  3201. }
  3202.  
  3203. function UI.Form:init(args)
  3204. local defaults = {
  3205. UIElement = 'Form',
  3206. values = {},
  3207. fields = {},
  3208. columns = {
  3209. { 'Name', 'name', 20 },
  3210. { 'Values', 'value', 20 }
  3211. },
  3212. x = 1,
  3213. y = 1,
  3214. labelWidth = 20,
  3215. accept = function() end,
  3216. cancel = function() end
  3217. }
  3218. UI.setProperties(defaults, args)
  3219. UI.Window.init(self, defaults)
  3220. self:initChildren(self.values)
  3221. end
  3222.  
  3223. function UI.Form:setValues(values)
  3224. self.values = values
  3225. for k,child in pairs(self.children) do
  3226. if child.key then
  3227. child.value = self.values[child.key]
  3228. if not child.value then
  3229. child.value = ''
  3230. end
  3231. end
  3232. end
  3233. end
  3234.  
  3235. function UI.Form:initChildren(values)
  3236. self.values = values
  3237.  
  3238. if not self.children then
  3239. self.children = { }
  3240. end
  3241.  
  3242. for k,field in pairs(self.fields) do
  3243. if field.label then
  3244. self['label_' .. k] = UI.Text({
  3245. x = 1,
  3246. y = k,
  3247. width = #field.label,
  3248. value = field.label
  3249. })
  3250. end
  3251. local value
  3252. if field.key then
  3253. value = self.values[field.key]
  3254. end
  3255. if not value then
  3256. value = ''
  3257. end
  3258. value = tostring(value)
  3259. local width = #value
  3260. if field.limit then
  3261. width = field.limit + 2
  3262. end
  3263. local fieldProperties = {
  3264. x = self.labelWidth + 2,
  3265. y = k,
  3266. width = width,
  3267. value = value
  3268. }
  3269. UI.setProperties(fieldProperties, field)
  3270. local child = field.display(fieldProperties)
  3271. child.parent = self
  3272. table.insert(self.children, child)
  3273. end
  3274. end
  3275.  
  3276. function UI.Form:eventHandler(event)
  3277.  
  3278. if event.type == 'accept' then
  3279. for _,child in pairs(self.children) do
  3280. if child.key then
  3281. self.values[child.key] = child.value
  3282. end
  3283. end
  3284. return false
  3285. end
  3286.  
  3287. return false
  3288. end
  3289.  
  3290. --[[-- Dialog --]]--
  3291. UI.Dialog = class.class(UI.Page)
  3292. function UI.Dialog:init(args)
  3293. local defaults = {
  3294. x = 7,
  3295. y = 4,
  3296. width = UI.term.width-11,
  3297. height = 7,
  3298. backgroundColor = colors.lightBlue,
  3299. titleBar = UI.TitleBar({ previousPage = true }),
  3300. acceptButton = UI.Button({
  3301. text = 'Accept',
  3302. event = 'accept',
  3303. x = 5,
  3304. y = 5
  3305. }),
  3306. cancelButton = UI.Button({
  3307. text = 'Cancel',
  3308. event = 'cancel',
  3309. x = 17,
  3310. y = 5
  3311. }),
  3312. statusBar = UI.StatusBar(),
  3313. }
  3314. UI.setProperties(defaults, args)
  3315. UI.Page.init(self, defaults)
  3316. end
  3317.  
  3318. function UI.Dialog:eventHandler(event)
  3319. if event.type == 'cancel' then
  3320. UI.pager:setPreviousPage()
  3321. end
  3322. return UI.Page.eventHandler(self, event)
  3323. end
  3324.  
  3325. --[[-- Spinner --]]--
  3326. UI.Spinner = class.class()
  3327. function UI.Spinner:init(args)
  3328. local defaults = {
  3329. UIElement = 'Spinner',
  3330. timeout = .095,
  3331. x = 1,
  3332. y = 1,
  3333. c = os.clock(),
  3334. spinIndex = 0,
  3335. spinSymbols = { '-', '/', '|', '\\' }
  3336. }
  3337. defaults.x, defaults.y = term.getCursorPos()
  3338. defaults.startX = defaults.x
  3339. defaults.startY = defaults.y
  3340.  
  3341. UI.setProperties(self, defaults)
  3342. UI.setProperties(self, args)
  3343. end
  3344.  
  3345. function UI.Spinner:spin(text)
  3346. local cc = os.clock()
  3347. if cc > self.c + self.timeout then
  3348. term.setCursorPos(self.x, self.y)
  3349. local str = self.spinSymbols[self.spinIndex % #self.spinSymbols + 1]
  3350. if text then
  3351. str = str .. ' ' .. text
  3352. end
  3353. term.write(str)
  3354. self.spinIndex = self.spinIndex + 1
  3355. self.c = cc
  3356. os.sleep(0)
  3357. end
  3358. end
  3359.  
  3360. function UI.Spinner:stop(text)
  3361. term.setCursorPos(self.x, self.y)
  3362. local str = string.rep(' ', #self.spinSymbols)
  3363. if text then
  3364. str = str .. ' ' .. text
  3365. end
  3366. term.write(str)
  3367. term.setCursorPos(self.startX, self.startY)
  3368. end
  3369.  
  3370. --[[-- Table Viewer --]]--
  3371. function UI.displayTable(t, title)
  3372.  
  3373. local resultPath = { }
  3374.  
  3375. local resultsPage = UI.Page({
  3376. parent = UI.term,
  3377. titleBar = UI.TitleBar(),
  3378. grid = UI.ScrollingGrid({
  3379. columns = {
  3380. { 'Name', 'name', 10 },
  3381. { 'Value', 'value', 10 }
  3382. },
  3383. sortColumn = 'name',
  3384. pageSize = UI.term.height - 2,
  3385. y = 2,
  3386. width = UI.term.width,
  3387. height = UI.term.height - 3,
  3388. autospace = true
  3389. }),
  3390. })
  3391.  
  3392. function resultsPage:setResult(result, title)
  3393. local t = { }
  3394. if type(result) == 'table' then
  3395. for k,v in pairs(result) do
  3396. local entry = {
  3397. name = k,
  3398. value = tostring(v)
  3399. }
  3400. if type(v) == 'table' then
  3401. if Util.size(v) == 0 then
  3402. entry.value = 'table: (empty)'
  3403. else
  3404. entry.value = 'table'
  3405. entry.table = v
  3406. end
  3407. end
  3408. table.insert(t, entry)
  3409. end
  3410. else
  3411. table.insert(t, {
  3412. name = 'result',
  3413. value = tostring(result)
  3414. })
  3415. end
  3416. self.grid.sortColumn = 'Name'
  3417. self.grid.columns = {
  3418. { 'Name', 'name', 10 },
  3419. { 'Value', 'value', 10 }
  3420. }
  3421. self.grid.t = t
  3422. self.grid:adjustWidth()
  3423. if title then
  3424. self.titleBar.title = title
  3425. end
  3426. end
  3427.  
  3428. function resultsPage.grid:flatten()
  3429. self.columns = { }
  3430. local _,first = next(self.t)
  3431. for k in pairs(first.table) do
  3432. table.insert(self.columns, {
  3433. k, k, 1
  3434. })
  3435. end
  3436. local t = { }
  3437. for k,v in pairs(self.t) do
  3438. v.table.__key = v.name
  3439. t[v.name] = v.table
  3440. end
  3441. self.t = t
  3442. self.sortColumn = '__key'
  3443. self:adjustWidth()
  3444. self:draw()
  3445. end
  3446.  
  3447. function resultsPage.grid:eventHandler(event)
  3448. if event.type == 'key' then
  3449. local ch = event.key
  3450. if ch == 'enter' or ch == 'l' then
  3451. local nameValue = self:getSelected()
  3452. if nameValue.table then
  3453. if Util.size(nameValue.table) > 0 then
  3454. table.insert(resultPath, self.t)
  3455. resultsPage:setResult(nameValue.table)
  3456. self:setIndex(1)
  3457. self:draw()
  3458. end
  3459. end
  3460. return true
  3461. elseif ch == 'f' then
  3462. self:flatten()
  3463. elseif ch == 'h' then
  3464. if #resultPath > 0 then
  3465. self.t = table.remove(resultPath)
  3466. self.columns = {
  3467. { 'Name', 'name', 10 },
  3468. { 'Value', 'value', 10 }
  3469. }
  3470. self.sortColumn = 'Name'
  3471. self:adjustWidth()
  3472. self:draw()
  3473. else
  3474. UI.pager:setPreviousPage()
  3475. end
  3476. return true
  3477. elseif ch == 'q' then
  3478. UI.pager:setPreviousPage()
  3479. return true
  3480. end
  3481. end
  3482. return UI.Grid.eventHandler(self, event)
  3483. end
  3484.  
  3485. resultsPage:setResult(t, title or 'Table Viewer')
  3486. UI.pager:setPage(resultsPage)
  3487. end
  3488.  
  3489. --Import
  3490. _G.TableDB = class.class()
  3491. function TableDB:init(args)
  3492. local defaults = {
  3493. fileName = '',
  3494. dirty = false,
  3495. data = { },
  3496. tabledef = { },
  3497. }
  3498. UI.setProperties(defaults, args)
  3499. UI.setProperties(self, defaults)
  3500. end
  3501.  
  3502. function TableDB:load()
  3503. local table = Util.readTable(self.fileName)
  3504. if table then
  3505. self.data = table.data
  3506. self.tabledef = table.tabledef
  3507. end
  3508. end
  3509.  
  3510. function TableDB:add(key, entry)
  3511. if type(key) == 'table' then
  3512. key = table.concat(key, ':')
  3513. end
  3514. self.data[key] = entry
  3515. self.dirty = true
  3516. end
  3517.  
  3518. function TableDB:get(key)
  3519. if type(key) == 'table' then
  3520. key = table.concat(key, ':')
  3521. end
  3522. return self.data[key]
  3523. end
  3524.  
  3525. function TableDB:remove(key)
  3526. self.data[key] = nil
  3527. self.dirty = true
  3528. end
  3529.  
  3530. function TableDB:flush()
  3531. if self.dirty then
  3532. Util.writeTable(self.fileName, {
  3533. tabledef = self.tabledef,
  3534. data = self.data,
  3535. })
  3536. self.dirty = false
  3537. end
  3538. end
  3539.  
  3540. if turtle then
  3541. --Import
  3542. --[[
  3543. Modes:
  3544. normal - oob functionality
  3545. pathfind - goes around blocks/mobs
  3546. destructive - destroys blocks
  3547. friendly - destructive and creates a 2 block walking passage (not implemented)
  3548.  
  3549. Dig strategies:
  3550. none - does not dig or kill mobs
  3551. normal - digs and kills mobs
  3552. wasteful - digs and drops mined blocks and kills mobs
  3553. cautious - digs and kills mobs but will not destroy other turtles
  3554. --]]
  3555.  
  3556. _G.TL2 = { }
  3557.  
  3558. TL2.actions = {
  3559. up = {
  3560. detect = turtle.detectUp,
  3561. dig = turtle.digUp,
  3562. move = turtle.up,
  3563. attack = turtle.attackUp,
  3564. place = turtle.placeUp,
  3565. suck = turtle.suckUp,
  3566. compare = turtle.compareUp,
  3567. side = 'top'
  3568. },
  3569. down = {
  3570. detect = turtle.detectDown,
  3571. dig = turtle.digDown,
  3572. move = turtle.down,
  3573. attack = turtle.attackDown,
  3574. place = turtle.placeDown,
  3575. suck = turtle.suckDown,
  3576. compare = turtle.compareDown,
  3577. side = 'bottom'
  3578. },
  3579. forward = {
  3580. detect = turtle.detect,
  3581. dig = turtle.dig,
  3582. move = turtle.forward,
  3583. attack = turtle.attack,
  3584. place = turtle.place,
  3585. suck = turtle.suck,
  3586. compare = turtle.compare,
  3587. side = 'front'
  3588. }
  3589. }
  3590.  
  3591. TL2.headings = {
  3592. [ 0 ] = { xd = 1, yd = 0, zd = 0, heading = 0 }, -- east
  3593. [ 1 ] = { xd = 0, yd = 1, zd = 0, heading = 1 }, -- south
  3594. [ 2 ] = { xd = -1, yd = 0, zd = 0, heading = 2 }, -- west
  3595. [ 3 ] = { xd = 0, yd = -1, zd = 0, heading = 3 }, -- north
  3596. [ 4 ] = { xd = 0, yd = 0, zd = 1, heading = 4 }, -- up
  3597. [ 5 ] = { xd = 0, yd = 0, zd = -1, heading = 5 } -- down
  3598. }
  3599.  
  3600. TL2.namedHeadings = {
  3601. east = 0,
  3602. south = 1,
  3603. west = 2,
  3604. north = 3,
  3605. up = 4,
  3606. down = 5
  3607. }
  3608.  
  3609. _G.Point = { }
  3610. -- used mainly to extract point specific info from a table
  3611. function Point.create(pt)
  3612. return { x = pt.x, y = pt.y, z = pt.z }
  3613. end
  3614.  
  3615. function Point.subtract(a, b)
  3616. a.x = a.x - b.x
  3617. a.y = a.y - b.y
  3618. a.z = a.z - b.z
  3619. end
  3620.  
  3621. function Point.tostring(pt)
  3622. local str = string.format('x:%d y:%d z:%d', pt.x, pt.y, pt.z)
  3623. if pt.heading then
  3624. str = str .. ' heading:' .. pt.heading
  3625. end
  3626. return str
  3627. end
  3628.  
  3629. function Point.calculateDistance(a, b)
  3630. if b.z then
  3631. return math.max(a.x, b.x) - math.min(a.x, b.x) +
  3632. math.max(a.y, b.y) - math.min(a.y, b.y) +
  3633. math.max(a.z, b.z) - math.min(a.z, b.z)
  3634. else
  3635. return math.max(a.x, b.x) - math.min(a.x, b.x) +
  3636. math.max(a.y, b.y) - math.min(a.y, b.y)
  3637. end
  3638. end
  3639.  
  3640. -- a better distance calculation - returns # moves + turns
  3641. -- calculate distance to location including turns
  3642. -- also returns the resuling heading
  3643. function Point.calculateMoves(pta, ptb, distance)
  3644. local heading = pta.heading
  3645. local moves = distance or Point.calculateDistance(pta, ptb)
  3646. if (pta.heading % 2) == 0 and pta.y ~= ptb.y then
  3647. moves = moves + 1
  3648. if ptb.heading and (ptb.heading % 2 == 1) then
  3649. heading = ptb.heading
  3650. elseif ptb.y > pta.y then
  3651. heading = 1
  3652. else
  3653. heading = 3
  3654. end
  3655. elseif (pta.heading % 2) == 1 and pta.x ~= ptb.x then
  3656. moves = moves + 1
  3657. if ptb.heading and (ptb.heading % 2 == 0) then
  3658. heading = ptb.heading
  3659. elseif ptb.x > pta.x then
  3660. heading = 0
  3661. else
  3662. heading = 2
  3663. end
  3664. end
  3665.  
  3666. if ptb.heading then
  3667. if heading ~= ptb.heading then
  3668. moves = moves + TL2.calculateTurns(heading, ptb.heading)
  3669. --(math.max(heading, ptb.heading) + 4 - math.min(heading, ptb.heading)) % 4
  3670. heading = ptb.heading
  3671. end
  3672. end
  3673.  
  3674. return moves, heading
  3675. end
  3676.  
  3677. -- deprecated
  3678. function TL2.createPoint(pt)
  3679. return Point.create(pt)
  3680. end
  3681.  
  3682. function TL2.pointToBox(pt, width, length, height)
  3683. return { ax = pt.x,
  3684. ay = pt.y,
  3685. az = pt.z,
  3686. bx = pt.x + width - 1,
  3687. by = pt.y + length - 1,
  3688. bz = pt.z + height - 1
  3689. }
  3690. end
  3691.  
  3692. function TL2.pointInBox(pt, box)
  3693. return pt.x >= box.ax and
  3694. pt.y >= box.ay and
  3695. pt.x <= box.bx and
  3696. pt.y <= box.by
  3697. end
  3698.  
  3699. function TL2.boxContain(boundingBox, containedBox)
  3700.  
  3701. local shiftX = boundingBox.ax - containedBox.ax
  3702. if shiftX > 0 then
  3703. containedBox.ax = containedBox.ax + shiftX
  3704. containedBox.bx = containedBox.bx + shiftX
  3705. end
  3706. local shiftY = boundingBox.ay - containedBox.ay
  3707. if shiftY > 0 then
  3708. containedBox.ay = containedBox.ay + shiftY
  3709. containedBox.by = containedBox.by + shiftY
  3710. end
  3711.  
  3712. shiftX = boundingBox.bx - containedBox.bx
  3713. if shiftX < 0 then
  3714. containedBox.ax = containedBox.ax + shiftX
  3715. containedBox.bx = containedBox.bx + shiftX
  3716. end
  3717. shiftY = boundingBox.by - containedBox.by
  3718. if shiftY < 0 then
  3719. containedBox.ay = containedBox.ay + shiftY
  3720. containedBox.by = containedBox.by + shiftY
  3721. end
  3722. end
  3723.  
  3724. function TL2.calculateTurns(ih, oh)
  3725. if ih == oh then
  3726. return 0
  3727. end
  3728. if (ih % 2) == (oh % 2) then
  3729. return 2
  3730. end
  3731. return 1
  3732. end
  3733.  
  3734. -- deprecated
  3735. function TL2.calculateDistance(a, b)
  3736. return Point.calculateDistance(a, b)
  3737. end
  3738.  
  3739. function TL2.calculateHeadingTowards(startPt, endPt, heading)
  3740. local xo = endPt.x - startPt.x
  3741. local yo = endPt.y - startPt.y
  3742.  
  3743. if heading == 0 and xo > 0 or
  3744. heading == 2 and xo < 0 or
  3745. heading == 1 and yo > 0 or
  3746. heading == 3 and yo < 0 then
  3747. -- maintain current heading
  3748. return heading
  3749. elseif heading == 0 and yo > 0 or
  3750. heading == 2 and yo < 0 or
  3751. heading == 1 and xo < 0 or
  3752. heading == 3 and xo > 0 then
  3753. -- right
  3754. return (heading + 1) % 4
  3755. elseif heading == 0 and yo < 0 or
  3756. heading == 2 and yo > 0 or
  3757. heading == 1 and xo < 0 or
  3758. heading == 3 and xo > 0 then
  3759. -- left
  3760. return (heading + 1) % 4
  3761. elseif yo == 0 and xo ~= 0 or
  3762. xo == 0 and yo ~= 0 then
  3763. -- behind
  3764. return (heading + 2) % 4
  3765. elseif endPt.z > startPt.z then
  3766. -- up
  3767. return 4
  3768. elseif endPt.z < startPt.z then
  3769. -- down
  3770. return 5
  3771. end
  3772. return heading
  3773. end
  3774.  
  3775. local function _attack(action)
  3776. if action.attack() then
  3777. Util.tryTimed(4,
  3778. function()
  3779. -- keep trying until attack fails
  3780. return not action.attack()
  3781. end)
  3782. return true
  3783. end
  3784. return false
  3785. end
  3786.  
  3787. local modes = {
  3788. normal = function(action)
  3789. return action.move()
  3790. end,
  3791.  
  3792. destructive = function(action, digStrategy)
  3793. while not action.move() do
  3794. if not _attack(action) then
  3795. if not digStrategy(action) then
  3796. return false
  3797. end
  3798. end
  3799. Logger.log('turtle', 'destructive move retry: ')
  3800. end
  3801. return true
  3802. end,
  3803.  
  3804. }
  3805.  
  3806. TL2.digStrategies = {
  3807. none = function(action)
  3808. return false
  3809. end,
  3810.  
  3811. normal = function(action)
  3812. if action.dig() then
  3813. return true
  3814. end
  3815. if not action.attack() then
  3816. return false
  3817. end
  3818. for i = 1, 50 do
  3819. if not action.attack() then
  3820. break
  3821. end
  3822. end
  3823. return true
  3824. end,
  3825.  
  3826. cautious = function(action)
  3827. if not TL2.isTurtleAt(action.side) then
  3828. return action.dig()
  3829. end
  3830. os.sleep(.5)
  3831. return not action.detect()
  3832. end,
  3833.  
  3834. wasteful = function(action)
  3835. -- why is there no method to get current slot ?
  3836. local slots = TL2.getSlots()
  3837.  
  3838. -- code below should be done -- only reconcile if no empty slot
  3839. -- taking the cautious approach for now
  3840. --if not selectOpenSlot() then
  3841. --return false
  3842. --end
  3843.  
  3844. if action.detect() and action.dig() then
  3845. TL2.reconcileSlots(slots)
  3846. return true
  3847. end
  3848. TL2.reconcileSlots(slots)
  3849. return false
  3850. end
  3851. }
  3852.  
  3853. local state = {
  3854. x = 0,
  3855. y = 0,
  3856. z = 0,
  3857. slot = 1, -- must track slot since there is no method to determine current slot
  3858. -- not relying on this for now (but will track)
  3859. heading = 0,
  3860. maxRetries = 100,
  3861. status = 'idle',
  3862. mode = modes.normal,
  3863. dig = TL2.digStrategies.normal
  3864. }
  3865.  
  3866. local memory = {
  3867. locations = {},
  3868. blocks = {}
  3869. }
  3870.  
  3871. function TL2.getState()
  3872. return state
  3873. end
  3874.  
  3875. function TL2.getStatus()
  3876. return state.status
  3877. end
  3878.  
  3879. function TL2.setStatus(status)
  3880. state.status = status
  3881. end
  3882.  
  3883. function TL2.getMemory()
  3884. return memory
  3885. end
  3886.  
  3887. function TL2.select(slot)
  3888. state.slot = slot
  3889. turtle.select(slot)
  3890. end
  3891.  
  3892. function TL2.forward()
  3893. if not state.mode(TL2.actions.forward, state.dig) then
  3894. return false
  3895. end
  3896. state.x = state.x + TL2.headings[state.heading].xd
  3897. state.y = state.y + TL2.headings[state.heading].yd
  3898. return true
  3899. end
  3900.  
  3901. function TL2.up()
  3902. if state.mode(TL2.actions.up, state.dig) then
  3903. state.z = state.z + 1
  3904. return true
  3905. end
  3906. return false
  3907. end
  3908.  
  3909. function TL2.down()
  3910. if not state.mode(TL2.actions.down, state.dig) then
  3911. return false
  3912. end
  3913. state.z = state.z - 1
  3914. return true
  3915. end
  3916.  
  3917. function TL2.back()
  3918. if not turtle.back() then
  3919. return false
  3920. end
  3921. state.x = state.x - TL2.headings[state.heading].xd
  3922. state.y = state.y - TL2.headings[state.heading].yd
  3923. return true
  3924. end
  3925.  
  3926. function TL2.dig()
  3927. return state.dig(TL2.actions.forward)
  3928. end
  3929.  
  3930. function TL2.digUp()
  3931. return state.dig(TL2.actions.up)
  3932. end
  3933.  
  3934. function TL2.digDown()
  3935. return state.dig(TL2.actions.down)
  3936. end
  3937.  
  3938. function TL2.attack()
  3939. _attack(TL2.actions.forward)
  3940. end
  3941.  
  3942. function TL2.attackUp()
  3943. _attack(TL2.actions.up)
  3944. end
  3945.  
  3946. function TL2.attackDown()
  3947. _attack(TL2.actions.down)
  3948. end
  3949.  
  3950. local complexActions = {
  3951. up = {
  3952. detect = turtle.detectUp,
  3953. dig = TL2.digUp,
  3954. move = TL2.up,
  3955. attack = TL2.attackUp,
  3956. place = turtle.placeUp,
  3957. side = 'top'
  3958. },
  3959. down = {
  3960. detect = turtle.detectDown,
  3961. dig = TL2.digDown,
  3962. move = TL2.down,
  3963. attack = TL2.attackDown,
  3964. place = turtle.placeDown,
  3965. side = 'bottom'
  3966. },
  3967. forward = {
  3968. detect = turtle.detect,
  3969. dig = TL2.dig,
  3970. move = TL2.forward,
  3971. attack = TL2.attack,
  3972. place = turtle.place,
  3973. side = 'front'
  3974. }
  3975. }
  3976.  
  3977. local function _place(action, slot)
  3978. return Util.tryTimed(5,
  3979. function()
  3980. if action.detect() then
  3981. action.dig()
  3982. end
  3983. if slot then
  3984. TL2.select(slot)
  3985. end
  3986. if action.place() then
  3987. return true
  3988. end
  3989. _attack(action)
  3990. end)
  3991. end
  3992.  
  3993. function TL2.place(slot)
  3994. return _place(complexActions.forward, slot)
  3995. end
  3996.  
  3997. function TL2.placeUp(slot)
  3998. return _place(complexActions.up, slot)
  3999. end
  4000.  
  4001. function TL2.placeDown(slot)
  4002. return _place(complexActions.down, slot)
  4003. end
  4004.  
  4005. function TL2.getModes()
  4006. return modes
  4007. end
  4008.  
  4009. function TL2.getMode()
  4010. return state.mode
  4011. end
  4012.  
  4013. function TL2.setMode(mode)
  4014. if not modes[mode] then
  4015. error('TL2.setMode: invalid mode', 2)
  4016. end
  4017. state.mode = modes[mode]
  4018. end
  4019.  
  4020. function TL2.setDigStrategy(digStrategy)
  4021. if not TL2.digStrategies[digStrategy] then
  4022. error('TL2.setDigStrategy: invalid strategy', 2)
  4023. end
  4024. state.dig = TL2.digStrategies[digStrategy]
  4025. end
  4026.  
  4027. function TL2.reset()
  4028. state.gxOff = state.gxOff - state.x
  4029. state.gyOff = state.gyOff - state.y
  4030. state.gzOff = state.gzOff - state.z
  4031. state.x = 0
  4032. state.y = 0
  4033. state.z = 0
  4034. end
  4035.  
  4036. function TL2.isTurtleAt(side)
  4037. local sideType = peripheral.getType(side)
  4038. return sideType and sideType == 'turtle'
  4039. end
  4040.  
  4041. function TL2.saveLocation()
  4042. return Util.shallowCopy(state)
  4043. end
  4044.  
  4045. function TL2.getNamedLocation(name)
  4046. return memory.locations[name]
  4047. end
  4048.  
  4049. function TL2.gotoNamedLocation(name)
  4050. local nl = memory.locations[name]
  4051. if nl then
  4052. return TL2.goto(nl.x, nl.y, nl.z, nl.heading)
  4053. end
  4054. end
  4055.  
  4056. function TL2.getStoredPoint(name)
  4057. return Util.readTable(name .. '.pt')
  4058. end
  4059.  
  4060. function TL2.gotoStoredPoint(name)
  4061. local pt = TL2.getStoredPoint(name)
  4062. if pt then
  4063. return TL2.gotoPoint(pt)
  4064. end
  4065. end
  4066.  
  4067. function TL2.storePoint(name, pt)
  4068. Util.writeTable(name .. '.pt', pt)
  4069. end
  4070.  
  4071. function TL2.storeCurrentPoint(name)
  4072. local ray = TL2.getPoint()
  4073. TL2.storePoint(name, ray)
  4074. end
  4075.  
  4076. function TL2.saveNamedLocation(name, x, y, z, heading)
  4077. if x then
  4078. memory.locations[name] = {
  4079. x = x,
  4080. y = y,
  4081. z = z,
  4082. heading = heading
  4083. }
  4084. else
  4085. memory.locations[name] = {
  4086. x = state.x,
  4087. y = state.y,
  4088. z = state.z,
  4089. heading = state.heading
  4090. }
  4091. end
  4092. end
  4093.  
  4094. function TL2.normalizeCoords(x, y, z, heading)
  4095. return
  4096. x - state.gxOff,
  4097. y - state.gyOff,
  4098. z - state.gzOff,
  4099. heading
  4100. end
  4101.  
  4102. function TL2.gotoLocation(nloc)
  4103. if nloc.gps then
  4104. return TL2.goto(
  4105. nloc.x - state.gxOff,
  4106. nloc.y - state.gyOff,
  4107. nloc.z - state.gzOff,
  4108. nloc.heading)
  4109. else
  4110. return TL2.goto(nloc.x, nloc.y, nloc.z, nloc.heading)
  4111. end
  4112. end
  4113.  
  4114. function TL2.turnRight()
  4115. TL2.setHeading(state.heading + 1)
  4116. end
  4117.  
  4118. function TL2.turnLeft()
  4119. TL2.setHeading(state.heading - 1)
  4120. end
  4121.  
  4122. function TL2.turnAround()
  4123. TL2.setHeading(state.heading + 2)
  4124. end
  4125.  
  4126. function TL2.getHeadingInfo(heading)
  4127. if heading and type(heading) == 'string' then
  4128. heading = TL2.namedHeadings[heading]
  4129. end
  4130. heading = heading or state.heading
  4131. return TL2.headings[heading]
  4132. end
  4133.  
  4134. function TL2.setNamedHeading(headingName)
  4135. local heading = TL2.namedHeadings[headingName]
  4136. if heading then
  4137. TL2.setHeading(heading)
  4138. end
  4139. return false
  4140. end
  4141.  
  4142. function TL2.getHeading()
  4143. return state.heading
  4144. end
  4145.  
  4146. function TL2.setHeading(heading)
  4147. if not heading then
  4148. return
  4149. end
  4150.  
  4151. if heading ~= state.heading then
  4152. while heading < state.heading do
  4153. heading = heading + 4
  4154. end
  4155. if heading - state.heading == 3 then
  4156. turtle.turnLeft()
  4157. state.heading = state.heading - 1
  4158. else
  4159. turns = heading - state.heading
  4160. while turns > 0 do
  4161. turns = turns - 1
  4162. state.heading = state.heading + 1
  4163. turtle.turnRight()
  4164. end
  4165. end
  4166.  
  4167. if state.heading > 3 then
  4168. state.heading = state.heading - 4
  4169. elseif state.heading < 0 then
  4170. state.heading = state.heading + 4
  4171. end
  4172. end
  4173. end
  4174.  
  4175. function TL2.gotoPoint(pt)
  4176. return TL2.goto(pt.x, pt.y, pt.z, pt.heading)
  4177. end
  4178.  
  4179. function TL2.gotoX2(dx)
  4180.  
  4181. local direction = dx - state.x
  4182. local move
  4183. if direction > 0 and state.heading == 0 or
  4184. direction < 0 and state.heading == 2 then
  4185. move = TL2.forward
  4186. else
  4187. move = TL2.back
  4188. end
  4189.  
  4190. repeat
  4191. if not move() then
  4192. return false
  4193. end
  4194. until state.x == dx
  4195. return true
  4196. end
  4197.  
  4198. function TL2.gotoY2(dy)
  4199.  
  4200. local direction = dy - state.y
  4201. local move
  4202. if direction > 0 and state.heading == 1 or
  4203. direction < 0 and state.heading == 3 then
  4204. move = TL2.forward
  4205. else
  4206. move = TL2.back
  4207. end
  4208.  
  4209. repeat
  4210. if not move() then
  4211. return false
  4212. end
  4213. until state.y == dy
  4214. return true
  4215. end
  4216.  
  4217. -- go backwards - turning around if necessary to fight mobs
  4218. function TL2.goback()
  4219. local hi = TL2.headings[state.heading]
  4220. return TL2.goto(state.x - hi.xd, state.y - hi.yd, state.z, state.heading)
  4221. end
  4222.  
  4223. function TL2.gotoZlast(pt)
  4224. if TL2.goto(pt.x, pt.y, nil, pt.heading) then
  4225. if TL2.gotoZ(pt.z) then
  4226. TL2.setHeading(pt.heading)
  4227. end
  4228. end
  4229. end
  4230.  
  4231. function TL2.goto(dx, dy, dz, dh)
  4232. if not TL2.goto2(dx, dy, dz, dh) then
  4233. if not TL2.goto3(dx, dy, dz) then
  4234. return false
  4235. end
  4236. end
  4237. TL2.setHeading(dh)
  4238. return true
  4239. end
  4240.  
  4241. -- 1 turn goto (going backwards if possible)
  4242. function TL2.goto2(dx, dy, dz, dh)
  4243.  
  4244. dz = dz or state.z
  4245.  
  4246. local function gx()
  4247. if state.x ~= dx then
  4248. TL2.gotoX2(dx)
  4249. end
  4250. if state.y ~= dy then
  4251. if dh and dh % 2 == 1 then
  4252. TL2.setHeading(dh)
  4253. else
  4254. TL2.headTowardsY(dy)
  4255. end
  4256. end
  4257. end
  4258.  
  4259. local function gy()
  4260. if state.y ~= dy then
  4261. TL2.gotoY2(dy)
  4262. end
  4263. if state.x ~= dx then
  4264. if dh and dh % 2 == 0 then
  4265. TL2.setHeading(dh)
  4266. else
  4267. TL2.headTowardsX(dx)
  4268. end
  4269. end
  4270. end
  4271.  
  4272. repeat
  4273. local x, y
  4274. local z = state.z
  4275.  
  4276. repeat
  4277. x, y = state.x, state.y
  4278.  
  4279. if state.heading % 2 == 0 then
  4280. gx()
  4281. gy()
  4282. else
  4283. gy()
  4284. gx()
  4285. end
  4286. until x == state.x and y == state.y
  4287.  
  4288. if state.z ~= dz then
  4289. TL2.gotoZ(dz)
  4290. end
  4291.  
  4292. if state.x == dx and state.y == dy and state.z == dz then
  4293. return true
  4294. end
  4295.  
  4296. until x == state.x and y == state.y and z == state.z
  4297.  
  4298. return false
  4299. end
  4300.  
  4301. -- fallback goto - will turn around if was previously moving backwards
  4302. function TL2.goto3(dx, dy, dz)
  4303.  
  4304. if TL2.gotoEx(dx, dy, dz) then
  4305. return true
  4306. end
  4307.  
  4308. local moved
  4309. repeat
  4310. local x, y, z = state.x, state.y, state.z
  4311.  
  4312. -- try going the other way
  4313. if (state.heading % 2) == 1 then
  4314. TL2.headTowardsX(dx)
  4315. else
  4316. TL2.headTowardsY(dy)
  4317. end
  4318.  
  4319. if TL2.gotoEx(dx, dy, dz) then
  4320. return true
  4321. end
  4322.  
  4323. if dz then
  4324. TL2.gotoZ(dz)
  4325. end
  4326.  
  4327. moved = x ~= state.x or y ~= state.y or z ~= state.z
  4328. until not moved
  4329.  
  4330. return false
  4331. end
  4332.  
  4333. function TL2.gotoEx(dx, dy, dz)
  4334.  
  4335. -- determine the heading to ensure the least amount of turns
  4336. -- first check is 1 turn needed - remaining require 2 turns
  4337. if state.heading == 0 and state.x <= dx or
  4338. state.heading == 2 and state.x >= dx or
  4339. state.heading == 1 and state.y <= dy or
  4340. state.heading == 3 and state.y >= dy then
  4341. -- maintain current heading
  4342. -- nop
  4343. elseif dy > state.y and state.heading == 0 or
  4344. dy < state.y and state.heading == 2 or
  4345. dx < state.x and state.heading == 1 or
  4346. dx > state.x and state.heading == 3 then
  4347. TL2.turnRight()
  4348. else
  4349. TL2.turnLeft()
  4350. end
  4351.  
  4352. if (state.heading % 2) == 1 then
  4353. if not TL2.gotoY(dy) then return false end
  4354. if not TL2.gotoX(dx) then return false end
  4355. else
  4356. if not TL2.gotoX(dx) then return false end
  4357. if not TL2.gotoY(dy) then return false end
  4358. end
  4359.  
  4360. if dz then
  4361. if not TL2.gotoZ(dz) then return false end
  4362. end
  4363.  
  4364. return true
  4365. end
  4366.  
  4367. function TL2.headTowardsX(dx)
  4368. if state.x ~= dx then
  4369. if state.x > dx then
  4370. TL2.setHeading(2)
  4371. else
  4372. TL2.setHeading(0)
  4373. end
  4374. end
  4375. end
  4376.  
  4377. function TL2.headTowardsY(dy)
  4378. if state.y ~= dy then
  4379. if state.y > dy then
  4380. TL2.setHeading(3)
  4381. else
  4382. TL2.setHeading(1)
  4383. end
  4384. end
  4385. end
  4386.  
  4387. function TL2.headTowards(pt)
  4388. if state.x ~= pt.x then
  4389. TL2.headTowardsX(pt.x)
  4390. else
  4391. TL2.headTowardsY(pt.y)
  4392. end
  4393. end
  4394.  
  4395. function TL2.gotoX(dx)
  4396. TL2.headTowardsX(dx)
  4397.  
  4398. while state.x ~= dx do
  4399. if not TL2.forward() then
  4400. return false
  4401. end
  4402. end
  4403. return true
  4404. end
  4405.  
  4406. function TL2.gotoY(dy)
  4407. TL2.headTowardsY(dy)
  4408.  
  4409. while state.y ~= dy do
  4410. if not TL2.forward() then
  4411. return false
  4412. end
  4413. end
  4414. return true
  4415. end
  4416.  
  4417. function TL2.gotoZ(dz)
  4418. while state.z > dz do
  4419. if not TL2.down() then
  4420. return false
  4421. end
  4422. end
  4423.  
  4424. while state.z < dz do
  4425. if not TL2.up() then
  4426. return false
  4427. end
  4428. end
  4429. return true
  4430. end
  4431.  
  4432. function TL2.emptySlots(dropAction)
  4433. dropAction = dropAction or turtle.drop
  4434. for i = 1, 16 do
  4435. TL2.emptySlot(i, dropAction)
  4436. end
  4437. end
  4438.  
  4439. function TL2.emptySlot(slot, dropAction)
  4440. dropAction = dropAction or turtle.drop
  4441. local count = turtle.getItemCount(slot)
  4442. if count > 0 then
  4443. TL2.select(slot)
  4444. dropAction(count)
  4445. end
  4446. end
  4447.  
  4448. function TL2.getSlots(slots)
  4449. slots = slots or { }
  4450. for i = 1, 16 do
  4451. slots[i] = {
  4452. qty = turtle.getItemCount(i),
  4453. index = i
  4454. }
  4455. end
  4456. return slots
  4457. end
  4458.  
  4459. function TL2.getFilledSlots(startSlot)
  4460. startSlot = startSlot or 1
  4461.  
  4462. local slots = { }
  4463. for i = startSlot, 16 do
  4464. local count = turtle.getItemCount(i)
  4465. if count > 0 then
  4466. slots[i] = {
  4467. qty = turtle.getItemCount(i),
  4468. index = i
  4469. }
  4470. end
  4471. end
  4472. return slots
  4473. end
  4474.  
  4475. function TL2.reconcileSlots(slots, dropAction)
  4476. dropAction = dropAction or turtle.drop
  4477. for _,s in pairs(slots) do
  4478. local qty = turtle.getItemCount(s.index)
  4479. if qty > s.qty then
  4480. TL2.select(s.index)
  4481. dropAction(qty-s.qty, s)
  4482. end
  4483. end
  4484. end
  4485.  
  4486. function TL2.selectSlotWithItems(startSlot)
  4487. startSlot = startSlot or 1
  4488. for i = startSlot, 16 do
  4489. if turtle.getItemCount(i) > 0 then
  4490. TL2.select(i)
  4491. return i
  4492. end
  4493. end
  4494. end
  4495.  
  4496. function TL2.selectOpenSlot(startSlot)
  4497. return TL2.selectSlotWithQuantity(0, startSlot)
  4498. end
  4499.  
  4500. function TL2.selectSlotWithQuantity(qty, startSlot)
  4501. startSlot = startSlot or 1
  4502.  
  4503. for i = startSlot, 16 do
  4504. if turtle.getItemCount(i) == qty then
  4505. TL2.select(i)
  4506. return i
  4507. end
  4508. end
  4509. end
  4510.  
  4511. function TL2.getPoint()
  4512. return { x = state.x, y = state.y, z = state.z, heading = state.heading }
  4513. end
  4514.  
  4515. function TL2.setPoint(pt)
  4516. state.x = pt.x
  4517. state.y = pt.y
  4518. state.z = pt.z
  4519. if pt.heading then
  4520. state.heading = pt.heading
  4521. end
  4522. end
  4523.  
  4524. _G.GPS = { }
  4525. function GPS.locate()
  4526. local pt = { }
  4527. pt.x, pt.z, pt.y = gps.locate(10)
  4528. if pt.x then
  4529. return pt
  4530. end
  4531. end
  4532.  
  4533. function GPS.isAvailable()
  4534. return Util.hasDevice("modem") and GPS.locate()
  4535. end
  4536.  
  4537. function GPS.initialize()
  4538. --[[
  4539. TL2.getState().gps = GPS.getPoint()
  4540. --]]
  4541. end
  4542.  
  4543. function GPS.gotoPoint(pt)
  4544. --[[
  4545. local heading
  4546. if pt.heading then
  4547. heading = (pt.heading + state.gps.heading) % 4
  4548. end
  4549. return TL2.goto(
  4550. pt.x - state.gps.x,
  4551. pt.y - state.gps.y,
  4552. pt.z - state.gps.z,
  4553. heading)
  4554. --]]
  4555. end
  4556.  
  4557. function GPS.getPoint()
  4558. local ray = TL2.getPoint()
  4559.  
  4560. local apt = GPS.locate()
  4561. if not apt then
  4562. error("GPS.getPoint: GPS not available")
  4563. end
  4564.  
  4565. while not TL2.forward() do
  4566. TL2.turnRight()
  4567. if TL2.getHeading() == ray.heading then
  4568. error('GPS.getPoint: Unable to move forward')
  4569. end
  4570. end
  4571.  
  4572. local bpt = GPS.locate()
  4573. if not apt then
  4574. error("No GPS")
  4575. end
  4576.  
  4577. if not TL2.back() then
  4578. error("GPS.getPoint: Unable to move back")
  4579. end
  4580.  
  4581. if apt.x < bpt.x then
  4582. apt.heading = 0
  4583. elseif apt.y < bpt.y then
  4584. apt.heading = 1
  4585. elseif apt.x > bpt.x then
  4586. apt.heading = 2
  4587. else
  4588. apt.heading = 3
  4589. end
  4590.  
  4591. return apt
  4592. end
  4593.  
  4594. function GPS.storeCurrentPoint(name)
  4595. local ray = GPS.getPoint()
  4596. TL2.storePoint(name, ray)
  4597. end
  4598.  
  4599. --[[
  4600. All pathfinding related follows
  4601.  
  4602. b = block
  4603. a = adjacent block
  4604. bb = bounding box
  4605. c = coordinates
  4606. --]]
  4607. local function addAdjacentBlock(blocks, b, dir, bb, a)
  4608. local key = a.x .. ':' .. a.y .. ':' .. a.z
  4609.  
  4610. if b.adj[dir] then
  4611. a = b.adj[dir]
  4612. else
  4613. local _a = blocks[key]
  4614. if _a then
  4615. a = _a
  4616. else
  4617. blocks[key] = a
  4618. end
  4619. end
  4620. local revDir = { 2, 3, 0, 1, 5, 4 }
  4621. b.adj[dir] = a
  4622. a.adj[revDir[dir+1]] = b
  4623. --[[
  4624. -- too much time...
  4625. if dir == 4 and turtle.detectUp() then
  4626. a.blocked = true
  4627. elseif dir == 5 and turtle.detectDown() then
  4628. a.blocked = true
  4629. elseif dir == state.heading and turtle.detect() then
  4630. a.blocked = true
  4631. --]]
  4632. if a.x < bb.ax or a.x > bb.bx or
  4633. a.y < bb.ay or a.y > bb.by or
  4634. a.z < bb.az or a.z > bb.bz then
  4635. a.blocked = true
  4636. end
  4637. end
  4638.  
  4639. local function addAdjacentBlocks(blocks, b, bb)
  4640. if not b.setAdj then
  4641. addAdjacentBlock(blocks, b, 0, bb,
  4642. { x = state.x+1, y = state.y , z = state.z , adj = {} })
  4643. addAdjacentBlock(blocks, b, 1, bb,
  4644. { x = state.x , y = state.y+1, z = state.z , adj = {} })
  4645. addAdjacentBlock(blocks, b, 2, bb,
  4646. { x = state.x-1, y = state.y , z = state.z , adj = {} })
  4647. addAdjacentBlock(blocks, b, 3, bb,
  4648. { x = state.x , y = state.y-1, z = state.z , adj = {} })
  4649. addAdjacentBlock(blocks, b, 4, bb,
  4650. { x = state.x , y = state.y , z = state.z + 1, adj = {} })
  4651. addAdjacentBlock(blocks, b, 5, bb,
  4652. { x = state.x , y = state.y , z = state.z - 1, adj = {} })
  4653. end
  4654. b.setAdj = true
  4655. end
  4656.  
  4657. local function getMovesTo(x, y, z)
  4658. local dest = { x = x, y = y, z = z }
  4659. return calculateMoves(state, dest, state.heading)
  4660. end
  4661.  
  4662. local function calculateAdjacentBlockDistances(b, dest)
  4663. for k,a in pairs(b.adj) do
  4664. if not a.blocked then
  4665. --a.distance = calculateMoves(a, dest, k)
  4666. a.distance = TL2.calculateDistance(a, dest)
  4667. else
  4668. a.distance = 9000
  4669. end
  4670. --print(string.format('%d: %f %d,%d,%d, %s', k, a.distance, a.x, a.y, a.z, tostring(a.blocked)))
  4671. --read()
  4672. end
  4673. -- read()
  4674. end
  4675.  
  4676. local function blockedIn(b)
  4677. for _,a in pairs(b.adj) do
  4678. if not a.blocked then
  4679. return false
  4680. end
  4681. end
  4682. return true
  4683. end
  4684.  
  4685. local function pathfindMove(b)
  4686. for _,a in Util.spairs(b.adj, function(a, b) return a.distance < b.distance end) do
  4687.  
  4688. --print('shortest: ' .. pc(a) .. ' distance: ' .. a.distance)
  4689. if not a.blocked then
  4690. local success = TL2.moveTowardsPoint(a)
  4691. if success then
  4692. return a
  4693. end
  4694. a.blocked = true
  4695. end
  4696. end
  4697. end
  4698.  
  4699. local function rpath(blocks, block, dest, boundingBox)
  4700.  
  4701. addAdjacentBlocks(blocks, block, boundingBox)
  4702. calculateAdjacentBlockDistances(block, dest)
  4703.  
  4704. block.blocked = true
  4705. repeat
  4706. local newBlock = pathfindMove(block)
  4707. if newBlock then
  4708. if state.x == dest.x and state.y == dest.y and state.z == dest.z then
  4709. block.blocked = false
  4710. return true
  4711. end
  4712. --[[
  4713. if goto then return true end
  4714. block = getCurrentBlock() (gets or creates block)
  4715. but this might - will - move us into a block we marked as blockedin
  4716. if block.blocked then
  4717. goto newBlock
  4718. block = getCurrentBlock() (gets or creates block)
  4719. end
  4720. maybe check if we have a clear line of sight to the destination
  4721. ie. keep traveling towards destination building up blocked blocks
  4722. as we encounter them (instead of adding all blocks on the path)
  4723. --]]
  4724. if rpath(blocks, newBlock, dest, boundingBox) then
  4725. block.blocked = false
  4726. return true
  4727. end
  4728. if not TL2.moveTowardsPoint(block) then
  4729. return false
  4730. end
  4731. end
  4732. until blockedIn(block)
  4733. return false
  4734. end
  4735.  
  4736. --[[
  4737. goto will traverse towards the destination until it is blocked
  4738. by something on the x, y, or z coordinate of the destination
  4739.  
  4740. pathfinding will attempt to find a way around the blockage
  4741.  
  4742. goto example:
  4743. . . >-X B D stuck behind block
  4744. . . | . B .
  4745. . . | . . .
  4746. S >-^B. . .
  4747.  
  4748. pathfind example:
  4749. . . >-v B D when goto fails, pathfinding kicks in
  4750. . . | |-B-|
  4751. . . | >---^
  4752. S >-^B. . .
  4753.  
  4754. --]]
  4755. function TL2.pathtofreely(dx, dy, dz, dh)
  4756. local boundingBox = {
  4757. ax = -9000,
  4758. ay = -9000,
  4759. az = -9000,
  4760. bx = 9000,
  4761. by = 9000,
  4762. bz = 9000
  4763. }
  4764. return TL2.pathto(dx, dy, dz, dh, boundingBox)
  4765. end
  4766.  
  4767. local function pathfind(dx, dy, dz, dh, boundingBox)
  4768.  
  4769. local blocks = { } -- memory.blocks
  4770. local dest = { x = dx, y = dy, z = dz }
  4771. local block = { x = state.x, y = state.y, z = state.z, adj = {} }
  4772. local key = block.x .. ':' .. block.y .. ':' .. block.z
  4773.  
  4774. blocks[key] = block
  4775.  
  4776. if rpath(blocks, block, dest, boundingBox) then
  4777. TL2.setHeading(dh)
  4778. return true
  4779. end
  4780.  
  4781. return false
  4782. end
  4783.  
  4784. function TL2.pathto(dx, dy, dz, dh, boundingBox)
  4785. local start = { x = state.x, y = state.y, z = state.z }
  4786. if TL2.goto(dx, dy, dz, dh) then
  4787. return true
  4788. end
  4789.  
  4790. if not dz then
  4791. dz = state.z
  4792. end
  4793.  
  4794. if not boundingBox then
  4795. boundingBox = {
  4796. ax = math.min(dx, start.x),
  4797. ay = math.min(dy, start.y),
  4798. az = math.min(dz, start.z),
  4799. bx = math.max(dx, start.x),
  4800. by = math.max(dy, start.y),
  4801. bz = math.max(dz, start.z),
  4802. }
  4803. end
  4804. return pathfind(dx, dy, dz, dh, boundingBox)
  4805. end
  4806.  
  4807. function TL2.moveTowardsPoint(c)
  4808.  
  4809. if c.z > state.z then
  4810. return TL2.up()
  4811. elseif c.z < state.z then
  4812. return TL2.down()
  4813. end
  4814. TL2.headTowards(c)
  4815. return TL2.forward()
  4816. end
  4817.  
  4818. --Import
  4819. --[[
  4820. turtle action chains and communications
  4821. --]]
  4822. _G.TLC = { }
  4823.  
  4824. local registeredActions = {
  4825.  
  4826. setBoss = function(args, action)
  4827. TL2.getState().boss = action.requestor
  4828. printf('setting boss to %d', action.requestor)
  4829. return true
  4830. end,
  4831.  
  4832. abort = function(args, action)
  4833. TL2.getState().abort = true
  4834. return true
  4835. end,
  4836.  
  4837. run = function(args, action)
  4838. printf('do file: %s', args.filename)
  4839. return dofile(args.filename)
  4840. end,
  4841.  
  4842. setState = function(args)
  4843. for k,v in pairs(args) do
  4844. TL2.getState()[k] = v
  4845. end
  4846. return true
  4847. end,
  4848.  
  4849. recall = function(args, action)
  4850. local pt = TLC.tracker(action.requestor, action.distance)
  4851. if pt then
  4852. TL2.getState().z = pt.z
  4853. if pt.z > 0 then
  4854. TL2.gotoZ(1)
  4855. else
  4856. TL2.gotoZ(-1)
  4857. end
  4858. end
  4859. return true
  4860. end,
  4861.  
  4862. goto = function(args)
  4863. if args.mode then
  4864. TL2.setMode(args.mode)
  4865. end
  4866. if args.digStrategy then
  4867. TL2.setDigStrategy(args.digStrategy)
  4868. end
  4869. for i = 1, 3 do
  4870. if TL2.goto(args.x, args.y, args.z, args.heading) then
  4871. return true
  4872. end
  4873. os.sleep(.5)
  4874. end
  4875. end,
  4876.  
  4877. gotoZ = function(args)
  4878. if args.mode then
  4879. TL2.setMode(args.mode)
  4880. end
  4881. if args.digStrategy then
  4882. TL2.setDigStrategy(args.digStrategy)
  4883. end
  4884. return TL2.gotoZ(args.z)
  4885. end,
  4886.  
  4887. move = function(args)
  4888. local result = false
  4889. if args.mode then
  4890. TL2.setMode(args.mode)
  4891. end
  4892. if args.digStrategy then
  4893. TL2.setDigStrategy(args.digStrategy)
  4894. end
  4895. for i = 1, args.moves do
  4896. if args.subaction == "u" then
  4897. result = TL2.up()
  4898. elseif args.subaction == "d" then
  4899. result = TL2.down()
  4900. elseif args.subaction == "f" then
  4901. result = TL2.forward()
  4902. elseif args.subaction == "r" then
  4903. result = TL2.turnRight()
  4904. elseif args.subaction == "l" then
  4905. result = TL2.turnLeft()
  4906. elseif args.subaction == "b" then
  4907. result = TL2.back()
  4908. end
  4909. if not result then
  4910. return false
  4911. end
  4912. end
  4913. return result
  4914. end,
  4915.  
  4916. reset = function(args)
  4917. TL2.getState().x = 0
  4918. TL2.getState().y = 0
  4919. TL2.getState().z = 0
  4920. return true
  4921. end,
  4922.  
  4923. shutdown = function(args)
  4924. os.shutdown()
  4925. end,
  4926.  
  4927. reboot = function(args)
  4928. os.reboot()
  4929. end,
  4930.  
  4931. gotoNamedLocation = function(args)
  4932. return TL2.gotoNamedLocation(args.name)
  4933. end,
  4934.  
  4935. setLocation = function(args)
  4936. TL2.getState().x = args.x
  4937. TL2.getState().y = args.y
  4938. TL2.getState().z = args.z
  4939. if args.heading then
  4940. TL2.getState().heading = args.heading
  4941. end
  4942. return true
  4943. end,
  4944.  
  4945. status = function(args, action)
  4946. TLC.sendStatus(action.requestor)
  4947. return true
  4948. end
  4949. }
  4950.  
  4951. function TLC.sendStatus(requestor, lastState)
  4952. local state = TL2.getState()
  4953.  
  4954. requestor = requestor or state.boss
  4955.  
  4956. if lastState then
  4957. if state.x == lastState.x and
  4958. state.y == lastState.y and
  4959. state.z == lastState.z and
  4960. state.status == lastState.status and
  4961. state.fuel == lastState.fuel then
  4962. return
  4963. end
  4964. end
  4965.  
  4966. Message.send(requestor, 'isAlive', {
  4967. label = os.getComputerLabel(),
  4968. fuel = turtle.getFuelLevel(),
  4969. status = state.status,
  4970. extStatus = state.extStatus,
  4971. --role = state.role,
  4972. x = state.x,
  4973. y = state.y,
  4974. z = state.z
  4975. })
  4976.  
  4977. TL2.getState().lastStatus = os.clock()
  4978.  
  4979. Logger.log('turtle', '>> Status to ' .. tostring(requestor) .. ' of ' .. state.status)
  4980. end
  4981.  
  4982. function TLC.registerAction(action, f)
  4983. registeredActions[action] = f
  4984. end
  4985.  
  4986. function TLC.performActions(actions)
  4987.  
  4988. local function performSingleAction(action)
  4989. local actionName = action.action
  4990. local actionFunction = registeredActions[actionName]
  4991.  
  4992. if not actionFunction then
  4993. Logger.log('turtle', 'Unknown turtle action: ' .. tostring(actionName))
  4994. Logger.log('turtle', action)
  4995. error('Unknown turtle action: ' .. tostring(actionName))
  4996. end
  4997. -- perform action
  4998. Logger.log('turtle', '<< Action: ' .. actionName)
  4999.  
  5000. --if actionName == 'status' then
  5001. --return actionFunction(action.args, action)
  5002. --end
  5003.  
  5004. local state = TL2.getState()
  5005. local previousStatus = state.status
  5006. result = actionFunction(action.args, action)
  5007. return result
  5008. end
  5009.  
  5010. for _,action in ipairs(actions) do
  5011. if not action.result then
  5012. action.result = performSingleAction(action)
  5013. if not action.result then
  5014. Logger.log('turtle', action)
  5015. Logger.log('turtle', '**Action failed')
  5016. if not action.retryCount then
  5017. action.retryCount = 1
  5018. else
  5019. action.retryCount = action.retryCount + 1
  5020. end
  5021. if action.retryCount < 3 then
  5022. -- queue action chain for another attempt
  5023. TL2.getState().status = 'busy'
  5024. os.queueEvent('turtleActions', actions)
  5025. end
  5026. return false
  5027. end
  5028. end
  5029. end
  5030. return true
  5031. end
  5032.  
  5033. function TLC.performAction(actionName, actionArgs)
  5034. return TLC.performActions({{ action = actionName, args = actionArgs }})
  5035. end
  5036.  
  5037. function TLC.sendAction(id, actionName, actionArgs)
  5038. local action = {
  5039. action = actionName,
  5040. args = actionArgs
  5041. }
  5042. Logger.log('turtle', '>>Sending to ' .. tostring(id) .. ' ' .. actionName)
  5043. Message.send(id, 'turtle', { action })
  5044. end
  5045.  
  5046. function TLC.sendActions(id, actions)
  5047. local str = ''
  5048. for _,v in pairs(actions) do
  5049. str = str .. ' ' .. v.action
  5050. end
  5051. Logger.log('turtle', '>>Sending to ' .. id .. str)
  5052. Message.send(id, 'turtle', actions)
  5053. end
  5054.  
  5055. function TLC.requestState(id)
  5056. Logger.log('turtle', '>>Requesting state from ' .. id)
  5057. Message.send(id, 'turtleState')
  5058. end
  5059.  
  5060. function TLC.setRole(role)
  5061. TL2.getState().role = role
  5062. end
  5063.  
  5064. function TLC.queueAction(actionName, actionArgs)
  5065. TLC.queueActions({{
  5066. action = actionName,
  5067. args = actionArgs
  5068. }})
  5069. end
  5070.  
  5071. function TLC.queueActions(actions)
  5072. table.insert(TL2.getState().actionQueue, actions)
  5073. os.queueEvent('turtleActions', actions)
  5074. end
  5075.  
  5076. function TLC.run(command)
  5077. TLC.queueAction('run', { filename = command })
  5078. end
  5079.  
  5080. function TLC.saveState()
  5081. end
  5082.  
  5083. function TLC.clearState()
  5084. end
  5085.  
  5086. function TLC.pullEvents(role, isworker, filename)
  5087.  
  5088. TL2.getState().status = 'idle'
  5089. TL2.getState().role = role
  5090. TL2.getState().actionQueue = { }
  5091.  
  5092. local function heartbeat()
  5093. local lastState
  5094. while not TL2.getState().boss do
  5095. os.sleep(3)
  5096. if TL2.getStatus() == 'idle' then
  5097. Message.broadcast('unassignedTurtle', {
  5098. label = os.getComputerLabel(),
  5099. fuel = turtle.getFuelLevel(),
  5100. status = TL2.getState().status,
  5101. role = TL2.getState().role,
  5102. })
  5103. end
  5104. end
  5105. while true do
  5106. if (not TL2.getState().lastStatus) or
  5107. (os.clock() - TL2.getState().lastStatus > 30) then
  5108. TLC.sendStatus(TL2.getState().boss)
  5109. else
  5110. TLC.sendStatus(TL2.getState().boss, lastState)
  5111. end
  5112. lastState = Util.shallowCopy(TL2.getState())
  5113. os.sleep(1)
  5114. end
  5115. end
  5116.  
  5117. Message.addHandler('turtle',
  5118. function(h, id, msg, distance)
  5119.  
  5120. local actions = msg.contents
  5121. for _,action in ipairs(actions) do
  5122. action.distance = distance
  5123. action.requestor = id
  5124. end
  5125. TLC.queueActions(actions)
  5126. end
  5127. )
  5128.  
  5129. Event.addHandler('turtleActions',
  5130. function(h, actions)
  5131. local state = TL2.getState()
  5132. if state.status == 'idle' then
  5133. TLC.clearState()
  5134. state.status = 'busy'
  5135. while #state.actionQueue > 0 do
  5136. local actions = table.remove(state.actionQueue, 1)
  5137. TLC.performActions(actions)
  5138. end
  5139. state.status = 'idle'
  5140. state.abort = nil
  5141. TLC.saveState()
  5142. if state.boss then
  5143. TLC.sendStatus(state.boss)
  5144. end
  5145. end
  5146. end
  5147. )
  5148.  
  5149. Message.addHandler('alive',
  5150. function(e, id, msg)
  5151. TLC.sendStatus(id)
  5152. end
  5153. )
  5154.  
  5155. if isworker then
  5156. parallel.waitForAny(heartbeat, Event.backgroundPullEvents)
  5157. else
  5158. parallel.waitForAny(Event.backgroundPullEvents)
  5159. end
  5160. end
  5161.  
  5162. --[[
  5163. function TLC.condence(slot)
  5164. local iQty = turtle.getItemCount(slot)
  5165. for i = 1, 16 do
  5166. if i ~= slot then
  5167. local qty = turtle.getItemCount(i)
  5168. if qty > 0 then
  5169. turtle.select(i)
  5170. if turtle.compareTo(slot) then
  5171. turtle.transferTo(slot, qty)
  5172. iQty = iQty + qty
  5173. if iQty >= 64 then
  5174. break
  5175. end
  5176. end
  5177. end
  5178. end
  5179. end
  5180. end
  5181. --]]
  5182.  
  5183. -- from stock gps API
  5184. local function trilaterate( A, B, C )
  5185. local a2b = B.position - A.position
  5186. local a2c = C.position - A.position
  5187.  
  5188. if math.abs( a2b:normalize():dot( a2c:normalize() ) ) > 0.999 then
  5189. return nil
  5190. end
  5191.  
  5192. local d = a2b:length()
  5193. local ex = a2b:normalize( )
  5194. local i = ex:dot( a2c )
  5195. local ey = (a2c - (ex * i)):normalize()
  5196. local j = ey:dot( a2c )
  5197. local ez = ex:cross( ey )
  5198.  
  5199. local r1 = A.distance
  5200. local r2 = B.distance
  5201. local r3 = C.distance
  5202.  
  5203. local x = (r1*r1 - r2*r2 + d*d) / (2*d)
  5204. local y = (r1*r1 - r3*r3 - x*x + (x-i)*(x-i) + j*j) / (2*j)
  5205.  
  5206. local result = A.position + (ex * x) + (ey * y)
  5207.  
  5208. local zSquared = r1*r1 - x*x - y*y
  5209. if zSquared > 0 then
  5210. local z = math.sqrt( zSquared )
  5211. local result1 = result + (ez * z)
  5212. local result2 = result - (ez * z)
  5213.  
  5214. local rounded1, rounded2 = result1:round(), result2:round()
  5215. if rounded1.x ~= rounded2.x or rounded1.y ~= rounded2.y or rounded1.z ~= rounded2.z then
  5216. return rounded1, rounded2
  5217. else
  5218. return rounded1
  5219. end
  5220. end
  5221. return result:round()
  5222. end
  5223.  
  5224. local function narrow( p1, p2, fix )
  5225. local dist1 = math.abs( (p1 - fix.position):length() - fix.distance )
  5226. local dist2 = math.abs( (p2 - fix.position):length() - fix.distance )
  5227.  
  5228. if math.abs(dist1 - dist2) < 0.05 then
  5229. return p1, p2
  5230. elseif dist1 < dist2 then
  5231. return p1:round()
  5232. else
  5233. return p2:round()
  5234. end
  5235. end
  5236.  
  5237. -- end stock gps api
  5238.  
  5239. TLC.tFixes = {}
  5240.  
  5241. Message.addHandler('position', function(h, id, msg, distance)
  5242.  
  5243. if TL2.getStatus() ~= 'idle' then
  5244. return
  5245. end
  5246.  
  5247. local tFix = {
  5248. position = vector.new(msg.contents.x, msg.contents.z, msg.contents.y),
  5249. distance = distance
  5250. }
  5251. table.insert(TLC.tFixes, tFix)
  5252.  
  5253. if #TLC.tFixes == 4 then
  5254. Logger.log('turtle', 'trilaterating')
  5255. local pos1, pos2 = trilaterate(TLC.tFixes[1], TLC.tFixes[2], TLC.tFixes[3])
  5256. pos1, pos2 = narrow(pos1, pos2, TLC.tFixes[3])
  5257. if pos2 then
  5258. pos1, pos2 = narrow(pos1, pos2, TLC.tFixes[4])
  5259. end
  5260.  
  5261. if pos1 and pos2 then
  5262. print("Ambiguous position")
  5263. print("Could be "..pos1.x..","..pos1.y..","..pos1.z.." or "..pos2.x..","..pos2.y..","..pos2.z )
  5264. elseif pos1 then
  5265. Logger.log('turtle', "Position is "..pos1.x..","..pos1.y..","..pos1.z)
  5266. TLC.performAction('setLocation', {
  5267. x = pos1.x,
  5268. y = pos1.z,
  5269. z = pos1.y
  5270. })
  5271. else
  5272. print("Could not determine position")
  5273. end
  5274. end
  5275. end)
  5276.  
  5277. function TLC.getDistance(id)
  5278. for i = 1, 10 do
  5279. rednet.send(id, { type = 'alive' })
  5280. local _, _, _, distance = Message.waitForMessage('isAlive', 1, id)
  5281. if distance then
  5282. Logger.log('turtle', 'distance: ' .. distance)
  5283. return distance
  5284. end
  5285. end
  5286. end
  5287.  
  5288. local function locate(id, d1, boundingBox)
  5289.  
  5290. local function checkBB(boundingBox)
  5291. if boundingBox then
  5292. local heading = TL2.headings[TL2.getState().heading]
  5293. local x = TL2.getState().x + heading.xd
  5294. local y = TL2.getState().y + heading.yd
  5295. if x < boundingBox.ax or x > boundingBox.bx or
  5296. y < boundingBox.ay or y > boundingBox.by then
  5297. return true
  5298. end
  5299. end
  5300. return false
  5301. end
  5302.  
  5303. if checkBB(boundingBox) then
  5304. TL2.turnAround()
  5305. end
  5306.  
  5307. if d1 == 1 then
  5308. return d1
  5309. end
  5310.  
  5311. TL2.forward()
  5312.  
  5313. local d2 = TLC.getDistance(id)
  5314. if not d2 then return end
  5315. if d2 == 1 then return d2 end
  5316.  
  5317. if d2 > d1 then
  5318. TL2.turnAround()
  5319. end
  5320.  
  5321. d1 = d2
  5322.  
  5323. while true do
  5324. if checkBB(boundingBox) then
  5325. break
  5326. end
  5327. TL2.forward()
  5328.  
  5329. d2 = TLC.getDistance(id)
  5330. if not d2 then return end
  5331. if d2 == 1 then return d2 end
  5332.  
  5333. if d2 > d1 then
  5334. TL2.back()
  5335. return d1
  5336. end
  5337. d1 = d2
  5338. end
  5339. return d2
  5340. end
  5341.  
  5342. function TLC.tracker(id, d1, nozcheck, boundingBox)
  5343.  
  5344. d1 = locate(id, d1, boundingBox)
  5345. if not d1 then return end
  5346.  
  5347. TL2.turnRight()
  5348.  
  5349. d1 = locate(id, d1, boundingBox)
  5350. if not d1 then return end
  5351.  
  5352. if math.floor(d1) == d1 then
  5353. local z = d1
  5354.  
  5355. if not nozcheck then
  5356. TL2.up()
  5357.  
  5358. d2 = TLC.getDistance(id)
  5359. if not d2 then return end
  5360.  
  5361. TL2.down()
  5362.  
  5363. z = TL2.getState().z + math.floor(d1)
  5364. if d1 < d2 then
  5365. z = TL2.getState().z - math.floor(d1)
  5366. end
  5367. end
  5368.  
  5369. return { x = TL2.getState().x, y = TL2.getState().y, z = -z }
  5370. end
  5371. end
  5372.  
  5373. end
Add Comment
Please, Sign In to add comment