Merge remote-tracking branch 'upstream/master'

This commit is contained in:
2025-10-05 14:00:18 -04:00
12 changed files with 1144 additions and 964 deletions

View File

@@ -4,7 +4,10 @@ cd /d %~dp0
set buildargs=-Wall -Werror -m32 -shared -Isrc -Iinc/tsfuncs -Iinc/lua -lpsapi -L. -llua5.1 -static-libgcc -static-libstdc++
echo on
g++ src/bllua4.cpp %buildargs% -o BlockLua.dll && g++ -DBLLUA_UNSAFE src/bllua4.cpp %buildargs% -o BlockLua-Unsafe.dll
g++ src/bllua4.cpp %buildargs% -o BlockLua.dll
@rem g++ -DBLLUA_UNSAFE src/bllua4.cpp %buildargs% -o BlockLua-Unsafe.dll
@echo off
rem objdump -d BlockLua.dll > BlockLua.dll.dump.txt

Binary file not shown.

152
readme.md
View File

@@ -37,27 +37,45 @@ Lua scripting for Blockland
`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.
### Advanced
### Timing/Schedules
`sched = bl.schedule(timeMs, function, args...)` - Schedule a Lua function to be called later, similar to schedule in Torque
`sched:cancel()` - Cancel a previously scheduled timer
`hitObject, hisPos, hitNormal = bl.raycast(vector{startPosX,y,z}, vector{endPosX,y,z}, 'objtype'/{'objtypes',...}, ignoreObjects...?)` - Cast a ray in the world over objects of the specified type(s) (possibly excluding some objects), and return the object hit, the position of the hit, and the normal vector to the surface hit. See the Types section for a list of valid object types.
### Raycasts and Searches
`hitObject, hitPos, hitNormal = bl.raycast(vector{startPosX,y,z}, vector{endPosX,y,z}, 'objtype'/{'objtypes',...}, ignoreObjects...?)` - Cast a ray in the world over objects of the specified type(s) (possibly excluding some objects), and return the object hit, the position of the hit, and the normal vector to the surface hit. 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.
`bl.serverCmd('commandName', function(client, args...) code() end)` - Register a /-command on the server
`bl.clientCmd('commandName', function(args...) code() end)` - Register a client command on the client
### 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.commandToAll('commandName', args...)` - As the server, execute a client command on all clients
### Packages/Hooks
`bl.hook('packageName', 'functionName', 'before'/'after'/'override', function(args...) code() end)` - Hook a Torque function with a Lua function
`bl.unhook('packageName', 'functionName', 'before'/'after'/'override')` - Remove a previously defined hook
`bl.hook('packageName', 'functionName', 'before'/'after', function(args) yourCode 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.
`bl.unhook('packageName', 'functionName', 'before'/'after')` - Remove a previously defined hook
`bl.unhook('packageName', 'functionName')` - Remove any previously defined hooks on the function within the package
`bl.unhook('packageName')` - Remove any previously defined hooks within the package
### Classes and Types
`bl.bool(thing)` - Convert a Torque boolean (0 or 1) into a Lua boolean. Done automatically for all built-in functions that return bools.
`bl.object(thing)` - Convert a Torque object reference (object ID or name) into a Lua object. Done automatically for all built-in functions that return objects.
`bl.type('varName', 'type')` - Register the type of a Torque global variable, for conversion when accessing from Lua. Valid types are 'bool', 'object', and nil - all other conversion is automatic.
`bl.type('funcName', 'type')` - Register the return type of a Torque function, for conversion when calling from Lua. Valid types are 'bool', 'object', and nil - all other conversion is automatic. Already done for all built-in functions that return objects.
`bl.type('className::funcName', 'type')` - Register the return type of a Torque object method.
`bl.class('className')` - Register a Torque class to be used from Lua (Already done for all built-in classes)
`bl.class('className', 'parentClassName')` - Same as above, with inheritance
### Modules and Dependencies
`dofile('Add-Ons/Path/file.lua')` - Execute a Lua file. Relative paths (`./file.lua`) are allowed. `..` is not allowed.
`require('modulePath.moduleName')` - Load a Lua file or external library.
`require` replaces `.` with `/` in the path, and then searches for files in the following order:
- `./modulePath/moduleName.lua`
- `./modulePath/moduleName/init.lua`
- `modulePath/moduleName.lua` (Relative to game directory)
- `modulePath/moduleName/init.lua` (Relative to game directory)
- `modules/lualib/modulePath/moduleName.lua`
- `modules/lualib/modulePath/moduleName/init.lua`
- `modules/lualib/modulePath/moduleName.dll` - C libraries for Lua can be loaded
Like in standard Lua, modules loaded using `require` are only executed the first time `require` is called with that path (from anywhere). Subsequent calls simply return the result from the initial execution. To allow hot reloading, use `dofile`.
### File I/O
Lua's builtin file I/O is emulated, and is confined to the same directories as TorqueScript file I/O.
@@ -69,26 +87,84 @@ Relative paths (`./`) are allowed. `..` is not allowed.
Reading files from ZIPs is supported, with caveats. Null characters are not allowed, and \r\n becomes \n. Generally, text formats work, and binary formats don't.
When reading from outside ZIPs, binary files are fully supported.
### Modules and Dependencies
`dofile('Add-Ons/Path/file.lua')` - Execute a Lua file. Relative paths (`./file.lua`) are allowed. `..` is not allowed.
`require('modulePath.moduleName')` - Load a Lua file or or external library.
Require replaces `.` with `/` in the path, and then searches for files in the following order:
- `./modulePath/moduleName.lua`
- `./modulePath/moduleName/init.lua`
- `modulePath/moduleName.lua` (Relative to game directory)
- `modulePath/moduleName/init.lua` (Relative to game directory)
- `modules/lualib/modulePath/moduleName.lua`
- `modules/lualib/modulePath/moduleName/init.lua`
- `modules/lualib/modulePath/moduleName.dll`
Like in standard Lua, modules loaded using `require` are only executed the first time `require` is called with that path. Subsequent calls simply return the result from the initial execution. To allow hot reloading, use `dofile`.
### Object Creation
`bl.new('className')` - Create a new Torque object
`bl.new('className', {fieldName = value, ...})` - Create a new Torque object with the given fields
`bl.new('className objectName', fields?)` - Create a new named Torque object
`bl.new('className objectName:parentName', fields?)` - Create a new named Torque object with inheritance
`bl.datablock('datablockClassName datablockName', fields?)` - Create a new datablock
`bl.datablock('datablockClassName datablockName:parentDatablockName', fields?)` - Create a new datablock with inheritance
### Classes and Types
`bl.type('varName', 'type')` - Register the type of a Torque global variable, for conversion when accessing from Lua. Valid types are 'boolean', 'object', and nil (default is nil, which applies automatic conversion).
`bl.type('funcName', 'type')` - Register the return type of a Torque function, for conversion when calling from Lua. Valid types are 'bool', 'object', and nil - all other conversion is automatic. Already done for all default functions.
`bl.type('className::funcName', 'type')` - Register the return type of a Torque object method.
`bl.class('className')` - Register an existing Torque class to be used from Lua. Already done for all built-in classes.
`bl.class('className', 'parentClassName')` - Same as above, with inheritance
`bl.boolean(thing)` - Manually convert a Torque boolean (0 or 1) into a Lua boolean.
`bl.object(thing)` - Manually convert a Torque object reference (object ID or name) into a Lua object.
### Vectors
`vec = vector{x,y,z}` - Create a vector. Can have any number of elements
`vec1 + vec2` - Add
`vec1 - vec2` - Subtract
`vec * number` - Scale (x\*n, y\*n, z\*n)
`vec / number` - Scale (x/n, y/n, z/n)
`vec ^ number` - Exponentiate (x^n, y^n, z^n)
`vec1 * vec2` - Multiply elements piecewise (x1\*x2, y1\*y2, z1\*z2)
`vec1 / vec2` - Divide elements piecewise (x1/x2, y1/y2, z1/z2)
`vec1 ^ vec2` - Exponentiate elements piecewise (x1^x2, y1^y2, z1^z2)
`-vec` - Negate/invert
`vec1 == vec2` - Compare by value
`vec:length()` - Length
`vec:normalize()` - Preserve direction but scale so magnitude is 1
`vec:floor()` - Floor each element
`vec:ceil()` - Ceil each element
`vec:abs()` - Absolute value each element
`vec1:dot(vec2)` - Dot product
`vec1:cross(vec2)` - Cross product (Only defined for two vectors of length 3)
`vec:rotateZ(radians)` - Rotate counterclockwise about the Z (vertical) axis
`vec:rotateByAngleId(0/1/2/3)` - Rotate counterclockwise about the Z (vertical) axis in increments of 90 degrees
`vec:tsString()` - Convert to string usable by Torque. Done automatically when a vector is passed to Torque.
`vec1:distance(vec2)` - Distance between two points
`vec2 = vec:copy()` - Clone a vector so its elements can be modified without affecting the original. Usually not needed - the builtin vector functions never modify vectors in-place.
### Extended Standard Lua Library
`string[index]`
`string[{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.map_list(func, ...)`
`table.swap(tbl)`
`table.reverse(list)`
`table.islist(list)`
`table.append(list, ...)`
`table.join(...)`
`table.contains(tbl, val)`
`table.contains_list(list, val)`
`table.copy(tbl)`
`table.copy_list(list)`
`table.sortcopy(tbl, sortFunction?)`
`table.removevalue(tbl, val)`
`table.removevalue_list(tbl, val)`
`table.tostring(tbl)`
`for char in string.chars(str) do`
`string.escape(str, escapes={['\n']='\\n', etc. (C standard)})`
`string.unescape(str, escapeChar='\\', unescapes={['\\n']='\n', etc.})`
`io.readall(filename)`
`io.writeall(filename, str)`
`math.round(num)`
`math.mod(divisor, modulus)`
`math.clamp(num, min, max)`
## Type Conversion
When a TorqueScript function is called from Lua or vice-versa, the arguments and return value must be converted between the two languages' type systems.
TorqueScript stores no type information; all values in TorqueScript are strings. So it's necessary to make some inferences when converting values between the two languages.
### From TorqueScript to Lua
- Any numeric value becomes a Lua `number`, except as specified with `ts.type`, which may convert a value into a `boolean` or a Torque object container.
- 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`
- A string containing three numbers separated by spaces becomes a `vector`
- A string containing six numbers separated by spaces becomes a table of two vectors
@@ -106,17 +182,17 @@ TorqueScript stores no type information; all values in TorqueScript are strings.
All Lua code is sandboxed, and file access is confied 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-Unsafe.dll can be used in place of BlockLua.dll, to remove the sandboxing of Lua code. This allows Lua code to access any file and use any library, including ffi.
BlockLua-Unsafe.dll can be built and used in place of BlockLua.dll (see compile.bat), to remove the sandboxing of Lua code. This allows Lua code to access any file and use any library, including ffi.
Please do not publish add-ons that require unsafe mode.
## Other Reference
### List of Object Types
'all' - Any object
'brick' - Bricks with Ray-Casting enabled
'brickalways' - All bricks including those with Ray-Casting disabled
'player' - Players or bots
'item' - Items
'vehicle' - Vehicles
'projectile' - Projectiles
Other types: 'static', 'environment', 'terrain', 'water', 'trigger', 'marker', 'gamebase', 'shapebase', 'camera', 'staticshape', 'vehicleblocker', 'explosion', 'corpse', 'debris', 'physicalzone', 'staticts', 'staticrendered', 'damagableitem'
`'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'`

View File

@@ -28,8 +28,8 @@ INCLUDE_BIN(bll_fileLuaEnv , "lua-env.lua");
INCLUDE_BIN(bll_fileTsEnv , "ts-env.cs" );
INCLUDE_BIN(bll_fileLuaStd , "util/std.lua");
INCLUDE_BIN(bll_fileLuaVector , "util/vector.lua");
INCLUDE_BIN(bll_fileLuaLibts , "util/libts.lua");
INCLUDE_BIN(bll_fileTsLibtsSupport, "util/libts-support.cs");
INCLUDE_BIN(bll_fileLuaLibts , "util/libts-lua.lua");
INCLUDE_BIN(bll_fileTsLibts , "util/libts-ts.cs");
INCLUDE_BIN(bll_fileLuaLibbl , "util/libbl.lua" );
INCLUDE_BIN(bll_fileLuaLibblTypes , "util/libbl-types.lua");
INCLUDE_BIN(bll_fileTsLibblSupport, "util/libbl-support.cs");
@@ -68,7 +68,7 @@ bool init() {
BLL_LOAD_LUA(gL, bll_fileLuaStd);
BLL_LOAD_LUA(gL, bll_fileLuaVector);
BLL_LOAD_LUA(gL, bll_fileLuaLibts);
BlEval(bll_fileTsLibtsSupport);
BlEval(bll_fileTsLibts);
BLL_LOAD_LUA(gL, bll_fileLuaLibbl);
BLL_LOAD_LUA(gL, bll_fileLuaLibblTypes);
BlEval(bll_fileTsLibblSupport);

View File

@@ -1,26 +0,0 @@
// Built-in functions
// Eval'd after BLLua4 has loaded the Lua environment and API
// Public Lua library for TS
function luacall(%func, %a,%b,%c,%d,%e,%f,%g,%h,%i,%j,%k,%l,%m,%n,%o,%p) {
if($_bllua_active)
return _bllua_luacall(%func, %a,%b,%c,%d,%e,%f,%g,%h,%i,%j,%k,%l,%m,%n,%o,%p);
}
function luaexec(%fn) {
if($_bllua_active)
return _bllua_luacall("_bllua_exec", %fn);
}
function luaeval(%code) {
if($_bllua_active)
return _bllua_luacall("_bllua_eval", %code);
}
function luaget(%name) {
if($_bllua_active)
return _bllua_luacall("_bllua_getvar", %name);
}
function luaset(%name, %val) {
if($_bllua_active)
_bllua_luacall("_bllua_setvar", %name, %val);
}
echo(" Executed bllua-env.cs");

View File

@@ -10,7 +10,7 @@ package _bllua_smartEval {
if($_bllua_active) {
%text = getSubStr(%text, 1, strLen(%text));
echo("Lua ==> " @ %text);
luacall("_bllua_smarteval", %text);
_bllua_luacall("_bllua_smarteval", %text);
} else {
echo("Lua: not loaded");
}
@@ -36,7 +36,8 @@ package _bllua_objectDeletionHook {
// note: no parent function exists by default,
// and this is loaded before any addons
//parent::onRemove(%obj);
if($_bllua_active) luacall("_bllua_objectDeleted", %obj);
// assuming obj is an ID and never a name
if($_bllua_active) _bllua_luacall("_bllua_objectDeleted", %obj);
}
};
activatePackage(_bllua_objectDeletionHook);

File diff suppressed because it is too large Load Diff

View File

@@ -106,21 +106,29 @@ local function valToTs(val)
error('valToTs: could not convert '..type(val), 3)
end
end
local function convertValFromTs(val, typ)
if typ=='boolean' then
return tsBool(val)
elseif typ=='object' then
return toTsObject(val)
else
error('valFromTs: invalid force type '..typ, 4)
end
end
bl._forceType = bl._forceType or {}
local function valFromTs(val, name)
local function valFromTs(val, name, name2) -- todo: ensure name and name2 are already lowercase
if type(val)~='string' then
error('valFromTs: expected string, got '..type(val), 3) end
if name then
local nameL = name:lower()
if bl._forceType[nameL] then
local typ = bl._forceType[nameL]
if typ=='boolean' then
return tsBool(val)
elseif typ=='object' then
return toTsObject(val)
else
error('valFromTs: invalid force type '..typ, 3)
end
name = name:lower()
if bl._forceType[name] then
return convertValFromTs(val, bl._forceType[name])
end
end
if name2 then
name2 = name2:lower()
if bl._forceType[name2] then
return convertValFromTs(val, bl._forceType[name2])
end
end
-- '' -> nil
@@ -151,14 +159,35 @@ end
local function arglistToTs(args)
return map(args, valToTs)
end
function bl.type(name,typ)
if typ~='bool' and typ~='boolean' and typ~='object' and typ~=nil then
error('bl.type: can only set type to \'bool\' or \'object\' or nil', 2) end
if not isValidFuncNameNsArgn(name) then
error('bl.type: invalid function or variable name \''..name..'\'', 2) end
if typ=='bool' then typ='boolean' end
bl._forceType[name:lower()] = typ
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
return class,rest
end
local setForceType
setForceType = function(ftname, typ)
if typ~='boolean' and typ~='object' and typ~=nil then
error('bl.type: can only set type to \'boolean\', \'object\', or nil', 2) end
if not isValidFuncNameNsArgn(ftname) then
error('bl.type: invalid function or variable name \''..ftname..'\'', 2) end
ftname = ftname:lower()
bl._forceType[ftname] = typ
-- apply to child classes if present
local cname, rest = classFromForceTypeStr(ftname)
if cname then
local meta = bl._objectUserMetas[cname]
if meta then
for chcname,_ in pairs(meta._children) do
setForceType(chcname..rest, typ)
end
end
end
end
bl.type = setForceType
-- Value detection
@@ -199,31 +228,50 @@ function bl.isFunction(a1, a2)
end
-- Torque object pseudo-class
local tsClassMeta = {
__tostring = function(t)
return 'torqueClass:'..t._name..
(t._inherit and (':'..t._inherit._name) or '')
end,
}
bl._objectUserMetas = bl._objectUserMetas or {}
function bl.class(name, inherit)
if not ( type(name)=='string' and isValidFuncName(name) ) then
function bl.class(cname, inhname)
if not ( type(cname)=='string' and isValidFuncName(cname) ) then
error('bl.class: argument #1: invalid class name', 2) end
if not ( inherit==nil or (type(inherit)=='string' and isValidFuncName(inherit)) ) 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
name = name:lower()
cname = cname:lower()
local met = bl._objectUserMetas[name] or {}
bl._objectUserMetas[name] = met
met._name = name
local met = bl._objectUserMetas[cname] or {
_name = cname,
_inherit = nil,
_children = {},
}
bl._objectUserMetas[cname] = met
setmetatable(met, tsClassMeta)
if inherit then
inherit = inherit:lower()
if inhname then
inhname = inhname:lower()
local inh = bl._objectUserMetas[inherit]
if not inh then error('bl.class: argument #2: \''..inherit..'\' is not the '..
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
local inhI = bl._objectUserMetas[name]._inherit
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
met._inherit = inh
bl._objectUserMetas[name]._inherit = inh
-- apply inherited method and field types
for ftname, typ in pairs(bl._forceType) do
local cname2, rest = classFromForceTypeStr(ftname)
if cname2==inhname then
setForceType(cname..rest, typ)
end
end
end
end
local function objectInheritedMetas(name)
@@ -259,11 +307,15 @@ local tsObjectMeta = {
return function(t, ...)
local args = {...}
local argsS = arglistToTs(args)
return valFromTs(_bllua_ts.callobj(rawget(t,'_tsObjectId'), name, unpack(argsS)),
return valFromTs(
_bllua_ts.callobj(rawget(t,'_tsObjectId'), name, unpack(argsS)),
rawget(t,'_tsName') and rawget(t,'_tsName')..'::'..name,
rawget(t,'_tsNamespace')..'::'..name)
end
else
return valFromTs(_bllua_ts.getfield(rawget(t,'_tsObjectId'), name),
return valFromTs(
_bllua_ts.getfield(rawget(t,'_tsObjectId'), name),
rawget(t,'_tsName') and rawget(t,'_tsName')..'.'..name,
rawget(t,'_tsNamespace')..'.'..name)
end
end
@@ -418,7 +470,7 @@ toTsObject = function(idiS)
_tsObjectId = _bllua_ts.callobj(idiS, 'getId' ),
_tsName = _bllua_ts.callobj(idiS, 'getName' ),
_tsNamespace = className,
_tsClassName = className:lower()
_tsClassName = className:lower(),
}
setmetatable(obj, tsObjectMeta)
@@ -438,7 +490,7 @@ local tsMeta = {
__index = function(t, name)
if getmetatable(t)[name] then
return getmetatable(t)[name]
elseif bl._objectUserMetas[name:lower()] then
elseif type(name)=='string' and bl._objectUserMetas[name:lower()] then
return bl._objectUserMetas[name:lower()]
else
if type(name)=='number' then
@@ -446,7 +498,7 @@ local tsMeta = {
elseif name:find('::') then
local ns, rest = name:match('^([^:]+)::(.+)$')
if not ns then error('ts index: invalid name \''..name..'\'', 2) end
if not rest:find('::') and tsIsFunction(ns, rest) then -- tsIsFunction is only defined with one argument
if not rest:find('::') and tsIsFunctionNs(ns, rest) then
error('ts index: can\'t call a namespaced function from lua', 2)
else
return valFromTs(_bllua_ts.getvar(name), name)
@@ -469,7 +521,7 @@ local tsMeta = {
-- bl.set(name, value)
-- Used to set global variables
function bl.set(name, val)
_bllua_ts.call('_bllua_set_var', name, valToTs(val))
_bllua_ts.setvar(name, valToTs(val))
end
-- Utility functions
@@ -479,19 +531,19 @@ function bl.call(func, ...)
return _bllua_ts.call(func, unpack(argsS))
end
function bl.eval(code)
return valFromTs(_bllua_ts.call('eval', code))
return valFromTs(_bllua_ts.eval(code))
end
function bl.exec(file)
return valFromTs(_bllua_ts.call('exec', file))
end
function bl.tobool(val)
function bl.boolean(val)
return val~=nil and
val~=false and
--val~='' and
--val~='0' and
val~=0
end
function bl.toobject(id)
function bl.object(id)
if type(id)=='table' and id._tsObjectId then
return id
elseif type(id)=='string' or type(id)=='number' then
@@ -504,8 +556,13 @@ function bl.array(name, ...)
local rest = {...}
return name..table.concat(rest, '_')
end
function _bllua_call(name, ...)
-- todo: call ts->lua using this instead of directly
function _bllua_call(fnameS, ...)
local args = arglistFromTs(fnameS:lower(), {...})
if not _G[fnameS] then
error('luacall: no global lua function named \''..fnameS..'\'') end
-- todo: library fields and object methods
local res = _G[fnameS](args)
return valToTs(res)
end
-- bl.schedule: Use TS's schedule function to schedule lua calls
@@ -524,7 +581,7 @@ function bl.schedule(time, cb, ...)
bl._scheduleNextId = bl._scheduleNextId+1
local args = {...}
local handle = tonumber(_bllua_ts.call('schedule',
time, 0, 'luacall', '_bllua_schedule_callback', id))
time, 0, '_bllua_luacall', '_bllua_schedule_callback', id))
local sch = {
callback = cb,
args = args,
@@ -534,38 +591,58 @@ function bl.schedule(time, cb, ...)
bl._scheduleTable[id] = sch
return sch
end
function _bllua_schedule_callback(id)
id = tonumber(id)
if id == nil then
error('_ts_schedule_callback: invalid id')
end
function _bllua_schedule_callback(idS)
local id = tonumber(idS) or
error('_bllua_schedule_callback: invalid id: '..tostring(idS))
local sch = bl._scheduleTable[id]
if not sch then error('_ts_schedule_callback: no schedule with id '..id) end
if not sch then error('_bllua_schedule_callback: no schedule with id '..id) end
bl._scheduleTable[id] = nil
sch.callback(unpack(sch.args))
end
-- serverCmd and clientCmd
-- bl.serverCmd('suicide', function(client) client.player:kill() end)
bl._cmds = bl._cmds or {}
function _bllua_process_cmd(cmdS, clientS, ...)
local client = toTsObject(clientS)
local argsS = {...}
local args = arglistFromTs(cmdS, argsS)
local func = bl._cmds[cmdS]
function _bllua_process_cmd(cmdS, ...)
local cmd = cmdS:lower()
local args = arglistFromTs(cmd, {...})
local func = bl._cmds[cmd]
if not func then error('_bllua_process_cmd: no cmd named \''..cmd..'\'') end
pcall(func, client, unpack(args))
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
bl._servercmds[cmd] = func
local arglist = '%a,%b,%c,%d,%e,%f,%g,%h'
_bllua_ts.eval('function '..cmd..'(%cl,'..arglist..'){'..
'luacall(_bllua_process_cmd,"'..cmd..'",%cl,'..arglist..');}')
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..');}')
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)
end
-- commandToServer and commandToClient
function bl.commandToServer(cmd, ...)
_bllua_ts.call('commandToServer',
_bllua_ts.call('addTaggedString', cmd),
unpack(arglistToTs({...})))
end
function bl.commandToClient(cmd, ...)
_bllua_ts.call('commandToClient',
_bllua_ts.call('addTaggedString', cmd),
unpack(arglistToTs({...})))
end
function bl.commandToAll(cmd, ...)
_bllua_ts.call('commandToAll',
_bllua_ts.call('addTaggedString', cmd),
unpack(arglistToTs({...})))
end
function bl.serverCmd(name, func) addCmd('serverCmd'..name, func) end
function bl.clientCmd(name, func) addCmd('clientCmd'..name, func) end
-- Hooks (using TS packages)
local function isPackageActive(pkg)
@@ -586,42 +663,67 @@ 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(pkgS, nameS, timeS, ...)
local argsS = {...}
local args = arglistFromTs(nameS, argsS)
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][timeS]
bl._hooks[pkgS][nameS].before
if not func then
error('_bllua_process_hook: no hook for '..pkgS..':'..nameS..':'..timeS) end
pcall(func, args)
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
_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, {...})
args._return = valFromTs(resultS, nameS)
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
func(args) --pcall(func, args)
return valToTs(args._return)
end
local function updateHook(pkg, name, hk)
local arglist = '%a,%b,%c,%d,%e,%f,%g,%h'
local beforeCode = hk.before and
('luacall("_bllua_process_hook", "'..pkg..'", "'..name..
'", "before", '..arglist..');') or ''
local parentCode = hk.override and
('luacall("_bllua_process_hook", "'..pkg..'", "'..name..
'", "override", '..arglist..');') or
(tsIsFunctionNsname(name) and
('parent::'..name:match('[^:]+$')..'('..arglist..');') or '')
('_bllua_luacall("_bllua_process_hook_before", "'..pkg..'","'..name..
'",'..hookArglistLocal..');') or ''
local arglist = (hk.before and hookArglistGlobal or hookArglistLocal)
local parentCode =
tsIsFunctionNsname(name) and -- only call parent if it exists
(hk.before and
'if($_bllua_hook_abort)return $_bllua_hook_return; else ' or '')..
((hk.after and '%result=' or 'return ')..
'parent::'..name:match('[^:]+$')..
'('..arglist..');') or ''
local afterCode = hk.after and
('luacall("_bllua_process_hook", "'..pkg..'", "'..name..
'", "after", '..arglist..');') or ''
bl.eval('package '..pkg..'{function '..name..'('..arglist..'){'..
beforeCode..parentCode..afterCode..'}};')
('return _bllua_luacall("_bllua_process_hook_after","'..pkg..'","'..name..'",%result,'..
arglist..');') or ''
local code =
'package '..pkg..'{'..
'function '..name..'('..hookArglistLocal..'){'..
beforeCode..parentCode..afterCode..
'}'..
'};'
_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
if not isValidFuncNameNs(name) then
error('bl.hook: argument #2: invalid function name \''..tostring(name)..'\'', 2) end
if time~='before' and time~='after' and time~='override' then
error('bl.hook: argument #3: time must be one of '..
'\'before\' \'after\' \'override\'', 2) end
if time~='before' and time~='after' then
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
@@ -632,14 +734,16 @@ 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' and time~='override' then
error('bl.unhook: argument #3: time must be one of '..
'\'before\' \'after\' \'override\'', 2) end
if 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
@@ -662,15 +766,16 @@ function bl.unhook(pkg, name, time)
end
updateHook(pkg, name, {})
else
if time~='before' and time~='after' and time~='override' then
error('bl.unhook: argument #3: time must be nil or one of '..
'\'before\' \'after\' \'override\'', 2) end
if time~='before' and time~='after' then
error('bl.unhook: argument #3: time must be nil, \'before\', or \'after\'', 2) end
bl._hooks[pkg][name][time] = nil
if table.empty(bl._hooks[pkg][name]) and table.empty(bl._hooks[pkg]) then
if tableEmpty(bl._hooks[pkg][name]) and tableEmpty(bl._hooks[pkg]) then
bl._hooks[pkg] = nil
deactivatePackage(pkg)
updateHook(pkg, name, {})
else
updateHook(pkg, name, bl._hooks[pkg][name])
end
updateHook(pkg, name, bl._hooks[pkg][name])
end
else
--error('bl.unhook: no hooks registered for function \''..name..
@@ -769,10 +874,15 @@ function bl.radiusSearch(pos, radius, mask)
end
-- Print/Talk/Echo
local maxTsArgLen = 8192
local function valsToString(vals)
local strs = {}
for i,v in ipairs(vals) do
strs[i] = table.tostring(v)
local tstr = table.tostring(v)
if #tstr>maxTsArgLen then
tstr = tostring(v)
end
strs[i] = tstr
end
return table.concat(strs, ' ')
end
@@ -787,11 +897,12 @@ bl.talk = function(...)
_bllua_ts.call('talk', str)
end
-- bl.new and bl.datablock
local function createTsObj(keyword, class, name, inherit, props)
local propsT = {}
for k,v in pairs(props) do
if not isValidFuncName(k) then
error('bl.new/datablock: invalid property name \''..k..'\'') end
error('bl.new/bl.datablock: invalid property name \''..k..'\'') end
table.insert(propsT, k..'="'..valToTs(v)..'";')
end
@@ -801,7 +912,7 @@ local function createTsObj(keyword, class, name, inherit, props)
table.concat(propsT)..'};')
local obj = toTsObject(objS)
if not obj then
error('bl.new/datablock: failed to create object', 3) end
error('bl.new/bl.datablock: failed to create object', 3) end
return obj
end
@@ -824,7 +935,7 @@ local function parseTsDecl(decl)
isValidFuncName(class) and
(name==nil or isValidFuncName(name)) and
(inherit==nil or isValidFuncName(inherit)) ) then
error('bl.new/datablock: invalid decl \''..decl..'\'\n'..
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
return class, name, inherit
@@ -835,6 +946,7 @@ function bl.new(decl, 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
return createTsObj('datablock', class, name, inherit, props)
end

View File

@@ -121,13 +121,13 @@ function io.open(fn, mode, errn)
local relfn = curfn and fn:find('^%./') and
curfn:gsub('[^/]+$', '')..fn:gsub('^%./', '')
if relfn then
local fi, err = io_open_absolute(relfn, mode, errn+1) -- defined with 2 args? function io_open_absolute(fn, mode)
local fi, err = io_open_absolute(relfn, mode)
return fi, err, relfn
else
return nil, 'Invalid path', fn
end
else
local fi, err = io_open_absolute(fn, mode, errn+1) -- defined with 2 args but passed 3 in.
local fi, err = io_open_absolute(fn, mode)
return fi, err, fn
end
end
@@ -224,4 +224,8 @@ function _bllua_smarteval(code)
end
end
_bllua_ts.call('echo', ' Executed libts.lua')
function ts.setvar(name, val)
_bllua_ts.call('_bllua_set_var', name, val)
end
_bllua_ts.call('echo', ' Executed libts-lua.lua')

View File

@@ -49,4 +49,26 @@ function _bllua_set_var(%name, %val) {
return "";
}
echo(" Executed libts-support.cs");
// Public Lua library for TS
function luacall(%func, %a,%b,%c,%d,%e,%f,%g,%h,%i,%j,%k,%l,%m,%n,%o,%p) {
if($_bllua_active)
return _bllua_luacall("_bllua_call", %func, %a,%b,%c,%d,%e,%f,%g,%h,%i,%j,%k,%l,%m,%n,%o,%p);
}
function luaexec(%fn) {
if($_bllua_active)
return _bllua_luacall("_bllua_exec", %fn);
}
function luaeval(%code) {
if($_bllua_active)
return _bllua_luacall("_bllua_eval", %code);
}
function luaget(%name) {
if($_bllua_active)
return _bllua_luacall("_bllua_getvar", %name);
}
function luaset(%name, %val) {
if($_bllua_active)
_bllua_luacall("_bllua_setvar", %name, %val);
}
echo(" Executed libts-ts.cs");

View File

@@ -5,8 +5,7 @@
-- Table / List
-- Whether a table contains no keys
function table.empty(t)
for _,_ in pairs(t) do return false end
return true
return next(t)~=nil
end
-- Apply a function to each key in a table
function table.map(f, ...)
@@ -41,18 +40,6 @@ function table.reverse(l)
for i=1,#l do m[#l-i+1] = l[i] end
return m
end
-- Convert i->v to v->true
function table.values(l)
local u = {}
for _,v in ipairs(l) do u[v] = true end
return u
end
-- Make a list of keys
function table.keys(t)
local u = {}
for k,_ in pairs(t) do table.insert(u, k) end
return u
end
-- Whether a table is a list/array (has only monotonic integer keys)
function table.islist(t)
local n = 0
@@ -244,8 +231,8 @@ function string.bytes(s)
end
-- Trim leading and trailing whitespace
function string.trim(s, ws)
ws = ws or '[ \t\r\t]'
return s:gsub('^'..ws..'+', ''):gsub(ws..'+$', '')..''
ws = ws or ' \t\r\n'
return s:gsub('^['..ws..']+', ''):gsub('['..ws..']+$', '')..''
end
-- String slicing and searching using [] operator
local str_meta = getmetatable('')
@@ -325,7 +312,7 @@ end
io = io or {}
-- Read entire file at once, return nil,err if access failed
function io.readfile(filename)
function io.readall(filename)
local fi,err = io.open(filename, 'rb')
if not fi then return nil,err end
local s = fi:read("*a")
@@ -333,7 +320,7 @@ function io.readfile(filename)
return s
end
-- Write data to file all at once, return true if success / false,err if failure
function io.writefile(filename, data)
function io.writeall(filename, data)
local fi,err = io.open(filename, 'wb')
if not fi then return false,err end
fi:write(data)

View File

@@ -127,7 +127,7 @@ local vector_meta = {
for i = 1, len do
table.insert(st, tostring(v1[i]))
end
return '{ '..table.concat(st, ', ')..' }'
return 'vector{ '..table.concat(st, ', ')..' }'
end,
unpack = function(v1) return unpack(v1) end,
floor = vector_opn0n('floor', function(x1) return math.floor(x1) end),
@@ -162,10 +162,10 @@ local vector_meta = {
else error('vector rotateByAngleId: invalid rotation '..r, 2) end
return v2
end,
rotate2d = function(v, r)
rotateZ = function(v, r)
--vector_check(v, 2, 'rotate2d')
if type(r)~='number' then
error('vector rotate2d: invalid rotation '..tostring(r), 2) end
error('vector rotateZ: invalid rotation '..tostring(r), 2) end
local len = math.sqrt(v[1]^2 + v[2]^2)
local ang = math.atan2(v[2], v[1]) + r
local v2 = vector_new{ math.cos(ang)*len, math.sin(ang)*len }