diff --git a/res/content/base/scripts/components/drop.lua b/res/content/base/scripts/components/drop.lua index 2b798a386..39bd03ceb 100644 --- a/res/content/base/scripts/components/drop.lua +++ b/res/content/base/scripts/components/drop.lua @@ -2,7 +2,6 @@ local tsf = entity.transform local body = entity.rigidbody local rig = entity.skeleton -inair = true target = -1 timer = 0.3 @@ -20,40 +19,58 @@ if SAVED_DATA.item then dropitem.data = SAVED_DATA.data end -local DROP_SCALE = 0.3 -local scale = {1, 1, 1} -local rotation = mat4.rotate({ - math.random(), math.random(), math.random() -}, 360) - function on_save() SAVED_DATA.item = item.name(dropitem.id) SAVED_DATA.count = dropitem.count SAVED_DATA.data = dropitem.data end -do -- setup visuals - local matrix = mat4.idt() - rig:set_model(0, item.model_name(dropitem.id)) - local bodysize = math.min(scale[1], scale[2], scale[3]) * DROP_SCALE - body:set_size({scale[1] * DROP_SCALE, bodysize, scale[3] * DROP_SCALE}) - mat4.mul(matrix, rotation, matrix) - mat4.scale(matrix, scale, matrix) - rig:set_matrix(0, matrix) +if vc.is_client() then + local scale = {1, 1, 1} + local rotation = mat4.rotate({ + math.random(), math.random(), math.random() + }, 360) + + do + local matrix = mat4.idt() + rig:set_model(0, item.model_name(dropitem.id)) + local bodysize = math.min(scale[1], scale[2], scale[3]) + body:set_size({scale[1], bodysize, scale[3]}) + mat4.mul(matrix, rotation, matrix) + mat4.scale(matrix, scale, matrix) + rig:set_matrix(0, matrix) + end + + local inair = true + + function on_grounded() + local matrix = mat4.idt() + mat4.rotate(matrix, {0, 1, 0}, math.random() * 360, matrix) + mat4.rotate(matrix, {1, 0, 0}, 90, matrix) + mat4.scale(matrix, scale, matrix) + rig:set_matrix(0, matrix) + inair = false + end + + function on_fall() + inair = true + end + + function on_render() + if inair then + local dt = time.delta(); + + mat4.rotate(rotation, {0, 1, 0}, 240*dt, rotation) + mat4.rotate(rotation, {0, 0, 1}, 240*dt, rotation) + + local matrix = mat4.idt() + mat4.mul(matrix, rotation, matrix) + mat4.scale(matrix, scale, matrix) + rig:set_matrix(0, matrix) + end + end end -function on_grounded(force) - local matrix = mat4.idt() - mat4.rotate(matrix, {0, 1, 0}, math.random() * 360, matrix) - mat4.rotate(matrix, {1, 0, 0}, 90, matrix) - mat4.scale(matrix, scale, matrix) - rig:set_matrix(0, matrix) - inair = false -end - -function on_fall() - inair = true -end function on_sensor_enter(index, oid) local other = entities.get(oid) @@ -98,20 +115,6 @@ function on_sensor_exit(index, oid) end end -function on_render() - if inair then - local dt = time.delta(); - - mat4.rotate(rotation, {0, 1, 0}, 240*dt, rotation) - mat4.rotate(rotation, {0, 0, 1}, 240*dt, rotation) - - local matrix = mat4.idt() - mat4.mul(matrix, rotation, matrix) - mat4.scale(matrix, scale, matrix) - rig:set_matrix(0, matrix) - end -end - function on_update(tps) timer = timer - 1.0/tps diff --git a/res/content/base/scripts/components/falling_block.lua b/res/content/base/scripts/components/falling_block.lua index b6bbe4656..01c5bc5f2 100644 --- a/res/content/base/scripts/components/falling_block.lua +++ b/res/content/base/scripts/components/falling_block.lua @@ -1,6 +1,5 @@ local tsf = entity.transform local body = entity.rigidbody -local rig = entity.skeleton local blockid = ARGS.block local blockstates = ARGS.states or 0 @@ -12,7 +11,8 @@ else SAVED_DATA.states = blockstates end -do -- setup visuals +if vc.is_client() then-- setup visuals + local rig = entity.skeleton local id = block.index(blockid) local rotation = block.decompose_state(blockstates)[1] local textures = block.get_textures(id) diff --git a/res/content/base/scripts/components/player_animator.lua b/res/content/base/scripts/components/player_animator.lua index f82927ccd..6bd118906 100644 --- a/res/content/base/scripts/components/player_animator.lua +++ b/res/content/base/scripts/components/player_animator.lua @@ -1,5 +1,3 @@ -local tsf = entity.transform -local body = entity.rigidbody local rig = entity.skeleton local mob = entity:require_component("core:mob") diff --git a/res/content/base/scripts/grass_block.lua b/res/content/base/scripts/grass_block.lua index e58597cc4..fb7482c20 100644 --- a/res/content/base/scripts/grass_block.lua +++ b/res/content/base/scripts/grass_block.lua @@ -1,20 +1,26 @@ +local offsets = {} +for lx=-1,1 do + for ly=-1,1 do + for lz=-1,1 do + offsets[(lx * 3 + ly) * 3 + lz] = {lx, ly, lz} + end + end +end + function on_random_update(x, y, z) local dirtid = block.index('base:dirt'); if block.is_solid_at(x, y+1, z) then block.set(x, y, z, dirtid, 0) - else - local grassblockid = block.index('base:grass_block') - for lx=-1,1 do - for ly=-1,1 do - for lz=-1,1 do - if block.get(x + lx, y + ly, z + lz) == dirtid then - if not block.is_solid_at(x + lx, y + ly + 1, z + lz) then - block.set(x + lx, y + ly, z + lz, grassblockid, 0) - return - end - end - end - end + return + end + local grassblockid = block.index('base:grass_block') + for _, offset in ipairs(offsets) do + local nx, ny, nz = x + offset[1], y + offset[2], z + offset[3] + + if block.get(nx, ny, nz) == dirtid and + not block.is_solid_at(nx, ny + 1, nz) then + block.set(nx, ny, nz, grassblockid, 0) + return end end end diff --git a/res/content/base/scripts/world.lua b/res/content/base/scripts/world.lua index 7f468c291..419dcc769 100644 --- a/res/content/base/scripts/world.lua +++ b/res/content/base/scripts/world.lua @@ -1,5 +1,5 @@ function on_block_broken(id, x, y, z, playerid) - if gfx then + if vc.is_client() then local size = {block.get_size(id)} gfx.particles.emit({ x + size[1] * 0.5, diff --git a/res/modules/internal/gui_util.lua b/res/modules/internal/gui_util.lua index 0f53faaba..0ec47291e 100644 --- a/res/modules/internal/gui_util.lua +++ b/res/modules/internal/gui_util.lua @@ -24,7 +24,7 @@ function gui_util.parse_query(query) end --- @param query string page query string -function gui_util.load_page(query) +function gui_util.load_page(app, query) local name, args = gui_util.parse_query(query) for i = #gui_util.local_dispatchers, 1, -1 do local newname, newargs = gui_util.local_dispatchers[i](name, args) @@ -34,7 +34,7 @@ function gui_util.load_page(query) local filename = file.find(string.format("layouts/pages/%s.xml", name)) if filename then name = file.prefix(filename)..":pages/"..name - gui.load_document(filename, name, args, { app = __vc_app }) + gui.load_document(filename, name, args, { app = app }) return name end end diff --git a/res/scripts/components/player.lua b/res/scripts/components/player.lua index e829f4b5a..4f2ec53a8 100644 --- a/res/scripts/components/player.lua +++ b/res/scripts/components/player.lua @@ -80,7 +80,7 @@ function on_physics_update(delta) local front = mat4.mul(rot, {0, 0, -1}) local pos = tsf:get_pos() - if hud and pid == hud.get_player() then + if vc.is_client() and pid == hud.get_player() then process_player_inputs(pid, rot, delta) end mob.look_at(vec3.add(pos, front)) diff --git a/res/scripts/hud.lua b/res/scripts/hud.lua index 322f03db5..562fe19d1 100644 --- a/res/scripts/hud.lua +++ b/res/scripts/hud.lua @@ -1,37 +1,3 @@ -local function configure_SSAO() - -- Temporary using slot to configure built-in SSAO effect - local slot = gfx.posteffects.index("core:ssao") - gfx.posteffects.set_effect(slot, "ssao") - - -- Generating random SSAO samples - local buffer = Bytearray(0) - for i = 0, 63 do - local x = math.random() * 2.0 - 1.0 - local y = math.random() * 2.0 - 1.0 - local z = math.random() * 2.0 - local len = math.sqrt(x * x + y * y + z * z) - if len > 0 then - x = x / len - y = y / len - z = z / len - end - Bytearray.append(buffer, byteutil.pack("fff", x, y, z)) - end - gfx.posteffects.set_array(slot, "u_ssaoSamples", Bytearray_as_string(buffer)) - - local function update_ssao_quality(value) - value = math.min(value, 3) - gfx.posteffects.set_params(slot, { - u_kernelSize = value * 16, - u_radius = 0.4 / value, - u_bias = 0.006 / value / value, - }) - end - events.on("core:setting.graphics.ssao.set", update_ssao_quality) - - update_ssao_quality(__vc_app.get_setting("graphics.ssao")) -end - local function update_hand() local skeleton = gfx.skeletons local pid = hud.get_player() @@ -123,8 +89,6 @@ function on_hud_open() end end) - configure_SSAO() - hud.default_hand_controller = update_hand end diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index 900b71b2e..0e72ad86d 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -1,4 +1,5 @@ -local enable_experimental = __vc_app.get_setting("debug.enable-experimental") +local __app = __vc_app +local enable_experimental = __app.get_setting("debug.enable-experimental") ------------------------------------------------ ------ Extended kit of standard functions ------ @@ -119,12 +120,7 @@ local function complete_app_lib(app) end end -if app then - complete_app_lib(app) -elseif __vc_app then - complete_app_lib(__vc_app) -end - +complete_app_lib(__app) require "core:internal/maths_inline" require "core:internal/debugging" require "core:internal/audio_input" @@ -175,18 +171,18 @@ function start_coroutine(chunk, name) local co = coroutine.create(function() local status, error = xpcall(chunk, function(err) local fullmsg = "error: "..string.match(err, ": (.+)").."\n"..debug.traceback() - - if hud then - gui.alert(fullmsg, function() - if world.is_open() then - __vc_app.close_world() - else - __vc_app.reset_content() - menu:reset() - menu.page = "main" - end - end) + if vc.is_headless() then + return fullmsg end + gui.alert(fullmsg, function() + if world.is_open() then + __app.close_world() + else + __app.reset_content() + menu:reset() + menu.page = "main" + end + end) return fullmsg end) if not status then @@ -204,7 +200,7 @@ function __vc_start_app_script(path, name) if chunk == nil then error(err) end - local script_env = setmetatable({app = app or __vc_app}, {__index=_G}) + local script_env = setmetatable({app = __app}, {__index=_G}) chunk = setfenv(chunk, script_env) if name then start_coroutine(chunk, name) @@ -222,7 +218,7 @@ gui_util = require "core:internal/gui_util" Document = gui_util.Document Element = gui_util.Element RadioGroup = gui_util.RadioGroup -__vc_page_loader = gui_util.load_page +__vc_page_loader = function(...) return gui_util.load_page(__app, ...) end function __vc_get_document_node(docname, nodeid) return Element.new(docname, nodeid) @@ -384,6 +380,40 @@ math.randomseed(time.uptime() * 1536227939) rules = require "core:internal/rules" local _rules = rules +local function configure_SSAO() + -- Temporary using slot to configure built-in SSAO effect + local slot = gfx.posteffects.index("core:ssao") + gfx.posteffects.set_effect(slot, "ssao") + + -- Generating random SSAO samples + local buffer = Bytearray(0) + for i = 0, 63 do + local x = math.random() * 2.0 - 1.0 + local y = math.random() * 2.0 - 1.0 + local z = math.random() * 2.0 + local len = math.sqrt(x * x + y * y + z * z) + if len > 0 then + x = x / len + y = y / len + z = z / len + end + Bytearray.append(buffer, byteutil.pack("fff", x, y, z)) + end + gfx.posteffects.set_array(slot, "u_ssaoSamples", Bytearray_as_string(buffer)) + + local function update_ssao_quality(value) + value = math.min(value, 3) + gfx.posteffects.set_params(slot, { + u_kernelSize = value * 16, + u_radius = 0.4 / value, + u_bias = 0.006 / value / value, + }) + end + events.on("core:setting.graphics.ssao.set", update_ssao_quality) + + update_ssao_quality(__app.get_setting("graphics.ssao")) +end + function __vc_on_hud_open() local _hud_is_content_access = hud._is_content_access local _hud_set_content_access = hud._set_content_access @@ -446,6 +476,8 @@ function __vc_on_hud_open() end end) hud.open_permanent("core:ingame_chat") + + configure_SSAO() end local Schedule = require "core:schedule" @@ -508,20 +540,27 @@ function __vc_on_world_save() file.write(RULES_FILE, toml.tostring(rule_values)) end +local __close_all_descriptors = file.__close_all_descriptors +local __gui_util_reset_local = gui_util.__reset_local +local __stdcomp_reset = stdcomp.__reset +file.__close_all_descriptors = nil +gui_util.__reset_local = nil +stdcomp.reset = nil + function __vc_on_world_quit() _rules.clear() - gui_util:__reset_local() - stdcomp.__reset() - file.__close_all_descriptors() + __gui_util_reset_local() + __stdcomp_reset() + __close_all_descriptors() end -local __post_runnables = {} - local fn_audio_reset_fetch_buffer = audio.__reset_fetch_buffer audio.__reset_fetch_buffer = nil core.get_core_token = audio.input.__get_core_token -local function __vc__process_post_runnables() +local __post_runnables = {} + +local function __process_post_runnables() if #__post_runnables > 0 then for _, func in ipairs(__post_runnables) do local status, result = xpcall(func, __vc__error) @@ -555,9 +594,9 @@ local function __vc__process_post_runnables() end end -function __process_post_runnables() +function __vc__process_post_runnables() __vc__is_post_runnable = true - local success, err = pcall(__vc__process_post_runnables) + local success, err = pcall(__process_post_runnables) if not success then debug.error("an error ocurred while processing post-runnables: ".. err) end @@ -590,4 +629,6 @@ end require "core:internal/deprecated" ffi = nil +__vc_app = nil __vc_lock_internal_modules() +__vc_lock_internal_modules = nil diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index 4ae34b2fd..70e27f561 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -1,5 +1,17 @@ +local _vc_headless = __VC_HEADLESS +__VC_HEADLESS = nil + +vc = { + is_headless = function() + return _vc_headless + end, + is_client = function () + return not _vc_headless + end +} + local _ffi = ffi -local _debug_getinfo = debug.getinfo +local _crc32 = crc32 function crc32(bytes, chksum) chksum = chksum or 0 @@ -103,6 +115,8 @@ end ---------------------------------------------- +local _debug_getinfo = debug.getinfo + function debug.count_frames() local frames = 1 while true do @@ -178,7 +192,7 @@ function reload_module(name) end end -local internal_locked = false +local __internal_locked = false -- Load script with caching -- @@ -188,9 +202,12 @@ local internal_locked = false -- nocache - ignore cached script, load anyway function __load_script(path, nocache, env) local packname, filename = parse_path(path) + local is_internal = (packname == "res" or packname == "core") + and filename:find("modules/internal") == 1 - if internal_locked and (packname == "res" or packname == "core") - and filename:starts_with("modules/internal") then + nocache = nocache or is_internal + + if is_internal and __internal_locked then error("access to core:internal modules outside of [core]") end @@ -218,16 +235,19 @@ function __load_script(path, nocache, env) end function __vc_lock_internal_modules() - internal_locked = true + __internal_locked = true end +local __pack_envs = __vc__pack_envs +__vc__pack_envs = nil + function require(path) if not string.find(path, ':') then local prefix, _ = parse_path(_debug_getinfo(2).source) return require(prefix .. ':' .. path) end local prefix, file = parse_path(path) - local env = __vc__pack_envs[prefix] + local env = __pack_envs[prefix] return __load_script(prefix .. ":modules/" .. file .. ".lua", nil, env) end @@ -248,7 +268,7 @@ function __scripts_cleanup(non_reset_packs) __cached_scripts[k] = nil package.loaded[k] = nil end - __vc__pack_envs[packname] = nil + __pack_envs[packname] = nil ::continue:: end end diff --git a/src/content/ContentPack.cpp b/src/content/ContentPack.cpp index d2ffdc9e5..ec7168a28 100644 --- a/src/content/ContentPack.cpp +++ b/src/content/ContentPack.cpp @@ -47,10 +47,6 @@ io::path ContentPack::getContentFile() const { return folder / CONTENT_FILENAME; } -bool ContentPack::is_pack(const io::path& folder) { - return io::is_regular_file(folder / PACKAGE_FILENAME); -} - std::optional ContentPack::loadStats() const { auto contentFile = getContentFile(); if (!io::exists(contentFile)) { @@ -85,6 +81,9 @@ static void checkContentPackId(const std::string& id, const io::path& folder) { ); } } + if (id == "none") { + throw contentpack_error(id, folder, "content-pack id is not specified"); + } if (std::find( ContentPack::RESERVED_NAMES.begin(), ContentPack::RESERVED_NAMES.end(), @@ -94,10 +93,71 @@ static void checkContentPackId(const std::string& id, const io::path& folder) { } } +static DependencyPack parse_dependency(std::string depName) { + auto level = DependencyLevel::REQUIRED; + switch (depName.at(0)) { + case '!': + depName = depName.substr(1); + break; + case '?': + depName = depName.substr(1); + level = DependencyLevel::OPTIONAL; + break; + case '~': + depName = depName.substr(1); + level = DependencyLevel::WEAK; + break; + } + + std::string depVer = "*"; + std::string depVerOperator = "="; + + size_t versionPos = depName.rfind("@"); + if (versionPos != std::string::npos) { + depVer = depName.substr(versionPos + 1); + depName = depName.substr(0, versionPos); + + if (depVer.size() >= 2) { + std::string op = depVer.substr(0, 2); + std::uint8_t op_size = 0; + + // Two symbol operators + if (op == ">=" || op == "<=") { + op_size = 2; + depVerOperator = op; + } + + // One symbol operators + else { + op = depVer.substr(0, 1); + + if (op == ">" || op == "<") { + op_size = 1; + depVerOperator = op; + } + } + + depVer = depVer.substr(op_size); + } else { + if (depVer == ">" || depVer == "<"){ + depVer = "*"; + } + } + } + + VersionOperator versionOperator; + if (VersionOperatorMeta.getItem(depVerOperator, versionOperator)) { + return DependencyPack{level, depName, depVer, versionOperator}; + } else { + throw std::runtime_error("invalid version operator"); + } +} + ContentPack ContentPack::read(const io::path& folder) { auto root = io::read_json(folder / PACKAGE_FILENAME); ContentPack pack; root.at("id").get(pack.id); + checkContentPackId(pack.id, folder); root.at("title").get(pack.title); root.at("version").get(pack.version); if (root.has("creators")) { @@ -115,81 +175,26 @@ ContentPack ContentPack::read(const io::path& folder) { root.at("source").get(pack.source); pack.folder = folder; + auto dependenciesList = root.at("dependencies"); + if (!dependenciesList) { + return pack; + } + if (auto found = root.at("dependencies")) { const auto& dependencies = *found; for (const auto& elem : dependencies) { std::string depName = elem.asString(); - auto level = DependencyLevel::REQUIRED; - switch (depName.at(0)) { - case '!': - depName = depName.substr(1); - break; - case '?': - depName = depName.substr(1); - level = DependencyLevel::OPTIONAL; - break; - case '~': - depName = depName.substr(1); - level = DependencyLevel::WEAK; - break; - } - - std::string depVer = "*"; - std::string depVerOperator = "="; - - size_t versionPos = depName.rfind("@"); - if (versionPos != std::string::npos) { - depVer = depName.substr(versionPos + 1); - depName = depName.substr(0, versionPos); - - if (depVer.size() >= 2) { - std::string op = depVer.substr(0, 2); - std::uint8_t op_size = 0; - - // Two symbol operators - if (op == ">=" || op == "<=") { - op_size = 2; - depVerOperator = op; - } - - // One symbol operators - else { - op = depVer.substr(0, 1); - - if (op == ">" || op == "<") { - op_size = 1; - depVerOperator = op; - } - } - - depVer = depVer.substr(op_size); - } else { - if (depVer == ">" || depVer == "<"){ - depVer = "*"; - } - } - } - - VersionOperator versionOperator; - if (VersionOperatorMeta.getItem(depVerOperator, versionOperator)) { - pack.dependencies.push_back( - {level, depName, depVer, versionOperator} - ); - } else { + try { + pack.dependencies.push_back(parse_dependency(std::move(depName))); + } catch (const std::runtime_error& err) { throw contentpack_error( - pack.id, folder, "invalid version operator" + pack.id, + folder, + "dependency parsing error: " + std::string(err.what()) ); } } } - - if (pack.id == "none") { - throw contentpack_error( - pack.id, folder, "content-pack id is not specified" - ); - } - checkContentPackId(pack.id, folder); - return pack; } @@ -200,15 +205,21 @@ void ContentPack::scanFolder( return; } for (const auto& packFolder : io::directory_iterator(folder)) { - if (!io::is_directory(packFolder)) continue; - if (!is_pack(packFolder)) continue; + if (!io::is_directory(packFolder)) { + continue; + } + auto packageFile = packFolder / PACKAGE_FILENAME; + if (!io::is_regular_file(packageFile)) { + continue; + } try { packs.push_back(read(packFolder)); } catch (const contentpack_error& err) { - std::cerr << "package.json error at " << err.getFolder().string(); - std::cerr << ": " << err.what() << std::endl; + logger.warning() << "package.json error at " + << err.getFolder().string() << ": " << err.what(); } catch (const std::runtime_error& err) { - std::cerr << err.what() << std::endl; + logger.error() << "reading " << packageFile.string() + << " error: " << err.what(); } } } diff --git a/src/content/ContentPack.hpp b/src/content/ContentPack.hpp index 06a8cf4dc..cd5f93dbc 100644 --- a/src/content/ContentPack.hpp +++ b/src/content/ContentPack.hpp @@ -85,7 +85,6 @@ struct ContentPack { static inline const io::path GENERATORS_FOLDER = "generators"; static const std::vector RESERVED_NAMES; - static bool is_pack(const io::path& folder); static ContentPack read(const io::path& folder); static void scanFolder( @@ -114,6 +113,14 @@ struct ContentPack { default: return ""; } } + + static std::string_view getPrefix(std::string_view path) { + size_t pos = path.find(':'); + if (pos == std::string::npos) { + return ""; + } + return path.substr(0, pos); + } }; struct WorldFuncsSet { diff --git a/src/logic/scripting/lua/libs/libgui.cpp b/src/logic/scripting/lua/libs/libgui.cpp index 382113171..0e4645adc 100644 --- a/src/logic/scripting/lua/libs/libgui.cpp +++ b/src/logic/scripting/lua/libs/libgui.cpp @@ -1010,8 +1010,9 @@ static int l_gui_get_env(lua::State* L) { "document '" + std::string(name) + "' not found" ); } - lua::getglobal(L, lua::env_name(*doc->getEnvironment())); - return 1; + return lua::getregistry( + L, lua::ENVS_TABLE, lua::env_name(*doc->getEnvironment()) + ); } static int l_gui_str(lua::State* L) { diff --git a/src/logic/scripting/lua/lua_engine.cpp b/src/logic/scripting/lua/lua_engine.cpp index 5edb24fd9..bd2f0359b 100644 --- a/src/logic/scripting/lua/lua_engine.cpp +++ b/src/logic/scripting/lua/lua_engine.cpp @@ -64,12 +64,13 @@ static void create_libs(State* L, StateType stateType) { openlib(L, "vec4", vec4lib); openlib(L, "yaml", yamllib); + openlib(L, "__vc_app", applib); + lua::getglobal(L, "__vc_app"); + lua::setregistry(L, "app"); + if (stateType == StateType::SCRIPT) { - openlib(L, "app", applib); - lua::getglobal(L, "app"); - lua::setglobal(L, "__vc_app"); - } else if (stateType == StateType::BASE) { - openlib(L, "__vc_app", applib); + lua::getregistry(L, "app"); + lua::setglobal(L, "app"); } if (stateType == StateType::BASE || stateType == StateType::SCRIPT) { openlib(L, "assets", assetslib); @@ -95,10 +96,18 @@ static void create_libs(State* L, StateType stateType) { } addfunc(L, "print", lua::wrap); - addfunc(L, "_crc32", lua::wrap); + addfunc(L, "crc32", lua::wrap); +} + +static int l_panic_handler(lua::State* L) { + logger.error() << "PANIC: unprotected error in call to Lua API: " << lua::tostring(L, -1); + logger.flush(); + abort(); } void lua::init_state(State* L, StateType stateType) { + lua_atpanic(L, l_panic_handler); + // Allowed standard libraries luaL_openlibs(L); @@ -112,21 +121,25 @@ void lua::init_state(State* L, StateType stateType) { setglobal(L, "io"); createtable(L, 0, 0); + pushvalue(L, -1); setglobal(L, "__vc__pack_envs"); + setregistry(L, lua::PACK_ENVS_TABLE); const char* removed_os[] { "execute", "exit", "remove", "rename", "setlocale", "tmpname", nullptr}; remove_lib_funcs(L, "os", removed_os); create_libs(L, stateType); + createtable(L, 0, 0); + setregistry(L, LAMBDAS_TABLE); + + createtable(L, 0, 0); + setregistry(L, CHUNKS_TABLE); + + createtable(L, 0, 0); pushglobals(L); - setglobal(L, env_name(0)); - - createtable(L, 0, 0); - setglobal(L, LAMBDAS_TABLE); - - createtable(L, 0, 0); - setglobal(L, CHUNKS_TABLE); + setfield(L, env_name(0)); + setregistry(L, ENVS_TABLE); initialize_libs_extends(L); @@ -144,6 +157,9 @@ void lua::initialize(const EnginePaths& paths, const CoreParameters& params) { ); lua::pushstring(main_thread, params.scriptFile.stem().u8string()); lua::setglobal(main_thread, "__VC_SCRIPT_NAME"); + + lua::pushboolean(main_thread, params.headless); + lua::setglobal(main_thread, "__VC_HEADLESS"); } void lua::finalize() { diff --git a/src/logic/scripting/lua/lua_util.cpp b/src/logic/scripting/lua/lua_util.cpp index 6fdbe91ea..ded9cc79b 100644 --- a/src/logic/scripting/lua/lua_util.cpp +++ b/src/logic/scripting/lua/lua_util.cpp @@ -8,7 +8,7 @@ using namespace lua; -static int nextEnvironment = 1; +static int next_environment = 1; std::unordered_map lua::usertypeNames; @@ -20,7 +20,7 @@ int lua::userdata_destructor(lua::State* L) { } std::string lua::env_name(int env) { - return "_ENV" + util::mangleid(env); + return std::to_string(env); } int lua::pushvalue(State* L, const dv::value& value) { @@ -220,7 +220,7 @@ void lua::dump_stack(State* L) { static std::shared_ptr create_lambda_handler(State* L) { auto ptr = reinterpret_cast(topointer(L, -1)); auto name = util::mangleid(ptr); - requireglobal(L, LAMBDAS_TABLE); + requireregistry(L, LAMBDAS_TABLE); pushvalue(L, -2); setfield(L, name); pop(L, 2); @@ -229,7 +229,7 @@ static std::shared_ptr create_lambda_handler(State* L) { new std::string(name), [=](std::string* name) { auto L = lua::get_main_state(); - requireglobal(L, LAMBDAS_TABLE); + requireregistry(L, LAMBDAS_TABLE); pushnil(L); setfield(L, *name); pop(L); @@ -242,8 +242,9 @@ runnable lua::create_runnable(State* L) { auto funcptr = create_lambda_handler(L); return [=]() { auto L = lua::get_main_state(); - if (!get_from(L, LAMBDAS_TABLE, *funcptr, false)) + if (!get_from_registry(L, LAMBDAS_TABLE, *funcptr, false)) { return; + } call_nothrow(L, 0, 0); pop(L); }; @@ -252,8 +253,9 @@ runnable lua::create_runnable(State* L) { KeyCallback lua::create_simple_handler(State* L) { auto funcptr = create_lambda_handler(L); return [=]() -> bool { - if (!get_from(L, LAMBDAS_TABLE, *funcptr, false)) + if (!get_from_registry(L, LAMBDAS_TABLE, *funcptr, false)) { return false; + } int top = gettop(L) - 1; if (call_nothrow(L, 0)) { int nres = gettop(L) - top; @@ -271,8 +273,9 @@ KeyCallback lua::create_simple_handler(State* L) { scripting::common_func lua::create_lambda(State* L) { auto funcptr = create_lambda_handler(L); return [=](const std::vector& args) -> dv::value { - if (!get_from(L, LAMBDAS_TABLE, *funcptr, false)) + if (!get_from_registry(L, LAMBDAS_TABLE, *funcptr, false)) { return nullptr; + } int top = gettop(L) - 1; for (const auto& arg : args) { pushvalue(L, arg); @@ -294,8 +297,9 @@ scripting::common_func lua::create_lambda(State* L) { scripting::common_func lua::create_lambda_nothrow(State* L) { auto funcptr = create_lambda_handler(L); return [=](const std::vector& args) -> dv::value { - if (!get_from(L, LAMBDAS_TABLE, *funcptr, false)) + if (!get_from_registry(L, LAMBDAS_TABLE, *funcptr, false)) { return nullptr; + } int top = gettop(L) - 1; for (const auto& arg : args) { pushvalue(L, arg); @@ -313,8 +317,16 @@ scripting::common_func lua::create_lambda_nothrow(State* L) { }; } +static void store_env(lua::State* L, int id) { + requireregistry(L, ENVS_TABLE); + pushvalue(L, -2); + lua_remove(L, -3); + setfield(L, env_name(id)); + pop(L); +} + int lua::create_environment(State* L, int parent) { - int id = nextEnvironment++; + int id = next_environment++; // local env = {} createtable(L, 0, 1); @@ -331,24 +343,22 @@ int lua::create_environment(State* L, int parent) { setfield(L, "__index"); setmetatable(L); - // envname = env - setglobal(L, env_name(id)); + store_env(L, id); return id; } int lua::restore_pack_environment(lua::State* L, const std::string& packid) { - if(!lua::getglobal(L, "__vc__pack_envs")) { + if(!requireregistry(L, PACK_ENVS_TABLE)) { return -1; } - int id = nextEnvironment++; + int id = next_environment++; - if (lua::getfield(L, packid)) { - // envname = env - setglobal(L, env_name(id)); - lua::pop(L); + if (getfield(L, packid)) { + store_env(L, id); + pop(L); return id; } - lua::pop(L); + pop(L); return -1; } @@ -357,5 +367,5 @@ void lua::remove_environment(State* L, int id) { return; } pushnil(L); - setglobal(L, env_name(id)); + store_env(L, id); } diff --git a/src/logic/scripting/lua/lua_util.hpp b/src/logic/scripting/lua/lua_util.hpp index 236eaf401..a7881bef7 100644 --- a/src/logic/scripting/lua/lua_util.hpp +++ b/src/logic/scripting/lua/lua_util.hpp @@ -14,21 +14,35 @@ namespace lua { inline std::string LAMBDAS_TABLE = "$L"; // lambdas storage inline std::string CHUNKS_TABLE = "$C"; // precompiled lua chunks + inline std::string PACK_ENVS_TABLE = "$P"; + inline std::string ENVS_TABLE = "$E"; extern std::unordered_map usertypeNames; int userdata_destructor(lua::State* L); std::string env_name(int env); void dump_stack(lua::State*); - inline bool getglobal(lua::State* L, const std::string& name) { - lua_getglobal(L, name.c_str()); - if (isnil(L, -1)) { + inline bool isnoneornil(lua::State* L, int idx) { + return lua_isnoneornil(L, idx); + } + + inline bool getfield(lua::State* L, const std::string& name, int idx = -1) { + lua_getfield(L, idx, name.c_str()); + if (isnoneornil(L, -1)) { pop(L); return false; } return true; } + inline bool getglobal(lua::State* L, const std::string& name) { + return getfield(L, name, LUA_GLOBALSINDEX); + } + + inline bool getregistry(lua::State* L, const std::string& name) { + return getfield(L, name, LUA_REGISTRYINDEX); + } + inline int requireglobal(lua::State* L, const std::string& name) { if (getglobal(L, name)) { return 1; @@ -37,6 +51,24 @@ namespace lua { } } + inline int requireregistry(lua::State* L, const std::string& name) { + if (getregistry(L, name)) { + return 1; + } else { + throw std::runtime_error("registry entry " + name + " not found"); + } + } + + inline bool getregistry(lua::State* L, const std::string& name, const std::string& key) { + requireregistry(L, name); + if (getfield(L, key)) { + lua_remove(L, -2); + return true; + } + pop(L); + return false; + } + inline bool hasglobal(lua::State* L, const std::string& name) { lua_getglobal(L, name.c_str()); if (isnil(L, -1)) { @@ -203,9 +235,6 @@ namespace lua { inline int pushglobals(lua::State* L) { return pushvalue(L, LUA_GLOBALSINDEX); } - inline bool isnoneornil(lua::State* L, int idx) { - return lua_isnoneornil(L, idx); - } inline bool isboolean(lua::State* L, int idx) { return lua_isboolean(L, idx); } @@ -267,6 +296,9 @@ namespace lua { inline void setglobal(lua::State* L, const std::string& name) { lua_setglobal(L, name.c_str()); } + inline void setregistry(lua::State* L, const std::string& key) { + lua_setfield(L, LUA_REGISTRYINDEX, key.c_str()); + } template inline T* touserdata(lua::State* L, int idx) { if (void* rawptr = lua_touserdata(L, idx)) { @@ -461,15 +493,6 @@ namespace lua { [[nodiscard]] dv::value tovalue(lua::State*, int idx); - inline bool getfield(lua::State* L, const std::string& name, int idx = -1) { - lua_getfield(L, idx, name.c_str()); - if (isnoneornil(L, -1)) { - pop(L); - return false; - } - return true; - } - inline int requirefield( lua::State* L, const std::string& name, int idx = -1 ) { @@ -538,7 +561,7 @@ namespace lua { if (luaL_loadbuffer(L, src.c_str(), src.length(), file.c_str())) { throw luaerror(tostring(L, -1)); } - if (env && getglobal(L, env_name(env))) { + if (env && getregistry(L, ENVS_TABLE, env_name(env))) { lua_setfenv(L, -2); } } @@ -551,7 +574,46 @@ namespace lua { setfield(L, name); pop(L, 2); } else { - throw std::runtime_error("table " + tableName + " not found"); + throw std::runtime_error("global table " + tableName + " not found"); + } + } + + inline void store_in_registry( + lua::State* L, const std::string& tableName, const std::string& name + ) { + if (getregistry(L, tableName)) { + pushvalue(L, -2); + setfield(L, name); + pop(L, 2); + } else { + throw std::runtime_error("table " + tableName + " not found in registry"); + } + } + + inline int get_from( + lua::State* L, + const std::string& tableName, + const std::string& name, + bool required, + int idx, + std::string_view context + ) { + if (getfield(L, tableName, idx)) { + if (getfield(L, name)) { + return 1; + } else if (required) { + pop(L); + throw std::runtime_error( + std::string(context) + " table " + tableName + + " has no member " + name + ); + } + pop(L); + return 0; + } else { + throw std::runtime_error( + std::string(context) + " table " + tableName + " not found" + ); } } @@ -561,20 +623,20 @@ namespace lua { const std::string& name, bool required = false ) { - if (getglobal(L, tableName)) { - if (getfield(L, name)) { - return 1; - } else if (required) { - pop(L); - throw std::runtime_error( - "table " + tableName + " has no member " + name - ); - } - pop(L); - return 0; - } else { - throw std::runtime_error("table " + tableName + " not found"); - } + return get_from( + L, tableName, name, required, LUA_GLOBALSINDEX, "global" + ); + } + + inline int get_from_registry( + lua::State* L, + const std::string& tableName, + const std::string& name, + bool required = false + ) { + return get_from( + L, tableName, name, required, LUA_REGISTRYINDEX, "registry" + ); } int call(lua::State*, int argc, int nresults = -1); @@ -607,10 +669,7 @@ namespace lua { scripting::common_func create_lambda_nothrow(lua::State*); inline int pushenv(lua::State* L, int env) { - if (getglobal(L, env_name(env))) { - return 1; - } - return 0; + return getregistry(L, ENVS_TABLE, env_name(env)); } int create_environment(lua::State*, int parent); int restore_pack_environment(lua::State*, const std::string& packid); @@ -739,14 +798,14 @@ namespace lua { inline void read_bytes_from_table( lua::State* L, int tableIndex, std::vector& bytes ) { - if (!lua::istable(L, tableIndex)) { + if (!istable(L, tableIndex)) { throw std::runtime_error("table expected"); } else { - size_t size = lua::objlen(L, tableIndex); + size_t size = objlen(L, tableIndex); for (size_t i = 0; i < size; i++) { - lua::rawgeti(L, i + 1, tableIndex); - const int byte = lua::tointeger(L, -1); - lua::pop(L); + rawgeti(L, i + 1, tableIndex); + const int byte = tointeger(L, -1); + pop(L); if (byte < 0 || byte > 255) { throw std::runtime_error( "invalid byte '" + std::to_string(byte) + "'" @@ -758,11 +817,11 @@ namespace lua { } inline int create_bytearray(lua::State* L, const void* bytes, size_t size) { - lua::requireglobal(L, "Bytearray_construct"); - lua::pushlstring( + requireglobal(L, "Bytearray_construct"); + pushlstring( L, std::string_view(reinterpret_cast(bytes), size) ); - return lua::call(L, 1, 1); + return call(L, 1, 1); } inline int create_bytearray(lua::State* L, const std::vector& bytes) { @@ -770,15 +829,15 @@ namespace lua { } inline std::string_view bytearray_as_string(lua::State* L, int idx) { - if (lua::type(L, idx) == LUA_TSTRING) { - return lua::tolstring(L, idx); + if (type(L, idx) == LUA_TSTRING) { + return tolstring(L, idx); } - lua::pushvalue(L, idx); - lua::requireglobal(L, "Bytearray_as_string"); - lua::pushvalue(L, -2); - lua::call(L, 1, 1); - auto view = lua::tolstring(L, -1); - lua::pop(L, 2); + pushvalue(L, idx); + requireglobal(L, "Bytearray_as_string"); + pushvalue(L, -2); + call(L, 1, 1); + auto view = tolstring(L, -1); + pop(L, 2); return view; } } diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index 341470c17..806f92c24 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -188,11 +188,7 @@ std::unique_ptr scripting::start_app_script(const io::path& script) { lua::pushstring(L, pack.id); lua::setfield(L, "PACK_ID"); - if(!lua::getglobal(L, "__vc__pack_envs")) { - lua::createtable(L, 0, 0); - lua::setglobal(L, "__vc__pack_envs"); - lua::pushvalue(L, -1); - } + lua::requireregistry(L, lua::PACK_ENVS_TABLE); lua::pushenv(L, id); lua::setfield(L, pack.id); lua::pop(L); @@ -248,7 +244,7 @@ std::unique_ptr scripting::start_app_script(const io::path& script) { void scripting::process_post_runnables() { auto L = lua::get_main_state(); - if (lua::getglobal(L, "__process_post_runnables")) { + if (lua::getglobal(L, "__vc__process_post_runnables")) { lua::call_nothrow(L, 0, 0); } } @@ -742,7 +738,7 @@ void scripting::load_entity_component( std::string src = io::read_string(file); logger.info() << "script (component) " << file.string(); lua::loadbuffer(L, *env, src, fileName); - lua::store_in(L, lua::CHUNKS_TABLE, name); + lua::store_in_registry(L, lua::CHUNKS_TABLE, name); } void scripting::load_world_script( diff --git a/src/logic/scripting/scripting_entities.cpp b/src/logic/scripting/scripting_entities.cpp index 32a396b25..375b382a9 100644 --- a/src/logic/scripting/scripting_entities.cpp +++ b/src/logic/scripting/scripting_entities.cpp @@ -1,5 +1,6 @@ #include "scripting.hpp" +#include "debug/Logger.hpp" #include "lua/lua_engine.hpp" #include "objects/Entities.hpp" #include "objects/EntityDef.hpp" @@ -11,6 +12,7 @@ using namespace scripting; +static debug::Logger logger("scripting-entities"); static inline const std::string STDCOMP = "stdcomp"; [[nodiscard]] static scriptenv create_component_environment( @@ -54,6 +56,24 @@ dv::value scripting::get_component_value( return nullptr; } +static void init_component_events( + lua::State* L, const scriptenv& env, UserComponent& component +) { + lua::pushenv(L, *env); + auto& funcsset = component.funcsset; + funcsset.on_grounded = lua::hasfield(L, "on_grounded"); + funcsset.on_fall = lua::hasfield(L, "on_fall"); + funcsset.on_despawn = lua::hasfield(L, "on_despawn"); + funcsset.on_sensor_enter = lua::hasfield(L, "on_sensor_enter"); + funcsset.on_sensor_exit = lua::hasfield(L, "on_sensor_exit"); + funcsset.on_save = lua::hasfield(L, "on_save"); + funcsset.on_aim_on = lua::hasfield(L, "on_aim_on"); + funcsset.on_aim_off = lua::hasfield(L, "on_aim_off"); + funcsset.on_attacked = lua::hasfield(L, "on_attacked"); + funcsset.on_used = lua::hasfield(L, "on_used"); + lua::pop(L); +} + static void create_component( lua::State* L, int entityIdx, @@ -61,11 +81,20 @@ static void create_component( const dv::value& args, const dv::value& saved ) { + auto parentEnv = get_root_environment(); + auto prefix = ContentPack::getPrefix(component.name); + const auto& content = *scripting::content; + if (auto runtime = content.getPackRuntime(std::string(prefix))) { + parentEnv = runtime->getEnvironment(); + } else { + logger.warning() << "pack environment is not available for " + << component.name; + } lua::pushvalue(L, entityIdx); auto compenv = create_component_environment( - get_root_environment(), -1, component.name + std::move(parentEnv), -1, component.name ); - lua::get_from(L, lua::CHUNKS_TABLE, component.name, true); + lua::get_from_registry(L, lua::CHUNKS_TABLE, component.name, true); lua::pushenv(L, *compenv); if (args != nullptr) { @@ -97,19 +126,8 @@ static void create_component( lua::setfenv(L); lua::call_nothrow(L, 0, 0); - lua::pushenv(L, *compenv); - auto& funcsset = component.funcsset; - funcsset.on_grounded = lua::hasfield(L, "on_grounded"); - funcsset.on_fall = lua::hasfield(L, "on_fall"); - funcsset.on_despawn = lua::hasfield(L, "on_despawn"); - funcsset.on_sensor_enter = lua::hasfield(L, "on_sensor_enter"); - funcsset.on_sensor_exit = lua::hasfield(L, "on_sensor_exit"); - funcsset.on_save = lua::hasfield(L, "on_save"); - funcsset.on_aim_on = lua::hasfield(L, "on_aim_on"); - funcsset.on_aim_off = lua::hasfield(L, "on_aim_off"); - funcsset.on_attacked = lua::hasfield(L, "on_attacked"); - funcsset.on_used = lua::hasfield(L, "on_used"); - lua::pop(L, 2); + init_component_events(L, compenv, component); + lua::pop(L); component.env = compenv; }