initial
This commit is contained in:
105
src/bllua4.cpp
Normal file
105
src/bllua4.cpp
Normal file
@@ -0,0 +1,105 @@
|
||||
// BlockLua (bllua4): Simple Lua interface for TorqueScript
|
||||
|
||||
// Includes
|
||||
|
||||
#include <Windows.h>
|
||||
#include <Psapi.h>
|
||||
|
||||
#include "lua.hpp"
|
||||
#include "BlHooks.cpp"
|
||||
#include "BlFuncs.cpp"
|
||||
|
||||
#include "luainterp.cpp"
|
||||
#include "lualibts.cpp"
|
||||
lua_State* gL;
|
||||
#include "tsliblua.cpp"
|
||||
|
||||
// Global variables
|
||||
|
||||
// Setup
|
||||
|
||||
// Hack to encode the contents of text files as static strings
|
||||
#define INCLUDE_BIN(varname, filename) \
|
||||
asm("_" #varname ": .incbin \"" filename "\""); \
|
||||
asm(".byte 0"); \
|
||||
extern char varname[];
|
||||
INCLUDE_BIN(bll_fileLuaEnvSafe, "lua-env-safe.lua");
|
||||
INCLUDE_BIN(bll_fileLuaEnv , "lua-env.lua");
|
||||
INCLUDE_BIN(bll_fileTsEnv , "ts-env.cs" );
|
||||
INCLUDE_BIN(bll_fileLuaStd , "util/std.lua");
|
||||
INCLUDE_BIN(bll_fileLuaVector , "util/vector.lua");
|
||||
INCLUDE_BIN(bll_fileLuaLibts , "util/libts.lua");
|
||||
INCLUDE_BIN(bll_fileTsLibtsSupport, "util/libts-support.cs");
|
||||
INCLUDE_BIN(bll_fileLuaLibbl , "util/libbl.lua" );
|
||||
INCLUDE_BIN(bll_fileLuaLibblTypes , "util/libbl-types.lua");
|
||||
INCLUDE_BIN(bll_fileTsLibblSupport, "util/libbl-support.cs");
|
||||
INCLUDE_BIN(bll_fileLoadaddons , "util/loadaddons.cs");
|
||||
|
||||
#define BLL_LOAD_LUA(lstate, vname) \
|
||||
if(!bll_LuaEval(lstate, vname)) { \
|
||||
BlPrintf(" Error executing " #vname); \
|
||||
return false; \
|
||||
}
|
||||
|
||||
bool init() {
|
||||
BlHooksInit();
|
||||
BlPrintf("BlockLua: Loading");
|
||||
|
||||
BlFuncsInit();
|
||||
|
||||
// Initialize Lua environment
|
||||
gL = lua_open();
|
||||
luaL_openlibs(gL);
|
||||
|
||||
// Expose TS API to Lua
|
||||
llibbl_init(gL);
|
||||
|
||||
// Set up Lua environment
|
||||
BLL_LOAD_LUA(gL, bll_fileLuaEnv);
|
||||
#ifndef BLLUA_UNSAFE
|
||||
BLL_LOAD_LUA(gL, bll_fileLuaEnvSafe);
|
||||
#endif
|
||||
|
||||
// Expose Lua API to TS
|
||||
BlAddFunction(NULL, NULL, "_bllua_luacall", bll_ts_luacall, "LuaCall(name, ...) - Call Lua function and return result", 2, 20);
|
||||
BlEval(bll_fileTsEnv);
|
||||
|
||||
// Load utilities
|
||||
BLL_LOAD_LUA(gL, bll_fileLuaStd);
|
||||
BLL_LOAD_LUA(gL, bll_fileLuaVector);
|
||||
BLL_LOAD_LUA(gL, bll_fileLuaLibts);
|
||||
BlEval(bll_fileTsLibtsSupport);
|
||||
BLL_LOAD_LUA(gL, bll_fileLuaLibbl);
|
||||
BLL_LOAD_LUA(gL, bll_fileLuaLibblTypes);
|
||||
BlEval(bll_fileTsLibblSupport);
|
||||
BlEval(bll_fileLoadaddons);
|
||||
|
||||
BlEval("$_bllua_active = 1;");
|
||||
|
||||
BlPrintf(" BlockLua: Done Loading");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool deinit() {
|
||||
BlPrintf("BlockLua: Unloading");
|
||||
|
||||
BlEval("deactivatePackage(_bllua_main);");
|
||||
BlEval("$_bllua_active = 0;");
|
||||
bll_LuaEval(gL, "for _,f in pairs(_bllua_on_unload) do f() end");
|
||||
|
||||
lua_close(gL);
|
||||
|
||||
BlHooksDeinit();
|
||||
BlFuncsDeinit();
|
||||
|
||||
BlPrintf(" BlockLua: Done Unloading");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool __stdcall DllMain(HINSTANCE hinstance, DWORD reason, void* reserved) {
|
||||
switch(reason) {
|
||||
case DLL_PROCESS_ATTACH: return init();
|
||||
case DLL_PROCESS_DETACH: return deinit();
|
||||
default : return true;
|
||||
}
|
||||
}
|
||||
142
src/lua-env-safe.lua
Normal file
142
src/lua-env-safe.lua
Normal file
@@ -0,0 +1,142 @@
|
||||
-- Sanitize the Lua environment to:
|
||||
-- Prevent scripts from accessing files outside the game directory
|
||||
-- Prevent usage of libraries other than
|
||||
|
||||
|
||||
-- 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
|
||||
|
||||
-- Save banned global variables for wrapping with safe functions
|
||||
local old_io = io
|
||||
local old_require = require
|
||||
local old_os = os
|
||||
local old_debug = debug
|
||||
local old_package = package
|
||||
|
||||
-- Remove all global variables except a whitelist
|
||||
local ok_names = tmap {
|
||||
'_G', '_bllua_ts', '_bllua_on_unload', '_bllua_on_error',
|
||||
'string', 'table', 'math', 'coroutine', 'bit',
|
||||
'pairs', 'ipairs', 'next', 'unpack', 'select',
|
||||
'error', 'assert', 'pcall', 'xpcall',
|
||||
'type', 'tostring', 'tonumber',
|
||||
'loadstring',
|
||||
'getmetatable', 'setmetatable',
|
||||
'rawget', 'rawset', 'rawequal', 'rawlen',
|
||||
'module', '_VERSION',
|
||||
}
|
||||
local not_ok_names = {}
|
||||
for n, _ in pairs(_G) do
|
||||
if not ok_names[n] then
|
||||
table.insert(not_ok_names, n)
|
||||
end
|
||||
end
|
||||
for _, n in ipairs(not_ok_names) do
|
||||
_G[n] = nil
|
||||
end
|
||||
|
||||
-- Sanitize file paths to point only to allowed files within the game directory
|
||||
-- List of allowed directories for reading/writing
|
||||
local allowed_dirs = tmap {
|
||||
'add-ons', 'base', 'config', 'saves', 'screenshots', 'shaders'
|
||||
}
|
||||
-- List of allowed directories for reading only
|
||||
local allowed_dirs_readonly = tmap {
|
||||
'lualib'
|
||||
}
|
||||
-- List of disallowed file extensions - basically executable file extensions
|
||||
-- Note that even without this protection, exploiting would still require somehow
|
||||
-- getting a file within the allowed directories to autorun,
|
||||
-- so this is just a precaution.
|
||||
local disallowed_exts = tmap {
|
||||
-- windows
|
||||
'bat','bin','cab','cmd','com','cpl','ex_','exe','gadget','inf','ins','inx','isu',
|
||||
'job','jse','lnk','msc','msi','msp','mst','paf','pif','ps1','reg','rgs','scr',
|
||||
'sct','shb','shs','u3p','vb','vbe','vbs','vbscript','ws','wsf','wsh',
|
||||
-- linux
|
||||
'csh','ksh','out','run','sh',
|
||||
-- mac/other
|
||||
'action','apk','app','command','ipa','osx','prg','workflow',
|
||||
}
|
||||
-- Arguments: file name (relative to game directory), boolean true if only reading
|
||||
-- Return: clean file path if allowed (or nil if disallowed),
|
||||
-- error string (or nil if allowed)
|
||||
local function safe_path(fn, readonly)
|
||||
fn = fn:gsub('\\', '/')
|
||||
fn = fn:gsub('^ +', '')
|
||||
fn = fn:gsub(' +$', '')
|
||||
-- whitelist characters
|
||||
local ic = fn:find('[^a-zA-Z0-9_%-/ %.]')
|
||||
if ic then
|
||||
return nil, 'Filename \''..fn..'\' contains invalid character \''..
|
||||
fn:sub(ic, ic)..'\' at position '..ic
|
||||
end
|
||||
-- disallow up-dirs, absolute paths, and relative paths
|
||||
-- './' and '../' are possible in scripts, because they're processed into
|
||||
-- absolute paths in util.lua before reaching here
|
||||
if fn:find('^%.') or fn:find('%.%.') or fn:find(':') or fn:find('^/') then
|
||||
return nil, 'Filename \''..fn..'\' contains invalid sequence'
|
||||
end
|
||||
-- allow only whitelisted dirs
|
||||
local dir = fn:match('^([^/]+)/')
|
||||
if (not dir) or (
|
||||
(not allowed_dirs[dir:lower()]) and
|
||||
((not readonly) or (not allowed_dirs_readonly[dir:lower()])) ) then
|
||||
return nil, 'filename is in disallowed directory '..(dir or 'nil')
|
||||
end
|
||||
-- disallow blacklisted extensions or no extension
|
||||
local ext = fn:match('%.([^/%.]+)$')
|
||||
if (not ext) or (disallowed_exts[ext:lower()]) then
|
||||
return nil, 'Filename \''..fn..'\' has disallowed extension \''..
|
||||
(ext or '')..'\''
|
||||
end
|
||||
return fn, nil
|
||||
end
|
||||
|
||||
-- Wrap io.open with path sanitization
|
||||
function _bllua_io_open(fn, md)
|
||||
md = md or 'r'
|
||||
local readonly = md=='r' or md=='rb'
|
||||
local fns, err = safe_path(fn, readonly)
|
||||
if fns then
|
||||
return old_io.open(fns, md)
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
-- Allow io.type (works on file handles returned by io.open)
|
||||
function _bllua_io_type(f)
|
||||
return old_io.type(f)
|
||||
end
|
||||
|
||||
-- Wrap require with a blacklist for unsafe built-in modules
|
||||
-- List of allowed packages to require
|
||||
-- Note that util.lua wraps this and provides 'require',
|
||||
-- only falling back here if the package is not found in user files
|
||||
local disallowed_packages = tmap {
|
||||
'ffi', 'debug', 'package', 'io', 'os',
|
||||
'_bllua_ts',
|
||||
}
|
||||
function _bllua_requiresecure(name)
|
||||
if name:find('[^a-zA-Z0-9_%-%.]') or name:find('%.%.') or
|
||||
name:find('^%.') or name:find('%.$') then
|
||||
error('require: package name contains invalid character', 3)
|
||||
elseif disallowed_packages[name] then
|
||||
error('require: attempt to require disallowed module \''..name..'\'', 3)
|
||||
else
|
||||
-- todo: reimplement require to not use package.* stuff?
|
||||
return old_require(name)
|
||||
end
|
||||
end
|
||||
package = {
|
||||
seeall = old_package.seeall,
|
||||
}
|
||||
|
||||
-- Provide limited debug
|
||||
debug = {
|
||||
traceback = old_debug.traceback,
|
||||
getinfo = old_debug.getinfo,
|
||||
getfilename = old_debug.getfilename, -- defined in lua.env.lua
|
||||
}
|
||||
|
||||
_bllua_ts.echo(' Executed bllua-env-safe.lua')
|
||||
40
src/lua-env.lua
Normal file
40
src/lua-env.lua
Normal file
@@ -0,0 +1,40 @@
|
||||
-- Set up the Lua environment
|
||||
|
||||
-- Point old require to modules/lua in game directory
|
||||
package.path = 'modules\\lualib\\?.lua;modules\\lualib\\?\\init.lua;'
|
||||
package.cpath = 'modules\\lualib\\?.dll;'
|
||||
|
||||
-- Set up table of unload/cleanup callbacks
|
||||
_bllua_on_unload = {}
|
||||
|
||||
-- Utility for getting the current filename
|
||||
function debug.getfilename(level)
|
||||
if type(level) == 'number' then level = level+1 end
|
||||
local info = debug.getinfo(level)
|
||||
if not info then return nil end
|
||||
local filename = info.source:match('^%-%-%[%[([^%]]+)%]%]')
|
||||
return filename
|
||||
end
|
||||
|
||||
-- Called when pcall fails on a ts->lua call, used to print detailed error info
|
||||
function _bllua_on_error(err)
|
||||
err = err:match(': (.+)$') or err
|
||||
local tracelines = {err}
|
||||
local level = 2
|
||||
while true do
|
||||
local info = debug.getinfo(level)
|
||||
if not info then break end
|
||||
local filename = debug.getfilename(level) or info.short_src
|
||||
local funcname = info.name
|
||||
if funcname=='dofile' then break end
|
||||
table.insert(tracelines, string.format('%s:%s in function \'%s\'',
|
||||
filename,
|
||||
info.currentline==-1 and '' or info.currentline..':',
|
||||
funcname
|
||||
))
|
||||
level = level+1
|
||||
end
|
||||
return table.concat(tracelines, '\n')
|
||||
end
|
||||
|
||||
_bllua_ts.echo(' Executed bllua-env.lua')
|
||||
68
src/luainterp.cpp
Normal file
68
src/luainterp.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
|
||||
// Handle errors with a Lua function, defined in lua-env.lua
|
||||
int bll_error_handler(lua_State *L) {
|
||||
lua_getfield(L, LUA_GLOBALSINDEX, "_bllua_on_error");
|
||||
if (!lua_isfunction(L, -1)) {
|
||||
BlPrintf(" Lua error handler: _bllua_on_error not defined.");
|
||||
lua_pop(L, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// move function to before arg
|
||||
int hpos = lua_gettop(L) - 1;
|
||||
lua_insert(L, hpos);
|
||||
|
||||
lua_pcall(L, 1, 1, 0);
|
||||
return 1;
|
||||
}
|
||||
int bll_pcall(lua_State* L, int nargs, int nret) {
|
||||
// calculate stack position for message handler
|
||||
int hpos = lua_gettop(L) - nargs;
|
||||
// push custom error message handler
|
||||
lua_pushcfunction(L, bll_error_handler);
|
||||
// move it before function and arguments
|
||||
lua_insert(L, hpos);
|
||||
// call lua_pcall function with custom handler
|
||||
int ret = lua_pcall(L, nargs, nret, hpos);
|
||||
// remove custom error message handler from stack
|
||||
lua_remove(L, hpos);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Display the last Lua error in the BL console
|
||||
void bll_printError(lua_State* L, const char* operation, const char* item) {
|
||||
//error_handler(L);
|
||||
BlPrintf("\x03Lua error: %s", lua_tostring(L, -1));
|
||||
BlPrintf("\x03 (%s: %s)", operation, item);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
// Eval a string of Lua code
|
||||
bool bll_LuaEval(lua_State* L, const char* str) {
|
||||
if(luaL_loadbuffer(L, str, strlen(str), "input") || bll_pcall(L, 0, 1)) {
|
||||
bll_printError(L, "eval", str);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Convert a Lua stack entry into a string for providing to TS
|
||||
// Use static buffer to avoid excessive malloc
|
||||
#define BLL_ARG_COUNT 20
|
||||
#define BLL_ARG_MAX 8192
|
||||
char bll_arg_buffer[BLL_ARG_COUNT][BLL_ARG_MAX];
|
||||
bool bll_toarg(lua_State* L, char* buf, int i, bool err) {
|
||||
if(lua_isstring(L, i)) {
|
||||
const char* str = lua_tostring(L, i);
|
||||
if(strlen(str) >= BLL_ARG_MAX) {
|
||||
if(err) luaL_error(L, "argument to TS is too long - max length is 8192");
|
||||
return true;
|
||||
} else {
|
||||
strcpy(buf, str);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if(err) luaL_error(L, "argument to TS must be a string");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
159
src/lualibts.cpp
Normal file
159
src/lualibts.cpp
Normal file
@@ -0,0 +1,159 @@
|
||||
//run ../compile
|
||||
|
||||
// TS -> Lua API
|
||||
|
||||
// Call a TS function from Lua, push the result to Lua stack
|
||||
int bll_TsCall(lua_State* L, const char* oname, const char* fname, int argc, int ofs) {
|
||||
ADDR obj = (ADDR)NULL;
|
||||
if(oname) {
|
||||
obj = BlObject(oname);
|
||||
if(!obj) { return luaL_error(L, "Lua->TS call: Object not found"); }
|
||||
}
|
||||
|
||||
if(argc > BLL_ARG_COUNT) { return luaL_error(L, "Lua->TS call: Too many arguments (Max is 19)"); }
|
||||
|
||||
char* argv[BLL_ARG_COUNT];
|
||||
for(int i=0; i<argc; i++) {
|
||||
char* argbuf = bll_arg_buffer[i];
|
||||
argv[i] = argbuf;
|
||||
bll_toarg(L, argbuf, i+ofs+1, true);
|
||||
}
|
||||
|
||||
// /:^| /
|
||||
const char* res;
|
||||
if(obj) {
|
||||
switch(argc) {
|
||||
case 0: res = BlCallObj(obj, fname); break; // no idea why this happens sometimes, it shouldnt be possible
|
||||
case 1: res = BlCallObj(obj, fname); break;
|
||||
case 2: res = BlCallObj(obj, fname, argv[0]); break;
|
||||
case 3: res = BlCallObj(obj, fname, argv[0], argv[1]); break;
|
||||
case 4: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2]); break;
|
||||
case 5: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3]); break;
|
||||
case 6: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4]); break;
|
||||
case 7: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]); break;
|
||||
case 8: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6]); break;
|
||||
case 9: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7]); break;
|
||||
case 10: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8]); break;
|
||||
case 11: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9]); break;
|
||||
case 12: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10]); break;
|
||||
case 13: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11]); break;
|
||||
case 14: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12]); break;
|
||||
case 15: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13]); break;
|
||||
case 16: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14]); break;
|
||||
case 17: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15]); break;
|
||||
case 18: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16]); break;
|
||||
case 19: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], argv[17]); break;
|
||||
case 20: res = BlCallObj(obj, fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], argv[17], argv[18]); break;
|
||||
default: res = ""; luaL_error(L, "Lua->TS object call: Too many arguments (Max is 19)");
|
||||
}
|
||||
} else {
|
||||
switch(argc) {
|
||||
case 0: res = BlCall(fname); break;
|
||||
case 1: res = BlCall(fname, argv[0]); break;
|
||||
case 2: res = BlCall(fname, argv[0], argv[1]); break;
|
||||
case 3: res = BlCall(fname, argv[0], argv[1], argv[2]); break;
|
||||
case 4: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3]); break;
|
||||
case 5: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4]); break;
|
||||
case 6: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]); break;
|
||||
case 7: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6]); break;
|
||||
case 8: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7]); break;
|
||||
case 9: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8]); break;
|
||||
case 10: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9]); break;
|
||||
case 11: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10]); break;
|
||||
case 12: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11]); break;
|
||||
case 13: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12]); break;
|
||||
case 14: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13]); break;
|
||||
case 15: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14]); break;
|
||||
case 16: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15]); break;
|
||||
case 17: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16]); break;
|
||||
case 18: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], argv[17]); break;
|
||||
case 19: res = BlCall(fname, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], argv[17], argv[18]); break;
|
||||
default: res = ""; luaL_error(L, "Lua->TS call: Too many arguments (Max is 19)");
|
||||
}
|
||||
}
|
||||
|
||||
lua_pushstring(L, res);
|
||||
|
||||
return 1;
|
||||
}
|
||||
// Lua lib function: ts.call
|
||||
int bll_lua_tscall(lua_State* L) {
|
||||
int argc = lua_gettop(L)-1; // number of arguments after function name
|
||||
if(argc < 0) return luaL_error(L, "_bllua_ts.call: Must provide a function name");
|
||||
|
||||
const char* fname = luaL_checkstring(L, 1);
|
||||
|
||||
return bll_TsCall(L, NULL, fname, argc, 1);
|
||||
}
|
||||
// Lua lib function: ts.callobj
|
||||
int bll_lua_tscallobj(lua_State* L) {
|
||||
int argc = lua_gettop(L)-2; // number of arguments after function name and object?
|
||||
if(argc < 0) return luaL_error(L, "_bllua_ts.callobj: Must provide an object and function name");
|
||||
|
||||
const char* oname = luaL_checkstring(L, 1);
|
||||
const char* fname = luaL_checkstring(L, 2);
|
||||
|
||||
return bll_TsCall(L, oname, fname, argc, 2);
|
||||
}
|
||||
|
||||
// Lua lib function: ts.getvar
|
||||
int bll_lua_tsgetvar(lua_State* L) {
|
||||
const char* vname = luaL_checkstring(L, 1);
|
||||
|
||||
const char* var = BlGetVar(vname);
|
||||
lua_pushstring(L, var);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Lua lib function: ts.getfield
|
||||
int bll_lua_tsgetfield(lua_State* L) {
|
||||
const char* oname = luaL_checkstring(L, 1);
|
||||
const char* vname = luaL_checkstring(L, 2);
|
||||
ADDR obj = BlObject(oname);
|
||||
if(!obj) { return luaL_error(L, "_bllua_ts.getfield: Object not found"); }
|
||||
|
||||
const char* val = BlGetField(obj, vname, NULL);
|
||||
lua_pushstring(L, val);
|
||||
return 1;
|
||||
}
|
||||
// Lua lib function: ts.setfield
|
||||
int bll_lua_tssetfield(lua_State* L) {
|
||||
const char* oname = luaL_checkstring(L, 1);
|
||||
const char* vname = luaL_checkstring(L, 2);
|
||||
const char* val = luaL_checkstring(L, 3);
|
||||
ADDR obj = BlObject(oname);
|
||||
if(!obj) { return luaL_error(L, "_bllua_ts.setfield: Object not found"); }
|
||||
|
||||
BlSetField(obj, vname, NULL, val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Lua lib function: ts.eval
|
||||
int bll_lua_tseval(lua_State* L) {
|
||||
const char* str = luaL_checkstring(L, 1);
|
||||
const char* res = BlEval(str);
|
||||
lua_pushstring(L, res);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Lua lib function: ts.echo
|
||||
// Print to BL console - used in Lua print implementation
|
||||
int bll_lua_tsecho(lua_State* L) {
|
||||
const char* str = luaL_checkstring(L, 1);
|
||||
BlPrintf("%s", str);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const luaL_Reg bll_lua_reg[] = {
|
||||
{"call" , bll_lua_tscall },
|
||||
{"callobj" , bll_lua_tscallobj },
|
||||
{"getvar" , bll_lua_tsgetvar },
|
||||
{"getfield", bll_lua_tsgetfield},
|
||||
{"setfield", bll_lua_tssetfield},
|
||||
{"eval" , bll_lua_tseval },
|
||||
{"echo" , bll_lua_tsecho },
|
||||
{NULL, NULL},
|
||||
};
|
||||
void llibbl_init(lua_State* L) {
|
||||
luaL_register(L, "_bllua_ts", bll_lua_reg);
|
||||
}
|
||||
26
src/ts-env.cs
Normal file
26
src/ts-env.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
// Built-in functions
|
||||
// Eval'd after BLLua4 has loaded the Lua environment and API
|
||||
|
||||
// Public Lua library for TS
|
||||
function luacall(%func, %a,%b,%c,%d,%e,%f,%g,%h,%i,%j,%k,%l,%m,%n,%o,%p) {
|
||||
if($_bllua_active)
|
||||
return _bllua_luacall(%func, %a,%b,%c,%d,%e,%f,%g,%h,%i,%j,%k,%l,%m,%n,%o,%p);
|
||||
}
|
||||
function luaexec(%fn) {
|
||||
if($_bllua_active)
|
||||
return _bllua_luacall("_bllua_exec", %fn);
|
||||
}
|
||||
function luaeval(%code) {
|
||||
if($_bllua_active)
|
||||
return _bllua_luacall("_bllua_eval", %code);
|
||||
}
|
||||
function luaget(%name) {
|
||||
if($_bllua_active)
|
||||
return _bllua_luacall("_bllua_getvar", %name);
|
||||
}
|
||||
function luaset(%name, %val) {
|
||||
if($_bllua_active)
|
||||
_bllua_luacall("_bllua_setvar", %name, %val);
|
||||
}
|
||||
|
||||
echo(" Executed bllua-env.cs");
|
||||
25
src/tsliblua.cpp
Normal file
25
src/tsliblua.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
// Call a Lua function from TS, return true if success - result will be on Lua stack
|
||||
bool bll_LuaCall(const char* fname, int argc, const char* argv[]) {
|
||||
lua_getglobal(gL, fname);
|
||||
for(int i=0; i<argc; i++) {
|
||||
lua_pushstring(gL, argv[i]);
|
||||
}
|
||||
if(bll_pcall(gL, argc, 1)) {
|
||||
bll_printError(gL, "call", fname);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// TS lib function: luacall
|
||||
const char* bll_ts_luacall(ADDR obj, int argc, const char* argv[]) {
|
||||
if(argc<2) return "";
|
||||
|
||||
if(!bll_LuaCall(argv[1], argc-2, &argv[2])) { return ""; }
|
||||
|
||||
char* retbuf = BlReturnBuffer(BLL_ARG_MAX);
|
||||
bll_toarg(gL, retbuf, -1, false); // provide returned value to ts
|
||||
lua_pop(gL, 1); // pop returned value
|
||||
return retbuf;
|
||||
}
|
||||
44
src/util/libbl-support.cs
Normal file
44
src/util/libbl-support.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
|
||||
// Private - Utilities used by libbl from the Lua side
|
||||
|
||||
package _bllua_smartEval {
|
||||
// Allow prepending ' to console commands to eval in lua instead of TS
|
||||
// Also wraps TS lines with echo(...); if they don't end in ; or }
|
||||
function ConsoleEntry::eval() {
|
||||
%text = ConsoleEntry.getValue();
|
||||
if(getSubStr(%text, 0, 1)$="\'") { // lua eval
|
||||
if($_bllua_active) {
|
||||
%text = getSubStr(%text, 1, strLen(%text));
|
||||
echo("Lua ==> " @ %text);
|
||||
luacall("_bllua_smarteval", %text);
|
||||
} else {
|
||||
echo("Lua: not loaded");
|
||||
}
|
||||
ConsoleEntry.setValue("");
|
||||
} else {
|
||||
%textT = trim(%text);
|
||||
if(strLen(%textT)>0) {
|
||||
%lastChar = getSubStr(%textT, strLen(%textT)-1, 1);
|
||||
if(%lastChar!$=";" && %lastChar!$="}") {
|
||||
%text = "echo(" @ %text @ ");";
|
||||
ConsoleEntry.setValue(%text);
|
||||
}
|
||||
}
|
||||
parent::eval();
|
||||
}
|
||||
}
|
||||
};
|
||||
activatePackage(_bllua_smartEval);
|
||||
|
||||
package _bllua_objectDeletionHook {
|
||||
// Hook object deletion to clean up its lua data
|
||||
function SimObject::onRemove(%obj) {
|
||||
// note: no parent function exists by default,
|
||||
// and this is loaded before any addons
|
||||
//parent::onRemove(%obj);
|
||||
if($_bllua_active) luacall("_bllua_objectDeleted", %obj);
|
||||
}
|
||||
};
|
||||
activatePackage(_bllua_objectDeletionHook);
|
||||
|
||||
echo(" Executed libbl-support.cs");
|
||||
3706
src/util/libbl-types.lua
Normal file
3706
src/util/libbl-types.lua
Normal file
File diff suppressed because it is too large
Load Diff
840
src/util/libbl.lua
Normal file
840
src/util/libbl.lua
Normal file
@@ -0,0 +1,840 @@
|
||||
-- bl library
|
||||
-- Main lua-side functionality of bllua,
|
||||
-- provided through the global table 'bl.'
|
||||
|
||||
-- todo: set
|
||||
|
||||
local _bllua_ts = ts
|
||||
|
||||
bl = bl or {}
|
||||
|
||||
-- Misc
|
||||
-- Apply a function to each element in a list, building a new list from the returns
|
||||
local function map(t,f)
|
||||
local u = {}
|
||||
for i,v in ipairs(t) do
|
||||
u[i] = f(v)
|
||||
end
|
||||
return u
|
||||
end
|
||||
local function isValidFuncName(name)
|
||||
return type(name)=='string' and name:find('^[a-zA-Z0-9_]+$')
|
||||
end
|
||||
local function isValidFuncNameNs(name)
|
||||
return type(name)=='string' and (
|
||||
name:find('^[a-zA-Z0-9_]+$') or
|
||||
name:find('^[a-zA-Z0-9_]+::[a-zA-Z0-9_]+$') )
|
||||
end
|
||||
local function isValidFuncNameNsArgn(name)
|
||||
return type(name)=='string' and (
|
||||
name:find('^[a-zA-Z0-9_]+$') or
|
||||
name:find('^[a-zA-Z0-9_]+%.[a-zA-Z0-9_]+$') or
|
||||
name:find('^[a-zA-Z0-9_]+::[a-zA-Z0-9_]+$') or
|
||||
name:find('^[a-zA-Z0-9_]+:[0-9]+$') or
|
||||
name:find('^[a-zA-Z0-9_]+::[a-zA-Z0-9_]+:[0-9]+$') )
|
||||
end
|
||||
-- Whether a var can be converted into a TS vector
|
||||
local function isTsVector(val)
|
||||
if type(val)~='table' then return false end
|
||||
if #val~=3 and #val~=2 then return false end
|
||||
if val.__is_vector then return true end
|
||||
for _,v in ipairs(val) do
|
||||
if type(v)~='number' then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
-- Use strings for object types instead of integer bitmasks like in TS
|
||||
local tsTypesByName = {
|
||||
['all'] = -1,
|
||||
['static'] = 1,
|
||||
['environment'] = 2,
|
||||
['terrain'] = 4,
|
||||
['water'] = 16,
|
||||
['trigger'] = 32,
|
||||
['marker'] = 64,
|
||||
['gamebase'] = 1024,
|
||||
['shapebase'] = 2048,
|
||||
['camera'] = 4096,
|
||||
['staticshape'] = 8192,
|
||||
['player'] = 16384,
|
||||
['item'] = 32768,
|
||||
['vehicle'] = 65536,
|
||||
['vehicleblocker'] = 131072,
|
||||
['projectile'] = 262144,
|
||||
['explosion'] = 524288,
|
||||
['corpse'] = 1048576,
|
||||
['debris'] = 4194304,
|
||||
['physicalzone'] = 8388608,
|
||||
['staticts'] = 16777216,
|
||||
['brick'] = 33554432,
|
||||
['brickalways'] = 67108864,
|
||||
['staticrendered'] = 134217728,
|
||||
['damagableitem'] = 268435456,
|
||||
}
|
||||
local tsTypesByNum = {}
|
||||
for k,v in pairs(tsTypesByName) do
|
||||
tsTypesByNum[v] = k
|
||||
end
|
||||
|
||||
-- Type conversion
|
||||
local toTsObject
|
||||
-- Convert a string from TS into a boolean
|
||||
-- Note: Nonempty nonnumeric strings evaluate to 1, unlike in TS
|
||||
local function tsBool(v) return v~='' and v~='0' end
|
||||
-- Convert a Lua var into a TS string, or error if not possible
|
||||
local function valToTs(val)
|
||||
if val==nil then -- nil -> ''
|
||||
return ''
|
||||
elseif type(val)=='boolean' then -- bool -> 0 or 1
|
||||
return val and '1' or '0'
|
||||
elseif type(val)=='number' then -- number
|
||||
return tostring(val)
|
||||
elseif type(val)=='string' then -- string
|
||||
return val
|
||||
elseif type(val)=='table' then
|
||||
if val._tsObjectId then -- object -> object id
|
||||
return tostring(val._tsObjectId)
|
||||
elseif isTsVector(val) then -- vector - > 3 numbers
|
||||
return table.concat(val, ' ')
|
||||
elseif #val==2 and isTsVector(val[1]) and isTsVector(val[2]) then
|
||||
-- box - > 6 numbers
|
||||
return table.concat(val[1], ' ')..' '..table.concat(val[2], ' ')
|
||||
else
|
||||
error('valToTs: could not convert table', 3)
|
||||
end
|
||||
else
|
||||
error('valToTs: could not convert '..type(val), 3)
|
||||
end
|
||||
end
|
||||
bl._forceType = bl._forceType or {}
|
||||
local function valFromTs(val, name)
|
||||
if type(val)~='string' then
|
||||
error('valFromTs: expected string, got '..type(val), 3) end
|
||||
if name then
|
||||
local nameL = name:lower()
|
||||
if bl._forceType[nameL] then
|
||||
local typ = bl._forceType[nameL]
|
||||
if typ=='boolean' then
|
||||
return tsBool(val)
|
||||
elseif typ=='object' then
|
||||
return toTsObject(val)
|
||||
else
|
||||
error('valFromTs: invalid force type '..typ, 3)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- '' -> nil
|
||||
if val=='' then return nil end
|
||||
-- number
|
||||
local num = tonumber(val)
|
||||
if num then return num end
|
||||
-- vector
|
||||
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)} 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
|
||||
return val
|
||||
end
|
||||
local function arglistFromTs(name, argsS)
|
||||
local args = {}
|
||||
for i,arg in ipairs(argsS) do
|
||||
args[i] = valFromTs(arg, name..':'..i)
|
||||
end
|
||||
return args
|
||||
end
|
||||
local function arglistToTs(args)
|
||||
return map(args, valToTs)
|
||||
end
|
||||
function bl.type(name,typ)
|
||||
if typ~='bool' and typ~='boolean' and typ~='object' and typ~=nil then
|
||||
error('bl.type: can only set type to \'bool\' or \'object\' or nil', 2) end
|
||||
if not isValidFuncNameNsArgn(name) then
|
||||
error('bl.type: invalid function or variable name \''..name..'\'', 2) end
|
||||
if typ=='bool' then typ='boolean' end
|
||||
bl._forceType[name:lower()] = typ
|
||||
end
|
||||
|
||||
|
||||
-- Value detection
|
||||
|
||||
local function isTsObject(t)
|
||||
return type(t)=='table' and t._tsObjectId~=nil
|
||||
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 tsIsFunctionNs(ns, name) return tsBool(_bllua_ts.call('isFunction', ns, name)) end
|
||||
local function tsIsFunctionNsname(nsname)
|
||||
local ns, name = nsname:match('^([^:]+)::([^:]+)$')
|
||||
if ns then return tsIsFunctionNs(ns, name)
|
||||
else return tsIsFunction(nsname) end
|
||||
end
|
||||
|
||||
function bl.isObject(obj)
|
||||
if isTsObject(obj) then
|
||||
obj = obj._tsObjectId
|
||||
elseif type(obj)=='number' then
|
||||
obj = tostring(obj)
|
||||
elseif type(obj)~='string' then
|
||||
error('bl.isObject: argument #1: expected torque object, number, or string', 2)
|
||||
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
|
||||
|
||||
-- Torque object pseudo-class
|
||||
bl._objectUserMetas = bl._objectUserMetas or {}
|
||||
function bl.class(name, inherit)
|
||||
if not ( type(name)=='string' and isValidFuncName(name) ) then
|
||||
error('bl.class: argument #1: invalid class name', 2) end
|
||||
if not ( inherit==nil or (type(inherit)=='string' and isValidFuncName(inherit)) ) then
|
||||
error('bl.class: argument #2: inherit name must be a string or nil', 2) end
|
||||
name = name:lower()
|
||||
|
||||
local met = bl._objectUserMetas[name] or {}
|
||||
bl._objectUserMetas[name] = met
|
||||
met._name = name
|
||||
|
||||
if inherit then
|
||||
inherit = inherit:lower()
|
||||
|
||||
local inh = bl._objectUserMetas[inherit]
|
||||
if not inh then error('bl.class: argument #2: \''..inherit..'\' is not the '..
|
||||
'name of an existing class', 2) end
|
||||
|
||||
local inhI = bl._objectUserMetas[name]._inherit
|
||||
if inhI and inhI~=inh then
|
||||
error('bl.class: argument #2: class already exists and '..
|
||||
'inherits a different parent.', 2) end
|
||||
|
||||
bl._objectUserMetas[name]._inherit = inh
|
||||
end
|
||||
end
|
||||
local function objectInheritedMetas(name)
|
||||
local inh = bl._objectUserMetas[name:lower()]
|
||||
return function()
|
||||
local inhP = inh
|
||||
if inhP==nil then return nil end
|
||||
inh = inh._inherit
|
||||
return inhP
|
||||
end
|
||||
end
|
||||
local tsObjectMeta = {
|
||||
-- __index: Called when accessing fields that don't exist in the object itself
|
||||
-- Return torque member function or value
|
||||
__index = function(t, name)
|
||||
if rawget(t,'_deleted') then
|
||||
error('ts object index: object no longer exists', 2) end
|
||||
if type(name)~='string' and type(name)~='number' then
|
||||
error('ts object index: index must be a string or number', 2) end
|
||||
if getmetatable(t)[name] then
|
||||
return getmetatable(t)[name]
|
||||
elseif type(name)=='number' then
|
||||
if not tsIsFunctionNs(rawget(t,'_tsNamespace'), 'getObject') then
|
||||
error('ts object __index: index is number, but object does not have '..
|
||||
'getObject method', 2) end
|
||||
return toTsObject(_bllua_ts.callobj(t._tsObjectId, 'getObject',
|
||||
tostring(name)))
|
||||
else
|
||||
for inh in objectInheritedMetas(rawget(t,'_tsClassName')) do
|
||||
if inh[name] then return inh[name] end
|
||||
end
|
||||
if tsIsFunctionNs(rawget(t,'_tsNamespace'), name) then
|
||||
return function(t, ...)
|
||||
local args = {...}
|
||||
local argsS = arglistToTs(args)
|
||||
return valFromTs(_bllua_ts.callobj(rawget(t,'_tsObjectId'), name, unpack(argsS)),
|
||||
rawget(t,'_tsNamespace')..'::'..name)
|
||||
end
|
||||
else
|
||||
return valFromTs(_bllua_ts.getfield(rawget(t,'_tsObjectId'), name),
|
||||
rawget(t,'_tsNamespace')..'.'..name)
|
||||
end
|
||||
end
|
||||
end,
|
||||
-- __newindex: Called when setting fields on the object
|
||||
-- Set lua data
|
||||
-- Use :set() to set Torque data
|
||||
__newindex = function(t, name, val)
|
||||
if rawget(t,'_deleted') then
|
||||
error('ts object newindex: object no longer exists', 2) end
|
||||
if type(name)~='string' then
|
||||
error('ts object newindex: index must be a string', 2) end
|
||||
rawset(t, name, val)
|
||||
-- create strong reference since it's now storing lua data
|
||||
bl._objectsStrong[rawget(t,'_tsObjectId')] = t
|
||||
end,
|
||||
-- object:set(fieldName, value)
|
||||
-- Use to set torque data
|
||||
set = function(t, name, val)
|
||||
if t==nil or type(t)~='table' or not t._tsObjectId then
|
||||
error('ts object method: be sure to use :func() not .func()', 2) end
|
||||
if t._deleted then
|
||||
error('ts object method: object no longer exists', 2) end
|
||||
if type(name)~='string' then
|
||||
error('ts object :set(): index must be a string', 2) end
|
||||
_bllua_ts.setfield(t._tsObjectId, name, valToTs(val))
|
||||
end,
|
||||
-- __tostring: Called when printing
|
||||
-- Display a nice info string
|
||||
__tostring = function(t)
|
||||
return 'torque:'..t._tsNamespace..':'..t._tsObjectId..
|
||||
(t._tsName~='' and ('('..t._tsName..')') or '')
|
||||
end,
|
||||
-- #object
|
||||
-- If the object has a getCount method, return its count
|
||||
__len = function(t)
|
||||
if t._deleted then
|
||||
error('ts object __len: object no longer exists', 2) end
|
||||
if not tsIsFunctionNs(t._tsNamespace, 'getCount') then
|
||||
error('ts object __len: object has no getCount method', 2) end
|
||||
return tonumber(_bllua_ts.callobj(t._tsObjectId, 'getCount'))
|
||||
end,
|
||||
-- object:iterate()
|
||||
-- Return an iterator for Torque objects with the getCount and getObject methods
|
||||
-- for index, object in group:iterate() do ... end
|
||||
iterate = function(t)
|
||||
if t==nil then
|
||||
error('ts object method: be sure to use :func() not .func()', 2) end
|
||||
if t._deleted then
|
||||
error('ts object method: object no longer exists', 2) end
|
||||
if not (
|
||||
tsIsFunctionNs(t._tsNamespace, 'getCount' ) and
|
||||
tsIsFunctionNs(t._tsNamespace, 'getObject')) then
|
||||
error('ts object :iterate() - '..
|
||||
'Object does not have getCount and getObject methods', 2) end
|
||||
local count = tonumber(_bllua_ts.callobj(t._tsObjectId, 'getCount'))
|
||||
local idx = 0
|
||||
return function()
|
||||
if idx < count then
|
||||
local obj = toTsObject(_bllua_ts.callobj(t._tsObjectId,
|
||||
'getObject', tostring(idx)))
|
||||
idx = idx+1
|
||||
return idx-1, obj
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
end,
|
||||
-- Wrap some Torque functions for performance and error checking
|
||||
getName = function(t)
|
||||
if t==nil then
|
||||
error('ts object method: be sure to use :func() not .func()', 2) end
|
||||
if t._deleted then
|
||||
error('ts object method: object no longer exists', 2) end
|
||||
return t._tsName
|
||||
end,
|
||||
getId = function(t)
|
||||
if t==nil then
|
||||
error('ts object method: be sure to use :func() not .func()', 2) end
|
||||
if t._deleted then
|
||||
error('ts object method: object no longer exists', 2) end
|
||||
return tonumber(t._tsObjectId)
|
||||
end,
|
||||
getType = function(t)
|
||||
if t==nil then
|
||||
error('ts object method: be sure to use :func() not .func()', 2) end
|
||||
if t._deleted then
|
||||
error('ts object method: object no longer exists', 2) end
|
||||
return tsTypesByNum[_bllua_ts.callobj(t._tsObjectId, 'getType')]
|
||||
end,
|
||||
---- Schedule method for objects
|
||||
--schedule = function(t, time, cb, ...)
|
||||
-- if type(t)~='table' or not t._tsObjectId then
|
||||
-- error('ts object method: be sure to use :func() not .func()', 2) end
|
||||
-- if t._deleted then
|
||||
-- error('ts object method: object no longer exists', 2) end
|
||||
-- if type(time)~='number' then
|
||||
-- error('ts object schedule: argument #2: time must be a number', 2) end
|
||||
-- if type(cb)~='function' then
|
||||
-- error('ts object schedule: argument #3: callback must be a function', 2) end
|
||||
-- local args = {...}
|
||||
-- bl.schedule(time, function()
|
||||
-- if tsBool(__bllua_ts.call('isObject', t._tsObjectId)) then
|
||||
-- pcall(cb, unpack(args))
|
||||
-- end
|
||||
-- end)
|
||||
--end,
|
||||
exists = function(t)
|
||||
if t==nil then
|
||||
error('ts object method: be sure to use :func() not .func()', 2) end
|
||||
if t._deleted then
|
||||
return false end
|
||||
return tsIsObject(t._tsObjectId)
|
||||
end,
|
||||
}
|
||||
-- Weak-values table for caching Torque object references
|
||||
-- Objects in this table can be garbage collected if there are no other refs to them
|
||||
if not bl._objectsWeak then
|
||||
bl._objectsWeak = {}
|
||||
setmetatable(bl._objectsWeak, { __mode='v' })
|
||||
end
|
||||
-- Strong table for preserving Torque object references containing lua data
|
||||
-- If an object in this table, it will remain here and in the Weak table until deleted
|
||||
if not bl._objectsStrong then
|
||||
bl._objectsStrong = {}
|
||||
end
|
||||
-- Hook object deletion to clean up its lua data
|
||||
-- idS is expected to be the object ID number, NOT the object name
|
||||
function _bllua_objectDeleted(idS)
|
||||
local obj = bl._objectsWeak[idS]
|
||||
if obj then
|
||||
obj._deleted = true
|
||||
bl._objectsStrong[idS] = nil
|
||||
bl._objectsWeak[idS] = nil
|
||||
bl._objectsWeak[obj._tsName:lower()] = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Return a Torque object for the object ID string, or create one if none exists
|
||||
toTsObject = function(idiS)
|
||||
if type(idiS)~='string' then
|
||||
error('toTsObject: input must be a string', 2) end
|
||||
idiS = idiS:lower()
|
||||
if bl._objectsWeak[idiS] then return bl._objectsWeak[idiS] end
|
||||
|
||||
if not tsBool(_bllua_ts.call('isObject', idiS)) then
|
||||
--error('toTsObject: object \''..idiS..'\' does not exist', 2) end
|
||||
return nil end
|
||||
|
||||
local className = _bllua_ts.callobj(idiS, 'getClassName')
|
||||
local obj = {
|
||||
_tsObjectId = _bllua_ts.callobj(idiS, 'getId' ),
|
||||
_tsName = _bllua_ts.callobj(idiS, 'getName' ),
|
||||
_tsNamespace = className,
|
||||
_tsClassName = className:lower()
|
||||
}
|
||||
setmetatable(obj, tsObjectMeta)
|
||||
|
||||
bl._objectsWeak[obj._tsObjectId ] = obj
|
||||
bl._objectsWeak[obj._tsName:lower()] = obj
|
||||
return obj
|
||||
end
|
||||
|
||||
-- Metatable for the global bl library
|
||||
-- Allows accessing Torque objects, variables, and functions by indexing it
|
||||
local tsMeta = {
|
||||
-- __index: Called when accessing fields that don't exist in the table itself
|
||||
-- Allow indexing by object id: bl[1234]
|
||||
-- by object name: bl.mainMenuGui
|
||||
-- by function name: bl.quit()
|
||||
-- by variable name: bl.iAmAdmin
|
||||
__index = function(t, name)
|
||||
if getmetatable(t)[name] then
|
||||
return getmetatable(t)[name]
|
||||
elseif bl._objectUserMetas[name:lower()] then
|
||||
return bl._objectUserMetas[name:lower()]
|
||||
else
|
||||
if type(name)=='number' then
|
||||
return toTsObject(tostring(name))
|
||||
elseif name:find('::') then
|
||||
local ns, rest = name:match('^([^:]+)::(.+)$')
|
||||
if not ns then error('ts index: invalid name \''..name..'\'', 2) end
|
||||
if not rest:find('::') and tsIsFunction(ns, rest) then
|
||||
error('ts index: can\'t call a namespaced function from lua', 2)
|
||||
else
|
||||
return valFromTs(_bllua_ts.getvar(name), name)
|
||||
end
|
||||
elseif tsIsFunction(name) then
|
||||
return function(...)
|
||||
local args = {...}
|
||||
local argsS = arglistToTs(args)
|
||||
return valFromTs(_bllua_ts.call(name, unpack(argsS)), name)
|
||||
end
|
||||
elseif tsIsObject(name) then
|
||||
return toTsObject(name)
|
||||
else
|
||||
return valFromTs(_bllua_ts.getvar(name), name)
|
||||
end
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
-- bl.set(name, value)
|
||||
-- Used to set global variables
|
||||
function bl.set(t, name, val)
|
||||
_bllua_ts.call('_bllua_set_var', name, valToTs(val))
|
||||
end
|
||||
|
||||
-- Utility functions
|
||||
function bl.call(func, ...)
|
||||
local args = {...}
|
||||
local argsS = arglistToTs(args)
|
||||
return _bllua_ts.call(func, unpack(argsS))
|
||||
end
|
||||
function bl.eval(code)
|
||||
return valFromTs(_bllua_ts.call('eval', code))
|
||||
end
|
||||
function bl.exec(file)
|
||||
return valFromTs(_bllua_ts.call('exec', file))
|
||||
end
|
||||
function bl.tobool(val)
|
||||
return val~=nil and
|
||||
val~=false and
|
||||
--val~='' and
|
||||
--val~='0' and
|
||||
val~=0
|
||||
end
|
||||
function bl.toobject(id)
|
||||
if type(id)=='table' and id._tsObjectId then
|
||||
return id
|
||||
elseif type(id)=='string' or type(id)=='number' then
|
||||
return toTsObject(tostring(id))
|
||||
else
|
||||
error('bl.toobject: id must be a ts object, number, or string', 2)
|
||||
end
|
||||
end
|
||||
function bl.array(name, ...)
|
||||
local rest = {...}
|
||||
return name..table.concat(rest, '_')
|
||||
end
|
||||
function _bllua_call(name, ...)
|
||||
-- todo: call ts->lua using this instead of directly
|
||||
end
|
||||
|
||||
-- bl.schedule: Use TS's schedule function to schedule lua calls
|
||||
-- bl.schedule(time, function, args...)
|
||||
bl._scheduleTable = bl._scheduleTable or {}
|
||||
bl._scheduleNextId = bl._scheduleNextId or 1
|
||||
local function cancelTsSched(sched)
|
||||
if not (sched and sched.handle) then
|
||||
error('schedule:cancel() - invalid object', 2)
|
||||
end
|
||||
_bllua_ts.call('cancel', sched.handle)
|
||||
bl._scheduleTable[id] = nil
|
||||
end
|
||||
function bl.schedule(time, cb, ...)
|
||||
local id = bl._scheduleNextId
|
||||
bl._scheduleNextId = bl._scheduleNextId+1
|
||||
local args = {...}
|
||||
local handle = tonumber(_bllua_ts.call('schedule',
|
||||
time, 0, 'luacall', '_bllua_schedule_callback', id))
|
||||
local sch = {
|
||||
callback = cb,
|
||||
args = args,
|
||||
handle = handle,
|
||||
cancel = cancelTsSched,
|
||||
}
|
||||
bl._scheduleTable[id] = sch
|
||||
return sch
|
||||
end
|
||||
function _bllua_schedule_callback(id)
|
||||
id = tonumber(id)
|
||||
local sch = bl._scheduleTable[id]
|
||||
if not sch then error('_ts_schedule_callback: no schedule with id '..id) end
|
||||
bl._scheduleTable[sched_id] = nil
|
||||
sch.callback(unpack(sch.args))
|
||||
end
|
||||
|
||||
-- serverCmd and clientCmd
|
||||
-- bl.serverCmd('suicide', function(client) client.player:kill() end)
|
||||
bl._cmds = bl._cmds or {}
|
||||
function _bllua_process_cmd(cmdS, clientS, ...)
|
||||
local client = toTsObject(clientS)
|
||||
local argsS = {...}
|
||||
local args = arglistFromTs(cmdS, argsS)
|
||||
local func = bl._cmds[cmdS]
|
||||
if not func then error('_bllua_process_cmd: no cmd named \''..cmd..'\'') end
|
||||
pcall(func, client, unpack(args))
|
||||
end
|
||||
local function addCmd(cmd, func)
|
||||
if not isValidFuncName(cmd) then
|
||||
error('addCmd: invalid function name \''..tostring(cmd)..'\'') end
|
||||
bl._servercmds[cmd] = func
|
||||
local arglist = '%a,%b,%c,%d,%e,%f,%g,%h'
|
||||
_bllua_ts.eval('function '..cmd..'(%cl,'..arglist..'){'..
|
||||
'luacall(_bllua_process_cmd,"'..cmd..'",%cl,'..arglist..');}')
|
||||
end
|
||||
function bl.serverCmd(name, func) addCmd('serverCmd'..name, func) end
|
||||
function bl.clientCmd(name, func) addCmd('clientCmd'..name, func) end
|
||||
|
||||
-- Hooks (using TS packages)
|
||||
local function isPackageActive(pkg)
|
||||
local numpkgs = tonumber(_bllua_ts.call('getNumActivePackages'))
|
||||
for i = 0, numpkgs-1 do
|
||||
local apkg = _bllua_ts.call('getActivePackage', tostring(i))
|
||||
if apkg==pkg then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
local function activatePackage(pkg)
|
||||
if not isPackageActive(pkg) then
|
||||
_bllua_ts.call('activatePackage', pkg)
|
||||
end
|
||||
end
|
||||
local function deactivatePackage(pkg)
|
||||
if isPackageActive(pkg) then
|
||||
_bllua_ts.call('deactivatePackage', pkg)
|
||||
end
|
||||
end
|
||||
bl._hooks = bl._hooks or {}
|
||||
function _bllua_process_hook(pkgS, nameS, timeS, ...)
|
||||
local argsS = {...}
|
||||
local args = arglistFromTs(nameS, argsS)
|
||||
|
||||
local func = bl._hooks[pkgS] and bl._hooks[pkgS][nameS] and
|
||||
bl._hooks[pkgS][nameS][timeS]
|
||||
if not func then
|
||||
error('_bllua_process_hook: no hook for '..pkgS..':'..nameS..':'..timeS) end
|
||||
|
||||
pcall(func, args)
|
||||
end
|
||||
local function updateHook(pkg, name, hk)
|
||||
local arglist = '%a,%b,%c,%d,%e,%f,%g,%h'
|
||||
local beforeCode = hk.before and
|
||||
('luacall("_bllua_process_hook", "'..pkg..'", "'..name..
|
||||
'", "before", '..arglist..');') or ''
|
||||
local parentCode = hk.override and
|
||||
('luacall("_bllua_process_hook", "'..pkg..'", "'..name..
|
||||
'", "override", '..arglist..');') or
|
||||
(tsIsFunctionNsname(name) and
|
||||
('parent::'..name:match('[^:]+$')..'('..arglist..');') or '')
|
||||
local afterCode = hk.after and
|
||||
('luacall("_bllua_process_hook", "'..pkg..'", "'..name..
|
||||
'", "after", '..arglist..');') or ''
|
||||
bl.eval('package '..pkg..'{function '..name..'('..arglist..'){'..
|
||||
beforeCode..parentCode..afterCode..'}};')
|
||||
end
|
||||
function bl.hook(pkg, name, time, func)
|
||||
if not isValidFuncName(pkg) then
|
||||
error('bl.hook: argument #1: invalid package name \''..tostring(pkg)..'\'', 2) end
|
||||
if not isValidFuncNameNs(name) then
|
||||
error('bl.hook: argument #2: invalid function name \''..tostring(name)..'\'', 2) end
|
||||
if time~='before' and time~='after' and time~='override' then
|
||||
error('bl.hook: argument #3: time must be one of '..
|
||||
'\'before\' \'after\' \'override\'', 2) end
|
||||
if type(func)~='function' then
|
||||
error('bl.hook: argument #4: expected a function', 2) end
|
||||
|
||||
bl._hooks[pkg] = bl._hooks[pkg] or {}
|
||||
bl._hooks[pkg][name] = bl._hooks[pkg][name] or {}
|
||||
bl._hooks[pkg][name][time] = func
|
||||
|
||||
updateHook(pkg, name, bl._hooks[pkg][name])
|
||||
activatePackage(pkg)
|
||||
end
|
||||
function bl.unhook(pkg, name, time)
|
||||
if not isValidFuncName(pkg) then
|
||||
error('bl.unhook: argument #1: invalid package name \''..tostring(pkg)..'\'', 2) end
|
||||
if not isValidFuncNameNs(name) then
|
||||
error('bl.unhook: argument #2: invalid function name \''..tostring(name)..'\'', 2) end
|
||||
if time~='before' and time~='after' and time~='override' then
|
||||
error('bl.unhook: argument #3: time must be one of '..
|
||||
'\'before\' \'after\' \'override\'', 2) end
|
||||
|
||||
if not name then
|
||||
if bl._hooks[pkg] then
|
||||
for name,hk in pairs(bl._hooks[pkg]) do
|
||||
updateHook(pkg, name, {})
|
||||
end
|
||||
bl._hooks[pkg] = nil
|
||||
else
|
||||
--error('bl.unhook: no hooks registered under package name \''..
|
||||
-- pkg..'\'', 2)
|
||||
end
|
||||
deactivatePackage(pkg)
|
||||
else
|
||||
if bl._hooks[pkg][name] then
|
||||
if not time then
|
||||
bl._hooks[pkg][name] = nil
|
||||
if table.isempty(bl._hooks[pkg]) then
|
||||
bl._hooks[pkg] = nil
|
||||
deactivatePackage(pkg)
|
||||
end
|
||||
updateHook(pkg, name, {})
|
||||
else
|
||||
if time~='before' and time~='after' and time~='override' then
|
||||
error('bl.unhook: argument #3: time must be nil or one of '..
|
||||
'\'before\' \'after\' \'override\'', 2) end
|
||||
bl._hooks[pkg][name][time] = nil
|
||||
if table.isempty(bl._hooks[pkg][name]) and table.empty(bl._hooks[pkg]) then
|
||||
bl._hooks[pkg] = nil
|
||||
deactivatePackage(pkg)
|
||||
end
|
||||
updateHook(pkg, name, bl._hooks[pkg][name])
|
||||
end
|
||||
else
|
||||
--error('bl.unhook: no hooks registered for function \''..name..
|
||||
-- '\' under package name \''..pkg..'\'', 2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Container search/raycast
|
||||
local function vecToTs(v)
|
||||
if not isTsVector(v) then
|
||||
error('vecToTs: argument is not a vector', 3) end
|
||||
return table.concat(v, ' ')
|
||||
end
|
||||
local function maskToTs(mask)
|
||||
if type(mask)=='string' then
|
||||
local val = tsTypesByName[mask:lower()]
|
||||
if not val then
|
||||
error('maskToTs: invalid mask \''..mask..'\'', 3) end
|
||||
return tostring(val)
|
||||
elseif type(mask)=='table' then
|
||||
local tval = 0
|
||||
local seen = {}
|
||||
for i,v in ipairs(mask) do
|
||||
if not seen[v] then
|
||||
local val = tsTypesByName[v:lower()]
|
||||
if not val then
|
||||
error('maskToTs: invalid mask \''..v..
|
||||
'\' at index '..i..' in mask list', 3) end
|
||||
tval = tval + val
|
||||
seen[v] = true
|
||||
end
|
||||
end
|
||||
return tostring(tval)
|
||||
else
|
||||
error('maskToTs: mask must be a string or table', 3)
|
||||
end
|
||||
end
|
||||
local function objToTs(obj)
|
||||
if type(obj)=='number' or type(obj)=='string' then
|
||||
return tostring(obj)
|
||||
elseif type(obj)=='table' and obj._tsObjectId then
|
||||
return tostring(obj._tsObjectId)
|
||||
else
|
||||
error('objToTs: invalid object \''..tostring(obj)..'\'', 3)
|
||||
end
|
||||
end
|
||||
function bl.raycast(start, stop, mask, ignores)
|
||||
local startS = vecToTs(start)
|
||||
local stopS = vecToTs(start)
|
||||
local maskS = maskToTs(mask)
|
||||
local ignoresS = {}
|
||||
for _,v in ipairs(ignores) do
|
||||
table.insert(ignoresS, objToTs(v))
|
||||
end
|
||||
|
||||
local retS = _bllua_ts.call('containerRaycast', startS, stopS, maskS, unpack(ignoresS))
|
||||
|
||||
if retS=='0' then
|
||||
return nil
|
||||
else
|
||||
local hitS, pxS,pyS,pzS, nxS,nyS,nzS = retS:match('^([0-9]+) '..
|
||||
'(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+) '..
|
||||
'(%-?[0-9%.e]+) (%-?[0-9%.e]+) (%-?[0-9%.e]+)$')
|
||||
local hit = toTsObject(hitS)
|
||||
local pos = vector{tonumber(pxS),tonumber(pyS),tonumber(pzS)}
|
||||
local norm = vector{tonumber(nxS),tonumber(nyS),tonumber(nzS)}
|
||||
return hit, pos, norm
|
||||
end
|
||||
end
|
||||
local function tsContainerSearchIterator()
|
||||
local retS = _bllua_ts.call('containerSearchNext')
|
||||
if retS=='0' then
|
||||
return nil
|
||||
else
|
||||
return toTsObject(retS)
|
||||
end
|
||||
end
|
||||
function bl.boxSearch(pos, size, mask)
|
||||
local posS = vecToTs(pos)
|
||||
local sizeS = vecToTs(size)
|
||||
local maskS = maskToTs(mask)
|
||||
|
||||
_bllua_ts.call('initContainerBoxSearch', posS, sizeS, maskS)
|
||||
return tsContainerSearchIterator
|
||||
end
|
||||
function bl.radiusSearch(pos, radius, mask)
|
||||
local posS = vecToTs(pos)
|
||||
if type(radius)~='number' then
|
||||
error('bl.radiusSearch: argument #2: radius must be a number', 2) end
|
||||
local radiusS = tostring(radius)
|
||||
local maskS = maskToTs(mask)
|
||||
|
||||
_bllua_ts.call('initContainerRadiusSearch', posS, radiusS, maskS)
|
||||
return tsContainerSearchIterator
|
||||
end
|
||||
|
||||
-- Print/Talk/Echo
|
||||
local function valsToString(vals)
|
||||
local strs = {}
|
||||
for i,v in ipairs(vals) do
|
||||
strs[i] = table.tostring(v)
|
||||
end
|
||||
return table.concat(strs, ' ')
|
||||
end
|
||||
bl.echo = function(...)
|
||||
local str = valsToString({...})
|
||||
_bllua_ts.call('echo', str)
|
||||
end
|
||||
print = bl.echo
|
||||
bl.talk = function(...)
|
||||
local str = valsToString({...})
|
||||
_bllua_ts.call('echo', str)
|
||||
_bllua_ts.call('talk', str)
|
||||
end
|
||||
|
||||
local function createTsObj(keyword, class, name, inherit, props)
|
||||
local propsT = {}
|
||||
for k,v in pairs(props) do
|
||||
if not isValidFuncName(k) then
|
||||
error('bl.new/datablock: invalid property name \''..k..'\'') end
|
||||
table.insert(propsT, k..'="'..valToTs(v)..'";')
|
||||
end
|
||||
|
||||
local objS = _bllua_ts.eval(
|
||||
'return '..keyword..' '..class..'('..
|
||||
(name or '')..(inherit and (':'..inherit) or '')..'){'..
|
||||
table.concat(propsT)..'};')
|
||||
local obj = toTsObject(objS)
|
||||
if not obj then
|
||||
error('bl.new/datablock: failed to create object', 3) end
|
||||
|
||||
return obj
|
||||
end
|
||||
local function parseTsDecl(decl)
|
||||
local class, name, inherit
|
||||
if decl:find(' ') then -- class ...
|
||||
local cl, rest = decl:match('^([^ ]+) ([^ ]+)$')
|
||||
class = cl
|
||||
if rest:find(':') then -- class name:inherit
|
||||
name, inherit = rest:match('^([^:]*):([^:]+)$')
|
||||
if not name then class = nil end -- error
|
||||
if name=='' then name = nil end -- class :inherit
|
||||
else
|
||||
name = rest
|
||||
end
|
||||
else -- class
|
||||
class = decl
|
||||
end
|
||||
if not (
|
||||
isValidFuncName(class) and
|
||||
(name==nil or isValidFuncName(name)) and
|
||||
(inherit==nil or isValidFuncName(inherit)) ) then
|
||||
error('bl.new/datablock: invalid decl \''..decl..'\'\n'..
|
||||
'must be of the format: \'className\', \'className name\', '..
|
||||
'\'className :inherit\', or \'className name:inherit\'', 3) end
|
||||
return class, name, inherit
|
||||
end
|
||||
function bl.new(decl, props)
|
||||
local class, name, inherit = parseTsDecl(decl)
|
||||
return createTsObj('new', class, name, inherit, props)
|
||||
end
|
||||
function bl.datablock(decl, props)
|
||||
local class, name, inherit = parseTsDecl(decl)
|
||||
return createTsObj('datablock', class, name, inherit, props)
|
||||
end
|
||||
|
||||
setmetatable(bl, tsMeta)
|
||||
|
||||
print(' Executed libbl.lua')
|
||||
52
src/util/libts-support.cs
Normal file
52
src/util/libts-support.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
|
||||
// Read an entire file as text and return its contents as a string
|
||||
// Used for reading files from zips
|
||||
function _bllua_ReadEntireFile(%fn) {
|
||||
%text = "";
|
||||
%file = new FileObject();
|
||||
%file.openForRead(%fn);
|
||||
while (!%file.isEOF()) { %text = %text @ %file.readLine() @ "\r\n"; }
|
||||
%file.close();
|
||||
%file.delete();
|
||||
return %text;
|
||||
}
|
||||
|
||||
// Hack to create/set global variables
|
||||
// since there's no easy way to do this from the DLL directly
|
||||
function _bllua_set_var(%name, %val) {
|
||||
%first = strLwr(getSubStr(%name, 0, 1));
|
||||
%rest = getSubStr(%name, 1, strLen(%name));
|
||||
switch$(%first) {
|
||||
case "a": $a[%rest] = %val; return;
|
||||
case "b": $b[%rest] = %val; return;
|
||||
case "c": $c[%rest] = %val; return;
|
||||
case "d": $d[%rest] = %val; return;
|
||||
case "e": $e[%rest] = %val; return;
|
||||
case "f": $f[%rest] = %val; return;
|
||||
case "g": $g[%rest] = %val; return;
|
||||
case "h": $h[%rest] = %val; return;
|
||||
case "i": $i[%rest] = %val; return;
|
||||
case "j": $j[%rest] = %val; return;
|
||||
case "k": $k[%rest] = %val; return;
|
||||
case "l": $l[%rest] = %val; return;
|
||||
case "m": $m[%rest] = %val; return;
|
||||
case "n": $n[%rest] = %val; return;
|
||||
case "o": $o[%rest] = %val; return;
|
||||
case "p": $p[%rest] = %val; return;
|
||||
case "q": $q[%rest] = %val; return;
|
||||
case "r": $r[%rest] = %val; return;
|
||||
case "s": $s[%rest] = %val; return;
|
||||
case "t": $t[%rest] = %val; return;
|
||||
case "u": $u[%rest] = %val; return;
|
||||
case "v": $v[%rest] = %val; return;
|
||||
case "w": $w[%rest] = %val; return;
|
||||
case "x": $x[%rest] = %val; return;
|
||||
case "y": $y[%rest] = %val; return;
|
||||
case "z": $z[%rest] = %val; return;
|
||||
case "_": $_[%rest] = %val; return;
|
||||
}
|
||||
error("_bllua_set_var: invalid variable name " @ %name);
|
||||
return "";
|
||||
}
|
||||
|
||||
echo(" Executed libts-support.cs");
|
||||
212
src/util/libts.lua
Normal file
212
src/util/libts.lua
Normal file
@@ -0,0 +1,212 @@
|
||||
|
||||
-- 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 only has access to the sandboxed lua environment, just like user code.
|
||||
|
||||
ts = _bllua_ts
|
||||
|
||||
-- Provide limited OS functions
|
||||
os = os or {}
|
||||
function os.time() return math.floor(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
|
||||
-- Used to wrap io.open to allow reading from zips (using TS)
|
||||
-- Not perfect because TS file I/O sucks
|
||||
-- Can't read nulls, can't distinguish between CRLF and LF.
|
||||
-- Todo someday: actually read the zip in lua?
|
||||
local file_meta = {
|
||||
read = function(file, mode)
|
||||
file:_init()
|
||||
if not file or type(file)~='table' or not file._is_file then error('File:read: Not a file', 2) end
|
||||
if file._is_open ~= true then error('File:read: File is closed', 2) end
|
||||
if mode=='*n' then
|
||||
local ws, n = file.data:match('^([ \t\r\n]*)([0-9%.%-e]+)', file.pos)
|
||||
if n then
|
||||
file.pos = file.pos + #ws + #n
|
||||
return n
|
||||
else
|
||||
return nil
|
||||
end
|
||||
elseif mode=='*a' then
|
||||
local d = file.data:sub(file.pos, #file.data)
|
||||
file.pos = #file.data + 1
|
||||
return d
|
||||
elseif mode=='*l' then
|
||||
local l, ws = file.data:match('^([^\r\n]*)(\r?\n)', file.pos)
|
||||
if not l then
|
||||
l = file.data:match('^([^\r\n]*)$', file.pos); ws = '';
|
||||
if l=='' then return nil end
|
||||
end
|
||||
if l then
|
||||
file.pos = file.pos + #l + #ws
|
||||
return l
|
||||
else
|
||||
return nil
|
||||
end
|
||||
elseif type(mode)=='number' then
|
||||
local d = file.data:sub(file.pos, file.pos+mode)
|
||||
file.pos = file.pos + #d
|
||||
return d
|
||||
else
|
||||
error('File:read: Invalid mode \''..mode..'\'', 2)
|
||||
end
|
||||
end,
|
||||
lines = function(file)
|
||||
file:_init()
|
||||
return function()
|
||||
return file:read('*l')
|
||||
end
|
||||
end,
|
||||
close = function(file)
|
||||
if not file._is_open then error('File:close: File is not open', 2) end
|
||||
file._is_open = false
|
||||
end,
|
||||
__index = function(f, k) return rawget(f, k) or getmetatable(f)[k] end,
|
||||
_init = function(f)
|
||||
if not f.data then
|
||||
f.data = _bllua_ts.call('_bllua_ReadEntireFile', f.filename)
|
||||
end
|
||||
end,
|
||||
}
|
||||
local function new_file_obj(fn)
|
||||
local file = {
|
||||
_is_file = true,
|
||||
_is_open = true,
|
||||
pos = 1,
|
||||
__index = file_meta.__index,
|
||||
filename = fn,
|
||||
data = nil,
|
||||
}
|
||||
setmetatable(file, file_meta)
|
||||
return file
|
||||
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{
|
||||
'add-ons', 'base', 'config', 'saves', 'screenshots', 'shaders'
|
||||
}
|
||||
local function io_open_absolute(fn, mode)
|
||||
-- if file exists, use original mode
|
||||
local res, err = _bllua_io_open(fn, mode)
|
||||
if res then return res end
|
||||
|
||||
-- otherwise, if TS sees file but Lua doesn't, it must be in a zip, so use TS reader
|
||||
local dir = fn:match('^[^/]+')
|
||||
if not allowed_zip_dirs[dir:lower()] then return nil, 'File is not in one of the allowed directories' end
|
||||
local exist = _bllua_ts.call('isFile', fn) == '1'
|
||||
if not exist then return nil, err end
|
||||
|
||||
if mode~=nil and mode~='r' and mode~='rb' then
|
||||
return nil, 'Files in zips can only be opened in read mode' end
|
||||
|
||||
-- return a temp lua file object with the data
|
||||
local fi = new_file_obj(fn)
|
||||
return fi
|
||||
end
|
||||
|
||||
io = io or {}
|
||||
function io.open(fn, mode, errn)
|
||||
errn = errn or 1
|
||||
|
||||
-- try to open the file with relative path, otherwise use absolute path
|
||||
local curfn = debug.getfilename(errn + 1) or _bllua_ts.getvar('Con::File')
|
||||
if curfn == '' then curfn = nil end
|
||||
if fn:find('^%.') then
|
||||
local relfn = curfn and fn:find('^%./') and
|
||||
curfn:gsub('[^/]+$', '')..fn:gsub('^%./', '')
|
||||
if relfn then
|
||||
local fi, err = io_open_absolute(relfn, mode, errn+1)
|
||||
return fi, err, relfn
|
||||
else
|
||||
return nil, 'Invalid path', fn
|
||||
end
|
||||
else
|
||||
local fi, err = io_open_absolute(fn, mode, errn+1)
|
||||
return fi, err, fn
|
||||
end
|
||||
end
|
||||
function io.lines(fn)
|
||||
local fi, err, fn2 = io.open(fn, nil, 2)
|
||||
if not fi then error('Error opening file \''..fn2..'\': '..err, 2) end
|
||||
return fi:lines()
|
||||
end
|
||||
function io.type(f)
|
||||
if type(f)=='table' and f._is_file then
|
||||
return f._is_open and 'file' or 'closed file'
|
||||
else
|
||||
return _bllua_io_type(f)
|
||||
end
|
||||
end
|
||||
|
||||
-- provide dofile
|
||||
function dofile(fn, errn)
|
||||
errn = errn or 1
|
||||
|
||||
local fi, err, fn2 = io.open(fn, 'r', errn+1)
|
||||
if not fi then error('Error executing file \''..fn2..'\': '..err, errn+1) end
|
||||
|
||||
print('Executing '..fn2)
|
||||
local text = fi:read('*a')
|
||||
fi:close()
|
||||
return assert(loadstring('--[['..fn2..']]'..text))()
|
||||
end
|
||||
|
||||
-- provide require (just a wrapper for dofile)
|
||||
-- searches for ?.lua and ?/init.lua in the following directories:
|
||||
-- location of current file
|
||||
-- blockland directory
|
||||
-- current add-on
|
||||
local function file_exists(fn, errn)
|
||||
local fi, err, fn2 = io.open(fn, 'r', errn+1)
|
||||
if fi then
|
||||
fi:close()
|
||||
return fn2
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
function require(mod)
|
||||
local fp = mod:gsub('%.', '/')
|
||||
local fns = {
|
||||
'./'..fp..'.lua', -- local file
|
||||
'./'..fp..'/init.lua', -- local library
|
||||
fp..'.lua', -- global file
|
||||
fp..'/init.lua', -- global library
|
||||
}
|
||||
if fp:lower():find('^add-ons/') then
|
||||
local addonpath = fp:lower():match('^add-ons/[^/]+')..'/'
|
||||
table.insert(fns, addonpath..fp..'.lua') -- add-on file
|
||||
table.insert(fns, addonpath..fp..'/init.lua') -- add-on library
|
||||
end
|
||||
for _,fn in ipairs(fns) do
|
||||
local fne = file_exists(fn, 2)
|
||||
if fne then
|
||||
return dofile(fne, 2)
|
||||
end
|
||||
end
|
||||
return _bllua_requiresecure(mod)
|
||||
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 f,e = loadstring(code)
|
||||
return f~=nil
|
||||
end
|
||||
function _bllua_smarteval(code)
|
||||
if (not code:find('^print%(')) and isValidCode('print('..code..')') then
|
||||
code = 'print('..code..')' end
|
||||
local f,e = loadstring(code)
|
||||
if f then
|
||||
return f()
|
||||
else
|
||||
print(e)
|
||||
end
|
||||
end
|
||||
|
||||
_bllua_ts.call('echo', ' Executed libts.lua')
|
||||
412
src/util/loadaddons.cs
Normal file
412
src/util/loadaddons.cs
Normal file
@@ -0,0 +1,412 @@
|
||||
// Package to allow add-ons to use server.lua or client.lua
|
||||
// instead of or in addition to server.cs or client.cs
|
||||
// Relevant .lua files are is executed before .cs files.
|
||||
|
||||
function _bllua_strEndsWith(%str, %sch) {
|
||||
%schL = strLen(%sch);
|
||||
return getSubStr(%str, strLen(%str)-%schL, %schL) $= %sch;
|
||||
}
|
||||
//function _bllua_strRemoveEnd(%str, %sch) {
|
||||
// %schL = strLen(%sch);
|
||||
// return getSubStr(%str, 0, strLen(%str)-%schL);
|
||||
//}
|
||||
function _bllua_fileIsExecCs(%fn) {
|
||||
return
|
||||
_bllua_strEndsWith(%fn, "/server.cs" ) ||
|
||||
_bllua_strEndsWith(%fn, "/server.lua") ||
|
||||
_bllua_strEndsWith(%fn, "/client.cs" ) ||
|
||||
_bllua_strEndsWith(%fn, "/client.lua");
|
||||
}
|
||||
function _bllua_execAddon(%dirName, %type) {
|
||||
%i = 0;
|
||||
%fnLua = "Add-Ons/" @ %dirName @ "/" @ %type @ ".lua";
|
||||
if(isFile(%fnLua)) { luaexec(%fnLua); %i++; }
|
||||
%fnCs = "Add-Ons/" @ %dirName @ "/" @ %type @ ".cs";
|
||||
if(isFile(%fnCs )) { exec(%fnCs ); %i++; }
|
||||
if(%i==0) {
|
||||
error("Error Loading Add-On " @ %dirName @ ": Neither " @
|
||||
%type @ ".cs nor " @ %type @ ".lua exist");
|
||||
}
|
||||
}
|
||||
|
||||
// Rewrite built-in functions that scan for server.cs or client.cs
|
||||
// and make them scan for server.lua or client.lua as well
|
||||
// Note: I had to completely override several large functions,
|
||||
// many of which are highly redundant, because Badspot didn't know
|
||||
// what functional decomposition was when he wrote this shit.
|
||||
package _bllua_addon_exec {
|
||||
function CustomGameGuiServer::populateAddOnList() {
|
||||
deleteVariables("$CustomGameGuiServer::AddOn*");
|
||||
$CustomGameGuiServer::AddOnCount = 0;
|
||||
%pattern = "Add-Ons/*/server.*";
|
||||
%filename = findFirstFile(%pattern);
|
||||
while(isFile(%filename)) {
|
||||
if(_bllua_fileIsExecCs(%filename)) {
|
||||
%path = filePath(%filename);
|
||||
%dirName = getSubStr(%path, strlen("Add-Ons/"), strlen(%path) - strlen("Add-Ons/"));
|
||||
if(!%seenDirName[%dirName]) {
|
||||
%seenDirName[%dirName] = 1;
|
||||
%varName = getSafeVariableName(%dirName);
|
||||
if(isValidAddOn(%dirName, 1)) {
|
||||
$CustomGameGuiServer::AddOn[$CustomGameGuiServer::AddOnCount] = %dirName;
|
||||
$CustomGameGuiServer::AddOnCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
%filename = findNextFile(%pattern);
|
||||
}
|
||||
}
|
||||
function GameModeGuiServer::GetMissingAddOns(%filename) {
|
||||
if(!isFile(%filename)) {
|
||||
error("ERROR: GameModeGuiServer::GetMissingAddOns(" @ %filename @ ") - file does not exist");
|
||||
return 0;
|
||||
}
|
||||
%path = filePath(%filename);
|
||||
%missingAddons = "";
|
||||
%descriptionFile = %path @ "/description.txt";
|
||||
%previewFile = %path @ "/preview.jpg";
|
||||
%thumbFile = %path @ "/thumb.jpg";
|
||||
%saveFile = %path @ "/save.bls";
|
||||
%colorSetFile = %path @ "/colorSet.txt";
|
||||
if(!isFile(%descriptionFile))
|
||||
%missingAddons = %missingAddons TAB %descriptionFile;
|
||||
if(!isFile(%previewFile))
|
||||
%missingAddons = %missingAddons TAB %previewFile;
|
||||
if(!isFile(%thumbFile))
|
||||
%missingAddons = %missingAddons TAB %thumbFile;
|
||||
if(!isFile(%saveFile))
|
||||
%missingAddons = %missingAddons TAB %saveFile;
|
||||
if(!isFile(%colorSetFile))
|
||||
%missingAddons = %missingAddons TAB %colorSetFile;
|
||||
%file = new FileObject(""){};
|
||||
%file.openForRead(%filename);
|
||||
while(!%file.isEOF()) {
|
||||
%line = %file.readLine();
|
||||
%label = getWord(%line, 0);
|
||||
%value = trim(getWords(%line, 1, 999));
|
||||
if(%label !$= "") {
|
||||
if(getSubStr(%label, 0, 2) !$= "//") {
|
||||
if(%label $= "ADDON") {
|
||||
if(!isFile("Add-Ons/" @ %value @ "/description.txt") ||
|
||||
(!isFile("Add-Ons/" @ %value @ "/server.cs" ) &&
|
||||
!isFile("Add-Ons/" @ %value @ "/server.lua") ) ) {
|
||||
if(strlen(%missingAddons) > 0)
|
||||
%missingAddons = %missingAddons TAB %value;
|
||||
else
|
||||
%missingAddons = %value;
|
||||
}
|
||||
} else {
|
||||
if(%label $= "MUSIC") {
|
||||
if(!isFile("Add-Ons/Music/" @ %value @ ".ogg")) {
|
||||
if(strlen(%missingAddons) > 0)
|
||||
%missingAddons = %missingAddons TAB %value @ ".ogg";
|
||||
else
|
||||
%missingAddons = %value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
%file.close();
|
||||
%file.delete();
|
||||
return %missingAddons;
|
||||
}
|
||||
function loadAddOns() {
|
||||
echo("");
|
||||
updateAddOnList();
|
||||
echo("--------- Loading Add-Ons (+BlockLua) ---------");
|
||||
deleteVariables("$AddOnLoaded__*");
|
||||
%dir = "Add-Ons/*/server.*";
|
||||
%filename = findFirstFile(%dir);
|
||||
%dirCount = 0;
|
||||
if(isFile("Add-Ons/System_ReturnToBlockland/server.cs")) {
|
||||
%dirNameList[%dirCount] = "System_ReturnToBlockland";
|
||||
%dirCount++;
|
||||
}
|
||||
while(%filename !$= "") {
|
||||
if(_bllua_fileIsExecCs(%filename)) {
|
||||
%path = filePath(%filename);
|
||||
%dirName = getSubStr(%path, strlen("Add-Ons/"), strlen(%path) - strlen("Add-Ons/"));
|
||||
if(!%seenDirName[%dirName]) {
|
||||
%seenDirName[%dirName] = 1;
|
||||
if(%dirName !$= "System_ReturnToBlockland") {
|
||||
%dirNameList[%dirCount] = %dirName;
|
||||
%dirCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
%filename = findNextFile(%dir);
|
||||
}
|
||||
for(%addOnItr = 0; %addOnItr < %dirCount; %addOnItr++) {
|
||||
%dirName = %dirNameList[%addOnItr];
|
||||
%varName = getSafeVariableName(%dirName);
|
||||
if(!$Server::Dedicated) {
|
||||
if(getRealTime() - $lastProgressBarTime > 200) {
|
||||
LoadingProgress.setValue(%addOnItr / %dirCount);
|
||||
$lastProgressBarTime = getRealTime();
|
||||
Canvas.repaint();
|
||||
}
|
||||
}
|
||||
if($AddOn__[%varName] $= 1 && isValidAddOn(%dirName)) {
|
||||
if(%dirName $= "JVS_Content" && $AddOn__["Support_LegacyDoors"] $= 1) {
|
||||
echo(" Skipping JVS_Content in favor of Support_LegacyDoors");
|
||||
} else if(!$AddOnLoaded__[%varName]) {
|
||||
$AddOnLoaded__[%varName] = 1;
|
||||
%zipFile = "Add-Ons/" @ %dirName @ ".zip";
|
||||
if(isFile(%zipFile)) {
|
||||
%zipCRC = getFileCRC(%zipFile);
|
||||
echo("\c5Loading Add-On: " @ %dirName @ " \c2(CRC:" @ %zipCRC @ ")");
|
||||
} else {
|
||||
echo("\c5Loading Add-On: " @ %dirName);
|
||||
}
|
||||
if(VerifyAddOnScripts(%dirName)==0) {
|
||||
echo("\c3ADD-ON " @ %dirName @ " CONTAINS SYNTAX ERRORS\n");
|
||||
} else {
|
||||
%oldDBCount = DataBlockGroup.getCount();
|
||||
_bllua_execAddon(%dirName, "server");
|
||||
%dbDiff = DataBlockGroup.getCount() - %oldDBCount;
|
||||
echo("\c2" @ %dbDiff @ " datablocks added.");
|
||||
echo("");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
echo("");
|
||||
}
|
||||
function loadGameModeAddOns() {
|
||||
echo("");
|
||||
echo("--------- Loading Add-Ons (Game Mode) (+BlockLua) ---------");
|
||||
deleteVariables("$AddOnLoaded__*");
|
||||
for(%i=0; %i<$GameMode::AddOnCount; %i++) {
|
||||
%dirName = $GameMode::AddOn[%i];
|
||||
%varName = getSafeVariableName(%dirName);
|
||||
if(!$Server::Dedicated) {
|
||||
if(getRealTime() - $lastProgressBarTime > 200) {
|
||||
LoadingProgress.setValue(%i / $GameMode::AddOnCount);
|
||||
$lastProgressBarTime = getRealTime();
|
||||
Canvas.repaint();
|
||||
}
|
||||
}
|
||||
if(!isValidAddOn(%dirName)) {
|
||||
error("ERROR: Invalid add-on \'" @ %dirName @ "\' specified for game mode \'" @ $GameModeArg @ "\'");
|
||||
} else {
|
||||
$AddOnLoaded__[%varName] = 1;
|
||||
%zipFile = "Add-Ons/" @ %dirName @ ".zip";
|
||||
if(isFile(%zipFile)) {
|
||||
%zipCRC = getFileCRC(%zipFile);
|
||||
echo("\c5Loading Add-On: " @ %dirName @ " \c2(CRC:" @ %zipCRC @ ")");
|
||||
} else {
|
||||
echo("\c5Loading Add-On: " @ %dirName);
|
||||
}
|
||||
if(VerifyAddOnScripts(%dirName) == 0) {
|
||||
echo("\c3ADD-ON " @ %dirName @ " CONTAINS SYNTAX ERRORS\n");
|
||||
} else {
|
||||
%oldDBCount = DataBlockGroup.getCount();
|
||||
_bllua_execAddon(%dirName, "server");
|
||||
%dbDiff = DataBlockGroup.getCount() - %oldDBCount;
|
||||
echo("\c2" @ %dbDiff @ " datablocks added.");
|
||||
echo("");
|
||||
}
|
||||
}
|
||||
}
|
||||
echo("");
|
||||
}
|
||||
function loadClientAddOns() {
|
||||
echo("");
|
||||
echo("--------- Loading Client Add-Ons (+BlockLua) ---------");
|
||||
if(isFile("base/server/crapOns_Cache.cs"))
|
||||
exec("base/server/crapOns_Cache.cs");
|
||||
%dir = "Add-Ons/*/client.*";
|
||||
%filename = findFirstFile(%dir);
|
||||
%dirCount = 0;
|
||||
if(isFile("Add-Ons/System_ReturnToBlockland/client.cs")) {
|
||||
%dirNameList[%dirCount] = "System_ReturnToBlockland";
|
||||
%dirCount++;
|
||||
}
|
||||
while(%filename !$= "") {
|
||||
if(_bllua_fileIsExecCs(%filename)) {
|
||||
%path = filePath(%filename);
|
||||
%dirName = getSubStr(%path, strlen("Add-Ons/"), strlen(%path) - strlen("Add-Ons/"));
|
||||
if(!%seenDirName[%dirName]) {
|
||||
%seenDirName[%dirName] = 1;
|
||||
if(%dirName !$= "System_ReturnToBlockland") {
|
||||
%dirNameList[%dirCount] = %dirName;
|
||||
%dirCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
%filename = findNextFile(%dir);
|
||||
}
|
||||
for(%i=0; %i<%dirCount; %i++) {
|
||||
%dirName = %dirNameList[%i];
|
||||
%varName = getSafeVariableName(%dirName);
|
||||
echo("");
|
||||
echo("Client checking Add-On: " @ %dirName);
|
||||
if(!clientIsValidAddOn(%dirName, 1)) {
|
||||
//deleteVariables("$AddOn__" @ %varName); // wtf
|
||||
} else {
|
||||
%name = %dirName;
|
||||
%zipFile = "Add-Ons/" @ %dirName @ ".zip";
|
||||
if(isFile(%zipFile)) {
|
||||
%zipCRC = getFileCRC(%zipFile);
|
||||
echo("\c5Loading Add-On: " @ %dirName @ " \c2(CRC:" @ %zipCRC @ ")");
|
||||
} else {
|
||||
echo("\c5Loading Add-On: " @ %dirName);
|
||||
}
|
||||
if(ClientVerifyAddOnScripts(%dirName)==0)
|
||||
echo("\c3ADD-ON " @ %dirName @ " CONTAINS SYNTAX ERRORS\n");
|
||||
else
|
||||
_bllua_execAddon(%dirName, "client");
|
||||
}
|
||||
}
|
||||
echo("");
|
||||
}
|
||||
function updateAddOnList() {
|
||||
echo("\n--------- Updating Add-On List (+BlockLua) ---------");
|
||||
deleteVariables("$AddOn__*");
|
||||
if(isFile("config/server/ADD_ON_LIST.cs")) {
|
||||
exec("config/server/ADD_ON_LIST.cs");
|
||||
} else {
|
||||
exec("base/server/defaultAddOnList.cs");
|
||||
}
|
||||
if(isFile("base/server/crapOns_Cache.cs")) {
|
||||
exec("base/server/crapOns_Cache.cs");
|
||||
}
|
||||
%dir = "Add-Ons/*/server.*";
|
||||
%fileCount = getFileCount(%dir);
|
||||
%filename = findFirstFile(%dir);
|
||||
while(%filename !$= "") {
|
||||
if(_bllua_fileIsExecCs(%filename)) {
|
||||
%path = filePath(%filename);
|
||||
%dirName = getSubStr(%path, strlen("Add-Ons/"), strlen(%path) - strlen("Add-Ons/"));
|
||||
if(!%seenDirName[%dirName]) {
|
||||
%seenDirName[%dirName] = 1;
|
||||
%varName = getSafeVariableName(%dirName);
|
||||
echo("Checking Add-On " @ %dirName);
|
||||
if(!isValidAddOn(%dirName, 1)) {
|
||||
deleteVariables("$AddOn__" @ %varName);
|
||||
} else {
|
||||
if (mFloor($AddOn__[%varName]) <= 0)
|
||||
$AddOn__[%varName] = -1;
|
||||
else
|
||||
$AddOn__[%varName] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
%filename = findNextFile(%dir);
|
||||
}
|
||||
echo("");
|
||||
export("$AddOn__*", "config/server/ADD_ON_LIST.cs");
|
||||
}
|
||||
};
|
||||
activatePackage(_bllua_addon_exec);
|
||||
|
||||
// Have to make new versions of these because packaging them is blocked FSFR
|
||||
function forceRequiredAddOn_L(%dirName) {
|
||||
if(%dirName $= "JVS_Content") {
|
||||
if($GameModeArg $= "") {
|
||||
if($AddOn__["Support_LegacyDoors"] == 1 || !isFile("add-ons/JVS_Content/server.cs") || ($AddOn__["Support_LegacyDoors"] != 1 && $AddOn__["JVS_Content"] != 1)) {
|
||||
%dirName = "Support_LegacyDoors";
|
||||
}
|
||||
} else {
|
||||
%foundJVSContent = 0;
|
||||
for(%i=0; %i<$GameMode::AddOnCount; %i++) {
|
||||
if ($GameMode::AddOn[%i] $= "JVS_Content") {
|
||||
%foundJVSContent = 1;
|
||||
}
|
||||
}
|
||||
if(!%foundJVSContent)
|
||||
%dirName = "Support_LegacyDoors";
|
||||
}
|
||||
}
|
||||
if(strstr(%dirName, " ") != -1)
|
||||
%dirName = strreplace(%dirName, " ", "_");
|
||||
//if(strstr(%dirName, "/") != -1)
|
||||
// return $Error::AddOn_Nested;
|
||||
%varName = getSafeVariableName(%dirName);
|
||||
if($GameModeArg !$= "") {
|
||||
%foundIt = 0;
|
||||
for(%i=0; %i<$GameMode::AddOnCount; %i++) {
|
||||
if ($GameMode::AddOn[%i] $= %dirName) {
|
||||
%foundIt = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!%foundIt) {
|
||||
error("ERROR: ForceRequiredAddOn_L(\'" @ %dirName @ "\') - you can\'t force load an add-on that is not included in gamemode.txt");
|
||||
if (GameWindowExists() && !$Server::Dedicated) {
|
||||
schedule(11, 0, MessageBoxOK, "Game Mode Error", "Required add-on " @ %dirName @ " should be added to gamemode.txt");
|
||||
}
|
||||
if (!isEventPending($disconnectEvent)) {
|
||||
$disconnectEvent = schedule(10, 0, disconnect);
|
||||
}
|
||||
return $Error::AddOn_NotFound;
|
||||
}
|
||||
}
|
||||
if($AddOnLoaded__[%varName] == 1)
|
||||
return $Error::None;
|
||||
if($AddOn__[%varName] $= "" && $GameModeArg $= "" || !isValidAddOn(%dirName)) {
|
||||
error("ERROR: ForceRequiredAddOn() - " @ %dirName @ " is not a valid add-on");
|
||||
return $Error::AddOn_NotFound;
|
||||
}
|
||||
echo(" Loading Add-On " @ %dirName @ "");
|
||||
_bllua_execAddon(%dirName, "server");
|
||||
$AddOnLoaded__[%varName] = 1;
|
||||
if($AddOn__[%varName] $= 1)
|
||||
return $Error::None;
|
||||
else
|
||||
return $Error::AddOn_Disabled;
|
||||
}
|
||||
function loadRequiredAddOn_L(%dirName) {
|
||||
if(%dirName $= "JVS_Content") {
|
||||
if($GameModeArg $= "") {
|
||||
if($AddOn__["Support_LegacyDoors"] == 1 || !isFile("add-ons/JVS_Content/server.cs") || ($AddOn__["Support_LegacyDoors"] != 1 && $AddOn__["JVS_Content"] != 1)) {
|
||||
%dirName = "Support_LegacyDoors";
|
||||
}
|
||||
} else {
|
||||
%foundJVSContent = 0;
|
||||
for(%i=0; %i<$GameMode::AddOnCount; %i++) {
|
||||
if ($GameMode::AddOn[%i] $= "JVS_Content") {
|
||||
%foundJVSContent = 1;
|
||||
}
|
||||
}
|
||||
if(!%foundJVSContent)
|
||||
%dirName = "Support_LegacyDoors";
|
||||
}
|
||||
}
|
||||
if(strstr(%dirName, " ") != -1)
|
||||
%dirName = strreplace(%dirName, " ", "_");
|
||||
//if(strstr(%dirName, "/") != -1)
|
||||
// return $Error::AddOn_Nested;
|
||||
%varName = getSafeVariableName(%dirName);
|
||||
if ($GameModeArg !$= "") {
|
||||
%foundIt = 0;
|
||||
for(%i=0; %i<$GameMode::AddOnCount; %i++) {
|
||||
if ($GameMode::AddOn[%i] $= %dirName) {
|
||||
%foundIt = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!%foundIt) {
|
||||
error("ERROR: LoadRequiredAddOn_L(\'" @ %dirName @ "\') - you can\'t force load an add-on that is not included in gamemode.txt");
|
||||
if (GameWindowExists() && !$Server::Dedicated)
|
||||
schedule(11, 0, MessageBoxOK, "Game Mode Error", "Required add-on " @ %dirName @ " should be added to gamemode.txt");
|
||||
if (!isEventPending($disconnectEvent))
|
||||
$disconnectEvent = schedule(10, 0, disconnect);
|
||||
return $Error::AddOn_NotFound;
|
||||
}
|
||||
}
|
||||
if($AddOnLoaded__[%varName] == 1)
|
||||
return $Error::None;
|
||||
if($AddOn__[%varName] $= 1) {
|
||||
echo(" Loading Add-On " @ %dirName @ "");
|
||||
_bllua_execAddon(%dirName, "server");
|
||||
$AddOnLoaded__[%varName] = 1;
|
||||
return $Error::None;
|
||||
} else {
|
||||
return $Error::AddOn_Disabled;
|
||||
}
|
||||
}
|
||||
|
||||
echo(" Executed loadaddons.cs");
|
||||
360
src/util/std.lua
Normal file
360
src/util/std.lua
Normal file
@@ -0,0 +1,360 @@
|
||||
|
||||
-- Basic functionality that should be standard in Lua
|
||||
|
||||
|
||||
-- Table / List
|
||||
-- Whether a table contains no keys
|
||||
function table.empty(t)
|
||||
for _,_ in pairs(t) do return false end
|
||||
return true
|
||||
end
|
||||
-- Apply a function to each key in a table
|
||||
function table.map(f, ...)
|
||||
local ts = {...}
|
||||
local u = {}
|
||||
for k,_ in pairs(ts[1]) do
|
||||
local args = {}
|
||||
for j=1,#ts do args[j] = ts[j][i] end
|
||||
u[i] = f(unpack(args))
|
||||
end
|
||||
return u
|
||||
end
|
||||
function table.map_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(unpack(args))
|
||||
end
|
||||
return u
|
||||
end
|
||||
-- Swap keys/values
|
||||
function table.swap(t)
|
||||
local u = {}
|
||||
for k,v in pairs(t) do u[v] = k end
|
||||
return u
|
||||
end
|
||||
-- Reverse a list
|
||||
function table.reverse(l)
|
||||
local m = {}
|
||||
for i=1,#l do m[#l-i+1] = l[i] end
|
||||
return m
|
||||
end
|
||||
-- Convert i->v to v->true
|
||||
function table.values(l)
|
||||
local u = {}
|
||||
for _,v in ipairs(l) do u[v] = true end
|
||||
return u
|
||||
end
|
||||
-- Make a list of keys
|
||||
function table.keys(t)
|
||||
local u = {}
|
||||
for k,_ in pairs(t) do table.insert(u, k) end
|
||||
return u
|
||||
end
|
||||
-- Whether a table is a list/array (has only monotonic integer keys)
|
||||
function table.islist(t)
|
||||
local n = 0
|
||||
for i,_ in pairs(t) do
|
||||
if type(i)~='number' or i%1~=0 then return false end
|
||||
n = n+1
|
||||
end
|
||||
return n==#t
|
||||
end
|
||||
-- Append contents of other tables to first table
|
||||
function table.append(t, ...)
|
||||
local a = {...}
|
||||
for _,u in ipairs(a) do
|
||||
for _,v in ipairs(u) do table.insert(t,v) end
|
||||
end
|
||||
return t
|
||||
end
|
||||
-- Create a new table containing all keys from any number of tables
|
||||
-- latter tables in the arg list override prior ones
|
||||
-- overlaps, NOT appends, integer keys
|
||||
function table.join(...)
|
||||
local ts = {...}
|
||||
local w = {}
|
||||
for _,t in ipairs(ts) do
|
||||
for k,v in pairs(t) do w[k] = v end
|
||||
end
|
||||
return w
|
||||
end
|
||||
-- Whether a table contains a certain value in any key
|
||||
function table.contains(t,s)
|
||||
for _,v in pairs(t) do
|
||||
if v==s then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
function table.contains_list(t,s)
|
||||
for _,v in ipairs(t) do
|
||||
if v==s then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
-- Copy a table to another table
|
||||
function table.copy(t)
|
||||
local u = {}
|
||||
for k,v in pairs(t) do u[k] = v end
|
||||
return u
|
||||
end
|
||||
function table.copy_list(l)
|
||||
local m = {}
|
||||
for i,v in ipairs(l) do m[i] = v end
|
||||
return m
|
||||
end
|
||||
-- Sort a table in a new copy
|
||||
function table.sortcopy(t, f)
|
||||
local u = table.copy_list(t)
|
||||
table.sort(u, f)
|
||||
return u
|
||||
end
|
||||
-- Remove a value from a table
|
||||
function table.removevalue(t, r)
|
||||
local rem = {}
|
||||
for k,v in pairs(t) do
|
||||
if v==r then table.insert(rem, k) end
|
||||
end
|
||||
for _,k in ipairs(rem) do t[k] = nil end
|
||||
end
|
||||
function table.removevalue_list(t, r)
|
||||
for i = #t, 1, -1 do
|
||||
if t[i]==r then
|
||||
table.remove(t, i)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Export tables into formatted executable strings
|
||||
local function tabs(tabLevel)
|
||||
return (' '):rep(tabLevel)
|
||||
end
|
||||
local valueToString
|
||||
local function tableToString(t, tabLevel, seen)
|
||||
if type(t)~='table' or (getmetatable(t) and getmetatable(t).__tostring) then
|
||||
return tostring(t)
|
||||
elseif table.islist(t) then
|
||||
if #t==0 then
|
||||
return '{}'
|
||||
else
|
||||
local strs = {}
|
||||
local containsTables = false
|
||||
for _,v in ipairs(t) do
|
||||
if type(v)=='table' then containsTables = true end
|
||||
table.insert(strs, valueToString(v, tabLevel+1, seen)..',')
|
||||
end
|
||||
if containsTables or #t>3 then
|
||||
return '{\n'..tabs(tabLevel+1)
|
||||
..table.concat(strs, '\n'..tabs(tabLevel+1))
|
||||
..'\n'..tabs(tabLevel)..'}'
|
||||
else
|
||||
return '{ '..table.concat(strs, ' ')..' }'
|
||||
end
|
||||
end
|
||||
else
|
||||
local containsNonStringKeys = false
|
||||
for k,v in pairs(t) do
|
||||
if type(k)~='string' or k:find('[^a-zA-Z0-9_]') then
|
||||
containsNonStringKeys = true
|
||||
elseif type(k)=='table' then
|
||||
error('table.tostring: table contains a table as key, cannot serialize')
|
||||
end
|
||||
end
|
||||
local strs = {}
|
||||
if containsNonStringKeys then
|
||||
for k,v in pairs(t) do
|
||||
table.insert(strs, '\n'..tabs(tabLevel+1)
|
||||
..'['..valueToString(k, tabLevel+1, seen)..'] = '
|
||||
..valueToString(v, tabLevel+1, seen)..',')
|
||||
end
|
||||
else
|
||||
for k,v in pairs(t) do
|
||||
table.insert(strs, '\n'..tabs(tabLevel+1)
|
||||
..k..' = '..valueToString(v, tabLevel+1, seen)..',')
|
||||
end
|
||||
end
|
||||
return '{'..table.concat(strs)..'\n'..tabs(tabLevel)..'}'
|
||||
end
|
||||
end
|
||||
valueToString = function(v, tabLevel, seen)
|
||||
local t = type(v)
|
||||
if t=='table' then
|
||||
if seen[v] then
|
||||
return 'nil --[[ already seen: '..tostring(v)..' ]]'
|
||||
else
|
||||
seen[v] = true
|
||||
return tableToString(v, tabLevel, seen)
|
||||
end
|
||||
elseif t=='string' then
|
||||
return '\''..string.escape(v)..'\''
|
||||
elseif t=='number' or t=='boolean' then
|
||||
return tostring(v)
|
||||
else
|
||||
--error('table.tostring: table contains a '..t..' value, cannot serialize')
|
||||
return 'nil --[[ cannot serialize '..t..': '..tostring(v)..' ]]'
|
||||
end
|
||||
end
|
||||
function table.tostring(t)
|
||||
return tableToString(t, 0, {})
|
||||
end
|
||||
|
||||
|
||||
-- String
|
||||
|
||||
-- Split string into table by separator
|
||||
-- or by chars if no separator given
|
||||
-- if regex is not true, sep is treated as a regex pattern
|
||||
function string.split(str, sep, noregex)
|
||||
if type(str)~='string' then
|
||||
error('string.split: argument #1: expected string, got '..type(str), 2) end
|
||||
if sep==nil or sep=='' then
|
||||
local t = {}
|
||||
local ns = #str
|
||||
for x = 1, ns do
|
||||
table.insert(t, str:sub(x, x))
|
||||
end
|
||||
return t
|
||||
elseif type(sep)=='string' then
|
||||
local t = {}
|
||||
if #str>0 then
|
||||
local first = 1
|
||||
while true do
|
||||
local last, newfirst = str:find(sep, first, noregex)
|
||||
if not last then break end
|
||||
table.insert(t, str:sub(first, last-1))
|
||||
first = newfirst+1
|
||||
end
|
||||
table.insert(t, str:sub(first, #str))
|
||||
end
|
||||
return t
|
||||
else
|
||||
error(
|
||||
'string.split: argument #2: expected string or nil, got '..type(sep), 2)
|
||||
end
|
||||
end
|
||||
-- Split string to a list of char bytes
|
||||
function string.bytes(s)
|
||||
local b = {}
|
||||
for i=1,#s do
|
||||
local c = s:sub(i,i)
|
||||
table.insert(b, c:byte())
|
||||
end
|
||||
return b
|
||||
end
|
||||
-- Trim leading and trailing whitespace
|
||||
function string.trim(s, ws)
|
||||
ws = ws or '[ \t\r\t]'
|
||||
return s:gsub('^'..ws..'+', ''):gsub(ws..'+$', '')..''
|
||||
end
|
||||
-- String slicing and searching using [] operator
|
||||
local str_meta = getmetatable('')
|
||||
local str_meta_index_old= str_meta.__index
|
||||
function str_meta.__index(s,k)
|
||||
if type(k)=='string' then
|
||||
return str_meta_index_old[k]
|
||||
elseif type(k)=='number' then
|
||||
if k<0 then k = #s+k+1 end
|
||||
return string.sub(s,k,k)
|
||||
elseif type(k)=='table' then
|
||||
local a = k[1]<0 and (#s+k[1]+1) or k[1]
|
||||
local b = k[2]<0 and (#s+k[2]+1) or k[2]
|
||||
return string.sub(s,a,b)
|
||||
end
|
||||
end
|
||||
-- String iterator
|
||||
function string.chars(s)
|
||||
local i = 0
|
||||
return function()
|
||||
i = i+1
|
||||
if i<=#s then return s:sub(i,i)
|
||||
else return nil end
|
||||
end
|
||||
end
|
||||
-- Escape sequences
|
||||
local defaultEscapes = {
|
||||
['\\'] = '\\\\',
|
||||
['\''] = '\\\'',
|
||||
['\"'] = '\\\"',
|
||||
['\t'] = '\\t',
|
||||
['\r'] = '\\r',
|
||||
['\n'] = '\\n',
|
||||
['\0'] = '\\0',
|
||||
}
|
||||
function string.escape(s, escapes)
|
||||
escapes = escapes or defaultEscapes
|
||||
local t = {}
|
||||
for i=1,#s do
|
||||
local c = s:sub(i,i)
|
||||
table.insert(t, escapes[c] or c)
|
||||
end
|
||||
return table.concat(t)
|
||||
end
|
||||
local defaultEscapeChar = '\\'
|
||||
local defaultUnescapes = {
|
||||
['\\'] = '\\',
|
||||
['\''] = '\'',
|
||||
['\"'] = '\"',
|
||||
['t'] = '\t',
|
||||
['r'] = '\r',
|
||||
['n'] = '\n',
|
||||
['0'] = '\0',
|
||||
}
|
||||
function string.unescape(s, escapeChar, unescapes)
|
||||
escapeChar = escapeChar or defaultEscapeChar
|
||||
unescapes = unescapes or defaultUnescapes
|
||||
local t = {}
|
||||
local inEscape = false
|
||||
for i=1,#s do
|
||||
local c = s:sub(i,i)
|
||||
if inEscape then
|
||||
table.insert(t, unescapes[c]
|
||||
or error('string.unescape: invalid escape sequence: \''
|
||||
..escapeChar..c..'\''))
|
||||
elseif c==escapeChar then
|
||||
inEscape = true
|
||||
else
|
||||
table.insert(t, c)
|
||||
end
|
||||
end
|
||||
return table.concat(t)
|
||||
end
|
||||
|
||||
|
||||
-- IO
|
||||
|
||||
io = io or {}
|
||||
-- Read entire file at once, return nil,err if access failed
|
||||
function io.readfile(filename)
|
||||
local fi,err = io.open(filename, 'rb')
|
||||
if not fi then return nil,err end
|
||||
local s = fi:read("*a")
|
||||
fi:close()
|
||||
return s
|
||||
end
|
||||
-- Write data to file all at once, return true if success / false,err if failure
|
||||
function io.writefile(filename, data)
|
||||
local fi,err = io.open(filename, 'wb')
|
||||
if not fi then return false,err end
|
||||
fi:write(data)
|
||||
fi:close()
|
||||
return true,nil
|
||||
end
|
||||
|
||||
|
||||
-- Math
|
||||
|
||||
-- Round
|
||||
function math.round(x)
|
||||
return math.floor(x+0.5)
|
||||
end
|
||||
-- Mod that accounts for floating point inaccuracy
|
||||
function math.mod(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
|
||||
else return m end
|
||||
end
|
||||
-- Clamp value between min and max
|
||||
function math.clamp(v, n, x)
|
||||
return math.min(x, math.max(v, n))
|
||||
end
|
||||
219
src/util/vector.lua
Normal file
219
src/util/vector.lua
Normal file
@@ -0,0 +1,219 @@
|
||||
|
||||
-- Vector math class with operators
|
||||
local vector_meta
|
||||
local vector_new
|
||||
local function vector_check(v, n, name, argn)
|
||||
if not v.__is_vector then
|
||||
error('vector '..name..': argument #'..(argn or 1)
|
||||
..': expected vector, got '..type(v), n+1) end
|
||||
end
|
||||
local function vector_checksamelen(v1, v2, name)
|
||||
vector_check(v1, 3, name, 1)
|
||||
vector_check(v2, 3, name, 2)
|
||||
if #v1~=#v2 then
|
||||
error('vector '..name..': vector lengths do not match (lengths are '
|
||||
..#v1..' and '..#v2..')', 3) end
|
||||
return #v1
|
||||
end
|
||||
local function vector_checklen(v1, v2, name, len)
|
||||
vector_check(v1, 3, name, 1)
|
||||
vector_check(v2, 3, name, 2)
|
||||
if #v1~=len or #v2~=len then
|
||||
error('vector '..name..': vector lengths are not '..len..' (lengths are '
|
||||
..#v1..' and '..#v2..')', 3) end
|
||||
end
|
||||
local function vector_opnnn(name, op)
|
||||
return function(v1, v2)
|
||||
local len = vector_checksamelen(v1, v2, name)
|
||||
local v3 = {}
|
||||
for i = 1, len do
|
||||
v3[i] = op(v1[i], v2[i])
|
||||
end
|
||||
return vector_new(v3)
|
||||
end
|
||||
end
|
||||
local function vector_opnxn(name, op)
|
||||
return function(v1, v2)
|
||||
local v1v = type(v1)=='table' and v1.__is_vector
|
||||
local v2v = type(v2)=='table' and v2.__is_vector
|
||||
if v1v and v2v then
|
||||
local len = vector_checksamelen(v1, v2, name)
|
||||
local v3 = {}
|
||||
for i = 1, len do
|
||||
v3[i] = op(v1[i], v2[i])
|
||||
end
|
||||
return vector_new(v3)
|
||||
else
|
||||
if v2v then v1,v2 = v2,v1 end
|
||||
local len = #v1
|
||||
local v3 = {}
|
||||
for i = 1, len do
|
||||
v3[i] = op(v1[i], v2)
|
||||
end
|
||||
return vector_new(v3)
|
||||
end
|
||||
end
|
||||
end
|
||||
local function vector_opn0n(name, op)
|
||||
return function(v1)
|
||||
--vector_check(v1, 1, name)
|
||||
local len = #v1
|
||||
local v2 = {}
|
||||
for i = 1, len do
|
||||
v2[i] = op(v1[i])
|
||||
end
|
||||
return vector_new(v2)
|
||||
end
|
||||
end
|
||||
local vector_indices = {x = 1, y = 2, z = 3, w = 4, r = 1, g = 2, b = 3, a = 4}
|
||||
local vector_meta = {
|
||||
__is_vector = true,
|
||||
__index = function(t, k)
|
||||
if tonumber(k) then return rawget(t, k)
|
||||
elseif vector_indices[k] then return rawget(t, vector_indices[k])
|
||||
else return getmetatable(t)[k]
|
||||
end
|
||||
end,
|
||||
__newindex = function(t, k, v)
|
||||
if tonumber(k) then rawset(t, k, v)
|
||||
elseif vector_indices[k] then rawset(t, vector_indices[k], v)
|
||||
else return
|
||||
end
|
||||
end,
|
||||
__add = vector_opnnn('add', function(x1, x2) return x1+x2 end),
|
||||
__sub = vector_opnnn('sub', function(x1, x2) return x1-x2 end),
|
||||
__mul = vector_opnxn('mul', function(x1, x2) return x1*x2 end),
|
||||
__div = vector_opnxn('div', function(x1, x2) return x1/x2 end),
|
||||
__pow = vector_opnxn('pow', function(x1, x2) return x1^x2 end),
|
||||
__unm = vector_opn0n('inv', function(x1) return -x1 end),
|
||||
__concat = nil,
|
||||
--__len = function(v1) return #v1 end,
|
||||
__len = nil,
|
||||
__eq = function(v1, v2)
|
||||
local len = vector_checksamelen(v1, v2, 'equals')
|
||||
for i = 1, len do
|
||||
if v1[i]~=v2[i] then return false end
|
||||
end
|
||||
return true
|
||||
end,
|
||||
__lt = nil,
|
||||
__le = nil,
|
||||
__call = nil,
|
||||
abs = vector_opn0n('abs', math.abs),
|
||||
length = function(v1)
|
||||
--vector_check(v1, 2, 'length')
|
||||
local len = #v1
|
||||
local l = 0
|
||||
for i = 1, len do
|
||||
l = l + v1[i]^2
|
||||
end
|
||||
return math.sqrt(l)
|
||||
end,
|
||||
normalize = function(v1)
|
||||
--vector_check(v1, 2, 'normal')
|
||||
local length = v1:length()
|
||||
local len = #v1
|
||||
local v3 = {}
|
||||
for i = 1, len do
|
||||
if length==0 then v3[i] = 0
|
||||
else v3[i] = v1[i]/length end
|
||||
end
|
||||
return vector_new(v3)
|
||||
end,
|
||||
__tostring = function(v1)
|
||||
--vector_check(v1, 2, 'tostring')
|
||||
local st = {}
|
||||
local len = #v1
|
||||
for i = 1, len do
|
||||
table.insert(st, tostring(v1[i]))
|
||||
end
|
||||
return '{ '..table.concat(st, ', ')..' }'
|
||||
end,
|
||||
unpack = function(v1) return unpack(v1) end,
|
||||
floor = vector_opn0n('floor', function(x1) return math.floor(x1) end),
|
||||
ceil = vector_opn0n('ceil' , function(x1) return math.ceil (x1) end),
|
||||
round = vector_opn0n('round', function(x1) return math.floor(x1+0.5) end),
|
||||
dot = function(v1, v2)
|
||||
local len = vector_checksamelen(v1, v2, 'dot')
|
||||
local x = 0
|
||||
for i = 1, len do
|
||||
x = x + v1[i]*v2[i]
|
||||
end
|
||||
return x
|
||||
end,
|
||||
cross = function(v1, v2)
|
||||
vector_checklen(v1, v2, 'cross', 3)
|
||||
return vector_new{
|
||||
v1[2]*v2[3] - v1[3]*v2[2],
|
||||
v1[3]*v2[1] - v1[1]*v2[3],
|
||||
v1[1]*v2[2] - v1[2]*v2[1],
|
||||
}
|
||||
end,
|
||||
rotateByAngleId = function(v1, r)
|
||||
--vector_check(v1, 2, 'rotate')
|
||||
if type(r)~='number' or r%1~=0 then
|
||||
error('vector rotateByAngleId: invalid rotation '..tostring(r), 2) end
|
||||
r = r%4
|
||||
local v2
|
||||
if r==0 then v2 = vector_new{ v1[1], v1[2], v1[3] }
|
||||
elseif r==1 then v2 = vector_new{ v1[2], -v1[1], v1[3] }
|
||||
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
|
||||
end,
|
||||
rotate2d = function(v, r)
|
||||
--vector_check(v, 2, 'rotate2d')
|
||||
if type(r)~='number' then
|
||||
error('vector rotate2d: invalid rotation '..tostring(r), 2) end
|
||||
local len = math.sqrt(v[1]^2 + v[2]^2)
|
||||
local ang = math.atan2(v[2], v[1]) + r
|
||||
local v2 = vector_new{ math.cos(ang)*len, math.sin(ang)*len }
|
||||
return v2
|
||||
end,
|
||||
tsString = function(v)
|
||||
--vector_check(v, 2, 'tsString')
|
||||
return table.concat(v, ' ')
|
||||
end,
|
||||
distance = function(v1, v2)
|
||||
local len = vector_checksamelen(v1, v2, 'distance')
|
||||
local sum = 0
|
||||
for i=1,len do
|
||||
sum = sum + (v1[i] - v2[i])^2
|
||||
end
|
||||
return math.sqrt(sum)
|
||||
end,
|
||||
copy = function(v)
|
||||
--vector_check(v, 2, 'copy')
|
||||
return vector_new(v)
|
||||
end,
|
||||
}
|
||||
vector_new = function(vi)
|
||||
if vi then
|
||||
if type(vi)=='string' then
|
||||
local vi2 = {}
|
||||
for val in vi:gmatch('[0-9%.%-e]+') do
|
||||
table.insert(vi2, tonumber(val))
|
||||
end
|
||||
vi = vi2
|
||||
elseif type(vi)~='table' then
|
||||
error('vector: argument #1: expected input table, got '..type(vi), 2)
|
||||
end
|
||||
local v = {}
|
||||
if #vi>0 then
|
||||
for i = 1, #vi do v[i] = vi[i] end
|
||||
else
|
||||
for n, i in pairs(vector_indices) do v[i] = vi[n] end
|
||||
if #v==0 then
|
||||
error('vector: argument #1: table contains no values', 2)
|
||||
end
|
||||
end
|
||||
setmetatable(v, vector_meta)
|
||||
return v
|
||||
else
|
||||
error('vector: argument #1: expected input table, got nil', 2)
|
||||
end
|
||||
end
|
||||
|
||||
vector = vector_new
|
||||
return vector_new
|
||||
Reference in New Issue
Block a user