From 76685b84f77bf82e9ff272c3469484deaee39652 Mon Sep 17 00:00:00 2001 From: grorp Date: Wed, 1 Jan 2025 21:19:15 +0100 Subject: [PATCH 1/5] In-game settings menu using separate Lua environment --- .luacheckrc | 5 +- builtin/client/init.lua | 5 +- builtin/client/register.lua | 66 +--------- builtin/common/register.lua | 6 +- .../settings/components.lua | 34 ++++- .../settings/dlg_change_mapgen_flags.lua | 0 .../settings/dlg_settings.lua | 54 ++++++-- .../settings/generate_from_settingtypes.lua | 0 .../{mainmenu => common}/settings/init.lua | 8 +- .../settings/settingtypes.lua | 11 +- .../settings/shadows_component.lua | 0 builtin/init.lua | 2 + builtin/mainmenu/init.lua | 2 +- builtin/pause_menu/init.lua | 12 ++ builtin/pause_menu/register.lua | 5 + doc/menu_lua_api.md | 5 - src/client/clientevent.h | 3 +- src/client/game.cpp | 19 ++- src/client/game_formspec.cpp | 116 +++++++++++++----- src/client/game_formspec.h | 15 +-- src/gui/mainmenumanager.h | 7 ++ src/script/CMakeLists.txt | 1 + src/script/cpp_api/CMakeLists.txt | 2 + src/script/cpp_api/s_base.cpp | 10 +- src/script/cpp_api/s_base.h | 3 +- src/script/cpp_api/s_client.cpp | 28 ----- src/script/cpp_api/s_client.h | 1 - src/script/cpp_api/s_client_common.cpp | 39 ++++++ src/script/cpp_api/s_client_common.h | 16 +++ src/script/cpp_api/s_pause_menu.cpp | 20 +++ src/script/cpp_api/s_pause_menu.h | 13 ++ src/script/lua_api/CMakeLists.txt | 3 + src/script/lua_api/l_client.cpp | 16 --- src/script/lua_api/l_client.h | 3 - src/script/lua_api/l_client_common.cpp | 33 +++++ src/script/lua_api/l_client_common.h | 19 +++ src/script/lua_api/l_mainmenu.cpp | 52 -------- src/script/lua_api/l_mainmenu.h | 8 -- src/script/lua_api/l_menu_common.cpp | 49 ++++++++ src/script/lua_api/l_menu_common.h | 20 +++ src/script/lua_api/l_pause_menu.cpp | 28 +++++ src/script/lua_api/l_pause_menu.h | 17 +++ src/script/lua_api/l_settings.cpp | 5 +- src/script/scripting_client.cpp | 2 + src/script/scripting_client.h | 2 + src/script/scripting_mainmenu.cpp | 3 + src/script/scripting_pause_menu.cpp | 68 ++++++++++ src/script/scripting_pause_menu.h | 28 +++++ 48 files changed, 609 insertions(+), 255 deletions(-) rename builtin/{mainmenu => common}/settings/components.lua (88%) rename builtin/{mainmenu => common}/settings/dlg_change_mapgen_flags.lua (100%) rename builtin/{mainmenu => common}/settings/dlg_settings.lua (93%) rename builtin/{mainmenu => common}/settings/generate_from_settingtypes.lua (100%) rename builtin/{mainmenu => common}/settings/init.lua (83%) rename builtin/{mainmenu => common}/settings/settingtypes.lua (96%) rename builtin/{mainmenu => common}/settings/shadows_component.lua (100%) create mode 100644 builtin/pause_menu/init.lua create mode 100644 builtin/pause_menu/register.lua create mode 100644 src/script/cpp_api/s_client_common.cpp create mode 100644 src/script/cpp_api/s_client_common.h create mode 100644 src/script/cpp_api/s_pause_menu.cpp create mode 100644 src/script/cpp_api/s_pause_menu.h create mode 100644 src/script/lua_api/l_client_common.cpp create mode 100644 src/script/lua_api/l_client_common.h create mode 100644 src/script/lua_api/l_menu_common.cpp create mode 100644 src/script/lua_api/l_menu_common.h create mode 100644 src/script/lua_api/l_pause_menu.cpp create mode 100644 src/script/lua_api/l_pause_menu.h create mode 100644 src/script/scripting_pause_menu.cpp create mode 100644 src/script/scripting_pause_menu.h diff --git a/.luacheckrc b/.luacheckrc index 3a4667b4aba46..82c10fcd3919c 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -10,6 +10,7 @@ ignore = { read_globals = { "ItemStack", "INIT", + "PLATFORM", "DIR_DELIM", "dump", "dump2", "fgettext", "fgettext_ne", @@ -75,10 +76,6 @@ files["builtin/mainmenu"] = { globals = { "gamedata", }, - - read_globals = { - "PLATFORM", - }, } files["builtin/common/tests"] = { diff --git a/builtin/client/init.lua b/builtin/client/init.lua index 1f83771eae2db..ee0f267db7ea6 100644 --- a/builtin/client/init.lua +++ b/builtin/client/init.lua @@ -2,7 +2,10 @@ local scriptpath = core.get_builtin_path() local clientpath = scriptpath.."client"..DIR_DELIM local commonpath = scriptpath.."common"..DIR_DELIM -dofile(clientpath .. "register.lua") +local builtin_shared = {} + +assert(loadfile(commonpath .. "register.lua"))(builtin_shared) +assert(loadfile(clientpath .. "register.lua"))(builtin_shared) dofile(commonpath .. "after.lua") dofile(commonpath .. "mod_storage.lua") dofile(commonpath .. "chatcommands.lua") diff --git a/builtin/client/register.lua b/builtin/client/register.lua index 61db4a30b7cd5..0e3e68fee7952 100644 --- a/builtin/client/register.lua +++ b/builtin/client/register.lua @@ -1,68 +1,6 @@ -core.callback_origins = {} +local builtin_shared = ... -local getinfo = debug.getinfo -debug.getinfo = nil - ---- Runs given callbacks. --- --- Note: this function is also called from C++ --- @tparam table callbacks a table with registered callbacks, like `core.registered_on_*` --- @tparam number mode a RunCallbacksMode, as defined in src/script/common/c_internal.h --- @param ... arguments for the callback --- @return depends on mode -function core.run_callbacks(callbacks, mode, ...) - assert(type(callbacks) == "table") - local cb_len = #callbacks - if cb_len == 0 then - if mode == 2 or mode == 3 then - return true - elseif mode == 4 or mode == 5 then - return false - end - end - local ret - for i = 1, cb_len do - local cb_ret = callbacks[i](...) - - if mode == 0 and i == 1 or mode == 1 and i == cb_len then - ret = cb_ret - elseif mode == 2 then - if not cb_ret or i == 1 then - ret = cb_ret - end - elseif mode == 3 then - if cb_ret then - return cb_ret - end - ret = cb_ret - elseif mode == 4 then - if (cb_ret and not ret) or i == 1 then - ret = cb_ret - end - elseif mode == 5 and cb_ret then - return cb_ret - end - end - return ret -end - --- --- Callback registration --- - -local function make_registration() - local t = {} - local registerfunc = function(func) - t[#t + 1] = func - core.callback_origins[func] = { - mod = core.get_current_modname() or "??", - name = getinfo(1, "n").name or "??" - } - --local origin = core.callback_origins[func] - --print(origin.name .. ": " .. origin.mod .. " registering cbk " .. tostring(func)) - end - return t, registerfunc -end +local make_registration = builtin_shared.make_registration core.registered_globalsteps, core.register_globalstep = make_registration() core.registered_on_mods_loaded, core.register_on_mods_loaded = make_registration() diff --git a/builtin/common/register.lua b/builtin/common/register.lua index c300ef90f17cd..cbeac7c64f19d 100644 --- a/builtin/common/register.lua +++ b/builtin/common/register.lua @@ -54,7 +54,8 @@ function builtin_shared.make_registration() local registerfunc = function(func) t[#t + 1] = func core.callback_origins[func] = { - mod = core.get_current_modname() or "??", + -- may be nil or return nil + mod = core.get_current_modname and core.get_current_modname() or "??", name = debug.getinfo(1, "n").name or "??" } end @@ -66,7 +67,8 @@ function builtin_shared.make_registration_reverse() local registerfunc = function(func) table.insert(t, 1, func) core.callback_origins[func] = { - mod = core.get_current_modname() or "??", + -- may be nil or return nil + mod = core.get_current_modname and core.get_current_modname() or "??", name = debug.getinfo(1, "n").name or "??" } end diff --git a/builtin/mainmenu/settings/components.lua b/builtin/common/settings/components.lua similarity index 88% rename from builtin/mainmenu/settings/components.lua rename to builtin/common/settings/components.lua index 64cd27c3cbe6b..aec2b8898f526 100644 --- a/builtin/mainmenu/settings/components.lua +++ b/builtin/common/settings/components.lua @@ -98,6 +98,7 @@ local function make_field(converter, validator, stringifier) local fs = ("field[0,0.3;%f,0.8;%s;%s;%s]"):format( avail_w - 1.5, setting.name, get_label(setting), core.formspec_escape(value)) fs = fs .. ("field_enter_after_edit[%s;true]"):format(setting.name) + fs = fs .. ("field_close_on_enter[%s;false]"):format(setting.name) -- for pause menu env fs = fs .. ("button[%f,0.3;1.5,0.8;%s;%s]"):format(avail_w - 1.5, "set_" .. setting.name, fgettext("Set")) return fs, 1.1 @@ -217,6 +218,8 @@ local function make_path(setting) local fs = ("field[0,0.3;%f,0.8;%s;%s;%s]"):format( avail_w - 3, setting.name, get_label(setting), core.formspec_escape(value)) + fs = fs .. ("field_enter_after_edit[%s;true]"):format(setting.name) + fs = fs .. ("field_close_on_enter[%s;false]"):format(setting.name) -- for pause menu env fs = fs .. ("button[%f,0.3;1.5,0.8;%s;%s]"):format(avail_w - 3, "pick_" .. setting.name, fgettext("Browse")) fs = fs .. ("button[%f,0.3;1.5,0.8;%s;%s]"):format(avail_w - 1.5, "set_" .. setting.name, fgettext("Set")) @@ -249,8 +252,11 @@ local function make_path(setting) } end -if PLATFORM == "Android" then +if PLATFORM == "Android" or INIT == "pause_menu" then -- The Irrlicht file picker doesn't work on Android. + -- Access to the Irrlicht file picker isn't implemented in the pause menu. + -- We want to delete the Irrlicht file picker anyway, so any time spent on + -- that would be wasted. make.path = make.string make.filepath = make.string else @@ -282,6 +288,14 @@ function make.v3f(setting) fs = fs .. ("field[%f,0.6;%f,0.8;%s;%s;%s]"):format( 2 * (field_width + 0.25), field_width, setting.name .. "_z", "Z", value.z) + fs = fs .. ("field_enter_after_edit[%s;true]"):format(setting.name .. "_x") + fs = fs .. ("field_enter_after_edit[%s;true]"):format(setting.name .. "_y") + fs = fs .. ("field_enter_after_edit[%s;true]"):format(setting.name .. "_z") + -- for pause menu env + fs = fs .. ("field_close_on_enter[%s;false]"):format(setting.name .. "_x") + fs = fs .. ("field_close_on_enter[%s;false]"):format(setting.name .. "_y") + fs = fs .. ("field_close_on_enter[%s;false]"):format(setting.name .. "_z") + fs = fs .. ("button[%f,0.6;1,0.8;%s;%s]"):format(avail_w, "set_" .. setting.name, fgettext("Set")) return fs, 1.4 @@ -428,8 +442,22 @@ local function make_noise_params(setting) } end -make.noise_params_2d = make_noise_params -make.noise_params_3d = make_noise_params +if INIT == "pause_menu" then + -- Making the noise parameter dialog work in the pause menu settings would + -- require porting "FSTK" (at least the dialog API) from the mainmenu formspec + -- API to the in-game formspec API. + -- There's no reason you'd want to adjust mapgen noise parameter settings + -- in-game (they only apply to new worlds), so there's no reason to implement + -- this. + local empty = function() + return { get_formspec = function() return "", 0 end } + end + make.noise_params_2d = empty + make.noise_params_3d = empty +else + make.noise_params_2d = make_noise_params + make.noise_params_3d = make_noise_params +end return make diff --git a/builtin/mainmenu/settings/dlg_change_mapgen_flags.lua b/builtin/common/settings/dlg_change_mapgen_flags.lua similarity index 100% rename from builtin/mainmenu/settings/dlg_change_mapgen_flags.lua rename to builtin/common/settings/dlg_change_mapgen_flags.lua diff --git a/builtin/mainmenu/settings/dlg_settings.lua b/builtin/common/settings/dlg_settings.lua similarity index 93% rename from builtin/mainmenu/settings/dlg_settings.lua rename to builtin/common/settings/dlg_settings.lua index f1861879a15d2..bf63bd29e3e83 100644 --- a/builtin/mainmenu/settings/dlg_settings.lua +++ b/builtin/common/settings/dlg_settings.lua @@ -16,11 +16,10 @@ --51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -local component_funcs = dofile(core.get_mainmenu_path() .. DIR_DELIM .. - "settings" .. DIR_DELIM .. "components.lua") +local path = core.get_builtin_path() .. "common" .. DIR_DELIM .. "settings" .. DIR_DELIM -local shadows_component = dofile(core.get_mainmenu_path() .. DIR_DELIM .. - "settings" .. DIR_DELIM .. "shadows_component.lua") +local component_funcs = dofile(path .. "components.lua") +local shadows_component = dofile(path .. "shadows_component.lua") local loaded = false local full_settings @@ -531,6 +530,7 @@ local function get_formspec(dialogdata) "field[0.25,0.25;", tostring(search_width), ",0.75;search_query;;", core.formspec_escape(dialogdata.query or ""), "]", "field_enter_after_edit[search_query;true]", + "field_close_on_enter[search_query;false]", -- for pause menu env "container[", tostring(search_width + 0.25), ", 0.25]", "image_button[0,0;0.75,0.75;", core.formspec_escape(defaulttexturedir .. "search.png"), ";search;]", "image_button[0.75,0;0.75,0.75;", core.formspec_escape(defaulttexturedir .. "clear.png"), ";search_clear;]", @@ -671,7 +671,8 @@ local function buttonhandler(this, fields) dialogdata.rightscroll = core.explode_scrollbar_event(fields.rightscroll).value or dialogdata.rightscroll dialogdata.query = fields.search_query - if fields.back then + -- "fields.quit" is for the pause menu env + if fields.back or fields.quit then this:delete() return true end @@ -765,11 +766,44 @@ local function eventhandler(event) end -function create_settings_dlg() - load() - local dlg = dialog_create("dlg_settings", get_formspec, buttonhandler, eventhandler) +if INIT == "mainmenu" then + function create_settings_dlg() + load() + local dlg = dialog_create("dlg_settings", get_formspec, buttonhandler, eventhandler) - dlg.data.page_id = update_filtered_pages("") + dlg.data.page_id = update_filtered_pages("") - return dlg + return dlg + end + +else + assert(INIT == "pause_menu") + + local dialog + + core.register_on_formspec_input(function(formname, fields) + if dialog and formname == "__builtin:settings" then + -- buttonhandler returning true means we should update the formspec. + -- dialog is re-checked since the buttonhandler may have closed it. + if buttonhandler(dialog, fields) and dialog then + core.show_formspec("__builtin:settings", get_formspec(dialog.data)) + end + return true + end + end) + + core.open_settings = function() + load() + dialog = {} + dialog.data = {} + dialog.data.page_id = update_filtered_pages("") + dialog.delete = function() + dialog = nil + -- only needed for the "fields.back" case, in the "fields.quit" + -- case it's a no-op + core.show_formspec("__builtin:settings", "") + end + + core.show_formspec("__builtin:settings", get_formspec(dialog.data)) + end end diff --git a/builtin/mainmenu/settings/generate_from_settingtypes.lua b/builtin/common/settings/generate_from_settingtypes.lua similarity index 100% rename from builtin/mainmenu/settings/generate_from_settingtypes.lua rename to builtin/common/settings/generate_from_settingtypes.lua diff --git a/builtin/mainmenu/settings/init.lua b/builtin/common/settings/init.lua similarity index 83% rename from builtin/mainmenu/settings/init.lua rename to builtin/common/settings/init.lua index c8615ba344409..d475e0c9644a1 100644 --- a/builtin/mainmenu/settings/init.lua +++ b/builtin/common/settings/init.lua @@ -15,11 +15,11 @@ --with this program; if not, write to the Free Software Foundation, Inc., --51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -local path = core.get_mainmenu_path() .. DIR_DELIM .. "settings" +local path = core.get_builtin_path() .. "common" .. DIR_DELIM .. "settings" .. DIR_DELIM -dofile(path .. DIR_DELIM .. "settingtypes.lua") -dofile(path .. DIR_DELIM .. "dlg_change_mapgen_flags.lua") -dofile(path .. DIR_DELIM .. "dlg_settings.lua") +dofile(path .. "settingtypes.lua") +dofile(path .. "dlg_change_mapgen_flags.lua") +dofile(path .. "dlg_settings.lua") -- Uncomment to generate 'minetest.conf.example' and 'settings_translation_file.cpp'. -- For RUN_IN_PLACE the generated files may appear in the 'bin' folder. diff --git a/builtin/mainmenu/settings/settingtypes.lua b/builtin/common/settings/settingtypes.lua similarity index 96% rename from builtin/mainmenu/settings/settingtypes.lua rename to builtin/common/settings/settingtypes.lua index e763535a903cf..a4dd28483d091 100644 --- a/builtin/mainmenu/settings/settingtypes.lua +++ b/builtin/common/settings/settingtypes.lua @@ -408,7 +408,16 @@ function settingtypes.parse_config_file(read_all, parse_mods) file:close() end - if parse_mods then + -- TODO: Support game/mod settings in the pause menu too + -- Note that this will need to work different from how it's done in the + -- mainmenu: + -- * Only if in singleplayer / on local server, not on remote servers + -- * Only show settings for the active game and mods + -- (add API function to get them, can return nil if on a remote server) + -- (names are probably not enough, will need paths for uniqueness) + -- This means just making "pkgmgr.lua" work won't get you very far. + + if INIT == "mainmenu" and parse_mods then -- Parse games local games_category_initialized = false for _, game in ipairs(pkgmgr.games) do diff --git a/builtin/mainmenu/settings/shadows_component.lua b/builtin/common/settings/shadows_component.lua similarity index 100% rename from builtin/mainmenu/settings/shadows_component.lua rename to builtin/common/settings/shadows_component.lua diff --git a/builtin/init.lua b/builtin/init.lua index 83e8a1df0855a..59d1558fcaf14 100644 --- a/builtin/init.lua +++ b/builtin/init.lua @@ -78,6 +78,8 @@ elseif INIT == "client" then dofile(scriptdir .. "client" .. DIR_DELIM .. "init.lua") elseif INIT == "emerge" then dofile(scriptdir .. "emerge" .. DIR_DELIM .. "init.lua") +elseif INIT == "pause_menu" then + dofile(scriptdir .. "pause_menu" .. DIR_DELIM .. "init.lua") else error(("Unrecognized builtin initialization type %s!"):format(tostring(INIT))) end diff --git a/builtin/mainmenu/init.lua b/builtin/mainmenu/init.lua index 9eceb41a8726d..ec33f33b34b2e 100644 --- a/builtin/mainmenu/init.lua +++ b/builtin/mainmenu/init.lua @@ -47,7 +47,7 @@ dofile(menupath .. DIR_DELIM .. "game_theme.lua") dofile(menupath .. DIR_DELIM .. "content" .. DIR_DELIM .. "init.lua") dofile(menupath .. DIR_DELIM .. "dlg_config_world.lua") -dofile(menupath .. DIR_DELIM .. "settings" .. DIR_DELIM .. "init.lua") +dofile(basepath .. "common" .. DIR_DELIM .. "settings" .. DIR_DELIM .. "init.lua") dofile(menupath .. DIR_DELIM .. "dlg_create_world.lua") dofile(menupath .. DIR_DELIM .. "dlg_delete_content.lua") dofile(menupath .. DIR_DELIM .. "dlg_delete_world.lua") diff --git a/builtin/pause_menu/init.lua b/builtin/pause_menu/init.lua new file mode 100644 index 0000000000000..035d2ba99025c --- /dev/null +++ b/builtin/pause_menu/init.lua @@ -0,0 +1,12 @@ +local scriptpath = core.get_builtin_path() +local pausepath = scriptpath.."pause_menu"..DIR_DELIM +local commonpath = scriptpath.."common"..DIR_DELIM + +-- we're in-game, so no absolute paths are needed +defaulttexturedir = "" + +local builtin_shared = {} + +assert(loadfile(commonpath .. "register.lua"))(builtin_shared) +assert(loadfile(pausepath .. "register.lua"))(builtin_shared) +dofile(commonpath .. "settings" .. DIR_DELIM .. "init.lua") diff --git a/builtin/pause_menu/register.lua b/builtin/pause_menu/register.lua new file mode 100644 index 0000000000000..ea97ca281f613 --- /dev/null +++ b/builtin/pause_menu/register.lua @@ -0,0 +1,5 @@ +local builtin_shared = ... + +local make_registration = builtin_shared.make_registration + +core.registered_on_formspec_input, core.register_on_formspec_input = make_registration() diff --git a/doc/menu_lua_api.md b/doc/menu_lua_api.md index b17e4b1c8c1af..49459e1918ab5 100644 --- a/doc/menu_lua_api.md +++ b/doc/menu_lua_api.md @@ -105,11 +105,6 @@ of manually putting one, as different OSs use different delimiters. E.g. * `spec` = `SimpleSoundSpec` (see `lua_api.md`) * `looped` = bool * `handle:stop()` or `core.sound_stop(handle)` -* `core.get_video_drivers()` - * get list of video drivers supported by engine (not all modes are guaranteed to work) - * returns list of available video drivers' settings name and 'friendly' display name - e.g. `{ {name="opengl", friendly_name="OpenGL"}, {name="software", friendly_name="Software Renderer"} }` - * first element of returned list is guaranteed to be the NULL driver * `core.get_mapgen_names([include_hidden=false])` -> table of map generator algorithms registered in the core (possible in async calls) * `core.get_cache_path()` -> path of cache diff --git a/src/client/clientevent.h b/src/client/clientevent.h index 297124b30ecc0..f21b8220f1ef1 100644 --- a/src/client/clientevent.h +++ b/src/client/clientevent.h @@ -22,7 +22,8 @@ enum ClientEventType : u8 CE_PLAYER_FORCE_MOVE, CE_DEATHSCREEN_LEGACY, CE_SHOW_FORMSPEC, - CE_SHOW_LOCAL_FORMSPEC, + CE_SHOW_CSM_FORMSPEC, + CE_SHOW_PAUSE_MENU_FORMSPEC, CE_SPAWN_PARTICLE, CE_ADD_PARTICLESPAWNER, CE_DELETE_PARTICLESPAWNER, diff --git a/src/client/game.cpp b/src/client/game.cpp index a4a4c99098ad1..32d07ae5fb90f 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -650,7 +650,8 @@ class Game { void handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam); void handleClientEvent_DeathscreenLegacy(ClientEvent *event, CameraOrientation *cam); void handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam); - void handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam); + void handleClientEvent_ShowCSMFormSpec(ClientEvent *event, CameraOrientation *cam); + void handleClientEvent_ShowPauseMenuFormSpec(ClientEvent *event, CameraOrientation *cam); void handleClientEvent_HandleParticleEvent(ClientEvent *event, CameraOrientation *cam); void handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam); @@ -2540,7 +2541,8 @@ const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = { {&Game::handleClientEvent_PlayerForceMove}, {&Game::handleClientEvent_DeathscreenLegacy}, {&Game::handleClientEvent_ShowFormSpec}, - {&Game::handleClientEvent_ShowLocalFormSpec}, + {&Game::handleClientEvent_ShowCSMFormSpec}, + {&Game::handleClientEvent_ShowPauseMenuFormSpec}, {&Game::handleClientEvent_HandleParticleEvent}, {&Game::handleClientEvent_HandleParticleEvent}, {&Game::handleClientEvent_HandleParticleEvent}, @@ -2608,9 +2610,18 @@ void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation delete event->show_formspec.formname; } -void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam) +void Game::handleClientEvent_ShowCSMFormSpec(ClientEvent *event, CameraOrientation *cam) { - m_game_formspec.showLocalFormSpec(*event->show_formspec.formspec, + m_game_formspec.showCSMFormSpec(*event->show_formspec.formspec, + *event->show_formspec.formname); + + delete event->show_formspec.formspec; + delete event->show_formspec.formname; +} + +void Game::handleClientEvent_ShowPauseMenuFormSpec(ClientEvent *event, CameraOrientation *cam) +{ + m_game_formspec.showPauseMenuFormSpec(*event->show_formspec.formspec, *event->show_formspec.formname); delete event->show_formspec.formspec; diff --git a/src/client/game_formspec.cpp b/src/client/game_formspec.cpp index cc5ddc0ede288..df3592364bc86 100644 --- a/src/client/game_formspec.cpp +++ b/src/client/game_formspec.cpp @@ -77,8 +77,8 @@ struct LocalFormspecHandler : public TextDest m_formname = formname; } - LocalFormspecHandler(const std::string &formname, Client *client): - m_client(client) + LocalFormspecHandler(const std::string &formname, Client *client, PauseMenuScripting *pause_script): + m_client(client), m_pause_script(pause_script) { m_formname = formname; } @@ -86,18 +86,13 @@ struct LocalFormspecHandler : public TextDest void gotText(const StringMap &fields) { if (m_formname == "MT_PAUSE_MENU") { - if (fields.find("btn_sound") != fields.end()) { - g_gamecallback->changeVolume(); + if (fields.find("btn_settings") != fields.end()) { + g_gamecallback->openSettings(); return; } - if (fields.find("btn_key_config") != fields.end()) { - g_gamecallback->keyConfig(); - return; - } - - if (fields.find("btn_touchscreen_layout") != fields.end()) { - g_gamecallback->touchscreenLayout(); + if (fields.find("btn_sound") != fields.end()) { + g_gamecallback->changeVolume(); return; } @@ -131,11 +126,17 @@ struct LocalFormspecHandler : public TextDest return; } - if (m_client->modsLoaded()) - m_client->getScript()->on_formspec_input(m_formname, fields); + if (m_pause_script && + m_pause_script->on_formspec_input(m_formname, fields)) + return; + + if (m_client && m_client->modsLoaded() && + m_client->getScript()->on_formspec_input(m_formname, fields)) + return; } Client *m_client = nullptr; + PauseMenuScripting *m_pause_script = nullptr; }; /* Form update callback */ @@ -193,6 +194,15 @@ class PlayerInventoryFormSource: public IFormSource //// GameFormSpec +void GameFormSpec::init(Client *client, RenderingEngine *rendering_engine, InputHandler *input) +{ + m_client = client; + m_rendering_engine = rendering_engine; + m_input = input; + m_pause_script = std::make_unique(client); + m_pause_script->loadBuiltin(); +} + void GameFormSpec::deleteFormspec() { if (m_formspec) { @@ -208,33 +218,70 @@ GameFormSpec::~GameFormSpec() { this->deleteFormspec(); } -void GameFormSpec::showFormSpec(const std::string &formspec, const std::string &formname) +bool GameFormSpec::handleEmptyFormspec(const std::string &formspec, const std::string &formname) { if (formspec.empty()) { if (m_formspec && (formname.empty() || formname == m_formname)) { m_formspec->quitMenu(); } - } else { - FormspecFormSource *fs_src = - new FormspecFormSource(formspec); - TextDestPlayerInventory *txt_dst = - new TextDestPlayerInventory(m_client, formname); + return true; + } + return false; +} - m_formname = formname; - GUIFormSpecMenu::create(m_formspec, m_client, m_rendering_engine->get_gui_env(), +void GameFormSpec::showFormSpec(const std::string &formspec, const std::string &formname) +{ + if (handleEmptyFormspec(formspec, formname)) + return; + + FormspecFormSource *fs_src = + new FormspecFormSource(formspec); + TextDestPlayerInventory *txt_dst = + new TextDestPlayerInventory(m_client, formname); + + m_formname = formname; + GUIFormSpecMenu::create(m_formspec, m_client, m_rendering_engine->get_gui_env(), + &m_input->joystick, fs_src, txt_dst, m_client->getFormspecPrepend(), + m_client->getSoundManager()); +} + +void GameFormSpec::showCSMFormSpec(const std::string &formspec, const std::string &formname) +{ + if (handleEmptyFormspec(formspec, formname)) + return; + + FormspecFormSource *fs_src = new FormspecFormSource(formspec); + LocalFormspecHandler *txt_dst = + new LocalFormspecHandler(formname, m_client, nullptr); + + m_formname = formname; + GUIFormSpecMenu::create(m_formspec, m_client, m_rendering_engine->get_gui_env(), &m_input->joystick, fs_src, txt_dst, m_client->getFormspecPrepend(), m_client->getSoundManager()); - } } -void GameFormSpec::showLocalFormSpec(const std::string &formspec, const std::string &formname) +void GameFormSpec::showPauseMenuFormSpec(const std::string &formspec, const std::string &formname) { + // The pause menu env is a trusted context like the mainmenu env and provides + // the in-game settings formspec. + // Neither CSM nor the server must be allowed to mess with it. + + if (handleEmptyFormspec(formspec, formname)) + return; + FormspecFormSource *fs_src = new FormspecFormSource(formspec); LocalFormspecHandler *txt_dst = - new LocalFormspecHandler(formname, m_client); + new LocalFormspecHandler(formname, nullptr, m_pause_script.get()); + + m_formname = formname; GUIFormSpecMenu::create(m_formspec, m_client, m_rendering_engine->get_gui_env(), - &m_input->joystick, fs_src, txt_dst, m_client->getFormspecPrepend(), + // Ignore formspec prepend. + &m_input->joystick, fs_src, txt_dst, "", m_client->getSoundManager()); + + // FIXME: can't enable this for now because "fps_max_unfocused" also applies + // when the game is paused, making the settings menu much less enjoyable. + // m_formspec->doPause = true; } void GameFormSpec::showNodeFormspec(const std::string &formspec, const v3s16 &nodepos) @@ -331,6 +378,9 @@ void GameFormSpec::showPauseMenu() os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]"; } + os << "button_exit[4," << (ypos++) << ";3,0.5;btn_settings;" + << strgettext("Settings") << "]"; + #ifndef __ANDROID__ #if USE_SOUND os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;" @@ -338,13 +388,6 @@ void GameFormSpec::showPauseMenu() #endif #endif - if (g_touchcontrols) { - os << "button_exit[4," << (ypos++) << ";3,0.5;btn_touchscreen_layout;" - << strgettext("Touchscreen Layout") << "]"; - } else { - os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;" - << strgettext("Controls") << "]"; - } os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;" << strgettext("Exit to Menu") << "]"; os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;" @@ -400,7 +443,7 @@ void GameFormSpec::showPauseMenu() &m_input->joystick, fs_src, txt_dst, m_client->getFormspecPrepend(), m_client->getSoundManager()); m_formspec->setFocus("btn_continue"); - // game will be paused in next step, if in singleplayer (see m_is_paused) + // game will be paused in next step, if in singleplayer (see Game::m_is_paused) m_formspec->doPause = true; } @@ -418,7 +461,7 @@ void GameFormSpec::showDeathFormspecLegacy() /* Note: FormspecFormSource and LocalFormspecHandler * * are deleted by guiFormSpecMenu */ FormspecFormSource *fs_src = new FormspecFormSource(formspec_str); - LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", m_client); + LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", m_client, nullptr); GUIFormSpecMenu::create(m_formspec, m_client, m_rendering_engine->get_gui_env(), &m_input->joystick, fs_src, txt_dst, m_client->getFormspecPrepend(), @@ -473,6 +516,11 @@ bool GameFormSpec::handleCallbacks() return false; } + if (g_gamecallback->settings_requested) { + m_pause_script->open_settings(); + g_gamecallback->settings_requested = false; + } + if (g_gamecallback->changepassword_requested) { (void)make_irr(guienv, guiroot, -1, &g_menumgr, m_client, texture_src); diff --git a/src/client/game_formspec.h b/src/client/game_formspec.h index 3703701514a35..5dd837803a4a7 100644 --- a/src/client/game_formspec.h +++ b/src/client/game_formspec.h @@ -4,8 +4,10 @@ #pragma once +#include #include #include "irr_v3d.h" +#include "scripting_pause_menu.h" class Client; class RenderingEngine; @@ -22,17 +24,13 @@ It includes: */ struct GameFormSpec { - void init(Client *client, RenderingEngine *rendering_engine, InputHandler *input) - { - m_client = client; - m_rendering_engine = rendering_engine; - m_input = input; - } + void init(Client *client, RenderingEngine *rendering_engine, InputHandler *input); ~GameFormSpec(); void showFormSpec(const std::string &formspec, const std::string &formname); - void showLocalFormSpec(const std::string &formspec, const std::string &formname); + void showCSMFormSpec(const std::string &formspec, const std::string &formname); + void showPauseMenuFormSpec(const std::string &formspec, const std::string &formname); void showNodeFormspec(const std::string &formspec, const v3s16 &nodepos); void showPlayerInventory(); void showDeathFormspecLegacy(); @@ -52,11 +50,14 @@ struct GameFormSpec Client *m_client; RenderingEngine *m_rendering_engine; InputHandler *m_input; + std::unique_ptr m_pause_script; // Default: "". If other than "": Empty show_formspec packets will only // close the formspec when the formname matches std::string m_formname; GUIFormSpecMenu *m_formspec = nullptr; + bool handleEmptyFormspec(const std::string &formspec, const std::string &formname); + void deleteFormspec(); }; diff --git a/src/gui/mainmenumanager.h b/src/gui/mainmenumanager.h index 87751bb62f267..2ac00fee70f83 100644 --- a/src/gui/mainmenumanager.h +++ b/src/gui/mainmenumanager.h @@ -21,6 +21,7 @@ class IGameCallback { public: virtual void exitToOS() = 0; + virtual void openSettings() = 0; virtual void keyConfig() = 0; virtual void disconnect() = 0; virtual void changePassword() = 0; @@ -115,6 +116,11 @@ class MainGameCallback : public IGameCallback shutdown_requested = true; } + void openSettings() override + { + settings_requested = true; + } + void disconnect() override { disconnect_requested = true; @@ -151,6 +157,7 @@ class MainGameCallback : public IGameCallback } bool disconnect_requested = false; + bool settings_requested = false; bool changepassword_requested = false; bool changevolume_requested = false; bool keyconfig_requested = false; diff --git a/src/script/CMakeLists.txt b/src/script/CMakeLists.txt index ccb5785bdaf36..fd46f522f5b18 100644 --- a/src/script/CMakeLists.txt +++ b/src/script/CMakeLists.txt @@ -15,6 +15,7 @@ set(common_SCRIPT_SRCS set(client_SCRIPT_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/scripting_mainmenu.cpp ${CMAKE_CURRENT_SOURCE_DIR}/scripting_client.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/scripting_pause_menu.cpp ${client_SCRIPT_COMMON_SRCS} ${client_SCRIPT_CPP_API_SRCS} ${client_SCRIPT_LUA_API_SRCS} diff --git a/src/script/cpp_api/CMakeLists.txt b/src/script/cpp_api/CMakeLists.txt index d9d157d5fd19d..123364141c92e 100644 --- a/src/script/cpp_api/CMakeLists.txt +++ b/src/script/cpp_api/CMakeLists.txt @@ -16,6 +16,8 @@ set(common_SCRIPT_CPP_API_SRCS set(client_SCRIPT_CPP_API_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/s_client.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/s_client_common.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_mainmenu.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/s_pause_menu.cpp PARENT_SCOPE) diff --git a/src/script/cpp_api/s_base.cpp b/src/script/cpp_api/s_base.cpp index 9cf886efd729c..9022cd8c3e2fb 100644 --- a/src/script/cpp_api/s_base.cpp +++ b/src/script/cpp_api/s_base.cpp @@ -203,9 +203,13 @@ void ScriptApiBase::checkSetByBuiltin() { lua_State *L = getStack(); - if (m_gamedef) { - CHECK(CUSTOM_RIDX_READ_VECTOR, "read_vector"); - CHECK(CUSTOM_RIDX_PUSH_VECTOR, "push_vector"); + CHECK(CUSTOM_RIDX_READ_VECTOR, "read_vector"); + CHECK(CUSTOM_RIDX_PUSH_VECTOR, "push_vector"); + + if (getType() == ScriptingType::Server || + (getType() == ScriptingType::Async && m_gamedef) || + getType() == ScriptingType::Emerge || + getType() == ScriptingType::Client) { CHECK(CUSTOM_RIDX_READ_NODE, "read_node"); CHECK(CUSTOM_RIDX_PUSH_NODE, "push_node"); } diff --git a/src/script/cpp_api/s_base.h b/src/script/cpp_api/s_base.h index 34ac225951472..135e9acfdbb9a 100644 --- a/src/script/cpp_api/s_base.h +++ b/src/script/cpp_api/s_base.h @@ -47,7 +47,8 @@ enum class ScriptingType: u8 { Client, MainMenu, Server, - Emerge + Emerge, + PauseMenu, }; class Server; diff --git a/src/script/cpp_api/s_client.cpp b/src/script/cpp_api/s_client.cpp index 772bc24121af1..f383d8172c286 100644 --- a/src/script/cpp_api/s_client.cpp +++ b/src/script/cpp_api/s_client.cpp @@ -126,34 +126,6 @@ void ScriptApiClient::environment_step(float dtime) } } -void ScriptApiClient::on_formspec_input(const std::string &formname, - const StringMap &fields) -{ - SCRIPTAPI_PRECHECKHEADER - - // Get core.registered_on_chat_messages - lua_getglobal(L, "core"); - lua_getfield(L, -1, "registered_on_formspec_input"); - // Call callbacks - // param 1 - lua_pushstring(L, formname.c_str()); - // param 2 - lua_newtable(L); - StringMap::const_iterator it; - for (it = fields.begin(); it != fields.end(); ++it) { - const std::string &name = it->first; - const std::string &value = it->second; - lua_pushstring(L, name.c_str()); - lua_pushlstring(L, value.c_str(), value.size()); - lua_settable(L, -3); - } - try { - runCallbacks(2, RUN_CALLBACKS_MODE_OR_SC); - } catch (LuaError &e) { - getClient()->setFatalError(e); - } -} - bool ScriptApiClient::on_dignode(v3s16 p, MapNode node) { SCRIPTAPI_PRECHECKHEADER diff --git a/src/script/cpp_api/s_client.h b/src/script/cpp_api/s_client.h index 89a0657527877..2308e13f4c72c 100644 --- a/src/script/cpp_api/s_client.h +++ b/src/script/cpp_api/s_client.h @@ -35,7 +35,6 @@ class ScriptApiClient : virtual public ScriptApiBase void on_damage_taken(int32_t damage_amount); void on_hp_modification(int32_t newhp); void environment_step(float dtime); - void on_formspec_input(const std::string &formname, const StringMap &fields); bool on_dignode(v3s16 p, MapNode node); bool on_punchnode(v3s16 p, MapNode node); diff --git a/src/script/cpp_api/s_client_common.cpp b/src/script/cpp_api/s_client_common.cpp new file mode 100644 index 0000000000000..7e5909fae0695 --- /dev/null +++ b/src/script/cpp_api/s_client_common.cpp @@ -0,0 +1,39 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2013 celeron55, Perttu Ahola +// Copyright (C) 2017 nerzhul, Loic Blot +// Copyright (C) 2025 grorp + +#include "s_client_common.h" +#include "s_internal.h" +#include "client/client.h" + +bool ScriptApiClientCommon::on_formspec_input(const std::string &formname, + const StringMap &fields) +{ + SCRIPTAPI_PRECHECKHEADER + + // Get core.registered_on_formspec_inputs + lua_getglobal(L, "core"); + lua_getfield(L, -1, "registered_on_formspec_input"); + // Call callbacks + // param 1 + lua_pushstring(L, formname.c_str()); + // param 2 + lua_newtable(L); + StringMap::const_iterator it; + for (it = fields.begin(); it != fields.end(); ++it) { + const std::string &name = it->first; + const std::string &value = it->second; + lua_pushstring(L, name.c_str()); + lua_pushlstring(L, value.c_str(), value.size()); + lua_settable(L, -3); + } + try { + runCallbacks(2, RUN_CALLBACKS_MODE_OR_SC); + } catch (LuaError &e) { + getClient()->setFatalError(e); + return true; + } + return readParam(L, -1); +} diff --git a/src/script/cpp_api/s_client_common.h b/src/script/cpp_api/s_client_common.h new file mode 100644 index 0000000000000..b75ce53e185ae --- /dev/null +++ b/src/script/cpp_api/s_client_common.h @@ -0,0 +1,16 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2013 celeron55, Perttu Ahola +// Copyright (C) 2017 nerzhul, Loic Blot +// Copyright (C) 2025 grorp + +#pragma once + +#include "cpp_api/s_base.h" +#include "util/string.h" + +class ScriptApiClientCommon : virtual public ScriptApiBase +{ +public: + bool on_formspec_input(const std::string &formname, const StringMap &fields); +}; diff --git a/src/script/cpp_api/s_pause_menu.cpp b/src/script/cpp_api/s_pause_menu.cpp new file mode 100644 index 0000000000000..9f4cdfb94b43d --- /dev/null +++ b/src/script/cpp_api/s_pause_menu.cpp @@ -0,0 +1,20 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2025 grorp + +#include "s_pause_menu.h" +#include "cpp_api/s_internal.h" + +void ScriptApiPauseMenu::open_settings() +{ + SCRIPTAPI_PRECHECKHEADER + + int error_handler = PUSH_ERROR_HANDLER(L); + + lua_getglobal(L, "core"); + lua_getfield(L, -1, "open_settings"); + + PCALL_RES(lua_pcall(L, 0, 0, error_handler)); + + lua_pop(L, 2); // Pop core, error handler +} diff --git a/src/script/cpp_api/s_pause_menu.h b/src/script/cpp_api/s_pause_menu.h new file mode 100644 index 0000000000000..b8feac81a8c2f --- /dev/null +++ b/src/script/cpp_api/s_pause_menu.h @@ -0,0 +1,13 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2025 grorp + +#pragma once + +#include "cpp_api/s_base.h" + +class ScriptApiPauseMenu : virtual public ScriptApiBase +{ +public: + void open_settings(); +}; diff --git a/src/script/lua_api/CMakeLists.txt b/src/script/lua_api/CMakeLists.txt index 2e12f8c56c5d9..ef1be9525b569 100644 --- a/src/script/lua_api/CMakeLists.txt +++ b/src/script/lua_api/CMakeLists.txt @@ -29,11 +29,14 @@ set(common_SCRIPT_LUA_API_SRCS set(client_SCRIPT_LUA_API_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/l_camera.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_client.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/l_client_common.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_client_sound.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_localplayer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_mainmenu.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_mainmenu_sound.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/l_menu_common.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_minimap.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_particles_local.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/l_pause_menu.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_storage.cpp PARENT_SCOPE) diff --git a/src/script/lua_api/l_client.cpp b/src/script/lua_api/l_client.cpp index 7735bff922eb0..2aef6d470f6fd 100644 --- a/src/script/lua_api/l_client.cpp +++ b/src/script/lua_api/l_client.cpp @@ -127,21 +127,6 @@ int ModApiClient::l_get_player_names(lua_State *L) return 1; } -// show_formspec(formspec) -int ModApiClient::l_show_formspec(lua_State *L) -{ - if (!lua_isstring(L, 1) || !lua_isstring(L, 2)) - return 0; - - ClientEvent *event = new ClientEvent(); - event->type = CE_SHOW_LOCAL_FORMSPEC; - event->show_formspec.formname = new std::string(luaL_checkstring(L, 1)); - event->show_formspec.formspec = new std::string(luaL_checkstring(L, 2)); - getClient(L)->pushToEventQueue(event); - lua_pushboolean(L, true); - return 1; -} - // disconnect() int ModApiClient::l_disconnect(lua_State *L) { @@ -325,7 +310,6 @@ void ModApiClient::Initialize(lua_State *L, int top) API_FCT(send_chat_message); API_FCT(clear_out_chat_queue); API_FCT(get_player_names); - API_FCT(show_formspec); API_FCT(gettext); API_FCT(get_node_or_nil); API_FCT(disconnect); diff --git a/src/script/lua_api/l_client.h b/src/script/lua_api/l_client.h index fb5c53171ca7a..777b128cc29af 100644 --- a/src/script/lua_api/l_client.h +++ b/src/script/lua_api/l_client.h @@ -33,9 +33,6 @@ class ModApiClient : public ModApiBase // get_player_names() static int l_get_player_names(lua_State *L); - // show_formspec(name, formspec) - static int l_show_formspec(lua_State *L); - // disconnect() static int l_disconnect(lua_State *L); diff --git a/src/script/lua_api/l_client_common.cpp b/src/script/lua_api/l_client_common.cpp new file mode 100644 index 0000000000000..21d040fa1efd5 --- /dev/null +++ b/src/script/lua_api/l_client_common.cpp @@ -0,0 +1,33 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2013 celeron55, Perttu Ahola +// Copyright (C) 2017 nerzhul, Loic Blot +// Copyright (C) 2025 grorp + +#include "l_client_common.h" + +#include "client/client.h" +#include "client/clientevent.h" +#include "cpp_api/s_base.h" +#include "lua_api/l_internal.h" + + +// show_formspec(formspec) +int ModApiClientCommon::l_show_formspec(lua_State *L) +{ + ClientEvent *event = new ClientEvent(); + event->type = getScriptApiBase(L)->getType() == ScriptingType::PauseMenu + ? CE_SHOW_PAUSE_MENU_FORMSPEC + : CE_SHOW_CSM_FORMSPEC; + event->show_formspec.formname = new std::string(luaL_checkstring(L, 1)); + event->show_formspec.formspec = new std::string(luaL_checkstring(L, 2)); + getClient(L)->pushToEventQueue(event); + lua_pushboolean(L, true); + return 1; +} + + +void ModApiClientCommon::Initialize(lua_State *L, int top) +{ + API_FCT(show_formspec); +} diff --git a/src/script/lua_api/l_client_common.h b/src/script/lua_api/l_client_common.h new file mode 100644 index 0000000000000..5063032abece7 --- /dev/null +++ b/src/script/lua_api/l_client_common.h @@ -0,0 +1,19 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2013 celeron55, Perttu Ahola +// Copyright (C) 2017 nerzhul, Loic Blot +// Copyright (C) 2025 grorp + +#pragma once + +#include "lua_api/l_base.h" + +class ModApiClientCommon : public ModApiBase +{ +private: + // show_formspec(name, formspec) + static int l_show_formspec(lua_State *L); + +public: + static void Initialize(lua_State *L, int top); +}; diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index 0353efe1d3fd7..f077a8cdc9d26 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -875,27 +875,6 @@ int ModApiMainMenu::l_download_file(lua_State *L) return 1; } -/******************************************************************************/ -int ModApiMainMenu::l_get_video_drivers(lua_State *L) -{ - auto drivers = RenderingEngine::getSupportedVideoDrivers(); - - lua_newtable(L); - for (u32 i = 0; i != drivers.size(); i++) { - auto &info = RenderingEngine::getVideoDriverInfo(drivers[i]); - - lua_newtable(L); - lua_pushstring(L, info.name.c_str()); - lua_setfield(L, -2, "name"); - lua_pushstring(L, info.friendly_name.c_str()); - lua_setfield(L, -2, "friendly_name"); - - lua_rawseti(L, -2, i + 1); - } - - return 1; -} - /******************************************************************************/ int ModApiMainMenu::l_get_language(lua_State *L) { @@ -907,16 +886,6 @@ int ModApiMainMenu::l_get_language(lua_State *L) return 1; } -/******************************************************************************/ -int ModApiMainMenu::l_gettext(lua_State *L) -{ - const char *srctext = luaL_checkstring(L, 1); - const char *text = *srctext ? gettext(srctext) : ""; - lua_pushstring(L, text); - - return 1; -} - /******************************************************************************/ int ModApiMainMenu::l_get_window_info(lua_State *L) { @@ -949,14 +918,6 @@ int ModApiMainMenu::l_get_window_info(lua_State *L) } /******************************************************************************/ -int ModApiMainMenu::l_get_active_driver(lua_State *L) -{ - auto drivertype = RenderingEngine::get_video_driver()->getDriverType(); - lua_pushstring(L, RenderingEngine::getVideoDriverInfo(drivertype).name.c_str()); - return 1; -} - - int ModApiMainMenu::l_get_active_renderer(lua_State *L) { lua_pushstring(L, RenderingEngine::get_video_driver()->getName()); @@ -980,14 +941,6 @@ int ModApiMainMenu::l_get_active_irrlicht_device(lua_State *L) return 1; } -/******************************************************************************/ -int ModApiMainMenu::l_irrlicht_device_supports_touch(lua_State *L) -{ - lua_pushboolean(L, RenderingEngine::get_raw_device()->supportsTouchEvents()); - return 1; -} - - /******************************************************************************/ int ModApiMainMenu::l_get_min_supp_proto(lua_State *L) { @@ -1122,13 +1075,9 @@ void ModApiMainMenu::Initialize(lua_State *L, int top) API_FCT(show_path_select_dialog); API_FCT(download_file); API_FCT(get_language); - API_FCT(gettext); - API_FCT(get_video_drivers); API_FCT(get_window_info); - API_FCT(get_active_driver); API_FCT(get_active_renderer); API_FCT(get_active_irrlicht_device); - API_FCT(irrlicht_device_supports_touch); API_FCT(get_min_supp_proto); API_FCT(get_max_supp_proto); API_FCT(get_formspec_version); @@ -1165,5 +1114,4 @@ void ModApiMainMenu::InitializeAsync(lua_State *L, int top) API_FCT(get_max_supp_proto); API_FCT(get_formspec_version); API_FCT(get_language); - API_FCT(gettext); } diff --git a/src/script/lua_api/l_mainmenu.h b/src/script/lua_api/l_mainmenu.h index 704924bf35e1d..63f45d74b7c62 100644 --- a/src/script/lua_api/l_mainmenu.h +++ b/src/script/lua_api/l_mainmenu.h @@ -53,8 +53,6 @@ class ModApiMainMenu: public ModApiBase static int l_get_language(lua_State *L); - static int l_gettext(lua_State *L); - //packages static int l_get_games(lua_State *L); @@ -89,14 +87,10 @@ class ModApiMainMenu: public ModApiBase static int l_get_window_info(lua_State *L); - static int l_get_active_driver(lua_State *L); - static int l_get_active_renderer(lua_State *L); static int l_get_active_irrlicht_device(lua_State *L); - static int l_irrlicht_device_supports_touch(lua_State *L); - //filesystem static int l_get_mainmenu_path(lua_State *L); @@ -133,8 +127,6 @@ class ModApiMainMenu: public ModApiBase static int l_download_file(lua_State *L); - static int l_get_video_drivers(lua_State *L); - //version compatibility static int l_get_min_supp_proto(lua_State *L); diff --git a/src/script/lua_api/l_menu_common.cpp b/src/script/lua_api/l_menu_common.cpp new file mode 100644 index 0000000000000..19c97c042f8d8 --- /dev/null +++ b/src/script/lua_api/l_menu_common.cpp @@ -0,0 +1,49 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2013 sapier +// Copyright (C) 2025 grorp + +#include "l_menu_common.h" + +#include "client/renderingengine.h" +#include "gettext.h" +#include "lua_api/l_internal.h" + + +int ModApiMenuCommon::l_gettext(lua_State *L) +{ + const char *srctext = luaL_checkstring(L, 1); + const char *text = *srctext ? gettext(srctext) : ""; + lua_pushstring(L, text); + + return 1; +} + + +int ModApiMenuCommon::l_get_active_driver(lua_State *L) +{ + auto drivertype = RenderingEngine::get_video_driver()->getDriverType(); + lua_pushstring(L, RenderingEngine::getVideoDriverInfo(drivertype).name.c_str()); + return 1; +} + + +int ModApiMenuCommon::l_irrlicht_device_supports_touch(lua_State *L) +{ + lua_pushboolean(L, RenderingEngine::get_raw_device()->supportsTouchEvents()); + return 1; +} + + +void ModApiMenuCommon::Initialize(lua_State *L, int top) +{ + API_FCT(gettext); + API_FCT(get_active_driver); + API_FCT(irrlicht_device_supports_touch); +} + + +void ModApiMenuCommon::InitializeAsync(lua_State *L, int top) +{ + API_FCT(gettext); +} diff --git a/src/script/lua_api/l_menu_common.h b/src/script/lua_api/l_menu_common.h new file mode 100644 index 0000000000000..e94e562e1bc49 --- /dev/null +++ b/src/script/lua_api/l_menu_common.h @@ -0,0 +1,20 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2013 sapier +// Copyright (C) 2025 grorp + +#pragma once + +#include "l_base.h" + +class ModApiMenuCommon: public ModApiBase +{ +private: + static int l_gettext(lua_State *L); + static int l_get_active_driver(lua_State *L); + static int l_irrlicht_device_supports_touch(lua_State *L); + +public: + static void Initialize(lua_State *L, int top); + static void InitializeAsync(lua_State *L, int top); +}; diff --git a/src/script/lua_api/l_pause_menu.cpp b/src/script/lua_api/l_pause_menu.cpp new file mode 100644 index 0000000000000..4fc17766d2e2c --- /dev/null +++ b/src/script/lua_api/l_pause_menu.cpp @@ -0,0 +1,28 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2025 grorp + +#include "l_pause_menu.h" +#include "gui/mainmenumanager.h" +#include "lua_api/l_internal.h" + + +int ModApiPauseMenu::l_show_keys_menu(lua_State *L) +{ + g_gamecallback->keyConfig(); + return 0; +} + + +int ModApiPauseMenu::l_show_touchscreen_layout(lua_State *L) +{ + g_gamecallback->touchscreenLayout(); + return 0; +} + + +void ModApiPauseMenu::Initialize(lua_State *L, int top) +{ + API_FCT(show_keys_menu); + API_FCT(show_touchscreen_layout); +} diff --git a/src/script/lua_api/l_pause_menu.h b/src/script/lua_api/l_pause_menu.h new file mode 100644 index 0000000000000..507c1c4b78ad3 --- /dev/null +++ b/src/script/lua_api/l_pause_menu.h @@ -0,0 +1,17 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2025 grorp + +#pragma once + +#include "l_base.h" + +class ModApiPauseMenu: public ModApiBase +{ +private: + static int l_show_keys_menu(lua_State *L); + static int l_show_touchscreen_layout(lua_State *L); + +public: + static void Initialize(lua_State *L, int top); +}; diff --git a/src/script/lua_api/l_settings.cpp b/src/script/lua_api/l_settings.cpp index 241eb2542fe31..326e969ad7373 100644 --- a/src/script/lua_api/l_settings.cpp +++ b/src/script/lua_api/l_settings.cpp @@ -30,8 +30,9 @@ static inline int checkSettingSecurity(lua_State* L, const std::string &name) { #if CHECK_CLIENT_BUILD() - // Main menu is allowed everything - if (ModApiBase::getGuiEngine(L) != nullptr) + // Main menu and pause menu are allowed everything + if (ModApiBase::getGuiEngine(L) || + ModApiBase::getScriptApiBase(L)->getType() == ScriptingType::PauseMenu) return 0; #endif diff --git a/src/script/scripting_client.cpp b/src/script/scripting_client.cpp index dc9b6212c4060..45a80bba92997 100644 --- a/src/script/scripting_client.cpp +++ b/src/script/scripting_client.cpp @@ -7,6 +7,7 @@ #include "client/client.h" #include "cpp_api/s_internal.h" #include "lua_api/l_client.h" +#include "lua_api/l_client_common.h" #include "lua_api/l_env.h" #include "lua_api/l_item.h" #include "lua_api/l_itemstackmeta.h" @@ -63,6 +64,7 @@ void ClientScripting::InitializeModApi(lua_State *L, int top) ClientSoundHandle::Register(L); ModApiUtil::InitializeClient(L, top); + ModApiClientCommon::Initialize(L, top); ModApiClient::Initialize(L, top); ModApiItem::InitializeClient(L, top); ModApiStorage::Initialize(L, top); diff --git a/src/script/scripting_client.h b/src/script/scripting_client.h index 872ad295aeb71..75636ded1d960 100644 --- a/src/script/scripting_client.h +++ b/src/script/scripting_client.h @@ -9,6 +9,7 @@ #include "cpp_api/s_base.h" #include "cpp_api/s_client.h" +#include "cpp_api/s_client_common.h" #include "cpp_api/s_modchannels.h" #include "cpp_api/s_security.h" @@ -20,6 +21,7 @@ class Minimap; class ClientScripting: virtual public ScriptApiBase, public ScriptApiSecurity, + public ScriptApiClientCommon, public ScriptApiClient, public ScriptApiModChannels { diff --git a/src/script/scripting_mainmenu.cpp b/src/script/scripting_mainmenu.cpp index 88f80a001d2fa..d0ab5d374265b 100644 --- a/src/script/scripting_mainmenu.cpp +++ b/src/script/scripting_mainmenu.cpp @@ -9,6 +9,7 @@ #include "lua_api/l_http.h" #include "lua_api/l_mainmenu.h" #include "lua_api/l_mainmenu_sound.h" +#include "lua_api/l_menu_common.h" #include "lua_api/l_util.h" #include "lua_api/l_settings.h" #include "log.h" @@ -53,12 +54,14 @@ void MainMenuScripting::initializeModApi(lua_State *L, int top) registerLuaClasses(L, top); // Initialize mod API modules + ModApiMenuCommon::Initialize(L, top); ModApiMainMenu::Initialize(L, top); ModApiUtil::Initialize(L, top); ModApiMainMenuSound::Initialize(L, top); ModApiHttp::Initialize(L, top); asyncEngine.registerStateInitializer(registerLuaClasses); + asyncEngine.registerStateInitializer(ModApiMenuCommon::InitializeAsync); asyncEngine.registerStateInitializer(ModApiMainMenu::InitializeAsync); asyncEngine.registerStateInitializer(ModApiUtil::InitializeAsync); asyncEngine.registerStateInitializer(ModApiHttp::InitializeAsync); diff --git a/src/script/scripting_pause_menu.cpp b/src/script/scripting_pause_menu.cpp new file mode 100644 index 0000000000000..42b48632dae9b --- /dev/null +++ b/src/script/scripting_pause_menu.cpp @@ -0,0 +1,68 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2025 grorp + +#include "scripting_pause_menu.h" +#include "client/client.h" +#include "cpp_api/s_internal.h" +#include "filesys.h" +#include "lua_api/l_client_common.h" +#include "lua_api/l_menu_common.h" +#include "lua_api/l_pause_menu.h" +#include "lua_api/l_settings.h" +#include "lua_api/l_util.h" +#include "porting.h" + +PauseMenuScripting::PauseMenuScripting(Client *client): + ScriptApiBase(ScriptingType::PauseMenu) +{ + setGameDef(client); + + SCRIPTAPI_PRECHECKHEADER + + initializeSecurity(); + + lua_getglobal(L, "core"); + int top = lua_gettop(L); + + // Initialize our lua_api modules + initializeModApi(L, top); + lua_pop(L, 1); + + // Push builtin initialization type + lua_pushstring(L, "pause_menu"); + lua_setglobal(L, "INIT"); + + infostream << "SCRIPTAPI: Initialized pause menu modules" << std::endl; +} + +void PauseMenuScripting::initializeModApi(lua_State *L, int top) +{ + // Register reference classes (userdata) + LuaSettings::Register(L); + + // Initialize mod API modules + ModApiPauseMenu::Initialize(L, top); + ModApiMenuCommon::Initialize(L, top); + ModApiClientCommon::Initialize(L, top); + ModApiUtil::Initialize(L, top); +} + +void PauseMenuScripting::loadBuiltin() +{ + loadScript(porting::path_share + DIR_DELIM "builtin" DIR_DELIM "init.lua"); + checkSetByBuiltin(); +} + +bool PauseMenuScripting::checkPathInternal(const std::string &abs_path, bool write_required, + bool *write_allowed) +{ + // NOTE: The pause menu env is on the same level of trust as the mainmenu env. + // However, since it doesn't need anything else at the moment, there's no + // reason to give it access to anything else. + + if (write_required) + return false; + std::string path_share = fs::AbsolutePath(porting::path_share); + return !path_share.empty() && fs::PathStartsWith(abs_path, path_share + DIR_DELIM "builtin"); +} diff --git a/src/script/scripting_pause_menu.h b/src/script/scripting_pause_menu.h new file mode 100644 index 0000000000000..aeb1e65b6b90e --- /dev/null +++ b/src/script/scripting_pause_menu.h @@ -0,0 +1,28 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2025 grorp + +#pragma once + +#include "cpp_api/s_base.h" +#include "cpp_api/s_client_common.h" +#include "cpp_api/s_pause_menu.h" +#include "cpp_api/s_security.h" + +class PauseMenuScripting: + virtual public ScriptApiBase, + public ScriptApiPauseMenu, + public ScriptApiClientCommon, + public ScriptApiSecurity +{ +public: + PauseMenuScripting(Client *client); + void loadBuiltin(); + +protected: + bool checkPathInternal(const std::string &abs_path, bool write_required, + bool *write_allowed) override; + +private: + void initializeModApi(lua_State *L, int top); +}; From 0bec1dfdb36b6094eaf41ff9f21fc7f54994c530 Mon Sep 17 00:00:00 2001 From: grorp Date: Thu, 16 Jan 2025 16:06:49 -0500 Subject: [PATCH 2/5] checkSettingSecurity: use script api type check for both cases note: this means async mainmenu env no longer get the privilege --- src/script/lua_api/l_settings.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/script/lua_api/l_settings.cpp b/src/script/lua_api/l_settings.cpp index 326e969ad7373..b67b5d44bfbfd 100644 --- a/src/script/lua_api/l_settings.cpp +++ b/src/script/lua_api/l_settings.cpp @@ -31,8 +31,8 @@ static inline int checkSettingSecurity(lua_State* L, const std::string &name) { #if CHECK_CLIENT_BUILD() // Main menu and pause menu are allowed everything - if (ModApiBase::getGuiEngine(L) || - ModApiBase::getScriptApiBase(L)->getType() == ScriptingType::PauseMenu) + auto context = ModApiBase::getScriptApiBase(L)->getType(); + if (context == ScriptingType::MainMenu || context == ScriptingType::PauseMenu) return 0; #endif From d20fb96bb0d2f4eaceba10e5884a37cd9e912113 Mon Sep 17 00:00:00 2001 From: grorp Date: Thu, 16 Jan 2025 16:41:07 -0500 Subject: [PATCH 3/5] Refactor: split up LocalFormspecHandler --- src/client/game_formspec.cpp | 100 ++++++++++++++++++----------------- 1 file changed, 52 insertions(+), 48 deletions(-) diff --git a/src/client/game_formspec.cpp b/src/client/game_formspec.cpp index df3592364bc86..40c004289d053 100644 --- a/src/client/game_formspec.cpp +++ b/src/client/game_formspec.cpp @@ -9,6 +9,7 @@ #include "renderingengine.h" #include "client.h" #include "scripting_client.h" +#include "cpp_api/s_client_common.h" #include "clientmap.h" #include "gui/guiFormSpecMenu.h" #include "gui/mainmenumanager.h" @@ -70,73 +71,76 @@ struct TextDestPlayerInventory : public TextDest Client *m_client; }; -struct LocalFormspecHandler : public TextDest +struct LocalScriptingFormspecHandler : public TextDest { - LocalFormspecHandler(const std::string &formname) + LocalScriptingFormspecHandler(const std::string &formname, ScriptApiClientCommon *script) { m_formname = formname; + m_script = script; } - LocalFormspecHandler(const std::string &formname, Client *client, PauseMenuScripting *pause_script): - m_client(client), m_pause_script(pause_script) + void gotText(const StringMap &fields) { - m_formname = formname; + m_script->on_formspec_input(m_formname, fields); + } + + ScriptApiClientCommon *m_script = nullptr; +}; + +struct HardcodedPauseFormspecHandler : public TextDest +{ + HardcodedPauseFormspecHandler() + { + m_formname = "MT_PAUSE_MENU"; } void gotText(const StringMap &fields) { - if (m_formname == "MT_PAUSE_MENU") { - if (fields.find("btn_settings") != fields.end()) { - g_gamecallback->openSettings(); - return; - } + if (fields.find("btn_settings") != fields.end()) { + g_gamecallback->openSettings(); + return; + } - if (fields.find("btn_sound") != fields.end()) { - g_gamecallback->changeVolume(); - return; - } + if (fields.find("btn_sound") != fields.end()) { + g_gamecallback->changeVolume(); + return; + } - if (fields.find("btn_exit_menu") != fields.end()) { - g_gamecallback->disconnect(); - return; - } + if (fields.find("btn_exit_menu") != fields.end()) { + g_gamecallback->disconnect(); + return; + } - if (fields.find("btn_exit_os") != fields.end()) { - g_gamecallback->exitToOS(); + if (fields.find("btn_exit_os") != fields.end()) { + g_gamecallback->exitToOS(); #ifndef __ANDROID__ - RenderingEngine::get_raw_device()->closeDevice(); + RenderingEngine::get_raw_device()->closeDevice(); #endif - return; - } - - if (fields.find("btn_change_password") != fields.end()) { - g_gamecallback->changePassword(); - return; - } - return; } - if (m_formname == "MT_DEATH_SCREEN") { - assert(m_client != nullptr); - - if (fields.find("quit") != fields.end()) - m_client->sendRespawnLegacy(); - + if (fields.find("btn_change_password") != fields.end()) { + g_gamecallback->changePassword(); return; } + } +}; - if (m_pause_script && - m_pause_script->on_formspec_input(m_formname, fields)) - return; +struct LegacyDeathFormspecHandler : public TextDest +{ + LegacyDeathFormspecHandler(Client *client) + { + m_formname = "MT_DEATH_SCREEN"; + m_client = client; + } - if (m_client && m_client->modsLoaded() && - m_client->getScript()->on_formspec_input(m_formname, fields)) - return; + void gotText(const StringMap &fields) + { + if (fields.find("quit") != fields.end()) + m_client->sendRespawnLegacy(); } Client *m_client = nullptr; - PauseMenuScripting *m_pause_script = nullptr; }; /* Form update callback */ @@ -251,8 +255,8 @@ void GameFormSpec::showCSMFormSpec(const std::string &formspec, const std::strin return; FormspecFormSource *fs_src = new FormspecFormSource(formspec); - LocalFormspecHandler *txt_dst = - new LocalFormspecHandler(formname, m_client, nullptr); + LocalScriptingFormspecHandler *txt_dst = + new LocalScriptingFormspecHandler(formname, m_client->getScript()); m_formname = formname; GUIFormSpecMenu::create(m_formspec, m_client, m_rendering_engine->get_gui_env(), @@ -270,8 +274,8 @@ void GameFormSpec::showPauseMenuFormSpec(const std::string &formspec, const std: return; FormspecFormSource *fs_src = new FormspecFormSource(formspec); - LocalFormspecHandler *txt_dst = - new LocalFormspecHandler(formname, nullptr, m_pause_script.get()); + LocalScriptingFormspecHandler *txt_dst = + new LocalScriptingFormspecHandler(formname, m_pause_script.get()); m_formname = formname; GUIFormSpecMenu::create(m_formspec, m_client, m_rendering_engine->get_gui_env(), @@ -437,7 +441,7 @@ void GameFormSpec::showPauseMenu() /* Note: FormspecFormSource and LocalFormspecHandler * * are deleted by guiFormSpecMenu */ FormspecFormSource *fs_src = new FormspecFormSource(os.str()); - LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU"); + HardcodedPauseFormspecHandler *txt_dst = new HardcodedPauseFormspecHandler(); GUIFormSpecMenu::create(m_formspec, m_client, m_rendering_engine->get_gui_env(), &m_input->joystick, fs_src, txt_dst, m_client->getFormspecPrepend(), @@ -461,7 +465,7 @@ void GameFormSpec::showDeathFormspecLegacy() /* Note: FormspecFormSource and LocalFormspecHandler * * are deleted by guiFormSpecMenu */ FormspecFormSource *fs_src = new FormspecFormSource(formspec_str); - LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", m_client, nullptr); + LegacyDeathFormspecHandler *txt_dst = new LegacyDeathFormspecHandler(m_client); GUIFormSpecMenu::create(m_formspec, m_client, m_rendering_engine->get_gui_env(), &m_input->joystick, fs_src, txt_dst, m_client->getFormspecPrepend(), From 5d9c67e720dca127152d0e56403e837869ee6fa0 Mon Sep 17 00:00:00 2001 From: grorp Date: Thu, 16 Jan 2025 16:51:05 -0500 Subject: [PATCH 4/5] Two header comments --- src/client/game_formspec.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/client/game_formspec.h b/src/client/game_formspec.h index 5dd837803a4a7..b380564637395 100644 --- a/src/client/game_formspec.h +++ b/src/client/game_formspec.h @@ -30,10 +30,13 @@ struct GameFormSpec void showFormSpec(const std::string &formspec, const std::string &formname); void showCSMFormSpec(const std::string &formspec, const std::string &formname); + // Used by the Lua pause menu environment to show formspecs. + // Currently only used for the in-game settings menu. void showPauseMenuFormSpec(const std::string &formspec, const std::string &formname); void showNodeFormspec(const std::string &formspec, const v3s16 &nodepos); void showPlayerInventory(); void showDeathFormspecLegacy(); + // Shows the hardcoded "main" pause menu. void showPauseMenu(); void update(); From e56df751e80c744540a4e33dc57d56e30213c567 Mon Sep 17 00:00:00 2001 From: grorp Date: Thu, 16 Jan 2025 17:01:37 -0500 Subject: [PATCH 5/5] Desour review: close button label --- builtin/common/settings/dlg_settings.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/builtin/common/settings/dlg_settings.lua b/builtin/common/settings/dlg_settings.lua index bf63bd29e3e83..9910270476ce0 100644 --- a/builtin/common/settings/dlg_settings.lua +++ b/builtin/common/settings/dlg_settings.lua @@ -513,7 +513,8 @@ local function get_formspec(dialogdata) "box[0,0;", tostring(tabsize.width), ",", tostring(tabsize.height), ";#0000008C]", ("button[0,%f;%f,0.8;back;%s]"):format( - tabsize.height + 0.2, back_w, fgettext("Back")), + tabsize.height + 0.2, back_w, + fgettext(INIT == "pause_menu" and "Exit" or "Back")), ("box[%f,%f;%f,0.8;#0000008C]"):format( back_w + 0.2, tabsize.height + 0.2, checkbox_w),