improve readme, improve std lib

This commit is contained in:
Redo
2025-10-04 01:44:25 -05:00
parent eaafb42317
commit 1a4c7bfefc
6 changed files with 129 additions and 75 deletions

Binary file not shown.

View File

@@ -6,11 +6,11 @@ set buildargs=-Wall -Werror -m32 -shared -Isrc -Iinc/tsfuncs -Iinc/lua -lpsapi -
echo on echo on
g++ src/bllua4.cpp %buildargs% -o BlockLua.dll g++ src/bllua4.cpp %buildargs% -o BlockLua.dll
@rem && g++ -DBLLUA_UNSAFE src/bllua4.cpp %buildargs% -o BlockLua-Unsafe.dll @rem g++ -DBLLUA_UNSAFE src/bllua4.cpp %buildargs% -o BlockLua-Unsafe.dll
@rem objdump -d BlockLua.dll > BlockLua.dll.dump.txt
@rem objdump -d BlockLua-Unsafe.dll > BlockLua-Unsafe.dll.dump.txt
@echo off @echo off
rem objdump -d BlockLua.dll > BlockLua.dll.dump.txt
rem objdump -d BlockLua-Unsafe.dll > BlockLua-Unsafe.dll.dump.txt
pause pause

145
readme.md
View File

@@ -37,39 +37,45 @@ Lua scripting for Blockland
`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 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 = 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 `sched:cancel()` - Cancel a previously scheduled timer
### Raycasts and Searches ### 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. `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.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.
### Server-Client Communication ### Server-Client Communication
`bl.addServerCmd('commandName', function(client, args...) code() end)` - Register a /-command on the server `bl.addServerCmd('commandName', function(client, args...) yourCode end)` - Register a /command on the server
`bl.addClientCmd('commandName', function(args...) code() end)` - Register a client command on the client `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.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.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 `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) code() end)` - Hook a Torque function with a Lua function. `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. `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.
`bl.unhook('packageName', 'functionName', 'before'/'after')` - Remove a previously defined hook `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', 'functionName')` - Remove any previously defined hooks on the function within the package
`bl.unhook('packageName')` - Remove any previously defined hooks within the package `bl.unhook('packageName')` - Remove any previously defined hooks within the package
### Classes and Types ### Modules and Dependencies
`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). `dofile('Add-Ons/Path/file.lua')` - Execute a Lua file. Relative paths (`./file.lua`) are allowed. `..` is not allowed.
`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. `require('modulePath.moduleName')` - Load a Lua file or external library.
`bl.class('className')` - Register a Torque class to be used from Lua (Already done for all built-in classes) `require` replaces `.` with `/` in the path, and then searches for files in the following order:
`bl.class('className', 'parentClassName')` - Same as above, with inheritance - `./modulePath/moduleName.lua`
`bl.bool(thing)` - Manually convert a Torque boolean (0 or 1) into a Lua boolean. - `./modulePath/moduleName/init.lua`
`bl.object(thing)` - Manually convert a Torque object reference (object ID or name) into a Lua object. - `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 ### File I/O
Lua's builtin file I/O is emulated, and is confined to the same directories as TorqueScript file I/O. Lua's builtin file I/O is emulated, and is confined to the same directories as TorqueScript file I/O.
@@ -81,27 +87,78 @@ 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. 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. When reading from outside ZIPs, binary files are fully supported.
### Modules and Dependencies ### Object Creation
`dofile('Add-Ons/Path/file.lua')` - Execute a Lua file. Relative paths (`./file.lua`) are allowed. `..` is not allowed. `bl.new('className')` - Create a new Torque object
`bl.new('className', {fieldName = value, ...})` - Create a new Torque object with the given fields
`require('modulePath.moduleName')` - Load a Lua file or or external library. `bl.new('className objectName', fields?)` - Create a new named Torque object
Require replaces `.` with `/` in the path, and then searches for files in the following order: `bl.new('className objectName:parentName', fields?)` - Create a new named Torque object with inheritance
- `./modulePath/moduleName.lua` `bl.datablock('datablockClassName datablockName', fields?)` - Create a new datablock
- `./modulePath/moduleName/init.lua` `bl.datablock('datablockClassName datablockName:parentDatablockName', fields?)` - Create a new datablock with inheritance
- `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`.
### TBD ### Classes and Types
- bl.class `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.new `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.datablock `bl.type('className::funcName', 'type')` - Register the return type of a Torque object method.
- vector `bl.class('className')` - Register an existing Torque class to be used from Lua. Already done for all built-in classes.
- Extended standard library functions `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 ## 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. 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.
@@ -125,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. 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. 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-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. Please do not publish add-ons that require unsafe mode.
## Other Reference ## Other Reference
### List of Object Types ### List of Object Types
'all' - Any object `'all'` - Any object
'brick' - Bricks with Ray-Casting enabled `'player'` - Players or bots
'brickalways' - All bricks including those with Ray-Casting disabled `'item'` - Items
'player' - Players or bots `'vehicle'` - Vehicles
'item' - Items `'projectile'` - Projectiles
'vehicle' - Vehicles `'brick'` - Bricks with raycasting enabled
'projectile' - Projectiles `'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' Other types: `'static'`, `'environment'`, `'terrain'`, `'water'`, `'trigger'`, `'marker'`, `'gamebase'`, `'shapebase'`, `'camera'`, `'staticshape'`, `'vehicleblocker'`, `'explosion'`, `'corpse'`, `'debris'`, `'physicalzone'`, `'staticts'`, `'staticrendered'`, `'damagableitem'`

View File

@@ -536,7 +536,7 @@ end
function bl.exec(file) function bl.exec(file)
return valFromTs(_bllua_ts.call('exec', file)) return valFromTs(_bllua_ts.call('exec', file))
end end
function bl.tobool(val) function bl.boolean(val)
return val~=nil and return val~=nil and
val~=false and val~=false and
--val~='' and --val~='' and
@@ -734,6 +734,9 @@ 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)
return next(t)~=nil
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) end
@@ -766,7 +769,7 @@ function bl.unhook(pkg, name, time)
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 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 bl._hooks[pkg] = nil
deactivatePackage(pkg) deactivatePackage(pkg)
updateHook(pkg, name, {}) updateHook(pkg, name, {})
@@ -871,10 +874,15 @@ function bl.radiusSearch(pos, radius, mask)
end end
-- Print/Talk/Echo -- Print/Talk/Echo
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 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 end
return table.concat(strs, ' ') return table.concat(strs, ' ')
end end
@@ -889,11 +897,12 @@ bl.talk = function(...)
_bllua_ts.call('talk', str) _bllua_ts.call('talk', str)
end end
-- bl.new and bl.datablock
local function createTsObj(keyword, class, name, inherit, props) local function createTsObj(keyword, class, name, inherit, props)
local propsT = {} local propsT = {}
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/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
@@ -903,7 +912,7 @@ 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/datablock: failed to create object', 3) end error('bl.new/bl.datablock: failed to create object', 3) end
return obj return obj
end end
@@ -926,7 +935,7 @@ local function parseTsDecl(decl)
isValidFuncName(class) and isValidFuncName(class) and
(name==nil or isValidFuncName(name)) and (name==nil or isValidFuncName(name)) and
(inherit==nil or isValidFuncName(inherit)) ) then (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\', '.. '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
@@ -937,6 +946,7 @@ function bl.new(decl, 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
return createTsObj('datablock', class, name, inherit, props) return createTsObj('datablock', class, name, inherit, props)
end end

View File

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

View File

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