123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290 |
- R""( do
- -- 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.
- -- Implement print using ts.echo so it prints to BL console
- print = function(...)
- local t = {...}; local o = {};
- for i = 1, #t do o[i] = tostring(t[i]) end
- ts.echo(table.concat(o, " "))
- end
- -- Provide limited OS functions
- os = {
- time = function() return math.floor(tonumber(ts.call("getSimTime"))/1000) end,
- clock = function() return tonumber(ts.call("getSimTime"))/1000 end,
- }
- -- schedule: Use TS's schedule function to schedule lua calls
- ts._schedule_table = ts._schedule_table or {}
- ts._schedule_by_obj = ts._schedule_by_obj or {}
- ts._schedule_nextid = ts._schedule_nextid or 1
- function schedule(time, func, ...)
- -- get a unique schedule ID
- local sched_id = ts._schedule_nextid
- ts._schedule_nextid = ts._schedule_nextid+1
- -- create a callback to call the function when the schedule ends
- local fargs = {...}
- local callback = function()
- func(unpack(fargs))
- end
- -- put it in the schedule table
- ts._schedule_table[sched_id] = { [1] = callback }
- -- use ts schedule to schedule the event
- local sched_obj = tonumber(ts.call("schedule", time, 0, "luacall", "_ts_schedule_callback", sched_id))
- -- save the schedule reference in a table and return the id
- ts._schedule_table[sched_id][2] = sched_obj
- return sched_id
- end
- function _ts_schedule_callback(sched_id)
- -- get schedule out of schedule table by id
- sched_id = tonumber(sched_id)
- local sched = ts._schedule_table[sched_id]
- assert(sched, "_ts_schedule_callback: no schedule with id "..sched_id)
- ts._schedule_table[sched_id] = nil
- -- call it
- sched[1]()
- end
- function cancel(sched_id_s)
- sched_id = tonumber(sched_id_s)
- -- get the schedule by id out of the table
- local sched = ts._schedule_table[sched_id]
- if not sched then return end
- -- cancel it in ts
- ts.call("cancel", sched[2])
- -- remove from table
- ts._schedule_table[sched_id] = nil
- end
- -- wrap io.open to allow reading from zips
- -- Not perfect because TS file I/O sucks - Can't read nulls, can't distinguish between CRLF and LF.
- 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 = ts.call("_bllua3_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 tflip = function(t) local u = {}; for _, n in ipairs(t) do u[n] = true end; return u; end
- local bl_allowed_zip_dirs = tflip{"add-ons", "base", "config", "saves", "screenshots"}
- local function io_open_absolute(fn, mode)
- -- use original mode if possible
- 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 bl_allowed_zip_dirs[dir:lower()] then return nil, "File is not in one of the allowed directories" end
- local exist = ts.call("isFile", fn) == "1"
- if not exist then return nil, err end
-
- if mode~=nil and mode~="r" 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 = {
- open = function(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 ts.get("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,
- lines = function(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,
- type = function(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 and require
- 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 eval("--[["..fn2.."]]"..text)
- end
- -- provide eval
- function eval(code)
- return assert(loadstring(code))()
- end
- local function file_exists(fn, errn)
- local fi, err, fn2 = io.open(fn, "r", errn+1)
- if fi then
- return fn2
- else
- return nil
- end
- end
- function require(fn)
- local fn1 = "./"..(fn:gsub("%.", "/"))..".lua"
- local fn2 = "luainc/"..(fn:gsub("%.", "/"))..".lua"
-
- local fn3 = file_exists(fn1, 2) or file_exists(fn2, 2)
- if fn3 then
- return dofile(fn3, 2)
- else
- return requiresecure(fn)
- end
- end
- -- get and set globals
- function ts.get(name) return ts.call("_bllua_get_var", name) end
- function ts.set(name, val) ts.call("_bllua_set_var", name, val) end
- function ts.getobj(obj, name) return ts.call("_bllua_get_var_obj", obj, name) end
- function ts.setobj(obj, name, val) ts.call("_bllua_set_var_obj", obj, name, val) end
- function _bllua_get_var(name) return _G[name] end
- function _bllua_set_var(name, val) _G[name] = val end
- -- ts.eval and ts.exec
- function ts.eval(code) return ts.call("eval", code) end
- function ts.exec(file) return ts.call("exec", file) end
- -- packages
- --ts._package_table = ts._package_table or {}
- --function _bllua_call_package(id, ...)
- -- id = tonumber(id)
- -- local func = ts._package_table[id] or error("no function with id "..id)
- -- --local parent =
- -- return func(parent, ...)
- --end
- --function ts.package(pname, fname, func)
- -- local arglist = [[%a, %b, %c, %d, %e, %f, %g, %h, %i, %j, %k, %l, %m, %n, %o, %p, %q, %r, %s, %t]]
- -- ts.eval [[
- -- package ]]..pname..[[ {
- -- function ]]..fname..[[ (]]..arglist..[[) {
- -- return luacall("_bllua_call_package", ]]..func_id_pre..[[, ]]..arglist..[[);
- -- }
- -- };
- -- ]]
- --end
- function ts.toBoolean(v) local n = tonumber(v); return n ~= nil and n ~= 0; end
- function ts.toNumber(v) return tonumber(v) end
- -- 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
- print(" Lua util loaded")
- end )""
|