diff --git a/res/layouts/pages/content.xml b/res/layouts/pages/content.xml index dfa7b6546..63474effd 100644 --- a/res/layouts/pages/content.xml +++ b/res/layouts/pages/content.xml @@ -15,7 +15,7 @@ - @@ -38,4 +38,4 @@ - \ No newline at end of file + diff --git a/res/layouts/pages/content_menu.xml b/res/layouts/pages/content_menu.xml index 85dd54d0d..6718a3028 100644 --- a/res/layouts/pages/content_menu.xml +++ b/res/layouts/pages/content_menu.xml @@ -9,7 +9,7 @@ - diff --git a/res/layouts/pages/main.xml b/res/layouts/pages/main.xml index b1fab2c9e..b05ac7158 100644 --- a/res/layouts/pages/main.xml +++ b/res/layouts/pages/main.xml @@ -3,5 +3,5 @@ - + diff --git a/res/layouts/pages/new_world.xml.lua b/res/layouts/pages/new_world.xml.lua index 694ca9565..ad95aad0d 100644 --- a/res/layouts/pages/new_world.xml.lua +++ b/res/layouts/pages/new_world.xml.lua @@ -21,7 +21,7 @@ function create_world() local seed = document.seed_box.text local generator = settings.generator session.reset_entry('new_world') - core.new_world(name, seed, generator) + app.new_world(name, seed, generator) end function world_name_placeholder() diff --git a/res/layouts/pages/pause.xml b/res/layouts/pages/pause.xml index 63d827e10..3e4427cae 100644 --- a/res/layouts/pages/pause.xml +++ b/res/layouts/pages/pause.xml @@ -2,7 +2,7 @@ - diff --git a/res/layouts/pages/settings.xml b/res/layouts/pages/settings.xml index 00437e68b..68a71aa04 100644 --- a/res/layouts/pages/settings.xml +++ b/res/layouts/pages/settings.xml @@ -10,7 +10,7 @@ - + diff --git a/res/layouts/pages/settings.xml.lua b/res/layouts/pages/settings.xml.lua index 8b41491a5..453583518 100644 --- a/res/layouts/pages/settings.xml.lua +++ b/res/layouts/pages/settings.xml.lua @@ -1,7 +1,7 @@ function on_open() document.langs_btn.text = string.format( "%s: %s", gui.str("Language", "settings"), - gui.get_locales_info()[core.get_setting("ui.language")].name + gui.get_locales_info()[app.get_setting("ui.language")].name ) sections = RadioGroup({ audio=document.s_aud, diff --git a/res/layouts/pages/settings_audio.xml.lua b/res/layouts/pages/settings_audio.xml.lua index f8a23baa9..c9b4be4b6 100644 --- a/res/layouts/pages/settings_audio.xml.lua +++ b/res/layouts/pages/settings_audio.xml.lua @@ -1,25 +1,25 @@ function create_setting(id, name, step, postfix) - local info = core.get_setting_info(id) + local info = app.get_setting_info(id) postfix = postfix or "" document.root:add(gui.template("track_setting", { id=id, name=gui.str(name, "settings"), - value=core.get_setting(id), + value=app.get_setting(id), min=info.min, max=info.max, step=step, postfix=postfix })) - update_setting(core.get_setting(id), id, name, postfix) + update_setting(app.get_setting(id), id, name, postfix) end function update_setting(x, id, name, postfix) - core.set_setting(id, x) + app.set_setting(id, x) -- updating label document[id..".L"].text = string.format( "%s: %s%s", gui.str(name, "settings"), - core.str_setting(id), + app.str_setting(id), postfix ) end @@ -54,7 +54,7 @@ function on_open() create_setting("audio.volume-music", "Music", 0.01) document.root:add("") document.root:add("".. + "", core.get_setting("display.window-mode")) + "", app.get_setting("display.window-mode")) ) create_checkbox("camera.shaking", "Camera Shaking") diff --git a/res/layouts/pages/settings_graphics.xml.lua b/res/layouts/pages/settings_graphics.xml.lua index d61057799..3b7f7b13a 100644 --- a/res/layouts/pages/settings_graphics.xml.lua +++ b/res/layouts/pages/settings_graphics.xml.lua @@ -1,12 +1,12 @@ function create_setting(id, name, step, postfix, tooltip, changeonrelease) - local info = core.get_setting_info(id) + local info = app.get_setting_info(id) postfix = postfix or "" tooltip = tooltip or "" changeonrelease = changeonrelease or "" document.root:add(gui.template("track_setting", { id=id, name=gui.str(name, "settings"), - value=core.get_setting(id), + value=app.get_setting(id), min=info.min, max=info.max, step=step, @@ -14,15 +14,15 @@ function create_setting(id, name, step, postfix, tooltip, changeonrelease) tooltip=tooltip, changeonrelease=changeonrelease })) - update_setting(core.get_setting(id), id, name, postfix) + update_setting(app.get_setting(id), id, name, postfix) end function update_setting(x, id, name, postfix) -- updating label document[id..".L"].text = string.format( - "%s: %s%s", - gui.str(name, "settings"), - core.str_setting(id), + "%s: %s%s", + gui.str(name, "settings"), + app.str_setting(id), postfix ) end @@ -30,8 +30,8 @@ end function create_checkbox(id, name, tooltip) tooltip = tooltip or '' document.root:add(string.format( - "%s", - id, core.str_setting(id), gui.str(tooltip, "settings"), gui.str(name, "settings") + "%s", + id, app.str_setting(id), gui.str(tooltip, "settings"), gui.str(name, "settings") )) end diff --git a/res/layouts/pages/settings_reset.xml.lua b/res/layouts/pages/settings_reset.xml.lua index a8ca2a9fc..59583eefa 100644 --- a/res/layouts/pages/settings_reset.xml.lua +++ b/res/layouts/pages/settings_reset.xml.lua @@ -11,7 +11,7 @@ function reset(category) end function reset_setting(name) - core.set_setting(name, core.get_setting_info(name).def) + app.set_setting(name, app.get_setting_info(name).def) end function reset_audio() diff --git a/res/layouts/pages/worlds.xml b/res/layouts/pages/worlds.xml index a8a536a21..34fe3400c 100644 --- a/res/layouts/pages/worlds.xml +++ b/res/layouts/pages/worlds.xml @@ -3,6 +3,6 @@ - + diff --git a/res/layouts/pages/worlds.xml.lua b/res/layouts/pages/worlds.xml.lua index 1cbcd53db..748bdfb0b 100644 --- a/res/layouts/pages/worlds.xml.lua +++ b/res/layouts/pages/worlds.xml.lua @@ -1,7 +1,7 @@ function on_open() local worlds = world.get_list() for _, info in ipairs(worlds) do - local major, minor = core.get_version() + local major, minor = app.get_version() if info.version[1] > major or info.version[2] > minor then info.versionColor = "#A02010" else diff --git a/res/layouts/templates/language.xml b/res/layouts/templates/language.xml index fea34b313..692744c96 100644 --- a/res/layouts/templates/language.xml +++ b/res/layouts/templates/language.xml @@ -1,4 +1,4 @@ diff --git a/res/layouts/templates/track_setting.xml b/res/layouts/templates/track_setting.xml index 519ba334b..f01b8f842 100644 --- a/res/layouts/templates/track_setting.xml +++ b/res/layouts/templates/track_setting.xml @@ -2,6 +2,6 @@ diff --git a/res/layouts/templates/world.xml b/res/layouts/templates/world.xml index a258604b4..4ae936b6e 100644 --- a/res/layouts/templates/world.xml +++ b/res/layouts/templates/world.xml @@ -2,7 +2,7 @@ size='380,66' color='#0F1E2DB2' hover-color='#162B3399' - onclick='core.open_world("%{name}")' + onclick='app.open_world("%{name}")' > @@ -11,7 +11,7 @@ color='#00000000' hover-color='#FFFFFF2B' padding='0' - onclick='core.delete_world("%{name}")'> + onclick='app.delete_world("%{name}")'> diff --git a/res/modules/internal/gui_util.lua b/res/modules/internal/gui_util.lua index f9b29c193..3309e94a0 100644 --- a/res/modules/internal/gui_util.lua +++ b/res/modules/internal/gui_util.lua @@ -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) + gui.load_document(filename, name, args, { app = __vc_app }) return name end end @@ -52,19 +52,21 @@ end -- class designed for simple UI-nodes access via properties syntax local Element = {} +local __gui_getattr = gui.getattr +local __gui_setattr = gui.setattr function Element.new(docname, name) return setmetatable({docname=docname, name=name}, { __index=function(self, k) - return gui.getattr(self.docname, self.name, k) + return __gui_getattr(self.docname, self.name, k) end, __newindex=function(self, k, v) - gui.setattr(self.docname, self.name, k, v) + __gui_setattr(self.docname, self.name, k, v) end, __ipairs=function(self) local i = 0 return function() i = i + 1 - local elem = gui.getattr(self.docname, self.name, i) + local elem = __gui_getattr(self.docname, self.name, i) if elem == nil then return end @@ -102,8 +104,8 @@ function RadioGroup:set(key) end function RadioGroup:__call(elements, onset, default) local group = setmetatable({ - elements=elements, - callback=onset, + elements=elements, + callback=onset, current=nil }, {__index=self}) group:set(default) @@ -161,7 +163,7 @@ function gui.show_input_dialog(text, actual_callback, validator, confirm_text) callback=callback, validator=validator or function() return #text > 0 end }) - input.add_callback("key:escape", callback, gui.root[id]) + input.add_callback("key:escape", function() gui.root[id]:destruct() end, gui.root[id]) input.add_callback("key:enter", callback, gui.root[id]) gui.root[id.."_input"].focused = true end diff --git a/res/scripts/hud.lua b/res/scripts/hud.lua index 59d8de36a..322f03db5 100644 --- a/res/scripts/hud.lua +++ b/res/scripts/hud.lua @@ -29,7 +29,7 @@ local function configure_SSAO() end events.on("core:setting.graphics.ssao.set", update_ssao_quality) - update_ssao_quality(core.get_setting("graphics.ssao")) + update_ssao_quality(__vc_app.get_setting("graphics.ssao")) end local function update_hand() diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index 1a8954172..4eb6dd17e 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -1,4 +1,4 @@ -local enable_experimental = core.get_setting("debug.enable-experimental") +local enable_experimental = __vc_app.get_setting("debug.enable-experimental") ------------------------------------------------ ------ Extended kit of standard functions ------ @@ -24,41 +24,48 @@ function tb_frame_tostring(frame) return s end -local core_set_setting = core.set_setting -function core.set_setting(name, value, ...) - core_set_setting(name, value, ...) - events.emit("core:setting."..name..".set", value) -end +local __vc__app_script_coroutine local function complete_app_lib(app) + local __app_load_content = app.load_content + local __app_reset_content = app.reset_content + local __app_reconfig_packs = app.reconfig_packs + local __app_tick = coroutine.yield + local __app_set_setting = app.set_setting + local __app_quit = app.quit + app.sleep = sleep app.script = __VC_SCRIPT_NAME - app.new_world = core.new_world - app.open_world = core.open_world - app.save_world = core.save_world - app.close_world = core.close_world - app.reopen_world = core.reopen_world - app.delete_world = core.delete_world - app.reconfig_packs = core.reconfig_packs - app.get_setting = core.get_setting - app.set_setting = core.set_setting - app.tick = function() - coroutine.yield() + + app.set_setting = function(name, value, ...) + __app_set_setting(name, value, ...) + events.emit("core:setting."..name..".set", value) end - app.get_version = core.get_version - app.get_setting_info = core.get_setting_info - app.load_content = function() - core.load_content() - app.tick() + app.tick = __app_tick + + local function call_in_app_script_co(func, ...) + local running = coroutine.running() + if not running or running ~= __vc__app_script_coroutine then + error("content must be reload in application script coroutine") + end + func(...) + __app_tick() end - app.reset_content = core.reset_content - app.is_content_loaded = core.is_content_loaded - app.set_title = core.set_title - + + app.reconfig_packs = function(...) + call_in_app_script_co(__app_reconfig_packs, ...) + end + app.load_content = function(...) + call_in_app_script_co(__app_load_content, ...) + end + app.reset_content = function(...) + call_in_app_script_co(__app_reset_content, ...) + end + function app.config_packs(packs_list) -- Check if packs are valid and add dependencies to the configuration packs_list = pack.assemble(packs_list) - + local installed = pack.get_installed() local toremove = {} for _, packid in ipairs(installed) do @@ -75,15 +82,16 @@ local function complete_app_lib(app) app.reconfig_packs(toadd, toremove) end - function app.quit() - local tb = debug.get_traceback(1) - local s = "app.quit() traceback:" - for i, frame in ipairs(tb) do - s = s .. "\n\t"..tb_frame_tostring(frame) + function app.quit(silent) + if not silent then + local tb = debug.get_traceback(1) + local s = "app.quit() traceback:" + for i, frame in ipairs(tb) do + s = s .. "\n\t"..tb_frame_tostring(frame) + end + debug.log(s) end - debug.log(s) - core.quit() - coroutine.yield() + __app_quit() end function app.sleep_until(predicate, max_ticks, max_time) @@ -123,6 +131,66 @@ function pack.unload(prefix) events.remove_by_prefix(prefix) end +local __vc_coroutines = {} +local __vc_named_coroutines = {} +local __vc_next_coroutine = 1 + +function __vc_start_coroutine(chunk) + local co = coroutine.create(chunk) + local id = __vc_next_coroutine + __vc_next_coroutine = __vc_next_coroutine + 1 + __vc_coroutines[id] = co + return id +end + +function __vc_resume_coroutine(id) + local co = __vc_coroutines[id] + if co then + local success, err = coroutine.resume(co) + if not success then + debug.error(err) + error(err) + end + return coroutine.status(co) ~= "dead" + end + return false +end + +function __vc_stop_coroutine(id) + local co = __vc_coroutines[id] + if co then + if coroutine.close then + coroutine.close(co) + end + __vc_coroutines[id] = nil + end +end + +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) + end + return fullmsg + end) + if not status then + debug.error(error) + end + end) + __vc_named_coroutines[name] = co +end + function __vc_start_app_script(path, name) debug.log("starting application script "..path) @@ -134,9 +202,13 @@ function __vc_start_app_script(path, name) local script_env = setmetatable({app = app or __vc_app}, {__index=_G}) chunk = setfenv(chunk, script_env) if name then - return start_coroutine(chunk, name) + start_coroutine(chunk, name) + __vc__app_script_coroutine = __vc_named_coroutines[name] + return else - return __vc_start_coroutine(chunk) + local id = __vc_start_coroutine(chunk) + __vc__app_script_coroutine = __vc_coroutines[id] + return id end end @@ -435,66 +507,6 @@ function __vc_on_world_quit() file.__close_all_descriptors() end -local __vc_coroutines = {} -local __vc_named_coroutines = {} -local __vc_next_coroutine = 1 - -function __vc_start_coroutine(chunk) - local co = coroutine.create(chunk) - local id = __vc_next_coroutine - __vc_next_coroutine = __vc_next_coroutine + 1 - __vc_coroutines[id] = co - return id -end - -function __vc_resume_coroutine(id) - local co = __vc_coroutines[id] - if co then - local success, err = coroutine.resume(co) - if not success then - debug.error(err) - error(err) - end - return coroutine.status(co) ~= "dead" - end - return false -end - -function __vc_stop_coroutine(id) - local co = __vc_coroutines[id] - if co then - if coroutine.close then - coroutine.close(co) - end - __vc_coroutines[id] = nil - end -end - -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) - end - return fullmsg - end) - if not status then - debug.error(error) - end - end) - __vc_named_coroutines[name] = co -end - local __post_runnables = {} local fn_audio_reset_fetch_buffer = audio.__reset_fetch_buffer diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 5cb0e7ca6..5a63991bb 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -139,7 +139,8 @@ void Engine::initialize(CoreParameters coreParameters) { editor = std::make_unique(*this); cmd = std::make_unique(); - if (project->permissions.has(Permissions::NETWORK)) { + if (project->permissions.has(Permissions::NETWORK) || + !params.debugServerString.empty()) { network = network::Network::create(settings.network); } @@ -177,7 +178,7 @@ void Engine::initialize(CoreParameters coreParameters) { scripting::initialize(this); if (!isHeadless()) { - gui->setPageLoader(scripting::create_page_loader()); + gui->getMenu()->setPageLoader(scripting::create_page_loader()); } keepAlive(settings.ui.language.observe([this](auto lang) { langs::setup(lang, paths->resPaths.collectRoots()); diff --git a/src/graphics/ui/GUI.cpp b/src/graphics/ui/GUI.cpp index dc6dd6ac5..e69c20e3b 100644 --- a/src/graphics/ui/GUI.cpp +++ b/src/graphics/ui/GUI.cpp @@ -67,15 +67,6 @@ GUI::GUI(Engine& engine) GUI::~GUI() = default; -void GUI::setPageLoader(PageLoaderFunc pageLoader) { - this->pagesLoader = std::move(pageLoader); - menu->setPageLoader(this->pagesLoader); -} - -PageLoaderFunc GUI::getPagesLoader() { - return pagesLoader; -} - std::shared_ptr GUI::getMenu() { return menu; } diff --git a/src/graphics/ui/GUI.hpp b/src/graphics/ui/GUI.hpp index 5d6140c27..a27872d76 100644 --- a/src/graphics/ui/GUI.hpp +++ b/src/graphics/ui/GUI.hpp @@ -62,8 +62,6 @@ namespace gui { class Container; class Menu; - using PageLoaderFunc = std::function(const std::string&)>; - /// @brief The main UI controller class GUI { Engine& engine; @@ -83,8 +81,6 @@ namespace gui { std::queue postRunnables; std::vector> mouseOver; - PageLoaderFunc pagesLoader; - float tooltipTimer = 0.0f; float doubleClickTimer = 0.0f; float doubleClickDelay = 0.5f; @@ -101,10 +97,6 @@ namespace gui { GUI(Engine& engine); ~GUI(); - void setPageLoader(PageLoaderFunc pageLoader); - - PageLoaderFunc getPagesLoader(); - /// @brief Get the main menu (Menu) node std::shared_ptr getMenu(); diff --git a/src/logic/scripting/lua/libs/libapp.cpp b/src/logic/scripting/lua/libs/libapp.cpp index d23f96cb0..09ea33ce3 100644 --- a/src/logic/scripting/lua/libs/libapp.cpp +++ b/src/logic/scripting/lua/libs/libapp.cpp @@ -1,18 +1,338 @@ #include "api_lua.hpp" -#include "io/io.hpp" -#include "io/devices/MemoryDevice.hpp" -#include "logic/scripting/scripting.hpp" #include "content/ContentControl.hpp" +#include "devtools/Project.hpp" #include "engine/Engine.hpp" #include "engine/EnginePaths.hpp" -#include "devtools/Project.hpp" +#include "frontend/locale.hpp" +#include "graphics/ui/elements/Menu.hpp" +#include "graphics/ui/gui_util.hpp" +#include "graphics/ui/GUI.hpp" +#include "io/devices/MemoryDevice.hpp" +#include "io/io.hpp" +#include "io/settings_io.hpp" +#include "logic/EngineController.hpp" +#include "logic/LevelController.hpp" +#include "logic/scripting/scripting.hpp" #include "network/Network.hpp" #include "util/platform.hpp" +#include "util/stringutil.hpp" #include "window/Window.hpp" +#include "world/Level.hpp" using namespace scripting; +/// @brief Check if content is loaded +static int l_is_content_loaded(lua::State* L) { + return lua::pushboolean(L, content != nullptr); +} + +/// @brief Load content +static int l_load_content(lua::State* L) { + content_control->loadContent(); + return 0; +} + +/// @brief Reset content, excluding specified packs modules +static int l_reset_content(lua::State* L) { + if (level != nullptr) { + throw std::runtime_error("world must be closed before"); + } + std::vector nonResetPacks; + if (lua::istable(L, 1)) { + int len = lua::objlen(L, 1); + for (int i = 0; i < len; i++) { + lua::rawgeti(L, i + 1, 1); + nonResetPacks.emplace_back(lua::require_lstring(L, -1)); + lua::pop(L); + } + } + content_control->resetContent(std::move(nonResetPacks)); + return 0; +} + +/// @brief Reconfigure packs +/// @param addPacks An array of packs to add +/// @param remPacks An array of packs to remove +static int l_reconfig_packs(lua::State* L) { + if (!lua::istable(L, 1)) { + throw std::runtime_error("strings array expected as the first argument"); + } + if (!lua::istable(L, 2)) { + throw std::runtime_error("strings array expected as the second argument"); + } + std::vector addPacks; + int addLen = lua::objlen(L, 1); + for (int i = 0; i < addLen; i++) { + lua::rawgeti(L, i + 1, 1); + addPacks.emplace_back(lua::require_lstring(L, -1)); + lua::pop(L); + } + std::vector remPacks; + int remLen = lua::objlen(L, 2); + for (int i = 0; i < remLen; i++) { + lua::rawgeti(L, i + 1, 2); + remPacks.emplace_back(lua::require_lstring(L, -1)); + lua::pop(L); + } + auto engineController = engine->getController(); + try { + engineController->reconfigPacks(controller, addPacks, remPacks); + } catch (const contentpack_error& err) { + throw std::runtime_error( + std::string(err.what()) + " [" + err.getPackId() + " ]" + ); + } + return 0; +} + +/// @brief Get content sources list +static int l_get_content_sources(lua::State* L) { + const auto& sources = engine->getContentControl().getContentSources(); + lua::createtable(L, static_cast(sources.size()), 0); + for (size_t i = 0; i < sources.size(); i++) { + lua::pushlstring(L, sources[i].string()); + lua::rawseti(L, static_cast(i + 1)); + } + return 1; +} + +/// @brief Set content sources list +static int l_set_content_sources(lua::State* L) { + if (!lua::istable(L, 1)) { + throw std::runtime_error("table expected as argument 1"); + } + int len = lua::objlen(L, 1); + std::vector sources; + for (int i = 0; i < len; i++) { + lua::rawgeti(L, i + 1); + sources.emplace_back(std::string(lua::require_lstring(L, -1))); + lua::pop(L); + } + engine->getContentControl().setContentSources(std::move(sources)); + return 0; +} + +/// @brief Reset content sources to defaults +static int l_reset_content_sources(lua::State* L) { + engine->getContentControl().resetContentSources(); + return 0; +} + +/// @brief Get a setting value +/// @param name The name of the setting +/// @return The value of the setting +static int l_get_setting(lua::State* L) { + auto name = lua::require_string(L, 1); + const auto value = engine->getSettingsHandler().getValue(name); + return lua::pushvalue(L, value); +} + +/// @brief Set a setting value +/// @param name The name of the setting +/// @param value The new value for the setting +static int l_set_setting(lua::State* L) { + auto name = lua::require_string(L, 1); + const auto value = lua::tovalue(L, 2); + engine->getSettingsHandler().setValue(name, value); + return 0; +} + +/// @brief Convert a setting value to a string +/// @param name The name of the setting +/// @return The string representation of the setting value +static int l_str_setting(lua::State* L) { + auto name = lua::require_string(L, 1); + const auto string = engine->getSettingsHandler().toString(name); + return lua::pushstring(L, string); +} + +/// @brief Get information about a setting +/// @param name The name of the setting +/// @return A table with information about the setting +static int l_get_setting_info(lua::State* L) { + auto name = lua::require_string(L, 1); + auto setting = engine->getSettingsHandler().getSetting(name); + lua::createtable(L, 0, 1); + if (auto number = dynamic_cast(setting)) { + lua::pushnumber(L, number->getMin()); + lua::setfield(L, "min"); + lua::pushnumber(L, number->getMax()); + lua::setfield(L, "max"); + lua::pushnumber(L, number->getDefault()); + lua::setfield(L, "def"); + return 1; + } + if (auto integer = dynamic_cast(setting)) { + lua::pushinteger(L, integer->getMin()); + lua::setfield(L, "min"); + lua::pushinteger(L, integer->getMax()); + lua::setfield(L, "max"); + lua::pushinteger(L, integer->getDefault()); + lua::setfield(L, "def"); + return 1; + } + if (auto boolean = dynamic_cast(setting)) { + lua::pushboolean(L, boolean->getDefault()); + lua::setfield(L, "def"); + return 1; + } + if (auto string = dynamic_cast(setting)) { + lua::pushstring(L, string->getDefault()); + lua::setfield(L, "def"); + return 1; + } + lua::pop(L); + throw std::runtime_error("unsupported setting type"); +} + +/// @brief Open folder in file explorer +static int l_open_folder(lua::State* L) { + platform::open_folder(io::resolve(lua::require_string(L, 1))); + return 0; +} + +/// @brief Open URL in default browser with confirmation dialog +static int l_open_url(lua::State* L) { + auto url = lua::require_string(L, 1); + + std::wstring msg = langs::get(L"Are you sure you want to open the link:") + + L"\n" + util::str2wstr_utf8(url) + + std::wstring(L"?"); + + auto menu = engine->getGUI().getMenu(); + + guiutil::confirm(*engine, msg, [url, menu]() { + platform::open_url(url); + if (!menu->back()) { + menu->reset(); + } + }); + return 0; +} + + +/// @brief Bring application window to focus +static int l_focus(lua::State* L) { + engine->getWindow().focus(); + return 0; +} + +/// @brief Set window title +static int l_set_title(lua::State* L) { + auto title = lua::require_string(L, 1); + engine->getWindow().setTitle(title); + return 0; +} + +/// @brief Quit the game +static int l_quit(lua::State*) { + engine->quit(); + return 0; +} + +/// @brief Creating new world +/// @param name Name world +/// @param seed Seed world +/// @param generator Type of generation +static int l_new_world(lua::State* L) { + auto name = lua::require_string(L, 1); + auto seed = lua::require_string(L, 2); + auto generator = lua::require_string(L, 3); + int64_t localPlayer = 0; + if (lua::gettop(L) >= 4) { + localPlayer = lua::tointeger(L, 4); + } + if (level != nullptr) { + throw std::runtime_error("world must be closed before"); + } + auto controller = engine->getController(); + controller->setLocalPlayer(localPlayer); + controller->createWorld(name, seed, generator); + return 0; +} + +/// @brief Open world +/// @param name Name world +static int l_open_world(lua::State* L) { + auto name = lua::require_string(L, 1); + if (level != nullptr) { + throw std::runtime_error("world must be closed before"); + } + auto controller = engine->getController(); + controller->setLocalPlayer(0); + controller->openWorld(name, false); + return 0; +} + +/// @brief Reopen world +static int l_reopen_world(lua::State*) { + auto controller = engine->getController(); + if (level == nullptr) { + throw std::runtime_error("no world open"); + } + controller->reopenWorld(level->getWorld()); + return 0; +} + +/// @brief Save world +static int l_save_world(lua::State* L) { + if (controller == nullptr) { + throw std::runtime_error("no world open"); + } + controller->saveWorld(); + return 0; +} + +/// @brief Close world +/// @param flag Save world (bool) +static int l_close_world(lua::State* L) { + if (controller == nullptr) { + throw std::runtime_error("no world open"); + } + controller->processBeforeQuit(); + bool save_world = lua::toboolean(L, 1); + if (save_world) { + controller->saveWorld(); + } + engine->onWorldClosed(); + return 0; +} + +/// @brief Delete world +/// @param name Name world +static int l_delete_world(lua::State* L) { + auto name = lua::require_string(L, 1); + auto controller = engine->getController(); + controller->deleteWorld(name); + return 0; +} + +/// @brief Get engine version +/// @return major, minor +static int l_get_version(lua::State* L) { + return lua::pushvec_stack( + L, glm::vec2(ENGINE_VERSION_MAJOR, ENGINE_VERSION_MINOR) + ); +} + +/// @brief Create in-memory named IO device +static int l_create_memory_device(lua::State* L) { + std::string name = lua::require_string(L, 1); + if (io::get_device(name)) { + throw std::runtime_error( + "entry-point '" + name + "' is already used" + ); + } + if (name.find(':') != std::string::npos) { + throw std::runtime_error("invalid entry point name"); + } + + io::set_device(name, std::make_unique()); + return 0; +} + +/// @brief Start a new engine instance with debugging server static int l_start_debug_instance(lua::State* L) { if (!engine->getProject().permissions.has(Permissions::DEBUGGING)) { throw std::runtime_error("project has no debugging permission"); @@ -29,6 +349,7 @@ static int l_start_debug_instance(lua::State* L) { throw std::runtime_error("could not find free port"); } } + auto projectPath = lua::isstring(L, 2) ? lua::require_lstring(L, 2) : ""; const auto& paths = engine->getPaths(); std::vector args { @@ -36,67 +357,46 @@ static int l_start_debug_instance(lua::State* L) { "--dir", paths.getUserFilesFolder().u8string(), "--dbg-server", "tcp:" + std::to_string(port), }; + if (!projectPath.empty()) { + args.emplace_back("--project"); + args.emplace_back(io::resolve(std::string(projectPath)).string()); + } + platform::new_engine_instance(std::move(args)); return lua::pushinteger(L, port); } -static int l_focus(lua::State* L) { - engine->getWindow().focus(); - return 0; -} - -static int l_create_memory_device(lua::State* L) { - std::string name = lua::require_string(L, 1); - if (io::get_device(name)) { - throw std::runtime_error( - "entry-point '" + name + "' is already used" - ); - } - if (name.find(':') != std::string::npos) { - throw std::runtime_error("invalid entry point name"); - } - - io::set_device(name, std::make_unique()); - return 0; -} - -static int l_get_content_sources(lua::State* L) { - const auto& sources = engine->getContentControl().getContentSources(); - lua::createtable(L, static_cast(sources.size()), 0); - for (size_t i = 0; i < sources.size(); i++) { - lua::pushlstring(L, sources[i].string()); - lua::rawseti(L, static_cast(i + 1)); - } - return 1; -} - -static int l_set_content_sources(lua::State* L) { - if (!lua::istable(L, 1)) { - throw std::runtime_error("table expected as argument 1"); - } - int len = lua::objlen(L, 1); - std::vector sources; - for (int i = 0; i < len; i++) { - lua::rawgeti(L, i + 1); - sources.emplace_back(std::string(lua::require_lstring(L, -1))); - lua::pop(L); - } - engine->getContentControl().setContentSources(std::move(sources)); - return 0; -} - -static int l_reset_content_sources(lua::State* L) { - engine->getContentControl().resetContentSources(); - return 0; -} - const luaL_Reg applib[] = { - {"start_debug_instance", lua::wrap}, - {"focus", lua::wrap}, - {"create_memory_device", lua::wrap}, + /// content + {"is_content_loaded", lua::wrap}, + {"load_content", lua::wrap}, + {"reset_content", lua::wrap}, + {"reconfig_packs", lua::wrap}, {"get_content_sources", lua::wrap}, {"set_content_sources", lua::wrap}, {"reset_content_sources", lua::wrap}, - // for other functions see libcore.cpp and stdlib.lua + /// settings + {"get_setting", lua::wrap}, + {"set_setting", lua::wrap}, + {"str_setting", lua::wrap}, + {"get_setting_info", lua::wrap}, + /// system applications + {"open_folder", lua::wrap}, + {"open_url", lua::wrap}, + /// window + {"focus", lua::wrap}, + {"set_title", lua::wrap}, + {"quit", lua::wrap}, + /// world + {"new_world", lua::wrap}, + {"open_world", lua::wrap}, + {"reopen_world", lua::wrap}, + {"save_world", lua::wrap}, + {"close_world", lua::wrap}, + {"delete_world", lua::wrap}, + /// other + {"get_version", lua::wrap}, + {"create_memory_device", lua::wrap}, + {"start_debug_instance", lua::wrap}, {nullptr, nullptr} }; diff --git a/src/logic/scripting/lua/libs/libcore.cpp b/src/logic/scripting/lua/libs/libcore.cpp index e4d5474c7..324cd5471 100644 --- a/src/logic/scripting/lua/libs/libcore.cpp +++ b/src/logic/scripting/lua/libs/libcore.cpp @@ -1,274 +1,9 @@ -#include -#include +#include "api_lua.hpp" + #include -#include "api_lua.hpp" -#include "constants.hpp" -#include "assets/Assets.hpp" -#include "content/Content.hpp" -#include "content/ContentControl.hpp" -#include "engine/Engine.hpp" -#include "engine/EnginePaths.hpp" -#include "io/io.hpp" -#include "io/settings_io.hpp" -#include "frontend/menu.hpp" -#include "frontend/screens/MenuScreen.hpp" -#include "graphics/core/Texture.hpp" -#include "logic/EngineController.hpp" -#include "logic/LevelController.hpp" -#include "util/listutil.hpp" -#include "util/platform.hpp" -#include "world/Level.hpp" -#include "world/generator/WorldGenerator.hpp" -#include "util/platform.hpp" -#include "frontend/locale.hpp" -#include "graphics/ui/gui_util.hpp" -#include "graphics/ui/GUI.hpp" -#include "graphics/ui/elements/Menu.hpp" -#include "window/Window.hpp" - using namespace scripting; -static int l_get_version(lua::State* L) { - return lua::pushvec_stack( - L, glm::vec2(ENGINE_VERSION_MAJOR, ENGINE_VERSION_MINOR) - ); -} - -static int l_load_content(lua::State* L) { - content_control->loadContent(); - return 0; -} - -static int l_reset_content(lua::State* L) { - if (level != nullptr) { - throw std::runtime_error("world must be closed before"); - } - std::vector nonResetPacks; - if (lua::istable(L, 1)) { - int len = lua::objlen(L, 1); - for (int i = 0; i < len; i++) { - lua::rawgeti(L, i + 1, 1); - nonResetPacks.emplace_back(lua::require_lstring(L, -1)); - lua::pop(L); - } - } - content_control->resetContent(std::move(nonResetPacks)); - return 0; -} - -static int l_is_content_loaded(lua::State* L) { - return lua::pushboolean(L, content != nullptr); -} - -/// @brief Creating new world -/// @param name Name world -/// @param seed Seed world -/// @param generator Type of generation -static int l_new_world(lua::State* L) { - auto name = lua::require_string(L, 1); - auto seed = lua::require_string(L, 2); - auto generator = lua::require_string(L, 3); - int64_t localPlayer = 0; - if (lua::gettop(L) >= 4) { - localPlayer = lua::tointeger(L, 4); - } - if (level != nullptr) { - throw std::runtime_error("world must be closed before"); - } - auto controller = engine->getController(); - controller->setLocalPlayer(localPlayer); - controller->createWorld(name, seed, generator); - return 0; -} - -/// @brief Open world -/// @param name Name world -static int l_open_world(lua::State* L) { - auto name = lua::require_string(L, 1); - if (level != nullptr) { - throw std::runtime_error("world must be closed before"); - } - auto controller = engine->getController(); - controller->setLocalPlayer(0); - controller->openWorld(name, false); - return 0; -} - -/// @brief Reopen world -static int l_reopen_world(lua::State*) { - auto controller = engine->getController(); - if (level == nullptr) { - throw std::runtime_error("no world open"); - } - controller->reopenWorld(level->getWorld()); - return 0; -} - -/// @brief Save world -static int l_save_world(lua::State* L) { - if (controller == nullptr) { - throw std::runtime_error("no world open"); - } - controller->saveWorld(); - return 0; -} - -/// @brief Close world -/// @param flag Save world (bool) -static int l_close_world(lua::State* L) { - if (controller == nullptr) { - throw std::runtime_error("no world open"); - } - controller->processBeforeQuit(); - bool save_world = lua::toboolean(L, 1); - if (save_world) { - controller->saveWorld(); - } - engine->onWorldClosed(); - return 0; -} - -/// @brief Delete world -/// @param name Name world -static int l_delete_world(lua::State* L) { - auto name = lua::require_string(L, 1); - auto controller = engine->getController(); - controller->deleteWorld(name); - return 0; -} - -/// @brief Reconfigure packs -/// @param addPacks An array of packs to add -/// @param remPacks An array of packs to remove -static int l_reconfig_packs(lua::State* L) { - if (!lua::istable(L, 1)) { - throw std::runtime_error("strings array expected as the first argument"); - } - if (!lua::istable(L, 2)) { - throw std::runtime_error("strings array expected as the second argument"); - } - std::vector addPacks; - int addLen = lua::objlen(L, 1); - for (int i = 0; i < addLen; i++) { - lua::rawgeti(L, i + 1, 1); - addPacks.emplace_back(lua::require_lstring(L, -1)); - lua::pop(L); - } - std::vector remPacks; - int remLen = lua::objlen(L, 2); - for (int i = 0; i < remLen; i++) { - lua::rawgeti(L, i + 1, 2); - remPacks.emplace_back(lua::require_lstring(L, -1)); - lua::pop(L); - } - auto engineController = engine->getController(); - try { - engineController->reconfigPacks(controller, addPacks, remPacks); - } catch (const contentpack_error& err) { - throw std::runtime_error( - std::string(err.what()) + " [" + err.getPackId() + " ]" - ); - } - return 0; -} - -/// @brief Get a setting value -/// @param name The name of the setting -/// @return The value of the setting -static int l_get_setting(lua::State* L) { - auto name = lua::require_string(L, 1); - const auto value = engine->getSettingsHandler().getValue(name); - return lua::pushvalue(L, value); -} - -/// @brief Set a setting value -/// @param name The name of the setting -/// @param value The new value for the setting -static int l_set_setting(lua::State* L) { - auto name = lua::require_string(L, 1); - const auto value = lua::tovalue(L, 2); - engine->getSettingsHandler().setValue(name, value); - return 0; -} - -/// @brief Convert a setting value to a string -/// @param name The name of the setting -/// @return The string representation of the setting value -static int l_str_setting(lua::State* L) { - auto name = lua::require_string(L, 1); - const auto string = engine->getSettingsHandler().toString(name); - return lua::pushstring(L, string); -} - -/// @brief Get information about a setting -/// @param name The name of the setting -/// @return A table with information about the setting -static int l_get_setting_info(lua::State* L) { - auto name = lua::require_string(L, 1); - auto setting = engine->getSettingsHandler().getSetting(name); - lua::createtable(L, 0, 1); - if (auto number = dynamic_cast(setting)) { - lua::pushnumber(L, number->getMin()); - lua::setfield(L, "min"); - lua::pushnumber(L, number->getMax()); - lua::setfield(L, "max"); - lua::pushnumber(L, number->getDefault()); - lua::setfield(L, "def"); - return 1; - } - if (auto integer = dynamic_cast(setting)) { - lua::pushinteger(L, integer->getMin()); - lua::setfield(L, "min"); - lua::pushinteger(L, integer->getMax()); - lua::setfield(L, "max"); - lua::pushinteger(L, integer->getDefault()); - lua::setfield(L, "def"); - return 1; - } - if (auto boolean = dynamic_cast(setting)) { - lua::pushboolean(L, boolean->getDefault()); - lua::setfield(L, "def"); - return 1; - } - if (auto string = dynamic_cast(setting)) { - lua::pushstring(L, string->getDefault()); - lua::setfield(L, "def"); - return 1; - } - lua::pop(L); - throw std::runtime_error("unsupported setting type"); -} - -static int l_open_folder(lua::State* L) { - platform::open_folder(io::resolve(lua::require_string(L, 1))); - return 0; -} - -static int l_open_url(lua::State* L) { - auto url = lua::require_string(L, 1); - - std::wstring msg = langs::get(L"Are you sure you want to open the link:") + - L"\n" + util::str2wstr_utf8(url) + - std::wstring(L"?"); - - auto menu = engine->getGUI().getMenu(); - - guiutil::confirm(*engine, msg, [url, menu]() { - platform::open_url(url); - if (!menu->back()) { - menu->reset(); - } - }); - return 0; -} - -/// @brief Quit the game -static int l_quit(lua::State*) { - engine->quit(); - return 0; -} - static int l_blank(lua::State*) { return 0; } @@ -300,33 +35,8 @@ static int l_capture_output(lua::State* L) { return 1; } -static int l_set_title(lua::State* L) { - auto title = lua::require_string(L, 1); - engine->getWindow().setTitle(title); - return 0; -} - const luaL_Reg corelib[] = { {"blank", lua::wrap}, - {"get_version", lua::wrap}, - {"load_content", lua::wrap}, - {"reset_content", lua::wrap}, - {"is_content_loaded", lua::wrap}, - {"new_world", lua::wrap}, - {"open_world", lua::wrap}, - {"reopen_world", lua::wrap}, - {"save_world", lua::wrap}, - {"close_world", lua::wrap}, - {"delete_world", lua::wrap}, - {"reconfig_packs", lua::wrap}, - {"get_setting", lua::wrap}, - {"set_setting", lua::wrap}, - {"str_setting", lua::wrap}, - {"get_setting_info", lua::wrap}, - {"open_folder", lua::wrap}, - {"open_url", lua::wrap}, - {"quit", lua::wrap}, {"capture_output", lua::wrap}, - {"set_title", lua::wrap}, {nullptr, nullptr} }; diff --git a/src/logic/scripting/lua/libs/libgui.cpp b/src/logic/scripting/lua/libs/libgui.cpp index 4d1506e23..cb98de7d7 100644 --- a/src/logic/scripting/lua/libs/libgui.cpp +++ b/src/logic/scripting/lua/libs/libgui.cpp @@ -1111,6 +1111,16 @@ static int l_gui_load_document(lua::State* L) { ); auto document = documentPtr.get(); engine->getAssets()->store(std::move(documentPtr), alias); + + // namespace extension + if (lua::istable(L, 4)) { + if (lua::get_from(L, "table", "merge")) { + lua::pushenv(L, *document->getEnvironment()); + lua::pushvalue(L, 4); + lua::call(L, 2, 0); + lua::pop(L); + } + } scripting::on_ui_open(document, {args}); return 0; } diff --git a/src/logic/scripting/lua/libs/libnetwork.cpp b/src/logic/scripting/lua/libs/libnetwork.cpp index 390845207..7fbf6df8d 100644 --- a/src/logic/scripting/lua/libs/libnetwork.cpp +++ b/src/logic/scripting/lua/libs/libnetwork.cpp @@ -2,6 +2,7 @@ #include "coders/json.hpp" #include "engine/Engine.hpp" #include "network/Network.hpp" +#include "devtools/Project.hpp" #include #include @@ -439,7 +440,7 @@ static int l_is_nodelay(lua::State* L, network::Network& network) { return lua::pushboolean(L, false); } -static int l_pull_events(lua::State* L, network::Network& network) { +static int l_pull_events(lua::State* L) { std::vector local_queue; { std::lock_guard lock(events_queue_mutex); @@ -528,7 +529,8 @@ int wrap(lua_State* L) { int result = 0; try { auto network = engine->getNetwork(); - if (network == nullptr) { + const auto& permissions = engine->getProject().permissions; + if (network == nullptr || !permissions.has(Permissions::NETWORK)) { throw std::runtime_error( "network subsystem is not available in the project" ); @@ -554,7 +556,7 @@ const luaL_Reg networklib[] = { {"get_total_download", wrap}, {"find_free_port", wrap}, {"is_available", lua::wrap}, - {"__pull_events", wrap}, + {"__pull_events", lua::wrap}, {"__open_tcp", wrap}, {"__open_udp", wrap}, {"__closeserver", wrap}, diff --git a/src/logic/scripting/lua/lua_engine.cpp b/src/logic/scripting/lua/lua_engine.cpp index eef391b54..2a9eb59cb 100644 --- a/src/logic/scripting/lua/lua_engine.cpp +++ b/src/logic/scripting/lua/lua_engine.cpp @@ -66,6 +66,8 @@ static void create_libs(State* L, StateType stateType) { 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); } diff --git a/src/util/platform.cpp b/src/util/platform.cpp index dce6f89b7..a7ba041db 100644 --- a/src/util/platform.cpp +++ b/src/util/platform.cpp @@ -281,6 +281,7 @@ void platform::new_engine_instance(const std::vector& args) { ss << " >/dev/null &"; auto command = ss.str(); + logger.info() << command; if (int res = system(command.c_str())) { throw std::runtime_error( "starting an engine instance failed with code: " +