1
0
forked from redo/BlockLua

Compare commits

...

6 Commits

8 changed files with 1055 additions and 890 deletions

Binary file not shown.

View File

@@ -14,10 +14,10 @@ Lua scripting for Blockland
### From TorqueScript ### From TorqueScript
`'print('hello world')` - Execute Lua in the console by prepending a `'` (single quote) `'print('hello world')` - Execute Lua in the console by prepending a `'` (single quote)
`luaeval("code");` - Execute Lua code `luaeval("code");` - Execute Lua code
`luacall("funcName", %args...);` - Call a Lua global function `luacall("funcName", %args...);` - Call a Lua function (supports indexing tables and object methods)
`luaexec("fileName");` - Execute a Lua file. Path rules are the same as executing .cs files. `luaexec("fileName");` - Execute a Lua file. Path rules are the same as when executing .cs files, relative paths are allowed.
`luaget("varName");` - Read a Lua global variable `luaget("varName");` - Read a Lua global variable (supports indexing tables)
`luaset("varName", %value);` - Write a Lua global variable `luaset("varName", %value);` - Write a Lua global variable (supports indexing tables)
### From Lua ### From Lua
`bl.eval('code')` - Eval TorqueScript code `bl.eval('code')` - Eval TorqueScript code
@@ -35,7 +35,7 @@ Lua scripting for Blockland
`object.key = value` - Associate Lua data with a Torque object `object.key = value` - Associate Lua data with a Torque object
`object:method(args)` - Call a Torque object method `object:method(args)` - Call a Torque object method
`object[index]` - Access a member of a Torque set or group `object[index]` - Access a member of a Torque set or group
`for childIndex, child in object:members() do` - Iterate objects within of a Torque set or group. Indices start at 0 like in Torque. `for child in object:members() do` - Iterate objects within of a Torque set or group. Indices start at 0 like in Torque.
`bl.isObject(object, objectID, or 'objectName')` - Check if an object exists `bl.isObject(object, objectID, or 'objectName')` - Check if an object exists
`object:exists()` - Check if an object exists `object:exists()` - Check if an object exists
@@ -48,15 +48,25 @@ Lua scripting for Blockland
`for object in bl.boxSearch(vector{centerX,y,z}, vector{sizeX,y,z}, 'objtype'/{'objtypes',...}) do` - Find all objects in the world of the specified type(s) whose bounding box overlaps with the specified box. See the Types section for a list of valid object types. `for object in bl.boxSearch(vector{centerX,y,z}, vector{sizeX,y,z}, 'objtype'/{'objtypes',...}) do` - Find all objects in the world of the specified type(s) whose bounding box overlaps with the specified box. See the Types section for a list of valid object types.
`for object in bl.radiusSearch(vector{centerX,y,z}, radius, 'objtype'/{'objtypes',...}) do` - Find all objects of the specified type(s) whose bounding box overlaps with the specified sphere. See the Types section for a list of valid object types. `for object in bl.radiusSearch(vector{centerX,y,z}, radius, 'objtype'/{'objtypes',...}) do` - Find all objects of the specified type(s) whose bounding box overlaps with the specified sphere. See the Types section for a list of valid object types.
### List of Object Classes (for raycasts and searches)
`'all'` - Any object
`'player'` - Players or bots
`'item'` - Items
`'vehicle'` - Vehicles
`'projectile'` - Projectiles
`'brick'` - Bricks with raycasting enabled
`'brickalways'` - All bricks including those with raycasting disabled
Other types: `'static'`, `'environment'`, `'terrain'`, `'water'`, `'trigger'`, `'marker'`, `'gamebase'`, `'shapebase'`, `'camera'`, `'staticshape'`, `'vehicleblocker'`, `'explosion'`, `'corpse'`, `'debris'`, `'physicalzone'`, `'staticts'`, `'staticrendered'`, `'damagableitem'`
### Server-Client Communication ### Server-Client Communication
`bl.addServerCmd('commandName', function(client, args...) yourCode end)` - Register a /command on the server `bl.addServerCmd('commandName', function(client, args...) ... end)` - Register a /command on the server
`bl.addClientCmd('commandName', function(args...) yourCode end)` - Register a client command on the client `bl.addClientCmd('commandName', function(args...) ... end)` - Register a client command on the client
`bl.commandToServer('commandName', args...)` - Execute a server command as a client `bl.commandToServer('commandName', args...)` - As a client, execute a server command
`bl.commandToClient('commandName', args...)` - As the server, execute a client command on a specific client `bl.commandToClient(client, 'commandName', args...)` - As the server, execute a client command on a specific client
`bl.commandToAll('commandName', args...)` - As the server, execute a client command on all clients `bl.commandToAll('commandName', args...)` - As the server, execute a client command on all clients
### Packages/Hooks ### Packages/Hooks
`bl.hook('packageName', 'functionName', 'before'/'after', function(args) yourCode end)` - Hook a Torque function with a Lua function. `bl.hook('packageName', 'functionName', 'before'/'after', function(args) ... end)` - Hook a Torque function with a Lua function.
`args` is an array containing the arguments provided to the function. If the hook is `before`, these can be modified before being passed to the parent function. `args` is an array containing the arguments provided to the function. If the hook is `before`, these can be modified before being passed to the parent function.
If `args._return` is set to anything other than nil by a `before` hook, the parent function will not be called, and the function will simply return that value. Also in this case, any `after` hook will not be executed. If `args._return` is set to anything other than nil by a `before` hook, the parent function will not be called, and the function will simply return that value. Also in this case, any `after` hook will not be executed.
In an `after` hook, `args._return` is set to the value returned by the parent function, and can be modified. In an `after` hook, `args._return` is set to the value returned by the parent function, and can be modified.
@@ -136,14 +146,16 @@ When reading from outside ZIPs, binary files are fully supported.
WIP WIP
### Extended Standard Lua Library ### Extended Standard Lua Library
`string[index]` `str[index]`
`string[{start,stop}]` `str[{start,stop}]`
`string.split(str, separator='' (splits into chars), noregex=false)` `string.split(str, separator='' (splits into chars), noregex=false)`
`string.bytes(str)` `string.bytes(str)`
`string.trim(str, charsToTrim=' \t\r\n')` `string.trim(str, charsToTrim=' \t\r\n')`
`table.empty` `table.empty`
`table.map(func, ...)` `table.map(func, ...)`
`table.mapk(func, ...)`
`table.map_list(func, ...)` `table.map_list(func, ...)`
`table.mapi_list(func, ...)`
`table.swap(tbl)` `table.swap(tbl)`
`table.reverse(list)` `table.reverse(list)`
`table.islist(list)` `table.islist(list)`
@@ -173,43 +185,35 @@ TorqueScript stores no type information; all values in TorqueScript are strings.
### From Lua to TorqueScript ### From Lua to TorqueScript
- `nil` becomes the empty string "" - `nil` becomes the empty string ""
- `true` and `false` become "1" and "0" respectively - `true` and `false` become "1" and "0" respectively
- Torque containers become their object ID - A Torque object container becomes its object ID
- A `vector` becomes a string containing three numbers separated by spaces - A `vector` becomes a string containing three numbers separated by spaces
- A table of two vectors becomes a string containing six numbers separated by spaces - A table of two `vector`s becomes a string containing six numbers separated by spaces
- (WIP) A `matrix` is converted into an axis-angle (a "transform"), a string containing seven numbers separated by spaces
- Any `string` is passed directly as a string - Any `string` is passed directly as a string
- Tables cannot be passed and will throw an error - Tables cannot be passed and will throw an error
### From TorqueScript to Lua ### From TorqueScript to Lua
- Any numeric value becomes a Lua `number`, except as specified with `bl.type`, which may convert a value into a `boolean` or a Torque object container.
- The empty string "" becomes `nil` - The empty string "" becomes `nil`
- Any numeric value becomes a Lua `number`, except as specified with `bl.type`, which may convert a value into a `boolean` or a Torque object container.
- A string containing two or three numbers separated by single spaces becomes a `vector` - A string containing two or three numbers separated by single spaces becomes a `vector`
- A string containing six numbers separated by single spaces becomes a table of two vectors, usually defining the corners a bounding box - A string containing six numbers separated by single spaces becomes a table of two vectors, usually defining the corners a bounding box
- (WIP) A string containing seven numbers separated by single spaces is treated as an axis-angle (a "transform" in TorqueScript parlance), and is converted into a `matrix` representing the translation and rotation. - (WIP) A string containing seven numbers separated by single spaces is treated as an axis-angle (a "transform"), and is converted into a `matrix` representing the translation and rotation
- Any other string is passed directly as a `string` - Any other string is passed directly as a `string`
For scenarios where the automatic TorqueScript->Lua conversion rules are insufficient or incorrect, use `bl.type`. For scenarios where the automatic TorqueScript->Lua conversion rules are insufficient or incorrect, use `bl.type`.
To convert objects by hand, use `bl.object`, `bl.boolean`, or `bl.string`. To convert things by hand, use `bl.object`, `bl.boolean`, or `bl.string`.
## I/O and Safety ## I/O and Safety
All Lua code is sandboxed, and file access is confined to the default directories in the same way TorqueScript is. All Lua code is sandboxed, and file access is confined to the default directories in the same way TorqueScript is.
BlockLua also has access to any C libraries installed in the `modules/lualib` folder, so be careful throwing things in there. BlockLua also has access to any C libraries installed in the `modules/lualib` folder, so be careful throwing things in there.
### Unsafe Mode ### Unsafe Mode
BlockLua can be built in Unsafe Mode by specifying the `-DBLLUA_UNSAFE` compiler flag. This removes the sandboxing of Lua code, allowing it to access any file and use any library, including ffi. BlockLua can be built in Unsafe Mode by specifying the `-DBLLUA_UNSAFE` compiler flag. This removes the sandboxing of Lua code, allowing it to access any file and use any library, including ffi.
Please do not publish add-ons that require unsafe mode. A more limited option is `-DBLLUA_ALLOWFFI`, which allows the use of the `ffi` library. This can still be exploited to grant all the same access as full unsafe mode.
Please do not publish add-ons that require either of these.
### List of Object Types
`'all'` - Any object
`'player'` - Players or bots
`'item'` - Items
`'vehicle'` - Vehicles
`'projectile'` - Projectiles
`'brick'` - Bricks with raycasting enabled
`'brickalways'` - All bricks including those with raycasting disabled
Other types: `'static'`, `'environment'`, `'terrain'`, `'water'`, `'trigger'`, `'marker'`, `'gamebase'`, `'shapebase'`, `'camera'`, `'staticshape'`, `'vehicleblocker'`, `'explosion'`, `'corpse'`, `'debris'`, `'physicalzone'`, `'staticts'`, `'staticrendered'`, `'damagableitem'`
## Compiling ## Compiling
With any *32-bit* variant of GCC installed (such as MinGW or MSYS2), run the following command in the repo directory: With any *32-bit* variant of GCC installed (such as MinGW or MSYS2), run the following command in the repo directory:
`g++ src/bllua4.cpp -o BlockLua.dll -m32 -shared -static-libgcc -Isrc -Iinc/tsfuncs -Iinc/lua -lpsapi -L. -llua5.1 src/bllua` `g++ src/bllua4.cpp -o BlockLua.dll -m32 -shared -static-libgcc -Isrc -Iinc/tsfuncs -Iinc/lua -lpsapi -L. -llua5.1`
LuaJIT (lua5.1.dll) can be obtained from https://luajit.org/ LuaJIT (lua5.1.dll) can be obtained from https://luajit.org/

