Merge branch 'master'

This commit is contained in:
2025-10-06 13:11:57 -04:00
12 changed files with 1339 additions and 1331 deletions

BIN
BlockLua.dll Normal file

Binary file not shown.

View File

@@ -36,6 +36,8 @@ Lua scripting for Blockland
`object:method(args)` - Call a Torque object method `object:method(args)` - Call a Torque object method
`object[index]` - Access a member of a Torque set or group `object[index]` - Access a member of a Torque set or group
`for childIndex, child in object:members() do` - Iterate objects within of a Torque set or group. Indices start at 0 like in Torque. `for 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
@@ -101,10 +103,11 @@ 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(thing)` - Manually convert a Torque boolean (0 or 1) into a Lua boolean. `bl.boolean(arg)` - 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. `bl.object(arg)` - 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.
### Vectors ### Vector
`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
@@ -129,6 +132,9 @@ 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
`string[index]` `string[index]`
`string[{start,stop}]` `string[{start,stop}]`
@@ -163,12 +169,7 @@ When reading from outside ZIPs, binary files are fully supported.
## 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
@@ -178,6 +179,17 @@ TorqueScript stores no type information; all values in TorqueScript are strings.
- 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
- 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 two or three numbers separated by single spaces becomes a `vector`
- A string containing six numbers separated by single spaces becomes a table of two vectors, usually defining the corners a bounding box
- (WIP) A string containing seven numbers separated by single spaces is treated as an axis-angle (a "transform" in TorqueScript parlance), and is converted into a `matrix` representing the translation and rotation.
- Any other string is passed directly as a `string`
For scenarios where the automatic TorqueScript->Lua conversion rules are insufficient or incorrect, use `bl.type`.
To convert objects by hand, use `bl.object`, `bl.boolean`, or `bl.string`.
## 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.

View File

@@ -31,6 +31,7 @@ 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");
@@ -72,6 +73,7 @@ bool init() {
// Load utilities // Load utilities
BLL_LOAD_LUA(gL, bll_fileLuaStd); BLL_LOAD_LUA(gL, bll_fileLuaStd);
BLL_LOAD_LUA(gL, bll_fileLuaVector); BLL_LOAD_LUA(gL, bll_fileLuaVector);
BLL_LOAD_LUA(gL, bll_fileLuaMatrix);
BLL_LOAD_LUA(gL, bll_fileLuaLibts); BLL_LOAD_LUA(gL, bll_fileLuaLibts);
BlEval(bll_fileTsLibts); BlEval(bll_fileTsLibts);
BLL_LOAD_LUA(gL, bll_fileLuaLibbl); BLL_LOAD_LUA(gL, bll_fileLuaLibbl);

View File

@@ -143,4 +143,5 @@ debug = {
getfilename = old_debug.getfilename, -- defined in lua.env.lua getfilename = old_debug.getfilename, -- defined in lua.env.lua
} }
_bllua_ts.echo(' Executed bllua-env-safe.lua') print = _bllua_ts.echo
print(' Executed bllua-env-safe.lua')

View File

@@ -37,4 +37,5 @@ function _bllua_on_error(err)
return table.concat(tracelines, '\n') return table.concat(tracelines, '\n')
end end
_bllua_ts.echo(' Executed bllua-env.lua') print = _bllua_ts.echo
print(' Executed bllua-env.lua')

View File

@@ -42,4 +42,26 @@ 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,8 +2,6 @@
-- 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 {}
@@ -18,7 +16,7 @@ local function map(t, f)
return u return u
end end
local function isValidFuncName(name) local function isValidFuncName(name)
return type(name) == 'string' and name:find('^[a-zA-Z0-9_]+$') return type(name)=='string' and name:find('^[a-zA-Z_][a-zA-Z0-9_]*$')
end end
local function isValidFuncNameNs(name) local function isValidFuncNameNs(name)
return type(name)=='string' and ( return type(name)=='string' and (
@@ -76,7 +74,7 @@ for k, v in pairs(tsTypesByName) do
tsTypesByNum[v] = k tsTypesByNum[v] = k
end end
-- Type conversion -- Type conversion from Lua to TS
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
@@ -100,36 +98,75 @@ 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: could not convert table', 3) error('valToTs: cannot pass Lua tables to TorqueScript.', 3)
end end
else else
error('valToTs: could not convert ' .. type(val), 3) error('valToTs: could not convert value to TorqueScript: '..tostring(val), 3)
end end
end end
-- Type conversion from TS to Lua
local fromTsForceTypes = { local fromTsForceTypes = {
['boolean'] = tsBool, ['boolean'] = tsBool,
['object'] = function(val) toTsObject(val) end, -- toTsObject not defined yet ['object'] = function(val) toTsObject(val) end, -- wrap because toTsObject not defined yet
['string'] = tostring, ['string'] = function(val) return val end,
} }
local function convertValFromTs(val, typ) local function forceValFromTs(val, typ)
return fromTsForceTypes[typ](val) or return fromTsForceTypes[typ](val) or
error('valFromTs: invalid force type '..typ, 4) error('valFromTs: invalid force type '..typ, 4)
end 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
bl._forceType = bl._forceType or {} bl._forceType = bl._forceType or {}
local function valFromTs(val, name, name2) -- todo: ensure name and name2 are already lowercase local function valFromTs(val, name, name2) -- todo: ensure name and name2 are already lowercase
if type(val)~='string' then if type(val)~='string' then
error('valFromTs: expected string, got ' .. type(val), 3) error('valFromTs: expected string, got '..type(val), 3) end
end
if name then if name then
name = name:lower() name = name:lower()
if bl._forceType[name] then if bl._forceType[name] then
return convertValFromTs(val, bl._forceType[name]) return forceValFromTs(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 convertValFromTs(val, bl._forceType[name2]) return forceValFromTs(val, bl._forceType[name2])
end end
end end
-- '' -> nil -- '' -> nil
@@ -137,18 +174,9 @@ local function valFromTs(val, name, name2) -- todo: ensure name and name2 are al
-- number -- number
local num = tonumber(val) local num = tonumber(val)
if num then return num end if num then return num end
-- vector -- vector, box, or axis->matrix
local xS, yS, zS = val:match('^(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+)$') local vec = multinumericFromTs(val)
if xS then return vector { tonumber(xS), tonumber(yS), tonumber(zS) } end if vec then return vec end
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]+)$')
-- 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
@@ -165,18 +193,15 @@ end
local function classFromForceTypeStr(name) local function classFromForceTypeStr(name)
local class, rest = name:match('^([a-zA-Z0-9_]+)(::.+)$') local class, rest = name:match('^([a-zA-Z0-9_]+)(::.+)$')
if not class then if not class then
class, rest = name:match('^([a-zA-Z0-9_]+)(%..+)$') class, rest = name:match('^([a-zA-Z0-9_]+)(%..+)$') end
end
return class,rest return class,rest
end end
local setForceType local setForceType
setForceType = function(ftname, typ) setForceType = function(ftname, typ)
if typ~=nil and not fromTsForceTypes[typ] then if typ~=nil and not fromTsForceTypes[typ] then
error('bl.type: invalid type \'' .. typ .. '\'', 2) error('bl.type: invalid type \''..typ..'\'', 2) end
end
if not isValidFuncNameNsArgn(ftname) then if not isValidFuncNameNsArgn(ftname) then
error('bl.type: invalid function or variable name \'' .. ftname .. '\'', 2) error('bl.type: invalid function or variable name \''..ftname..'\'', 2) end
end
ftname = ftname:lower() ftname = ftname:lower()
@@ -195,48 +220,66 @@ 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
-- Value detection -- Getting info from TS about functions and objects
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
local function tsIsFunctionNsname(nsname) local function tsIsFunctionNsname(nsname)
local ns, name = nsname:match('^([^:]+)::([^:]+)$') local ns, name = nsname:match('^([^:]+)::([^:]+)$')
if ns then if ns then return tsIsFunctionNs(ns, name)
return tsIsFunctionNs(ns, name) else return tsIsFunction(nsname) end
else
return tsIsFunction(nsname)
end end
-- sanity check to make sure objects that don't isObject are always marked as ._deleted
-- can be removed later
local function assertTsObjectExists(obj)
local is = tsIsObject(obj._tsObjectId)
if not is then
print('Warning: TS object :exists or isobject from lua but no longer exists') end
return is
end end
function bl.isObject(obj) function bl.isObject(obj)
if isTsObject(obj) then if type(obj)=='number' then -- object id
obj = obj._tsObjectId return tsIsObject(tostring(obj))
elseif type(obj) == 'number' then elseif type(obj)=='string' then -- object name
obj = tostring(obj) return tsIsObject(obj)
elseif type(obj) ~= 'string' then elseif isTsObject(obj) then -- lua object
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
@@ -249,11 +292,9 @@ local tsClassMeta = {
bl._objectUserMetas = bl._objectUserMetas or {} bl._objectUserMetas = bl._objectUserMetas or {}
function bl.class(cname, inhname) function bl.class(cname, inhname)
if not ( type(cname)=='string' and isValidFuncName(cname) ) then if not ( type(cname)=='string' and isValidFuncName(cname) ) then
error('bl.class: argument #1: invalid class name', 2) error('bl.class: argument #1: invalid class name', 2) end
end
if not ( inhname==nil or (type(inhname)=='string' and isValidFuncName(inhname)) ) then if not ( inhname==nil or (type(inhname)=='string' and isValidFuncName(inhname)) ) then
error('bl.class: argument #2: inherit name must be a string or nil', 2) error('bl.class: argument #2: inherit name must be a string or nil', 2) end
end
cname = cname:lower() cname = cname:lower()
local met = bl._objectUserMetas[cname] or { local met = bl._objectUserMetas[cname] or {
@@ -268,18 +309,15 @@ function bl.class(cname, inhname)
inhname = inhname:lower() inhname = inhname:lower()
local inh = bl._objectUserMetas[inhname] local inh = bl._objectUserMetas[inhname]
if not inh then if not inh then error('bl.class: argument #2: \''..inhname..'\' is not the '..
error('bl.class: argument #2: \'' .. inhname .. '\' is not the ' .. 'name of an existing class', 2) end
'name of an existing class', 2)
end
inh._children[cname] = true inh._children[cname] = true
local inhI = met._inherit local inhI = met._inherit
if inhI and inhI~=inh then if inhI and inhI~=inh then
error('bl.class: argument #2: class already exists and '.. error('bl.class: argument #2: class already exists and '..
'inherits a different parent.', 2) 'inherits a different parent.', 2) end
end
met._inherit = inh met._inherit = inh
-- apply inherited method and field types -- apply inherited method and field types
@@ -291,7 +329,6 @@ function bl.class(cname, inhname)
end end
end end
end end
local function objectInheritedMetas(name) local function objectInheritedMetas(name)
local inh = bl._objectUserMetas[name:lower()] local inh = bl._objectUserMetas[name:lower()]
return function() return function()
@@ -306,18 +343,15 @@ local tsObjectMeta = {
-- Return torque member function or value -- Return torque member function or value
__index = function(t, name) __index = function(t, name)
if rawget(t,'_deleted') then if rawget(t,'_deleted') then
error('ts object index: object no longer exists', 2) error('ts object index: object no longer exists', 2) end
end
if type(name)~='string' and type(name)~='number' then if type(name)~='string' and type(name)~='number' then
error('ts object index: index must be a string or number', 2) error('ts object index: index must be a string or number', 2) end
end
if getmetatable(t)[name] then if getmetatable(t)[name] then
return getmetatable(t)[name] return getmetatable(t)[name]
elseif type(name)=='number' then elseif type(name)=='number' then
if not tsIsFunctionNs(rawget(t,'_tsNamespace'), 'getObject') then if not tsIsFunctionNs(rawget(t,'_tsNamespace'), 'getObject') then
error('ts object __index: index is number, but object does not have '.. error('ts object __index: index is number, but object does not have '..
'getObject method', 2) 'getObject method', 2) end
end
return toTsObject(_bllua_ts.callobj(t._tsObjectId, 'getObject', return toTsObject(_bllua_ts.callobj(t._tsObjectId, 'getObject',
tostring(name))) tostring(name)))
else else
@@ -349,11 +383,9 @@ local tsObjectMeta = {
-- Use :set() to set Torque data -- Use :set() to set Torque data
__newindex = function(t, name, val) __newindex = function(t, name, val)
if rawget(t,'_deleted') then if rawget(t,'_deleted') then
error('ts object newindex: object no longer exists', 2) error('ts object newindex: object no longer exists', 2) end
end
if type(name)~='string' then if type(name)~='string' then
error('ts object newindex: index must be a string', 2) error('ts object newindex: index must be a string', 2) end
end
rawset(t, name, val) rawset(t, name, val)
-- create strong reference since it's now storing lua data -- create strong reference since it's now storing lua data
bl._objectsStrong[rawget(t,'_tsObjectId')] = t bl._objectsStrong[rawget(t,'_tsObjectId')] = t
@@ -362,31 +394,27 @@ local tsObjectMeta = {
-- Use to set torque data -- Use to set torque data
set = function(t, name, val) set = function(t, name, val)
if t==nil or type(t)~='table' or not t._tsObjectId then if t==nil or type(t)~='table' or not t._tsObjectId then
error('ts object method: be sure to use :func() not .func()', 2) error('ts object method: be sure to use :func() not .func()', 2) end
end
if t._deleted then if t._deleted then
error('ts object method: object no longer exists', 2) error('ts object method: object no longer exists', 2) end
end
if type(name)~='string' then if type(name)~='string' then
error('ts object :set(): index must be a string', 2) error('ts object :set(): index must be a string', 2) end
end
_bllua_ts.setfield(t._tsObjectId, name, valToTs(val)) _bllua_ts.setfield(t._tsObjectId, name, valToTs(val))
end, end,
-- __tostring: Called when printing -- __tostring: Called when printing
-- 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
__len = function(t) __len = function(t)
if t._deleted then if t._deleted then
error('ts object __len: object no longer exists', 2) error('ts object __len: object no longer exists', 2) end
end
if not tsIsFunctionNs(t._tsNamespace, 'getCount') then if not tsIsFunctionNs(t._tsNamespace, 'getCount') then
error('ts object __len: object has no getCount method', 2) error('ts object __len: object has no getCount method', 2) end
end
return tonumber(_bllua_ts.callobj(t._tsObjectId, 'getCount')) return tonumber(_bllua_ts.callobj(t._tsObjectId, 'getCount'))
end, end,
-- object:members() -- object:members()
@@ -394,17 +422,14 @@ local tsObjectMeta = {
-- for index, object in group:members() do ... end -- for index, object in group:members() do ... end
members = function(t) members = function(t)
if t==nil then if t==nil then
error('ts object method: be sure to use :func() not .func()', 2) error('ts object method: be sure to use :func() not .func()', 2) end
end
if t._deleted then if t._deleted then
error('ts object method: object no longer exists', 2) error('ts object method: object no longer exists', 2) end
end
if not ( if not (
tsIsFunctionNs(t._tsNamespace, 'getCount' ) and tsIsFunctionNs(t._tsNamespace, 'getCount' ) and
tsIsFunctionNs(t._tsNamespace, 'getObject')) then tsIsFunctionNs(t._tsNamespace, 'getObject')) then
error('ts object :members() - '.. error('ts object :members() - '..
'Object does not have getCount and getObject methods', 2) 'Object does not have getCount and getObject methods', 2) end
end
local count = tonumber(_bllua_ts.callobj(t._tsObjectId, 'getCount')) local count = tonumber(_bllua_ts.callobj(t._tsObjectId, 'getCount'))
local idx = 0 local idx = 0
return function() return function()
@@ -421,29 +446,23 @@ local tsObjectMeta = {
-- Wrap some Torque functions for performance and error checking -- Wrap some Torque functions for performance and error checking
getName = function(t) getName = function(t)
if t==nil then if t==nil then
error('ts object method: be sure to use :func() not .func()', 2) error('ts object method: be sure to use :func() not .func()', 2) end
end
if t._deleted then if t._deleted then
error('ts object method: object no longer exists', 2) error('ts object method: object no longer exists', 2) end
end
return t._tsName return t._tsName
end, end,
getId = function(t) getId = function(t)
if t==nil then if t==nil then
error('ts object method: be sure to use :func() not .func()', 2) error('ts object method: be sure to use :func() not .func()', 2) end
end
if t._deleted then if t._deleted then
error('ts object method: object no longer exists', 2) error('ts object method: object no longer exists', 2) end
end
return tonumber(t._tsObjectId) return tonumber(t._tsObjectId)
end, end,
getType = function(t) getType = function(t)
if t==nil then if t==nil then
error('ts object method: be sure to use :func() not .func()', 2) error('ts object method: be sure to use :func() not .func()', 2) end
end
if t._deleted then if t._deleted then
error('ts object method: object no longer exists', 2) error('ts object method: object no longer exists', 2) end
end
return tsTypesByNum[_bllua_ts.callobj(t._tsObjectId, 'getType')] return tsTypesByNum[_bllua_ts.callobj(t._tsObjectId, 'getType')]
end, end,
---- Schedule method for objects ---- Schedule method for objects
@@ -465,12 +484,12 @@ local tsObjectMeta = {
--end, --end,
exists = function(t) exists = function(t)
if t==nil then if t==nil then
error('ts object method: be sure to use :func() not .func()', 2) error('ts object method: be sure to use :func() not .func()', 2) end
end
if t._deleted then if t._deleted then
return false return false
else
return assertTsObjectExists(t)
end end
return tsIsObject(t._tsObjectId)
end, end,
} }
-- Weak-values table for caching Torque object references -- Weak-values table for caching Torque object references
@@ -499,15 +518,13 @@ end
-- Return a Torque object for the object ID string, or create one if none exists -- Return a Torque object for the object ID string, or create one if none exists
toTsObject = function(idiS) toTsObject = function(idiS)
if type(idiS)~='string' then if type(idiS)~='string' then
error('toTsObject: input must be a string', 2) error('toTsObject: input must be a string', 2) end
end
idiS = idiS:lower() idiS = idiS:lower()
if bl._objectsWeak[idiS] then return bl._objectsWeak[idiS] end if bl._objectsWeak[idiS] then return bl._objectsWeak[idiS] end
if not tsBool(_bllua_ts.call('isObject', idiS)) then if not tsIsObject(idiS) then
--error('toTsObject: object \''..idiS..'\' does not exist', 2) end --error('toTsObject: object \''..idiS..'\' does not exist', 2) end
return nil return nil end
end
local className = _bllua_ts.callobj(idiS, 'getClassName') local className = _bllua_ts.callobj(idiS, 'getClassName')
local obj = { local obj = {
@@ -595,15 +612,16 @@ function bl.call(func, ...)
local argsS = arglistToTs(args) local argsS = arglistToTs(args)
return _bllua_ts.call(func, unpack(argsS)) return _bllua_ts.call(func, unpack(argsS))
end end
function bl.eval(code) function bl.eval(code)
return valFromTs(_bllua_ts.eval(code)) return valFromTs(_bllua_ts.eval(code))
end 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.array(name, ...)
local rest = {...}
return name..table.concat(rest, '_')
end
function bl.boolean(val) function bl.boolean(val)
return val~=nil and return val~=nil and
val~=false and val~=false and
@@ -611,7 +629,6 @@ function bl.boolean(val)
--val~='0' and --val~='0' and
val~=0 val~=0
end end
function bl.object(id) function bl.object(id)
if type(id)=='table' and id._tsObjectId then if type(id)=='table' and id._tsObjectId then
return id return id
@@ -621,21 +638,62 @@ 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 .. '\'') print('lookup', tbl, name, set, val)
if name:find('%.') then
local first, rest = name:match('^([^%.:]+)%.(.+)$')
if not isValidFuncName(first) then
error('luaLookup: invalid name \''..tostring(first)..'\'', 3) end
if not tbl[first] then
if set then tbl[first] = {}
else return nil end
end end
-- todo: library fields and object methods return luaLookup(tbl[first], rest, set, val)
local res = _G[fnameS](unpack(args)) 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 tbl[first] then
error('luacall: no object named \''..rest..'\'', 3) end
if not tbl[first][rest] then
error('luacall: no method named \''..rest..'\'', 3) end
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 \''..name..'\'', 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...)
@@ -663,7 +721,6 @@ function bl.schedule(time, cb, ...)
bl._scheduleTable[id] = sch bl._scheduleTable[id] = sch
return sch return sch
end end
function _bllua_schedule_callback(idS) function _bllua_schedule_callback(idS)
local id = tonumber(idS) or local id = tonumber(idS) or
error('_bllua_schedule_callback: invalid id: '..tostring(idS)) error('_bllua_schedule_callback: invalid id: '..tostring(idS))
@@ -682,11 +739,9 @@ function _bllua_process_cmd(cmdS, ...)
if not func then error('_bllua_process_cmd: no cmd named \''..cmd..'\'') end if not func then error('_bllua_process_cmd: no cmd named \''..cmd..'\'') end
func(unpack(args)) --pcall(func, unpack(args)) func(unpack(args)) --pcall(func, unpack(args))
end end
local function addCmd(cmd, func) local function addCmd(cmd, func)
if not isValidFuncName(cmd) then if not isValidFuncName(cmd) then
error('addCmd: invalid function name \'' .. tostring(cmd) .. '\'') error('addCmd: invalid function name \''..tostring(cmd)..'\'') end
end
bl._cmds[cmd] = func bl._cmds[cmd] = func
local arglist = '%a,%b,%c,%d,%e,%f,%g,%h,%i,%j,%k,%l,%m,%n,%o,%p' local arglist = '%a,%b,%c,%d,%e,%f,%g,%h,%i,%j,%k,%l,%m,%n,%o,%p'
_bllua_ts.eval('function '..cmd..'('..arglist..'){'.. _bllua_ts.eval('function '..cmd..'('..arglist..'){'..
@@ -697,7 +752,6 @@ function bl.addServerCmd(name, func)
addCmd('servercmd'..name, func) addCmd('servercmd'..name, func)
bl._forceType['servercmd'..name..':1'] = 'object' bl._forceType['servercmd'..name..':1'] = 'object'
end end
function bl.addClientCmd(name, func) function bl.addClientCmd(name, func)
name = name:lower() name = name:lower()
addCmd('clientcmd'..name, func) addCmd('clientcmd'..name, func)
@@ -709,13 +763,11 @@ function bl.commandToServer(cmd, ...)
_bllua_ts.call('addTaggedString', cmd), _bllua_ts.call('addTaggedString', cmd),
unpack(arglistToTs({...}))) unpack(arglistToTs({...})))
end end
function bl.commandToClient(cmd, ...) function bl.commandToClient(cmd, ...)
_bllua_ts.call('commandToClient', _bllua_ts.call('commandToClient',
_bllua_ts.call('addTaggedString', cmd), _bllua_ts.call('addTaggedString', cmd),
unpack(arglistToTs({...}))) unpack(arglistToTs({...})))
end end
function bl.commandToAll(cmd, ...) function bl.commandToAll(cmd, ...)
_bllua_ts.call('commandToAll', _bllua_ts.call('commandToAll',
_bllua_ts.call('addTaggedString', cmd), _bllua_ts.call('addTaggedString', cmd),
@@ -743,16 +795,14 @@ local function deactivatePackage(pkg)
end end
local hookNargs = 8 local hookNargs = 8
local hookArglistLocal = '%a,%b,%c,%d,%e,%f,%g,%h' local hookArglistLocal = '%a,%b,%c,%d,%e,%f,%g,%h'
local hookArglistGlobal = 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'
'$_bllua_hook_arg1,$_bllua_hook_arg2,$_bllua_hook_arg3,$_bllua_hook_arg4,$_bllua_hook_arg5,$_bllua_hook_arg6,$_bllua_hook_arg7,$_bllua_hook_arg8'
bl._hooks = bl._hooks or {} bl._hooks = bl._hooks or {}
function _bllua_process_hook_before(pkgS, nameS, ...) function _bllua_process_hook_before(pkgS, nameS, ...)
local args = arglistFromTs(nameS, {...}) local args = arglistFromTs(nameS, {...})
local func = bl._hooks[pkgS] and bl._hooks[pkgS][nameS] and local func = bl._hooks[pkgS] and bl._hooks[pkgS][nameS] and
bl._hooks[pkgS][nameS].before bl._hooks[pkgS][nameS].before
if not func then if not func then
error('_bllua_process_hook_before: no hook for ' .. pkgs .. ':' .. nameS) error('_bllua_process_hook_before: no hook for '..pkgs..':'..nameS) end
end
_bllua_ts.setvar('_bllua_hook_abort', '0') _bllua_ts.setvar('_bllua_hook_abort', '0')
func(args) --pcall(func, args) func(args) --pcall(func, args)
if args._return then if args._return then
@@ -763,7 +813,6 @@ function _bllua_process_hook_before(pkgS, nameS, ...)
_bllua_ts.setvar('_bllua_hook_arg'..i, valToTs(args[i])) _bllua_ts.setvar('_bllua_hook_arg'..i, valToTs(args[i]))
end end
end end
function _bllua_process_hook_after(pkgS, nameS, resultS, ...) function _bllua_process_hook_after(pkgS, nameS, resultS, ...)
nameS = nameS:lower() nameS = nameS:lower()
local args = arglistFromTs(nameS, {...}) local args = arglistFromTs(nameS, {...})
@@ -771,12 +820,10 @@ function _bllua_process_hook_after(pkgS, nameS, resultS, ...)
local func = bl._hooks[pkgS] and bl._hooks[pkgS][nameS] and local func = bl._hooks[pkgS] and bl._hooks[pkgS][nameS] and
bl._hooks[pkgS][nameS].after bl._hooks[pkgS][nameS].after
if not func then if not func then
error('_bllua_process_hook_after: no hook for ' .. pkgs .. ':' .. nameS) error('_bllua_process_hook_after: no hook for '..pkgs..':'..nameS) end
end
func(args) --pcall(func, args) func(args) --pcall(func, args)
return valToTs(args._return) return valToTs(args._return)
end end
local function updateHook(pkg, name, hk) local function updateHook(pkg, name, hk)
local beforeCode = hk.before and local beforeCode = hk.before and
('_bllua_luacall("_bllua_process_hook_before", "'..pkg..'","'..name.. ('_bllua_luacall("_bllua_process_hook_before", "'..pkg..'","'..name..
@@ -802,17 +849,13 @@ local function updateHook(pkg, name, hk)
end end
function bl.hook(pkg, name, time, func) function bl.hook(pkg, name, time, func)
if not isValidFuncName(pkg) then if not isValidFuncName(pkg) then
error('bl.hook: argument #1: invalid package name \'' .. tostring(pkg) .. '\'', 2) error('bl.hook: argument #1: invalid package name \''..tostring(pkg)..'\'', 2) end
end
if not isValidFuncNameNs(name) then if not isValidFuncNameNs(name) then
error('bl.hook: argument #2: invalid function name \'' .. tostring(name) .. '\'', 2) error('bl.hook: argument #2: invalid function name \''..tostring(name)..'\'', 2) end
end
if time~='before' and time~='after' then if time~='before' and time~='after' then
error('bl.hook: argument #3: time must be \'before\' or \'after\'', 2) error('bl.hook: argument #3: time must be \'before\' or \'after\'', 2) end
end
if type(func)~='function' then if type(func)~='function' then
error('bl.hook: argument #4: expected a function', 2) error('bl.hook: argument #4: expected a function', 2) end
end
bl._hooks[pkg] = bl._hooks[pkg] or {} bl._hooks[pkg] = bl._hooks[pkg] or {}
bl._hooks[pkg][name] = bl._hooks[pkg][name] or {} bl._hooks[pkg][name] = bl._hooks[pkg][name] or {}
@@ -821,20 +864,16 @@ function bl.hook(pkg, name, time, func)
updateHook(pkg, name, bl._hooks[pkg][name]) updateHook(pkg, name, bl._hooks[pkg][name])
activatePackage(pkg) activatePackage(pkg)
end end
local function tableEmpty(t) local function tableEmpty(t)
return next(t)~=nil return next(t)~=nil
end end
function bl.unhook(pkg, name, time) function bl.unhook(pkg, name, time)
if not isValidFuncName(pkg) then if not isValidFuncName(pkg) then
error('bl.unhook: argument #1: invalid package name \'' .. tostring(pkg) .. '\'', 2) error('bl.unhook: argument #1: invalid package name \''..tostring(pkg)..'\'', 2) end
end
if not isValidFuncNameNs(name) then if not isValidFuncNameNs(name) then
error('bl.unhook: argument #2: invalid function name \'' .. tostring(name) .. '\'', 2) error('bl.unhook: argument #2: invalid function name \''..tostring(name)..'\'', 2) end
end
if 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) error('bl.unhook: argument #3: time must be \'before\' or \'after\'', 2) end
end
if not name then if not name then
if bl._hooks[pkg] then if bl._hooks[pkg] then
@@ -858,8 +897,7 @@ function bl.unhook(pkg, name, time)
updateHook(pkg, name, {}) updateHook(pkg, name, {})
else else
if time~='before' and time~='after' then if time~='before' and time~='after' then
error('bl.unhook: argument #3: time must be nil, \'before\', or \'after\'', 2) error('bl.unhook: argument #3: time must be nil, \'before\', or \'after\'', 2) end
end
bl._hooks[pkg][name][time] = nil bl._hooks[pkg][name][time] = nil
if tableEmpty(bl._hooks[pkg][name]) and tableEmpty(bl._hooks[pkg]) then if tableEmpty(bl._hooks[pkg][name]) and tableEmpty(bl._hooks[pkg]) then
bl._hooks[pkg] = nil bl._hooks[pkg] = nil
@@ -879,16 +917,14 @@ end
-- Container search/raycast -- Container search/raycast
local function vecToTs(v) local function vecToTs(v)
if not isTsVector(v) then if not isTsVector(v) then
error('vecToTs: argument is not a vector', 3) error('vecToTs: argument is not a vector', 3) end
end
return table.concat(v, ' ') return table.concat(v, ' ')
end end
local function maskToTs(mask) local function maskToTs(mask)
if type(mask)=='string' then if type(mask)=='string' then
local val = tsTypesByName[mask:lower()] local val = tsTypesByName[mask:lower()]
if not val then if not val then
error('maskToTs: invalid mask \'' .. mask .. '\'', 3) error('maskToTs: invalid mask \''..mask..'\'', 3) end
end
return tostring(val) return tostring(val)
elseif type(mask)=='table' then elseif type(mask)=='table' then
local tval = 0 local tval = 0
@@ -898,8 +934,7 @@ local function maskToTs(mask)
local val = tsTypesByName[v:lower()] local val = tsTypesByName[v:lower()]
if not val then if not val then
error('maskToTs: invalid mask \''..v.. error('maskToTs: invalid mask \''..v..
'\' at index ' .. i .. ' in mask list', 3) '\' at index '..i..' in mask list', 3) end
end
tval = tval + val tval = tval + val
seen[v] = true seen[v] = true
end end
@@ -941,7 +976,6 @@ function bl.raycast(start, stop, mask, ignores)
return hit, pos, norm return hit, pos, norm
end end
end end
local function tsContainerSearchIterator() local function tsContainerSearchIterator()
local retS = _bllua_ts.call('containerSearchNext') local retS = _bllua_ts.call('containerSearchNext')
if retS=='0' then if retS=='0' then
@@ -958,12 +992,10 @@ function bl.boxSearch(pos, size, mask)
_bllua_ts.call('initContainerBoxSearch', posS, sizeS, maskS) _bllua_ts.call('initContainerBoxSearch', posS, sizeS, maskS)
return tsContainerSearchIterator return tsContainerSearchIterator
end end
function bl.radiusSearch(pos, radius, mask) function bl.radiusSearch(pos, radius, mask)
local posS = vecToTs(pos) local posS = vecToTs(pos)
if type(radius)~='number' then if type(radius)~='number' then
error('bl.radiusSearch: argument #2: radius must be a number', 2) error('bl.radiusSearch: argument #2: radius must be a number', 2) end
end
local radiusS = tostring(radius) local radiusS = tostring(radius)
local maskS = maskToTs(mask) local maskS = maskToTs(mask)
@@ -1001,8 +1033,7 @@ local function createTsObj(keyword, class, name, inherit, props)
if props then if props then
for k,v in pairs(props) do for k,v in pairs(props) do
if not isValidFuncName(k) then if not isValidFuncName(k) then
error('bl.new/bl.datablock: invalid property name \'' .. k .. '\'') error('bl.new/bl.datablock: invalid property name \''..k..'\'') end
end
table.insert(propsT, k..'="'..valToTs(v)..'";') table.insert(propsT, k..'="'..valToTs(v)..'";')
end end
end end
@@ -1013,8 +1044,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/bl.datablock: failed to create object', 3) error('bl.new/bl.datablock: failed to create object', 3) end
end
return obj return obj
end end
@@ -1039,15 +1069,13 @@ local function parseTsDecl(decl)
(inherit==nil or isValidFuncName(inherit)) ) then (inherit==nil or isValidFuncName(inherit)) ) then
error('bl.new/bl.datablock: invalid decl \''..decl..'\'\n'.. error('bl.new/bl.datablock: invalid decl \''..decl..'\'\n'..
'must be of the format: \'className\', \'className name\', '.. 'must be of the format: \'className\', \'className name\', '..
'\'className :inherit\', or \'className name:inherit\'', 3) '\'className :inherit\', or \'className name:inherit\'', 3) end
end
return class, name, inherit return class, name, inherit
end end
function bl.new(decl, props) function bl.new(decl, props)
local class, name, inherit = parseTsDecl(decl) local class, name, inherit = parseTsDecl(decl)
return createTsObj('new', class, name, inherit, props) return createTsObj('new', class, name, inherit, props)
end end
function bl.datablock(decl, props) function bl.datablock(decl, props)
local class, name, inherit = parseTsDecl(decl) local class, name, inherit = parseTsDecl(decl)
if not name then error('bl.datablock: must specify a name', 2) end if not name then error('bl.datablock: must specify a name', 2) end

View File

@@ -1,3 +1,4 @@
-- 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.
@@ -6,10 +7,7 @@ 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
@@ -84,9 +82,7 @@ local function new_file_obj(fn)
return file return file
end end
local function tflip(t) local function tflip(t) local u = {}; for _, n in ipairs(t) do u[n] = true end; return u; end
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'
} }
@@ -102,8 +98,7 @@ local function io_open_absolute(fn, mode)
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' return nil, 'Files in zips can only be opened in read mode' end
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)
@@ -111,7 +106,6 @@ 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
@@ -132,19 +126,13 @@ 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, 'r', 2) local fi, err, fn2 = io.open(fn, nil, 2)
if not fi then error('Error opening file \'' .. tostring(fn2) .. '\': ' .. tostring(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)
@@ -156,7 +144,7 @@ function dofile(fn, errn)
errn = errn or 1 errn = errn or 1
local fi, err, fn2 = io.open(fn, 'r', errn+1) local fi, err, fn2 = io.open(fn, 'r', errn+1)
if not fi then error('Error executing file \'' .. tostring(fn2) .. '\': ' .. tostring(err), errn + 1) end if not fi then error('Error executing file \''..fn2..'\': '..err, errn+1) end
print('Executing '..fn2) print('Executing '..fn2)
local text = fi:read('*a') local text = fi:read('*a')
@@ -204,23 +192,13 @@ 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 .. ')' code = 'print('..code..')' end
end
local f,e = loadstring(code) local f,e = loadstring(code)
if f then if f then
return f() return f()

View File

@@ -49,26 +49,4 @@ 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");

7
src/util/matrix.lua Normal file
View File

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

View File

@@ -1,3 +1,4 @@
-- Basic functionality that should be standard in Lua -- Basic functionality that should be standard in Lua
@@ -6,7 +7,6 @@
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 = {...}
@@ -18,7 +18,6 @@ function table.map(f, ...)
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 = {}
@@ -29,21 +28,18 @@ function table.map_list(f, ...)
end end
return u return u
end 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
@@ -53,7 +49,6 @@ 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 = {...}
@@ -62,7 +57,6 @@ 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
@@ -74,7 +68,6 @@ 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
@@ -82,34 +75,29 @@ 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 = {}
@@ -118,7 +106,6 @@ 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
@@ -126,7 +113,6 @@ 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)
@@ -193,13 +179,14 @@ valueToString = function(v, tabLevel, seen)
return tostring(v) return tostring(v)
else else
--error('table.tostring: table contains a '..t..' value, cannot serialize') --error('table.tostring: table contains a '..t..' value, cannot serialize')
return 'nil --[[ cannot serialize ' .. t .. ': ' .. tostring(v) .. ' ]]' return 'nil --[[ cannot serialize '..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
@@ -207,8 +194,7 @@ 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) error('string.split: argument #1: expected string, got '..type(str), 2) end
end
if sep==nil or sep=='' then if sep==nil or sep=='' then
local t = {} local t = {}
local ns = #str local ns = #str
@@ -234,7 +220,6 @@ 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 = {}
@@ -244,13 +229,11 @@ 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
@@ -266,20 +249,15 @@ 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 if i<=#s then return s:sub(i,i)
return s:sub(i, i) else return nil end
else
return nil
end end
end end
end
-- Escape sequences -- Escape sequences
local defaultEscapes = { local defaultEscapes = {
['\\'] = '\\\\', ['\\'] = '\\\\',
@@ -299,7 +277,6 @@ 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 = {
['\\'] = '\\', ['\\'] = '\\',
@@ -330,6 +307,7 @@ function string.unescape(s, escapeChar, unescapes)
return table.concat(t) return table.concat(t)
end end
-- IO -- IO
io = io or {} io = io or {}
@@ -341,7 +319,6 @@ 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')
@@ -351,24 +328,23 @@ 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 if m==0 or math.abs(m)<1e-15 or math.abs(m-b)<1e-15 then return 0
return 0 else return m end
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

@@ -235,4 +235,7 @@ vector_new = function(vi)
end end
vector = vector_new vector = vector_new
print(' Executed vector.lua')
return vector_new return vector_new