This commit is contained in:
2025-10-05 19:50:29 -04:00
parent f447c039c7
commit 01f216f31e
16 changed files with 2824 additions and 2596 deletions

2
.gitignore vendored
View File

@@ -1,3 +1 @@
.*
!.gitignore
build/

View File

@@ -46,8 +46,10 @@ function filter.chain(...)
while true do
if index == top then
chunk = arg[index](chunk)
if chunk == "" or top == n then return chunk
elseif chunk then index = index + 1
if chunk == "" or top == n then
return chunk
elseif chunk then
index = index + 1
else
top = top + 1
index = top
@@ -58,9 +60,14 @@ function filter.chain(...)
index = index - 1
chunk = retry
elseif chunk then
if index == n then return chunk
else index = index + 1 end
else base.error("filter returned inappropriate nil") end
if index == n then
return chunk
else
index = index + 1
end
else
base.error("filter returned inappropriate nil")
end
end
end
end
@@ -93,7 +100,9 @@ function source.file(handle, io_err)
if not chunk then handle:close() end
return chunk
end
else return source.error(io_err or "unable to open file") end
else
return source.error(io_err or "unable to open file")
end
end
-- turns a fancy source into a simple source
@@ -102,8 +111,11 @@ function source.simplify(src)
return function()
local chunk, err_or_new = src()
src = err_or_new or src
if not chunk then return nil, err_or_new
else return chunk end
if not chunk then
return nil, err_or_new
else
return chunk
end
end
end
@@ -114,10 +126,15 @@ function source.string(s)
return function()
local chunk = string.sub(s, i, i + BLOCKSIZE - 1)
i = i + BLOCKSIZE
if chunk ~= "" then return chunk
else return nil end
if chunk ~= "" then
return chunk
else
return nil
end
end
else
return source.empty()
end
else return source.empty() end
end
-- creates rewindable source
@@ -127,8 +144,11 @@ function source.rewind(src)
return function(chunk)
if not chunk then
chunk = table.remove(t)
if not chunk then return src()
else return chunk end
if not chunk then
return src()
else
return chunk
end
else
table.insert(t, chunk)
end
@@ -228,9 +248,13 @@ function sink.file(handle, io_err)
if not chunk then
handle:close()
return 1
else return handle:write(chunk) end
else
return handle:write(chunk)
end
end
else
return sink.error(io_err or "unable to open file")
end
else return sink.error(io_err or "unable to open file") end
end
-- creates a sink that discards data
@@ -262,7 +286,9 @@ function sink.chain(f, snk)
if filtered == done then return 1 end
filtered = f(done)
end
else return 1 end
else
return 1
end
end
end
@@ -273,8 +299,11 @@ end
function pump.step(src, snk)
local chunk, src_err = src()
local ret, snk_err = snk(chunk, src_err)
if chunk and ret then return 1
else return nil, src_err or snk_err end
if chunk and ret then
return 1
else
return nil, src_err or snk_err
end
end
-- pumps all data from a source to a sink, using a step function
@@ -284,9 +313,11 @@ function pump.all(src, snk, step)
while true do
local ret, err = step(src, snk)
if not ret then
if err then return nil, err
else return 1 end
if err then
return nil, err
else
return 1
end
end
end
end

View File

@@ -29,7 +29,9 @@ local function choose(table)
local f = table[name or "nil"]
if not f then
base.error("unknown key (" .. base.tostring(name) .. ")", 3)
else return f(opt1, opt2) end
else
return f(opt1, opt2)
end
end
end
@@ -54,9 +56,14 @@ end
local function format(chunk)
if chunk then
if chunk == "" then return "''"
else return string.len(chunk) end
else return "nil" end
if chunk == "" then
return "''"
else
return string.len(chunk)
end
else
return "nil"
end
end
-- define the line-wrap filters

View File

@@ -47,8 +47,11 @@ function choose(table)
name, opt1, opt2 = "default", name, opt1
end
local f = table[name or "nil"]
if not f then base.error("unknown key (".. base.tostring(name) ..")", 3)
else return f(opt1, opt2) end
if not f then
base.error("unknown key (" .. base.tostring(name) .. ")", 3)
else
return f(opt1, opt2)
end
end
end
@@ -70,7 +73,9 @@ sinkt["close-when-done"] = function(sock)
if not chunk then
sock:close()
return 1
else return sock:send(chunk) end
else
return sock:send(chunk)
end
end
})
end
@@ -81,8 +86,11 @@ sinkt["keep-open"] = function(sock)
dirty = function() return sock:dirty() end
}, {
__call = function(self, chunk, err)
if chunk then return sock:send(chunk)
else return 1 end
if chunk then
return sock:send(chunk)
else
return 1
end
end
})
end
@@ -116,12 +124,15 @@ sourcet["until-closed"] = function(sock)
__call = function()
if done then return nil end
local chunk, err, partial = sock:receive(socket.BLOCKSIZE)
if not err then return chunk
if not err then
return chunk
elseif err == "closed" then
sock:close()
done = 1
return partial
else return nil, err end
else
return nil, err
end
end
})
end
@@ -130,4 +141,3 @@ end
sourcet["default"] = sourcet["until-closed"]
source = choose(sourcet)

View File

@@ -194,7 +194,9 @@ local function override(t)
u[i] = v
end
return u
else return t end
else
return t
end
end
local function tput(putt)
@@ -236,8 +238,11 @@ local function sput(u, body)
end
put = socket.protect(function(putt, body)
if base.type(putt) == "string" then return sput(putt, body)
else return tput(putt) end
if base.type(putt) == "string" then
return sput(putt, body)
else
return tput(putt)
end
end)
local function tget(gett)
@@ -275,7 +280,9 @@ command = socket.protect(function(cmdt)
end)
get = socket.protect(function(gett)
if base.type(gett) == "string" then return sget(gett)
else return tget(gett) end
if base.type(gett) == "string" then
return sget(gett)
else
return tget(gett)
end
end)

