Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13ec8ea04f | ||
|
|
911fec9118 | ||
|
|
1528bab3ac | ||
|
|
dc9d174085 | ||
|
|
1108c173d7 | ||
|
|
0830e46fb8 | ||
|
|
743959c1fa | ||
|
|
dd4211745e | ||
|
|
0df22efdc2 | ||
|
|
a8a4ceb85d | ||
|
|
f533e42c0c | ||
|
|
1b9450017d | ||
|
|
cac15722b8 | ||
|
|
7fd93e8a8b | ||
|
|
84b2b8ce63 | ||
|
|
22a432492c | ||
|
|
8d3f5329f2 | ||
|
|
8e9ff9c626 | ||
|
|
31b3787695 | ||
|
|
fb0f3e567a | ||
|
|
f6d1cfc7ee | ||
|
|
39ba226a82 | ||
|
|
fe0ca72b8b | ||
|
|
2721840596 | ||
|
|
9b8b5238b0 | ||
|
|
8b187f2813 | ||
|
|
153b0b86ff | ||
|
|
a9634cb438 | ||
|
|
3460dd68b2 | ||
|
|
f9221e67be | ||
|
|
852ad193f0 | ||
|
|
05c99b583a | ||
|
|
f5b99d91e5 | ||
|
|
955f11042b | ||
|
|
a625b52bad | ||
|
|
98ec840db1 | ||
|
|
af981dd1f8 | ||
|
|
91a05c07dd | ||
|
|
fc69d4be83 | ||
|
|
f0846c8daa |
@@ -1,5 +1,5 @@
|
|||||||
local Ansi = setmetatable({ }, {
|
local Ansi = setmetatable({ }, {
|
||||||
__call = function(self, ...)
|
__call = function(_, ...)
|
||||||
local str = '\027['
|
local str = '\027['
|
||||||
for k,v in ipairs({ ...}) do
|
for k,v in ipairs({ ...}) do
|
||||||
if k == 1 then
|
if k == 1 then
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ return function(base)
|
|||||||
-- expose a constructor which can be called by <classname>(<args>)
|
-- expose a constructor which can be called by <classname>(<args>)
|
||||||
setmetatable(c, {
|
setmetatable(c, {
|
||||||
__call = function(class_tbl, ...)
|
__call = function(class_tbl, ...)
|
||||||
local obj = {}
|
local obj = { }
|
||||||
setmetatable(obj,c)
|
setmetatable(obj,c)
|
||||||
if class_tbl.init then
|
if class_tbl.init then
|
||||||
class_tbl.init(obj, ...)
|
class_tbl.init(obj, ...)
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
local Util = require('util')
|
local Util = require('util')
|
||||||
|
|
||||||
|
local fs = _G.fs
|
||||||
|
local shell = _ENV.shell
|
||||||
|
|
||||||
local Config = { }
|
local Config = { }
|
||||||
|
|
||||||
Config.load = function(fname, data)
|
function Config.load(fname, data)
|
||||||
local filename = 'usr/config/' .. fname
|
local filename = 'usr/config/' .. fname
|
||||||
|
|
||||||
if not fs.exists('usr/config') then
|
if not fs.exists('usr/config') then
|
||||||
@@ -16,7 +19,24 @@ Config.load = function(fname, data)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Config.update = function(fname, data)
|
function Config.loadWithCheck(fname, data)
|
||||||
|
local filename = 'usr/config/' .. fname
|
||||||
|
|
||||||
|
if not fs.exists(filename) then
|
||||||
|
Config.load(fname, data)
|
||||||
|
print()
|
||||||
|
print('The configuration file has been created.')
|
||||||
|
print('The file name is: ' .. filename)
|
||||||
|
print()
|
||||||
|
_G.printError('Press enter to configure')
|
||||||
|
_G.read()
|
||||||
|
shell.run('edit ' .. filename)
|
||||||
|
end
|
||||||
|
|
||||||
|
Config.load(fname, data)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Config.update(fname, data)
|
||||||
local filename = 'usr/config/' .. fname
|
local filename = 'usr/config/' .. fname
|
||||||
Util.writeTable(filename, data)
|
Util.writeTable(filename, data)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
local os = _G.os
|
||||||
|
|
||||||
local Event = {
|
local Event = {
|
||||||
uid = 1, -- unique id for handlers
|
uid = 1, -- unique id for handlers
|
||||||
routines = { }, -- coroutines
|
routines = { }, -- coroutines
|
||||||
@@ -22,6 +24,9 @@ function Routine:terminate()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Routine:resume(event, ...)
|
function Routine:resume(event, ...)
|
||||||
|
--if coroutine.status(self.co) == 'running' then
|
||||||
|
--return
|
||||||
|
--end
|
||||||
|
|
||||||
if not self.co then
|
if not self.co then
|
||||||
error('Cannot resume a dead routine')
|
error('Cannot resume a dead routine')
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
local git = require('git')
|
local git = require('git')
|
||||||
|
|
||||||
|
local fs = _G.fs
|
||||||
|
|
||||||
local gitfs = { }
|
local gitfs = { }
|
||||||
|
|
||||||
function gitfs.mount(dir, repo)
|
function gitfs.mount(dir, repo)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
local fs = _G.fs
|
||||||
|
|
||||||
local linkfs = { }
|
local linkfs = { }
|
||||||
|
|
||||||
local methods = { 'exists', 'getFreeSpace', 'getSize',
|
local methods = { 'exists', 'getFreeSpace', 'getSize',
|
||||||
@@ -10,7 +12,7 @@ for _,m in pairs(methods) do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function linkfs.mount(dir, source)
|
function linkfs.mount(_, source)
|
||||||
if not source then
|
if not source then
|
||||||
error('Source is required')
|
error('Source is required')
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
local Socket = require('socket')
|
local Socket = require('socket')
|
||||||
local synchronized = require('sync')
|
local synchronized = require('sync')
|
||||||
|
|
||||||
|
local fs = _G.fs
|
||||||
|
|
||||||
local netfs = { }
|
local netfs = { }
|
||||||
|
|
||||||
local function remoteCommand(node, msg)
|
local function remoteCommand(node, msg)
|
||||||
|
|
||||||
for i = 1, 2 do
|
for _ = 1, 2 do
|
||||||
if not node.socket then
|
if not node.socket then
|
||||||
node.socket = Socket.connect(node.id, 139)
|
node.socket = Socket.connect(node.id, 139)
|
||||||
end
|
end
|
||||||
@@ -49,7 +51,7 @@ for _,m in pairs(methods) do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function netfs.mount(dir, id, directory)
|
function netfs.mount(_, id, directory)
|
||||||
if not id or not tonumber(id) then
|
if not id or not tonumber(id) then
|
||||||
error('ramfs syntax: computerId [directory]')
|
error('ramfs syntax: computerId [directory]')
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
local Util = require('util')
|
local Util = require('util')
|
||||||
|
|
||||||
|
local fs = _G.fs
|
||||||
|
|
||||||
local ramfs = { }
|
local ramfs = { }
|
||||||
|
|
||||||
function ramfs.mount(dir, nodeType)
|
function ramfs.mount(_, nodeType)
|
||||||
if nodeType == 'directory' then
|
if nodeType == 'directory' then
|
||||||
return {
|
return {
|
||||||
nodes = { },
|
nodes = { },
|
||||||
@@ -34,7 +36,7 @@ function ramfs.isReadOnly()
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
function ramfs.makeDir(node, dir)
|
function ramfs.makeDir(_, dir)
|
||||||
fs.mount(dir, 'ramfs', 'directory')
|
fs.mount(dir, 'ramfs', 'directory')
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -46,10 +48,10 @@ function ramfs.getDrive()
|
|||||||
return 'ram'
|
return 'ram'
|
||||||
end
|
end
|
||||||
|
|
||||||
function ramfs.list(node, dir, full)
|
function ramfs.list(node, dir)
|
||||||
if node.nodes and node.mountPoint == dir then
|
if node.nodes and node.mountPoint == dir then
|
||||||
local files = { }
|
local files = { }
|
||||||
for k,v in pairs(node.nodes) do
|
for k in pairs(node.nodes) do
|
||||||
table.insert(files, k)
|
table.insert(files, k)
|
||||||
end
|
end
|
||||||
return files
|
return files
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
local synchronized = require('sync')
|
local Util = require('util')
|
||||||
local Util = require('util')
|
|
||||||
|
local fs = _G.fs
|
||||||
|
|
||||||
local urlfs = { }
|
local urlfs = { }
|
||||||
|
|
||||||
function urlfs.mount(dir, url)
|
function urlfs.mount(_, url)
|
||||||
if not url then
|
if not url then
|
||||||
error('URL is required')
|
error('URL is required')
|
||||||
end
|
end
|
||||||
@@ -12,7 +13,7 @@ function urlfs.mount(dir, url)
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
function urlfs.delete(node, dir)
|
function urlfs.delete(_, dir)
|
||||||
fs.unmount(dir)
|
fs.unmount(dir)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -49,9 +50,7 @@ function urlfs.open(node, fn, fl)
|
|||||||
|
|
||||||
local c = node.cache
|
local c = node.cache
|
||||||
if not c then
|
if not c then
|
||||||
synchronized(node.url, function()
|
c = Util.httpGet(node.url)
|
||||||
c = Util.download(node.url)
|
|
||||||
end)
|
|
||||||
if c then
|
if c then
|
||||||
node.cache = c
|
node.cache = c
|
||||||
node.size = #c
|
node.size = #c
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ local FILE_URL = 'https://raw.githubusercontent.com/%s/%s/%s/%s'
|
|||||||
|
|
||||||
local git = { }
|
local git = { }
|
||||||
|
|
||||||
function git.list(repo)
|
function git.list(repository)
|
||||||
|
|
||||||
local t = Util.split(repo, '(.-)/')
|
local t = Util.split(repository, '(.-)/')
|
||||||
|
|
||||||
local user = t[1]
|
local user = t[1]
|
||||||
local repo = t[2]
|
local repo = t[2]
|
||||||
@@ -33,7 +33,7 @@ function git.list(repo)
|
|||||||
|
|
||||||
local list = { }
|
local list = { }
|
||||||
|
|
||||||
for k,v in pairs(data.tree) do
|
for _,v in pairs(data.tree) do
|
||||||
if v.type == "blob" then
|
if v.type == "blob" then
|
||||||
v.path = v.path:gsub("%s","%%20")
|
v.path = v.path:gsub("%s","%%20")
|
||||||
list[v.path] = {
|
list[v.path] = {
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
local GPS = { }
|
local GPS = { }
|
||||||
|
|
||||||
|
local device = _G.device
|
||||||
|
local gps = _G.gps
|
||||||
|
local turtle = _G.turtle
|
||||||
|
|
||||||
function GPS.locate(timeout, debug)
|
function GPS.locate(timeout, debug)
|
||||||
local pt = { }
|
local pt = { }
|
||||||
timeout = timeout or 10
|
timeout = timeout or 10
|
||||||
@@ -14,7 +18,6 @@ function GPS.isAvailable()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function GPS.getPoint(timeout, debug)
|
function GPS.getPoint(timeout, debug)
|
||||||
|
|
||||||
local pt = GPS.locate(timeout, debug)
|
local pt = GPS.locate(timeout, debug)
|
||||||
if not pt then
|
if not pt then
|
||||||
return
|
return
|
||||||
@@ -24,7 +27,7 @@ function GPS.getPoint(timeout, debug)
|
|||||||
pt.y = math.floor(pt.y)
|
pt.y = math.floor(pt.y)
|
||||||
pt.z = math.floor(pt.z)
|
pt.z = math.floor(pt.z)
|
||||||
|
|
||||||
if pocket then
|
if _G.pocket then
|
||||||
pt.y = pt.y - 1
|
pt.y = pt.y - 1
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -47,7 +50,7 @@ function GPS.getHeading(timeout)
|
|||||||
while not turtle.forward() do
|
while not turtle.forward() do
|
||||||
turtle.turnRight()
|
turtle.turnRight()
|
||||||
if turtle.getHeading() == heading then
|
if turtle.getHeading() == heading then
|
||||||
printError('GPS.getPoint: Unable to move forward')
|
_G.printError('GPS.getPoint: Unable to move forward')
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -79,12 +82,12 @@ function GPS.getPointAndHeading(timeout)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- from stock gps API
|
-- from stock gps API
|
||||||
local function trilaterate( A, B, C )
|
local function trilaterate(A, B, C)
|
||||||
local a2b = B.position - A.position
|
local a2b = B.position - A.position
|
||||||
local a2c = C.position - A.position
|
local a2c = C.position - A.position
|
||||||
|
|
||||||
if math.abs( a2b:normalize():dot( a2c:normalize() ) ) > 0.999 then
|
if math.abs( a2b:normalize():dot( a2c:normalize() ) ) > 0.999 then
|
||||||
return nil
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local d = a2b:length()
|
local d = a2b:length()
|
||||||
|
|||||||
@@ -1,42 +1,55 @@
|
|||||||
local DEFAULT_UPATH = 'https://raw.githubusercontent.com/kepler155c/opus/master/sys/apis'
|
local DEFAULT_UPATH = 'https://raw.githubusercontent.com/kepler155c/opus/develop/sys/apis'
|
||||||
local PASTEBIN_URL = 'http://pastebin.com/raw'
|
local PASTEBIN_URL = 'http://pastebin.com/raw'
|
||||||
local GIT_URL = 'https://raw.githubusercontent.com'
|
local GIT_URL = 'https://raw.githubusercontent.com'
|
||||||
|
|
||||||
-- fix broken http get
|
local fs = _G.fs
|
||||||
local syncLocks = { }
|
local http = _G.http
|
||||||
|
local os = _G.os
|
||||||
|
|
||||||
local function sync(obj, fn)
|
if not http._patched then
|
||||||
local key = tostring(obj)
|
-- fix broken http get
|
||||||
if syncLocks[key] then
|
local syncLocks = { }
|
||||||
local cos = tostring(coroutine.running())
|
|
||||||
table.insert(syncLocks[key], cos)
|
local function sync(obj, fn)
|
||||||
repeat
|
local key = tostring(obj)
|
||||||
local _, co = os.pullEvent('sync_lock')
|
if syncLocks[key] then
|
||||||
until co == cos
|
local cos = tostring(coroutine.running())
|
||||||
else
|
table.insert(syncLocks[key], cos)
|
||||||
syncLocks[key] = { }
|
repeat
|
||||||
|
local _, co = os.pullEvent('sync_lock')
|
||||||
|
until co == cos
|
||||||
|
else
|
||||||
|
syncLocks[key] = { }
|
||||||
|
end
|
||||||
|
fn()
|
||||||
|
local co = table.remove(syncLocks[key], 1)
|
||||||
|
if co then
|
||||||
|
os.queueEvent('sync_lock', co)
|
||||||
|
else
|
||||||
|
syncLocks[key] = nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
local s, m = pcall(fn)
|
|
||||||
local co = table.remove(syncLocks[key], 1)
|
-- todo -- completely replace http.get with function that
|
||||||
if co then
|
-- checks for success on permanent redirects (minecraft 1.75 bug)
|
||||||
os.queueEvent('sync_lock', co)
|
|
||||||
else
|
http._patched = http.get
|
||||||
syncLocks[key] = nil
|
function http.get(url, headers)
|
||||||
end
|
local s, m
|
||||||
if not s then
|
sync(url, function()
|
||||||
error(m)
|
s, m = http._patched(url, headers)
|
||||||
|
end)
|
||||||
|
return s, m
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function loadUrl(url)
|
local function loadUrl(url)
|
||||||
local c
|
local c
|
||||||
sync(url, function()
|
local h = http.get(url)
|
||||||
local h = http.get(url)
|
if h then
|
||||||
if h then
|
c = h.readAll()
|
||||||
c = h.readAll()
|
h.close()
|
||||||
h.close()
|
end
|
||||||
end
|
|
||||||
end)
|
|
||||||
if c and #c > 0 then
|
if c and #c > 0 then
|
||||||
return c
|
return c
|
||||||
end
|
end
|
||||||
@@ -44,7 +57,7 @@ end
|
|||||||
|
|
||||||
local function requireWrapper(env)
|
local function requireWrapper(env)
|
||||||
|
|
||||||
local function standardSearcher(modname, env, shell)
|
local function standardSearcher(modname)
|
||||||
if package.loaded[modname] then
|
if package.loaded[modname] then
|
||||||
return function()
|
return function()
|
||||||
return package.loaded[modname]
|
return package.loaded[modname]
|
||||||
@@ -52,18 +65,18 @@ local function requireWrapper(env)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function shellSearcher(modname, env, shell)
|
local function shellSearcher(modname)
|
||||||
local fname = modname:gsub('%.', '/') .. '.lua'
|
local fname = modname:gsub('%.', '/') .. '.lua'
|
||||||
|
|
||||||
if shell and type(shell.dir) == 'function' then
|
if env.shell and type(env.shell.dir) == 'function' then
|
||||||
local path = shell.resolve(fname)
|
local path = env.shell.resolve(fname)
|
||||||
if fs.exists(path) and not fs.isDir(path) then
|
if fs.exists(path) and not fs.isDir(path) then
|
||||||
return loadfile(path, env)
|
return loadfile(path, env)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function pathSearcher(modname, env, shell)
|
local function pathSearcher(modname)
|
||||||
local fname = modname:gsub('%.', '/') .. '.lua'
|
local fname = modname:gsub('%.', '/') .. '.lua'
|
||||||
|
|
||||||
for dir in string.gmatch(package.path, "[^:]+") do
|
for dir in string.gmatch(package.path, "[^:]+") do
|
||||||
@@ -75,7 +88,7 @@ local function requireWrapper(env)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- require('BniCQPVf')
|
-- require('BniCQPVf')
|
||||||
local function pastebinSearcher(modname, env, shell)
|
local function pastebinSearcher(modname)
|
||||||
if #modname == 8 and not modname:match('%W') then
|
if #modname == 8 and not modname:match('%W') then
|
||||||
local url = PASTEBIN_URL .. '/' .. modname
|
local url = PASTEBIN_URL .. '/' .. modname
|
||||||
local c = loadUrl(url)
|
local c = loadUrl(url)
|
||||||
@@ -86,7 +99,7 @@ local function requireWrapper(env)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- require('kepler155c.opus.master.sys.apis.util')
|
-- require('kepler155c.opus.master.sys.apis.util')
|
||||||
local function gitSearcher(modname, env, shell)
|
local function gitSearcher(modname)
|
||||||
local fname = modname:gsub('%.', '/') .. '.lua'
|
local fname = modname:gsub('%.', '/') .. '.lua'
|
||||||
local _, count = fname:gsub("/", "")
|
local _, count = fname:gsub("/", "")
|
||||||
if count >= 3 then
|
if count >= 3 then
|
||||||
@@ -98,7 +111,7 @@ local function requireWrapper(env)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function urlSearcher(modname, env, shell)
|
local function urlSearcher(modname)
|
||||||
local fname = modname:gsub('%.', '/') .. '.lua'
|
local fname = modname:gsub('%.', '/') .. '.lua'
|
||||||
|
|
||||||
if fname:sub(1, 1) ~= '/' then
|
if fname:sub(1, 1) ~= '/' then
|
||||||
@@ -113,7 +126,7 @@ local function requireWrapper(env)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- place package and require function into env
|
-- place package and require function into env
|
||||||
package = {
|
env.package = {
|
||||||
path = LUA_PATH or 'sys/apis',
|
path = LUA_PATH or 'sys/apis',
|
||||||
upath = LUA_UPATH or DEFAULT_UPATH,
|
upath = LUA_UPATH or DEFAULT_UPATH,
|
||||||
config = '/\n:\n?\n!\n-',
|
config = '/\n:\n?\n!\n-',
|
||||||
@@ -134,14 +147,14 @@ local function requireWrapper(env)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function require(modname)
|
function env.require(modname)
|
||||||
|
|
||||||
for _,searcher in ipairs(package.loaders) do
|
for _,searcher in ipairs(package.loaders) do
|
||||||
local fn, msg = searcher(modname, env, shell)
|
local fn, msg = searcher(modname)
|
||||||
if fn then
|
if fn then
|
||||||
local module, msg = fn(modname, env)
|
local module, msg2 = fn(modname, env)
|
||||||
if not module then
|
if not module then
|
||||||
error(msg or (modname .. ' module returned nil'), 2)
|
error(msg2 or (modname .. ' module returned nil'), 2)
|
||||||
end
|
end
|
||||||
package.loaded[modname] = module
|
package.loaded[modname] = module
|
||||||
return module
|
return module
|
||||||
@@ -153,10 +166,11 @@ local function requireWrapper(env)
|
|||||||
error('Unable to find module ' .. modname)
|
error('Unable to find module ' .. modname)
|
||||||
end
|
end
|
||||||
|
|
||||||
return require -- backwards compatible
|
return env.require -- backwards compatible
|
||||||
end
|
end
|
||||||
|
|
||||||
return function(env)
|
return function(env)
|
||||||
|
env = env or getfenv(2)
|
||||||
setfenv(requireWrapper, env)
|
setfenv(requireWrapper, env)
|
||||||
return requireWrapper(env)
|
return requireWrapper(env)
|
||||||
end
|
end
|
||||||
|
|||||||
150
sys/apis/input.lua
Normal file
150
sys/apis/input.lua
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
local Util = require('util')
|
||||||
|
|
||||||
|
local keys = _G.keys
|
||||||
|
local os = _G.os
|
||||||
|
|
||||||
|
local modifiers = Util.transpose {
|
||||||
|
keys.leftCtrl, keys.rightCtrl,
|
||||||
|
keys.leftShift, keys.rightShift,
|
||||||
|
keys.leftAlt, keys.rightAlt,
|
||||||
|
}
|
||||||
|
|
||||||
|
local input = {
|
||||||
|
pressed = { },
|
||||||
|
}
|
||||||
|
|
||||||
|
function input:modifierPressed()
|
||||||
|
return self.pressed[keys.leftCtrl] or
|
||||||
|
self.pressed[keys.rightCtrl] or
|
||||||
|
self.pressed[keys.leftAlt] or
|
||||||
|
self.pressed[keys.rightAlt]
|
||||||
|
end
|
||||||
|
|
||||||
|
function input:toCode(ch, code)
|
||||||
|
local result = { }
|
||||||
|
|
||||||
|
if self.pressed[keys.leftCtrl] or self.pressed[keys.rightCtrl] then
|
||||||
|
table.insert(result, 'control')
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.pressed[keys.leftAlt] or self.pressed[keys.rightAlt] then
|
||||||
|
table.insert(result, 'alt')
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.pressed[keys.leftShift] or self.pressed[keys.rightShift] then
|
||||||
|
if code and modifiers[code] then
|
||||||
|
table.insert(result, 'shift')
|
||||||
|
elseif #ch == 1 then
|
||||||
|
table.insert(result, ch:upper())
|
||||||
|
else
|
||||||
|
table.insert(result, 'shift')
|
||||||
|
table.insert(result, ch)
|
||||||
|
end
|
||||||
|
elseif not code or not modifiers[code] then
|
||||||
|
table.insert(result, ch)
|
||||||
|
end
|
||||||
|
|
||||||
|
return table.concat(result, '-')
|
||||||
|
end
|
||||||
|
|
||||||
|
function input:reset()
|
||||||
|
self.pressed = { }
|
||||||
|
self.fired = nil
|
||||||
|
|
||||||
|
self.timer = nil
|
||||||
|
self.mch = nil
|
||||||
|
self.mfired = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function input:translate(event, code, p1, p2)
|
||||||
|
if event == 'key' then
|
||||||
|
if p1 then -- key is held down
|
||||||
|
if not modifiers[code] then
|
||||||
|
self.fired = true
|
||||||
|
return input:toCode(keys.getName(code), code)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
self.pressed[code] = true
|
||||||
|
if self:modifierPressed() and not modifiers[code] or code == 57 then
|
||||||
|
self.fired = true
|
||||||
|
return input:toCode(keys.getName(code), code)
|
||||||
|
else
|
||||||
|
self.fired = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif event == 'char' then
|
||||||
|
if not self:modifierPressed() then
|
||||||
|
self.fired = true
|
||||||
|
return input:toCode(code)
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif event == 'key_up' then
|
||||||
|
if not self.fired then
|
||||||
|
if self.pressed[code] then
|
||||||
|
self.fired = true
|
||||||
|
local ch = input:toCode(keys.getName(code), code)
|
||||||
|
self.pressed[code] = nil
|
||||||
|
return ch
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self.pressed[code] = nil
|
||||||
|
|
||||||
|
elseif event == 'paste' then
|
||||||
|
self.pressed[keys.leftCtrl] = nil
|
||||||
|
self.pressed[keys.rightCtrl] = nil
|
||||||
|
self.fired = true
|
||||||
|
return input:toCode('paste', 255)
|
||||||
|
|
||||||
|
elseif event == 'mouse_click' then
|
||||||
|
local buttons = { 'mouse_click', 'mouse_rightclick' }
|
||||||
|
self.mch = buttons[code]
|
||||||
|
self.mfired = nil
|
||||||
|
|
||||||
|
elseif event == 'mouse_drag' then
|
||||||
|
self.mfired = true
|
||||||
|
self.fired = true
|
||||||
|
return input:toCode('mouse_drag', 255)
|
||||||
|
|
||||||
|
elseif event == 'mouse_up' then
|
||||||
|
if not self.mfired then
|
||||||
|
local clock = os.clock()
|
||||||
|
if self.timer and
|
||||||
|
p1 == self.x and p2 == self.y and
|
||||||
|
(clock - self.timer < .5) then
|
||||||
|
|
||||||
|
self.mch = 'mouse_doubleclick'
|
||||||
|
self.timer = nil
|
||||||
|
else
|
||||||
|
self.timer = os.clock()
|
||||||
|
self.x = p1
|
||||||
|
self.y = p2
|
||||||
|
end
|
||||||
|
self.mfired = input:toCode(self.mch, 255)
|
||||||
|
else
|
||||||
|
self.mch = 'mouse_up'
|
||||||
|
self.mfired = input:toCode(self.mch, 255)
|
||||||
|
end
|
||||||
|
self.fired = true
|
||||||
|
return self.mfired
|
||||||
|
|
||||||
|
elseif event == "mouse_scroll" then
|
||||||
|
local directions = {
|
||||||
|
[ -1 ] = 'scrollUp',
|
||||||
|
[ 1 ] = 'scrollDown'
|
||||||
|
}
|
||||||
|
self.fired = true
|
||||||
|
return input:toCode(directions[code], 255)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function input:test()
|
||||||
|
while true do
|
||||||
|
local ch = self:translate(os.pullEvent())
|
||||||
|
if ch then
|
||||||
|
print('GOT: ' .. tostring(ch))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return input
|
||||||
@@ -189,7 +189,7 @@ function json.parseObject(str)
|
|||||||
|
|
||||||
local val = {}
|
local val = {}
|
||||||
while str:sub(1, 1) ~= "}" do
|
while str:sub(1, 1) ~= "}" do
|
||||||
local k, v = nil, nil
|
local k, v
|
||||||
k, v, str = json.parseMember(str)
|
k, v, str = json.parseMember(str)
|
||||||
val[k] = v
|
val[k] = v
|
||||||
str = removeWhite(str)
|
str = removeWhite(str)
|
||||||
|
|||||||
@@ -1,105 +0,0 @@
|
|||||||
-- Various assertion function for API methods argument-checking
|
|
||||||
|
|
||||||
if (...) then
|
|
||||||
|
|
||||||
-- Dependancies
|
|
||||||
local _PATH = (...):gsub('%.core.assert$','')
|
|
||||||
local Utils = require (_PATH .. '.core.utils')
|
|
||||||
|
|
||||||
-- Local references
|
|
||||||
local lua_type = type
|
|
||||||
local floor = math.floor
|
|
||||||
local concat = table.concat
|
|
||||||
local next = next
|
|
||||||
local pairs = pairs
|
|
||||||
local getmetatable = getmetatable
|
|
||||||
|
|
||||||
-- Is I an integer ?
|
|
||||||
local function isInteger(i)
|
|
||||||
return lua_type(i) ==('number') and (floor(i)==i)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Override lua_type to return integers
|
|
||||||
local function type(v)
|
|
||||||
return isInteger(v) and 'int' or lua_type(v)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Does the given array contents match a predicate type ?
|
|
||||||
local function arrayContentsMatch(t,...)
|
|
||||||
local n_count = Utils.arraySize(t)
|
|
||||||
if n_count < 1 then return false end
|
|
||||||
local init_count = t[0] and 0 or 1
|
|
||||||
local n_count = (t[0] and n_count-1 or n_count)
|
|
||||||
local types = {...}
|
|
||||||
if types then types = concat(types) end
|
|
||||||
for i=init_count,n_count,1 do
|
|
||||||
if not t[i] then return false end
|
|
||||||
if types then
|
|
||||||
if not types:match(type(t[i])) then return false end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Checks if arg is a valid array map
|
|
||||||
local function isMap(m)
|
|
||||||
if not arrayContentsMatch(m, 'table') then return false end
|
|
||||||
local lsize = Utils.arraySize(m[next(m)])
|
|
||||||
for k,v in pairs(m) do
|
|
||||||
if not arrayContentsMatch(m[k], 'string', 'int') then return false end
|
|
||||||
if Utils.arraySize(v)~=lsize then return false end
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Checks if s is a valid string map
|
|
||||||
local function isStringMap(s)
|
|
||||||
if lua_type(s) ~= 'string' then return false end
|
|
||||||
local w
|
|
||||||
for row in s:gmatch('[^\n\r]+') do
|
|
||||||
if not row then return false end
|
|
||||||
w = w or #row
|
|
||||||
if w ~= #row then return false end
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Does instance derive straight from class
|
|
||||||
local function derives(instance, class)
|
|
||||||
return getmetatable(instance) == class
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Does instance inherits from class
|
|
||||||
local function inherits(instance, class)
|
|
||||||
return (getmetatable(getmetatable(instance)) == class)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Is arg a boolean
|
|
||||||
local function isBoolean(b)
|
|
||||||
return (b==true or b==false)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Is arg nil ?
|
|
||||||
local function isNil(n)
|
|
||||||
return (n==nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function matchType(value, types)
|
|
||||||
return types:match(type(value))
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
|
||||||
arrayContentsMatch = arrayContentsMatch,
|
|
||||||
derives = derives,
|
|
||||||
inherits = inherits,
|
|
||||||
isInteger = isInteger,
|
|
||||||
isBool = isBoolean,
|
|
||||||
isMap = isMap,
|
|
||||||
isStrMap = isStringMap,
|
|
||||||
isOutOfRange = isOutOfRange,
|
|
||||||
isNil = isNil,
|
|
||||||
type = type,
|
|
||||||
matchType = matchType
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
--- Heuristic functions for search algorithms.
|
|
||||||
-- A <a href="http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html">distance heuristic</a>
|
|
||||||
-- provides an *estimate of the optimal distance cost* from a given location to a target.
|
|
||||||
-- As such, it guides the pathfinder to the goal, helping it to decide which route is the best.
|
|
||||||
--
|
|
||||||
-- This script holds the definition of some built-in heuristics available through jumper.
|
|
||||||
--
|
|
||||||
-- Distance functions are internally used by the `pathfinder` to evaluate the optimal path
|
|
||||||
-- from the start location to the goal. These functions share the same prototype:
|
|
||||||
-- local function myHeuristic(nodeA, nodeB)
|
|
||||||
-- -- function body
|
|
||||||
-- end
|
|
||||||
-- Jumper features some built-in distance heuristics, namely `MANHATTAN`, `EUCLIDIAN`, `DIAGONAL`, `CARDINTCARD`.
|
|
||||||
-- You can also supply your own heuristic function, following the same template as above.
|
|
||||||
|
|
||||||
|
|
||||||
local abs = math.abs
|
|
||||||
local sqrt = math.sqrt
|
|
||||||
local sqrt2 = sqrt(2)
|
|
||||||
local max, min = math.max, math.min
|
|
||||||
|
|
||||||
local Heuristics = {}
|
|
||||||
--- Manhattan distance.
|
|
||||||
-- <br/>This heuristic is the default one being used by the `pathfinder` object.
|
|
||||||
-- <br/>Evaluates as <code>distance = |dx|+|dy|</code>
|
|
||||||
-- @class function
|
|
||||||
-- @tparam node nodeA a node
|
|
||||||
-- @tparam node nodeB another node
|
|
||||||
-- @treturn number the distance from __nodeA__ to __nodeB__
|
|
||||||
-- @usage
|
|
||||||
-- -- First method
|
|
||||||
-- pathfinder:setHeuristic('MANHATTAN')
|
|
||||||
-- -- Second method
|
|
||||||
-- local Distance = require ('jumper.core.heuristics')
|
|
||||||
-- pathfinder:setHeuristic(Distance.MANHATTAN)
|
|
||||||
function Heuristics.MANHATTAN(nodeA, nodeB)
|
|
||||||
local dx = abs(nodeA._x - nodeB._x)
|
|
||||||
local dy = abs(nodeA._y - nodeB._y)
|
|
||||||
local dz = abs(nodeA._z - nodeB._z)
|
|
||||||
return (dx + dy + dz)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Euclidian distance.
|
|
||||||
-- <br/>Evaluates as <code>distance = squareRoot(dx*dx+dy*dy)</code>
|
|
||||||
-- @class function
|
|
||||||
-- @tparam node nodeA a node
|
|
||||||
-- @tparam node nodeB another node
|
|
||||||
-- @treturn number the distance from __nodeA__ to __nodeB__
|
|
||||||
-- @usage
|
|
||||||
-- -- First method
|
|
||||||
-- pathfinder:setHeuristic('EUCLIDIAN')
|
|
||||||
-- -- Second method
|
|
||||||
-- local Distance = require ('jumper.core.heuristics')
|
|
||||||
-- pathfinder:setHeuristic(Distance.EUCLIDIAN)
|
|
||||||
function Heuristics.EUCLIDIAN(nodeA, nodeB)
|
|
||||||
local dx = nodeA._x - nodeB._x
|
|
||||||
local dy = nodeA._y - nodeB._y
|
|
||||||
local dz = nodeA._z - nodeB._z
|
|
||||||
return sqrt(dx*dx+dy*dy+dz*dz)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Diagonal distance.
|
|
||||||
-- <br/>Evaluates as <code>distance = max(|dx|, abs|dy|)</code>
|
|
||||||
-- @class function
|
|
||||||
-- @tparam node nodeA a node
|
|
||||||
-- @tparam node nodeB another node
|
|
||||||
-- @treturn number the distance from __nodeA__ to __nodeB__
|
|
||||||
-- @usage
|
|
||||||
-- -- First method
|
|
||||||
-- pathfinder:setHeuristic('DIAGONAL')
|
|
||||||
-- -- Second method
|
|
||||||
-- local Distance = require ('jumper.core.heuristics')
|
|
||||||
-- pathfinder:setHeuristic(Distance.DIAGONAL)
|
|
||||||
function Heuristics.DIAGONAL(nodeA, nodeB)
|
|
||||||
local dx = abs(nodeA._x - nodeB._x)
|
|
||||||
local dy = abs(nodeA._y - nodeB._y)
|
|
||||||
return max(dx,dy)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Cardinal/Intercardinal distance.
|
|
||||||
-- <br/>Evaluates as <code>distance = min(dx, dy)*squareRoot(2) + max(dx, dy) - min(dx, dy)</code>
|
|
||||||
-- @class function
|
|
||||||
-- @tparam node nodeA a node
|
|
||||||
-- @tparam node nodeB another node
|
|
||||||
-- @treturn number the distance from __nodeA__ to __nodeB__
|
|
||||||
-- @usage
|
|
||||||
-- -- First method
|
|
||||||
-- pathfinder:setHeuristic('CARDINTCARD')
|
|
||||||
-- -- Second method
|
|
||||||
-- local Distance = require ('jumper.core.heuristics')
|
|
||||||
-- pathfinder:setHeuristic(Distance.CARDINTCARD)
|
|
||||||
function Heuristics.CARDINTCARD(nodeA, nodeB)
|
|
||||||
local dx = abs(nodeA._x - nodeB._x)
|
|
||||||
local dy = abs(nodeA._y - nodeB._y)
|
|
||||||
return min(dx,dy) * sqrt2 + max(dx,dy) - min(dx,dy)
|
|
||||||
end
|
|
||||||
|
|
||||||
return Heuristics
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
local addNode(self, node, nextNode, ed)
|
|
||||||
if not self._pathDB[node] then self._pathDB[node] = {} end
|
|
||||||
self._pathDB[node][ed] = (nextNode == ed and node or nextNode)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Path lookupTable
|
|
||||||
local lookupTable = {}
|
|
||||||
lookupTable.__index = lookupTable
|
|
||||||
|
|
||||||
function lookupTable:new()
|
|
||||||
local lut = {_pathDB = {}}
|
|
||||||
return setmetatable(lut, lookupTable)
|
|
||||||
end
|
|
||||||
|
|
||||||
function lookupTable:addPath(path)
|
|
||||||
local st, ed = path._nodes[1], path._nodes[#path._nodes]
|
|
||||||
for node, count in path:nodes() do
|
|
||||||
local nextNode = path._nodes[count+1]
|
|
||||||
if nextNode then addNode(self, node, nextNode, ed) end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function lookupTable:hasPath(nodeA, nodeB)
|
|
||||||
local found
|
|
||||||
found = self._pathDB[nodeA] and self._path[nodeA][nodeB]
|
|
||||||
if found then return true, true end
|
|
||||||
found = self._pathDB[nodeB] and self._path[nodeB][nodeA]
|
|
||||||
if found then return true, false end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
return lookupTable
|
|
||||||
@@ -7,85 +7,26 @@
|
|||||||
-- made with regards of their `f` cost. From a given node being examined, the `pathfinder` will expand the search
|
-- made with regards of their `f` cost. From a given node being examined, the `pathfinder` will expand the search
|
||||||
-- to the next neighbouring node having the lowest `f` cost. See `core.bheap` for more details.
|
-- to the next neighbouring node having the lowest `f` cost. See `core.bheap` for more details.
|
||||||
--
|
--
|
||||||
|
|
||||||
if (...) then
|
if (...) then
|
||||||
|
|
||||||
local assert = assert
|
|
||||||
|
|
||||||
--- The `Node` class.<br/>
|
|
||||||
-- This class is callable.
|
|
||||||
-- Therefore,_ <code>Node(...)</code> _acts as a shortcut to_ <code>Node:new(...)</code>.
|
|
||||||
-- @type Node
|
|
||||||
local Node = {}
|
local Node = {}
|
||||||
Node.__index = Node
|
Node.__index = Node
|
||||||
|
|
||||||
--- Inits a new `node`
|
|
||||||
-- @class function
|
|
||||||
-- @tparam int x the x-coordinate of the node on the collision map
|
|
||||||
-- @tparam int y the y-coordinate of the node on the collision map
|
|
||||||
-- @treturn node a new `node`
|
|
||||||
-- @usage local node = Node(3,4)
|
|
||||||
function Node:new(x,y,z)
|
function Node:new(x,y,z)
|
||||||
return setmetatable({_x = x, _y = y, _z = z, _clearance = {}}, Node)
|
return setmetatable({x = x, y = y, z = z }, Node)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Enables the use of operator '<' to compare nodes.
|
-- Enables the use of operator '<' to compare nodes.
|
||||||
-- Will be used to sort a collection of nodes in a binary heap on the basis of their F-cost
|
-- Will be used to sort a collection of nodes in a binary heap on the basis of their F-cost
|
||||||
function Node.__lt(A,B) return (A._f < B._f) end
|
function Node.__lt(A,B) return (A._f < B._f) end
|
||||||
|
|
||||||
--- Returns x-coordinate of a `node`
|
function Node:getX() return self.x end
|
||||||
-- @class function
|
function Node:getY() return self.y end
|
||||||
-- @treturn number the x-coordinate of the `node`
|
function Node:getZ() return self.z end
|
||||||
-- @usage local x = node:getX()
|
|
||||||
function Node:getX() return self._x end
|
|
||||||
|
|
||||||
--- Returns y-coordinate of a `node`
|
|
||||||
-- @class function
|
|
||||||
-- @treturn number the y-coordinate of the `node`
|
|
||||||
-- @usage local y = node:getY()
|
|
||||||
function Node:getY() return self._y end
|
|
||||||
|
|
||||||
function Node:getZ() return self._z end
|
|
||||||
|
|
||||||
--- Returns x and y coordinates of a `node`
|
|
||||||
-- @class function
|
|
||||||
-- @treturn number the x-coordinate of the `node`
|
|
||||||
-- @treturn number the y-coordinate of the `node`
|
|
||||||
-- @usage local x, y = node:getPos()
|
|
||||||
function Node:getPos() return self._x, self._y, self._z end
|
|
||||||
|
|
||||||
--- Returns the amount of true [clearance](http://aigamedev.com/open/tutorial/clearance-based-pathfinding/#TheTrueClearanceMetric)
|
|
||||||
-- for a given `node`
|
|
||||||
-- @class function
|
|
||||||
-- @tparam string|int|func walkable the value for walkable locations in the collision map array.
|
|
||||||
-- @treturn int the clearance of the `node`
|
|
||||||
-- @usage
|
|
||||||
-- -- Assuming walkable was 0
|
|
||||||
-- local clearance = node:getClearance(0)
|
|
||||||
function Node:getClearance(walkable)
|
|
||||||
return self._clearance[walkable]
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Removes the clearance value for a given walkable.
|
|
||||||
-- @class function
|
|
||||||
-- @tparam string|int|func walkable the value for walkable locations in the collision map array.
|
|
||||||
-- @treturn node self (the calling `node` itself, can be chained)
|
|
||||||
-- @usage
|
|
||||||
-- -- Assuming walkable is defined
|
|
||||||
-- node:removeClearance(walkable)
|
|
||||||
function Node:removeClearance(walkable)
|
|
||||||
self._clearance[walkable] = nil
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Clears temporary cached attributes of a `node`.
|
--- Clears temporary cached attributes of a `node`.
|
||||||
-- Deletes the attributes cached within a given node after a pathfinding call.
|
-- Deletes the attributes cached within a given node after a pathfinding call.
|
||||||
-- This function is internally used by the search algorithms, so you should not use it explicitely.
|
-- This function is internally used by the search algorithms, so you should not use it explicitely.
|
||||||
-- @class function
|
|
||||||
-- @treturn node self (the calling `node` itself, can be chained)
|
|
||||||
-- @usage
|
|
||||||
-- local thisNode = Node(1,2)
|
|
||||||
-- thisNode:reset()
|
|
||||||
function Node:reset()
|
function Node:reset()
|
||||||
self._g, self._h, self._f = nil, nil, nil
|
self._g, self._h, self._f = nil, nil, nil
|
||||||
self._opened, self._closed, self._parent = nil, nil, nil
|
self._opened, self._closed, self._parent = nil, nil, nil
|
||||||
@@ -93,7 +34,7 @@ if (...) then
|
|||||||
end
|
end
|
||||||
|
|
||||||
return setmetatable(Node,
|
return setmetatable(Node,
|
||||||
{__call = function(self,...)
|
{__call = function(_,...)
|
||||||
return Node:new(...)
|
return Node:new(...)
|
||||||
end}
|
end}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,44 +6,25 @@
|
|||||||
-- It should normally not be used explicitely, yet it remains fully accessible.
|
-- It should normally not be used explicitely, yet it remains fully accessible.
|
||||||
--
|
--
|
||||||
|
|
||||||
|
|
||||||
if (...) then
|
if (...) then
|
||||||
|
|
||||||
-- Dependencies
|
local t_remove = table.remove
|
||||||
local _PATH = (...):match('(.+)%.path$')
|
|
||||||
local Heuristic = require (_PATH .. '.heuristics')
|
|
||||||
|
|
||||||
-- Local references
|
|
||||||
local abs, max = math.abs, math.max
|
|
||||||
local t_insert, t_remove = table.insert, table.remove
|
|
||||||
|
|
||||||
--- The `Path` class.<br/>
|
|
||||||
-- This class is callable.
|
|
||||||
-- Therefore, <em><code>Path(...)</code></em> acts as a shortcut to <em><code>Path:new(...)</code></em>.
|
|
||||||
-- @type Path
|
|
||||||
local Path = {}
|
local Path = {}
|
||||||
Path.__index = Path
|
Path.__index = Path
|
||||||
|
|
||||||
--- Inits a new `path`.
|
|
||||||
-- @class function
|
|
||||||
-- @treturn path a `path`
|
|
||||||
-- @usage local p = Path()
|
|
||||||
function Path:new()
|
function Path:new()
|
||||||
return setmetatable({_nodes = {}}, Path)
|
return setmetatable({_nodes = {}}, Path)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Iterates on each single `node` along a `path`. At each step of iteration,
|
--- Iterates on each single `node` along a `path`. At each step of iteration,
|
||||||
-- returns the `node` plus a count value. Aliased as @{Path:nodes}
|
-- returns the `node` plus a count value. Aliased as @{Path:nodes}
|
||||||
-- @class function
|
|
||||||
-- @treturn node a `node`
|
|
||||||
-- @treturn int the count for the number of nodes
|
|
||||||
-- @see Path:nodes
|
|
||||||
-- @usage
|
-- @usage
|
||||||
-- for node, count in p:iter() do
|
-- for node, count in p:iter() do
|
||||||
-- ...
|
-- ...
|
||||||
-- end
|
-- end
|
||||||
function Path:iter()
|
function Path:nodes()
|
||||||
local i,pathLen = 1,#self._nodes
|
local i = 1
|
||||||
return function()
|
return function()
|
||||||
if self._nodes[i] then
|
if self._nodes[i] then
|
||||||
i = i+1
|
i = i+1
|
||||||
@@ -52,149 +33,34 @@ if (...) then
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Iterates on each single `node` along a `path`. At each step of iteration,
|
|
||||||
-- returns a `node` plus a count value. Alias for @{Path:iter}
|
|
||||||
-- @class function
|
|
||||||
-- @name Path:nodes
|
|
||||||
-- @treturn node a `node`
|
|
||||||
-- @treturn int the count for the number of nodes
|
|
||||||
-- @see Path:iter
|
|
||||||
-- @usage
|
|
||||||
-- for node, count in p:nodes() do
|
|
||||||
-- ...
|
|
||||||
-- end
|
|
||||||
Path.nodes = Path.iter
|
|
||||||
|
|
||||||
--- Evaluates the `path` length
|
|
||||||
-- @class function
|
|
||||||
-- @treturn number the `path` length
|
|
||||||
-- @usage local len = p:getLength()
|
|
||||||
function Path:getLength()
|
|
||||||
local len = 0
|
|
||||||
for i = 2,#self._nodes do
|
|
||||||
len = len + Heuristic.EUCLIDIAN(self._nodes[i], self._nodes[i-1])
|
|
||||||
end
|
|
||||||
return len
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Counts the number of steps.
|
|
||||||
-- Returns the number of waypoints (nodes) in the current path.
|
|
||||||
-- @class function
|
|
||||||
-- @tparam node node a node to be added to the path
|
|
||||||
-- @tparam[opt] int index the index at which the node will be inserted. If omitted, the node will be appended after the last node in the path.
|
|
||||||
-- @treturn path self (the calling `path` itself, can be chained)
|
|
||||||
-- @usage local nSteps = p:countSteps()
|
|
||||||
function Path:addNode(node, index)
|
|
||||||
index = index or #self._nodes+1
|
|
||||||
t_insert(self._nodes, index, node)
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
--- `Path` filling modifier. Interpolates between non contiguous nodes along a `path`
|
|
||||||
-- to build a fully continuous `path`. This maybe useful when using search algorithms such as Jump Point Search.
|
|
||||||
-- Does the opposite of @{Path:filter}
|
|
||||||
-- @class function
|
|
||||||
-- @treturn path self (the calling `path` itself, can be chained)
|
|
||||||
-- @see Path:filter
|
|
||||||
-- @usage p:fill()
|
|
||||||
function Path:fill()
|
|
||||||
local i = 2
|
|
||||||
local xi,yi,dx,dy
|
|
||||||
local N = #self._nodes
|
|
||||||
local incrX, incrY
|
|
||||||
while true do
|
|
||||||
xi,yi = self._nodes[i]._x,self._nodes[i]._y
|
|
||||||
dx,dy = xi-self._nodes[i-1]._x,yi-self._nodes[i-1]._y
|
|
||||||
if (abs(dx) > 1 or abs(dy) > 1) then
|
|
||||||
incrX = dx/max(abs(dx),1)
|
|
||||||
incrY = dy/max(abs(dy),1)
|
|
||||||
t_insert(self._nodes, i, self._grid:getNodeAt(self._nodes[i-1]._x + incrX, self._nodes[i-1]._y +incrY))
|
|
||||||
N = N+1
|
|
||||||
else i=i+1
|
|
||||||
end
|
|
||||||
if i>N then break end
|
|
||||||
end
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
--- `Path` compression modifier. Given a `path`, eliminates useless nodes to return a lighter `path`
|
--- `Path` compression modifier. Given a `path`, eliminates useless nodes to return a lighter `path`
|
||||||
-- consisting of straight moves. Does the opposite of @{Path:fill}
|
-- consisting of straight moves. Does the opposite of @{Path:fill}
|
||||||
-- @class function
|
-- @class function
|
||||||
-- @treturn path self (the calling `path` itself, can be chained)
|
-- @treturn path self (the calling `path` itself, can be chained)
|
||||||
-- @see Path:fill
|
-- @see Path:fill
|
||||||
-- @usage p:filter()
|
-- @usage p:filter()
|
||||||
function Path:filter()
|
function Path:filter()
|
||||||
local i = 2
|
local i = 2
|
||||||
local xi,yi,dx,dy, olddx, olddy
|
local xi,yi,zi,dx,dy,dz, olddx, olddy, olddz
|
||||||
xi,yi = self._nodes[i]._x, self._nodes[i]._y
|
xi,yi,zi = self._nodes[i].x, self._nodes[i].y, self._nodes[i].z
|
||||||
dx, dy = xi - self._nodes[i-1]._x, yi-self._nodes[i-1]._y
|
dx, dy,dz = xi - self._nodes[i-1].x, yi-self._nodes[i-1].y, zi-self._nodes[i-1].z
|
||||||
while true do
|
while true do
|
||||||
olddx, olddy = dx, dy
|
olddx, olddy, olddz = dx, dy, dz
|
||||||
if self._nodes[i+1] then
|
if self._nodes[i+1] then
|
||||||
i = i+1
|
i = i+1
|
||||||
xi, yi = self._nodes[i]._x, self._nodes[i]._y
|
xi, yi, zi = self._nodes[i].x, self._nodes[i].y, self._nodes[i].z
|
||||||
dx, dy = xi - self._nodes[i-1]._x, yi - self._nodes[i-1]._y
|
dx, dy, dz = xi - self._nodes[i-1].x, yi - self._nodes[i-1].y, zi - self._nodes[i-1].z
|
||||||
if olddx == dx and olddy == dy then
|
if olddx == dx and olddy == dy and olddz == dz then
|
||||||
t_remove(self._nodes, i-1)
|
t_remove(self._nodes, i-1)
|
||||||
i = i - 1
|
i = i - 1
|
||||||
end
|
end
|
||||||
else break end
|
else break end
|
||||||
end
|
end
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Clones a `path`.
|
|
||||||
-- @class function
|
|
||||||
-- @treturn path a `path`
|
|
||||||
-- @usage local p = path:clone()
|
|
||||||
function Path:clone()
|
|
||||||
local p = Path:new()
|
|
||||||
for node in self:nodes() do p:addNode(node) end
|
|
||||||
return p
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Checks if a `path` is equal to another. It also supports *filtered paths* (see @{Path:filter}).
|
|
||||||
-- @class function
|
|
||||||
-- @tparam path p2 a path
|
|
||||||
-- @treturn boolean a boolean
|
|
||||||
-- @usage print(myPath:isEqualTo(anotherPath))
|
|
||||||
function Path:isEqualTo(p2)
|
|
||||||
local p1 = self:clone():filter()
|
|
||||||
local p2 = p2:clone():filter()
|
|
||||||
for node, count in p1:nodes() do
|
|
||||||
if not p2._nodes[count] then return false end
|
|
||||||
local n = p2._nodes[count]
|
|
||||||
if n._x~=node._x or n._y~=node._y then return false end
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Reverses a `path`.
|
|
||||||
-- @class function
|
|
||||||
-- @treturn path self (the calling `path` itself, can be chained)
|
|
||||||
-- @usage myPath:reverse()
|
|
||||||
function Path:reverse()
|
|
||||||
local _nodes = {}
|
|
||||||
for i = #self._nodes,1,-1 do
|
|
||||||
_nodes[#_nodes+1] = self._nodes[i]
|
|
||||||
end
|
|
||||||
self._nodes = _nodes
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Appends a given `path` to self.
|
|
||||||
-- @class function
|
|
||||||
-- @tparam path p a path
|
|
||||||
-- @treturn path self (the calling `path` itself, can be chained)
|
|
||||||
-- @usage myPath:append(anotherPath)
|
|
||||||
function Path:append(p)
|
|
||||||
for node in p:nodes() do self:addNode(node) end
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
return setmetatable(Path,
|
return setmetatable(Path,
|
||||||
{__call = function(self,...)
|
{__call = function(_,...)
|
||||||
return Path:new(...)
|
return Path:new(...)
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,125 +5,20 @@ if (...) then
|
|||||||
-- Dependencies
|
-- Dependencies
|
||||||
local _PATH = (...):gsub('%.utils$','')
|
local _PATH = (...):gsub('%.utils$','')
|
||||||
local Path = require (_PATH .. '.path')
|
local Path = require (_PATH .. '.path')
|
||||||
local Node = require (_PATH .. '.node')
|
|
||||||
|
|
||||||
-- Local references
|
-- Local references
|
||||||
local pairs = pairs
|
local pairs = pairs
|
||||||
local type = type
|
|
||||||
local t_insert = table.insert
|
local t_insert = table.insert
|
||||||
local assert = assert
|
|
||||||
local coroutine = coroutine
|
|
||||||
|
|
||||||
-- Raw array items count
|
-- Raw array items count
|
||||||
local function arraySize(t)
|
local function arraySize(t)
|
||||||
local count = 0
|
local count = 0
|
||||||
for k,v in pairs(t) do
|
for _ in pairs(t) do
|
||||||
count = count+1
|
count = count+1
|
||||||
end
|
end
|
||||||
return count
|
return count
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Parses a string map and builds an array map
|
|
||||||
local function stringMapToArray(str)
|
|
||||||
local map = {}
|
|
||||||
local w, h
|
|
||||||
for line in str:gmatch('[^\n\r]+') do
|
|
||||||
if line then
|
|
||||||
w = not w and #line or w
|
|
||||||
assert(#line == w, 'Error parsing map, rows must have the same size!')
|
|
||||||
h = (h or 0) + 1
|
|
||||||
map[h] = {}
|
|
||||||
for char in line:gmatch('.') do
|
|
||||||
map[h][#map[h]+1] = char
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return map
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Collects and returns the keys of a given array
|
|
||||||
local function getKeys(t)
|
|
||||||
local keys = {}
|
|
||||||
for k,v in pairs(t) do keys[#keys+1] = k end
|
|
||||||
return keys
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Calculates the bounds of a 2d array
|
|
||||||
local function getArrayBounds(map)
|
|
||||||
local min_x, max_x
|
|
||||||
local min_y, max_y
|
|
||||||
for y in pairs(map) do
|
|
||||||
min_y = not min_y and y or (y<min_y and y or min_y)
|
|
||||||
max_y = not max_y and y or (y>max_y and y or max_y)
|
|
||||||
for x in pairs(map[y]) do
|
|
||||||
min_x = not min_x and x or (x<min_x and x or min_x)
|
|
||||||
max_x = not max_x and x or (x>max_x and x or max_x)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return min_x,max_x,min_y,max_y
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Converts an array to a set of nodes
|
|
||||||
local function arrayToNodes(map)
|
|
||||||
local min_x, max_x
|
|
||||||
local min_y, max_y
|
|
||||||
local min_z, max_z
|
|
||||||
local nodes = {}
|
|
||||||
for y in pairs(map) do
|
|
||||||
min_y = not min_y and y or (y<min_y and y or min_y)
|
|
||||||
max_y = not max_y and y or (y>max_y and y or max_y)
|
|
||||||
nodes[y] = {}
|
|
||||||
for x in pairs(map[y]) do
|
|
||||||
min_x = not min_x and x or (x<min_x and x or min_x)
|
|
||||||
max_x = not max_x and x or (x>max_x and x or max_x)
|
|
||||||
nodes[y][x] = {}
|
|
||||||
for z in pairs(map[y][x]) do
|
|
||||||
min_z = not min_z and z or (z<min_z and z or min_z)
|
|
||||||
max_z = not max_z and z or (z>max_z and z or max_z)
|
|
||||||
nodes[y][x][z] = Node:new(x,y,z)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return nodes,
|
|
||||||
(min_x or 0), (max_x or 0),
|
|
||||||
(min_y or 0), (max_y or 0),
|
|
||||||
(min_z or 0), (max_z or 0)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Iterator, wrapped within a coroutine
|
|
||||||
-- Iterates around a given position following the outline of a square
|
|
||||||
local function around()
|
|
||||||
local iterf = function(x0, y0, z0, s)
|
|
||||||
local x, y, z = x0-s, y0-s, z0-s
|
|
||||||
coroutine.yield(x, y, z)
|
|
||||||
repeat
|
|
||||||
x = x + 1
|
|
||||||
coroutine.yield(x,y,z)
|
|
||||||
until x == x0+s
|
|
||||||
repeat
|
|
||||||
y = y + 1
|
|
||||||
coroutine.yield(x,y,z)
|
|
||||||
until y == y0 + s
|
|
||||||
repeat
|
|
||||||
z = z + 1
|
|
||||||
coroutine.yield(x,y,z)
|
|
||||||
until z == z0 + s
|
|
||||||
repeat
|
|
||||||
x = x - 1
|
|
||||||
coroutine.yield(x, y,z)
|
|
||||||
until x == x0-s
|
|
||||||
repeat
|
|
||||||
y = y - 1
|
|
||||||
coroutine.yield(x,y,z)
|
|
||||||
until y == y0-s+1
|
|
||||||
repeat
|
|
||||||
z = z - 1
|
|
||||||
coroutine.yield(x,y,z)
|
|
||||||
until z == z0-s+1
|
|
||||||
end
|
|
||||||
return coroutine.create(iterf)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Extract a path from a given start/end position
|
-- Extract a path from a given start/end position
|
||||||
local function traceBackPath(finder, node, startNode)
|
local function traceBackPath(finder, node, startNode)
|
||||||
local path = Path:new()
|
local path = Path:new()
|
||||||
@@ -154,14 +49,8 @@ if (...) then
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
arraySize = arraySize,
|
arraySize = arraySize,
|
||||||
getKeys = getKeys,
|
|
||||||
indexOf = indexOf,
|
indexOf = indexOf,
|
||||||
outOfRange = outOfRange,
|
outOfRange = outOfRange,
|
||||||
getArrayBounds = getArrayBounds,
|
|
||||||
arrayToNodes = arrayToNodes,
|
|
||||||
strToMap = stringMapToArray,
|
|
||||||
around = around,
|
|
||||||
drAround = drAround,
|
|
||||||
traceBackPath = traceBackPath
|
traceBackPath = traceBackPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
-- Implementation of the `grid` class.
|
-- Implementation of the `grid` class.
|
||||||
-- The `grid` is a implicit graph which represents the 2D
|
-- The `grid` is a implicit graph which represents the 2D
|
||||||
-- world map layout on which the `pathfinder` object will run.
|
-- world map layout on which the `pathfinder` object will run.
|
||||||
-- During a search, the `pathfinder` object needs to save some critical values. These values are cached within each `node`
|
-- During a search, the `pathfinder` object needs to save some critical values.
|
||||||
|
-- These values are cached within each `node`
|
||||||
-- object, and the whole set of nodes are tight inside the `grid` object itself.
|
-- object, and the whole set of nodes are tight inside the `grid` object itself.
|
||||||
|
|
||||||
if (...) then
|
if (...) then
|
||||||
@@ -12,16 +13,10 @@ if (...) then
|
|||||||
|
|
||||||
-- Local references
|
-- Local references
|
||||||
local Utils = require (_PATH .. '.core.utils')
|
local Utils = require (_PATH .. '.core.utils')
|
||||||
local Assert = require (_PATH .. '.core.assert')
|
|
||||||
local Node = require (_PATH .. '.core.node')
|
local Node = require (_PATH .. '.core.node')
|
||||||
|
|
||||||
-- Local references
|
-- Local references
|
||||||
local pairs = pairs
|
|
||||||
local assert = assert
|
|
||||||
local next = next
|
|
||||||
local setmetatable = setmetatable
|
local setmetatable = setmetatable
|
||||||
local floor = math.floor
|
|
||||||
local coroutine = coroutine
|
|
||||||
|
|
||||||
-- Offsets for straights moves
|
-- Offsets for straights moves
|
||||||
local straightOffsets = {
|
local straightOffsets = {
|
||||||
@@ -30,390 +25,67 @@ if (...) then
|
|||||||
{x = 0, y = 0, z = 1} --[[U]], {x = 0, y = -0, z = -1}, --[[D]]
|
{x = 0, y = 0, z = 1} --[[U]], {x = 0, y = -0, z = -1}, --[[D]]
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Offsets for diagonal moves
|
|
||||||
local diagonalOffsets = {
|
|
||||||
{x = -1, y = -1} --[[NW]], {x = 1, y = -1}, --[[NE]]
|
|
||||||
{x = -1, y = 1} --[[SW]], {x = 1, y = 1}, --[[SE]]
|
|
||||||
}
|
|
||||||
|
|
||||||
--- The `Grid` class.<br/>
|
|
||||||
-- This class is callable.
|
|
||||||
-- Therefore,_ <code>Grid(...)</code> _acts as a shortcut to_ <code>Grid:new(...)</code>.
|
|
||||||
-- @type Grid
|
|
||||||
local Grid = {}
|
local Grid = {}
|
||||||
Grid.__index = Grid
|
Grid.__index = Grid
|
||||||
|
|
||||||
-- Specialized grids
|
function Grid:new(dim)
|
||||||
local PreProcessGrid = setmetatable({},Grid)
|
local newGrid = { }
|
||||||
local PostProcessGrid = setmetatable({},Grid)
|
newGrid._min_x, newGrid._max_x = dim.x, dim.ex
|
||||||
PreProcessGrid.__index = PreProcessGrid
|
newGrid._min_y, newGrid._max_y = dim.y, dim.ey
|
||||||
PostProcessGrid.__index = PostProcessGrid
|
newGrid._min_z, newGrid._max_z = dim.z, dim.ez
|
||||||
PreProcessGrid.__call = function (self,x,y,z)
|
newGrid._nodes = { }
|
||||||
return self:getNodeAt(x,y,z)
|
newGrid._width = (newGrid._max_x-newGrid._min_x)+1
|
||||||
end
|
newGrid._height = (newGrid._max_y-newGrid._min_y)+1
|
||||||
PostProcessGrid.__call = function (self,x,y,z,create)
|
newGrid._length = (newGrid._max_z-newGrid._min_z)+1
|
||||||
if create then return self:getNodeAt(x,y,z) end
|
return setmetatable(newGrid,Grid)
|
||||||
return self._nodes[y] and self._nodes[y][x] and self._nodes[y][x][z]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Inits a new `grid`
|
function Grid:isWalkableAt(x, y, z)
|
||||||
-- @class function
|
local node = self:getNodeAt(x,y,z)
|
||||||
-- @tparam table|string map A collision map - (2D array) with consecutive indices (starting at 0 or 1)
|
return node and node.walkable ~= 1
|
||||||
-- or a `string` with line-break chars (<code>\n</code> or <code>\r</code>) as row delimiters.
|
|
||||||
-- @tparam[opt] bool cacheNodeAtRuntime When __true__, returns an empty `grid` instance, so that
|
|
||||||
-- later on, indexing a non-cached `node` will cause it to be created and cache within the `grid` on purpose (i.e, when needed).
|
|
||||||
-- This is a __memory-safe__ option, in case your dealing with some tight memory constraints.
|
|
||||||
-- Defaults to __false__ when omitted.
|
|
||||||
-- @treturn grid a new `grid` instance
|
|
||||||
-- @usage
|
|
||||||
-- -- A simple 3x3 grid
|
|
||||||
-- local myGrid = Grid:new({{0,0,0},{0,0,0},{0,0,0}})
|
|
||||||
--
|
|
||||||
-- -- A memory-safe 3x3 grid
|
|
||||||
-- myGrid = Grid('000\n000\n000', true)
|
|
||||||
function Grid:new(map, cacheNodeAtRuntime)
|
|
||||||
if type(map) == 'string' then
|
|
||||||
assert(Assert.isStrMap(map), 'Wrong argument #1. Not a valid string map')
|
|
||||||
map = Utils.strToMap(map)
|
|
||||||
end
|
|
||||||
--assert(Assert.isMap(map),('Bad argument #1. Not a valid map'))
|
|
||||||
assert(Assert.isBool(cacheNodeAtRuntime) or Assert.isNil(cacheNodeAtRuntime),
|
|
||||||
('Bad argument #2. Expected \'boolean\', got %s.'):format(type(cacheNodeAtRuntime)))
|
|
||||||
if cacheNodeAtRuntime then
|
|
||||||
return PostProcessGrid:new(map,walkable)
|
|
||||||
end
|
|
||||||
return PreProcessGrid:new(map,walkable)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Checks if `node` at [x,y] is __walkable__.
|
|
||||||
-- Will check if `node` at location [x,y] both *exists* on the collision map and *is walkable*
|
|
||||||
-- @class function
|
|
||||||
-- @tparam int x the x-location of the node
|
|
||||||
-- @tparam int y the y-location of the node
|
|
||||||
-- @tparam[opt] string|int|func walkable the value for walkable locations in the collision map array (see @{Grid:new}).
|
|
||||||
-- Defaults to __false__ when omitted.
|
|
||||||
-- If this parameter is a function, it should be prototyped as __f(value)__ and return a `boolean`:
|
|
||||||
-- __true__ when value matches a __walkable__ `node`, __false__ otherwise. If this parameter is not given
|
|
||||||
-- while location [x,y] __is valid__, this actual function returns __true__.
|
|
||||||
-- @tparam[optchain] int clearance the amount of clearance needed. Defaults to 1 (normal clearance) when not given.
|
|
||||||
-- @treturn bool __true__ if `node` exists and is __walkable__, __false__ otherwise
|
|
||||||
-- @usage
|
|
||||||
-- -- Always true
|
|
||||||
-- print(myGrid:isWalkableAt(2,3))
|
|
||||||
--
|
|
||||||
-- -- True if node at [2,3] collision map value is 0
|
|
||||||
-- print(myGrid:isWalkableAt(2,3,0))
|
|
||||||
--
|
|
||||||
-- -- True if node at [2,3] collision map value is 0 and has a clearance higher or equal to 2
|
|
||||||
-- print(myGrid:isWalkableAt(2,3,0,2))
|
|
||||||
--
|
|
||||||
function Grid:isWalkableAt(x, y, z, walkable, clearance)
|
|
||||||
local nodeValue = self._map[y] and self._map[y][x] and self._map[y][x][z]
|
|
||||||
if nodeValue then
|
|
||||||
if not walkable then return true end
|
|
||||||
else
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
local hasEnoughClearance = not clearance and true or false
|
|
||||||
if not hasEnoughClearance then
|
|
||||||
if not self._isAnnotated[walkable] then return false end
|
|
||||||
local node = self:getNodeAt(x,y,z)
|
|
||||||
local nodeClearance = node:getClearance(walkable)
|
|
||||||
hasEnoughClearance = (nodeClearance >= clearance)
|
|
||||||
end
|
|
||||||
if self._eval then
|
|
||||||
return walkable(nodeValue) and hasEnoughClearance
|
|
||||||
end
|
|
||||||
return ((nodeValue == walkable) and hasEnoughClearance)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Returns the `grid` width.
|
|
||||||
-- @class function
|
|
||||||
-- @treturn int the `grid` width
|
|
||||||
-- @usage print(myGrid:getWidth())
|
|
||||||
function Grid:getWidth()
|
function Grid:getWidth()
|
||||||
return self._width
|
return self._width
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Returns the `grid` height.
|
|
||||||
-- @class function
|
|
||||||
-- @treturn int the `grid` height
|
|
||||||
-- @usage print(myGrid:getHeight())
|
|
||||||
function Grid:getHeight()
|
function Grid:getHeight()
|
||||||
return self._height
|
return self._height
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Returns the collision map.
|
|
||||||
-- @class function
|
|
||||||
-- @treturn map the collision map (see @{Grid:new})
|
|
||||||
-- @usage local map = myGrid:getMap()
|
|
||||||
function Grid:getMap()
|
|
||||||
return self._map
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Returns the set of nodes.
|
|
||||||
-- @class function
|
|
||||||
-- @treturn {{node,...},...} an array of nodes
|
|
||||||
-- @usage local nodes = myGrid:getNodes()
|
|
||||||
function Grid:getNodes()
|
function Grid:getNodes()
|
||||||
return self._nodes
|
return self._nodes
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Returns the `grid` bounds. Returned values corresponds to the upper-left
|
|
||||||
-- and lower-right coordinates (in tile units) of the actual `grid` instance.
|
|
||||||
-- @class function
|
|
||||||
-- @treturn int the upper-left corner x-coordinate
|
|
||||||
-- @treturn int the upper-left corner y-coordinate
|
|
||||||
-- @treturn int the lower-right corner x-coordinate
|
|
||||||
-- @treturn int the lower-right corner y-coordinate
|
|
||||||
-- @usage local left_x, left_y, right_x, right_y = myGrid:getBounds()
|
|
||||||
function Grid:getBounds()
|
function Grid:getBounds()
|
||||||
return self._min_x, self._min_y, self._min_z, self._max_x, self._max_y, self._max_z
|
return self._min_x, self._min_y, self._min_z, self._max_x, self._max_y, self._max_z
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Returns neighbours. The returned value is an array of __walkable__ nodes neighbouring a given `node`.
|
--- Returns neighbours. The returned value is an array of __walkable__ nodes neighbouring a given `node`.
|
||||||
-- @class function
|
|
||||||
-- @tparam node node a given `node`
|
|
||||||
-- @tparam[opt] string|int|func walkable the value for walkable locations in the collision map array (see @{Grid:new}).
|
|
||||||
-- Defaults to __false__ when omitted.
|
|
||||||
-- @tparam[optchain] bool allowDiagonal when __true__, allows adjacent nodes are included (8-neighbours).
|
|
||||||
-- Defaults to __false__ when omitted.
|
|
||||||
-- @tparam[optchain] bool tunnel When __true__, allows the `pathfinder` to tunnel through walls when heading diagonally.
|
|
||||||
-- @tparam[optchain] int clearance When given, will prune for the neighbours set all nodes having a clearance value lower than the passed-in value
|
|
||||||
-- Defaults to __false__ when omitted.
|
|
||||||
-- @treturn {node,...} an array of nodes neighbouring a given node
|
-- @treturn {node,...} an array of nodes neighbouring a given node
|
||||||
-- @usage
|
function Grid:getNeighbours(node)
|
||||||
-- local aNode = myGrid:getNodeAt(5,6)
|
|
||||||
-- local neighbours = myGrid:getNeighbours(aNode, 0, true)
|
|
||||||
function Grid:getNeighbours(node, walkable, allowDiagonal, tunnel, clearance)
|
|
||||||
local neighbours = {}
|
local neighbours = {}
|
||||||
for i = 1,#straightOffsets do
|
for i = 1,#straightOffsets do
|
||||||
local n = self:getNodeAt(
|
local n = self:getNodeAt(
|
||||||
node._x + straightOffsets[i].x,
|
node.x + straightOffsets[i].x,
|
||||||
node._y + straightOffsets[i].y,
|
node.y + straightOffsets[i].y,
|
||||||
node._z + straightOffsets[i].z
|
node.z + straightOffsets[i].z
|
||||||
)
|
)
|
||||||
if n and self:isWalkableAt(n._x, n._y, n._z, walkable, clearance) then
|
if n and self:isWalkableAt(n.x, n.y, n.z) then
|
||||||
neighbours[#neighbours+1] = n
|
neighbours[#neighbours+1] = n
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if not allowDiagonal then return neighbours end
|
|
||||||
|
|
||||||
tunnel = not not tunnel
|
|
||||||
for i = 1,#diagonalOffsets do
|
|
||||||
local n = self:getNodeAt(
|
|
||||||
node._x + diagonalOffsets[i].x,
|
|
||||||
node._y + diagonalOffsets[i].y
|
|
||||||
)
|
|
||||||
if n and self:isWalkableAt(n._x, n._y, walkable, clearance) then
|
|
||||||
if tunnel then
|
|
||||||
neighbours[#neighbours+1] = n
|
|
||||||
else
|
|
||||||
local skipThisNode = false
|
|
||||||
local n1 = self:getNodeAt(node._x+diagonalOffsets[i].x, node._y)
|
|
||||||
local n2 = self:getNodeAt(node._x, node._y+diagonalOffsets[i].y)
|
|
||||||
if ((n1 and n2) and not self:isWalkableAt(n1._x, n1._y, walkable, clearance) and not self:isWalkableAt(n2._x, n2._y, walkable, clearance)) then
|
|
||||||
skipThisNode = true
|
|
||||||
end
|
|
||||||
if not skipThisNode then neighbours[#neighbours+1] = n end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return neighbours
|
return neighbours
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Grid iterator. Iterates on every single node
|
function Grid:getNodeAt(x,y,z)
|
||||||
-- in the `grid`. Passing __lx, ly, ex, ey__ arguments will iterate
|
|
||||||
-- only on nodes inside the bounding-rectangle delimited by those given coordinates.
|
|
||||||
-- @class function
|
|
||||||
-- @tparam[opt] int lx the leftmost x-coordinate of the rectangle. Default to the `grid` leftmost x-coordinate (see @{Grid:getBounds}).
|
|
||||||
-- @tparam[optchain] int ly the topmost y-coordinate of the rectangle. Default to the `grid` topmost y-coordinate (see @{Grid:getBounds}).
|
|
||||||
-- @tparam[optchain] int ex the rightmost x-coordinate of the rectangle. Default to the `grid` rightmost x-coordinate (see @{Grid:getBounds}).
|
|
||||||
-- @tparam[optchain] int ey the bottom-most y-coordinate of the rectangle. Default to the `grid` bottom-most y-coordinate (see @{Grid:getBounds}).
|
|
||||||
-- @treturn node a `node` on the collision map, upon each iteration step
|
|
||||||
-- @treturn int the iteration count
|
|
||||||
-- @usage
|
|
||||||
-- for node, count in myGrid:iter() do
|
|
||||||
-- print(node:getX(), node:getY(), count)
|
|
||||||
-- end
|
|
||||||
function Grid:iter(lx,ly,lz,ex,ey,ez)
|
|
||||||
local min_x = lx or self._min_x
|
|
||||||
local min_y = ly or self._min_y
|
|
||||||
local min_z = lz or self._min_z
|
|
||||||
local max_x = ex or self._max_x
|
|
||||||
local max_y = ey or self._max_y
|
|
||||||
local max_z = ez or self._max_z
|
|
||||||
|
|
||||||
local x, y, z
|
|
||||||
z = min_z
|
|
||||||
return function()
|
|
||||||
x = not x and min_x or x+1
|
|
||||||
if x > max_x then
|
|
||||||
x = min_x
|
|
||||||
y = y+1
|
|
||||||
end
|
|
||||||
y = not y and min_y or y+1
|
|
||||||
if y > max_y then
|
|
||||||
y = min_y
|
|
||||||
z = z+1
|
|
||||||
end
|
|
||||||
if z > max_z then
|
|
||||||
z = nil
|
|
||||||
end
|
|
||||||
return self._nodes[y] and self._nodes[y][x] and self._nodes[y][x][z] or self:getNodeAt(x,y,z)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Grid iterator. Iterates on each node along the outline (border) of a squared area
|
|
||||||
-- centered on the given node.
|
|
||||||
-- @tparam node node a given `node`
|
|
||||||
-- @tparam[opt] int radius the area radius (half-length). Defaults to __1__ when not given.
|
|
||||||
-- @treturn node a `node` at each iteration step
|
|
||||||
-- @usage
|
|
||||||
-- for node in myGrid:around(node, 2) do
|
|
||||||
-- ...
|
|
||||||
-- end
|
|
||||||
function Grid:around(node, radius)
|
|
||||||
local x, y, z = node._x, node._y, node._z
|
|
||||||
radius = radius or 1
|
|
||||||
local _around = Utils.around()
|
|
||||||
local _nodes = {}
|
|
||||||
repeat
|
|
||||||
local state, x, y, z = coroutine.resume(_around,x,y,z,radius)
|
|
||||||
local nodeAt = state and self:getNodeAt(x, y, z)
|
|
||||||
if nodeAt then _nodes[#_nodes+1] = nodeAt end
|
|
||||||
until (not state)
|
|
||||||
local _i = 0
|
|
||||||
return function()
|
|
||||||
_i = _i+1
|
|
||||||
return _nodes[_i]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Each transformation. Calls the given function on each `node` in the `grid`,
|
|
||||||
-- passing the `node` as the first argument to function __f__.
|
|
||||||
-- @class function
|
|
||||||
-- @tparam func f a function prototyped as __f(node,...)__
|
|
||||||
-- @tparam[opt] vararg ... args to be passed to function __f__
|
|
||||||
-- @treturn grid self (the calling `grid` itself, can be chained)
|
|
||||||
-- @usage
|
|
||||||
-- local function printNode(node)
|
|
||||||
-- print(node:getX(), node:getY())
|
|
||||||
-- end
|
|
||||||
-- myGrid:each(printNode)
|
|
||||||
function Grid:each(f,...)
|
|
||||||
for node in self:iter() do f(node,...) end
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Each (in range) transformation. Calls a function on each `node` in the range of a rectangle of cells,
|
|
||||||
-- passing the `node` as the first argument to function __f__.
|
|
||||||
-- @class function
|
|
||||||
-- @tparam int lx the leftmost x-coordinate coordinate of the rectangle
|
|
||||||
-- @tparam int ly the topmost y-coordinate of the rectangle
|
|
||||||
-- @tparam int ex the rightmost x-coordinate of the rectangle
|
|
||||||
-- @tparam int ey the bottom-most y-coordinate of the rectangle
|
|
||||||
-- @tparam func f a function prototyped as __f(node,...)__
|
|
||||||
-- @tparam[opt] vararg ... args to be passed to function __f__
|
|
||||||
-- @treturn grid self (the calling `grid` itself, can be chained)
|
|
||||||
-- @usage
|
|
||||||
-- local function printNode(node)
|
|
||||||
-- print(node:getX(), node:getY())
|
|
||||||
-- end
|
|
||||||
-- myGrid:eachRange(1,1,8,8,printNode)
|
|
||||||
function Grid:eachRange(lx,ly,ex,ey,f,...)
|
|
||||||
for node in self:iter(lx,ly,ex,ey) do f(node,...) end
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Map transformation.
|
|
||||||
-- Calls function __f(node,...)__ on each `node` in a given range, passing the `node` as the first arg to function __f__ and replaces
|
|
||||||
-- it with the returned value. Therefore, the function should return a `node`.
|
|
||||||
-- @class function
|
|
||||||
-- @tparam func f a function prototyped as __f(node,...)__
|
|
||||||
-- @tparam[opt] vararg ... args to be passed to function __f__
|
|
||||||
-- @treturn grid self (the calling `grid` itself, can be chained)
|
|
||||||
-- @usage
|
|
||||||
-- local function nothing(node)
|
|
||||||
-- return node
|
|
||||||
-- end
|
|
||||||
-- myGrid:imap(nothing)
|
|
||||||
function Grid:imap(f,...)
|
|
||||||
for node in self:iter() do
|
|
||||||
node = f(node,...)
|
|
||||||
end
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Map in range transformation.
|
|
||||||
-- Calls function __f(node,...)__ on each `node` in a rectangle range, passing the `node` as the first argument to the function and replaces
|
|
||||||
-- it with the returned value. Therefore, the function should return a `node`.
|
|
||||||
-- @class function
|
|
||||||
-- @tparam int lx the leftmost x-coordinate coordinate of the rectangle
|
|
||||||
-- @tparam int ly the topmost y-coordinate of the rectangle
|
|
||||||
-- @tparam int ex the rightmost x-coordinate of the rectangle
|
|
||||||
-- @tparam int ey the bottom-most y-coordinate of the rectangle
|
|
||||||
-- @tparam func f a function prototyped as __f(node,...)__
|
|
||||||
-- @tparam[opt] vararg ... args to be passed to function __f__
|
|
||||||
-- @treturn grid self (the calling `grid` itself, can be chained)
|
|
||||||
-- @usage
|
|
||||||
-- local function nothing(node)
|
|
||||||
-- return node
|
|
||||||
-- end
|
|
||||||
-- myGrid:imap(1,1,6,6,nothing)
|
|
||||||
function Grid:imapRange(lx,ly,ex,ey,f,...)
|
|
||||||
for node in self:iter(lx,ly,ex,ey) do
|
|
||||||
node = f(node,...)
|
|
||||||
end
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Specialized grids
|
|
||||||
-- Inits a preprocessed grid
|
|
||||||
function PreProcessGrid:new(map)
|
|
||||||
local newGrid = {}
|
|
||||||
newGrid._map = map
|
|
||||||
newGrid._nodes, newGrid._min_x, newGrid._max_x, newGrid._min_y, newGrid._max_y, newGrid._min_z, newGrid._max_z = Utils.arrayToNodes(newGrid._map)
|
|
||||||
newGrid._width = (newGrid._max_x-newGrid._min_x)+1
|
|
||||||
newGrid._height = (newGrid._max_y-newGrid._min_y)+1
|
|
||||||
newGrid._length = (newGrid._max_z-newGrid._min_z)+1
|
|
||||||
newGrid._isAnnotated = {}
|
|
||||||
return setmetatable(newGrid,PreProcessGrid)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Inits a postprocessed grid
|
|
||||||
function PostProcessGrid:new(map)
|
|
||||||
local newGrid = {}
|
|
||||||
newGrid._map = map
|
|
||||||
newGrid._nodes = {}
|
|
||||||
newGrid._min_x, newGrid._max_x, newGrid._min_y, newGrid._max_y = Utils.getArrayBounds(newGrid._map)
|
|
||||||
newGrid._width = (newGrid._max_x-newGrid._min_x)+1
|
|
||||||
newGrid._height = (newGrid._max_y-newGrid._min_y)+1
|
|
||||||
newGrid._isAnnotated = {}
|
|
||||||
return setmetatable(newGrid,PostProcessGrid)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Returns the `node` at location [x,y].
|
|
||||||
-- @class function
|
|
||||||
-- @name Grid:getNodeAt
|
|
||||||
-- @tparam int x the x-coordinate coordinate
|
|
||||||
-- @tparam int y the y-coordinate coordinate
|
|
||||||
-- @treturn node a `node`
|
|
||||||
-- @usage local aNode = myGrid:getNodeAt(2,2)
|
|
||||||
|
|
||||||
-- Gets the node at location <x,y> on a preprocessed grid
|
|
||||||
function PreProcessGrid:getNodeAt(x,y,z)
|
|
||||||
return self._nodes[y] and self._nodes[y][x] and self._nodes[y][x][z] or nil
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Gets the node at location <x,y> on a postprocessed grid
|
|
||||||
function PostProcessGrid:getNodeAt(x,y,z)
|
|
||||||
if not x or not y or not z then return end
|
if not x or not y or not z then return end
|
||||||
if Utils.outOfRange(x,self._min_x,self._max_x) then return end
|
if Utils.outOfRange(x,self._min_x,self._max_x) then return end
|
||||||
if Utils.outOfRange(y,self._min_y,self._max_y) then return end
|
if Utils.outOfRange(y,self._min_y,self._max_y) then return end
|
||||||
if Utils.outOfRange(z,self._min_z,self._max_z) then return end
|
if Utils.outOfRange(z,self._min_z,self._max_z) then return end
|
||||||
|
|
||||||
|
-- inefficient
|
||||||
if not self._nodes[y] then self._nodes[y] = {} end
|
if not self._nodes[y] then self._nodes[y] = {} end
|
||||||
if not self._nodes[y][x] then self._nodes[y][x] = {} end
|
if not self._nodes[y][x] then self._nodes[y][x] = {} end
|
||||||
if not self._nodes[y][x][z] then self._nodes[y][x][z] = Node:new(x,y,z) end
|
if not self._nodes[y][x][z] then self._nodes[y][x][z] = Node:new(x,y,z) end
|
||||||
|
|||||||
@@ -28,11 +28,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
--]]
|
--]]
|
||||||
|
|
||||||
--- The Pathfinder class
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Implementation of the `pathfinder` class.
|
|
||||||
|
|
||||||
local _VERSION = ""
|
local _VERSION = ""
|
||||||
local _RELEASEDATE = ""
|
local _RELEASEDATE = ""
|
||||||
|
|
||||||
@@ -41,333 +36,46 @@ if (...) then
|
|||||||
-- Dependencies
|
-- Dependencies
|
||||||
local _PATH = (...):gsub('%.pathfinder$','')
|
local _PATH = (...):gsub('%.pathfinder$','')
|
||||||
local Utils = require (_PATH .. '.core.utils')
|
local Utils = require (_PATH .. '.core.utils')
|
||||||
local Assert = require (_PATH .. '.core.assert')
|
|
||||||
local Heap = require (_PATH .. '.core.bheap')
|
|
||||||
local Heuristic = require (_PATH .. '.core.heuristics')
|
|
||||||
local Grid = require (_PATH .. '.grid')
|
|
||||||
local Path = require (_PATH .. '.core.path')
|
|
||||||
|
|
||||||
-- Internalization
|
-- Internalization
|
||||||
local t_insert, t_remove = table.insert, table.remove
|
|
||||||
local floor = math.floor
|
|
||||||
local pairs = pairs
|
local pairs = pairs
|
||||||
local assert = assert
|
local assert = assert
|
||||||
local type = type
|
local setmetatable = setmetatable
|
||||||
local setmetatable, getmetatable = setmetatable, getmetatable
|
|
||||||
|
|
||||||
--- Finders (search algorithms implemented). Refers to the search algorithms actually implemented in Jumper.
|
--- Finders (search algorithms implemented). Refers to the search algorithms actually implemented in Jumper.
|
||||||
--
|
|
||||||
-- <li>[A*](http://en.wikipedia.org/wiki/A*_search_algorithm)</li>
|
-- <li>[A*](http://en.wikipedia.org/wiki/A*_search_algorithm)</li>
|
||||||
-- <li>[Dijkstra](http://en.wikipedia.org/wiki/Dijkstra%27s_algorithm)</li>
|
|
||||||
-- <li>[Theta Astar](http://aigamedev.com/open/tutorials/theta-star-any-angle-paths/)</li>
|
|
||||||
-- <li>[BFS](http://en.wikipedia.org/wiki/Breadth-first_search)</li>
|
|
||||||
-- <li>[DFS](http://en.wikipedia.org/wiki/Depth-first_search)</li>
|
|
||||||
-- <li>[JPS](http://harablog.wordpress.com/2011/09/07/jump-point-search/)</li>
|
|
||||||
-- @finder Finders
|
|
||||||
-- @see Pathfinder:getFinders
|
|
||||||
local Finders = {
|
local Finders = {
|
||||||
['ASTAR'] = require (_PATH .. '.search.astar'),
|
['ASTAR'] = require (_PATH .. '.search.astar'),
|
||||||
-- ['DIJKSTRA'] = require (_PATH .. '.search.dijkstra'),
|
|
||||||
-- ['THETASTAR'] = require (_PATH .. '.search.thetastar'),
|
|
||||||
['BFS'] = require (_PATH .. '.search.bfs'),
|
|
||||||
-- ['DFS'] = require (_PATH .. '.search.dfs'),
|
|
||||||
-- ['JPS'] = require (_PATH .. '.search.jps')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Will keep track of all nodes expanded during the search
|
-- Will keep track of all nodes expanded during the search
|
||||||
-- to easily reset their properties for the next pathfinding call
|
-- to easily reset their properties for the next pathfinding call
|
||||||
local toClear = {}
|
local toClear = {}
|
||||||
|
|
||||||
--- Search modes. Refers to the search modes. In ORTHOGONAL mode, 4-directions are only possible when moving,
|
|
||||||
-- including North, East, West, South. In DIAGONAL mode, 8-directions are possible when moving,
|
|
||||||
-- including North, East, West, South and adjacent directions.
|
|
||||||
--
|
|
||||||
-- <li>ORTHOGONAL</li>
|
|
||||||
-- <li>DIAGONAL</li>
|
|
||||||
-- @mode Modes
|
|
||||||
-- @see Pathfinder:getModes
|
|
||||||
local searchModes = {['DIAGONAL'] = true, ['ORTHOGONAL'] = true}
|
|
||||||
|
|
||||||
-- Performs a traceback from the goal node to the start node
|
-- Performs a traceback from the goal node to the start node
|
||||||
-- Only happens when the path was found
|
-- Only happens when the path was found
|
||||||
|
|
||||||
--- The `Pathfinder` class.<br/>
|
|
||||||
-- This class is callable.
|
|
||||||
-- Therefore,_ <code>Pathfinder(...)</code> _acts as a shortcut to_ <code>Pathfinder:new(...)</code>.
|
|
||||||
-- @type Pathfinder
|
|
||||||
local Pathfinder = {}
|
local Pathfinder = {}
|
||||||
Pathfinder.__index = Pathfinder
|
Pathfinder.__index = Pathfinder
|
||||||
|
|
||||||
--- Inits a new `pathfinder`
|
function Pathfinder:new(heuristic)
|
||||||
-- @class function
|
|
||||||
-- @tparam grid grid a `grid`
|
|
||||||
-- @tparam[opt] string finderName the name of the `Finder` (search algorithm) to be used for search.
|
|
||||||
-- Defaults to `ASTAR` when not given (see @{Pathfinder:getFinders}).
|
|
||||||
-- @tparam[optchain] string|int|func walkable the value for __walkable__ nodes.
|
|
||||||
-- If this parameter is a function, it should be prototyped as __f(value)__, returning a boolean:
|
|
||||||
-- __true__ when value matches a __walkable__ `node`, __false__ otherwise.
|
|
||||||
-- @treturn pathfinder a new `pathfinder` instance
|
|
||||||
-- @usage
|
|
||||||
-- -- Example one
|
|
||||||
-- local finder = Pathfinder:new(myGrid, 'ASTAR', 0)
|
|
||||||
--
|
|
||||||
-- -- Example two
|
|
||||||
-- local function walkable(value)
|
|
||||||
-- return value > 0
|
|
||||||
-- end
|
|
||||||
-- local finder = Pathfinder(myGrid, 'JPS', walkable)
|
|
||||||
function Pathfinder:new(grid, finderName, walkable)
|
|
||||||
local newPathfinder = {}
|
local newPathfinder = {}
|
||||||
setmetatable(newPathfinder, Pathfinder)
|
setmetatable(newPathfinder, Pathfinder)
|
||||||
--newPathfinder:setGrid(grid)
|
self._finder = Finders.ASTAR
|
||||||
newPathfinder:setFinder(finderName)
|
self._heuristic = heuristic
|
||||||
--newPathfinder:setWalkable(walkable)
|
|
||||||
newPathfinder:setMode('DIAGONAL')
|
|
||||||
newPathfinder:setHeuristic('MANHATTAN')
|
|
||||||
newPathfinder:setTunnelling(false)
|
|
||||||
return newPathfinder
|
return newPathfinder
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Evaluates [clearance](http://aigamedev.com/open/tutorial/clearance-based-pathfinding/#TheTrueClearanceMetric)
|
|
||||||
-- for the whole `grid`. It should be called only once, unless the collision map or the
|
|
||||||
-- __walkable__ attribute changes. The clearance values are calculated and cached within the grid nodes.
|
|
||||||
-- @class function
|
|
||||||
-- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)
|
|
||||||
-- @usage myFinder:annotateGrid()
|
|
||||||
function Pathfinder:annotateGrid()
|
|
||||||
assert(self._walkable, 'Finder must implement a walkable value')
|
|
||||||
for x=self._grid._max_x,self._grid._min_x,-1 do
|
|
||||||
for y=self._grid._max_y,self._grid._min_y,-1 do
|
|
||||||
local node = self._grid:getNodeAt(x,y)
|
|
||||||
if self._grid:isWalkableAt(x,y,self._walkable) then
|
|
||||||
local nr = self._grid:getNodeAt(node._x+1, node._y)
|
|
||||||
local nrd = self._grid:getNodeAt(node._x+1, node._y+1)
|
|
||||||
local nd = self._grid:getNodeAt(node._x, node._y+1)
|
|
||||||
if nr and nrd and nd then
|
|
||||||
local m = nrd._clearance[self._walkable] or 0
|
|
||||||
m = (nd._clearance[self._walkable] or 0)<m and (nd._clearance[self._walkable] or 0) or m
|
|
||||||
m = (nr._clearance[self._walkable] or 0)<m and (nr._clearance[self._walkable] or 0) or m
|
|
||||||
node._clearance[self._walkable] = m+1
|
|
||||||
else
|
|
||||||
node._clearance[self._walkable] = 1
|
|
||||||
end
|
|
||||||
else node._clearance[self._walkable] = 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
self._grid._isAnnotated[self._walkable] = true
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Removes [clearance](http://aigamedev.com/open/tutorial/clearance-based-pathfinding/#TheTrueClearanceMetric)values.
|
|
||||||
-- Clears cached clearance values for the current __walkable__.
|
|
||||||
-- @class function
|
|
||||||
-- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)
|
|
||||||
-- @usage myFinder:clearAnnotations()
|
|
||||||
function Pathfinder:clearAnnotations()
|
|
||||||
assert(self._walkable, 'Finder must implement a walkable value')
|
|
||||||
for node in self._grid:iter() do
|
|
||||||
node:removeClearance(self._walkable)
|
|
||||||
end
|
|
||||||
self._grid._isAnnotated[self._walkable] = false
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Sets the `grid`. Defines the given `grid` as the one on which the `pathfinder` will perform the search.
|
|
||||||
-- @class function
|
|
||||||
-- @tparam grid grid a `grid`
|
|
||||||
-- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)
|
|
||||||
-- @usage myFinder:setGrid(myGrid)
|
|
||||||
function Pathfinder:setGrid(grid)
|
function Pathfinder:setGrid(grid)
|
||||||
assert(Assert.inherits(grid, Grid), 'Wrong argument #1. Expected a \'grid\' object')
|
|
||||||
self._grid = grid
|
self._grid = grid
|
||||||
self._grid._eval = self._walkable and type(self._walkable) == 'function'
|
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Returns the `grid`. This is a reference to the actual `grid` used by the `pathfinder`.
|
--- Calculates a `path`. Returns the `path` from start to end location
|
||||||
-- @class function
|
|
||||||
-- @treturn grid the `grid`
|
|
||||||
-- @usage local myGrid = myFinder:getGrid()
|
|
||||||
function Pathfinder:getGrid()
|
|
||||||
return self._grid
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Sets the __walkable__ value or function.
|
|
||||||
-- @class function
|
|
||||||
-- @tparam string|int|func walkable the value for walkable nodes.
|
|
||||||
-- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)
|
|
||||||
-- @usage
|
|
||||||
-- -- Value '0' is walkable
|
|
||||||
-- myFinder:setWalkable(0)
|
|
||||||
--
|
|
||||||
-- -- Any value greater than 0 is walkable
|
|
||||||
-- myFinder:setWalkable(function(n)
|
|
||||||
-- return n>0
|
|
||||||
-- end
|
|
||||||
function Pathfinder:setWalkable(walkable)
|
|
||||||
assert(Assert.matchType(walkable,'stringintfunctionnil'),
|
|
||||||
('Wrong argument #1. Expected \'string\', \'number\' or \'function\', got %s.'):format(type(walkable)))
|
|
||||||
self._walkable = walkable
|
|
||||||
self._grid._eval = type(self._walkable) == 'function'
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Gets the __walkable__ value or function.
|
|
||||||
-- @class function
|
|
||||||
-- @treturn string|int|func the `walkable` value or function
|
|
||||||
-- @usage local walkable = myFinder:getWalkable()
|
|
||||||
function Pathfinder:getWalkable()
|
|
||||||
return self._walkable
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Defines the `finder`. It refers to the search algorithm used by the `pathfinder`.
|
|
||||||
-- Default finder is `ASTAR`. Use @{Pathfinder:getFinders} to get the list of available finders.
|
|
||||||
-- @class function
|
|
||||||
-- @tparam string finderName the name of the `finder` to be used for further searches.
|
|
||||||
-- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)
|
|
||||||
-- @usage
|
|
||||||
-- --To use Breadth-First-Search
|
|
||||||
-- myFinder:setFinder('BFS')
|
|
||||||
-- @see Pathfinder:getFinders
|
|
||||||
function Pathfinder:setFinder(finderName)
|
|
||||||
if not finderName then
|
|
||||||
if not self._finder then
|
|
||||||
finderName = 'ASTAR'
|
|
||||||
else return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
assert(Finders[finderName],'Not a valid finder name!')
|
|
||||||
self._finder = finderName
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Returns the name of the `finder` being used.
|
|
||||||
-- @class function
|
|
||||||
-- @treturn string the name of the `finder` to be used for further searches.
|
|
||||||
-- @usage local finderName = myFinder:getFinder()
|
|
||||||
function Pathfinder:getFinder()
|
|
||||||
return self._finder
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Returns the list of all available finders names.
|
|
||||||
-- @class function
|
|
||||||
-- @treturn {string,...} array of built-in finders names.
|
|
||||||
-- @usage
|
|
||||||
-- local finders = myFinder:getFinders()
|
|
||||||
-- for i, finderName in ipairs(finders) do
|
|
||||||
-- print(i, finderName)
|
|
||||||
-- end
|
|
||||||
function Pathfinder:getFinders()
|
|
||||||
return Utils.getKeys(Finders)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Sets a heuristic. This is a function internally used by the `pathfinder` to find the optimal path during a search.
|
|
||||||
-- Use @{Pathfinder:getHeuristics} to get the list of all available `heuristics`. One can also define
|
|
||||||
-- his own `heuristic` function.
|
|
||||||
-- @class function
|
|
||||||
-- @tparam func|string heuristic `heuristic` function, prototyped as __f(dx,dy)__ or as a `string`.
|
|
||||||
-- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)
|
|
||||||
-- @see Pathfinder:getHeuristics
|
|
||||||
-- @see core.heuristics
|
|
||||||
-- @usage myFinder:setHeuristic('MANHATTAN')
|
|
||||||
function Pathfinder:setHeuristic(heuristic)
|
|
||||||
assert(Heuristic[heuristic] or (type(heuristic) == 'function'),'Not a valid heuristic!')
|
|
||||||
self._heuristic = Heuristic[heuristic] or heuristic
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Returns the `heuristic` used. Returns the function itself.
|
|
||||||
-- @class function
|
|
||||||
-- @treturn func the `heuristic` function being used by the `pathfinder`
|
|
||||||
-- @see core.heuristics
|
|
||||||
-- @usage local h = myFinder:getHeuristic()
|
|
||||||
function Pathfinder:getHeuristic()
|
|
||||||
return self._heuristic
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Gets the list of all available `heuristics`.
|
|
||||||
-- @class function
|
|
||||||
-- @treturn {string,...} array of heuristic names.
|
|
||||||
-- @see core.heuristics
|
|
||||||
-- @usage
|
|
||||||
-- local heur = myFinder:getHeuristic()
|
|
||||||
-- for i, heuristicName in ipairs(heur) do
|
|
||||||
-- ...
|
|
||||||
-- end
|
|
||||||
function Pathfinder:getHeuristics()
|
|
||||||
return Utils.getKeys(Heuristic)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Defines the search `mode`.
|
|
||||||
-- The default search mode is the `DIAGONAL` mode, which implies 8-possible directions when moving (north, south, east, west and diagonals).
|
|
||||||
-- In `ORTHOGONAL` mode, only 4-directions are allowed (north, south, east and west).
|
|
||||||
-- Use @{Pathfinder:getModes} to get the list of all available search modes.
|
|
||||||
-- @class function
|
|
||||||
-- @tparam string mode the new search `mode`.
|
|
||||||
-- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)
|
|
||||||
-- @see Pathfinder:getModes
|
|
||||||
-- @see Modes
|
|
||||||
-- @usage myFinder:setMode('ORTHOGONAL')
|
|
||||||
function Pathfinder:setMode(mode)
|
|
||||||
assert(searchModes[mode],'Invalid mode')
|
|
||||||
self._allowDiagonal = (mode == 'DIAGONAL')
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Returns the search mode.
|
|
||||||
-- @class function
|
|
||||||
-- @treturn string the current search mode
|
|
||||||
-- @see Modes
|
|
||||||
-- @usage local mode = myFinder:getMode()
|
|
||||||
function Pathfinder:getMode()
|
|
||||||
return (self._allowDiagonal and 'DIAGONAL' or 'ORTHOGONAL')
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Gets the list of all available search modes.
|
|
||||||
-- @class function
|
|
||||||
-- @treturn {string,...} array of search modes.
|
|
||||||
-- @see Modes
|
|
||||||
-- @usage local modes = myFinder:getModes()
|
|
||||||
-- for modeName in ipairs(modes) do
|
|
||||||
-- ...
|
|
||||||
-- end
|
|
||||||
function Pathfinder:getModes()
|
|
||||||
return Utils.getKeys(searchModes)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Enables tunnelling. Defines the ability for the `pathfinder` to tunnel through walls when heading diagonally.
|
|
||||||
-- This feature __is not compatible__ with Jump Point Search algorithm (i.e. enabling it will not affect Jump Point Search)
|
|
||||||
-- @class function
|
|
||||||
-- @tparam bool bool a boolean
|
|
||||||
-- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)
|
|
||||||
-- @usage myFinder:setTunnelling(true)
|
|
||||||
function Pathfinder:setTunnelling(bool)
|
|
||||||
assert(Assert.isBool(bool), ('Wrong argument #1. Expected boolean, got %s'):format(type(bool)))
|
|
||||||
self._tunnel = bool
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Returns tunnelling feature state.
|
|
||||||
-- @class function
|
|
||||||
-- @treturn bool tunnelling feature actual state
|
|
||||||
-- @usage local isTunnellingEnabled = myFinder:getTunnelling()
|
|
||||||
function Pathfinder:getTunnelling()
|
|
||||||
return self._tunnel
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Calculates a `path`. Returns the `path` from location __[startX, startY]__ to location __[endX, endY]__.
|
|
||||||
-- Both locations must exist on the collision map. The starting location can be unwalkable.
|
-- Both locations must exist on the collision map. The starting location can be unwalkable.
|
||||||
-- @class function
|
|
||||||
-- @tparam int startX the x-coordinate for the starting location
|
|
||||||
-- @tparam int startY the y-coordinate for the starting location
|
|
||||||
-- @tparam int endX the x-coordinate for the goal location
|
|
||||||
-- @tparam int endY the y-coordinate for the goal location
|
|
||||||
-- @tparam int clearance the amount of clearance (i.e the pathing agent size) to consider
|
|
||||||
-- @treturn path a path (array of nodes) when found, otherwise nil
|
-- @treturn path a path (array of nodes) when found, otherwise nil
|
||||||
-- @usage local path = myFinder:getPath(1,1,5,5)
|
-- @usage local path = myFinder:getPath(1,1,5,5)
|
||||||
function Pathfinder:getPath(startX, startY, startZ, ih, endX, endY, endZ, oh, clearance)
|
function Pathfinder:getPath(startX, startY, startZ, ih, endX, endY, endZ, oh)
|
||||||
|
|
||||||
self:reset()
|
self:reset()
|
||||||
local startNode = self._grid:getNodeAt(startX, startY, startZ)
|
local startNode = self._grid:getNodeAt(startX, startY, startZ)
|
||||||
local endNode = self._grid:getNodeAt(endX, endY, endZ)
|
local endNode = self._grid:getNodeAt(endX, endY, endZ)
|
||||||
@@ -375,20 +83,21 @@ if (...) then
|
|||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
startNode._heading = ih
|
startNode.heading = ih
|
||||||
endNode._heading = oh
|
endNode.heading = oh
|
||||||
|
|
||||||
assert(startNode, ('Invalid location [%d, %d, %d]'):format(startX, startY, startZ))
|
assert(startNode, ('Invalid location [%d, %d, %d]'):format(startX, startY, startZ))
|
||||||
assert(endNode and self._grid:isWalkableAt(endX, endY, endZ),
|
assert(endNode and self._grid:isWalkableAt(endX, endY, endZ),
|
||||||
('Invalid or unreachable location [%d, %d, %d]'):format(endX, endY, endZ))
|
('Invalid or unreachable location [%d, %d, %d]'):format(endX, endY, endZ))
|
||||||
local _endNode = Finders[self._finder](self, startNode, endNode, clearance, toClear)
|
local _endNode = self._finder(self, startNode, endNode, toClear)
|
||||||
if _endNode then
|
if _endNode then
|
||||||
return Utils.traceBackPath(self, _endNode, startNode)
|
return Utils.traceBackPath(self, _endNode, startNode)
|
||||||
end
|
end
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Resets the `pathfinder`. This function is called internally between successive pathfinding calls, so you should not
|
--- Resets the `pathfinder`. This function is called internally between
|
||||||
|
-- successive pathfinding calls, so you should not
|
||||||
-- use it explicitely, unless under specific circumstances.
|
-- use it explicitely, unless under specific circumstances.
|
||||||
-- @class function
|
-- @class function
|
||||||
-- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)
|
-- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)
|
||||||
@@ -399,7 +108,6 @@ if (...) then
|
|||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
-- Returns Pathfinder class
|
-- Returns Pathfinder class
|
||||||
Pathfinder._VERSION = _VERSION
|
Pathfinder._VERSION = _VERSION
|
||||||
Pathfinder._RELEASEDATE = _RELEASEDATE
|
Pathfinder._RELEASEDATE = _RELEASEDATE
|
||||||
@@ -408,5 +116,4 @@ if (...) then
|
|||||||
return self:new(...)
|
return self:new(...)
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,51 +5,42 @@
|
|||||||
if (...) then
|
if (...) then
|
||||||
|
|
||||||
-- Internalization
|
-- Internalization
|
||||||
local ipairs = ipairs
|
|
||||||
local huge = math.huge
|
local huge = math.huge
|
||||||
|
|
||||||
-- Dependancies
|
-- Dependancies
|
||||||
local _PATH = (...):match('(.+)%.search.astar$')
|
local _PATH = (...):match('(.+)%.search.astar$')
|
||||||
local Heuristics = require (_PATH .. '.core.heuristics')
|
|
||||||
local Heap = require (_PATH.. '.core.bheap')
|
local Heap = require (_PATH.. '.core.bheap')
|
||||||
|
|
||||||
-- Updates G-cost
|
-- Updates G-cost
|
||||||
local function computeCost(node, neighbour, finder, clearance, heuristic)
|
local function computeCost(node, neighbour, heuristic)
|
||||||
local mCost, heading = heuristic(neighbour, node) -- Heuristics.EUCLIDIAN(neighbour, node)
|
local mCost, heading = heuristic(neighbour, node) -- Heuristics.EUCLIDIAN(neighbour, node)
|
||||||
|
|
||||||
if node._g + mCost < neighbour._g then
|
if node._g + mCost < neighbour._g then
|
||||||
neighbour._parent = node
|
neighbour._parent = node
|
||||||
neighbour._g = node._g + mCost
|
neighbour._g = node._g + mCost
|
||||||
neighbour._heading = heading
|
neighbour.heading = heading
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Updates vertex node-neighbour
|
-- Updates vertex node-neighbour
|
||||||
local function updateVertex(finder, openList, node, neighbour, endNode, clearance, heuristic, overrideCostEval)
|
local function updateVertex(openList, node, neighbour, endNode, heuristic)
|
||||||
local oldG = neighbour._g
|
local oldG = neighbour._g
|
||||||
local cmpCost = overrideCostEval or computeCost
|
computeCost(node, neighbour, heuristic)
|
||||||
cmpCost(node, neighbour, finder, clearance, heuristic)
|
|
||||||
if neighbour._g < oldG then
|
if neighbour._g < oldG then
|
||||||
local nClearance = neighbour._clearance[finder._walkable]
|
if neighbour._opened then neighbour._opened = false end
|
||||||
local pushThisNode = clearance and nClearance and (nClearance >= clearance)
|
neighbour._h = heuristic(endNode, neighbour)
|
||||||
if (clearance and pushThisNode) or (not clearance) then
|
neighbour._f = neighbour._g + neighbour._h
|
||||||
if neighbour._opened then neighbour._opened = false end
|
openList:push(neighbour)
|
||||||
neighbour._h = heuristic(endNode, neighbour)
|
neighbour._opened = true
|
||||||
neighbour._f = neighbour._g + neighbour._h
|
|
||||||
openList:push(neighbour)
|
|
||||||
neighbour._opened = true
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Calculates a path.
|
-- Calculates a path.
|
||||||
-- Returns the path from location `<startX, startY>` to location `<endX, endY>`.
|
-- Returns the path from location `<startX, startY>` to location `<endX, endY>`.
|
||||||
return function (finder, startNode, endNode, clearance, toClear, overrideHeuristic, overrideCostEval)
|
return function (finder, startNode, endNode, toClear)
|
||||||
|
|
||||||
local heuristic = overrideHeuristic or finder._heuristic
|
|
||||||
local openList = Heap()
|
local openList = Heap()
|
||||||
startNode._g = 0
|
startNode._g = 0
|
||||||
startNode._h = heuristic(endNode, startNode)
|
startNode._h = finder._heuristic(endNode, startNode)
|
||||||
startNode._f = startNode._g + startNode._h
|
startNode._f = startNode._g + startNode._h
|
||||||
openList:push(startNode)
|
openList:push(startNode)
|
||||||
toClear[startNode] = true
|
toClear[startNode] = true
|
||||||
@@ -59,7 +50,7 @@ if (...) then
|
|||||||
local node = openList:pop()
|
local node = openList:pop()
|
||||||
node._closed = true
|
node._closed = true
|
||||||
if node == endNode then return node end
|
if node == endNode then return node end
|
||||||
local neighbours = finder._grid:getNeighbours(node, finder._walkable, finder._allowDiagonal, finder._tunnel)
|
local neighbours = finder._grid:getNeighbours(node)
|
||||||
for i = 1,#neighbours do
|
for i = 1,#neighbours do
|
||||||
local neighbour = neighbours[i]
|
local neighbour = neighbours[i]
|
||||||
if not neighbour._closed then
|
if not neighbour._closed then
|
||||||
@@ -68,21 +59,19 @@ if (...) then
|
|||||||
neighbour._g = huge
|
neighbour._g = huge
|
||||||
neighbour._parent = nil
|
neighbour._parent = nil
|
||||||
end
|
end
|
||||||
updateVertex(finder, openList, node, neighbour, endNode, clearance, heuristic, overrideCostEval)
|
updateVertex(openList, node, neighbour, endNode, finder._heuristic)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
printf('x:%d y:%d z:%d g:%d', node._x, node._y, node._z, node._g)
|
printf('x:%d y:%d z:%d g:%d', node.x, node.y, node.z, node._g)
|
||||||
for i = 1,#neighbours do
|
for i = 1,#neighbours do
|
||||||
local n = neighbours[i]
|
local n = neighbours[i]
|
||||||
printf('x:%d y:%d z:%d f:%f g:%f h:%d', n._x, n._y, n._z, n._f, n._g, n._heading or -1)
|
printf('x:%d y:%d z:%d f:%f g:%f h:%d', n.x, n.y, n.z, n._f, n._g, n.heading or -1)
|
||||||
end
|
end
|
||||||
--]]
|
--]]
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
-- Breadth-First search algorithm
|
|
||||||
|
|
||||||
if (...) then
|
|
||||||
-- Internalization
|
|
||||||
local t_remove = table.remove
|
|
||||||
|
|
||||||
local function breadth_first_search(finder, openList, node, endNode, clearance, toClear)
|
|
||||||
local neighbours = finder._grid:getNeighbours(node, finder._walkable, finder._allowDiagonal, finder._tunnel)
|
|
||||||
for i = 1,#neighbours do
|
|
||||||
local neighbour = neighbours[i]
|
|
||||||
if not neighbour._closed and not neighbour._opened then
|
|
||||||
local nClearance = neighbour._clearance[finder._walkable]
|
|
||||||
local pushThisNode = clearance and nClearance and (nClearance >= clearance)
|
|
||||||
if (clearance and pushThisNode) or (not clearance) then
|
|
||||||
openList[#openList+1] = neighbour
|
|
||||||
neighbour._opened = true
|
|
||||||
neighbour._parent = node
|
|
||||||
toClear[neighbour] = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Calculates a path.
|
|
||||||
-- Returns the path from location `<startX, startY>` to location `<endX, endY>`.
|
|
||||||
return function (finder, startNode, endNode, clearance, toClear)
|
|
||||||
|
|
||||||
local openList = {} -- We'll use a FIFO queue (simple array)
|
|
||||||
openList[1] = startNode
|
|
||||||
startNode._opened = true
|
|
||||||
toClear[startNode] = true
|
|
||||||
|
|
||||||
local node
|
|
||||||
while (#openList > 0) do
|
|
||||||
node = openList[1]
|
|
||||||
t_remove(openList,1)
|
|
||||||
node._closed = true
|
|
||||||
if node == endNode then return node end
|
|
||||||
breadth_first_search(finder, openList, node, endNode, clearance, toClear)
|
|
||||||
end
|
|
||||||
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
@@ -21,8 +21,12 @@ function NFT.parse(imageText)
|
|||||||
}
|
}
|
||||||
|
|
||||||
local num = 1
|
local num = 1
|
||||||
local index = 1
|
local lines = Util.split(imageText)
|
||||||
for _,sLine in ipairs(Util.split(imageText)) do
|
while #lines[#lines] == 0 do
|
||||||
|
table.remove(lines, #lines)
|
||||||
|
end
|
||||||
|
|
||||||
|
for _,sLine in ipairs(lines) do
|
||||||
table.insert(image.fg, { })
|
table.insert(image.fg, { })
|
||||||
table.insert(image.bg, { })
|
table.insert(image.bg, { })
|
||||||
table.insert(image.text, { })
|
table.insert(image.text, { })
|
||||||
@@ -47,7 +51,7 @@ function NFT.parse(imageText)
|
|||||||
fgNext = false
|
fgNext = false
|
||||||
else
|
else
|
||||||
if nextChar ~= " " and currFG == nil then
|
if nextChar ~= " " and currFG == nil then
|
||||||
currFG = colours.white
|
currFG = _G.colors.white
|
||||||
end
|
end
|
||||||
image.bg[num][writeIndex] = currBG
|
image.bg[num][writeIndex] = currBG
|
||||||
image.fg[num][writeIndex] = currFG
|
image.fg[num][writeIndex] = currFG
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
local Opus = { }
|
|
||||||
|
|
||||||
local function runDir(directory, open)
|
|
||||||
if not fs.exists(directory) then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local success = true
|
|
||||||
local files = fs.list(directory)
|
|
||||||
table.sort(files)
|
|
||||||
|
|
||||||
for _,file in ipairs(files) do
|
|
||||||
os.sleep(0)
|
|
||||||
local result, err = open(directory .. '/' .. file)
|
|
||||||
if result then
|
|
||||||
if term.isColor() then
|
|
||||||
term.setTextColor(colors.green)
|
|
||||||
end
|
|
||||||
term.write('[PASS] ')
|
|
||||||
term.setTextColor(colors.white)
|
|
||||||
term.write(fs.combine(directory, file))
|
|
||||||
else
|
|
||||||
if term.isColor() then
|
|
||||||
term.setTextColor(colors.red)
|
|
||||||
end
|
|
||||||
term.write('[FAIL] ')
|
|
||||||
term.setTextColor(colors.white)
|
|
||||||
term.write(fs.combine(directory, file))
|
|
||||||
if err then
|
|
||||||
printError(err)
|
|
||||||
end
|
|
||||||
success = false
|
|
||||||
end
|
|
||||||
print()
|
|
||||||
end
|
|
||||||
|
|
||||||
return success
|
|
||||||
end
|
|
||||||
|
|
||||||
function Opus.loadServices()
|
|
||||||
return runDir('sys/services', shell.openHiddenTab)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Opus.autorun()
|
|
||||||
local s = runDir('sys/autorun', shell.run)
|
|
||||||
return runDir('usr/autorun', shell.run) and s
|
|
||||||
end
|
|
||||||
|
|
||||||
return Opus
|
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
local Util = require('util')
|
local Event = require('event')
|
||||||
|
local Socket = require('socket')
|
||||||
|
local Util = require('util')
|
||||||
|
|
||||||
local Peripheral = { }
|
local Peripheral = Util.shallowCopy(_G.peripheral)
|
||||||
|
|
||||||
local function getDeviceList()
|
|
||||||
|
|
||||||
|
function Peripheral.getList()
|
||||||
if _G.device then
|
if _G.device then
|
||||||
return _G.device
|
return _G.device
|
||||||
end
|
end
|
||||||
|
|
||||||
local deviceList = { }
|
local deviceList = { }
|
||||||
|
for _,side in pairs(Peripheral.getNames()) do
|
||||||
for _,side in pairs(peripheral.getNames()) do
|
|
||||||
Peripheral.addDevice(deviceList, side)
|
Peripheral.addDevice(deviceList, side)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -19,14 +19,14 @@ end
|
|||||||
|
|
||||||
function Peripheral.addDevice(deviceList, side)
|
function Peripheral.addDevice(deviceList, side)
|
||||||
local name = side
|
local name = side
|
||||||
local ptype = peripheral.getType(side)
|
local ptype = Peripheral.getType(side)
|
||||||
|
|
||||||
if not ptype then
|
if not ptype then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if ptype == 'modem' then
|
if ptype == 'modem' then
|
||||||
if peripheral.call(name, 'isWireless') then
|
if Peripheral.call(name, 'isWireless') then
|
||||||
ptype = 'wireless_modem'
|
ptype = 'wireless_modem'
|
||||||
else
|
else
|
||||||
ptype = 'wired_modem'
|
ptype = 'wired_modem'
|
||||||
@@ -52,10 +52,10 @@ function Peripheral.addDevice(deviceList, side)
|
|||||||
name = uniqueName
|
name = uniqueName
|
||||||
end
|
end
|
||||||
|
|
||||||
local s, m pcall(function() deviceList[name] = peripheral.wrap(side) end)
|
local s, m = pcall(function() deviceList[name] = Peripheral.wrap(side) end)
|
||||||
if not s and m then
|
if not s and m then
|
||||||
printError('wrap failed')
|
_G.printError('wrap failed')
|
||||||
printError(m)
|
_G.printError(m)
|
||||||
end
|
end
|
||||||
|
|
||||||
if deviceList[name] then
|
if deviceList[name] then
|
||||||
@@ -70,15 +70,15 @@ function Peripheral.addDevice(deviceList, side)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Peripheral.getBySide(side)
|
function Peripheral.getBySide(side)
|
||||||
return Util.find(getDeviceList(), 'side', side)
|
return Util.find(Peripheral.getList(), 'side', side)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Peripheral.getByType(typeName)
|
function Peripheral.getByType(typeName)
|
||||||
return Util.find(getDeviceList(), 'type', typeName)
|
return Util.find(Peripheral.getList(), 'type', typeName)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Peripheral.getByMethod(method)
|
function Peripheral.getByMethod(method)
|
||||||
for _,p in pairs(getDeviceList()) do
|
for _,p in pairs(Peripheral.getList()) do
|
||||||
if p[method] then
|
if p[method] then
|
||||||
return p
|
return p
|
||||||
end
|
end
|
||||||
@@ -92,7 +92,9 @@ function Peripheral.get(args)
|
|||||||
args = { type = args }
|
args = { type = args }
|
||||||
end
|
end
|
||||||
|
|
||||||
args = args or { type = pType }
|
if args.name then
|
||||||
|
return _G.device[args.name]
|
||||||
|
end
|
||||||
|
|
||||||
if args.type then
|
if args.type then
|
||||||
local p = Peripheral.getByType(args.type)
|
local p = Peripheral.getByType(args.type)
|
||||||
@@ -116,4 +118,113 @@ function Peripheral.get(args)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function getProxy(pi)
|
||||||
|
local socket = Socket.connect(pi.host, 189)
|
||||||
|
|
||||||
|
if not socket then
|
||||||
|
error("Timed out attaching peripheral: " .. pi.uri)
|
||||||
|
end
|
||||||
|
|
||||||
|
socket:write(pi.path)
|
||||||
|
local proxy = socket:read(3)
|
||||||
|
|
||||||
|
if not proxy then
|
||||||
|
error("Timed out attaching peripheral: " .. pi.uri)
|
||||||
|
end
|
||||||
|
|
||||||
|
local methods = proxy.methods
|
||||||
|
proxy.methods = nil
|
||||||
|
|
||||||
|
for _,method in pairs(methods) do
|
||||||
|
proxy[method] = function(...)
|
||||||
|
socket:write({ fn = method, args = { ... } })
|
||||||
|
local resp = socket:read()
|
||||||
|
if not resp then
|
||||||
|
error("Timed out communicating with peripheral: " .. pi.uri)
|
||||||
|
end
|
||||||
|
return table.unpack(resp)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if proxy.blit then
|
||||||
|
local methods = { 'clear', 'clearLine', 'setCursorPos', 'write', 'blit',
|
||||||
|
'setTextColor', 'setTextColour', 'setBackgroundColor',
|
||||||
|
'setBackgroundColour', 'scroll', 'setCursorBlink', }
|
||||||
|
local queue = nil
|
||||||
|
|
||||||
|
for _,method in pairs(methods) do
|
||||||
|
proxy[method] = function(...)
|
||||||
|
if not queue then
|
||||||
|
queue = { }
|
||||||
|
Event.onTimeout(0, function()
|
||||||
|
if not socket:write({ fn = 'fastBlit', args = { queue } }) then
|
||||||
|
error("Timed out communicating with peripheral: " .. pi.uri)
|
||||||
|
end
|
||||||
|
queue = nil
|
||||||
|
socket:read()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(queue, {
|
||||||
|
fn = method,
|
||||||
|
args = { ... },
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if proxy.type == 'monitor' then
|
||||||
|
Event.addRoutine(function()
|
||||||
|
while true do
|
||||||
|
local event = socket:read()
|
||||||
|
if not event then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
if not Util.empty(event) then
|
||||||
|
os.queueEvent(table.unpack(event))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
return proxy
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Parse a uri into it's components
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
monitor = { name = 'monitor' }
|
||||||
|
side/top = { side = 'top' }
|
||||||
|
method/list = { method = 'list' }
|
||||||
|
12://name/monitor = { host = 12, name = 'monitor' }
|
||||||
|
]]--
|
||||||
|
local function parse(uri)
|
||||||
|
local pi = Util.split(uri:gsub('^%d*://', ''), '(.-)/')
|
||||||
|
|
||||||
|
if #pi == 1 then
|
||||||
|
pi = {
|
||||||
|
'name',
|
||||||
|
pi[1],
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
host = uri:match('^(%d*)%:'), -- 12
|
||||||
|
uri = uri, -- 12://name/monitor
|
||||||
|
path = uri:gsub('^%d*://', ''), -- name/monitor
|
||||||
|
[ pi[1] ] = pi[2], -- name = 'monitor'
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function Peripheral.lookup(uri)
|
||||||
|
local pi = parse(uri)
|
||||||
|
|
||||||
|
if pi.host then
|
||||||
|
return getProxy(pi)
|
||||||
|
end
|
||||||
|
|
||||||
|
return Peripheral.get(pi)
|
||||||
|
end
|
||||||
|
|
||||||
return Peripheral
|
return Peripheral
|
||||||
|
|||||||
@@ -2,6 +2,48 @@ local Util = require('util')
|
|||||||
|
|
||||||
local Point = { }
|
local Point = { }
|
||||||
|
|
||||||
|
Point.directions = {
|
||||||
|
[ 0 ] = { xd = 1, zd = 0, yd = 0, heading = 0, direction = 'east' },
|
||||||
|
[ 1 ] = { xd = 0, zd = 1, yd = 0, heading = 1, direction = 'south' },
|
||||||
|
[ 2 ] = { xd = -1, zd = 0, yd = 0, heading = 2, direction = 'west' },
|
||||||
|
[ 3 ] = { xd = 0, zd = -1, yd = 0, heading = 3, direction = 'north' },
|
||||||
|
[ 4 ] = { xd = 0, zd = 0, yd = 1, heading = 4, direction = 'up' },
|
||||||
|
[ 5 ] = { xd = 0, zd = 0, yd = -1, heading = 5, direction = 'down' },
|
||||||
|
}
|
||||||
|
|
||||||
|
Point.facings = {
|
||||||
|
[ 0 ] = Point.directions[0],
|
||||||
|
[ 1 ] = Point.directions[1],
|
||||||
|
[ 2 ] = Point.directions[2],
|
||||||
|
[ 3 ] = Point.directions[3],
|
||||||
|
east = Point.directions[0],
|
||||||
|
south = Point.directions[1],
|
||||||
|
west = Point.directions[2],
|
||||||
|
north = Point.directions[3],
|
||||||
|
}
|
||||||
|
|
||||||
|
Point.headings = {
|
||||||
|
[ 0 ] = Point.directions[0],
|
||||||
|
[ 1 ] = Point.directions[1],
|
||||||
|
[ 2 ] = Point.directions[2],
|
||||||
|
[ 3 ] = Point.directions[3],
|
||||||
|
[ 4 ] = Point.directions[4],
|
||||||
|
[ 5 ] = Point.directions[5],
|
||||||
|
east = Point.directions[0],
|
||||||
|
south = Point.directions[1],
|
||||||
|
west = Point.directions[2],
|
||||||
|
north = Point.directions[3],
|
||||||
|
up = Point.directions[4],
|
||||||
|
down = Point.directions[5],
|
||||||
|
}
|
||||||
|
|
||||||
|
Point.EAST = 0
|
||||||
|
Point.SOUTH = 1
|
||||||
|
Point.WEST = 2
|
||||||
|
Point.NORTH = 3
|
||||||
|
Point.UP = 4
|
||||||
|
Point.DOWN = 5
|
||||||
|
|
||||||
function Point.copy(pt)
|
function Point.copy(pt)
|
||||||
return { x = pt.x, y = pt.y, z = pt.z }
|
return { x = pt.x, y = pt.y, z = pt.z }
|
||||||
end
|
end
|
||||||
@@ -27,7 +69,7 @@ function Point.subtract(a, b)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Euclidian distance
|
-- Euclidian distance
|
||||||
function Point.pythagoreanDistance(a, b)
|
function Point.distance(a, b)
|
||||||
return math.sqrt(
|
return math.sqrt(
|
||||||
math.pow(a.x - b.x, 2) +
|
math.pow(a.x - b.x, 2) +
|
||||||
math.pow(a.y - b.y, 2) +
|
math.pow(a.y - b.y, 2) +
|
||||||
@@ -57,28 +99,20 @@ function Point.calculateTurns(ih, oh)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Point.calculateHeading(pta, ptb)
|
function Point.calculateHeading(pta, ptb)
|
||||||
|
|
||||||
local heading
|
local heading
|
||||||
|
local xd, zd = pta.x - ptb.x, pta.z - ptb.z
|
||||||
|
|
||||||
if (pta.heading % 2) == 0 and pta.z ~= ptb.z then
|
if (pta.heading % 2) == 0 and zd ~= 0 then
|
||||||
if ptb.z > pta.z then
|
heading = zd < 0 and 1 or 3
|
||||||
heading = 1
|
elseif (pta.heading % 2) == 1 and xd ~= 0 then
|
||||||
else
|
heading = xd < 0 and 0 or 2
|
||||||
heading = 3
|
elseif pta.heading == 0 and xd > 0 then
|
||||||
end
|
|
||||||
elseif (pta.heading % 2) == 1 and pta.x ~= ptb.x then
|
|
||||||
if ptb.x > pta.x then
|
|
||||||
heading = 0
|
|
||||||
else
|
|
||||||
heading = 2
|
|
||||||
end
|
|
||||||
elseif pta.heading == 0 and pta.x > ptb.x then
|
|
||||||
heading = 2
|
heading = 2
|
||||||
elseif pta.heading == 2 and pta.x < ptb.x then
|
elseif pta.heading == 2 and xd < 0 then
|
||||||
heading = 0
|
heading = 0
|
||||||
elseif pta.heading == 1 and pta.z > ptb.z then
|
elseif pta.heading == 1 and zd > 0 then
|
||||||
heading = 3
|
heading = 3
|
||||||
elseif pta.heading == 3 and pta.z < ptb.z then
|
elseif pta.heading == 3 and zd < 0 then
|
||||||
heading = 1
|
heading = 1
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -122,19 +156,25 @@ end
|
|||||||
|
|
||||||
-- given a set of points, find the one taking the least moves
|
-- given a set of points, find the one taking the least moves
|
||||||
function Point.closest(reference, pts)
|
function Point.closest(reference, pts)
|
||||||
local lpt, lm -- lowest
|
if #pts == 1 then
|
||||||
|
return pts[1]
|
||||||
|
end
|
||||||
|
|
||||||
|
local lm, lpt = math.huge
|
||||||
for _,pt in pairs(pts) do
|
for _,pt in pairs(pts) do
|
||||||
local m = Point.calculateMoves(reference, pt)
|
local distance = Point.turtleDistance(reference, pt)
|
||||||
if not lm or m < lm then
|
if distance < lm then
|
||||||
lpt = pt
|
local m = Point.calculateMoves(reference, pt, distance)
|
||||||
lm = m
|
if m < lm then
|
||||||
|
lpt = pt
|
||||||
|
lm = m
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return lpt
|
return lpt
|
||||||
end
|
end
|
||||||
|
|
||||||
function Point.eachClosest(spt, ipts, fn)
|
function Point.eachClosest(spt, ipts, fn)
|
||||||
|
|
||||||
local pts = Util.shallowCopy(ipts)
|
local pts = Util.shallowCopy(ipts)
|
||||||
while #pts > 0 do
|
while #pts > 0 do
|
||||||
local pt = Point.closest(spt, pts)
|
local pt = Point.closest(spt, pts)
|
||||||
@@ -149,13 +189,87 @@ end
|
|||||||
function Point.adjacentPoints(pt)
|
function Point.adjacentPoints(pt)
|
||||||
local pts = { }
|
local pts = { }
|
||||||
|
|
||||||
for _, hi in pairs(turtle.getHeadings()) do
|
for i = 0, 5 do
|
||||||
|
local hi = Point.headings[i]
|
||||||
table.insert(pts, { x = pt.x + hi.xd, y = pt.y + hi.yd, z = pt.z + hi.zd })
|
table.insert(pts, { x = pt.x + hi.xd, y = pt.y + hi.yd, z = pt.z + hi.zd })
|
||||||
end
|
end
|
||||||
|
|
||||||
return pts
|
return pts
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- get the point nearest A that is in the direction of B
|
||||||
|
function Point.nearestTo(pta, ptb)
|
||||||
|
local heading
|
||||||
|
|
||||||
|
if pta.x < ptb.x then
|
||||||
|
heading = 0
|
||||||
|
elseif pta.z < ptb.z then
|
||||||
|
heading = 1
|
||||||
|
elseif pta.x > ptb.x then
|
||||||
|
heading = 2
|
||||||
|
elseif pta.z > ptb.z then
|
||||||
|
heading = 3
|
||||||
|
elseif pta.y < ptb.y then
|
||||||
|
heading = 4
|
||||||
|
elseif pta.y > ptb.y then
|
||||||
|
heading = 5
|
||||||
|
end
|
||||||
|
|
||||||
|
if heading then
|
||||||
|
return {
|
||||||
|
x = pta.x + Point.headings[heading].xd,
|
||||||
|
y = pta.y + Point.headings[heading].yd,
|
||||||
|
z = pta.z + Point.headings[heading].zd,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
return pta -- error ?
|
||||||
|
end
|
||||||
|
|
||||||
|
function Point.rotate(pt, facing)
|
||||||
|
local x, z = pt.x, pt.z
|
||||||
|
if facing == 1 then
|
||||||
|
pt.x = z
|
||||||
|
pt.z = -x
|
||||||
|
elseif facing == 2 then
|
||||||
|
pt.x = -x
|
||||||
|
pt.z = -z
|
||||||
|
elseif facing == 3 then
|
||||||
|
pt.x = -z
|
||||||
|
pt.z = x
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Point.makeBox(pt1, pt2)
|
||||||
|
return {
|
||||||
|
x = pt1.x,
|
||||||
|
y = pt1.y,
|
||||||
|
z = pt1.z,
|
||||||
|
ex = pt2.x,
|
||||||
|
ey = pt2.y,
|
||||||
|
ez = pt2.z,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- expand box to include point
|
||||||
|
function Point.expandBox(box, pt)
|
||||||
|
if pt.x < box.x then
|
||||||
|
box.x = pt.x
|
||||||
|
elseif pt.x > box.ex then
|
||||||
|
box.ex = pt.x
|
||||||
|
end
|
||||||
|
if pt.y < box.y then
|
||||||
|
box.y = pt.y
|
||||||
|
elseif pt.y > box.ey then
|
||||||
|
box.ey = pt.y
|
||||||
|
end
|
||||||
|
if pt.z < box.z then
|
||||||
|
box.z = pt.z
|
||||||
|
elseif pt.z > box.ez then
|
||||||
|
box.ez = pt.z
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function Point.normalizeBox(box)
|
function Point.normalizeBox(box)
|
||||||
return {
|
return {
|
||||||
x = math.min(box.x, box.ex),
|
x = math.min(box.x, box.ex),
|
||||||
@@ -176,33 +290,17 @@ function Point.inBox(pt, box)
|
|||||||
pt.z <= box.ez
|
pt.z <= box.ez
|
||||||
end
|
end
|
||||||
|
|
||||||
return Point
|
function Point.closestPointInBox(pt, box)
|
||||||
|
local cpt = {
|
||||||
|
x = math.abs(pt.x - box.x) < math.abs(pt.x - box.ex) and box.x or box.ex,
|
||||||
|
y = math.abs(pt.y - box.y) < math.abs(pt.y - box.ey) and box.y or box.ey,
|
||||||
|
z = math.abs(pt.z - box.z) < math.abs(pt.z - box.ez) and box.z or box.ez,
|
||||||
|
}
|
||||||
|
cpt.x = pt.x > box.x and pt.x < box.ex and pt.x or cpt.x
|
||||||
|
cpt.y = pt.y > box.y and pt.y < box.ey and pt.y or cpt.y
|
||||||
|
cpt.z = pt.z > box.z and pt.z < box.ez and pt.z or cpt.z
|
||||||
|
|
||||||
--[[
|
return cpt
|
||||||
Box = { }
|
|
||||||
|
|
||||||
function Box.contain(boundingBox, containedBox)
|
|
||||||
|
|
||||||
local shiftX = boundingBox.ax - containedBox.ax
|
|
||||||
if shiftX > 0 then
|
|
||||||
containedBox.ax = containedBox.ax + shiftX
|
|
||||||
containedBox.bx = containedBox.bx + shiftX
|
|
||||||
end
|
|
||||||
local shiftZ = boundingBox.az - containedBox.az
|
|
||||||
if shiftZ > 0 then
|
|
||||||
containedBox.az = containedBox.az + shiftZ
|
|
||||||
containedBox.bz = containedBox.bz + shiftZ
|
|
||||||
end
|
|
||||||
|
|
||||||
shiftX = boundingBox.bx - containedBox.bx
|
|
||||||
if shiftX < 0 then
|
|
||||||
containedBox.ax = containedBox.ax + shiftX
|
|
||||||
containedBox.bx = containedBox.bx + shiftX
|
|
||||||
end
|
|
||||||
shiftZ = boundingBox.bz - containedBox.bz
|
|
||||||
if shiftZ < 0 then
|
|
||||||
containedBox.az = containedBox.az + shiftZ
|
|
||||||
containedBox.bz = containedBox.bz + shiftZ
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
--]]
|
|
||||||
|
return Point
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ function Security.verifyPassword(password)
|
|||||||
return config.password and password == config.password
|
return config.password and password == config.password
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Security.hasPassword()
|
||||||
|
return not not config.password
|
||||||
|
end
|
||||||
|
|
||||||
function Security.getSecretKey()
|
function Security.getSecretKey()
|
||||||
Config.load('os', config)
|
Config.load('os', config)
|
||||||
if not config.secretKey then
|
if not config.secretKey then
|
||||||
@@ -28,7 +32,7 @@ function Security.getPublicKey()
|
|||||||
local function modexp(base, exponent, modulo)
|
local function modexp(base, exponent, modulo)
|
||||||
local remainder = base
|
local remainder = base
|
||||||
|
|
||||||
for i = 1, exponent-1 do
|
for _ = 1, exponent-1 do
|
||||||
remainder = remainder * remainder
|
remainder = remainder * remainder
|
||||||
if remainder >= modulo then
|
if remainder >= modulo then
|
||||||
remainder = remainder % modulo
|
remainder = remainder % modulo
|
||||||
|
|||||||
@@ -3,11 +3,14 @@ local Logger = require('logger')
|
|||||||
local Security = require('security')
|
local Security = require('security')
|
||||||
local Util = require('util')
|
local Util = require('util')
|
||||||
|
|
||||||
|
local device = _G.device
|
||||||
|
local os = _G.os
|
||||||
|
|
||||||
local socketClass = { }
|
local socketClass = { }
|
||||||
|
|
||||||
function socketClass:read(timeout)
|
function socketClass:read(timeout)
|
||||||
|
|
||||||
local data, distance = transport.read(self)
|
local data, distance = _G.transport.read(self)
|
||||||
if data then
|
if data then
|
||||||
return data, distance
|
return data, distance
|
||||||
end
|
end
|
||||||
@@ -23,7 +26,7 @@ function socketClass:read(timeout)
|
|||||||
local e, id = os.pullEvent()
|
local e, id = os.pullEvent()
|
||||||
|
|
||||||
if e == 'transport_' .. self.sport then
|
if e == 'transport_' .. self.sport then
|
||||||
data, distance = transport.read(self)
|
data, distance = _G.transport.read(self)
|
||||||
if data then
|
if data then
|
||||||
os.cancelTimer(timerId)
|
os.cancelTimer(timerId)
|
||||||
return data, distance
|
return data, distance
|
||||||
@@ -34,13 +37,14 @@ function socketClass:read(timeout)
|
|||||||
break
|
break
|
||||||
end
|
end
|
||||||
timerId = os.startTimer(5)
|
timerId = os.startTimer(5)
|
||||||
|
self:ping()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function socketClass:write(data)
|
function socketClass:write(data)
|
||||||
if self.connected then
|
if self.connected then
|
||||||
transport.write(self, {
|
_G.transport.write(self, {
|
||||||
type = 'DATA',
|
type = 'DATA',
|
||||||
seq = self.wseq,
|
seq = self.wseq,
|
||||||
data = data,
|
data = data,
|
||||||
@@ -51,10 +55,7 @@ end
|
|||||||
|
|
||||||
function socketClass:ping()
|
function socketClass:ping()
|
||||||
if self.connected then
|
if self.connected then
|
||||||
transport.write(self, {
|
_G.transport.ping(self)
|
||||||
type = 'PING',
|
|
||||||
seq = self.wseq,
|
|
||||||
})
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -68,7 +69,7 @@ function socketClass:close()
|
|||||||
self.connected = false
|
self.connected = false
|
||||||
end
|
end
|
||||||
device.wireless_modem.close(self.sport)
|
device.wireless_modem.close(self.sport)
|
||||||
transport.close(self)
|
_G.transport.close(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
local Socket = { }
|
local Socket = { }
|
||||||
@@ -105,7 +106,7 @@ end
|
|||||||
function Socket.connect(host, port)
|
function Socket.connect(host, port)
|
||||||
|
|
||||||
local socket = newSocket(host == os.getComputerID())
|
local socket = newSocket(host == os.getComputerID())
|
||||||
socket.dhost = host
|
socket.dhost = tonumber(host)
|
||||||
Logger.log('socket', 'connecting to ' .. port)
|
Logger.log('socket', 'connecting to ' .. port)
|
||||||
|
|
||||||
socket.transmit(port, socket.sport, {
|
socket.transmit(port, socket.sport, {
|
||||||
@@ -122,23 +123,29 @@ function Socket.connect(host, port)
|
|||||||
local e, id, sport, dport, msg = os.pullEvent()
|
local e, id, sport, dport, msg = os.pullEvent()
|
||||||
if e == 'modem_message' and
|
if e == 'modem_message' and
|
||||||
sport == socket.sport and
|
sport == socket.sport and
|
||||||
msg.dhost == socket.shost and
|
msg.dhost == socket.shost then
|
||||||
msg.type == 'CONN' then
|
|
||||||
|
|
||||||
socket.dport = dport
|
|
||||||
socket.connected = true
|
|
||||||
Logger.log('socket', 'connection established to %d %d->%d',
|
|
||||||
host, socket.sport, socket.dport)
|
|
||||||
|
|
||||||
os.cancelTimer(timerId)
|
os.cancelTimer(timerId)
|
||||||
|
|
||||||
transport.open(socket)
|
if msg.type == 'CONN' then
|
||||||
|
|
||||||
return socket
|
socket.dport = dport
|
||||||
|
socket.connected = true
|
||||||
|
Logger.log('socket', 'connection established to %d %d->%d',
|
||||||
|
host, socket.sport, socket.dport)
|
||||||
|
|
||||||
|
_G.transport.open(socket)
|
||||||
|
|
||||||
|
return socket
|
||||||
|
elseif msg.type == 'REJE' then
|
||||||
|
return false, 'Password not set on target or not trusted'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
until e == 'timer' and id == timerId
|
until e == 'timer' and id == timerId
|
||||||
|
|
||||||
socket:close()
|
socket:close()
|
||||||
|
|
||||||
|
return false, 'Connection timed out'
|
||||||
end
|
end
|
||||||
|
|
||||||
local function trusted(msg, port)
|
local function trusted(msg, port)
|
||||||
@@ -148,6 +155,11 @@ local function trusted(msg, port)
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if not Security.hasPassword() then
|
||||||
|
-- no password has been set on this computer
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
local trustList = Util.readTable('usr/.known_hosts') or { }
|
local trustList = Util.readTable('usr/.known_hosts') or { }
|
||||||
local pubKey = trustList[msg.shost]
|
local pubKey = trustList[msg.shost]
|
||||||
|
|
||||||
@@ -165,20 +177,21 @@ function Socket.server(port)
|
|||||||
Logger.log('socket', 'Waiting for connections on port ' .. port)
|
Logger.log('socket', 'Waiting for connections on port ' .. port)
|
||||||
|
|
||||||
while true do
|
while true do
|
||||||
local e, _, sport, dport, msg = os.pullEvent('modem_message')
|
local _, _, sport, dport, msg = os.pullEvent('modem_message')
|
||||||
|
|
||||||
if sport == port and
|
if sport == port and
|
||||||
msg and
|
msg and
|
||||||
msg.dhost == os.getComputerID() and
|
msg.dhost == os.getComputerID() and
|
||||||
msg.type == 'OPEN' then
|
msg.type == 'OPEN' then
|
||||||
|
|
||||||
|
local socket = newSocket(msg.shost == os.getComputerID())
|
||||||
|
socket.dport = dport
|
||||||
|
socket.dhost = msg.shost
|
||||||
|
socket.wseq = msg.wseq
|
||||||
|
socket.rseq = msg.rseq
|
||||||
|
|
||||||
if trusted(msg, port) then
|
if trusted(msg, port) then
|
||||||
local socket = newSocket(msg.shost == os.getComputerID())
|
|
||||||
socket.dport = dport
|
|
||||||
socket.dhost = msg.shost
|
|
||||||
socket.connected = true
|
socket.connected = true
|
||||||
socket.wseq = msg.wseq
|
|
||||||
socket.rseq = msg.rseq
|
|
||||||
socket.transmit(socket.dport, socket.sport, {
|
socket.transmit(socket.dport, socket.sport, {
|
||||||
type = 'CONN',
|
type = 'CONN',
|
||||||
dhost = socket.dhost,
|
dhost = socket.dhost,
|
||||||
@@ -186,9 +199,16 @@ function Socket.server(port)
|
|||||||
})
|
})
|
||||||
Logger.log('socket', 'Connection established %d->%d', socket.sport, socket.dport)
|
Logger.log('socket', 'Connection established %d->%d', socket.sport, socket.dport)
|
||||||
|
|
||||||
transport.open(socket)
|
_G.transport.open(socket)
|
||||||
return socket
|
return socket
|
||||||
end
|
end
|
||||||
|
|
||||||
|
socket.transmit(socket.dport, socket.sport, {
|
||||||
|
type = 'REJE',
|
||||||
|
dhost = socket.dhost,
|
||||||
|
shost = socket.shost,
|
||||||
|
})
|
||||||
|
socket:close()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
local syncLocks = { }
|
local syncLocks = { }
|
||||||
|
|
||||||
|
local os = _G.os
|
||||||
|
|
||||||
return function(obj, fn)
|
return function(obj, fn)
|
||||||
local key = tostring(obj)
|
local key = tostring(obj)
|
||||||
if syncLocks[key] then
|
if syncLocks[key] then
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
local Util = require('util')
|
local Util = require('util')
|
||||||
|
|
||||||
|
local colors = _G.colors
|
||||||
|
local term = _G.term
|
||||||
|
local _gsub = string.gsub
|
||||||
|
|
||||||
local Terminal = { }
|
local Terminal = { }
|
||||||
|
|
||||||
local _sgsub = string.gsub
|
|
||||||
|
|
||||||
function Terminal.scrollable(ct, size)
|
function Terminal.scrollable(ct, size)
|
||||||
|
|
||||||
local size = size or 25
|
|
||||||
local w, h = ct.getSize()
|
local w, h = ct.getSize()
|
||||||
local win = window.create(ct, 1, 1, w, h + size, true)
|
local win = _G.window.create(ct, 1, 1, w, h + size, true)
|
||||||
local oldWin = Util.shallowCopy(win)
|
local oldWin = Util.shallowCopy(win)
|
||||||
local scrollPos = 0
|
local scrollPos = 0
|
||||||
|
|
||||||
@@ -87,7 +88,7 @@ function Terminal.toGrayscale(ct)
|
|||||||
local methods = { 'setBackgroundColor', 'setBackgroundColour',
|
local methods = { 'setBackgroundColor', 'setBackgroundColour',
|
||||||
'setTextColor', 'setTextColour' }
|
'setTextColor', 'setTextColour' }
|
||||||
for _,v in pairs(methods) do
|
for _,v in pairs(methods) do
|
||||||
local fn = ct[v]
|
local fn = ct[v]
|
||||||
ct[v] = function(c)
|
ct[v] = function(c)
|
||||||
fn(scolors[c])
|
fn(scolors[c])
|
||||||
end
|
end
|
||||||
@@ -110,10 +111,7 @@ function Terminal.toGrayscale(ct)
|
|||||||
|
|
||||||
local function translate(s)
|
local function translate(s)
|
||||||
if s then
|
if s then
|
||||||
for k,v in pairs(bcolors) do
|
s = _gsub(s, "%w", bcolors)
|
||||||
s = _sgsub(s, k, v)
|
|
||||||
end
|
|
||||||
-- s = _sgsub(s, "%d+", bcolors) -- not working in cc 1.75 ???
|
|
||||||
end
|
end
|
||||||
return s
|
return s
|
||||||
end
|
end
|
||||||
@@ -139,9 +137,9 @@ end
|
|||||||
function Terminal.copy(it, ot)
|
function Terminal.copy(it, ot)
|
||||||
ot = ot or { }
|
ot = ot or { }
|
||||||
for k,v in pairs(it) do
|
for k,v in pairs(it) do
|
||||||
if type(v) == 'function' then
|
if type(v) == 'function' then
|
||||||
ot[k] = v
|
ot[k] = v
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return ot
|
return ot
|
||||||
end
|
end
|
||||||
@@ -151,8 +149,8 @@ function Terminal.mirror(ct, dt)
|
|||||||
ct[k] = function(...)
|
ct[k] = function(...)
|
||||||
local ret = { f(...) }
|
local ret = { f(...) }
|
||||||
if dt[k] then
|
if dt[k] then
|
||||||
dt[k](...)
|
dt[k](...)
|
||||||
end
|
end
|
||||||
return unpack(ret)
|
return unpack(ret)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -165,7 +163,7 @@ function Terminal.readPassword(prompt)
|
|||||||
local fn = term.current().write
|
local fn = term.current().write
|
||||||
term.current().write = function() end
|
term.current().write = function() end
|
||||||
local s
|
local s
|
||||||
pcall(function() s = read(prompt) end)
|
pcall(function() s = _G.read(prompt) end)
|
||||||
term.current().write = fn
|
term.current().write = fn
|
||||||
|
|
||||||
if s == '' then
|
if s == '' then
|
||||||
|
|||||||
@@ -1,118 +1,73 @@
|
|||||||
requireInjector(getfenv(1))
|
_G.requireInjector()
|
||||||
|
|
||||||
local Grid = require ("jumper.grid")
|
local Grid = require('jumper.grid')
|
||||||
local Pathfinder = require ("jumper.pathfinder")
|
local Pathfinder = require('jumper.pathfinder')
|
||||||
local Point = require('point')
|
local Point = require('point')
|
||||||
local Util = require('util')
|
local Util = require('util')
|
||||||
|
|
||||||
local WALKABLE = 0
|
local turtle = _G.turtle
|
||||||
|
|
||||||
local function createMap(dim)
|
local function addBlock(grid, b, dim)
|
||||||
local map = { }
|
if Point.inBox(b, dim) then
|
||||||
for z = 1, dim.ez do
|
local node = grid:getNodeAt(b.x, b.y, b.z)
|
||||||
local row = {}
|
if node then
|
||||||
for x = 1, dim.ex do
|
node.walkable = 1
|
||||||
local col = { }
|
|
||||||
for y = 1, dim.ey do
|
|
||||||
table.insert(col, WALKABLE)
|
|
||||||
end
|
|
||||||
table.insert(row, col)
|
|
||||||
end
|
end
|
||||||
table.insert(map, row)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return map
|
|
||||||
end
|
|
||||||
|
|
||||||
local function addBlock(map, dim, b)
|
|
||||||
map[b.z + dim.oz][b.x + dim.ox][b.y + dim.oy] = 1
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- map shrinks/grows depending upon blocks encountered
|
-- map shrinks/grows depending upon blocks encountered
|
||||||
-- the map will encompass any blocks encountered, the turtle position, and the destination
|
-- the map will encompass any blocks encountered, the turtle position, and the destination
|
||||||
local function mapDimensions(dest, blocks, boundingBox)
|
local function mapDimensions(dest, blocks, boundingBox, dests)
|
||||||
local sx, sz, sy = turtle.point.x, turtle.point.z, turtle.point.y
|
local box = Point.makeBox(turtle.point, turtle.point)
|
||||||
local ex, ez, ey = turtle.point.x, turtle.point.z, turtle.point.y
|
|
||||||
|
|
||||||
local function adjust(pt)
|
Point.expandBox(box, dest)
|
||||||
if pt.x < sx then
|
|
||||||
sx = pt.x
|
for _,d in pairs(dests) do
|
||||||
end
|
Point.expandBox(box, d)
|
||||||
if pt.z < sz then
|
|
||||||
sz = pt.z
|
|
||||||
end
|
|
||||||
if pt.y < sy then
|
|
||||||
sy = pt.y
|
|
||||||
end
|
|
||||||
if pt.x > ex then
|
|
||||||
ex = pt.x
|
|
||||||
end
|
|
||||||
if pt.z > ez then
|
|
||||||
ez = pt.z
|
|
||||||
end
|
|
||||||
if pt.y > ey then
|
|
||||||
ey = pt.y
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
adjust(dest)
|
for _,b in pairs(blocks) do
|
||||||
|
Point.expandBox(box, b)
|
||||||
for _,b in ipairs(blocks) do
|
|
||||||
adjust(b)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- expand one block out in all directions
|
-- expand one block out in all directions
|
||||||
if boundingBox then
|
if boundingBox then
|
||||||
sx = math.max(sx - 1, boundingBox.x)
|
box.x = math.max(box.x - 1, boundingBox.x)
|
||||||
sz = math.max(sz - 1, boundingBox.z)
|
box.z = math.max(box.z - 1, boundingBox.z)
|
||||||
sy = math.max(sy - 1, boundingBox.y)
|
box.y = math.max(box.y - 1, boundingBox.y)
|
||||||
ex = math.min(ex + 1, boundingBox.ex)
|
box.ex = math.min(box.ex + 1, boundingBox.ex)
|
||||||
ez = math.min(ez + 1, boundingBox.ez)
|
box.ez = math.min(box.ez + 1, boundingBox.ez)
|
||||||
ey = math.min(ey + 1, boundingBox.ey)
|
box.ey = math.min(box.ey + 1, boundingBox.ey)
|
||||||
else
|
else
|
||||||
sx = sx - 1
|
box.x = box.x - 1
|
||||||
sz = sz - 1
|
box.z = box.z - 1
|
||||||
sy = sy - 1
|
box.y = box.y - 1
|
||||||
ex = ex + 1
|
box.ex = box.ex + 1
|
||||||
ez = ez + 1
|
box.ez = box.ez + 1
|
||||||
ey = ey + 1
|
box.ey = box.ey + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
return {
|
return box
|
||||||
ex = ex - sx + 1,
|
|
||||||
ez = ez - sz + 1,
|
|
||||||
ey = ey - sy + 1,
|
|
||||||
ox = -sx + 1,
|
|
||||||
oz = -sz + 1,
|
|
||||||
oy = -sy + 1
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- shifting and coordinate flipping
|
local function nodeToPoint(node)
|
||||||
local function pointToMap(dim, pt)
|
return { x = node.x, y = node.y, z = node.z, heading = node.heading }
|
||||||
return { x = pt.x + dim.ox, z = pt.y + dim.oy, y = pt.z + dim.oz }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function nodeToPoint(dim, node)
|
local function heuristic(n, node)
|
||||||
return { x = node:getX() - dim.ox, z = node:getY() - dim.oz, y = node:getZ() - dim.oy }
|
return Point.calculateMoves(node, n)
|
||||||
end
|
-- { x = node.x, y = node.y, z = node.z, heading = node.heading },
|
||||||
|
-- { x = n.x, y = n.y, z = n.z, heading = n.heading })
|
||||||
local heuristic = function(n, node)
|
|
||||||
|
|
||||||
local m, h = Point.calculateMoves(
|
|
||||||
{ x = node._x, z = node._y, y = node._z, heading = node._heading },
|
|
||||||
{ x = n._x, z = n._y, y = n._z, heading = n._heading })
|
|
||||||
|
|
||||||
return m, h
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function dimsAreEqual(d1, d2)
|
local function dimsAreEqual(d1, d2)
|
||||||
return d1.ex == d2.ex and
|
return d1.ex == d2.ex and
|
||||||
d1.ey == d2.ey and
|
d1.ey == d2.ey and
|
||||||
d1.ez == d2.ez and
|
d1.ez == d2.ez and
|
||||||
d1.ox == d2.ox and
|
d1.x == d2.x and
|
||||||
d1.oy == d2.oy and
|
d1.y == d2.y and
|
||||||
d1.oz == d2.oz
|
d1.z == d2.z
|
||||||
end
|
end
|
||||||
|
|
||||||
-- turtle sensor returns blocks in relation to the world - not turtle orientation
|
-- turtle sensor returns blocks in relation to the world - not turtle orientation
|
||||||
@@ -120,7 +75,6 @@ end
|
|||||||
-- really kinda dumb since it returns the coordinates as offsets of our location
|
-- really kinda dumb since it returns the coordinates as offsets of our location
|
||||||
-- instead of true coordinates
|
-- instead of true coordinates
|
||||||
local function addSensorBlocks(blocks, sblocks)
|
local function addSensorBlocks(blocks, sblocks)
|
||||||
|
|
||||||
for _,b in pairs(sblocks) do
|
for _,b in pairs(sblocks) do
|
||||||
if b.type ~= 'AIR' then
|
if b.type ~= 'AIR' then
|
||||||
local pt = { x = turtle.point.x, y = turtle.point.y + b.y, z = turtle.point.z }
|
local pt = { x = turtle.point.x, y = turtle.point.y + b.y, z = turtle.point.z }
|
||||||
@@ -140,63 +94,53 @@ local function addSensorBlocks(blocks, sblocks)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function selectDestination(pts, box, map, dim)
|
local function selectDestination(pts, box, grid)
|
||||||
|
|
||||||
while #pts > 0 do
|
while #pts > 0 do
|
||||||
local pt = Point.closest(turtle.point, pts)
|
local pt = Point.closest(turtle.point, pts)
|
||||||
|
if box and not Point.inBox(pt, box) then
|
||||||
if (box and not Point.inBox(pt, box)) or
|
Util.removeByValue(pts, pt)
|
||||||
map[pt.z + dim.oz][pt.x + dim.ox][pt.y + dim.oy] == 1 then
|
else
|
||||||
|
if grid:isWalkableAt(pt.x, pt.y, pt.z) then
|
||||||
|
return pt
|
||||||
|
end
|
||||||
Util.removeByValue(pts, pt)
|
Util.removeByValue(pts, pt)
|
||||||
else
|
|
||||||
return pt
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function pathTo(dest, options)
|
local function pathTo(dest, options)
|
||||||
|
|
||||||
local blocks = options.blocks or turtle.getState().blocks or { }
|
local blocks = options.blocks or turtle.getState().blocks or { }
|
||||||
local dests = options.dest or { dest } -- support alternative destinations
|
local dests = options.dest or { dest } -- support alternative destinations
|
||||||
local box = options.box or turtle.getState().box
|
local box = options.box or turtle.getState().box
|
||||||
|
local lastDim
|
||||||
local lastDim = nil
|
local grid
|
||||||
local map = nil
|
|
||||||
local grid = nil
|
|
||||||
|
|
||||||
if box then
|
if box then
|
||||||
box = Point.normalizeBox(box)
|
box = Point.normalizeBox(box)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Creates a pathfinder object
|
-- Creates a pathfinder object
|
||||||
local myFinder = Pathfinder(grid, 'ASTAR', walkable)
|
local finder = Pathfinder(heuristic)
|
||||||
|
|
||||||
myFinder:setMode('ORTHOGONAL')
|
|
||||||
myFinder:setHeuristic(heuristic)
|
|
||||||
|
|
||||||
while turtle.point.x ~= dest.x or turtle.point.z ~= dest.z or turtle.point.y ~= dest.y do
|
while turtle.point.x ~= dest.x or turtle.point.z ~= dest.z or turtle.point.y ~= dest.y do
|
||||||
|
|
||||||
-- map expands as we encounter obstacles
|
-- map expands as we encounter obstacles
|
||||||
local dim = mapDimensions(dest, blocks, box)
|
local dim = mapDimensions(dest, blocks, box, dests)
|
||||||
|
|
||||||
-- reuse map if possible
|
-- reuse map if possible
|
||||||
if not lastDim or not dimsAreEqual(dim, lastDim) then
|
if not lastDim or not dimsAreEqual(dim, lastDim) then
|
||||||
map = createMap(dim)
|
|
||||||
-- Creates a grid object
|
-- Creates a grid object
|
||||||
grid = Grid(map)
|
grid = Grid(dim)
|
||||||
myFinder:setGrid(grid)
|
finder:setGrid(grid)
|
||||||
myFinder:setWalkable(WALKABLE)
|
|
||||||
|
|
||||||
lastDim = dim
|
lastDim = dim
|
||||||
end
|
end
|
||||||
|
for _,b in pairs(blocks) do
|
||||||
for _,b in ipairs(blocks) do
|
addBlock(grid, b, dim)
|
||||||
addBlock(map, dim, b)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
dest = selectDestination(dests, box, map, dim)
|
dest = selectDestination(dests, box, grid)
|
||||||
if not dest then
|
if not dest then
|
||||||
-- error('failed to reach destination')
|
|
||||||
return false, 'failed to reach destination'
|
return false, 'failed to reach destination'
|
||||||
end
|
end
|
||||||
if turtle.point.x == dest.x and turtle.point.z == dest.z and turtle.point.y == dest.y then
|
if turtle.point.x == dest.x and turtle.point.z == dest.z and turtle.point.y == dest.y then
|
||||||
@@ -204,29 +148,50 @@ local function pathTo(dest, options)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Define start and goal locations coordinates
|
-- Define start and goal locations coordinates
|
||||||
local startPt = pointToMap(dim, turtle.point)
|
local startPt = turtle.point
|
||||||
local endPt = pointToMap(dim, dest)
|
|
||||||
|
|
||||||
-- Calculates the path, and its length
|
-- Calculates the path, and its length
|
||||||
local path = myFinder:getPath(startPt.x, startPt.y, startPt.z, turtle.point.heading, endPt.x, endPt.y, endPt.z, dest.heading)
|
local path = finder:getPath(
|
||||||
|
startPt.x, startPt.y, startPt.z, turtle.point.heading,
|
||||||
|
dest.x, dest.y, dest.z, dest.heading)
|
||||||
|
|
||||||
if not path then
|
if not path then
|
||||||
Util.removeByValue(dests, dest)
|
Util.removeByValue(dests, dest)
|
||||||
else
|
else
|
||||||
for node, count in path:nodes() do
|
path:filter()
|
||||||
local pt = nodeToPoint(dim, node)
|
|
||||||
|
|
||||||
if turtle.abort then
|
for node in path:nodes() do
|
||||||
|
local pt = nodeToPoint(node)
|
||||||
|
|
||||||
|
if turtle.isAborted() then
|
||||||
return false, 'aborted'
|
return false, 'aborted'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--if this is the next to last node
|
||||||
|
--and we are traveling up or down, then the
|
||||||
|
--heading for this node should be the heading of the last node
|
||||||
|
--or, maybe..
|
||||||
|
--if last node is up or down (or either?)
|
||||||
|
|
||||||
-- use single turn method so the turtle doesn't turn around
|
-- use single turn method so the turtle doesn't turn around
|
||||||
-- when encountering obstacles -- IS THIS RIGHT ??
|
-- when encountering obstacles
|
||||||
if not turtle.gotoSingleTurn(pt.x, pt.z, pt.y, node.heading) then
|
if not turtle.gotoSingleTurn(pt.x, pt.y, pt.z, pt.heading) then
|
||||||
table.insert(blocks, pt)
|
local bpt = Point.nearestTo(turtle.point, pt)
|
||||||
--if device.turtlesensorenvironment then
|
|
||||||
-- addSensorBlocks(blocks, device.turtlesensorenvironment.sonicScan())
|
table.insert(blocks, bpt)
|
||||||
--end
|
-- really need to check if the block we ran into was a turtle.
|
||||||
|
-- if so, this block should be temporary (1-2 secs)
|
||||||
|
|
||||||
|
--local side = turtle.getSide(turtle.point, pt)
|
||||||
|
--if turtle.isTurtleAtSide(side) then
|
||||||
|
-- pt.timestamp = os.clock() + ?
|
||||||
|
--end
|
||||||
|
-- if dim has not changed, then need to update grid with
|
||||||
|
-- walkable = nil (after time has elapsed)
|
||||||
|
|
||||||
|
--if device.turtlesensorenvironment then
|
||||||
|
-- addSensorBlocks(blocks, device.turtlesensorenvironment.sonicScan())
|
||||||
|
--end
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -258,6 +223,12 @@ return {
|
|||||||
turtle.getState().blocks = blocks
|
turtle.getState().blocks = blocks
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
addBlock = function(block)
|
||||||
|
if turtle.getState().blocks then
|
||||||
|
table.insert(turtle.getState().blocks, block)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
reset = function()
|
reset = function()
|
||||||
turtle.getState().box = nil
|
turtle.getState().box = nil
|
||||||
turtle.getState().blocks = nil
|
turtle.getState().blocks = nil
|
||||||
|
|||||||
1817
sys/apis/ui.lua
1817
sys/apis/ui.lua
File diff suppressed because it is too large
Load Diff
@@ -2,50 +2,40 @@ local class = require('class')
|
|||||||
local Region = require('ui.region')
|
local Region = require('ui.region')
|
||||||
local Util = require('util')
|
local Util = require('util')
|
||||||
|
|
||||||
local _srep = string.rep
|
local _rep = string.rep
|
||||||
local _ssub = string.sub
|
local _sub = string.sub
|
||||||
|
local _gsub = string.gsub
|
||||||
local mapColorToGray = {
|
local colors = _G.colors
|
||||||
[ colors.white ] = colors.white,
|
|
||||||
[ colors.orange ] = colors.lightGray,
|
|
||||||
[ colors.magenta ] = colors.lightGray,
|
|
||||||
[ colors.lightBlue ] = colors.lightGray,
|
|
||||||
[ colors.yellow ] = colors.lightGray,
|
|
||||||
[ colors.lime ] = colors.lightGray,
|
|
||||||
[ colors.pink ] = colors.lightGray,
|
|
||||||
[ colors.gray ] = colors.gray,
|
|
||||||
[ colors.lightGray ] = colors.lightGray,
|
|
||||||
[ colors.cyan ] = colors.lightGray,
|
|
||||||
[ colors.purple ] = colors.gray,
|
|
||||||
[ colors.blue ] = colors.gray,
|
|
||||||
[ colors.brown ] = colors.gray,
|
|
||||||
[ colors.green ] = colors.lightGray,
|
|
||||||
[ colors.red ] = colors.gray,
|
|
||||||
[ colors.black ] = colors.black,
|
|
||||||
}
|
|
||||||
|
|
||||||
local mapColorToPaint = { }
|
|
||||||
for n = 1, 16 do
|
|
||||||
mapColorToPaint[2 ^ (n - 1)] = _ssub("0123456789abcdef", n, n)
|
|
||||||
end
|
|
||||||
|
|
||||||
local mapGrayToPaint = { }
|
|
||||||
for n = 0, 15 do
|
|
||||||
local gs = mapColorToGray[2 ^ n]
|
|
||||||
mapGrayToPaint[2 ^ n] = mapColorToPaint[gs]
|
|
||||||
end
|
|
||||||
|
|
||||||
local Canvas = class()
|
local Canvas = class()
|
||||||
function Canvas:init(args)
|
|
||||||
|
|
||||||
|
Canvas.colorPalette = { }
|
||||||
|
Canvas.darkPalette = { }
|
||||||
|
Canvas.grayscalePalette = { }
|
||||||
|
|
||||||
|
for n = 1, 16 do
|
||||||
|
Canvas.colorPalette[2 ^ (n - 1)] = _sub("0123456789abcdef", n, n)
|
||||||
|
Canvas.grayscalePalette[2 ^ (n - 1)] = _sub("088888878877787f", n, n)
|
||||||
|
Canvas.darkPalette[2 ^ (n - 1)] = _sub("8777777f77fff77f", n, n)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Canvas:init(args)
|
||||||
self.x = 1
|
self.x = 1
|
||||||
self.y = 1
|
self.y = 1
|
||||||
self.layers = { }
|
self.layers = { }
|
||||||
|
|
||||||
Util.merge(self, args)
|
Util.merge(self, args)
|
||||||
|
|
||||||
self.height = self.ey - self.y + 1
|
self.ex = self.x + self.width - 1
|
||||||
self.width = self.ex - self.x + 1
|
self.ey = self.y + self.height - 1
|
||||||
|
|
||||||
|
if not self.palette then
|
||||||
|
if self.isColor then
|
||||||
|
self.palette = Canvas.colorPalette
|
||||||
|
else
|
||||||
|
self.palette = Canvas.grayscalePalette
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
self.lines = { }
|
self.lines = { }
|
||||||
for i = 1, self.height do
|
for i = 1, self.height do
|
||||||
@@ -53,6 +43,12 @@ function Canvas:init(args)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Canvas:move(x, y)
|
||||||
|
self.x, self.y = x, y
|
||||||
|
self.ex = self.x + self.width - 1
|
||||||
|
self.ey = self.y + self.height - 1
|
||||||
|
end
|
||||||
|
|
||||||
function Canvas:resize(w, h)
|
function Canvas:resize(w, h)
|
||||||
for i = self.height, h do
|
for i = self.height, h do
|
||||||
self.lines[i] = { }
|
self.lines[i] = { }
|
||||||
@@ -64,29 +60,25 @@ function Canvas:resize(w, h)
|
|||||||
|
|
||||||
if w ~= self.width then
|
if w ~= self.width then
|
||||||
for i = 1, self.height do
|
for i = 1, self.height do
|
||||||
self.lines[i] = { }
|
self.lines[i] = { dirty = true }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
self.ex = self.x + w - 1
|
self.ex = self.x + w - 1
|
||||||
self.ey = self.y + h - 1
|
self.ey = self.y + h - 1
|
||||||
|
|
||||||
self.width = w
|
self.width = w
|
||||||
self.height = h
|
self.height = h
|
||||||
|
|
||||||
self:dirty()
|
|
||||||
end
|
|
||||||
|
|
||||||
function Canvas:colorToPaintColor(c)
|
|
||||||
if self.isColor then
|
|
||||||
return mapColorToPaint[c]
|
|
||||||
end
|
|
||||||
return mapGrayToPaint[c]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function Canvas:copy()
|
function Canvas:copy()
|
||||||
local b = Canvas({ x = self.x, y = self.y, ex = self.ex, ey = self.ey })
|
local b = Canvas({
|
||||||
for i = 1, self.ey - self.y + 1 do
|
x = self.x,
|
||||||
|
y = self.y,
|
||||||
|
width = self.width,
|
||||||
|
height = self.height,
|
||||||
|
isColor = self.isColor,
|
||||||
|
})
|
||||||
|
for i = 1, self.height do
|
||||||
b.lines[i].text = self.lines[i].text
|
b.lines[i].text = self.lines[i].text
|
||||||
b.lines[i].fg = self.lines[i].fg
|
b.lines[i].fg = self.lines[i].fg
|
||||||
b.lines[i].bg = self.lines[i].bg
|
b.lines[i].bg = self.lines[i].bg
|
||||||
@@ -94,16 +86,14 @@ function Canvas:copy()
|
|||||||
return b
|
return b
|
||||||
end
|
end
|
||||||
|
|
||||||
function Canvas:addLayer(layer, bg, fg)
|
function Canvas:addLayer(layer)
|
||||||
local canvas = Canvas({
|
local canvas = Canvas({
|
||||||
x = layer.x,
|
x = layer.x,
|
||||||
y = layer.y,
|
y = layer.y,
|
||||||
ex = layer.x + layer.width - 1,
|
width = layer.width,
|
||||||
ey = layer.y + layer.height - 1,
|
height = layer.height,
|
||||||
isColor = self.isColor,
|
isColor = self.isColor,
|
||||||
})
|
})
|
||||||
canvas:clear(bg, fg)
|
|
||||||
|
|
||||||
canvas.parent = self
|
canvas.parent = self
|
||||||
table.insert(self.layers, canvas)
|
table.insert(self.layers, canvas)
|
||||||
return canvas
|
return canvas
|
||||||
@@ -129,10 +119,10 @@ end
|
|||||||
|
|
||||||
function Canvas:write(x, y, text, bg, fg)
|
function Canvas:write(x, y, text, bg, fg)
|
||||||
if bg then
|
if bg then
|
||||||
bg = _srep(self:colorToPaintColor(bg), #text)
|
bg = _rep(self.palette[bg], #text)
|
||||||
end
|
end
|
||||||
if fg then
|
if fg then
|
||||||
fg = _srep(self:colorToPaintColor(fg), #text)
|
fg = _rep(self.palette[fg], #text)
|
||||||
end
|
end
|
||||||
self:writeBlit(x, y, text, bg, fg)
|
self:writeBlit(x, y, text, bg, fg)
|
||||||
end
|
end
|
||||||
@@ -144,24 +134,24 @@ function Canvas:writeBlit(x, y, text, bg, fg)
|
|||||||
|
|
||||||
-- fix ffs
|
-- fix ffs
|
||||||
if x < 1 then
|
if x < 1 then
|
||||||
text = _ssub(text, 2 - x)
|
text = _sub(text, 2 - x)
|
||||||
if bg then
|
if bg then
|
||||||
bg = _ssub(bg, 2 - x)
|
bg = _sub(bg, 2 - x)
|
||||||
end
|
end
|
||||||
if bg then
|
if bg then
|
||||||
fg = _ssub(fg, 2 - x)
|
fg = _sub(fg, 2 - x)
|
||||||
end
|
end
|
||||||
width = width + x - 1
|
width = width + x - 1
|
||||||
x = 1
|
x = 1
|
||||||
end
|
end
|
||||||
|
|
||||||
if x + width - 1 > self.width then
|
if x + width - 1 > self.width then
|
||||||
text = _ssub(text, 1, self.width - x + 1)
|
text = _sub(text, 1, self.width - x + 1)
|
||||||
if bg then
|
if bg then
|
||||||
bg = _ssub(bg, 1, self.width - x + 1)
|
bg = _sub(bg, 1, self.width - x + 1)
|
||||||
end
|
end
|
||||||
if bg then
|
if bg then
|
||||||
fg = _ssub(fg, 1, self.width - x + 1)
|
fg = _sub(fg, 1, self.width - x + 1)
|
||||||
end
|
end
|
||||||
width = #text
|
width = #text
|
||||||
end
|
end
|
||||||
@@ -172,11 +162,11 @@ function Canvas:writeBlit(x, y, text, bg, fg)
|
|||||||
if pos == 1 and width == self.width then
|
if pos == 1 and width == self.width then
|
||||||
return rstr
|
return rstr
|
||||||
elseif pos == 1 then
|
elseif pos == 1 then
|
||||||
return rstr .. _ssub(sstr, pos+width)
|
return rstr .. _sub(sstr, pos+width)
|
||||||
elseif pos + width > self.width then
|
elseif pos + width > self.width then
|
||||||
return _ssub(sstr, 1, pos-1) .. rstr
|
return _sub(sstr, 1, pos-1) .. rstr
|
||||||
end
|
end
|
||||||
return _ssub(sstr, 1, pos-1) .. rstr .. _ssub(sstr, pos+width)
|
return _sub(sstr, 1, pos-1) .. rstr .. _sub(sstr, pos+width)
|
||||||
end
|
end
|
||||||
|
|
||||||
local line = self.lines[y]
|
local line = self.lines[y]
|
||||||
@@ -204,11 +194,10 @@ function Canvas:reset()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Canvas:clear(bg, fg)
|
function Canvas:clear(bg, fg)
|
||||||
local width = self.ex - self.x + 1
|
local text = _rep(' ', self.width)
|
||||||
local text = _srep(' ', width)
|
fg = _rep(self.palette[fg or colors.white], self.width)
|
||||||
fg = _srep(self:colorToPaintColor(fg), width)
|
bg = _rep(self.palette[bg or colors.black], self.width)
|
||||||
bg = _srep(self:colorToPaintColor(bg), width)
|
for i = 1, self.height do
|
||||||
for i = 1, self.ey - self.y + 1 do
|
|
||||||
self:writeLine(i, text, fg, bg)
|
self:writeLine(i, text, fg, bg)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -259,7 +248,7 @@ function Canvas:dirty()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Canvas:clean()
|
function Canvas:clean()
|
||||||
for y, line in pairs(self.lines) do
|
for _, line in pairs(self.lines) do
|
||||||
line.dirty = false
|
line.dirty = false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -293,9 +282,9 @@ function Canvas:blit(device, src, tgt)
|
|||||||
if line and line.dirty then
|
if line and line.dirty then
|
||||||
local t, fg, bg = line.text, line.fg, line.bg
|
local t, fg, bg = line.text, line.fg, line.bg
|
||||||
if src.x > 1 or src.ex < self.ex then
|
if src.x > 1 or src.ex < self.ex then
|
||||||
t = _ssub(t, src.x, src.ex)
|
t = _sub(t, src.x, src.ex)
|
||||||
fg = _ssub(fg, src.x, src.ex)
|
fg = _sub(fg, src.x, src.ex)
|
||||||
bg = _ssub(bg, src.x, src.ex)
|
bg = _sub(bg, src.x, src.ex)
|
||||||
end
|
end
|
||||||
--if tgt.y + i > self.ey then -- wrong place to do clipping ??
|
--if tgt.y + i > self.ey then -- wrong place to do clipping ??
|
||||||
-- break
|
-- break
|
||||||
@@ -306,15 +295,30 @@ function Canvas:blit(device, src, tgt)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Canvas.convertWindow(win, parent, x, y)
|
function Canvas:applyPalette(palette)
|
||||||
|
|
||||||
|
local lookup = { }
|
||||||
|
for n = 1, 16 do
|
||||||
|
lookup[self.palette[2 ^ (n - 1)]] = palette[2 ^ (n - 1)]
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, l in pairs(self.lines) do
|
||||||
|
l.fg = _gsub(l.fg, '%w', lookup)
|
||||||
|
l.bg = _gsub(l.bg, '%w', lookup)
|
||||||
|
l.dirty = true
|
||||||
|
end
|
||||||
|
|
||||||
|
self.palette = palette
|
||||||
|
end
|
||||||
|
|
||||||
|
function Canvas.convertWindow(win, parent, wx, wy)
|
||||||
local w, h = win.getSize()
|
local w, h = win.getSize()
|
||||||
|
|
||||||
win.canvas = Canvas({
|
win.canvas = Canvas({
|
||||||
x = x,
|
x = wx,
|
||||||
y = y,
|
y = wy,
|
||||||
ex = x + w - 1,
|
width = w,
|
||||||
ey = y + h - 1,
|
height = h,
|
||||||
isColor = win.isColor(),
|
isColor = win.isColor(),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -323,10 +327,10 @@ function Canvas.convertWindow(win, parent, x, y)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function win.clearLine()
|
function win.clearLine()
|
||||||
local x, y = win.getCursorPos()
|
local _, y = win.getCursorPos()
|
||||||
win.canvas:write(1,
|
win.canvas:write(1,
|
||||||
y,
|
y,
|
||||||
_srep(' ', win.canvas.width),
|
_rep(' ', win.canvas.width),
|
||||||
win.getBackgroundColor(),
|
win.getBackgroundColor(),
|
||||||
win.getTextColor())
|
win.getTextColor())
|
||||||
end
|
end
|
||||||
@@ -350,7 +354,7 @@ function Canvas.convertWindow(win, parent, x, y)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function win.scroll()
|
function win.scroll()
|
||||||
error('CWin:scroll: not implemented')
|
error('scroll: not implemented')
|
||||||
end
|
end
|
||||||
|
|
||||||
function win.reposition(x, y, width, height)
|
function win.reposition(x, y, width, height)
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
local UI = require('ui')
|
local UI = require('ui')
|
||||||
local Util = require('util')
|
local Util = require('util')
|
||||||
|
|
||||||
|
local colors = _G.colors
|
||||||
|
local fs = _G.fs
|
||||||
|
|
||||||
return function(args)
|
return function(args)
|
||||||
|
|
||||||
local columns = {
|
local columns = {
|
||||||
@@ -23,7 +26,7 @@ return function(args)
|
|||||||
-- rey = args.rey or -3,
|
-- rey = args.rey or -3,
|
||||||
height = args.height,
|
height = args.height,
|
||||||
width = args.width,
|
width = args.width,
|
||||||
title = 'Select file',
|
title = 'Select File',
|
||||||
grid = UI.ScrollingGrid {
|
grid = UI.ScrollingGrid {
|
||||||
x = 2,
|
x = 2,
|
||||||
y = 2,
|
y = 2,
|
||||||
@@ -86,7 +89,7 @@ return function(args)
|
|||||||
return row
|
return row
|
||||||
end
|
end
|
||||||
|
|
||||||
function selectFile.grid:getRowTextColor(file, selected)
|
function selectFile.grid:getRowTextColor(file)
|
||||||
if file.isDir then
|
if file.isDir then
|
||||||
return colors.cyan
|
return colors.cyan
|
||||||
end
|
end
|
||||||
|
|||||||
90
sys/apis/ui/transition.lua
Normal file
90
sys/apis/ui/transition.lua
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
local Tween = require('ui.tween')
|
||||||
|
|
||||||
|
local Transition = { }
|
||||||
|
|
||||||
|
function Transition.slideLeft(args)
|
||||||
|
local ticks = args.ticks or 6
|
||||||
|
local easing = args.easing or 'outQuint'
|
||||||
|
local pos = { x = args.ex }
|
||||||
|
local tween = Tween.new(ticks, pos, { x = args.x }, easing)
|
||||||
|
local lastScreen = args.canvas:copy()
|
||||||
|
|
||||||
|
return function(device)
|
||||||
|
local finished = tween:update(1)
|
||||||
|
local x = math.floor(pos.x)
|
||||||
|
lastScreen:dirty()
|
||||||
|
lastScreen:blit(device, {
|
||||||
|
x = args.ex - x + args.x,
|
||||||
|
y = args.y,
|
||||||
|
ex = args.ex,
|
||||||
|
ey = args.ey },
|
||||||
|
{ x = args.x, y = args.y })
|
||||||
|
args.canvas:blit(device, {
|
||||||
|
x = args.x,
|
||||||
|
y = args.y,
|
||||||
|
ex = args.ex - x + args.x,
|
||||||
|
ey = args.ey },
|
||||||
|
{ x = x, y = args.y })
|
||||||
|
return not finished
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Transition.slideRight(args)
|
||||||
|
local ticks = args.ticks or 6
|
||||||
|
local easing = args.easing or'outQuint'
|
||||||
|
local pos = { x = args.x }
|
||||||
|
local tween = Tween.new(ticks, pos, { x = args.ex }, easing)
|
||||||
|
local lastScreen = args.canvas:copy()
|
||||||
|
|
||||||
|
return function(device)
|
||||||
|
local finished = tween:update(1)
|
||||||
|
local x = math.floor(pos.x)
|
||||||
|
lastScreen:dirty()
|
||||||
|
lastScreen:blit(device, {
|
||||||
|
x = args.x,
|
||||||
|
y = args.y,
|
||||||
|
ex = args.ex - x + args.x,
|
||||||
|
ey = args.ey },
|
||||||
|
{ x = x, y = args.y })
|
||||||
|
args.canvas:blit(device, {
|
||||||
|
x = args.ex - x + args.x,
|
||||||
|
y = args.y,
|
||||||
|
ex = args.ex,
|
||||||
|
ey = args.ey },
|
||||||
|
{ x = args.x, y = args.y })
|
||||||
|
return not finished
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Transition.expandUp(args)
|
||||||
|
local ticks = args.ticks or 3
|
||||||
|
local easing = args.easing or 'linear'
|
||||||
|
local pos = { y = args.ey + 1 }
|
||||||
|
local tween = Tween.new(ticks, pos, { y = args.y }, easing)
|
||||||
|
|
||||||
|
return function(device)
|
||||||
|
local finished = tween:update(1)
|
||||||
|
args.canvas:blit(device, nil, { x = args.x, y = math.floor(pos.y) })
|
||||||
|
return not finished
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Transition.grow(args)
|
||||||
|
local ticks = args.ticks or 3
|
||||||
|
local easing = args.easing or 'linear'
|
||||||
|
local tween = Tween.new(ticks,
|
||||||
|
{ x = args.width / 2 - 1, y = args.height / 2 - 1, w = 1, h = 1 },
|
||||||
|
{ x = 1, y = 1, w = args.width, h = args.height }, easing)
|
||||||
|
|
||||||
|
return function(device)
|
||||||
|
local finished = tween:update(1)
|
||||||
|
local subj = tween.subject
|
||||||
|
local rect = { x = math.floor(subj.x), y = math.floor(subj.y) }
|
||||||
|
rect.ex = math.floor(rect.x + subj.w - 1)
|
||||||
|
rect.ey = math.floor(rect.y + subj.h - 1)
|
||||||
|
args.canvas:blit(device, rect, { x = args.x + rect.x - 1, y = args.y + rect.y - 1})
|
||||||
|
return not finished
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return Transition
|
||||||
@@ -1,5 +1,11 @@
|
|||||||
local Util = { }
|
local Util = { }
|
||||||
|
|
||||||
|
local fs = _G.fs
|
||||||
|
local http = _G.http
|
||||||
|
local os = _G.os
|
||||||
|
local term = _G.term
|
||||||
|
local textutils = _G.textutils
|
||||||
|
|
||||||
function Util.tryTimed(timeout, f, ...)
|
function Util.tryTimed(timeout, f, ...)
|
||||||
local c = os.clock()
|
local c = os.clock()
|
||||||
repeat
|
repeat
|
||||||
@@ -12,7 +18,7 @@ end
|
|||||||
|
|
||||||
function Util.tryTimes(attempts, f, ...)
|
function Util.tryTimes(attempts, f, ...)
|
||||||
local result
|
local result
|
||||||
for i = 1, attempts do
|
for _ = 1, attempts do
|
||||||
result = { f(...) }
|
result = { f(...) }
|
||||||
if result[1] then
|
if result[1] then
|
||||||
return unpack(result)
|
return unpack(result)
|
||||||
@@ -72,16 +78,37 @@ end
|
|||||||
function Util.getVersion()
|
function Util.getVersion()
|
||||||
local version
|
local version
|
||||||
|
|
||||||
if _CC_VERSION then
|
if _G._CC_VERSION then
|
||||||
version = tonumber(_CC_VERSION:gmatch('[%d]+%.?[%d][%d]', '%1')())
|
version = tonumber(_G._CC_VERSION:match('[%d]+%.?[%d][%d]'))
|
||||||
end
|
end
|
||||||
if not version and _HOST then
|
if not version and _G._HOST then
|
||||||
version = tonumber(_HOST:gmatch('[%d]+%.?[%d][%d]', '%1')())
|
version = tonumber(_G._HOST:match('[%d]+%.?[%d][%d]'))
|
||||||
end
|
end
|
||||||
|
|
||||||
return version or 1.7
|
return version or 1.7
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Util.getMinecraftVersion()
|
||||||
|
local mcVersion = _G._MC_VERSION or 'unknown'
|
||||||
|
if _G._HOST then
|
||||||
|
local version = _G._HOST:match('%S+ %S+ %((%S.+)%)')
|
||||||
|
if version then
|
||||||
|
mcVersion = version:match('Minecraft (%S+)') or version
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return mcVersion
|
||||||
|
end
|
||||||
|
|
||||||
|
function Util.checkMinecraftVersion(minVersion)
|
||||||
|
local version = Util.getMinecraftVersion()
|
||||||
|
local function convert(v)
|
||||||
|
local m1, m2, m3 = v:match('(%d)%.(%d)%.?(%d?)')
|
||||||
|
return tonumber(m1) * 10000 + tonumber(m2) * 100 + (tonumber(m3) or 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
return convert(version) >= convert(tostring(minVersion))
|
||||||
|
end
|
||||||
|
|
||||||
-- http://lua-users.org/wiki/SimpleRound
|
-- http://lua-users.org/wiki/SimpleRound
|
||||||
function Util.round(num, idp)
|
function Util.round(num, idp)
|
||||||
local mult = 10^(idp or 0)
|
local mult = 10^(idp or 0)
|
||||||
@@ -114,7 +141,7 @@ function Util.key(t, value)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Util.keys(t)
|
function Util.keys(t)
|
||||||
local keys = {}
|
local keys = { }
|
||||||
for k in pairs(t) do
|
for k in pairs(t) do
|
||||||
keys[#keys+1] = k
|
keys[#keys+1] = k
|
||||||
end
|
end
|
||||||
@@ -152,6 +179,14 @@ function Util.transpose(t)
|
|||||||
return tt
|
return tt
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Util.contains(t, value)
|
||||||
|
for k,v in pairs(t) do
|
||||||
|
if v == value then
|
||||||
|
return k
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function Util.find(t, name, value)
|
function Util.find(t, name, value)
|
||||||
for k,v in pairs(t) do
|
for k,v in pairs(t) do
|
||||||
if v[name] == value then
|
if v[name] == value then
|
||||||
@@ -162,7 +197,7 @@ end
|
|||||||
|
|
||||||
function Util.findAll(t, name, value)
|
function Util.findAll(t, name, value)
|
||||||
local rt = { }
|
local rt = { }
|
||||||
for k,v in pairs(t) do
|
for _,v in pairs(t) do
|
||||||
if v[name] == value then
|
if v[name] == value then
|
||||||
table.insert(rt, v)
|
table.insert(rt, v)
|
||||||
end
|
end
|
||||||
@@ -217,7 +252,7 @@ end
|
|||||||
function Util.filter(it, f)
|
function Util.filter(it, f)
|
||||||
local ot = { }
|
local ot = { }
|
||||||
for k,v in pairs(it) do
|
for k,v in pairs(it) do
|
||||||
if f(k, v) then
|
if f(v) then
|
||||||
ot[k] = v
|
ot[k] = v
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -227,7 +262,9 @@ end
|
|||||||
function Util.size(list)
|
function Util.size(list)
|
||||||
if type(list) == 'table' then
|
if type(list) == 'table' then
|
||||||
local length = 0
|
local length = 0
|
||||||
table.foreach(list, function() length = length + 1 end)
|
for _ in pairs(list) do
|
||||||
|
length = length + 1
|
||||||
|
end
|
||||||
return length
|
return length
|
||||||
end
|
end
|
||||||
return 0
|
return 0
|
||||||
@@ -248,6 +285,19 @@ function Util.each(list, func)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Util.rpairs(t)
|
||||||
|
local tkeys = Util.keys(t)
|
||||||
|
local i = #tkeys
|
||||||
|
return function()
|
||||||
|
local key = tkeys[i]
|
||||||
|
local k,v = key, t[key]
|
||||||
|
i = i - 1
|
||||||
|
if v then
|
||||||
|
return k, v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- http://stackoverflow.com/questions/15706270/sort-a-table-in-lua
|
-- http://stackoverflow.com/questions/15706270/sort-a-table-in-lua
|
||||||
function Util.spairs(t, order)
|
function Util.spairs(t, order)
|
||||||
local keys = Util.keys(t)
|
local keys = Util.keys(t)
|
||||||
@@ -317,7 +367,7 @@ function Util.writeLines(fname, lines)
|
|||||||
local file = fs.open(fname, 'w')
|
local file = fs.open(fname, 'w')
|
||||||
if file then
|
if file then
|
||||||
for _,line in ipairs(lines) do
|
for _,line in ipairs(lines) do
|
||||||
line = file.writeLine(line)
|
file.writeLine(line)
|
||||||
end
|
end
|
||||||
file.close()
|
file.close()
|
||||||
return true
|
return true
|
||||||
@@ -351,13 +401,18 @@ function Util.loadTable(fname)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--[[ loading and running functions ]] --
|
--[[ loading and running functions ]] --
|
||||||
function Util.download(url, filename)
|
function Util.httpGet(url, headers)
|
||||||
local h = http.get(url)
|
local h, msg = http.get(url, headers)
|
||||||
if not h then
|
if h then
|
||||||
error('Failed to download ' .. url)
|
local contents = h.readAll()
|
||||||
|
h.close()
|
||||||
|
return contents
|
||||||
end
|
end
|
||||||
local contents = h.readAll()
|
return h, msg
|
||||||
h.close()
|
end
|
||||||
|
|
||||||
|
function Util.download(url, filename)
|
||||||
|
local contents = Util.httpGet(url)
|
||||||
if not contents then
|
if not contents then
|
||||||
error('Failed to download ' .. url)
|
error('Failed to download ' .. url)
|
||||||
end
|
end
|
||||||
@@ -369,23 +424,18 @@ function Util.download(url, filename)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Util.loadUrl(url, env) -- loadfile equivalent
|
function Util.loadUrl(url, env) -- loadfile equivalent
|
||||||
local s, m = pcall(function()
|
local c, msg = Util.httpGet(url)
|
||||||
local c = Util.download(url)
|
if not c then
|
||||||
return load(c, url, nil, env)
|
return c, msg
|
||||||
end)
|
|
||||||
|
|
||||||
if s then
|
|
||||||
return m
|
|
||||||
end
|
end
|
||||||
return s, m
|
return load(c, url, nil, env)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Util.runUrl(env, url, ...) -- os.run equivalent
|
function Util.runUrl(env, url, ...) -- os.run equivalent
|
||||||
setmetatable(env, { __index = _G })
|
setmetatable(env, { __index = _G })
|
||||||
local fn, m = Util.loadUrl(url, env)
|
local fn, m = Util.loadUrl(url, env)
|
||||||
if fn then
|
if fn then
|
||||||
local args = { ... }
|
return pcall(fn, ...)
|
||||||
return pcall(function() return fn(table.unpack(args)) end)
|
|
||||||
end
|
end
|
||||||
return fn, m
|
return fn, m
|
||||||
end
|
end
|
||||||
@@ -394,8 +444,7 @@ function Util.run(env, path, ...)
|
|||||||
setmetatable(env, { __index = _G })
|
setmetatable(env, { __index = _G })
|
||||||
local fn, m = loadfile(path, env)
|
local fn, m = loadfile(path, env)
|
||||||
if fn then
|
if fn then
|
||||||
local args = { ... }
|
return pcall(fn, ...)
|
||||||
return pcall(function() return fn(table.unpack(args)) end)
|
|
||||||
end
|
end
|
||||||
return fn, m
|
return fn, m
|
||||||
end
|
end
|
||||||
@@ -403,23 +452,22 @@ end
|
|||||||
function Util.runFunction(env, fn, ...)
|
function Util.runFunction(env, fn, ...)
|
||||||
setfenv(fn, env)
|
setfenv(fn, env)
|
||||||
setmetatable(env, { __index = _G })
|
setmetatable(env, { __index = _G })
|
||||||
|
return pcall(fn, ...)
|
||||||
local args = { ... }
|
|
||||||
return pcall(function() return fn(table.unpack(args)) end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[ String functions ]] --
|
--[[ String functions ]] --
|
||||||
function Util.toBytes(n)
|
function Util.toBytes(n)
|
||||||
|
if not tonumber(n) then error('Util.toBytes: n must be a number', 2) end
|
||||||
if n >= 1000000 or n <= -1000000 then
|
if n >= 1000000 or n <= -1000000 then
|
||||||
return string.format('%sM', Util.round(n/1000000, 1))
|
return string.format('%sM', math.floor(n/1000000 * 10) / 10)
|
||||||
elseif n >= 1000 or n <= -1000 then
|
elseif n >= 1000 or n <= -1000 then
|
||||||
return string.format('%sK', Util.round(n/1000, 1))
|
return string.format('%sK', math.floor(n/1000 * 10) / 10)
|
||||||
end
|
end
|
||||||
return tostring(n)
|
return tostring(n)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Util.insertString(os, is, pos)
|
function Util.insertString(str, istr, pos)
|
||||||
return os:sub(1, pos - 1) .. is .. os:sub(pos)
|
return str:sub(1, pos - 1) .. istr .. str:sub(pos)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Util.split(str, pattern)
|
function Util.split(str, pattern)
|
||||||
@@ -548,7 +596,7 @@ end
|
|||||||
|
|
||||||
function Util.showOptions(options)
|
function Util.showOptions(options)
|
||||||
print('Arguments: ')
|
print('Arguments: ')
|
||||||
for k, v in pairs(options) do
|
for _, v in pairs(options) do
|
||||||
print(string.format('-%s %s', v.arg, v.desc))
|
print(string.format('-%s %s', v.arg, v.desc))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -561,7 +609,6 @@ function Util.getOptions(options, args, ignoreInvalid)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
local rawOptions = getopt(args, argLetters)
|
local rawOptions = getopt(args, argLetters)
|
||||||
local argCount = 0
|
|
||||||
|
|
||||||
for k,ro in pairs(rawOptions) do
|
for k,ro in pairs(rawOptions) do
|
||||||
local found = false
|
local found = false
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
requireInjector(getfenv(1))
|
_G.requireInjector()
|
||||||
|
|
||||||
local Config = require('config')
|
local Config = require('config')
|
||||||
local Event = require('event')
|
local Event = require('event')
|
||||||
local UI = require('ui')
|
local UI = require('ui')
|
||||||
local Util = require('util')
|
local Util = require('util')
|
||||||
|
|
||||||
|
local colors = _G.colors
|
||||||
|
local fs = _G.fs
|
||||||
|
local multishell = _ENV.multishell
|
||||||
|
local os = _G.os
|
||||||
|
local shell = _ENV.shell
|
||||||
|
|
||||||
multishell.setTitle(multishell.getCurrent(), 'Files')
|
multishell.setTitle(multishell.getCurrent(), 'Files')
|
||||||
UI:configure('Files', ...)
|
UI:configure('Files', ...)
|
||||||
|
|
||||||
@@ -20,7 +26,7 @@ local marked = { }
|
|||||||
local directories = { }
|
local directories = { }
|
||||||
local cutMode = false
|
local cutMode = false
|
||||||
|
|
||||||
function formatSize(size)
|
local function formatSize(size)
|
||||||
if size >= 1000000 then
|
if size >= 1000000 then
|
||||||
return string.format('%dM', math.floor(size/1000000, 2))
|
return string.format('%dM', math.floor(size/1000000, 2))
|
||||||
elseif size >= 1000 then
|
elseif size >= 1000 then
|
||||||
@@ -32,48 +38,36 @@ end
|
|||||||
local Browser = UI.Page {
|
local Browser = UI.Page {
|
||||||
menuBar = UI.MenuBar {
|
menuBar = UI.MenuBar {
|
||||||
buttons = {
|
buttons = {
|
||||||
{ text = '^-', event = 'updir' },
|
{ text = '^-', event = 'updir' },
|
||||||
{ text = 'File', event = 'dropdown', dropdown = 'fileMenu' },
|
{ text = 'File', dropdown = {
|
||||||
{ text = 'Edit', event = 'dropdown', dropdown = 'editMenu' },
|
{ text = 'Run', event = 'run' },
|
||||||
{ text = 'View', event = 'dropdown', dropdown = 'viewMenu' },
|
{ text = 'Edit e', event = 'edit' },
|
||||||
|
{ text = 'Shell s', event = 'shell' },
|
||||||
|
UI.MenuBar.spacer,
|
||||||
|
{ text = 'Quit q', event = 'quit' },
|
||||||
|
} },
|
||||||
|
{ text = 'Edit', dropdown = {
|
||||||
|
{ text = 'Cut ^x', event = 'cut' },
|
||||||
|
{ text = 'Copy ^c', event = 'copy' },
|
||||||
|
{ text = 'Paste ^v', event = 'paste' },
|
||||||
|
UI.MenuBar.spacer,
|
||||||
|
{ text = 'Mark m', event = 'mark' },
|
||||||
|
{ text = 'Unmark all u', event = 'unmark' },
|
||||||
|
UI.MenuBar.spacer,
|
||||||
|
{ text = 'Delete del', event = 'delete' },
|
||||||
|
} },
|
||||||
|
{ text = 'View', dropdown = {
|
||||||
|
{ text = 'Refresh r', event = 'refresh' },
|
||||||
|
{ text = 'Hidden ^h', event = 'toggle_hidden' },
|
||||||
|
{ text = 'Dir Size ^s', event = 'toggle_dirSize' },
|
||||||
|
} },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
fileMenu = UI.DropMenu {
|
|
||||||
buttons = {
|
|
||||||
{ text = 'Run', event = 'run' },
|
|
||||||
{ text = 'Edit e', event = 'edit' },
|
|
||||||
{ text = 'Shell s', event = 'shell' },
|
|
||||||
UI.Text { value = ' ------------ ' },
|
|
||||||
{ text = 'Quit q', event = 'quit' },
|
|
||||||
UI.Text { },
|
|
||||||
}
|
|
||||||
},
|
|
||||||
editMenu = UI.DropMenu {
|
|
||||||
buttons = {
|
|
||||||
{ text = 'Cut ^x', event = 'cut' },
|
|
||||||
{ text = 'Copy ^c', event = 'copy' },
|
|
||||||
{ text = 'Paste ^v', event = 'paste' },
|
|
||||||
UI.Text { value = ' --------------- ' },
|
|
||||||
{ text = 'Mark m', event = 'mark' },
|
|
||||||
{ text = 'Unmark all u', event = 'unmark' },
|
|
||||||
UI.Text { value = ' --------------- ' },
|
|
||||||
{ text = 'Delete del', event = 'delete' },
|
|
||||||
UI.Text { },
|
|
||||||
}
|
|
||||||
},
|
|
||||||
viewMenu = UI.DropMenu {
|
|
||||||
buttons = {
|
|
||||||
{ text = 'Refresh r', event = 'refresh' },
|
|
||||||
{ text = 'Hidden ^h', event = 'toggle_hidden' },
|
|
||||||
{ text = 'Dir Size ^s', event = 'toggle_dirSize' },
|
|
||||||
UI.Text { },
|
|
||||||
}
|
|
||||||
},
|
|
||||||
grid = UI.ScrollingGrid {
|
grid = UI.ScrollingGrid {
|
||||||
columns = {
|
columns = {
|
||||||
{ heading = 'Name', key = 'name' },
|
{ heading = 'Name', key = 'name' },
|
||||||
{ key = 'flags', width = 2 },
|
{ key = 'flags', width = 2 },
|
||||||
{ heading = 'Size', key = 'fsize', width = 6 },
|
{ heading = 'Size', key = 'fsize', width = 5 },
|
||||||
},
|
},
|
||||||
sortColumn = 'name',
|
sortColumn = 'name',
|
||||||
y = 2, ey = -2,
|
y = 2, ey = -2,
|
||||||
@@ -96,6 +90,7 @@ local Browser = UI.Page {
|
|||||||
d = 'delete',
|
d = 'delete',
|
||||||
delete = 'delete',
|
delete = 'delete',
|
||||||
[ 'control-h' ] = 'toggle_hidden',
|
[ 'control-h' ] = 'toggle_hidden',
|
||||||
|
[ 'control-s' ] = 'toggle_dirSize',
|
||||||
[ 'control-x' ] = 'cut',
|
[ 'control-x' ] = 'cut',
|
||||||
[ 'control-c' ] = 'copy',
|
[ 'control-c' ] = 'copy',
|
||||||
paste = 'paste',
|
paste = 'paste',
|
||||||
@@ -107,6 +102,16 @@ function Browser:enable()
|
|||||||
self:setFocus(self.grid)
|
self:setFocus(self.grid)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Browser.menuBar:getActive(menuItem)
|
||||||
|
local file = Browser.grid:getSelected()
|
||||||
|
if file then
|
||||||
|
if menuItem.event == 'edit' or menuItem.event == 'run' then
|
||||||
|
return not file.isDir
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
function Browser.grid:sortCompare(a, b)
|
function Browser.grid:sortCompare(a, b)
|
||||||
if self.sortColumn == 'fsize' then
|
if self.sortColumn == 'fsize' then
|
||||||
return a.size < b.size
|
return a.size < b.size
|
||||||
@@ -119,7 +124,7 @@ function Browser.grid:sortCompare(a, b)
|
|||||||
return a.isDir
|
return a.isDir
|
||||||
end
|
end
|
||||||
|
|
||||||
function Browser.grid:getRowTextColor(file, selected)
|
function Browser.grid:getRowTextColor(file)
|
||||||
if file.marked then
|
if file.marked then
|
||||||
return colors.green
|
return colors.green
|
||||||
end
|
end
|
||||||
@@ -132,13 +137,6 @@ function Browser.grid:getRowTextColor(file, selected)
|
|||||||
return colors.white
|
return colors.white
|
||||||
end
|
end
|
||||||
|
|
||||||
function Browser.grid:getRowBackgroundColorX(file, selected)
|
|
||||||
if selected then
|
|
||||||
return colors.gray
|
|
||||||
end
|
|
||||||
return self.backgroundColor
|
|
||||||
end
|
|
||||||
|
|
||||||
function Browser.grid:eventHandler(event)
|
function Browser.grid:eventHandler(event)
|
||||||
if event.type == 'copy' then -- let copy be handled by parent
|
if event.type == 'copy' then -- let copy be handled by parent
|
||||||
return false
|
return false
|
||||||
@@ -164,14 +162,13 @@ function Browser:setStatus(status, ...)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Browser:unmarkAll()
|
function Browser:unmarkAll()
|
||||||
for k,m in pairs(marked) do
|
for _,m in pairs(marked) do
|
||||||
m.marked = false
|
m.marked = false
|
||||||
end
|
end
|
||||||
Util.clear(marked)
|
Util.clear(marked)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Browser:getDirectory(directory)
|
function Browser:getDirectory(directory)
|
||||||
|
|
||||||
local s, dir = pcall(function()
|
local s, dir = pcall(function()
|
||||||
|
|
||||||
local dir = directories[directory]
|
local dir = directories[directory]
|
||||||
@@ -205,7 +202,6 @@ function Browser:updateDirectory(dir)
|
|||||||
dir.size = #files
|
dir.size = #files
|
||||||
for _, file in pairs(files) do
|
for _, file in pairs(files) do
|
||||||
file.fullName = fs.combine(dir.name, file.name)
|
file.fullName = fs.combine(dir.name, file.name)
|
||||||
file.directory = directory
|
|
||||||
file.flags = ''
|
file.flags = ''
|
||||||
if not file.isDir then
|
if not file.isDir then
|
||||||
dir.totalSize = dir.totalSize + file.size
|
dir.totalSize = dir.totalSize + file.size
|
||||||
@@ -239,7 +235,7 @@ function Browser:setDir(dirName, noStatus)
|
|||||||
if self.dir then
|
if self.dir then
|
||||||
self.dir.index = self.grid:getIndex()
|
self.dir.index = self.grid:getIndex()
|
||||||
end
|
end
|
||||||
DIR = fs.combine('', dirName)
|
local DIR = fs.combine('', dirName)
|
||||||
shell.setDir(DIR)
|
shell.setDir(DIR)
|
||||||
local s, dir = self:getDirectory(DIR)
|
local s, dir = self:getDirectory(DIR)
|
||||||
if s then
|
if s then
|
||||||
@@ -352,7 +348,7 @@ function Browser:eventHandler(event)
|
|||||||
self.statusBar:sync()
|
self.statusBar:sync()
|
||||||
local _, ch = os.pullEvent('char')
|
local _, ch = os.pullEvent('char')
|
||||||
if ch == 'y' or ch == 'Y' then
|
if ch == 'y' or ch == 'Y' then
|
||||||
for k,m in pairs(marked) do
|
for _,m in pairs(marked) do
|
||||||
pcall(function()
|
pcall(function()
|
||||||
fs.delete(m.fullName)
|
fs.delete(m.fullName)
|
||||||
end)
|
end)
|
||||||
@@ -379,7 +375,7 @@ function Browser:eventHandler(event)
|
|||||||
end
|
end
|
||||||
|
|
||||||
elseif event.type == 'paste' then
|
elseif event.type == 'paste' then
|
||||||
for k,m in pairs(copied) do
|
for _,m in pairs(copied) do
|
||||||
local s, m = pcall(function()
|
local s, m = pcall(function()
|
||||||
if cutMode then
|
if cutMode then
|
||||||
fs.move(m.fullName, fs.combine(self.dir.name, m.name))
|
fs.move(m.fullName, fs.combine(self.dir.name, m.name))
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
requireInjector(getfenv(1))
|
_G.requireInjector()
|
||||||
|
|
||||||
local Event = require('event')
|
|
||||||
local UI = require('ui')
|
local UI = require('ui')
|
||||||
|
local Util = require('util')
|
||||||
|
|
||||||
|
local colors = _G.colors
|
||||||
|
local help = _G.help
|
||||||
|
local multishell = _ENV.multishell
|
||||||
|
|
||||||
multishell.setTitle(multishell.getCurrent(), 'Help')
|
multishell.setTitle(multishell.getCurrent(), 'Help')
|
||||||
UI:configure('Help', ...)
|
UI:configure('Help', ...)
|
||||||
|
|
||||||
local files = { }
|
local topics = { }
|
||||||
for _,f in pairs(help.topics()) do
|
for _,topic in pairs(help.topics()) do
|
||||||
table.insert(files, { name = f })
|
if help.lookup(topic) then
|
||||||
|
table.insert(topics, { name = topic })
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local page = UI.Page {
|
local page = UI.Page {
|
||||||
@@ -22,9 +28,9 @@ local page = UI.Page {
|
|||||||
},
|
},
|
||||||
grid = UI.ScrollingGrid {
|
grid = UI.ScrollingGrid {
|
||||||
y = 4,
|
y = 4,
|
||||||
values = files,
|
values = topics,
|
||||||
columns = {
|
columns = {
|
||||||
{ heading = 'Name', key = 'name' },
|
{ heading = 'Topic', key = 'name' },
|
||||||
},
|
},
|
||||||
sortColumn = 'name',
|
sortColumn = 'name',
|
||||||
},
|
},
|
||||||
@@ -34,36 +40,51 @@ local page = UI.Page {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
local function showHelp(name)
|
local topicPage = UI.Page {
|
||||||
UI.term:reset()
|
backgroundColor = colors.black,
|
||||||
shell.run('help ' .. name)
|
titleBar = UI.TitleBar {
|
||||||
print('Press enter to return')
|
title = 'text',
|
||||||
repeat
|
previousPage = true,
|
||||||
os.pullEvent('key')
|
},
|
||||||
local _, k = os.pullEvent('key_up')
|
helpText = UI.TextArea {
|
||||||
until k == keys.enter
|
backgroundColor = colors.black,
|
||||||
|
x = 2, ex = -1, y = 3, ey = -2,
|
||||||
|
},
|
||||||
|
accelerators = {
|
||||||
|
q = 'back',
|
||||||
|
backspace = 'back',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function topicPage:eventHandler(event)
|
||||||
|
if event.type == 'back' then
|
||||||
|
UI:setPreviousPage()
|
||||||
|
end
|
||||||
|
return UI.Page.eventHandler(self, event)
|
||||||
end
|
end
|
||||||
|
|
||||||
function page:eventHandler(event)
|
function page:eventHandler(event)
|
||||||
|
|
||||||
if event.type == 'quit' then
|
if event.type == 'quit' then
|
||||||
Event.exitPullEvents()
|
UI:exitPullEvents()
|
||||||
|
|
||||||
elseif event.type == 'grid_select' then
|
elseif event.type == 'grid_select' then
|
||||||
if self.grid:getSelected() then
|
if self.grid:getSelected() then
|
||||||
showHelp(self.grid:getSelected().name)
|
local name = self.grid:getSelected().name
|
||||||
self:setFocus(self.filter)
|
local f = help.lookup(name)
|
||||||
self:draw()
|
|
||||||
|
topicPage.titleBar.title = name
|
||||||
|
topicPage.helpText:setText(Util.readFile(f))
|
||||||
|
|
||||||
|
UI:setPage(topicPage)
|
||||||
end
|
end
|
||||||
|
|
||||||
elseif event.type == 'text_change' then
|
elseif event.type == 'text_change' then
|
||||||
local text = event.text
|
if #event.text == 0 then
|
||||||
if #text == 0 then
|
self.grid.values = topics
|
||||||
self.grid.values = files
|
|
||||||
else
|
else
|
||||||
self.grid.values = { }
|
self.grid.values = { }
|
||||||
for _,f in pairs(files) do
|
for _,f in pairs(topics) do
|
||||||
if string.find(f.name, text) then
|
if string.find(f.name, event.text) then
|
||||||
table.insert(self.grid.values, f)
|
table.insert(self.grid.values, f)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -72,7 +93,7 @@ function page:eventHandler(event)
|
|||||||
self.grid:setIndex(1)
|
self.grid:setIndex(1)
|
||||||
self.grid:draw()
|
self.grid:draw()
|
||||||
else
|
else
|
||||||
UI.Page.eventHandler(self, event)
|
return UI.Page.eventHandler(self, event)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,28 @@
|
|||||||
requireInjector = requireInjector or load(http.get('https://raw.githubusercontent.com/kepler155c/opus/master/sys/apis/injector.lua').readAll())()
|
local injector = _G.requireInjector or load(http.get('https://raw.githubusercontent.com/kepler155c/opus/master/sys/apis/injector.lua').readAll())()
|
||||||
requireInjector(getfenv(1))
|
injector()
|
||||||
|
|
||||||
local Event = require('event')
|
local Event = require('event')
|
||||||
local History = require('history')
|
local History = require('history')
|
||||||
local UI = require('ui')
|
local Peripheral = require('peripheral')
|
||||||
local Util = require('util')
|
local UI = require('ui')
|
||||||
|
local Util = require('util')
|
||||||
|
|
||||||
local sandboxEnv = setmetatable(Util.shallowCopy(getfenv(1)), { __index = _G })
|
local colors = _G.colors
|
||||||
|
local multishell = _ENV.multishell
|
||||||
|
local os = _G.os
|
||||||
|
local textutils = _G.textutils
|
||||||
|
|
||||||
|
local sandboxEnv = setmetatable(Util.shallowCopy(_ENV), { __index = _G })
|
||||||
sandboxEnv.exit = function() Event.exitPullEvents() end
|
sandboxEnv.exit = function() Event.exitPullEvents() end
|
||||||
sandboxEnv._echo = function( ... ) return ... end
|
sandboxEnv._echo = function( ... ) return { ... } end
|
||||||
requireInjector(sandboxEnv)
|
injector(sandboxEnv)
|
||||||
|
|
||||||
multishell.setTitle(multishell.getCurrent(), 'Lua')
|
multishell.setTitle(multishell.getCurrent(), 'Lua')
|
||||||
UI:configure('Lua', ...)
|
UI:configure('Lua', ...)
|
||||||
|
|
||||||
local command = ''
|
local command = ''
|
||||||
local history = History.load('usr/.lua_history', 25)
|
local history = History.load('usr/.lua_history', 25)
|
||||||
|
local extChars = Util.getVersion() > 1.76
|
||||||
|
|
||||||
local page = UI.Page {
|
local page = UI.Page {
|
||||||
menuBar = UI.MenuBar {
|
menuBar = UI.MenuBar {
|
||||||
@@ -34,9 +41,13 @@ local page = UI.Page {
|
|||||||
up = 'history_back',
|
up = 'history_back',
|
||||||
down = 'history_forward',
|
down = 'history_forward',
|
||||||
mouse_rightclick = 'clear_prompt',
|
mouse_rightclick = 'clear_prompt',
|
||||||
-- [ 'control-space' ] = 'autocomplete',
|
[ 'control-space' ] = 'autocomplete',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
indicator = UI.Text {
|
||||||
|
backgroundColor = colors.black,
|
||||||
|
y = 2, x = -1, width = 1,
|
||||||
|
},
|
||||||
grid = UI.ScrollingGrid {
|
grid = UI.ScrollingGrid {
|
||||||
y = 3,
|
y = 3,
|
||||||
columns = {
|
columns = {
|
||||||
@@ -49,6 +60,17 @@ local page = UI.Page {
|
|||||||
notification = UI.Notification(),
|
notification = UI.Notification(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function page.indicator:showResult(s)
|
||||||
|
local values = {
|
||||||
|
[ true ] = { c = colors.green, i = (extChars and '\003') or '+' },
|
||||||
|
[ false ] = { c = colors.red, i = 'x' }
|
||||||
|
}
|
||||||
|
|
||||||
|
self.textColor = values[s].c
|
||||||
|
self.value = values[s].i
|
||||||
|
self:draw()
|
||||||
|
end
|
||||||
|
|
||||||
function page:setPrompt(value, focus)
|
function page:setPrompt(value, focus)
|
||||||
self.prompt:setValue(value)
|
self.prompt:setValue(value)
|
||||||
self.prompt.scroll = 0
|
self.prompt.scroll = 0
|
||||||
@@ -71,7 +93,6 @@ function page:enable()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function autocomplete(env, oLine, x)
|
local function autocomplete(env, oLine, x)
|
||||||
|
|
||||||
local sLine = oLine:sub(1, x)
|
local sLine = oLine:sub(1, x)
|
||||||
local nStartPos = sLine:find("[a-zA-Z0-9_%.]+$")
|
local nStartPos = sLine:find("[a-zA-Z0-9_%.]+$")
|
||||||
if nStartPos then
|
if nStartPos then
|
||||||
@@ -81,10 +102,7 @@ local function autocomplete(env, oLine, x)
|
|||||||
if #sLine > 0 then
|
if #sLine > 0 then
|
||||||
local results = textutils.complete(sLine, env)
|
local results = textutils.complete(sLine, env)
|
||||||
|
|
||||||
if #results == 0 then
|
if #results == 1 then
|
||||||
-- setError('No completions available')
|
|
||||||
|
|
||||||
elseif #results == 1 then
|
|
||||||
return Util.insertString(oLine, results[1], x + 1)
|
return Util.insertString(oLine, results[1], x + 1)
|
||||||
|
|
||||||
elseif #results > 1 then
|
elseif #results > 1 then
|
||||||
@@ -100,8 +118,6 @@ local function autocomplete(env, oLine, x)
|
|||||||
end
|
end
|
||||||
if #prefix > 0 then
|
if #prefix > 0 then
|
||||||
return Util.insertString(oLine, prefix, x + 1)
|
return Util.insertString(oLine, prefix, x + 1)
|
||||||
else
|
|
||||||
-- setStatus('Too many results')
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -109,15 +125,14 @@ local function autocomplete(env, oLine, x)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function page:eventHandler(event)
|
function page:eventHandler(event)
|
||||||
|
|
||||||
if event.type == 'global' then
|
if event.type == 'global' then
|
||||||
self:setPrompt('', true)
|
self:setPrompt('', true)
|
||||||
self:executeStatement('getfenv(0)')
|
self:executeStatement('_G')
|
||||||
command = nil
|
command = nil
|
||||||
|
|
||||||
elseif event.type == 'local' then
|
elseif event.type == 'local' then
|
||||||
self:setPrompt('', true)
|
self:setPrompt('', true)
|
||||||
self:executeStatement('getfenv(1)')
|
self:executeStatement('_ENV')
|
||||||
command = nil
|
command = nil
|
||||||
|
|
||||||
elseif event.type == 'autocomplete' then
|
elseif event.type == 'autocomplete' then
|
||||||
@@ -129,11 +144,7 @@ function page:eventHandler(event)
|
|||||||
|
|
||||||
elseif event.type == 'device' then
|
elseif event.type == 'device' then
|
||||||
if not _G.device then
|
if not _G.device then
|
||||||
sandboxEnv.device = { }
|
sandboxEnv.device = Peripheral.getList()
|
||||||
for _,side in pairs(peripheral.getNames()) do
|
|
||||||
local key = string.format('%s:%s', peripheral.getType(side), side)
|
|
||||||
sandboxEnv.device[ key ] = peripheral.wrap(side)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
self:setPrompt('device', true)
|
self:setPrompt('device', true)
|
||||||
self:executeStatement('device')
|
self:executeStatement('device')
|
||||||
@@ -187,8 +198,7 @@ function page:setResult(result)
|
|||||||
local t = { }
|
local t = { }
|
||||||
|
|
||||||
local function safeValue(v)
|
local function safeValue(v)
|
||||||
local t = type(v)
|
if type(v) == 'string' or type(v) == 'number' then
|
||||||
if t == 'string' or t == 'number' then
|
|
||||||
return v
|
return v
|
||||||
end
|
end
|
||||||
return tostring(v)
|
return tostring(v)
|
||||||
@@ -206,7 +216,7 @@ function page:setResult(result)
|
|||||||
if Util.size(v) == 0 then
|
if Util.size(v) == 0 then
|
||||||
entry.value = 'table: (empty)'
|
entry.value = 'table: (empty)'
|
||||||
else
|
else
|
||||||
entry.value = 'table'
|
entry.value = tostring(v)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
table.insert(t, entry)
|
table.insert(t, entry)
|
||||||
@@ -225,7 +235,6 @@ function page:setResult(result)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function page.grid:eventHandler(event)
|
function page.grid:eventHandler(event)
|
||||||
|
|
||||||
local entry = self:getSelected()
|
local entry = self:getSelected()
|
||||||
|
|
||||||
local function commandAppend()
|
local function commandAppend()
|
||||||
@@ -259,9 +268,10 @@ function page.grid:eventHandler(event)
|
|||||||
elseif event.type == 'grid_select' then
|
elseif event.type == 'grid_select' then
|
||||||
page:setPrompt(commandAppend(), true)
|
page:setPrompt(commandAppend(), true)
|
||||||
page:executeStatement(commandAppend())
|
page:executeStatement(commandAppend())
|
||||||
|
|
||||||
elseif event.type == 'copy' then
|
elseif event.type == 'copy' then
|
||||||
if entry then
|
if entry then
|
||||||
clipboard.setData(entry.rawValue)
|
os.queueEvent('clipboard_copy', entry.rawValue)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
return UI.ScrollingGrid.eventHandler(self, event)
|
return UI.ScrollingGrid.eventHandler(self, event)
|
||||||
@@ -270,10 +280,16 @@ function page.grid:eventHandler(event)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function page:rawExecute(s)
|
function page:rawExecute(s)
|
||||||
local fn, m = load('return _echo(' ..s.. ');', 'lua', nil, sandboxEnv)
|
local fn, m
|
||||||
|
|
||||||
|
fn = load('return (' ..s.. ')', 'lua', nil, sandboxEnv)
|
||||||
|
|
||||||
if fn then
|
if fn then
|
||||||
m = { pcall(fn) }
|
fn = load('return {' ..s.. '}', 'lua', nil, sandboxEnv)
|
||||||
fn = table.remove(m, 1)
|
end
|
||||||
|
|
||||||
|
if fn then
|
||||||
|
fn, m = pcall(fn)
|
||||||
if #m == 1 then
|
if #m == 1 then
|
||||||
m = m[1]
|
m = m[1]
|
||||||
end
|
end
|
||||||
@@ -289,7 +305,6 @@ function page:rawExecute(s)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function page:executeStatement(statement)
|
function page:executeStatement(statement)
|
||||||
|
|
||||||
command = statement
|
command = statement
|
||||||
|
|
||||||
local s, m = self:rawExecute(command)
|
local s, m = self:rawExecute(command)
|
||||||
@@ -303,6 +318,7 @@ function page:executeStatement(statement)
|
|||||||
self.notification:error(m, 5)
|
self.notification:error(m, 5)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
self.indicator:showResult(not not s)
|
||||||
end
|
end
|
||||||
|
|
||||||
local args = { ... }
|
local args = { ... }
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
requireInjector(getfenv(1))
|
_G.requireInjector()
|
||||||
|
|
||||||
local Event = require('event')
|
local Event = require('event')
|
||||||
local Socket = require('socket')
|
local Socket = require('socket')
|
||||||
local UI = require('ui')
|
local UI = require('ui')
|
||||||
local Util = require('util')
|
local Util = require('util')
|
||||||
|
|
||||||
|
local colors = _G.colors
|
||||||
|
local device = _G.device
|
||||||
|
local multishell = _ENV.multishell
|
||||||
|
local network = _G.network
|
||||||
|
local os = _G.os
|
||||||
|
local shell = _ENV.shell
|
||||||
|
|
||||||
multishell.setTitle(multishell.getCurrent(), 'Network')
|
multishell.setTitle(multishell.getCurrent(), 'Network')
|
||||||
UI:configure('Network', ...)
|
UI:configure('Network', ...)
|
||||||
|
|
||||||
@@ -22,10 +29,18 @@ end
|
|||||||
local page = UI.Page {
|
local page = UI.Page {
|
||||||
menuBar = UI.MenuBar {
|
menuBar = UI.MenuBar {
|
||||||
buttons = {
|
buttons = {
|
||||||
{ text = 'Telnet', event = 'telnet' },
|
{ text = 'Connect', dropdown = {
|
||||||
{ text = 'VNC', event = 'vnc' },
|
{ text = 'Telnet t', event = 'telnet' },
|
||||||
{ text = 'Trust', event = 'trust' },
|
{ text = 'VNC v', event = 'vnc' },
|
||||||
{ text = 'Reboot', event = 'reboot' },
|
UI.MenuBar.spacer,
|
||||||
|
{ text = 'Reboot r', event = 'reboot' },
|
||||||
|
} },
|
||||||
|
--{ text = 'Chat', event = 'chat' },
|
||||||
|
{ text = 'Trust', dropdown = {
|
||||||
|
{ text = 'Establish', event = 'trust' },
|
||||||
|
{ text = 'Remove', event = 'untrust' },
|
||||||
|
} },
|
||||||
|
{ text = 'Help', event = 'help' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
grid = UI.ScrollingGrid {
|
grid = UI.ScrollingGrid {
|
||||||
@@ -37,13 +52,15 @@ local page = UI.Page {
|
|||||||
},
|
},
|
||||||
notification = UI.Notification { },
|
notification = UI.Notification { },
|
||||||
accelerators = {
|
accelerators = {
|
||||||
|
t = 'telnet',
|
||||||
|
v = 'vnc',
|
||||||
|
r = 'reboot',
|
||||||
q = 'quit',
|
q = 'quit',
|
||||||
c = 'clear',
|
c = 'clear',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
local function sendCommand(host, command)
|
local function sendCommand(host, command)
|
||||||
|
|
||||||
if not device.wireless_modem then
|
if not device.wireless_modem then
|
||||||
page.notification:error('Wireless modem not present')
|
page.notification:error('Wireless modem not present')
|
||||||
return
|
return
|
||||||
@@ -65,7 +82,7 @@ end
|
|||||||
function page:eventHandler(event)
|
function page:eventHandler(event)
|
||||||
local t = self.grid:getSelected()
|
local t = self.grid:getSelected()
|
||||||
if t then
|
if t then
|
||||||
if event.type == 'telnet' or event.type == 'grid_select' then
|
if event.type == 'telnet' then
|
||||||
multishell.openTab({
|
multishell.openTab({
|
||||||
path = 'sys/apps/telnet.lua',
|
path = 'sys/apps/telnet.lua',
|
||||||
focused = true,
|
focused = true,
|
||||||
@@ -79,20 +96,69 @@ function page:eventHandler(event)
|
|||||||
args = { t.id },
|
args = { t.id },
|
||||||
title = t.label,
|
title = t.label,
|
||||||
})
|
})
|
||||||
|
elseif event.type == 'clear' then
|
||||||
|
Util.clear(network)
|
||||||
|
page.grid:update()
|
||||||
|
page.grid:draw()
|
||||||
|
|
||||||
elseif event.type == 'trust' then
|
elseif event.type == 'trust' then
|
||||||
shell.openForegroundTab('trust ' .. t.id)
|
shell.openForegroundTab('trust ' .. t.id)
|
||||||
|
|
||||||
|
elseif event.type == 'untrust' then
|
||||||
|
local trustList = Util.readTable('usr/.known_hosts') or { }
|
||||||
|
trustList[t.id] = nil
|
||||||
|
Util.writeTable('usr/.known_hosts', trustList)
|
||||||
|
|
||||||
|
elseif event.type == 'chat' then
|
||||||
|
multishell.openTab({
|
||||||
|
path = 'sys/apps/shell',
|
||||||
|
args = { 'chat join opusChat-' .. t.id .. ' guest-' .. os.getComputerID() },
|
||||||
|
title = 'Chatroom',
|
||||||
|
focused = true,
|
||||||
|
})
|
||||||
elseif event.type == 'reboot' then
|
elseif event.type == 'reboot' then
|
||||||
sendCommand(t.id, 'reboot')
|
sendCommand(t.id, 'reboot')
|
||||||
|
|
||||||
elseif event.type == 'shutdown' then
|
elseif event.type == 'shutdown' then
|
||||||
sendCommand(t.id, 'shutdown')
|
sendCommand(t.id, 'shutdown')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if event.type == 'quit' then
|
if event.type == 'help' then
|
||||||
|
UI:setPage(UI.Dialog {
|
||||||
|
title = 'Network Help',
|
||||||
|
height = 10,
|
||||||
|
backgroundColor = colors.white,
|
||||||
|
text = UI.TextArea {
|
||||||
|
x = 2, y = 2,
|
||||||
|
backgroundColor = colors.white,
|
||||||
|
value = [[
|
||||||
|
In order to connect to another computer:
|
||||||
|
|
||||||
|
1. The target computer must have a password set (run 'password' from the shell prompt).
|
||||||
|
2. From this computer, click trust and enter the password for that computer.
|
||||||
|
|
||||||
|
This only needs to be done once.
|
||||||
|
]],
|
||||||
|
},
|
||||||
|
accelerators = {
|
||||||
|
q = 'cancel',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
elseif event.type == 'quit' then
|
||||||
Event.exitPullEvents()
|
Event.exitPullEvents()
|
||||||
end
|
end
|
||||||
UI.Page.eventHandler(self, event)
|
UI.Page.eventHandler(self, event)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function page.menuBar:getActive(menuItem)
|
||||||
|
local t = page.grid:getSelected()
|
||||||
|
if menuItem.event == 'untrust' then
|
||||||
|
local trustList = Util.readTable('usr/.known_hosts') or { }
|
||||||
|
return t and trustList[t.id]
|
||||||
|
end
|
||||||
|
return not not t
|
||||||
|
end
|
||||||
|
|
||||||
function page.grid:getRowTextColor(row, selected)
|
function page.grid:getRowTextColor(row, selected)
|
||||||
if not row.active then
|
if not row.active then
|
||||||
return colors.orange
|
return colors.orange
|
||||||
@@ -124,14 +190,14 @@ Event.onInterval(1, function()
|
|||||||
page:sync()
|
page:sync()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
Event.on('device_attach', function(h, deviceName)
|
Event.on('device_attach', function(_, deviceName)
|
||||||
if deviceName == 'wireless_modem' then
|
if deviceName == 'wireless_modem' then
|
||||||
page.notification:success('Modem connected')
|
page.notification:success('Modem connected')
|
||||||
page:sync()
|
page:sync()
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
Event.on('device_detach', function(h, deviceName)
|
Event.on('device_detach', function(_, deviceName)
|
||||||
if deviceName == 'wireless_modem' then
|
if deviceName == 'wireless_modem' then
|
||||||
page.notification:error('Wireless modem not attached')
|
page.notification:error('Wireless modem not attached')
|
||||||
page:sync()
|
page:sync()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
requireInjector(getfenv(1))
|
_G.requireInjector()
|
||||||
|
|
||||||
local class = require('class')
|
local class = require('class')
|
||||||
local Config = require('config')
|
local Config = require('config')
|
||||||
@@ -10,19 +10,13 @@ local Tween = require('ui.tween')
|
|||||||
local UI = require('ui')
|
local UI = require('ui')
|
||||||
local Util = require('util')
|
local Util = require('util')
|
||||||
|
|
||||||
local REGISTRY_DIR = 'usr/.registry'
|
local fs = _G.fs
|
||||||
local TEMPLATE = [[
|
local multishell = _ENV.multishell
|
||||||
local env = { }
|
local pocket = _G.pocket
|
||||||
for k,v in pairs(getfenv(1)) do
|
local term = _G.term
|
||||||
env[k] = v
|
local turtle = _G.turtle
|
||||||
end
|
|
||||||
setmetatable(env, { __index = _G })
|
|
||||||
|
|
||||||
local s, m = os.run(env, 'sys/apps/appRun.lua', %s, ...)
|
local REGISTRY_DIR = 'usr/.registry'
|
||||||
if not s then
|
|
||||||
error(m)
|
|
||||||
end
|
|
||||||
]]
|
|
||||||
|
|
||||||
multishell.setTitle(multishell.getCurrent(), 'Overview')
|
multishell.setTitle(multishell.getCurrent(), 'Overview')
|
||||||
UI:configure('Overview', ...)
|
UI:configure('Overview', ...)
|
||||||
@@ -59,7 +53,7 @@ local function loadApplications()
|
|||||||
end
|
end
|
||||||
|
|
||||||
Util.each(applications, function(v, k) v.key = k end)
|
Util.each(applications, function(v, k) v.key = k end)
|
||||||
applications = Util.filter(applications, function(_, a)
|
applications = Util.filter(applications, function(a)
|
||||||
if a.disabled then
|
if a.disabled then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
@@ -93,13 +87,14 @@ end
|
|||||||
|
|
||||||
local buttons = { }
|
local buttons = { }
|
||||||
local categories = { }
|
local categories = { }
|
||||||
table.insert(buttons, { text = 'Recent', event = 'category' })
|
|
||||||
for _,f in pairs(applications) do
|
for _,f in pairs(applications) do
|
||||||
if not categories[f.category] then
|
if not categories[f.category] then
|
||||||
categories[f.category] = true
|
categories[f.category] = true
|
||||||
table.insert(buttons, { text = f.category, event = 'category' })
|
table.insert(buttons, { text = f.category })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
table.sort(buttons, function(a, b) return a.text < b.text end)
|
||||||
|
table.insert(buttons, 1, { text = 'Recent' })
|
||||||
table.insert(buttons, { text = '+', event = 'new' })
|
table.insert(buttons, { text = '+', event = 'new' })
|
||||||
|
|
||||||
local function parseIcon(iconText)
|
local function parseIcon(iconText)
|
||||||
@@ -123,15 +118,16 @@ local function parseIcon(iconText)
|
|||||||
end
|
end
|
||||||
|
|
||||||
UI.VerticalTabBar = class(UI.TabBar)
|
UI.VerticalTabBar = class(UI.TabBar)
|
||||||
function UI.VerticalTabBar:init(args)
|
function UI.VerticalTabBar:setParent()
|
||||||
UI.TabBar.init(self, args)
|
|
||||||
self.x = 1
|
self.x = 1
|
||||||
self.width = 8
|
self.width = 8
|
||||||
self.height = nil
|
self.height = nil
|
||||||
self.ey = -1
|
self.ey = -1
|
||||||
|
UI.TabBar.setParent(self)
|
||||||
for k,c in pairs(self.children) do
|
for k,c in pairs(self.children) do
|
||||||
c.x = 1
|
c.x = 1
|
||||||
c.y = k + 1
|
c.y = k + 1
|
||||||
|
c.ox, c.oy = c.x, c.y
|
||||||
c.width = 8
|
c.width = 8
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -148,7 +144,7 @@ local page = UI.Page {
|
|||||||
tabBar = UI.VerticalTabBar {
|
tabBar = UI.VerticalTabBar {
|
||||||
buttons = buttons,
|
buttons = buttons,
|
||||||
},
|
},
|
||||||
container = UI.ViewportWindow {
|
container = UI.Viewport {
|
||||||
x = cx,
|
x = cx,
|
||||||
y = cy,
|
y = cy,
|
||||||
},
|
},
|
||||||
@@ -159,28 +155,17 @@ local page = UI.Page {
|
|||||||
f = 'files',
|
f = 'files',
|
||||||
s = 'shell',
|
s = 'shell',
|
||||||
l = 'lua',
|
l = 'lua',
|
||||||
[ 'control-l' ] = 'refresh',
|
|
||||||
[ 'control-n' ] = 'new',
|
[ 'control-n' ] = 'new',
|
||||||
delete = 'delete',
|
delete = 'delete',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
function page:draw()
|
|
||||||
self.tabBar:draw()
|
|
||||||
self.container:draw()
|
|
||||||
end
|
|
||||||
|
|
||||||
UI.Icon = class(UI.Window)
|
UI.Icon = class(UI.Window)
|
||||||
function UI.Icon:init(args)
|
UI.Icon.defaults = {
|
||||||
local defaults = {
|
UIElement = 'Icon',
|
||||||
UIElement = 'Icon',
|
width = 14,
|
||||||
width = 14,
|
height = 4,
|
||||||
height = 4,
|
}
|
||||||
}
|
|
||||||
UI:setProperties(defaults, args)
|
|
||||||
UI.Window.init(self, defaults)
|
|
||||||
end
|
|
||||||
|
|
||||||
function UI.Icon:eventHandler(event)
|
function UI.Icon:eventHandler(event)
|
||||||
if event.type == 'mouse_click' then
|
if event.type == 'mouse_click' then
|
||||||
self:setFocus(self.button)
|
self:setFocus(self.button)
|
||||||
@@ -194,7 +179,7 @@ function UI.Icon:eventHandler(event)
|
|||||||
return UI.Window.eventHandler(self, event)
|
return UI.Window.eventHandler(self, event)
|
||||||
end
|
end
|
||||||
|
|
||||||
function page.container:setCategory(categoryName)
|
function page.container:setCategory(categoryName, animate)
|
||||||
|
|
||||||
-- reset the viewport window
|
-- reset the viewport window
|
||||||
self.children = { }
|
self.children = { }
|
||||||
@@ -255,7 +240,9 @@ function page.container:setCategory(categoryName)
|
|||||||
y = 4,
|
y = 4,
|
||||||
text = title,
|
text = title,
|
||||||
backgroundColor = self.backgroundColor,
|
backgroundColor = self.backgroundColor,
|
||||||
--backgroundFocusColor = colors.gray,
|
backgroundFocusColor = colors.gray,
|
||||||
|
textColor = colors.white,
|
||||||
|
textFocusColor = colors.white,
|
||||||
width = #title + 2,
|
width = #title + 2,
|
||||||
event = 'button',
|
event = 'button',
|
||||||
app = program,
|
app = program,
|
||||||
@@ -270,7 +257,7 @@ function page.container:setCategory(categoryName)
|
|||||||
local col, row = gutter, 2
|
local col, row = gutter, 2
|
||||||
local count = #self.children
|
local count = #self.children
|
||||||
|
|
||||||
local r = math.random(1, 4)
|
local r = math.random(1, 5)
|
||||||
-- reposition all children
|
-- reposition all children
|
||||||
for k,child in ipairs(self.children) do
|
for k,child in ipairs(self.children) do
|
||||||
if r == 1 then
|
if r == 1 then
|
||||||
@@ -285,9 +272,21 @@ function page.container:setCategory(categoryName)
|
|||||||
elseif r == 4 then
|
elseif r == 4 then
|
||||||
child.x = self.width - col
|
child.x = self.width - col
|
||||||
child.y = row
|
child.y = row
|
||||||
|
elseif r == 5 then
|
||||||
|
child.x = col
|
||||||
|
child.y = row
|
||||||
|
if k == #self.children then
|
||||||
|
child.x = self.width
|
||||||
|
child.y = self.height
|
||||||
|
end
|
||||||
end
|
end
|
||||||
child.tween = Tween.new(6, child, { x = col, y = row }, 'linear')
|
child.tween = Tween.new(6, child, { x = col, y = row }, 'linear')
|
||||||
|
|
||||||
|
if not animate then
|
||||||
|
child.x = col
|
||||||
|
child.y = row
|
||||||
|
end
|
||||||
|
|
||||||
if k < count then
|
if k < count then
|
||||||
col = col + child.width
|
col = col + child.width
|
||||||
if col + self.children[k + 1].width + gutter - 2 > self.width then
|
if col + self.children[k + 1].width + gutter - 2 > self.width then
|
||||||
@@ -298,25 +297,24 @@ function page.container:setCategory(categoryName)
|
|||||||
end
|
end
|
||||||
|
|
||||||
self:initChildren()
|
self:initChildren()
|
||||||
|
if animate then -- need to fix transitions under layers
|
||||||
local transition = { i = 1, parent = self, children = self.children }
|
local function transition(args)
|
||||||
function transition:update(device)
|
local i = 1
|
||||||
self.parent:clear()
|
return function(device)
|
||||||
for _,child in ipairs(self.children) do
|
self:clear()
|
||||||
child.tween:update(1)
|
for _,child in pairs(self.children) do
|
||||||
child.x = math.floor(child.x)
|
child.tween:update(1)
|
||||||
child.y = math.floor(child.y)
|
child.x = math.floor(child.x)
|
||||||
child:draw()
|
child.y = math.floor(child.y)
|
||||||
|
child:draw()
|
||||||
|
end
|
||||||
|
args.canvas:blit(device, args, args)
|
||||||
|
i = i + 1
|
||||||
|
return i < 7
|
||||||
|
end
|
||||||
end
|
end
|
||||||
self.canvas:blit(device, self, self)
|
self:addTransition(transition)
|
||||||
self.i = self.i + 1
|
|
||||||
return self.i < 7
|
|
||||||
end
|
end
|
||||||
self:addTransition(transition)
|
|
||||||
end
|
|
||||||
|
|
||||||
function page.container:draw()
|
|
||||||
UI.ViewportWindow.draw(self)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function page:refresh()
|
function page:refresh()
|
||||||
@@ -333,11 +331,9 @@ end
|
|||||||
|
|
||||||
function page:eventHandler(event)
|
function page:eventHandler(event)
|
||||||
|
|
||||||
if event.type == 'category' then
|
if event.type == 'tab_select' then
|
||||||
self.tabBar:selectTab(event.button.text)
|
self.container:setCategory(event.button.text, true)
|
||||||
self.container:setCategory(event.button.text)
|
|
||||||
self.container:draw()
|
self.container:draw()
|
||||||
self:sync()
|
|
||||||
|
|
||||||
config.currentCategory = event.button.text
|
config.currentCategory = event.button.text
|
||||||
Config.update('Overview', config)
|
Config.update('Overview', config)
|
||||||
@@ -384,14 +380,7 @@ function page:eventHandler(event)
|
|||||||
event.focused.parent:scrollIntoView()
|
event.focused.parent:scrollIntoView()
|
||||||
end
|
end
|
||||||
|
|
||||||
elseif event.type == 'tab_change' then
|
elseif event.type == 'refresh' then -- remove this after fixing notification
|
||||||
if event.current > event.last then
|
|
||||||
--self.container:setTransition(UI.effect.slideLeft)
|
|
||||||
else
|
|
||||||
--self.container:setTransition(UI.effect.slideRight)
|
|
||||||
end
|
|
||||||
|
|
||||||
elseif event.type == 'refresh' then
|
|
||||||
loadApplications()
|
loadApplications()
|
||||||
self:refresh()
|
self:refresh()
|
||||||
self:draw()
|
self:draw()
|
||||||
@@ -433,7 +422,7 @@ local formWidth = math.max(UI.term.width - 8, 26)
|
|||||||
local editor = UI.Dialog {
|
local editor = UI.Dialog {
|
||||||
height = 11,
|
height = 11,
|
||||||
width = formWidth,
|
width = formWidth,
|
||||||
title = 'Edit application',
|
title = 'Edit Application',
|
||||||
form = UI.Form {
|
form = UI.Form {
|
||||||
y = 2,
|
y = 2,
|
||||||
height = 9,
|
height = 9,
|
||||||
@@ -506,7 +495,6 @@ function editor:eventHandler(event)
|
|||||||
width = self.width,
|
width = self.width,
|
||||||
height = self.height,
|
height = self.height,
|
||||||
})
|
})
|
||||||
--fileui:setTransition(UI.effect.explode)
|
|
||||||
UI:setPage(fileui, fs.getDir(self.iconFile), function(fileName)
|
UI:setPage(fileui, fs.getDir(self.iconFile), function(fileName)
|
||||||
if fileName then
|
if fileName then
|
||||||
self.iconFile = fileName
|
self.iconFile = fileName
|
||||||
@@ -561,5 +549,4 @@ page.tabBar:selectTab(config.currentCategory or 'Apps')
|
|||||||
page.container:setCategory(config.currentCategory or 'Apps')
|
page.container:setCategory(config.currentCategory or 'Apps')
|
||||||
UI:setPage(page)
|
UI:setPage(page)
|
||||||
|
|
||||||
Event.pullEvents()
|
UI:pullEvents()
|
||||||
UI.term:reset()
|
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
requireInjector(getfenv(1))
|
_G.requireInjector()
|
||||||
|
|
||||||
local Config = require('config')
|
local Config = require('config')
|
||||||
local Event = require('event')
|
local Security = require('security')
|
||||||
local UI = require('ui')
|
local SHA1 = require('sha1')
|
||||||
local Util = require('util')
|
local UI = require('ui')
|
||||||
|
local Util = require('util')
|
||||||
|
|
||||||
|
local fs = _G.fs
|
||||||
|
local multishell = _ENV.multishell
|
||||||
|
local os = _G.os
|
||||||
|
local settings = _G.settings
|
||||||
|
local shell = _ENV.shell
|
||||||
|
local turtle = _G.turtle
|
||||||
|
|
||||||
multishell.setTitle(multishell.getCurrent(), 'System')
|
multishell.setTitle(multishell.getCurrent(), 'System')
|
||||||
UI:configure('System', ...)
|
UI:configure('System', ...)
|
||||||
@@ -11,7 +19,7 @@ UI:configure('System', ...)
|
|||||||
local env = {
|
local env = {
|
||||||
path = shell.path(),
|
path = shell.path(),
|
||||||
aliases = shell.aliases(),
|
aliases = shell.aliases(),
|
||||||
lua_path = LUA_PATH,
|
lua_path = _ENV.LUA_PATH,
|
||||||
}
|
}
|
||||||
Config.load('shell', env)
|
Config.load('shell', env)
|
||||||
|
|
||||||
@@ -30,7 +38,6 @@ local systemPage = UI.Page {
|
|||||||
},
|
},
|
||||||
grid = UI.Grid {
|
grid = UI.Grid {
|
||||||
y = 4,
|
y = 4,
|
||||||
values = paths,
|
|
||||||
disableHeader = true,
|
disableHeader = true,
|
||||||
columns = { { key = 'value' } },
|
columns = { { key = 'value' } },
|
||||||
autospace = true,
|
autospace = true,
|
||||||
@@ -38,7 +45,7 @@ local systemPage = UI.Page {
|
|||||||
},
|
},
|
||||||
|
|
||||||
aliasTab = UI.Window {
|
aliasTab = UI.Window {
|
||||||
tabTitle = 'Aliases',
|
tabTitle = 'Alias',
|
||||||
alias = UI.TextEntry {
|
alias = UI.TextEntry {
|
||||||
x = 2, y = 2, ex = -2,
|
x = 2, y = 2, ex = -2,
|
||||||
limit = 32,
|
limit = 32,
|
||||||
@@ -54,8 +61,6 @@ local systemPage = UI.Page {
|
|||||||
},
|
},
|
||||||
grid = UI.Grid {
|
grid = UI.Grid {
|
||||||
y = 5,
|
y = 5,
|
||||||
values = aliases,
|
|
||||||
autospace = true,
|
|
||||||
sortColumn = 'alias',
|
sortColumn = 'alias',
|
||||||
columns = {
|
columns = {
|
||||||
{ heading = 'Alias', key = 'alias' },
|
{ heading = 'Alias', key = 'alias' },
|
||||||
@@ -67,6 +72,37 @@ local systemPage = UI.Page {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
passwordTab = UI.Window {
|
||||||
|
tabTitle = 'Password',
|
||||||
|
oldPass = UI.TextEntry {
|
||||||
|
x = 2, y = 2, ex = -2,
|
||||||
|
limit = 32,
|
||||||
|
mask = true,
|
||||||
|
shadowText = 'old password',
|
||||||
|
inactive = not Security.getPassword(),
|
||||||
|
},
|
||||||
|
newPass = UI.TextEntry {
|
||||||
|
y = 3, x = 2, ex = -2,
|
||||||
|
limit = 32,
|
||||||
|
mask = true,
|
||||||
|
shadowText = 'new password',
|
||||||
|
accelerators = {
|
||||||
|
enter = 'new_password',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
button = UI.Button {
|
||||||
|
x = 2, y = 5,
|
||||||
|
text = 'Update',
|
||||||
|
event = 'update_password',
|
||||||
|
},
|
||||||
|
info = UI.TextArea {
|
||||||
|
x = 2, ex = -2,
|
||||||
|
y = 7,
|
||||||
|
inactive = true,
|
||||||
|
value = 'Add a password to enable other computers to connect to this one.',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
infoTab = UI.Window {
|
infoTab = UI.Window {
|
||||||
tabTitle = 'Info',
|
tabTitle = 'Info',
|
||||||
labelText = UI.Text {
|
labelText = UI.Text {
|
||||||
@@ -87,12 +123,12 @@ local systemPage = UI.Page {
|
|||||||
{ name = '', value = '' },
|
{ name = '', value = '' },
|
||||||
{ name = 'CC version', value = Util.getVersion() },
|
{ name = 'CC version', value = Util.getVersion() },
|
||||||
{ name = 'Lua version', value = _VERSION },
|
{ name = 'Lua version', value = _VERSION },
|
||||||
{ name = 'MC version', value = _MC_VERSION or 'unknown' },
|
{ name = 'MC version', value = Util.getMinecraftVersion() },
|
||||||
{ name = 'Disk free', value = Util.toBytes(fs.getFreeSpace('/')) },
|
{ name = 'Disk free', value = Util.toBytes(fs.getFreeSpace('/')) },
|
||||||
{ name = 'Computer ID', value = tostring(os.getComputerID()) },
|
{ name = 'Computer ID', value = tostring(os.getComputerID()) },
|
||||||
{ name = 'Day', value = tostring(os.day()) },
|
{ name = 'Day', value = tostring(os.day()) },
|
||||||
},
|
},
|
||||||
selectable = false,
|
inactive = true,
|
||||||
columns = {
|
columns = {
|
||||||
{ key = 'name', width = 12 },
|
{ key = 'name', width = 12 },
|
||||||
{ key = 'value' },
|
{ key = 'value' },
|
||||||
@@ -106,6 +142,99 @@ local systemPage = UI.Page {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if turtle then
|
||||||
|
local Home = require('turtle.home')
|
||||||
|
|
||||||
|
local values = { }
|
||||||
|
Config.load('gps', values.home or { })
|
||||||
|
|
||||||
|
systemPage.tabs:add({
|
||||||
|
gpsTab = UI.Window {
|
||||||
|
tabTitle = 'GPS',
|
||||||
|
labelText = UI.Text {
|
||||||
|
x = 3, y = 2,
|
||||||
|
value = 'On restart, return to this location'
|
||||||
|
},
|
||||||
|
grid = UI.Grid {
|
||||||
|
x = 3, ex = -3, y = 4,
|
||||||
|
height = 2,
|
||||||
|
values = values,
|
||||||
|
inactive = true,
|
||||||
|
columns = {
|
||||||
|
{ heading = 'x', key = 'x' },
|
||||||
|
{ heading = 'y', key = 'y' },
|
||||||
|
{ heading = 'z', key = 'z' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
button1 = UI.Button {
|
||||||
|
x = 3, y = 7,
|
||||||
|
text = 'Set home',
|
||||||
|
event = 'gps_set',
|
||||||
|
},
|
||||||
|
button2 = UI.Button {
|
||||||
|
ex = -3, y = 7, width = 7,
|
||||||
|
text = 'Clear',
|
||||||
|
event = 'gps_clear',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
function systemPage.tabs.gpsTab:eventHandler(event)
|
||||||
|
if event.type == 'gps_set' then
|
||||||
|
systemPage.notification:info('Determining location', 10)
|
||||||
|
systemPage:sync()
|
||||||
|
if Home.set() then
|
||||||
|
Config.load('gps', values)
|
||||||
|
self.grid:setValues(values.home or { })
|
||||||
|
self.grid:draw()
|
||||||
|
systemPage.notification:success('Location set')
|
||||||
|
else
|
||||||
|
systemPage.notification:error('Unable to determine location')
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
elseif event.type == 'gps_clear' then
|
||||||
|
fs.delete('usr/config/gps')
|
||||||
|
self.grid:setValues({ })
|
||||||
|
self.grid:draw()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if settings then
|
||||||
|
local values = { }
|
||||||
|
for _,v in pairs(settings.getNames()) do
|
||||||
|
table.insert(values, {
|
||||||
|
name = v,
|
||||||
|
value = not not settings.get(v),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
systemPage.tabs:add({
|
||||||
|
settingsTab = UI.Window {
|
||||||
|
tabTitle = 'Settings',
|
||||||
|
grid = UI.Grid {
|
||||||
|
y = 1,
|
||||||
|
values = values,
|
||||||
|
autospace = true,
|
||||||
|
sortColumn = 'name',
|
||||||
|
columns = {
|
||||||
|
{ heading = 'Setting', key = 'name' },
|
||||||
|
{ heading = 'Value', key = 'value' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
function systemPage.tabs.settingsTab:eventHandler(event)
|
||||||
|
if event.type == 'grid_select' then
|
||||||
|
event.selected.value = not event.selected.value
|
||||||
|
settings.set(event.selected.name, event.selected.value)
|
||||||
|
settings.save('.settings')
|
||||||
|
self.grid:draw()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function systemPage.tabs.pathTab.grid:draw()
|
function systemPage.tabs.pathTab.grid:draw()
|
||||||
self.values = { }
|
self.values = { }
|
||||||
for _,v in ipairs(Util.split(env.path, '(.-):')) do
|
for _,v in ipairs(Util.split(env.path, '(.-):')) do
|
||||||
@@ -116,7 +245,6 @@ function systemPage.tabs.pathTab.grid:draw()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function systemPage.tabs.pathTab:eventHandler(event)
|
function systemPage.tabs.pathTab:eventHandler(event)
|
||||||
|
|
||||||
if event.type == 'update_path' then
|
if event.type == 'update_path' then
|
||||||
env.path = self.entry.value
|
env.path = self.entry.value
|
||||||
self.grid:setIndex(self.grid:getIndex())
|
self.grid:setIndex(self.grid:getIndex())
|
||||||
@@ -129,7 +257,6 @@ end
|
|||||||
|
|
||||||
function systemPage.tabs.aliasTab.grid:draw()
|
function systemPage.tabs.aliasTab.grid:draw()
|
||||||
self.values = { }
|
self.values = { }
|
||||||
local aliases = { }
|
|
||||||
for k,v in pairs(env.aliases) do
|
for k,v in pairs(env.aliases) do
|
||||||
table.insert(self.values, { alias = k, path = v })
|
table.insert(self.values, { alias = k, path = v })
|
||||||
end
|
end
|
||||||
@@ -138,7 +265,6 @@ function systemPage.tabs.aliasTab.grid:draw()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function systemPage.tabs.aliasTab:eventHandler(event)
|
function systemPage.tabs.aliasTab:eventHandler(event)
|
||||||
|
|
||||||
if event.type == 'delete_alias' then
|
if event.type == 'delete_alias' then
|
||||||
env.aliases[self.grid:getSelected().alias] = nil
|
env.aliases[self.grid:getSelected().alias] = nil
|
||||||
self.grid:setIndex(self.grid:getIndex())
|
self.grid:setIndex(self.grid:getIndex())
|
||||||
@@ -159,6 +285,22 @@ function systemPage.tabs.aliasTab:eventHandler(event)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function systemPage.tabs.passwordTab:eventHandler(event)
|
||||||
|
if event.type == 'update_password' then
|
||||||
|
if #self.newPass.value == 0 then
|
||||||
|
systemPage.notification:error('Invalid password')
|
||||||
|
elseif Security.getPassword() and not Security.verifyPassword(SHA1.sha1(self.oldPass.value)) then
|
||||||
|
systemPage.notification:error('Passwords do not match')
|
||||||
|
else
|
||||||
|
Security.updatePassword(SHA1.sha1(self.newPass.value))
|
||||||
|
self.oldPass.inactive = false
|
||||||
|
systemPage.notification:success('Password updated')
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function systemPage.tabs.infoTab:eventHandler(event)
|
function systemPage.tabs.infoTab:eventHandler(event)
|
||||||
if event.type == 'update_label' then
|
if event.type == 'update_label' then
|
||||||
os.setComputerLabel(self.label.value)
|
os.setComputerLabel(self.label.value)
|
||||||
@@ -168,9 +310,8 @@ function systemPage.tabs.infoTab:eventHandler(event)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function systemPage:eventHandler(event)
|
function systemPage:eventHandler(event)
|
||||||
|
|
||||||
if event.type == 'quit' then
|
if event.type == 'quit' then
|
||||||
Event.exitPullEvents()
|
UI:exitPullEvents()
|
||||||
elseif event.type == 'tab_activate' then
|
elseif event.type == 'tab_activate' then
|
||||||
event.activated:focusFirst()
|
event.activated:focusFirst()
|
||||||
else
|
else
|
||||||
@@ -180,5 +321,4 @@ function systemPage:eventHandler(event)
|
|||||||
end
|
end
|
||||||
|
|
||||||
UI:setPage(systemPage)
|
UI:setPage(systemPage)
|
||||||
Event.pullEvents()
|
UI:pullEvents()
|
||||||
UI.term:reset()
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
requireInjector(getfenv(1))
|
_G.requireInjector()
|
||||||
|
|
||||||
local Event = require('event')
|
local Event = require('event')
|
||||||
local UI = require('ui')
|
local UI = require('ui')
|
||||||
local Util = require('util')
|
local Util = require('util')
|
||||||
|
|
||||||
|
local multishell = _ENV.multishell
|
||||||
|
|
||||||
multishell.setTitle(multishell.getCurrent(), 'Tabs')
|
multishell.setTitle(multishell.getCurrent(), 'Tabs')
|
||||||
UI:configure('Tabs', ...)
|
UI:configure('Tabs', ...)
|
||||||
|
|
||||||
|
|||||||
@@ -1,43 +1,54 @@
|
|||||||
|
local sandboxEnv = { }
|
||||||
|
for k,v in pairs(_ENV) do
|
||||||
|
sandboxEnv[k] = v
|
||||||
|
end
|
||||||
|
|
||||||
|
_G.requireInjector()
|
||||||
|
|
||||||
|
local Config = require('config')
|
||||||
|
local Input = require('input')
|
||||||
|
local Util = require('util')
|
||||||
|
|
||||||
|
local colors = _G.colors
|
||||||
|
local fs = _G.fs
|
||||||
|
local keys = _G.keys
|
||||||
|
local multishell = _ENV.multishell
|
||||||
|
local os = _G.os
|
||||||
|
local printError = _G.printError
|
||||||
|
local shell = _ENV.shell
|
||||||
|
local term = _G.term
|
||||||
|
local window = _G.window
|
||||||
|
|
||||||
|
local parentTerm = term.current()
|
||||||
|
local w,h = parentTerm.getSize()
|
||||||
|
local tabs = { }
|
||||||
|
local currentTab
|
||||||
|
local _tabId = 0
|
||||||
|
local overviewId
|
||||||
|
local runningTab
|
||||||
|
local tabsDirty = false
|
||||||
|
local closeInd = '*'
|
||||||
|
local hooks = { }
|
||||||
|
local hotkeys = { }
|
||||||
|
local downState = { }
|
||||||
|
|
||||||
|
multishell.term = term.current()
|
||||||
|
|
||||||
-- Default label
|
-- Default label
|
||||||
if not os.getComputerLabel() then
|
if not os.getComputerLabel() then
|
||||||
local id = os.getComputerID()
|
local id = os.getComputerID()
|
||||||
if turtle then
|
if _G.turtle then
|
||||||
os.setComputerLabel('turtle_' .. id)
|
os.setComputerLabel('turtle_' .. id)
|
||||||
elseif pocket then
|
elseif _G.pocket then
|
||||||
os.setComputerLabel('pocket_' .. id)
|
os.setComputerLabel('pocket_' .. id)
|
||||||
elseif commands then
|
elseif _G.commands then
|
||||||
os.setComputerLabel('command_' .. id)
|
os.setComputerLabel('command_' .. id)
|
||||||
else
|
else
|
||||||
os.setComputerLabel('computer_' .. id)
|
os.setComputerLabel('computer_' .. id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
multishell.term = term.current()
|
if Util.getVersion() >= 1.76 then
|
||||||
|
|
||||||
local defaultEnv = { }
|
|
||||||
for k,v in pairs(getfenv(1)) do
|
|
||||||
defaultEnv[k] = v
|
|
||||||
end
|
|
||||||
|
|
||||||
requireInjector(getfenv(1))
|
|
||||||
|
|
||||||
local Config = require('config')
|
|
||||||
local Opus = require('opus')
|
|
||||||
local Util = require('util')
|
|
||||||
|
|
||||||
local SESSION_FILE = 'usr/config/multishell.session'
|
|
||||||
|
|
||||||
local parentTerm = term.current()
|
|
||||||
local w,h = parentTerm.getSize()
|
|
||||||
local tabs = {}
|
|
||||||
local currentTab
|
|
||||||
local _tabId = 0
|
|
||||||
local overviewTab
|
|
||||||
local runningTab
|
|
||||||
local tabsDirty = false
|
|
||||||
local closeInd = '*'
|
|
||||||
|
|
||||||
if Util.getVersion() >= 1.79 then
|
|
||||||
closeInd = '\215'
|
closeInd = '\215'
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -59,7 +70,6 @@ local config = {
|
|||||||
focusBackgroundColor = colors.gray,
|
focusBackgroundColor = colors.gray,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
Config.load('multishell', config)
|
Config.load('multishell', config)
|
||||||
|
|
||||||
local _colors = config.standard
|
local _colors = config.standard
|
||||||
@@ -69,92 +79,11 @@ end
|
|||||||
|
|
||||||
local function redrawMenu()
|
local function redrawMenu()
|
||||||
if not tabsDirty then
|
if not tabsDirty then
|
||||||
os.queueEvent('multishell', 'draw')
|
os.queueEvent('multishell_redraw')
|
||||||
tabsDirty = true
|
tabsDirty = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Draw menu
|
|
||||||
local function draw()
|
|
||||||
tabsDirty = false
|
|
||||||
|
|
||||||
parentTerm.setBackgroundColor( _colors.tabBarBackgroundColor )
|
|
||||||
if currentTab and currentTab.isOverview then
|
|
||||||
parentTerm.setTextColor( _colors.focusTextColor )
|
|
||||||
else
|
|
||||||
parentTerm.setTextColor( _colors.tabBarTextColor )
|
|
||||||
end
|
|
||||||
parentTerm.setCursorPos( 1, 1 )
|
|
||||||
parentTerm.clearLine()
|
|
||||||
parentTerm.write('+')
|
|
||||||
|
|
||||||
local tabX = 2
|
|
||||||
local function compareTab(a, b)
|
|
||||||
return a.tabId < b.tabId
|
|
||||||
end
|
|
||||||
for _,tab in Util.spairs(tabs, compareTab) do
|
|
||||||
|
|
||||||
if tab.hidden and tab ~= currentTab or tab.isOverview then
|
|
||||||
tab.sx = nil
|
|
||||||
tab.ex = nil
|
|
||||||
else
|
|
||||||
tab.sx = tabX + 1
|
|
||||||
tab.ex = tabX + #tab.title
|
|
||||||
tabX = tabX + #tab.title + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
for _,tab in Util.spairs(tabs) do
|
|
||||||
if tab.sx then
|
|
||||||
if tab == currentTab then
|
|
||||||
parentTerm.setTextColor(_colors.focusTextColor)
|
|
||||||
parentTerm.setBackgroundColor(_colors.focusBackgroundColor)
|
|
||||||
else
|
|
||||||
parentTerm.setTextColor(_colors.textColor)
|
|
||||||
parentTerm.setBackgroundColor(_colors.backgroundColor)
|
|
||||||
end
|
|
||||||
parentTerm.setCursorPos(tab.sx, 1)
|
|
||||||
parentTerm.write(tab.title)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if currentTab and not currentTab.isOverview then
|
|
||||||
parentTerm.setTextColor(_colors.focusTextColor)
|
|
||||||
parentTerm.setBackgroundColor(_colors.backgroundColor)
|
|
||||||
parentTerm.setCursorPos( w, 1 )
|
|
||||||
parentTerm.write(closeInd)
|
|
||||||
end
|
|
||||||
|
|
||||||
if currentTab then
|
|
||||||
currentTab.window.restoreCursor()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function selectTab( tab )
|
|
||||||
if not tab then
|
|
||||||
for _,ftab in pairs(tabs) do
|
|
||||||
if not ftab.hidden then
|
|
||||||
tab = ftab
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if not tab then
|
|
||||||
tab = overviewTab
|
|
||||||
end
|
|
||||||
|
|
||||||
if currentTab and currentTab ~= tab then
|
|
||||||
currentTab.window.setVisible(false)
|
|
||||||
if tab and not currentTab.hidden then
|
|
||||||
tab.previousTabId = currentTab.tabId
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if tab then
|
|
||||||
currentTab = tab
|
|
||||||
tab.window.setVisible(true)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function resumeTab(tab, event, eventData)
|
local function resumeTab(tab, event, eventData)
|
||||||
if not tab or coroutine.status(tab.co) == 'dead' then
|
if not tab or coroutine.status(tab.co) == 'dead' then
|
||||||
return
|
return
|
||||||
@@ -179,18 +108,50 @@ local function resumeTab(tab, event, eventData)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function selectTab(tab)
|
||||||
|
if not tab then
|
||||||
|
for _,ftab in pairs(tabs) do
|
||||||
|
if not ftab.hidden then
|
||||||
|
tab = ftab
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not tab then
|
||||||
|
tab = tabs[overviewId]
|
||||||
|
end
|
||||||
|
|
||||||
|
if currentTab and currentTab ~= tab then
|
||||||
|
currentTab.window.setVisible(false)
|
||||||
|
if coroutine.status(currentTab.co) == 'suspended' then
|
||||||
|
-- the process that opens a new tab won't get the lose focus event
|
||||||
|
-- os.queueEvent('multishell_notifyfocus', currentTab.tabId)
|
||||||
|
--resumeTab(currentTab, 'multishell_losefocus')
|
||||||
|
end
|
||||||
|
if tab and not currentTab.hidden then
|
||||||
|
tab.previousTabId = currentTab.tabId
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if tab ~= currentTab then
|
||||||
|
currentTab = tab
|
||||||
|
tab.window.setVisible(true)
|
||||||
|
resumeTab(tab, 'multishell_focus')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local function nextTabId()
|
local function nextTabId()
|
||||||
_tabId = _tabId + 1
|
_tabId = _tabId + 1
|
||||||
return _tabId
|
return _tabId
|
||||||
end
|
end
|
||||||
|
|
||||||
local function launchProcess(tab)
|
local function launchProcess(tab)
|
||||||
|
|
||||||
tab.tabId = nextTabId()
|
tab.tabId = nextTabId()
|
||||||
tab.timestamp = os.clock()
|
tab.timestamp = os.clock()
|
||||||
tab.window = window.create(parentTerm, 1, 2, w, h - 1, false)
|
tab.window = window.create(parentTerm, 1, 2, w, h - 1, false)
|
||||||
tab.terminal = tab.window
|
tab.terminal = tab.window
|
||||||
tab.env = Util.shallowCopy(tab.env or defaultEnv)
|
tab.env = Util.shallowCopy(tab.env or sandboxEnv)
|
||||||
|
|
||||||
tab.co = coroutine.create(function()
|
tab.co = coroutine.create(function()
|
||||||
|
|
||||||
@@ -213,9 +174,6 @@ local function launchProcess(tab)
|
|||||||
while true do
|
while true do
|
||||||
local e, code = os.pullEventRaw('key')
|
local e, code = os.pullEventRaw('key')
|
||||||
if e == 'terminate' or e == 'key' and code == keys.enter then
|
if e == 'terminate' or e == 'key' and code == keys.enter then
|
||||||
if tab.isOverview then
|
|
||||||
os.queueEvent('multishell', 'terminate')
|
|
||||||
end
|
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -225,78 +183,20 @@ local function launchProcess(tab)
|
|||||||
local previousTab
|
local previousTab
|
||||||
if tab.previousTabId then
|
if tab.previousTabId then
|
||||||
previousTab = tabs[tab.previousTabId]
|
previousTab = tabs[tab.previousTabId]
|
||||||
|
if previousTab and previousTab.hidden then
|
||||||
|
previousTab = nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
selectTab(previousTab)
|
selectTab(previousTab)
|
||||||
end
|
end
|
||||||
redrawMenu()
|
redrawMenu()
|
||||||
saveSession()
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
tabs[tab.tabId] = tab
|
tabs[tab.tabId] = tab
|
||||||
|
|
||||||
resumeTab(tab)
|
resumeTab(tab)
|
||||||
|
|
||||||
return tab
|
return tab
|
||||||
end
|
end
|
||||||
|
|
||||||
local function resizeWindows()
|
|
||||||
local windowY = 2
|
|
||||||
local windowHeight = h-1
|
|
||||||
|
|
||||||
local keys = Util.keys(tabs)
|
|
||||||
for _,key in pairs(keys) do
|
|
||||||
local tab = tabs[key]
|
|
||||||
local x,y = tab.window.getCursorPos()
|
|
||||||
if y > windowHeight then
|
|
||||||
tab.window.scroll( y - windowHeight )
|
|
||||||
tab.window.setCursorPos( x, windowHeight )
|
|
||||||
end
|
|
||||||
tab.window.reposition( 1, windowY, w, windowHeight )
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Pass term_resize to all processes
|
|
||||||
local keys = Util.keys(tabs)
|
|
||||||
for _,key in pairs(keys) do
|
|
||||||
resumeTab(tabs[key], "term_resize")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function saveSession()
|
|
||||||
local t = { }
|
|
||||||
for _,process in pairs(tabs) do
|
|
||||||
if process.path and not process.isOverview and not process.hidden then
|
|
||||||
table.insert(t, {
|
|
||||||
path = process.path,
|
|
||||||
args = process.args,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
--Util.writeTable(SESSION_FILE, t)
|
|
||||||
end
|
|
||||||
|
|
||||||
local control
|
|
||||||
local hotkeys = { }
|
|
||||||
|
|
||||||
local function processKeyEvent(event, code)
|
|
||||||
if event == 'key_up' then
|
|
||||||
if code == keys.leftCtrl or code == keys.rightCtrl then
|
|
||||||
control = false
|
|
||||||
end
|
|
||||||
elseif event == 'char' then
|
|
||||||
control = false
|
|
||||||
elseif event == 'key' then
|
|
||||||
if code == keys.leftCtrl or code == keys.rightCtrl then
|
|
||||||
control = true
|
|
||||||
elseif control then
|
|
||||||
local hotkey = hotkeys[code]
|
|
||||||
control = false
|
|
||||||
if hotkey then
|
|
||||||
hotkey()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function multishell.addHotkey(code, fn)
|
function multishell.addHotkey(code, fn)
|
||||||
hotkeys[code] = fn
|
hotkeys[code] = fn
|
||||||
end
|
end
|
||||||
@@ -326,10 +226,12 @@ function multishell.getTitle(tabId)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function multishell.setTitle(tabId, sTitle)
|
function multishell.setTitle(tabId, title)
|
||||||
local tab = tabs[tabId]
|
local tab = tabs[tabId]
|
||||||
if tab then
|
if tab then
|
||||||
tab.title = sTitle or ''
|
if not tab.isOverview then
|
||||||
|
tab.title = title or ''
|
||||||
|
end
|
||||||
redrawMenu()
|
redrawMenu()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -345,23 +247,7 @@ function multishell.getTab(tabId)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function multishell.terminate(tabId)
|
function multishell.terminate(tabId)
|
||||||
local tab = tabs[tabId]
|
os.queueEvent('multishell_terminate', tabId)
|
||||||
if tab and not tab.isOverview then
|
|
||||||
if coroutine.status(tab.co) ~= 'dead' then
|
|
||||||
--os.queueEvent('multishell', 'terminate', tab)
|
|
||||||
resumeTab(tab, "terminate")
|
|
||||||
else
|
|
||||||
tabs[tabId] = nil
|
|
||||||
if tab == currentTab then
|
|
||||||
local previousTab
|
|
||||||
if tab.previousTabId then
|
|
||||||
previousTab = tabs[tab.previousTabId]
|
|
||||||
end
|
|
||||||
selectTab(previousTab)
|
|
||||||
end
|
|
||||||
redrawMenu()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function multishell.getTabs()
|
function multishell.getTabs()
|
||||||
@@ -378,11 +264,9 @@ function multishell.launch( tProgramEnv, sProgramPath, ... )
|
|||||||
end
|
end
|
||||||
|
|
||||||
function multishell.openTab(tab)
|
function multishell.openTab(tab)
|
||||||
|
|
||||||
if not tab.title and tab.path then
|
if not tab.title and tab.path then
|
||||||
tab.title = fs.getName(tab.path)
|
tab.title = fs.getName(tab.path)
|
||||||
end
|
end
|
||||||
|
|
||||||
tab.title = tab.title or 'untitled'
|
tab.title = tab.title or 'untitled'
|
||||||
|
|
||||||
local previousTerm = term.current()
|
local previousTerm = term.current()
|
||||||
@@ -399,10 +283,6 @@ function multishell.openTab(tab)
|
|||||||
redrawMenu()
|
redrawMenu()
|
||||||
end
|
end
|
||||||
|
|
||||||
if not tab.hidden then
|
|
||||||
saveSession()
|
|
||||||
end
|
|
||||||
|
|
||||||
return tab.tabId
|
return tab.tabId
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -410,6 +290,9 @@ function multishell.hideTab(tabId)
|
|||||||
local tab = tabs[tabId]
|
local tab = tabs[tabId]
|
||||||
if tab then
|
if tab then
|
||||||
tab.hidden = true
|
tab.hidden = true
|
||||||
|
if currentTab.tabId == tabId then
|
||||||
|
selectTab(tabs[currentTab.previousTabId])
|
||||||
|
end
|
||||||
redrawMenu()
|
redrawMenu()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -423,202 +306,311 @@ function multishell.unhideTab(tabId)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function multishell.getCount()
|
function multishell.getCount()
|
||||||
local count
|
return Util.size(tabs)
|
||||||
for _,tab in pairs(tabs) do
|
|
||||||
count = count + 1
|
|
||||||
end
|
|
||||||
return count
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- control-o - overview
|
function multishell.hook(event, fn)
|
||||||
multishell.addHotkey(24, function()
|
if type(event) == 'table' then
|
||||||
multishell.setFocus(overviewTab.tabId)
|
for _,v in pairs(event) do
|
||||||
end)
|
multishell.hook(v, fn)
|
||||||
|
end
|
||||||
-- control-backspace
|
else
|
||||||
multishell.addHotkey(14, function()
|
if not hooks[event] then
|
||||||
local tabId = multishell.getFocus()
|
hooks[event] = { }
|
||||||
local tab = tabs[tabId]
|
end
|
||||||
if not tab.isOverview then
|
table.insert(hooks[event], fn)
|
||||||
os.queueEvent('multishell', 'terminateTab', tabId)
|
|
||||||
tab = Util.shallowCopy(tab)
|
|
||||||
tab.isDead = false
|
|
||||||
tab.focused = true
|
|
||||||
multishell.openTab(tab)
|
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- you can only unhook from within the function that hooked
|
||||||
|
function multishell.unhook(event, fn)
|
||||||
|
local eventHooks = hooks[event]
|
||||||
|
if eventHooks then
|
||||||
|
Util.removeByValue(eventHooks, fn)
|
||||||
|
if #eventHooks == 0 then
|
||||||
|
hooks[event] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
multishell.hook('multishell_terminate', function(_, eventData)
|
||||||
|
local tabId = eventData[1] or -1
|
||||||
|
local tab = tabs[tabId]
|
||||||
|
|
||||||
|
if tab and not tab.isOverview then
|
||||||
|
if coroutine.status(tab.co) ~= 'dead' then
|
||||||
|
resumeTab(tab, "terminate")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- control-tab - next tab
|
multishell.hook('multishell_redraw', function()
|
||||||
multishell.addHotkey(15, function()
|
tabsDirty = false
|
||||||
|
|
||||||
|
local function write(x, text, bg, fg)
|
||||||
|
parentTerm.setBackgroundColor(bg)
|
||||||
|
parentTerm.setTextColor(fg)
|
||||||
|
parentTerm.setCursorPos(x, 1)
|
||||||
|
parentTerm.write(text)
|
||||||
|
end
|
||||||
|
|
||||||
|
local bg = _colors.tabBarBackgroundColor
|
||||||
|
parentTerm.setBackgroundColor(bg)
|
||||||
|
parentTerm.setCursorPos(1, 1)
|
||||||
|
parentTerm.clearLine()
|
||||||
|
|
||||||
local function compareTab(a, b)
|
local function compareTab(a, b)
|
||||||
return a.tabId < b.tabId
|
return a.tabId < b.tabId
|
||||||
end
|
end
|
||||||
local visibleTabs = { }
|
|
||||||
for _,tab in Util.spairs(tabs, compareTab) do
|
for _,tab in pairs(tabs) do
|
||||||
if not tab.hidden then
|
if tab.hidden and tab ~= currentTab then
|
||||||
table.insert(visibleTabs, tab)
|
tab.width = 0
|
||||||
|
else
|
||||||
|
tab.width = #tab.title + 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
for k,tab in ipairs(visibleTabs) do
|
|
||||||
if tab.tabId == currentTab.tabId then
|
local function width()
|
||||||
if k < #visibleTabs then
|
local tw = 0
|
||||||
multishell.setFocus(visibleTabs[k + 1].tabId)
|
Util.each(tabs, function(t) tw = tw + t.width end)
|
||||||
return
|
return tw
|
||||||
|
end
|
||||||
|
|
||||||
|
while width() > w - 3 do
|
||||||
|
local tab = select(2,
|
||||||
|
Util.spairs(tabs, function(a, b) return a.width > b.width end)())
|
||||||
|
tab.width = tab.width - 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local tabX = 0
|
||||||
|
for _,tab in Util.spairs(tabs, compareTab) do
|
||||||
|
if tab.width > 0 then
|
||||||
|
tab.sx = tabX + 1
|
||||||
|
tab.ex = tabX + tab.width
|
||||||
|
tabX = tabX + tab.width
|
||||||
|
if tab ~= currentTab then
|
||||||
|
write(tab.sx, tab.title:sub(1, tab.width - 1),
|
||||||
|
_colors.backgroundColor, _colors.textColor)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if #visibleTabs > 0 then
|
|
||||||
multishell.setFocus(visibleTabs[1].tabId)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
local function startup()
|
if currentTab then
|
||||||
local hasError
|
write(currentTab.sx - 1,
|
||||||
local session = Util.readTable(SESSION_FILE)
|
' ' .. currentTab.title:sub(1, currentTab.width - 1) .. ' ',
|
||||||
|
_colors.focusBackgroundColor, _colors.focusTextColor)
|
||||||
local overviewId = multishell.openTab({
|
if not currentTab.isOverview then
|
||||||
path = 'sys/apps/Overview.lua',
|
write(w, closeInd, _colors.backgroundColor, _colors.focusTextColor)
|
||||||
focused = true,
|
|
||||||
hidden = true,
|
|
||||||
isOverview = true,
|
|
||||||
})
|
|
||||||
overviewTab = tabs[overviewId]
|
|
||||||
|
|
||||||
if not Opus.loadServices() then
|
|
||||||
hasError = true
|
|
||||||
end
|
|
||||||
|
|
||||||
if not Opus.autorun() then
|
|
||||||
hasError = true
|
|
||||||
end
|
|
||||||
|
|
||||||
if session then
|
|
||||||
for _,v in pairs(session) do
|
|
||||||
multishell.openTab(v)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if hasError then
|
if currentTab then
|
||||||
|
currentTab.window.restoreCursor()
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end)
|
||||||
|
|
||||||
|
multishell.hook('term_resize', function(_, eventData)
|
||||||
|
if not eventData[1] then --- TEST
|
||||||
|
w,h = parentTerm.getSize()
|
||||||
|
|
||||||
|
local windowHeight = h-1
|
||||||
|
|
||||||
|
for _,key in pairs(Util.keys(tabs)) do
|
||||||
|
local tab = tabs[key]
|
||||||
|
local x,y = tab.window.getCursorPos()
|
||||||
|
if y > windowHeight then
|
||||||
|
tab.window.scroll(y - windowHeight)
|
||||||
|
tab.window.setCursorPos(x, windowHeight)
|
||||||
|
end
|
||||||
|
tab.window.reposition(1, 2, w, windowHeight)
|
||||||
|
end
|
||||||
|
|
||||||
|
redrawMenu()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- downstate should be stored in the tab (maybe)
|
||||||
|
multishell.hook('key_up', function(_, eventData)
|
||||||
|
local code = eventData[1]
|
||||||
|
|
||||||
|
if downState[code] ~= currentTab then
|
||||||
|
downState[code] = nil
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
downState[code] = nil
|
||||||
|
end)
|
||||||
|
|
||||||
|
multishell.hook('key', function(_, eventData)
|
||||||
|
local code = eventData[1]
|
||||||
|
local firstPress = not eventData[2]
|
||||||
|
|
||||||
|
if firstPress then
|
||||||
|
downState[code] = currentTab
|
||||||
|
else
|
||||||
|
--key was pressed initially in a previous window
|
||||||
|
if downState[code] ~= currentTab then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
multishell.hook({ 'key', 'key_up', 'char', 'paste' }, function(event, eventData)
|
||||||
|
local code = Input:translate(event, eventData[1], eventData[2])
|
||||||
|
|
||||||
|
if code and hotkeys[code] then
|
||||||
|
hotkeys[code](event, eventData)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
multishell.hook('mouse_click', function(_, eventData)
|
||||||
|
local x, y = eventData[2], eventData[3]
|
||||||
|
if y == 1 then
|
||||||
|
if x == 1 then
|
||||||
|
multishell.setFocus(overviewId)
|
||||||
|
elseif x == w then
|
||||||
|
if currentTab then
|
||||||
|
multishell.terminate(currentTab.tabId)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
for _,tab in pairs(tabs) do
|
||||||
|
if not tab.hidden and tab.sx then
|
||||||
|
if x >= tab.sx and x <= tab.ex then
|
||||||
|
multishell.setFocus(tab.tabId)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
downState.mouse = nil
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
downState.mouse = currentTab
|
||||||
|
eventData[3] = eventData[3] - 1
|
||||||
|
end)
|
||||||
|
|
||||||
|
multishell.hook({ 'mouse_up', 'mouse_drag' }, function(event, eventData)
|
||||||
|
if downState.mouse ~= currentTab then
|
||||||
|
-- don't send mouse up as the mouse click event was on another window
|
||||||
|
if event == 'mouse_up' then
|
||||||
|
downState.mouse = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
return true -- stop propagation
|
||||||
|
end
|
||||||
|
eventData[3] = eventData[3] - 1
|
||||||
|
end)
|
||||||
|
|
||||||
|
multishell.hook('mouse_scroll', function(_, eventData)
|
||||||
|
local dir, y = eventData[1], eventData[3]
|
||||||
|
|
||||||
|
if y == 1 then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
if currentTab.terminal.scrollUp then
|
||||||
|
if dir == -1 then
|
||||||
|
currentTab.terminal.scrollUp()
|
||||||
|
else
|
||||||
|
currentTab.terminal.scrollDown()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
eventData[3] = y - 1
|
||||||
|
end)
|
||||||
|
|
||||||
|
local function startup()
|
||||||
|
local success = true
|
||||||
|
|
||||||
|
local function runDir(directory, open)
|
||||||
|
if not fs.exists(directory) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local files = fs.list(directory)
|
||||||
|
table.sort(files)
|
||||||
|
|
||||||
|
for _,file in ipairs(files) do
|
||||||
|
os.sleep(0)
|
||||||
|
local result, err = open(directory .. '/' .. file)
|
||||||
|
if result then
|
||||||
|
if term.isColor() then
|
||||||
|
term.setTextColor(colors.green)
|
||||||
|
end
|
||||||
|
term.write('[PASS] ')
|
||||||
|
term.setTextColor(colors.white)
|
||||||
|
term.write(fs.combine(directory, file))
|
||||||
|
else
|
||||||
|
if term.isColor() then
|
||||||
|
term.setTextColor(colors.red)
|
||||||
|
end
|
||||||
|
term.write('[FAIL] ')
|
||||||
|
term.setTextColor(colors.white)
|
||||||
|
term.write(fs.combine(directory, file))
|
||||||
|
if err then
|
||||||
|
_G.printError(err)
|
||||||
|
end
|
||||||
|
success = false
|
||||||
|
end
|
||||||
|
print()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
runDir('sys/services', shell.openHiddenTab)
|
||||||
|
runDir('sys/autorun', shell.run)
|
||||||
|
runDir('usr/autorun', shell.run)
|
||||||
|
|
||||||
|
if not success then
|
||||||
print()
|
print()
|
||||||
error('An autorun program has errored')
|
error('An autorun program has errored')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Begin
|
overviewId = multishell.openTab({
|
||||||
parentTerm.clear()
|
path = 'sys/apps/Overview.lua',
|
||||||
|
isOverview = true,
|
||||||
|
})
|
||||||
|
tabs[overviewId].title = '+'
|
||||||
|
|
||||||
multishell.openTab({
|
multishell.openTab({
|
||||||
focused = true,
|
focused = true,
|
||||||
fn = startup,
|
fn = startup,
|
||||||
env = defaultEnv,
|
|
||||||
title = 'Autorun',
|
title = 'Autorun',
|
||||||
})
|
})
|
||||||
|
|
||||||
if not overviewTab or coroutine.status(overviewTab.co) == 'dead' then
|
local currentTabEvents = Util.transpose {
|
||||||
--error('Overview aborted')
|
'char', 'key', 'key_up',
|
||||||
end
|
'mouse_click', 'mouse_drag', 'mouse_scroll', 'mouse_up',
|
||||||
|
'paste', 'terminate',
|
||||||
if not currentTab then
|
}
|
||||||
multishell.setFocus(overviewTab.tabId)
|
|
||||||
end
|
|
||||||
|
|
||||||
draw()
|
|
||||||
|
|
||||||
local lastClicked
|
|
||||||
|
|
||||||
while true do
|
while true do
|
||||||
|
|
||||||
-- Get the event
|
|
||||||
local tEventData = { os.pullEventRaw() }
|
local tEventData = { os.pullEventRaw() }
|
||||||
local sEvent = table.remove(tEventData, 1)
|
local sEvent = table.remove(tEventData, 1)
|
||||||
|
local stopPropagation
|
||||||
|
|
||||||
if sEvent == 'key_up' then
|
local eventHooks = hooks[sEvent]
|
||||||
processKeyEvent(sEvent, tEventData[1])
|
if eventHooks then
|
||||||
|
for i = #eventHooks, 1, -1 do
|
||||||
|
stopPropagation = eventHooks[i](sEvent, tEventData)
|
||||||
|
if stopPropagation then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if sEvent == "term_resize" then
|
if not stopPropagation then
|
||||||
-- Resize event
|
if currentTabEvents[sEvent] then
|
||||||
w,h = parentTerm.getSize()
|
resumeTab(currentTab, sEvent, tEventData)
|
||||||
resizeWindows()
|
|
||||||
redrawMenu()
|
|
||||||
|
|
||||||
elseif sEvent == 'multishell' then
|
else
|
||||||
local action = tEventData[1]
|
-- Passthrough to all processes
|
||||||
|
for _,key in pairs(Util.keys(tabs)) do
|
||||||
if action == 'terminate' then
|
resumeTab(tabs[key], sEvent, tEventData)
|
||||||
break
|
|
||||||
elseif action == 'terminateTab' then
|
|
||||||
multishell.terminate(tEventData[2])
|
|
||||||
elseif action == 'draw' then
|
|
||||||
draw()
|
|
||||||
end
|
|
||||||
|
|
||||||
elseif sEvent == "char" or
|
|
||||||
sEvent == "key" or
|
|
||||||
sEvent == "paste" or
|
|
||||||
sEvent == "terminate" then
|
|
||||||
|
|
||||||
processKeyEvent(sEvent, tEventData[1])
|
|
||||||
|
|
||||||
-- Keyboard event - Passthrough to current process
|
|
||||||
resumeTab(currentTab, sEvent, tEventData)
|
|
||||||
|
|
||||||
elseif sEvent == "mouse_click" then
|
|
||||||
local button, x, y = tEventData[1], tEventData[2], tEventData[3]
|
|
||||||
lastClicked = nil
|
|
||||||
if y == 1 then
|
|
||||||
-- Switch process
|
|
||||||
local w, h = parentTerm.getSize()
|
|
||||||
if x == 1 then
|
|
||||||
multishell.setFocus(overviewTab.tabId)
|
|
||||||
elseif x == w then
|
|
||||||
if currentTab then
|
|
||||||
multishell.terminate(currentTab.tabId)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
for _,tab in pairs(tabs) do
|
|
||||||
if not tab.hidden and tab.sx then
|
|
||||||
if x >= tab.sx and x <= tab.ex then
|
|
||||||
multishell.setFocus(tab.tabId)
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
elseif currentTab then
|
|
||||||
-- Passthrough to current process
|
|
||||||
lastClicked = currentTab
|
|
||||||
resumeTab(currentTab, sEvent, { button, x, y-1 })
|
|
||||||
end
|
|
||||||
|
|
||||||
elseif sEvent == "mouse_up" then
|
|
||||||
if currentTab and lastClicked == currentTab then
|
|
||||||
local button, x, y = tEventData[1], tEventData[2], tEventData[3]
|
|
||||||
resumeTab(currentTab, sEvent, { button, x, y-1 })
|
|
||||||
end
|
|
||||||
|
|
||||||
elseif sEvent == "mouse_drag" or sEvent == "mouse_scroll" then
|
|
||||||
-- Other mouse event
|
|
||||||
local p1, x, y = tEventData[1], tEventData[2], tEventData[3]
|
|
||||||
if currentTab and (y ~= 1) then
|
|
||||||
if currentTab.terminal.scrollUp then
|
|
||||||
if p1 == -1 then
|
|
||||||
currentTab.terminal.scrollUp()
|
|
||||||
else
|
|
||||||
currentTab.terminal.scrollDown()
|
|
||||||
end
|
|
||||||
else
|
|
||||||
-- Passthrough to current process
|
|
||||||
resumeTab(currentTab, sEvent, { p1, x, y-1 })
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
else
|
|
||||||
-- Other event
|
|
||||||
-- Passthrough to all processes
|
|
||||||
local keys = Util.keys(tabs)
|
|
||||||
for _,key in pairs(keys) do
|
|
||||||
resumeTab(tabs[key], sEvent, tEventData)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
requireInjector(getfenv(1))
|
_G.requireInjector()
|
||||||
|
|
||||||
local Security = require('security')
|
local Security = require('security')
|
||||||
local SHA1 = require('sha1')
|
local SHA1 = require('sha1')
|
||||||
|
|||||||
457
sys/apps/shell
457
sys/apps/shell
@@ -1,28 +1,32 @@
|
|||||||
local parentShell = shell
|
local parentShell = _ENV.shell
|
||||||
|
|
||||||
shell = { }
|
_ENV.shell = { }
|
||||||
multishell = multishell or { }
|
_ENV.multishell = _ENV.multishell or { }
|
||||||
|
|
||||||
|
local fs = _G.fs
|
||||||
|
local shell = _ENV.shell
|
||||||
|
local multishell = _ENV.multishell
|
||||||
|
|
||||||
local sandboxEnv = setmetatable({ }, { __index = _G })
|
local sandboxEnv = setmetatable({ }, { __index = _G })
|
||||||
for k,v in pairs(getfenv(1)) do
|
for k,v in pairs(_ENV) do
|
||||||
sandboxEnv[k] = v
|
sandboxEnv[k] = v
|
||||||
end
|
end
|
||||||
sandboxEnv.shell = shell
|
sandboxEnv.shell = shell
|
||||||
sandboxEnv.multishell = multishell
|
sandboxEnv.multishell = multishell
|
||||||
|
|
||||||
requireInjector(getfenv(1))
|
_G.requireInjector()
|
||||||
|
|
||||||
local Util = require('util')
|
local Util = require('util')
|
||||||
|
|
||||||
local DIR = (parentShell and parentShell.dir()) or ""
|
local DIR = (parentShell and parentShell.dir()) or ""
|
||||||
local PATH = (parentShell and parentShell.path()) or ".:/rom/programs"
|
local PATH = (parentShell and parentShell.path()) or ".:/rom/programs"
|
||||||
local ALIASES = (parentShell and parentShell.aliases()) or {}
|
local tAliases = (parentShell and parentShell.aliases()) or {}
|
||||||
local tCompletionInfo = (parentShell and parentShell.getCompletionInfo()) or {}
|
local tCompletionInfo = (parentShell and parentShell.getCompletionInfo()) or {}
|
||||||
|
|
||||||
local bExit = false
|
local bExit = false
|
||||||
local tProgramStack = {}
|
local tProgramStack = {}
|
||||||
|
|
||||||
local function parseCommandLine( ... )
|
local function tokenise( ... )
|
||||||
local sLine = table.concat( { ... }, " " )
|
local sLine = table.concat( { ... }, " " )
|
||||||
local tWords = {}
|
local tWords = {}
|
||||||
local bQuoted = false
|
local bQuoted = false
|
||||||
@@ -37,45 +41,61 @@ local function parseCommandLine( ... )
|
|||||||
bQuoted = not bQuoted
|
bQuoted = not bQuoted
|
||||||
end
|
end
|
||||||
|
|
||||||
return table.remove(tWords, 1), tWords
|
return tWords
|
||||||
|
end
|
||||||
|
|
||||||
|
local function run(env, ...)
|
||||||
|
local args = tokenise(...)
|
||||||
|
local command = table.remove(args, 1) or error('No such program')
|
||||||
|
local isUrl = not not command:match("^(https?:)")
|
||||||
|
|
||||||
|
local path, loadFn
|
||||||
|
if isUrl then
|
||||||
|
path = command
|
||||||
|
loadFn = Util.loadUrl
|
||||||
|
else
|
||||||
|
path = shell.resolveProgram(command) or error('No such program')
|
||||||
|
loadFn = loadfile
|
||||||
|
end
|
||||||
|
|
||||||
|
local fn, err = loadFn(path, env)
|
||||||
|
if not fn then
|
||||||
|
error(err)
|
||||||
|
end
|
||||||
|
|
||||||
|
if multishell and multishell.setTitle then
|
||||||
|
multishell.setTitle(multishell.getCurrent(), fs.getName(path))
|
||||||
|
end
|
||||||
|
|
||||||
|
if isUrl then
|
||||||
|
tProgramStack[#tProgramStack + 1] = path:match("^https?://([^/:]+:?[0-9]*/?.*)$")
|
||||||
|
else
|
||||||
|
tProgramStack[#tProgramStack + 1] = path
|
||||||
|
end
|
||||||
|
|
||||||
|
local r = { fn(table.unpack(args)) }
|
||||||
|
|
||||||
|
tProgramStack[#tProgramStack] = nil
|
||||||
|
|
||||||
|
return table.unpack(r)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Install shell API
|
-- Install shell API
|
||||||
function shell.run(...)
|
function shell.run(...)
|
||||||
|
local oldTitle
|
||||||
|
|
||||||
local path, args = parseCommandLine(...)
|
if multishell and multishell.getTitle then
|
||||||
local isUrl = not not path:match("^(https?:)//(([^/:]+):?([0-9]*))(/?.*)$")
|
oldTitle = multishell.getTitle(multishell.getCurrent())
|
||||||
|
|
||||||
if not isUrl then
|
|
||||||
path = shell.resolveProgram(path)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if path then
|
local env = setmetatable(Util.shallowCopy(sandboxEnv), { __index = _G })
|
||||||
tProgramStack[#tProgramStack + 1] = path
|
local r = { pcall(run, env, ...) }
|
||||||
local oldTitle
|
|
||||||
|
|
||||||
if multishell and multishell.getTitle then
|
if multishell and multishell.setTitle then
|
||||||
oldTitle = multishell.getTitle(multishell.getCurrent())
|
multishell.setTitle(multishell.getCurrent(), oldTitle or 'shell')
|
||||||
multishell.setTitle(multishell.getCurrent(), fs.getName(path))
|
|
||||||
end
|
|
||||||
|
|
||||||
local result, err
|
|
||||||
|
|
||||||
local env = Util.shallowCopy(sandboxEnv)
|
|
||||||
if isUrl then
|
|
||||||
result, err = Util.runUrl(env, path, unpack(args))
|
|
||||||
else
|
|
||||||
result, err = Util.run(env, path, unpack(args))
|
|
||||||
end
|
|
||||||
tProgramStack[#tProgramStack] = nil
|
|
||||||
|
|
||||||
if multishell and multishell.getTitle then
|
|
||||||
multishell.setTitle(multishell.getCurrent(), oldTitle or 'shell')
|
|
||||||
end
|
|
||||||
|
|
||||||
return result, err
|
|
||||||
end
|
end
|
||||||
return false, 'No such program'
|
|
||||||
|
return table.unpack(r)
|
||||||
end
|
end
|
||||||
|
|
||||||
function shell.exit()
|
function shell.exit()
|
||||||
@@ -97,9 +117,8 @@ function shell.resolve( _sPath )
|
|||||||
end
|
end
|
||||||
|
|
||||||
function shell.resolveProgram( _sCommand )
|
function shell.resolveProgram( _sCommand )
|
||||||
|
if tAliases[_sCommand] ~= nil then
|
||||||
if ALIASES[ _sCommand ] ~= nil then
|
_sCommand = tAliases[_sCommand]
|
||||||
_sCommand = ALIASES[ _sCommand ]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local path = shell.resolve(_sCommand)
|
local path = shell.resolve(_sCommand)
|
||||||
@@ -130,7 +149,6 @@ function shell.resolveProgram( _sCommand )
|
|||||||
return sPath .. '.lua'
|
return sPath .. '.lua'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Not found
|
-- Not found
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
@@ -143,7 +161,7 @@ function shell.programs( _bIncludeHidden )
|
|||||||
sPath = shell.resolve(sPath)
|
sPath = shell.resolve(sPath)
|
||||||
if fs.isDir( sPath ) then
|
if fs.isDir( sPath ) then
|
||||||
local tList = fs.list( sPath )
|
local tList = fs.list( sPath )
|
||||||
for n,sFile in pairs( tList ) do
|
for _,sFile in pairs( tList ) do
|
||||||
if not fs.isDir( fs.combine( sPath, sFile ) ) and
|
if not fs.isDir( fs.combine( sPath, sFile ) ) and
|
||||||
(_bIncludeHidden or string.sub( sFile, 1, 1 ) ~= ".") then
|
(_bIncludeHidden or string.sub( sFile, 1, 1 ) ~= ".") then
|
||||||
tItems[ sFile ] = true
|
tItems[ sFile ] = true
|
||||||
@@ -154,15 +172,96 @@ function shell.programs( _bIncludeHidden )
|
|||||||
|
|
||||||
-- Sort and return
|
-- Sort and return
|
||||||
local tItemList = {}
|
local tItemList = {}
|
||||||
for sItem, b in pairs( tItems ) do
|
for sItem in pairs( tItems ) do
|
||||||
table.insert( tItemList, sItem )
|
table.insert( tItemList, sItem )
|
||||||
end
|
end
|
||||||
table.sort( tItemList )
|
table.sort( tItemList )
|
||||||
return tItemList
|
return tItemList
|
||||||
end
|
end
|
||||||
|
|
||||||
function shell.complete(sLine) end
|
local function completeProgram( sLine )
|
||||||
function shell.completeProgram(sProgram) end
|
if #sLine > 0 and string.sub( sLine, 1, 1 ) == "/" then
|
||||||
|
-- Add programs from the root
|
||||||
|
return fs.complete( sLine, "", true, false )
|
||||||
|
else
|
||||||
|
local tResults = {}
|
||||||
|
local tSeen = {}
|
||||||
|
|
||||||
|
-- Add aliases
|
||||||
|
for sAlias in pairs( tAliases ) do
|
||||||
|
if #sAlias > #sLine and string.sub( sAlias, 1, #sLine ) == sLine then
|
||||||
|
local sResult = string.sub( sAlias, #sLine + 1 )
|
||||||
|
if not tSeen[ sResult ] then
|
||||||
|
table.insert( tResults, sResult )
|
||||||
|
tSeen[ sResult ] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Add programs from the path
|
||||||
|
local tPrograms = shell.programs()
|
||||||
|
for n=1,#tPrograms do
|
||||||
|
local sProgram = tPrograms[n]
|
||||||
|
if #sProgram > #sLine and string.sub( sProgram, 1, #sLine ) == sLine then
|
||||||
|
local sResult = string.sub( sProgram, #sLine + 1 )
|
||||||
|
if not tSeen[ sResult ] then
|
||||||
|
table.insert( tResults, sResult )
|
||||||
|
tSeen[ sResult ] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Sort and return
|
||||||
|
table.sort( tResults )
|
||||||
|
return tResults
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function completeProgramArgument( sProgram, nArgument, sPart, tPreviousParts )
|
||||||
|
local tInfo = tCompletionInfo[ sProgram ]
|
||||||
|
if tInfo then
|
||||||
|
return tInfo.fnComplete( shell, nArgument, sPart, tPreviousParts )
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function shell.complete(sLine)
|
||||||
|
if #sLine > 0 then
|
||||||
|
local tWords = tokenise( sLine )
|
||||||
|
local nIndex = #tWords
|
||||||
|
if string.sub( sLine, #sLine, #sLine ) == " " then
|
||||||
|
nIndex = nIndex + 1
|
||||||
|
end
|
||||||
|
if nIndex == 1 then
|
||||||
|
local sBit = tWords[1] or ""
|
||||||
|
local sPath = shell.resolveProgram( sBit )
|
||||||
|
if tCompletionInfo[ sPath ] then
|
||||||
|
return { " " }
|
||||||
|
else
|
||||||
|
local tResults = completeProgram( sBit )
|
||||||
|
for n=1,#tResults do
|
||||||
|
local sResult = tResults[n]
|
||||||
|
local cPath = shell.resolveProgram( sBit .. sResult )
|
||||||
|
if tCompletionInfo[ cPath ] then
|
||||||
|
tResults[n] = sResult .. " "
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return tResults
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif nIndex > 1 then
|
||||||
|
local sPath = shell.resolveProgram( tWords[1] )
|
||||||
|
local sPart = tWords[nIndex] or ""
|
||||||
|
local tPreviousParts = tWords
|
||||||
|
tPreviousParts[nIndex] = nil
|
||||||
|
return completeProgramArgument( sPath , nIndex - 1, sPart, tPreviousParts )
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function shell.completeProgram( sProgram )
|
||||||
|
return completeProgram( sProgram )
|
||||||
|
end
|
||||||
|
|
||||||
function shell.setCompletionFunction(sProgram, fnComplete)
|
function shell.setCompletionFunction(sProgram, fnComplete)
|
||||||
tCompletionInfo[sProgram] = { fnComplete = fnComplete }
|
tCompletionInfo[sProgram] = { fnComplete = fnComplete }
|
||||||
@@ -177,29 +276,30 @@ function shell.getRunningProgram()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function shell.setAlias( _sCommand, _sProgram )
|
function shell.setAlias( _sCommand, _sProgram )
|
||||||
ALIASES[ _sCommand ] = _sProgram
|
tAliases[_sCommand] = _sProgram
|
||||||
end
|
end
|
||||||
|
|
||||||
function shell.clearAlias( _sCommand )
|
function shell.clearAlias( _sCommand )
|
||||||
ALIASES[ _sCommand ] = nil
|
tAliases[_sCommand] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
function shell.aliases()
|
function shell.aliases()
|
||||||
local tCopy = {}
|
local tCopy = {}
|
||||||
for sAlias, sCommand in pairs(ALIASES) do
|
for sAlias, sCommand in pairs(tAliases) do
|
||||||
tCopy[sAlias] = sCommand
|
tCopy[sAlias] = sCommand
|
||||||
end
|
end
|
||||||
return tCopy
|
return tCopy
|
||||||
end
|
end
|
||||||
|
|
||||||
function shell.newTab(tabInfo, ...)
|
function shell.newTab(tabInfo, ...)
|
||||||
local path, args = parseCommandLine(...)
|
local args = tokenise(...)
|
||||||
|
local path = table.remove(args, 1)
|
||||||
path = shell.resolveProgram(path)
|
path = shell.resolveProgram(path)
|
||||||
|
|
||||||
if path then
|
if path then
|
||||||
tabInfo.path = path
|
tabInfo.path = path
|
||||||
tabInfo.env = sandboxEnv
|
tabInfo.env = sandboxEnv
|
||||||
tabInfo.args = Util.shallowCopy(args)
|
tabInfo.args = args
|
||||||
tabInfo.title = fs.getName(path)
|
tabInfo.title = fs.getName(path)
|
||||||
|
|
||||||
if path ~= 'sys/apps/shell' then
|
if path ~= 'sys/apps/shell' then
|
||||||
@@ -229,40 +329,19 @@ end
|
|||||||
|
|
||||||
local tArgs = { ... }
|
local tArgs = { ... }
|
||||||
if #tArgs > 0 then
|
if #tArgs > 0 then
|
||||||
|
local env = setmetatable(Util.shallowCopy(sandboxEnv), { __index = _G })
|
||||||
local path, args = parseCommandLine(...)
|
return run(env, ...)
|
||||||
|
|
||||||
if not path then
|
|
||||||
error('No such program')
|
|
||||||
end
|
|
||||||
|
|
||||||
local isUrl = not not path:match("^(https?:)//(([^/:]+):?([0-9]*))(/?.*)$")
|
|
||||||
if not isUrl then
|
|
||||||
path = shell.resolveProgram(path)
|
|
||||||
if not path then
|
|
||||||
error('No such program')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local fn, err
|
|
||||||
|
|
||||||
if isUrl then
|
|
||||||
fn, err = Util.loadUrl(path, getfenv(1))
|
|
||||||
else
|
|
||||||
fn, err = loadfile(path, getfenv(1))
|
|
||||||
end
|
|
||||||
|
|
||||||
if not fn then
|
|
||||||
error(err)
|
|
||||||
end
|
|
||||||
|
|
||||||
tProgramStack[#tProgramStack + 1] = path
|
|
||||||
return fn(table.unpack(args))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local Config = require('config')
|
local Config = require('config')
|
||||||
local History = require('history')
|
local History = require('history')
|
||||||
|
|
||||||
|
local colors = _G.colors
|
||||||
|
local keys = _G.keys
|
||||||
|
local os = _G.os
|
||||||
|
local term = _G.term
|
||||||
|
local textutils = _G.textutils
|
||||||
|
|
||||||
local config = {
|
local config = {
|
||||||
standard = {
|
standard = {
|
||||||
textColor = colors.white,
|
textColor = colors.white,
|
||||||
@@ -285,96 +364,37 @@ local config = {
|
|||||||
displayDirectory = true,
|
displayDirectory = true,
|
||||||
}
|
}
|
||||||
|
|
||||||
--Config.load('shell', config)
|
Config.load('shellprompt', config)
|
||||||
|
|
||||||
local _colors = config.standard
|
local _colors = config.standard
|
||||||
if term.isColor() then
|
if term.isColor() then
|
||||||
_colors = config.color
|
_colors = config.color
|
||||||
end
|
end
|
||||||
|
|
||||||
local function autocompleteFile(results, words)
|
local function autocompleteArgument(program, words)
|
||||||
|
|
||||||
local function getBaseDir(path)
|
|
||||||
if #path > 1 then
|
|
||||||
if path:sub(-1) ~= '/' then
|
|
||||||
path = fs.getDir(path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if path:sub(1, 1) == '/' then
|
|
||||||
path = fs.combine(path, '')
|
|
||||||
else
|
|
||||||
path = fs.combine(shell.dir(), path)
|
|
||||||
end
|
|
||||||
while not fs.isDir(path) do
|
|
||||||
path = fs.getDir(path)
|
|
||||||
end
|
|
||||||
return path
|
|
||||||
end
|
|
||||||
|
|
||||||
local function getRawPath(path)
|
|
||||||
local baseDir = ''
|
|
||||||
if path:sub(1, 1) ~= '/' then
|
|
||||||
baseDir = shell.dir()
|
|
||||||
end
|
|
||||||
if #path > 1 then
|
|
||||||
if path:sub(-1) ~= '/' then
|
|
||||||
path = fs.getDir(path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if fs.isDir(fs.combine(baseDir, path)) then
|
|
||||||
return path
|
|
||||||
end
|
|
||||||
return fs.getDir(path)
|
|
||||||
end
|
|
||||||
|
|
||||||
local match = words[#words] or ''
|
|
||||||
local startDir = getBaseDir(match)
|
|
||||||
local rawPath = getRawPath(match)
|
|
||||||
|
|
||||||
if fs.isDir(startDir) then
|
|
||||||
local files = fs.list(startDir)
|
|
||||||
for _,f in pairs(files) do
|
|
||||||
local path = fs.combine(rawPath, f)
|
|
||||||
if fs.isDir(fs.combine(startDir, f)) then
|
|
||||||
results[path .. '/'] = 'directory'
|
|
||||||
else
|
|
||||||
results[path .. ' '] = 'program'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function autocompleteProgram(results, words)
|
|
||||||
if #words == 1 then
|
|
||||||
local files = shell.programs(true)
|
|
||||||
for _,f in ipairs(files) do
|
|
||||||
results[f .. ' '] = 'program'
|
|
||||||
end
|
|
||||||
for f in pairs(ALIASES) do
|
|
||||||
results[f .. ' '] = 'program'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function autocompleteArgument(results, program, words)
|
|
||||||
local word = ''
|
local word = ''
|
||||||
if #words > 1 then
|
if #words > 1 then
|
||||||
word = words[#words]
|
word = words[#words]
|
||||||
end
|
end
|
||||||
|
|
||||||
local tInfo = tCompletionInfo[program]
|
local tInfo = tCompletionInfo[program]
|
||||||
local args = tInfo.fnComplete(shell, #words - 1, word, words)
|
return tInfo.fnComplete(shell, #words - 1, word, words)
|
||||||
if args then
|
|
||||||
Util.filterInplace(args, function(f)
|
|
||||||
return not Util.key(args, f .. '/')
|
|
||||||
end)
|
|
||||||
for _,arg in ipairs(args) do
|
|
||||||
results[word .. arg] = 'argument'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function autocomplete(line, suggestions)
|
local function autocompleteAnything(line, words)
|
||||||
|
local results = shell.complete(line)
|
||||||
|
|
||||||
|
if results and #results == 0 and #words == 1 then
|
||||||
|
results = nil
|
||||||
|
end
|
||||||
|
if not results then
|
||||||
|
results = fs.complete(words[#words] or '', shell.dir(), true, false)
|
||||||
|
end
|
||||||
|
|
||||||
|
return results
|
||||||
|
end
|
||||||
|
|
||||||
|
local function autocomplete(line)
|
||||||
local words = { }
|
local words = { }
|
||||||
for word in line:gmatch("%S+") do
|
for word in line:gmatch("%S+") do
|
||||||
table.insert(words, word)
|
table.insert(words, word)
|
||||||
@@ -382,39 +402,68 @@ local function autocomplete(line, suggestions)
|
|||||||
if line:match(' $') then
|
if line:match(' $') then
|
||||||
table.insert(words, '')
|
table.insert(words, '')
|
||||||
end
|
end
|
||||||
|
|
||||||
local results = { }
|
|
||||||
|
|
||||||
if #words == 0 then
|
if #words == 0 then
|
||||||
files = autocompleteFile(results, words)
|
words = { '' }
|
||||||
|
end
|
||||||
|
|
||||||
|
local results
|
||||||
|
|
||||||
|
local program = shell.resolveProgram(words[1])
|
||||||
|
if tCompletionInfo[program] then
|
||||||
|
results = autocompleteArgument(program, words) or { }
|
||||||
else
|
else
|
||||||
local program = shell.resolveProgram(words[1])
|
results = autocompleteAnything(line, words) or { }
|
||||||
if tCompletionInfo[program] then
|
|
||||||
autocompleteArgument(results, program, words)
|
|
||||||
else
|
|
||||||
autocompleteProgram(results, words)
|
|
||||||
autocompleteFile(results, words)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local match = words[#words] or ''
|
Util.filterInplace(results, function(f)
|
||||||
local files = { }
|
return not Util.key(results, f .. '/')
|
||||||
for f in pairs(results) do
|
end)
|
||||||
if f:sub(1, #match) == match then
|
local w = words[#words] or ''
|
||||||
table.insert(files, f)
|
for k,arg in pairs(results) do
|
||||||
end
|
results[k] = w .. arg
|
||||||
end
|
end
|
||||||
|
|
||||||
if #files == 1 then
|
if #results == 1 then
|
||||||
words[#words] = files[1]
|
words[#words] = results[1]
|
||||||
return table.concat(words, ' ')
|
return table.concat(words, ' ')
|
||||||
elseif #files > 1 and suggestions then
|
elseif #results > 1 then
|
||||||
|
|
||||||
|
local function someComplete()
|
||||||
|
-- ugly (complete as much as possible)
|
||||||
|
local word = words[#words] or ''
|
||||||
|
local i = #word + 1
|
||||||
|
while true do
|
||||||
|
local ch
|
||||||
|
for _,f in ipairs(results) do
|
||||||
|
if #f < i then
|
||||||
|
words[#words] = string.sub(f, 1, i - 1)
|
||||||
|
return table.concat(words, ' ')
|
||||||
|
end
|
||||||
|
if not ch then
|
||||||
|
ch = string.sub(f, i, i)
|
||||||
|
elseif string.sub(f, i, i) ~= ch then
|
||||||
|
if i == #word + 1 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
words[#words] = string.sub(f, 1, i - 1)
|
||||||
|
return table.concat(words, ' ')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local t = someComplete()
|
||||||
|
if t then
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
print()
|
print()
|
||||||
|
|
||||||
local word = words[#words] or ''
|
local word = words[#words] or ''
|
||||||
local prefix = word:match("(.*/)") or ''
|
local prefix = word:match("(.*/)") or ''
|
||||||
if #prefix > 0 then
|
if #prefix > 0 then
|
||||||
for _,f in ipairs(files) do
|
for _,f in ipairs(results) do
|
||||||
if f:match("^" .. prefix) ~= prefix then
|
if f:match("^" .. prefix) ~= prefix then
|
||||||
prefix = ''
|
prefix = ''
|
||||||
break
|
break
|
||||||
@@ -423,8 +472,8 @@ local function autocomplete(line, suggestions)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local tDirs, tFiles = { }, { }
|
local tDirs, tFiles = { }, { }
|
||||||
for _,f in ipairs(files) do
|
for _,f in ipairs(results) do
|
||||||
if results[f] == 'directory' then
|
if fs.isDir(shell.resolve(f)) then
|
||||||
f = f:gsub(prefix, '', 1)
|
f = f:gsub(prefix, '', 1)
|
||||||
table.insert(tDirs, f)
|
table.insert(tDirs, f)
|
||||||
else
|
else
|
||||||
@@ -436,14 +485,14 @@ local function autocomplete(line, suggestions)
|
|||||||
table.sort(tFiles)
|
table.sort(tFiles)
|
||||||
|
|
||||||
if #tDirs > 0 and #tDirs < #tFiles then
|
if #tDirs > 0 and #tDirs < #tFiles then
|
||||||
local w = term.getSize()
|
local tw = term.getSize()
|
||||||
local nMaxLen = w / 8
|
local nMaxLen = tw / 8
|
||||||
for n, sItem in pairs(files) do
|
for _,sItem in pairs(results) do
|
||||||
nMaxLen = math.max(string.len(sItem) + 1, nMaxLen)
|
nMaxLen = math.max(string.len(sItem) + 1, nMaxLen)
|
||||||
end
|
end
|
||||||
local nCols = math.floor(w / nMaxLen)
|
local nCols = math.floor(w / nMaxLen)
|
||||||
if #tDirs < nCols then
|
if #tDirs < nCols then
|
||||||
for i = #tDirs + 1, nCols do
|
for _ = #tDirs + 1, nCols do
|
||||||
table.insert(tDirs, '')
|
table.insert(tDirs, '')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -457,35 +506,11 @@ local function autocomplete(line, suggestions)
|
|||||||
|
|
||||||
term.setTextColour(_colors.promptTextColor)
|
term.setTextColour(_colors.promptTextColor)
|
||||||
term.setBackgroundColor(_colors.promptBackgroundColor)
|
term.setBackgroundColor(_colors.promptBackgroundColor)
|
||||||
write("$ " )
|
term.write("$ " )
|
||||||
|
|
||||||
term.setTextColour(_colors.commandTextColor)
|
term.setTextColour(_colors.commandTextColor)
|
||||||
term.setBackgroundColor(colors.black)
|
term.setBackgroundColor(colors.black)
|
||||||
return line
|
return line
|
||||||
elseif #files > 1 then
|
|
||||||
|
|
||||||
-- ugly (complete as much as possible)
|
|
||||||
local word = words[#words] or ''
|
|
||||||
local i = #word + 1
|
|
||||||
while true do
|
|
||||||
local ch
|
|
||||||
for _,f in ipairs(files) do
|
|
||||||
if #f < i then
|
|
||||||
words[#words] = string.sub(f, 1, i - 1)
|
|
||||||
return table.concat(words, ' ')
|
|
||||||
end
|
|
||||||
if not ch then
|
|
||||||
ch = string.sub(f, i, i)
|
|
||||||
elseif string.sub(f, i, i) ~= ch then
|
|
||||||
if i == #word + 1 then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
words[#words] = string.sub(f, 1, i - 1)
|
|
||||||
return table.concat(words, ' ')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
i = i + 1
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -494,7 +519,6 @@ local function shellRead(history)
|
|||||||
|
|
||||||
local sLine = ""
|
local sLine = ""
|
||||||
local nPos = 0
|
local nPos = 0
|
||||||
local lastPattern
|
|
||||||
|
|
||||||
local w = term.getSize()
|
local w = term.getSize()
|
||||||
local sx = term.getCursorPos()
|
local sx = term.getCursorPos()
|
||||||
@@ -507,7 +531,7 @@ local function shellRead(history)
|
|||||||
nScroll = (sx + nPos) - w
|
nScroll = (sx + nPos) - w
|
||||||
end
|
end
|
||||||
|
|
||||||
local cx,cy = term.getCursorPos()
|
local _,cy = term.getCursorPos()
|
||||||
term.setCursorPos( sx, cy )
|
term.setCursorPos( sx, cy )
|
||||||
if sReplace then
|
if sReplace then
|
||||||
term.write( string.rep( sReplace, math.max( string.len(sLine) - nScroll, 0 ) ) )
|
term.write( string.rep( sReplace, math.max( string.len(sLine) - nScroll, 0 ) ) )
|
||||||
@@ -518,7 +542,7 @@ local function shellRead(history)
|
|||||||
end
|
end
|
||||||
|
|
||||||
while true do
|
while true do
|
||||||
local sEvent, param, param2 = os.pullEventRaw()
|
local sEvent, param = os.pullEventRaw()
|
||||||
|
|
||||||
if sEvent == "char" then
|
if sEvent == "char" then
|
||||||
sLine = string.sub( sLine, 1, nPos ) .. param .. string.sub( sLine, nPos + 1 )
|
sLine = string.sub( sLine, 1, nPos ) .. param .. string.sub( sLine, nPos + 1 )
|
||||||
@@ -542,10 +566,7 @@ local function shellRead(history)
|
|||||||
break
|
break
|
||||||
elseif param == keys.tab then
|
elseif param == keys.tab then
|
||||||
if nPos == #sLine then
|
if nPos == #sLine then
|
||||||
local showSuggestions = lastPattern == sLine
|
local cline = autocomplete(sLine)
|
||||||
lastPattern = sLine
|
|
||||||
|
|
||||||
local cline = autocomplete(sLine, showSuggestions)
|
|
||||||
if cline then
|
if cline then
|
||||||
sLine = cline
|
sLine = cline
|
||||||
nPos = #sLine
|
nPos = #sLine
|
||||||
@@ -605,7 +626,7 @@ local function shellRead(history)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local cx, cy = term.getCursorPos()
|
local _, cy = term.getCursorPos()
|
||||||
term.setCursorPos( w + 1, cy )
|
term.setCursorPos( w + 1, cy )
|
||||||
print()
|
print()
|
||||||
term.setCursorBlink( false )
|
term.setCursorBlink( false )
|
||||||
@@ -622,7 +643,7 @@ while not bExit do
|
|||||||
end
|
end
|
||||||
term.setTextColour(_colors.promptTextColor)
|
term.setTextColour(_colors.promptTextColor)
|
||||||
term.setBackgroundColor(_colors.promptBackgroundColor)
|
term.setBackgroundColor(_colors.promptBackgroundColor)
|
||||||
write("$ " )
|
term.write("$ " )
|
||||||
term.setTextColour(_colors.commandTextColor)
|
term.setTextColour(_colors.commandTextColor)
|
||||||
term.setBackgroundColor(colors.black)
|
term.setBackgroundColor(colors.black)
|
||||||
local sLine = shellRead(history)
|
local sLine = shellRead(history)
|
||||||
@@ -635,9 +656,9 @@ while not bExit do
|
|||||||
end
|
end
|
||||||
term.setTextColour(_colors.textColor)
|
term.setTextColour(_colors.textColor)
|
||||||
if #sLine > 0 then
|
if #sLine > 0 then
|
||||||
local result, err = shell.run( sLine )
|
local result, err = shell.run(sLine)
|
||||||
if not result and err then
|
if not result and err then
|
||||||
printError(err)
|
_G.printError(err)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
requireInjector(getfenv(1))
|
_G.requireInjector()
|
||||||
|
|
||||||
local Event = require('event')
|
local Event = require('event')
|
||||||
local Socket = require('socket')
|
local Socket = require('socket')
|
||||||
local Terminal = require('terminal')
|
local Terminal = require('terminal')
|
||||||
local Util = require('util')
|
local Util = require('util')
|
||||||
|
|
||||||
|
local os = _G.os
|
||||||
|
local read = _G.read
|
||||||
|
local term = _G.term
|
||||||
|
|
||||||
local remoteId
|
local remoteId
|
||||||
local args = { ... }
|
local args = { ... }
|
||||||
if #args == 1 then
|
if #args == 1 then
|
||||||
@@ -19,10 +23,10 @@ if not remoteId then
|
|||||||
end
|
end
|
||||||
|
|
||||||
print('connecting...')
|
print('connecting...')
|
||||||
local socket = Socket.connect(remoteId, 23)
|
local socket, msg = Socket.connect(remoteId, 23)
|
||||||
|
|
||||||
if not socket then
|
if not socket then
|
||||||
error('Unable to connect to ' .. remoteId .. ' on port 23')
|
error(msg)
|
||||||
end
|
end
|
||||||
|
|
||||||
local ct = Util.shallowCopy(term.current())
|
local ct = Util.shallowCopy(term.current())
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
requireInjector(getfenv(1))
|
_G.requireInjector()
|
||||||
|
|
||||||
local Crypto = require('crypto')
|
local Crypto = require('crypto')
|
||||||
local Security = require('security')
|
local Security = require('security')
|
||||||
@@ -6,6 +6,8 @@ local SHA1 = require('sha1')
|
|||||||
local Socket = require('socket')
|
local Socket = require('socket')
|
||||||
local Terminal = require('terminal')
|
local Terminal = require('terminal')
|
||||||
|
|
||||||
|
local os = _G.os
|
||||||
|
|
||||||
local remoteId
|
local remoteId
|
||||||
local args = { ... }
|
local args = { ... }
|
||||||
|
|
||||||
@@ -13,7 +15,7 @@ if #args == 1 then
|
|||||||
remoteId = tonumber(args[1])
|
remoteId = tonumber(args[1])
|
||||||
else
|
else
|
||||||
print('Enter host ID')
|
print('Enter host ID')
|
||||||
remoteId = tonumber(read())
|
remoteId = tonumber(_G.read())
|
||||||
end
|
end
|
||||||
|
|
||||||
if not remoteId then
|
if not remoteId then
|
||||||
@@ -27,16 +29,15 @@ if not password then
|
|||||||
end
|
end
|
||||||
|
|
||||||
print('connecting...')
|
print('connecting...')
|
||||||
local socket = Socket.connect(remoteId, 19)
|
local socket, msg = Socket.connect(remoteId, 19)
|
||||||
|
|
||||||
if not socket then
|
if not socket then
|
||||||
error('Unable to connect to ' .. remoteId .. ' on port 19')
|
error(msg)
|
||||||
end
|
end
|
||||||
|
|
||||||
local publicKey = Security.getPublicKey()
|
local publicKey = Security.getPublicKey()
|
||||||
local password = SHA1.sha1(password)
|
|
||||||
|
|
||||||
socket:write(Crypto.encrypt({ pk = publicKey, dh = os.getComputerID() }, password))
|
socket:write(Crypto.encrypt({ pk = publicKey, dh = os.getComputerID() }, SHA1.sha1(password)))
|
||||||
|
|
||||||
local data = socket:read(2)
|
local data = socket:read(2)
|
||||||
socket:close()
|
socket:close()
|
||||||
|
|||||||
@@ -1,17 +1,21 @@
|
|||||||
requireInjector(getfenv(1))
|
_G.requireInjector()
|
||||||
|
|
||||||
local Event = require('event')
|
local Event = require('event')
|
||||||
local Socket = require('socket')
|
local Socket = require('socket')
|
||||||
local Terminal = require('terminal')
|
local Terminal = require('terminal')
|
||||||
local Util = require('util')
|
local Util = require('util')
|
||||||
|
|
||||||
|
local colors = _G.colors
|
||||||
|
local multishell = _ENV.multishell
|
||||||
|
local term = _G.term
|
||||||
|
|
||||||
local remoteId
|
local remoteId
|
||||||
local args = { ... }
|
local args = { ... }
|
||||||
if #args == 1 then
|
if #args == 1 then
|
||||||
remoteId = tonumber(args[1])
|
remoteId = tonumber(args[1])
|
||||||
else
|
else
|
||||||
print('Enter host ID')
|
print('Enter host ID')
|
||||||
remoteId = tonumber(read())
|
remoteId = tonumber(_G.read())
|
||||||
end
|
end
|
||||||
|
|
||||||
if not remoteId then
|
if not remoteId then
|
||||||
@@ -21,10 +25,10 @@ end
|
|||||||
multishell.setTitle(multishell.getCurrent(), 'VNC-' .. remoteId)
|
multishell.setTitle(multishell.getCurrent(), 'VNC-' .. remoteId)
|
||||||
|
|
||||||
print('connecting...')
|
print('connecting...')
|
||||||
local socket = Socket.connect(remoteId, 5900)
|
local socket, msg = Socket.connect(remoteId, 5900)
|
||||||
|
|
||||||
if not socket then
|
if not socket then
|
||||||
error('Unable to connect to ' .. remoteId .. ' on port 5900')
|
error(msg)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function writeTermInfo()
|
local function writeTermInfo()
|
||||||
@@ -73,7 +77,7 @@ while true do
|
|||||||
print()
|
print()
|
||||||
print('Connection lost')
|
print('Connection lost')
|
||||||
print('Press enter to exit')
|
print('Press enter to exit')
|
||||||
read()
|
_G.read()
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
21
sys/autorun/clipboard.lua
Normal file
21
sys/autorun/clipboard.lua
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
_G.requireInjector()
|
||||||
|
|
||||||
|
local Util = require('util')
|
||||||
|
|
||||||
|
local multishell = _ENV.multishell
|
||||||
|
local textutils = _G.textutils
|
||||||
|
|
||||||
|
local data
|
||||||
|
|
||||||
|
multishell.hook('clipboard_copy', function(_, args)
|
||||||
|
data = args[1]
|
||||||
|
end)
|
||||||
|
|
||||||
|
multishell.addHotkey('shift-paste', function(_, args)
|
||||||
|
if type(data) == 'table' then
|
||||||
|
local s, m = pcall(textutils.serialize, data)
|
||||||
|
data = (s and m) or Util.tostring(data)
|
||||||
|
end
|
||||||
|
-- replace the event paste data with our internal data
|
||||||
|
args[1] = Util.tostring(data or '')
|
||||||
|
end)
|
||||||
56
sys/autorun/hotkeys.lua
Normal file
56
sys/autorun/hotkeys.lua
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
_G.requireInjector()
|
||||||
|
|
||||||
|
local Util = require('util')
|
||||||
|
|
||||||
|
local multishell = _ENV.multishell
|
||||||
|
|
||||||
|
-- overview
|
||||||
|
multishell.addHotkey('control-o', function()
|
||||||
|
for _,tab in pairs(multishell.getTabs()) do
|
||||||
|
if tab.isOverview then
|
||||||
|
multishell.setFocus(tab.tabId)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- restart tab
|
||||||
|
multishell.addHotkey('control-backspace', function()
|
||||||
|
local tabs = multishell.getTabs()
|
||||||
|
local tabId = multishell.getFocus()
|
||||||
|
local tab = tabs[tabId]
|
||||||
|
if not tab.isOverview then
|
||||||
|
multishell.terminate(tabId)
|
||||||
|
tab = Util.shallowCopy(tab)
|
||||||
|
tab.isDead = false
|
||||||
|
tab.focused = true
|
||||||
|
multishell.openTab(tab)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- next tab
|
||||||
|
multishell.addHotkey('control-tab', function()
|
||||||
|
local tabs = multishell.getTabs()
|
||||||
|
local visibleTabs = { }
|
||||||
|
local currentTabId = multishell.getFocus()
|
||||||
|
|
||||||
|
local function compareTab(a, b)
|
||||||
|
return a.tabId < b.tabId
|
||||||
|
end
|
||||||
|
for _,tab in Util.spairs(tabs, compareTab) do
|
||||||
|
if not tab.hidden then
|
||||||
|
table.insert(visibleTabs, tab)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for k,tab in ipairs(visibleTabs) do
|
||||||
|
if tab.tabId == currentTabId then
|
||||||
|
if k < #visibleTabs then
|
||||||
|
multishell.setFocus(visibleTabs[k + 1].tabId)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if #visibleTabs > 0 then
|
||||||
|
multishell.setFocus(visibleTabs[1].tabId)
|
||||||
|
end
|
||||||
|
end)
|
||||||
@@ -1,18 +1,22 @@
|
|||||||
-- Loads the Opus environment regardless if the file system is local or not
|
-- Loads the Opus environment regardless if the file system is local or not
|
||||||
|
local colors = _G.colors
|
||||||
|
local fs = _G.fs
|
||||||
|
local http = _G.http
|
||||||
|
local shell = _ENV.shell
|
||||||
|
local term = _G.term
|
||||||
|
|
||||||
local w, h = term.getSize()
|
local w, h = term.getSize()
|
||||||
local str = 'Loading Opus...'
|
|
||||||
term.setTextColor(colors.white)
|
term.setTextColor(colors.white)
|
||||||
if term.isColor() then
|
if term.isColor() then
|
||||||
term.setBackgroundColor(colors.cyan)
|
term.setBackgroundColor(colors.black)
|
||||||
term.clear()
|
term.clear()
|
||||||
local opus = {
|
local opus = {
|
||||||
'9999900',
|
'fffff00',
|
||||||
'999907000',
|
'ffff07000',
|
||||||
'9900770b00 4444',
|
'ff00770b00 4444',
|
||||||
'99077777444444444',
|
'ff077777444444444',
|
||||||
'907777744444444444',
|
'f07777744444444444',
|
||||||
'90000777444444444',
|
'f0000777444444444',
|
||||||
'070000111744444',
|
'070000111744444',
|
||||||
'777770000',
|
'777770000',
|
||||||
'7777000000',
|
'7777000000',
|
||||||
@@ -25,16 +29,21 @@ if term.isColor() then
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
term.setCursorPos((w - #str) / 2, h)
|
term.setCursorPos((w - 18) / 2, h)
|
||||||
term.write(str)
|
term.write('Loading Opus...')
|
||||||
term.setCursorPos(w, h)
|
term.setCursorPos(w, h)
|
||||||
|
|
||||||
local GIT_REPO = 'kepler155c/opus/master'
|
local GIT_REPO = 'kepler155c/opus/develop'
|
||||||
local BASE = 'https://raw.githubusercontent.com/' .. GIT_REPO
|
local BASE = 'https://raw.githubusercontent.com/' .. GIT_REPO
|
||||||
|
|
||||||
|
local sandboxEnv = setmetatable({ }, { __index = _G })
|
||||||
|
for k,v in pairs(_ENV) do
|
||||||
|
sandboxEnv[k] = v
|
||||||
|
end
|
||||||
|
|
||||||
local function makeEnv()
|
local function makeEnv()
|
||||||
local env = setmetatable({ }, { __index = _G })
|
local env = setmetatable({ }, { __index = _G })
|
||||||
for k,v in pairs(getfenv(1)) do
|
for k,v in pairs(sandboxEnv) do
|
||||||
env[k] = v
|
env[k] = v
|
||||||
end
|
end
|
||||||
return env
|
return env
|
||||||
@@ -52,10 +61,10 @@ end
|
|||||||
local function runUrl(file, ...)
|
local function runUrl(file, ...)
|
||||||
local url = BASE .. '/' .. file
|
local url = BASE .. '/' .. file
|
||||||
|
|
||||||
local h = http.get(url)
|
local u = http.get(url)
|
||||||
if h then
|
if u then
|
||||||
local fn, m = load(h.readAll(), url, nil, makeEnv())
|
local fn = load(u.readAll(), url, nil, makeEnv())
|
||||||
h.close()
|
u.close()
|
||||||
if fn then
|
if fn then
|
||||||
return fn(...)
|
return fn(...)
|
||||||
end
|
end
|
||||||
@@ -86,9 +95,8 @@ end
|
|||||||
if not fs.exists('usr/autorun') then
|
if not fs.exists('usr/autorun') then
|
||||||
fs.makeDir('usr/autorun')
|
fs.makeDir('usr/autorun')
|
||||||
end
|
end
|
||||||
if not fs.exists('usr/etc/fstab') or not fs.exists('usr/etc/fstab.ignore') then
|
if not fs.exists('usr/etc/fstab') then
|
||||||
Util.writeFile('usr/etc/fstab', 'usr gitfs kepler155c/opus-apps/master')
|
Util.writeFile('usr/etc/fstab', 'usr gitfs kepler155c/opus-apps/develop')
|
||||||
Util.writeFile('usr/etc/fstab.ignore', 'forced fstab overwrite')
|
|
||||||
end
|
end
|
||||||
if not fs.exists('usr/config/shell') then
|
if not fs.exists('usr/config/shell') then
|
||||||
Util.writeTable('usr/config/shell', {
|
Util.writeTable('usr/config/shell', {
|
||||||
@@ -109,7 +117,7 @@ if config.aliases then
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
shell.setPath(config.path)
|
shell.setPath(config.path)
|
||||||
LUA_PATH = config.lua_path
|
sandboxEnv.LUA_PATH = config.lua_path
|
||||||
|
|
||||||
-- extensions
|
-- extensions
|
||||||
local dir = 'sys/extensions'
|
local dir = 'sys/extensions'
|
||||||
|
|||||||
@@ -1,37 +1,15 @@
|
|||||||
local pullEvent = os.pullEventRaw
|
local pullEvent = os.pullEventRaw
|
||||||
local redirect = term.redirect
|
|
||||||
local current = term.current
|
|
||||||
local shutdown = os.shutdown
|
local shutdown = os.shutdown
|
||||||
|
|
||||||
local cos = { }
|
os.pullEventRaw = function()
|
||||||
|
|
||||||
os.pullEventRaw = function(...)
|
|
||||||
local co = coroutine.running()
|
|
||||||
if not cos[co] then
|
|
||||||
cos[co] = true
|
|
||||||
error('die')
|
|
||||||
end
|
|
||||||
return pullEvent(...)
|
|
||||||
end
|
|
||||||
|
|
||||||
os.shutdown = function()
|
|
||||||
end
|
|
||||||
|
|
||||||
term.current = function()
|
|
||||||
term.redirect = function()
|
|
||||||
os.pullEventRaw = pullEvent
|
|
||||||
os.shutdown = shutdown
|
|
||||||
term.current = current
|
|
||||||
term.redirect = redirect
|
|
||||||
|
|
||||||
term.redirect(term.native())
|
|
||||||
--for co in pairs(cos) do
|
|
||||||
-- print(tostring(co) .. ' ' .. coroutine.status(co))
|
|
||||||
--end
|
|
||||||
os.run(getfenv(1), 'sys/boot/multishell.boot')
|
|
||||||
os.run(getfenv(1), 'rom/programs/shell')
|
|
||||||
end
|
|
||||||
error('die')
|
error('die')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
os.shutdown = function()
|
||||||
|
os.pullEventRaw = pullEvent
|
||||||
|
os.shutdown = shutdown
|
||||||
|
|
||||||
|
os.run(getfenv(1), 'sys/boot/multishell.boot')
|
||||||
|
end
|
||||||
|
|
||||||
os.queueEvent('modem_message')
|
os.queueEvent('modem_message')
|
||||||
|
|||||||
@@ -88,6 +88,16 @@
|
|||||||
run = "simpleMiner.lua",
|
run = "simpleMiner.lua",
|
||||||
requires = 'turtle',
|
requires = 'turtle',
|
||||||
},
|
},
|
||||||
|
[ "131260cbfbb0c821f8eae5e7c3c296c7aa4d50b9" ] = {
|
||||||
|
title = "Music",
|
||||||
|
category = "Apps",
|
||||||
|
icon = "\030 \031f === \
|
||||||
|
\030 \031f | |\
|
||||||
|
\030 \031fo| o|\
|
||||||
|
",
|
||||||
|
run = "usr/apps/music.lua",
|
||||||
|
requires = 'turtle',
|
||||||
|
},
|
||||||
c47ae15370cfe1ed2781eedc1dc2547d12d9e972 = {
|
c47ae15370cfe1ed2781eedc1dc2547d12d9e972 = {
|
||||||
title = "Help",
|
title = "Help",
|
||||||
category = "Apps",
|
category = "Apps",
|
||||||
@@ -222,7 +232,7 @@
|
|||||||
[ "d8c298dd41e4a4ec20e8307901797b64688b3b77" ] = {
|
[ "d8c298dd41e4a4ec20e8307901797b64688b3b77" ] = {
|
||||||
title = "GPS Deploy",
|
title = "GPS Deploy",
|
||||||
category = "Apps",
|
category = "Apps",
|
||||||
run = "http://pastebin.com/raw/qLthLak5",
|
run = "http://pastebin.com/raw/VXAyXqBv",
|
||||||
requires = "turtle",
|
requires = "turtle",
|
||||||
},
|
},
|
||||||
[ "53a5d150062b1e03206b9e15854b81060e3c7552" ] = {
|
[ "53a5d150062b1e03206b9e15854b81060e3c7552" ] = {
|
||||||
@@ -233,13 +243,21 @@
|
|||||||
\030f\03131\0308\031f \030f\03131\031e3",
|
\030f\03131\0308\031f \030f\03131\031e3",
|
||||||
run = "https://pastebin.com/raw/nsKrHTbN",
|
run = "https://pastebin.com/raw/nsKrHTbN",
|
||||||
},
|
},
|
||||||
|
[ "a2accffe95b2c8be30e8a05e0c6ab7e8f5966f43" ] = {
|
||||||
|
title = "Strafe",
|
||||||
|
category = "Games",
|
||||||
|
icon = "\0308\031f \0300 \0308 \
|
||||||
|
\0308\031f \0300 \030f \
|
||||||
|
\0300\031f \030f ",
|
||||||
|
run = "https://pastebin.com/raw/jyDH7mLH",
|
||||||
|
},
|
||||||
[ "48d6857f6b2869d031f463b13aa34df47e18c548" ] = {
|
[ "48d6857f6b2869d031f463b13aa34df47e18c548" ] = {
|
||||||
title = "Breakout",
|
title = "Breakout",
|
||||||
category = "Games",
|
category = "Games",
|
||||||
icon = "\0301\031f \0309 \030c \030b \030e \030c \0306 \
|
icon = "\0301\031f \0309 \030c \030b \030e \030c \0306 \
|
||||||
\030 \031f \
|
\030 \031f \
|
||||||
\030 \031f \0300 \0310 ",
|
\030 \031f \0300 \0310 ",
|
||||||
run = "https://pastebin.com/raw/LTRYaSKt",
|
run = "https://gist.github.com/LDDestroier/c7528d95bc0103545c2a/raw",
|
||||||
},
|
},
|
||||||
[ "8d59207c8a84153b3e9f035cc3b6ec7a23671323" ] = {
|
[ "8d59207c8a84153b3e9f035cc3b6ec7a23671323" ] = {
|
||||||
title = "Micropaint",
|
title = "Micropaint",
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
ScrollingGrid = {
|
ScrollBar = {
|
||||||
lineChar = '|',
|
lineChar = '|',
|
||||||
sliderChar = '\127',
|
sliderChar = '\127',
|
||||||
upArrowChar = '\30',
|
upArrowChar = '\30',
|
||||||
downArrowChar = '\31',
|
downArrowChar = '\31',
|
||||||
},
|
},
|
||||||
Button = {
|
Button = {
|
||||||
focusIndicator = '\183',
|
--focusIndicator = '\183',
|
||||||
},
|
},
|
||||||
Grid = {
|
Grid = {
|
||||||
focusIndicator = '\183',
|
focusIndicator = '\183',
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
if _G.clipboard then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
requireInjector(getfenv(1))
|
|
||||||
|
|
||||||
local Util = require('util')
|
|
||||||
|
|
||||||
_G.clipboard = { internal, data }
|
|
||||||
|
|
||||||
function clipboard.getData()
|
|
||||||
return clipboard.data
|
|
||||||
end
|
|
||||||
|
|
||||||
function clipboard.setData(data)
|
|
||||||
clipboard.data = data
|
|
||||||
if data then
|
|
||||||
clipboard.useInternal(true)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function clipboard.getText()
|
|
||||||
if clipboard.data then
|
|
||||||
return Util.tostring(clipboard.data)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function clipboard.isInternal()
|
|
||||||
return clipboard.internal
|
|
||||||
end
|
|
||||||
|
|
||||||
function clipboard.useInternal(mode)
|
|
||||||
if mode ~= clipboard.internal then
|
|
||||||
clipboard.internal = mode
|
|
||||||
os.queueEvent('clipboard_mode', mode)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if multishell and multishell.addHotkey then
|
|
||||||
multishell.addHotkey(20, function()
|
|
||||||
clipboard.useInternal(not clipboard.isInternal())
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
if _G.device then
|
_G.requireInjector()
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
requireInjector(getfenv(1))
|
|
||||||
|
|
||||||
local Peripheral = require('peripheral')
|
local Peripheral = require('peripheral')
|
||||||
|
|
||||||
_G.device = { }
|
_G.device = Peripheral.getList()
|
||||||
|
|
||||||
for _,side in pairs(peripheral.getNames()) do
|
-- register the main term in the devices list
|
||||||
Peripheral.addDevice(device, side)
|
_G.device.terminal = _G.term.current()
|
||||||
end
|
_G.device.terminal.side = 'terminal'
|
||||||
|
_G.device.terminal.type = 'terminal'
|
||||||
|
_G.device.terminal.name = 'terminal'
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
if not turtle or turtle.enableGPS then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
requireInjector(getfenv(1))
|
|
||||||
|
|
||||||
local GPS = require('gps')
|
|
||||||
local Config = require('config')
|
|
||||||
|
|
||||||
function turtle.enableGPS(timeout)
|
|
||||||
if turtle.point.gps then
|
|
||||||
return turtle.point
|
|
||||||
end
|
|
||||||
|
|
||||||
local pt = GPS.getPointAndHeading(timeout)
|
|
||||||
if pt then
|
|
||||||
turtle.setPoint(pt, true)
|
|
||||||
return turtle.point
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function turtle.gotoGPSHome()
|
|
||||||
local config = { }
|
|
||||||
Config.load('gps', config)
|
|
||||||
|
|
||||||
if config.home then
|
|
||||||
if turtle.enableGPS() then
|
|
||||||
turtle.pathfind(config.home)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function turtle.setGPSHome()
|
|
||||||
local config = { }
|
|
||||||
Config.load('gps', config)
|
|
||||||
|
|
||||||
if turtle.point.gps then
|
|
||||||
config.home = turtle.point
|
|
||||||
Config.update('gps', config)
|
|
||||||
else
|
|
||||||
local pt = GPS.getPoint()
|
|
||||||
if pt then
|
|
||||||
local originalHeading = turtle.point.heading
|
|
||||||
local heading = GPS.getHeading()
|
|
||||||
if heading then
|
|
||||||
local turns = (turtle.point.heading - originalHeading) % 4
|
|
||||||
pt.heading = (heading - turns) % 4
|
|
||||||
config.home = pt
|
|
||||||
Config.update('gps', config)
|
|
||||||
|
|
||||||
pt = GPS.getPoint()
|
|
||||||
pt.heading = heading
|
|
||||||
turtle.setPoint(pt, true)
|
|
||||||
turtle.gotoPoint(config.home)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,24 +1,31 @@
|
|||||||
if not turtle or turtle.getPoint then
|
if not _G.turtle then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
requireInjector(getfenv(1))
|
_G.requireInjector()
|
||||||
|
|
||||||
|
local Pathing = require('turtle.pathfind')
|
||||||
|
local GPS = require('gps')
|
||||||
local Point = require('point')
|
local Point = require('point')
|
||||||
local synchronized = require('sync')
|
local synchronized = require('sync')
|
||||||
local Util = require('util')
|
local Util = require('util')
|
||||||
local Pathing = require('turtle.pathfind')
|
|
||||||
|
local os = _G.os
|
||||||
|
local peripheral = _G.peripheral
|
||||||
|
local turtle = _G.turtle
|
||||||
|
|
||||||
local function noop() end
|
local function noop() end
|
||||||
|
local headings = Point.headings
|
||||||
|
local state = { }
|
||||||
|
|
||||||
turtle.pathfind = Pathing.pathfind
|
turtle.pathfind = Pathing.pathfind
|
||||||
turtle.point = { x = 0, y = 0, z = 0, heading = 0 }
|
turtle.point = { x = 0, y = 0, z = 0, heading = 0 }
|
||||||
turtle.status = 'idle'
|
|
||||||
turtle.abort = false
|
|
||||||
local state = { }
|
|
||||||
|
|
||||||
function turtle.getPoint() return turtle.point end
|
function turtle.getPoint() return turtle.point end
|
||||||
function turtle.getState() return state end
|
function turtle.getState() return state end
|
||||||
|
function turtle.isAborted() return state.abort end
|
||||||
|
function turtle.getStatus() return state.status end
|
||||||
|
function turtle.setStatus(s) state.status = s end
|
||||||
|
|
||||||
local function _defaultMove(action)
|
local function _defaultMove(action)
|
||||||
while not action.move() do
|
while not action.move() do
|
||||||
@@ -41,14 +48,13 @@ function turtle.setPoint(pt, isGPS)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function turtle.resetState()
|
function turtle.resetState()
|
||||||
--turtle.abort = false -- should be part of state
|
state.abort = false
|
||||||
--turtle.status = 'idle' -- should be part of state
|
state.status = 'idle'
|
||||||
state.attackPolicy = noop
|
state.attackPolicy = noop
|
||||||
state.digPolicy = noop
|
state.digPolicy = noop
|
||||||
state.movePolicy = _defaultMove
|
state.movePolicy = _defaultMove
|
||||||
state.moveCallback = noop
|
state.moveCallback = noop
|
||||||
Pathing.reset()
|
Pathing.reset()
|
||||||
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -56,13 +62,10 @@ function turtle.reset()
|
|||||||
turtle.point.x = 0
|
turtle.point.x = 0
|
||||||
turtle.point.y = 0
|
turtle.point.y = 0
|
||||||
turtle.point.z = 0
|
turtle.point.z = 0
|
||||||
turtle.point.heading = 0
|
turtle.point.heading = 0 -- should be facing
|
||||||
turtle.point.gps = false
|
turtle.point.gps = false
|
||||||
turtle.abort = false -- should be part of state
|
|
||||||
--turtle.status = 'idle' -- should be part of state
|
|
||||||
|
|
||||||
turtle.resetState()
|
turtle.resetState()
|
||||||
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -121,31 +124,7 @@ function turtle.getAction(direction)
|
|||||||
return actions[direction]
|
return actions[direction]
|
||||||
end
|
end
|
||||||
|
|
||||||
-- [[ Heading data ]] --
|
|
||||||
local headings = {
|
|
||||||
[ 0 ] = { xd = 1, zd = 0, yd = 0, heading = 0, direction = 'east' },
|
|
||||||
[ 1 ] = { xd = 0, zd = 1, yd = 0, heading = 1, direction = 'south' },
|
|
||||||
[ 2 ] = { xd = -1, zd = 0, yd = 0, heading = 2, direction = 'west' },
|
|
||||||
[ 3 ] = { xd = 0, zd = -1, yd = 0, heading = 3, direction = 'north' },
|
|
||||||
[ 4 ] = { xd = 0, zd = 0, yd = 1, heading = 4, direction = 'up' },
|
|
||||||
[ 5 ] = { xd = 0, zd = 0, yd = -1, heading = 5, direction = 'down' }
|
|
||||||
}
|
|
||||||
|
|
||||||
local namedHeadings = {
|
|
||||||
east = headings[0],
|
|
||||||
south = headings[1],
|
|
||||||
west = headings[2],
|
|
||||||
north = headings[3],
|
|
||||||
up = headings[4],
|
|
||||||
down = headings[5]
|
|
||||||
}
|
|
||||||
|
|
||||||
function turtle.getHeadings() return headings end
|
|
||||||
|
|
||||||
function turtle.getHeadingInfo(heading)
|
function turtle.getHeadingInfo(heading)
|
||||||
if heading and type(heading) == 'string' then
|
|
||||||
return namedHeadings[heading]
|
|
||||||
end
|
|
||||||
heading = heading or turtle.point.heading
|
heading = heading or turtle.point.heading
|
||||||
return headings[heading]
|
return headings[heading]
|
||||||
end
|
end
|
||||||
@@ -174,6 +153,7 @@ local function inventoryAction(fn, name, qty)
|
|||||||
return s
|
return s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- [[ Attack ]] --
|
||||||
local function _attack(action)
|
local function _attack(action)
|
||||||
if action.attack() then
|
if action.attack() then
|
||||||
repeat until not action.attack()
|
repeat until not action.attack()
|
||||||
@@ -182,10 +162,21 @@ local function _attack(action)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
turtle.attackPolicies = {
|
||||||
|
none = noop,
|
||||||
|
|
||||||
|
attack = function(action)
|
||||||
|
return _attack(action)
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
function turtle.attack() return _attack(actions.forward) end
|
function turtle.attack() return _attack(actions.forward) end
|
||||||
function turtle.attackUp() return _attack(actions.up) end
|
function turtle.attackUp() return _attack(actions.up) end
|
||||||
function turtle.attackDown() return _attack(actions.down) end
|
function turtle.attackDown() return _attack(actions.down) end
|
||||||
|
|
||||||
|
function turtle.setAttackPolicy(policy) state.attackPolicy = policy end
|
||||||
|
|
||||||
|
-- [[ Place ]] --
|
||||||
local function _place(action, indexOrId)
|
local function _place(action, indexOrId)
|
||||||
|
|
||||||
local slot
|
local slot
|
||||||
@@ -238,25 +229,11 @@ function turtle.refuel(qtyOrName, qty)
|
|||||||
return inventoryAction(turtle.native.refuel, qtyOrName, qty or 64)
|
return inventoryAction(turtle.native.refuel, qtyOrName, qty or 64)
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[
|
|
||||||
function turtle.dig() return state.dig(actions.forward) end
|
|
||||||
function turtle.digUp() return state.dig(actions.up) end
|
|
||||||
function turtle.digDown() return state.dig(actions.down) end
|
|
||||||
--]]
|
|
||||||
|
|
||||||
function turtle.isTurtleAtSide(side)
|
function turtle.isTurtleAtSide(side)
|
||||||
local sideType = peripheral.getType(side)
|
local sideType = peripheral.getType(side)
|
||||||
return sideType and sideType == 'turtle'
|
return sideType and sideType == 'turtle'
|
||||||
end
|
end
|
||||||
|
|
||||||
turtle.attackPolicies = {
|
|
||||||
none = noop,
|
|
||||||
|
|
||||||
attack = function(action)
|
|
||||||
return _attack(action)
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
|
|
||||||
turtle.digPolicies = {
|
turtle.digPolicies = {
|
||||||
none = noop,
|
none = noop,
|
||||||
|
|
||||||
@@ -302,13 +279,13 @@ turtle.movePolicies = {
|
|||||||
if action.side == 'back' then
|
if action.side == 'back' then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
local oldStatus = turtle.status
|
local oldStatus = state.status
|
||||||
print('assured move: stuck')
|
print('assured move: stuck')
|
||||||
turtle.status = 'stuck'
|
state.status = 'stuck'
|
||||||
repeat
|
repeat
|
||||||
os.sleep(1)
|
os.sleep(1)
|
||||||
until _defaultMove(action)
|
until _defaultMove(action)
|
||||||
turtle.status = oldStatus
|
state.status = oldStatus
|
||||||
end
|
end
|
||||||
return true
|
return true
|
||||||
end,
|
end,
|
||||||
@@ -351,7 +328,6 @@ function turtle.setPolicy(...)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function turtle.setDigPolicy(policy) state.digPolicy = policy end
|
function turtle.setDigPolicy(policy) state.digPolicy = policy end
|
||||||
function turtle.setAttackPolicy(policy) state.attackPolicy = policy end
|
|
||||||
function turtle.setMoveCallback(cb) state.moveCallback = cb end
|
function turtle.setMoveCallback(cb) state.moveCallback = cb end
|
||||||
function turtle.clearMoveCallback() state.moveCallback = noop end
|
function turtle.clearMoveCallback() state.moveCallback = noop end
|
||||||
function turtle.getMoveCallback() return state.moveCallback end
|
function turtle.getMoveCallback() return state.moveCallback end
|
||||||
@@ -362,35 +338,31 @@ function turtle.getHeading()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function turtle.turnRight()
|
function turtle.turnRight()
|
||||||
turtle.setHeading(turtle.point.heading + 1)
|
turtle.setHeading((turtle.point.heading + 1) % 4)
|
||||||
return turtle.point
|
return turtle.point
|
||||||
end
|
end
|
||||||
|
|
||||||
function turtle.turnLeft()
|
function turtle.turnLeft()
|
||||||
turtle.setHeading(turtle.point.heading - 1)
|
turtle.setHeading((turtle.point.heading - 1) % 4)
|
||||||
return turtle.point
|
return turtle.point
|
||||||
end
|
end
|
||||||
|
|
||||||
function turtle.turnAround()
|
function turtle.turnAround()
|
||||||
turtle.setHeading(turtle.point.heading + 2)
|
turtle.setHeading((turtle.point.heading + 2) % 4)
|
||||||
return turtle.point
|
return turtle.point
|
||||||
end
|
end
|
||||||
|
|
||||||
-- combine with setHeading
|
|
||||||
function turtle.setNamedHeading(headingName)
|
|
||||||
local headingInfo = namedHeadings[headingName]
|
|
||||||
if headingInfo then
|
|
||||||
return turtle.setHeading(headingInfo.heading)
|
|
||||||
end
|
|
||||||
return false, 'Invalid heading'
|
|
||||||
end
|
|
||||||
|
|
||||||
function turtle.setHeading(heading)
|
function turtle.setHeading(heading)
|
||||||
if not heading then
|
if not heading then
|
||||||
return
|
return false, 'Invalid heading'
|
||||||
end
|
end
|
||||||
|
|
||||||
heading = heading % 4
|
local fi = Point.facings[heading]
|
||||||
|
if not fi then
|
||||||
|
return false, 'Invalid heading'
|
||||||
|
end
|
||||||
|
|
||||||
|
heading = fi.heading % 4
|
||||||
if heading ~= turtle.point.heading then
|
if heading ~= turtle.point.heading then
|
||||||
while heading < turtle.point.heading do
|
while heading < turtle.point.heading do
|
||||||
heading = heading + 4
|
heading = heading + 4
|
||||||
@@ -478,8 +450,7 @@ function turtle.back()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function turtle.moveTowardsX(dx)
|
local function moveTowardsX(dx)
|
||||||
|
|
||||||
local direction = dx - turtle.point.x
|
local direction = dx - turtle.point.x
|
||||||
local move
|
local move
|
||||||
|
|
||||||
@@ -502,8 +473,7 @@ function turtle.moveTowardsX(dx)
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
function turtle.moveTowardsZ(dz)
|
local function moveTowardsZ(dz)
|
||||||
|
|
||||||
local direction = dz - turtle.point.z
|
local direction = dz - turtle.point.z
|
||||||
local move
|
local move
|
||||||
|
|
||||||
@@ -528,13 +498,14 @@ end
|
|||||||
|
|
||||||
-- [[ go ]] --
|
-- [[ go ]] --
|
||||||
-- 1 turn goto (going backwards if possible)
|
-- 1 turn goto (going backwards if possible)
|
||||||
function turtle.gotoSingleTurn(dx, dz, dy, dh)
|
function turtle.gotoSingleTurn(dx, dy, dz, dh)
|
||||||
|
dx = dx or turtle.point.x
|
||||||
dy = dy or turtle.point.y
|
dy = dy or turtle.point.y
|
||||||
|
dz = dz or turtle.point.z
|
||||||
|
|
||||||
local function gx()
|
local function gx()
|
||||||
if turtle.point.x ~= dx then
|
if turtle.point.x ~= dx then
|
||||||
turtle.moveTowardsX(dx)
|
moveTowardsX(dx)
|
||||||
end
|
end
|
||||||
if turtle.point.z ~= dz then
|
if turtle.point.z ~= dz then
|
||||||
if dh and dh % 2 == 1 then
|
if dh and dh % 2 == 1 then
|
||||||
@@ -547,7 +518,7 @@ function turtle.gotoSingleTurn(dx, dz, dy, dh)
|
|||||||
|
|
||||||
local function gz()
|
local function gz()
|
||||||
if turtle.point.z ~= dz then
|
if turtle.point.z ~= dz then
|
||||||
turtle.moveTowardsZ(dz)
|
moveTowardsZ(dz)
|
||||||
end
|
end
|
||||||
if turtle.point.x ~= dx then
|
if turtle.point.x ~= dx then
|
||||||
if dh and dh % 2 == 0 then
|
if dh and dh % 2 == 0 then
|
||||||
@@ -587,8 +558,7 @@ function turtle.gotoSingleTurn(dx, dz, dy, dh)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
local function gotoEx(dx, dz, dy)
|
local function gotoEx(dx, dy, dz)
|
||||||
|
|
||||||
-- determine the heading to ensure the least amount of turns
|
-- determine the heading to ensure the least amount of turns
|
||||||
-- first check is 1 turn needed - remaining require 2 turns
|
-- first check is 1 turn needed - remaining require 2 turns
|
||||||
if turtle.point.heading == 0 and turtle.point.x <= dx or
|
if turtle.point.heading == 0 and turtle.point.x <= dx or
|
||||||
@@ -622,9 +592,8 @@ local function gotoEx(dx, dz, dy)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- fallback goto - will turn around if was previously moving backwards
|
-- fallback goto - will turn around if was previously moving backwards
|
||||||
local function gotoMultiTurn(dx, dz, dy)
|
local function gotoMultiTurn(dx, dy, dz)
|
||||||
|
if gotoEx(dx, dy, dz) then
|
||||||
if gotoEx(dx, dz, dy) then
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -653,19 +622,20 @@ local function gotoMultiTurn(dx, dz, dy)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
function turtle.gotoPoint(pt)
|
|
||||||
return turtle.goto(pt.x, pt.z, pt.y, pt.heading)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- go backwards - turning around if necessary to fight mobs / break blocks
|
-- go backwards - turning around if necessary to fight mobs / break blocks
|
||||||
function turtle.goback()
|
function turtle.goback()
|
||||||
local hi = headings[turtle.point.heading]
|
local hi = headings[turtle.point.heading]
|
||||||
return turtle.goto(turtle.point.x - hi.xd, turtle.point.z - hi.zd, turtle.point.y, turtle.point.heading)
|
return turtle._goto({
|
||||||
|
x = turtle.point.x - hi.xd,
|
||||||
|
y = turtle.point.y,
|
||||||
|
z = turtle.point.z - hi.zd,
|
||||||
|
heading = turtle.point.heading,
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
function turtle.gotoYfirst(pt)
|
function turtle.gotoYfirst(pt)
|
||||||
if turtle.gotoY(pt.y) then
|
if turtle._gotoY(pt.y) then
|
||||||
if turtle.goto(pt.x, pt.z, nil, pt.heading) then
|
if turtle._goto(pt) then
|
||||||
turtle.setHeading(pt.heading)
|
turtle.setHeading(pt.heading)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
@@ -673,7 +643,7 @@ function turtle.gotoYfirst(pt)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function turtle.gotoYlast(pt)
|
function turtle.gotoYlast(pt)
|
||||||
if turtle.goto(pt.x, pt.z, nil, pt.heading) then
|
if turtle._goto({ x = pt.x, z = pt.z, heading = pt.heading }) then
|
||||||
if turtle.gotoY(pt.y) then
|
if turtle.gotoY(pt.y) then
|
||||||
turtle.setHeading(pt.heading)
|
turtle.setHeading(pt.heading)
|
||||||
return true
|
return true
|
||||||
@@ -681,9 +651,10 @@ function turtle.gotoYlast(pt)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function turtle.goto(dx, dz, dy, dh)
|
function turtle._goto(pt)
|
||||||
if not turtle.gotoSingleTurn(dx, dz, dy, dh) then
|
local dx, dy, dz, dh = pt.x, pt.y, pt.z, pt.heading
|
||||||
if not gotoMultiTurn(dx, dz, dy) then
|
if not turtle.gotoSingleTurn(dx, dy, dz, dh) then
|
||||||
|
if not gotoMultiTurn(dx, dy, dz) then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -691,6 +662,9 @@ function turtle.goto(dx, dz, dy, dh)
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- avoid lint errors
|
||||||
|
turtle['goto'] = turtle._goto
|
||||||
|
|
||||||
function turtle.gotoX(dx)
|
function turtle.gotoX(dx)
|
||||||
turtle.headTowardsX(dx)
|
turtle.headTowardsX(dx)
|
||||||
|
|
||||||
@@ -730,7 +704,6 @@ end
|
|||||||
|
|
||||||
-- [[ Slot management ]] --
|
-- [[ Slot management ]] --
|
||||||
function turtle.getSlot(indexOrId, slots)
|
function turtle.getSlot(indexOrId, slots)
|
||||||
|
|
||||||
if type(indexOrId) == 'string' then
|
if type(indexOrId) == 'string' then
|
||||||
slots = slots or turtle.getInventory()
|
slots = slots or turtle.getInventory()
|
||||||
local _,c = string.gsub(indexOrId, ':', '')
|
local _,c = string.gsub(indexOrId, ':', '')
|
||||||
@@ -766,7 +739,6 @@ function turtle.getSlot(indexOrId, slots)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function turtle.select(indexOrId)
|
function turtle.select(indexOrId)
|
||||||
|
|
||||||
if type(indexOrId) == 'number' then
|
if type(indexOrId) == 'number' then
|
||||||
return turtle.native.select(indexOrId)
|
return turtle.native.select(indexOrId)
|
||||||
end
|
end
|
||||||
@@ -814,6 +786,11 @@ function turtle.getSummedInventory()
|
|||||||
return t
|
return t
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function turtle.has(item, count)
|
||||||
|
local slot = turtle.getSummedInventory()[item]
|
||||||
|
return slot and slot.count >= (count or 1)
|
||||||
|
end
|
||||||
|
|
||||||
function turtle.getFilledSlots(startSlot)
|
function turtle.getFilledSlots(startSlot)
|
||||||
startSlot = startSlot or 1
|
startSlot = startSlot or 1
|
||||||
|
|
||||||
@@ -917,7 +894,6 @@ function turtle.getItemCount(idOrName)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function turtle.equip(side, item)
|
function turtle.equip(side, item)
|
||||||
|
|
||||||
if item then
|
if item then
|
||||||
if not turtle.select(item) then
|
if not turtle.select(item) then
|
||||||
return false, 'Unable to equip ' .. item
|
return false, 'Unable to equip ' .. item
|
||||||
@@ -930,6 +906,15 @@ function turtle.equip(side, item)
|
|||||||
return turtle.equipRight()
|
return turtle.equipRight()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function turtle.isEquipped(item)
|
||||||
|
if peripheral.getType('left') == item then
|
||||||
|
return 'left'
|
||||||
|
elseif peripheral.getType('right') == item then
|
||||||
|
return 'right'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- [[ ]] --
|
||||||
function turtle.run(fn, ...)
|
function turtle.run(fn, ...)
|
||||||
local args = { ... }
|
local args = { ... }
|
||||||
local s, m
|
local s, m
|
||||||
@@ -939,36 +924,47 @@ function turtle.run(fn, ...)
|
|||||||
end
|
end
|
||||||
|
|
||||||
synchronized(turtle, function()
|
synchronized(turtle, function()
|
||||||
turtle.abort = false
|
|
||||||
turtle.status = 'busy'
|
|
||||||
turtle.resetState()
|
turtle.resetState()
|
||||||
s, m = pcall(function() fn(unpack(args)) end)
|
s, m = pcall(function() fn(unpack(args)) end)
|
||||||
turtle.abort = false
|
turtle.resetState()
|
||||||
turtle.status = 'idle'
|
|
||||||
if not s and m then
|
if not s and m then
|
||||||
printError(m)
|
_G.printError(m)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
return s, m
|
return s, m
|
||||||
end
|
end
|
||||||
|
|
||||||
function turtle.abortAction()
|
function turtle.abort(abort)
|
||||||
if turtle.status ~= 'idle' then
|
state.abort = abort
|
||||||
turtle.abort = true
|
if abort then
|
||||||
os.queueEvent('turtle_abort')
|
os.queueEvent('turtle_abort')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- [[ Pathing ]] --
|
-- [[ Pathing ]] --
|
||||||
function turtle.faceAgainst(pt, options) -- 4 sided
|
function turtle.setPersistent(isPersistent)
|
||||||
|
if isPersistent then
|
||||||
|
Pathing.setBlocks({ })
|
||||||
|
else
|
||||||
|
Pathing.setBlocks()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function turtle.setPathingBox(box)
|
||||||
|
Pathing.setBox(box)
|
||||||
|
end
|
||||||
|
|
||||||
|
function turtle.addWorldBlock(pt)
|
||||||
|
Pathing.addBlock(pt)
|
||||||
|
end
|
||||||
|
|
||||||
|
function turtle.faceAgainst(pt, options) -- 4 sided
|
||||||
options = options or { }
|
options = options or { }
|
||||||
options.dest = { }
|
options.dest = { }
|
||||||
|
|
||||||
for i = 0, 3 do
|
for i = 0, 3 do
|
||||||
local hi = turtle.getHeadingInfo(i)
|
local hi = Point.facings[i]
|
||||||
|
|
||||||
table.insert(options.dest, {
|
table.insert(options.dest, {
|
||||||
x = pt.x + hi.xd,
|
x = pt.x + hi.xd,
|
||||||
z = pt.z + hi.zd,
|
z = pt.z + hi.zd,
|
||||||
@@ -980,8 +976,11 @@ function turtle.faceAgainst(pt, options) -- 4 sided
|
|||||||
return turtle.pathfind(Point.closest(turtle.point, options.dest), options)
|
return turtle.pathfind(Point.closest(turtle.point, options.dest), options)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- move against this point
|
||||||
|
-- if the point does not contain a heading, then the turtle
|
||||||
|
-- will face the block (if on same plane)
|
||||||
|
-- if above or below, the heading is undetermined unless specified
|
||||||
function turtle.moveAgainst(pt, options) -- 6 sided
|
function turtle.moveAgainst(pt, options) -- 6 sided
|
||||||
|
|
||||||
options = options or { }
|
options = options or { }
|
||||||
options.dest = { }
|
options.dest = { }
|
||||||
|
|
||||||
@@ -1002,7 +1001,7 @@ function turtle.moveAgainst(pt, options) -- 6 sided
|
|||||||
z = pt.z + hi.zd,
|
z = pt.z + hi.zd,
|
||||||
y = pt.y + hi.yd,
|
y = pt.y + hi.yd,
|
||||||
direction = direction,
|
direction = direction,
|
||||||
heading = heading,
|
heading = pt.heading or heading,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1057,67 +1056,114 @@ local actionsAt = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- pt = { x,y,z,heading,direction }
|
||||||
|
-- direction should only be up or down if provided
|
||||||
|
-- heading can be provided to tell which way to face during action
|
||||||
|
-- ex: place a block at the point from above facing east
|
||||||
local function _actionAt(action, pt, ...)
|
local function _actionAt(action, pt, ...)
|
||||||
local pt = turtle.moveAgainst(pt)
|
if not pt.heading and not pt.direction then
|
||||||
if pt then
|
local msg
|
||||||
return action[pt.direction](...)
|
pt, msg = turtle.moveAgainst(pt)
|
||||||
|
if pt then
|
||||||
|
return action[pt.direction](...)
|
||||||
|
end
|
||||||
|
return pt, msg
|
||||||
|
end
|
||||||
|
|
||||||
|
local reversed =
|
||||||
|
{ [0] = 2, [1] = 3, [2] = 0, [3] = 1, [4] = 5, [5] = 4, }
|
||||||
|
local dir = reversed[headings[pt.direction or pt.heading].heading]
|
||||||
|
local apt = { x = pt.x + headings[dir].xd,
|
||||||
|
y = pt.y + headings[dir].yd,
|
||||||
|
z = pt.z + headings[dir].zd, }
|
||||||
|
local direction
|
||||||
|
|
||||||
|
-- ex: place a block at this point, from above, facing east
|
||||||
|
if dir < 4 then
|
||||||
|
apt.heading = (dir + 2) % 4
|
||||||
|
direction = 'forward'
|
||||||
|
elseif dir == 4 then
|
||||||
|
apt.heading = pt.heading
|
||||||
|
direction = 'down'
|
||||||
|
elseif dir == 5 then
|
||||||
|
apt.heading = pt.heading
|
||||||
|
direction = 'up'
|
||||||
|
end
|
||||||
|
|
||||||
|
if turtle.pathfind(apt) then
|
||||||
|
return action[direction](...)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function _actionDownAt(action, pt, ...)
|
local function _actionDownAt(action, pt, ...)
|
||||||
if turtle.pathfind(Point.above(pt)) then
|
pt = Util.shallowCopy(pt)
|
||||||
return action.down(...)
|
pt.direction = Point.DOWN
|
||||||
end
|
return _actionAt(action, pt, ...)
|
||||||
end
|
end
|
||||||
|
|
||||||
function _actionForwardAt(action, pt, ...)
|
local function _actionUpAt(action, pt, ...)
|
||||||
|
pt = Util.shallowCopy(pt)
|
||||||
|
pt.direction = Point.UP
|
||||||
|
return _actionAt(action, pt, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function _actionForwardAt(action, pt, ...)
|
||||||
if turtle.faceAgainst(pt) then
|
if turtle.faceAgainst(pt) then
|
||||||
return action.forward(...)
|
return action.forward(...)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function _actionUpAt(action, pt, ...)
|
function turtle.detectAt(pt) return _actionAt(actionsAt.detect, pt) end
|
||||||
if turtle.pathfind(Point.below(pt)) then
|
function turtle.detectDownAt(pt) return _actionDownAt(actionsAt.detect, pt) end
|
||||||
return action.up(...)
|
function turtle.detectForwardAt(pt) return _actionForwardAt(actionsAt.detect, pt) end
|
||||||
|
function turtle.detectUpAt(pt) return _actionUpAt(actionsAt.detect, pt) end
|
||||||
|
|
||||||
|
function turtle.digAt(pt) return _actionAt(actionsAt.dig, pt) end
|
||||||
|
function turtle.digDownAt(pt) return _actionDownAt(actionsAt.dig, pt) end
|
||||||
|
function turtle.digForwardAt(pt) return _actionForwardAt(actionsAt.dig, pt) end
|
||||||
|
function turtle.digUpAt(pt) return _actionUpAt(actionsAt.dig, pt) end
|
||||||
|
|
||||||
|
function turtle.attackAt(pt) return _actionAt(actionsAt.attack, pt) end
|
||||||
|
function turtle.attackDownAt(pt) return _actionDownAt(actionsAt.attack, pt) end
|
||||||
|
function turtle.attackForwardAt(pt) return _actionForwardAt(actionsAt.attack, pt) end
|
||||||
|
function turtle.attackUpAt(pt) return _actionUpAt(actionsAt.attack, pt) end
|
||||||
|
|
||||||
|
function turtle.placeAt(pt, arg, dir) return _actionAt(actionsAt.place, pt, arg, dir) end
|
||||||
|
function turtle.placeDownAt(pt, arg) return _actionDownAt(actionsAt.place, pt, arg) end
|
||||||
|
function turtle.placeForwardAt(pt, arg) return _actionForwardAt(actionsAt.place, pt, arg) end
|
||||||
|
function turtle.placeUpAt(pt, arg) return _actionUpAt(actionsAt.place, pt, arg) end
|
||||||
|
|
||||||
|
function turtle.dropAt(pt, ...) return _actionAt(actionsAt.drop, pt, ...) end
|
||||||
|
function turtle.dropDownAt(pt, ...) return _actionDownAt(actionsAt.drop, pt, ...) end
|
||||||
|
function turtle.dropForwardAt(pt, ...) return _actionForwardAt(actionsAt.drop, pt, ...) end
|
||||||
|
function turtle.dropUpAt(pt, ...) return _actionUpAt(actionsAt.drop, pt, ...) end
|
||||||
|
|
||||||
|
function turtle.suckAt(pt, qty) return _actionAt(actionsAt.suck, pt, qty or 64) end
|
||||||
|
function turtle.suckDownAt(pt, qty) return _actionDownAt(actionsAt.suck, pt, qty or 64) end
|
||||||
|
function turtle.suckForwardAt(pt, qty) return _actionForwardAt(actionsAt.suck, pt, qty or 64) end
|
||||||
|
function turtle.suckUpAt(pt, qty) return _actionUpAt(actionsAt.suck, pt, qty or 64) end
|
||||||
|
|
||||||
|
function turtle.compareAt(pt) return _actionAt(actionsAt.compare, pt) end
|
||||||
|
function turtle.compareDownAt(pt) return _actionDownAt(actionsAt.compare, pt) end
|
||||||
|
function turtle.compareForwardAt(pt) return _actionForwardAt(actionsAt.compare, pt) end
|
||||||
|
function turtle.compareUpAt(pt) return _actionUpAt(actionsAt.compare, pt) end
|
||||||
|
|
||||||
|
function turtle.inspectAt(pt) return _actionAt(actionsAt.inspect, pt) end
|
||||||
|
function turtle.inspectDownAt(pt) return _actionDownAt(actionsAt.inspect, pt) end
|
||||||
|
function turtle.inspectForwardAt(pt) return _actionForwardAt(actionsAt.inspect, pt) end
|
||||||
|
function turtle.inspectUpAt(pt) return _actionUpAt(actionsAt.inspect, pt) end
|
||||||
|
|
||||||
|
-- [[ GPS ]] --
|
||||||
|
function turtle.enableGPS(timeout)
|
||||||
|
local pt = GPS.getPointAndHeading(timeout)
|
||||||
|
if pt then
|
||||||
|
turtle.setPoint(pt, true)
|
||||||
|
return turtle.point
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function turtle.detectAt(pt) return _actionAt(actionsAt.detect, pt) end
|
function turtle.addFeatures(...)
|
||||||
function turtle.detectDownAt(pt) return _actionDownAt(actionsAt.detect, pt) end
|
for _,feature in pairs({ ... }) do
|
||||||
function turtle.detectForwardAt(pt) return _actionForwardAt(actionsAt.detect, pt) end
|
require('turtle.' .. feature)
|
||||||
function turtle.detectUpAt(pt) return _actionUpAt(actionsAt.detect, pt) end
|
end
|
||||||
|
end
|
||||||
function turtle.digAt(pt) return _actionAt(actionsAt.dig, pt) end
|
|
||||||
function turtle.digDownAt(pt) return _actionDownAt(actionsAt.dig, pt) end
|
|
||||||
function turtle.digForwardAt(pt) return _actionForwardAt(actionsAt.dig, pt) end
|
|
||||||
function turtle.digUpAt(pt) return _actionUpAt(actionsAt.dig, pt) end
|
|
||||||
|
|
||||||
function turtle.attackAt(pt) return _actionAt(actionsAt.attack, pt) end
|
|
||||||
function turtle.attackDownAt(pt) return _actionDownAt(actionsAt.attack, pt) end
|
|
||||||
function turtle.attackForwardAt(pt) return _actionForwardAt(actionsAt.attack, pt) end
|
|
||||||
function turtle.attackUpAt(pt) return _actionUpAt(actionsAt.attack, pt) end
|
|
||||||
|
|
||||||
function turtle.placeAt(pt, arg) return _actionAt(actionsAt.place, pt, arg) end
|
|
||||||
function turtle.placeDownAt(pt, arg) return _actionDownAt(actionsAt.place, pt, arg) end
|
|
||||||
function turtle.placeForwardAt(pt, arg) return _actionForwardAt(actionsAt.place, pt, arg) end
|
|
||||||
function turtle.placeUpAt(pt, arg) return _actionUpAt(actionsAt.place, pt, arg) end
|
|
||||||
|
|
||||||
function turtle.dropAt(pt, ...) return _actionAt(actionsAt.drop, pt, ...) end
|
|
||||||
function turtle.dropDownAt(pt, ...) return _actionDownAt(actionsAt.drop, pt, ...) end
|
|
||||||
function turtle.dropForwardAt(pt, ...) return _actionForwardAt(actionsAt.drop, pt, ...) end
|
|
||||||
function turtle.dropUpAt(pt, ...) return _actionUpAt(actionsAt.drop, pt, ...) end
|
|
||||||
|
|
||||||
function turtle.suckAt(pt, qty) return _actionAt(actionsAt.suck, pt, qty or 64) end
|
|
||||||
function turtle.suckDownAt(pt, qty) return _actionDownAt(actionsAt.suck, pt, qty or 64) end
|
|
||||||
function turtle.suckForwardAt(pt, qty) return _actionForwardAt(actionsAt.suck, pt, qty or 64) end
|
|
||||||
function turtle.suckUpAt(pt, qty) return _actionUpAt(actionsAt.suck, pt, qty or 64) end
|
|
||||||
|
|
||||||
function turtle.compareAt(pt) return _actionAt(actionsAt.compare, pt) end
|
|
||||||
function turtle.compareDownAt(pt) return _actionDownAt(actionsAt.compare, pt) end
|
|
||||||
function turtle.compareForwardAt(pt) return _actionForwardAt(actionsAt.compare, pt) end
|
|
||||||
function turtle.compareUpAt(pt) return _actionUpAt(actionsAt.compare, pt) end
|
|
||||||
|
|
||||||
function turtle.inspectAt(pt) return _actionAt(actionsAt.inspect, pt) end
|
|
||||||
function turtle.inspectDownAt(pt) return _actionDownAt(actionsAt.inspect, pt) end
|
|
||||||
function turtle.inspectForwardAt(pt) return _actionForwardAt(actionsAt.inspect, pt) end
|
|
||||||
function turtle.inspectUpAt(pt) return _actionUpAt(actionsAt.inspect, pt) end
|
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ if fs.native then
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
requireInjector(getfenv(1))
|
_G.requireInjector()
|
||||||
local Util = require('util')
|
local Util = require('util')
|
||||||
|
|
||||||
|
local fs = _G.fs
|
||||||
|
|
||||||
fs.native = Util.shallowCopy(fs)
|
fs.native = Util.shallowCopy(fs)
|
||||||
|
|
||||||
local fstypes = { }
|
local fstypes = { }
|
||||||
@@ -18,7 +20,7 @@ for k,fn in pairs(fs) do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function nativefs.list(node, dir, full)
|
function nativefs.list(node, dir)
|
||||||
|
|
||||||
local files
|
local files
|
||||||
if fs.native.isDir(dir) then
|
if fs.native.isDir(dir) then
|
||||||
@@ -43,7 +45,7 @@ function nativefs.list(node, dir, full)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if not files then
|
if not files then
|
||||||
error('Not a directory')
|
error('Not a directory', 2)
|
||||||
end
|
end
|
||||||
|
|
||||||
return files
|
return files
|
||||||
@@ -131,7 +133,7 @@ local methods = { 'delete', 'getFreeSpace', 'exists', 'isDir', 'getSize',
|
|||||||
|
|
||||||
for _,m in pairs(methods) do
|
for _,m in pairs(methods) do
|
||||||
fs[m] = function(dir, ...)
|
fs[m] = function(dir, ...)
|
||||||
dir = fs.combine(dir, '')
|
dir = fs.combine(dir or '', '')
|
||||||
local node = getNode(dir)
|
local node = getNode(dir)
|
||||||
return node.fs[m](node, dir, ...)
|
return node.fs[m](node, dir, ...)
|
||||||
end
|
end
|
||||||
@@ -314,28 +316,14 @@ local function getNodeByParts(parts)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function fs.unmount(path)
|
function fs.unmount(path)
|
||||||
|
|
||||||
local parts = splitpath(path)
|
local parts = splitpath(path)
|
||||||
local targetName = table.remove(parts, #parts)
|
local targetName = table.remove(parts, #parts)
|
||||||
|
|
||||||
local node = getNodeByParts(parts)
|
local node = getNodeByParts(parts)
|
||||||
|
|
||||||
if not node or not node.nodes[targetName] then
|
if node and node.nodes[targetName] then
|
||||||
error('Invalid node')
|
|
||||||
end
|
|
||||||
|
|
||||||
node.nodes[targetName] = nil
|
|
||||||
--[[
|
|
||||||
-- remove the shadow directories
|
|
||||||
while #parts > 0 do
|
|
||||||
targetName = table.remove(parts, #parts)
|
|
||||||
node = getNodeByParts(parts)
|
|
||||||
if not Util.empty(node.nodes[targetName].nodes) then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
node.nodes[targetName] = nil
|
node.nodes[targetName] = nil
|
||||||
end
|
end
|
||||||
--]]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function fs.registerType(name, fs)
|
function fs.registerType(name, fs)
|
||||||
|
|||||||
70
sys/network/peripheral.lua
Normal file
70
sys/network/peripheral.lua
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
--[[
|
||||||
|
Allow sharing of local peripherals.
|
||||||
|
]]--
|
||||||
|
|
||||||
|
local Event = require('event')
|
||||||
|
local Peripheral = require('peripheral')
|
||||||
|
local Socket = require('socket')
|
||||||
|
local Util = require('util')
|
||||||
|
|
||||||
|
Event.addRoutine(function()
|
||||||
|
print('peripheral: listening on port 189')
|
||||||
|
while true do
|
||||||
|
local socket = Socket.server(189)
|
||||||
|
|
||||||
|
print('peripheral: connection from ' .. socket.dhost)
|
||||||
|
|
||||||
|
Event.addRoutine(function()
|
||||||
|
local uri = socket:read(2)
|
||||||
|
if uri then
|
||||||
|
local peripheral = Peripheral.lookup(uri)
|
||||||
|
|
||||||
|
if not peripheral then
|
||||||
|
print('peripheral: invalid peripheral ' .. uri)
|
||||||
|
else
|
||||||
|
print('peripheral: proxing ' .. uri)
|
||||||
|
local proxy = {
|
||||||
|
methods = { }
|
||||||
|
}
|
||||||
|
|
||||||
|
if peripheral.blit then
|
||||||
|
peripheral = Util.shallowCopy(peripheral)
|
||||||
|
peripheral.fastBlit = function(data)
|
||||||
|
for _,v in ipairs(data) do
|
||||||
|
peripheral[v.fn](unpack(v.args))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for k,v in pairs(peripheral) do
|
||||||
|
if type(v) == 'function' then
|
||||||
|
table.insert(proxy.methods, k)
|
||||||
|
else
|
||||||
|
proxy[k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
socket:write(proxy)
|
||||||
|
|
||||||
|
if proxy.type == 'monitor' then
|
||||||
|
local h
|
||||||
|
h = Event.on('monitor_touch', function(...)
|
||||||
|
if not socket:write({ ... }) then
|
||||||
|
Event.off(h)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local data = socket:read()
|
||||||
|
if not data then
|
||||||
|
print('peripheral: lost connection from ' .. socket.dhost)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
socket:write({ peripheral[data.fn](table.unpack(data.args)) })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end)
|
||||||
35
sys/network/proxy.lua
Normal file
35
sys/network/proxy.lua
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
local Event = require('event')
|
||||||
|
local Socket = require('socket')
|
||||||
|
|
||||||
|
Event.addRoutine(function()
|
||||||
|
while true do
|
||||||
|
print('proxy: listening on port 188')
|
||||||
|
local socket = Socket.server(188)
|
||||||
|
|
||||||
|
print('proxy: connection from ' .. socket.dhost)
|
||||||
|
|
||||||
|
Event.addRoutine(function()
|
||||||
|
local api = socket:read(2)
|
||||||
|
if api then
|
||||||
|
local proxy = _G[api]
|
||||||
|
|
||||||
|
local methods = { }
|
||||||
|
for k,v in pairs(proxy) do
|
||||||
|
if type(v) == 'function' then
|
||||||
|
table.insert(methods, k)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
socket:write(methods)
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local data = socket:read()
|
||||||
|
if not data then
|
||||||
|
print('proxy: lost connection from ' .. socket.dhost)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
socket:write({ proxy[data.fn](table.unpack(data.args)) })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end)
|
||||||
@@ -3,6 +3,12 @@ local GPS = require('gps')
|
|||||||
local Socket = require('socket')
|
local Socket = require('socket')
|
||||||
local Util = require('util')
|
local Util = require('util')
|
||||||
|
|
||||||
|
local device = _G.device
|
||||||
|
local multishell = _ENV.multishell
|
||||||
|
local network = _G.network
|
||||||
|
local os = _G.os
|
||||||
|
local turtle = _G.turtle
|
||||||
|
|
||||||
-- move this into gps api
|
-- move this into gps api
|
||||||
local gpsRequested
|
local gpsRequested
|
||||||
local gpsLastPoint
|
local gpsLastPoint
|
||||||
@@ -26,20 +32,19 @@ local function snmpConnection(socket)
|
|||||||
socket:write('pong')
|
socket:write('pong')
|
||||||
|
|
||||||
elseif msg.type == 'script' then
|
elseif msg.type == 'script' then
|
||||||
local fn, msg = loadstring(msg.args, 'script')
|
local fn, err = loadstring(msg.args, 'script')
|
||||||
if fn then
|
if fn then
|
||||||
multishell.openTab({
|
multishell.openTab({
|
||||||
fn = fn,
|
fn = fn,
|
||||||
env = getfenv(1),
|
|
||||||
title = 'script',
|
title = 'script',
|
||||||
})
|
})
|
||||||
else
|
else
|
||||||
printError(msg)
|
_G.printError(err)
|
||||||
end
|
end
|
||||||
|
|
||||||
elseif msg.type == 'scriptEx' then
|
elseif msg.type == 'scriptEx' then
|
||||||
local s, m = pcall(function()
|
local s, m = pcall(function()
|
||||||
local env = setmetatable(Util.shallowCopy(getfenv(1)), { __index = _G })
|
local env = setmetatable(Util.shallowCopy(_ENV), { __index = _G })
|
||||||
local fn, m = load(msg.args, 'script', nil, env)
|
local fn, m = load(msg.args, 'script', nil, env)
|
||||||
if not fn then
|
if not fn then
|
||||||
error(m)
|
error(m)
|
||||||
@@ -85,7 +90,7 @@ local function snmpConnection(socket)
|
|||||||
}
|
}
|
||||||
if turtle then
|
if turtle then
|
||||||
info.fuel = turtle.getFuelLevel()
|
info.fuel = turtle.getFuelLevel()
|
||||||
info.status = turtle.status
|
info.status = turtle.getStatus()
|
||||||
end
|
end
|
||||||
socket:write(info)
|
socket:write(info)
|
||||||
end
|
end
|
||||||
@@ -110,8 +115,7 @@ end)
|
|||||||
device.wireless_modem.open(999)
|
device.wireless_modem.open(999)
|
||||||
print('discovery: listening on port 999')
|
print('discovery: listening on port 999')
|
||||||
|
|
||||||
Event.on('modem_message', function(e, s, sport, id, info, distance)
|
Event.on('modem_message', function(_, _, sport, id, info, distance)
|
||||||
|
|
||||||
if sport == 999 and tonumber(id) and type(info) == 'table' then
|
if sport == 999 and tonumber(id) and type(info) == 'table' then
|
||||||
if not network[id] then
|
if not network[id] then
|
||||||
network[id] = { }
|
network[id] = { }
|
||||||
@@ -140,7 +144,7 @@ local function sendInfo()
|
|||||||
info.uptime = math.floor(os.clock())
|
info.uptime = math.floor(os.clock())
|
||||||
if turtle then
|
if turtle then
|
||||||
info.fuel = turtle.getFuelLevel()
|
info.fuel = turtle.getFuelLevel()
|
||||||
info.status = turtle.status
|
info.status = turtle.getStatus()
|
||||||
info.point = turtle.point
|
info.point = turtle.point
|
||||||
info.inventory = turtle.getInventory()
|
info.inventory = turtle.getInventory()
|
||||||
info.slotIndex = turtle.getSelectedSlot()
|
info.slotIndex = turtle.getSelectedSlot()
|
||||||
@@ -162,7 +166,7 @@ Event.onInterval(10, function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
Event.on('turtle_response', function()
|
Event.on('turtle_response', function()
|
||||||
if turtle.status ~= info.status or
|
if turtle.getStatus() ~= info.status or
|
||||||
turtle.fuel ~= info.fuel then
|
turtle.fuel ~= info.fuel then
|
||||||
sendInfo()
|
sendInfo()
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ local Event = require('event')
|
|||||||
local Socket = require('socket')
|
local Socket = require('socket')
|
||||||
local Util = require('util')
|
local Util = require('util')
|
||||||
|
|
||||||
local function telnetHost(socket)
|
local multishell = _ENV.multishell
|
||||||
|
local os = _G.os
|
||||||
|
local term = _G.term
|
||||||
|
|
||||||
requireInjector(getfenv(1))
|
local function telnetHost(socket)
|
||||||
|
_G.requireInjector()
|
||||||
|
|
||||||
local Event = require('event')
|
local Event = require('event')
|
||||||
|
|
||||||
@@ -14,7 +17,7 @@ local function telnetHost(socket)
|
|||||||
|
|
||||||
local termInfo = socket:read(5)
|
local termInfo = socket:read(5)
|
||||||
if not termInfo then
|
if not termInfo then
|
||||||
printtError('read failed')
|
_G.printError('read failed')
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -45,7 +48,7 @@ local function telnetHost(socket)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local shellThread = Event.addRoutine(function()
|
local shellThread = Event.addRoutine(function()
|
||||||
os.run(getfenv(1), 'sys/apps/shell')
|
os.run(_ENV, 'sys/apps/shell')
|
||||||
Event.exitPullEvents()
|
Event.exitPullEvents()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@@ -67,7 +70,6 @@ local function telnetHost(socket)
|
|||||||
end
|
end
|
||||||
|
|
||||||
Event.addRoutine(function()
|
Event.addRoutine(function()
|
||||||
|
|
||||||
print('telnet: listening on port 23')
|
print('telnet: listening on port 23')
|
||||||
while true do
|
while true do
|
||||||
local socket = Socket.server(23)
|
local socket = Socket.server(23)
|
||||||
@@ -77,7 +79,6 @@ Event.addRoutine(function()
|
|||||||
multishell.openTab({
|
multishell.openTab({
|
||||||
fn = telnetHost,
|
fn = telnetHost,
|
||||||
args = { socket },
|
args = { socket },
|
||||||
env = getfenv(1),
|
|
||||||
title = 'Telnet Client',
|
title = 'Telnet Client',
|
||||||
hidden = true,
|
hidden = true,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
requireInjector(getfenv(1))
|
_G.requireInjector()
|
||||||
|
|
||||||
local Event = require('event')
|
local Event = require('event')
|
||||||
local Peripheral = require('peripheral')
|
local Peripheral = require('peripheral')
|
||||||
local Util = require('util')
|
local Util = require('util')
|
||||||
|
|
||||||
|
local colors = _G.colors
|
||||||
|
local device = _G.device
|
||||||
|
local multishell = _ENV.multishell
|
||||||
|
local os = _G.os
|
||||||
|
local term = _G.term
|
||||||
|
|
||||||
multishell.setTitle(multishell.getCurrent(), 'Devices')
|
multishell.setTitle(multishell.getCurrent(), 'Devices')
|
||||||
|
|
||||||
local attachColor = colors.green
|
local attachColor = colors.green
|
||||||
@@ -14,7 +20,7 @@ if not term.isColor() then
|
|||||||
detachColor = colors.lightGray
|
detachColor = colors.lightGray
|
||||||
end
|
end
|
||||||
|
|
||||||
Event.on('peripheral', function(event, side)
|
Event.on('peripheral', function(_, side)
|
||||||
if side then
|
if side then
|
||||||
local dev = Peripheral.addDevice(device, side)
|
local dev = Peripheral.addDevice(device, side)
|
||||||
if dev then
|
if dev then
|
||||||
@@ -25,7 +31,7 @@ Event.on('peripheral', function(event, side)
|
|||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
Event.on('peripheral_detach', function(event, side)
|
Event.on('peripheral_detach', function(_, side)
|
||||||
if side then
|
if side then
|
||||||
local dev = Util.find(device, 'side', side)
|
local dev = Util.find(device, 'side', side)
|
||||||
if dev then
|
if dev then
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
if device.wireless_modem then
|
if device.wireless_modem then
|
||||||
|
|
||||||
requireInjector(getfenv(1))
|
_G.requireInjector()
|
||||||
local Config = require('config')
|
local Config = require('config')
|
||||||
|
|
||||||
local config = { }
|
local config = { }
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
requireInjector(getfenv(1))
|
_G.requireInjector()
|
||||||
|
|
||||||
local Terminal = require('terminal')
|
local Terminal = require('terminal')
|
||||||
local Util = require('util')
|
local Util = require('util')
|
||||||
|
|
||||||
|
local multishell = _ENV.multishell
|
||||||
|
local os = _G.os
|
||||||
|
local term = _G.term
|
||||||
|
|
||||||
multishell.setTitle(multishell.getCurrent(), 'Debug')
|
multishell.setTitle(multishell.getCurrent(), 'Debug')
|
||||||
|
|
||||||
term.redirect(Terminal.scrollable(term.current(), 50))
|
term.redirect(Terminal.scrollable(term.current(), 50))
|
||||||
|
|
||||||
local tabId = multishell.getCurrent()
|
local tabId = multishell.getCurrent()
|
||||||
local tab = multishell.getTab(tabId)
|
|
||||||
local terminal = term.current()
|
local terminal = term.current()
|
||||||
local previousId
|
local previousId
|
||||||
|
|
||||||
@@ -22,7 +25,7 @@ end
|
|||||||
print('Debug started')
|
print('Debug started')
|
||||||
print('Press ^d to activate debug window')
|
print('Press ^d to activate debug window')
|
||||||
|
|
||||||
multishell.addHotkey(32, function()
|
multishell.addHotkey('control-d', function()
|
||||||
local currentId = multishell.getFocus()
|
local currentId = multishell.getFocus()
|
||||||
if currentId ~= tabId then
|
if currentId ~= tabId then
|
||||||
previousId = currentId
|
previousId = currentId
|
||||||
@@ -37,4 +40,4 @@ os.pullEventRaw('terminate')
|
|||||||
print('Debug stopped')
|
print('Debug stopped')
|
||||||
|
|
||||||
_G.debug = function() end
|
_G.debug = function() end
|
||||||
multishell.removeHotkey(32)
|
multishell.removeHotkey('control-d')
|
||||||
|
|||||||
@@ -1,17 +1,25 @@
|
|||||||
requireInjector(getfenv(1))
|
_G.requireInjector()
|
||||||
|
|
||||||
local Util = require('util')
|
local Util = require('util')
|
||||||
|
|
||||||
|
local device = _G.device
|
||||||
|
local fs = _G.fs
|
||||||
|
local multishell = _ENV.multishell
|
||||||
|
local os = _G.os
|
||||||
|
local printError = _G.printError
|
||||||
|
|
||||||
|
local network = { }
|
||||||
|
_G.network = network
|
||||||
|
|
||||||
multishell.setTitle(multishell.getCurrent(), 'Net Daemon')
|
multishell.setTitle(multishell.getCurrent(), 'Net Daemon')
|
||||||
|
|
||||||
_G.network = { }
|
|
||||||
|
|
||||||
local function netUp()
|
local function netUp()
|
||||||
requireInjector(getfenv(1))
|
_G.requireInjector()
|
||||||
|
|
||||||
local Event = require('event')
|
local Event = require('event')
|
||||||
|
|
||||||
for _,file in pairs(fs.list('sys/network')) do
|
for _,file in pairs(fs.list('sys/network')) do
|
||||||
local fn, msg = Util.run(getfenv(1), 'sys/network/' .. file)
|
local fn, msg = Util.run(_ENV, 'sys/network/' .. file)
|
||||||
if not fn then
|
if not fn then
|
||||||
printError(msg)
|
printError(msg)
|
||||||
end
|
end
|
||||||
@@ -41,7 +49,7 @@ local function startNetwork()
|
|||||||
print('Starting network services')
|
print('Starting network services')
|
||||||
|
|
||||||
local success, msg = Util.runFunction(
|
local success, msg = Util.runFunction(
|
||||||
Util.shallowCopy(getfenv(1)), netUp)
|
Util.shallowCopy(_ENV), netUp)
|
||||||
|
|
||||||
if not success and msg then
|
if not success and msg then
|
||||||
printError(msg)
|
printError(msg)
|
||||||
@@ -56,7 +64,7 @@ else
|
|||||||
end
|
end
|
||||||
|
|
||||||
while true do
|
while true do
|
||||||
local e, deviceName = os.pullEvent('device_attach')
|
local _, deviceName = os.pullEvent('device_attach')
|
||||||
if deviceName == 'wireless_modem' then
|
if deviceName == 'wireless_modem' then
|
||||||
startNetwork()
|
startNetwork()
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,14 +6,18 @@
|
|||||||
* background read buffering
|
* background read buffering
|
||||||
]]--
|
]]--
|
||||||
|
|
||||||
|
local multishell = _ENV.multishell
|
||||||
|
local os = _G.os
|
||||||
|
|
||||||
multishell.setTitle(multishell.getCurrent(), 'Net transport')
|
multishell.setTitle(multishell.getCurrent(), 'Net transport')
|
||||||
|
|
||||||
local computerId = os.getComputerID()
|
local computerId = os.getComputerID()
|
||||||
|
|
||||||
_G.transport = {
|
local transport = {
|
||||||
timers = { },
|
timers = { },
|
||||||
sockets = { },
|
sockets = { },
|
||||||
}
|
}
|
||||||
|
_G.transport = transport
|
||||||
|
|
||||||
function transport.open(socket)
|
function transport.open(socket)
|
||||||
transport.sockets[socket.sport] = socket
|
transport.sockets[socket.sport] = socket
|
||||||
@@ -27,11 +31,10 @@ function transport.read(socket)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function transport.write(socket, data)
|
function transport.write(socket, data)
|
||||||
|
|
||||||
--debug('>> ' .. Util.tostring({ type = 'DATA', seq = socket.wseq }))
|
--debug('>> ' .. Util.tostring({ type = 'DATA', seq = socket.wseq }))
|
||||||
socket.transmit(socket.dport, socket.dhost, data)
|
socket.transmit(socket.dport, socket.dhost, data)
|
||||||
|
|
||||||
local timerId = os.startTimer(2)
|
local timerId = os.startTimer(3)
|
||||||
|
|
||||||
transport.timers[timerId] = socket
|
transport.timers[timerId] = socket
|
||||||
socket.timers[socket.wseq] = timerId
|
socket.timers[socket.wseq] = timerId
|
||||||
@@ -39,6 +42,18 @@ function transport.write(socket, data)
|
|||||||
socket.wseq = socket.wseq + 1
|
socket.wseq = socket.wseq + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function transport.ping(socket)
|
||||||
|
--debug('>> ' .. Util.tostring({ type = 'DATA', seq = socket.wseq }))
|
||||||
|
socket.transmit(socket.dport, socket.dhost, {
|
||||||
|
type = 'PING',
|
||||||
|
seq = -1,
|
||||||
|
})
|
||||||
|
|
||||||
|
local timerId = os.startTimer(3)
|
||||||
|
transport.timers[timerId] = socket
|
||||||
|
socket.timers[-1] = timerId
|
||||||
|
end
|
||||||
|
|
||||||
function transport.close(socket)
|
function transport.close(socket)
|
||||||
transport.sockets[socket.sport] = nil
|
transport.sockets[socket.sport] = nil
|
||||||
end
|
end
|
||||||
@@ -50,6 +65,7 @@ while true do
|
|||||||
|
|
||||||
if e == 'timer' then
|
if e == 'timer' then
|
||||||
local socket = transport.timers[timerId]
|
local socket = transport.timers[timerId]
|
||||||
|
|
||||||
if socket and socket.connected then
|
if socket and socket.connected then
|
||||||
print('transport timeout - closing socket ' .. socket.sport)
|
print('transport timeout - closing socket ' .. socket.sport)
|
||||||
socket:close()
|
socket:close()
|
||||||
@@ -68,11 +84,12 @@ while true do
|
|||||||
socket:close()
|
socket:close()
|
||||||
|
|
||||||
elseif msg.type == 'ACK' then
|
elseif msg.type == 'ACK' then
|
||||||
local timerId = socket.timers[msg.seq]
|
local ackTimerId = socket.timers[msg.seq]
|
||||||
|
if ackTimerId then
|
||||||
os.cancelTimer(timerId)
|
os.cancelTimer(ackTimerId)
|
||||||
socket.timers[msg.seq] = nil
|
socket.timers[msg.seq] = nil
|
||||||
transport.timers[timerId] = nil
|
transport.timers[ackTimerId] = nil
|
||||||
|
end
|
||||||
|
|
||||||
elseif msg.type == 'PING' then
|
elseif msg.type == 'PING' then
|
||||||
socket.transmit(socket.dport, socket.dhost, {
|
socket.transmit(socket.dport, socket.dhost, {
|
||||||
|
|||||||
Reference in New Issue
Block a user