Compare commits

..

10 Commits

18 changed files with 1180 additions and 1223 deletions

9
.editorconfig Normal file
View File

@@ -0,0 +1,9 @@
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

3
.gitignore vendored
View File

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

12
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,12 @@
{
"Lua.diagnostics.globals": [
"_bllua_ts",
"_bllua_requiresecure",
"_bllua_on_unload"
],
"Lua.runtime.version": "Lua 5.1",
"Lua.diagnostics.disable": [
"lowercase-global",
"undefined-global"
]
}

Binary file not shown.

13
compile.bat Normal file
View File

@@ -0,0 +1,13 @@
@echo off
cd /d %~dp0
set "PATH=C:\msys64\mingw32\bin;%PATH%"
if not exist build mkdir build
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 build\BlockLua.dll
g++ -DBLLUA_UNSAFE src/bllua4.cpp %buildargs% -o build\BlockLua-Unsafe.dll
@echo off

52
compiling.md Normal file
View File

@@ -0,0 +1,52 @@
## Compiling on Windows with MSYS2 (32-bit)
Follow these steps to build `BlockLua.dll` for 32-bit Windows using MSYS2's MinGW-w64 i686 toolchain.
### 1) Install MSYS2
- Download and install MSYS2 from `https://www.msys2.org/`.
- After installing, open the "MSYS2 MSYS" terminal once and update the package database if prompted.
### 2) Install required packages (i686 / 32-bit)
Run this in the "MSYS2 MSYS" or "MSYS2 MinGW 32-bit" terminal:
```bash
pacman -Sy --needed mingw-w64-i686-toolchain mingw-w64-i686-binutils mingw-w64-i686-lua51
```
What these packages are for:
- mingw-w64-i686-toolchain: 32-bit C/C++ compiler suite (g++, libstdc++, runtime libs) to build Windows binaries.
- mingw-w64-i686-binutils: Linker and binary utilities (ld, as, objdump) used by the compiler and for optional inspection.
- mingw-w64-i686-lua51: Lua 5.1 development files for 32-bit (import library). We use its import library to link; at runtime you can use the provided `lua5.1.dll`.
### 3) Build (PowerShell, recommended)
- Open PowerShell in the repo root.
- Run the script:
```powershell
compile.bat
```
What the script does:
- Temporarily prepends `C:\\msys64\\mingw32\\bin` to PATH so the 32-bit toolchain is used.
- Compiles the project with `-m32` and links against `lua5.1`.
- Produces `build\BlockLua.dll` and `build\BlockLua-Unsafe.dll`.
### 4) Optional: Verify 32-bit output
If you installed binutils, you can check the architecture:
```powershell
objdump -f build\BlockLua.dll | Select-String i386
```
You should see `architecture: i386` in the output.
### Notes
- Ensure you installed the i686 (32-bit) variants of the packages; x86_64 packages wont work for a 32-bit build.
- If the linker cannot find `-llua5.1`, confirm `mingw-w64-i686-lua51` is installed and you are using the `mingw32` toolchain (not `x86_64`).

View File