View File

@@ -1,4 +1,4 @@
// BlockLua (bllua4): Simple Lua interface for TorqueScript // BlockLua (bllua4): Advanced Lua interface for TorqueScript
// Includes // Includes
@@ -60,23 +60,27 @@ bool init() {
// Set up Lua environment // Set up Lua environment
BLL_LOAD_LUA(gL, bll_fileLuaEnv); BLL_LOAD_LUA(gL, bll_fileLuaEnv);
#ifdef BLLUA_ALLOWFFI
lua_pushboolean(gL, true);
lua_setglobal(gL, "_bllua_allowffi");
#endif
#ifndef BLLUA_UNSAFE #ifndef BLLUA_UNSAFE
BLL_LOAD_LUA(gL, bll_fileLuaEnvSafe); BLL_LOAD_LUA(gL, bll_fileLuaEnvSafe);
#endif #endif
// Load utilities in Lua
BLL_LOAD_LUA(gL, bll_fileLuaStd);
BLL_LOAD_LUA(gL, bll_fileLuaVector);
BLL_LOAD_LUA(gL, bll_fileLuaMatrix);
BLL_LOAD_LUA(gL, bll_fileLuaLibts);
BLL_LOAD_LUA(gL, bll_fileLuaLibbl);
BLL_LOAD_LUA(gL, bll_fileLuaLibblTypes);
// Expose Lua API to TS // Expose Lua API to TS
BlAddFunction( BlAddFunction(
NULL, NULL, "_bllua_luacall", bll_ts_luacall, "LuaCall(name, ...) - Call Lua function and return result", 2, 20); NULL, NULL, "_bllua_luacall", bll_ts_luacall, "LuaCall(name, ...) - Call Lua function and return result", 2, 20);
BlEval(bll_fileTsEnv); BlEval(bll_fileTsEnv);
// Load utilities
BLL_LOAD_LUA(gL, bll_fileLuaStd);
BLL_LOAD_LUA(gL, bll_fileLuaVector);
BLL_LOAD_LUA(gL, bll_fileLuaMatrix);
BLL_LOAD_LUA(gL, bll_fileLuaLibts);
BlEval(bll_fileTsLibts); BlEval(bll_fileTsLibts);
BLL_LOAD_LUA(gL, bll_fileLuaLibbl);
BLL_LOAD_LUA(gL, bll_fileLuaLibblTypes);
BlEval(bll_fileTsLibblSupport); BlEval(bll_fileTsLibblSupport);
BlEval(bll_fileLoadaddons); BlEval(bll_fileLoadaddons);
@@ -89,8 +93,7 @@ bool init() {
bool deinit() { bool deinit() {
BlPrintf("BlockLua: Unloading"); BlPrintf("BlockLua: Unloading");
BlEval("deactivatePackage(_bllua_main);"); BlEval("$_bllua_active=0;deactivatePackage(_bllua_main);");
BlEval("$_bllua_active = 0;");
bll_LuaEval(gL, "for _,f in pairs(_bllua_on_unload) do f() end"); bll_LuaEval(gL, "for _,f in pairs(_bllua_on_unload) do f() end");
lua_close(gL); lua_close(gL);

View File

@@ -14,6 +14,7 @@ local old_require = require
local old_os = os local old_os = os
local old_debug = debug local old_debug = debug
local old_package = package local old_package = package
local old_allowffi = _bllua_allowffi
-- Remove all global variables except a whitelist -- Remove all global variables except a whitelist
local ok_names = tmap { local ok_names = tmap {
@@ -39,13 +40,10 @@ end
-- Sanitize file paths to point only to allowed files within the game directory -- Sanitize file paths to point only to allowed files within the game directory
-- List of allowed directories for reading/writing -- List of allowed directories for reading/writing
-- modules/lualib is also allowed as read-only
local allowed_dirs = tmap { local allowed_dirs = tmap {
'add-ons', 'base', 'config', 'saves', 'screenshots', 'shaders' 'add-ons', 'base', 'config', 'saves', 'screenshots', 'shaders'
} }
-- List of allowed directories for reading only
local allowed_dirs_readonly = tmap {
'lualib'
}
-- List of disallowed file extensions - basically executable file extensions -- List of disallowed file extensions - basically executable file extensions
-- Note that even without this protection, exploiting would still require somehow -- Note that even without this protection, exploiting would still require somehow
-- getting a file within the allowed directories to autorun, -- getting a file within the allowed directories to autorun,
@@ -81,14 +79,15 @@ local function safe_path(fn, readonly)
end end
-- allow only whitelisted dirs -- allow only whitelisted dirs
local dir = fn:match('^([^/]+)/') local dir = fn:match('^([^/]+)/')
if (not dir) or ( if not (dir and (
(not allowed_dirs[dir:lower()]) and allowed_dirs[dir:lower()] or
((not readonly) or (not allowed_dirs_readonly[dir:lower()]))) then (readonly and fn:find('^modules/lualib/'))))
return nil, 'filename is in disallowed directory ' .. (dir or 'nil') then
return nil, 'File is in disallowed directory ' .. (dir or 'nil')
end end
-- disallow blacklisted extensions or no extension -- disallow blacklisted extensions
local ext = fn:match('%.([^/%.]+)$') local ext = fn:match('%.([^/%.]+)$')
if (not ext) or (disallowed_exts[ext:lower()]) then if ext and disallowed_exts[ext:lower()] then
return nil, 'Filename \'' .. fn .. '\' has disallowed extension \'' .. return nil, 'Filename \'' .. fn .. '\' has disallowed extension \'' ..
(ext or '') .. '\'' (ext or '') .. '\''
end end
@@ -120,6 +119,7 @@ local disallowed_packages = tmap {
'ffi', 'debug', 'package', 'io', 'os', 'ffi', 'debug', 'package', 'io', 'os',
'_bllua_ts', '_bllua_ts',
} }
if old_allowffi then disallowed_packages['ffi'] = nil end
function _bllua_requiresecure(name) function _bllua_requiresecure(name)
if name:find('[^a-zA-Z0-9_%-%.]') or name:find('%.%.') or if name:find('[^a-zA-Z0-9_%-%.]') or name:find('%.%.') or
name:find('^%.') or name:find('%.$') then name:find('^%.') or name:find('%.$') then

View File

@@ -37,5 +37,9 @@ function _bllua_on_error(err)
return table.concat(tracelines, '\n') return table.concat(tracelines, '\n')
end end
-- overridden in lua-env-safe.lua (executed if not BLLUA_UNSAFE)
_bllua_io_open = io.open
_bllua_requiresecure = require
print = _bllua_ts.echo print = _bllua_ts.echo
print(' Executed bllua-env.lua') print(' Executed bllua-env.lua')

View File

@@ -6,15 +6,33 @@ local _bllua_ts = ts
bl = bl or {} bl = bl or {}
-- Config
local tsMaxArgs = 16
local tsArgsLocal = '%a,%b,%c,%d,%e,%f,%g,%h,%i,%j,%k,%l,%m,%n,%o,%p'
local tsArgsGlobal =
'$_bllua_hook_arg1,$_bllua_hook_arg2,$_bllua_hook_arg3,$_bllua_hook_arg4,' ..
'$_bllua_hook_arg5,$_bllua_hook_arg6,$_bllua_hook_arg7,$_bllua_hook_arg8,' ..
'$_bllua_hook_arg9,$_bllua_hook_arg10,$_bllua_hook_arg11,$_bllua_hook_arg12,' ..
'$_bllua_hook_arg13,$_bllua_hook_arg14,$_bllua_hook_arg15,$_bllua_hook_arg16'
-- Misc -- Misc
-- Apply a function to each element in a list, building a new list from the returns local function ipairsNilable(t)
local function map(t,f) local maxk = 0
local u = {} for k, _ in pairs(t) do
for i,v in ipairs(t) do if k > maxk then maxk = k end
u[i] = f(v)
end end
return u local i = 0
return function()
i = i + 1
if i > maxk then
return nil
else
return i, t[i]
end end
end
end
-- Validation
local function isValidFuncName(name) local function isValidFuncName(name)
return type(name) == 'string' and name:find('^[a-zA-Z_][a-zA-Z0-9_]*$') return type(name) == 'string' and name:find('^[a-zA-Z_][a-zA-Z0-9_]*$')
end end
@@ -107,27 +125,34 @@ end
-- Type conversion from TS to Lua -- Type conversion from TS to Lua
local fromTsForceTypes = { local fromTsForceTypes = {
['boolean'] = tsBool, ['boolean'] = function(val) return tsBool(val) end,
['object'] = function(val) toTsObject(val) end, -- wrap because toTsObject not defined yet ['object'] = function(val) return toTsObject(val) end, -- wrap because toTsObject not defined yet
['string'] = function(val) return val end, ['string'] = function(val) return val end,
} }
local function forceValFromTs(val, typ) local function forceValFromTs(val, typ)
return fromTsForceTypes[typ](val) or local func = fromTsForceTypes[typ]
error('valFromTs: invalid force type '..typ, 4) if not func then error('valFromTs: invalid force type \'' .. typ .. '\'', 4) end
return func(val)
end end
local function vectorFromTs(val) local function vectorFromTs(val)
local xS, yS, zS = val:match('^(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+)$') local xS, yS, zS = val:match('^(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+)$')
if xS then return vector{tonumber(xS),tonumber(yS),tonumber(zS)} if xS then
else return nil end return vector { tonumber(xS), tonumber(yS), tonumber(zS) }
else
return nil
end
end end
local function boxFromTs(val) local function boxFromTs(val)
local x1S, y1S, z1S, x2S, y2S, z2S = val:match( local x1S, y1S, z1S, x2S, y2S, z2S = val:match(
'^(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+) ' .. '^(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+) ' ..
'(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+)$') '(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+)$')
if x1S then return { if x1S then
return {
vector { tonumber(x1S), tonumber(y1S), tonumber(z1S) }, vector { tonumber(x1S), tonumber(y1S), tonumber(z1S) },
vector { tonumber(x2S), tonumber(y2S), tonumber(z2S) } } vector { tonumber(x2S), tonumber(y2S), tonumber(z2S) } }
else return nil end else
return nil
end
end end
local function multinumericFromTs(val) local function multinumericFromTs(val)
local tsNumPat = '%-?[0-9]+%.?[0-9]*e?[0-9]*' local tsNumPat = '%-?[0-9]+%.?[0-9]*e?[0-9]*'
@@ -154,9 +179,11 @@ local function multinumericFromTs(val)
end end
end end
bl._forceType = bl._forceType or {} bl._forceType = bl._forceType or {}
local function valFromTs(val, name, name2) -- todo: ensure name and name2 are already lowercase -- todo: ensure name and name2 are already lowercase
local function valFromTs(val, name, name2)
if type(val) ~= 'string' then 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 if name then
name = name:lower() name = name:lower()
if bl._forceType[name] then if bl._forceType[name] then
@@ -177,31 +204,42 @@ local function valFromTs(val, name, name2) -- todo: ensure name and name2 are al
-- vector, box, or axis->matrix -- vector, box, or axis->matrix
local vec = multinumericFromTs(val) local vec = multinumericFromTs(val)
if vec then return vec end if vec then return vec end
-- net string
if val:sub(1, 1) == '\x01' then
return _bllua_ts.call('getTaggedString', val)
end
-- string -- string
return val return val
end end
local function arglistFromTs(name, argsS) local function arglistFromTs(name, argsS)
local args = {} local args = {}
for i,arg in ipairs(argsS) do for i, arg in ipairsNilable(argsS) do
args[i] = valFromTs(arg, name .. ':' .. i) args[i] = valFromTs(arg, name .. ':' .. i)
end end
return args return args
end end
local function arglistToTs(args) local function arglistToTs(args)
return map(args, valToTs) local argsS = {}
for i, v in ipairsNilable(args) do
table.insert(argsS, valToTs(v))
end
return argsS
end end
local function classFromForceTypeStr(name) local function classFromForceTypeStr(name)
local class, rest = name:match('^([a-zA-Z0-9_]+)(::.+)$') local class, rest = name:match('^([a-zA-Z0-9_]+)(::.+)$')
if not class then 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 return class, rest
end end
local setForceType local setForceType
setForceType = function(ftname, typ) setForceType = function(ftname, typ)
if typ ~= nil and not fromTsForceTypes[typ] then if typ ~= nil and not fromTsForceTypes[typ] then
error('bl.type: invalid type \''..typ..'\'', 2) end error('bl.type: invalid type \'' .. typ .. '\'', 2)
end
if not isValidFuncNameNsArgn(ftname) then 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() ftname = ftname:lower()
@@ -252,15 +290,19 @@ 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 tsIsFunctionNs(ns, name) return tsBool(_bllua_ts.call('isFunction', ns, name)) end
local function tsIsFunctionNsname(nsname) local function tsIsFunctionNsname(nsname)
local ns, name = nsname:match('^([^:]+)::([^:]+)$') local ns, name = nsname:match('^([^:]+)::([^:]+)$')
if ns then return tsIsFunctionNs(ns, name) if ns then
else return tsIsFunction(nsname) end return tsIsFunctionNs(ns, name)
else
return tsIsFunction(nsname)
end
end end
-- sanity check to make sure objects that don't isObject are always marked as ._deleted -- sanity check to make sure objects that don't isObject are always marked as ._deleted
-- can be removed later -- can be removed later
local function assertTsObjectExists(obj) local function assertTsObjectExists(obj)
local is = tsIsObject(obj._tsObjectId) local is = tsIsObject(obj._tsObjectId)
if not is then if not is then
print('Warning: TS object :exists or isobject from lua but no longer exists') end print('Warning: TS object :exists or isobject from lua but no longer exists')
end
return is return is
end end
function bl.isObject(obj) function bl.isObject(obj)
@@ -278,6 +320,7 @@ function bl.isObject(obj)
error('bl.isObject: argument #1: expected torque object, number, or string', 2) error('bl.isObject: argument #1: expected torque object, number, or string', 2)
end end
end end
function bl.isFunction(name) function bl.isFunction(name)
return tsIsFunctionNsname(name) return tsIsFunctionNsname(name)
end end
@@ -292,9 +335,11 @@ local tsClassMeta = {
bl._objectUserMetas = bl._objectUserMetas or {} bl._objectUserMetas = bl._objectUserMetas or {}
function bl.class(cname, inhname) function bl.class(cname, inhname)
if not (type(cname) == 'string' and isValidFuncName(cname)) then 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 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() cname = cname:lower()
local met = bl._objectUserMetas[cname] or { local met = bl._objectUserMetas[cname] or {
@@ -309,15 +354,18 @@ function bl.class(cname, inhname)
inhname = inhname:lower() inhname = inhname:lower()
local inh = bl._objectUserMetas[inhname] local inh = bl._objectUserMetas[inhname]
if not inh then error('bl.class: argument #2: \''..inhname..'\' is not the '.. if not inh then
'name of an existing class', 2) end error('bl.class: argument #2: \'' .. inhname .. '\' is not the ' ..
'name of an existing class', 2)
end
inh._children[cname] = true inh._children[cname] = true
local inhI = met._inherit local inhI = met._inherit
if inhI and inhI ~= inh then if inhI and inhI ~= inh then
error('bl.class: argument #2: class already exists and ' .. error('bl.class: argument #2: class already exists and ' ..
'inherits a different parent.', 2) end 'inherits a different parent.', 2)
end
met._inherit = inh met._inherit = inh
-- apply inherited method and field types -- apply inherited method and field types
@@ -329,6 +377,7 @@ function bl.class(cname, inhname)
end end
end end
end end
local function objectInheritedMetas(name) local function objectInheritedMetas(name)
local inh = bl._objectUserMetas[name:lower()] local inh = bl._objectUserMetas[name:lower()]
return function() return function()
@@ -343,15 +392,18 @@ local tsObjectMeta = {
-- Return torque member function or value -- Return torque member function or value
__index = function(t, name) __index = function(t, name)
if rawget(t, '_deleted') then 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 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 if getmetatable(t)[name] then
return getmetatable(t)[name] return getmetatable(t)[name]
elseif type(name) == 'number' then elseif type(name) == 'number' then
if not tsIsFunctionNs(rawget(t, '_tsNamespace'), 'getObject') then if not tsIsFunctionNs(rawget(t, '_tsNamespace'), 'getObject') then
error('ts object __index: index is number, but object does not have ' .. 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', return toTsObject(_bllua_ts.callobj(t._tsObjectId, 'getObject',
tostring(name))) tostring(name)))
else else
@@ -362,17 +414,20 @@ local tsObjectMeta = {
tsIsFunctionNs(rawget(t, '_tsNamespace'), name) or tsIsFunctionNs(rawget(t, '_tsNamespace'), name) or
tsIsFunctionNs(rawget(t, '_tsName'), name) tsIsFunctionNs(rawget(t, '_tsName'), name)
then then
return function(t, ...) return function(t2, ...)
local args = {...} if t2 == nil or type(t2) ~= 'table' or not t2._tsObjectId then
local argsS = arglistToTs(args) error('ts object method: be sure to use :func() not .func()', 2)
return valFromTs( end
_bllua_ts.callobj(rawget(t,'_tsObjectId'), name, unpack(argsS)), local argsS = arglistToTs({ ... })
rawget(t,'_tsName') and rawget(t,'_tsName')..'::'..name, local res =
rawget(t,'_tsNamespace')..'::'..name) _bllua_ts.callobj(t2._tsObjectId, name, unpack(argsS))
return valFromTs(res,
t2._tsName and t2._tsName .. '::' .. name,
t2._tsNamespace .. '::' .. name)
end end
else else
return valFromTs( local res = _bllua_ts.getfield(rawget(t, '_tsObjectId'), name)
_bllua_ts.getfield(rawget(t,'_tsObjectId'), name), return valFromTs(res,
rawget(t, '_tsName') and rawget(t, '_tsName') .. '.' .. name, rawget(t, '_tsName') and rawget(t, '_tsName') .. '.' .. name,
rawget(t, '_tsNamespace') .. '.' .. name) rawget(t, '_tsNamespace') .. '.' .. name)
end end
@@ -383,9 +438,11 @@ local tsObjectMeta = {
-- Use :set() to set Torque data -- Use :set() to set Torque data
__newindex = function(t, name, val) __newindex = function(t, name, val)
if rawget(t, '_deleted') then 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 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) rawset(t, name, val)
-- create strong reference since it's now storing lua data -- create strong reference since it's now storing lua data
bl._objectsStrong[rawget(t, '_tsObjectId')] = t bl._objectsStrong[rawget(t, '_tsObjectId')] = t
@@ -394,11 +451,14 @@ local tsObjectMeta = {
-- Use to set torque data -- Use to set torque data
set = function(t, name, val) set = function(t, name, val)
if t == nil or type(t) ~= 'table' or not t._tsObjectId then 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 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 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)) _bllua_ts.setfield(t._tsObjectId, name, valToTs(val))
end, end,
-- __tostring: Called when printing -- __tostring: Called when printing
@@ -412,9 +472,11 @@ local tsObjectMeta = {
-- If the object has a getCount method, return its count -- If the object has a getCount method, return its count
__len = function(t) __len = function(t)
if t._deleted then 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 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')) return tonumber(_bllua_ts.callobj(t._tsObjectId, 'getCount'))
end, end,
-- object:members() -- object:members()
@@ -422,14 +484,17 @@ local tsObjectMeta = {
-- for index, object in group:members() do ... end -- for index, object in group:members() do ... end
members = function(t) members = function(t)
if t == nil then 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 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 ( if not (
tsIsFunctionNs(t._tsNamespace, 'getCount') and tsIsFunctionNs(t._tsNamespace, 'getCount') and
tsIsFunctionNs(t._tsNamespace, 'getObject')) then tsIsFunctionNs(t._tsNamespace, 'getObject')) then
error('ts object :members() - ' .. 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 count = tonumber(_bllua_ts.callobj(t._tsObjectId, 'getCount'))
local idx = 0 local idx = 0
return function() return function()
@@ -437,7 +502,8 @@ local tsObjectMeta = {
local obj = toTsObject(_bllua_ts.callobj(t._tsObjectId, local obj = toTsObject(_bllua_ts.callobj(t._tsObjectId,
'getObject', tostring(idx))) 'getObject', tostring(idx)))
idx = idx + 1 idx = idx + 1
return idx-1, obj --return idx-1, obj
return obj
else else
return nil return nil
end end
@@ -446,23 +512,29 @@ local tsObjectMeta = {
-- Wrap some Torque functions for performance and error checking -- Wrap some Torque functions for performance and error checking
getName = function(t) getName = function(t)
if t == nil then 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 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 return t._tsName
end, end,
getId = function(t) getId = function(t)
if t == nil then 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 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) return tonumber(t._tsObjectId)
end, end,
getType = function(t) getType = function(t)
if t == nil then 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 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')] return tsTypesByNum[_bllua_ts.callobj(t._tsObjectId, 'getType')]
end, end,
---- Schedule method for objects ---- Schedule method for objects
@@ -484,7 +556,8 @@ local tsObjectMeta = {
--end, --end,
exists = function(t) exists = function(t)
if t == nil then 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 if t._deleted then
return false return false
else else
@@ -518,13 +591,15 @@ end
-- Return a Torque object for the object ID string, or create one if none exists -- Return a Torque object for the object ID string, or create one if none exists
toTsObject = function(idiS) toTsObject = function(idiS)
if type(idiS) ~= 'string' then 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() idiS = idiS:lower()
if bl._objectsWeak[idiS] then return bl._objectsWeak[idiS] end if bl._objectsWeak[idiS] then return bl._objectsWeak[idiS] end
if not tsIsObject(idiS) then if not tsIsObject(idiS) then
--error('toTsObject: object \''..idiS..'\' does not exist', 2) end --error('toTsObject: object \''..idiS..'\' does not exist', 2) end
return nil end return nil
end
local className = _bllua_ts.callobj(idiS, 'getClassName') local className = _bllua_ts.callobj(idiS, 'getClassName')
local obj = { local obj = {
@@ -544,14 +619,13 @@ end
local function safeNamespaceName(name) local function safeNamespaceName(name)
return tostring(name:gsub(':', '_')) return tostring(name:gsub(':', '_'))
end end
local nscallArgStr = '%a,%b,%c,%d,%e,%f,%g,%h'
bl._cachedNamespaceCalls = {} bl._cachedNamespaceCalls = {}
local function tsNamespacedCallTfname(name) local function tsNamespacedCallTfname(name)
local tfname = bl._cachedNamespaceCalls[name] local tfname = bl._cachedNamespaceCalls[name]
if not tfname then if not tfname then
tfname = '_bllua_nscall_' .. safeNamespaceName(name) tfname = '_bllua_nscall_' .. safeNamespaceName(name)
local tfcode = 'function '..tfname..'('..nscallArgStr..'){'.. local tfcode = 'function ' .. tfname .. '(' .. tsArgsLocal .. '){' ..
name..'('..nscallArgStr..');}' name .. '(' .. tsArgsLocal .. ');}'
_bllua_ts.eval(tfcode) _bllua_ts.eval(tfcode)
bl._cachedNamespaceCalls[name] = tfname bl._cachedNamespaceCalls[name] = tfname
end end
@@ -559,9 +633,9 @@ local function tsNamespacedCallTfname(name)
end end
local function tsCallGen(name) local function tsCallGen(name)
return function(...) return function(...)
local args = {...} local argsS = arglistToTs({ ... })
local argsS = arglistToTs(args) local res = _bllua_ts.call(name, unpack(argsS))
return valFromTs(_bllua_ts.call(name, unpack(argsS)), name) return valFromTs(res, name)
end end
end end
@@ -587,14 +661,16 @@ local tsMeta = {
if not rest:find('::') and tsIsFunctionNs(ns, rest) then if not rest:find('::') and tsIsFunctionNs(ns, rest) then
return tsCallGen(tsNamespacedCallTfname(name)) return tsCallGen(tsNamespacedCallTfname(name))
else else
return valFromTs(_bllua_ts.getvar(name), name) local res = _bllua_ts.getvar(name)
return valFromTs(res, name)
end end
elseif tsIsFunction(name) then elseif tsIsFunction(name) then
return tsCallGen(name) return tsCallGen(name)
elseif tsIsObject(name) then elseif tsIsObject(name) then
return toTsObject(name) return toTsObject(name)
else else
return valFromTs(_bllua_ts.getvar(name), name) local res = _bllua_ts.getvar(name)
return valFromTs(res, name)
end end
end end
end, end,
@@ -612,16 +688,22 @@ function bl.call(func, ...)
local argsS = arglistToTs(args) local argsS = arglistToTs(args)
return _bllua_ts.call(func, unpack(argsS)) return _bllua_ts.call(func, unpack(argsS))
end end
function bl.eval(code) function bl.eval(code)
return valFromTs(_bllua_ts.eval(code)) local res = _bllua_ts.eval(code)
return valFromTs(res)
end end
function bl.exec(file) function bl.exec(file)
return valFromTs(_bllua_ts.call('exec', file)) local res = _bllua_ts.call('exec', file)
return valFromTs(res)
end end
function bl.array(name, ...) function bl.array(name, ...)
local rest = { ... } local rest = { ... }
return name .. table.concat(rest, '_') return name .. table.concat(rest, '_')
end end
function bl.boolean(val) function bl.boolean(val)
return val ~= nil and return val ~= nil and
val ~= false and val ~= false and
@@ -629,6 +711,7 @@ function bl.boolean(val)
--val~='0' and --val~='0' and
val ~= 0 val ~= 0
end end
function bl.object(id) function bl.object(id)
if type(id) == 'table' and id._tsObjectId then if type(id) == 'table' and id._tsObjectId then
return id return id
@@ -638,6 +721,7 @@ function bl.object(id)
error('bl.object: id must be a ts object, number, or string', 2) error('bl.object: id must be a ts object, number, or string', 2)
end end
end end
function bl.string(val) function bl.string(val)
return valFromTsTostring(val) return valFromTsTostring(val)
end end
@@ -645,26 +729,36 @@ end
-- Lua calling from TS -- Lua calling from TS
local luaLookup local luaLookup
luaLookup = function(tbl, name, set, val) luaLookup = function(tbl, name, set, val)
print('lookup', tbl, name, set, val)
if name:find('%.') then if name:find('%.') then
local first, rest = name:match('^([^%.:]+)%.(.+)$') local first, rest = name:match('^([^%.:]+)%.(.+)$')
if not isValidFuncName(first) then if not isValidFuncName(first) then
error('luaLookup: invalid name \''..tostring(first)..'\'', 3) end error('luaLookup: invalid name \'' .. tostring(first) .. '\'', 3)
if not tbl[first] then end
if set then tbl[first] = {} if tbl[first] == nil then
else return nil end if set then
tbl[first] = {}
else
return nil
end
end end
return luaLookup(tbl[first], rest, set, val) return luaLookup(tbl[first], rest, set, val)
elseif name:find(':') then elseif name:find(':') then
local first, rest = name:match('^([^%.:]+):(.*)$') local first, rest = name:match('^([^%.:]+):(.*)$')
if rest:find('[%.:]') then if rest:find('[%.:]') then
error('luacall: cannot have : or . after :', 3) end error('luacall: cannot have : or . after :', 3)
end
if not isValidFuncName(first) then if not isValidFuncName(first) then
error('luacall: invalid name \''..tostring(first)..'\'', 3) end error('luacall: invalid name \'' .. tostring(first) .. '\'', 3)
end
if not isValidFuncName(rest) then
error('luacall: invalid method name \'' .. tostring(first) .. '\'', 3)
end
if not tbl[first] then if not tbl[first] then
error('luacall: no object named \''..rest..'\'', 3) end error('luacall: no object named \'' .. rest .. '\'', 3)
if not tbl[first][rest] then end
error('luacall: no method named \''..rest..'\'', 3) end if tbl[first][rest] == nil then
error('luacall: no method named \'' .. rest .. '\'', 3)
end
return function(...) return function(...)
tbl[first][rest](tbl[first], ...) tbl[first][rest](tbl[first], ...)
end end
@@ -681,19 +775,23 @@ function _bllua_call(fname, ...)
local args = arglistFromTs(fname:lower(), { ... }) -- todo: separate lua from ts func names? local args = arglistFromTs(fname:lower(), { ... }) -- todo: separate lua from ts func names?
local func = luaLookup(_G, fname) local func = luaLookup(_G, fname)
if not func then if not func then
error('luacall: no global in lua named \''..name..'\'', 2) end error('luacall: no global in lua named \'' .. fname .. '\'', 2)
end
local res = func(unpack(args)) local res = func(unpack(args))
return valToTs(res) return valToTs(res)
end end
function _bllua_getvar(vname) function _bllua_getvar(vname)
return valToTs(luaLookup(_G, vname)) return valToTs(luaLookup(_G, vname))
end end
function _bllua_setvar(vname, valS) function _bllua_setvar(vname, valS)
return valToTs(luaLookup(_G, vname, true, valFromTs(valS, vname))) -- todo: separate lua from ts var names? return valToTs(luaLookup(_G, vname, true, valFromTs(valS, vname))) -- todo: separate lua from ts var names?
end end
function _bllua_eval(code) return loadstring(code)() end
function _bllua_exec(fn) return dofile(fn, 2) end
function _bllua_eval(code) return loadstring(code)() end
function _bllua_exec(fn) return dofile(fn, 2) end
-- bl.schedule: Use TS's schedule function to schedule lua calls -- bl.schedule: Use TS's schedule function to schedule lua calls
-- bl.schedule(time, function, args...) -- bl.schedule(time, function, args...)
@@ -721,6 +819,7 @@ function bl.schedule(time, cb, ...)
bl._scheduleTable[id] = sch bl._scheduleTable[id] = sch
return sch return sch
end end
function _bllua_schedule_callback(idS) function _bllua_schedule_callback(idS)
local id = tonumber(idS) or local id = tonumber(idS) or
error('_bllua_schedule_callback: invalid id: ' .. tostring(idS)) error('_bllua_schedule_callback: invalid id: ' .. tostring(idS))
@@ -739,19 +838,21 @@ function _bllua_process_cmd(cmdS, ...)
if not func then error('_bllua_process_cmd: no cmd named \'' .. cmd .. '\'') end if not func then error('_bllua_process_cmd: no cmd named \'' .. cmd .. '\'') end
func(unpack(args)) --pcall(func, unpack(args)) func(unpack(args)) --pcall(func, unpack(args))
end end
local function addCmd(cmd, func) local function addCmd(cmd, func)
if not isValidFuncName(cmd) then 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 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 .. '(' .. tsArgsLocal .. '){' ..
_bllua_ts.eval('function '..cmd..'('..arglist..'){'.. '_bllua_luacall(_bllua_process_cmd,"' .. cmd .. '",' .. tsArgsLocal .. ');}')
'_bllua_luacall(_bllua_process_cmd,"'..cmd..'",'..arglist..');}')
end end
function bl.addServerCmd(name, func) function bl.addServerCmd(name, func)
name = name:lower() name = name:lower()
addCmd('servercmd' .. name, func) addCmd('servercmd' .. name, func)
bl._forceType['servercmd' .. name .. ':1'] = 'object' bl._forceType['servercmd' .. name .. ':1'] = 'object'
end end
function bl.addClientCmd(name, func) function bl.addClientCmd(name, func)
name = name:lower() name = name:lower()
addCmd('clientcmd' .. name, func) addCmd('clientcmd' .. name, func)
@@ -763,11 +864,14 @@ function bl.commandToServer(cmd, ...)
_bllua_ts.call('addTaggedString', cmd), _bllua_ts.call('addTaggedString', cmd),
unpack(arglistToTs({ ... }))) unpack(arglistToTs({ ... })))
end end
function bl.commandToClient(cmd, ...)
function bl.commandToClient(client, cmd, ...)
_bllua_ts.call('commandToClient', _bllua_ts.call('commandToClient',
valToTs(client),
_bllua_ts.call('addTaggedString', cmd), _bllua_ts.call('addTaggedString', cmd),
unpack(arglistToTs({ ... }))) unpack(arglistToTs({ ... })))
end end
function bl.commandToAll(cmd, ...) function bl.commandToAll(cmd, ...)
_bllua_ts.call('commandToAll', _bllua_ts.call('commandToAll',
_bllua_ts.call('addTaggedString', cmd), _bllua_ts.call('addTaggedString', cmd),
@@ -793,26 +897,25 @@ local function deactivatePackage(pkg)
_bllua_ts.call('deactivatePackage', pkg) _bllua_ts.call('deactivatePackage', pkg)
end end
end 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'
bl._hooks = bl._hooks or {} bl._hooks = bl._hooks or {}
function _bllua_process_hook_before(pkgS, nameS, ...) function _bllua_process_hook_before(pkgS, nameS, ...)
local args = arglistFromTs(nameS, { ... }) local args = arglistFromTs(nameS, { ... })
local func = bl._hooks[pkgS] and bl._hooks[pkgS][nameS] and local func = bl._hooks[pkgS] and bl._hooks[pkgS][nameS] and
bl._hooks[pkgS][nameS].before bl._hooks[pkgS][nameS].before
if not func then 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') _bllua_ts.setvar('_bllua_hook_abort', '0')
func(args) --pcall(func, args) func(args) --pcall(func, args)
if args._return then if args._return then
_bllua_ts.setvar('_bllua_hook_abort', '1') _bllua_ts.setvar('_bllua_hook_abort', '1')
_bllua_ts.setvar('_bllua_hook_return', valToTs(args._return)) _bllua_ts.setvar('_bllua_hook_return', valToTs(args._return))
end end
for i=1,hookNargs do for i = 1, tsMaxArgs do
_bllua_ts.setvar('_bllua_hook_arg' .. i, valToTs(args[i])) _bllua_ts.setvar('_bllua_hook_arg' .. i, valToTs(args[i]))
end end
end end
function _bllua_process_hook_after(pkgS, nameS, resultS, ...) function _bllua_process_hook_after(pkgS, nameS, resultS, ...)
nameS = nameS:lower() nameS = nameS:lower()
local args = arglistFromTs(nameS, { ... }) local args = arglistFromTs(nameS, { ... })
@@ -820,15 +923,17 @@ function _bllua_process_hook_after(pkgS, nameS, resultS, ...)
local func = bl._hooks[pkgS] and bl._hooks[pkgS][nameS] and local func = bl._hooks[pkgS] and bl._hooks[pkgS][nameS] and
bl._hooks[pkgS][nameS].after bl._hooks[pkgS][nameS].after
if not func then 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) func(args) --pcall(func, args)
return valToTs(args._return) return valToTs(args._return)
end end
local function updateHook(pkg, name, hk) local function updateHook(pkg, name, hk)
local beforeCode = hk.before and local beforeCode = hk.before and
('_bllua_luacall("_bllua_process_hook_before","' .. pkg .. '","' .. name .. ('_bllua_luacall("_bllua_process_hook_before","' .. pkg .. '","' .. name ..
'",'..hookArglistLocal..');') or '' '",' .. tsArgsLocal .. ');') or ''
local arglist = (hk.before and hookArglistGlobal or hookArglistLocal) local arglist = (hk.before and tsArgsGlobal or tsArgsLocal)
local parentCode = local parentCode =
tsIsFunctionNsname(name) and -- only call parent if it exists tsIsFunctionNsname(name) and -- only call parent if it exists
(hk.before and (hk.before and
@@ -841,21 +946,26 @@ local function updateHook(pkg, name, hk)
arglist .. ');') or '' arglist .. ');') or ''
local code = local code =
'package ' .. pkg .. '{' .. 'package ' .. pkg .. '{' ..
'function '..name..'('..hookArglistLocal..'){'.. 'function ' .. name .. '(' .. tsArgsLocal .. '){' ..
beforeCode .. parentCode .. afterCode .. beforeCode .. parentCode .. afterCode ..
'}' .. '}' ..
'};' '};'
print('bl.hook eval output: [[' .. code .. ']]')
_bllua_ts.eval(code) _bllua_ts.eval(code)
end end
function bl.hook(pkg, name, time, func) function bl.hook(pkg, name, time, func)
if not isValidFuncName(pkg) then 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 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 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 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] = bl._hooks[pkg] or {}
bl._hooks[pkg][name] = bl._hooks[pkg][name] or {} bl._hooks[pkg][name] = bl._hooks[pkg][name] or {}
@@ -864,16 +974,20 @@ function bl.hook(pkg, name, time, func)
updateHook(pkg, name, bl._hooks[pkg][name]) updateHook(pkg, name, bl._hooks[pkg][name])
activatePackage(pkg) activatePackage(pkg)
end end
local function tableEmpty(t) local function tableEmpty(t)
return next(t) ~= nil return next(t) ~= nil
end end
function bl.unhook(pkg, name, time) function bl.unhook(pkg, name, time)
if not isValidFuncName(pkg) then 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)
if not isValidFuncNameNs(name) then end
error('bl.unhook: argument #2: invalid function name \''..tostring(name)..'\'', 2) end if name and not isValidFuncNameNs(name) then
if time~='before' and time~='after' then error('bl.unhook: argument #2: invalid function name \'' .. tostring(name) .. '\'', 2)
error('bl.unhook: argument #3: time must be \'before\' or \'after\'', 2) end end
if time and time ~= 'before' and time ~= 'after' then
error('bl.unhook: argument #3: time must be \'before\' or \'after\'', 2)
end
if not name then if not name then
if bl._hooks[pkg] then if bl._hooks[pkg] then
@@ -897,12 +1011,16 @@ function bl.unhook(pkg, name, time)
updateHook(pkg, name, {}) updateHook(pkg, name, {})
else else
if time ~= 'before' and time ~= 'after' then 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 bl._hooks[pkg][name][time] = nil
if tableEmpty(bl._hooks[pkg][name]) and tableEmpty(bl._hooks[pkg]) then if tableEmpty(bl._hooks[pkg][name]) then
bl._hooks[pkg][name] = nil
end
if tableEmpty(bl._hooks[pkg]) then
bl._hooks[pkg] = nil bl._hooks[pkg] = nil
deactivatePackage(pkg)
updateHook(pkg, name, {}) updateHook(pkg, name, {})
deactivatePackage(pkg)
else else
updateHook(pkg, name, bl._hooks[pkg][name]) updateHook(pkg, name, bl._hooks[pkg][name])
end end
@@ -917,14 +1035,16 @@ end
-- Container search/raycast -- Container search/raycast
local function vecToTs(v) local function vecToTs(v)
if not isTsVector(v) then 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, ' ') return table.concat(v, ' ')
end end
local function maskToTs(mask) local function maskToTs(mask)
if type(mask) == 'string' then if type(mask) == 'string' then
local val = tsTypesByName[mask:lower()] local val = tsTypesByName[mask:lower()]
if not val then if not val then
error('maskToTs: invalid mask \''..mask..'\'', 3) end error('maskToTs: invalid mask \'' .. mask .. '\'', 3)
end
return tostring(val) return tostring(val)
elseif type(mask) == 'table' then elseif type(mask) == 'table' then
local tval = 0 local tval = 0
@@ -934,7 +1054,8 @@ local function maskToTs(mask)
local val = tsTypesByName[v:lower()] local val = tsTypesByName[v:lower()]
if not val then if not val then
error('maskToTs: invalid mask \'' .. v .. error('maskToTs: invalid mask \'' .. v ..
'\' at index '..i..' in mask list', 3) end '\' at index ' .. i .. ' in mask list', 3)
end
tval = tval + val tval = tval + val
seen[v] = true seen[v] = true
end end
@@ -958,7 +1079,7 @@ function bl.raycast(start, stop, mask, ignores)
local stopS = vecToTs(start) local stopS = vecToTs(start)
local maskS = maskToTs(mask) local maskS = maskToTs(mask)
local ignoresS = {} local ignoresS = {}
for _,v in ipairs(ignores) do for _, v in ipairsNilable(ignores) do
table.insert(ignoresS, objToTs(v)) table.insert(ignoresS, objToTs(v))
end end
@@ -976,6 +1097,7 @@ function bl.raycast(start, stop, mask, ignores)
return hit, pos, norm return hit, pos, norm
end end
end end
local function tsContainerSearchIterator() local function tsContainerSearchIterator()
local retS = _bllua_ts.call('containerSearchNext') local retS = _bllua_ts.call('containerSearchNext')
if retS == '0' then if retS == '0' then
@@ -992,10 +1114,12 @@ function bl.boxSearch(pos, size, mask)
_bllua_ts.call('initContainerBoxSearch', posS, sizeS, maskS) _bllua_ts.call('initContainerBoxSearch', posS, sizeS, maskS)
return tsContainerSearchIterator return tsContainerSearchIterator
end end
function bl.radiusSearch(pos, radius, mask) function bl.radiusSearch(pos, radius, mask)
local posS = vecToTs(pos) local posS = vecToTs(pos)
if type(radius) ~= 'number' then 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 radiusS = tostring(radius)
local maskS = maskToTs(mask) local maskS = maskToTs(mask)
@@ -1007,7 +1131,7 @@ end
local maxTsArgLen = 8192 local maxTsArgLen = 8192
local function valsToString(vals) local function valsToString(vals)
local strs = {} local strs = {}
for i,v in ipairs(vals) do for i, v in ipairsNilable(vals) do
local tstr = table.tostring(v) local tstr = table.tostring(v)
if #tstr > maxTsArgLen then if #tstr > maxTsArgLen then
tstr = tostring(v) tstr = tostring(v)
@@ -1033,7 +1157,8 @@ local function createTsObj(keyword, class, name, inherit, props)
if props then if props then
for k, v in pairs(props) do for k, v in pairs(props) do
if not isValidFuncName(k) then 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) .. '";') table.insert(propsT, k .. '="' .. valToTs(v) .. '";')
end end
end end
@@ -1044,7 +1169,8 @@ local function createTsObj(keyword, class, name, inherit, props)
table.concat(propsT) .. '};') table.concat(propsT) .. '};')
local obj = toTsObject(objS) local obj = toTsObject(objS)
if not obj then 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 return obj
end end
@@ -1069,13 +1195,15 @@ local function parseTsDecl(decl)
(inherit == nil or isValidFuncName(inherit))) then (inherit == nil or isValidFuncName(inherit))) then
error('bl.new/bl.datablock: invalid decl \'' .. decl .. '\'\n' .. error('bl.new/bl.datablock: invalid decl \'' .. decl .. '\'\n' ..
'must be of the format: \'className\', \'className name\', ' .. '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 return class, name, inherit
end end
function bl.new(decl, props) function bl.new(decl, props)
local class, name, inherit = parseTsDecl(decl) local class, name, inherit = parseTsDecl(decl)
return createTsObj('new', class, name, inherit, props) return createTsObj('new', class, name, inherit, props)
end end
function bl.datablock(decl, props) function bl.datablock(decl, props)
local class, name, inherit = parseTsDecl(decl) local class, name, inherit = parseTsDecl(decl)
if not name then error('bl.datablock: must specify a name', 2) end if not name then error('bl.datablock: must specify a name', 2) end

View File

@@ -93,11 +93,15 @@ local allowed_zip_dirs = tflip {
local function io_open_absolute(fn, mode) local function io_open_absolute(fn, mode)
-- if file exists, use original mode -- if file exists, use original mode
local res, err = _bllua_io_open(fn, mode) local res, err = _bllua_io_open(fn, mode)
if res then return res end if res then
return res
elseif err and not err:find('No such file or directory$') then
return nil, err
end
-- otherwise, if TS sees file but Lua doesn't, it must be in a zip, so use TS reader -- otherwise, if TS sees file but Lua doesn't, it must be in a zip, so use TS reader
local dir = fn:match('^[^/]+') local dir = fn:match('^[^/]+')
if not allowed_zip_dirs[dir:lower()] then return nil, 'File is not in one of the allowed directories' end if not allowed_zip_dirs[dir:lower()] then return nil, 'Zip is not in one of the allowed directories' end
local exist = _bllua_ts.call('isFile', fn) == '1' local exist = _bllua_ts.call('isFile', fn) == '1'
if not exist then return nil, err end if not exist then return nil, err end

View File

@@ -13,8 +13,19 @@ function table.map(f, ...)
local u = {} local u = {}
for k, _ in pairs(ts[1]) do for k, _ in pairs(ts[1]) do
local args = {} local args = {}
for j = 1, #ts do args[j] = ts[j][i] end for j = 1, #ts do args[j] = ts[j][k] end
u[i] = f(unpack(args)) u[k] = f(unpack(args))
end
return u
end
function table.mapk(f, ...)
local ts = { ... }
local u = {}
for k, _ in pairs(ts[1]) do
local args = {}
for j = 1, #ts do args[j] = ts[j][k] end
u[k] = f(k, unpack(args))
end end
return u return u
end end
@@ -30,6 +41,17 @@ function table.map_list(f, ...)
return u return u
end end
function table.mapi_list(f, ...)
local ts = { ... }
local u = {}
for i = 1, #ts[1] do
local args = {}
for j = 1, #ts do args[j] = ts[j][i] end
u[i] = f(i, unpack(args))
end
return u
end
-- Swap keys/values -- Swap keys/values
function table.swap(t) function table.swap(t)
local u = {} local u = {}
@@ -193,7 +215,7 @@ valueToString = function(v, tabLevel, seen)
return tostring(v) return tostring(v)
else else
--error('table.tostring: table contains a '..t..' value, cannot serialize') --error('table.tostring: table contains a '..t..' value, cannot serialize')
return 'nil --[[ cannot serialize ' .. tostring(v) .. ' ]]' return 'nil --[[ ' .. tostring(v) .. ' ]]'
end end
end end
function table.tostring(t) function table.tostring(t)