forked from redo/BlockLua
Compare commits
6 Commits
ed5c254480
...
5885dcbed3
| Author | SHA1 | Date | |
|---|---|---|---|
| 5885dcbed3 | |||
| d9a416f5d5 | |||
|
|
2191e004ad | ||
|
|
ae34bb8b7a | ||
|
|
b71bfdb73e | ||
|
|
7232ede09d |
BIN
BlockLua.dll
BIN
BlockLua.dll
Binary file not shown.
62
readme.md
62
readme.md
@@ -14,10 +14,10 @@ Lua scripting for Blockland
|
||||
### From TorqueScript
|
||||
`'print('hello world')` - Execute Lua in the console by prepending a `'` (single quote)
|
||||
`luaeval("code");` - Execute Lua code
|
||||
`luacall("funcName", %args...);` - Call a Lua global function
|
||||
`luaexec("fileName");` - Execute a Lua file. Path rules are the same as executing .cs files.
|
||||
`luaget("varName");` - Read a Lua global variable
|
||||
`luaset("varName", %value);` - Write a Lua global variable
|
||||
`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 when executing .cs files, relative paths are allowed.
|
||||
`luaget("varName");` - Read a Lua global variable (supports indexing tables)
|
||||
`luaset("varName", %value);` - Write a Lua global variable (supports indexing tables)
|
||||
|
||||
### From Lua
|
||||
`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:method(args)` - Call a Torque object method
|
||||
`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
|
||||
`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.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
|
||||
`bl.addServerCmd('commandName', function(client, args...) yourCode end)` - Register a /command on the server
|
||||
`bl.addClientCmd('commandName', function(args...) yourCode end)` - Register a client command on the client
|
||||
`bl.commandToServer('commandName', args...)` - Execute a server command as a client
|
||||
`bl.commandToClient('commandName', args...)` - As the server, execute a client command on a specific client
|
||||
`bl.addServerCmd('commandName', function(client, args...) ... end)` - Register a /command on the server
|
||||
`bl.addClientCmd('commandName', function(args...) ... end)` - Register a client command on the client
|
||||
`bl.commandToServer('commandName', args...)` - As a client, execute a server command
|
||||
`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
|
||||
|
||||
### 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.
|
||||
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.
|
||||
@@ -136,14 +146,16 @@ When reading from outside ZIPs, binary files are fully supported.
|
||||
WIP
|
||||
|
||||
### Extended Standard Lua Library
|
||||
`string[index]`
|
||||
`string[{start,stop}]`
|
||||
`str[index]`
|
||||
`str[{start,stop}]`
|
||||
`string.split(str, separator='' (splits into chars), noregex=false)`
|
||||
`string.bytes(str)`
|
||||
`string.trim(str, charsToTrim=' \t\r\n')`
|
||||
`table.empty`
|
||||
`table.map(func, ...)`
|
||||
`table.mapk(func, ...)`
|
||||
`table.map_list(func, ...)`
|
||||
`table.mapi_list(func, ...)`
|
||||
`table.swap(tbl)`
|
||||
`table.reverse(list)`
|
||||
`table.islist(list)`
|
||||
@@ -173,43 +185,35 @@ TorqueScript stores no type information; all values in TorqueScript are strings.
|
||||
### From Lua to TorqueScript
|
||||
- `nil` becomes the empty string ""
|
||||
- `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 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
|
||||
- Tables cannot be passed and will throw an error
|
||||
|
||||
### 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`
|
||||
- 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 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`
|
||||
|
||||
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
|
||||
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.
|
||||
### 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.
|
||||
Please do not publish add-ons that require unsafe mode.
|
||||
|
||||
### 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'`
|
||||
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.
|
||||
|
||||
## Compiling
|
||||
|
||||
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/
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// BlockLua (bllua4): Simple Lua interface for TorqueScript
|
||||
// BlockLua (bllua4): Advanced Lua interface for TorqueScript
|
||||
|
||||
// Includes
|
||||
|
||||
@@ -60,23 +60,27 @@ bool init() {
|
||||
|
||||
// Set up Lua environment
|
||||
BLL_LOAD_LUA(gL, bll_fileLuaEnv);
|
||||
#ifdef BLLUA_ALLOWFFI
|
||||
lua_pushboolean(gL, true);
|
||||
lua_setglobal(gL, "_bllua_allowffi");
|
||||
#endif
|
||||
#ifndef BLLUA_UNSAFE
|
||||
BLL_LOAD_LUA(gL, bll_fileLuaEnvSafe);
|
||||
#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
|
||||
BlAddFunction(
|
||||
NULL, NULL, "_bllua_luacall", bll_ts_luacall, "LuaCall(name, ...) - Call Lua function and return result", 2, 20);
|
||||
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);
|
||||
BLL_LOAD_LUA(gL, bll_fileLuaLibbl);
|
||||
BLL_LOAD_LUA(gL, bll_fileLuaLibblTypes);
|
||||
BlEval(bll_fileTsLibblSupport);
|
||||
BlEval(bll_fileLoadaddons);
|
||||
|
||||
@@ -89,8 +93,7 @@ bool init() {
|
||||
bool deinit() {
|
||||
BlPrintf("BlockLua: Unloading");
|
||||
|
||||
BlEval("deactivatePackage(_bllua_main);");
|
||||
BlEval("$_bllua_active = 0;");
|
||||
BlEval("$_bllua_active=0;deactivatePackage(_bllua_main);");
|
||||
bll_LuaEval(gL, "for _,f in pairs(_bllua_on_unload) do f() end");
|
||||
|
||||
lua_close(gL);
|
||||
|
||||
@@ -14,6 +14,7 @@ local old_require = require
|
||||
local old_os = os
|
||||
local old_debug = debug
|
||||
local old_package = package
|
||||
local old_allowffi = _bllua_allowffi
|
||||
|
||||
-- Remove all global variables except a whitelist
|
||||
local ok_names = tmap {
|
||||
@@ -39,13 +40,10 @@ end
|
||||
|
||||
-- Sanitize file paths to point only to allowed files within the game directory
|
||||
-- List of allowed directories for reading/writing
|
||||
-- modules/lualib is also allowed as read-only
|
||||
local allowed_dirs = tmap {
|
||||
'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
|
||||
-- Note that even without this protection, exploiting would still require somehow
|
||||
-- getting a file within the allowed directories to autorun,
|
||||
@@ -81,14 +79,15 @@ local function safe_path(fn, readonly)
|
||||
end
|
||||
-- allow only whitelisted dirs
|
||||
local dir = fn:match('^([^/]+)/')
|
||||
if (not dir) or (
|
||||
(not allowed_dirs[dir:lower()]) and
|
||||
((not readonly) or (not allowed_dirs_readonly[dir:lower()]))) then
|
||||
return nil, 'filename is in disallowed directory ' .. (dir or 'nil')
|
||||
if not (dir and (
|
||||
allowed_dirs[dir:lower()] or
|
||||
(readonly and fn:find('^modules/lualib/'))))
|
||||
then
|
||||
return nil, 'File is in disallowed directory ' .. (dir or 'nil')
|
||||
end
|
||||
-- disallow blacklisted extensions or no extension
|
||||
-- disallow blacklisted extensions
|
||||
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 \'' ..
|
||||
(ext or '') .. '\''
|
||||
end
|
||||
@@ -120,6 +119,7 @@ local disallowed_packages = tmap {
|
||||
'ffi', 'debug', 'package', 'io', 'os',
|
||||
'_bllua_ts',
|
||||
}
|
||||
if old_allowffi then disallowed_packages['ffi'] = nil end
|
||||
function _bllua_requiresecure(name)
|
||||
if name:find('[^a-zA-Z0-9_%-%.]') or name:find('%.%.') or
|
||||
name:find('^%.') or name:find('%.$') then
|
||||
|
||||
@@ -37,5 +37,9 @@ function _bllua_on_error(err)
|
||||
return table.concat(tracelines, '\n')
|
||||
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(' Executed bllua-env.lua')
|
||||
|
||||
@@ -6,15 +6,33 @@ local _bllua_ts = ts
|
||||
|
||||
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
|
||||
-- Apply a function to each element in a list, building a new list from the returns
|
||||
local function map(t,f)
|
||||
local u = {}
|
||||
for i,v in ipairs(t) do
|
||||
u[i] = f(v)
|
||||
local function ipairsNilable(t)
|
||||
local maxk = 0
|
||||
for k, _ in pairs(t) do
|
||||
if k > maxk then maxk = k 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
|
||||
|
||||
-- Validation
|
||||
local function isValidFuncName(name)
|
||||
return type(name) == 'string' and name:find('^[a-zA-Z_][a-zA-Z0-9_]*$')
|
||||
end
|
||||
@@ -107,27 +125,34 @@ end
|
||||
|
||||
-- Type conversion from TS to Lua
|
||||
local fromTsForceTypes = {
|
||||
['boolean'] = tsBool,
|
||||
['object'] = function(val) toTsObject(val) end, -- wrap because toTsObject not defined yet
|
||||
['boolean'] = function(val) return tsBool(val) end,
|
||||
['object'] = function(val) return toTsObject(val) end, -- wrap because toTsObject not defined yet
|
||||
['string'] = function(val) return val end,
|
||||
}
|
||||
local function forceValFromTs(val, typ)
|
||||
return fromTsForceTypes[typ](val) or
|
||||
error('valFromTs: invalid force type '..typ, 4)
|
||||
local func = fromTsForceTypes[typ]
|
||||
if not func then error('valFromTs: invalid force type \'' .. typ .. '\'', 4) end
|
||||
return func(val)
|
||||
end
|
||||
local function vectorFromTs(val)
|
||||
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)}
|
||||
else return nil end
|
||||
if xS then
|
||||
return vector { tonumber(xS), tonumber(yS), tonumber(zS) }
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
local function boxFromTs(val)
|
||||
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]+)$')
|
||||
if x1S then return {
|
||||
if x1S then
|
||||
return {
|
||||
vector { tonumber(x1S), tonumber(y1S), tonumber(z1S) },
|
||||
vector { tonumber(x2S), tonumber(y2S), tonumber(z2S) } }
|
||||
else return nil end
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
local function multinumericFromTs(val)
|
||||
local tsNumPat = '%-?[0-9]+%.?[0-9]*e?[0-9]*'
|
||||
@@ -154,9 +179,11 @@ local function multinumericFromTs(val)
|
||||
end
|
||||
end
|
||||
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
|
||||
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
|
||||
@@ -177,31 +204,42 @@ local function valFromTs(val, name, name2) -- todo: ensure name and name2 are al
|
||||
-- vector, box, or axis->matrix
|
||||
local vec = multinumericFromTs(val)
|
||||
if vec then return vec end
|
||||
-- net string
|
||||
if val:sub(1, 1) == '\x01' then
|
||||
return _bllua_ts.call('getTaggedString', val)
|
||||
end
|
||||
-- string
|
||||
return val
|
||||
end
|
||||
local function arglistFromTs(name, argsS)
|
||||
local args = {}
|
||||
for i,arg in ipairs(argsS) do
|
||||
for i, arg in ipairsNilable(argsS) do
|
||||
args[i] = valFromTs(arg, name .. ':' .. i)
|
||||
end
|
||||
return args
|
||||
end
|
||||
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
|
||||
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 ~= 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
|
||||
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()
|
||||
|
||||
@@ -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 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
|
||||
-- sanity check to make sure objects that don't isObject are always marked as ._deleted
|
||||
-- can be removed later
|
||||
local function assertTsObjectExists(obj)
|
||||
local is = tsIsObject(obj._tsObjectId)
|
||||
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
|
||||
end
|
||||
function bl.isObject(obj)
|
||||
@@ -278,6 +320,7 @@ function bl.isObject(obj)
|
||||
error('bl.isObject: argument #1: expected torque object, number, or string', 2)
|
||||
end
|
||||
end
|
||||
|
||||
function bl.isFunction(name)
|
||||
return tsIsFunctionNsname(name)
|
||||
end
|
||||
@@ -292,9 +335,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 {
|
||||
@@ -309,15 +354,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
|
||||
@@ -329,6 +377,7 @@ function bl.class(cname, inhname)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function objectInheritedMetas(name)
|
||||
local inh = bl._objectUserMetas[name:lower()]
|
||||
return function()
|
||||
@@ -343,15 +392,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
|
||||
@@ -362,17 +414,20 @@ local tsObjectMeta = {
|
||||
tsIsFunctionNs(rawget(t, '_tsNamespace'), name) or
|
||||
tsIsFunctionNs(rawget(t, '_tsName'), name)
|
||||
then
|
||||
return function(t, ...)
|
||||
local args = {...}
|
||||
local argsS = arglistToTs(args)
|
||||
return valFromTs(
|
||||
_bllua_ts.callobj(rawget(t,'_tsObjectId'), name, unpack(argsS)),
|
||||
rawget(t,'_tsName') and rawget(t,'_tsName')..'::'..name,
|
||||
rawget(t,'_tsNamespace')..'::'..name)
|
||||
return function(t2, ...)
|
||||
if t2 == nil or type(t2) ~= 'table' or not t2._tsObjectId then
|
||||
error('ts object method: be sure to use :func() not .func()', 2)
|
||||
end
|
||||
local argsS = arglistToTs({ ... })
|
||||
local res =
|
||||
_bllua_ts.callobj(t2._tsObjectId, name, unpack(argsS))
|
||||
return valFromTs(res,
|
||||
t2._tsName and t2._tsName .. '::' .. name,
|
||||
t2._tsNamespace .. '::' .. name)
|
||||
end
|
||||
else
|
||||
return valFromTs(
|
||||
_bllua_ts.getfield(rawget(t,'_tsObjectId'), name),
|
||||
local res = _bllua_ts.getfield(rawget(t, '_tsObjectId'), name)
|
||||
return valFromTs(res,
|
||||
rawget(t, '_tsName') and rawget(t, '_tsName') .. '.' .. name,
|
||||
rawget(t, '_tsNamespace') .. '.' .. name)
|
||||
end
|
||||
@@ -383,9 +438,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
|
||||
@@ -394,11 +451,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
|
||||
@@ -412,9 +472,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()
|
||||
@@ -422,14 +484,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()
|
||||
@@ -437,7 +502,8 @@ local tsObjectMeta = {
|
||||
local obj = toTsObject(_bllua_ts.callobj(t._tsObjectId,
|
||||
'getObject', tostring(idx)))
|
||||
idx = idx + 1
|
||||
return idx-1, obj
|
||||
--return idx-1, obj
|
||||
return obj
|
||||
else
|
||||
return nil
|
||||
end
|
||||
@@ -446,23 +512,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
|
||||
@@ -484,7 +556,8 @@ 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
|
||||
else
|
||||
@@ -518,13 +591,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 tsIsObject(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 = {
|
||||
@@ -544,14 +619,13 @@ end
|
||||
local function safeNamespaceName(name)
|
||||
return tostring(name:gsub(':', '_'))
|
||||
end
|
||||
local nscallArgStr = '%a,%b,%c,%d,%e,%f,%g,%h'
|
||||
bl._cachedNamespaceCalls = {}
|
||||
local function tsNamespacedCallTfname(name)
|
||||
local tfname = bl._cachedNamespaceCalls[name]
|
||||
if not tfname then
|
||||
tfname = '_bllua_nscall_' .. safeNamespaceName(name)
|
||||
local tfcode = 'function '..tfname..'('..nscallArgStr..'){'..
|
||||
name..'('..nscallArgStr..');}'
|
||||
local tfcode = 'function ' .. tfname .. '(' .. tsArgsLocal .. '){' ..
|
||||
name .. '(' .. tsArgsLocal .. ');}'
|
||||
_bllua_ts.eval(tfcode)
|
||||
bl._cachedNamespaceCalls[name] = tfname
|
||||
end
|
||||
@@ -559,9 +633,9 @@ local function tsNamespacedCallTfname(name)
|
||||
end
|
||||
local function tsCallGen(name)
|
||||
return function(...)
|
||||
local args = {...}
|
||||
local argsS = arglistToTs(args)
|
||||
return valFromTs(_bllua_ts.call(name, unpack(argsS)), name)
|
||||
local argsS = arglistToTs({ ... })
|
||||
local res = _bllua_ts.call(name, unpack(argsS))
|
||||
return valFromTs(res, name)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -587,14 +661,16 @@ local tsMeta = {
|
||||
if not rest:find('::') and tsIsFunctionNs(ns, rest) then
|
||||
return tsCallGen(tsNamespacedCallTfname(name))
|
||||
else
|
||||
return valFromTs(_bllua_ts.getvar(name), name)
|
||||
local res = _bllua_ts.getvar(name)
|
||||
return valFromTs(res, name)
|
||||
end
|
||||
elseif tsIsFunction(name) then
|
||||
return tsCallGen(name)
|
||||
elseif tsIsObject(name) then
|
||||
return toTsObject(name)
|
||||
else
|
||||
return valFromTs(_bllua_ts.getvar(name), name)
|
||||
local res = _bllua_ts.getvar(name)
|
||||
return valFromTs(res, name)
|
||||
end
|
||||
end
|
||||
end,
|
||||
@@ -612,16 +688,22 @@ 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))
|
||||
local res = _bllua_ts.eval(code)
|
||||
return valFromTs(res)
|
||||
end
|
||||
|
||||
function bl.exec(file)
|
||||
return valFromTs(_bllua_ts.call('exec', file))
|
||||
local res = _bllua_ts.call('exec', file)
|
||||
return valFromTs(res)
|
||||
end
|
||||
|
||||
function bl.array(name, ...)
|
||||
local rest = { ... }
|
||||
return name .. table.concat(rest, '_')
|
||||
end
|
||||
|
||||
function bl.boolean(val)
|
||||
return val ~= nil and
|
||||
val ~= false and
|
||||
@@ -629,6 +711,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
|
||||
@@ -638,6 +721,7 @@ function bl.object(id)
|
||||
error('bl.object: id must be a ts object, number, or string', 2)
|
||||
end
|
||||
end
|
||||
|
||||
function bl.string(val)
|
||||
return valFromTsTostring(val)
|
||||
end
|
||||
@@ -645,26 +729,36 @@ end
|
||||
-- Lua calling from TS
|
||||
local luaLookup
|
||||
luaLookup = function(tbl, name, set, val)
|
||||
print('lookup', tbl, name, set, val)
|
||||
if name:find('%.') then
|
||||
local first, rest = name:match('^([^%.:]+)%.(.+)$')
|
||||
if not isValidFuncName(first) then
|
||||
error('luaLookup: invalid name \''..tostring(first)..'\'', 3) end
|
||||
if not tbl[first] then
|
||||
if set then tbl[first] = {}
|
||||
else return nil end
|
||||
error('luaLookup: invalid name \'' .. tostring(first) .. '\'', 3)
|
||||
end
|
||||
if tbl[first] == nil then
|
||||
if set then
|
||||
tbl[first] = {}
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
return luaLookup(tbl[first], rest, set, val)
|
||||
elseif name:find(':') then
|
||||
local first, rest = name:match('^([^%.:]+):(.*)$')
|
||||
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
|
||||
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
|
||||
error('luacall: no object named \''..rest..'\'', 3) end
|
||||
if not tbl[first][rest] then
|
||||
error('luacall: no method named \''..rest..'\'', 3) end
|
||||
error('luacall: no object named \'' .. rest .. '\'', 3)
|
||||
end
|
||||
if tbl[first][rest] == nil then
|
||||
error('luacall: no method named \'' .. rest .. '\'', 3)
|
||||
end
|
||||
return function(...)
|
||||
tbl[first][rest](tbl[first], ...)
|
||||
end
|
||||
@@ -681,19 +775,23 @@ function _bllua_call(fname, ...)
|
||||
local args = arglistFromTs(fname:lower(), { ... }) -- todo: separate lua from ts func names?
|
||||
local func = luaLookup(_G, fname)
|
||||
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))
|
||||
return valToTs(res)
|
||||
end
|
||||
|
||||
function _bllua_getvar(vname)
|
||||
return valToTs(luaLookup(_G, vname))
|
||||
end
|
||||
|
||||
function _bllua_setvar(vname, valS)
|
||||
return valToTs(luaLookup(_G, vname, true, valFromTs(valS, vname))) -- todo: separate lua from ts var names?
|
||||
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(time, function, args...)
|
||||
@@ -721,6 +819,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))
|
||||
@@ -739,19 +838,21 @@ 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..'){'..
|
||||
'_bllua_luacall(_bllua_process_cmd,"'..cmd..'",'..arglist..');}')
|
||||
_bllua_ts.eval('function ' .. cmd .. '(' .. tsArgsLocal .. '){' ..
|
||||
'_bllua_luacall(_bllua_process_cmd,"' .. cmd .. '",' .. tsArgsLocal .. ');}')
|
||||
end
|
||||
function bl.addServerCmd(name, func)
|
||||
name = name:lower()
|
||||
addCmd('servercmd' .. name, func)
|
||||
bl._forceType['servercmd' .. name .. ':1'] = 'object'
|
||||
end
|
||||
|
||||
function bl.addClientCmd(name, func)
|
||||
name = name:lower()
|
||||
addCmd('clientcmd' .. name, func)
|
||||
@@ -763,11 +864,14 @@ function bl.commandToServer(cmd, ...)
|
||||
_bllua_ts.call('addTaggedString', cmd),
|
||||
unpack(arglistToTs({ ... })))
|
||||
end
|
||||
function bl.commandToClient(cmd, ...)
|
||||
|
||||
function bl.commandToClient(client, cmd, ...)
|
||||
_bllua_ts.call('commandToClient',
|
||||
valToTs(client),
|
||||
_bllua_ts.call('addTaggedString', cmd),
|
||||
unpack(arglistToTs({ ... })))
|
||||
end
|
||||
|
||||
function bl.commandToAll(cmd, ...)
|
||||
_bllua_ts.call('commandToAll',
|
||||
_bllua_ts.call('addTaggedString', cmd),
|
||||
@@ -793,26 +897,25 @@ local function deactivatePackage(pkg)
|
||||
_bllua_ts.call('deactivatePackage', pkg)
|
||||
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 {}
|
||||
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
|
||||
_bllua_ts.setvar('_bllua_hook_abort', '1')
|
||||
_bllua_ts.setvar('_bllua_hook_return', valToTs(args._return))
|
||||
end
|
||||
for i=1,hookNargs do
|
||||
for i = 1, tsMaxArgs do
|
||||
_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, { ... })
|
||||
@@ -820,15 +923,17 @@ 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 ..
|
||||
'",'..hookArglistLocal..');') or ''
|
||||
local arglist = (hk.before and hookArglistGlobal or hookArglistLocal)
|
||||
'",' .. tsArgsLocal .. ');') or ''
|
||||
local arglist = (hk.before and tsArgsGlobal or tsArgsLocal)
|
||||
local parentCode =
|
||||
tsIsFunctionNsname(name) and -- only call parent if it exists
|
||||
(hk.before and
|
||||
@@ -841,21 +946,26 @@ local function updateHook(pkg, name, hk)
|
||||
arglist .. ');') or ''
|
||||
local code =
|
||||
'package ' .. pkg .. '{' ..
|
||||
'function '..name..'('..hookArglistLocal..'){'..
|
||||
'function ' .. name .. '(' .. tsArgsLocal .. '){' ..
|
||||
beforeCode .. parentCode .. afterCode ..
|
||||
'}' ..
|
||||
'};'
|
||||
print('bl.hook eval output: [[' .. code .. ']]')
|
||||
_bllua_ts.eval(code)
|
||||
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 {}
|
||||
@@ -864,16 +974,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
|
||||
if not isValidFuncNameNs(name) then
|
||||
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 #1: invalid package name \'' .. tostring(pkg) .. '\'', 2)
|
||||
end
|
||||
if name and not isValidFuncNameNs(name) then
|
||||
error('bl.unhook: argument #2: invalid function name \'' .. tostring(name) .. '\'', 2)
|
||||
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 bl._hooks[pkg] then
|
||||
@@ -897,12 +1011,16 @@ 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
|
||||
if tableEmpty(bl._hooks[pkg][name]) then
|
||||
bl._hooks[pkg][name] = nil
|
||||
end
|
||||
if tableEmpty(bl._hooks[pkg]) then
|
||||
bl._hooks[pkg] = nil
|
||||
deactivatePackage(pkg)
|
||||
updateHook(pkg, name, {})
|
||||
deactivatePackage(pkg)
|
||||
else
|
||||
updateHook(pkg, name, bl._hooks[pkg][name])
|
||||
end
|
||||
@@ -917,14 +1035,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
|
||||
@@ -934,7 +1054,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
|
||||
@@ -958,7 +1079,7 @@ function bl.raycast(start, stop, mask, ignores)
|
||||
local stopS = vecToTs(start)
|
||||
local maskS = maskToTs(mask)
|
||||
local ignoresS = {}
|
||||
for _,v in ipairs(ignores) do
|
||||
for _, v in ipairsNilable(ignores) do
|
||||
table.insert(ignoresS, objToTs(v))
|
||||
end
|
||||
|
||||
@@ -976,6 +1097,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
|
||||
@@ -992,10 +1114,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)
|
||||
|
||||
@@ -1007,7 +1131,7 @@ end
|
||||
local maxTsArgLen = 8192
|
||||
local function valsToString(vals)
|
||||
local strs = {}
|
||||
for i,v in ipairs(vals) do
|
||||
for i, v in ipairsNilable(vals) do
|
||||
local tstr = table.tostring(v)
|
||||
if #tstr > maxTsArgLen then
|
||||
tstr = tostring(v)
|
||||
@@ -1033,7 +1157,8 @@ local function createTsObj(keyword, class, name, inherit, props)
|
||||
if props then
|
||||
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
|
||||
end
|
||||
@@ -1044,7 +1169,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
|
||||
@@ -1069,13 +1195,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
|
||||
|
||||
@@ -93,11 +93,15 @@ local allowed_zip_dirs = tflip {
|
||||
local function io_open_absolute(fn, mode)
|
||||
-- if file exists, use original 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
|
||||
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'
|
||||
if not exist then return nil, err end
|
||||
|
||||
|
||||
@@ -13,8 +13,19 @@ function table.map(f, ...)
|
||||
local u = {}
|
||||
for k, _ in pairs(ts[1]) do
|
||||
local args = {}
|
||||
for j = 1, #ts do args[j] = ts[j][i] end
|
||||
u[i] = f(unpack(args))
|
||||
for j = 1, #ts do args[j] = ts[j][k] end
|
||||
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
|
||||
return u
|
||||
end
|
||||
@@ -30,6 +41,17 @@ function table.map_list(f, ...)
|
||||
return u
|
||||
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
|
||||
function table.swap(t)
|
||||
local u = {}
|
||||
@@ -193,7 +215,7 @@ valueToString = function(v, tabLevel, seen)
|
||||
return tostring(v)
|
||||
else
|
||||
--error('table.tostring: table contains a '..t..' value, cannot serialize')
|
||||
return 'nil --[[ cannot serialize ' .. tostring(v) .. ' ]]'
|
||||
return 'nil --[[ ' .. tostring(v) .. ' ]]'
|
||||
end
|
||||
end
|
||||
function table.tostring(t)
|
||||
|
||||
Reference in New Issue
Block a user