@@ -14,10 +14,10 @@ Lua scripting for Blockland
### From TorqueScript ### From TorqueScript
`'print('hello world')` - Execute Lua in the console by prepending a `'` (single quote) `'print('hello world')` - Execute Lua in the console by prepending a `'` (single quote)
`luaeval("code");` - Execute Lua code `luaeval("code");` - Execute Lua code
`luacall("funcName", %args...);` - Call a Lua function (supports indexing tables and object methods) `luacall("funcName", %args...);` - Call a Lua global function
`luaexec("fileName");` - Execute a Lua file. Path rules are the same as when executing .cs files, relative paths are allowed. `luaexec("fileName");` - Execute a Lua file. Path rules are the same as executing .cs files.
`luaget("varName");` - Read a Lua global variable (supports indexing tables) `luaget("varName");` - Read a Lua global variable
`luaset("varName", %value);` - Write a Lua global variable (supports indexing tables) `luaset("varName", %value);` - Write a Lua global variable
### From Lua ### From Lua
`bl.eval('code')` - Eval TorqueScript code `bl.eval('code')` - Eval TorqueScript code
@@ -35,9 +35,7 @@ Lua scripting for Blockland
`object.key = value` - Associate Lua data with a Torque object `object.key = value` - Associate Lua data with a Torque object
`object:method(args)` - Call a Torque object method `object:method(args)` - Call a Torque object method
`object[index]` - Access a member of a Torque set or group `object[index]` - Access a member of a Torque set or group
`for 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.
`bl.isObject(object, objectID, or 'objectName')` - Check if an object exists
`object:exists()` - Check if an object exists
### Timing/Schedules ### 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
@@ -48,25 +46,15 @@ Lua scripting for Blockland
`for object in bl.boxSearch(vector{centerX,y,z}, vector{sizeX,y,z}, 'objtype'/{'objtypes',...}) do` - Find all objects in the world of the specified type(s) whose bounding box overlaps with the specified box. See the Types section for a list of valid object types. `for object in bl.boxSearch(vector{centerX,y,z}, vector{sizeX,y,z}, 'objtype'/{'objtypes',...}) do` - Find all objects in the world of the specified type(s) whose bounding box overlaps with the specified box. See the Types section for a list of valid object types.
`for object in bl.radiusSearch(vector{centerX,y,z}, radius, 'objtype'/{'objtypes',...}) do` - Find all objects of the specified type(s) whose bounding box overlaps with the specified sphere. See the Types section for a list of valid object types. `for object in bl.radiusSearch(vector{centerX,y,z}, radius, 'objtype'/{'objtypes',...}) do` - Find all objects of the specified type(s) whose bounding box overlaps with the specified sphere. See the Types section for a list of valid object types.
### List of Object Classes (for raycasts and searches)
`'all'` - Any object
`'player'` - Players or bots
`'item'` - Items
`'vehicle'` - Vehicles
`'projectile'` - Projectiles
`'brick'` - Bricks with raycasting enabled
`'brickalways'` - All bricks including those with raycasting disabled
Other types: `'static'`, `'environment'`, `'terrain'`, `'water'`, `'trigger'`, `'marker'`, `'gamebase'`, `'shapebase'`, `'camera'`, `'staticshape'`, `'vehicleblocker'`, `'explosion'`, `'corpse'`, `'debris'`, `'physicalzone'`, `'staticts'`, `'staticrendered'`, `'damagableitem'`
### Server-Client Communication ### Server-Client Communication
`bl.addServerCmd('commandName', function(client, args...) ... 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...) ... 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...)` - As a client, execute a server command `bl.commandToServer('commandName', args...)` - Execute a server command as a client
`bl.commandToClient(client, '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) ... 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.
@@ -113,11 +101,10 @@ When reading from outside ZIPs, binary files are fully supported.
`bl.type('className::funcName', 'type')` - Register the return type of a Torque object method. `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')` - 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.class('className', 'parentClassName')` - Same as above, with inheritance
`bl.boolean(arg)` - Manually convert a Torque boolean (0 or 1) into a Lua boolean. `bl.boolean(thing)` - Manually convert a Torque boolean (0 or 1) into a Lua boolean.
`bl.object(arg)` - Manually convert a Torque object reference (object ID or name) into a Lua object. `bl.object(thing)` - Manually convert a Torque object reference (object ID or name) into a Lua object.
`bl.string(arg)` - Manually convert any automatically-converted Torque value back into a string. This is not as reliable as using `bl.type` to specify the type as a string beforehand.
### Vector ### Vectors
`vec = vector{x,y,z}` - Create a vector. Can have any number of elements `vec = vector{x,y,z}` - Create a vector. Can have any number of elements
`vec1 + vec2` - Add `vec1 + vec2` - Add
`vec1 - vec2` - Subtract `vec1 - vec2` - Subtract
@@ -142,20 +129,15 @@ When reading from outside ZIPs, binary files are fully supported.
`vec1:distance(vec2)` - Distance between two points `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. `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.
### Matrix
WIP
### Extended Standard Lua Library ### Extended Standard Lua Library
`str[index]` `string[index]`
`str[{start,stop}]` `string[{start,stop}]`
`string.split(str, separator='' (splits into chars), noregex=false)` `string.split(str, separator='' (splits into chars), noregex=false)`
`string.bytes(str)` `string.bytes(str)`
`string.trim(str, charsToTrim=' \t\r\n')` `string.trim(str, charsToTrim=' \t\r\n')`
`table.empty` `table.empty`
`table.map(func, ...)` `table.map(func, ...)`
`table.mapk(func, ...)`
`table.map_list(func, ...)` `table.map_list(func, ...)`
`table.mapi_list(func, ...)`
`table.swap(tbl)` `table.swap(tbl)`
`table.reverse(list)` `table.reverse(list)`
`table.islist(list)` `table.islist(list)`
@@ -181,39 +163,41 @@ WIP
## 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.
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. 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 `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
- Any other string is passed directly as a `string`
### From Lua to TorqueScript ### From Lua to TorqueScript
- `nil` becomes the empty string "" - `nil` becomes the empty string ""
- `true` and `false` become "1" and "0" respectively - `true` and `false` become "1" and "0" respectively
- A Torque object container becomes its object ID - Torque containers become their object ID
- A `vector` becomes a string containing three numbers separated by spaces - A `vector` becomes a string containing three numbers separated by spaces
- A table of two `vector`s becomes a string containing six numbers separated by spaces - A table of two vectors becomes a string containing six numbers separated by spaces
- (WIP) A `matrix` is converted into an axis-angle (a "transform"), a string containing seven numbers separated by spaces
- Any `string` is passed directly as a string - Any `string` is passed directly as a string
- Tables cannot be passed and will throw an error - Tables cannot be passed and will throw an error
### From TorqueScript to Lua
- 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"), 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 things by hand, use `bl.object`, `bl.boolean`, or `bl.string`.
## I/O and Safety ## I/O and Safety
All Lua code is sandboxed, and file access is confined to the default directories in the same way TorqueScript is. All Lua code is sandboxed, and file access is confined to the default directories in the same way TorqueScript is.
BlockLua also has access to any C libraries installed in the `modules/lualib` folder, so be careful throwing things in there. BlockLua also has access to any C libraries installed in the `modules/lualib` folder, so be careful throwing things in there.
### Unsafe Mode ### Unsafe Mode
BlockLua can be built in Unsafe Mode by specifying the `-DBLLUA_UNSAFE` compiler flag. This removes the sandboxing of Lua code, allowing it to access any file and use any library, including ffi. BlockLua can be built in Unsafe Mode by specifying the `-DBLLUA_UNSAFE` compiler flag. This removes the sandboxing of Lua code, allowing it to access any file and use any library, including ffi.
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 unsafe mode.
Please do not publish add-ons that require either of these.
### List of Object Types
`'all'` - Any object
`'player'` - Players or bots
`'item'` - Items
`'vehicle'` - Vehicles
`'projectile'` - Projectiles
`'brick'` - Bricks with raycasting enabled
`'brickalways'` - All bricks including those with raycasting disabled
Other types: `'static'`, `'environment'`, `'terrain'`, `'water'`, `'trigger'`, `'marker'`, `'gamebase'`, `'shapebase'`, `'camera'`, `'staticshape'`, `'vehicleblocker'`, `'explosion'`, `'corpse'`, `'debris'`, `'physicalzone'`, `'staticts'`, `'staticrendered'`, `'damagableitem'`
## Compiling ## Compiling
With any *32-bit* variant of GCC installed (such as MinGW or MSYS2), run the following command in the repo directory: With any *32-bit* variant of GCC installed (such as MinGW or MSYS2), run the following command in the repo directory:
`g++ src/bllua4.cpp -o BlockLua.dll -m32 -shared -static-libgcc -Isrc -Iinc/tsfuncs -Iinc/lua -lpsapi -L. -llua5.1` `g++ src/bllua4.cpp -o BlockLua.dll -m32 -shared -static-libgcc -Isrc -Iinc/tsfuncs -Iinc/lua -lpsapi -L. -llua5.1 src/bllua`
LuaJIT (lua5.1.dll) can be obtained from https://luajit.org/ LuaJIT (lua5.1.dll) can be obtained from https://luajit.org/

View File

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

View File

@@ -4,7 +4,9 @@
-- Utility: Convert a list of strings into a map of string->true -- Utility: Convert a list of strings into a map of string->true
local function tmap(t) local u = {}; for _, n in ipairs(t) do u[n] = true end; return u; end local function tmap(t)
local u = {}; for _, n in ipairs(t) do u[n] = true end; return u;
end
-- Save banned global variables for wrapping with safe functions -- Save banned global variables for wrapping with safe functions
local old_io = io local old_io = io
@@ -12,7 +14,6 @@ local old_require = require
local old_os = os local old_os = os
local old_debug = debug local old_debug = debug
local old_package = package local old_package = package
local old_allowffi = _bllua_allowffi
-- Remove all global variables except a whitelist -- Remove all global variables except a whitelist
local ok_names = tmap { local ok_names = tmap {
@@ -38,10 +39,13 @@ end
-- Sanitize file paths to point only to allowed files within the game directory -- Sanitize file paths to point only to allowed files within the game directory
-- List of allowed directories for reading/writing -- List of allowed directories for reading/writing
-- modules/lualib is also allowed as read-only
local allowed_dirs = tmap { local allowed_dirs = tmap {
'add-ons', 'base', 'config', 'saves', 'screenshots', 'shaders' 'add-ons', 'base', 'config', 'saves', 'screenshots', 'shaders'
} }
-- List of allowed directories for reading only
local allowed_dirs_readonly = tmap {
'lualib'
}
-- List of disallowed file extensions - basically executable file extensions -- List of disallowed file extensions - basically executable file extensions
-- Note that even without this protection, exploiting would still require somehow -- Note that even without this protection, exploiting would still require somehow
-- getting a file within the allowed directories to autorun, -- getting a file within the allowed directories to autorun,
@@ -77,15 +81,14 @@ local function safe_path(fn, readonly)
end end
-- allow only whitelisted dirs -- allow only whitelisted dirs
local dir = fn:match('^([^/]+)/') local dir = fn:match('^([^/]+)/')
if not (dir and ( if (not dir) or (
allowed_dirs[dir:lower()] or (not allowed_dirs[dir:lower()]) and
( readonly and fn:find('^modules/lualib/') ) )) ((not readonly) or (not allowed_dirs_readonly[dir:lower()]))) then
then return nil, 'filename is in disallowed directory ' .. (dir or 'nil')
return nil, 'File is in disallowed directory '..(dir or 'nil')
end end
-- disallow blacklisted extensions -- disallow blacklisted extensions or no extension
local ext = fn:match('%.([^/%.]+)$') local ext = fn:match('%.([^/%.]+)$')
if ext and disallowed_exts[ext:lower()] then if (not ext) or (disallowed_exts[ext:lower()]) then
return nil, 'Filename \'' .. fn .. '\' has disallowed extension \'' .. return nil, 'Filename \'' .. fn .. '\' has disallowed extension \'' ..
(ext or '') .. '\'' (ext or '') .. '\''
end end
@@ -103,6 +106,7 @@ function _bllua_io_open(fn, md)
return nil, err return nil, err
end end
end end
-- Allow io.type (works on file handles returned by io.open) -- Allow io.type (works on file handles returned by io.open)
function _bllua_io_type(f) function _bllua_io_type(f)
return old_io.type(f) return old_io.type(f)
@@ -116,7 +120,6 @@ local disallowed_packages = tmap {
'ffi', 'debug', 'package', 'io', 'os', 'ffi', 'debug', 'package', 'io', 'os',
'_bllua_ts', '_bllua_ts',
} }
if old_allowffi then disallowed_packages['ffi'] = nil end
function _bllua_requiresecure(name) function _bllua_requiresecure(name)
if name:find('[^a-zA-Z0-9_%-%.]') or name:find('%.%.') or if name:find('[^a-zA-Z0-9_%-%.]') or name:find('%.%.') or
name:find('^%.') or name:find('%.$') then name:find('^%.') or name:find('%.$') then
@@ -128,6 +131,7 @@ function _bllua_requiresecure(name)
return old_require(name) return old_require(name)
end end
end end
package = { package = {
seeall = old_package.seeall, seeall = old_package.seeall,
} }
@@ -139,5 +143,4 @@ debug = {
getfilename = old_debug.getfilename, -- defined in lua.env.lua getfilename = old_debug.getfilename, -- defined in lua.env.lua
} }
print = _bllua_ts.echo _bllua_ts.echo(' Executed bllua-env-safe.lua')
print(' Executed bllua-env-safe.lua')

View File

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

View File

@@ -42,26 +42,4 @@ package _bllua_objectDeletionHook {
}; };
activatePackage(_bllua_objectDeletionHook); activatePackage(_bllua_objectDeletionHook);
// 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 libbl-support.cs"); echo(" Executed libbl-support.cs");