View File

@@ -52,8 +52,11 @@ local function receiveheaders(sock, headers)
if err then return nil, err end
end
-- save pair in table
if headers[name] then headers[name] = headers[name] .. ", " .. value
else headers[name] = value end
if headers[name] then
headers[name] = headers[name] .. ", " .. value
else
headers[name] = value
end
end
return headers
end
@@ -162,8 +165,11 @@ function metat.__index:receivebody(headers, sink, step)
local length = base.tonumber(headers["content-length"])
local t = headers["transfer-encoding"] -- shortcut
local mode = "default" -- connection close
if t and t ~= "identity" then mode = "http-chunked"
elseif base.tonumber(headers["content-length"]) then mode = "by-length" end
if t and t ~= "identity" then
mode = "http-chunked"
elseif base.tonumber(headers["content-length"]) then
mode = "by-length"
end
return self.try(ltn12.pump.all(socket.source(mode, self.c, length),
sink, step))
end
@@ -345,6 +351,9 @@ local function srequest(u, b)
end
request = socket.protect(function(reqt, body)
if base.type(reqt) == "string" then return srequest(reqt, body)
else return trequest(reqt) end
if base.type(reqt) == "string" then
return srequest(reqt, body)
else
return trequest(reqt)
end
end)

View File

@@ -192,9 +192,13 @@ local function send_source(mesgt)
-- send body from source
while true do
local chunk, err = mesgt.body()
if err then coroutine.yield(nil, err)
elseif chunk then coroutine.yield(chunk)
else break end
if err then
coroutine.yield(nil, err)
elseif chunk then
coroutine.yield(chunk)
else
break
end
end
end
@@ -211,9 +215,13 @@ end
-- message source
function send_message(mesgt)
if base.type(mesgt.body) == "table" then send_multipart(mesgt)
elseif base.type(mesgt.body) == "function" then send_source(mesgt)
else send_string(mesgt) end
if base.type(mesgt.body) == "table" then
send_multipart(mesgt)
elseif base.type(mesgt.body) == "function" then
send_source(mesgt)
else
send_string(mesgt)
end
end
-- set defaul headers
@@ -233,8 +241,11 @@ function message(mesgt)
local co = coroutine.create(function() send_message(mesgt) end)
return function()
local ret, a, b = coroutine.resume(co)
if ret then return a, b
else return nil, a end
if ret then
return a, b
else
return nil, a
end
end
end

View File

@@ -57,10 +57,15 @@ function metat.__index:check(ok)
end
return nil, reply
else
if string.find(code, ok) then return base.tonumber(code), reply
else return nil, reply end
if string.find(code, ok) then
return base.tonumber(code), reply
else
return nil, reply
end
end
else
return ok(base.tonumber(code), reply)
end
else return ok(base.tonumber(code), reply) end
end
function metat.__index:command(cmd, arg)
@@ -120,4 +125,3 @@ function connect(host, port, timeout, create)
end
return base.setmetatable({ c = c }, metat)
end

View File

@@ -56,8 +56,11 @@ local segment_set = make_set {
local function protect_segment(s)
return string.gsub(s, "([^A-Za-z0-9_])", function(c)
if segment_set[c] then return c
else return string.format("%%%02x", string.byte(c)) end
if segment_set[c] then
return c
else
return string.format("%%%02x", string.byte(c))
end
end)
end
@@ -136,7 +139,9 @@ function parse(url, default)
end)
-- get scheme
url = string.gsub(url, "^([%w][%w%+%-%.]*)%:",
function(s) parsed.scheme = s; return "" end)
function(s)
parsed.scheme = s; return ""
end)
-- get authority
url = string.gsub(url, "^//([^/]*)", function(n)
parsed.authority = n
@@ -157,14 +162,20 @@ function parse(url, default)
local authority = parsed.authority
if not authority then return parsed end
authority = string.gsub(authority, "^([^@]*)@",
function(u) parsed.userinfo = u; return "" end)
function(u)
parsed.userinfo = u; return ""
end)
authority = string.gsub(authority, ":([^:]*)$",
function(p) parsed.port = p; return "" end)
function(p)
parsed.port = p; return ""
end)
if authority ~= "" then parsed.host = authority end
local userinfo = parsed.userinfo
if not userinfo then return parsed end
userinfo = string.gsub(userinfo, ":([^:]*)$",
function(p) parsed.password = p; return "" end)
function(p)
parsed.password = p; return ""
end)
parsed.user = userinfo
return parsed
end
@@ -218,9 +229,12 @@ function absolute(base_url, relative_url)
base_parsed = parse(base_url)
end
local relative_parsed = parse(relative_url)
if not base_parsed then return relative_url
elseif not relative_parsed then return base_url
elseif relative_parsed.scheme then return relative_url
if not base_parsed then
return relative_url
elseif not relative_parsed then
return base_url
elseif relative_parsed.scheme then
return relative_url
else
relative_parsed.scheme = base_parsed.scheme
if not relative_parsed.authority then

View File

@@ -4,7 +4,9 @@
-- Utility: Convert a list of strings into a map of string->true
local function tmap(t) local u = {}; for _, n in ipairs(t) do u[n] = true end; return u; end
local function tmap(t)
local u = {}; for _, n in ipairs(t) do u[n] = true end; return u;
end
-- Save banned global variables for wrapping with safe functions
local old_io = io
@@ -104,6 +106,7 @@ function _bllua_io_open(fn, md)
return nil, err
end
end
-- Allow io.type (works on file handles returned by io.open)
function _bllua_io_type(f)
return old_io.type(f)
@@ -128,6 +131,7 @@ function _bllua_requiresecure(name)
return old_require(name)
end
end
package = {
seeall = old_package.seeall,
}