View File

@@ -2,36 +2,23 @@
-- Main lua-side functionality of bllua, -- Main lua-side functionality of bllua,
-- provided through the global table 'bl.' -- provided through the global table 'bl.'
-- todo: set
local _bllua_ts = ts local _bllua_ts = ts
bl = bl or {} bl = bl or {}
-- Config
local tsMaxArgs = 16
local tsArgsLocal = '%a,%b,%c,%d,%e,%f,%g,%h,%i,%j,%k,%l,%m,%n,%o,%p'
local tsArgsGlobal =
'$_bllua_hook_arg1,$_bllua_hook_arg2,$_bllua_hook_arg3,$_bllua_hook_arg4,'..
'$_bllua_hook_arg5,$_bllua_hook_arg6,$_bllua_hook_arg7,$_bllua_hook_arg8,'..
'$_bllua_hook_arg9,$_bllua_hook_arg10,$_bllua_hook_arg11,$_bllua_hook_arg12,'..
'$_bllua_hook_arg13,$_bllua_hook_arg14,$_bllua_hook_arg15,$_bllua_hook_arg16'
-- Misc -- Misc
local function ipairsNilable(t) -- Apply a function to each element in a list, building a new list from the returns
local maxk = 0 local function map(t,f)
for k,_ in pairs(t) do local u = {}
if k>maxk then maxk = k end for i,v in ipairs(t) do
u[i] = f(v)
end end
local i = 0 return u
return function()
i = i+1
if i>maxk then return nil
else return i, t[i] end
end end
end
-- Validation
local function isValidFuncName(name) local function isValidFuncName(name)
return type(name)=='string' and name:find('^[a-zA-Z_][a-zA-Z0-9_]*$') return type(name)=='string' and name:find('^[a-zA-Z0-9_]+$')
end end
local function isValidFuncNameNs(name) local function isValidFuncNameNs(name)
return type(name)=='string' and ( return type(name)=='string' and (
@@ -89,7 +76,7 @@ for k,v in pairs(tsTypesByName) do
tsTypesByNum[v] = k tsTypesByNum[v] = k
end end
-- Type conversion from Lua to TS -- Type conversion
local toTsObject local toTsObject
-- Convert a string from TS into a boolean -- Convert a string from TS into a boolean
-- Note: Nonempty nonnumeric strings evaluate to 1, unlike in TS -- Note: Nonempty nonnumeric strings evaluate to 1, unlike in TS
@@ -113,77 +100,35 @@ local function valToTs(val)
-- box - > 6 numbers -- box - > 6 numbers
return table.concat(val[1], ' ')..' '..table.concat(val[2], ' ') return table.concat(val[1], ' ')..' '..table.concat(val[2], ' ')
else else
error('valToTs: cannot pass Lua tables to TorqueScript.', 3) error('valToTs: could not convert table', 3)
end end
else else
error('valToTs: could not convert value to TorqueScript: '..tostring(val), 3) error('valToTs: could not convert '..type(val), 3)
end end
end end
-- Type conversion from TS to Lua
local fromTsForceTypes = { local fromTsForceTypes = {
['boolean'] = function(val) return tsBool(val) end, ['boolean'] = tsBool,
['object'] = function(val) return toTsObject(val) end, -- wrap because toTsObject not defined yet ['object'] = function(val) toTsObject(val) end, -- toTsObject not defined yet
['string'] = function(val) return val end, ['string'] = tostring,
} }
local function forceValFromTs(val, typ) local function convertValFromTs(val, typ)
local func = fromTsForceTypes[typ] return fromTsForceTypes[typ](val) or
if not func then error('valFromTs: invalid force type \''..typ..'\'', 4) end error('valFromTs: invalid force type '..typ, 4)
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
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 {
vector{tonumber(x1S),tonumber(y1S),tonumber(z1S)},
vector{tonumber(x2S),tonumber(y2S),tonumber(z2S)} }
else return nil end
end
local function multinumericFromTs(val)
local tsNumPat = '%-?[0-9]+%.?[0-9]*e?[0-9]*'
if val:find('^'..tsNumPat) then
local nums = {}
for _,part in ipairs(val:split(' ')) do
if part:find('^'..tsNumPat..'$') then
table.insert(nums, tonumber(part))
else
return nil
end
end
if #nums==2 or #nums==3 then
return vector(nums)
elseif #nums==6 then -- box
return {
vector{nums[1], nums[2], nums[3]},
vector{nums[4], nums[5], nums[6]} }
elseif #nums==7 then -- axis
return nil --return matrix(nums)
else
return nil
end
end
end end
bl._forceType = bl._forceType or {} bl._forceType = bl._forceType or {}
-- todo: ensure name and name2 are already lowercase local function valFromTs(val, name, name2) -- todo: ensure name and name2 are already lowercase
local function valFromTs(val, name, name2)
if type(val)~='string' then if type(val)~='string' then
error('valFromTs: expected string, got '..type(val), 3) end error('valFromTs: expected string, got '..type(val), 3) end
if name then if name then
name = name:lower() name = name:lower()
if bl._forceType[name] then if bl._forceType[name] then
return forceValFromTs(val, bl._forceType[name]) return convertValFromTs(val, bl._forceType[name])
end end
end end
if name2 then if name2 then
name2 = name2:lower() name2 = name2:lower()
if bl._forceType[name2] then if bl._forceType[name2] then
return forceValFromTs(val, bl._forceType[name2]) return convertValFromTs(val, bl._forceType[name2])
end end
end end
-- '' -> nil -- '' -> nil
@@ -191,29 +136,28 @@ local function valFromTs(val, name, name2)
-- number -- number
local num = tonumber(val) local num = tonumber(val)
if num then return num end if num then return num end
-- vector, box, or axis->matrix -- vector
local vec = multinumericFromTs(val) local xS,yS,zS = val:match('^(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+)$')
if vec then return vec end if xS then return vector{tonumber(xS),tonumber(yS),tonumber(zS)} end
-- net string local x1S,y1S,z1S,x2S,y2S,z2S = val:match(
if val:sub(1,1)=='\x01' then '^(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+) '..
return _bllua_ts.call('getTaggedString', val) '(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+)$')
end -- box (2 vectors)
if x1S then return {
vector{tonumber(x1S),tonumber(y1S),tonumber(z1S)},
vector{tonumber(x2S),tonumber(y2S),tonumber(z2S)} } end
-- string -- string
return val return val
end end
local function arglistFromTs(name, argsS) local function arglistFromTs(name, argsS)
local args = {} local args = {}
for i,arg in ipairsNilable(argsS) do for i,arg in ipairs(argsS) do
args[i] = valFromTs(arg, name..':'..i) args[i] = valFromTs(arg, name..':'..i)
end end
return args return args
end end
local function arglistToTs(args) local function arglistToTs(args)
local argsS = {} return map(args, valToTs)
for i,v in ipairsNilable(args) do
table.insert(argsS, valToTs(v))
end
return argsS
end end
local function classFromForceTypeStr(name) local function classFromForceTypeStr(name)
local class, rest = name:match('^([a-zA-Z0-9_]+)(::.+)$') local class, rest = name:match('^([a-zA-Z0-9_]+)(::.+)$')
@@ -245,33 +189,13 @@ setForceType = function(ftname, typ)
end end
bl.type = setForceType bl.type = setForceType
-- Type conversion TS->Lua backwards, convert back to string
local function numFromTsTostring(val)
-- todo: as-good-as-possible scientific notation for numbers
return tostring(val)
end
local function valFromTsTostring(val)
if type(val)=='string' then
return val
elseif type(val)=='number' then
return numFromTsTostring(val)
elseif type(val)=='table' then
if val.__is_vector then
local strs = {}
for i=1,#val do strs[i] = numFromTsTostring(val[i]) end
return table.concat(strs, ' ')
elseif val.__is_matrix then
-- todo: matrix back to axis-angle string
error('bl.string: matrix not yet supported', 3)
end
end
error('bl.string: cannot convert \''..type(val)..'\'', 3)
end
-- Getting info from TS about functions and objects -- Value detection
local function isTsObject(t) local function isTsObject(t)
return type(t)=='table' and t._tsObjectId~=nil return type(t)=='table' and t._tsObjectId~=nil
end end
local function tsIsObject(name) return tsBool(_bllua_ts.call('isObject', name)) end local function tsIsObject(name) return tsBool(_bllua_ts.call('isObject', name)) end
local function tsIsFunction(name) return tsBool(_bllua_ts.call('isFunction', name)) end local function tsIsFunction(name) return tsBool(_bllua_ts.call('isFunction', name)) end
local function tsIsFunctionNs(ns, name) return tsBool(_bllua_ts.call('isFunction', ns, name)) end local function tsIsFunctionNs(ns, name) return tsBool(_bllua_ts.call('isFunction', ns, name)) end
@@ -280,31 +204,27 @@ local function tsIsFunctionNsname(nsname)
if ns then return tsIsFunctionNs(ns, name) if ns then return tsIsFunctionNs(ns, name)
else return tsIsFunction(nsname) end else return tsIsFunction(nsname) end
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
return is
end
function bl.isObject(obj) function bl.isObject(obj)
if type(obj)=='number' then -- object id if isTsObject(obj) then
return tsIsObject(tostring(obj)) obj = obj._tsObjectId
elseif type(obj)=='string' then -- object name elseif type(obj)=='number' then
return tsIsObject(obj) obj = tostring(obj)
elseif isTsObject(obj) then -- lua object elseif type(obj)~='string' then
if obj._deleted then
return false
else
return assertTsObjectExists(obj)
end
else
error('bl.isObject: argument #1: expected torque object, number, or string', 2) error('bl.isObject: argument #1: expected torque object, number, or string', 2)
end end
return tsIsObject(obj)
end
function bl.isFunction(a1, a2)
if type(a1)~='string' then
error('bl.isFunction: argument #1: expected string', 2) end
if a2 then
if type(a2)~='string' then
error('bl.isFunction: argument #2: expected string', 2) end
return tsIsFunctionNs(a1, a2)
else
return tsIsFunction(a1)
end end
function bl.isFunction(name)
return tsIsFunctionNsname(name)
end end
-- Torque object pseudo-class -- Torque object pseudo-class
@@ -387,19 +307,17 @@ local tsObjectMeta = {
tsIsFunctionNs(rawget(t,'_tsNamespace'), name) or tsIsFunctionNs(rawget(t,'_tsNamespace'), name) or
tsIsFunctionNs(rawget(t,'_tsName'), name) tsIsFunctionNs(rawget(t,'_tsName'), name)
then then
return function(t2, ...) return function(t, ...)
if t2==nil or type(t2)~='table' or not t2._tsObjectId then local args = {...}
error('ts object method: be sure to use :func() not .func()', 2) end local argsS = arglistToTs(args)
local argsS = arglistToTs({...}) return valFromTs(
local res = _bllua_ts.callobj(rawget(t,'_tsObjectId'), name, unpack(argsS)),
_bllua_ts.callobj(t2._tsObjectId, name, unpack(argsS)) rawget(t,'_tsName') and rawget(t,'_tsName')..'::'..name,
return valFromTs(res, rawget(t,'_tsNamespace')..'::'..name)
t2._tsName and t2._tsName..'::'..name,
t2._tsNamespace..'::'..name)
end end
else else
local res = _bllua_ts.getfield(rawget(t,'_tsObjectId'), name) return valFromTs(
return valFromTs(res, _bllua_ts.getfield(rawget(t,'_tsObjectId'), name),
rawget(t,'_tsName') and rawget(t,'_tsName')..'.'..name, rawget(t,'_tsName') and rawget(t,'_tsName')..'.'..name,
rawget(t,'_tsNamespace')..'.'..name) rawget(t,'_tsNamespace')..'.'..name)
end end
@@ -432,8 +350,7 @@ local tsObjectMeta = {
-- Display a nice info string -- Display a nice info string
__tostring = function(t) __tostring = function(t)
return 'torque:'..t._tsNamespace..':'..t._tsObjectId.. return 'torque:'..t._tsNamespace..':'..t._tsObjectId..
(t._tsName~='' and ('('..t._tsName..')') or '').. (t._tsName~='' and ('('..t._tsName..')') or '')
(t._deleted and '(deleted)' or '')
end, end,
-- #object -- #object
-- If the object has a getCount method, return its count -- If the object has a getCount method, return its count
@@ -464,8 +381,7 @@ local tsObjectMeta = {
local obj = toTsObject(_bllua_ts.callobj(t._tsObjectId, local obj = toTsObject(_bllua_ts.callobj(t._tsObjectId,
'getObject', tostring(idx))) 'getObject', tostring(idx)))
idx = idx+1 idx = idx+1
--return idx-1, obj return idx-1, obj
return obj
else else
return nil return nil
end end
@@ -514,10 +430,8 @@ local tsObjectMeta = {
if t==nil then if t==nil then
error('ts object method: be sure to use :func() not .func()', 2) end error('ts object method: be sure to use :func() not .func()', 2) end
if t._deleted then if t._deleted then
return false return false end
else return tsIsObject(t._tsObjectId)
return assertTsObjectExists(t)
end
end, end,
} }
-- Weak-values table for caching Torque object references -- Weak-values table for caching Torque object references
@@ -550,7 +464,7 @@ toTsObject = function(idiS)
idiS = idiS:lower() idiS = idiS:lower()
if bl._objectsWeak[idiS] then return bl._objectsWeak[idiS] end if bl._objectsWeak[idiS] then return bl._objectsWeak[idiS] end
if not tsIsObject(idiS) then if not tsBool(_bllua_ts.call('isObject', idiS)) then
--error('toTsObject: object \''..idiS..'\' does not exist', 2) end --error('toTsObject: object \''..idiS..'\' does not exist', 2) end
return nil end return nil end
@@ -572,13 +486,14 @@ end
local function safeNamespaceName(name) local function safeNamespaceName(name)
return tostring(name:gsub(':', '_')) return tostring(name:gsub(':', '_'))
end end
local nscallArgStr = '%a,%b,%c,%d,%e,%f,%g,%h'
bl._cachedNamespaceCalls = {} bl._cachedNamespaceCalls = {}
local function tsNamespacedCallTfname(name) local function tsNamespacedCallTfname(name)
local tfname = bl._cachedNamespaceCalls[name] local tfname = bl._cachedNamespaceCalls[name]
if not tfname then if not tfname then
tfname = '_bllua_nscall_'..safeNamespaceName(name) tfname = '_bllua_nscall_'..safeNamespaceName(name)
local tfcode = 'function '..tfname..'('..tsArgsLocal..'){'.. local tfcode = 'function '..tfname..'('..nscallArgStr..'){'..
name..'('..tsArgsLocal..');}' name..'('..nscallArgStr..');}'
_bllua_ts.eval(tfcode) _bllua_ts.eval(tfcode)
bl._cachedNamespaceCalls[name] = tfname bl._cachedNamespaceCalls[name] = tfname
end end
@@ -586,9 +501,9 @@ local function tsNamespacedCallTfname(name)
end end
local function tsCallGen(name) local function tsCallGen(name)
return function(...) return function(...)
local argsS = arglistToTs({...}) local args = {...}
local res = _bllua_ts.call(name, unpack(argsS)) local argsS = arglistToTs(args)
return valFromTs(res, name) return valFromTs(_bllua_ts.call(name, unpack(argsS)), name)
end end
end end
@@ -614,16 +529,14 @@ local tsMeta = {
if not rest:find('::') and tsIsFunctionNs(ns, rest) then if not rest:find('::') and tsIsFunctionNs(ns, rest) then
return tsCallGen(tsNamespacedCallTfname(name)) return tsCallGen(tsNamespacedCallTfname(name))
else else
local res = _bllua_ts.getvar(name) return valFromTs(_bllua_ts.getvar(name), name)
return valFromTs(res, name)
end end
elseif tsIsFunction(name) then elseif tsIsFunction(name) then
return tsCallGen(name) return tsCallGen(name)
elseif tsIsObject(name) then elseif tsIsObject(name) then
return toTsObject(name) return toTsObject(name)
else else
local res = _bllua_ts.getvar(name) return valFromTs(_bllua_ts.getvar(name), name)
return valFromTs(res, name)
end end
end end
end, end,
@@ -642,16 +555,10 @@ function bl.call(func, ...)
return _bllua_ts.call(func, unpack(argsS)) return _bllua_ts.call(func, unpack(argsS))
end end
function bl.eval(code) function bl.eval(code)
local res = _bllua_ts.eval(code) return valFromTs(_bllua_ts.eval(code))
return valFromTs(res)
end end
function bl.exec(file) function bl.exec(file)
local res = _bllua_ts.call('exec', file) return valFromTs(_bllua_ts.call('exec', file))
return valFromTs(res)
end
function bl.array(name, ...)
local rest = {...}
return name..table.concat(rest, '_')
end end
function bl.boolean(val) function bl.boolean(val)
return val~=nil and return val~=nil and
@@ -669,63 +576,18 @@ function bl.object(id)
error('bl.object: id must be a ts object, number, or string', 2) error('bl.object: id must be a ts object, number, or string', 2)
end end
end end
function bl.string(val) function bl.array(name, ...)
return valFromTsTostring(val) local rest = {...}
return name..table.concat(rest, '_')
end end
function _bllua_call(fnameS, ...)
-- Lua calling from TS local args = arglistFromTs('lua:'..fnameS:lower(), {...}) -- todo: allow this though bl.type
local luaLookup if not _G[fnameS] then
luaLookup = function(tbl, name, set, val) error('luacall: no global lua function named \''..fnameS..'\'') end
if name:find('%.') then -- todo: library fields and object methods
local first, rest = name:match('^([^%.:]+)%.(.+)$') local res = _G[fnameS](unpack(args))
if not isValidFuncName(first) then
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
if not isValidFuncName(first) then
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 tbl[first][rest]==nil then
error('luacall: no method named \''..rest..'\'', 3) end
return function(...)
tbl[first][rest](tbl[first], ...)
end
else
if set then
tbl[name] = val
else
return tbl[name]
end
end
end
-- Todo: similar deep access for luaget and luaset
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 \''..fname..'\'', 2) end
local res = func(unpack(args))
return valToTs(res) return valToTs(res)
end 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
-- bl.schedule: Use TS's schedule function to schedule lua calls -- bl.schedule: Use TS's schedule function to schedule lua calls
-- bl.schedule(time, function, args...) -- bl.schedule(time, function, args...)
@@ -775,8 +637,9 @@ local function addCmd(cmd, func)
if not isValidFuncName(cmd) then if not isValidFuncName(cmd) then
error('addCmd: invalid function name \''..tostring(cmd)..'\'') end error('addCmd: invalid function name \''..tostring(cmd)..'\'') end
bl._cmds[cmd] = func bl._cmds[cmd] = func
_bllua_ts.eval('function '..cmd..'('..tsArgsLocal..'){'.. local arglist = '%a,%b,%c,%d,%e,%f,%g,%h,%i,%j,%k,%l,%m,%n,%o,%p'
'_bllua_luacall(_bllua_process_cmd,"'..cmd..'",'..tsArgsLocal..');}') _bllua_ts.eval('function '..cmd..'('..arglist..'){'..
'_bllua_luacall(_bllua_process_cmd,"'..cmd..'",'..arglist..');}')
end end
function bl.addServerCmd(name, func) function bl.addServerCmd(name, func)
name = name:lower() name = name:lower()
@@ -794,9 +657,8 @@ function bl.commandToServer(cmd, ...)
_bllua_ts.call('addTaggedString', cmd), _bllua_ts.call('addTaggedString', cmd),
unpack(arglistToTs({...}))) unpack(arglistToTs({...})))
end end
function bl.commandToClient(client, cmd, ...) function bl.commandToClient(cmd, ...)
_bllua_ts.call('commandToClient', _bllua_ts.call('commandToClient',
valToTs(client),
_bllua_ts.call('addTaggedString', cmd), _bllua_ts.call('addTaggedString', cmd),
unpack(arglistToTs({...}))) unpack(arglistToTs({...})))
end end
@@ -825,6 +687,9 @@ local function deactivatePackage(pkg)
_bllua_ts.call('deactivatePackage', pkg) _bllua_ts.call('deactivatePackage', pkg)
end end
end end
local hookNargs = 8
local hookArglistLocal = '%a,%b,%c,%d,%e,%f,%g,%h'
local hookArglistGlobal = '$_bllua_hook_arg1,$_bllua_hook_arg2,$_bllua_hook_arg3,$_bllua_hook_arg4,$_bllua_hook_arg5,$_bllua_hook_arg6,$_bllua_hook_arg7,$_bllua_hook_arg8'
bl._hooks = bl._hooks or {} bl._hooks = bl._hooks or {}
function _bllua_process_hook_before(pkgS, nameS, ...) function _bllua_process_hook_before(pkgS, nameS, ...)
local args = arglistFromTs(nameS, {...}) local args = arglistFromTs(nameS, {...})
@@ -838,7 +703,7 @@ function _bllua_process_hook_before(pkgS, nameS, ...)
_bllua_ts.setvar('_bllua_hook_abort', '1') _bllua_ts.setvar('_bllua_hook_abort', '1')
_bllua_ts.setvar('_bllua_hook_return', valToTs(args._return)) _bllua_ts.setvar('_bllua_hook_return', valToTs(args._return))
end end
for i=1,tsMaxArgs do for i=1,hookNargs do
_bllua_ts.setvar('_bllua_hook_arg'..i, valToTs(args[i])) _bllua_ts.setvar('_bllua_hook_arg'..i, valToTs(args[i]))
end end
end end
@@ -856,8 +721,8 @@ end
local function updateHook(pkg, name, hk) local function updateHook(pkg, name, hk)
local beforeCode = hk.before and local beforeCode = hk.before and
('_bllua_luacall("_bllua_process_hook_before", "'..pkg..'","'..name.. ('_bllua_luacall("_bllua_process_hook_before", "'..pkg..'","'..name..
'",'..tsArgsLocal..');') or '' '",'..hookArglistLocal..');') or ''
local arglist = (hk.before and tsArgsGlobal or tsArgsLocal) local arglist = (hk.before and hookArglistGlobal or hookArglistLocal)
local parentCode = local parentCode =
tsIsFunctionNsname(name) and -- only call parent if it exists tsIsFunctionNsname(name) and -- only call parent if it exists
(hk.before and (hk.before and
@@ -870,11 +735,10 @@ local function updateHook(pkg, name, hk)
arglist..');') or '' arglist..');') or ''
local code = local code =
'package '..pkg..'{'.. 'package '..pkg..'{'..
'function '..name..'('..tsArgsLocal..'){'.. 'function '..name..'('..hookArglistLocal..'){'..
beforeCode..parentCode..afterCode.. beforeCode..parentCode..afterCode..
'}'.. '}'..
'};' '};'
print('bl.hook eval output: [['..code..']]')
_bllua_ts.eval(code) _bllua_ts.eval(code)
end end
function bl.hook(pkg, name, time, func) function bl.hook(pkg, name, time, func)
@@ -900,9 +764,9 @@ 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
if name and not isValidFuncNameNs(name) then if not isValidFuncNameNs(name) then
error('bl.unhook: argument #2: invalid function name \''..tostring(name)..'\'', 2) end error('bl.unhook: argument #2: invalid function name \''..tostring(name)..'\'', 2) end
if time and time~='before' and time~='after' then if time~='before' and time~='after' then
error('bl.unhook: argument #3: time must be \'before\' or \'after\'', 2) end error('bl.unhook: argument #3: time must be \'before\' or \'after\'', 2) end
if not name then if not name then
@@ -929,13 +793,10 @@ 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 tableEmpty(bl._hooks[pkg][name]) then if tableEmpty(bl._hooks[pkg][name]) and tableEmpty(bl._hooks[pkg]) then
bl._hooks[pkg][name] = nil
end
if tableEmpty(bl._hooks[pkg]) then
bl._hooks[pkg] = nil bl._hooks[pkg] = nil
updateHook(pkg, name, {})
deactivatePackage(pkg) deactivatePackage(pkg)
updateHook(pkg, name, {})
else else
updateHook(pkg, name, bl._hooks[pkg][name]) updateHook(pkg, name, bl._hooks[pkg][name])
end end
@@ -991,7 +852,7 @@ function bl.raycast(start, stop, mask, ignores)
local stopS = vecToTs(start) local stopS = vecToTs(start)
local maskS = maskToTs(mask) local maskS = maskToTs(mask)
local ignoresS = {} local ignoresS = {}
for _,v in ipairsNilable(ignores) do for _,v in ipairs(ignores) do
table.insert(ignoresS, objToTs(v)) table.insert(ignoresS, objToTs(v))
end end
@@ -1040,7 +901,7 @@ end
local maxTsArgLen = 8192 local maxTsArgLen = 8192
local function valsToString(vals) local function valsToString(vals)
local strs = {} local strs = {}
for i,v in ipairsNilable(vals) do for i,v in ipairs(vals) do
local tstr = table.tostring(v) local tstr = table.tostring(v)
if #tstr>maxTsArgLen then if #tstr>maxTsArgLen then
tstr = tostring(v) tstr = tostring(v)

View File

@@ -1,4 +1,3 @@
-- This Lua code provides some built-in utilities for writing Lua add-ons -- This Lua code provides some built-in utilities for writing Lua add-ons
-- It is eval'd automatically once BLLua3 has loaded the TS API and environment -- It is eval'd automatically once BLLua3 has loaded the TS API and environment
-- It only has access to the sandboxed lua environment, just like user code. -- It only has access to the sandboxed lua environment, just like user code.
@@ -7,7 +6,10 @@ ts = _bllua_ts
-- Provide limited OS functions -- Provide limited OS functions
os = os or {} os = os or {}
---@diagnostic disable-next-line: duplicate-set-field
function os.time() return math.floor(tonumber(_bllua_ts.call('getSimTime')) / 1000) end function os.time() return math.floor(tonumber(_bllua_ts.call('getSimTime')) / 1000) end
---@diagnostic disable-next-line: duplicate-set-field
function os.clock() return tonumber(_bllua_ts.call('getSimTime')) / 1000 end function os.clock() return tonumber(_bllua_ts.call('getSimTime')) / 1000 end
-- Virtual file class, emulating a file object as returned by io.open -- Virtual file class, emulating a file object as returned by io.open
@@ -82,24 +84,26 @@ local function new_file_obj(fn)
return file return file
end end
local function tflip(t) local u = {}; for _, n in ipairs(t) do u[n] = true end; return u; end local function tflip(t)
local u = {}; for _, n in ipairs(t) do u[n] = true end; return u;
end
local allowed_zip_dirs = tflip { local allowed_zip_dirs = tflip {
'add-ons', 'base', 'config', 'saves', 'screenshots', 'shaders' 'add-ons', 'base', 'config', 'saves', 'screenshots', 'shaders'
} }
local function io_open_absolute(fn, mode) local function io_open_absolute(fn, mode)
-- if file exists, use original mode -- if file exists, use original mode
local res, err = _bllua_io_open(fn, mode) local res, err = _bllua_io_open(fn, mode)
if res then return res if res then return res end
elseif err and not err:find('No such file or directory$') then return nil, err end
-- otherwise, if TS sees file but Lua doesn't, it must be in a zip, so use TS reader -- otherwise, if TS sees file but Lua doesn't, it must be in a zip, so use TS reader
local dir = fn:match('^[^/]+') local dir = fn:match('^[^/]+')
if not allowed_zip_dirs[dir:lower()] then return nil, 'Zip is not in one of the allowed directories' end if not allowed_zip_dirs[dir:lower()] then return nil, 'File is not in one of the allowed directories' end
local exist = _bllua_ts.call('isFile', fn) == '1' local exist = _bllua_ts.call('isFile', fn) == '1'
if not exist then return nil, err end if not exist then return nil, err end
if mode ~= nil and mode ~= 'r' and mode ~= 'rb' then if mode ~= nil and mode ~= 'r' and mode ~= 'rb' then
return nil, 'Files in zips can only be opened in read mode' end return nil, 'Files in zips can only be opened in read mode'
end
-- return a temp lua file object with the data -- return a temp lua file object with the data
local fi = new_file_obj(fn) local fi = new_file_obj(fn)
@@ -107,6 +111,7 @@ local function io_open_absolute(fn, mode)
end end
io = io or {} io = io or {}
---@diagnostic disable-next-line: duplicate-set-field
function io.open(fn, mode, errn) function io.open(fn, mode, errn)
errn = errn or 1 errn = errn or 1
@@ -127,13 +132,19 @@ function io.open(fn, mode, errn)
return fi, err, fn return fi, err, fn
end end
end end
---@diagnostic disable-next-line: duplicate-set-field
function io.lines(fn) function io.lines(fn)
local fi, err, fn2 = io.open(fn, nil, 2) local fi, err, fn2 = io.open(fn, nil, 2)
if not fi then error('Error opening file \'' .. fn2 .. '\': ' .. err, 2) end if not fi then error('Error opening file \'' .. fn2 .. '\': ' .. err, 2) end
return fi:lines() return fi:lines()
end end
---@diagnostic disable-next-line: duplicate-set-field
function io.type(f) function io.type(f)
---@diagnostic disable-next-line: undefined-field
if type(f) == 'table' and f._is_file then if type(f) == 'table' and f._is_file then
---@diagnostic disable-next-line: undefined-field
return f._is_open and 'file' or 'closed file' return f._is_open and 'file' or 'closed file'
else else
return _bllua_io_type(f) return _bllua_io_type(f)
@@ -193,13 +204,23 @@ function require(mod)
return _bllua_requiresecure(mod) return _bllua_requiresecure(mod)
end end
-- Exposure to TS
function _bllua_getvar(name) return _G[name] end
function _bllua_setvar(name, val) _G[name] = val end
function _bllua_eval(code) return loadstring(code)() end
function _bllua_exec(fn) return dofile(fn, 2) end
local function isValidCode(code) local function isValidCode(code)
local f, e = loadstring(code) local f, e = loadstring(code)
return f ~= nil return f ~= nil
end end
function _bllua_smarteval(code) function _bllua_smarteval(code)
if (not code:find('^print%(')) and isValidCode('print(' .. code .. ')') then if (not code:find('^print%(')) and isValidCode('print(' .. code .. ')') then
code = 'print('..code..')' end code = 'print(' .. code .. ')'
end
local f, e = loadstring(code) local f, e = loadstring(code)
if f then if f then
return f() return f()

View File

@@ -49,4 +49,26 @@ function _bllua_set_var(%name, %val) {
return ""; return "";
} }
// 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"); echo(" Executed libts-ts.cs");

View File

@@ -1,7 +0,0 @@
-- todo
-- Matrix class with math operators
print(' Executed matrix.lua')

View File

@@ -1,33 +1,24 @@
-- Basic functionality that should be standard in Lua -- Basic functionality that should be standard in Lua
-- Table / List -- Table / List
-- Whether a table contains no keys -- Whether a table contains no keys
function table.empty(t) function table.empty(t)
return next(t)==nil return next(t) ~= nil
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, ...)
local ts = { ... } local ts = { ... }
local u = {} local u = {}
for k, _ in pairs(ts[1]) do for k, _ in pairs(ts[1]) do
local args = {} local args = {}
for j=1,#ts do args[j] = ts[j][k] end for j = 1, #ts do args[j] = ts[j][i] end
u[k] = f(unpack(args)) u[i] = f(unpack(args))
end
return u
end
function table.mapk(f, ...)
local ts = {...}
local u = {}
for k,_ in pairs(ts[1]) do
local args = {}
for j=1,#ts do args[j] = ts[j][k] end
u[k] = f(k, unpack(args))
end end
return u return u
end end
function table.map_list(f, ...) function table.map_list(f, ...)
local ts = { ... } local ts = { ... }
local u = {} local u = {}
@@ -38,28 +29,21 @@ function table.map_list(f, ...)
end end
return u return u
end end
function table.mapi_list(f, ...)
local ts = {...}
local u = {}
for i=1,#ts[1] do
local args = {}
for j=1,#ts do args[j] = ts[j][i] end
u[i] = f(i, unpack(args))
end
return u
end
-- Swap keys/values -- Swap keys/values
function table.swap(t) function table.swap(t)
local u = {} local u = {}
for k, v in pairs(t) do u[v] = k end for k, v in pairs(t) do u[v] = k end
return u return u
end end
-- Reverse a list -- Reverse a list
function table.reverse(l) function table.reverse(l)
local m = {} local m = {}
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
-- 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
@@ -69,6 +53,7 @@ function table.islist(t)
end end
return n == #t return n == #t
end end
-- Append contents of other tables to first table -- Append contents of other tables to first table
function table.append(t, ...) function table.append(t, ...)
local a = { ... } local a = { ... }
@@ -77,6 +62,7 @@ function table.append(t, ...)
end end
return t return t
end end
-- Create a new table containing all keys from any number of tables -- Create a new table containing all keys from any number of tables
-- latter tables in the arg list override prior ones -- latter tables in the arg list override prior ones
-- overlaps, NOT appends, integer keys -- overlaps, NOT appends, integer keys
@@ -88,6 +74,7 @@ function table.join(...)
end end
return w return w
end end
-- Whether a table contains a certain value in any key -- Whether a table contains a certain value in any key
function table.contains(t, s) function table.contains(t, s)
for _, v in pairs(t) do for _, v in pairs(t) do
@@ -95,29 +82,34 @@ function table.contains(t,s)
end end
return false return false
end end
function table.contains_list(t, s) function table.contains_list(t, s)
for _, v in ipairs(t) do for _, v in ipairs(t) do
if v == s then return true end if v == s then return true end
end end
return false return false
end end
-- Copy a table to another table -- Copy a table to another table
function table.copy(t) function table.copy(t)
local u = {} local u = {}
for k, v in pairs(t) do u[k] = v end for k, v in pairs(t) do u[k] = v end
return u return u
end end
function table.copy_list(l) function table.copy_list(l)
local m = {} local m = {}
for i, v in ipairs(l) do m[i] = v end for i, v in ipairs(l) do m[i] = v end
return m return m
end end
-- Sort a table in a new copy -- Sort a table in a new copy
function table.sortcopy(t, f) function table.sortcopy(t, f)
local u = table.copy_list(t) local u = table.copy_list(t)
table.sort(u, f) table.sort(u, f)
return u return u
end end
-- Remove a value from a table -- Remove a value from a table
function table.removevalue(t, r) function table.removevalue(t, r)
local rem = {} local rem = {}
@@ -126,6 +118,7 @@ function table.removevalue(t, r)
end end
for _, k in ipairs(rem) do t[k] = nil end for _, k in ipairs(rem) do t[k] = nil end
end end
function table.removevalue_list(t, r) function table.removevalue_list(t, r)
for i = #t, 1, -1 do for i = #t, 1, -1 do
if t[i] == r then if t[i] == r then
@@ -133,6 +126,7 @@ function table.removevalue_list(t, r)
end end
end end
end end
-- Export tables into formatted executable strings -- Export tables into formatted executable strings
local function tabs(tabLevel) local function tabs(tabLevel)
return (' '):rep(tabLevel) return (' '):rep(tabLevel)
@@ -199,14 +193,13 @@ valueToString = function(v, tabLevel, seen)
return tostring(v) return tostring(v)
else else
--error('table.tostring: table contains a '..t..' value, cannot serialize') --error('table.tostring: table contains a '..t..' value, cannot serialize')
return 'nil --[[ '..tostring(v)..' ]]' return 'nil --[[ cannot serialize ' .. t .. ': ' .. tostring(v) .. ' ]]'
end end
end end
function table.tostring(t) function table.tostring(t)
return tableToString(t, 0, {}) return tableToString(t, 0, {})
end end
-- String -- String
-- Split string into table by separator -- Split string into table by separator
@@ -214,7 +207,8 @@ end
-- if regex is not true, sep is treated as a regex pattern -- if regex is not true, sep is treated as a regex pattern
function string.split(str, sep, noregex) function string.split(str, sep, noregex)
if type(str) ~= 'string' then if type(str) ~= 'string' then
error('string.split: argument #1: expected string, got '..type(str), 2) end error('string.split: argument #1: expected string, got ' .. type(str), 2)
end
if sep == nil or sep == '' then if sep == nil or sep == '' then
local t = {} local t = {}
local ns = #str local ns = #str
@@ -240,6 +234,7 @@ function string.split(str, sep, noregex)
'string.split: argument #2: expected string or nil, got ' .. type(sep), 2) 'string.split: argument #2: expected string or nil, got ' .. type(sep), 2)
end end
end end
-- Split string to a list of char bytes -- Split string to a list of char bytes
function string.bytes(s) function string.bytes(s)
local b = {} local b = {}
@@ -249,11 +244,13 @@ function string.bytes(s)
end end
return b return b
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\n' 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('')
local str_meta_index_old = str_meta.__index local str_meta_index_old = str_meta.__index
@@ -269,15 +266,20 @@ function str_meta.__index(s,k)
return string.sub(s, a, b) return string.sub(s, a, b)
end end
end end
-- String iterator -- String iterator
function string.chars(s) function string.chars(s)
local i = 0 local i = 0
return function() return function()
i = i + 1 i = i + 1
if i<=#s then return s:sub(i,i) if i <= #s then
else return nil end return s:sub(i, i)
else
return nil
end end
end end
end
-- Escape sequences -- Escape sequences
local defaultEscapes = { local defaultEscapes = {
['\\'] = '\\\\', ['\\'] = '\\\\',
@@ -297,6 +299,7 @@ function string.escape(s, escapes)
end end
return table.concat(t) return table.concat(t)
end end
local defaultEscapeChar = '\\' local defaultEscapeChar = '\\'
local defaultUnescapes = { local defaultUnescapes = {
['\\'] = '\\', ['\\'] = '\\',
@@ -327,7 +330,6 @@ function string.unescape(s, escapeChar, unescapes)
return table.concat(t) return table.concat(t)
end end
-- IO -- IO
io = io or {} io = io or {}
@@ -339,6 +341,7 @@ function io.readall(filename)
fi:close() fi:close()
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.writeall(filename, data) function io.writeall(filename, data)
local fi, err = io.open(filename, 'wb') local fi, err = io.open(filename, 'wb')
@@ -348,23 +351,24 @@ function io.writeall(filename, data)
return true, nil return true, nil
end end
-- Math -- Math
-- Round -- Round
function math.round(x) function math.round(x)
return math.floor(x + 0.5) return math.floor(x + 0.5)
end end
-- Mod that accounts for floating point inaccuracy -- Mod that accounts for floating point inaccuracy
function math.mod(a, b) function math.mod(a, b)
local m = a % b local m = a % b
if m==0 or math.abs(m)<1e-15 or math.abs(m-b)<1e-15 then return 0 if m == 0 or math.abs(m) < 1e-15 or math.abs(m - b) < 1e-15 then
else return m end return 0
else
return m
end end
end
-- Clamp value between min and max -- Clamp value between min and max
function math.clamp(v, n, x) function math.clamp(v, n, x)
return math.min(x, math.max(v, n)) return math.min(x, math.max(v, n))
end end
print(' Executed std.lua')

View File

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