View File

@@ -118,7 +118,8 @@ end
bl._forceType = bl._forceType or {}
local function valFromTs(val, name, name2) -- todo: ensure name and name2 are already lowercase
if type(val) ~= 'string' then
error('valFromTs: expected string, got '..type(val), 3) end
error('valFromTs: expected string, got ' .. type(val), 3)
end
if name then
name = name:lower()
if bl._forceType[name] then
@@ -143,9 +144,11 @@ local function valFromTs(val, name, name2) -- todo: ensure name and name2 are al
'^(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+) ' ..
'(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+)$')
-- box (2 vectors)
if x1S then return {
if x1S then
return {
vector { tonumber(x1S), tonumber(y1S), tonumber(z1S) },
vector{tonumber(x2S),tonumber(y2S),tonumber(z2S)} } end
vector { tonumber(x2S), tonumber(y2S), tonumber(z2S) } }
end
-- string
return val
end
@@ -162,15 +165,18 @@ end
local function classFromForceTypeStr(name)
local class, rest = name:match('^([a-zA-Z0-9_]+)(::.+)$')
if not class then
class, rest = name:match('^([a-zA-Z0-9_]+)(%..+)$') end
class, rest = name:match('^([a-zA-Z0-9_]+)(%..+)$')
end
return class, rest
end
local setForceType
setForceType = function(ftname, typ)
if typ ~= 'boolean' and typ ~= 'object' and typ ~= nil then
error('bl.type: can only set type to \'boolean\', \'object\', or nil', 2) end
error('bl.type: can only set type to \'boolean\', \'object\', or nil', 2)
end
if not isValidFuncNameNsArgn(ftname) then
error('bl.type: invalid function or variable name \''..ftname..'\'', 2) end
error('bl.type: invalid function or variable name \'' .. ftname .. '\'', 2)
end
ftname = ftname:lower()
@@ -201,8 +207,11 @@ local function tsIsFunction(name) return tsBool(_bllua_ts.call('isFunction', nam
local function tsIsFunctionNs(ns, name) return tsBool(_bllua_ts.call('isFunction', ns, name)) end
local function tsIsFunctionNsname(nsname)
local ns, name = nsname:match('^([^:]+)::([^:]+)$')
if ns then return tsIsFunctionNs(ns, name)
else return tsIsFunction(nsname) end
if ns then
return tsIsFunctionNs(ns, name)
else
return tsIsFunction(nsname)
end
end
function bl.isObject(obj)
@@ -215,12 +224,15 @@ function bl.isObject(obj)
end
return tsIsObject(obj)
end
function bl.isFunction(a1, a2)
if type(a1) ~= 'string' then
error('bl.isFunction: argument #1: expected string', 2) end
error('bl.isFunction: argument #1: expected string', 2)
end
if a2 then
if type(a2) ~= 'string' then
error('bl.isFunction: argument #2: expected string', 2) end
error('bl.isFunction: argument #2: expected string', 2)
end
return tsIsFunctionNs(a1, a2)
else
return tsIsFunction(a1)
@@ -237,9 +249,11 @@ local tsClassMeta = {
bl._objectUserMetas = bl._objectUserMetas or {}
function bl.class(cname, inhname)
if not (type(cname) == 'string' and isValidFuncName(cname)) then
error('bl.class: argument #1: invalid class name', 2) end
error('bl.class: argument #1: invalid class name', 2)
end
if not (inhname == nil or (type(inhname) == 'string' and isValidFuncName(inhname))) then
error('bl.class: argument #2: inherit name must be a string or nil', 2) end
error('bl.class: argument #2: inherit name must be a string or nil', 2)
end
cname = cname:lower()
local met = bl._objectUserMetas[cname] or {
@@ -254,15 +268,18 @@ function bl.class(cname, inhname)
inhname = inhname:lower()
local inh = bl._objectUserMetas[inhname]
if not inh then error('bl.class: argument #2: \''..inhname..'\' is not the '..
'name of an existing class', 2) end
if not inh then
error('bl.class: argument #2: \'' .. inhname .. '\' is not the ' ..
'name of an existing class', 2)
end
inh._children[cname] = true
local inhI = met._inherit
if inhI and inhI ~= inh then
error('bl.class: argument #2: class already exists and ' ..
'inherits a different parent.', 2) end
'inherits a different parent.', 2)
end
met._inherit = inh
-- apply inherited method and field types
@@ -274,6 +291,7 @@ function bl.class(cname, inhname)
end
end
end
local function objectInheritedMetas(name)
local inh = bl._objectUserMetas[name:lower()]
return function()
@@ -288,15 +306,18 @@ local tsObjectMeta = {
-- Return torque member function or value
__index = function(t, name)
if rawget(t, '_deleted') then
error('ts object index: object no longer exists', 2) end
error('ts object index: object no longer exists', 2)
end
if type(name) ~= 'string' and type(name) ~= 'number' then
error('ts object index: index must be a string or number', 2) end
error('ts object index: index must be a string or number', 2)
end
if getmetatable(t)[name] then
return getmetatable(t)[name]
elseif type(name) == 'number' then
if not tsIsFunctionNs(rawget(t, '_tsNamespace'), 'getObject') then
error('ts object __index: index is number, but object does not have ' ..
'getObject method', 2) end
'getObject method', 2)
end
return toTsObject(_bllua_ts.callobj(t._tsObjectId, 'getObject',
tostring(name)))
else
@@ -325,9 +346,11 @@ local tsObjectMeta = {
-- Use :set() to set Torque data
__newindex = function(t, name, val)
if rawget(t, '_deleted') then
error('ts object newindex: object no longer exists', 2) end
error('ts object newindex: object no longer exists', 2)
end
if type(name) ~= 'string' then
error('ts object newindex: index must be a string', 2) end
error('ts object newindex: index must be a string', 2)
end
rawset(t, name, val)
-- create strong reference since it's now storing lua data
bl._objectsStrong[rawget(t, '_tsObjectId')] = t
@@ -336,11 +359,14 @@ local tsObjectMeta = {
-- Use to set torque data
set = function(t, name, val)
if t == nil or type(t) ~= 'table' or not t._tsObjectId then
error('ts object method: be sure to use :func() not .func()', 2) end
error('ts object method: be sure to use :func() not .func()', 2)
end
if t._deleted then
error('ts object method: object no longer exists', 2) end
error('ts object method: object no longer exists', 2)
end
if type(name) ~= 'string' then
error('ts object :set(): index must be a string', 2) end
error('ts object :set(): index must be a string', 2)
end
_bllua_ts.setfield(t._tsObjectId, name, valToTs(val))
end,
-- __tostring: Called when printing
@@ -353,9 +379,11 @@ local tsObjectMeta = {
-- If the object has a getCount method, return its count
__len = function(t)
if t._deleted then
error('ts object __len: object no longer exists', 2) end
error('ts object __len: object no longer exists', 2)
end
if not tsIsFunctionNs(t._tsNamespace, 'getCount') then
error('ts object __len: object has no getCount method', 2) end
error('ts object __len: object has no getCount method', 2)
end
return tonumber(_bllua_ts.callobj(t._tsObjectId, 'getCount'))
end,
-- object:members()
@@ -363,14 +391,17 @@ local tsObjectMeta = {
-- for index, object in group:members() do ... end
members = function(t)
if t == nil then
error('ts object method: be sure to use :func() not .func()', 2) end
error('ts object method: be sure to use :func() not .func()', 2)
end
if t._deleted then
error('ts object method: object no longer exists', 2) end
error('ts object method: object no longer exists', 2)
end
if not (
tsIsFunctionNs(t._tsNamespace, 'getCount') and
tsIsFunctionNs(t._tsNamespace, 'getObject')) then
error('ts object :members() - ' ..
'Object does not have getCount and getObject methods', 2) end
'Object does not have getCount and getObject methods', 2)
end
local count = tonumber(_bllua_ts.callobj(t._tsObjectId, 'getCount'))
local idx = 0
return function()
@@ -387,23 +418,29 @@ local tsObjectMeta = {
-- Wrap some Torque functions for performance and error checking
getName = function(t)
if t == nil then
error('ts object method: be sure to use :func() not .func()', 2) end
error('ts object method: be sure to use :func() not .func()', 2)
end
if t._deleted then
error('ts object method: object no longer exists', 2) end
error('ts object method: object no longer exists', 2)
end
return t._tsName
end,
getId = function(t)
if t == nil then
error('ts object method: be sure to use :func() not .func()', 2) end
error('ts object method: be sure to use :func() not .func()', 2)
end
if t._deleted then
error('ts object method: object no longer exists', 2) end
error('ts object method: object no longer exists', 2)
end
return tonumber(t._tsObjectId)
end,
getType = function(t)
if t == nil then
error('ts object method: be sure to use :func() not .func()', 2) end
error('ts object method: be sure to use :func() not .func()', 2)
end
if t._deleted then
error('ts object method: object no longer exists', 2) end
error('ts object method: object no longer exists', 2)
end
return tsTypesByNum[_bllua_ts.callobj(t._tsObjectId, 'getType')]
end,
---- Schedule method for objects
@@ -425,9 +462,11 @@ local tsObjectMeta = {
--end,
exists = function(t)
if t == nil then
error('ts object method: be sure to use :func() not .func()', 2) end
error('ts object method: be sure to use :func() not .func()', 2)
end
if t._deleted then
return false end
return false
end
return tsIsObject(t._tsObjectId)
end,
}
@@ -457,13 +496,15 @@ end
-- Return a Torque object for the object ID string, or create one if none exists
toTsObject = function(idiS)
if type(idiS) ~= 'string' then
error('toTsObject: input must be a string', 2) end
error('toTsObject: input must be a string', 2)
end
idiS = idiS:lower()
if bl._objectsWeak[idiS] then return bl._objectsWeak[idiS] end
if not tsBool(_bllua_ts.call('isObject', idiS)) then
--error('toTsObject: object \''..idiS..'\' does not exist', 2) end
return nil end
return nil
end
local className = _bllua_ts.callobj(idiS, 'getClassName')
local obj = {
@@ -530,12 +571,15 @@ function bl.call(func, ...)
local argsS = arglistToTs(args)
return _bllua_ts.call(func, unpack(argsS))
end
function bl.eval(code)
return valFromTs(_bllua_ts.eval(code))
end
function bl.exec(file)
return valFromTs(_bllua_ts.call('exec', file))
end
function bl.boolean(val)
return val ~= nil and
val ~= false and
@@ -543,6 +587,7 @@ function bl.boolean(val)
--val~='0' and
val ~= 0
end
function bl.object(id)
if type(id) == 'table' and id._tsObjectId then
return id
@@ -552,14 +597,17 @@ function bl.object(id)
error('bl.toobject: id must be a ts object, number, or string', 2)
end
end
function bl.array(name, ...)
local rest = { ... }
return name .. table.concat(rest, '_')
end
function _bllua_call(fnameS, ...)
local args = arglistFromTs(fnameS:lower(), { ... })
if not _G[fnameS] then
error('luacall: no global lua function named \''..fnameS..'\'') end
error('luacall: no global lua function named \'' .. fnameS .. '\'')
end
-- todo: library fields and object methods
local res = _G[fnameS](args)
return valToTs(res)
@@ -591,6 +639,7 @@ function bl.schedule(time, cb, ...)
bl._scheduleTable[id] = sch
return sch
end
function _bllua_schedule_callback(idS)
local id = tonumber(idS) or
error('_bllua_schedule_callback: invalid id: ' .. tostring(idS))
@@ -609,9 +658,11 @@ function _bllua_process_cmd(cmdS, ...)
if not func then error('_bllua_process_cmd: no cmd named \'' .. cmd .. '\'') end
func(unpack(args)) --pcall(func, unpack(args))
end
local function addCmd(cmd, func)
if not isValidFuncName(cmd) then
error('addCmd: invalid function name \''..tostring(cmd)..'\'') end
error('addCmd: invalid function name \'' .. tostring(cmd) .. '\'')
end
bl._cmds[cmd] = func
local arglist = '%a,%b,%c,%d,%e,%f,%g,%h,%i,%j,%k,%l,%m,%n,%o,%p'
_bllua_ts.eval('function ' .. cmd .. '(' .. arglist .. '){' ..
@@ -622,6 +673,7 @@ function bl.addServerCmd(name, func)
addCmd('servercmd' .. name, func)
bl._forceType['servercmd' .. name .. ':1'] = 'object'
end
function bl.addClientCmd(name, func)
name = name:lower()
addCmd('clientcmd' .. name, func)
@@ -633,11 +685,13 @@ function bl.commandToServer(cmd, ...)
_bllua_ts.call('addTaggedString', cmd),
unpack(arglistToTs({ ... })))
end
function bl.commandToClient(cmd, ...)
_bllua_ts.call('commandToClient',
_bllua_ts.call('addTaggedString', cmd),
unpack(arglistToTs({ ... })))
end
function bl.commandToAll(cmd, ...)
_bllua_ts.call('commandToAll',
_bllua_ts.call('addTaggedString', cmd),
@@ -665,14 +719,16 @@ local function deactivatePackage(pkg)
end
local hookNargs = 8
local hookArglistLocal = '%a,%b,%c,%d,%e,%f,%g,%h'
local hookArglistGlobal = '$_bllua_hook_arg1,$_bllua_hook_arg2,$_bllua_hook_arg3,$_bllua_hook_arg4,$_bllua_hook_arg5,$_bllua_hook_arg6,$_bllua_hook_arg7,$_bllua_hook_arg8'
local hookArglistGlobal =
'$_bllua_hook_arg1,$_bllua_hook_arg2,$_bllua_hook_arg3,$_bllua_hook_arg4,$_bllua_hook_arg5,$_bllua_hook_arg6,$_bllua_hook_arg7,$_bllua_hook_arg8'
bl._hooks = bl._hooks or {}
function _bllua_process_hook_before(pkgS, nameS, ...)
local args = arglistFromTs(nameS, { ... })
local func = bl._hooks[pkgS] and bl._hooks[pkgS][nameS] and
bl._hooks[pkgS][nameS].before
if not func then
error('_bllua_process_hook_before: no hook for '..pkgs..':'..nameS) end
error('_bllua_process_hook_before: no hook for ' .. pkgs .. ':' .. nameS)
end
_bllua_ts.setvar('_bllua_hook_abort', '0')
func(args) --pcall(func, args)
if args._return then
@@ -683,6 +739,7 @@ function _bllua_process_hook_before(pkgS, nameS, ...)
_bllua_ts.setvar('_bllua_hook_arg' .. i, valToTs(args[i]))
end
end
function _bllua_process_hook_after(pkgS, nameS, resultS, ...)
nameS = nameS:lower()
local args = arglistFromTs(nameS, { ... })
@@ -690,10 +747,12 @@ function _bllua_process_hook_after(pkgS, nameS, resultS, ...)
local func = bl._hooks[pkgS] and bl._hooks[pkgS][nameS] and
bl._hooks[pkgS][nameS].after
if not func then
error('_bllua_process_hook_after: no hook for '..pkgs..':'..nameS) end
error('_bllua_process_hook_after: no hook for ' .. pkgs .. ':' .. nameS)
end
func(args) --pcall(func, args)
return valToTs(args._return)
end
local function updateHook(pkg, name, hk)
local beforeCode = hk.before and
('_bllua_luacall("_bllua_process_hook_before", "' .. pkg .. '","' .. name ..
@@ -719,13 +778,17 @@ local function updateHook(pkg, name, hk)
end
function bl.hook(pkg, name, time, func)
if not isValidFuncName(pkg) then
error('bl.hook: argument #1: invalid package name \''..tostring(pkg)..'\'', 2) end
error('bl.hook: argument #1: invalid package name \'' .. tostring(pkg) .. '\'', 2)
end
if not isValidFuncNameNs(name) then
error('bl.hook: argument #2: invalid function name \''..tostring(name)..'\'', 2) end
error('bl.hook: argument #2: invalid function name \'' .. tostring(name) .. '\'', 2)
end
if time ~= 'before' and time ~= 'after' then
error('bl.hook: argument #3: time must be \'before\' or \'after\'', 2) end
error('bl.hook: argument #3: time must be \'before\' or \'after\'', 2)
end
if type(func) ~= 'function' then
error('bl.hook: argument #4: expected a function', 2) end
error('bl.hook: argument #4: expected a function', 2)
end
bl._hooks[pkg] = bl._hooks[pkg] or {}
bl._hooks[pkg][name] = bl._hooks[pkg][name] or {}
@@ -734,16 +797,20 @@ function bl.hook(pkg, name, time, func)
updateHook(pkg, name, bl._hooks[pkg][name])
activatePackage(pkg)
end
local function tableEmpty(t)
return next(t) ~= nil
end
function bl.unhook(pkg, name, time)
if not isValidFuncName(pkg) then
error('bl.unhook: argument #1: invalid package name \''..tostring(pkg)..'\'', 2) end
error('bl.unhook: argument #1: invalid package name \'' .. tostring(pkg) .. '\'', 2)
end
if not isValidFuncNameNs(name) then
error('bl.unhook: argument #2: invalid function name \''..tostring(name)..'\'', 2) end
error('bl.unhook: argument #2: invalid function name \'' .. tostring(name) .. '\'', 2)
end
if time ~= 'before' and time ~= 'after' then
error('bl.unhook: argument #3: time must be \'before\' or \'after\'', 2) end
error('bl.unhook: argument #3: time must be \'before\' or \'after\'', 2)
end
if not name then
if bl._hooks[pkg] then
@@ -767,7 +834,8 @@ function bl.unhook(pkg, name, time)
updateHook(pkg, name, {})
else
if time ~= 'before' and time ~= 'after' then
error('bl.unhook: argument #3: time must be nil, \'before\', or \'after\'', 2) end
error('bl.unhook: argument #3: time must be nil, \'before\', or \'after\'', 2)
end
bl._hooks[pkg][name][time] = nil
if tableEmpty(bl._hooks[pkg][name]) and tableEmpty(bl._hooks[pkg]) then
bl._hooks[pkg] = nil
@@ -787,14 +855,16 @@ end
-- Container search/raycast
local function vecToTs(v)
if not isTsVector(v) then
error('vecToTs: argument is not a vector', 3) end
error('vecToTs: argument is not a vector', 3)
end
return table.concat(v, ' ')
end
local function maskToTs(mask)
if type(mask) == 'string' then
local val = tsTypesByName[mask:lower()]
if not val then
error('maskToTs: invalid mask \''..mask..'\'', 3) end
error('maskToTs: invalid mask \'' .. mask .. '\'', 3)
end
return tostring(val)
elseif type(mask) == 'table' then
local tval = 0
@@ -804,7 +874,8 @@ local function maskToTs(mask)
local val = tsTypesByName[v:lower()]
if not val then
error('maskToTs: invalid mask \'' .. v ..
'\' at index '..i..' in mask list', 3) end
'\' at index ' .. i .. ' in mask list', 3)
end
tval = tval + val
seen[v] = true
end
@@ -846,6 +917,7 @@ function bl.raycast(start, stop, mask, ignores)
return hit, pos, norm
end
end
local function tsContainerSearchIterator()
local retS = _bllua_ts.call('containerSearchNext')
if retS == '0' then
@@ -862,10 +934,12 @@ function bl.boxSearch(pos, size, mask)
_bllua_ts.call('initContainerBoxSearch', posS, sizeS, maskS)
return tsContainerSearchIterator
end
function bl.radiusSearch(pos, radius, mask)
local posS = vecToTs(pos)
if type(radius) ~= 'number' then
error('bl.radiusSearch: argument #2: radius must be a number', 2) end
error('bl.radiusSearch: argument #2: radius must be a number', 2)
end
local radiusS = tostring(radius)
local maskS = maskToTs(mask)
@@ -902,7 +976,8 @@ local function createTsObj(keyword, class, name, inherit, props)
local propsT = {}
for k, v in pairs(props) do
if not isValidFuncName(k) then
error('bl.new/bl.datablock: invalid property name \''..k..'\'') end
error('bl.new/bl.datablock: invalid property name \'' .. k .. '\'')
end
table.insert(propsT, k .. '="' .. valToTs(v) .. '";')
end
@@ -912,7 +987,8 @@ local function createTsObj(keyword, class, name, inherit, props)
table.concat(propsT) .. '};')
local obj = toTsObject(objS)
if not obj then
error('bl.new/bl.datablock: failed to create object', 3) end
error('bl.new/bl.datablock: failed to create object', 3)
end
return obj
end
@@ -937,13 +1013,15 @@ local function parseTsDecl(decl)
(inherit == nil or isValidFuncName(inherit))) then
error('bl.new/bl.datablock: invalid decl \'' .. decl .. '\'\n' ..
'must be of the format: \'className\', \'className name\', ' ..
'\'className :inherit\', or \'className name:inherit\'', 3) end
'\'className :inherit\', or \'className name:inherit\'', 3)
end
return class, name, inherit
end
function bl.new(decl, props)
local class, name, inherit = parseTsDecl(decl)
return createTsObj('new', class, name, inherit, props)
end
function bl.datablock(decl, props)
local class, name, inherit = parseTsDecl(decl)
if not name then error('bl.datablock: must specify a name', 2) end

View File

@@ -1,4 +1,3 @@
-- This Lua code provides some built-in utilities for writing Lua add-ons
-- It is eval'd automatically once BLLua3 has loaded the TS API and environment
-- It only has access to the sandboxed lua environment, just like user code.
@@ -9,6 +8,7 @@ ts = _bllua_ts
os = os or {}
---@diagnostic disable-next-line: duplicate-set-field
function os.time() return math.floor(tonumber(_bllua_ts.call('getSimTime')) / 1000) end
---@diagnostic disable-next-line: duplicate-set-field
function os.clock() return tonumber(_bllua_ts.call('getSimTime')) / 1000 end
@@ -84,7 +84,9 @@ local function new_file_obj(fn)
return file
end
local function tflip(t) local u = {}; for _, n in ipairs(t) do u[n] = true end; return u; end
local function tflip(t)
local u = {}; for _, n in ipairs(t) do u[n] = true end; return u;
end
local allowed_zip_dirs = tflip {
'add-ons', 'base', 'config', 'saves', 'screenshots', 'shaders'
}
@@ -100,7 +102,8 @@ local function io_open_absolute(fn, mode)
if not exist then return nil, err end
if mode ~= nil and mode ~= 'r' and mode ~= 'rb' then
return nil, 'Files in zips can only be opened in read mode' end
return nil, 'Files in zips can only be opened in read mode'
end
-- return a temp lua file object with the data
local fi = new_file_obj(fn)
@@ -129,12 +132,14 @@ function io.open(fn, mode, errn)
return fi, err, fn
end
end
---@diagnostic disable-next-line: duplicate-set-field
function io.lines(fn)
local fi, err, fn2 = io.open(fn, nil, 2)
if not fi then error('Error opening file \'' .. fn2 .. '\': ' .. err, 2) end
return fi:lines()
end
---@diagnostic disable-next-line: duplicate-set-field
function io.type(f)
---@diagnostic disable-next-line: undefined-field
@@ -201,8 +206,11 @@ end
-- Exposure to TS
function _bllua_getvar(name) return _G[name] end
function _bllua_setvar(name, val) _G[name] = val end
function _bllua_eval(code) return loadstring(code)() end
function _bllua_exec(fn) return dofile(fn, 2) end
local function isValidCode(code)
@@ -211,7 +219,8 @@ local function isValidCode(code)
end
function _bllua_smarteval(code)
if (not code:find('^print%(')) and isValidCode('print(' .. code .. ')') then
code = 'print('..code..')' end
code = 'print(' .. code .. ')'
end
local f, e = loadstring(code)
if f then
return f()

View File

@@ -1,4 +1,3 @@
-- Basic functionality that should be standard in Lua
@@ -7,6 +6,7 @@
function table.empty(t)
return next(t) ~= nil
end
-- Apply a function to each key in a table
function table.map(f, ...)
local ts = { ... }
@@ -18,6 +18,7 @@ function table.map(f, ...)
end
return u
end
function table.map_list(f, ...)
local ts = { ... }
local u = {}
@@ -28,18 +29,21 @@ function table.map_list(f, ...)
end
return u
end
-- Swap keys/values
function table.swap(t)
local u = {}
for k, v in pairs(t) do u[v] = k end
return u
end
-- Reverse a list
function table.reverse(l)
local m = {}
for i = 1, #l do m[#l - i + 1] = l[i] end
return m
end
-- Whether a table is a list/array (has only monotonic integer keys)
function table.islist(t)
local n = 0
@@ -49,6 +53,7 @@ function table.islist(t)
end
return n == #t
end
-- Append contents of other tables to first table
function table.append(t, ...)
local a = { ... }
@@ -57,6 +62,7 @@ function table.append(t, ...)
end
return t
end
-- Create a new table containing all keys from any number of tables
-- latter tables in the arg list override prior ones
-- overlaps, NOT appends, integer keys
@@ -68,6 +74,7 @@ function table.join(...)
end
return w
end
-- Whether a table contains a certain value in any key
function table.contains(t, s)
for _, v in pairs(t) do
@@ -75,29 +82,34 @@ function table.contains(t,s)
end
return false
end
function table.contains_list(t, s)
for _, v in ipairs(t) do
if v == s then return true end
end
return false
end
-- Copy a table to another table
function table.copy(t)
local u = {}
for k, v in pairs(t) do u[k] = v end
return u
end
function table.copy_list(l)
local m = {}
for i, v in ipairs(l) do m[i] = v end
return m
end
-- Sort a table in a new copy
function table.sortcopy(t, f)
local u = table.copy_list(t)
table.sort(u, f)
return u
end
-- Remove a value from a table
function table.removevalue(t, r)
local rem = {}
@@ -106,6 +118,7 @@ function table.removevalue(t, r)
end
for _, k in ipairs(rem) do t[k] = nil end
end
function table.removevalue_list(t, r)
for i = #t, 1, -1 do
if t[i] == r then
@@ -113,6 +126,7 @@ function table.removevalue_list(t, r)
end
end
end
-- Export tables into formatted executable strings
local function tabs(tabLevel)
return (' '):rep(tabLevel)
@@ -186,7 +200,6 @@ function table.tostring(t)
return tableToString(t, 0, {})
end
-- String
-- Split string into table by separator
@@ -194,7 +207,8 @@ end
-- if regex is not true, sep is treated as a regex pattern
function string.split(str, sep, noregex)
if type(str) ~= 'string' then
error('string.split: argument #1: expected string, got '..type(str), 2) end
error('string.split: argument #1: expected string, got ' .. type(str), 2)
end
if sep == nil or sep == '' then
local t = {}
local ns = #str
@@ -220,6 +234,7 @@ function string.split(str, sep, noregex)
'string.split: argument #2: expected string or nil, got ' .. type(sep), 2)
end
end
-- Split string to a list of char bytes
function string.bytes(s)
local b = {}
@@ -229,11 +244,13 @@ function string.bytes(s)
end
return b
end
-- Trim leading and trailing whitespace
function string.trim(s, ws)
ws = ws or ' \t\r\n'
return s:gsub('^[' .. ws .. ']+', ''):gsub('[' .. ws .. ']+$', '') .. ''
end
-- String slicing and searching using [] operator
local str_meta = getmetatable('')
local str_meta_index_old = str_meta.__index
@@ -249,15 +266,20 @@ function str_meta.__index(s,k)
return string.sub(s, a, b)
end
end
-- String iterator
function string.chars(s)
local i = 0
return function()
i = i + 1
if i<=#s then return s:sub(i,i)
else return nil end
if i <= #s then
return s:sub(i, i)
else
return nil
end
end
end
-- Escape sequences
local defaultEscapes = {
['\\'] = '\\\\',
@@ -277,6 +299,7 @@ function string.escape(s, escapes)
end
return table.concat(t)
end
local defaultEscapeChar = '\\'
local defaultUnescapes = {
['\\'] = '\\',
@@ -307,7 +330,6 @@ function string.unescape(s, escapeChar, unescapes)
return table.concat(t)
end
-- IO
io = io or {}
@@ -319,6 +341,7 @@ function io.readall(filename)
fi:close()
return s
end
-- Write data to file all at once, return true if success / false,err if failure
function io.writeall(filename, data)
local fi, err = io.open(filename, 'wb')
@@ -328,19 +351,23 @@ function io.writeall(filename, data)
return true, nil
end
-- Math
-- Round
function math.round(x)
return math.floor(x + 0.5)
end
-- Mod that accounts for floating point inaccuracy
function math.mod(a, b)
local m = a % b
if m==0 or math.abs(m)<1e-15 or math.abs(m-b)<1e-15 then return 0
else return m end
if m == 0 or math.abs(m) < 1e-15 or math.abs(m - b) < 1e-15 then
return 0
else
return m
end
end
-- Clamp value between min and max
function math.clamp(v, n, x)
return math.min(x, math.max(v, n))

View File

@@ -1,18 +1,19 @@
-- Vector math class with operators
local vector_meta
local vector_new
local function vector_check(v, n, name, argn)
if not v.__is_vector then
error('vector ' .. name .. ': argument #' .. (argn or 1)
..': expected vector, got '..type(v), n+1) end
.. ': expected vector, got ' .. type(v), n + 1)
end
end
local function vector_checksamelen(v1, v2, name)
vector_check(v1, 3, name, 1)
vector_check(v2, 3, name, 2)
if #v1 ~= #v2 then
error('vector ' .. name .. ': vector lengths do not match (lengths are '
..#v1..' and '..#v2..')', 3) end
.. #v1 .. ' and ' .. #v2 .. ')', 3)
end
return #v1
end
local function vector_checklen(v1, v2, name, len)
@@ -20,7 +21,8 @@ local function vector_checklen(v1, v2, name, len)
vector_check(v2, 3, name, 2)
if #v1 ~= len or #v2 ~= len then
error('vector ' .. name .. ': vector lengths are not ' .. len .. ' (lengths are '
..#v1..' and '..#v2..')', 3) end
.. #v1 .. ' and ' .. #v2 .. ')', 3)
end
end
local function vector_opnnn(name, op)
return function(v1, v2)
@@ -69,15 +71,21 @@ local vector_indices = {x = 1, y = 2, z = 3, w = 4, r = 1, g = 2, b = 3, a = 4}
local vector_meta = {
__is_vector = true,
__index = function(t, k)
if tonumber(k) then return rawget(t, k)
elseif vector_indices[k] then return rawget(t, vector_indices[k])
else return getmetatable(t)[k]
if tonumber(k) then
return rawget(t, k)
elseif vector_indices[k] then
return rawget(t, vector_indices[k])
else
return getmetatable(t)[k]
end
end,
__newindex = function(t, k, v)
if tonumber(k) then rawset(t, k, v)
elseif vector_indices[k] then rawset(t, vector_indices[k], v)
else return
if tonumber(k) then
rawset(t, k, v)
elseif vector_indices[k] then
rawset(t, vector_indices[k], v)
else
return
end
end,
__add = vector_opnnn('add', function(x1, x2) return x1 + x2 end),
@@ -115,8 +123,11 @@ local vector_meta = {
local len = #v1
local v3 = {}
for i = 1, len do
if length==0 then v3[i] = 0
else v3[i] = v1[i]/length end
if length == 0 then
v3[i] = 0
else
v3[i] = v1[i] / length
end
end
return vector_new(v3)
end,
@@ -152,20 +163,28 @@ local vector_meta = {
rotateByAngleId = function(v1, r)
--vector_check(v1, 2, 'rotate')
if type(r) ~= 'number' or r % 1 ~= 0 then
error('vector rotateByAngleId: invalid rotation '..tostring(r), 2) end
error('vector rotateByAngleId: invalid rotation ' .. tostring(r), 2)
end
r = r % 4
local v2
if r==0 then v2 = vector_new{ v1[1], v1[2], v1[3] }
elseif r==1 then v2 = vector_new{ v1[2], -v1[1], v1[3] }
elseif r==2 then v2 = vector_new{ -v1[1], -v1[2], v1[3] }
elseif r==3 then v2 = vector_new{ -v1[2], v1[1], v1[3] }
else error('vector rotateByAngleId: invalid rotation '..r, 2) end
if r == 0 then
v2 = vector_new { v1[1], v1[2], v1[3] }
elseif r == 1 then
v2 = vector_new { v1[2], -v1[1], v1[3] }
elseif r == 2 then
v2 = vector_new { -v1[1], -v1[2], v1[3] }
elseif r == 3 then
v2 = vector_new { -v1[2], v1[1], v1[3] }
else
error('vector rotateByAngleId: invalid rotation ' .. r, 2)
end
return v2
end,
rotateZ = function(v, r)
--vector_check(v, 2, 'rotate2d')
if type(r) ~= 'number' then
error('vector rotateZ: invalid rotation '..tostring(r), 2) end
error('vector rotateZ: invalid rotation ' .. tostring(r), 2)
end
local len = math.sqrt(v[1] ^ 2 + v[2] ^ 2)
local ang = math.atan2(v[2], v[1]) + r
local v2 = vector_new { math.cos(ang) * len, math.sin(ang) * len }