From 21113ad4105dd3fb181b3d0638b907af94a352ab Mon Sep 17 00:00:00 2001 From: Wuzzy Date: Fri, 1 Oct 2021 14:21:24 +0000 Subject: Split liquid_viscosity to liquid_viscosity and move_resistance (#10810) --- src/script/lua_api/l_localplayer.cpp | 7 ++++--- src/script/lua_api/l_localplayer.h | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) (limited to 'src/script/lua_api') diff --git a/src/script/lua_api/l_localplayer.cpp b/src/script/lua_api/l_localplayer.cpp index 9f3569ecc..bdbe98cb0 100644 --- a/src/script/lua_api/l_localplayer.cpp +++ b/src/script/lua_api/l_localplayer.cpp @@ -128,11 +128,11 @@ int LuaLocalPlayer::l_is_in_liquid_stable(lua_State *L) return 1; } -int LuaLocalPlayer::l_get_liquid_viscosity(lua_State *L) +int LuaLocalPlayer::l_get_move_resistance(lua_State *L) { LocalPlayer *player = getobject(L, 1); - lua_pushinteger(L, player->liquid_viscosity); + lua_pushinteger(L, player->move_resistance); return 1; } @@ -466,7 +466,6 @@ const luaL_Reg LuaLocalPlayer::methods[] = { luamethod(LuaLocalPlayer, is_touching_ground), luamethod(LuaLocalPlayer, is_in_liquid), luamethod(LuaLocalPlayer, is_in_liquid_stable), - luamethod(LuaLocalPlayer, get_liquid_viscosity), luamethod(LuaLocalPlayer, is_climbing), luamethod(LuaLocalPlayer, swimming_vertical), luamethod(LuaLocalPlayer, get_physics_override), @@ -488,5 +487,7 @@ const luaL_Reg LuaLocalPlayer::methods[] = { luamethod(LuaLocalPlayer, hud_change), luamethod(LuaLocalPlayer, hud_get), + luamethod(LuaLocalPlayer, get_move_resistance), + {0, 0} }; diff --git a/src/script/lua_api/l_localplayer.h b/src/script/lua_api/l_localplayer.h index 4413f2bdb..041545a49 100644 --- a/src/script/lua_api/l_localplayer.h +++ b/src/script/lua_api/l_localplayer.h @@ -51,7 +51,6 @@ private: static int l_is_touching_ground(lua_State *L); static int l_is_in_liquid(lua_State *L); static int l_is_in_liquid_stable(lua_State *L); - static int l_get_liquid_viscosity(lua_State *L); static int l_is_climbing(lua_State *L); static int l_swimming_vertical(lua_State *L); @@ -96,6 +95,8 @@ private: // hud_get(self, id) static int l_hud_get(lua_State *L); + static int l_get_move_resistance(lua_State *L); + LocalPlayer *m_localplayer = nullptr; public: -- cgit v1.2.3 From 4fca601e0cf74ce642e4e49ca7d4fe3e59915846 Mon Sep 17 00:00:00 2001 From: Wuzzy Date: Tue, 5 Oct 2021 12:35:55 +0000 Subject: Add get_server_max_lag() (#11671) --- doc/lua_api.txt | 2 ++ src/script/lua_api/l_server.cpp | 12 ++++++++++++ src/script/lua_api/l_server.h | 3 +++ 3 files changed, 17 insertions(+) (limited to 'src/script/lua_api') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 9efe1afe7..e6cabb68e 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -5647,6 +5647,8 @@ Server a player joined. * This function may be overwritten by mods to customize the status message. * `minetest.get_server_uptime()`: returns the server uptime in seconds +* `minetest.get_server_max_lag()`: returns the current maximum lag + of the server in seconds or nil if server is not fully loaded yet * `minetest.remove_player(name)`: remove player from database (if they are not connected). * As auth data is not removed, minetest.player_exists will continue to diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index 473faaa14..6438fa6fd 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -57,6 +57,17 @@ int ModApiServer::l_get_server_uptime(lua_State *L) return 1; } +// get_server_max_lag() +int ModApiServer::l_get_server_max_lag(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ServerEnvironment *s_env = dynamic_cast(getEnv(L)); + if (!s_env) + lua_pushnil(L); + else + lua_pushnumber(L, s_env->getMaxLagEstimate()); + return 1; +} // print(text) int ModApiServer::l_print(lua_State *L) @@ -512,6 +523,7 @@ void ModApiServer::Initialize(lua_State *L, int top) API_FCT(request_shutdown); API_FCT(get_server_status); API_FCT(get_server_uptime); + API_FCT(get_server_max_lag); API_FCT(get_worldpath); API_FCT(is_singleplayer); diff --git a/src/script/lua_api/l_server.h b/src/script/lua_api/l_server.h index c688e494b..a6f709787 100644 --- a/src/script/lua_api/l_server.h +++ b/src/script/lua_api/l_server.h @@ -33,6 +33,9 @@ private: // get_server_uptime() static int l_get_server_uptime(lua_State *L); + // get_server_max_lag() + static int l_get_server_max_lag(lua_State *L); + // get_worldpath() static int l_get_worldpath(lua_State *L); -- cgit v1.2.3 From 5aa95fef102db02aa4b2f4a5c2b59ac985a54cef Mon Sep 17 00:00:00 2001 From: Jude Melton-Houghton Date: Tue, 5 Oct 2021 08:38:33 -0400 Subject: Make MetaDataRef:get return nil instead of nothing (#11666) --- src/script/lua_api/l_metadata.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/script/lua_api') diff --git a/src/script/lua_api/l_metadata.cpp b/src/script/lua_api/l_metadata.cpp index 21002e6a7..d00cb4daa 100644 --- a/src/script/lua_api/l_metadata.cpp +++ b/src/script/lua_api/l_metadata.cpp @@ -82,9 +82,10 @@ int MetaDataRef::l_get(lua_State *L) std::string str; if (meta->getStringToRef(name, str)) { lua_pushlstring(L, str.c_str(), str.size()); - return 1; + } else { + lua_pushnil(L); } - return 0; + return 1; } // get_string(self, name) -- cgit v1.2.3 From bc71622d2121347ad6dbbe3c174ad485fe1a8f8c Mon Sep 17 00:00:00 2001 From: Wuzzy Date: Tue, 5 Oct 2021 19:53:47 +0000 Subject: Fix crash when calling remove/kick/ban_player on start (#11672) --- src/script/lua_api/l_server.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'src/script/lua_api') diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index 6438fa6fd..476f74c9c 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -293,8 +293,10 @@ int ModApiServer::l_ban_player(lua_State *L) { NO_MAP_LOCK_REQUIRED; - Server *server = getServer(L); + if (!getEnv(L)) + throw LuaError("Can't ban player before server has started up"); + Server *server = getServer(L); const char *name = luaL_checkstring(L, 1); RemotePlayer *player = server->getEnv().getPlayer(name); if (!player) { @@ -312,6 +314,10 @@ int ModApiServer::l_ban_player(lua_State *L) int ModApiServer::l_kick_player(lua_State *L) { NO_MAP_LOCK_REQUIRED; + + if (!getEnv(L)) + throw LuaError("Can't kick player before server has started up"); + const char *name = luaL_checkstring(L, 1); std::string message("Kicked"); if (lua_isstring(L, 2)) @@ -334,7 +340,8 @@ int ModApiServer::l_remove_player(lua_State *L) NO_MAP_LOCK_REQUIRED; std::string name = luaL_checkstring(L, 1); ServerEnvironment *s_env = dynamic_cast(getEnv(L)); - assert(s_env); + if (!s_env) + throw LuaError("Can't remove player before server has started up"); RemotePlayer *player = s_env->getPlayer(name.c_str()); if (!player) -- cgit v1.2.3 From 9fab5d594cab4c0a027f0aecf356382f3a37c1de Mon Sep 17 00:00:00 2001 From: emixa-d <85313564+emixa-d@users.noreply.github.com> Date: Wed, 6 Oct 2021 22:19:41 +0000 Subject: Add "MINETEST_MOD_PATH" environment variable (#11515) This adds an environment variable MINETEST_MOD_PATH. When it exists, Minetest will look there for mods in addition to ~/.minetest/mods/. --- builtin/mainmenu/pkgmgr.lua | 8 +++----- doc/menu_lua_api.txt | 8 +++++++- doc/minetest.6 | 3 +++ src/content/subgames.cpp | 14 ++++++++++++++ src/content/subgames.h | 2 ++ src/script/lua_api/l_mainmenu.cpp | 17 +++++++++++++++++ src/script/lua_api/l_mainmenu.h | 2 ++ src/unittest/CMakeLists.txt | 1 + src/unittest/test_config.h.in | 1 + src/unittest/test_mod/test_mod/init.lua | 1 + src/unittest/test_mod/test_mod/mod.conf | 2 ++ src/unittest/test_servermodmanager.cpp | 21 +++++++++++++++++++++ 12 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 src/unittest/test_mod/test_mod/init.lua create mode 100644 src/unittest/test_mod/test_mod/mod.conf (limited to 'src/script/lua_api') diff --git a/builtin/mainmenu/pkgmgr.lua b/builtin/mainmenu/pkgmgr.lua index 787936e31..76d4a4123 100644 --- a/builtin/mainmenu/pkgmgr.lua +++ b/builtin/mainmenu/pkgmgr.lua @@ -682,11 +682,9 @@ function pkgmgr.preparemodlist(data) local game_mods = {} --read global mods - local modpath = core.get_modpath() - - if modpath ~= nil and - modpath ~= "" then - get_mods(modpath,global_mods) + local modpaths = core.get_modpaths() + for _, modpath in ipairs(modpaths) do + get_mods(modpath, global_mods) end for i=1,#global_mods,1 do diff --git a/doc/menu_lua_api.txt b/doc/menu_lua_api.txt index f4dfff261..b4b6eaba2 100644 --- a/doc/menu_lua_api.txt +++ b/doc/menu_lua_api.txt @@ -219,7 +219,13 @@ Package - content which is downloadable from the content db, may or may not be i * returns path to global user data, the directory that contains user-provided mods, worlds, games, and texture packs. * core.get_modpath() (possible in async calls) - * returns path to global modpath + * returns path to global modpath, where mods can be installed +* core.get_modpaths() (possible in async calls) + * returns list of paths to global modpaths, where mods have been installed + + The difference with "core.get_modpath" is that no mods should be installed in these + directories by Minetest -- they might be read-only. + * core.get_clientmodpath() (possible in async calls) * returns path to global client-side modpath * core.get_gamepath() (possible in async calls) diff --git a/doc/minetest.6 b/doc/minetest.6 index bac70fe1a..42ed1a45f 100644 --- a/doc/minetest.6 +++ b/doc/minetest.6 @@ -119,6 +119,9 @@ Display an interactive terminal over ncurses during execution. .TP .B MINETEST_SUBGAME_PATH Colon delimited list of directories to search for games. +.TP +.B MINETEST_MOD_PATH +Colon delimited list of directories to search for mods. .SH BUGS Please report all bugs at https://github.com/minetest/minetest/issues. diff --git a/src/content/subgames.cpp b/src/content/subgames.cpp index e9dc609b0..30447c838 100644 --- a/src/content/subgames.cpp +++ b/src/content/subgames.cpp @@ -113,6 +113,10 @@ SubgameSpec findSubgame(const std::string &id) if (user != share || user_game) mods_paths.insert(user + DIR_DELIM + "mods"); + for (const std::string &mod_path : getEnvModPaths()) { + mods_paths.insert(mod_path); + } + // Get meta std::string conf_path = game_path + DIR_DELIM + "game.conf"; Settings conf; @@ -384,3 +388,13 @@ void loadGameConfAndInitWorld(const std::string &path, const std::string &name, if (new_game_settings) delete game_settings; } + +std::vector getEnvModPaths() +{ + const char *c_mod_path = getenv("MINETEST_MOD_PATH"); + std::vector paths; + Strfnd search_paths(c_mod_path ? c_mod_path : ""); + while (!search_paths.at_end()) + paths.push_back(search_paths.next(PATH_DELIM)); + return paths; +} diff --git a/src/content/subgames.h b/src/content/subgames.h index 60392639b..4a50803e8 100644 --- a/src/content/subgames.h +++ b/src/content/subgames.h @@ -58,6 +58,8 @@ SubgameSpec findWorldSubgame(const std::string &world_path); std::set getAvailableGameIds(); std::vector getAvailableGames(); +// Get the list of paths to mods in the environment variable $MINETEST_MOD_PATH +std::vector getEnvModPaths(); bool getWorldExists(const std::string &world_path); //! Try to get the displayed name of a world diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index 6e9a5c34f..57fddc0be 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -502,6 +502,21 @@ int ModApiMainMenu::l_get_modpath(lua_State *L) return 1; } +/******************************************************************************/ +int ModApiMainMenu::l_get_modpaths(lua_State *L) +{ + int index = 1; + lua_newtable(L); + ModApiMainMenu::l_get_modpath(L); + lua_rawseti(L, -2, index); + for (const std::string &component : getEnvModPaths()) { + index++; + lua_pushstring(L, component.c_str()); + lua_rawseti(L, -2, index); + } + return 1; +} + /******************************************************************************/ int ModApiMainMenu::l_get_clientmodpath(lua_State *L) { @@ -856,6 +871,7 @@ void ModApiMainMenu::Initialize(lua_State *L, int top) API_FCT(get_mapgen_names); API_FCT(get_user_path); API_FCT(get_modpath); + API_FCT(get_modpaths); API_FCT(get_clientmodpath); API_FCT(get_gamepath); API_FCT(get_texturepath); @@ -889,6 +905,7 @@ void ModApiMainMenu::InitializeAsync(lua_State *L, int top) API_FCT(get_mapgen_names); API_FCT(get_user_path); API_FCT(get_modpath); + API_FCT(get_modpaths); API_FCT(get_clientmodpath); API_FCT(get_gamepath); API_FCT(get_texturepath); diff --git a/src/script/lua_api/l_mainmenu.h b/src/script/lua_api/l_mainmenu.h index ec2d20da2..781185425 100644 --- a/src/script/lua_api/l_mainmenu.h +++ b/src/script/lua_api/l_mainmenu.h @@ -112,6 +112,8 @@ private: static int l_get_modpath(lua_State *L); + static int l_get_modpaths(lua_State *L); + static int l_get_clientmodpath(lua_State *L); static int l_get_gamepath(lua_State *L); diff --git a/src/unittest/CMakeLists.txt b/src/unittest/CMakeLists.txt index 5703b8906..52f870901 100644 --- a/src/unittest/CMakeLists.txt +++ b/src/unittest/CMakeLists.txt @@ -44,6 +44,7 @@ set (UNITTEST_CLIENT_SRCS set (TEST_WORLDDIR ${CMAKE_CURRENT_SOURCE_DIR}/test_world) set (TEST_SUBGAME_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../games/devtest) +set (TEST_MOD_PATH ${CMAKE_CURRENT_SOURCE_DIR}/test_mod) configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/test_config.h.in" diff --git a/src/unittest/test_config.h.in b/src/unittest/test_config.h.in index 36850b00d..50d2398e4 100644 --- a/src/unittest/test_config.h.in +++ b/src/unittest/test_config.h.in @@ -4,3 +4,4 @@ #define TEST_WORLDDIR "@TEST_WORLDDIR@" #define TEST_SUBGAME_PATH "@TEST_SUBGAME_PATH@" +#define TEST_MOD_PATH "@TEST_MOD_PATH@" diff --git a/src/unittest/test_mod/test_mod/init.lua b/src/unittest/test_mod/test_mod/init.lua new file mode 100644 index 000000000..724a863f5 --- /dev/null +++ b/src/unittest/test_mod/test_mod/init.lua @@ -0,0 +1 @@ +-- deliberately empty diff --git a/src/unittest/test_mod/test_mod/mod.conf b/src/unittest/test_mod/test_mod/mod.conf new file mode 100644 index 000000000..56c64b2d8 --- /dev/null +++ b/src/unittest/test_mod/test_mod/mod.conf @@ -0,0 +1,2 @@ +name = test_mod +description = A mod doing nothing, to test if MINETEST_MOD_PATH is recognised diff --git a/src/unittest/test_servermodmanager.cpp b/src/unittest/test_servermodmanager.cpp index e3edb0c32..4c473d8b5 100644 --- a/src/unittest/test_servermodmanager.cpp +++ b/src/unittest/test_servermodmanager.cpp @@ -48,14 +48,20 @@ static TestServerModManager g_test_instance; void TestServerModManager::runTests(IGameDef *gamedef) { const char *saved_env_mt_subgame_path = getenv("MINETEST_SUBGAME_PATH"); + const char *saved_env_mt_mod_path = getenv("MINETEST_MOD_PATH"); #ifdef WIN32 { std::string subgame_path("MINETEST_SUBGAME_PATH="); subgame_path.append(TEST_SUBGAME_PATH); _putenv(subgame_path.c_str()); + + std::string mod_path("MINETEST_MOD_PATH="); + mod_path.append(TEST_MOD_PATH); + _putenv(mod_path.c_str()); } #else setenv("MINETEST_SUBGAME_PATH", TEST_SUBGAME_PATH, 1); + setenv("MINETEST_MOD_PATH", TEST_MOD_PATH, 1); #endif TEST(testCreation); @@ -75,12 +81,21 @@ void TestServerModManager::runTests(IGameDef *gamedef) if (saved_env_mt_subgame_path) subgame_path.append(saved_env_mt_subgame_path); _putenv(subgame_path.c_str()); + + std::string mod_path("MINETEST_MOD_PATH="); + if (saved_env_mt_mod_path) + mod_path.append(saved_env_mt_mod_path); + _putenv(mod_path.c_str()); } #else if (saved_env_mt_subgame_path) setenv("MINETEST_SUBGAME_PATH", saved_env_mt_subgame_path, 1); else unsetenv("MINETEST_SUBGAME_PATH"); + if (saved_env_mt_mod_path) + setenv("MINETEST_MOD_PATH", saved_env_mt_mod_path, 1); + else + unsetenv("MINETEST_MOD_PATH"); #endif } @@ -89,6 +104,7 @@ void TestServerModManager::testCreation() std::string path = std::string(TEST_WORLDDIR) + DIR_DELIM + "world.mt"; Settings world_config; world_config.set("gameid", "devtest"); + world_config.set("load_mod_test_mod", "true"); UASSERTEQ(bool, world_config.updateConfigFile(path.c_str()), true); ServerModManager sm(TEST_WORLDDIR); } @@ -119,16 +135,21 @@ void TestServerModManager::testGetMods() UASSERTEQ(bool, mods.empty(), false); // Ensure we found basenodes mod (part of devtest) + // and test_mod (for testing MINETEST_MOD_PATH). bool default_found = false; + bool test_mod_found = false; for (const auto &m : mods) { if (m.name == "basenodes") default_found = true; + if (m.name == "test_mod") + test_mod_found = true; // Verify if paths are not empty UASSERTEQ(bool, m.path.empty(), false); } UASSERTEQ(bool, default_found, true); + UASSERTEQ(bool, test_mod_found, true); } void TestServerModManager::testGetModspec() -- cgit v1.2.3 From 2d5b7b5fb48d182fbab8e4ad69e9a552a3c07c6e Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 19 Sep 2021 16:55:35 +0200 Subject: Make fs::extractZipFile thread-safe --- src/filesys.cpp | 102 +++++++++++++++++++++----------------- src/filesys.h | 6 ++- src/script/lua_api/l_mainmenu.cpp | 8 +-- 3 files changed, 65 insertions(+), 51 deletions(-) (limited to 'src/script/lua_api') diff --git a/src/filesys.cpp b/src/filesys.cpp index a07370c0e..0972acbf9 100644 --- a/src/filesys.cpp +++ b/src/filesys.cpp @@ -28,11 +28,18 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "log.h" #include "config.h" #include "porting.h" +#ifndef SERVER +#include "irr_ptr.h" +#endif namespace fs { -#ifdef _WIN32 // WINDOWS +#ifdef _WIN32 + +/*********** + * Windows * + ***********/ #define _WIN32_WINNT 0x0501 #include @@ -201,7 +208,11 @@ std::string CreateTempFile() return path; } -#else // POSIX +#else + +/********* + * POSIX * + *********/ #include #include @@ -392,6 +403,10 @@ std::string CreateTempFile() #endif +/**************************** + * portable implementations * + ****************************/ + void GetRecursiveDirs(std::vector &dirs, const std::string &dir) { static const std::set chars_to_ignore = { '_', '.' }; @@ -753,69 +768,66 @@ bool safeWriteToFile(const std::string &path, const std::string &content) return true; } +#ifndef SERVER bool extractZipFile(io::IFileSystem *fs, const char *filename, const std::string &destination) { - if (!fs->addFileArchive(filename, false, false, io::EFAT_ZIP)) { + // Be careful here not to touch the global file hierarchy in Irrlicht + // since this function needs to be thread-safe! + + io::IArchiveLoader *zip_loader = nullptr; + for (u32 i = 0; i < fs->getArchiveLoaderCount(); i++) { + if (fs->getArchiveLoader(i)->isALoadableFileFormat(io::EFAT_ZIP)) { + zip_loader = fs->getArchiveLoader(i); + break; + } + } + if (!zip_loader) { + warningstream << "fs::extractZipFile(): Irrlicht said it doesn't support ZIPs." << std::endl; return false; } - sanity_check(fs->getFileArchiveCount() > 0); - - /**********************************************************************/ - /* WARNING this is not threadsafe!! */ - /**********************************************************************/ - io::IFileArchive* opened_zip = fs->getFileArchive(fs->getFileArchiveCount() - 1); - + irr_ptr opened_zip(zip_loader->createArchive(filename, false, false)); const io::IFileList* files_in_zip = opened_zip->getFileList(); - unsigned int number_of_files = files_in_zip->getFileCount(); - - for (unsigned int i=0; i < number_of_files; i++) { - std::string fullpath = destination; - fullpath += DIR_DELIM; + for (u32 i = 0; i < files_in_zip->getFileCount(); i++) { + std::string fullpath = destination + DIR_DELIM; fullpath += files_in_zip->getFullFileName(i).c_str(); std::string fullpath_dir = fs::RemoveLastPathComponent(fullpath); - if (!files_in_zip->isDirectory(i)) { - if (!fs::PathExists(fullpath_dir) && !fs::CreateAllDirs(fullpath_dir)) { - fs->removeFileArchive(fs->getFileArchiveCount()-1); - return false; - } - - io::IReadFile* toread = opened_zip->createAndOpenFile(i); + if (files_in_zip->isDirectory(i)) + continue; // ignore, we create dirs as necessary - FILE *targetfile = fopen(fullpath.c_str(),"wb"); + if (!fs::PathExists(fullpath_dir) && !fs::CreateAllDirs(fullpath_dir)) + return false; - if (targetfile == NULL) { - fs->removeFileArchive(fs->getFileArchiveCount()-1); - return false; - } + irr_ptr toread(opened_zip->createAndOpenFile(i)); - char read_buffer[1024]; - long total_read = 0; + std::ofstream os(fullpath.c_str(), std::ios::binary); + if (!os.good()) + return false; - while (total_read < toread->getSize()) { + char buffer[4096]; + long total_read = 0; - unsigned int bytes_read = - toread->read(read_buffer,sizeof(read_buffer)); - if ((bytes_read == 0 ) || - (fwrite(read_buffer, 1, bytes_read, targetfile) != bytes_read)) - { - fclose(targetfile); - fs->removeFileArchive(fs->getFileArchiveCount() - 1); - return false; - } - total_read += bytes_read; + while (total_read < toread->getSize()) { + long bytes_read = toread->read(buffer, sizeof(buffer)); + bool error = true; + if (bytes_read != 0) { + os.write(buffer, bytes_read); + error = os.fail(); } - - fclose(targetfile); + if (error) { + os.close(); + remove(fullpath.c_str()); + return false; + } + total_read += bytes_read; } - } - fs->removeFileArchive(fs->getFileArchiveCount() - 1); return true; } +#endif bool ReadFile(const std::string &path, std::string &out) { @@ -829,7 +841,7 @@ bool ReadFile(const std::string &path, std::string &out) is.seekg(0); is.read(&out[0], size); - return true; + return !is.fail(); } bool Rename(const std::string &from, const std::string &to) diff --git a/src/filesys.h b/src/filesys.h index f72cb0ba2..233e56bba 100644 --- a/src/filesys.h +++ b/src/filesys.h @@ -24,12 +24,12 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include "exceptions.h" -#ifdef _WIN32 // WINDOWS +#ifdef _WIN32 #define DIR_DELIM "\\" #define DIR_DELIM_CHAR '\\' #define FILESYS_CASE_INSENSITIVE true #define PATH_DELIM ";" -#else // POSIX +#else #define DIR_DELIM "/" #define DIR_DELIM_CHAR '/' #define FILESYS_CASE_INSENSITIVE false @@ -133,7 +133,9 @@ const char *GetFilenameFromPath(const char *path); bool safeWriteToFile(const std::string &path, const std::string &content); +#ifndef SERVER bool extractZipFile(irr::io::IFileSystem *fs, const char *filename, const std::string &destination); +#endif bool ReadFile(const std::string &path, std::string &out); diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index 57fddc0be..4cfbaec71 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -644,9 +644,9 @@ int ModApiMainMenu::l_extract_zip(lua_State *L) std::string absolute_destination = fs::RemoveRelativePathComponents(destination); if (ModApiMainMenu::mayModifyPath(absolute_destination)) { - auto rendering_engine = getGuiEngine(L)->m_rendering_engine; - fs::CreateAllDirs(absolute_destination); - lua_pushboolean(L, fs::extractZipFile(rendering_engine->get_filesystem(), zipfile, destination)); + auto fs = RenderingEngine::get_raw_device()->getFileSystem(); + bool ok = fs::extractZipFile(fs, zipfile, destination); + lua_pushboolean(L, ok); return 1; } @@ -916,7 +916,7 @@ void ModApiMainMenu::InitializeAsync(lua_State *L, int top) API_FCT(delete_dir); API_FCT(copy_dir); API_FCT(is_dir); - //API_FCT(extract_zip); //TODO remove dependency to GuiEngine + API_FCT(extract_zip); API_FCT(may_modify_path); API_FCT(download_file); API_FCT(get_min_supp_proto); -- cgit v1.2.3 From 2b5075f0e2a8223cdb07f000b7e8f874416ed3a8 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 19 Sep 2021 17:55:01 +0200 Subject: Move archive extraction in content store to async job --- builtin/common/misc_helpers.lua | 2 +- builtin/mainmenu/common.lua | 12 +----- builtin/mainmenu/dlg_contentstore.lua | 45 ++++++++++++++-------- builtin/mainmenu/pkgmgr.lua | 71 ----------------------------------- doc/menu_lua_api.txt | 4 +- src/script/lua_api/l_mainmenu.cpp | 12 ++++-- 6 files changed, 44 insertions(+), 102 deletions(-) (limited to 'src/script/lua_api') diff --git a/builtin/common/misc_helpers.lua b/builtin/common/misc_helpers.lua index c2452fe00..f5f89acd7 100644 --- a/builtin/common/misc_helpers.lua +++ b/builtin/common/misc_helpers.lua @@ -532,7 +532,7 @@ if INIT == "mainmenu" then end end -if INIT == "client" or INIT == "mainmenu" then +if core.gettext then -- for client and mainmenu function fgettext_ne(text, ...) text = core.gettext(text) local arg = {n=select('#', ...), ...} diff --git a/builtin/mainmenu/common.lua b/builtin/mainmenu/common.lua index 6db351048..b36c9596a 100644 --- a/builtin/mainmenu/common.lua +++ b/builtin/mainmenu/common.lua @@ -119,17 +119,9 @@ function render_serverlist_row(spec) return table.concat(details, ",") end - --------------------------------------------------------------------------------- -os.tempfolder = function() - local temp = core.get_temp_path() - return temp .. DIR_DELIM .. "MT_" .. math.random(0, 10000) -end - +--------------------------------------------------------------------------------- os.tmpname = function() - local path = os.tempfolder() - io.open(path, "w"):close() - return path + error('do not use') -- instead use core.get_temp_path() end -------------------------------------------------------------------------------- diff --git a/builtin/mainmenu/dlg_contentstore.lua b/builtin/mainmenu/dlg_contentstore.lua index a3c72aee4..58421ef75 100644 --- a/builtin/mainmenu/dlg_contentstore.lua +++ b/builtin/mainmenu/dlg_contentstore.lua @@ -72,34 +72,52 @@ local function get_download_url(package, reason) end -local function download_package(param) - if core.download_file(param.url, param.filename) then +local function download_and_extract(param) + local package = param.package + + local filename = core.get_temp_path(true) + if filename == "" or not core.download_file(param.url, filename) then + core.log("error", "Downloading " .. dump(param.url) .. " failed") return { - filename = param.filename, - successful = true, + msg = fgettext("Failed to download $1", package.name) } + end + + local tempfolder = core.get_temp_path() + if tempfolder ~= "" then + tempfolder = tempfolder .. DIR_DELIM .. "MT_" .. math.random(1, 1024000) + if not core.extract_zip(filename, tempfolder) then + tempfolder = nil + end else - core.log("error", "downloading " .. dump(param.url) .. " failed") + tempfolder = nil + end + os.remove(filename) + if not tempfolder then return { - successful = false, + msg = fgettext("Install: Unsupported file type or broken archive"), } end + + return { + path = tempfolder + } end local function start_install(package, reason) local params = { package = package, url = get_download_url(package, reason), - filename = os.tempfolder() .. "_MODNAME_" .. package.name .. ".zip", } number_downloading = number_downloading + 1 local function callback(result) - if result.successful then - local path, msg = pkgmgr.install(package.type, - result.filename, package.name, - package.path) + if result.msg then + gamedata.errormessage = result.msg + else + local path, msg = pkgmgr.install_dir(package.type, result.path, package.name, package.path) + core.delete_dir(result.path) if not path then gamedata.errormessage = msg else @@ -137,9 +155,6 @@ local function start_install(package, reason) conf:write() end end - os.remove(result.filename) - else - gamedata.errormessage = fgettext("Failed to download $1", package.name) end package.downloading = false @@ -159,7 +174,7 @@ local function start_install(package, reason) package.queued = false package.downloading = true - if not core.handle_async(download_package, params, callback) then + if not core.handle_async(download_and_extract, params, callback) then core.log("error", "ERROR: async event failed") gamedata.errormessage = fgettext("Failed to download $1", package.name) return diff --git a/builtin/mainmenu/pkgmgr.lua b/builtin/mainmenu/pkgmgr.lua index 76d4a4123..d07dc019c 100644 --- a/builtin/mainmenu/pkgmgr.lua +++ b/builtin/mainmenu/pkgmgr.lua @@ -181,21 +181,6 @@ function pkgmgr.get_texture_packs() end -------------------------------------------------------------------------------- -function pkgmgr.extract(modfile) - if modfile.type == "zip" then - local tempfolder = os.tempfolder() - - if tempfolder ~= nil and - tempfolder ~= "" then - core.create_dir(tempfolder) - if core.extract_zip(modfile.name,tempfolder) then - return tempfolder - end - end - end - return nil -end - function pkgmgr.get_folder_type(path) local testfile = io.open(path .. DIR_DELIM .. "init.lua","r") if testfile ~= nil then @@ -657,23 +642,6 @@ function pkgmgr.install_dir(type, path, basename, targetpath) return targetpath, nil end --------------------------------------------------------------------------------- -function pkgmgr.install(type, modfilename, basename, dest) - local archive_info = pkgmgr.identify_filetype(modfilename) - local path = pkgmgr.extract(archive_info) - - if path == nil then - return nil, - fgettext("Install: file: \"$1\"", archive_info.name) .. "\n" .. - fgettext("Install: Unsupported file type \"$1\" or broken archive", - archive_info.type) - end - - local targetpath, msg = pkgmgr.install_dir(type, path, basename, dest) - core.delete_dir(path) - return targetpath, msg -end - -------------------------------------------------------------------------------- function pkgmgr.preparemodlist(data) local retval = {} @@ -817,45 +785,6 @@ function pkgmgr.refresh_globals() pkgmgr.global_mods:set_sortmode("alphabetic") end --------------------------------------------------------------------------------- -function pkgmgr.identify_filetype(name) - - if name:sub(-3):lower() == "zip" then - return { - name = name, - type = "zip" - } - end - - if name:sub(-6):lower() == "tar.gz" or - name:sub(-3):lower() == "tgz"then - return { - name = name, - type = "tgz" - } - end - - if name:sub(-6):lower() == "tar.bz2" then - return { - name = name, - type = "tbz" - } - end - - if name:sub(-2):lower() == "7z" then - return { - name = name, - type = "7z" - } - end - - return { - name = name, - type = "ukn" - } -end - - -------------------------------------------------------------------------------- function pkgmgr.find_by_gameid(gameid) for i=1,#pkgmgr.games,1 do diff --git a/doc/menu_lua_api.txt b/doc/menu_lua_api.txt index b4b6eaba2..9bc0c46bd 100644 --- a/doc/menu_lua_api.txt +++ b/doc/menu_lua_api.txt @@ -85,7 +85,9 @@ core.get_video_drivers() 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 -core.get_temp_path() -> path of temp folder +core.get_temp_path([param]) (possible in async calls) +^ param=true: returns path to a temporary file +^ otherwise: returns path to the temporary folder HTTP Requests diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index 4cfbaec71..2a6a9c32d 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -563,7 +563,10 @@ int ModApiMainMenu::l_get_cache_path(lua_State *L) /******************************************************************************/ int ModApiMainMenu::l_get_temp_path(lua_State *L) { - lua_pushstring(L, fs::TempPath().c_str()); + if (lua_isnoneornil(L, 1) || !lua_toboolean(L, 1)) + lua_pushstring(L, fs::TempPath().c_str()); + else + lua_pushstring(L, fs::CreateTempFile().c_str()); return 1; } @@ -770,8 +773,9 @@ int ModApiMainMenu::l_get_video_drivers(lua_State *L) /******************************************************************************/ int ModApiMainMenu::l_gettext(lua_State *L) { - std::string text = strgettext(std::string(luaL_checkstring(L, 1))); - lua_pushstring(L, text.c_str()); + const char *srctext = luaL_checkstring(L, 1); + const char *text = *srctext ? gettext(srctext) : ""; + lua_pushstring(L, text); return 1; } @@ -921,5 +925,5 @@ void ModApiMainMenu::InitializeAsync(lua_State *L, int top) API_FCT(download_file); API_FCT(get_min_supp_proto); API_FCT(get_max_supp_proto); - //API_FCT(gettext); (gettext lib isn't threadsafe) + API_FCT(gettext); } -- cgit v1.2.3 From 6de8d77e17017cd5cc7b065d42566b6b1cd076cc Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 19 Sep 2021 18:16:53 +0200 Subject: Move instead of copy during content install if possible --- builtin/mainmenu/pkgmgr.lua | 8 ++------ src/filesys.cpp | 24 ++++++++++++++++++++++++ src/filesys.h | 4 ++++ src/script/lua_api/l_mainmenu.cpp | 30 ++++++++++++++---------------- 4 files changed, 44 insertions(+), 22 deletions(-) (limited to 'src/script/lua_api') diff --git a/builtin/mainmenu/pkgmgr.lua b/builtin/mainmenu/pkgmgr.lua index d07dc019c..e83a93c91 100644 --- a/builtin/mainmenu/pkgmgr.lua +++ b/builtin/mainmenu/pkgmgr.lua @@ -546,11 +546,10 @@ function pkgmgr.install_dir(type, path, basename, targetpath) local from = basefolder and basefolder.path or path if targetpath then core.delete_dir(targetpath) - core.create_dir(targetpath) else targetpath = core.get_texturepath() .. DIR_DELIM .. basename end - if not core.copy_dir(from, targetpath) then + if not core.copy_dir(from, targetpath, false) then return nil, fgettext("Failed to install $1 to $2", basename, targetpath) end @@ -571,7 +570,6 @@ function pkgmgr.install_dir(type, path, basename, targetpath) -- Get destination name for modpack if targetpath then core.delete_dir(targetpath) - core.create_dir(targetpath) else local clean_path = nil if basename ~= nil then @@ -595,7 +593,6 @@ function pkgmgr.install_dir(type, path, basename, targetpath) if targetpath then core.delete_dir(targetpath) - core.create_dir(targetpath) else local targetfolder = basename if targetfolder == nil then @@ -621,14 +618,13 @@ function pkgmgr.install_dir(type, path, basename, targetpath) if targetpath then core.delete_dir(targetpath) - core.create_dir(targetpath) else targetpath = core.get_gamepath() .. DIR_DELIM .. basename end end -- Copy it - if not core.copy_dir(basefolder.path, targetpath) then + if not core.copy_dir(basefolder.path, targetpath, false) then return nil, fgettext("Failed to install $1 to $2", basename, targetpath) end diff --git a/src/filesys.cpp b/src/filesys.cpp index 0972acbf9..44f1c88b3 100644 --- a/src/filesys.cpp +++ b/src/filesys.cpp @@ -558,6 +558,30 @@ bool CopyDir(const std::string &source, const std::string &target) return false; } +bool MoveDir(const std::string &source, const std::string &target) +{ + infostream << "Moving \"" << source << "\" to \"" << target << "\"" << std::endl; + + // If target exists as empty folder delete, otherwise error + if (fs::PathExists(target)) { + if (rmdir(target.c_str()) != 0) { + errorstream << "MoveDir: target \"" << target + << "\" exists as file or non-empty folder" << std::endl; + return false; + } + } + + // Try renaming first which is instant + if (fs::Rename(source, target)) + return true; + + infostream << "MoveDir: rename not possible, will copy instead" << std::endl; + bool retval = fs::CopyDir(source, target); + if (retval) + retval &= fs::RecursiveDelete(source); + return retval; +} + bool PathStartsWith(const std::string &path, const std::string &prefix) { size_t pathsize = path.size(); diff --git a/src/filesys.h b/src/filesys.h index 233e56bba..3fa2524c3 100644 --- a/src/filesys.h +++ b/src/filesys.h @@ -106,6 +106,10 @@ bool CopyFileContents(const std::string &source, const std::string &target); // Omits files and subdirectories that start with a period bool CopyDir(const std::string &source, const std::string &target); +// Move directory and all subdirectories +// Behavior with files/subdirs that start with a period is undefined +bool MoveDir(const std::string &source, const std::string &target); + // Check if one path is prefix of another // For example, "/tmp" is a prefix of "/tmp" and "/tmp/file" but not "/tmp2" // Ignores case differences and '/' vs. '\\' on Windows diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index 2a6a9c32d..3d80bdafa 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -606,26 +606,24 @@ int ModApiMainMenu::l_copy_dir(lua_State *L) const char *destination = luaL_checkstring(L, 2); bool keep_source = true; + if (!lua_isnoneornil(L, 3)) + keep_source = readParam(L, 3); - if ((!lua_isnone(L,3)) && - (!lua_isnil(L,3))) { - keep_source = readParam(L,3); - } - - std::string absolute_destination = fs::RemoveRelativePathComponents(destination); - std::string absolute_source = fs::RemoveRelativePathComponents(source); - - if ((ModApiMainMenu::mayModifyPath(absolute_destination))) { - bool retval = fs::CopyDir(absolute_source,absolute_destination); - - if (retval && (!keep_source)) { + std::string abs_destination = fs::RemoveRelativePathComponents(destination); + std::string abs_source = fs::RemoveRelativePathComponents(source); - retval &= fs::RecursiveDelete(absolute_source); - } - lua_pushboolean(L,retval); + if (!ModApiMainMenu::mayModifyPath(abs_destination) || + (!keep_source && !ModApiMainMenu::mayModifyPath(abs_source))) { + lua_pushboolean(L, false); return 1; } - lua_pushboolean(L,false); + + bool retval; + if (keep_source) + retval = fs::CopyDir(abs_source, abs_destination); + else + retval = fs::MoveDir(abs_source, abs_destination); + lua_pushboolean(L, retval); return 1; } -- cgit v1.2.3 From ecc6f4ba25cd49599922333a5f8d4b4ce368992d Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Tue, 12 Oct 2021 20:12:20 +0200 Subject: Remove a few unused functions reported by callcatcher (#11658) --- src/chat.cpp | 5 - src/chat.h | 2 - src/client/mesh.cpp | 589 ------------------------------------- src/client/mesh.h | 7 - src/clientiface.cpp | 25 -- src/clientiface.h | 1 - src/emerge.cpp | 17 -- src/emerge.h | 3 - src/inventory.cpp | 5 - src/inventory.h | 1 - src/map.cpp | 7 - src/map.h | 3 - src/mapblock.cpp | 25 -- src/mapblock.h | 14 - src/mapgen/mapgen_v6.cpp | 13 - src/mapgen/mapgen_v6.h | 4 +- src/noise.cpp | 45 --- src/noise.h | 9 - src/rollback.cpp | 6 - src/rollback.h | 1 - src/script/lua_api/l_inventory.cpp | 13 - src/script/lua_api/l_inventory.h | 2 - src/script/lua_api/l_nodemeta.cpp | 5 +- src/settings.cpp | 3 +- 24 files changed, 6 insertions(+), 799 deletions(-) (limited to 'src/script/lua_api') diff --git a/src/chat.cpp b/src/chat.cpp index d8b577aab..92df038e8 100644 --- a/src/chat.cpp +++ b/src/chat.cpp @@ -142,11 +142,6 @@ u32 ChatBuffer::getRows() const return m_rows; } -void ChatBuffer::scrollTop() -{ - m_scroll = getTopScrollPos(); -} - void ChatBuffer::reformat(u32 cols, u32 rows) { if (cols == 0 || rows == 0) diff --git a/src/chat.h b/src/chat.h index 696d805eb..fc080f64b 100644 --- a/src/chat.h +++ b/src/chat.h @@ -110,8 +110,6 @@ public: void scrollAbsolute(s32 scroll); // Scroll to bottom of buffer (newest) void scrollBottom(); - // Scroll to top of buffer (oldest) - void scrollTop(); // Functions for keeping track of whether the lines were modified by any // preceding operations diff --git a/src/client/mesh.cpp b/src/client/mesh.cpp index e43139218..c56eba2e2 100644 --- a/src/client/mesh.cpp +++ b/src/client/mesh.cpp @@ -498,592 +498,3 @@ scene::IMesh* convertNodeboxesToMesh(const std::vector &boxes, } return dst_mesh; } - -struct vcache -{ - core::array tris; - float score; - s16 cachepos; - u16 NumActiveTris; -}; - -struct tcache -{ - u16 ind[3]; - float score; - bool drawn; -}; - -const u16 cachesize = 32; - -float FindVertexScore(vcache *v) -{ - const float CacheDecayPower = 1.5f; - const float LastTriScore = 0.75f; - const float ValenceBoostScale = 2.0f; - const float ValenceBoostPower = 0.5f; - const float MaxSizeVertexCache = 32.0f; - - if (v->NumActiveTris == 0) - { - // No tri needs this vertex! - return -1.0f; - } - - float Score = 0.0f; - int CachePosition = v->cachepos; - if (CachePosition < 0) - { - // Vertex is not in FIFO cache - no score. - } - else - { - if (CachePosition < 3) - { - // This vertex was used in the last triangle, - // so it has a fixed score. - Score = LastTriScore; - } - else - { - // Points for being high in the cache. - const float Scaler = 1.0f / (MaxSizeVertexCache - 3); - Score = 1.0f - (CachePosition - 3) * Scaler; - Score = powf(Score, CacheDecayPower); - } - } - - // Bonus points for having a low number of tris still to - // use the vert, so we get rid of lone verts quickly. - float ValenceBoost = powf(v->NumActiveTris, - -ValenceBoostPower); - Score += ValenceBoostScale * ValenceBoost; - - return Score; -} - -/* - A specialized LRU cache for the Forsyth algorithm. -*/ - -class f_lru -{ - -public: - f_lru(vcache *v, tcache *t): vc(v), tc(t) - { - for (int &i : cache) { - i = -1; - } - } - - // Adds this vertex index and returns the highest-scoring triangle index - u32 add(u16 vert, bool updatetris = false) - { - bool found = false; - - // Mark existing pos as empty - for (u16 i = 0; i < cachesize; i++) - { - if (cache[i] == vert) - { - // Move everything down - for (u16 j = i; j; j--) - { - cache[j] = cache[j - 1]; - } - - found = true; - break; - } - } - - if (!found) - { - if (cache[cachesize-1] != -1) - vc[cache[cachesize-1]].cachepos = -1; - - // Move everything down - for (u16 i = cachesize - 1; i; i--) - { - cache[i] = cache[i - 1]; - } - } - - cache[0] = vert; - - u32 highest = 0; - float hiscore = 0; - - if (updatetris) - { - // Update cache positions - for (u16 i = 0; i < cachesize; i++) - { - if (cache[i] == -1) - break; - - vc[cache[i]].cachepos = i; - vc[cache[i]].score = FindVertexScore(&vc[cache[i]]); - } - - // Update triangle scores - for (int i : cache) { - if (i == -1) - break; - - const u16 trisize = vc[i].tris.size(); - for (u16 t = 0; t < trisize; t++) - { - tcache *tri = &tc[vc[i].tris[t]]; - - tri->score = - vc[tri->ind[0]].score + - vc[tri->ind[1]].score + - vc[tri->ind[2]].score; - - if (tri->score > hiscore) - { - hiscore = tri->score; - highest = vc[i].tris[t]; - } - } - } - } - - return highest; - } - -private: - s32 cache[cachesize]; - vcache *vc; - tcache *tc; -}; - -/** -Vertex cache optimization according to the Forsyth paper: -http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html - -The function is thread-safe (read: you can optimize several meshes in different threads) - -\param mesh Source mesh for the operation. */ -scene::IMesh* createForsythOptimizedMesh(const scene::IMesh *mesh) -{ - if (!mesh) - return 0; - - scene::SMesh *newmesh = new scene::SMesh(); - newmesh->BoundingBox = mesh->getBoundingBox(); - - const u32 mbcount = mesh->getMeshBufferCount(); - - for (u32 b = 0; b < mbcount; ++b) - { - const scene::IMeshBuffer *mb = mesh->getMeshBuffer(b); - - if (mb->getIndexType() != video::EIT_16BIT) - { - //os::Printer::log("Cannot optimize a mesh with 32bit indices", ELL_ERROR); - newmesh->drop(); - return 0; - } - - const u32 icount = mb->getIndexCount(); - const u32 tcount = icount / 3; - const u32 vcount = mb->getVertexCount(); - const u16 *ind = mb->getIndices(); - - vcache *vc = new vcache[vcount]; - tcache *tc = new tcache[tcount]; - - f_lru lru(vc, tc); - - // init - for (u16 i = 0; i < vcount; i++) - { - vc[i].score = 0; - vc[i].cachepos = -1; - vc[i].NumActiveTris = 0; - } - - // First pass: count how many times a vert is used - for (u32 i = 0; i < icount; i += 3) - { - vc[ind[i]].NumActiveTris++; - vc[ind[i + 1]].NumActiveTris++; - vc[ind[i + 2]].NumActiveTris++; - - const u32 tri_ind = i/3; - tc[tri_ind].ind[0] = ind[i]; - tc[tri_ind].ind[1] = ind[i + 1]; - tc[tri_ind].ind[2] = ind[i + 2]; - } - - // Second pass: list of each triangle - for (u32 i = 0; i < tcount; i++) - { - vc[tc[i].ind[0]].tris.push_back(i); - vc[tc[i].ind[1]].tris.push_back(i); - vc[tc[i].ind[2]].tris.push_back(i); - - tc[i].drawn = false; - } - - // Give initial scores - for (u16 i = 0; i < vcount; i++) - { - vc[i].score = FindVertexScore(&vc[i]); - } - for (u32 i = 0; i < tcount; i++) - { - tc[i].score = - vc[tc[i].ind[0]].score + - vc[tc[i].ind[1]].score + - vc[tc[i].ind[2]].score; - } - - switch(mb->getVertexType()) - { - case video::EVT_STANDARD: - { - video::S3DVertex *v = (video::S3DVertex *) mb->getVertices(); - - scene::SMeshBuffer *buf = new scene::SMeshBuffer(); - buf->Material = mb->getMaterial(); - - buf->Vertices.reallocate(vcount); - buf->Indices.reallocate(icount); - - core::map sind; // search index for fast operation - typedef core::map::Node snode; - - // Main algorithm - u32 highest = 0; - u32 drawcalls = 0; - for (;;) - { - if (tc[highest].drawn) - { - bool found = false; - float hiscore = 0; - for (u32 t = 0; t < tcount; t++) - { - if (!tc[t].drawn) - { - if (tc[t].score > hiscore) - { - highest = t; - hiscore = tc[t].score; - found = true; - } - } - } - if (!found) - break; - } - - // Output the best triangle - u16 newind = buf->Vertices.size(); - - snode *s = sind.find(v[tc[highest].ind[0]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[0]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[0]], newind); - newind++; - } - else - { - buf->Indices.push_back(s->getValue()); - } - - s = sind.find(v[tc[highest].ind[1]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[1]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[1]], newind); - newind++; - } - else - { - buf->Indices.push_back(s->getValue()); - } - - s = sind.find(v[tc[highest].ind[2]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[2]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[2]], newind); - } - else - { - buf->Indices.push_back(s->getValue()); - } - - vc[tc[highest].ind[0]].NumActiveTris--; - vc[tc[highest].ind[1]].NumActiveTris--; - vc[tc[highest].ind[2]].NumActiveTris--; - - tc[highest].drawn = true; - - for (u16 j : tc[highest].ind) { - vcache *vert = &vc[j]; - for (u16 t = 0; t < vert->tris.size(); t++) - { - if (highest == vert->tris[t]) - { - vert->tris.erase(t); - break; - } - } - } - - lru.add(tc[highest].ind[0]); - lru.add(tc[highest].ind[1]); - highest = lru.add(tc[highest].ind[2], true); - drawcalls++; - } - - buf->setBoundingBox(mb->getBoundingBox()); - newmesh->addMeshBuffer(buf); - buf->drop(); - } - break; - case video::EVT_2TCOORDS: - { - video::S3DVertex2TCoords *v = (video::S3DVertex2TCoords *) mb->getVertices(); - - scene::SMeshBufferLightMap *buf = new scene::SMeshBufferLightMap(); - buf->Material = mb->getMaterial(); - - buf->Vertices.reallocate(vcount); - buf->Indices.reallocate(icount); - - core::map sind; // search index for fast operation - typedef core::map::Node snode; - - // Main algorithm - u32 highest = 0; - u32 drawcalls = 0; - for (;;) - { - if (tc[highest].drawn) - { - bool found = false; - float hiscore = 0; - for (u32 t = 0; t < tcount; t++) - { - if (!tc[t].drawn) - { - if (tc[t].score > hiscore) - { - highest = t; - hiscore = tc[t].score; - found = true; - } - } - } - if (!found) - break; - } - - // Output the best triangle - u16 newind = buf->Vertices.size(); - - snode *s = sind.find(v[tc[highest].ind[0]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[0]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[0]], newind); - newind++; - } - else - { - buf->Indices.push_back(s->getValue()); - } - - s = sind.find(v[tc[highest].ind[1]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[1]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[1]], newind); - newind++; - } - else - { - buf->Indices.push_back(s->getValue()); - } - - s = sind.find(v[tc[highest].ind[2]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[2]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[2]], newind); - } - else - { - buf->Indices.push_back(s->getValue()); - } - - vc[tc[highest].ind[0]].NumActiveTris--; - vc[tc[highest].ind[1]].NumActiveTris--; - vc[tc[highest].ind[2]].NumActiveTris--; - - tc[highest].drawn = true; - - for (u16 j : tc[highest].ind) { - vcache *vert = &vc[j]; - for (u16 t = 0; t < vert->tris.size(); t++) - { - if (highest == vert->tris[t]) - { - vert->tris.erase(t); - break; - } - } - } - - lru.add(tc[highest].ind[0]); - lru.add(tc[highest].ind[1]); - highest = lru.add(tc[highest].ind[2]); - drawcalls++; - } - - buf->setBoundingBox(mb->getBoundingBox()); - newmesh->addMeshBuffer(buf); - buf->drop(); - - } - break; - case video::EVT_TANGENTS: - { - video::S3DVertexTangents *v = (video::S3DVertexTangents *) mb->getVertices(); - - scene::SMeshBufferTangents *buf = new scene::SMeshBufferTangents(); - buf->Material = mb->getMaterial(); - - buf->Vertices.reallocate(vcount); - buf->Indices.reallocate(icount); - - core::map sind; // search index for fast operation - typedef core::map::Node snode; - - // Main algorithm - u32 highest = 0; - u32 drawcalls = 0; - for (;;) - { - if (tc[highest].drawn) - { - bool found = false; - float hiscore = 0; - for (u32 t = 0; t < tcount; t++) - { - if (!tc[t].drawn) - { - if (tc[t].score > hiscore) - { - highest = t; - hiscore = tc[t].score; - found = true; - } - } - } - if (!found) - break; - } - - // Output the best triangle - u16 newind = buf->Vertices.size(); - - snode *s = sind.find(v[tc[highest].ind[0]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[0]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[0]], newind); - newind++; - } - else - { - buf->Indices.push_back(s->getValue()); - } - - s = sind.find(v[tc[highest].ind[1]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[1]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[1]], newind); - newind++; - } - else - { - buf->Indices.push_back(s->getValue()); - } - - s = sind.find(v[tc[highest].ind[2]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[2]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[2]], newind); - } - else - { - buf->Indices.push_back(s->getValue()); - } - - vc[tc[highest].ind[0]].NumActiveTris--; - vc[tc[highest].ind[1]].NumActiveTris--; - vc[tc[highest].ind[2]].NumActiveTris--; - - tc[highest].drawn = true; - - for (u16 j : tc[highest].ind) { - vcache *vert = &vc[j]; - for (u16 t = 0; t < vert->tris.size(); t++) - { - if (highest == vert->tris[t]) - { - vert->tris.erase(t); - break; - } - } - } - - lru.add(tc[highest].ind[0]); - lru.add(tc[highest].ind[1]); - highest = lru.add(tc[highest].ind[2]); - drawcalls++; - } - - buf->setBoundingBox(mb->getBoundingBox()); - newmesh->addMeshBuffer(buf); - buf->drop(); - } - break; - } - - delete [] vc; - delete [] tc; - - } // for each meshbuffer - - return newmesh; -} diff --git a/src/client/mesh.h b/src/client/mesh.h index dbc091a06..1ed753c01 100644 --- a/src/client/mesh.h +++ b/src/client/mesh.h @@ -133,10 +133,3 @@ void recalculateBoundingBox(scene::IMesh *src_mesh); We assume normal to be valid when it's 0 < length < Inf. and not NaN */ bool checkMeshNormals(scene::IMesh *mesh); - -/* - Vertex cache optimization according to the Forsyth paper: - http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html - Ported from irrlicht 1.8 -*/ -scene::IMesh* createForsythOptimizedMesh(const scene::IMesh *mesh); diff --git a/src/clientiface.cpp b/src/clientiface.cpp index f35dcd0eb..a1c3e1187 100644 --- a/src/clientiface.cpp +++ b/src/clientiface.cpp @@ -714,31 +714,6 @@ void ClientInterface::sendToAll(NetworkPacket *pkt) } } -void ClientInterface::sendToAllCompat(NetworkPacket *pkt, NetworkPacket *legacypkt, - u16 min_proto_ver) -{ - RecursiveMutexAutoLock clientslock(m_clients_mutex); - for (auto &client_it : m_clients) { - RemoteClient *client = client_it.second; - NetworkPacket *pkt_to_send = nullptr; - - if (client->net_proto_version >= min_proto_ver) { - pkt_to_send = pkt; - } else if (client->net_proto_version != 0) { - pkt_to_send = legacypkt; - } else { - warningstream << "Client with unhandled version to handle: '" - << client->net_proto_version << "'"; - continue; - } - - m_con->Send(client->peer_id, - clientCommandFactoryTable[pkt_to_send->getCommand()].channel, - pkt_to_send, - clientCommandFactoryTable[pkt_to_send->getCommand()].reliable); - } -} - RemoteClient* ClientInterface::getClientNoEx(session_t peer_id, ClientState state_min) { RecursiveMutexAutoLock clientslock(m_clients_mutex); diff --git a/src/clientiface.h b/src/clientiface.h index dfd976741..b1591ddb0 100644 --- a/src/clientiface.h +++ b/src/clientiface.h @@ -465,7 +465,6 @@ public: /* send to all clients */ void sendToAll(NetworkPacket *pkt); - void sendToAllCompat(NetworkPacket *pkt, NetworkPacket *legacypkt, u16 min_proto_ver); /* delete a client */ void DeleteClient(session_t peer_id); diff --git a/src/emerge.cpp b/src/emerge.cpp index 9234fe6d3..be64d744a 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -367,12 +367,6 @@ bool EmergeManager::isBlockInQueue(v3s16 pos) // -// TODO(hmmmm): Move this to ServerMap -v3s16 EmergeManager::getContainingChunk(v3s16 blockpos) -{ - return getContainingChunk(blockpos, mgparams->chunksize); -} - // TODO(hmmmm): Move this to ServerMap v3s16 EmergeManager::getContainingChunk(v3s16 blockpos, s16 chunksize) { @@ -396,17 +390,6 @@ int EmergeManager::getSpawnLevelAtPoint(v2s16 p) } -int EmergeManager::getGroundLevelAtPoint(v2s16 p) -{ - if (m_mapgens.empty() || !m_mapgens[0]) { - errorstream << "EmergeManager: getGroundLevelAtPoint() called" - " before mapgen init" << std::endl; - return 0; - } - - return m_mapgens[0]->getGroundLevelAtPoint(p); -} - // TODO(hmmmm): Move this to ServerMap bool EmergeManager::isBlockUnderground(v3s16 blockpos) { diff --git a/src/emerge.h b/src/emerge.h index e2d727973..61e7bda63 100644 --- a/src/emerge.h +++ b/src/emerge.h @@ -176,13 +176,10 @@ public: bool isBlockInQueue(v3s16 pos); - v3s16 getContainingChunk(v3s16 blockpos); - Mapgen *getCurrentMapgen(); // Mapgen helpers methods int getSpawnLevelAtPoint(v2s16 p); - int getGroundLevelAtPoint(v2s16 p); bool isBlockUnderground(v3s16 blockpos); static v3s16 getContainingChunk(v3s16 blockpos, s16 chunksize); diff --git a/src/inventory.cpp b/src/inventory.cpp index 029fcbf4f..d14b12f9d 100644 --- a/src/inventory.cpp +++ b/src/inventory.cpp @@ -560,11 +560,6 @@ u32 InventoryList::getUsedSlots() const return num; } -u32 InventoryList::getFreeSlots() const -{ - return getSize() - getUsedSlots(); -} - const ItemStack& InventoryList::getItem(u32 i) const { assert(i < m_size); // Pre-condition diff --git a/src/inventory.h b/src/inventory.h index 276002d28..eb063d4ad 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -211,7 +211,6 @@ public: u32 getWidth() const; // Count used slots u32 getUsedSlots() const; - u32 getFreeSlots() const; // Get reference to item const ItemStack& getItem(u32 i) const; diff --git a/src/map.cpp b/src/map.cpp index 30ce064d6..77031e17d 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -139,13 +139,6 @@ MapBlock * Map::getBlockNoCreate(v3s16 p3d) return block; } -bool Map::isNodeUnderground(v3s16 p) -{ - v3s16 blockpos = getNodeBlockPos(p); - MapBlock *block = getBlockNoCreateNoEx(blockpos); - return block && block->getIsUnderground(); -} - bool Map::isValidPosition(v3s16 p) { v3s16 blockpos = getNodeBlockPos(p); diff --git a/src/map.h b/src/map.h index 8d20c4a44..fe580b20f 100644 --- a/src/map.h +++ b/src/map.h @@ -167,9 +167,6 @@ public: inline const NodeDefManager * getNodeDefManager() { return m_nodedef; } - // Returns InvalidPositionException if not found - bool isNodeUnderground(v3s16 p); - bool isValidPosition(v3s16 p); // throws InvalidPositionException if not found diff --git a/src/mapblock.cpp b/src/mapblock.cpp index 4958d3a65..e3a6caa19 100644 --- a/src/mapblock.cpp +++ b/src/mapblock.cpp @@ -218,31 +218,6 @@ void MapBlock::expireDayNightDiff() m_day_night_differs_expired = true; } -s16 MapBlock::getGroundLevel(v2s16 p2d) -{ - if(isDummy()) - return -3; - try - { - s16 y = MAP_BLOCKSIZE-1; - for(; y>=0; y--) - { - MapNode n = getNodeRef(p2d.X, y, p2d.Y); - if (m_gamedef->ndef()->get(n).walkable) { - if(y == MAP_BLOCKSIZE-1) - return -2; - - return y; - } - } - return -1; - } - catch(InvalidPositionException &e) - { - return -3; - } -} - /* Serialization */ diff --git a/src/mapblock.h b/src/mapblock.h index 8de631a29..e729fdb1c 100644 --- a/src/mapblock.h +++ b/src/mapblock.h @@ -363,20 +363,6 @@ public: return m_day_night_differs; } - //// - //// Miscellaneous stuff - //// - - /* - Tries to measure ground level. - Return value: - -1 = only air - -2 = only ground - -3 = random fail - 0...MAP_BLOCKSIZE-1 = ground level - */ - s16 getGroundLevel(v2s16 p2d); - //// //// Timestamp (see m_timestamp) //// diff --git a/src/mapgen/mapgen_v6.cpp b/src/mapgen/mapgen_v6.cpp index bce9cee81..a418acace 100644 --- a/src/mapgen/mapgen_v6.cpp +++ b/src/mapgen/mapgen_v6.cpp @@ -360,19 +360,6 @@ int MapgenV6::getSpawnLevelAtPoint(v2s16 p) //////////////////////// Noise functions -float MapgenV6::getMudAmount(v2s16 p) -{ - int index = (p.Y - node_min.Z) * ystride + (p.X - node_min.X); - return getMudAmount(index); -} - - -bool MapgenV6::getHaveBeach(v2s16 p) -{ - int index = (p.Y - node_min.Z) * ystride + (p.X - node_min.X); - return getHaveBeach(index); -} - BiomeV6Type MapgenV6::getBiome(v2s16 p) { diff --git a/src/mapgen/mapgen_v6.h b/src/mapgen/mapgen_v6.h index a6e6da8c6..b0eb67893 100644 --- a/src/mapgen/mapgen_v6.h +++ b/src/mapgen/mapgen_v6.h @@ -154,9 +154,7 @@ public: float getHumidity(v2s16 p); float getTreeAmount(v2s16 p); bool getHaveAppleTree(v2s16 p); - float getMudAmount(v2s16 p); - virtual float getMudAmount(int index); - bool getHaveBeach(v2s16 p); + float getMudAmount(int index); bool getHaveBeach(int index); BiomeV6Type getBiome(v2s16 p); BiomeV6Type getBiome(int index, v2s16 p); diff --git a/src/noise.cpp b/src/noise.cpp index a10efa3c4..2f4de6855 100644 --- a/src/noise.cpp +++ b/src/noise.cpp @@ -312,51 +312,6 @@ float noise2d_perlin(float x, float y, s32 seed, } -float noise2d_perlin_abs(float x, float y, s32 seed, - int octaves, float persistence, bool eased) -{ - float a = 0; - float f = 1.0; - float g = 1.0; - for (int i = 0; i < octaves; i++) { - a += g * std::fabs(noise2d_gradient(x * f, y * f, seed + i, eased)); - f *= 2.0; - g *= persistence; - } - return a; -} - - -float noise3d_perlin(float x, float y, float z, s32 seed, - int octaves, float persistence, bool eased) -{ - float a = 0; - float f = 1.0; - float g = 1.0; - for (int i = 0; i < octaves; i++) { - a += g * noise3d_gradient(x * f, y * f, z * f, seed + i, eased); - f *= 2.0; - g *= persistence; - } - return a; -} - - -float noise3d_perlin_abs(float x, float y, float z, s32 seed, - int octaves, float persistence, bool eased) -{ - float a = 0; - float f = 1.0; - float g = 1.0; - for (int i = 0; i < octaves; i++) { - a += g * std::fabs(noise3d_gradient(x * f, y * f, z * f, seed + i, eased)); - f *= 2.0; - g *= persistence; - } - return a; -} - - float contour(float v) { v = std::fabs(v); diff --git a/src/noise.h b/src/noise.h index 854781731..e4a9ed6c7 100644 --- a/src/noise.h +++ b/src/noise.h @@ -224,15 +224,6 @@ float noise3d_gradient(float x, float y, float z, s32 seed, bool eased=false); float noise2d_perlin(float x, float y, s32 seed, int octaves, float persistence, bool eased=true); -float noise2d_perlin_abs(float x, float y, s32 seed, - int octaves, float persistence, bool eased=true); - -float noise3d_perlin(float x, float y, float z, s32 seed, - int octaves, float persistence, bool eased=false); - -float noise3d_perlin_abs(float x, float y, float z, s32 seed, - int octaves, float persistence, bool eased=false); - inline float easeCurve(float t) { return t * t * t * (t * (6.f * t - 15.f) + 10.f); diff --git a/src/rollback.cpp b/src/rollback.cpp index 3cd9c7ce7..33b7958b9 100644 --- a/src/rollback.cpp +++ b/src/rollback.cpp @@ -941,12 +941,6 @@ void RollbackManager::addAction(const RollbackAction & action) } } -std::list RollbackManager::getEntriesSince(time_t first_time) -{ - flush(); - return getActionsSince(first_time); -} - std::list RollbackManager::getNodeActors(v3s16 pos, int range, time_t seconds, int limit) { diff --git a/src/rollback.h b/src/rollback.h index 1d9949d15..ff96e513f 100644 --- a/src/rollback.h +++ b/src/rollback.h @@ -46,7 +46,6 @@ public: void flush(); void addAction(const RollbackAction & action); - std::list getEntriesSince(time_t first_time); std::list getNodeActors(v3s16 pos, int range, time_t seconds, int limit); std::list getRevertActions( diff --git a/src/script/lua_api/l_inventory.cpp b/src/script/lua_api/l_inventory.cpp index 0dd418462..b0a4ee194 100644 --- a/src/script/lua_api/l_inventory.cpp +++ b/src/script/lua_api/l_inventory.cpp @@ -421,19 +421,6 @@ void InvRef::create(lua_State *L, const InventoryLocation &loc) luaL_getmetatable(L, className); lua_setmetatable(L, -2); } -void InvRef::createPlayer(lua_State *L, RemotePlayer *player) -{ - NO_MAP_LOCK_REQUIRED; - InventoryLocation loc; - loc.setPlayer(player->getName()); - create(L, loc); -} -void InvRef::createNodeMeta(lua_State *L, v3s16 p) -{ - InventoryLocation loc; - loc.setNodeMeta(p); - create(L, loc); -} void InvRef::Register(lua_State *L) { diff --git a/src/script/lua_api/l_inventory.h b/src/script/lua_api/l_inventory.h index 94f670c9d..6a75bac0f 100644 --- a/src/script/lua_api/l_inventory.h +++ b/src/script/lua_api/l_inventory.h @@ -111,8 +111,6 @@ public: // Creates an InvRef and leaves it on top of stack // Not callable from Lua; all references are created on the C side. static void create(lua_State *L, const InventoryLocation &loc); - static void createPlayer(lua_State *L, RemotePlayer *player); - static void createNodeMeta(lua_State *L, v3s16 p); static void Register(lua_State *L); }; diff --git a/src/script/lua_api/l_nodemeta.cpp b/src/script/lua_api/l_nodemeta.cpp index 60d14f8f2..34760157d 100644 --- a/src/script/lua_api/l_nodemeta.cpp +++ b/src/script/lua_api/l_nodemeta.cpp @@ -89,7 +89,10 @@ int NodeMetaRef::l_get_inventory(lua_State *L) NodeMetaRef *ref = checkobject(L, 1); ref->getmeta(true); // try to ensure the metadata exists - InvRef::createNodeMeta(L, ref->m_p); + + InventoryLocation loc; + loc.setNodeMeta(ref->m_p); + InvRef::create(L, loc); return 1; } diff --git a/src/settings.cpp b/src/settings.cpp index f4de5bec9..818d2bc41 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -104,8 +104,7 @@ Settings *Settings::createLayer(SettingsLayer sl, const std::string &end_tag) Settings *Settings::getLayer(SettingsLayer sl) { - sanity_check((int)sl >= 0 && sl < SL_TOTAL_COUNT); - return g_hierarchy.layers[(int)sl]; + return g_hierarchy.getLayer(sl); } -- cgit v1.2.3 From 660e63dbae5901f8178b39ab40e6f770b8d7a215 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 25 Oct 2021 20:30:27 +0200 Subject: Fix item duplication if player dies during interact callback (alternative) (#11662) --- builtin/game/item.lua | 50 +++++++++++++++++++++---------------- doc/lua_api.txt | 11 +++++--- src/network/serverpackethandler.cpp | 44 +++++++++++++++++++------------- src/script/cpp_api/s_item.cpp | 21 +++++++++++----- src/script/cpp_api/s_item.h | 14 ++++++++--- src/script/lua_api/l_env.cpp | 2 +- src/util/Optional.h | 32 ++++++++++++++++++++++-- 7 files changed, 118 insertions(+), 56 deletions(-) (limited to 'src/script/lua_api') diff --git a/builtin/game/item.lua b/builtin/game/item.lua index c495a67bd..039947584 100644 --- a/builtin/game/item.lua +++ b/builtin/game/item.lua @@ -499,34 +499,40 @@ function core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed return result end end + if itemstack:take_item():is_empty() then + return itemstack + end + local def = itemstack:get_definition() - if itemstack:take_item() ~= nil then - user:set_hp(user:get_hp() + hp_change) - - if def and def.sound and def.sound.eat then - core.sound_play(def.sound.eat, { - pos = user:get_pos(), - max_hear_distance = 16 - }, true) - end + if def and def.sound and def.sound.eat then + core.sound_play(def.sound.eat, { + pos = user:get_pos(), + max_hear_distance = 16 + }, true) + end - if replace_with_item then - if itemstack:is_empty() then - itemstack:add_item(replace_with_item) + -- Changing hp might kill the player causing mods to do who-knows-what to the + -- inventory, so do this before set_hp(). + if replace_with_item then + if itemstack:is_empty() then + itemstack:add_item(replace_with_item) + else + local inv = user:get_inventory() + -- Check if inv is null, since non-players don't have one + if inv and inv:room_for_item("main", {name=replace_with_item}) then + inv:add_item("main", replace_with_item) else - local inv = user:get_inventory() - -- Check if inv is null, since non-players don't have one - if inv and inv:room_for_item("main", {name=replace_with_item}) then - inv:add_item("main", replace_with_item) - else - local pos = user:get_pos() - pos.y = math.floor(pos.y + 0.5) - core.add_item(pos, replace_with_item) - end + local pos = user:get_pos() + pos.y = math.floor(pos.y + 0.5) + core.add_item(pos, replace_with_item) end end end - return itemstack + user:set_wielded_item(itemstack) + + user:set_hp(user:get_hp() + hp_change) + + return nil -- don't overwrite wield item a second time end function core.item_eat(hp_change, replace_with_item) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 69ac55493..e47df4686 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -5421,7 +5421,7 @@ Inventory * `minetest.remove_detached_inventory(name)` * Returns a `boolean` indicating whether the removal succeeded. * `minetest.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed_thing)`: - returns left over ItemStack. + returns leftover ItemStack or nil to indicate no inventory change * See `minetest.item_eat` and `minetest.register_on_item_eat` Formspec @@ -7542,12 +7542,15 @@ Used by `minetest.register_node`, `minetest.register_craftitem`, and on_place = function(itemstack, placer, pointed_thing), -- When the 'place' key was pressed with the item in hand -- and a node was pointed at. - -- Shall place item and return the leftover itemstack. + -- Shall place item and return the leftover itemstack + -- or nil to not modify the inventory. -- The placer may be any ObjectRef or nil. -- default: minetest.item_place on_secondary_use = function(itemstack, user, pointed_thing), -- Same as on_place but called when not pointing at a node. + -- Function must return either nil if inventory shall not be modified, + -- or an itemstack to replace the original itemstack. -- The user may be any ObjectRef or nil. -- default: nil @@ -7559,8 +7562,8 @@ Used by `minetest.register_node`, `minetest.register_craftitem`, and on_use = function(itemstack, user, pointed_thing), -- default: nil -- When user pressed the 'punch/mine' key with the item in hand. - -- Function must return either nil if no item shall be removed from - -- inventory, or an itemstack to replace the original itemstack. + -- Function must return either nil if inventory shall not be modified, + -- or an itemstack to replace the original itemstack. -- e.g. itemstack:take_item(); return itemstack -- Otherwise, the function is free to do what it wants. -- The user may be any ObjectRef or nil. diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index dc7be0e23..d4bef3ca2 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -921,6 +921,13 @@ bool Server::checkInteractDistance(RemotePlayer *player, const f32 d, const std: return true; } +// Tiny helper to retrieve the selected item into an Optional +static inline void getWieldedItem(const PlayerSAO *playersao, Optional &ret) +{ + ret = ItemStack(); + playersao->getWieldedItem(&(*ret)); +} + void Server::handleCommand_Interact(NetworkPacket *pkt) { /* @@ -1228,14 +1235,17 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) // Place block or right-click object case INTERACT_PLACE: { - ItemStack selected_item; - playersao->getWieldedItem(&selected_item, nullptr); + Optional selected_item; + getWieldedItem(playersao, selected_item); // Reset build time counter if (pointed.type == POINTEDTHING_NODE && - selected_item.getDefinition(m_itemdef).type == ITEM_NODE) + selected_item->getDefinition(m_itemdef).type == ITEM_NODE) getClient(peer_id)->m_time_from_building = 0.0; + const bool had_prediction = !selected_item->getDefinition(m_itemdef). + node_placement_prediction.empty(); + if (pointed.type == POINTEDTHING_OBJECT) { // Right click object @@ -1248,11 +1258,9 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) << pointed_object->getDescription() << std::endl; // Do stuff - if (m_script->item_OnSecondaryUse( - selected_item, playersao, pointed)) { - if (playersao->setWieldedItem(selected_item)) { + if (m_script->item_OnSecondaryUse(selected_item, playersao, pointed)) { + if (selected_item.has_value() && playersao->setWieldedItem(*selected_item)) SendInventory(playersao, true); - } } pointed_object->rightClick(playersao); @@ -1260,7 +1268,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) // Placement was handled in lua // Apply returned ItemStack - if (playersao->setWieldedItem(selected_item)) + if (selected_item.has_value() && playersao->setWieldedItem(*selected_item)) SendInventory(playersao, true); } @@ -1272,8 +1280,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) RemoteClient *client = getClient(peer_id); v3s16 blockpos = getNodeBlockPos(pointed.node_abovesurface); v3s16 blockpos2 = getNodeBlockPos(pointed.node_undersurface); - if (!selected_item.getDefinition(m_itemdef - ).node_placement_prediction.empty()) { + if (had_prediction) { client->SetBlockNotSent(blockpos); if (blockpos2 != blockpos) client->SetBlockNotSent(blockpos2); @@ -1287,15 +1294,15 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) } // action == INTERACT_PLACE case INTERACT_USE: { - ItemStack selected_item; - playersao->getWieldedItem(&selected_item, nullptr); + Optional selected_item; + getWieldedItem(playersao, selected_item); - actionstream << player->getName() << " uses " << selected_item.name + actionstream << player->getName() << " uses " << selected_item->name << ", pointing at " << pointed.dump() << std::endl; if (m_script->item_OnUse(selected_item, playersao, pointed)) { // Apply returned ItemStack - if (playersao->setWieldedItem(selected_item)) + if (selected_item.has_value() && playersao->setWieldedItem(*selected_item)) SendInventory(playersao, true); } @@ -1304,16 +1311,17 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) // Rightclick air case INTERACT_ACTIVATE: { - ItemStack selected_item; - playersao->getWieldedItem(&selected_item, nullptr); + Optional selected_item; + getWieldedItem(playersao, selected_item); actionstream << player->getName() << " activates " - << selected_item.name << std::endl; + << selected_item->name << std::endl; pointed.type = POINTEDTHING_NOTHING; // can only ever be NOTHING if (m_script->item_OnSecondaryUse(selected_item, playersao, pointed)) { - if (playersao->setWieldedItem(selected_item)) + // Apply returned ItemStack + if (selected_item.has_value() && playersao->setWieldedItem(*selected_item)) SendInventory(playersao, true); } diff --git a/src/script/cpp_api/s_item.cpp b/src/script/cpp_api/s_item.cpp index 48dce14f3..b1916070e 100644 --- a/src/script/cpp_api/s_item.cpp +++ b/src/script/cpp_api/s_item.cpp @@ -59,13 +59,14 @@ bool ScriptApiItem::item_OnDrop(ItemStack &item, return true; } -bool ScriptApiItem::item_OnPlace(ItemStack &item, +bool ScriptApiItem::item_OnPlace(Optional &ret_item, ServerActiveObject *placer, const PointedThing &pointed) { SCRIPTAPI_PRECHECKHEADER int error_handler = PUSH_ERROR_HANDLER(L); + const ItemStack &item = *ret_item; // Push callback function on stack if (!getItemCallback(item.name.c_str(), "on_place")) return false; @@ -82,22 +83,25 @@ bool ScriptApiItem::item_OnPlace(ItemStack &item, PCALL_RES(lua_pcall(L, 3, 1, error_handler)); if (!lua_isnil(L, -1)) { try { - item = read_item(L, -1, getServer()->idef()); + ret_item = read_item(L, -1, getServer()->idef()); } catch (LuaError &e) { throw WRAP_LUAERROR(e, "item=" + item.name); } + } else { + ret_item = nullopt; } lua_pop(L, 2); // Pop item and error handler return true; } -bool ScriptApiItem::item_OnUse(ItemStack &item, +bool ScriptApiItem::item_OnUse(Optional &ret_item, ServerActiveObject *user, const PointedThing &pointed) { SCRIPTAPI_PRECHECKHEADER int error_handler = PUSH_ERROR_HANDLER(L); + const ItemStack &item = *ret_item; // Push callback function on stack if (!getItemCallback(item.name.c_str(), "on_use")) return false; @@ -109,22 +113,25 @@ bool ScriptApiItem::item_OnUse(ItemStack &item, PCALL_RES(lua_pcall(L, 3, 1, error_handler)); if(!lua_isnil(L, -1)) { try { - item = read_item(L, -1, getServer()->idef()); + ret_item = read_item(L, -1, getServer()->idef()); } catch (LuaError &e) { throw WRAP_LUAERROR(e, "item=" + item.name); } + } else { + ret_item = nullopt; } lua_pop(L, 2); // Pop item and error handler return true; } -bool ScriptApiItem::item_OnSecondaryUse(ItemStack &item, +bool ScriptApiItem::item_OnSecondaryUse(Optional &ret_item, ServerActiveObject *user, const PointedThing &pointed) { SCRIPTAPI_PRECHECKHEADER int error_handler = PUSH_ERROR_HANDLER(L); + const ItemStack &item = *ret_item; if (!getItemCallback(item.name.c_str(), "on_secondary_use")) return false; @@ -134,10 +141,12 @@ bool ScriptApiItem::item_OnSecondaryUse(ItemStack &item, PCALL_RES(lua_pcall(L, 3, 1, error_handler)); if (!lua_isnil(L, -1)) { try { - item = read_item(L, -1, getServer()->idef()); + ret_item = read_item(L, -1, getServer()->idef()); } catch (LuaError &e) { throw WRAP_LUAERROR(e, "item=" + item.name); } + } else { + ret_item = nullopt; } lua_pop(L, 2); // Pop item and error handler return true; diff --git a/src/script/cpp_api/s_item.h b/src/script/cpp_api/s_item.h index 25a3501f9..5015d8bd4 100644 --- a/src/script/cpp_api/s_item.h +++ b/src/script/cpp_api/s_item.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "cpp_api/s_base.h" #include "irr_v3d.h" +#include "util/Optional.h" struct PointedThing; struct ItemStack; @@ -35,13 +36,20 @@ class ScriptApiItem : virtual public ScriptApiBase { public: + /* + * Functions with Optional are for callbacks where Lua may + * want to prevent the engine from modifying the inventory after it's done. + * This has a longer backstory where on_use may need to empty the player's + * inventory without the engine interfering (see issue #6546). + */ + bool item_OnDrop(ItemStack &item, ServerActiveObject *dropper, v3f pos); - bool item_OnPlace(ItemStack &item, + bool item_OnPlace(Optional &item, ServerActiveObject *placer, const PointedThing &pointed); - bool item_OnUse(ItemStack &item, + bool item_OnUse(Optional &item, ServerActiveObject *user, const PointedThing &pointed); - bool item_OnSecondaryUse(ItemStack &item, + bool item_OnSecondaryUse(Optional &item, ServerActiveObject *user, const PointedThing &pointed); bool item_OnCraft(ItemStack &item, ServerActiveObject *user, const InventoryList *old_craft_grid, const InventoryLocation &craft_inv); diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index 98f8861fa..2c709d31b 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -477,7 +477,7 @@ int ModApiEnvMod::l_place_node(lua_State *L) return 1; } // Create item to place - ItemStack item(ndef->get(n).name, 1, 0, idef); + Optional item = ItemStack(ndef->get(n).name, 1, 0, idef); // Make pointed position PointedThing pointed; pointed.type = POINTEDTHING_NODE; diff --git a/src/util/Optional.h b/src/util/Optional.h index 9c2842b43..eda7fff89 100644 --- a/src/util/Optional.h +++ b/src/util/Optional.h @@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once +#include #include "debug.h" struct nullopt_t @@ -43,18 +44,38 @@ class Optional public: Optional() noexcept {} Optional(nullopt_t) noexcept {} + Optional(const T &value) noexcept : m_has_value(true), m_value(value) {} + Optional(T &&value) noexcept : m_has_value(true), m_value(std::move(value)) {} + Optional(const Optional &other) noexcept : m_has_value(other.m_has_value), m_value(other.m_value) + {} + Optional(Optional &&other) noexcept : + m_has_value(other.m_has_value), m_value(std::move(other.m_value)) { + other.m_has_value = false; } - void operator=(nullopt_t) noexcept { m_has_value = false; } + Optional &operator=(nullopt_t) noexcept { m_has_value = false; return *this; } - void operator=(const Optional &other) noexcept + Optional &operator=(const Optional &other) noexcept { + if (&other == this) + return *this; m_has_value = other.m_has_value; m_value = other.m_value; + return *this; + } + + Optional &operator=(Optional &&other) noexcept + { + if (&other == this) + return *this; + m_has_value = other.m_has_value; + m_value = std::move(other.m_value); + other.m_has_value = false; + return *this; } T &value() @@ -71,6 +92,13 @@ public: const T &value_or(const T &def) const { return m_has_value ? m_value : def; } + // Unchecked access consistent with std::optional + T* operator->() { return &m_value; } + const T* operator->() const { return &m_value; } + + T& operator*() { return m_value; } + const T& operator*() const { return m_value; } + bool has_value() const noexcept { return m_has_value; } explicit operator bool() const { return m_has_value; } -- cgit v1.2.3 From 6910c8d920acedb3f1df1ac03a5cdf14f5fb6081 Mon Sep 17 00:00:00 2001 From: Wuzzy Date: Sun, 31 Oct 2021 22:33:33 +0000 Subject: Fix number of tool uses being off by 1..32767 (#11110) --- builtin/game/item.lua | 2 +- doc/lua_api.txt | 13 +-- games/devtest/mods/basetools/init.lua | 44 +++++---- .../mods/basetools/textures/basetools_dirtpick.png | Bin 307 -> 0 bytes games/devtest/mods/util_commands/init.lua | 53 +++++++++++ src/client/content_cao.cpp | 3 +- src/client/game.cpp | 3 +- src/network/serverpackethandler.cpp | 7 +- src/script/lua_api/l_object.cpp | 2 +- src/script/lua_api/l_util.cpp | 19 ++-- src/script/lua_api/l_util.h | 4 +- src/server/luaentity_sao.cpp | 8 +- src/server/luaentity_sao.h | 5 +- src/server/player_sao.cpp | 7 +- src/server/player_sao.h | 4 +- src/server/serveractiveobject.h | 7 +- src/tool.cpp | 99 ++++++++++++++++++--- src/tool.h | 18 ++-- 18 files changed, 228 insertions(+), 70 deletions(-) delete mode 100644 games/devtest/mods/basetools/textures/basetools_dirtpick.png (limited to 'src/script/lua_api') diff --git a/builtin/game/item.lua b/builtin/game/item.lua index 039947584..c9ccb8801 100644 --- a/builtin/game/item.lua +++ b/builtin/game/item.lua @@ -613,7 +613,7 @@ function core.node_dig(pos, node, digger) if wielded then local wdef = wielded:get_definition() local tp = wielded:get_tool_capabilities() - local dp = core.get_dig_params(def and def.groups, tp) + local dp = core.get_dig_params(def and def.groups, tp, wielded:get_wear()) if wdef and wdef.after_use then wielded = wdef.after_use(wielded, digger, node, dp) or wielded else diff --git a/doc/lua_api.txt b/doc/lua_api.txt index e47df4686..f3007671b 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -1953,8 +1953,9 @@ to implement this. ### Uses (tools only) Determines how many uses the tool has when it is used for digging a node, -of this group, of the maximum level. For lower leveled nodes, the use count -is multiplied by `3^leveldiff`. +of this group, of the maximum level. The maximum supported number of +uses is 65535. The special number 0 is used for infinite uses. +For lower leveled nodes, the use count is multiplied by `3^leveldiff`. `leveldiff` is the difference of the tool's `maxlevel` `groupcaps` and the node's `level` group. The node cannot be dug if `leveldiff` is less than zero. @@ -3475,8 +3476,8 @@ Helper functions * `minetest.pointed_thing_to_face_pos(placer, pointed_thing)`: returns a position. * returns the exact position on the surface of a pointed node -* `minetest.get_dig_params(groups, tool_capabilities)`: Simulates an item - that digs a node. +* `minetest.get_dig_params(groups, tool_capabilities [, wear])`: + Simulates an item that digs a node. Returns a table with the following fields: * `diggable`: `true` if node can be dug, `false` otherwise. * `time`: Time it would take to dig the node. @@ -3485,7 +3486,8 @@ Helper functions Parameters: * `groups`: Table of the node groups of the node that would be dug * `tool_capabilities`: Tool capabilities table of the item -* `minetest.get_hit_params(groups, tool_capabilities [, time_from_last_punch])`: + * `wear`: Amount of wear the tool starts with (default: 0) +* `minetest.get_hit_params(groups, tool_capabilities [, time_from_last_punch [, wear]])`: Simulates an item that punches an object. Returns a table with the following fields: * `hp`: How much damage the punch would cause. @@ -3494,6 +3496,7 @@ Helper functions * `groups`: Damage groups of the object * `tool_capabilities`: Tool capabilities table of the item * `time_from_last_punch`: time in seconds since last punch action + * `wear`: Amount of wear the item starts with (default: 0) diff --git a/games/devtest/mods/basetools/init.lua b/games/devtest/mods/basetools/init.lua index bd7480030..fd83b82eb 100644 --- a/games/devtest/mods/basetools/init.lua +++ b/games/devtest/mods/basetools/init.lua @@ -16,11 +16,11 @@ Tool types: Tool materials: -* Dirt: dig nodes of rating 3, one use only * Wood: dig nodes of rating 3 * Stone: dig nodes of rating 3 or 2 * Steel: dig nodes of rating 3, 2 or 1 * Mese: dig "everything" instantly +* n-Uses: can be used n times before breaking ]] -- The hand @@ -92,20 +92,6 @@ minetest.register_tool("basetools:pick_mese", { -- Pickaxes: Dig cracky -- --- This should break after only 1 use -minetest.register_tool("basetools:pick_dirt", { - description = "Dirt Pickaxe".."\n".. - "Digs cracky=3".."\n".. - "1 use only", - inventory_image = "basetools_dirtpick.png", - tool_capabilities = { - max_drop_level=0, - groupcaps={ - cracky={times={[3]=2.00}, uses=1, maxlevel=0} - }, - }, -}) - minetest.register_tool("basetools:pick_wood", { description = "Wooden Pickaxe".."\n".. "Digs cracky=3", @@ -348,3 +334,31 @@ minetest.register_tool("basetools:dagger_steel", { damage_groups = {fleshy=2}, } }) + +-- Test tool uses and punch_attack_uses +local uses = { 1, 2, 3, 5, 10, 50, 100, 1000, 10000, 65535 } +for i=1, #uses do + local u = uses[i] + local color = string.format("#FF00%02X", math.floor(((i-1)/#uses) * 255)) + minetest.register_tool("basetools:pick_uses_"..string.format("%05d", u), { + description = u.."-Uses Pickaxe".."\n".. + "Digs cracky=3", + inventory_image = "basetools_steelpick.png^[colorize:"..color..":127", + tool_capabilities = { + max_drop_level=0, + groupcaps={ + cracky={times={[3]=0.1, [2]=0.2, [1]=0.3}, uses=u, maxlevel=0} + }, + }, + }) + + minetest.register_tool("basetools:sword_uses_"..string.format("%05d", u), { + description = u.."-Uses Sword".."\n".. + "Damage: fleshy=1", + inventory_image = "basetools_woodsword.png^[colorize:"..color..":127", + tool_capabilities = { + damage_groups = {fleshy=1}, + punch_attack_uses = u, + }, + }) +end diff --git a/games/devtest/mods/basetools/textures/basetools_dirtpick.png b/games/devtest/mods/basetools/textures/basetools_dirtpick.png deleted file mode 100644 index 20a021d72..000000000 Binary files a/games/devtest/mods/basetools/textures/basetools_dirtpick.png and /dev/null differ diff --git a/games/devtest/mods/util_commands/init.lua b/games/devtest/mods/util_commands/init.lua index ca5dca2d9..79acaa0d0 100644 --- a/games/devtest/mods/util_commands/init.lua +++ b/games/devtest/mods/util_commands/init.lua @@ -114,6 +114,59 @@ minetest.register_chatcommand("detach", { end, }) +minetest.register_chatcommand("use_tool", { + params = "(dig ) | (hit ) []", + description = "Apply tool wear a number of times, as if it were used for digging", + func = function(name, param) + local player = minetest.get_player_by_name(name) + if not player then + return false, "No player." + end + local mode, group, level, uses = string.match(param, "([a-z]+) ([a-z0-9]+) (-?%d+) (%d+)") + if not mode then + mode, group, level = string.match(param, "([a-z]+) ([a-z0-9]+) (-?%d+)") + uses = 1 + end + if not mode or not group or not level then + return false + end + if mode ~= "dig" and mode ~= "hit" then + return false + end + local tool = player:get_wielded_item() + local caps = tool:get_tool_capabilities() + if not caps or tool:get_count() == 0 then + return false, "No tool in hand." + end + local actual_uses = 0 + for u=1, uses do + local wear = tool:get_wear() + local dp + if mode == "dig" then + dp = minetest.get_dig_params({[group]=3, level=level}, caps, wear) + else + dp = minetest.get_hit_params({[group]=100}, caps, level, wear) + end + tool:add_wear(dp.wear) + actual_uses = actual_uses + 1 + if tool:get_count() == 0 then + break + end + end + player:set_wielded_item(tool) + if tool:get_count() == 0 then + return true, string.format("Tool used %d time(s). ".. + "The tool broke after %d use(s).", uses, actual_uses) + else + local wear = tool:get_wear() + return true, string.format("Tool used %d time(s). ".. + "Final wear=%d", uses, wear) + end + end, +}) + + + -- Use this to test waypoint capabilities minetest.register_chatcommand("test_waypoints", { params = "[change_immediate]", diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index 1e79d00c9..5c8465b22 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -1870,7 +1870,8 @@ bool GenericCAO::directReportPunch(v3f dir, const ItemStack *punchitem, m_armor_groups, toolcap, punchitem, - time_from_last_punch); + time_from_last_punch, + punchitem->wear); if(result.did_punch && result.damage != 0) { diff --git a/src/client/game.cpp b/src/client/game.cpp index 57951dc95..7f0aff49c 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -3619,7 +3619,8 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos, // cheat detection. // Get digging parameters DigParams params = getDigParams(nodedef_manager->get(n).groups, - &selected_item.getToolCapabilities(itemdef_manager)); + &selected_item.getToolCapabilities(itemdef_manager), + selected_item.wear); // If can't dig, try hand if (!params.diggable) { diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index d4bef3ca2..c1ddb5005 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -1119,8 +1119,8 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) float time_from_last_punch = playersao->resetTimeFromLastPunch(); - u16 wear = pointed_object->punch(dir, &toolcap, playersao, - time_from_last_punch); + u32 wear = pointed_object->punch(dir, &toolcap, playersao, + time_from_last_punch, tool_item.wear); // Callback may have changed item, so get it again playersao->getWieldedItem(&selected_item); @@ -1173,7 +1173,8 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) // Get diggability and expected digging time DigParams params = getDigParams(m_nodedef->get(n).groups, - &selected_item.getToolCapabilities(m_itemdef)); + &selected_item.getToolCapabilities(m_itemdef), + selected_item.wear); // If can't dig, try hand if (!params.diggable) { params = getDigParams(m_nodedef->get(n).groups, diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index b7185f7ec..072b13d80 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -174,7 +174,7 @@ int ObjectRef::l_punch(lua_State *L) v3f dir = readParam(L, 5, sao->getBasePosition() - puncher->getBasePosition()); dir.normalize(); - u16 wear = sao->punch(dir, &toolcap, puncher, time_from_last_punch); + u32 wear = sao->punch(dir, &toolcap, puncher, time_from_last_punch); lua_pushnumber(L, wear); return 1; diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index 2405cd90d..53319ccfd 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -160,28 +160,33 @@ int ModApiUtil::l_write_json(lua_State *L) return 1; } -// get_dig_params(groups, tool_capabilities) +// get_dig_params(groups, tool_capabilities[, wear]) int ModApiUtil::l_get_dig_params(lua_State *L) { NO_MAP_LOCK_REQUIRED; ItemGroupList groups; read_groups(L, 1, groups); ToolCapabilities tp = read_tool_capabilities(L, 2); - push_dig_params(L, getDigParams(groups, &tp)); + if (lua_isnoneornil(L, 3)) { + push_dig_params(L, getDigParams(groups, &tp)); + } else { + u16 wear = readParam(L, 3); + push_dig_params(L, getDigParams(groups, &tp, wear)); + } return 1; } -// get_hit_params(groups, tool_capabilities[, time_from_last_punch]) +// get_hit_params(groups, tool_capabilities[, time_from_last_punch, [, wear]]) int ModApiUtil::l_get_hit_params(lua_State *L) { NO_MAP_LOCK_REQUIRED; std::unordered_map groups; read_groups(L, 1, groups); ToolCapabilities tp = read_tool_capabilities(L, 2); - if(lua_isnoneornil(L, 3)) - push_hit_params(L, getHitParams(groups, &tp)); - else - push_hit_params(L, getHitParams(groups, &tp, readParam(L, 3))); + float time_from_last_punch = readParam(L, 3, 1000000); + int wear = readParam(L, 4, 0); + push_hit_params(L, getHitParams(groups, &tp, + time_from_last_punch, wear)); return 1; } diff --git a/src/script/lua_api/l_util.h b/src/script/lua_api/l_util.h index cc91e8d39..314e92f5c 100644 --- a/src/script/lua_api/l_util.h +++ b/src/script/lua_api/l_util.h @@ -50,10 +50,10 @@ private: // write_json(data[, styled]) static int l_write_json(lua_State *L); - // get_dig_params(groups, tool_capabilities[, time_from_last_punch]) + // get_dig_params(groups, tool_capabilities[, wear]) static int l_get_dig_params(lua_State *L); - // get_hit_params(groups, tool_capabilities[, time_from_last_punch]) + // get_hit_params(groups, tool_capabilities[, time_from_last_punch[, wear]]) static int l_get_hit_params(lua_State *L); // check_password_entry(name, entry, password) diff --git a/src/server/luaentity_sao.cpp b/src/server/luaentity_sao.cpp index 1d65ac306..82f6da231 100644 --- a/src/server/luaentity_sao.cpp +++ b/src/server/luaentity_sao.cpp @@ -305,10 +305,11 @@ void LuaEntitySAO::getStaticData(std::string *result) const *result = os.str(); } -u16 LuaEntitySAO::punch(v3f dir, +u32 LuaEntitySAO::punch(v3f dir, const ToolCapabilities *toolcap, ServerActiveObject *puncher, - float time_from_last_punch) + float time_from_last_punch, + u16 initial_wear) { if (!m_registered) { // Delete unknown LuaEntities when punched @@ -326,7 +327,8 @@ u16 LuaEntitySAO::punch(v3f dir, m_armor_groups, toolcap, &tool_item, - time_from_last_punch); + time_from_last_punch, + initial_wear); bool damage_handled = m_env->getScriptIface()->luaentity_Punch(m_id, puncher, time_from_last_punch, toolcap, dir, result.did_punch ? result.damage : 0); diff --git a/src/server/luaentity_sao.h b/src/server/luaentity_sao.h index 6883ae1b9..87b664a8b 100644 --- a/src/server/luaentity_sao.h +++ b/src/server/luaentity_sao.h @@ -44,9 +44,10 @@ public: bool isStaticAllowed() const { return m_prop.static_save; } bool shouldUnload() const { return true; } void getStaticData(std::string *result) const; - u16 punch(v3f dir, const ToolCapabilities *toolcap = nullptr, + u32 punch(v3f dir, const ToolCapabilities *toolcap = nullptr, ServerActiveObject *puncher = nullptr, - float time_from_last_punch = 1000000.0f); + float time_from_last_punch = 1000000.0f, + u16 initial_wear = 0); void rightClick(ServerActiveObject *clicker); void setPos(const v3f &pos); void moveTo(v3f pos, bool continuous); diff --git a/src/server/player_sao.cpp b/src/server/player_sao.cpp index 690823bb7..83e17f830 100644 --- a/src/server/player_sao.cpp +++ b/src/server/player_sao.cpp @@ -409,10 +409,11 @@ void PlayerSAO::setLookPitchAndSend(const float pitch) m_env->getGameDef()->SendMovePlayer(m_peer_id); } -u16 PlayerSAO::punch(v3f dir, +u32 PlayerSAO::punch(v3f dir, const ToolCapabilities *toolcap, ServerActiveObject *puncher, - float time_from_last_punch) + float time_from_last_punch, + u16 initial_wear) { if (!toolcap) return 0; @@ -430,7 +431,7 @@ u16 PlayerSAO::punch(v3f dir, s32 old_hp = getHP(); HitParams hitparams = getHitParams(m_armor_groups, toolcap, - time_from_last_punch); + time_from_last_punch, initial_wear); PlayerSAO *playersao = m_player->getPlayerSAO(); diff --git a/src/server/player_sao.h b/src/server/player_sao.h index 1429d7129..47fe85413 100644 --- a/src/server/player_sao.h +++ b/src/server/player_sao.h @@ -109,8 +109,8 @@ public: Interaction interface */ - u16 punch(v3f dir, const ToolCapabilities *toolcap, ServerActiveObject *puncher, - float time_from_last_punch); + u32 punch(v3f dir, const ToolCapabilities *toolcap, ServerActiveObject *puncher, + float time_from_last_punch, u16 initial_wear = 0); void rightClick(ServerActiveObject *clicker); void setHP(s32 hp, const PlayerHPChangeReason &reason) override { diff --git a/src/server/serveractiveobject.h b/src/server/serveractiveobject.h index 51f445914..5b0ee2d9b 100644 --- a/src/server/serveractiveobject.h +++ b/src/server/serveractiveobject.h @@ -145,11 +145,12 @@ public: virtual bool shouldUnload() const { return true; } - // Returns tool wear - virtual u16 punch(v3f dir, + // Returns added tool wear + virtual u32 punch(v3f dir, const ToolCapabilities *toolcap = nullptr, ServerActiveObject *puncher = nullptr, - float time_from_last_punch = 1000000.0f) + float time_from_last_punch = 1000000.0f, + u16 initial_wear = 0) { return 0; } virtual void rightClick(ServerActiveObject *clicker) {} diff --git a/src/tool.cpp b/src/tool.cpp index 3f639a69e..b0749286d 100644 --- a/src/tool.cpp +++ b/src/tool.cpp @@ -183,9 +183,74 @@ void ToolCapabilities::deserializeJson(std::istream &is) } } +static u32 calculateResultWear(const u32 uses, const u16 initial_wear) +{ + if (uses == 0) { + // Trivial case: Infinite uses + return 0; + } + /* Finite uses. This is not trivial, + as the maximum wear is not neatly evenly divisible by + most possible uses numbers. For example, for 128 + uses, the calculation of wear is trivial, as + 65536 / 128 uses = 512 wear, + so the tool will get 512 wear 128 times in its lifetime. + But for a number like 130, this does not work: + 65536 / 130 uses = 504.123... wear. + Since wear must be an integer, we will get + 504*130 = 65520, which would lead to the wrong number + of uses. + + Instead, we partition the "wear range" into blocks: + A block represents a single use and can be + of two possible sizes: normal and oversized. + A normal block is equal to floor(65536 / uses). + An oversized block is a normal block plus 1. + Then we determine how many oversized and normal + blocks we need and finally, whether we add + the normal wear or the oversized wear. + + Example for 130 uses: + * Normal wear = 504 + * Number of normal blocks = 114 + * Oversized wear = 505 + * Number of oversized blocks = 16 + + If we add everything together, we get: + 114*504 + 16*505 = 65536 + */ + u32 result_wear; + u32 wear_normal = ((U16_MAX+1) / uses); + // Will be non-zero if its not evenly divisible + u16 blocks_oversize = (U16_MAX+1) % uses; + // Whether to add one extra wear point in case + // of oversized wear. + u16 wear_extra = 0; + if (blocks_oversize > 0) { + u16 blocks_normal = uses - blocks_oversize; + /* When the wear has reached this value, we + know that wear_normal has been applied + for blocks_normal times, therefore, + only oversized blocks remain. + This also implies the raw tool wear number + increases a bit faster after this point, + but this should be barely noticable by the + player. + */ + u16 wear_extra_at = blocks_normal * wear_normal; + if (initial_wear >= wear_extra_at) { + wear_extra = 1; + } + } + result_wear = wear_normal + wear_extra; + return result_wear; +} + DigParams getDigParams(const ItemGroupList &groups, - const ToolCapabilities *tp) + const ToolCapabilities *tp, + const u16 initial_wear) { + // Group dig_immediate defaults to fixed time and no wear if (tp->groupcaps.find("dig_immediate") == tp->groupcaps.cend()) { switch (itemgroup_get(groups, "dig_immediate")) { @@ -201,7 +266,7 @@ DigParams getDigParams(const ItemGroupList &groups, // Values to be returned (with a bit of conversion) bool result_diggable = false; float result_time = 0.0; - float result_wear = 0.0; + u32 result_wear = 0; std::string result_main_group; int level = itemgroup_get(groups, "level"); @@ -224,20 +289,22 @@ DigParams getDigParams(const ItemGroupList &groups, if (!result_diggable || time < result_time) { result_time = time; result_diggable = true; - if (cap.uses != 0) - result_wear = 1.0 / cap.uses / pow(3.0, leveldiff); - else - result_wear = 0; + // The actual number of uses increases + // exponentially with leveldiff. + // If the levels are equal, real_uses equals cap.uses. + u32 real_uses = cap.uses * pow(3.0, leveldiff); + real_uses = MYMIN(real_uses, U16_MAX); + result_wear = calculateResultWear(real_uses, initial_wear); result_main_group = groupname; } } - u16 wear_i = U16_MAX * result_wear; - return DigParams(result_diggable, result_time, wear_i, result_main_group); + return DigParams(result_diggable, result_time, result_wear, result_main_group); } HitParams getHitParams(const ItemGroupList &armor_groups, - const ToolCapabilities *tp, float time_from_last_punch) + const ToolCapabilities *tp, float time_from_last_punch, + u16 initial_wear) { s16 damage = 0; float result_wear = 0.0f; @@ -249,10 +316,12 @@ HitParams getHitParams(const ItemGroupList &armor_groups, damage += damageGroup.second * punch_interval_multiplier * armor / 100.0; } - if (tp->punch_attack_uses > 0) - result_wear = 1.0f / tp->punch_attack_uses * punch_interval_multiplier; + if (tp->punch_attack_uses > 0) { + result_wear = calculateResultWear(tp->punch_attack_uses, initial_wear); + result_wear *= punch_interval_multiplier; + } - u16 wear_i = U16_MAX * result_wear; + u32 wear_i = (u32) result_wear; return {damage, wear_i}; } @@ -266,7 +335,8 @@ PunchDamageResult getPunchDamage( const ItemGroupList &armor_groups, const ToolCapabilities *toolcap, const ItemStack *punchitem, - float time_from_last_punch + float time_from_last_punch, + u16 initial_wear ){ bool do_hit = true; { @@ -286,7 +356,8 @@ PunchDamageResult getPunchDamage( if(do_hit) { HitParams hitparams = getHitParams(armor_groups, toolcap, - time_from_last_punch); + time_from_last_punch, + punchitem->wear); result.did_punch = true; result.wear = hitparams.wear; result.damage = hitparams.hp; diff --git a/src/tool.h b/src/tool.h index 59dd501f5..0e3388485 100644 --- a/src/tool.h +++ b/src/tool.h @@ -88,10 +88,10 @@ struct DigParams // Digging time in seconds float time; // Caused wear - u16 wear; + u32 wear; // u32 because wear could be 65536 (single-use tool) std::string main_group; - DigParams(bool a_diggable = false, float a_time = 0.0f, u16 a_wear = 0, + DigParams(bool a_diggable = false, float a_time = 0.0f, u32 a_wear = 0, const std::string &a_main_group = ""): diggable(a_diggable), time(a_time), @@ -101,21 +101,24 @@ struct DigParams }; DigParams getDigParams(const ItemGroupList &groups, - const ToolCapabilities *tp); + const ToolCapabilities *tp, + const u16 initial_wear = 0); struct HitParams { s16 hp; - u16 wear; + // Caused wear + u32 wear; // u32 because wear could be 65536 (single-use weapon) - HitParams(s16 hp_ = 0, u16 wear_ = 0): + HitParams(s16 hp_ = 0, u32 wear_ = 0): hp(hp_), wear(wear_) {} }; HitParams getHitParams(const ItemGroupList &armor_groups, - const ToolCapabilities *tp, float time_from_last_punch); + const ToolCapabilities *tp, float time_from_last_punch, + u16 initial_wear = 0); HitParams getHitParams(const ItemGroupList &armor_groups, const ToolCapabilities *tp); @@ -135,7 +138,8 @@ PunchDamageResult getPunchDamage( const ItemGroupList &armor_groups, const ToolCapabilities *toolcap, const ItemStack *punchitem, - float time_from_last_punch + float time_from_last_punch, + u16 initial_wear = 0 ); f32 getToolRange(const ItemDefinition &def_selected, const ItemDefinition &def_hand); -- cgit v1.2.3 From cbf658f83d206bf340ab4aa8eab02b058e9b293f Mon Sep 17 00:00:00 2001 From: Elijah Duffy Date: Wed, 10 Nov 2021 10:10:20 -0800 Subject: Lua API: Add `rmdir`, `cpdir` and `mvdir` (#9638) Co-authored-by: rubenwardy --- doc/lua_api.txt | 13 ++++++++++++ src/script/lua_api/l_util.cpp | 49 +++++++++++++++++++++++++++++++++++++++++++ src/script/lua_api/l_util.h | 9 ++++++++ 3 files changed, 71 insertions(+) (limited to 'src/script/lua_api') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index f3007671b..3b9f4c339 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -4624,6 +4624,19 @@ Utilities * `minetest.mkdir(path)`: returns success. * Creates a directory specified by `path`, creating parent directories if they don't exist. +* `minetest.rmdir(path, recursive)`: returns success. + * Removes a directory specified by `path`. + * If `recursive` is set to `true`, the directory is recursively removed. + Otherwise, the directory will only be removed if it is empty. + * Returns true on success, false on failure. +* `minetest.cpdir(source, destination)`: returns success. + * Copies a directory specified by `path` to `destination` + * Any files in `destination` will be overwritten if they already exist. + * Returns true on success, false on failure. +* `minetest.mvdir(source, destination)`: returns success. + * Moves a directory specified by `path` to `destination`. + * If the `destination` is a non-empty directory, then the move will fail. + * Returns true on success, false on failure. * `minetest.get_dir_list(path, [is_dir])`: returns list of entry names * is_dir is one of: * nil: return all entries, diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index 53319ccfd..528d9c6dd 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -349,6 +349,49 @@ int ModApiUtil::l_mkdir(lua_State *L) return 1; } +// rmdir(path, recursive) +int ModApiUtil::l_rmdir(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + const char *path = luaL_checkstring(L, 1); + CHECK_SECURE_PATH(L, path, true); + + bool recursive = readParam(L, 2, false); + + if (recursive) + lua_pushboolean(L, fs::RecursiveDelete(path)); + else + lua_pushboolean(L, fs::DeleteSingleFileOrEmptyDirectory(path)); + + return 1; +} + +// cpdir(source, destination) +int ModApiUtil::l_cpdir(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + const char *source = luaL_checkstring(L, 1); + const char *destination = luaL_checkstring(L, 2); + CHECK_SECURE_PATH(L, source, false); + CHECK_SECURE_PATH(L, destination, true); + + lua_pushboolean(L, fs::CopyDir(source, destination)); + return 1; +} + +// mpdir(source, destination) +int ModApiUtil::l_mvdir(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + const char *source = luaL_checkstring(L, 1); + const char *destination = luaL_checkstring(L, 2); + CHECK_SECURE_PATH(L, source, true); + CHECK_SECURE_PATH(L, destination, true); + + lua_pushboolean(L, fs::MoveDir(source, destination)); + return 1; +} + // get_dir_list(path, is_dir) int ModApiUtil::l_get_dir_list(lua_State *L) { @@ -588,6 +631,9 @@ void ModApiUtil::Initialize(lua_State *L, int top) API_FCT(decompress); API_FCT(mkdir); + API_FCT(rmdir); + API_FCT(cpdir); + API_FCT(mvdir); API_FCT(get_dir_list); API_FCT(safe_file_write); @@ -651,6 +697,9 @@ void ModApiUtil::InitializeAsync(lua_State *L, int top) API_FCT(decompress); API_FCT(mkdir); + API_FCT(rmdir); + API_FCT(cpdir); + API_FCT(mvdir); API_FCT(get_dir_list); API_FCT(encode_base64); diff --git a/src/script/lua_api/l_util.h b/src/script/lua_api/l_util.h index 314e92f5c..fcf8a1057 100644 --- a/src/script/lua_api/l_util.h +++ b/src/script/lua_api/l_util.h @@ -80,6 +80,15 @@ private: // mkdir(path) static int l_mkdir(lua_State *L); + // rmdir(path, recursive) + static int l_rmdir(lua_State *L); + + // cpdir(source, destination, remove_source) + static int l_cpdir(lua_State *L); + + // mvdir(source, destination) + static int l_mvdir(lua_State *L); + // get_dir_list(path, is_dir) static int l_get_dir_list(lua_State *L); -- cgit v1.2.3 From 87ab97da2ace31fdb46a88a0901ec664dd666feb Mon Sep 17 00:00:00 2001 From: sfan5 Date: Fri, 26 Nov 2021 19:32:41 +0100 Subject: Fix find_nodes_in_area misbehaving with out-of-map coordinates (#11770) This ensures that no overflows (side-effects) happen within the find_nodes_in_area function by limiting coordinates like done in the map generation code. --- src/script/lua_api/l_env.cpp | 31 +++++++++++++++++-------------- src/unittest/test_voxelarea.cpp | 18 ++++++++++++++++-- 2 files changed, 33 insertions(+), 16 deletions(-) (limited to 'src/script/lua_api') diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index 2c709d31b..18ee3a521 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -880,6 +880,21 @@ int ModApiEnvMod::l_find_node_near(lua_State *L) return 0; } +static void checkArea(v3s16 &minp, v3s16 &maxp) +{ + auto volume = VoxelArea(minp, maxp).getVolume(); + // Volume limit equal to 8 default mapchunks, (80 * 2) ^ 3 = 4,096,000 + if (volume > 4096000) { + throw LuaError("Area volume exceeds allowed value of 4096000"); + } + + // Clamp to map range to avoid problems +#define CLAMP(arg) core::clamp(arg, (s16)-MAX_MAP_GENERATION_LIMIT, (s16)MAX_MAP_GENERATION_LIMIT) + minp = v3s16(CLAMP(minp.X), CLAMP(minp.Y), CLAMP(minp.Z)); + maxp = v3s16(CLAMP(maxp.X), CLAMP(maxp.Y), CLAMP(maxp.Z)); +#undef CLAMP +} + // find_nodes_in_area(minp, maxp, nodenames, [grouped]) int ModApiEnvMod::l_find_nodes_in_area(lua_State *L) { @@ -899,13 +914,7 @@ int ModApiEnvMod::l_find_nodes_in_area(lua_State *L) } #endif - v3s16 cube = maxp - minp + 1; - // Volume limit equal to 8 default mapchunks, (80 * 2) ^ 3 = 4,096,000 - if ((u64)cube.X * (u64)cube.Y * (u64)cube.Z > 4096000) { - luaL_error(L, "find_nodes_in_area(): area volume" - " exceeds allowed value of 4096000"); - return 0; - } + checkArea(minp, maxp); std::vector filter; collectNodeIds(L, 3, ndef, filter); @@ -1010,13 +1019,7 @@ int ModApiEnvMod::l_find_nodes_in_area_under_air(lua_State *L) } #endif - v3s16 cube = maxp - minp + 1; - // Volume limit equal to 8 default mapchunks, (80 * 2) ^ 3 = 4,096,000 - if ((u64)cube.X * (u64)cube.Y * (u64)cube.Z > 4096000) { - luaL_error(L, "find_nodes_in_area_under_air(): area volume" - " exceeds allowed value of 4096000"); - return 0; - } + checkArea(minp, maxp); std::vector filter; collectNodeIds(L, 3, ndef, filter); diff --git a/src/unittest/test_voxelarea.cpp b/src/unittest/test_voxelarea.cpp index 6ec0412d5..9826d2ee7 100644 --- a/src/unittest/test_voxelarea.cpp +++ b/src/unittest/test_voxelarea.cpp @@ -30,6 +30,7 @@ public: void test_addarea(); void test_pad(); + void test_extent(); void test_volume(); void test_contains_voxelarea(); void test_contains_point(); @@ -65,6 +66,7 @@ void TestVoxelArea::runTests(IGameDef *gamedef) { TEST(test_addarea); TEST(test_pad); + TEST(test_extent); TEST(test_volume); TEST(test_contains_voxelarea); TEST(test_contains_point); @@ -113,10 +115,22 @@ void TestVoxelArea::test_pad() UASSERT(v1.MaxEdge == v3s16(-47, -9347, 969)); } +void TestVoxelArea::test_extent() +{ + VoxelArea v1(v3s16(-1337, -547, -789), v3s16(-147, 447, 669)); + UASSERT(v1.getExtent() == v3s16(1191, 995, 1459)); + + VoxelArea v2(v3s16(32493, -32507, 32753), v3s16(32508, -32492, 32768)); + UASSERT(v2.getExtent() == v3s16(16, 16, 16)); +} + void TestVoxelArea::test_volume() { - VoxelArea v1(v3s16(-1337, 447, -789), v3s16(-147, -9547, 669)); - UASSERTEQ(s32, v1.getVolume(), -184657133); + VoxelArea v1(v3s16(-1337, -547, -789), v3s16(-147, 447, 669)); + UASSERTEQ(s32, v1.getVolume(), 1728980655); + + VoxelArea v2(v3s16(32493, -32507, 32753), v3s16(32508, -32492, 32768)); + UASSERTEQ(s32, v2.getVolume(), 4096); } void TestVoxelArea::test_contains_voxelarea() -- cgit v1.2.3 From 413be76c63309266d3d271f01cc74385067d7263 Mon Sep 17 00:00:00 2001 From: Corey Powell Date: Fri, 26 Nov 2021 14:19:40 -0500 Subject: Implemented disconnect_player (#10492) Co-authored-by: rubenwardy --- builtin/game/misc.lua | 10 ++++++++++ doc/lua_api.txt | 4 ++++ src/script/lua_api/l_server.cpp | 12 ++++++------ src/script/lua_api/l_server.h | 4 ++-- 4 files changed, 22 insertions(+), 8 deletions(-) (limited to 'src/script/lua_api') diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua index 05237662c..ef826eda7 100644 --- a/builtin/game/misc.lua +++ b/builtin/game/misc.lua @@ -6,6 +6,16 @@ local S = core.get_translator("__builtin") -- Misc. API functions -- +-- @spec core.kick_player(String, String) :: Boolean +function core.kick_player(player_name, reason) + if type(reason) == "string" then + reason = "Kicked: " .. reason + else + reason = "Kicked." + end + return core.disconnect_player(player_name, reason) +end + function core.check_player_privs(name, ...) if core.is_player(name) then name = name:get_player_name() diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 0a63642af..aff739cfb 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -5741,6 +5741,10 @@ Bans * `minetest.kick_player(name, [reason])`: disconnect a player with an optional reason. * Returns boolean indicating success (false if player nonexistant) +* `minetest.disconnect_player(name, [reason])`: disconnect a player with an + optional reason, this will not prefix with 'Kicked: ' like kick_player. + If no reason is given, it will default to 'Disconnected.' + * Returns boolean indicating success (false if player nonexistant) Particles --------- diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index 476f74c9c..82a692070 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -310,8 +310,8 @@ int ModApiServer::l_ban_player(lua_State *L) return 1; } -// kick_player(name, [reason]) -> success -int ModApiServer::l_kick_player(lua_State *L) +// disconnect_player(name, [reason]) -> success +int ModApiServer::l_disconnect_player(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -319,11 +319,11 @@ int ModApiServer::l_kick_player(lua_State *L) throw LuaError("Can't kick player before server has started up"); const char *name = luaL_checkstring(L, 1); - std::string message("Kicked"); + std::string message; if (lua_isstring(L, 2)) - message.append(": ").append(readParam(L, 2)); + message.append(readParam(L, 2)); else - message.append("."); + message.append("Disconnected."); RemotePlayer *player = dynamic_cast(getEnv(L))->getPlayer(name); if (player == NULL) { @@ -554,7 +554,7 @@ void ModApiServer::Initialize(lua_State *L, int top) API_FCT(get_ban_list); API_FCT(get_ban_description); API_FCT(ban_player); - API_FCT(kick_player); + API_FCT(disconnect_player); API_FCT(remove_player); API_FCT(unban_player_or_ip); API_FCT(notify_authentication_modified); diff --git a/src/script/lua_api/l_server.h b/src/script/lua_api/l_server.h index a6f709787..f05c0b7c9 100644 --- a/src/script/lua_api/l_server.h +++ b/src/script/lua_api/l_server.h @@ -97,8 +97,8 @@ private: // unban_player_or_ip() static int l_unban_player_or_ip(lua_State *L); - // kick_player(name, [message]) -> success - static int l_kick_player(lua_State *L); + // disconnect_player(name, [reason]) -> success + static int l_disconnect_player(lua_State *L); // remove_player(name) static int l_remove_player(lua_State *L); -- cgit v1.2.3 From 8472141b79c25092c90dea24aa873bd7ff792142 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sat, 18 Dec 2021 20:36:43 +0100 Subject: Restructure devtest's unittests and run them in CI (#11859) --- .github/workflows/build.yml | 2 +- games/devtest/mods/unittests/crafting.lua | 18 +- games/devtest/mods/unittests/init.lua | 199 ++++++++++++++++++++++- games/devtest/mods/unittests/itemdescription.lua | 3 +- games/devtest/mods/unittests/misc.lua | 38 +++++ games/devtest/mods/unittests/player.lua | 46 +++--- games/devtest/mods/unittests/random.lua | 10 -- src/script/cpp_api/s_base.h | 9 + src/script/cpp_api/s_server.cpp | 6 +- src/script/cpp_api/s_server.h | 2 +- src/script/lua_api/l_server.cpp | 2 +- util/test_multiplayer.sh | 26 ++- 12 files changed, 289 insertions(+), 72 deletions(-) create mode 100644 games/devtest/mods/unittests/misc.lua delete mode 100644 games/devtest/mods/unittests/random.lua (limited to 'src/script/lua_api') diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 417b4f650..af1de15ec 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -93,7 +93,7 @@ jobs: run: | ./bin/minetest --run-unittests - - name: Integration test + - name: Integration test + devtest run: | ./util/test_multiplayer.sh diff --git a/games/devtest/mods/unittests/crafting.lua b/games/devtest/mods/unittests/crafting.lua index eff13ce09..8c16d3efb 100644 --- a/games/devtest/mods/unittests/crafting.lua +++ b/games/devtest/mods/unittests/crafting.lua @@ -1,6 +1,7 @@ +dofile(core.get_modpath(core.get_current_modname()) .. "/crafting_prepare.lua") + -- Test minetest.clear_craft function local function test_clear_craft() - minetest.log("info", "[unittests] Testing minetest.clear_craft") -- Clearing by output minetest.register_craft({ output = "foo", @@ -22,11 +23,10 @@ local function test_clear_craft() minetest.clear_craft({recipe={{"foo", "bar"}}}) assert(minetest.get_all_craft_recipes("foo") == nil) end +unittests.register("test_clear_craft", test_clear_craft) -- Test minetest.get_craft_result function local function test_get_craft_result() - minetest.log("info", "[unittests] Testing minetest.get_craft_result") - -- normal local input = { method = "normal", @@ -107,14 +107,6 @@ local function test_get_craft_result() assert(output.item) minetest.log("info", "[unittests] unrepairable tool crafting output.item:to_table(): "..dump(output.item:to_table())) -- unrepairable tool must not yield any output - assert(output.item:get_name() == "") - + assert(output.item:is_empty()) end - -function unittests.test_crafting() - test_clear_craft() - test_get_craft_result() - minetest.log("action", "[unittests] Crafting tests passed!") - return true -end - +unittests.register("test_get_craft_result", test_get_craft_result) diff --git a/games/devtest/mods/unittests/init.lua b/games/devtest/mods/unittests/init.lua index 12c67f78b..0754d507f 100644 --- a/games/devtest/mods/unittests/init.lua +++ b/games/devtest/mods/unittests/init.lua @@ -1,18 +1,199 @@ unittests = {} +unittests.list = {} + +-- name: Name of the test +-- func: +-- for sync: function(player, pos), should error on failure +-- for async: function(callback, player, pos) +-- MUST call callback() or callback("error msg") in case of error once test is finished +-- this means you cannot use assert() in the test implementation +-- opts: { +-- player = false, -- Does test require a player? +-- map = false, -- Does test require map access? +-- async = false, -- Does the test run asynchronously? (read notes above!) +-- } +function unittests.register(name, func, opts) + local def = table.copy(opts or {}) + def.name = name + def.func = func + table.insert(unittests.list, def) +end + +function unittests.on_finished(all_passed) + -- free to override +end + +-- Calls invoke with a callback as argument +-- Suspends coroutine until that callback is called +-- Return values are passed through +local function await(invoke) + local co = coroutine.running() + assert(co) + local called_early = true + invoke(function(...) + if called_early == true then + called_early = {...} + else + coroutine.resume(co, ...) + end + end) + if called_early ~= true then + -- callback was already called before yielding + return unpack(called_early) + end + called_early = nil + return coroutine.yield() +end + +function unittests.run_one(idx, counters, out_callback, player, pos) + local def = unittests.list[idx] + if not def.player then + player = nil + elseif player == nil then + out_callback(false) + return false + end + if not def.map then + pos = nil + elseif pos == nil then + out_callback(false) + return false + end + + local tbegin = core.get_us_time() + local function done(status, err) + local tend = core.get_us_time() + local ms_taken = (tend - tbegin) / 1000 + + if not status then + core.log("error", err) + end + print(string.format("[%s] %s - %dms", + status and "PASS" or "FAIL", def.name, ms_taken)) + counters.time = counters.time + ms_taken + counters.total = counters.total + 1 + if status then + counters.passed = counters.passed + 1 + end + end + + if def.async then + core.log("info", "[unittest] running " .. def.name .. " (async)") + def.func(function(err) + done(err == nil, err) + out_callback(true) + end, player, pos) + else + core.log("info", "[unittest] running " .. def.name) + local status, err = pcall(def.func, player, pos) + done(status, err) + out_callback(true) + end + + return true +end + +local function wait_for_player(callback) + if #core.get_connected_players() > 0 then + return callback(core.get_connected_players()[1]) + end + local first = true + core.register_on_joinplayer(function(player) + if first then + callback(player) + first = false + end + end) +end + +local function wait_for_map(player, callback) + local check = function() + if core.get_node_or_nil(player:get_pos()) ~= nil then + callback() + else + minetest.after(0, check) + end + end + check() +end + +function unittests.run_all() + -- This runs in a coroutine so it uses await(). + local counters = { time = 0, total = 0, passed = 0 } + + -- Run standalone tests first + for idx = 1, #unittests.list do + local def = unittests.list[idx] + def.done = await(function(cb) + unittests.run_one(idx, counters, cb, nil, nil) + end) + end + + -- Wait for a player to join, run tests that require a player + local player = await(wait_for_player) + for idx = 1, #unittests.list do + local def = unittests.list[idx] + if not def.done then + def.done = await(function(cb) + unittests.run_one(idx, counters, cb, player, nil) + end) + end + end + + -- Wait for the world to generate/load, run tests that require map access + await(function(cb) + wait_for_map(player, cb) + end) + local pos = vector.round(player:get_pos()) + for idx = 1, #unittests.list do + local def = unittests.list[idx] + if not def.done then + def.done = await(function(cb) + unittests.run_one(idx, counters, cb, player, pos) + end) + end + end + + -- Print stats + assert(#unittests.list == counters.total) + print(string.rep("+", 80)) + print(string.format("Unit Test Results: %s", + counters.total == counters.passed and "PASSED" or "FAILED")) + print(string.format(" %d / %d failed tests.", + counters.total - counters.passed, counters.total)) + print(string.format(" Testing took %dms total.", counters.time)) + print(string.rep("+", 80)) + unittests.on_finished(counters.total == counters.passed) + return counters.total == counters.passed +end + +-------------- + local modpath = minetest.get_modpath("unittests") -dofile(modpath .. "/random.lua") +dofile(modpath .. "/misc.lua") dofile(modpath .. "/player.lua") -dofile(modpath .. "/crafting_prepare.lua") dofile(modpath .. "/crafting.lua") dofile(modpath .. "/itemdescription.lua") -if minetest.settings:get_bool("devtest_unittests_autostart", false) then - unittests.test_random() - unittests.test_crafting() - unittests.test_short_desc() - minetest.register_on_joinplayer(function(player) - unittests.test_player(player) +-------------- + +if core.settings:get_bool("devtest_unittests_autostart", false) then + core.after(0, function() + coroutine.wrap(unittests.run_all)() end) +else + minetest.register_chatcommand("unittests", { + privs = {basic_privs=true}, + description = "Runs devtest unittests (may modify player or map state)", + func = function(name, param) + unittests.on_finished = function(ok) + core.chat_send_player(name, + (ok and "All tests passed." or "There were test failures.") .. + " Check the console for detailed output.") + end + coroutine.wrap(unittests.run_all)() + return true, "" + end, + }) end - diff --git a/games/devtest/mods/unittests/itemdescription.lua b/games/devtest/mods/unittests/itemdescription.lua index d6ee6551a..dc62de7f0 100644 --- a/games/devtest/mods/unittests/itemdescription.lua +++ b/games/devtest/mods/unittests/itemdescription.lua @@ -25,7 +25,7 @@ minetest.register_chatcommand("item_description", { end }) -function unittests.test_short_desc() +local function test_short_desc() local function get_short_description(item) return ItemStack(item):get_short_description() end @@ -49,3 +49,4 @@ function unittests.test_short_desc() return true end +unittests.register("test_short_desc", test_short_desc) diff --git a/games/devtest/mods/unittests/misc.lua b/games/devtest/mods/unittests/misc.lua new file mode 100644 index 000000000..cf4f92cfa --- /dev/null +++ b/games/devtest/mods/unittests/misc.lua @@ -0,0 +1,38 @@ +local function test_random() + -- Try out PseudoRandom + local pseudo = PseudoRandom(13) + assert(pseudo:next() == 22290) + assert(pseudo:next() == 13854) +end +unittests.register("test_random", test_random) + +local function test_dynamic_media(cb, player) + if core.get_player_information(player:get_player_name()).protocol_version < 40 then + core.log("warning", "test_dynamic_media: Client too old, skipping test.") + return cb() + end + + -- Check that the client acknowledges media transfers + local path = core.get_worldpath() .. "/test_media.obj" + local f = io.open(path, "w") + f:write("# contents don't matter\n") + f:close() + + local call_ok = false + local ok = core.dynamic_add_media({ + filepath = path, + to_player = player:get_player_name(), + }, function(name) + if not call_ok then + cb("impossible condition") + end + cb() + end) + if not ok then + return cb("dynamic_add_media() returned error") + end + call_ok = true + + -- if the callback isn't called this test will just hang :shrug: +end +unittests.register("test_dynamic_media", test_dynamic_media, {async=true, player=true}) diff --git a/games/devtest/mods/unittests/player.lua b/games/devtest/mods/unittests/player.lua index 4a681310d..fa0557960 100644 --- a/games/devtest/mods/unittests/player.lua +++ b/games/devtest/mods/unittests/player.lua @@ -2,6 +2,21 @@ -- HP Change Reasons -- local expect = nil +minetest.register_on_player_hpchange(function(player, hp, reason) + if expect == nil then + return + end + + for key, value in pairs(reason) do + assert(expect[key] == value) + end + for key, value in pairs(expect) do + assert(reason[key] == value) + end + + expect = nil +end) + local function run_hpchangereason_tests(player) local old_hp = player:get_hp() @@ -20,7 +35,11 @@ local function run_hpchangereason_tests(player) player:set_hp(old_hp) end +unittests.register("test_hpchangereason", run_hpchangereason_tests, {player=true}) +-- +-- Player meta +-- local function run_player_meta_tests(player) local meta = player:get_meta() meta:set_string("foo", "bar") @@ -48,29 +67,4 @@ local function run_player_meta_tests(player) assert(meta:get_string("foo") == "") assert(meta:equals(meta2)) end - -function unittests.test_player(player) - minetest.register_on_player_hpchange(function(player, hp, reason) - if not expect then - return - end - - for key, value in pairs(reason) do - assert(expect[key] == value) - end - - for key, value in pairs(expect) do - assert(reason[key] == value) - end - - expect = nil - end) - - run_hpchangereason_tests(player) - run_player_meta_tests(player) - local msg = "Player tests passed for player '"..player:get_player_name().."'!" - minetest.chat_send_all(msg) - minetest.log("action", "[unittests] "..msg) - return true -end - +unittests.register("test_player_meta", run_player_meta_tests, {player=true}) diff --git a/games/devtest/mods/unittests/random.lua b/games/devtest/mods/unittests/random.lua deleted file mode 100644 index f94f0a88e..000000000 --- a/games/devtest/mods/unittests/random.lua +++ /dev/null @@ -1,10 +0,0 @@ -function unittests.test_random() - -- Try out PseudoRandom - minetest.log("action", "[unittests] Testing PseudoRandom ...") - local pseudo = PseudoRandom(13) - assert(pseudo:next() == 22290) - assert(pseudo:next() == 13854) - minetest.log("action", "[unittests] PseudoRandom test passed!") - return true -end - diff --git a/src/script/cpp_api/s_base.h b/src/script/cpp_api/s_base.h index 06df2abe3..244d81605 100644 --- a/src/script/cpp_api/s_base.h +++ b/src/script/cpp_api/s_base.h @@ -125,6 +125,15 @@ protected: friend class ModApiEnvMod; friend class LuaVoxelManip; + /* + Subtle edge case with coroutines: If for whatever reason you have a + method in a subclass that's called from existing lua_CFunction + (any of the l_*.cpp files) then make it static and take the lua_State* + as an argument. This is REQUIRED because getStack() will not return the + correct state if called inside coroutines. + + Also note that src/script/common/ is the better place for such helpers. + */ lua_State* getStack() { return m_luastack; } diff --git a/src/script/cpp_api/s_server.cpp b/src/script/cpp_api/s_server.cpp index 6ddb2630d..c255b0c71 100644 --- a/src/script/cpp_api/s_server.cpp +++ b/src/script/cpp_api/s_server.cpp @@ -198,10 +198,8 @@ std::string ScriptApiServer::formatChatMessage(const std::string &name, return ret; } -u32 ScriptApiServer::allocateDynamicMediaCallback(int f_idx) +u32 ScriptApiServer::allocateDynamicMediaCallback(lua_State *L, int f_idx) { - lua_State *L = getStack(); - if (f_idx < 0) f_idx = lua_gettop(L) + f_idx + 1; @@ -235,7 +233,7 @@ u32 ScriptApiServer::allocateDynamicMediaCallback(int f_idx) void ScriptApiServer::freeDynamicMediaCallback(u32 token) { - lua_State *L = getStack(); + SCRIPTAPI_PRECHECKHEADER verbosestream << "freeDynamicMediaCallback(" << token << ")" << std::endl; diff --git a/src/script/cpp_api/s_server.h b/src/script/cpp_api/s_server.h index c5c3d5596..58c8c0e48 100644 --- a/src/script/cpp_api/s_server.h +++ b/src/script/cpp_api/s_server.h @@ -51,7 +51,7 @@ public: const std::string &password); /* dynamic media handling */ - u32 allocateDynamicMediaCallback(int f_idx); + static u32 allocateDynamicMediaCallback(lua_State *L, int f_idx); void freeDynamicMediaCallback(u32 token); void on_dynamic_media_added(u32 token, const char *playername); diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index 82a692070..88ab5e16b 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -496,7 +496,7 @@ int ModApiServer::l_dynamic_add_media(lua_State *L) CHECK_SECURE_PATH(L, filepath.c_str(), false); - u32 token = server->getScriptIface()->allocateDynamicMediaCallback(2); + u32 token = server->getScriptIface()->allocateDynamicMediaCallback(L, 2); bool ok = server->dynamicAddMedia(filepath, token, to_player, ephemeral); if (!ok) diff --git a/util/test_multiplayer.sh b/util/test_multiplayer.sh index 9fb894a30..5ffc044e0 100755 --- a/util/test_multiplayer.sh +++ b/util/test_multiplayer.sh @@ -20,7 +20,7 @@ waitfor () { } gdbrun () { - gdb -q -ex 'set confirm off' -ex 'r' -ex 'bt' -ex 'quit' --args "$@" + gdb -q -batch -ex 'set confirm off' -ex 'r' -ex 'bt' --args "$@" } [ -e $minetest ] || { echo "executable $minetest missing"; exit 1; } @@ -33,17 +33,27 @@ printf '%s\n' >$testspath/client1.conf \ enable_{sound,minimap,shaders}=false printf '%s\n' >$testspath/server.conf \ - max_block_send_distance=1 + max_block_send_distance=1 devtest_unittests_autostart=true cat >$worldpath/worldmods/test/init.lua <<"LUA" core.after(0, function() io.close(io.open(core.get_worldpath() .. "/startup", "w")) end) -core.register_on_joinplayer(function(player) - io.close(io.open(core.get_worldpath() .. "/player_joined", "w")) +local function callback(test_ok) + if not test_ok then + io.close(io.open(core.get_worldpath() .. "/test_failure", "w")) + end + io.close(io.open(core.get_worldpath() .. "/done", "w")) core.request_shutdown("", false, 2) -end) +end +if core.settings:get_bool("devtest_unittests_autostart") then + unittests.on_finished = callback +else + core.register_on_joinplayer(function() callback(true) end) +end LUA +printf '%s\n' >$worldpath/worldmods/test/mod.conf \ + name=test optional_depends=unittests echo "Starting server" gdbrun $minetest --server --config $conf_server --world $worldpath --gameid $gameid 2>&1 | sed -u 's/^/(server) /' & @@ -51,10 +61,14 @@ waitfor $worldpath/startup echo "Starting client" gdbrun $minetest --config $conf_client1 --go --address 127.0.0.1 2>&1 | sed -u 's/^/(client) /' & -waitfor $worldpath/player_joined +waitfor $worldpath/done echo "Waiting for client and server to exit" wait +if [ -f $worldpath/test_failure ]; then + echo "There were test failures." + exit 1 +fi echo "Success" exit 0 -- cgit v1.2.3 From 8c99f2232bdb52459ccf2a5b751cbe3f7797abc3 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Fri, 17 Dec 2021 18:31:29 +0100 Subject: Don't let HTTP API pass through untrusted function This has been a problem since the first day, oops. --- builtin/game/misc.lua | 5 +++-- src/script/common/c_internal.h | 2 ++ src/script/lua_api/l_http.cpp | 23 +++++++++++++++++++---- src/script/lua_api/l_http.h | 3 +++ 4 files changed, 27 insertions(+), 6 deletions(-) (limited to 'src/script/lua_api') diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua index ef826eda7..e86efc50c 100644 --- a/builtin/game/misc.lua +++ b/builtin/game/misc.lua @@ -250,7 +250,7 @@ end -- HTTP callback interface -function core.http_add_fetch(httpenv) +core.set_http_api_lua(function(httpenv) httpenv.fetch = function(req, callback) local handle = httpenv.fetch_async(req) @@ -266,7 +266,8 @@ function core.http_add_fetch(httpenv) end return httpenv -end +end) +core.set_http_api_lua = nil function core.close_formspec(player_name, formname) diff --git a/src/script/common/c_internal.h b/src/script/common/c_internal.h index ab2d7b975..94cfd61fb 100644 --- a/src/script/common/c_internal.h +++ b/src/script/common/c_internal.h @@ -54,6 +54,8 @@ extern "C" { #define CUSTOM_RIDX_GLOBALS_BACKUP (CUSTOM_RIDX_BASE + 1) #define CUSTOM_RIDX_CURRENT_MOD_NAME (CUSTOM_RIDX_BASE + 2) #define CUSTOM_RIDX_BACKTRACE (CUSTOM_RIDX_BASE + 3) +#define CUSTOM_RIDX_HTTP_API_LUA (CUSTOM_RIDX_BASE + 4) + // Determine if CUSTOM_RIDX_SCRIPTAPI will hold a light or full userdata #if defined(__aarch64__) && USE_LUAJIT diff --git a/src/script/lua_api/l_http.cpp b/src/script/lua_api/l_http.cpp index 751ec9837..b385b698c 100644 --- a/src/script/lua_api/l_http.cpp +++ b/src/script/lua_api/l_http.cpp @@ -163,6 +163,20 @@ int ModApiHttp::l_http_fetch_async_get(lua_State *L) return 1; } +int ModApiHttp::l_set_http_api_lua(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + // This is called by builtin to give us a function that will later + // populate the http_api table with additional method(s). + // We need this because access to the HTTP api is security-relevant and + // any mod could just mess with a global variable. + luaL_checktype(L, 1, LUA_TFUNCTION); + lua_rawseti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_HTTP_API_LUA); + + return 0; +} + int ModApiHttp::l_request_http_api(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -205,16 +219,16 @@ int ModApiHttp::l_request_http_api(lua_State *L) return 1; } - lua_getglobal(L, "core"); - lua_getfield(L, -1, "http_add_fetch"); + lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_HTTP_API_LUA); + assert(lua_isfunction(L, -1)); lua_newtable(L); HTTP_API(fetch_async); HTTP_API(fetch_async_get); // Stack now looks like this: - // - // Now call core.http_add_fetch to append .fetch(request, callback) to table + //
+ // Now call it to append .fetch(request, callback) to table lua_call(L, 1, 1); return 1; @@ -247,6 +261,7 @@ void ModApiHttp::Initialize(lua_State *L, int top) API_FCT(get_http_api); } else { API_FCT(request_http_api); + API_FCT(set_http_api_lua); } #endif diff --git a/src/script/lua_api/l_http.h b/src/script/lua_api/l_http.h index c3a2a5276..17fa283ba 100644 --- a/src/script/lua_api/l_http.h +++ b/src/script/lua_api/l_http.h @@ -41,6 +41,9 @@ private: // http_fetch_async_get(handle) static int l_http_fetch_async_get(lua_State *L); + // set_http_api_lua() [internal] + static int l_set_http_api_lua(lua_State *L); + // request_http_api() static int l_request_http_api(lua_State *L); -- cgit v1.2.3 From b2409b14d0682655363c1b3b3b6bafbaa7e7c1bf Mon Sep 17 00:00:00 2001 From: sfan5 Date: Fri, 17 Dec 2021 19:04:46 +0100 Subject: Refactor trusted mod checking code --- src/script/cpp_api/s_security.cpp | 33 +++++++++++++++++++++++++++++++++ src/script/cpp_api/s_security.h | 14 +++++++++----- src/script/lua_api/l_http.cpp | 39 +++------------------------------------ src/script/lua_api/l_util.cpp | 32 +------------------------------- 4 files changed, 46 insertions(+), 72 deletions(-) (limited to 'src/script/lua_api') diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index 11c277839..ccd1214e3 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -27,6 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include +#include #include @@ -604,6 +605,38 @@ bool ScriptApiSecurity::checkPath(lua_State *L, const char *path, return false; } +bool ScriptApiSecurity::checkWhitelisted(lua_State *L, const std::string &setting) +{ + assert(str_starts_with(setting, "secure.")); + + // We have to make sure that this function is being called directly by + // a mod, otherwise a malicious mod could override this function and + // steal its return value. + lua_Debug info; + + // Make sure there's only one item below this function on the stack... + if (lua_getstack(L, 2, &info)) + return false; + FATAL_ERROR_IF(!lua_getstack(L, 1, &info), "lua_getstack() failed"); + FATAL_ERROR_IF(!lua_getinfo(L, "S", &info), "lua_getinfo() failed"); + + // ...and that that item is the main file scope. + if (strcmp(info.what, "main") != 0) + return false; + + // Mod must be listed in secure.http_mods or secure.trusted_mods + lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); + if (!lua_isstring(L, -1)) + return false; + std::string mod_name = readParam(L, -1); + + std::string value = g_settings->get(setting); + value.erase(std::remove(value.begin(), value.end(), ' '), value.end()); + auto mod_list = str_split(value, ','); + + return CONTAINS(mod_list, mod_name); +} + int ScriptApiSecurity::sl_g_dofile(lua_State *L) { diff --git a/src/script/cpp_api/s_security.h b/src/script/cpp_api/s_security.h index 73e763548..619bf824f 100644 --- a/src/script/cpp_api/s_security.h +++ b/src/script/cpp_api/s_security.h @@ -40,11 +40,6 @@ with this program; if not, write to the Free Software Foundation, Inc., class ScriptApiSecurity : virtual public ScriptApiBase { public: - int getThread(lua_State *L); - // creates an empty Lua environment - void createEmptyEnv(lua_State *L); - // sets the enviroment to the table thats on top of the stack - void setLuaEnv(lua_State *L, int thread); // Sets up security on the ScriptApi's Lua state void initializeSecurity(); void initializeSecurityClient(); @@ -57,8 +52,17 @@ public: // Checks if mods are allowed to read (and optionally write) to the path static bool checkPath(lua_State *L, const char *path, bool write_required, bool *write_allowed=NULL); + // Check if mod is whitelisted in the given setting + // This additionally checks that the mod's main file scope is executing. + static bool checkWhitelisted(lua_State *L, const std::string &setting); private: + int getThread(lua_State *L); + // sets the enviroment to the table thats on top of the stack + void setLuaEnv(lua_State *L, int thread); + // creates an empty Lua environment + void createEmptyEnv(lua_State *L); + // Syntax: "sl_" '_' // (sl stands for Secure Lua) diff --git a/src/script/lua_api/l_http.cpp b/src/script/lua_api/l_http.cpp index b385b698c..bd359b3cc 100644 --- a/src/script/lua_api/l_http.cpp +++ b/src/script/lua_api/l_http.cpp @@ -21,14 +21,13 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "common/c_converter.h" #include "common/c_content.h" #include "lua_api/l_http.h" +#include "cpp_api/s_security.h" #include "httpfetch.h" #include "settings.h" #include "debug.h" #include "log.h" -#include #include -#include #define HTTP_API(name) \ lua_pushstring(L, #name); \ @@ -181,40 +180,8 @@ int ModApiHttp::l_request_http_api(lua_State *L) { NO_MAP_LOCK_REQUIRED; - // We have to make sure that this function is being called directly by - // a mod, otherwise a malicious mod could override this function and - // steal its return value. - lua_Debug info; - - // Make sure there's only one item below this function on the stack... - if (lua_getstack(L, 2, &info)) { - return 0; - } - FATAL_ERROR_IF(!lua_getstack(L, 1, &info), "lua_getstack() failed"); - FATAL_ERROR_IF(!lua_getinfo(L, "S", &info), "lua_getinfo() failed"); - - // ...and that that item is the main file scope. - if (strcmp(info.what, "main") != 0) { - return 0; - } - - // Mod must be listed in secure.http_mods or secure.trusted_mods - lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); - if (!lua_isstring(L, -1)) { - return 0; - } - - std::string mod_name = readParam(L, -1); - std::string http_mods = g_settings->get("secure.http_mods"); - http_mods.erase(std::remove(http_mods.begin(), http_mods.end(), ' '), http_mods.end()); - std::vector mod_list_http = str_split(http_mods, ','); - - std::string trusted_mods = g_settings->get("secure.trusted_mods"); - trusted_mods.erase(std::remove(trusted_mods.begin(), trusted_mods.end(), ' '), trusted_mods.end()); - std::vector mod_list_trusted = str_split(trusted_mods, ','); - - mod_list_http.insert(mod_list_http.end(), mod_list_trusted.begin(), mod_list_trusted.end()); - if (std::find(mod_list_http.begin(), mod_list_http.end(), mod_name) == mod_list_http.end()) { + if (!ScriptApiSecurity::checkWhitelisted(L, "secure.http_mods") && + !ScriptApiSecurity::checkWhitelisted(L, "secure.trusted_mods")) { lua_pushnil(L); return 1; } diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index 528d9c6dd..b04f26fda 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -41,7 +41,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/hex.h" #include "util/sha1.h" #include "util/png.h" -#include #include // log([level,] text) @@ -444,36 +443,7 @@ int ModApiUtil::l_request_insecure_environment(lua_State *L) return 1; } - // We have to make sure that this function is being called directly by - // a mod, otherwise a malicious mod could override this function and - // steal its return value. - lua_Debug info; - // Make sure there's only one item below this function on the stack... - if (lua_getstack(L, 2, &info)) { - return 0; - } - FATAL_ERROR_IF(!lua_getstack(L, 1, &info), "lua_getstack() failed"); - FATAL_ERROR_IF(!lua_getinfo(L, "S", &info), "lua_getinfo() failed"); - // ...and that that item is the main file scope. - if (strcmp(info.what, "main") != 0) { - return 0; - } - - // Get mod name - lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); - if (!lua_isstring(L, -1)) { - return 0; - } - - // Check secure.trusted_mods - std::string mod_name = readParam(L, -1); - std::string trusted_mods = g_settings->get("secure.trusted_mods"); - trusted_mods.erase(std::remove_if(trusted_mods.begin(), - trusted_mods.end(), static_cast(&std::isspace)), - trusted_mods.end()); - std::vector mod_list = str_split(trusted_mods, ','); - if (std::find(mod_list.begin(), mod_list.end(), mod_name) == - mod_list.end()) { + if (!ScriptApiSecurity::checkWhitelisted(L, "secure.trusted_mods")) { return 0; } -- cgit v1.2.3 From bf22569019749e421e8ffe0a73cff988a9a9c846 Mon Sep 17 00:00:00 2001 From: Jude Melton-Houghton Date: Fri, 7 Jan 2022 13:28:49 -0500 Subject: Use a database for mod storage (#11763) --- doc/minetest.6 | 4 + src/client/client.cpp | 33 ++-- src/client/client.h | 3 +- src/content/mods.cpp | 83 ++-------- src/content/mods.h | 10 +- src/content/subgames.cpp | 1 + src/database/database-dummy.cpp | 38 +++++ src/database/database-dummy.h | 9 +- src/database/database-files.cpp | 136 +++++++++++++++- src/database/database-files.h | 26 +++ src/database/database-sqlite3.cpp | 105 +++++++++++++ src/database/database-sqlite3.h | 25 +++ src/database/database.h | 13 ++ src/gamedef.h | 3 +- src/main.cpp | 5 + src/script/lua_api/l_storage.cpp | 18 ++- src/server.cpp | 140 ++++++++++++++--- src/server.h | 11 +- src/unittest/CMakeLists.txt | 1 + src/unittest/test.cpp | 6 +- src/unittest/test_modmetadatadatabase.cpp | 253 ++++++++++++++++++++++++++++++ 21 files changed, 797 insertions(+), 126 deletions(-) create mode 100644 src/unittest/test_modmetadatadatabase.cpp (limited to 'src/script/lua_api') diff --git a/doc/minetest.6 b/doc/minetest.6 index 42ed1a45f..6a3601f80 100644 --- a/doc/minetest.6 +++ b/doc/minetest.6 @@ -112,6 +112,10 @@ leveldb, and files. Migrate from current players backend to another. Possible values are sqlite3, leveldb, postgresql, dummy, and files. .TP +.B \-\-migrate-mod-storage +Migrate from current mod storage backend to another. Possible values are +sqlite3, dummy, and files. +.TP .B \-\-terminal Display an interactive terminal over ncurses during execution. diff --git a/src/client/client.cpp b/src/client/client.cpp index 6e4a90a79..2caa953e4 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -128,6 +128,11 @@ Client::Client( // Add local player m_env.setLocalPlayer(new LocalPlayer(this, playername)); + // Make the mod storage database and begin the save for later + m_mod_storage_database = + new ModMetadataDatabaseSQLite3(porting::path_user + DIR_DELIM + "client"); + m_mod_storage_database->beginSave(); + if (g_settings->getBool("enable_minimap")) { m_minimap = new Minimap(this); } @@ -305,6 +310,11 @@ Client::~Client() m_minimap = nullptr; delete m_media_downloader; + + // Write the changes and delete + if (m_mod_storage_database) + m_mod_storage_database->endSave(); + delete m_mod_storage_database; } void Client::connect(Address address, bool is_local_server) @@ -641,19 +651,12 @@ void Client::step(float dtime) } } + // Write changes to the mod storage m_mod_storage_save_timer -= dtime; if (m_mod_storage_save_timer <= 0.0f) { m_mod_storage_save_timer = g_settings->getFloat("server_map_save_interval"); - int n = 0; - for (std::unordered_map::const_iterator - it = m_mod_storages.begin(); it != m_mod_storages.end(); ++it) { - if (it->second->isModified()) { - it->second->save(getModStoragePath()); - n++; - } - } - if (n > 0) - infostream << "Saved " << n << " modified mod storages." << std::endl; + m_mod_storage_database->endSave(); + m_mod_storage_database->beginSave(); } // Write server map @@ -1960,16 +1963,8 @@ void Client::unregisterModStorage(const std::string &name) { std::unordered_map::const_iterator it = m_mod_storages.find(name); - if (it != m_mod_storages.end()) { - // Save unconditionaly on unregistration - it->second->save(getModStoragePath()); + if (it != m_mod_storages.end()) m_mod_storages.erase(name); - } -} - -std::string Client::getModStoragePath() const -{ - return porting::path_user + DIR_DELIM + "client" + DIR_DELIM + "mod_storage"; } /* diff --git a/src/client/client.h b/src/client/client.h index bae40f389..694cd7d1b 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -380,8 +380,8 @@ public: { return checkPrivilege(priv); } virtual scene::IAnimatedMesh* getMesh(const std::string &filename, bool cache = false); const std::string* getModFile(std::string filename); + ModMetadataDatabase *getModStorageDatabase() override { return m_mod_storage_database; } - std::string getModStoragePath() const override; bool registerModStorage(ModMetadata *meta) override; void unregisterModStorage(const std::string &name) override; @@ -590,6 +590,7 @@ private: // Client modding ClientScripting *m_script = nullptr; std::unordered_map m_mod_storages; + ModMetadataDatabase *m_mod_storage_database = nullptr; float m_mod_storage_save_timer = 10.0f; std::vector m_mods; StringMap m_mod_vfs; diff --git a/src/content/mods.cpp b/src/content/mods.cpp index 6f088a5b3..455506967 100644 --- a/src/content/mods.cpp +++ b/src/content/mods.cpp @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include "content/mods.h" +#include "database/database.h" #include "filesys.h" #include "log.h" #include "content/subgames.h" @@ -422,83 +423,29 @@ ClientModConfiguration::ClientModConfiguration(const std::string &path) : } #endif -ModMetadata::ModMetadata(const std::string &mod_name) : m_mod_name(mod_name) +ModMetadata::ModMetadata(const std::string &mod_name, ModMetadataDatabase *database): + m_mod_name(mod_name), m_database(database) { + m_database->getModEntries(m_mod_name, &m_stringvars); } void ModMetadata::clear() { + for (const auto &pair : m_stringvars) { + m_database->removeModEntry(m_mod_name, pair.first); + } Metadata::clear(); - m_modified = true; } -bool ModMetadata::save(const std::string &root_path) +bool ModMetadata::setString(const std::string &name, const std::string &var) { - Json::Value json; - for (StringMap::const_iterator it = m_stringvars.begin(); - it != m_stringvars.end(); ++it) { - json[it->first] = it->second; - } - - if (!fs::PathExists(root_path)) { - if (!fs::CreateAllDirs(root_path)) { - errorstream << "ModMetadata[" << m_mod_name - << "]: Unable to save. '" << root_path - << "' tree cannot be created." << std::endl; - return false; + if (Metadata::setString(name, var)) { + if (var.empty()) { + m_database->removeModEntry(m_mod_name, name); + } else { + m_database->setModEntry(m_mod_name, name, var); } - } else if (!fs::IsDir(root_path)) { - errorstream << "ModMetadata[" << m_mod_name << "]: Unable to save. '" - << root_path << "' is not a directory." << std::endl; - return false; - } - - bool w_ok = fs::safeWriteToFile( - root_path + DIR_DELIM + m_mod_name, fastWriteJson(json)); - - if (w_ok) { - m_modified = false; - } else { - errorstream << "ModMetadata[" << m_mod_name << "]: failed write file." - << std::endl; - } - return w_ok; -} - -bool ModMetadata::load(const std::string &root_path) -{ - m_stringvars.clear(); - - std::ifstream is((root_path + DIR_DELIM + m_mod_name).c_str(), - std::ios_base::binary); - if (!is.good()) { - return false; - } - - Json::Value root; - Json::CharReaderBuilder builder; - builder.settings_["collectComments"] = false; - std::string errs; - - if (!Json::parseFromStream(builder, is, &root, &errs)) { - errorstream << "ModMetadata[" << m_mod_name - << "]: failed read data " - "(Json decoding failure). Message: " - << errs << std::endl; - return false; - } - - const Json::Value::Members attr_list = root.getMemberNames(); - for (const auto &it : attr_list) { - Json::Value attr_value = root[it]; - m_stringvars[it] = attr_value.asString(); + return true; } - - return true; -} - -bool ModMetadata::setString(const std::string &name, const std::string &var) -{ - m_modified = Metadata::setString(name, var); - return m_modified; + return false; } diff --git a/src/content/mods.h b/src/content/mods.h index b56a97edb..dd3b6e0e6 100644 --- a/src/content/mods.h +++ b/src/content/mods.h @@ -31,6 +31,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "config.h" #include "metadata.h" +class ModMetadataDatabase; + #define MODNAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_" struct ModSpec @@ -149,20 +151,16 @@ class ModMetadata : public Metadata { public: ModMetadata() = delete; - ModMetadata(const std::string &mod_name); + ModMetadata(const std::string &mod_name, ModMetadataDatabase *database); ~ModMetadata() = default; virtual void clear(); - bool save(const std::string &root_path); - bool load(const std::string &root_path); - - bool isModified() const { return m_modified; } const std::string &getModName() const { return m_mod_name; } virtual bool setString(const std::string &name, const std::string &var); private: std::string m_mod_name; - bool m_modified = false; + ModMetadataDatabase *m_database; }; diff --git a/src/content/subgames.cpp b/src/content/subgames.cpp index 30447c838..e834f40cd 100644 --- a/src/content/subgames.cpp +++ b/src/content/subgames.cpp @@ -358,6 +358,7 @@ void loadGameConfAndInitWorld(const std::string &path, const std::string &name, conf.set("backend", "sqlite3"); conf.set("player_backend", "sqlite3"); conf.set("auth_backend", "sqlite3"); + conf.set("mod_storage_backend", "sqlite3"); conf.setBool("creative_mode", g_settings->getBool("creative_mode")); conf.setBool("enable_damage", g_settings->getBool("enable_damage")); diff --git a/src/database/database-dummy.cpp b/src/database/database-dummy.cpp index b56f341c5..629b2fb04 100644 --- a/src/database/database-dummy.cpp +++ b/src/database/database-dummy.cpp @@ -80,3 +80,41 @@ void Database_Dummy::listPlayers(std::vector &res) res.emplace_back(player); } } + +bool Database_Dummy::getModEntries(const std::string &modname, StringMap *storage) +{ + const auto mod_pair = m_mod_meta_database.find(modname); + if (mod_pair != m_mod_meta_database.cend()) { + for (const auto &pair : mod_pair->second) { + (*storage)[pair.first] = pair.second; + } + } + return true; +} + +bool Database_Dummy::setModEntry(const std::string &modname, + const std::string &key, const std::string &value) +{ + auto mod_pair = m_mod_meta_database.find(modname); + if (mod_pair == m_mod_meta_database.end()) { + m_mod_meta_database[modname] = StringMap({{key, value}}); + } else { + mod_pair->second[key] = value; + } + return true; +} + +bool Database_Dummy::removeModEntry(const std::string &modname, const std::string &key) +{ + auto mod_pair = m_mod_meta_database.find(modname); + if (mod_pair != m_mod_meta_database.end()) + return mod_pair->second.erase(key) > 0; + return false; +} + +void Database_Dummy::listMods(std::vector *res) +{ + for (const auto &pair : m_mod_meta_database) { + res->push_back(pair.first); + } +} diff --git a/src/database/database-dummy.h b/src/database/database-dummy.h index b69919f84..44b9e8d68 100644 --- a/src/database/database-dummy.h +++ b/src/database/database-dummy.h @@ -24,7 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "database.h" #include "irrlichttypes.h" -class Database_Dummy : public MapDatabase, public PlayerDatabase +class Database_Dummy : public MapDatabase, public PlayerDatabase, public ModMetadataDatabase { public: bool saveBlock(const v3s16 &pos, const std::string &data); @@ -37,10 +37,17 @@ public: bool removePlayer(const std::string &name); void listPlayers(std::vector &res); + bool getModEntries(const std::string &modname, StringMap *storage); + bool setModEntry(const std::string &modname, + const std::string &key, const std::string &value); + bool removeModEntry(const std::string &modname, const std::string &key); + void listMods(std::vector *res); + void beginSave() {} void endSave() {} private: std::map m_database; std::set m_player_database; + std::unordered_map m_mod_meta_database; }; diff --git a/src/database/database-files.cpp b/src/database/database-files.cpp index d9d113b4e..9021ae61b 100644 --- a/src/database/database-files.cpp +++ b/src/database/database-files.cpp @@ -18,7 +18,6 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include -#include #include "convert_json.h" #include "database-files.h" #include "remoteplayer.h" @@ -376,3 +375,138 @@ bool AuthDatabaseFiles::writeAuthFile() } return true; } + +ModMetadataDatabaseFiles::ModMetadataDatabaseFiles(const std::string &savedir): + m_storage_dir(savedir + DIR_DELIM + "mod_storage") +{ +} + +bool ModMetadataDatabaseFiles::getModEntries(const std::string &modname, StringMap *storage) +{ + Json::Value *meta = getOrCreateJson(modname); + if (!meta) + return false; + + const Json::Value::Members attr_list = meta->getMemberNames(); + for (const auto &it : attr_list) { + Json::Value attr_value = (*meta)[it]; + (*storage)[it] = attr_value.asString(); + } + + return true; +} + +bool ModMetadataDatabaseFiles::setModEntry(const std::string &modname, + const std::string &key, const std::string &value) +{ + Json::Value *meta = getOrCreateJson(modname); + if (!meta) + return false; + + (*meta)[key] = Json::Value(value); + m_modified.insert(modname); + + return true; +} + +bool ModMetadataDatabaseFiles::removeModEntry(const std::string &modname, + const std::string &key) +{ + Json::Value *meta = getOrCreateJson(modname); + if (!meta) + return false; + + Json::Value removed; + if (meta->removeMember(key, &removed)) { + m_modified.insert(modname); + return true; + } + return false; +} + +void ModMetadataDatabaseFiles::beginSave() +{ +} + +void ModMetadataDatabaseFiles::endSave() +{ + if (!fs::CreateAllDirs(m_storage_dir)) { + errorstream << "ModMetadataDatabaseFiles: Unable to save. '" << m_storage_dir + << "' tree cannot be created." << std::endl; + return; + } + + for (auto it = m_modified.begin(); it != m_modified.end();) { + const std::string &modname = *it; + + if (!fs::PathExists(m_storage_dir)) { + if (!fs::CreateAllDirs(m_storage_dir)) { + errorstream << "ModMetadataDatabaseFiles[" << modname + << "]: Unable to save. '" << m_storage_dir + << "' tree cannot be created." << std::endl; + ++it; + continue; + } + } else if (!fs::IsDir(m_storage_dir)) { + errorstream << "ModMetadataDatabaseFiles[" << modname << "]: Unable to save. '" + << m_storage_dir << "' is not a directory." << std::endl; + ++it; + continue; + } + + const Json::Value &json = m_mod_meta[modname]; + + if (!fs::safeWriteToFile(m_storage_dir + DIR_DELIM + modname, fastWriteJson(json))) { + errorstream << "ModMetadataDatabaseFiles[" << modname + << "]: failed write file." << std::endl; + ++it; + continue; + } + + it = m_modified.erase(it); + } +} + +void ModMetadataDatabaseFiles::listMods(std::vector *res) +{ + // List in-memory metadata first. + for (const auto &pair : m_mod_meta) { + res->push_back(pair.first); + } + + // List other metadata present in the filesystem. + for (const auto &entry : fs::GetDirListing(m_storage_dir)) { + if (!entry.dir && m_mod_meta.count(entry.name) == 0) + res->push_back(entry.name); + } +} + +Json::Value *ModMetadataDatabaseFiles::getOrCreateJson(const std::string &modname) +{ + auto found = m_mod_meta.find(modname); + if (found == m_mod_meta.end()) { + fs::CreateAllDirs(m_storage_dir); + + Json::Value meta(Json::objectValue); + + std::string path = m_storage_dir + DIR_DELIM + modname; + if (fs::PathExists(path)) { + std::ifstream is(path.c_str(), std::ios_base::binary); + + Json::CharReaderBuilder builder; + builder.settings_["collectComments"] = false; + std::string errs; + + if (!Json::parseFromStream(builder, is, &meta, &errs)) { + errorstream << "ModMetadataDatabaseFiles[" << modname + << "]: failed read data (Json decoding failure). Message: " + << errs << std::endl; + return nullptr; + } + } + + return &(m_mod_meta[modname] = meta); + } else { + return &found->second; + } +} diff --git a/src/database/database-files.h b/src/database/database-files.h index e647a2e24..962e4d7bb 100644 --- a/src/database/database-files.h +++ b/src/database/database-files.h @@ -25,6 +25,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "database.h" #include +#include +#include class PlayerDatabaseFiles : public PlayerDatabase { @@ -69,3 +71,27 @@ private: bool readAuthFile(); bool writeAuthFile(); }; + +class ModMetadataDatabaseFiles : public ModMetadataDatabase +{ +public: + ModMetadataDatabaseFiles(const std::string &savedir); + virtual ~ModMetadataDatabaseFiles() = default; + + virtual bool getModEntries(const std::string &modname, StringMap *storage); + virtual bool setModEntry(const std::string &modname, + const std::string &key, const std::string &value); + virtual bool removeModEntry(const std::string &modname, const std::string &key); + virtual void listMods(std::vector *res); + + virtual void beginSave(); + virtual void endSave(); + +private: + Json::Value *getOrCreateJson(const std::string &modname); + bool writeJson(const std::string &modname, const Json::Value &json); + + std::string m_storage_dir; + std::unordered_map m_mod_meta; + std::unordered_set m_modified; +}; diff --git a/src/database/database-sqlite3.cpp b/src/database/database-sqlite3.cpp index 898acc265..e9442118e 100644 --- a/src/database/database-sqlite3.cpp +++ b/src/database/database-sqlite3.cpp @@ -779,3 +779,108 @@ void AuthDatabaseSQLite3::writePrivileges(const AuthEntry &authEntry) sqlite3_reset(m_stmt_write_privs); } } + +ModMetadataDatabaseSQLite3::ModMetadataDatabaseSQLite3(const std::string &savedir): + Database_SQLite3(savedir, "mod_storage"), ModMetadataDatabase() +{ +} + +ModMetadataDatabaseSQLite3::~ModMetadataDatabaseSQLite3() +{ + FINALIZE_STATEMENT(m_stmt_remove) + FINALIZE_STATEMENT(m_stmt_set) + FINALIZE_STATEMENT(m_stmt_get) +} + +void ModMetadataDatabaseSQLite3::createDatabase() +{ + assert(m_database); // Pre-condition + + SQLOK(sqlite3_exec(m_database, + "CREATE TABLE IF NOT EXISTS `entries` (\n" + " `modname` TEXT NOT NULL,\n" + " `key` BLOB NOT NULL,\n" + " `value` BLOB NOT NULL,\n" + " PRIMARY KEY (`modname`, `key`)\n" + ");\n", + NULL, NULL, NULL), + "Failed to create database table"); +} + +void ModMetadataDatabaseSQLite3::initStatements() +{ + PREPARE_STATEMENT(get, "SELECT `key`, `value` FROM `entries` WHERE `modname` = ?"); + PREPARE_STATEMENT(set, + "REPLACE INTO `entries` (`modname`, `key`, `value`) VALUES (?, ?, ?)"); + PREPARE_STATEMENT(remove, "DELETE FROM `entries` WHERE `modname` = ? AND `key` = ?"); +} + +bool ModMetadataDatabaseSQLite3::getModEntries(const std::string &modname, StringMap *storage) +{ + verifyDatabase(); + + str_to_sqlite(m_stmt_get, 1, modname); + while (sqlite3_step(m_stmt_get) == SQLITE_ROW) { + const char *key_data = (const char *) sqlite3_column_blob(m_stmt_get, 0); + size_t key_len = sqlite3_column_bytes(m_stmt_get, 0); + const char *value_data = (const char *) sqlite3_column_blob(m_stmt_get, 1); + size_t value_len = sqlite3_column_bytes(m_stmt_get, 1); + (*storage)[std::string(key_data, key_len)] = std::string(value_data, value_len); + } + sqlite3_vrfy(sqlite3_errcode(m_database), SQLITE_DONE); + + sqlite3_reset(m_stmt_get); + + return true; +} + +bool ModMetadataDatabaseSQLite3::setModEntry(const std::string &modname, + const std::string &key, const std::string &value) +{ + verifyDatabase(); + + str_to_sqlite(m_stmt_set, 1, modname); + SQLOK(sqlite3_bind_blob(m_stmt_set, 2, key.data(), key.size(), NULL), + "Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__)); + SQLOK(sqlite3_bind_blob(m_stmt_set, 3, value.data(), value.size(), NULL), + "Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__)); + SQLRES(sqlite3_step(m_stmt_set), SQLITE_DONE, "Failed to set mod entry") + + sqlite3_reset(m_stmt_set); + + return true; +} + +bool ModMetadataDatabaseSQLite3::removeModEntry(const std::string &modname, + const std::string &key) +{ + verifyDatabase(); + + str_to_sqlite(m_stmt_remove, 1, modname); + SQLOK(sqlite3_bind_blob(m_stmt_remove, 2, key.data(), key.size(), NULL), + "Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__)); + sqlite3_vrfy(sqlite3_step(m_stmt_remove), SQLITE_DONE); + int changes = sqlite3_changes(m_database); + + sqlite3_reset(m_stmt_remove); + + return changes > 0; +} + +void ModMetadataDatabaseSQLite3::listMods(std::vector *res) +{ + verifyDatabase(); + + char *errmsg; + int status = sqlite3_exec(m_database, + "SELECT `modname` FROM `entries` GROUP BY `modname`;", + [](void *res_vp, int n_col, char **cols, char **col_names) -> int { + ((decltype(res)) res_vp)->emplace_back(cols[0]); + return 0; + }, (void *) res, &errmsg); + if (status != SQLITE_OK) { + DatabaseException e(std::string("Error trying to list mods with metadata: ") + errmsg); + sqlite3_free(errmsg); + throw e; + } +} diff --git a/src/database/database-sqlite3.h b/src/database/database-sqlite3.h index d7202a918..5e3d7c96c 100644 --- a/src/database/database-sqlite3.h +++ b/src/database/database-sqlite3.h @@ -232,3 +232,28 @@ private: sqlite3_stmt *m_stmt_delete_privs = nullptr; sqlite3_stmt *m_stmt_last_insert_rowid = nullptr; }; + +class ModMetadataDatabaseSQLite3 : private Database_SQLite3, public ModMetadataDatabase +{ +public: + ModMetadataDatabaseSQLite3(const std::string &savedir); + virtual ~ModMetadataDatabaseSQLite3(); + + virtual bool getModEntries(const std::string &modname, StringMap *storage); + virtual bool setModEntry(const std::string &modname, + const std::string &key, const std::string &value); + virtual bool removeModEntry(const std::string &modname, const std::string &key); + virtual void listMods(std::vector *res); + + virtual void beginSave() { Database_SQLite3::beginSave(); } + virtual void endSave() { Database_SQLite3::endSave(); } + +protected: + virtual void createDatabase(); + virtual void initStatements(); + +private: + sqlite3_stmt *m_stmt_get = nullptr; + sqlite3_stmt *m_stmt_set = nullptr; + sqlite3_stmt *m_stmt_remove = nullptr; +}; diff --git a/src/database/database.h b/src/database/database.h index b7d551935..fbb5befea 100644 --- a/src/database/database.h +++ b/src/database/database.h @@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irr_v3d.h" #include "irrlichttypes.h" #include "util/basic_macros.h" +#include "util/string.h" class Database { @@ -84,3 +85,15 @@ public: virtual void listNames(std::vector &res) = 0; virtual void reload() = 0; }; + +class ModMetadataDatabase : public Database +{ +public: + virtual ~ModMetadataDatabase() = default; + + virtual bool getModEntries(const std::string &modname, StringMap *storage) = 0; + virtual bool setModEntry(const std::string &modname, + const std::string &key, const std::string &value) = 0; + virtual bool removeModEntry(const std::string &modname, const std::string &key) = 0; + virtual void listMods(std::vector *res) = 0; +}; diff --git a/src/gamedef.h b/src/gamedef.h index bc0ee14c3..8a9246da2 100644 --- a/src/gamedef.h +++ b/src/gamedef.h @@ -33,6 +33,7 @@ class EmergeManager; class Camera; class ModChannel; class ModMetadata; +class ModMetadataDatabase; namespace irr { namespace scene { class IAnimatedMesh; @@ -70,9 +71,9 @@ public: virtual const std::vector &getMods() const = 0; virtual const ModSpec* getModSpec(const std::string &modname) const = 0; virtual std::string getWorldPath() const { return ""; } - virtual std::string getModStoragePath() const = 0; virtual bool registerModStorage(ModMetadata *storage) = 0; virtual void unregisterModStorage(const std::string &name) = 0; + virtual ModMetadataDatabase *getModStorageDatabase() = 0; virtual bool joinModChannel(const std::string &channel) = 0; virtual bool leaveModChannel(const std::string &channel) = 0; diff --git a/src/main.cpp b/src/main.cpp index 1044b327a..ca95ef874 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -299,6 +299,8 @@ static void set_allowed_options(OptionList *allowed_options) _("Migrate from current players backend to another (Only works when using minetestserver or with --server)")))); allowed_options->insert(std::make_pair("migrate-auth", ValueSpec(VALUETYPE_STRING, _("Migrate from current auth backend to another (Only works when using minetestserver or with --server)")))); + allowed_options->insert(std::make_pair("migrate-mod-storage", ValueSpec(VALUETYPE_STRING, + _("Migrate from current mod storage backend to another (Only works when using minetestserver or with --server)")))); allowed_options->insert(std::make_pair("terminal", ValueSpec(VALUETYPE_FLAG, _("Feature an interactive terminal (Only works when using minetestserver or with --server)")))); allowed_options->insert(std::make_pair("recompress", ValueSpec(VALUETYPE_FLAG, @@ -886,6 +888,9 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings & if (cmd_args.exists("migrate-auth")) return ServerEnvironment::migrateAuthDatabase(game_params, cmd_args); + if (cmd_args.exists("migrate-mod-storage")) + return Server::migrateModStorageDatabase(game_params, cmd_args); + if (cmd_args.getFlag("recompress")) return recompress_map_database(game_params, cmd_args, bind_addr); diff --git a/src/script/lua_api/l_storage.cpp b/src/script/lua_api/l_storage.cpp index 978b315d5..b8f4347a8 100644 --- a/src/script/lua_api/l_storage.cpp +++ b/src/script/lua_api/l_storage.cpp @@ -32,19 +32,23 @@ int ModApiStorage::l_get_mod_storage(lua_State *L) std::string mod_name = readParam(L, -1); - ModMetadata *store = new ModMetadata(mod_name); + ModMetadata *store = nullptr; + if (IGameDef *gamedef = getGameDef(L)) { - store->load(gamedef->getModStoragePath()); - gamedef->registerModStorage(store); + store = new ModMetadata(mod_name, gamedef->getModStorageDatabase()); + if (gamedef->registerModStorage(store)) { + StorageRef::create(L, store); + int object = lua_gettop(L); + lua_pushvalue(L, object); + return 1; + } } else { - delete store; assert(false); // this should not happen } - StorageRef::create(L, store); - int object = lua_gettop(L); + delete store; - lua_pushvalue(L, object); + lua_pushnil(L); return 1; } diff --git a/src/server.cpp b/src/server.cpp index a910185b9..6cf790de1 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -66,6 +66,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "server/player_sao.h" #include "server/serverinventorymgr.h" #include "translation.h" +#include "database/database-sqlite3.h" +#include "database/database-files.h" +#include "database/database-dummy.h" +#include "gameparams.h" class ClientNotFoundException : public BaseException { @@ -344,10 +348,15 @@ Server::~Server() delete m_thread; } + // Write any changes before deletion. + if (m_mod_storage_database) + m_mod_storage_database->endSave(); + // Delete things in the reverse order of creation delete m_emerge; delete m_env; delete m_rollback; + delete m_mod_storage_database; delete m_banmanager; delete m_itemdef; delete m_nodedef; @@ -393,6 +402,10 @@ void Server::init() std::string ban_path = m_path_world + DIR_DELIM "ipban.txt"; m_banmanager = new BanManager(ban_path); + // Create mod storage database and begin a save for later + m_mod_storage_database = openModStorageDatabase(m_path_world); + m_mod_storage_database->beginSave(); + m_modmgr = std::unique_ptr(new ServerModManager(m_path_world)); std::vector unsatisfied_mods = m_modmgr->getUnsatisfiedMods(); // complain about mods with unsatisfied dependencies @@ -733,20 +746,12 @@ void Server::AsyncRunStep(bool initial_step) } m_clients.unlock(); - // Save mod storages if modified + // Write changes to the mod storage m_mod_storage_save_timer -= dtime; if (m_mod_storage_save_timer <= 0.0f) { m_mod_storage_save_timer = g_settings->getFloat("server_map_save_interval"); - int n = 0; - for (std::unordered_map::const_iterator - it = m_mod_storages.begin(); it != m_mod_storages.end(); ++it) { - if (it->second->isModified()) { - it->second->save(getModStoragePath()); - n++; - } - } - if (n > 0) - infostream << "Saved " << n << " modified mod storages." << std::endl; + m_mod_storage_database->endSave(); + m_mod_storage_database->beginSave(); } } @@ -3689,11 +3694,6 @@ std::string Server::getBuiltinLuaPath() return porting::path_share + DIR_DELIM + "builtin"; } -std::string Server::getModStoragePath() const -{ - return m_path_world + DIR_DELIM + "mod_storage"; -} - v3f Server::findSpawnPos() { ServerMap &map = m_env->getServerMap(); @@ -3857,11 +3857,8 @@ bool Server::registerModStorage(ModMetadata *storage) void Server::unregisterModStorage(const std::string &name) { std::unordered_map::const_iterator it = m_mod_storages.find(name); - if (it != m_mod_storages.end()) { - // Save unconditionaly on unregistration - it->second->save(getModStoragePath()); + if (it != m_mod_storages.end()) m_mod_storages.erase(name); - } } void dedicated_server_loop(Server &server, bool &kill) @@ -3999,3 +3996,106 @@ Translations *Server::getTranslationLanguage(const std::string &lang_code) return translations; } + +ModMetadataDatabase *Server::openModStorageDatabase(const std::string &world_path) +{ + std::string world_mt_path = world_path + DIR_DELIM + "world.mt"; + Settings world_mt; + if (!world_mt.readConfigFile(world_mt_path.c_str())) + throw BaseException("Cannot read world.mt!"); + + std::string backend = world_mt.exists("mod_storage_backend") ? + world_mt.get("mod_storage_backend") : "files"; + if (backend == "files") + warningstream << "/!\\ You are using the old mod storage files backend. " + << "This backend is deprecated and may be removed in a future release /!\\" + << std::endl << "Switching to SQLite3 is advised, " + << "please read http://wiki.minetest.net/Database_backends." << std::endl; + + return openModStorageDatabase(backend, world_path, world_mt); +} + +ModMetadataDatabase *Server::openModStorageDatabase(const std::string &backend, + const std::string &world_path, const Settings &world_mt) +{ + if (backend == "sqlite3") + return new ModMetadataDatabaseSQLite3(world_path); + + if (backend == "files") + return new ModMetadataDatabaseFiles(world_path); + + if (backend == "dummy") + return new Database_Dummy(); + + throw BaseException("Mod storage database backend " + backend + " not supported"); +} + +bool Server::migrateModStorageDatabase(const GameParams &game_params, const Settings &cmd_args) +{ + std::string migrate_to = cmd_args.get("migrate-mod-storage"); + Settings world_mt; + std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt"; + if (!world_mt.readConfigFile(world_mt_path.c_str())) { + errorstream << "Cannot read world.mt!" << std::endl; + return false; + } + + std::string backend = world_mt.exists("mod_storage_backend") ? + world_mt.get("mod_storage_backend") : "files"; + if (backend == migrate_to) { + errorstream << "Cannot migrate: new backend is same" + << " as the old one" << std::endl; + return false; + } + + ModMetadataDatabase *srcdb = nullptr; + ModMetadataDatabase *dstdb = nullptr; + + bool succeeded = false; + + try { + srcdb = Server::openModStorageDatabase(backend, game_params.world_path, world_mt); + dstdb = Server::openModStorageDatabase(migrate_to, game_params.world_path, world_mt); + + dstdb->beginSave(); + + std::vector mod_list; + srcdb->listMods(&mod_list); + for (const std::string &modname : mod_list) { + StringMap meta; + srcdb->getModEntries(modname, &meta); + for (const auto &pair : meta) { + dstdb->setModEntry(modname, pair.first, pair.second); + } + } + + dstdb->endSave(); + + succeeded = true; + + actionstream << "Successfully migrated the metadata of " + << mod_list.size() << " mods" << std::endl; + world_mt.set("mod_storage_backend", migrate_to); + if (!world_mt.updateConfigFile(world_mt_path.c_str())) + errorstream << "Failed to update world.mt!" << std::endl; + else + actionstream << "world.mt updated" << std::endl; + + } catch (BaseException &e) { + errorstream << "An error occurred during migration: " << e.what() << std::endl; + } + + delete srcdb; + delete dstdb; + + if (succeeded && backend == "files") { + // Back up files + const std::string storage_path = game_params.world_path + DIR_DELIM + "mod_storage"; + const std::string backup_path = game_params.world_path + DIR_DELIM + "mod_storage.bak"; + if (!fs::Rename(storage_path, backup_path)) + warningstream << "After migration, " << storage_path + << " could not be renamed to " << backup_path << std::endl; + } + + return succeeded; +} diff --git a/src/server.h b/src/server.h index c5db0fdfb..12158feb7 100644 --- a/src/server.h +++ b/src/server.h @@ -283,6 +283,7 @@ public: virtual u16 allocateUnknownNodeId(const std::string &name); IRollbackManager *getRollbackManager() { return m_rollback; } virtual EmergeManager *getEmergeManager() { return m_emerge; } + virtual ModMetadataDatabase *getModStorageDatabase() { return m_mod_storage_database; } IWritableItemDefManager* getWritableItemDefManager(); NodeDefManager* getWritableNodeDefManager(); @@ -293,7 +294,6 @@ public: void getModNames(std::vector &modlist); std::string getBuiltinLuaPath(); virtual std::string getWorldPath() const { return m_path_world; } - virtual std::string getModStoragePath() const; inline bool isSingleplayer() { return m_simple_singleplayer_mode; } @@ -377,6 +377,14 @@ public: // Get or load translations for a language Translations *getTranslationLanguage(const std::string &lang_code); + static ModMetadataDatabase *openModStorageDatabase(const std::string &world_path); + + static ModMetadataDatabase *openModStorageDatabase(const std::string &backend, + const std::string &world_path, const Settings &world_mt); + + static bool migrateModStorageDatabase(const GameParams &game_params, + const Settings &cmd_args); + // Bind address Address m_bind_addr; @@ -678,6 +686,7 @@ private: s32 nextSoundId(); std::unordered_map m_mod_storages; + ModMetadataDatabase *m_mod_storage_database = nullptr; float m_mod_storage_save_timer = 10.0f; // CSM restrictions byteflag diff --git a/src/unittest/CMakeLists.txt b/src/unittest/CMakeLists.txt index 4d295e4ed..ce7921b55 100644 --- a/src/unittest/CMakeLists.txt +++ b/src/unittest/CMakeLists.txt @@ -14,6 +14,7 @@ set (UNITTEST_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/test_map_settings_manager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_mapnode.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_modchannels.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_modmetadatadatabase.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_nodedef.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_noderesolver.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_noise.cpp diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index af324e1b1..f223d567e 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gamedef.h" #include "modchannels.h" #include "content/mods.h" +#include "database/database-dummy.h" #include "util/numeric.h" #include "porting.h" @@ -55,6 +56,7 @@ public: scene::ISceneManager *getSceneManager() { return m_scenemgr; } IRollbackManager *getRollbackManager() { return m_rollbackmgr; } EmergeManager *getEmergeManager() { return m_emergemgr; } + ModMetadataDatabase *getModStorageDatabase() { return m_mod_storage_database; } scene::IAnimatedMesh *getMesh(const std::string &filename) { return NULL; } bool checkLocalPrivilege(const std::string &priv) { return false; } @@ -68,7 +70,6 @@ public: return testmodspec; } virtual const ModSpec* getModSpec(const std::string &modname) const { return NULL; } - virtual std::string getModStoragePath() const { return "."; } virtual bool registerModStorage(ModMetadata *meta) { return true; } virtual void unregisterModStorage(const std::string &name) {} bool joinModChannel(const std::string &channel); @@ -89,11 +90,13 @@ private: scene::ISceneManager *m_scenemgr = nullptr; IRollbackManager *m_rollbackmgr = nullptr; EmergeManager *m_emergemgr = nullptr; + ModMetadataDatabase *m_mod_storage_database = nullptr; std::unique_ptr m_modchannel_mgr; }; TestGameDef::TestGameDef() : + m_mod_storage_database(new Database_Dummy()), m_modchannel_mgr(new ModChannelMgr()) { m_itemdef = createItemDefManager(); @@ -107,6 +110,7 @@ TestGameDef::~TestGameDef() { delete m_itemdef; delete m_nodedef; + delete m_mod_storage_database; } diff --git a/src/unittest/test_modmetadatadatabase.cpp b/src/unittest/test_modmetadatadatabase.cpp new file mode 100644 index 000000000..be97fae5e --- /dev/null +++ b/src/unittest/test_modmetadatadatabase.cpp @@ -0,0 +1,253 @@ +/* +Minetest +Copyright (C) 2018 bendeutsch, Ben Deutsch +Copyright (C) 2021 TurkeyMcMac, Jude Melton-Houghton + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +// This file is an edited copy of test_authdatabase.cpp + +#include "test.h" + +#include +#include "database/database-files.h" +#include "database/database-sqlite3.h" +#include "filesys.h" + +namespace +{ +// Anonymous namespace to create classes that are only +// visible to this file +// +// These are helpers that return a *ModMetadataDatabase and +// allow us to run the same tests on different databases and +// database acquisition strategies. + +class ModMetadataDatabaseProvider +{ +public: + virtual ~ModMetadataDatabaseProvider() = default; + virtual ModMetadataDatabase *getModMetadataDatabase() = 0; +}; + +class FixedProvider : public ModMetadataDatabaseProvider +{ +public: + FixedProvider(ModMetadataDatabase *mod_meta_db) : mod_meta_db(mod_meta_db){}; + virtual ~FixedProvider(){}; + virtual ModMetadataDatabase *getModMetadataDatabase() { return mod_meta_db; }; + +private: + ModMetadataDatabase *mod_meta_db; +}; + +class FilesProvider : public ModMetadataDatabaseProvider +{ +public: + FilesProvider(const std::string &dir) : dir(dir){}; + virtual ~FilesProvider() + { + if (mod_meta_db) + mod_meta_db->endSave(); + delete mod_meta_db; + } + virtual ModMetadataDatabase *getModMetadataDatabase() + { + if (mod_meta_db) + mod_meta_db->endSave(); + delete mod_meta_db; + mod_meta_db = new ModMetadataDatabaseFiles(dir); + mod_meta_db->beginSave(); + return mod_meta_db; + }; + +private: + std::string dir; + ModMetadataDatabase *mod_meta_db = nullptr; +}; + +class SQLite3Provider : public ModMetadataDatabaseProvider +{ +public: + SQLite3Provider(const std::string &dir) : dir(dir){}; + virtual ~SQLite3Provider() + { + if (mod_meta_db) + mod_meta_db->endSave(); + delete mod_meta_db; + } + virtual ModMetadataDatabase *getModMetadataDatabase() + { + if (mod_meta_db) + mod_meta_db->endSave(); + delete mod_meta_db; + mod_meta_db = new ModMetadataDatabaseSQLite3(dir); + mod_meta_db->beginSave(); + return mod_meta_db; + }; + +private: + std::string dir; + ModMetadataDatabase *mod_meta_db = nullptr; +}; +} + +class TestModMetadataDatabase : public TestBase +{ +public: + TestModMetadataDatabase() { TestManager::registerTestModule(this); } + const char *getName() { return "TestModMetadataDatabase"; } + + void runTests(IGameDef *gamedef); + void runTestsForCurrentDB(); + + void testRecallFail(); + void testCreate(); + void testRecall(); + void testChange(); + void testRecallChanged(); + void testListMods(); + void testRemove(); + +private: + ModMetadataDatabaseProvider *mod_meta_provider; +}; + +static TestModMetadataDatabase g_test_instance; + +void TestModMetadataDatabase::runTests(IGameDef *gamedef) +{ + // fixed directory, for persistence + thread_local const std::string test_dir = getTestTempDirectory(); + + // Each set of tests is run twice for each database type: + // one where we reuse the same ModMetadataDatabase object (to test local caching), + // and one where we create a new ModMetadataDatabase object for each call + // (to test actual persistence). + + rawstream << "-------- Files database (same object)" << std::endl; + + ModMetadataDatabase *mod_meta_db = new ModMetadataDatabaseFiles(test_dir); + mod_meta_provider = new FixedProvider(mod_meta_db); + + runTestsForCurrentDB(); + + delete mod_meta_db; + delete mod_meta_provider; + + // reset database + fs::RecursiveDelete(test_dir + DIR_DELIM + "mod_storage"); + + rawstream << "-------- Files database (new objects)" << std::endl; + + mod_meta_provider = new FilesProvider(test_dir); + + runTestsForCurrentDB(); + + delete mod_meta_provider; + + rawstream << "-------- SQLite3 database (same object)" << std::endl; + + mod_meta_db = new ModMetadataDatabaseSQLite3(test_dir); + mod_meta_provider = new FixedProvider(mod_meta_db); + + runTestsForCurrentDB(); + + delete mod_meta_db; + delete mod_meta_provider; + + // reset database + fs::DeleteSingleFileOrEmptyDirectory(test_dir + DIR_DELIM + "mod_storage.sqlite"); + + rawstream << "-------- SQLite3 database (new objects)" << std::endl; + + mod_meta_provider = new SQLite3Provider(test_dir); + + runTestsForCurrentDB(); + + delete mod_meta_provider; +} + +//////////////////////////////////////////////////////////////////////////////// + +void TestModMetadataDatabase::runTestsForCurrentDB() +{ + TEST(testRecallFail); + TEST(testCreate); + TEST(testRecall); + TEST(testChange); + TEST(testRecallChanged); + TEST(testListMods); + TEST(testRemove); + TEST(testRecallFail); +} + +void TestModMetadataDatabase::testRecallFail() +{ + ModMetadataDatabase *mod_meta_db = mod_meta_provider->getModMetadataDatabase(); + StringMap recalled; + mod_meta_db->getModEntries("mod1", &recalled); + UASSERT(recalled.empty()); +} + +void TestModMetadataDatabase::testCreate() +{ + ModMetadataDatabase *mod_meta_db = mod_meta_provider->getModMetadataDatabase(); + StringMap recalled; + UASSERT(mod_meta_db->setModEntry("mod1", "key1", "value1")); +} + +void TestModMetadataDatabase::testRecall() +{ + ModMetadataDatabase *mod_meta_db = mod_meta_provider->getModMetadataDatabase(); + StringMap recalled; + mod_meta_db->getModEntries("mod1", &recalled); + UASSERT(recalled.size() == 1); + UASSERT(recalled["key1"] == "value1"); +} + +void TestModMetadataDatabase::testChange() +{ + ModMetadataDatabase *mod_meta_db = mod_meta_provider->getModMetadataDatabase(); + StringMap recalled; + UASSERT(mod_meta_db->setModEntry("mod1", "key1", "value2")); +} + +void TestModMetadataDatabase::testRecallChanged() +{ + ModMetadataDatabase *mod_meta_db = mod_meta_provider->getModMetadataDatabase(); + StringMap recalled; + mod_meta_db->getModEntries("mod1", &recalled); + UASSERT(recalled.size() == 1); + UASSERT(recalled["key1"] == "value2"); +} + +void TestModMetadataDatabase::testListMods() +{ + ModMetadataDatabase *mod_meta_db = mod_meta_provider->getModMetadataDatabase(); + UASSERT(mod_meta_db->setModEntry("mod2", "key1", "value1")); + std::vector mod_list; + mod_meta_db->listMods(&mod_list); + UASSERT(mod_list.size() == 2); + UASSERT(std::find(mod_list.cbegin(), mod_list.cend(), "mod1") != mod_list.cend()); + UASSERT(std::find(mod_list.cbegin(), mod_list.cend(), "mod2") != mod_list.cend()); +} + +void TestModMetadataDatabase::testRemove() +{ + ModMetadataDatabase *mod_meta_db = mod_meta_provider->getModMetadataDatabase(); + UASSERT(mod_meta_db->removeModEntry("mod1", "key1")); +} -- cgit v1.2.3 From 5eb45e1ea03c6104f007efec6dd9c351f310193d Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 9 Jan 2022 18:46:36 +0100 Subject: Restore pass-through of direction keys (#11924) This moves relevant code into the PlayerControl class and gets rid of separate keyPressed variable. --- src/client/client.cpp | 16 +++++----- src/client/clientlauncher.cpp | 4 +-- src/client/game.cpp | 36 +++------------------- src/client/inputhandler.h | 50 +++++++++++++++++++++--------- src/client/localplayer.cpp | 4 +-- src/client/localplayer.h | 2 +- src/network/serverpackethandler.cpp | 9 +----- src/player.cpp | 59 ++++++++++++++++++++++++++++++++++++ src/player.h | 33 ++++++++++++-------- src/script/lua_api/l_localplayer.cpp | 14 +++++---- src/script/lua_api/l_object.cpp | 36 ++++++++++++++-------- 11 files changed, 165 insertions(+), 98 deletions(-) (limited to 'src/script/lua_api') diff --git a/src/client/client.cpp b/src/client/client.cpp index 2caa953e4..d4c271bab 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -931,7 +931,7 @@ void writePlayerPos(LocalPlayer *myplayer, ClientMap *clientMap, NetworkPacket * v3f sf = myplayer->getSpeed() * 100; s32 pitch = myplayer->getPitch() * 100; s32 yaw = myplayer->getYaw() * 100; - u32 keyPressed = myplayer->keyPressed; + u32 keyPressed = myplayer->control.getKeysPressed(); // scaled by 80, so that pi can fit into a u8 u8 fov = clientMap->getCameraFov() * 80; u8 wanted_range = MYMIN(255, @@ -1287,22 +1287,24 @@ void Client::sendPlayerPos() if (!player) return; - ClientMap &map = m_env.getClientMap(); - u8 camera_fov = map.getCameraFov(); - u8 wanted_range = map.getControl().wanted_range; - // Save bandwidth by only updating position when // player is not dead and something changed if (m_activeobjects_received && player->isDead()) return; + ClientMap &map = m_env.getClientMap(); + u8 camera_fov = map.getCameraFov(); + u8 wanted_range = map.getControl().wanted_range; + + u32 keyPressed = player->control.getKeysPressed(); + if ( player->last_position == player->getPosition() && player->last_speed == player->getSpeed() && player->last_pitch == player->getPitch() && player->last_yaw == player->getYaw() && - player->last_keyPressed == player->keyPressed && + player->last_keyPressed == keyPressed && player->last_camera_fov == camera_fov && player->last_wanted_range == wanted_range) return; @@ -1311,7 +1313,7 @@ void Client::sendPlayerPos() player->last_speed = player->getSpeed(); player->last_pitch = player->getPitch(); player->last_yaw = player->getYaw(); - player->last_keyPressed = player->keyPressed; + player->last_keyPressed = keyPressed; player->last_camera_fov = camera_fov; player->last_wanted_range = wanted_range; diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp index 95be72ca0..063154316 100644 --- a/src/client/clientlauncher.cpp +++ b/src/client/clientlauncher.cpp @@ -70,10 +70,10 @@ static void dump_start_data(const GameStartData &data) ClientLauncher::~ClientLauncher() { - delete receiver; - delete input; + delete receiver; + delete g_fontengine; delete g_gamecallback; diff --git a/src/client/game.cpp b/src/client/game.cpp index f62d26e8f..182dc3815 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -2481,6 +2481,10 @@ void Game::updatePlayerControl(const CameraOrientation &cam) //TimeTaker tt("update player control", NULL, PRECISION_NANO); PlayerControl control( + isKeyDown(KeyType::FORWARD), + isKeyDown(KeyType::BACKWARD), + isKeyDown(KeyType::LEFT), + isKeyDown(KeyType::RIGHT), isKeyDown(KeyType::JUMP) || player->getAutojump(), isKeyDown(KeyType::AUX1), isKeyDown(KeyType::SNEAK), @@ -2511,39 +2515,7 @@ void Game::updatePlayerControl(const CameraOrientation &cam) } #endif - u32 keypress_bits = ( - ( (u32)(control.jump & 0x1) << 4) | - ( (u32)(control.aux1 & 0x1) << 5) | - ( (u32)(control.sneak & 0x1) << 6) | - ( (u32)(control.dig & 0x1) << 7) | - ( (u32)(control.place & 0x1) << 8) | - ( (u32)(control.zoom & 0x1) << 9) - ); - - // Set direction keys to ensure mod compatibility - if (control.movement_speed > 0.001f) { - float absolute_direction; - - // Check in original orientation (absolute value indicates forward / backward) - absolute_direction = abs(control.movement_direction); - if (absolute_direction < (3.0f / 8.0f * M_PI)) - keypress_bits |= (u32)(0x1 << 0); // Forward - if (absolute_direction > (5.0f / 8.0f * M_PI)) - keypress_bits |= (u32)(0x1 << 1); // Backward - - // Rotate entire coordinate system by 90 degrees (absolute value indicates left / right) - absolute_direction = control.movement_direction + M_PI_2; - if (absolute_direction >= M_PI) - absolute_direction -= 2 * M_PI; - absolute_direction = abs(absolute_direction); - if (absolute_direction < (3.0f / 8.0f * M_PI)) - keypress_bits |= (u32)(0x1 << 2); // Left - if (absolute_direction > (5.0f / 8.0f * M_PI)) - keypress_bits |= (u32)(0x1 << 3); // Right - } - client->setPlayerControl(control); - player->keyPressed = keypress_bits; //tt.stop(); } diff --git a/src/client/inputhandler.h b/src/client/inputhandler.h index e630b860e..3db105c51 100644 --- a/src/client/inputhandler.h +++ b/src/client/inputhandler.h @@ -152,8 +152,14 @@ public: // in the subsequent iteration of Game::processPlayerInteraction bool WasKeyReleased(const KeyPress &keycode) const { return keyWasReleased[keycode]; } - void listenForKey(const KeyPress &keyCode) { keysListenedFor.set(keyCode); } - void dontListenForKeys() { keysListenedFor.clear(); } + void listenForKey(const KeyPress &keyCode) + { + keysListenedFor.set(keyCode); + } + void dontListenForKeys() + { + keysListenedFor.clear(); + } s32 getMouseWheel() { @@ -189,8 +195,6 @@ public: #endif } - s32 mouse_wheel = 0; - JoystickController *joystick = nullptr; #ifdef HAVE_TOUCHSCREENGUI @@ -198,6 +202,8 @@ public: #endif private: + s32 mouse_wheel = 0; + // The current state of keys KeyList keyIsDown; @@ -272,6 +278,12 @@ public: { m_receiver->joystick = &joystick; } + + virtual ~RealInputHandler() + { + m_receiver->joystick = nullptr; + } + virtual bool isKeyDown(GameKeyType k) { return m_receiver->IsKeyDown(keycache.key[k]) || joystick.isKeyDown(k); @@ -288,6 +300,7 @@ public: { return m_receiver->WasKeyReleased(keycache.key[k]) || joystick.wasKeyReleased(k); } + virtual float getMovementSpeed() { bool f = m_receiver->IsKeyDown(keycache.key[KeyType::FORWARD]), @@ -307,6 +320,7 @@ public: } return joystick.getMovementSpeed(); } + virtual float getMovementDirection() { float x = 0, z = 0; @@ -326,10 +340,12 @@ public: else return joystick.getMovementDirection(); } + virtual bool cancelPressed() { return wasKeyDown(KeyType::ESC) || m_receiver->WasKeyDown(CancelKey); } + virtual void clearWasKeyPressed() { m_receiver->clearWasKeyPressed(); @@ -338,17 +354,21 @@ public: { m_receiver->clearWasKeyReleased(); } + virtual void listenForKey(const KeyPress &keyCode) { m_receiver->listenForKey(keyCode); } - virtual void dontListenForKeys() { m_receiver->dontListenForKeys(); } + virtual void dontListenForKeys() + { + m_receiver->dontListenForKeys(); + } + virtual v2s32 getMousePos() { - if (RenderingEngine::get_raw_device()->getCursorControl()) { - return RenderingEngine::get_raw_device() - ->getCursorControl() - ->getPosition(); + auto control = RenderingEngine::get_raw_device()->getCursorControl(); + if (control) { + return control->getPosition(); } return m_mousepos; @@ -356,16 +376,18 @@ public: virtual void setMousePos(s32 x, s32 y) { - if (RenderingEngine::get_raw_device()->getCursorControl()) { - RenderingEngine::get_raw_device() - ->getCursorControl() - ->setPosition(x, y); + auto control = RenderingEngine::get_raw_device()->getCursorControl(); + if (control) { + control->setPosition(x, y); } else { m_mousepos = v2s32(x, y); } } - virtual s32 getMouseWheel() { return m_receiver->getMouseWheel(); } + virtual s32 getMouseWheel() + { + return m_receiver->getMouseWheel(); + } void clear() { diff --git a/src/client/localplayer.cpp b/src/client/localplayer.cpp index 3f78d201d..4f1ea7bda 100644 --- a/src/client/localplayer.cpp +++ b/src/client/localplayer.cpp @@ -1096,10 +1096,8 @@ void LocalPlayer::handleAutojump(f32 dtime, Environment *env, if (m_autojump) return; - bool control_forward = keyPressed & (1 << 0); - bool could_autojump = - m_can_jump && !control.jump && !control.sneak && control_forward; + m_can_jump && !control.jump && !control.sneak && control.isMoving(); if (!could_autojump) return; diff --git a/src/client/localplayer.h b/src/client/localplayer.h index 13b35ae4e..577be2803 100644 --- a/src/client/localplayer.h +++ b/src/client/localplayer.h @@ -86,7 +86,7 @@ public: v3f last_speed; float last_pitch = 0.0f; float last_yaw = 0.0f; - unsigned int last_keyPressed = 0; + u32 last_keyPressed = 0; u8 last_camera_fov = 0; u8 last_wanted_range = 0; diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index e5a1bab1e..37b62c5cb 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -482,7 +482,6 @@ void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao, f32 yaw = (f32)f32yaw / 100.0f; u32 keyPressed = 0; - // default behavior (in case an old client doesn't send these) f32 fov = 0; u8 wanted_range = 0; @@ -508,13 +507,7 @@ void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao, playersao->setFov(fov); playersao->setWantedRange(wanted_range); - player->keyPressed = keyPressed; - player->control.jump = (keyPressed & (0x1 << 4)); - player->control.aux1 = (keyPressed & (0x1 << 5)); - player->control.sneak = (keyPressed & (0x1 << 6)); - player->control.dig = (keyPressed & (0x1 << 7)); - player->control.place = (keyPressed & (0x1 << 8)); - player->control.zoom = (keyPressed & (0x1 << 9)); + player->control.unpackKeysPressed(keyPressed); if (playersao->checkMovementCheat()) { // Call callbacks diff --git a/src/player.cpp b/src/player.cpp index d3ba5c2c2..347be30f1 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "player.h" +#include #include "threading/mutex_auto_lock.h" #include "util/numeric.h" #include "hud.h" @@ -159,6 +160,64 @@ void Player::clearHud() } } +#ifndef SERVER + +u32 PlayerControl::getKeysPressed() const +{ + u32 keypress_bits = + ( (u32)(jump & 1) << 4) | + ( (u32)(aux1 & 1) << 5) | + ( (u32)(sneak & 1) << 6) | + ( (u32)(dig & 1) << 7) | + ( (u32)(place & 1) << 8) | + ( (u32)(zoom & 1) << 9) + ; + + // If any direction keys are pressed pass those through + if (direction_keys != 0) + { + keypress_bits |= direction_keys; + } + // Otherwise set direction keys based on joystick movement (for mod compatibility) + else if (isMoving()) + { + float abs_d; + + // (absolute value indicates forward / backward) + abs_d = abs(movement_direction); + if (abs_d < 3.0f / 8.0f * M_PI) + keypress_bits |= (u32)1; // Forward + if (abs_d > 5.0f / 8.0f * M_PI) + keypress_bits |= (u32)1 << 1; // Backward + + // rotate entire coordinate system by 90 degree + abs_d = movement_direction + M_PI_2; + if (abs_d >= M_PI) + abs_d -= 2 * M_PI; + abs_d = abs(abs_d); + // (value now indicates left / right) + if (abs_d < 3.0f / 8.0f * M_PI) + keypress_bits |= (u32)1 << 2; // Left + if (abs_d > 5.0f / 8.0f * M_PI) + keypress_bits |= (u32)1 << 3; // Right + } + + return keypress_bits; +} + +#endif + +void PlayerControl::unpackKeysPressed(u32 keypress_bits) +{ + direction_keys = keypress_bits & 0xf; + jump = keypress_bits & (1 << 4); + aux1 = keypress_bits & (1 << 5); + sneak = keypress_bits & (1 << 6); + dig = keypress_bits & (1 << 7); + place = keypress_bits & (1 << 8); + zoom = keypress_bits & (1 << 9); +} + void PlayerSettings::readGlobalSettings() { free_move = g_settings->getBool("free_move"); diff --git a/src/player.h b/src/player.h index 3800e1a33..d769acdad 100644 --- a/src/player.h +++ b/src/player.h @@ -49,18 +49,18 @@ struct PlayerControl PlayerControl() = default; PlayerControl( - bool a_jump, - bool a_aux1, - bool a_sneak, + bool a_up, bool a_down, bool a_left, bool a_right, + bool a_jump, bool a_aux1, bool a_sneak, bool a_zoom, - bool a_dig, - bool a_place, - float a_pitch, - float a_yaw, - float a_movement_speed, - float a_movement_direction + bool a_dig, bool a_place, + float a_pitch, float a_yaw, + float a_movement_speed, float a_movement_direction ) { + // Encode direction keys into a single value so nobody uses it accidentally + // as movement_{speed,direction} is supposed to be the source of truth. + direction_keys = (a_up&1) | ((a_down&1) << 1) | + ((a_left&1) << 2) | ((a_right&1) << 3); jump = a_jump; aux1 = a_aux1; sneak = a_sneak; @@ -72,15 +72,26 @@ struct PlayerControl movement_speed = a_movement_speed; movement_direction = a_movement_direction; } + +#ifndef SERVER + // For client use + u32 getKeysPressed() const; + inline bool isMoving() const { return movement_speed > 0.001f; } +#endif + + // For server use + void unpackKeysPressed(u32 keypress_bits); + + u8 direction_keys = 0; bool jump = false; bool aux1 = false; bool sneak = false; bool zoom = false; bool dig = false; bool place = false; + // Note: These four are NOT available on the server float pitch = 0.0f; float yaw = 0.0f; - // Note: These two are NOT available on the server float movement_speed = 0.0f; float movement_direction = 0.0f; }; @@ -189,8 +200,6 @@ public: return m_fov_override_spec; } - u32 keyPressed = 0; - HudElement* getHud(u32 id); u32 addHud(HudElement* hud); HudElement* removeHud(u32 id); diff --git a/src/script/lua_api/l_localplayer.cpp b/src/script/lua_api/l_localplayer.cpp index bdbe98cb0..2efb976c7 100644 --- a/src/script/lua_api/l_localplayer.cpp +++ b/src/script/lua_api/l_localplayer.cpp @@ -230,13 +230,15 @@ int LuaLocalPlayer::l_get_control(lua_State *L) set("dig", c.dig); set("place", c.place); // Player movement in polar coordinates and non-binary speed - set("movement_speed", c.movement_speed); - set("movement_direction", c.movement_direction); + lua_pushnumber(L, c.movement_speed); + lua_setfield(L, -2, "movement_speed"); + lua_pushnumber(L, c.movement_direction); + lua_setfield(L, -2, "movement_direction"); // Provide direction keys to ensure compatibility - set("up", player->keyPressed & (1 << 0)); // Up, down, left, and right were removed in favor of - set("down", player->keyPressed & (1 << 1)); // analog direction indicators and are therefore not - set("left", player->keyPressed & (1 << 2)); // available as booleans anymore. The corresponding values - set("right", player->keyPressed & (1 << 3)); // can still be read from the keyPressed bits though. + set("up", c.direction_keys & (1 << 0)); + set("down", c.direction_keys & (1 << 1)); + set("left", c.direction_keys & (1 << 2)); + set("right", c.direction_keys & (1 << 3)); return 1; } diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index 072b13d80..7d937b306 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -1367,20 +1367,18 @@ int ObjectRef::l_get_player_control(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == nullptr) { - lua_pushlstring(L, "", 0); - return 1; - } + if (player == nullptr) + return 0; const PlayerControl &control = player->getPlayerControl(); lua_newtable(L); - lua_pushboolean(L, player->keyPressed & (1 << 0)); + lua_pushboolean(L, control.direction_keys & (1 << 0)); lua_setfield(L, -2, "up"); - lua_pushboolean(L, player->keyPressed & (1 << 1)); + lua_pushboolean(L, control.direction_keys & (1 << 1)); lua_setfield(L, -2, "down"); - lua_pushboolean(L, player->keyPressed & (1 << 2)); + lua_pushboolean(L, control.direction_keys & (1 << 2)); lua_setfield(L, -2, "left"); - lua_pushboolean(L, player->keyPressed & (1 << 3)); + lua_pushboolean(L, control.direction_keys & (1 << 3)); lua_setfield(L, -2, "right"); lua_pushboolean(L, control.jump); lua_setfield(L, -2, "jump"); @@ -1408,12 +1406,24 @@ int ObjectRef::l_get_player_control_bits(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == nullptr) { - lua_pushlstring(L, "", 0); - return 1; - } + if (player == nullptr) + return 0; + + const auto &c = player->getPlayerControl(); + + // This is very close to PlayerControl::getKeysPressed() but duplicated + // here so the encoding in the API is not inadvertedly changed. + u32 keypress_bits = + c.direction_keys | + ( (u32)(c.jump & 1) << 4) | + ( (u32)(c.aux1 & 1) << 5) | + ( (u32)(c.sneak & 1) << 6) | + ( (u32)(c.dig & 1) << 7) | + ( (u32)(c.place & 1) << 8) | + ( (u32)(c.zoom & 1) << 9) + ; - lua_pushnumber(L, player->keyPressed); + lua_pushinteger(L, keypress_bits); return 1; } -- cgit v1.2.3 From 4c8c6497799c83cb5bac773ac4eac7ea572ec78f Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 9 Jan 2022 21:15:35 +0100 Subject: Mainmenu game-related changes (#11887) fixes: * Switching between games does not immediately hide creative mode / damage buttons if so specified * World creation menu has a game selection list even though the menu already provides a gamebar * Showing gameid in world list is unnecessary * Choice of mapgen parameters in menu persists between games (and was half-broken) --- builtin/mainmenu/common.lua | 16 +- builtin/mainmenu/dlg_create_world.lua | 266 ++++++++++++++++++---------------- builtin/mainmenu/tab_local.lua | 84 +++++------ src/content/subgames.cpp | 17 +-- src/map_settings_manager.cpp | 9 +- src/mapgen/mapgen.cpp | 7 +- src/script/lua_api/l_mainmenu.cpp | 50 +++++-- src/settings.cpp | 12 +- src/settings.h | 5 +- 9 files changed, 259 insertions(+), 207 deletions(-) (limited to 'src/script/lua_api') diff --git a/builtin/mainmenu/common.lua b/builtin/mainmenu/common.lua index b36c9596a..8db8bb8d1 100644 --- a/builtin/mainmenu/common.lua +++ b/builtin/mainmenu/common.lua @@ -125,17 +125,21 @@ os.tmpname = function() end -------------------------------------------------------------------------------- -function menu_render_worldlist() - local retval = "" +function menu_render_worldlist(show_gameid) + local retval = {} local current_worldlist = menudata.worldlist:get_list() + local row for i, v in ipairs(current_worldlist) do - if retval ~= "" then retval = retval .. "," end - retval = retval .. core.formspec_escape(v.name) .. - " \\[" .. core.formspec_escape(v.gameid) .. "\\]" + row = v.name + if show_gameid == nil or show_gameid == true then + row = row .. " [" .. v.gameid .. "]" + end + retval[#retval+1] = core.formspec_escape(row) + end - return retval + return table.concat(retval, ",") end function menu_handle_key_up_down(fields, textlist, settingname) diff --git a/builtin/mainmenu/dlg_create_world.lua b/builtin/mainmenu/dlg_create_world.lua index 5456eb3eb..8d1509f33 100644 --- a/builtin/mainmenu/dlg_create_world.lua +++ b/builtin/mainmenu/dlg_create_world.lua @@ -15,7 +15,8 @@ --with this program; if not, write to the Free Software Foundation, Inc., --51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -local worldname = "" +-- cf. tab_local, the gamebar already provides game selection so we hide the list from here +local hide_gamelist = PLATFORM ~= "Android" local function table_to_flags(ftable) -- Convert e.g. { jungles = true, caves = false } to "jungles,nocaves" @@ -31,9 +32,8 @@ local function strflag(flags, flag) return (flags[flag] == true) and "true" or "false" end -local cb_caverns = { "caverns", fgettext("Caverns"), "caverns", +local cb_caverns = { "caverns", fgettext("Caverns"), fgettext("Very large caverns deep in the underground") } -local tt_sea_rivers = fgettext("Sea level rivers") local flag_checkboxes = { v5 = { @@ -41,39 +41,38 @@ local flag_checkboxes = { }, v7 = { cb_caverns, - { "ridges", fgettext("Rivers"), "ridges", tt_sea_rivers }, - { "mountains", fgettext("Mountains"), "mountains" }, - { "floatlands", fgettext("Floatlands (experimental)"), "floatlands", + { "ridges", fgettext("Rivers"), fgettext("Sea level rivers") }, + { "mountains", fgettext("Mountains") }, + { "floatlands", fgettext("Floatlands (experimental)"), fgettext("Floating landmasses in the sky") }, }, carpathian = { cb_caverns, - { "rivers", fgettext("Rivers"), "rivers", tt_sea_rivers }, + { "rivers", fgettext("Rivers"), fgettext("Sea level rivers") }, }, valleys = { - { "altitude-chill", fgettext("Altitude chill"), "altitude_chill", + { "altitude_chill", fgettext("Altitude chill"), fgettext("Reduces heat with altitude") }, - { "altitude-dry", fgettext("Altitude dry"), "altitude_dry", + { "altitude_dry", fgettext("Altitude dry"), fgettext("Reduces humidity with altitude") }, - { "humid-rivers", fgettext("Humid rivers"), "humid_rivers", + { "humid_rivers", fgettext("Humid rivers"), fgettext("Increases humidity around rivers") }, - { "vary-river-depth", fgettext("Vary river depth"), "vary_river_depth", + { "vary_river_depth", fgettext("Vary river depth"), fgettext("Low humidity and high heat causes shallow or dry rivers") }, }, flat = { cb_caverns, - { "hills", fgettext("Hills"), "hills" }, - { "lakes", fgettext("Lakes"), "lakes" }, + { "hills", fgettext("Hills") }, + { "lakes", fgettext("Lakes") }, }, fractal = { - { "terrain", fgettext("Additional terrain"), "terrain", + { "terrain", fgettext("Additional terrain"), fgettext("Generate non-fractal terrain: Oceans and underground") }, }, v6 = { - { "trees", fgettext("Trees and jungle grass"), "trees" }, - { "flat", fgettext("Flat terrain"), "flat" }, - { "mudflow", fgettext("Mud flow"), "mudflow", - fgettext("Terrain surface erosion") }, + { "trees", fgettext("Trees and jungle grass") }, + { "flat", fgettext("Flat terrain") }, + { "mudflow", fgettext("Mud flow"), fgettext("Terrain surface erosion") }, -- Biome settings are in mgv6_biomes below }, } @@ -105,38 +104,26 @@ local function create_world_formspec(dialogdata) "button[4.75,2.5;3,0.5;world_create_cancel;" .. fgettext("Cancel") .. "]" end + local current_mg = dialogdata.mg local mapgens = core.get_mapgen_names() - local current_seed = core.settings:get("fixed_map_seed") or "" - local current_mg = core.settings:get("mg_name") local gameid = core.settings:get("menu_last_game") - local flags = { - main = core.settings:get_flags("mg_flags"), - v5 = core.settings:get_flags("mgv5_spflags"), - v6 = core.settings:get_flags("mgv6_spflags"), - v7 = core.settings:get_flags("mgv7_spflags"), - fractal = core.settings:get_flags("mgfractal_spflags"), - carpathian = core.settings:get_flags("mgcarpathian_spflags"), - valleys = core.settings:get_flags("mgvalleys_spflags"), - flat = core.settings:get_flags("mgflat_spflags"), - } - - local gameidx = 0 - if gameid ~= nil then - local _ - _, gameidx = pkgmgr.find_by_gameid(gameid) + local flags = dialogdata.flags - if gameidx == nil then - gameidx = 0 - end + local game, gameidx = pkgmgr.find_by_gameid(gameid) + if game == nil and hide_gamelist then + -- should never happen but just pick the first game + game = pkgmgr.get_game(1) + gameidx = 1 + core.settings:set("menu_last_game", game.id) + elseif game == nil then + gameidx = 0 end - local game_by_gameidx = core.get_game(gameidx) local disallowed_mapgen_settings = {} - if game_by_gameidx ~= nil then - local gamepath = game_by_gameidx.path - local gameconfig = Settings(gamepath.."/game.conf") + if game ~= nil then + local gameconfig = Settings(game.path.."/game.conf") local allowed_mapgens = (gameconfig:get("allowed_mapgens") or ""):split() for key, value in pairs(allowed_mapgens) do @@ -156,7 +143,7 @@ local function create_world_formspec(dialogdata) end end - if disallowed_mapgens then + if #disallowed_mapgens > 0 then for i = #mapgens, 1, -1 do if table.indexof(disallowed_mapgens, mapgens[i]) > 0 then table.remove(mapgens, i) @@ -172,23 +159,29 @@ local function create_world_formspec(dialogdata) local mglist = "" local selindex - local i = 1 - local first_mg - for k,v in pairs(mapgens) do - if not first_mg then - first_mg = v + do -- build the list of mapgens + local i = 1 + local first_mg + for k, v in pairs(mapgens) do + if not first_mg then + first_mg = v + end + if current_mg == v then + selindex = i + end + i = i + 1 + mglist = mglist .. core.formspec_escape(v) .. "," end - if current_mg == v then - selindex = i + if not selindex then + selindex = 1 + current_mg = first_mg end - i = i + 1 - mglist = mglist .. v .. "," - end - if not selindex then - selindex = 1 - current_mg = first_mg + mglist = mglist:sub(1, -2) end - mglist = mglist:sub(1, -2) + + -- The logic of the flag element IDs is as follows: + -- "flag_main_foo-bar-baz" controls dialogdata.flags["main"]["foo_bar_baz"] + -- see the buttonhandler for the implementation of this local mg_main_flags = function(mapgen, y) if mapgen == "singlenode" then @@ -198,11 +191,11 @@ local function create_world_formspec(dialogdata) return "", y end - local form = "checkbox[0," .. y .. ";flag_mg_caves;" .. + local form = "checkbox[0," .. y .. ";flag_main_caves;" .. fgettext("Caves") .. ";"..strflag(flags.main, "caves").."]" y = y + 0.5 - form = form .. "checkbox[0,"..y..";flag_mg_dungeons;" .. + form = form .. "checkbox[0,"..y..";flag_main_dungeons;" .. fgettext("Dungeons") .. ";"..strflag(flags.main, "dungeons").."]" y = y + 0.5 @@ -213,7 +206,7 @@ local function create_world_formspec(dialogdata) else d_tt = fgettext("Structures appearing on the terrain, typically trees and plants") end - form = form .. "checkbox[0,"..y..";flag_mg_decorations;" .. + form = form .. "checkbox[0,"..y..";flag_main_decorations;" .. d_name .. ";" .. strflag(flags.main, "decorations").."]" .. "tooltip[flag_mg_decorations;" .. @@ -221,7 +214,7 @@ local function create_world_formspec(dialogdata) "]" y = y + 0.5 - form = form .. "tooltip[flag_mg_caves;" .. + form = form .. "tooltip[flag_main_caves;" .. fgettext("Network of tunnels and caves") .. "]" return form, y @@ -235,13 +228,13 @@ local function create_world_formspec(dialogdata) return "", y end local form = "" - for _,tab in pairs(flag_checkboxes[mapgen]) do - local id = "flag_mg"..mapgen.."_"..tab[1] + for _, tab in pairs(flag_checkboxes[mapgen]) do + local id = "flag_"..mapgen.."_"..tab[1]:gsub("_", "-") form = form .. ("checkbox[0,%f;%s;%s;%s]"): - format(y, id, tab[2], strflag(flags[mapgen], tab[3])) + format(y, id, tab[2], strflag(flags[mapgen], tab[1])) - if tab[4] then - form = form .. "tooltip["..id..";"..tab[4].."]" + if tab[3] then + form = form .. "tooltip["..id..";"..tab[3].."]" end y = y + 0.5 end @@ -277,16 +270,14 @@ local function create_world_formspec(dialogdata) -- biomeblend y = y + 0.55 - form = form .. "checkbox[0,"..y..";flag_mgv6_biomeblend;" .. + form = form .. "checkbox[0,"..y..";flag_v6_biomeblend;" .. fgettext("Biome blending") .. ";"..strflag(flags.v6, "biomeblend").."]" .. - "tooltip[flag_mgv6_biomeblend;" .. + "tooltip[flag_v6_biomeblend;" .. fgettext("Smooth transition between biomes") .. "]" return form, y end - current_seed = core.formspec_escape(current_seed) - local y_start = 0.0 local y = y_start local str_flags, str_spflags @@ -323,21 +314,27 @@ local function create_world_formspec(dialogdata) "container[0,0]".. "field[0.3,0.6;6,0.5;te_world_name;" .. fgettext("World name") .. - ";" .. core.formspec_escape(worldname) .. "]" .. + ";" .. core.formspec_escape(dialogdata.worldname) .. "]" .. + "set_focus[te_world_name;false]" .. "field[0.3,1.7;6,0.5;te_seed;" .. fgettext("Seed") .. - ";".. current_seed .. "]" .. + ";".. core.formspec_escape(dialogdata.seed) .. "]" .. "label[0,2;" .. fgettext("Mapgen") .. "]".. - "dropdown[0,2.5;6.3;dd_mapgen;" .. mglist .. ";" .. selindex .. "]" .. + "dropdown[0,2.5;6.3;dd_mapgen;" .. mglist .. ";" .. selindex .. "]" + + if not hide_gamelist or devtest_only ~= "" then + retval = retval .. + "label[0,3.35;" .. fgettext("Game") .. "]".. + "textlist[0,3.85;5.8,"..gamelist_height..";games;" .. + pkgmgr.gamelist() .. ";" .. gameidx .. ";false]" .. + "container[0,4.5]" .. + devtest_only .. + "container_end[]" + end - "label[0,3.35;" .. fgettext("Game") .. "]".. - "textlist[0,3.85;5.8,"..gamelist_height..";games;" .. - pkgmgr.gamelist() .. ";" .. gameidx .. ";false]" .. - "container[0,4.5]" .. - devtest_only .. - "container_end[]" .. + retval = retval .. "container_end[]" .. -- Right side @@ -360,9 +357,20 @@ local function create_world_buttonhandler(this, fields) fields["key_enter"] then local worldname = fields["te_world_name"] - local gameindex = core.get_textlist_index("games") + local game, gameindex + if hide_gamelist then + game, gameindex = pkgmgr.find_by_gameid(core.settings:get("menu_last_game")) + else + gameindex = core.get_textlist_index("games") + game = pkgmgr.get_game(gameindex) + end - if gameindex ~= nil then + local message + if game == nil then + message = fgettext("No game selected") + end + + if message == nil then -- For unnamed worlds use the generated name 'world', -- where the number increments: it is set to 1 larger than the largest -- generated name number found. @@ -377,36 +385,48 @@ local function create_world_buttonhandler(this, fields) worldname = "world" .. worldnum_max + 1 end - core.settings:set("fixed_map_seed", fields["te_seed"]) - - local message - if not menudata.worldlist:uid_exists_raw(worldname) then - core.settings:set("mg_name",fields["dd_mapgen"]) - message = core.create_world(worldname,gameindex) - else + if menudata.worldlist:uid_exists_raw(worldname) then message = fgettext("A world named \"$1\" already exists", worldname) end + end - if message ~= nil then - gamedata.errormessage = message - else - core.settings:set("menu_last_game",pkgmgr.games[gameindex].id) - if this.data.update_worldlist_filter then - menudata.worldlist:set_filtercriteria(pkgmgr.games[gameindex].id) - mm_game_theme.update("singleplayer", pkgmgr.games[gameindex].id) - end - menudata.worldlist:refresh() - core.settings:set("mainmenu_last_selected_world", - menudata.worldlist:raw_index_by_uid(worldname)) + if message == nil then + this.data.seed = fields["te_seed"] + this.data.mg = fields["dd_mapgen"] + + -- actual names as used by engine + local settings = { + fixed_map_seed = this.data.seed, + mg_name = this.data.mg, + mg_flags = table_to_flags(this.data.flags.main), + mgv5_spflags = table_to_flags(this.data.flags.v5), + mgv6_spflags = table_to_flags(this.data.flags.v6), + mgv7_spflags = table_to_flags(this.data.flags.v7), + mgfractal_spflags = table_to_flags(this.data.flags.fractal), + mgcarpathian_spflags = table_to_flags(this.data.flags.carpathian), + mgvalleys_spflags = table_to_flags(this.data.flags.valleys), + mgflat_spflags = table_to_flags(this.data.flags.flat), + } + message = core.create_world(worldname, gameindex, settings) + end + + if message == nil then + core.settings:set("menu_last_game", game.id) + if this.data.update_worldlist_filter then + menudata.worldlist:set_filtercriteria(game.id) end - else - gamedata.errormessage = fgettext("No game selected") + menudata.worldlist:refresh() + core.settings:set("mainmenu_last_selected_world", + menudata.worldlist:raw_index_by_uid(worldname)) end + + gamedata.errormessage = message this:delete() return true end - worldname = fields.te_world_name + this.data.worldname = fields["te_world_name"] + this.data.seed = fields["te_seed"] if fields["games"] then local gameindex = core.get_textlist_index("games") @@ -417,22 +437,11 @@ local function create_world_buttonhandler(this, fields) for k,v in pairs(fields) do local split = string.split(k, "_", nil, 3) if split and split[1] == "flag" then - local setting - if split[2] == "mg" then - setting = "mg_flags" - else - setting = split[2].."_spflags" - end -- We replaced the underscore of flag names with a dash. local flag = string.gsub(split[3], "-", "_") - local ftable = core.settings:get_flags(setting) - if v == "true" then - ftable[flag] = true - else - ftable[flag] = false - end - local flags = table_to_flags(ftable) - core.settings:set(setting, flags) + local ftable = this.data.flags[split[2]] + assert(ftable) + ftable[flag] = v == "true" return true end end @@ -446,18 +455,16 @@ local function create_world_buttonhandler(this, fields) local entry = core.formspec_escape(fields["mgv6_biomes"]) for b=1, #mgv6_biomes do if entry == mgv6_biomes[b][1] then - local ftable = core.settings:get_flags("mgv6_spflags") + local ftable = this.data.flags.v6 ftable.jungles = mgv6_biomes[b][2].jungles ftable.snowbiomes = mgv6_biomes[b][2].snowbiomes - local flags = table_to_flags(ftable) - core.settings:set("mgv6_spflags", flags) return true end end end if fields["dd_mapgen"] then - core.settings:set("mg_name", fields["dd_mapgen"]) + this.data.mg = fields["dd_mapgen"] return true end @@ -466,12 +473,27 @@ end function create_create_world_dlg(update_worldlistfilter) - worldname = "" local retval = dialog_create("sp_create_world", create_world_formspec, create_world_buttonhandler, nil) - retval.update_worldlist_filter = update_worldlistfilter + retval.data = { + update_worldlist_filter = update_worldlistfilter, + worldname = "", + -- settings the world is created with: + seed = core.settings:get("fixed_map_seed") or "", + mg = core.settings:get("mg_name"), + flags = { + main = core.settings:get_flags("mg_flags"), + v5 = core.settings:get_flags("mgv5_spflags"), + v6 = core.settings:get_flags("mgv6_spflags"), + v7 = core.settings:get_flags("mgv7_spflags"), + fractal = core.settings:get_flags("mgfractal_spflags"), + carpathian = core.settings:get_flags("mgcarpathian_spflags"), + valleys = core.settings:get_flags("mgvalleys_spflags"), + flat = core.settings:get_flags("mgflat_spflags"), + } + } return retval end diff --git a/builtin/mainmenu/tab_local.lua b/builtin/mainmenu/tab_local.lua index 2d1a616a8..e77c6f04d 100644 --- a/builtin/mainmenu/tab_local.lua +++ b/builtin/mainmenu/tab_local.lua @@ -33,10 +33,30 @@ if enable_gamebar then return game end + -- Apply menu changes from given game + function apply_game(game) + core.set_topleft_text(game.name) + core.settings:set("menu_last_game", game.id) + menudata.worldlist:set_filtercriteria(game.id) + + mm_game_theme.update("singleplayer", game) -- this refreshes the formspec + + local index = filterlist.get_current_index(menudata.worldlist, + tonumber(core.settings:get("mainmenu_last_selected_world"))) + if not index or index < 1 then + local selected = core.get_textlist_index("sp_worlds") + if selected ~= nil and selected < #menudata.worldlist:get_list() then + index = selected + else + index = #menudata.worldlist:get_list() + end + end + menu_worldmt_legacy(index) + end + function singleplayer_refresh_gamebar() local old_bar = ui.find_by_name("game_button_bar") - if old_bar ~= nil then old_bar:delete() end @@ -51,26 +71,10 @@ if enable_gamebar then return true end - for key,value in pairs(fields) do - for j=1,#pkgmgr.games,1 do - if ("game_btnbar_" .. pkgmgr.games[j].id == key) then - mm_game_theme.update("singleplayer", pkgmgr.games[j]) - core.set_topleft_text(pkgmgr.games[j].name) - core.settings:set("menu_last_game",pkgmgr.games[j].id) - menudata.worldlist:set_filtercriteria(pkgmgr.games[j].id) - local index = filterlist.get_current_index(menudata.worldlist, - tonumber(core.settings:get("mainmenu_last_selected_world"))) - if not index or index < 1 then - local selected = core.get_textlist_index("sp_worlds") - if selected ~= nil and selected < #menudata.worldlist:get_list() then - index = selected - else - index = #menudata.worldlist:get_list() - end - end - menu_worldmt_legacy(index) - return true - end + for _, game in ipairs(pkgmgr.games) do + if fields["game_btnbar_" .. game.id] then + apply_game(game) + return true end end end @@ -79,25 +83,22 @@ if enable_gamebar then game_buttonbar_button_handler, {x=-0.3,y=5.9}, "horizontal", {x=12.4,y=1.15}) - for i=1,#pkgmgr.games,1 do - local btn_name = "game_btnbar_" .. pkgmgr.games[i].id + for _, game in ipairs(pkgmgr.games) do + local btn_name = "game_btnbar_" .. game.id local image = nil local text = nil - local tooltip = core.formspec_escape(pkgmgr.games[i].name) + local tooltip = core.formspec_escape(game.name) - if pkgmgr.games[i].menuicon_path ~= nil and - pkgmgr.games[i].menuicon_path ~= "" then - image = core.formspec_escape(pkgmgr.games[i].menuicon_path) + if (game.menuicon_path or "") ~= "" then + image = core.formspec_escape(game.menuicon_path) else - - local part1 = pkgmgr.games[i].id:sub(1,5) - local part2 = pkgmgr.games[i].id:sub(6,10) - local part3 = pkgmgr.games[i].id:sub(11) + local part1 = game.id:sub(1,5) + local part2 = game.id:sub(6,10) + local part3 = game.id:sub(11) text = part1 .. "\n" .. part2 - if part3 ~= nil and - part3 ~= "" then + if part3 ~= "" then text = text .. "\n" .. part3 end end @@ -147,8 +148,12 @@ local function get_formspec(tabview, name, tabdata) tonumber(core.settings:get("mainmenu_last_selected_world"))) local list = menudata.worldlist:get_list() local world = list and index and list[index] - local gameid = world and world.gameid - local game = gameid and pkgmgr.find_by_gameid(gameid) + local game + if world then + game = pkgmgr.find_by_gameid(world.gameid) + else + game = current_game() + end local disabled_settings = get_disabled_settings(game) local creative, damage, host = "", "", "" @@ -182,7 +187,7 @@ local function get_formspec(tabview, name, tabdata) damage .. host .. "textlist[3.9,0.4;7.9,3.45;sp_worlds;" .. - menu_render_worldlist() .. + menu_render_worldlist(not enable_gamebar) .. ";" .. index .. "]" if core.settings:get_bool("enable_server") and disabled_settings["enable_server"] == nil then @@ -319,7 +324,7 @@ local function main_button_handler(this, fields, name, tabdata) end if fields["world_create"] ~= nil then - local create_world_dlg = create_create_world_dlg(true) + local create_world_dlg = create_create_world_dlg(enable_gamebar) create_world_dlg:set_parent(this) this:hide() create_world_dlg:show() @@ -371,11 +376,8 @@ if enable_gamebar then function on_change(type, old_tab, new_tab) if (type == "ENTER") then local game = current_game() - if game then - menudata.worldlist:set_filtercriteria(game.id) - core.set_topleft_text(game.name) - mm_game_theme.update("singleplayer",game) + apply_game(game) end singleplayer_refresh_gamebar() diff --git a/src/content/subgames.cpp b/src/content/subgames.cpp index e834f40cd..62e82e0e4 100644 --- a/src/content/subgames.cpp +++ b/src/content/subgames.cpp @@ -24,7 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "log.h" #include "util/strfnd.h" #include "defaultsettings.h" // for set_default_settings -#include "mapgen/mapgen.h" // for MapgenParams +#include "map_settings_manager.h" #include "util/string.h" #ifndef SERVER @@ -370,19 +370,12 @@ void loadGameConfAndInitWorld(const std::string &path, const std::string &name, // Create map_meta.txt if does not already exist std::string map_meta_path = final_path + DIR_DELIM + "map_meta.txt"; if (!fs::PathExists(map_meta_path)) { - verbosestream << "Creating map_meta.txt (" << map_meta_path << ")" - << std::endl; - std::ostringstream oss(std::ios_base::binary); + MapSettingsManager mgr(map_meta_path); - Settings conf; - MapgenParams params; - - params.readParams(g_settings); - params.writeParams(&conf); - conf.writeLines(oss); - oss << "[end_of_params]\n"; + mgr.setMapSetting("seed", g_settings->get("fixed_map_seed")); - fs::safeWriteToFile(map_meta_path, oss.str()); + mgr.makeMapgenParams(); + mgr.saveMapMeta(); } // The Settings object is no longer needed for created worlds diff --git a/src/map_settings_manager.cpp b/src/map_settings_manager.cpp index 7e86a9937..c75483edb 100644 --- a/src/map_settings_manager.cpp +++ b/src/map_settings_manager.cpp @@ -52,14 +52,7 @@ MapSettingsManager::~MapSettingsManager() bool MapSettingsManager::getMapSetting( const std::string &name, std::string *value_out) { - // Try getting it normally first - if (m_map_settings->getNoEx(name, *value_out)) - return true; - - // If not we may have to resolve some compatibility kludges - if (name == "seed") - return Settings::getLayer(SL_GLOBAL)->getNoEx("fixed_map_seed", *value_out); - return false; + return m_map_settings->getNoEx(name, *value_out); } diff --git a/src/mapgen/mapgen.cpp b/src/mapgen/mapgen.cpp index 7984ff609..d767bd264 100644 --- a/src/mapgen/mapgen.cpp +++ b/src/mapgen/mapgen.cpp @@ -1018,10 +1018,11 @@ MapgenParams::~MapgenParams() void MapgenParams::readParams(const Settings *settings) { - std::string seed_str; - const char *seed_name = (settings == g_settings) ? "fixed_map_seed" : "seed"; + // should always be used via MapSettingsManager + assert(settings != g_settings); - if (settings->getNoEx(seed_name, seed_str)) { + std::string seed_str; + if (settings->getNoEx("seed", seed_str)) { if (!seed_str.empty()) seed = read_seed(seed_str.c_str()); else diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index 3d80bdafa..736ad022f 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -414,25 +414,53 @@ int ModApiMainMenu::l_create_world(lua_State *L) const char *name = luaL_checkstring(L, 1); int gameidx = luaL_checkinteger(L,2) -1; + StringMap use_settings; + luaL_checktype(L, 3, LUA_TTABLE); + lua_pushnil(L); + while (lua_next(L, 3) != 0) { + // key at index -2 and value at index -1 + use_settings[luaL_checkstring(L, -2)] = luaL_checkstring(L, -1); + lua_pop(L, 1); + } + lua_pop(L, 1); + std::string path = porting::path_user + DIR_DELIM "worlds" + DIR_DELIM + sanitizeDirName(name, "world_"); std::vector games = getAvailableGames(); + if (gameidx < 0 || gameidx >= (int) games.size()) { + lua_pushstring(L, "Invalid game index"); + return 1; + } - if ((gameidx >= 0) && - (gameidx < (int) games.size())) { + // Set the settings for world creation + // this is a bad hack but the best we have right now.. + StringMap backup; + for (auto it : use_settings) { + if (g_settings->existsLocal(it.first)) + backup[it.first] = g_settings->get(it.first); + g_settings->set(it.first, it.second); + } - // Create world if it doesn't exist - try { - loadGameConfAndInitWorld(path, name, games[gameidx], true); - lua_pushnil(L); - } catch (const BaseException &e) { - lua_pushstring(L, (std::string("Failed to initialize world: ") + e.what()).c_str()); - } - } else { - lua_pushstring(L, "Invalid game index"); + // Create world if it doesn't exist + try { + loadGameConfAndInitWorld(path, name, games[gameidx], true); + lua_pushnil(L); + } catch (const BaseException &e) { + auto err = std::string("Failed to initialize world: ") + e.what(); + lua_pushstring(L, err.c_str()); } + + // Restore previous settings + for (auto it : use_settings) { + auto it2 = backup.find(it.first); + if (it2 == backup.end()) + g_settings->remove(it.first); // wasn't set before + else + g_settings->set(it.first, it2->second); // was set before + } + return 1; } diff --git a/src/settings.cpp b/src/settings.cpp index cf7ec1b72..0e44ee0bc 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -659,9 +659,7 @@ bool Settings::getNoiseParamsFromGroup(const std::string &name, bool Settings::exists(const std::string &name) const { - MutexAutoLock lock(m_mutex); - - if (m_settings.find(name) != m_settings.end()) + if (existsLocal(name)) return true; if (auto parent = getParent()) return parent->exists(name); @@ -669,6 +667,14 @@ bool Settings::exists(const std::string &name) const } +bool Settings::existsLocal(const std::string &name) const +{ + MutexAutoLock lock(m_mutex); + + return m_settings.find(name) != m_settings.end(); +} + + std::vector Settings::getNames() const { MutexAutoLock lock(m_mutex); diff --git a/src/settings.h b/src/settings.h index 4e32a3488..767d057f9 100644 --- a/src/settings.h +++ b/src/settings.h @@ -172,9 +172,12 @@ public: bool getNoiseParamsFromValue(const std::string &name, NoiseParams &np) const; bool getNoiseParamsFromGroup(const std::string &name, NoiseParams &np) const; - // return all keys used + // return all keys used in this object std::vector getNames() const; + // check if setting exists anywhere in the hierarchy bool exists(const std::string &name) const; + // check if setting exists in this object ("locally") + bool existsLocal(const std::string &name) const; /*************************************** -- cgit v1.2.3 From 37d80784ddfc0ff07baee214570c80dc5dd92ca7 Mon Sep 17 00:00:00 2001 From: Zughy <63455151+Zughy@users.noreply.github.com> Date: Sat, 22 Jan 2022 12:42:49 +0100 Subject: Allow resetting celestial vault elements by leaving its arguments empty (#11922) --- doc/lua_api.txt | 11 +++- src/client/clouds.h | 2 +- src/cloudparams.h | 30 --------- src/remoteplayer.cpp | 17 ++--- src/remoteplayer.h | 1 - src/script/lua_api/l_object.cpp | 140 ++++++++++++++++++++-------------------- src/skyparams.h | 43 ++++++++++-- 7 files changed, 124 insertions(+), 120 deletions(-) delete mode 100644 src/cloudparams.h (limited to 'src/script/lua_api') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 3edfd5bb1..00c29f791 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -6794,12 +6794,15 @@ object you are working with still exists. * `set_sky(sky_parameters)` * The presence of the function `set_sun`, `set_moon` or `set_stars` indicates whether `set_sky` accepts this format. Check the legacy format otherwise. + * Passing no arguments resets the sky to its default values. * `sky_parameters` is a table with the following optional fields: * `base_color`: ColorSpec, changes fog in "skybox" and "plain". + (default: `#ffffff`) * `type`: Available types: * `"regular"`: Uses 0 textures, `base_color` ignored * `"skybox"`: Uses 6 textures, `base_color` used as fog. * `"plain"`: Uses 0 textures, `base_color` used as both fog and sky. + (default: `"regular"`) * `textures`: A table containing up to six textures in the following order: Y+ (top), Y- (bottom), X- (west), X+ (east), Z+ (north), Z- (south). * `clouds`: Boolean for whether clouds appear. (default: `true`) @@ -6828,9 +6831,9 @@ object you are working with still exists. * `indoors`: ColorSpec, for when you're either indoors or underground. (default: `#646464`) * `fog_sun_tint`: ColorSpec, changes the fog tinting for the sun - at sunrise and sunset. + at sunrise and sunset. (default: `#f47d1d`) * `fog_moon_tint`: ColorSpec, changes the fog tinting for the moon - at sunrise and sunset. + at sunrise and sunset. (default: `#7f99cc`) * `fog_tint_type`: string, changes which mode the directional fog abides by, `"custom"` uses `sun_tint` and `moon_tint`, while `"default"` uses the classic Minetest sun and moon tinting. @@ -6848,6 +6851,7 @@ object you are working with still exists. * `get_sky_color()`: returns a table with the `sky_color` parameters as in `set_sky`. * `set_sun(sun_parameters)`: + * Passing no arguments resets the sun to its default values. * `sun_parameters` is a table with the following optional fields: * `visible`: Boolean for whether the sun is visible. (default: `true`) @@ -6863,6 +6867,7 @@ object you are working with still exists. * `get_sun()`: returns a table with the current sun parameters as in `set_sun`. * `set_moon(moon_parameters)`: + * Passing no arguments resets the moon to its default values. * `moon_parameters` is a table with the following optional fields: * `visible`: Boolean for whether the moon is visible. (default: `true`) @@ -6874,6 +6879,7 @@ object you are working with still exists. * `get_moon()`: returns a table with the current moon parameters as in `set_moon`. * `set_stars(star_parameters)`: + * Passing no arguments resets stars to their default values. * `star_parameters` is a table with the following optional fields: * `visible`: Boolean for whether the stars are visible. (default: `true`) @@ -6887,6 +6893,7 @@ object you are working with still exists. * `get_stars()`: returns a table with the current stars parameters as in `set_stars`. * `set_clouds(cloud_parameters)`: set cloud parameters + * Passing no arguments resets clouds to their default values. * `cloud_parameters` is a table with the following optional fields: * `density`: from `0` (no clouds) to `1` (full clouds) (default `0.4`) * `color`: basic cloud color with alpha channel, ColorSpec diff --git a/src/client/clouds.h b/src/client/clouds.h index c009a05b7..6db88d93c 100644 --- a/src/client/clouds.h +++ b/src/client/clouds.h @@ -22,7 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_extrabloated.h" #include #include "constants.h" -#include "cloudparams.h" +#include "skyparams.h" // Menu clouds class Clouds; diff --git a/src/cloudparams.h b/src/cloudparams.h deleted file mode 100644 index 88b5760ee..000000000 --- a/src/cloudparams.h +++ /dev/null @@ -1,30 +0,0 @@ -/* -Minetest -Copyright (C) 2017 bendeutsch, Ben Deutsch - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#pragma once - -struct CloudParams -{ - float density; - video::SColor color_bright; - video::SColor color_ambient; - float thickness; - float height; - v2f speed; -}; diff --git a/src/remoteplayer.cpp b/src/remoteplayer.cpp index d537965a2..3f0eae0f0 100644 --- a/src/remoteplayer.cpp +++ b/src/remoteplayer.cpp @@ -68,19 +68,10 @@ RemotePlayer::RemotePlayer(const char *name, IItemDefManager *idef): m_cloud_params.speed = v2f(0.0f, -2.0f); // Skybox defaults: - - SkyboxDefaults sky_defaults; - - m_skybox_params.sky_color = sky_defaults.getSkyColorDefaults(); - m_skybox_params.type = "regular"; - m_skybox_params.clouds = true; - m_skybox_params.fog_sun_tint = video::SColor(255, 244, 125, 29); - m_skybox_params.fog_moon_tint = video::SColorf(0.5, 0.6, 0.8, 1).toSColor(); - m_skybox_params.fog_tint_type = "default"; - - m_sun_params = sky_defaults.getSunDefaults(); - m_moon_params = sky_defaults.getMoonDefaults(); - m_star_params = sky_defaults.getStarDefaults(); + m_skybox_params = SkyboxDefaults::getSkyDefaults(); + m_sun_params = SkyboxDefaults::getSunDefaults(); + m_moon_params = SkyboxDefaults::getMoonDefaults(); + m_star_params = SkyboxDefaults::getStarDefaults(); } diff --git a/src/remoteplayer.h b/src/remoteplayer.h index bd39b68ba..c8991480b 100644 --- a/src/remoteplayer.h +++ b/src/remoteplayer.h @@ -21,7 +21,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include "player.h" -#include "cloudparams.h" #include "skyparams.h" class PlayerSAO; diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index 7d937b306..b177a9f7e 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -1732,9 +1732,11 @@ int ObjectRef::l_set_sky(lua_State *L) return 0; SkyboxParams sky_params = player->getSkyParams(); - bool is_colorspec = is_color_table(L, 2); - if (lua_istable(L, 2) && !is_colorspec) { + // reset if empty + if (lua_isnoneornil(L, 2) && lua_isnone(L, 3)) { + sky_params = SkyboxDefaults::getSkyDefaults(); + } else if (lua_istable(L, 2) && !is_color_table(L, 2)) { lua_getfield(L, 2, "base_color"); if (!lua_isnil(L, -1)) read_color(L, -1, &sky_params.bgcolor); @@ -1758,17 +1760,11 @@ int ObjectRef::l_set_sky(lua_State *L) } lua_pop(L, 1); - /* - We want to avoid crashes, so we're checking even if we're not using them. - However, we want to ensure that the skybox can be set to nil when - using "regular" or "plain" skybox modes as textures aren't needed. - */ - - if (sky_params.textures.size() != 6 && sky_params.textures.size() > 0) + // Validate that we either have six or zero textures + if (sky_params.textures.size() != 6 && !sky_params.textures.empty()) throw LuaError("Skybox expects 6 textures!"); - sky_params.clouds = getboolfield_default(L, 2, - "clouds", sky_params.clouds); + sky_params.clouds = getboolfield_default(L, 2, "clouds", sky_params.clouds); lua_getfield(L, 2, "sky_color"); if (lua_istable(L, -1)) { @@ -1816,7 +1812,7 @@ int ObjectRef::l_set_sky(lua_State *L) sky_params.fog_tint_type = luaL_checkstring(L, -1); lua_pop(L, 1); - // Because we need to leave the "sky_color" table. + // pop "sky_color" table lua_pop(L, 1); } } else { @@ -1852,11 +1848,8 @@ int ObjectRef::l_set_sky(lua_State *L) if (lua_istable(L, 4)) { lua_pushnil(L); while (lua_next(L, 4) != 0) { - // Key at index -2, and value at index -1 - if (lua_isstring(L, -1)) - sky_params.textures.emplace_back(readParam(L, -1)); - else - sky_params.textures.emplace_back(""); + // Key at index -2, and value at index -1 + sky_params.textures.emplace_back(readParam(L, -1)); // Remove the value, keep the key for the next iteration lua_pop(L, 1); } @@ -1872,6 +1865,7 @@ int ObjectRef::l_set_sky(lua_State *L) getServer(L)->setMoon(player, moon_params); getServer(L)->setStars(player, star_params); } + getServer(L)->setSky(player, sky_params); lua_pushboolean(L, true); return 1; @@ -1947,21 +1941,20 @@ int ObjectRef::l_set_sun(lua_State *L) if (player == nullptr) return 0; - luaL_checktype(L, 2, LUA_TTABLE); SunParams sun_params = player->getSunParams(); - sun_params.visible = getboolfield_default(L, 2, - "visible", sun_params.visible); - sun_params.texture = getstringfield_default(L, 2, - "texture", sun_params.texture); - sun_params.tonemap = getstringfield_default(L, 2, - "tonemap", sun_params.tonemap); - sun_params.sunrise = getstringfield_default(L, 2, - "sunrise", sun_params.sunrise); - sun_params.sunrise_visible = getboolfield_default(L, 2, - "sunrise_visible", sun_params.sunrise_visible); - sun_params.scale = getfloatfield_default(L, 2, - "scale", sun_params.scale); + // reset if empty + if (lua_isnoneornil(L, 2)) { + sun_params = SkyboxDefaults::getSunDefaults(); + } else { + luaL_checktype(L, 2, LUA_TTABLE); + sun_params.visible = getboolfield_default(L, 2, "visible", sun_params.visible); + sun_params.texture = getstringfield_default(L, 2, "texture", sun_params.texture); + sun_params.tonemap = getstringfield_default(L, 2, "tonemap", sun_params.tonemap); + sun_params.sunrise = getstringfield_default(L, 2, "sunrise", sun_params.sunrise); + sun_params.sunrise_visible = getboolfield_default(L, 2, "sunrise_visible", sun_params.sunrise_visible); + sun_params.scale = getfloatfield_default(L, 2, "scale", sun_params.scale); + } getServer(L)->setSun(player, sun_params); lua_pushboolean(L, true); @@ -2004,17 +1997,18 @@ int ObjectRef::l_set_moon(lua_State *L) if (player == nullptr) return 0; - luaL_checktype(L, 2, LUA_TTABLE); MoonParams moon_params = player->getMoonParams(); - moon_params.visible = getboolfield_default(L, 2, - "visible", moon_params.visible); - moon_params.texture = getstringfield_default(L, 2, - "texture", moon_params.texture); - moon_params.tonemap = getstringfield_default(L, 2, - "tonemap", moon_params.tonemap); - moon_params.scale = getfloatfield_default(L, 2, - "scale", moon_params.scale); + // reset if empty + if (lua_isnoneornil(L, 2)) { + moon_params = SkyboxDefaults::getMoonDefaults(); + } else { + luaL_checktype(L, 2, LUA_TTABLE); + moon_params.visible = getboolfield_default(L, 2, "visible", moon_params.visible); + moon_params.texture = getstringfield_default(L, 2, "texture", moon_params.texture); + moon_params.tonemap = getstringfield_default(L, 2, "tonemap", moon_params.tonemap); + moon_params.scale = getfloatfield_default(L, 2, "scale", moon_params.scale); + } getServer(L)->setMoon(player, moon_params); lua_pushboolean(L, true); @@ -2053,21 +2047,24 @@ int ObjectRef::l_set_stars(lua_State *L) if (player == nullptr) return 0; - luaL_checktype(L, 2, LUA_TTABLE); StarParams star_params = player->getStarParams(); - star_params.visible = getboolfield_default(L, 2, - "visible", star_params.visible); - star_params.count = getintfield_default(L, 2, - "count", star_params.count); + // reset if empty + if (lua_isnoneornil(L, 2)) { + star_params = SkyboxDefaults::getStarDefaults(); + } else { + luaL_checktype(L, 2, LUA_TTABLE); + star_params.visible = getboolfield_default(L, 2, "visible", star_params.visible); + star_params.count = getintfield_default(L, 2, "count", star_params.count); - lua_getfield(L, 2, "star_color"); - if (!lua_isnil(L, -1)) - read_color(L, -1, &star_params.starcolor); - lua_pop(L, 1); + lua_getfield(L, 2, "star_color"); + if (!lua_isnil(L, -1)) + read_color(L, -1, &star_params.starcolor); + lua_pop(L, 1); - star_params.scale = getfloatfield_default(L, 2, - "scale", star_params.scale); + star_params.scale = getfloatfield_default(L, 2, + "scale", star_params.scale); + } getServer(L)->setStars(player, star_params); lua_pushboolean(L, true); @@ -2106,31 +2103,36 @@ int ObjectRef::l_set_clouds(lua_State *L) if (player == nullptr) return 0; - luaL_checktype(L, 2, LUA_TTABLE); CloudParams cloud_params = player->getCloudParams(); - cloud_params.density = getfloatfield_default(L, 2, "density", cloud_params.density); + // reset if empty + if (lua_isnoneornil(L, 2)) { + cloud_params = SkyboxDefaults::getCloudDefaults(); + } else { + luaL_checktype(L, 2, LUA_TTABLE); + cloud_params.density = getfloatfield_default(L, 2, "density", cloud_params.density); - lua_getfield(L, 2, "color"); - if (!lua_isnil(L, -1)) - read_color(L, -1, &cloud_params.color_bright); - lua_pop(L, 1); - lua_getfield(L, 2, "ambient"); - if (!lua_isnil(L, -1)) - read_color(L, -1, &cloud_params.color_ambient); - lua_pop(L, 1); + lua_getfield(L, 2, "color"); + if (!lua_isnil(L, -1)) + read_color(L, -1, &cloud_params.color_bright); + lua_pop(L, 1); + lua_getfield(L, 2, "ambient"); + if (!lua_isnil(L, -1)) + read_color(L, -1, &cloud_params.color_ambient); + lua_pop(L, 1); - cloud_params.height = getfloatfield_default(L, 2, "height", cloud_params.height ); - cloud_params.thickness = getfloatfield_default(L, 2, "thickness", cloud_params.thickness); + cloud_params.height = getfloatfield_default(L, 2, "height", cloud_params.height); + cloud_params.thickness = getfloatfield_default(L, 2, "thickness", cloud_params.thickness); - lua_getfield(L, 2, "speed"); - if (lua_istable(L, -1)) { - v2f new_speed; - new_speed.X = getfloatfield_default(L, -1, "x", 0); - new_speed.Y = getfloatfield_default(L, -1, "z", 0); - cloud_params.speed = new_speed; + lua_getfield(L, 2, "speed"); + if (lua_istable(L, -1)) { + v2f new_speed; + new_speed.X = getfloatfield_default(L, -1, "x", 0); + new_speed.Y = getfloatfield_default(L, -1, "z", 0); + cloud_params.speed = new_speed; + } + lua_pop(L, 1); } - lua_pop(L, 1); getServer(L)->setClouds(player, cloud_params); lua_pushboolean(L, true); diff --git a/src/skyparams.h b/src/skyparams.h index 1de494d69..cabbf531c 100644 --- a/src/skyparams.h +++ b/src/skyparams.h @@ -68,11 +68,34 @@ struct StarParams f32 scale; }; +struct CloudParams +{ + float density; + video::SColor color_bright; + video::SColor color_ambient; + float thickness; + float height; + v2f speed; +}; + // Utility class for setting default sky, sun, moon, stars values: class SkyboxDefaults { public: - const SkyColor getSkyColorDefaults() + static const SkyboxParams getSkyDefaults() + { + SkyboxParams sky; + sky.bgcolor = video::SColor(255, 255, 255, 255); + sky.type = "regular"; + sky.clouds = true; + sky.sky_color = getSkyColorDefaults(); + sky.fog_sun_tint = video::SColor(255, 244, 125, 29); + sky.fog_moon_tint = video::SColorf(0.5, 0.6, 0.8, 1).toSColor(); + sky.fog_tint_type = "default"; + return sky; + } + + static const SkyColor getSkyColorDefaults() { SkyColor sky; // Horizon colors @@ -87,7 +110,7 @@ public: return sky; } - const SunParams getSunDefaults() + static const SunParams getSunDefaults() { SunParams sun; sun.visible = true; @@ -99,7 +122,7 @@ public: return sun; } - const MoonParams getMoonDefaults() + static const MoonParams getMoonDefaults() { MoonParams moon; moon.visible = true; @@ -109,7 +132,7 @@ public: return moon; } - const StarParams getStarDefaults() + static const StarParams getStarDefaults() { StarParams stars; stars.visible = true; @@ -118,4 +141,16 @@ public: stars.scale = 1; return stars; } + + static const CloudParams getCloudDefaults() + { + CloudParams clouds; + clouds.density = 0.4f; + clouds.color_bright = video::SColor(229, 240, 240, 255); + clouds.color_ambient = video::SColor(255, 0, 0, 0); + clouds.thickness = 16.0f; + clouds.height = 120; + clouds.speed = v2f(0.0f, -2.0f); + return clouds; + } }; -- cgit v1.2.3 From fe0b2d02bf07966ce4554578a1efd4b07bbb4734 Mon Sep 17 00:00:00 2001 From: Lars Müller <34514239+appgurueu@users.noreply.github.com> Date: Thu, 27 Jan 2022 22:22:58 +0100 Subject: Define control(bits) as "unset" for entities (#11995) --- doc/lua_api.txt | 25 ++++++++++++++----------- src/script/lua_api/l_object.cpp | 13 ++++++++----- 2 files changed, 22 insertions(+), 16 deletions(-) (limited to 'src/script/lua_api') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index e37567ec3..faaed55e1 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -6716,18 +6716,21 @@ object you are working with still exists. `aux1`, `sneak`, `dig`, `place`, `LMB`, `RMB`, and `zoom`. * The fields `LMB` and `RMB` are equal to `dig` and `place` respectively, and exist only to preserve backwards compatibility. + * Returns an empty table `{}` if the object is not a player. * `get_player_control_bits()`: returns integer with bit packed player pressed - keys. Bits: - * 0 - up - * 1 - down - * 2 - left - * 3 - right - * 4 - jump - * 5 - aux1 - * 6 - sneak - * 7 - dig - * 8 - place - * 9 - zoom + keys. + * Bits: + * 0 - up + * 1 - down + * 2 - left + * 3 - right + * 4 - jump + * 5 - aux1 + * 6 - sneak + * 7 - dig + * 8 - place + * 9 - zoom + * Returns `0` (no bits set) if the object is not a player. * `set_physics_override(override_table)` * `override_table` is a table with the following fields: * `speed`: multiplier to default walking speed value (default: `1`) diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index b177a9f7e..407b48db0 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -1367,11 +1367,12 @@ int ObjectRef::l_get_player_control(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == nullptr) - return 0; - const PlayerControl &control = player->getPlayerControl(); lua_newtable(L); + if (player == nullptr) + return 1; + + const PlayerControl &control = player->getPlayerControl(); lua_pushboolean(L, control.direction_keys & (1 << 0)); lua_setfield(L, -2, "up"); lua_pushboolean(L, control.direction_keys & (1 << 1)); @@ -1406,8 +1407,10 @@ int ObjectRef::l_get_player_control_bits(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == nullptr) - return 0; + if (player == nullptr) { + lua_pushinteger(L, 0); + return 1; + } const auto &c = player->getPlayerControl(); -- cgit v1.2.3 From 128f6359e936bcdc5e26409ddd73438bce9c6dd6 Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Sun, 30 Jan 2022 22:40:53 +0000 Subject: Use virtual paths to specify exact mod to enable (#11784) --- builtin/mainmenu/dlg_config_world.lua | 25 ++++++++-- builtin/mainmenu/dlg_settings_advanced.lua | 2 +- builtin/mainmenu/pkgmgr.lua | 72 +++++++++++++++++++--------- doc/menu_lua_api.txt | 17 +++++-- doc/world_format.txt | 13 +++++ src/content/mods.cpp | 76 +++++++++++++++++++++--------- src/content/mods.h | 57 ++++++++++++++++++---- src/content/subgames.cpp | 11 ++--- src/content/subgames.h | 10 ++-- src/script/lua_api/l_mainmenu.cpp | 12 ++--- src/server/mods.cpp | 6 ++- 11 files changed, 222 insertions(+), 79 deletions(-) (limited to 'src/script/lua_api') diff --git a/builtin/mainmenu/dlg_config_world.lua b/builtin/mainmenu/dlg_config_world.lua index 9bdf92a74..510d9f804 100644 --- a/builtin/mainmenu/dlg_config_world.lua +++ b/builtin/mainmenu/dlg_config_world.lua @@ -205,14 +205,19 @@ local function handle_buttons(this, fields) local mods = worldfile:to_table() local rawlist = this.data.list:get_raw_list() + local was_set = {} for i = 1, #rawlist do local mod = rawlist[i] if not mod.is_modpack and not mod.is_game_content then if modname_valid(mod.name) then - worldfile:set("load_mod_" .. mod.name, - mod.enabled and "true" or "false") + if mod.enabled then + worldfile:set("load_mod_" .. mod.name, mod.virtual_path) + was_set[mod.name] = true + elseif not was_set[mod.name] then + worldfile:set("load_mod_" .. mod.name, "false") + end elseif mod.enabled then gamedata.errormessage = fgettext_ne("Failed to enable mo" .. "d \"$1\" as it contains disallowed characters. " .. @@ -256,12 +261,26 @@ local function handle_buttons(this, fields) if fields.btn_enable_all_mods then local list = this.data.list:get_raw_list() + -- When multiple copies of a mod are installed, we need to avoid enabling multiple of them + -- at a time. So lets first collect all the enabled mods, and then use this to exclude + -- multiple enables. + + local was_enabled = {} for i = 1, #list do if not list[i].is_game_content - and not list[i].is_modpack then + and not list[i].is_modpack and list[i].enabled then + was_enabled[list[i].name] = true + end + end + + for i = 1, #list do + if not list[i].is_game_content and not list[i].is_modpack and + not was_enabled[list[i].name] then list[i].enabled = true + was_enabled[list[i].name] = true end end + enabled_all = true return true end diff --git a/builtin/mainmenu/dlg_settings_advanced.lua b/builtin/mainmenu/dlg_settings_advanced.lua index 06fd32d84..772509670 100644 --- a/builtin/mainmenu/dlg_settings_advanced.lua +++ b/builtin/mainmenu/dlg_settings_advanced.lua @@ -378,7 +378,7 @@ local function parse_config_file(read_all, parse_mods) -- Parse mods local mods_category_initialized = false local mods = {} - get_mods(core.get_modpath(), mods) + get_mods(core.get_modpath(), "mods", mods) for _, mod in ipairs(mods) do local path = mod.path .. DIR_DELIM .. FILENAME local file = io.open(path, "r") diff --git a/builtin/mainmenu/pkgmgr.lua b/builtin/mainmenu/pkgmgr.lua index 6de671529..eeeb5641b 100644 --- a/builtin/mainmenu/pkgmgr.lua +++ b/builtin/mainmenu/pkgmgr.lua @@ -100,12 +100,13 @@ local function load_texture_packs(txtpath, retval) end end -function get_mods(path,retval,modpack) +function get_mods(path, virtual_path, retval, modpack) local mods = core.get_dir_list(path, true) for _, name in ipairs(mods) do if name:sub(1, 1) ~= "." then - local prefix = path .. DIR_DELIM .. name + local mod_path = path .. DIR_DELIM .. name + local mod_virtual_path = virtual_path .. "/" .. name local toadd = { dir_name = name, parent_dir = path, @@ -114,18 +115,18 @@ function get_mods(path,retval,modpack) -- Get config file local mod_conf - local modpack_conf = io.open(prefix .. DIR_DELIM .. "modpack.conf") + local modpack_conf = io.open(mod_path .. DIR_DELIM .. "modpack.conf") if modpack_conf then toadd.is_modpack = true modpack_conf:close() - mod_conf = Settings(prefix .. DIR_DELIM .. "modpack.conf"):to_table() + mod_conf = Settings(mod_path .. DIR_DELIM .. "modpack.conf"):to_table() if mod_conf.name then name = mod_conf.name toadd.is_name_explicit = true end else - mod_conf = Settings(prefix .. DIR_DELIM .. "mod.conf"):to_table() + mod_conf = Settings(mod_path .. DIR_DELIM .. "mod.conf"):to_table() if mod_conf.name then name = mod_conf.name toadd.is_name_explicit = true @@ -136,12 +137,13 @@ function get_mods(path,retval,modpack) toadd.name = name toadd.author = mod_conf.author toadd.release = tonumber(mod_conf.release) or 0 - toadd.path = prefix + toadd.path = mod_path + toadd.virtual_path = mod_virtual_path toadd.type = "mod" -- Check modpack.txt -- Note: modpack.conf is already checked above - local modpackfile = io.open(prefix .. DIR_DELIM .. "modpack.txt") + local modpackfile = io.open(mod_path .. DIR_DELIM .. "modpack.txt") if modpackfile then modpackfile:close() toadd.is_modpack = true @@ -153,7 +155,7 @@ function get_mods(path,retval,modpack) elseif toadd.is_modpack then toadd.type = "modpack" toadd.is_modpack = true - get_mods(prefix, retval, name) + get_mods(mod_path, mod_virtual_path, retval, name) end end end @@ -397,6 +399,14 @@ function pkgmgr.is_modpack_entirely_enabled(data, name) return true end +local function disable_all_by_name(list, name, except) + for i=1, #list do + if list[i].name == name and list[i] ~= except then + list[i].enabled = false + end + end +end + ---------- toggles or en/disables a mod or modpack and its dependencies -------- local function toggle_mod_or_modpack(list, toggled_mods, enabled_mods, toset, mod) if not mod.is_modpack then @@ -404,6 +414,9 @@ local function toggle_mod_or_modpack(list, toggled_mods, enabled_mods, toset, mo if toset == nil then toset = not mod.enabled end + if toset then + disable_all_by_name(list, mod.name, mod) + end if mod.enabled ~= toset then mod.enabled = toset toggled_mods[#toggled_mods+1] = mod.name @@ -648,8 +661,8 @@ function pkgmgr.preparemodlist(data) --read global mods local modpaths = core.get_modpaths() - for _, modpath in ipairs(modpaths) do - get_mods(modpath, global_mods) + for key, modpath in pairs(modpaths) do + get_mods(modpath, key, global_mods) end for i=1,#global_mods,1 do @@ -688,22 +701,37 @@ function pkgmgr.preparemodlist(data) DIR_DELIM .. "world.mt" local worldfile = Settings(filename) - - for key,value in pairs(worldfile:to_table()) do + for key, value in pairs(worldfile:to_table()) do if key:sub(1, 9) == "load_mod_" then key = key:sub(10) - local element = nil - for i=1,#retval,1 do + local mod_found = false + + local fallback_found = false + local fallback_mod = nil + + for i=1, #retval do if retval[i].name == key and - not retval[i].is_modpack then - element = retval[i] - break + not retval[i].is_modpack then + if core.is_yes(value) or retval[i].virtual_path == value then + retval[i].enabled = true + mod_found = true + break + elseif fallback_found then + -- Only allow fallback if only one mod matches + fallback_mod = nil + else + fallback_found = true + fallback_mod = retval[i] + end end end - if element ~= nil then - element.enabled = value ~= "false" and value ~= "nil" and value - else - core.log("info", "Mod: " .. key .. " " .. dump(value) .. " but not found") + + if not mod_found then + if fallback_mod and value:find("/") then + fallback_mod.enabled = true + else + core.log("info", "Mod: " .. key .. " " .. dump(value) .. " but not found") + end end end end @@ -797,7 +825,7 @@ function pkgmgr.get_game_mods(gamespec, retval) if gamespec ~= nil and gamespec.gamemods_path ~= nil and gamespec.gamemods_path ~= "" then - get_mods(gamespec.gamemods_path, retval) + get_mods(gamespec.gamemods_path, ("games/%s/mods"):format(gamespec.id), retval) end end diff --git a/doc/menu_lua_api.txt b/doc/menu_lua_api.txt index a8928441e..c2931af31 100644 --- a/doc/menu_lua_api.txt +++ b/doc/menu_lua_api.txt @@ -221,13 +221,24 @@ Package - content which is downloadable from the content db, may or may not be i * returns path to global user data, the directory that contains user-provided mods, worlds, games, and texture packs. * core.get_modpath() (possible in async calls) - * returns path to global modpath, where mods can be installed + * returns path to global modpath in the user path, where mods can be installed * core.get_modpaths() (possible in async calls) - * returns list of paths to global modpaths, where mods have been installed - + * returns table of virtual path to global modpaths, where mods have been installed The difference with "core.get_modpath" is that no mods should be installed in these directories by Minetest -- they might be read-only. + Ex: + + ``` + { + mods = "/home/user/.minetest/mods", + share = "/usr/share/minetest/mods", + + -- Custom dirs can be specified by the MINETEST_MOD_DIR env variable + ["/path/to/custom/dir"] = "/path/to/custom/dir", + } + ``` + * core.get_clientmodpath() (possible in async calls) * returns path to global client-side modpath * core.get_gamepath() (possible in async calls) diff --git a/doc/world_format.txt b/doc/world_format.txt index eb1d7f728..98c9d2009 100644 --- a/doc/world_format.txt +++ b/doc/world_format.txt @@ -133,6 +133,19 @@ Example content (added indentation and - explanations): load_mod_ = false - whether is to be loaded in this world auth_backend = files - which DB backend to use for authentication data +For load_mod_, the possible values are: + +* `false` - Do not load the mod. +* `true` - Load the mod from wherever it is found (may cause conflicts if the same mod appears also in some other place). +* `mods/modpack/moddir` - Relative path to the mod + * Must be one of the following: + * `mods/`: mods in the user path's mods folder (ex `/home/user/.minetest/mods`) + * `share/`: mods in the share's mods folder (ex: `/usr/share/minetest/mods`) + * `/path/to/env`: you can use absolute paths to mods inside folders specified with the `MINETEST_MOD_PATH` env variable. + * Other locations and absolute paths are not supported + * Note that `moddir` is the directory name, not the mod name specified in mod.conf. + + Player File Format =================== diff --git a/src/content/mods.cpp b/src/content/mods.cpp index 455506967..f75119bbb 100644 --- a/src/content/mods.cpp +++ b/src/content/mods.cpp @@ -89,7 +89,7 @@ void parseModContents(ModSpec &spec) modpack2_is.close(); spec.is_modpack = true; - spec.modpack_content = getModsInPath(spec.path, true); + spec.modpack_content = getModsInPath(spec.path, spec.virtual_path, true); } else { Settings info; @@ -167,13 +167,14 @@ void parseModContents(ModSpec &spec) } std::map getModsInPath( - const std::string &path, bool part_of_modpack) + const std::string &path, const std::string &virtual_path, bool part_of_modpack) { // NOTE: this function works in mutual recursion with parseModContents std::map result; std::vector dirlist = fs::GetDirListing(path); - std::string modpath; + std::string mod_path; + std::string mod_virtual_path; for (const fs::DirListNode &dln : dirlist) { if (!dln.dir) @@ -185,10 +186,14 @@ std::map getModsInPath( if (modname[0] == '.') continue; - modpath.clear(); - modpath.append(path).append(DIR_DELIM).append(modname); + mod_path.clear(); + mod_path.append(path).append(DIR_DELIM).append(modname); - ModSpec spec(modname, modpath, part_of_modpack); + mod_virtual_path.clear(); + // Intentionally uses / to keep paths same on different platforms + mod_virtual_path.append(virtual_path).append("/").append(modname); + + ModSpec spec(modname, mod_path, part_of_modpack, mod_virtual_path); parseModContents(spec); result.insert(std::make_pair(modname, spec)); } @@ -228,9 +233,9 @@ void ModConfiguration::printUnsatisfiedModsError() const } } -void ModConfiguration::addModsInPath(const std::string &path) +void ModConfiguration::addModsInPath(const std::string &path, const std::string &virtual_path) { - addMods(flattenMods(getModsInPath(path))); + addMods(flattenMods(getModsInPath(path, virtual_path))); } void ModConfiguration::addMods(const std::vector &new_mods) @@ -294,29 +299,39 @@ void ModConfiguration::addMods(const std::vector &new_mods) } void ModConfiguration::addModsFromConfig( - const std::string &settings_path, const std::set &mods) + const std::string &settings_path, + const std::unordered_map &modPaths) { Settings conf; - std::set load_mod_names; + std::unordered_map load_mod_names; conf.readConfigFile(settings_path.c_str()); std::vector names = conf.getNames(); for (const std::string &name : names) { - if (name.compare(0, 9, "load_mod_") == 0 && conf.get(name) != "false" && - conf.get(name) != "nil") - load_mod_names.insert(name.substr(9)); + const auto &value = conf.get(name); + if (name.compare(0, 9, "load_mod_") == 0 && value != "false" && + value != "nil") + load_mod_names[name.substr(9)] = value; } std::vector addon_mods; - for (const std::string &i : mods) { - std::vector addon_mods_in_path = flattenMods(getModsInPath(i)); + std::unordered_map> candidates; + + for (const auto &modPath : modPaths) { + std::vector addon_mods_in_path = flattenMods(getModsInPath(modPath.second, modPath.first)); for (std::vector::const_iterator it = addon_mods_in_path.begin(); it != addon_mods_in_path.end(); ++it) { const ModSpec &mod = *it; - if (load_mod_names.count(mod.name) != 0) - addon_mods.push_back(mod); - else + const auto &pair = load_mod_names.find(mod.name); + if (pair != load_mod_names.end()) { + if (is_yes(pair->second) || pair->second == mod.virtual_path) { + addon_mods.push_back(mod); + } else { + candidates[pair->first].emplace_back(mod.virtual_path); + } + } else { conf.setBool("load_mod_" + mod.name, false); + } } } conf.updateConfigFile(settings_path.c_str()); @@ -335,9 +350,22 @@ void ModConfiguration::addModsFromConfig( if (!load_mod_names.empty()) { errorstream << "The following mods could not be found:"; - for (const std::string &mod : load_mod_names) - errorstream << " \"" << mod << "\""; + for (const auto &pair : load_mod_names) + errorstream << " \"" << pair.first << "\""; errorstream << std::endl; + + for (const auto &pair : load_mod_names) { + const auto &candidate = candidates.find(pair.first); + if (candidate != candidates.end()) { + errorstream << "Unable to load " << pair.first << " as the specified path " + << pair.second << " could not be found. " + << "However, it is available in the following locations:" + << std::endl; + for (const auto &path : candidate->second) { + errorstream << " - " << path << std::endl; + } + } + } } } @@ -413,10 +441,12 @@ void ModConfiguration::resolveDependencies() ClientModConfiguration::ClientModConfiguration(const std::string &path) : ModConfiguration(path) { - std::set paths; + std::unordered_map paths; std::string path_user = porting::path_user + DIR_DELIM + "clientmods"; - paths.insert(path); - paths.insert(path_user); + if (path != path_user) { + paths["share"] = path; + } + paths["mods"] = path_user; std::string settings_path = path_user + DIR_DELIM + "mods.conf"; addModsFromConfig(settings_path, paths); diff --git a/src/content/mods.h b/src/content/mods.h index dd3b6e0e6..ab0a9300e 100644 --- a/src/content/mods.h +++ b/src/content/mods.h @@ -51,17 +51,36 @@ struct ModSpec bool part_of_modpack = false; bool is_modpack = false; + /** + * A constructed canonical path to represent this mod's location. + * This intended to be used as an identifier for a modpath that tolerates file movement, + * and cannot be used to read the mod files. + * + * Note that `mymod` is the directory name, not the mod name specified in mod.conf. + * + * Ex: + * + * - mods/mymod + * - mods/mymod (1) + * (^ this would have name=mymod in mod.conf) + * - mods/modpack1/mymod + * - games/mygame/mods/mymod + * - worldmods/mymod + */ + std::string virtual_path; + // For logging purposes std::vector deprecation_msgs; // if modpack: std::map modpack_content; - ModSpec(const std::string &name = "", const std::string &path = "") : - name(name), path(path) + + ModSpec() { } - ModSpec(const std::string &name, const std::string &path, bool part_of_modpack) : - name(name), path(path), part_of_modpack(part_of_modpack) + + ModSpec(const std::string &name, const std::string &path, bool part_of_modpack, const std::string &virtual_path) : + name(name), path(path), part_of_modpack(part_of_modpack), virtual_path(virtual_path) { } @@ -71,8 +90,16 @@ struct ModSpec // Retrieves depends, optdepends, is_modpack and modpack_content void parseModContents(ModSpec &mod); -std::map getModsInPath( - const std::string &path, bool part_of_modpack = false); +/** + * Gets a list of all mods and modpacks in path + * + * @param Path to search, should be absolute + * @param part_of_modpack Is this searching within a modpack? + * @param virtual_path Virtual path for this directory, see comment in ModSpec + * @returns map of mods + */ +std::map getModsInPath(const std::string &path, + const std::string &virtual_path, bool part_of_modpack = false); // replaces modpack Modspecs with their content std::vector flattenMods(const std::map &mods); @@ -97,15 +124,25 @@ public: protected: ModConfiguration(const std::string &worldpath); - // adds all mods in the given path. used for games, modpacks - // and world-specific mods (worldmods-folders) - void addModsInPath(const std::string &path); + + /** + * adds all mods in the given path. used for games, modpacks + * and world-specific mods (worldmods-folders) + * + * @param path To search, should be absolute + * @param virtual_path Virtual path for this directory, see comment in ModSpec + */ + void addModsInPath(const std::string &path, const std::string &virtual_path); // adds all mods in the set. void addMods(const std::vector &new_mods); + /** + * @param settings_path Path to world.mt + * @param modPaths Map from virtual name to mod path + */ void addModsFromConfig(const std::string &settings_path, - const std::set &mods); + const std::unordered_map &modPaths); void checkConflictsAndDeps(); diff --git a/src/content/subgames.cpp b/src/content/subgames.cpp index 62e82e0e4..23355990e 100644 --- a/src/content/subgames.cpp +++ b/src/content/subgames.cpp @@ -107,14 +107,13 @@ SubgameSpec findSubgame(const std::string &id) std::string gamemod_path = game_path + DIR_DELIM + "mods"; // Find mod directories - std::set mods_paths; - if (!user_game) - mods_paths.insert(share + DIR_DELIM + "mods"); - if (user != share || user_game) - mods_paths.insert(user + DIR_DELIM + "mods"); + std::unordered_map mods_paths; + mods_paths["mods"] = user + DIR_DELIM + "mods"; + if (!user_game && user != share) + mods_paths["share"] = share + DIR_DELIM + "mods"; for (const std::string &mod_path : getEnvModPaths()) { - mods_paths.insert(mod_path); + mods_paths[fs::AbsolutePath(mod_path)] = mod_path; } // Get meta diff --git a/src/content/subgames.h b/src/content/subgames.h index 4a50803e8..d36b4952f 100644 --- a/src/content/subgames.h +++ b/src/content/subgames.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include +#include #include class Settings; @@ -33,13 +34,16 @@ struct SubgameSpec int release; std::string path; std::string gamemods_path; - std::set addon_mods_paths; + + /** + * Map from virtual path to mods path + */ + std::unordered_map addon_mods_paths; std::string menuicon_path; SubgameSpec(const std::string &id = "", const std::string &path = "", const std::string &gamemods_path = "", - const std::set &addon_mods_paths = - std::set(), + const std::unordered_map &addon_mods_paths = {}, const std::string &name = "", const std::string &menuicon_path = "", const std::string &author = "", int release = 0) : diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index 736ad022f..db031dde5 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -323,9 +323,9 @@ int ModApiMainMenu::l_get_games(lua_State *L) lua_newtable(L); int table2 = lua_gettop(L); int internal_index = 1; - for (const std::string &addon_mods_path : game.addon_mods_paths) { + for (const auto &addon_mods_path : game.addon_mods_paths) { lua_pushnumber(L, internal_index); - lua_pushstring(L, addon_mods_path.c_str()); + lua_pushstring(L, addon_mods_path.second.c_str()); lua_settable(L, table2); internal_index++; } @@ -533,14 +533,14 @@ int ModApiMainMenu::l_get_modpath(lua_State *L) /******************************************************************************/ int ModApiMainMenu::l_get_modpaths(lua_State *L) { - int index = 1; lua_newtable(L); + ModApiMainMenu::l_get_modpath(L); - lua_rawseti(L, -2, index); + lua_setfield(L, -2, "mods"); + for (const std::string &component : getEnvModPaths()) { - index++; lua_pushstring(L, component.c_str()); - lua_rawseti(L, -2, index); + lua_setfield(L, -2, fs::AbsolutePath(component).c_str()); } return 1; } diff --git a/src/server/mods.cpp b/src/server/mods.cpp index 609d8c346..ba76d4746 100644 --- a/src/server/mods.cpp +++ b/src/server/mods.cpp @@ -41,8 +41,10 @@ ServerModManager::ServerModManager(const std::string &worldpath) : SubgameSpec gamespec = findWorldSubgame(worldpath); // Add all game mods and all world mods - addModsInPath(gamespec.gamemods_path); - addModsInPath(worldpath + DIR_DELIM + "worldmods"); + std::string game_virtual_path; + game_virtual_path.append("games/").append(gamespec.id).append("/mods"); + addModsInPath(gamespec.gamemods_path, game_virtual_path); + addModsInPath(worldpath + DIR_DELIM + "worldmods", "worldmods"); // Load normal mods std::string worldmt = worldpath + DIR_DELIM + "world.mt"; -- cgit v1.2.3 From afb061c374ed6797f47b0806aba26845713d15ac Mon Sep 17 00:00:00 2001 From: sfan5 Date: Fri, 4 Feb 2022 20:29:28 +0100 Subject: Fix broken server startup if curl is disabled (#12046) --- src/script/lua_api/l_http.cpp | 35 +++++++++++++++++++++-------------- src/script/lua_api/l_http.h | 7 ++++--- 2 files changed, 25 insertions(+), 17 deletions(-) (limited to 'src/script/lua_api') diff --git a/src/script/lua_api/l_http.cpp b/src/script/lua_api/l_http.cpp index bd359b3cc..5566a8523 100644 --- a/src/script/lua_api/l_http.cpp +++ b/src/script/lua_api/l_http.cpp @@ -162,20 +162,6 @@ int ModApiHttp::l_http_fetch_async_get(lua_State *L) return 1; } -int ModApiHttp::l_set_http_api_lua(lua_State *L) -{ - NO_MAP_LOCK_REQUIRED; - - // This is called by builtin to give us a function that will later - // populate the http_api table with additional method(s). - // We need this because access to the HTTP api is security-relevant and - // any mod could just mess with a global variable. - luaL_checktype(L, 1, LUA_TFUNCTION); - lua_rawseti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_HTTP_API_LUA); - - return 0; -} - int ModApiHttp::l_request_http_api(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -215,6 +201,22 @@ int ModApiHttp::l_get_http_api(lua_State *L) #endif +int ModApiHttp::l_set_http_api_lua(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + +#if USE_CURL + // This is called by builtin to give us a function that will later + // populate the http_api table with additional method(s). + // We need this because access to the HTTP api is security-relevant and + // any mod could just mess with a global variable. + luaL_checktype(L, 1, LUA_TFUNCTION); + lua_rawseti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_HTTP_API_LUA); +#endif + + return 0; +} + void ModApiHttp::Initialize(lua_State *L, int top) { #if USE_CURL @@ -231,6 +233,11 @@ void ModApiHttp::Initialize(lua_State *L, int top) API_FCT(set_http_api_lua); } +#else + + // Define this function anyway so builtin can call it without checking + API_FCT(set_http_api_lua); + #endif } diff --git a/src/script/lua_api/l_http.h b/src/script/lua_api/l_http.h index 17fa283ba..8d084ecd9 100644 --- a/src/script/lua_api/l_http.h +++ b/src/script/lua_api/l_http.h @@ -41,9 +41,6 @@ private: // http_fetch_async_get(handle) static int l_http_fetch_async_get(lua_State *L); - // set_http_api_lua() [internal] - static int l_set_http_api_lua(lua_State *L); - // request_http_api() static int l_request_http_api(lua_State *L); @@ -51,6 +48,10 @@ private: static int l_get_http_api(lua_State *L); #endif + // set_http_api_lua() [internal] + static int l_set_http_api_lua(lua_State *L); + + public: static void Initialize(lua_State *L, int top); static void InitializeAsync(lua_State *L, int top); -- cgit v1.2.3 From f7311e0d97cb89bcb197a3e6b89e039151bb510f Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Wed, 23 Feb 2022 21:21:37 +0100 Subject: Lua API documentation: Various fixes (#12059) Change 1: Clarify when on_step collision information is provided Change 2: Document PostgreSQL and Redis settings Change 3: Overall AreaStore documentation improvements including consistent parameter naming based on community suggestions --- doc/lua_api.txt | 50 +++++++++++++++++++++++--------------- doc/world_format.txt | 14 ++++++++++- src/script/lua_api/l_areastore.cpp | 49 +++++++++++++++++++------------------ 3 files changed, 68 insertions(+), 45 deletions(-) (limited to 'src/script/lua_api') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 543f00542..571ddf40e 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -6211,45 +6211,53 @@ Sorted alphabetically. `AreaStore` ----------- -A fast access data structure to store areas, and find areas near a given -position or area. -Every area has a `data` string attribute to store additional information. -You can create an empty `AreaStore` by calling `AreaStore()`, or -`AreaStore(type_name)`. The mod decides where to save and load AreaStore. -If you chose the parameter-less constructor, a fast implementation will be -automatically chosen for you. +AreaStore is a data structure to calculate intersections of 3D cuboid volumes +and points. The `data` field (string) may be used to store and retrieve any +mod-relevant information to the specified area. + +Despite its name, mods must take care of persisting AreaStore data. They may +use the provided load and write functions for this. + ### Methods -* `get_area(id, include_borders, include_data)` +* `AreaStore(type_name)` + * Returns a new AreaStore instance + * `type_name`: optional, forces the internally used API. + * Possible values: `"LibSpatial"` (default). + * When other values are specified, or SpatialIndex is not available, + the custom Minetest functions are used. +* `get_area(id, include_corners, include_data)` * Returns the area information about the specified ID. * Returned values are either of these: nil -- Area not found - true -- Without `include_borders` and `include_data` + true -- Without `include_corners` and `include_data` { - min = pos, max = pos -- `include_borders == true` + min = pos, max = pos -- `include_corners == true` data = string -- `include_data == true` } -* `get_areas_for_pos(pos, include_borders, include_data)` +* `get_areas_for_pos(pos, include_corners, include_data)` * Returns all areas as table, indexed by the area ID. * Table values: see `get_area`. -* `get_areas_in_area(edge1, edge2, accept_overlap, include_borders, include_data)` - * Returns all areas that contain all nodes inside the area specified by `edge1` - and `edge2` (inclusive). +* `get_areas_in_area(corner1, corner2, accept_overlap, include_corners, include_data)` + * Returns all areas that contain all nodes inside the area specified by` + `corner1 and `corner2` (inclusive). * `accept_overlap`: if `true`, areas are returned that have nodes in common (intersect) with the specified area. * Returns the same values as `get_areas_for_pos`. -* `insert_area(edge1, edge2, data, [id])`: inserts an area into the store. +* `insert_area(corner1, corner2, data, [id])`: inserts an area into the store. * Returns the new area's ID, or nil if the insertion failed. - * The (inclusive) positions `edge1` and `edge2` describe the area. + * The (inclusive) positions `corner1` and `corner2` describe the area. * `data` is a string stored with the area. * `id` (optional): will be used as the internal area ID if it is an unique number between 0 and 2^32-2. -* `reserve(count)`: reserves resources for at most `count` many contained - areas. - Only needed for efficiency, and only some implementations profit. +* `reserve(count)` + * Requires SpatialIndex, no-op function otherwise. + * Reserves resources for `count` many contained areas to improve + efficiency when working with many area entries. Additional areas can still + be inserted afterwards at the usual complexity. * `remove_area(id)`: removes the area with the given id from the store, returns success. * `set_cache_params(params)`: sets params for the included prefiltering cache. @@ -7362,7 +7370,7 @@ Used by `minetest.register_entity`. -- for more info) by using a '_' prefix } -Collision info passed to `on_step`: +Collision info passed to `on_step` (`moveresult` argument): { touching_ground = boolean, @@ -7379,6 +7387,8 @@ Collision info passed to `on_step`: }, ... } + -- `collisions` does not contain data of unloaded mapblock collisions + -- or when the velocity changes are negligibly small } ABM (ActiveBlockModifier) definition diff --git a/doc/world_format.txt b/doc/world_format.txt index 3035c4efb..17923df8e 100644 --- a/doc/world_format.txt +++ b/doc/world_format.txt @@ -129,9 +129,9 @@ Example content (added indentation and - explanations): backend = sqlite3 - which DB backend to use for blocks (sqlite3, dummy, leveldb, redis, postgresql) player_backend = sqlite3 - which DB backend to use for player data readonly_backend = sqlite3 - optionally readonly seed DB (DB file _must_ be located in "readonly" subfolder) + auth_backend = files - which DB backend to use for authentication data server_announce = false - whether the server is publicly announced or not load_mod_ = false - whether is to be loaded in this world - auth_backend = files - which DB backend to use for authentication data For load_mod_, the possible values are: @@ -145,6 +145,18 @@ For load_mod_, the possible values are: * Other locations and absolute paths are not supported * Note that `moddir` is the directory name, not the mod name specified in mod.conf. +PostgreSQL backend specific settings: + pgsql_connection = host=127.0.0.1 port=5432 user=mt_user password=mt_password dbname=minetest + pgsql_player_connection = (same parameters as above) + pgsql_readonly_connection = (same parameters as above) + pgsql_auth_connection = (same parameters as above) + +Redis backend specific settings: + redis_address = 127.0.0.1 - Redis server address + redis_hash = foo - Database hash + redis_port = 6379 - (optional) connection port + redis_password = hunter2 - (optional) server password + Player File Format =================== diff --git a/src/script/lua_api/l_areastore.cpp b/src/script/lua_api/l_areastore.cpp index 45724e604..ec2656c4a 100644 --- a/src/script/lua_api/l_areastore.cpp +++ b/src/script/lua_api/l_areastore.cpp @@ -27,26 +27,26 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "filesys.h" #include -static inline void get_data_and_border_flags(lua_State *L, u8 start_i, - bool *borders, bool *data) +static inline void get_data_and_corner_flags(lua_State *L, u8 start_i, + bool *corners, bool *data) { if (!lua_isboolean(L, start_i)) return; - *borders = lua_toboolean(L, start_i); + *corners = lua_toboolean(L, start_i); if (!lua_isboolean(L, start_i + 1)) return; *data = lua_toboolean(L, start_i + 1); } static void push_area(lua_State *L, const Area *a, - bool include_borders, bool include_data) + bool include_corners, bool include_data) { - if (!include_borders && !include_data) { + if (!include_corners && !include_data) { lua_pushboolean(L, true); return; } lua_newtable(L); - if (include_borders) { + if (include_corners) { push_v3s16(L, a->minedge); lua_setfield(L, -2, "min"); push_v3s16(L, a->maxedge); @@ -59,13 +59,13 @@ static void push_area(lua_State *L, const Area *a, } static inline void push_areas(lua_State *L, const std::vector &areas, - bool borders, bool data) + bool corners, bool data) { lua_newtable(L); size_t cnt = areas.size(); for (size_t i = 0; i < cnt; i++) { lua_pushnumber(L, areas[i]->id); - push_area(L, areas[i], borders, data); + push_area(L, areas[i], corners, data); lua_settable(L, -3); } } @@ -94,7 +94,7 @@ int LuaAreaStore::gc_object(lua_State *L) return 0; } -// get_area(id, include_borders, include_data) +// get_area(id, include_corners, include_data) int LuaAreaStore::l_get_area(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -104,9 +104,9 @@ int LuaAreaStore::l_get_area(lua_State *L) u32 id = luaL_checknumber(L, 2); - bool include_borders = true; + bool include_corners = true; bool include_data = false; - get_data_and_border_flags(L, 3, &include_borders, &include_data); + get_data_and_corner_flags(L, 3, &include_corners, &include_data); const Area *res; @@ -114,12 +114,12 @@ int LuaAreaStore::l_get_area(lua_State *L) if (!res) return 0; - push_area(L, res, include_borders, include_data); + push_area(L, res, include_corners, include_data); return 1; } -// get_areas_for_pos(pos, include_borders, include_data) +// get_areas_for_pos(pos, include_corners, include_data) int LuaAreaStore::l_get_areas_for_pos(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -129,19 +129,19 @@ int LuaAreaStore::l_get_areas_for_pos(lua_State *L) v3s16 pos = check_v3s16(L, 2); - bool include_borders = true; + bool include_corners = true; bool include_data = false; - get_data_and_border_flags(L, 3, &include_borders, &include_data); + get_data_and_corner_flags(L, 3, &include_corners, &include_data); std::vector res; ast->getAreasForPos(&res, pos); - push_areas(L, res, include_borders, include_data); + push_areas(L, res, include_corners, include_data); return 1; } -// get_areas_in_area(edge1, edge2, accept_overlap, include_borders, include_data) +// get_areas_in_area(corner1, corner2, accept_overlap, include_corners, include_data) int LuaAreaStore::l_get_areas_in_area(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -149,25 +149,26 @@ int LuaAreaStore::l_get_areas_in_area(lua_State *L) LuaAreaStore *o = checkobject(L, 1); AreaStore *ast = o->as; - v3s16 minedge = check_v3s16(L, 2); - v3s16 maxedge = check_v3s16(L, 3); + v3s16 minp = check_v3s16(L, 2); + v3s16 maxp = check_v3s16(L, 3); + sortBoxVerticies(minp, maxp); - bool include_borders = true; + bool include_corners = true; bool include_data = false; bool accept_overlap = false; if (lua_isboolean(L, 4)) { accept_overlap = readParam(L, 4); - get_data_and_border_flags(L, 5, &include_borders, &include_data); + get_data_and_corner_flags(L, 5, &include_corners, &include_data); } std::vector res; - ast->getAreasInArea(&res, minedge, maxedge, accept_overlap); - push_areas(L, res, include_borders, include_data); + ast->getAreasInArea(&res, minp, maxp, accept_overlap); + push_areas(L, res, include_corners, include_data); return 1; } -// insert_area(edge1, edge2, data, id) +// insert_area(corner1, corner2, data, id) int LuaAreaStore::l_insert_area(lua_State *L) { NO_MAP_LOCK_REQUIRED; -- cgit v1.2.3 From 44fc888bd64cc00836b0fea0666aa763b2565513 Mon Sep 17 00:00:00 2001 From: Zughy <63455151+Zughy@users.noreply.github.com> Date: Sat, 5 Mar 2022 22:15:41 +0100 Subject: Allow get_sky to return a table (#11963) --- builtin/game/features.lua | 1 + doc/lua_api.txt | 14 +++++-- src/script/lua_api/l_object.cpp | 87 ++++++++++++++++++++++++++++------------- src/script/lua_api/l_object.h | 3 +- 4 files changed, 74 insertions(+), 31 deletions(-) (limited to 'src/script/lua_api') diff --git a/builtin/game/features.lua b/builtin/game/features.lua index 583ef5092..0d55bb01f 100644 --- a/builtin/game/features.lua +++ b/builtin/game/features.lua @@ -22,6 +22,7 @@ core.features = { degrotate_240_steps = true, abm_min_max_y = true, dynamic_add_media_table = true, + get_sky_as_table = true, } function core.has_feature(arg) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 571ddf40e..8af261e0c 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -4634,6 +4634,8 @@ Utilities abm_min_max_y = true, -- dynamic_add_media supports passing a table with options (5.5.0) dynamic_add_media_table = true, + -- allows get_sky to return a table instead of separate values (5.6.0) + get_sky_as_table = true, } * `minetest.has_feature(arg)`: returns `boolean, missing_features` @@ -6869,9 +6871,15 @@ object you are working with still exists. * `"plain"`: Uses 0 textures, `bgcolor` used * `clouds`: Boolean for whether clouds appear in front of `"skybox"` or `"plain"` custom skyboxes (default: `true`) -* `get_sky()`: returns base_color, type, table of textures, clouds. -* `get_sky_color()`: returns a table with the `sky_color` parameters as in - `set_sky`. +* `get_sky(as_table)`: + * `as_table`: boolean that determines whether the deprecated version of this + function is being used. + * `true` returns a table containing sky parameters as defined in `set_sky(sky_parameters)`. + * Deprecated: `false` or `nil` returns base_color, type, table of textures, + clouds. +* `get_sky_color()`: + * Deprecated: Use `get_sky(as_table)` instead. + * returns a table with the `sky_color` parameters as in `set_sky`. * `set_sun(sun_parameters)`: * Passing no arguments resets the sun to its default values. * `sun_parameters` is a table with the following optional fields: diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index 407b48db0..ba86fbc48 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -1874,7 +1874,34 @@ int ObjectRef::l_set_sky(lua_State *L) return 1; } -// get_sky(self) +static void push_sky_color(lua_State *L, const SkyboxParams ¶ms) +{ + lua_newtable(L); + if (params.type == "regular") { + push_ARGB8(L, params.sky_color.day_sky); + lua_setfield(L, -2, "day_sky"); + push_ARGB8(L, params.sky_color.day_horizon); + lua_setfield(L, -2, "day_horizon"); + push_ARGB8(L, params.sky_color.dawn_sky); + lua_setfield(L, -2, "dawn_sky"); + push_ARGB8(L, params.sky_color.dawn_horizon); + lua_setfield(L, -2, "dawn_horizon"); + push_ARGB8(L, params.sky_color.night_sky); + lua_setfield(L, -2, "night_sky"); + push_ARGB8(L, params.sky_color.night_horizon); + lua_setfield(L, -2, "night_horizon"); + push_ARGB8(L, params.sky_color.indoors); + lua_setfield(L, -2, "indoors"); + } + push_ARGB8(L, params.fog_sun_tint); + lua_setfield(L, -2, "fog_sun_tint"); + push_ARGB8(L, params.fog_moon_tint); + lua_setfield(L, -2, "fog_moon_tint"); + lua_pushstring(L, params.fog_tint_type.c_str()); + lua_setfield(L, -2, "fog_tint_type"); +} + +// get_sky(self, as_table) int ObjectRef::l_get_sky(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -1883,10 +1910,30 @@ int ObjectRef::l_get_sky(lua_State *L) if (player == nullptr) return 0; - SkyboxParams skybox_params = player->getSkyParams(); + const SkyboxParams &skybox_params = player->getSkyParams(); + + // handle the deprecated version + if (!readParam(L, 2, false)) { + log_deprecated(L, "Deprecated call to get_sky, please check lua_api.txt"); + + push_ARGB8(L, skybox_params.bgcolor); + lua_pushlstring(L, skybox_params.type.c_str(), skybox_params.type.size()); + + lua_newtable(L); + s16 i = 1; + for (const std::string &texture : skybox_params.textures) { + lua_pushlstring(L, texture.c_str(), texture.size()); + lua_rawseti(L, -2, i++); + } + lua_pushboolean(L, skybox_params.clouds); + return 4; + } + lua_newtable(L); push_ARGB8(L, skybox_params.bgcolor); + lua_setfield(L, -2, "base_color"); lua_pushlstring(L, skybox_params.type.c_str(), skybox_params.type.size()); + lua_setfield(L, -2, "type"); lua_newtable(L); s16 i = 1; @@ -1894,44 +1941,30 @@ int ObjectRef::l_get_sky(lua_State *L) lua_pushlstring(L, texture.c_str(), texture.size()); lua_rawseti(L, -2, i++); } + lua_setfield(L, -2, "textures"); lua_pushboolean(L, skybox_params.clouds); - return 4; + lua_setfield(L, -2, "clouds"); + + push_sky_color(L, skybox_params); + lua_setfield(L, -2, "sky_color"); + return 1; } +// DEPRECATED // get_sky_color(self) int ObjectRef::l_get_sky_color(lua_State *L) { NO_MAP_LOCK_REQUIRED; + + log_deprecated(L, "Deprecated call to get_sky_color, use get_sky instead"); + ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); if (player == nullptr) return 0; const SkyboxParams &skybox_params = player->getSkyParams(); - - lua_newtable(L); - if (skybox_params.type == "regular") { - push_ARGB8(L, skybox_params.sky_color.day_sky); - lua_setfield(L, -2, "day_sky"); - push_ARGB8(L, skybox_params.sky_color.day_horizon); - lua_setfield(L, -2, "day_horizon"); - push_ARGB8(L, skybox_params.sky_color.dawn_sky); - lua_setfield(L, -2, "dawn_sky"); - push_ARGB8(L, skybox_params.sky_color.dawn_horizon); - lua_setfield(L, -2, "dawn_horizon"); - push_ARGB8(L, skybox_params.sky_color.night_sky); - lua_setfield(L, -2, "night_sky"); - push_ARGB8(L, skybox_params.sky_color.night_horizon); - lua_setfield(L, -2, "night_horizon"); - push_ARGB8(L, skybox_params.sky_color.indoors); - lua_setfield(L, -2, "indoors"); - } - push_ARGB8(L, skybox_params.fog_sun_tint); - lua_setfield(L, -2, "fog_sun_tint"); - push_ARGB8(L, skybox_params.fog_moon_tint); - lua_setfield(L, -2, "fog_moon_tint"); - lua_pushstring(L, skybox_params.fog_tint_type.c_str()); - lua_setfield(L, -2, "fog_tint_type"); + push_sky_color(L, skybox_params); return 1; } diff --git a/src/script/lua_api/l_object.h b/src/script/lua_api/l_object.h index db3a3a7cf..084d40c05 100644 --- a/src/script/lua_api/l_object.h +++ b/src/script/lua_api/l_object.h @@ -316,9 +316,10 @@ private: // set_sky(self, sky_parameters) static int l_set_sky(lua_State *L); - // get_sky(self) + // get_sky(self, as_table) static int l_get_sky(lua_State *L); + // DEPRECATED // get_sky_color(self) static int l_get_sky_color(lua_State* L); -- cgit v1.2.3 From b9e886726c68613d39459acc8cb1a2f663b0362c Mon Sep 17 00:00:00 2001 From: Lars Müller <34514239+appgurueu@users.noreply.github.com> Date: Sat, 5 Mar 2022 22:16:17 +0100 Subject: Readd basic_debug as a HUD flag (#12020) --- doc/lua_api.txt | 21 ++++++------ src/client/game.cpp | 71 +++++++++++++++++++++-------------------- src/hud.cpp | 1 + src/hud.h | 1 + src/player.cpp | 2 +- src/script/lua_api/l_object.cpp | 19 +++-------- 6 files changed, 55 insertions(+), 60 deletions(-) (limited to 'src/script/lua_api') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 8af261e0c..89bc7dc4b 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -6772,17 +6772,18 @@ object you are working with still exists. * `hud_get(id)`: gets the HUD element definition structure of the specified ID * `hud_set_flags(flags)`: sets specified HUD flags of player. * `flags`: A table with the following fields set to boolean values - * hotbar - * healthbar - * crosshair - * wielditem - * breathbar - * minimap - * minimap_radar + * `hotbar` + * `healthbar` + * `crosshair` + * `wielditem` + * `breathbar` + * `minimap`: Modifies the client's permission to view the minimap. + The client may locally elect to not view the minimap. + * `minimap_radar`: is only usable when `minimap` is true + * `basic_debug`: Allow showing basic debug info that might give a gameplay advantage. + This includes map seed, player position, look direction, the pointed node and block bounds. + Does not affect players with the `debug` privilege. * If a flag equals `nil`, the flag is not modified - * `minimap`: Modifies the client's permission to view the minimap. - The client may locally elect to not view the minimap. - * `minimap_radar` is only usable when `minimap` is true * `hud_get_flags()`: returns a table of player HUD flags with boolean values. * See `hud_set_flags` for a list of flags that can be toggled. * `hud_set_hotbar_itemcount(count)`: sets number of items in builtin hotbar diff --git a/src/client/game.cpp b/src/client/game.cpp index 4337d308e..7450fb91c 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -723,7 +723,7 @@ protected: void processClientEvents(CameraOrientation *cam); void updateCamera(f32 dtime); void updateSound(f32 dtime); - void processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug); + void processPlayerInteraction(f32 dtime, bool show_hud); /*! * Returns the object or node the player is pointing at. * Also updates the selected thing in the Hud. @@ -1134,8 +1134,7 @@ void Game::run() updateDebugState(); updateCamera(dtime); updateSound(dtime); - processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud, - m_game_ui->m_flags.show_basic_debug); + processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud); updateFrame(&graph, &stats, dtime, cam_view); updateProfilerGraphs(&graph); @@ -1740,17 +1739,16 @@ void Game::processQueues() void Game::updateDebugState() { - const bool has_basic_debug = true; + LocalPlayer *player = client->getEnv().getLocalPlayer(); bool has_debug = client->checkPrivilege("debug"); + bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG); if (m_game_ui->m_flags.show_basic_debug) { - if (!has_basic_debug) { + if (!has_basic_debug) m_game_ui->m_flags.show_basic_debug = false; - } } else if (m_game_ui->m_flags.show_minimal_debug) { - if (has_basic_debug) { + if (has_basic_debug) m_game_ui->m_flags.show_basic_debug = true; - } } if (!has_basic_debug) hud->disableBlockBounds(); @@ -2211,27 +2209,27 @@ void Game::toggleCinematic() void Game::toggleBlockBounds() { - if (true /* basic_debug */) { - enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds(); - switch (newmode) { - case Hud::BLOCK_BOUNDS_OFF: - m_game_ui->showTranslatedStatusText("Block bounds hidden"); - break; - case Hud::BLOCK_BOUNDS_CURRENT: - m_game_ui->showTranslatedStatusText("Block bounds shown for current block"); - break; - case Hud::BLOCK_BOUNDS_NEAR: - m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks"); - break; - case Hud::BLOCK_BOUNDS_MAX: - m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks"); - break; - default: - break; - } - - } else { - m_game_ui->showTranslatedStatusText("Can't show block bounds (need 'basic_debug' privilege)"); + LocalPlayer *player = client->getEnv().getLocalPlayer(); + if (!(client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG))) { + m_game_ui->showTranslatedStatusText("Can't show block bounds (disabled by mod or game)"); + return; + } + enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds(); + switch (newmode) { + case Hud::BLOCK_BOUNDS_OFF: + m_game_ui->showTranslatedStatusText("Block bounds hidden"); + break; + case Hud::BLOCK_BOUNDS_CURRENT: + m_game_ui->showTranslatedStatusText("Block bounds shown for current block"); + break; + case Hud::BLOCK_BOUNDS_NEAR: + m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks"); + break; + case Hud::BLOCK_BOUNDS_MAX: + m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks"); + break; + default: + break; } } @@ -2298,6 +2296,9 @@ void Game::toggleFog() void Game::toggleDebug() { + LocalPlayer *player = client->getEnv().getLocalPlayer(); + bool has_debug = client->checkPrivilege("debug"); + bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG); // Initial: No debug info // 1x toggle: Debug text // 2x toggle: Debug text with profiler graph @@ -2307,9 +2308,8 @@ void Game::toggleDebug() // The debug text can be in 2 modes: minimal and basic. // * Minimal: Only technical client info that not gameplay-relevant // * Basic: Info that might give gameplay advantage, e.g. pos, angle - // Basic mode is always used. - - const bool has_basic_debug = true; + // Basic mode is used when player has the debug HUD flag set, + // otherwise the Minimal mode is used. if (!m_game_ui->m_flags.show_minimal_debug) { m_game_ui->m_flags.show_minimal_debug = true; if (has_basic_debug) @@ -2333,7 +2333,7 @@ void Game::toggleDebug() m_game_ui->m_flags.show_basic_debug = false; m_game_ui->m_flags.show_profiler_graph = false; draw_control->show_wireframe = false; - if (client->checkPrivilege("debug")) { + if (has_debug) { m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden"); } else { m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden"); @@ -3039,7 +3039,7 @@ void Game::updateSound(f32 dtime) } -void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug) +void Game::processPlayerInteraction(f32 dtime, bool show_hud) { LocalPlayer *player = client->getEnv().getLocalPlayer(); @@ -3157,7 +3157,8 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug) handlePointingAtNode(pointed, selected_item, hand_item, dtime); } else if (pointed.type == POINTEDTHING_OBJECT) { v3f player_position = player->getPosition(); - handlePointingAtObject(pointed, tool_item, player_position, show_debug); + handlePointingAtObject(pointed, tool_item, player_position, + client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG)); } else if (isKeyDown(KeyType::DIG)) { // When button is held down in air, show continuous animation runData.punching = true; diff --git a/src/hud.cpp b/src/hud.cpp index e4ad7940f..841c90758 100644 --- a/src/hud.cpp +++ b/src/hud.cpp @@ -63,5 +63,6 @@ const struct EnumString es_HudBuiltinElement[] = {HUD_FLAG_BREATHBAR_VISIBLE, "breathbar"}, {HUD_FLAG_MINIMAP_VISIBLE, "minimap"}, {HUD_FLAG_MINIMAP_RADAR_VISIBLE, "minimap_radar"}, + {HUD_FLAG_BASIC_DEBUG, "basic_debug"}, {0, NULL}, }; diff --git a/src/hud.h b/src/hud.h index 769966688..173633fcc 100644 --- a/src/hud.h +++ b/src/hud.h @@ -47,6 +47,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #define HUD_FLAG_BREATHBAR_VISIBLE (1 << 4) #define HUD_FLAG_MINIMAP_VISIBLE (1 << 5) #define HUD_FLAG_MINIMAP_RADAR_VISIBLE (1 << 6) +#define HUD_FLAG_BASIC_DEBUG (1 << 7) #define HUD_PARAM_HOTBAR_ITEMCOUNT 1 #define HUD_PARAM_HOTBAR_IMAGE 2 diff --git a/src/player.cpp b/src/player.cpp index 347be30f1..1e064c1da 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -71,7 +71,7 @@ Player::Player(const char *name, IItemDefManager *idef): HUD_FLAG_HOTBAR_VISIBLE | HUD_FLAG_HEALTHBAR_VISIBLE | HUD_FLAG_CROSSHAIR_VISIBLE | HUD_FLAG_WIELDITEM_VISIBLE | HUD_FLAG_BREATHBAR_VISIBLE | HUD_FLAG_MINIMAP_VISIBLE | - HUD_FLAG_MINIMAP_RADAR_VISIBLE; + HUD_FLAG_MINIMAP_RADAR_VISIBLE | HUD_FLAG_BASIC_DEBUG; hud_hotbar_itemcount = HUD_HOTBAR_ITEMCOUNT_DEFAULT; diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index ba86fbc48..1ed6b0d5c 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -1617,20 +1617,11 @@ int ObjectRef::l_hud_get_flags(lua_State *L) return 0; lua_newtable(L); - lua_pushboolean(L, player->hud_flags & HUD_FLAG_HOTBAR_VISIBLE); - lua_setfield(L, -2, "hotbar"); - lua_pushboolean(L, player->hud_flags & HUD_FLAG_HEALTHBAR_VISIBLE); - lua_setfield(L, -2, "healthbar"); - lua_pushboolean(L, player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE); - lua_setfield(L, -2, "crosshair"); - lua_pushboolean(L, player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE); - lua_setfield(L, -2, "wielditem"); - lua_pushboolean(L, player->hud_flags & HUD_FLAG_BREATHBAR_VISIBLE); - lua_setfield(L, -2, "breathbar"); - lua_pushboolean(L, player->hud_flags & HUD_FLAG_MINIMAP_VISIBLE); - lua_setfield(L, -2, "minimap"); - lua_pushboolean(L, player->hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE); - lua_setfield(L, -2, "minimap_radar"); + const EnumString *esp = es_HudBuiltinElement; + for (int i = 0; esp[i].str; i++) { + lua_pushboolean(L, (player->hud_flags & esp[i].num) != 0); + lua_setfield(L, -2, esp[i].str); + } return 1; } -- cgit v1.2.3 From 0f25fa7af655b98fa401176a523f269c843d1943 Mon Sep 17 00:00:00 2001 From: x2048 Date: Sat, 26 Mar 2022 16:58:26 +0100 Subject: Add API to control shadow intensity from the game/mod (#11944) * Also Disable shadows when sun/moon is hidden. Fixes #11972. --- builtin/settingtypes.txt | 5 +- client/shaders/nodes_shader/opengl_fragment.glsl | 85 ++++++++++---------- client/shaders/nodes_shader/opengl_vertex.glsl | 57 +++++++------- client/shaders/object_shader/opengl_fragment.glsl | 84 ++++++++++---------- client/shaders/object_shader/opengl_vertex.glsl | 56 ++++++------- doc/lua_api.txt | 7 ++ games/devtest/mods/experimental/init.lua | 1 + games/devtest/mods/experimental/lighting.lua | 8 ++ src/client/client.h | 1 + src/client/game.cpp | 7 +- src/client/localplayer.h | 4 + src/client/shadows/dynamicshadowsrender.cpp | 95 ++++++++++++++++------- src/client/shadows/dynamicshadowsrender.h | 10 ++- src/client/sky.h | 2 + src/defaultsettings.cpp | 2 +- src/lighting.h | 27 +++++++ src/network/clientopcodes.cpp | 1 + src/network/clientpackethandler.cpp | 8 ++ src/network/networkprotocol.h | 7 +- src/remoteplayer.h | 7 ++ src/script/lua_api/l_object.cpp | 43 ++++++++++ src/script/lua_api/l_object.h | 6 ++ src/server.cpp | 17 ++++ src/server.h | 4 + 24 files changed, 375 insertions(+), 169 deletions(-) create mode 100644 games/devtest/mods/experimental/lighting.lua create mode 100644 src/lighting.h (limited to 'src/script/lua_api') diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index ff2d72927..3dc165bd1 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -592,9 +592,10 @@ enable_waving_plants (Waving plants) bool false # Requires shaders to be enabled. enable_dynamic_shadows (Dynamic shadows) bool false -# Set the shadow strength. +# Set the shadow strength gamma. +# Adjusts the intensity of in-game dynamic shadows. # Lower value means lighter shadows, higher value means darker shadows. -shadow_strength (Shadow strength) float 0.2 0.05 1.0 +shadow_strength_gamma (Shadow strength gamma) float 1.0 0.1 10.0 # Maximum distance to render shadows. shadow_map_max_distance (Shadow map max distance in nodes to render shadows) float 120.0 10.0 1000.0 diff --git a/client/shaders/nodes_shader/opengl_fragment.glsl b/client/shaders/nodes_shader/opengl_fragment.glsl index e5f5c703a..adc8adccb 100644 --- a/client/shaders/nodes_shader/opengl_fragment.glsl +++ b/client/shaders/nodes_shader/opengl_fragment.glsl @@ -16,6 +16,7 @@ uniform float animationTimer; uniform float f_textureresolution; uniform mat4 m_ShadowViewProj; uniform float f_shadowfar; + uniform float f_shadow_strength; varying float normalOffsetScale; varying float adj_shadow_strength; varying float cosLight; @@ -483,55 +484,57 @@ void main(void) vec4 col = vec4(color.rgb * varColor.rgb, 1.0); #ifdef ENABLE_DYNAMIC_SHADOWS - float shadow_int = 0.0; - vec3 shadow_color = vec3(0.0, 0.0, 0.0); - vec3 posLightSpace = getLightSpacePosition(); + if (f_shadow_strength > 0.0) { + float shadow_int = 0.0; + vec3 shadow_color = vec3(0.0, 0.0, 0.0); + vec3 posLightSpace = getLightSpacePosition(); - float distance_rate = (1 - pow(clamp(2.0 * length(posLightSpace.xy - 0.5),0.0,1.0), 20.0)); - float f_adj_shadow_strength = max(adj_shadow_strength-mtsmoothstep(0.9,1.1, posLightSpace.z ),0.0); + float distance_rate = (1 - pow(clamp(2.0 * length(posLightSpace.xy - 0.5),0.0,1.0), 20.0)); + float f_adj_shadow_strength = max(adj_shadow_strength-mtsmoothstep(0.9,1.1, posLightSpace.z ),0.0); - if (distance_rate > 1e-7) { - + if (distance_rate > 1e-7) { + #ifdef COLORED_SHADOWS - vec4 visibility; - if (cosLight > 0.0) - visibility = getShadowColor(ShadowMapSampler, posLightSpace.xy, posLightSpace.z); - else - visibility = vec4(1.0, 0.0, 0.0, 0.0); - shadow_int = visibility.r; - shadow_color = visibility.gba; + vec4 visibility; + if (cosLight > 0.0) + visibility = getShadowColor(ShadowMapSampler, posLightSpace.xy, posLightSpace.z); + else + visibility = vec4(1.0, 0.0, 0.0, 0.0); + shadow_int = visibility.r; + shadow_color = visibility.gba; #else - if (cosLight > 0.0) - shadow_int = getShadow(ShadowMapSampler, posLightSpace.xy, posLightSpace.z); - else - shadow_int = 1.0; + if (cosLight > 0.0) + shadow_int = getShadow(ShadowMapSampler, posLightSpace.xy, posLightSpace.z); + else + shadow_int = 1.0; #endif - shadow_int *= distance_rate; - shadow_int = clamp(shadow_int, 0.0, 1.0); + shadow_int *= distance_rate; + shadow_int = clamp(shadow_int, 0.0, 1.0); - } + } - // turns out that nightRatio falls off much faster than - // actual brightness of artificial light in relation to natual light. - // Power ratio was measured on torches in MTG (brightness = 14). - float adjusted_night_ratio = pow(max(0.0, nightRatio), 0.6); - - // Apply self-shadowing when light falls at a narrow angle to the surface - // Cosine of the cut-off angle. - const float self_shadow_cutoff_cosine = 0.035; - if (f_normal_length != 0 && cosLight < self_shadow_cutoff_cosine) { - shadow_int = max(shadow_int, 1 - clamp(cosLight, 0.0, self_shadow_cutoff_cosine)/self_shadow_cutoff_cosine); - shadow_color = mix(vec3(0.0), shadow_color, min(cosLight, self_shadow_cutoff_cosine)/self_shadow_cutoff_cosine); - } + // turns out that nightRatio falls off much faster than + // actual brightness of artificial light in relation to natual light. + // Power ratio was measured on torches in MTG (brightness = 14). + float adjusted_night_ratio = pow(max(0.0, nightRatio), 0.6); + + // Apply self-shadowing when light falls at a narrow angle to the surface + // Cosine of the cut-off angle. + const float self_shadow_cutoff_cosine = 0.035; + if (f_normal_length != 0 && cosLight < self_shadow_cutoff_cosine) { + shadow_int = max(shadow_int, 1 - clamp(cosLight, 0.0, self_shadow_cutoff_cosine)/self_shadow_cutoff_cosine); + shadow_color = mix(vec3(0.0), shadow_color, min(cosLight, self_shadow_cutoff_cosine)/self_shadow_cutoff_cosine); + } - shadow_int *= f_adj_shadow_strength; - - // calculate fragment color from components: - col.rgb = - adjusted_night_ratio * col.rgb + // artificial light - (1.0 - adjusted_night_ratio) * ( // natural light - col.rgb * (1.0 - shadow_int * (1.0 - shadow_color)) + // filtered texture color - dayLight * shadow_color * shadow_int); // reflected filtered sunlight/moonlight + shadow_int *= f_adj_shadow_strength; + + // calculate fragment color from components: + col.rgb = + adjusted_night_ratio * col.rgb + // artificial light + (1.0 - adjusted_night_ratio) * ( // natural light + col.rgb * (1.0 - shadow_int * (1.0 - shadow_color)) + // filtered texture color + dayLight * shadow_color * shadow_int); // reflected filtered sunlight/moonlight + } #endif #if ENABLE_TONE_MAPPING diff --git a/client/shaders/nodes_shader/opengl_vertex.glsl b/client/shaders/nodes_shader/opengl_vertex.glsl index 95cd138a8..5e77ac719 100644 --- a/client/shaders/nodes_shader/opengl_vertex.glsl +++ b/client/shaders/nodes_shader/opengl_vertex.glsl @@ -195,34 +195,35 @@ void main(void) varColor = clamp(color, 0.0, 1.0); #ifdef ENABLE_DYNAMIC_SHADOWS - vec3 nNormal = normalize(vNormal); - cosLight = dot(nNormal, -v_LightDirection); - - // Calculate normal offset scale based on the texel size adjusted for - // curvature of the SM texture. This code must be change together with - // getPerspectiveFactor or any light-space transformation. - vec3 eyeToVertex = worldPosition - eyePosition + cameraOffset; - // Distance from the vertex to the player - float distanceToPlayer = length(eyeToVertex - v_LightDirection * dot(eyeToVertex, v_LightDirection)) / f_shadowfar; - // perspective factor estimation according to the - float perspectiveFactor = distanceToPlayer * bias0 + bias1; - float texelSize = f_shadowfar * perspectiveFactor * perspectiveFactor / - (f_textureresolution * bias1 - perspectiveFactor * bias0); - float slopeScale = clamp(pow(1.0 - cosLight*cosLight, 0.5), 0.0, 1.0); - normalOffsetScale = texelSize * slopeScale; - - if (f_timeofday < 0.2) { - adj_shadow_strength = f_shadow_strength * 0.5 * - (1.0 - mtsmoothstep(0.18, 0.2, f_timeofday)); - } else if (f_timeofday >= 0.8) { - adj_shadow_strength = f_shadow_strength * 0.5 * - mtsmoothstep(0.8, 0.83, f_timeofday); - } else { - adj_shadow_strength = f_shadow_strength * - mtsmoothstep(0.20, 0.25, f_timeofday) * - (1.0 - mtsmoothstep(0.7, 0.8, f_timeofday)); + if (f_shadow_strength > 0.0) { + vec3 nNormal = normalize(vNormal); + cosLight = dot(nNormal, -v_LightDirection); + + // Calculate normal offset scale based on the texel size adjusted for + // curvature of the SM texture. This code must be change together with + // getPerspectiveFactor or any light-space transformation. + vec3 eyeToVertex = worldPosition - eyePosition + cameraOffset; + // Distance from the vertex to the player + float distanceToPlayer = length(eyeToVertex - v_LightDirection * dot(eyeToVertex, v_LightDirection)) / f_shadowfar; + // perspective factor estimation according to the + float perspectiveFactor = distanceToPlayer * bias0 + bias1; + float texelSize = f_shadowfar * perspectiveFactor * perspectiveFactor / + (f_textureresolution * bias1 - perspectiveFactor * bias0); + float slopeScale = clamp(pow(1.0 - cosLight*cosLight, 0.5), 0.0, 1.0); + normalOffsetScale = texelSize * slopeScale; + + if (f_timeofday < 0.2) { + adj_shadow_strength = f_shadow_strength * 0.5 * + (1.0 - mtsmoothstep(0.18, 0.2, f_timeofday)); + } else if (f_timeofday >= 0.8) { + adj_shadow_strength = f_shadow_strength * 0.5 * + mtsmoothstep(0.8, 0.83, f_timeofday); + } else { + adj_shadow_strength = f_shadow_strength * + mtsmoothstep(0.20, 0.25, f_timeofday) * + (1.0 - mtsmoothstep(0.7, 0.8, f_timeofday)); + } + f_normal_length = length(vNormal); } - f_normal_length = length(vNormal); #endif - } diff --git a/client/shaders/object_shader/opengl_fragment.glsl b/client/shaders/object_shader/opengl_fragment.glsl index fdfcec0c8..edb9d2bb1 100644 --- a/client/shaders/object_shader/opengl_fragment.glsl +++ b/client/shaders/object_shader/opengl_fragment.glsl @@ -34,6 +34,8 @@ const float fogShadingParameter = 1.0 / (1.0 - fogStart); uniform float f_textureresolution; uniform mat4 m_ShadowViewProj; uniform float f_shadowfar; + uniform float f_timeofday; + uniform float f_shadow_strength; varying float normalOffsetScale; varying float adj_shadow_strength; varying float cosLight; @@ -470,55 +472,57 @@ void main(void) col.rgb *= vIDiff; #ifdef ENABLE_DYNAMIC_SHADOWS - float shadow_int = 0.0; - vec3 shadow_color = vec3(0.0, 0.0, 0.0); - vec3 posLightSpace = getLightSpacePosition(); + if (f_shadow_strength > 0.0) { + float shadow_int = 0.0; + vec3 shadow_color = vec3(0.0, 0.0, 0.0); + vec3 posLightSpace = getLightSpacePosition(); - float distance_rate = (1 - pow(clamp(2.0 * length(posLightSpace.xy - 0.5),0.0,1.0), 20.0)); - float f_adj_shadow_strength = max(adj_shadow_strength-mtsmoothstep(0.9,1.1, posLightSpace.z ),0.0); + float distance_rate = (1 - pow(clamp(2.0 * length(posLightSpace.xy - 0.5),0.0,1.0), 20.0)); + float f_adj_shadow_strength = max(adj_shadow_strength-mtsmoothstep(0.9,1.1, posLightSpace.z ),0.0); - if (distance_rate > 1e-7) { + if (distance_rate > 1e-7) { #ifdef COLORED_SHADOWS - vec4 visibility; - if (cosLight > 0.0) - visibility = getShadowColor(ShadowMapSampler, posLightSpace.xy, posLightSpace.z); - else - visibility = vec4(1.0, 0.0, 0.0, 0.0); - shadow_int = visibility.r; - shadow_color = visibility.gba; + vec4 visibility; + if (cosLight > 0.0) + visibility = getShadowColor(ShadowMapSampler, posLightSpace.xy, posLightSpace.z); + else + visibility = vec4(1.0, 0.0, 0.0, 0.0); + shadow_int = visibility.r; + shadow_color = visibility.gba; #else - if (cosLight > 0.0) - shadow_int = getShadow(ShadowMapSampler, posLightSpace.xy, posLightSpace.z); - else - shadow_int = 1.0; + if (cosLight > 0.0) + shadow_int = getShadow(ShadowMapSampler, posLightSpace.xy, posLightSpace.z); + else + shadow_int = 1.0; #endif - shadow_int *= distance_rate; - shadow_int = clamp(shadow_int, 0.0, 1.0); + shadow_int *= distance_rate; + shadow_int = clamp(shadow_int, 0.0, 1.0); - } + } - // turns out that nightRatio falls off much faster than - // actual brightness of artificial light in relation to natual light. - // Power ratio was measured on torches in MTG (brightness = 14). - float adjusted_night_ratio = pow(max(0.0, nightRatio), 0.6); - - // cosine of the normal-to-light angle when - // we start to apply self-shadowing - const float self_shadow_cutoff_cosine = 0.14; - if (f_normal_length != 0 && cosLight < self_shadow_cutoff_cosine) { - shadow_int = max(shadow_int, 1 - clamp(cosLight, 0.0, self_shadow_cutoff_cosine)/self_shadow_cutoff_cosine); - shadow_color = mix(vec3(0.0), shadow_color, min(cosLight, self_shadow_cutoff_cosine)/self_shadow_cutoff_cosine); - } + // turns out that nightRatio falls off much faster than + // actual brightness of artificial light in relation to natual light. + // Power ratio was measured on torches in MTG (brightness = 14). + float adjusted_night_ratio = pow(max(0.0, nightRatio), 0.6); + + // cosine of the normal-to-light angle when + // we start to apply self-shadowing + const float self_shadow_cutoff_cosine = 0.14; + if (f_normal_length != 0 && cosLight < self_shadow_cutoff_cosine) { + shadow_int = max(shadow_int, 1 - clamp(cosLight, 0.0, self_shadow_cutoff_cosine)/self_shadow_cutoff_cosine); + shadow_color = mix(vec3(0.0), shadow_color, min(cosLight, self_shadow_cutoff_cosine)/self_shadow_cutoff_cosine); + } - shadow_int *= f_adj_shadow_strength; - - // calculate fragment color from components: - col.rgb = - adjusted_night_ratio * col.rgb + // artificial light - (1.0 - adjusted_night_ratio) * ( // natural light - col.rgb * (1.0 - shadow_int * (1.0 - shadow_color)) + // filtered texture color - dayLight * shadow_color * shadow_int); // reflected filtered sunlight/moonlight + shadow_int *= f_adj_shadow_strength; + + // calculate fragment color from components: + col.rgb = + adjusted_night_ratio * col.rgb + // artificial light + (1.0 - adjusted_night_ratio) * ( // natural light + col.rgb * (1.0 - shadow_int * (1.0 - shadow_color)) + // filtered texture color + dayLight * shadow_color * shadow_int); // reflected filtered sunlight/moonlight + } #endif #if ENABLE_TONE_MAPPING diff --git a/client/shaders/object_shader/opengl_vertex.glsl b/client/shaders/object_shader/opengl_vertex.glsl index 185551c58..ff0067302 100644 --- a/client/shaders/object_shader/opengl_vertex.glsl +++ b/client/shaders/object_shader/opengl_vertex.glsl @@ -105,33 +105,35 @@ void main(void) #ifdef ENABLE_DYNAMIC_SHADOWS - vec3 nNormal = normalize(vNormal); - cosLight = dot(nNormal, -v_LightDirection); - - // Calculate normal offset scale based on the texel size adjusted for - // curvature of the SM texture. This code must be change together with - // getPerspectiveFactor or any light-space transformation. - vec3 eyeToVertex = worldPosition - eyePosition + cameraOffset; - // Distance from the vertex to the player - float distanceToPlayer = length(eyeToVertex - v_LightDirection * dot(eyeToVertex, v_LightDirection)) / f_shadowfar; - // perspective factor estimation according to the - float perspectiveFactor = distanceToPlayer * bias0 + bias1; - float texelSize = f_shadowfar * perspectiveFactor * perspectiveFactor / - (f_textureresolution * bias1 - perspectiveFactor * bias0); - float slopeScale = clamp(pow(1.0 - cosLight*cosLight, 0.5), 0.0, 1.0); - normalOffsetScale = texelSize * slopeScale; - - if (f_timeofday < 0.2) { - adj_shadow_strength = f_shadow_strength * 0.5 * - (1.0 - mtsmoothstep(0.18, 0.2, f_timeofday)); - } else if (f_timeofday >= 0.8) { - adj_shadow_strength = f_shadow_strength * 0.5 * - mtsmoothstep(0.8, 0.83, f_timeofday); - } else { - adj_shadow_strength = f_shadow_strength * - mtsmoothstep(0.20, 0.25, f_timeofday) * - (1.0 - mtsmoothstep(0.7, 0.8, f_timeofday)); + if (f_shadow_strength > 0.0) { + vec3 nNormal = normalize(vNormal); + cosLight = dot(nNormal, -v_LightDirection); + + // Calculate normal offset scale based on the texel size adjusted for + // curvature of the SM texture. This code must be change together with + // getPerspectiveFactor or any light-space transformation. + vec3 eyeToVertex = worldPosition - eyePosition + cameraOffset; + // Distance from the vertex to the player + float distanceToPlayer = length(eyeToVertex - v_LightDirection * dot(eyeToVertex, v_LightDirection)) / f_shadowfar; + // perspective factor estimation according to the + float perspectiveFactor = distanceToPlayer * bias0 + bias1; + float texelSize = f_shadowfar * perspectiveFactor * perspectiveFactor / + (f_textureresolution * bias1 - perspectiveFactor * bias0); + float slopeScale = clamp(pow(1.0 - cosLight*cosLight, 0.5), 0.0, 1.0); + normalOffsetScale = texelSize * slopeScale; + + if (f_timeofday < 0.2) { + adj_shadow_strength = f_shadow_strength * 0.5 * + (1.0 - mtsmoothstep(0.18, 0.2, f_timeofday)); + } else if (f_timeofday >= 0.8) { + adj_shadow_strength = f_shadow_strength * 0.5 * + mtsmoothstep(0.8, 0.83, f_timeofday); + } else { + adj_shadow_strength = f_shadow_strength * + mtsmoothstep(0.20, 0.25, f_timeofday) * + (1.0 - mtsmoothstep(0.7, 0.8, f_timeofday)); + } + f_normal_length = length(vNormal); } - f_normal_length = length(vNormal); #endif } diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 52da17e9c..029c53616 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -7019,6 +7019,13 @@ object you are working with still exists. * Returns `false` if failed. * Resource intensive - use sparsely * To get blockpos, integer divide pos by 16 +* `set_lighting(light_definition)`: sets lighting for the player + * `light_definition` is a table with the following optional fields: + * `shadows` is a table that controls ambient shadows + * `intensity` sets the intensity of the shadows from 0 (no shadows, default) to 1 (blackness) + * Returns true on success. +* `get_lighting()`: returns the current state of lighting for the player. + * Result is a table with the same fields as `light_definition` in `set_lighting`. `PcgRandom` ----------- diff --git a/games/devtest/mods/experimental/init.lua b/games/devtest/mods/experimental/init.lua index b292f792e..70091179c 100644 --- a/games/devtest/mods/experimental/init.lua +++ b/games/devtest/mods/experimental/init.lua @@ -7,6 +7,7 @@ experimental = {} dofile(minetest.get_modpath("experimental").."/detached.lua") dofile(minetest.get_modpath("experimental").."/items.lua") dofile(minetest.get_modpath("experimental").."/commands.lua") +dofile(minetest.get_modpath("experimental").."/lighting.lua") function experimental.print_to_everything(msg) minetest.log("action", msg) diff --git a/games/devtest/mods/experimental/lighting.lua b/games/devtest/mods/experimental/lighting.lua new file mode 100644 index 000000000..2b350550f --- /dev/null +++ b/games/devtest/mods/experimental/lighting.lua @@ -0,0 +1,8 @@ +core.register_chatcommand("set_lighting", { + params = "shadow_intensity", + description = "Set lighting parameters.", + func = function(player_name, param) + local shadow_intensity = tonumber(param) + minetest.get_player_by_name(player_name):set_lighting({shadows = { intensity = shadow_intensity} }) + end +}) \ No newline at end of file diff --git a/src/client/client.h b/src/client/client.h index 84c85471d..0e29fdbe2 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -227,6 +227,7 @@ public: void handleCommand_PlayerSpeed(NetworkPacket *pkt); void handleCommand_MediaPush(NetworkPacket *pkt); void handleCommand_MinimapModes(NetworkPacket *pkt); + void handleCommand_SetLighting(NetworkPacket *pkt); void ProcessData(NetworkPacket *pkt); diff --git a/src/client/game.cpp b/src/client/game.cpp index 7450fb91c..edc69dcc2 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -4037,7 +4037,12 @@ void Game::updateShadows() float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f); - float timeoftheday = fmod(getWickedTimeOfDay(in_timeofday) + 0.75f, 0.5f) + 0.25f; + float timeoftheday = getWickedTimeOfDay(in_timeofday); + bool is_day = timeoftheday > 0.25 && timeoftheday < 0.75; + bool is_shadow_visible = is_day ? sky->getSunVisible() : sky->getMoonVisible(); + shadow->setShadowIntensity(is_shadow_visible ? client->getEnv().getLocalPlayer()->getLighting().shadow_intensity : 0.0f); + + timeoftheday = fmod(timeoftheday + 0.75f, 0.5f) + 0.25f; const float offset_constant = 10000.0f; v3f light(0.0f, 0.0f, -1.0f); diff --git a/src/client/localplayer.h b/src/client/localplayer.h index 577be2803..3d0072fc1 100644 --- a/src/client/localplayer.h +++ b/src/client/localplayer.h @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "environment.h" #include "constants.h" #include "settings.h" +#include "lighting.h" #include class Client; @@ -158,6 +159,8 @@ public: added_velocity += vel; } + inline Lighting& getLighting() { return m_lighting; } + private: void accelerate(const v3f &target_speed, const f32 max_increase_H, const f32 max_increase_V, const bool use_pitch); @@ -209,4 +212,5 @@ private: GenericCAO *m_cao = nullptr; Client *m_client; + Lighting m_lighting; }; diff --git a/src/client/shadows/dynamicshadowsrender.cpp b/src/client/shadows/dynamicshadowsrender.cpp index 528415aaf..24adb21e2 100644 --- a/src/client/shadows/dynamicshadowsrender.cpp +++ b/src/client/shadows/dynamicshadowsrender.cpp @@ -18,6 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include +#include #include "client/shadows/dynamicshadowsrender.h" #include "client/shadows/shadowsScreenQuad.h" #include "client/shadows/shadowsshadercallbacks.h" @@ -33,9 +34,13 @@ ShadowRenderer::ShadowRenderer(IrrlichtDevice *device, Client *client) : m_device(device), m_smgr(device->getSceneManager()), m_driver(device->getVideoDriver()), m_client(client), m_current_frame(0) { + m_shadows_supported = true; // assume shadows supported. We will check actual support in initialize m_shadows_enabled = true; - m_shadow_strength = g_settings->getFloat("shadow_strength"); + m_shadow_strength_gamma = g_settings->getFloat("shadow_strength_gamma"); + if (std::isnan(m_shadow_strength_gamma)) + m_shadow_strength_gamma = 1.0f; + m_shadow_strength_gamma = core::clamp(m_shadow_strength_gamma, 0.1f, 10.0f); m_shadow_map_max_distance = g_settings->getFloat("shadow_map_max_distance"); @@ -49,6 +54,9 @@ ShadowRenderer::ShadowRenderer(IrrlichtDevice *device, Client *client) : ShadowRenderer::~ShadowRenderer() { + // call to disable releases dynamically allocated resources + disable(); + if (m_shadow_depth_cb) delete m_shadow_depth_cb; if (m_shadow_mix_cb) @@ -72,15 +80,25 @@ ShadowRenderer::~ShadowRenderer() m_driver->removeTexture(shadowMapClientMapFuture); } +void ShadowRenderer::disable() +{ + m_shadows_enabled = false; + if (shadowMapTextureFinal) { + m_driver->setRenderTarget(shadowMapTextureFinal, true, true, + video::SColor(255, 255, 255, 255)); + m_driver->setRenderTarget(0, true, true); + } +} + void ShadowRenderer::initialize() { auto *gpu = m_driver->getGPUProgrammingServices(); // we need glsl - if (m_shadows_enabled && gpu && m_driver->queryFeature(video::EVDF_ARB_GLSL)) { + if (m_shadows_supported && gpu && m_driver->queryFeature(video::EVDF_ARB_GLSL)) { createShaders(); } else { - m_shadows_enabled = false; + m_shadows_supported = false; warningstream << "Shadows: GLSL Shader not supported on this system." << std::endl; @@ -94,6 +112,8 @@ void ShadowRenderer::initialize() m_texture_format_color = m_shadow_map_texture_32bit ? video::ECOLOR_FORMAT::ECF_G32R32F : video::ECOLOR_FORMAT::ECF_G16R16F; + + m_shadows_enabled &= m_shadows_supported; } @@ -124,6 +144,16 @@ f32 ShadowRenderer::getMaxShadowFar() const return 0.0f; } +void ShadowRenderer::setShadowIntensity(float shadow_intensity) +{ + m_shadow_strength = pow(shadow_intensity, 1.0f / m_shadow_strength_gamma); + if (m_shadow_strength > 1E-2) + enable(); + else + disable(); +} + + void ShadowRenderer::addNodeToShadowList( scene::ISceneNode *node, E_SHADOW_MODE shadowMode) { @@ -153,6 +183,7 @@ void ShadowRenderer::updateSMTextures() shadowMapTextureDynamicObjects = getSMTexture( std::string("shadow_dynamic_") + itos(m_shadow_map_texture_size), m_texture_format, true); + assert(shadowMapTextureDynamicObjects != nullptr); } if (!shadowMapClientMap) { @@ -161,6 +192,7 @@ void ShadowRenderer::updateSMTextures() std::string("shadow_clientmap_") + itos(m_shadow_map_texture_size), m_shadow_map_colored ? m_texture_format_color : m_texture_format, true); + assert(shadowMapClientMap != nullptr); } if (!shadowMapClientMapFuture && m_map_shadow_update_frames > 1) { @@ -168,6 +200,7 @@ void ShadowRenderer::updateSMTextures() std::string("shadow_clientmap_bb_") + itos(m_shadow_map_texture_size), m_shadow_map_colored ? m_texture_format_color : m_texture_format, true); + assert(shadowMapClientMapFuture != nullptr); } if (m_shadow_map_colored && !shadowMapTextureColors) { @@ -175,6 +208,7 @@ void ShadowRenderer::updateSMTextures() std::string("shadow_colored_") + itos(m_shadow_map_texture_size), m_shadow_map_colored ? m_texture_format_color : m_texture_format, true); + assert(shadowMapTextureColors != nullptr); } // The merge all shadowmaps texture @@ -194,6 +228,7 @@ void ShadowRenderer::updateSMTextures() shadowMapTextureFinal = getSMTexture( std::string("shadowmap_final_") + itos(m_shadow_map_texture_size), frt, true); + assert(shadowMapTextureFinal != nullptr); } if (!m_shadow_node_array.empty() && !m_light_list.empty()) { @@ -270,6 +305,10 @@ void ShadowRenderer::update(video::ITexture *outputTarget) updateSMTextures(); + if (shadowMapTextureFinal == nullptr) { + return; + } + if (!m_shadow_node_array.empty() && !m_light_list.empty()) { for (DirectionalLight &light : m_light_list) { @@ -307,19 +346,23 @@ void ShadowRenderer::drawDebug() /* this code just shows shadows textures in screen and in ONLY for debugging*/ #if 0 // this is debug, ignore for now. - m_driver->draw2DImage(shadowMapTextureFinal, - core::rect(0, 50, 128, 128 + 50), - core::rect({0, 0}, shadowMapTextureFinal->getSize())); + if (shadowMapTextureFinal) + m_driver->draw2DImage(shadowMapTextureFinal, + core::rect(0, 50, 128, 128 + 50), + core::rect({0, 0}, shadowMapTextureFinal->getSize())); - m_driver->draw2DImage(shadowMapClientMap, - core::rect(0, 50 + 128, 128, 128 + 50 + 128), - core::rect({0, 0}, shadowMapTextureFinal->getSize())); - m_driver->draw2DImage(shadowMapTextureDynamicObjects, - core::rect(0, 128 + 50 + 128, 128, - 128 + 50 + 128 + 128), - core::rect({0, 0}, shadowMapTextureDynamicObjects->getSize())); + if (shadowMapClientMap) + m_driver->draw2DImage(shadowMapClientMap, + core::rect(0, 50 + 128, 128, 128 + 50 + 128), + core::rect({0, 0}, shadowMapTextureFinal->getSize())); + + if (shadowMapTextureDynamicObjects) + m_driver->draw2DImage(shadowMapTextureDynamicObjects, + core::rect(0, 128 + 50 + 128, 128, + 128 + 50 + 128 + 128), + core::rect({0, 0}, shadowMapTextureDynamicObjects->getSize())); - if (m_shadow_map_colored) { + if (m_shadow_map_colored && shadowMapTextureColors) { m_driver->draw2DImage(shadowMapTextureColors, core::rect(128,128 + 50 + 128 + 128, @@ -469,13 +512,13 @@ void ShadowRenderer::createShaders() if (depth_shader == -1) { std::string depth_shader_vs = getShaderPath("shadow_shaders", "pass1_vertex.glsl"); if (depth_shader_vs.empty()) { - m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error shadow mapping vs shader not found." << std::endl; return; } std::string depth_shader_fs = getShaderPath("shadow_shaders", "pass1_fragment.glsl"); if (depth_shader_fs.empty()) { - m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error shadow mapping fs shader not found." << std::endl; return; } @@ -490,7 +533,7 @@ void ShadowRenderer::createShaders() if (depth_shader == -1) { // upsi, something went wrong loading shader. delete m_shadow_depth_cb; - m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error compiling shadow mapping shader." << std::endl; return; } @@ -506,13 +549,13 @@ void ShadowRenderer::createShaders() if (depth_shader_entities == -1) { std::string depth_shader_vs = getShaderPath("shadow_shaders", "pass1_vertex.glsl"); if (depth_shader_vs.empty()) { - m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error shadow mapping vs shader not found." << std::endl; return; } std::string depth_shader_fs = getShaderPath("shadow_shaders", "pass1_fragment.glsl"); if (depth_shader_fs.empty()) { - m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error shadow mapping fs shader not found." << std::endl; return; } @@ -525,7 +568,7 @@ void ShadowRenderer::createShaders() if (depth_shader_entities == -1) { // upsi, something went wrong loading shader. - m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error compiling shadow mapping shader (dynamic)." << std::endl; return; } @@ -539,14 +582,14 @@ void ShadowRenderer::createShaders() if (mixcsm_shader == -1) { std::string depth_shader_vs = getShaderPath("shadow_shaders", "pass2_vertex.glsl"); if (depth_shader_vs.empty()) { - m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error cascade shadow mapping fs shader not found." << std::endl; return; } std::string depth_shader_fs = getShaderPath("shadow_shaders", "pass2_fragment.glsl"); if (depth_shader_fs.empty()) { - m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error cascade shadow mapping fs shader not found." << std::endl; return; } @@ -565,7 +608,7 @@ void ShadowRenderer::createShaders() // upsi, something went wrong loading shader. delete m_shadow_mix_cb; delete m_screen_quad; - m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error compiling cascade shadow mapping shader." << std::endl; return; } @@ -579,13 +622,13 @@ void ShadowRenderer::createShaders() if (m_shadow_map_colored && depth_shader_trans == -1) { std::string depth_shader_vs = getShaderPath("shadow_shaders", "pass1_trans_vertex.glsl"); if (depth_shader_vs.empty()) { - m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error shadow mapping vs shader not found." << std::endl; return; } std::string depth_shader_fs = getShaderPath("shadow_shaders", "pass1_trans_fragment.glsl"); if (depth_shader_fs.empty()) { - m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error shadow mapping fs shader not found." << std::endl; return; } @@ -601,7 +644,7 @@ void ShadowRenderer::createShaders() // upsi, something went wrong loading shader. delete m_shadow_depth_trans_cb; m_shadow_map_colored = false; - m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error compiling colored shadow mapping shader." << std::endl; return; } diff --git a/src/client/shadows/dynamicshadowsrender.h b/src/client/shadows/dynamicshadowsrender.h index e4b3c3e22..dc8f79c56 100644 --- a/src/client/shadows/dynamicshadowsrender.h +++ b/src/client/shadows/dynamicshadowsrender.h @@ -82,11 +82,12 @@ public: } - bool is_active() const { return m_shadows_enabled; } + bool is_active() const { return m_shadows_enabled && shadowMapTextureFinal != nullptr; } void setTimeOfDay(float isDay) { m_time_day = isDay; }; + void setShadowIntensity(float shadow_intensity); s32 getShadowSamples() const { return m_shadow_samples; } - float getShadowStrength() const { return m_shadow_strength; } + float getShadowStrength() const { return m_shadows_enabled ? m_shadow_strength : 0.0f; } float getTimeOfDay() const { return m_time_day; } private: @@ -101,6 +102,9 @@ private: void mixShadowsQuad(); void updateSMTextures(); + void disable(); + void enable() { m_shadows_enabled = m_shadows_supported; } + // a bunch of variables IrrlichtDevice *m_device{nullptr}; scene::ISceneManager *m_smgr{nullptr}; @@ -116,12 +120,14 @@ private: std::vector m_shadow_node_array; float m_shadow_strength; + float m_shadow_strength_gamma; float m_shadow_map_max_distance; float m_shadow_map_texture_size; float m_time_day{0.0f}; int m_shadow_samples; bool m_shadow_map_texture_32bit; bool m_shadows_enabled; + bool m_shadows_supported; bool m_shadow_map_colored; u8 m_map_shadow_update_frames; /* Use this number of frames to update map shaodw */ u8 m_current_frame{0}; /* Current frame */ diff --git a/src/client/sky.h b/src/client/sky.h index 3dc057b70..e03683f12 100644 --- a/src/client/sky.h +++ b/src/client/sky.h @@ -65,6 +65,7 @@ public: } void setSunVisible(bool sun_visible) { m_sun_params.visible = sun_visible; } + bool getSunVisible() const { return m_sun_params.visible; } void setSunTexture(const std::string &sun_texture, const std::string &sun_tonemap, ITextureSource *tsrc); void setSunScale(f32 sun_scale) { m_sun_params.scale = sun_scale; } @@ -72,6 +73,7 @@ public: void setSunriseTexture(const std::string &sunglow_texture, ITextureSource* tsrc); void setMoonVisible(bool moon_visible) { m_moon_params.visible = moon_visible; } + bool getMoonVisible() const { return m_moon_params.visible; } void setMoonTexture(const std::string &moon_texture, const std::string &moon_tonemap, ITextureSource *tsrc); void setMoonScale(f32 moon_scale) { m_moon_params.scale = moon_scale; } diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index b935c0e21..2e9a19199 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -266,7 +266,7 @@ void set_default_settings() // Effects Shadows settings->setDefault("enable_dynamic_shadows", "false"); - settings->setDefault("shadow_strength", "0.2"); + settings->setDefault("shadow_strength_gamma", "1.0"); settings->setDefault("shadow_map_max_distance", "200.0"); settings->setDefault("shadow_map_texture_size", "2048"); settings->setDefault("shadow_map_texture_32bit", "true"); diff --git a/src/lighting.h b/src/lighting.h new file mode 100644 index 000000000..e0d9cee09 --- /dev/null +++ b/src/lighting.h @@ -0,0 +1,27 @@ +/* +Minetest +Copyright (C) 2021 x2048, Dmitry Kostenko + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +/** Describes ambient light settings for a player + */ +struct Lighting +{ + float shadow_intensity {0.0f}; +}; diff --git a/src/network/clientopcodes.cpp b/src/network/clientopcodes.cpp index a98a5e7d1..6a78b4652 100644 --- a/src/network/clientopcodes.cpp +++ b/src/network/clientopcodes.cpp @@ -123,6 +123,7 @@ const ToClientCommandHandler toClientCommandTable[TOCLIENT_NUM_MSG_TYPES] = { "TOCLIENT_SRP_BYTES_S_B", TOCLIENT_STATE_NOT_CONNECTED, &Client::handleCommand_SrpBytesSandB }, // 0x60 { "TOCLIENT_FORMSPEC_PREPEND", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_FormspecPrepend }, // 0x61, { "TOCLIENT_MINIMAP_MODES", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_MinimapModes }, // 0x62, + { "TOCLIENT_SET_LIGHTING", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_SetLighting }, // 0x63, }; const static ServerCommandFactory null_command_factory = { "TOSERVER_NULL", 0, false }; diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 48ad60ac6..15b576640 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -1682,3 +1682,11 @@ void Client::handleCommand_MinimapModes(NetworkPacket *pkt) if (m_minimap) m_minimap->setModeIndex(mode); } + +void Client::handleCommand_SetLighting(NetworkPacket *pkt) +{ + Lighting& lighting = m_env.getLocalPlayer()->getLighting(); + + if (pkt->getRemainingBytes() >= 4) + *pkt >> lighting.shadow_intensity; +} diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index a5ff53216..f98a829ba 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -762,7 +762,12 @@ enum ToClientCommand std::string extra */ - TOCLIENT_NUM_MSG_TYPES = 0x63, + TOCLIENT_SET_LIGHTING = 0x63, + /* + f32 shadow_intensity + */ + + TOCLIENT_NUM_MSG_TYPES = 0x64, }; enum ToServerCommand diff --git a/src/remoteplayer.h b/src/remoteplayer.h index e33630841..0ab33adfe 100644 --- a/src/remoteplayer.h +++ b/src/remoteplayer.h @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "player.h" #include "skyparams.h" +#include "lighting.h" class PlayerSAO; @@ -125,6 +126,10 @@ public: *frame_speed = local_animation_speed; } + void setLighting(const Lighting &lighting) { m_lighting = lighting; } + + const Lighting& getLighting() const { return m_lighting; } + void setDirty(bool dirty) { m_dirty = true; } u16 protocol_version = 0; @@ -160,5 +165,7 @@ private: MoonParams m_moon_params; StarParams m_star_params; + Lighting m_lighting; + session_t m_peer_id = PEER_ID_INEXISTENT; }; diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index 1ed6b0d5c..6153a0399 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -2295,6 +2295,47 @@ int ObjectRef::l_set_minimap_modes(lua_State *L) return 0; } +// set_lighting(self, lighting) +int ObjectRef::l_set_lighting(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + RemotePlayer *player = getplayer(ref); + if (player == nullptr) + return 0; + + luaL_checktype(L, 2, LUA_TTABLE); + Lighting lighting = player->getLighting(); + lua_getfield(L, 2, "shadows"); + if (lua_istable(L, -1)) { + lighting.shadow_intensity = getfloatfield_default(L, -1, "intensity", lighting.shadow_intensity); + } + lua_pop(L, -1); + + getServer(L)->setLighting(player, lighting); + lua_pushboolean(L, true); + return 1; +} + +// get_lighting(self) +int ObjectRef::l_get_lighting(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + RemotePlayer *player = getplayer(ref); + if (player == nullptr) + return 0; + + const Lighting &lighting = player->getLighting(); + + lua_newtable(L); // result + lua_newtable(L); // "shadows" + lua_pushnumber(L, lighting.shadow_intensity); + lua_setfield(L, -2, "intensity"); + lua_setfield(L, -2, "shadows"); + return 1; +} + ObjectRef::ObjectRef(ServerActiveObject *object): m_object(object) {} @@ -2448,5 +2489,7 @@ luaL_Reg ObjectRef::methods[] = { luamethod(ObjectRef, get_eye_offset), luamethod(ObjectRef, send_mapblock), luamethod(ObjectRef, set_minimap_modes), + luamethod(ObjectRef, set_lighting), + luamethod(ObjectRef, get_lighting), {0,0} }; diff --git a/src/script/lua_api/l_object.h b/src/script/lua_api/l_object.h index 084d40c05..3e4e6681a 100644 --- a/src/script/lua_api/l_object.h +++ b/src/script/lua_api/l_object.h @@ -376,4 +376,10 @@ private: // set_minimap_modes(self, modes, wanted_mode) static int l_set_minimap_modes(lua_State *L); + + // set_lighting(self, lighting) + static int l_set_lighting(lua_State *L); + + // get_lighting(self) + static int l_get_lighting(lua_State *L); }; diff --git a/src/server.cpp b/src/server.cpp index d9205c895..4bc049e32 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1795,6 +1795,16 @@ void Server::SendOverrideDayNightRatio(session_t peer_id, bool do_override, Send(&pkt); } +void Server::SendSetLighting(session_t peer_id, const Lighting &lighting) +{ + NetworkPacket pkt(TOCLIENT_SET_LIGHTING, + 4, peer_id); + + pkt << lighting.shadow_intensity; + + Send(&pkt); +} + void Server::SendTimeOfDay(session_t peer_id, u16 time, f32 time_speed) { NetworkPacket pkt(TOCLIENT_TIME_OF_DAY, 0, peer_id); @@ -3386,6 +3396,13 @@ void Server::overrideDayNightRatio(RemotePlayer *player, bool do_override, SendOverrideDayNightRatio(player->getPeerId(), do_override, ratio); } +void Server::setLighting(RemotePlayer *player, const Lighting &lighting) +{ + sanity_check(player); + player->setLighting(lighting); + SendSetLighting(player->getPeerId(), lighting); +} + void Server::notifyPlayers(const std::wstring &msg) { SendChatMessage(PEER_ID_INEXISTENT, ChatMessage(msg)); diff --git a/src/server.h b/src/server.h index 2741b3157..c05393291 100644 --- a/src/server.h +++ b/src/server.h @@ -69,6 +69,7 @@ struct SkyboxParams; struct SunParams; struct MoonParams; struct StarParams; +struct Lighting; class ServerThread; class ServerModManager; class ServerInventoryManager; @@ -333,6 +334,8 @@ public: void overrideDayNightRatio(RemotePlayer *player, bool do_override, float brightness); + void setLighting(RemotePlayer *player, const Lighting &lighting); + /* con::PeerHandler implementation. */ void peerAdded(con::Peer *peer); void deletingPeer(con::Peer *peer, bool timeout); @@ -459,6 +462,7 @@ private: void SendSetStars(session_t peer_id, const StarParams ¶ms); void SendCloudParams(session_t peer_id, const CloudParams ¶ms); void SendOverrideDayNightRatio(session_t peer_id, bool do_override, float ratio); + void SendSetLighting(session_t peer_id, const Lighting &lighting); void broadcastModChannelMessage(const std::string &channel, const std::string &message, session_t from_peer); -- cgit v1.2.3 From 8d387433b14791db95e59127b5e6e30f58155c1e Mon Sep 17 00:00:00 2001 From: DS Date: Tue, 29 Mar 2022 18:06:16 +0200 Subject: Fix the documentation of InvRef:get_lists() and clean up code (#12150) --- doc/lua_api.txt | 4 ++-- src/database/database-postgresql.cpp | 2 +- src/database/database-sqlite3.cpp | 4 ++-- src/inventory.cpp | 42 ------------------------------------ src/inventory.h | 26 +++++++++++++++------- src/script/common/c_content.cpp | 23 ++++++++++++-------- src/script/common/c_content.h | 6 ++++-- src/script/cpp_api/s_client.cpp | 10 +-------- src/script/lua_api/l_inventory.cpp | 23 +++++++++----------- src/script/lua_api/l_nodemeta.cpp | 12 ++++------- 10 files changed, 56 insertions(+), 96 deletions(-) (limited to 'src/script/lua_api') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 029c53616..161bdd249 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -6355,9 +6355,9 @@ An `InvRef` is a reference to an inventory. * `set_width(listname, width)`: set width of list; currently used for crafting * `get_stack(listname, i)`: get a copy of stack index `i` in list * `set_stack(listname, i, stack)`: copy `stack` to index `i` in list -* `get_list(listname)`: return full list +* `get_list(listname)`: return full list (list of `ItemStack`s) * `set_list(listname, list)`: set full list (size will not change) -* `get_lists()`: returns list of inventory lists +* `get_lists()`: returns table that maps listnames to inventory lists * `set_lists(lists)`: sets inventory lists (size will not change) * `add_item(listname, stack)`: add item somewhere in list, returns leftover `ItemStack`. diff --git a/src/database/database-postgresql.cpp b/src/database/database-postgresql.cpp index 3469f4242..9d6501e68 100644 --- a/src/database/database-postgresql.cpp +++ b/src/database/database-postgresql.cpp @@ -495,7 +495,7 @@ void PlayerDatabasePostgreSQL::savePlayer(RemotePlayer *player) execPrepared("remove_player_inventories", 1, rmvalues); execPrepared("remove_player_inventory_items", 1, rmvalues); - std::vector inventory_lists = sao->getInventory()->getLists(); + const auto &inventory_lists = sao->getInventory()->getLists(); std::ostringstream oss; for (u16 i = 0; i < inventory_lists.size(); i++) { const InventoryList* list = inventory_lists[i]; diff --git a/src/database/database-sqlite3.cpp b/src/database/database-sqlite3.cpp index 1e63ae9d8..9521085e9 100644 --- a/src/database/database-sqlite3.cpp +++ b/src/database/database-sqlite3.cpp @@ -476,10 +476,10 @@ void PlayerDatabaseSQLite3::savePlayer(RemotePlayer *player) sqlite3_vrfy(sqlite3_step(m_stmt_player_remove_inventory_items), SQLITE_DONE); sqlite3_reset(m_stmt_player_remove_inventory_items); - std::vector inventory_lists = sao->getInventory()->getLists(); + const auto &inventory_lists = sao->getInventory()->getLists(); std::ostringstream oss; for (u16 i = 0; i < inventory_lists.size(); i++) { - const InventoryList* list = inventory_lists[i]; + const InventoryList *list = inventory_lists[i]; str_to_sqlite(m_stmt_player_add_inventory, 1, player->getName()); int_to_sqlite(m_stmt_player_add_inventory, 2, i); diff --git a/src/inventory.cpp b/src/inventory.cpp index d14b12f9d..6d2b7fba3 100644 --- a/src/inventory.cpp +++ b/src/inventory.cpp @@ -503,11 +503,6 @@ void InventoryList::deSerialize(std::istream &is) throw SerializationError(ss.str()); } -InventoryList::InventoryList(const InventoryList &other) -{ - *this = other; -} - InventoryList & InventoryList::operator = (const InventoryList &other) { m_items = other.m_items; @@ -535,21 +530,6 @@ bool InventoryList::operator == (const InventoryList &other) const return true; } -const std::string &InventoryList::getName() const -{ - return m_name; -} - -u32 InventoryList::getSize() const -{ - return m_items.size(); -} - -u32 InventoryList::getWidth() const -{ - return m_width; -} - u32 InventoryList::getUsedSlots() const { u32 num = 0; @@ -560,18 +540,6 @@ u32 InventoryList::getUsedSlots() const return num; } -const ItemStack& InventoryList::getItem(u32 i) const -{ - assert(i < m_size); // Pre-condition - return m_items[i]; -} - -ItemStack& InventoryList::getItem(u32 i) -{ - assert(i < m_size); // Pre-condition - return m_items[i]; -} - ItemStack InventoryList::changeItem(u32 i, const ItemStack &newitem) { if(i >= m_items.size()) @@ -960,16 +928,6 @@ InventoryList * Inventory::getList(const std::string &name) return m_lists[i]; } -std::vector Inventory::getLists() -{ - std::vector lists; - lists.reserve(m_lists.size()); - for (auto list : m_lists) { - lists.push_back(list); - } - return lists; -} - bool Inventory::deleteList(const std::string &name) { s32 i = getListIndex(name); diff --git a/src/inventory.h b/src/inventory.h index eb063d4ad..8b31de3a8 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -198,7 +198,7 @@ public: void serialize(std::ostream &os, bool incremental) const; void deSerialize(std::istream &is); - InventoryList(const InventoryList &other); + InventoryList(const InventoryList &other) { *this = other; } InventoryList & operator = (const InventoryList &other); bool operator == (const InventoryList &other) const; bool operator != (const InventoryList &other) const @@ -206,15 +206,25 @@ public: return !(*this == other); } - const std::string &getName() const; - u32 getSize() const; - u32 getWidth() const; + const std::string &getName() const { return m_name; } + u32 getSize() const { return static_cast(m_items.size()); } + u32 getWidth() const { return m_width; } // Count used slots u32 getUsedSlots() const; // Get reference to item - const ItemStack& getItem(u32 i) const; - ItemStack& getItem(u32 i); + const ItemStack &getItem(u32 i) const + { + assert(i < m_size); // Pre-condition + return m_items[i]; + } + ItemStack &getItem(u32 i) + { + assert(i < m_size); // Pre-condition + return m_items[i]; + } + // Get reference to all items + const std::vector &getItems() const { return m_items; } // Returns old item. Parameter can be an empty item. ItemStack changeItem(u32 i, const ItemStack &newitem); // Delete item @@ -271,7 +281,7 @@ public: private: std::vector m_items; std::string m_name; - u32 m_size; + u32 m_size; // always the same as m_items.size() u32 m_width = 0; IItemDefManager *m_itemdef; bool m_dirty = true; @@ -301,7 +311,7 @@ public: InventoryList * addList(const std::string &name, u32 size); InventoryList * getList(const std::string &name); const InventoryList * getList(const std::string &name) const; - std::vector getLists(); + const std::vector &getLists() const { return m_lists; } bool deleteList(const std::string &name); // A shorthand for adding items. Returns leftover item (possibly empty). ItemStack addItem(const std::string &listname, const ItemStack &newitem) diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index b6eaa6b13..36f4316ee 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -1351,17 +1351,22 @@ void push_tool_capabilities(lua_State *L, } /******************************************************************************/ -void push_inventory_list(lua_State *L, Inventory *inv, const char *name) +void push_inventory_list(lua_State *L, const InventoryList &invlist) { - InventoryList *invlist = inv->getList(name); - if(invlist == NULL){ - lua_pushnil(L); - return; + push_items(L, invlist.getItems()); +} + +/******************************************************************************/ +void push_inventory_lists(lua_State *L, const Inventory &inv) +{ + const auto &lists = inv.getLists(); + lua_createtable(L, 0, lists.size()); + for(const InventoryList *list : lists) { + const std::string &name = list->getName(); + lua_pushlstring(L, name.c_str(), name.size()); + push_inventory_list(L, *list); + lua_rawset(L, -3); } - std::vector items; - for(u32 i=0; igetSize(); i++) - items.push_back(invlist->getItem(i)); - push_items(L, items); } /******************************************************************************/ diff --git a/src/script/common/c_content.h b/src/script/common/c_content.h index e762604a4..11b39364f 100644 --- a/src/script/common/c_content.h +++ b/src/script/common/c_content.h @@ -55,6 +55,7 @@ struct ObjectProperties; struct SimpleSoundSpec; struct ServerSoundParams; class Inventory; +class InventoryList; struct NodeBox; struct ContentFeatures; struct TileDef; @@ -120,8 +121,9 @@ void push_object_properties (lua_State *L, ObjectProperties *prop); void push_inventory_list (lua_State *L, - Inventory *inv, - const char *name); + const InventoryList &invlist); +void push_inventory_lists (lua_State *L, + const Inventory &inv); void read_inventory_list (lua_State *L, int tableindex, Inventory *inv, const char *name, Server *srv, int forcesize=-1); diff --git a/src/script/cpp_api/s_client.cpp b/src/script/cpp_api/s_client.cpp index c889fffa0..b02a0c7be 100644 --- a/src/script/cpp_api/s_client.cpp +++ b/src/script/cpp_api/s_client.cpp @@ -281,15 +281,7 @@ bool ScriptApiClient::on_inventory_open(Inventory *inventory) lua_getglobal(L, "core"); lua_getfield(L, -1, "registered_on_inventory_open"); - std::vector lists = inventory->getLists(); - std::vector::iterator iter = lists.begin(); - lua_createtable(L, 0, lists.size()); - for (; iter != lists.end(); iter++) { - const char* name = (*iter)->getName().c_str(); - lua_pushstring(L, name); - push_inventory_list(L, inventory, name); - lua_rawset(L, -3); - } + push_inventory_lists(L, *inventory); try { runCallbacks(1, RUN_CALLBACKS_MODE_OR); diff --git a/src/script/lua_api/l_inventory.cpp b/src/script/lua_api/l_inventory.cpp index b0a4ee194..175047e58 100644 --- a/src/script/lua_api/l_inventory.cpp +++ b/src/script/lua_api/l_inventory.cpp @@ -214,11 +214,16 @@ int InvRef::l_get_list(lua_State *L) InvRef *ref = checkobject(L, 1); const char *listname = luaL_checkstring(L, 2); Inventory *inv = getinv(L, ref); - if(inv){ - push_inventory_list(L, inv, listname); - } else { + if (!inv) { lua_pushnil(L); + return 1; } + InventoryList *invlist = inv->getList(listname); + if (!invlist) { + lua_pushnil(L); + return 1; + } + push_inventory_list(L, *invlist); return 1; } @@ -242,7 +247,7 @@ int InvRef::l_set_list(lua_State *L) return 0; } -// get_lists(self) -> list of InventoryLists +// get_lists(self) -> table that maps listnames to InventoryLists int InvRef::l_get_lists(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -251,15 +256,7 @@ int InvRef::l_get_lists(lua_State *L) if (!inv) { return 0; } - std::vector lists = inv->getLists(); - std::vector::iterator iter = lists.begin(); - lua_createtable(L, 0, lists.size()); - for (; iter != lists.end(); iter++) { - const char* name = (*iter)->getName().c_str(); - lua_pushstring(L, name); - push_inventory_list(L, inv, name); - lua_rawset(L, -3); - } + push_inventory_lists(L, *inv); return 1; } diff --git a/src/script/lua_api/l_nodemeta.cpp b/src/script/lua_api/l_nodemeta.cpp index 34760157d..1d052685e 100644 --- a/src/script/lua_api/l_nodemeta.cpp +++ b/src/script/lua_api/l_nodemeta.cpp @@ -127,18 +127,14 @@ void NodeMetaRef::handleToTable(lua_State *L, Metadata *_meta) // fields MetaDataRef::handleToTable(L, _meta); - NodeMetadata *meta = (NodeMetadata*) _meta; + NodeMetadata *meta = (NodeMetadata *) _meta; // inventory - lua_newtable(L); Inventory *inv = meta->getInventory(); if (inv) { - std::vector lists = inv->getLists(); - for(std::vector::const_iterator - i = lists.begin(); i != lists.end(); ++i) { - push_inventory_list(L, inv, (*i)->getName().c_str()); - lua_setfield(L, -2, (*i)->getName().c_str()); - } + push_inventory_lists(L, *inv); + } else { + lua_newtable(L); } lua_setfield(L, -2, "inventory"); } -- cgit v1.2.3 From 0b5b2b2633609f646a534d353a2c587af4be46fa Mon Sep 17 00:00:00 2001 From: Jude Melton-Houghton Date: Thu, 7 Apr 2022 15:58:04 -0400 Subject: Disentangle map implementations (#12148) Fixes violation of Liskov substitution principle Fixes #12144 --- src/client/clientmap.h | 4 +-- src/map.cpp | 45 ++++++++++++++++++------------- src/map.h | 59 ++++++++++++++++++----------------------- src/script/lua_api/l_env.cpp | 2 +- src/script/lua_api/l_vmanip.cpp | 2 +- src/server.cpp | 2 +- 6 files changed, 57 insertions(+), 57 deletions(-) (limited to 'src/script/lua_api') diff --git a/src/client/clientmap.h b/src/client/clientmap.h index d8554313d..4edad0d20 100644 --- a/src/client/clientmap.h +++ b/src/client/clientmap.h @@ -76,9 +76,9 @@ public: virtual ~ClientMap() = default; - s32 mapType() const + bool maySaveBlocks() override { - return MAPTYPE_CLIENT; + return false; } void drop() diff --git a/src/map.cpp b/src/map.cpp index 1cbc55707..9c9324f5f 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -246,22 +246,6 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n, action.setSetNode(p, rollback_oldnode, rollback_newnode); m_gamedef->rollback()->reportAction(action); } - - /* - Add neighboring liquid nodes and this node to transform queue. - (it's vital for the node itself to get updated last, if it was removed.) - */ - - for (const v3s16 &dir : g_7dirs) { - v3s16 p2 = p + dir; - - bool is_valid_position; - MapNode n2 = getNode(p2, &is_valid_position); - if(is_valid_position && - (m_nodedef->get(n2).isLiquid() || - n2.getContent() == CONTENT_AIR)) - m_transforming_liquid.push_back(p2); - } } void Map::removeNodeAndUpdate(v3s16 p, @@ -342,7 +326,7 @@ struct TimeOrderedMapBlock { void Map::timerUpdate(float dtime, float unload_timeout, u32 max_loaded_blocks, std::vector *unloaded_blocks) { - bool save_before_unloading = (mapType() == MAPTYPE_SERVER); + bool save_before_unloading = maySaveBlocks(); // Profile modified reasons Profiler modprofiler; @@ -527,11 +511,11 @@ struct NodeNeighbor { { } }; -void Map::transforming_liquid_add(v3s16 p) { +void ServerMap::transforming_liquid_add(v3s16 p) { m_transforming_liquid.push_back(p); } -void Map::transformLiquids(std::map &modified_blocks, +void ServerMap::transformLiquids(std::map &modified_blocks, ServerEnvironment *env) { u32 loopcount = 0; @@ -1565,6 +1549,29 @@ bool ServerMap::isBlockInQueue(v3s16 pos) return m_emerge && m_emerge->isBlockInQueue(pos); } +void ServerMap::addNodeAndUpdate(v3s16 p, MapNode n, + std::map &modified_blocks, + bool remove_metadata) +{ + Map::addNodeAndUpdate(p, n, modified_blocks, remove_metadata); + + /* + Add neighboring liquid nodes and this node to transform queue. + (it's vital for the node itself to get updated last, if it was removed.) + */ + + for (const v3s16 &dir : g_7dirs) { + v3s16 p2 = p + dir; + + bool is_valid_position; + MapNode n2 = getNode(p2, &is_valid_position); + if(is_valid_position && + (m_nodedef->get(n2).isLiquid() || + n2.getContent() == CONTENT_AIR)) + m_transforming_liquid.push_back(p2); + } +} + // N.B. This requires no synchronization, since data will not be modified unless // the VoxelManipulator being updated belongs to the same thread. void ServerMap::updateVManip(v3s16 pos) diff --git a/src/map.h b/src/map.h index fe580b20f..9dc7a101c 100644 --- a/src/map.h +++ b/src/map.h @@ -54,10 +54,6 @@ struct BlockMakeData; MapEditEvent */ -#define MAPTYPE_BASE 0 -#define MAPTYPE_SERVER 1 -#define MAPTYPE_CLIENT 2 - enum MapEditEventType{ // Node added (changed from air or something else to something) MEET_ADDNODE, @@ -127,11 +123,6 @@ public: virtual ~Map(); DISABLE_CLASS_COPY(Map); - virtual s32 mapType() const - { - return MAPTYPE_BASE; - } - /* Drop (client) or delete (server) the map. */ @@ -180,7 +171,7 @@ public: /* These handle lighting but not faces. */ - void addNodeAndUpdate(v3s16 p, MapNode n, + virtual void addNodeAndUpdate(v3s16 p, MapNode n, std::map &modified_blocks, bool remove_metadata = true); void removeNodeAndUpdate(v3s16 p, @@ -200,6 +191,11 @@ public: virtual void save(ModifiedState save_level) { FATAL_ERROR("FIXME"); } + /* + Return true unless the map definitely cannot save blocks. + */ + virtual bool maySaveBlocks() { return true; } + // Server implements these. // Client leaves them as no-op. virtual bool saveBlock(MapBlock *block) { return false; } @@ -207,14 +203,14 @@ public: /* Updates usage timers and unloads unused blocks and sectors. - Saves modified blocks before unloading on MAPTYPE_SERVER. + Saves modified blocks before unloading if possible. */ void timerUpdate(float dtime, float unload_timeout, u32 max_loaded_blocks, std::vector *unloaded_blocks=NULL); /* Unloads all blocks with a zero refCount(). - Saves modified blocks before unloading on MAPTYPE_SERVER. + Saves modified blocks before unloading if possible. */ void unloadUnreferencedBlocks(std::vector *unloaded_blocks=NULL); @@ -226,9 +222,6 @@ public: // For debug printing. Prints "Map: ", "ServerMap: " or "ClientMap: " virtual void PrintInfo(std::ostream &out); - void transformLiquids(std::map & modified_blocks, - ServerEnvironment *env); - /* Node metadata These are basically coordinate wrappers to MapBlock @@ -267,12 +260,8 @@ public: Variables */ - void transforming_liquid_add(v3s16 p); - bool isBlockOccluded(MapBlock *block, v3s16 cam_pos_nodes); protected: - friend class LuaVoxelManip; - IGameDef *m_gamedef; std::set m_event_receivers; @@ -283,9 +272,6 @@ protected: MapSector *m_sector_cache = nullptr; v2s16 m_sector_cache_p; - // Queued transforming water nodes - UniqueQueue m_transforming_liquid; - // This stores the properties of the nodes on the map. const NodeDefManager *m_nodedef; @@ -294,12 +280,6 @@ protected: bool isOccluded(const v3s16 &pos_camera, const v3s16 &pos_target, float step, float stepfac, float start_offset, float end_offset, u32 needed_count); - -private: - f32 m_transforming_liquid_loop_count_multiplier = 1.0f; - u32 m_unprocessed_count = 0; - u64 m_inc_trending_up_start_time = 0; // milliseconds - bool m_queue_size_timer_started = false; }; /* @@ -317,11 +297,6 @@ public: ServerMap(const std::string &savedir, IGameDef *gamedef, EmergeManager *emerge, MetricsBackend *mb); ~ServerMap(); - s32 mapType() const - { - return MAPTYPE_SERVER; - } - /* Get a sector from somewhere. - Check memory @@ -364,6 +339,10 @@ public: bool isBlockInQueue(v3s16 pos); + void addNodeAndUpdate(v3s16 p, MapNode n, + std::map &modified_blocks, + bool remove_metadata) override; + /* Database functions */ @@ -406,9 +385,16 @@ public: bool repairBlockLight(v3s16 blockpos, std::map *modified_blocks); + void transformLiquids(std::map & modified_blocks, + ServerEnvironment *env); + + void transforming_liquid_add(v3s16 p); + MapSettingsManager settings_mgr; private: + friend class LuaVoxelManip; + // Emerge manager EmergeManager *m_emerge; @@ -419,6 +405,13 @@ private: std::set m_chunks_in_progress; + // Queued transforming water nodes + UniqueQueue m_transforming_liquid; + f32 m_transforming_liquid_loop_count_multiplier = 1.0f; + u32 m_unprocessed_count = 0; + u64 m_inc_trending_up_start_time = 0; // milliseconds + bool m_queue_size_timer_started = false; + /* Metadata is re-written on disk only if this is true. This is reset to false when written on disk. diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index 18ee3a521..54821df24 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -1386,7 +1386,7 @@ int ModApiEnvMod::l_transforming_liquid_add(lua_State *L) GET_ENV_PTR; v3s16 p0 = read_v3s16(L, 1); - env->getMap().transforming_liquid_add(p0); + env->getServerMap().transforming_liquid_add(p0); return 1; } diff --git a/src/script/lua_api/l_vmanip.cpp b/src/script/lua_api/l_vmanip.cpp index e040e545b..1fa080210 100644 --- a/src/script/lua_api/l_vmanip.cpp +++ b/src/script/lua_api/l_vmanip.cpp @@ -166,7 +166,7 @@ int LuaVoxelManip::l_update_liquids(lua_State *L) LuaVoxelManip *o = checkobject(L, 1); - Map *map = &(env->getMap()); + ServerMap *map = &(env->getServerMap()); const NodeDefManager *ndef = getServer(L)->getNodeDefManager(); MMVManip *vm = o->vm; diff --git a/src/server.cpp b/src/server.cpp index 4bc049e32..8ca8a9bda 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -665,7 +665,7 @@ void Server::AsyncRunStep(bool initial_step) ScopeProfiler sp(g_profiler, "Server: liquid transform"); std::map modified_blocks; - m_env->getMap().transformLiquids(modified_blocks, m_env); + m_env->getServerMap().transformLiquids(modified_blocks, m_env); /* Set the modified blocks unsent for all the clients -- cgit v1.2.3 From 7993909fabce4f796ca67b5a8139936667de25df Mon Sep 17 00:00:00 2001 From: ShadowNinja Date: Wed, 1 Dec 2021 18:54:12 -0500 Subject: Spacing fixes --- CMakeLists.txt | 2 +- client/shaders/nodes_shader/opengl_fragment.glsl | 2 +- client/shaders/nodes_shader/opengl_vertex.glsl | 4 +- clientmods/preview/mod.conf | 2 +- cmake/Modules/FindSQLite3.cmake | 2 +- cmake/Modules/FindVorbis.cmake | 17 ++++--- doc/lgpl-2.1.txt | 18 ++++---- minetest.conf.example | 58 ++++++++++++------------ src/CMakeLists.txt | 2 +- src/client/CMakeLists.txt | 4 +- src/client/clientmap.cpp | 4 +- src/client/imagefilters.cpp | 2 +- src/client/render/core.cpp | 2 +- src/clientiface.h | 2 +- src/collision.cpp | 4 +- src/config.h | 2 +- src/database/database-leveldb.cpp | 2 +- src/inventorymanager.cpp | 2 +- src/irrlicht_changes/CGUITTFont.cpp | 2 +- src/mapgen/dungeongen.cpp | 2 +- src/mapgen/mapgen_flat.cpp | 2 +- src/mapgen/mg_biome.cpp | 2 +- src/mapgen/mg_ore.cpp | 4 +- src/network/connection.h | 4 +- src/script/cpp_api/s_env.cpp | 4 +- src/script/lua_api/l_env.cpp | 4 +- src/script/lua_api/l_env.h | 2 +- src/serverenvironment.cpp | 2 +- src/util/ieee_float.cpp | 2 +- src/util/string.h | 2 +- util/generate-texture-normals.sh | 2 +- 31 files changed, 82 insertions(+), 83 deletions(-) (limited to 'src/script/lua_api') diff --git a/CMakeLists.txt b/CMakeLists.txt index 827191835..5dfc857d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,7 +69,7 @@ if(NOT "${IRRLICHTMT_BUILD_DIR}" STREQUAL "") find_package(IrrlichtMt QUIET PATHS "${IRRLICHTMT_BUILD_DIR}" NO_DEFAULT_PATH -) + ) if(NOT TARGET IrrlichtMt::IrrlichtMt) # find_package() searches certain subdirectories. ${PATH}/cmake is not diff --git a/client/shaders/nodes_shader/opengl_fragment.glsl b/client/shaders/nodes_shader/opengl_fragment.glsl index 4d0d107d1..fea350788 100644 --- a/client/shaders/nodes_shader/opengl_fragment.glsl +++ b/client/shaders/nodes_shader/opengl_fragment.glsl @@ -557,6 +557,6 @@ void main(void) - fogShadingParameter * length(eyeVec) / fogDistance, 0.0, 1.0); col = mix(skyBgColor, col, clarity); col = vec4(col.rgb, base.a); - + gl_FragColor = col; } diff --git a/client/shaders/nodes_shader/opengl_vertex.glsl b/client/shaders/nodes_shader/opengl_vertex.glsl index 935fbf043..8c7e27459 100644 --- a/client/shaders/nodes_shader/opengl_vertex.glsl +++ b/client/shaders/nodes_shader/opengl_vertex.glsl @@ -199,13 +199,13 @@ void main(void) vec3 nNormal = normalize(vNormal); cosLight = dot(nNormal, -v_LightDirection); - // Calculate normal offset scale based on the texel size adjusted for + // Calculate normal offset scale based on the texel size adjusted for // curvature of the SM texture. This code must be change together with // getPerspectiveFactor or any light-space transformation. vec3 eyeToVertex = worldPosition - eyePosition + cameraOffset; // Distance from the vertex to the player float distanceToPlayer = length(eyeToVertex - v_LightDirection * dot(eyeToVertex, v_LightDirection)) / f_shadowfar; - // perspective factor estimation according to the + // perspective factor estimation according to the float perspectiveFactor = distanceToPlayer * xyPerspectiveBias0 + xyPerspectiveBias1; float texelSize = f_shadowfar * perspectiveFactor * perspectiveFactor / (f_textureresolution * xyPerspectiveBias1 - perspectiveFactor * xyPerspectiveBias0); diff --git a/clientmods/preview/mod.conf b/clientmods/preview/mod.conf index 4e56ec293..23a5c3e90 100644 --- a/clientmods/preview/mod.conf +++ b/clientmods/preview/mod.conf @@ -1 +1 @@ -name = preview +name = preview diff --git a/cmake/Modules/FindSQLite3.cmake b/cmake/Modules/FindSQLite3.cmake index b23553a80..8a66cb241 100644 --- a/cmake/Modules/FindSQLite3.cmake +++ b/cmake/Modules/FindSQLite3.cmake @@ -1,4 +1,4 @@ -mark_as_advanced(SQLITE3_LIBRARY SQLITE3_INCLUDE_DIR) +mark_as_advanced(SQLITE3_LIBRARY SQLITE3_INCLUDE_DIR) find_path(SQLITE3_INCLUDE_DIR sqlite3.h) diff --git a/cmake/Modules/FindVorbis.cmake b/cmake/Modules/FindVorbis.cmake index e5fe7f25e..222ddd9d5 100644 --- a/cmake/Modules/FindVorbis.cmake +++ b/cmake/Modules/FindVorbis.cmake @@ -29,18 +29,17 @@ else(NOT GP2XWIZ) find_package_handle_standard_args(Vorbis DEFAULT_MSG VORBIS_INCLUDE_DIR VORBIS_LIBRARY) endif(NOT GP2XWIZ) - + if(VORBIS_FOUND) - if(NOT GP2XWIZ) - set(VORBIS_LIBRARIES ${VORBISFILE_LIBRARY} ${VORBIS_LIBRARY} - ${OGG_LIBRARY}) - else(NOT GP2XWIZ) - set(VORBIS_LIBRARIES ${VORBIS_LIBRARY}) - endif(NOT GP2XWIZ) + if(NOT GP2XWIZ) + set(VORBIS_LIBRARIES ${VORBISFILE_LIBRARY} ${VORBIS_LIBRARY} + ${OGG_LIBRARY}) + else(NOT GP2XWIZ) + set(VORBIS_LIBRARIES ${VORBIS_LIBRARY}) + endif(NOT GP2XWIZ) else(VORBIS_FOUND) - set(VORBIS_LIBRARIES) + set(VORBIS_LIBRARIES) endif(VORBIS_FOUND) mark_as_advanced(OGG_INCLUDE_DIR VORBIS_INCLUDE_DIR) mark_as_advanced(OGG_LIBRARY VORBIS_LIBRARY VORBISFILE_LIBRARY) - diff --git a/doc/lgpl-2.1.txt b/doc/lgpl-2.1.txt index 4362b4915..e5ab03e12 100644 --- a/doc/lgpl-2.1.txt +++ b/doc/lgpl-2.1.txt @@ -55,7 +55,7 @@ modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. - + Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a @@ -111,7 +111,7 @@ modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. - + GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION @@ -158,7 +158,7 @@ Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. - + 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 @@ -216,7 +216,7 @@ instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. - + Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. @@ -267,7 +267,7 @@ Library will still fall under Section 6.) distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. - + 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work @@ -329,7 +329,7 @@ restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. - + 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined @@ -370,7 +370,7 @@ subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. - + 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or @@ -422,7 +422,7 @@ conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. - + 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is @@ -456,7 +456,7 @@ SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS - + How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest diff --git a/minetest.conf.example b/minetest.conf.example index ed2ebc969..21aeb3546 100644 --- a/minetest.conf.example +++ b/minetest.conf.example @@ -1385,7 +1385,7 @@ # ask_reconnect_on_crash = false # From how far clients know about objects, stated in mapblocks (16 nodes). -# +# # Setting this larger than active_block_range will also cause the server # to maintain active objects up to this distance in the direction the # player is looking. (This can avoid mobs suddenly disappearing from view) @@ -1938,7 +1938,7 @@ # octaves = 3, # persistence = 0.5, # lacunarity = 2.0, -# flags = +# flags = # } # Second of two 3D noises that together define tunnels. @@ -1951,7 +1951,7 @@ # octaves = 3, # persistence = 0.5, # lacunarity = 2.0, -# flags = +# flags = # } # 3D noise defining giant caverns. @@ -1964,7 +1964,7 @@ # octaves = 5, # persistence = 0.63, # lacunarity = 2.0, -# flags = +# flags = # } # 3D noise defining terrain. @@ -1990,7 +1990,7 @@ # octaves = 2, # persistence = 0.8, # lacunarity = 2.0, -# flags = +# flags = # } ## Mapgen V6 @@ -2377,7 +2377,7 @@ # octaves = 5, # persistence = 0.63, # lacunarity = 2.0, -# flags = +# flags = # } # 3D noise defining structure of river canyon walls. @@ -2390,7 +2390,7 @@ # octaves = 4, # persistence = 0.75, # lacunarity = 2.0, -# flags = +# flags = # } # 3D noise defining structure of floatlands. @@ -2406,7 +2406,7 @@ # octaves = 4, # persistence = 0.75, # lacunarity = 1.618, -# flags = +# flags = # } # 3D noise defining giant caverns. @@ -2419,7 +2419,7 @@ # octaves = 5, # persistence = 0.63, # lacunarity = 2.0, -# flags = +# flags = # } # First of two 3D noises that together define tunnels. @@ -2432,7 +2432,7 @@ # octaves = 3, # persistence = 0.5, # lacunarity = 2.0, -# flags = +# flags = # } # Second of two 3D noises that together define tunnels. @@ -2445,7 +2445,7 @@ # octaves = 3, # persistence = 0.5, # lacunarity = 2.0, -# flags = +# flags = # } # 3D noise that determines number of dungeons per mapchunk. @@ -2458,7 +2458,7 @@ # octaves = 2, # persistence = 0.8, # lacunarity = 2.0, -# flags = +# flags = # } ## Mapgen Carpathian @@ -2701,7 +2701,7 @@ # octaves = 5, # persistence = 0.55, # lacunarity = 2.0, -# flags = +# flags = # } # First of two 3D noises that together define tunnels. @@ -2714,7 +2714,7 @@ # octaves = 3, # persistence = 0.5, # lacunarity = 2.0, -# flags = +# flags = # } # Second of two 3D noises that together define tunnels. @@ -2727,7 +2727,7 @@ # octaves = 3, # persistence = 0.5, # lacunarity = 2.0, -# flags = +# flags = # } # 3D noise defining giant caverns. @@ -2740,7 +2740,7 @@ # octaves = 5, # persistence = 0.63, # lacunarity = 2.0, -# flags = +# flags = # } # 3D noise that determines number of dungeons per mapchunk. @@ -2753,7 +2753,7 @@ # octaves = 2, # persistence = 0.8, # lacunarity = 2.0, -# flags = +# flags = # } ## Mapgen Flat @@ -2875,7 +2875,7 @@ # octaves = 3, # persistence = 0.5, # lacunarity = 2.0, -# flags = +# flags = # } # Second of two 3D noises that together define tunnels. @@ -2888,7 +2888,7 @@ # octaves = 3, # persistence = 0.5, # lacunarity = 2.0, -# flags = +# flags = # } # 3D noise defining giant caverns. @@ -2901,7 +2901,7 @@ # octaves = 5, # persistence = 0.63, # lacunarity = 2.0, -# flags = +# flags = # } # 3D noise that determines number of dungeons per mapchunk. @@ -2914,7 +2914,7 @@ # octaves = 2, # persistence = 0.8, # lacunarity = 2.0, -# flags = +# flags = # } ## Mapgen Fractal @@ -3088,7 +3088,7 @@ # octaves = 3, # persistence = 0.5, # lacunarity = 2.0, -# flags = +# flags = # } # Second of two 3D noises that together define tunnels. @@ -3101,7 +3101,7 @@ # octaves = 3, # persistence = 0.5, # lacunarity = 2.0, -# flags = +# flags = # } # 3D noise that determines number of dungeons per mapchunk. @@ -3114,7 +3114,7 @@ # octaves = 2, # persistence = 0.8, # lacunarity = 2.0, -# flags = +# flags = # } ## Mapgen Valleys @@ -3204,7 +3204,7 @@ # octaves = 3, # persistence = 0.5, # lacunarity = 2.0, -# flags = +# flags = # } # Second of two 3D noises that together define tunnels. @@ -3217,7 +3217,7 @@ # octaves = 3, # persistence = 0.5, # lacunarity = 2.0, -# flags = +# flags = # } # The depth of dirt or other biome filler node. @@ -3243,7 +3243,7 @@ # octaves = 6, # persistence = 0.63, # lacunarity = 2.0, -# flags = +# flags = # } # Defines large-scale river channel structure. @@ -3295,7 +3295,7 @@ # octaves = 6, # persistence = 0.8, # lacunarity = 2.0, -# flags = +# flags = # } # Amplifies the valleys. @@ -3334,7 +3334,7 @@ # octaves = 2, # persistence = 0.8, # lacunarity = 2.0, -# flags = +# flags = # } ## Advanced diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2de68a8f0..0323603fc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -711,7 +711,7 @@ else() # Move text segment below LuaJIT's 47-bit limit (see issue #9367) if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD") # FreeBSD uses lld, and lld does not support -Ttext-segment, suggesting - # --image-base instead. Not sure if it's equivalent change for the purpose + # --image-base instead. Not sure if it's equivalent change for the purpose # but at least if fixes build on FreeBSD/aarch64 # XXX: the condition should also be changed to check for lld regardless of # os, bit CMake doesn't have anything like CMAKE_LINKER_IS_LLD yet diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt index 8d058852a..656ad45ce 100644 --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -60,7 +60,7 @@ set(client_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/wieldmesh.cpp ${CMAKE_CURRENT_SOURCE_DIR}/shadows/dynamicshadows.cpp ${CMAKE_CURRENT_SOURCE_DIR}/shadows/dynamicshadowsrender.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/shadows/shadowsshadercallbacks.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/shadows/shadowsScreenQuad.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/shadows/shadowsshadercallbacks.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/shadows/shadowsScreenQuad.cpp PARENT_SCOPE ) diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp index 99ff0aefb..10967c0cb 100644 --- a/src/client/clientmap.cpp +++ b/src/client/clientmap.cpp @@ -847,12 +847,12 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver, vertex_count += buf->getIndexCount(); } - // restore the driver material state + // restore the driver material state video::SMaterial clean; clean.BlendOperation = video::EBO_ADD; driver->setMaterial(clean); // reset material to defaults driver->draw3DLine(v3f(), v3f(), video::SColor(0)); - + g_profiler->avg(prefix + "draw meshes [ms]", draw.stop(true)); g_profiler->avg(prefix + "vertices drawn [#]", vertex_count); g_profiler->avg(prefix + "drawcalls [#]", drawcall_count); diff --git a/src/client/imagefilters.cpp b/src/client/imagefilters.cpp index b62e336f7..c9d1504ad 100644 --- a/src/client/imagefilters.cpp +++ b/src/client/imagefilters.cpp @@ -124,7 +124,7 @@ void imageCleanTransparent(video::IImage *src, u32 threshold) // Ignore pixels we haven't processed if (!bitmap.get(sx, sy)) continue; - + // Add RGB values weighted by alpha IF the pixel is opaque, otherwise // use full weight since we want to propagate colors. video::SColor d = src->getPixel(sx, sy); diff --git a/src/client/render/core.cpp b/src/client/render/core.cpp index f151832f3..c67f297c4 100644 --- a/src/client/render/core.cpp +++ b/src/client/render/core.cpp @@ -103,7 +103,7 @@ void RenderingCore::drawHUD() if (show_hud) { if (draw_crosshair) hud->drawCrosshair(); - + hud->drawHotbar(client->getEnv().getLocalPlayer()->getWieldIndex()); hud->drawLuaElements(camera->getOffset()); camera->drawNametags(); diff --git a/src/clientiface.h b/src/clientiface.h index 1be9c972a..947952e82 100644 --- a/src/clientiface.h +++ b/src/clientiface.h @@ -341,7 +341,7 @@ public: u8 getMinor() const { return m_version_minor; } u8 getPatch() const { return m_version_patch; } const std::string &getFullVer() const { return m_full_version; } - + void setLangCode(const std::string &code) { m_lang_code = code; } const std::string &getLangCode() const { return m_lang_code; } diff --git a/src/collision.cpp b/src/collision.cpp index ccc3a058d..be135a225 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -153,7 +153,7 @@ CollisionAxis axisAlignedCollision( (std::max(movingbox.MaxEdge.Z + speed.Z * time, staticbox.MaxEdge.Z) - std::min(movingbox.MinEdge.Z + speed.Z * time, staticbox.MinEdge.Z) - relbox.MinEdge.Z < 0) - ) + ) return COLLISION_AXIS_X; } } else { @@ -180,7 +180,7 @@ CollisionAxis axisAlignedCollision( (std::max(movingbox.MaxEdge.Y + speed.Y * time, staticbox.MaxEdge.Y) - std::min(movingbox.MinEdge.Y + speed.Y * time, staticbox.MinEdge.Y) - relbox.MinEdge.Y < 0) - ) + ) return COLLISION_AXIS_Z; } } diff --git a/src/config.h b/src/config.h index 5e1164642..8d920b150 100644 --- a/src/config.h +++ b/src/config.h @@ -16,7 +16,7 @@ #define PROJECT_NAME_C "Minetest" #define STATIC_SHAREDIR "" #define VERSION_STRING STR(VERSION_MAJOR) "." STR(VERSION_MINOR) "." STR(VERSION_PATCH) STR(VERSION_EXTRA) -#ifdef NDEBUG + #ifdef NDEBUG #define BUILD_TYPE "Release" #else #define BUILD_TYPE "Debug" diff --git a/src/database/database-leveldb.cpp b/src/database/database-leveldb.cpp index 39f4c8442..6e59daab3 100644 --- a/src/database/database-leveldb.cpp +++ b/src/database/database-leveldb.cpp @@ -74,7 +74,7 @@ void Database_LevelDB::loadBlock(const v3s16 &pos, std::string *block) i64tos(getBlockAsInteger(pos)), block); if (!status.ok()) - block->clear(); + block->clear(); } bool Database_LevelDB::deleteBlock(const v3s16 &pos) diff --git a/src/inventorymanager.cpp b/src/inventorymanager.cpp index a159bf786..ecdb56a97 100644 --- a/src/inventorymanager.cpp +++ b/src/inventorymanager.cpp @@ -172,7 +172,7 @@ void IMoveAction::onPutAndOnTake(const ItemStack &src_item, ServerActiveObject * sa->player_inventory_OnPut(*this, src_item, player); else assert(false); - + if (from_inv.type == InventoryLocation::DETACHED) sa->detached_inventory_OnTake(*this, src_item, player); else if (from_inv.type == InventoryLocation::NODEMETA) diff --git a/src/irrlicht_changes/CGUITTFont.cpp b/src/irrlicht_changes/CGUITTFont.cpp index e785ea837..787f4cd5a 100644 --- a/src/irrlicht_changes/CGUITTFont.cpp +++ b/src/irrlicht_changes/CGUITTFont.cpp @@ -478,7 +478,7 @@ CGUITTGlyphPage* CGUITTFont::getLastGlyphPage() const CGUITTGlyphPage* CGUITTFont::createGlyphPage(const u8& pixel_mode) { CGUITTGlyphPage* page = 0; - + // Name of our page. io::path name("TTFontGlyphPage_"); name += tt_face->family_name; diff --git a/src/mapgen/dungeongen.cpp b/src/mapgen/dungeongen.cpp index acdb1a0f0..1d439abeb 100644 --- a/src/mapgen/dungeongen.cpp +++ b/src/mapgen/dungeongen.cpp @@ -71,7 +71,7 @@ DungeonGen::DungeonGen(const NodeDefManager *ndef, dp.num_dungeons = 1; dp.notifytype = GENNOTIFY_DUNGEON; - dp.np_alt_wall = + dp.np_alt_wall = NoiseParams(-0.4, 1.0, v3f(40.0, 40.0, 40.0), 32474, 6, 1.1, 2.0); } } diff --git a/src/mapgen/mapgen_flat.cpp b/src/mapgen/mapgen_flat.cpp index 342455029..6b249ea1f 100644 --- a/src/mapgen/mapgen_flat.cpp +++ b/src/mapgen/mapgen_flat.cpp @@ -177,7 +177,7 @@ void MapgenFlatParams::setDefaultSettings(Settings *settings) int MapgenFlat::getSpawnLevelAtPoint(v2s16 p) { s16 stone_level = ground_level; - float n_terrain = + float n_terrain = ((spflags & MGFLAT_LAKES) || (spflags & MGFLAT_HILLS)) ? NoisePerlin2D(&noise_terrain->np, p.X, p.Y, seed) : 0.0f; diff --git a/src/mapgen/mg_biome.cpp b/src/mapgen/mg_biome.cpp index f08cc190f..8b4c96cd5 100644 --- a/src/mapgen/mg_biome.cpp +++ b/src/mapgen/mg_biome.cpp @@ -273,7 +273,7 @@ Biome *BiomeGenOriginal::calcBiomeFromNoise(float heat, float humidity, v3s16 po pos.Y - biome_closest_blend->max_pos.Y) return biome_closest_blend; - return (biome_closest) ? biome_closest : (Biome *)m_bmgr->getRaw(BIOME_NONE); + return (biome_closest) ? biome_closest : (Biome *)m_bmgr->getRaw(BIOME_NONE); } diff --git a/src/mapgen/mg_ore.cpp b/src/mapgen/mg_ore.cpp index 5814f433a..4f0c35548 100644 --- a/src/mapgen/mg_ore.cpp +++ b/src/mapgen/mg_ore.cpp @@ -498,8 +498,8 @@ void OreVein::generate(MMVManip *vm, int mapseed, u32 blockseed, } // randval ranges from -1..1 - /* - Note: can generate values slightly larger than 1 + /* + Note: can generate values slightly larger than 1 but this can't be changed as mapgen must be deterministic accross versions. */ float randval = (float)pr.next() / float(pr.RANDOM_RANGE / 2) - 1.f; diff --git a/src/network/connection.h b/src/network/connection.h index 1afb4ae84..88e323bb1 100644 --- a/src/network/connection.h +++ b/src/network/connection.h @@ -752,8 +752,8 @@ protected: void putEvent(ConnectionEventPtr e); void TriggerSend(); - - bool ConnectedToServer() + + bool ConnectedToServer() { return getPeerNoEx(PEER_ID_SERVER) != nullptr; } diff --git a/src/script/cpp_api/s_env.cpp b/src/script/cpp_api/s_env.cpp index 874c37b6e..af68f689f 100644 --- a/src/script/cpp_api/s_env.cpp +++ b/src/script/cpp_api/s_env.cpp @@ -140,10 +140,10 @@ void ScriptApiEnv::initializeEnvironment(ServerEnvironment *env) bool simple_catch_up = true; getboolfield(L, current_abm, "catch_up", simple_catch_up); - + s16 min_y = INT16_MIN; getintfield(L, current_abm, "min_y", min_y); - + s16 max_y = INT16_MAX; getintfield(L, current_abm, "max_y", max_y); diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index 54821df24..7640f2782 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -757,7 +757,7 @@ int ModApiEnvMod::l_get_objects_in_area(lua_State *L) { GET_ENV_PTR; ScriptApiBase *script = getScriptApiBase(L); - + v3f minp = read_v3f(L, 1) * BS; v3f maxp = read_v3f(L, 2) * BS; aabb3f box(minp, maxp); @@ -1409,7 +1409,7 @@ int ModApiEnvMod::l_compare_block_status(lua_State *L) v3s16 nodepos = check_v3s16(L, 1); std::string condition_s = luaL_checkstring(L, 2); auto status = env->getBlockStatus(getNodeBlockPos(nodepos)); - + int condition_i = -1; if (!string_to_enum(es_BlockStatusType, condition_i, condition_s)) return 0; // Unsupported diff --git a/src/script/lua_api/l_env.h b/src/script/lua_api/l_env.h index 67c76faae..a7d406d2a 100644 --- a/src/script/lua_api/l_env.h +++ b/src/script/lua_api/l_env.h @@ -114,7 +114,7 @@ private: // get_objects_inside_radius(pos, radius) static int l_get_objects_inside_radius(lua_State *L); - + // get_objects_in_area(pos, minp, maxp) static int l_get_objects_in_area(lua_State *L); diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index f3711652c..34a1e33e5 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -892,7 +892,7 @@ public: for (ActiveABM &aabm : *m_aabms[c]) { if ((p.Y < aabm.min_y) || (p.Y > aabm.max_y)) continue; - + if (myrand() % aabm.chance != 0) continue; diff --git a/src/util/ieee_float.cpp b/src/util/ieee_float.cpp index 887258921..b73763c55 100644 --- a/src/util/ieee_float.cpp +++ b/src/util/ieee_float.cpp @@ -39,7 +39,7 @@ f32 u32Tof32Slow(u32 i) if (exp == 0xFF) { // Inf/NaN if (imant == 0) { - if (std::numeric_limits::has_infinity) + if (std::numeric_limits::has_infinity) return sign ? -std::numeric_limits::infinity() : std::numeric_limits::infinity(); return sign ? std::numeric_limits::max() : diff --git a/src/util/string.h b/src/util/string.h index 8a9e83f22..f4ca1a7de 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -410,7 +410,7 @@ DEFINE_STD_TOSTRING_FLOATINGPOINT(long double) template inline wstring to_wstring(T val) { - return utf8_to_wide(to_string(val)); + return utf8_to_wide(to_string(val)); } } #endif diff --git a/util/generate-texture-normals.sh b/util/generate-texture-normals.sh index 6279dfa69..b2fcbf77b 100755 --- a/util/generate-texture-normals.sh +++ b/util/generate-texture-normals.sh @@ -197,7 +197,7 @@ normalMap() (gimp-convert-rgb image) () ) - (plug-in-normalmap + (plug-in-normalmap RUN-NONINTERACTIVE image drawable -- cgit v1.2.3 From 1f27bf6380f35656db75437bfbaecf26bf95405e Mon Sep 17 00:00:00 2001 From: Lars Müller <34514239+appgurueu@users.noreply.github.com> Date: Sun, 10 Apr 2022 23:20:51 +0200 Subject: Remove unneeded ObjectRef setter return values (#12179) --- doc/lua_api.txt | 1 - src/script/lua_api/l_object.cpp | 47 +++++++++++++++-------------------------- 2 files changed, 17 insertions(+), 31 deletions(-) (limited to 'src/script/lua_api') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 161bdd249..aa58bd48e 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -7023,7 +7023,6 @@ object you are working with still exists. * `light_definition` is a table with the following optional fields: * `shadows` is a table that controls ambient shadows * `intensity` sets the intensity of the shadows from 0 (no shadows, default) to 1 (blackness) - * Returns true on success. * `get_lighting()`: returns the current state of lighting for the player. * Result is a table with the same fields as `light_definition` in `set_lighting`. diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index 6153a0399..39b19364e 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -420,8 +420,7 @@ int ObjectRef::l_set_local_animation(lua_State *L) float frame_speed = readParam(L, 6, 30.0f); getServer(L)->setLocalPlayerAnimations(player, frames, frame_speed); - lua_pushboolean(L, true); - return 1; + return 0; } // get_local_animation(self) @@ -464,8 +463,7 @@ int ObjectRef::l_set_eye_offset(lua_State *L) offset_third.Y = rangelim(offset_third.Y,-10,15); //1.5*BS getServer(L)->setPlayerEyeOffset(player, offset_first, offset_third); - lua_pushboolean(L, true); - return 1; + return 0; } // get_eye_offset(self) @@ -737,8 +735,7 @@ int ObjectRef::l_set_nametag_attributes(lua_State *L) prop->validate(); sao->notifyObjectPropertiesModified(); - lua_pushboolean(L, true); - return 1; + return 0; } // get_nametag_attributes(self) @@ -1116,7 +1113,7 @@ int ObjectRef::l_set_look_vertical(lua_State *L) float pitch = readParam(L, 2) * core::RADTODEG; playersao->setLookPitchAndSend(pitch); - return 1; + return 0; } // set_look_horizontal(self, radians) @@ -1131,7 +1128,7 @@ int ObjectRef::l_set_look_horizontal(lua_State *L) float yaw = readParam(L, 2) * core::RADTODEG; playersao->setPlayerYawAndSend(yaw); - return 1; + return 0; } // DEPRECATED @@ -1151,7 +1148,7 @@ int ObjectRef::l_set_look_pitch(lua_State *L) float pitch = readParam(L, 2) * core::RADTODEG; playersao->setLookPitchAndSend(pitch); - return 1; + return 0; } // DEPRECATED @@ -1171,7 +1168,7 @@ int ObjectRef::l_set_look_yaw(lua_State *L) float yaw = readParam(L, 2) * core::RADTODEG; playersao->setPlayerYawAndSend(yaw); - return 1; + return 0; } // set_fov(self, degrees, is_multiplier, transition_time) @@ -1310,8 +1307,7 @@ int ObjectRef::l_set_inventory_formspec(lua_State *L) player->inventory_formspec = formspec; getServer(L)->reportInventoryFormspecModified(player->getName()); - lua_pushboolean(L, true); - return 1; + return 0; } // get_inventory_formspec(self) -> formspec @@ -1342,8 +1338,7 @@ int ObjectRef::l_set_formspec_prepend(lua_State *L) player->formspec_prepend = formspec; getServer(L)->reportFormspecPrependModified(player->getName()); - lua_pushboolean(L, true); - return 1; + return 0; } // get_formspec_prepend(self) @@ -1603,8 +1598,7 @@ int ObjectRef::l_hud_set_flags(lua_State *L) if (!getServer(L)->hudSetFlags(player, flags, mask)) return 0; - lua_pushboolean(L, true); - return 1; + return 0; } // hud_get_flags(self) @@ -1861,8 +1855,7 @@ int ObjectRef::l_set_sky(lua_State *L) } getServer(L)->setSky(player, sky_params); - lua_pushboolean(L, true); - return 1; + return 0; } static void push_sky_color(lua_State *L, const SkyboxParams ¶ms) @@ -1984,8 +1977,7 @@ int ObjectRef::l_set_sun(lua_State *L) } getServer(L)->setSun(player, sun_params); - lua_pushboolean(L, true); - return 1; + return 0; } //get_sun(self) @@ -2038,8 +2030,7 @@ int ObjectRef::l_set_moon(lua_State *L) } getServer(L)->setMoon(player, moon_params); - lua_pushboolean(L, true); - return 1; + return 0; } // get_moon(self) @@ -2094,8 +2085,7 @@ int ObjectRef::l_set_stars(lua_State *L) } getServer(L)->setStars(player, star_params); - lua_pushboolean(L, true); - return 1; + return 0; } // get_stars(self) @@ -2162,8 +2152,7 @@ int ObjectRef::l_set_clouds(lua_State *L) } getServer(L)->setClouds(player, cloud_params); - lua_pushboolean(L, true); - return 1; + return 0; } int ObjectRef::l_get_clouds(lua_State *L) @@ -2217,8 +2206,7 @@ int ObjectRef::l_override_day_night_ratio(lua_State *L) } getServer(L)->overrideDayNightRatio(player, do_override, ratio); - lua_pushboolean(L, true); - return 1; + return 0; } // get_day_night_ratio(self) @@ -2313,8 +2301,7 @@ int ObjectRef::l_set_lighting(lua_State *L) lua_pop(L, -1); getServer(L)->setLighting(player, lighting); - lua_pushboolean(L, true); - return 1; + return 0; } // get_lighting(self) -- cgit v1.2.3 From a65f6f07f3a5601207b790edcc8cc945133112f7 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 27 Apr 2022 19:55:13 +0200 Subject: Clean up some auth packet handling related code --- src/clientiface.cpp | 16 +++------- src/network/clientpackethandler.cpp | 11 ++++--- src/network/networkprotocol.h | 18 +++++------ src/network/serveropcodes.cpp | 2 +- src/network/serverpackethandler.cpp | 24 +++++++-------- src/script/lua_api/l_server.cpp | 9 ++++-- src/server.cpp | 59 +++++++++---------------------------- src/server.h | 5 +--- src/serverenvironment.cpp | 6 ++-- 9 files changed, 52 insertions(+), 98 deletions(-) (limited to 'src/script/lua_api') diff --git a/src/clientiface.cpp b/src/clientiface.cpp index a4bfb8242..5733b05de 100644 --- a/src/clientiface.cpp +++ b/src/clientiface.cpp @@ -472,20 +472,14 @@ void RemoteClient::notifyEvent(ClientStateEvent event) { case CSE_AuthAccept: m_state = CS_AwaitingInit2; - if (chosen_mech == AUTH_MECHANISM_SRP || - chosen_mech == AUTH_MECHANISM_LEGACY_PASSWORD) - srp_verifier_delete((SRPVerifier *) auth_data); - chosen_mech = AUTH_MECHANISM_NONE; + resetChosenMech(); break; case CSE_Disconnect: m_state = CS_Disconnecting; break; case CSE_SetDenied: m_state = CS_Denied; - if (chosen_mech == AUTH_MECHANISM_SRP || - chosen_mech == AUTH_MECHANISM_LEGACY_PASSWORD) - srp_verifier_delete((SRPVerifier *) auth_data); - chosen_mech = AUTH_MECHANISM_NONE; + resetChosenMech(); break; default: myerror << "HelloSent: Invalid client state transition! " << event; @@ -561,9 +555,7 @@ void RemoteClient::notifyEvent(ClientStateEvent event) break; case CSE_SudoSuccess: m_state = CS_SudoMode; - if (chosen_mech == AUTH_MECHANISM_SRP) - srp_verifier_delete((SRPVerifier *) auth_data); - chosen_mech = AUTH_MECHANISM_NONE; + resetChosenMech(); break; /* Init GotInit2 SetDefinitionsSent SetMediaSent SetDenied */ default: @@ -598,7 +590,7 @@ void RemoteClient::notifyEvent(ClientStateEvent event) void RemoteClient::resetChosenMech() { - if (chosen_mech == AUTH_MECHANISM_SRP) { + if (auth_data) { srp_verifier_delete((SRPVerifier *) auth_data); auth_data = nullptr; } diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 15b576640..55d20d673 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -183,7 +183,7 @@ void Client::handleCommand_AccessDenied(NetworkPacket* pkt) m_access_denied_reason = "Unknown"; if (pkt->getCommand() != TOCLIENT_ACCESS_DENIED) { - // 13/03/15 Legacy code from 0.4.12 and lesser but is still used + // Legacy code from 0.4.12 and older but is still used // in some places of the server code if (pkt->getSize() >= 2) { std::wstring wide_reason; @@ -196,14 +196,14 @@ void Client::handleCommand_AccessDenied(NetworkPacket* pkt) if (pkt->getSize() < 1) return; - u8 denyCode = SERVER_ACCESSDENIED_UNEXPECTED_DATA; + u8 denyCode; *pkt >> denyCode; + if (denyCode == SERVER_ACCESSDENIED_SHUTDOWN || denyCode == SERVER_ACCESSDENIED_CRASH) { *pkt >> m_access_denied_reason; - if (m_access_denied_reason.empty()) { + if (m_access_denied_reason.empty()) m_access_denied_reason = accessDeniedStrings[denyCode]; - } u8 reconnect; *pkt >> reconnect; m_access_denied_reconnect = reconnect & 1; @@ -220,9 +220,8 @@ void Client::handleCommand_AccessDenied(NetworkPacket* pkt) // Until then (which may be never), this is outside // of the defined protocol. *pkt >> m_access_denied_reason; - if (m_access_denied_reason.empty()) { + if (m_access_denied_reason.empty()) m_access_denied_reason = "Unknown"; - } } } diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index f98a829ba..3923cb858 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -1006,7 +1006,7 @@ enum AuthMechanism AUTH_MECHANISM_FIRST_SRP = 1 << 2, }; -enum AccessDeniedCode { +enum AccessDeniedCode : u8 { SERVER_ACCESSDENIED_WRONG_PASSWORD, SERVER_ACCESSDENIED_UNEXPECTED_DATA, SERVER_ACCESSDENIED_SINGLEPLAYER, @@ -1029,18 +1029,18 @@ enum NetProtoCompressionMode { const static std::string accessDeniedStrings[SERVER_ACCESSDENIED_MAX] = { "Invalid password", - "Your client sent something the server didn't expect. Try reconnecting or updating your client", + "Your client sent something the server didn't expect. Try reconnecting or updating your client.", "The server is running in simple singleplayer mode. You cannot connect.", - "Your client's version is not supported.\nPlease contact server administrator.", - "Player name contains disallowed characters.", - "Player name not allowed.", - "Too many users.", + "Your client's version is not supported.\nPlease contact the server administrator.", + "Player name contains disallowed characters", + "Player name not allowed", + "Too many users", "Empty passwords are disallowed. Set a password and try again.", "Another client is connected with this name. If your client closed unexpectedly, try again in a minute.", - "Server authentication failed. This is likely a server error.", + "Internal server error", "", - "Server shutting down.", - "This server has experienced an internal error. You will now be disconnected." + "Server shutting down", + "The server has experienced an internal error. You will now be disconnected." }; enum PlayerListModifer : u8 diff --git a/src/network/serveropcodes.cpp b/src/network/serveropcodes.cpp index 44b65e8da..12665e7f1 100644 --- a/src/network/serveropcodes.cpp +++ b/src/network/serveropcodes.cpp @@ -176,7 +176,7 @@ const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] = { "TOCLIENT_ACTIVE_OBJECT_MESSAGES", 0, true }, // 0x32 (may be sent as unrel over channel 1 too) { "TOCLIENT_HP", 0, true }, // 0x33 { "TOCLIENT_MOVE_PLAYER", 0, true }, // 0x34 - { "TOCLIENT_ACCESS_DENIED_LEGACY", 0, true }, // 0x35 + null_command_factory, // 0x35 { "TOCLIENT_FOV", 0, true }, // 0x36 { "TOCLIENT_DEATHSCREEN", 0, true }, // 0x37 { "TOCLIENT_MEDIA", 2, true }, // 0x38 diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index 125e85cab..4b9de488c 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -227,7 +227,7 @@ void Server::handleCommand_Init(NetworkPacket* pkt) Compose auth methods for answer */ std::string encpwd; // encrypted Password field for the user - bool has_auth = m_script->getAuth(playername, &encpwd, NULL); + bool has_auth = m_script->getAuth(playername, &encpwd, nullptr); u32 auth_mechs = 0; client->chosen_mech = AUTH_MECHANISM_NONE; @@ -1461,11 +1461,9 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt) session_t peer_id = pkt->getPeerId(); RemoteClient *client = getClient(peer_id, CS_Invalid); ClientState cstate = client->getState(); + const std::string playername = client->getName(); - std::string playername = client->getName(); - - std::string salt; - std::string verification_key; + std::string salt, verification_key; std::string addr_s = getPeerAddress(peer_id).serializeString(); u8 is_empty; @@ -1551,8 +1549,6 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt) RemoteClient *client = getClient(peer_id, CS_Invalid); ClientState cstate = client->getState(); - bool wantSudo = (cstate == CS_Active); - if (!((cstate == CS_HelloSent) || (cstate == CS_Active))) { actionstream << "Server: got SRP _A packet in wrong state " << cstate << " from " << getPeerAddress(peer_id).serializeString() << @@ -1560,6 +1556,8 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt) return; } + const bool wantSudo = (cstate == CS_Active); + if (client->chosen_mech != AUTH_MECHANISM_NONE) { actionstream << "Server: got SRP _A packet, while auth is already " "going on with mech " << client->chosen_mech << " from " << @@ -1606,8 +1604,7 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt) client->chosen_mech = chosen; - std::string salt; - std::string verifier; + std::string salt, verifier; if (based_on == 0) { @@ -1657,10 +1654,10 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt) session_t peer_id = pkt->getPeerId(); RemoteClient *client = getClient(peer_id, CS_Invalid); ClientState cstate = client->getState(); - std::string addr_s = getPeerAddress(pkt->getPeerId()).serializeString(); - std::string playername = client->getName(); + const std::string addr_s = client->getAddress().serializeString(); + const std::string playername = client->getName(); - bool wantSudo = (cstate == CS_Active); + const bool wantSudo = (cstate == CS_Active); verbosestream << "Server: Received TOSERVER_SRP_BYTES_M." << std::endl; @@ -1720,8 +1717,7 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt) if (client->create_player_on_auth_success) { m_script->createAuth(playername, client->enc_pwd); - std::string checkpwd; // not used, but needed for passing something - if (!m_script->getAuth(playername, &checkpwd, NULL)) { + if (!m_script->getAuth(playername, nullptr, nullptr)) { errorstream << "Server: " << playername << " cannot be authenticated (auth handler does not work?)" << std::endl; diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index 88ab5e16b..5b3054d17 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -325,12 +325,15 @@ int ModApiServer::l_disconnect_player(lua_State *L) else message.append("Disconnected."); - RemotePlayer *player = dynamic_cast(getEnv(L))->getPlayer(name); - if (player == NULL) { + Server *server = getServer(L); + + RemotePlayer *player = server->getEnv().getPlayer(name); + if (!player) { lua_pushboolean(L, false); // No such player return 1; } - getServer(L)->DenyAccess_Legacy(player->getPeerId(), utf8_to_wide(message)); + + server->DenyAccess(player->getPeerId(), SERVER_ACCESSDENIED_CUSTOM_STRING, message); lua_pushboolean(L, true); return 1; } diff --git a/src/server.cpp b/src/server.cpp index 8ca8a9bda..9d7e8e563 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1038,8 +1038,7 @@ void Server::Receive() } catch (const ClientStateError &e) { errorstream << "ProcessData: peer=" << peer_id << " what()=" << e.what() << std::endl; - DenyAccess_Legacy(peer_id, L"Your client sent something server didn't expect." - L"Try reconnecting or updating your client"); + DenyAccess(peer_id, SERVER_ACCESSDENIED_UNEXPECTED_DATA); } catch (const con::PeerNotFoundException &e) { // Do nothing } catch (const con::NoIncomingDataException &e) { @@ -1068,15 +1067,13 @@ PlayerSAO* Server::StageTwoClientInit(session_t peer_id) if (player && player->getPeerId() != PEER_ID_INEXISTENT) { actionstream << "Server: Failed to emerge player \"" << playername << "\" (player allocated to an another client)" << std::endl; - DenyAccess_Legacy(peer_id, L"Another client is connected with this " - L"name. If your client closed unexpectedly, try again in " - L"a minute."); + DenyAccess(peer_id, SERVER_ACCESSDENIED_ALREADY_CONNECTED); } else { errorstream << "Server: " << playername << ": Failed to emerge player" << std::endl; - DenyAccess_Legacy(peer_id, L"Could not allocate player."); + DenyAccess(peer_id, SERVER_ACCESSDENIED_SERVER_FAIL); } - return NULL; + return nullptr; } /* @@ -1141,18 +1138,16 @@ void Server::ProcessData(NetworkPacket *pkt) Address address = getPeerAddress(peer_id); std::string addr_s = address.serializeString(); - if(m_banmanager->isIpBanned(addr_s)) { + // FIXME: Isn't it a bit excessive to check this for every packet? + if (m_banmanager->isIpBanned(addr_s)) { std::string ban_name = m_banmanager->getBanName(addr_s); infostream << "Server: A banned client tried to connect from " - << addr_s << "; banned name was " - << ban_name << std::endl; - // This actually doesn't seem to transfer to the client - DenyAccess_Legacy(peer_id, L"Your ip is banned. Banned name was " - + utf8_to_wide(ban_name)); + << addr_s << "; banned name was " << ban_name << std::endl; + DenyAccess(peer_id, SERVER_ACCESSDENIED_CUSTOM_STRING, + "Your IP is banned. Banned name was " + ban_name); return; } - } - catch(con::PeerNotFoundException &e) { + } catch (con::PeerNotFoundException &e) { /* * no peer for this packet found * most common reason is peer timeout, e.g. peer didn't @@ -1406,13 +1401,6 @@ void Server::SendAccessDenied(session_t peer_id, AccessDeniedCode reason, Send(&pkt); } -void Server::SendAccessDenied_Legacy(session_t peer_id,const std::wstring &reason) -{ - NetworkPacket pkt(TOCLIENT_ACCESS_DENIED_LEGACY, 0, peer_id); - pkt << reason; - Send(&pkt); -} - void Server::SendDeathscreen(session_t peer_id, bool set_camera_point_target, v3f camera_point_target) { @@ -2777,29 +2765,10 @@ void Server::DenySudoAccess(session_t peer_id) } -void Server::DenyAccessVerCompliant(session_t peer_id, u16 proto_ver, AccessDeniedCode reason, - const std::string &str_reason, bool reconnect) -{ - SendAccessDenied(peer_id, reason, str_reason, reconnect); - - m_clients.event(peer_id, CSE_SetDenied); - DisconnectPeer(peer_id); -} - - void Server::DenyAccess(session_t peer_id, AccessDeniedCode reason, - const std::string &custom_reason) -{ - SendAccessDenied(peer_id, reason, custom_reason); - m_clients.event(peer_id, CSE_SetDenied); - DisconnectPeer(peer_id); -} - -// 13/03/15: remove this function when protocol version 25 will become -// the minimum version for MT users, maybe in 1 year -void Server::DenyAccess_Legacy(session_t peer_id, const std::wstring &reason) + const std::string &custom_reason, bool reconnect) { - SendAccessDenied_Legacy(peer_id, reason); + SendAccessDenied(peer_id, reason, custom_reason, reconnect); m_clients.event(peer_id, CSE_SetDenied); DisconnectPeer(peer_id); } @@ -2985,8 +2954,8 @@ std::wstring Server::handleChat(const std::string &name, return ws.str(); } case RPLAYER_CHATRESULT_KICK: - DenyAccess_Legacy(player->getPeerId(), - L"You have been kicked due to message flooding."); + DenyAccess(player->getPeerId(), SERVER_ACCESSDENIED_CUSTOM_STRING, + "You have been kicked due to message flooding."); return L""; case RPLAYER_CHATRESULT_OK: break; diff --git a/src/server.h b/src/server.h index c05393291..008213c5d 100644 --- a/src/server.h +++ b/src/server.h @@ -341,12 +341,9 @@ public: void deletingPeer(con::Peer *peer, bool timeout); void DenySudoAccess(session_t peer_id); - void DenyAccessVerCompliant(session_t peer_id, u16 proto_ver, AccessDeniedCode reason, - const std::string &str_reason = "", bool reconnect = false); void DenyAccess(session_t peer_id, AccessDeniedCode reason, - const std::string &custom_reason = ""); + const std::string &custom_reason = "", bool reconnect = false); void acceptAuth(session_t peer_id, bool forSudoMode); - void DenyAccess_Legacy(session_t peer_id, const std::wstring &reason); void DisconnectPeer(session_t peer_id); bool getClientConInfo(session_t peer_id, con::rtt_stat_type type, float *retval); bool getClientInfo(session_t peer_id, ClientInfo &ret); diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index 34a1e33e5..f8d84604b 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -552,10 +552,8 @@ bool ServerEnvironment::removePlayerFromDatabase(const std::string &name) void ServerEnvironment::kickAllPlayers(AccessDeniedCode reason, const std::string &str_reason, bool reconnect) { - for (RemotePlayer *player : m_players) { - m_server->DenyAccessVerCompliant(player->getPeerId(), - player->protocol_version, reason, str_reason, reconnect); - } + for (RemotePlayer *player : m_players) + m_server->DenyAccess(player->getPeerId(), reason, str_reason, reconnect); } void ServerEnvironment::saveLoadedPlayers(bool force) -- cgit v1.2.3 From 5362f472ff75ffe1885b54d09f22faa85284f817 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Fri, 8 Apr 2022 15:52:22 +0200 Subject: Remove some unused variable from Lua class wrappers --- src/script/lua_api/l_noise.cpp | 14 ++++++-------- src/script/lua_api/l_noise.h | 11 ++++++----- src/script/lua_api/l_vmanip.cpp | 16 ++++++++-------- src/script/lua_api/l_vmanip.h | 2 -- 4 files changed, 20 insertions(+), 23 deletions(-) (limited to 'src/script/lua_api') diff --git a/src/script/lua_api/l_noise.cpp b/src/script/lua_api/l_noise.cpp index f43ba837a..0eee49b7d 100644 --- a/src/script/lua_api/l_noise.cpp +++ b/src/script/lua_api/l_noise.cpp @@ -30,7 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc., LuaPerlinNoise */ -LuaPerlinNoise::LuaPerlinNoise(NoiseParams *params) : +LuaPerlinNoise::LuaPerlinNoise(const NoiseParams *params) : np(*params) { } @@ -141,12 +141,10 @@ luaL_Reg LuaPerlinNoise::methods[] = { LuaPerlinNoiseMap */ -LuaPerlinNoiseMap::LuaPerlinNoiseMap(NoiseParams *params, s32 seed, v3s16 size) +LuaPerlinNoiseMap::LuaPerlinNoiseMap(const NoiseParams *np, s32 seed, v3s16 size) { - m_is3d = size.Z > 1; - np = *params; try { - noise = new Noise(&np, seed, size.X, size.Y, size.Z); + noise = new Noise(np, seed, size.X, size.Y, size.Z); } catch (InvalidNoiseParamsException &e) { throw LuaError(e.what()); } @@ -217,7 +215,7 @@ int LuaPerlinNoiseMap::l_get_3d_map(lua_State *L) LuaPerlinNoiseMap *o = checkobject(L, 1); v3f p = check_v3f(L, 2); - if (!o->m_is3d) + if (!o->is3D()) return 0; Noise *n = o->noise; @@ -248,7 +246,7 @@ int LuaPerlinNoiseMap::l_get_3d_map_flat(lua_State *L) v3f p = check_v3f(L, 2); bool use_buffer = lua_istable(L, 3); - if (!o->m_is3d) + if (!o->is3D()) return 0; Noise *n = o->noise; @@ -289,7 +287,7 @@ int LuaPerlinNoiseMap::l_calc_3d_map(lua_State *L) LuaPerlinNoiseMap *o = checkobject(L, 1); v3f p = check_v3f(L, 2); - if (!o->m_is3d) + if (!o->is3D()) return 0; Noise *n = o->noise; diff --git a/src/script/lua_api/l_noise.h b/src/script/lua_api/l_noise.h index 9f50dfd3f..29ab41a31 100644 --- a/src/script/lua_api/l_noise.h +++ b/src/script/lua_api/l_noise.h @@ -30,6 +30,7 @@ class LuaPerlinNoise : public ModApiBase { private: NoiseParams np; + static const char className[]; static luaL_Reg methods[]; @@ -42,7 +43,7 @@ private: static int l_get_3d(lua_State *L); public: - LuaPerlinNoise(NoiseParams *params); + LuaPerlinNoise(const NoiseParams *params); ~LuaPerlinNoise() = default; // LuaPerlinNoise(seed, octaves, persistence, scale) @@ -59,9 +60,8 @@ public: */ class LuaPerlinNoiseMap : public ModApiBase { - NoiseParams np; Noise *noise; - bool m_is3d; + static const char className[]; static luaL_Reg methods[]; @@ -80,10 +80,11 @@ class LuaPerlinNoiseMap : public ModApiBase static int l_get_map_slice(lua_State *L); public: - LuaPerlinNoiseMap(NoiseParams *np, s32 seed, v3s16 size); - + LuaPerlinNoiseMap(const NoiseParams *np, s32 seed, v3s16 size); ~LuaPerlinNoiseMap(); + inline bool is3D() const { return noise->sz > 1; } + // LuaPerlinNoiseMap(np, size) // Creates an LuaPerlinNoiseMap and leaves it on top of stack static int create_object(lua_State *L); diff --git a/src/script/lua_api/l_vmanip.cpp b/src/script/lua_api/l_vmanip.cpp index 1fa080210..a3ece627c 100644 --- a/src/script/lua_api/l_vmanip.cpp +++ b/src/script/lua_api/l_vmanip.cpp @@ -17,7 +17,7 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ - +#include #include "lua_api/l_vmanip.h" #include "lua_api/l_internal.h" #include "common/c_content.h" @@ -112,23 +112,23 @@ int LuaVoxelManip::l_write_to_map(lua_State *L) LuaVoxelManip *o = checkobject(L, 1); bool update_light = !lua_isboolean(L, 2) || readParam(L, 2); + GET_ENV_PTR; ServerMap *map = &(env->getServerMap()); + + std::map modified_blocks; if (o->is_mapgen_vm || !update_light) { - o->vm->blitBackAll(&(o->modified_blocks)); + o->vm->blitBackAll(&modified_blocks); } else { - voxalgo::blit_back_with_light(map, o->vm, - &(o->modified_blocks)); + voxalgo::blit_back_with_light(map, o->vm, &modified_blocks); } MapEditEvent event; event.type = MEET_OTHER; - for (const auto &modified_block : o->modified_blocks) - event.modified_blocks.insert(modified_block.first); - + for (const auto &it : modified_blocks) + event.modified_blocks.insert(it.first); map->dispatchEvent(event); - o->modified_blocks.clear(); return 0; } diff --git a/src/script/lua_api/l_vmanip.h b/src/script/lua_api/l_vmanip.h index 15ab9eef8..5113070dc 100644 --- a/src/script/lua_api/l_vmanip.h +++ b/src/script/lua_api/l_vmanip.h @@ -19,7 +19,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once -#include #include "irr_v3d.h" #include "lua_api/l_base.h" @@ -33,7 +32,6 @@ class MMVManip; class LuaVoxelManip : public ModApiBase { private: - std::map modified_blocks; bool is_mapgen_vm = false; static const char className[]; -- cgit v1.2.3 From 56a558baf8ef43810014347ae624cb4ef70b6aa3 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sat, 9 Apr 2022 14:47:59 +0200 Subject: Refactor some Lua API functions in preparation for async env --- src/gamedef.h | 2 ++ src/script/common/c_content.cpp | 8 ++++---- src/script/common/c_content.h | 6 +++--- src/script/lua_api/l_craft.cpp | 24 +++++++++++------------- src/script/lua_api/l_item.cpp | 6 +++--- src/script/lua_api/l_server.cpp | 28 +++++++++++++--------------- src/server.cpp | 5 ----- src/server.h | 3 +-- 8 files changed, 37 insertions(+), 45 deletions(-) (limited to 'src/script/lua_api') diff --git a/src/gamedef.h b/src/gamedef.h index 8a9246da2..45b9c4750 100644 --- a/src/gamedef.h +++ b/src/gamedef.h @@ -63,6 +63,8 @@ public: virtual IRollbackManager* getRollbackManager() { return NULL; } // Shorthands + // TODO: these should be made const-safe so that a const IGameDef* is + // actually usable IItemDefManager *idef() { return getItemDefManager(); } const NodeDefManager *ndef() { return getNodeDefManager(); } ICraftDefManager *cdef() { return getCraftDefManager(); } diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 36f4316ee..a233afb05 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -1371,7 +1371,7 @@ void push_inventory_lists(lua_State *L, const Inventory &inv) /******************************************************************************/ void read_inventory_list(lua_State *L, int tableindex, - Inventory *inv, const char *name, Server* srv, int forcesize) + Inventory *inv, const char *name, IGameDef *gdef, int forcesize) { if(tableindex < 0) tableindex = lua_gettop(L) + 1 + tableindex; @@ -1383,7 +1383,7 @@ void read_inventory_list(lua_State *L, int tableindex, } // Get Lua-specified items to insert into the list - std::vector items = read_items(L, tableindex,srv); + std::vector items = read_items(L, tableindex, gdef); size_t listsize = (forcesize >= 0) ? forcesize : items.size(); // Create or resize/clear list @@ -1635,7 +1635,7 @@ void push_items(lua_State *L, const std::vector &items) } /******************************************************************************/ -std::vector read_items(lua_State *L, int index, Server *srv) +std::vector read_items(lua_State *L, int index, IGameDef *gdef) { if(index < 0) index = lua_gettop(L) + 1 + index; @@ -1651,7 +1651,7 @@ std::vector read_items(lua_State *L, int index, Server *srv) if (items.size() < (u32) key) { items.resize(key); } - items[key - 1] = read_item(L, -1, srv->idef()); + items[key - 1] = read_item(L, -1, gdef->idef()); lua_pop(L, 1); } return items; diff --git a/src/script/common/c_content.h b/src/script/common/c_content.h index 11b39364f..a7b8709c6 100644 --- a/src/script/common/c_content.h +++ b/src/script/common/c_content.h @@ -59,7 +59,7 @@ class InventoryList; struct NodeBox; struct ContentFeatures; struct TileDef; -class Server; +class IGameDef; struct DigParams; struct HitParams; struct EnumString; @@ -126,7 +126,7 @@ void push_inventory_lists (lua_State *L, const Inventory &inv); void read_inventory_list (lua_State *L, int tableindex, Inventory *inv, const char *name, - Server *srv, int forcesize=-1); + IGameDef *gdef, int forcesize=-1); MapNode readnode (lua_State *L, int index, const NodeDefManager *ndef); @@ -166,7 +166,7 @@ void push_items (lua_State *L, std::vector read_items (lua_State *L, int index, - Server* srv); + IGameDef* gdef); void push_soundspec (lua_State *L, const SimpleSoundSpec &spec); diff --git a/src/script/lua_api/l_craft.cpp b/src/script/lua_api/l_craft.cpp index 18622ee00..c2c5a5551 100644 --- a/src/script/lua_api/l_craft.cpp +++ b/src/script/lua_api/l_craft.cpp @@ -371,8 +371,9 @@ int ModApiCraft::l_clear_craft(lua_State *L) int ModApiCraft::l_get_craft_result(lua_State *L) { NO_MAP_LOCK_REQUIRED; + IGameDef *gdef = getGameDef(L); - int input_i = 1; + const int input_i = 1; std::string method_s = getstringfield_default(L, input_i, "method", "normal"); enum CraftMethod method = (CraftMethod)getenumfield(L, input_i, "method", es_CraftMethod, CRAFT_METHOD_NORMAL); @@ -382,10 +383,9 @@ int ModApiCraft::l_get_craft_result(lua_State *L) width = luaL_checkinteger(L, -1); lua_pop(L, 1); lua_getfield(L, input_i, "items"); - std::vector items = read_items(L, -1,getServer(L)); + std::vector items = read_items(L, -1, gdef); lua_pop(L, 1); // items - IGameDef *gdef = getServer(L); ICraftDefManager *cdef = gdef->cdef(); CraftInput input(method, width, items); CraftOutput output; @@ -465,13 +465,13 @@ static void push_craft_recipes(lua_State *L, IGameDef *gdef, const std::vector &recipes, const CraftOutput &output) { - lua_createtable(L, recipes.size(), 0); - if (recipes.empty()) { lua_pushnil(L); return; } + lua_createtable(L, recipes.size(), 0); + std::vector::const_iterator it = recipes.begin(); for (unsigned i = 0; it != recipes.end(); ++it) { lua_newtable(L); @@ -487,10 +487,9 @@ int ModApiCraft::l_get_craft_recipe(lua_State *L) NO_MAP_LOCK_REQUIRED; std::string item = luaL_checkstring(L, 1); - Server *server = getServer(L); + IGameDef *gdef = getGameDef(L); CraftOutput output(item, 0); - std::vector recipes = server->cdef() - ->getCraftRecipes(output, server, 1); + auto recipes = gdef->cdef()->getCraftRecipes(output, gdef, 1); lua_createtable(L, 1, 0); @@ -500,7 +499,7 @@ int ModApiCraft::l_get_craft_recipe(lua_State *L) setintfield(L, -1, "width", 0); return 1; } - push_craft_recipe(L, server, recipes[0], output); + push_craft_recipe(L, gdef, recipes[0], output); return 1; } @@ -510,12 +509,11 @@ int ModApiCraft::l_get_all_craft_recipes(lua_State *L) NO_MAP_LOCK_REQUIRED; std::string item = luaL_checkstring(L, 1); - Server *server = getServer(L); + IGameDef *gdef = getGameDef(L); CraftOutput output(item, 0); - std::vector recipes = server->cdef() - ->getCraftRecipes(output, server); + auto recipes = gdef->cdef()->getCraftRecipes(output, gdef); - push_craft_recipes(L, server, recipes, output); + push_craft_recipes(L, gdef, recipes, output); return 1; } diff --git a/src/script/lua_api/l_item.cpp b/src/script/lua_api/l_item.cpp index 794d8a6e5..fc97a1736 100644 --- a/src/script/lua_api/l_item.cpp +++ b/src/script/lua_api/l_item.cpp @@ -632,8 +632,8 @@ int ModApiItemMod::l_get_content_id(lua_State *L) NO_MAP_LOCK_REQUIRED; std::string name = luaL_checkstring(L, 1); - const IItemDefManager *idef = getGameDef(L)->getItemDefManager(); - const NodeDefManager *ndef = getGameDef(L)->getNodeDefManager(); + const IItemDefManager *idef = getGameDef(L)->idef(); + const NodeDefManager *ndef = getGameDef(L)->ndef(); // If this is called at mod load time, NodeDefManager isn't aware of // aliases yet, so we need to handle them manually @@ -658,7 +658,7 @@ int ModApiItemMod::l_get_name_from_content_id(lua_State *L) NO_MAP_LOCK_REQUIRED; content_t c = luaL_checkint(L, 1); - const NodeDefManager *ndef = getGameDef(L)->getNodeDefManager(); + const NodeDefManager *ndef = getGameDef(L)->ndef(); const char *name = ndef->get(c).name.c_str(); lua_pushstring(L, name); diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index 5b3054d17..42725e5d2 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -61,11 +61,8 @@ int ModApiServer::l_get_server_uptime(lua_State *L) int ModApiServer::l_get_server_max_lag(lua_State *L) { NO_MAP_LOCK_REQUIRED; - ServerEnvironment *s_env = dynamic_cast(getEnv(L)); - if (!s_env) - lua_pushnil(L); - else - lua_pushnumber(L, s_env->getMaxLagEstimate()); + GET_ENV_PTR; + lua_pushnumber(L, env->getMaxLagEstimate()); return 1; } @@ -395,12 +392,11 @@ int ModApiServer::l_get_modpath(lua_State *L) { NO_MAP_LOCK_REQUIRED; std::string modname = luaL_checkstring(L, 1); - const ModSpec *mod = getServer(L)->getModSpec(modname); - if (!mod) { + const ModSpec *mod = getGameDef(L)->getModSpec(modname); + if (!mod) lua_pushnil(L); - return 1; - } - lua_pushstring(L, mod->path.c_str()); + else + lua_pushstring(L, mod->path.c_str()); return 1; } @@ -412,13 +408,14 @@ int ModApiServer::l_get_modnames(lua_State *L) // Get a list of mods std::vector modlist; - getServer(L)->getModNames(modlist); + for (auto &it : getGameDef(L)->getMods()) + modlist.emplace_back(it.name); std::sort(modlist.begin(), modlist.end()); // Package them up for Lua lua_createtable(L, modlist.size(), 0); - std::vector::iterator iter = modlist.begin(); + auto iter = modlist.begin(); for (u16 i = 0; iter != modlist.end(); ++iter) { lua_pushstring(L, iter->c_str()); lua_rawseti(L, -2, ++i); @@ -430,8 +427,8 @@ int ModApiServer::l_get_modnames(lua_State *L) int ModApiServer::l_get_worldpath(lua_State *L) { NO_MAP_LOCK_REQUIRED; - std::string worldpath = getServer(L)->getWorldPath(); - lua_pushstring(L, worldpath.c_str()); + const Server *srv = getServer(L); + lua_pushstring(L, srv->getWorldPath().c_str()); return 1; } @@ -513,7 +510,8 @@ int ModApiServer::l_dynamic_add_media(lua_State *L) int ModApiServer::l_is_singleplayer(lua_State *L) { NO_MAP_LOCK_REQUIRED; - lua_pushboolean(L, getServer(L)->isSingleplayer()); + const Server *srv = getServer(L); + lua_pushboolean(L, srv->isSingleplayer()); return 1; } diff --git a/src/server.cpp b/src/server.cpp index 9d7e8e563..dec6cf44c 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -3658,11 +3658,6 @@ const ModSpec *Server::getModSpec(const std::string &modname) const return m_modmgr->getModSpec(modname); } -void Server::getModNames(std::vector &modlist) -{ - m_modmgr->getModNames(modlist); -} - std::string Server::getBuiltinLuaPath() { return porting::path_share + DIR_DELIM + "builtin"; diff --git a/src/server.h b/src/server.h index 008213c5d..bd799c313 100644 --- a/src/server.h +++ b/src/server.h @@ -292,11 +292,10 @@ public: virtual const std::vector &getMods() const; virtual const ModSpec* getModSpec(const std::string &modname) const; - void getModNames(std::vector &modlist); std::string getBuiltinLuaPath(); virtual std::string getWorldPath() const { return m_path_world; } - inline bool isSingleplayer() + inline bool isSingleplayer() const { return m_simple_singleplayer_mode; } inline void setAsyncFatalError(const std::string &error) -- cgit v1.2.3 From e7659883cc6fca343785da2a1af3890ae273abbf Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 2 May 2022 20:55:04 +0200 Subject: Async environment for mods to do concurrent tasks (#11131) --- builtin/async/game.lua | 46 ++ builtin/async/init.lua | 11 - builtin/async/mainmenu.lua | 9 + builtin/game/async.lua | 22 + builtin/game/init.lua | 1 + builtin/game/misc.lua | 29 ++ builtin/init.lua | 6 +- doc/lua_api.txt | 62 +++ games/devtest/mods/unittests/async_env.lua | 149 ++++++ games/devtest/mods/unittests/init.lua | 1 + games/devtest/mods/unittests/inside_async_env.lua | 15 + src/map.cpp | 33 ++ src/map.h | 17 +- src/script/common/CMakeLists.txt | 1 + src/script/common/c_internal.cpp | 14 + src/script/common/c_internal.h | 5 + src/script/common/c_packer.cpp | 583 ++++++++++++++++++++++ src/script/common/c_packer.h | 123 +++++ src/script/cpp_api/s_async.cpp | 183 +++++-- src/script/cpp_api/s_async.h | 54 +- src/script/lua_api/l_craft.cpp | 8 + src/script/lua_api/l_craft.h | 1 + src/script/lua_api/l_internal.h | 2 +- src/script/lua_api/l_item.cpp | 25 + src/script/lua_api/l_item.h | 7 +- src/script/lua_api/l_noise.cpp | 53 ++ src/script/lua_api/l_noise.h | 6 + src/script/lua_api/l_server.cpp | 85 ++++ src/script/lua_api/l_server.h | 10 + src/script/lua_api/l_util.cpp | 5 + src/script/lua_api/l_util.h | 2 - src/script/lua_api/l_vmanip.cpp | 33 ++ src/script/lua_api/l_vmanip.h | 3 + src/script/scripting_server.cpp | 67 ++- src/script/scripting_server.h | 17 + src/server.cpp | 4 + src/server.h | 8 +- src/serverenvironment.cpp | 2 + src/util/basic_macros.h | 8 +- 39 files changed, 1654 insertions(+), 56 deletions(-) create mode 100644 builtin/async/game.lua delete mode 100644 builtin/async/init.lua create mode 100644 builtin/async/mainmenu.lua create mode 100644 builtin/game/async.lua create mode 100644 games/devtest/mods/unittests/async_env.lua create mode 100644 games/devtest/mods/unittests/inside_async_env.lua create mode 100644 src/script/common/c_packer.cpp create mode 100644 src/script/common/c_packer.h (limited to 'src/script/lua_api') diff --git a/builtin/async/game.lua b/builtin/async/game.lua new file mode 100644 index 000000000..212a33e17 --- /dev/null +++ b/builtin/async/game.lua @@ -0,0 +1,46 @@ +core.log("info", "Initializing asynchronous environment (game)") + +local function pack2(...) + return {n=select('#', ...), ...} +end + +-- Entrypoint to run async jobs, called by C++ +function core.job_processor(func, params) + local retval = pack2(func(unpack(params, 1, params.n))) + + return retval +end + +-- Import a bunch of individual files from builtin/game/ +local gamepath = core.get_builtin_path() .. "game" .. DIR_DELIM + +dofile(gamepath .. "constants.lua") +dofile(gamepath .. "item_s.lua") +dofile(gamepath .. "misc_s.lua") +dofile(gamepath .. "features.lua") +dofile(gamepath .. "voxelarea.lua") + +-- Transfer of globals +do + assert(core.transferred_globals) + local all = core.deserialize(core.transferred_globals, true) + core.transferred_globals = nil + + -- reassemble other tables + all.registered_nodes = {} + all.registered_craftitems = {} + all.registered_tools = {} + for k, v in pairs(all.registered_items) do + if v.type == "node" then + all.registered_nodes[k] = v + elseif v.type == "craftitem" then + all.registered_craftitems[k] = v + elseif v.type == "tool" then + all.registered_tools[k] = v + end + end + + for k, v in pairs(all) do + core[k] = v + end +end diff --git a/builtin/async/init.lua b/builtin/async/init.lua deleted file mode 100644 index 3803994d6..000000000 --- a/builtin/async/init.lua +++ /dev/null @@ -1,11 +0,0 @@ - -core.log("info", "Initializing Asynchronous environment") - -function core.job_processor(func, serialized_param) - local param = core.deserialize(serialized_param) - - local retval = core.serialize(func(param)) - - return retval or core.serialize(nil) -end - diff --git a/builtin/async/mainmenu.lua b/builtin/async/mainmenu.lua new file mode 100644 index 000000000..0e9c222d1 --- /dev/null +++ b/builtin/async/mainmenu.lua @@ -0,0 +1,9 @@ +core.log("info", "Initializing asynchronous environment") + +function core.job_processor(func, serialized_param) + local param = core.deserialize(serialized_param) + + local retval = core.serialize(func(param)) + + return retval or core.serialize(nil) +end diff --git a/builtin/game/async.lua b/builtin/game/async.lua new file mode 100644 index 000000000..469f179d7 --- /dev/null +++ b/builtin/game/async.lua @@ -0,0 +1,22 @@ + +core.async_jobs = {} + +function core.async_event_handler(jobid, retval) + local callback = core.async_jobs[jobid] + assert(type(callback) == "function") + callback(unpack(retval, 1, retval.n)) + core.async_jobs[jobid] = nil +end + +function core.handle_async(func, callback, ...) + assert(type(func) == "function" and type(callback) == "function", + "Invalid minetest.handle_async invocation") + local args = {n = select("#", ...), ...} + local mod_origin = core.get_last_run_mod() + + local jobid = core.do_async_callback(func, args, mod_origin) + core.async_jobs[jobid] = callback + + return true +end + diff --git a/builtin/game/init.lua b/builtin/game/init.lua index c5f08113b..68d6a10f8 100644 --- a/builtin/game/init.lua +++ b/builtin/game/init.lua @@ -34,5 +34,6 @@ dofile(gamepath .. "voxelarea.lua") dofile(gamepath .. "forceloading.lua") dofile(gamepath .. "statbars.lua") dofile(gamepath .. "knockback.lua") +dofile(gamepath .. "async.lua") profiler = nil diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua index 18d5a7310..9f5e3312b 100644 --- a/builtin/game/misc.lua +++ b/builtin/game/misc.lua @@ -235,3 +235,32 @@ end -- Used for callback handling with dynamic_add_media core.dynamic_media_callbacks = {} + + +-- Transfer of certain globals into async environment +-- see builtin/async/game.lua for the other side + +local function copy_filtering(t, seen) + if type(t) == "userdata" or type(t) == "function" then + return true -- don't use nil so presence can still be detected + elseif type(t) ~= "table" then + return t + end + local n = {} + seen = seen or {} + seen[t] = n + for k, v in pairs(t) do + local k_ = seen[k] or copy_filtering(k, seen) + local v_ = seen[v] or copy_filtering(v, seen) + n[k_] = v_ + end + return n +end + +function core.get_globals_to_transfer() + local all = { + registered_items = copy_filtering(core.registered_items), + registered_aliases = core.registered_aliases, + } + return core.serialize(all) +end diff --git a/builtin/init.lua b/builtin/init.lua index 7a9b5c427..869136016 100644 --- a/builtin/init.lua +++ b/builtin/init.lua @@ -56,8 +56,10 @@ elseif INIT == "mainmenu" then if not custom_loaded then dofile(core.get_mainmenu_path() .. DIR_DELIM .. "init.lua") end -elseif INIT == "async" then - dofile(asyncpath .. "init.lua") +elseif INIT == "async" then + dofile(asyncpath .. "mainmenu.lua") +elseif INIT == "async_game" then + dofile(asyncpath .. "game.lua") elseif INIT == "client" then dofile(clientpath .. "init.lua") else diff --git a/doc/lua_api.txt b/doc/lua_api.txt index f54672db7..339ce8a27 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -5767,6 +5767,68 @@ Timing * `job:cancel()` * Cancels the job function from being called +Async environment +----------------- + +The engine allows you to submit jobs to be ran in an isolated environment +concurrently with normal server operation. +A job consists of a function to be ran in the async environment, any amount of +arguments (will be serialized) and a callback that will be called with the return +value of the job function once it is finished. + +The async environment does *not* have access to the map, entities, players or any +globals defined in the 'usual' environment. Consequently, functions like +`minetest.get_node()` or `minetest.get_player_by_name()` simply do not exist in it. + +Arguments and return values passed through this can contain certain userdata +objects that will be seamlessly copied (not shared) to the async environment. +This allows you easy interoperability for delegating work to jobs. + +* `minetest.handle_async(func, callback, ...)`: + * Queue the function `func` to be ran in an async environment. + Note that there are multiple persistent workers and any of them may + end up running a given job. The engine will scale the amount of + worker threads automatically. + * When `func` returns the callback is called (in the normal environment) + with all of the return values as arguments. + * Optional: Variable number of arguments that are passed to `func` +* `minetest.register_async_dofile(path)`: + * Register a path to a Lua file to be imported when an async environment + is initialized. You can use this to preload code which you can then call + later using `minetest.handle_async()`. + +### List of APIs available in an async environment + +Classes: +* `ItemStack` +* `PerlinNoise` +* `PerlinNoiseMap` +* `PseudoRandom` +* `PcgRandom` +* `SecureRandom` +* `VoxelArea` +* `VoxelManip` + * only if transferred into environment; can't read/write to map +* `Settings` + +Class instances that can be transferred between environments: +* `ItemStack` +* `PerlinNoise` +* `PerlinNoiseMap` +* `VoxelManip` + +Functions: +* Standalone helpers such as logging, filesystem, encoding, + hashing or compression APIs +* `minetest.request_insecure_environment` (same restrictions apply) + +Variables: +* `minetest.settings` +* `minetest.registered_items`, `registered_nodes`, `registered_tools`, + `registered_craftitems` and `registered_aliases` + * with all functions and userdata values replaced by `true`, calling any + callbacks here is obviously not possible + Server ------ diff --git a/games/devtest/mods/unittests/async_env.lua b/games/devtest/mods/unittests/async_env.lua new file mode 100644 index 000000000..aff1fc4d9 --- /dev/null +++ b/games/devtest/mods/unittests/async_env.lua @@ -0,0 +1,149 @@ +-- helper + +core.register_async_dofile(core.get_modpath(core.get_current_modname()) .. + DIR_DELIM .. "inside_async_env.lua") + +local function deepequal(a, b) + if type(a) == "function" then + return type(b) == "function" + elseif type(a) ~= "table" then + return a == b + elseif type(b) ~= "table" then + return false + end + for k, v in pairs(a) do + if not deepequal(v, b[k]) then + return false + end + end + for k, v in pairs(b) do + if not deepequal(a[k], v) then + return false + end + end + return true +end + +-- Object Passing / Serialization + +local test_object = { + name = "stairs:stair_glass", + type = "node", + groups = {oddly_breakable_by_hand = 3, cracky = 3, stair = 1}, + description = "Glass Stair", + sounds = { + dig = {name = "default_glass_footstep", gain = 0.5}, + footstep = {name = "default_glass_footstep", gain = 0.3}, + dug = {name = "default_break_glass", gain = 1} + }, + node_box = { + fixed = { + {-0.5, -0.5, -0.5, 0.5, 0, 0.5}, + {-0.5, 0, 0, 0.5, 0.5, 0.5} + }, + type = "fixed" + }, + tiles = { + {name = "stairs_glass_split.png", backface_culling = true}, + {name = "default_glass.png", backface_culling = true}, + {name = "stairs_glass_stairside.png^[transformFX", backface_culling = true} + }, + on_place = function(itemstack, placer) + return core.is_player(placer) + end, + sunlight_propagates = true, + is_ground_content = false, + light_source = 0, +} + +local function test_object_passing() + local tmp = core.serialize_roundtrip(test_object) + assert(deepequal(test_object, tmp)) + + -- Circular key, should error + tmp = {"foo", "bar"} + tmp[tmp] = true + assert(not pcall(core.serialize_roundtrip, tmp)) + + -- Circular value, should error + tmp = {"foo"} + tmp[2] = tmp + assert(not pcall(core.serialize_roundtrip, tmp)) +end +unittests.register("test_object_passing", test_object_passing) + +local function test_userdata_passing(_, pos) + -- basic userdata passing + local obj = table.copy(test_object.tiles[1]) + obj.test = ItemStack("default:cobble 99") + local tmp = core.serialize_roundtrip(obj) + assert(type(tmp.test) == "userdata") + assert(obj.test:to_string() == tmp.test:to_string()) + + -- object can't be passed, should error + obj = core.raycast(pos, pos) + assert(not pcall(core.serialize_roundtrip, obj)) + + -- VManip + local vm = core.get_voxel_manip(pos, pos) + local expect = vm:get_node_at(pos) + local vm2 = core.serialize_roundtrip(vm) + assert(deepequal(vm2:get_node_at(pos), expect)) +end +unittests.register("test_userdata_passing", test_userdata_passing, {map=true}) + +-- Asynchronous jobs + +local function test_handle_async(cb) + -- Basic test including mod name tracking and unittests.async_test() + -- which is defined inside_async_env.lua + local func = function(x) + return core.get_last_run_mod(), _VERSION, unittests[x]() + end + local expect = {core.get_last_run_mod(), _VERSION, true} + + core.handle_async(func, function(...) + if not deepequal(expect, {...}) then + cb("Values did not equal") + end + if core.get_last_run_mod() ~= expect[1] then + cb("Mod name not tracked correctly") + end + + -- Test passing of nil arguments and return values + core.handle_async(function(a, b) + return a, b + end, function(a, b) + if b ~= 123 then + cb("Argument went missing") + end + cb() + end, nil, 123) + end, "async_test") +end +unittests.register("test_handle_async", test_handle_async, {async=true}) + +local function test_userdata_passing2(cb, _, pos) + -- VManip: check transfer into other env + local vm = core.get_voxel_manip(pos, pos) + local expect = vm:get_node_at(pos) + + core.handle_async(function(vm_, pos_) + return vm_:get_node_at(pos_) + end, function(ret) + if not deepequal(expect, ret) then + cb("Node data mismatch (one-way)") + end + + -- VManip: test a roundtrip + core.handle_async(function(vm_) + return vm_ + end, function(vm2) + if not deepequal(expect, vm2:get_node_at(pos)) then + cb("Node data mismatch (roundtrip)") + end + cb() + end, vm) + end, vm, pos) +end +unittests.register("test_userdata_passing2", test_userdata_passing2, {map=true, async=true}) diff --git a/games/devtest/mods/unittests/init.lua b/games/devtest/mods/unittests/init.lua index 0754d507f..0608f2dd2 100644 --- a/games/devtest/mods/unittests/init.lua +++ b/games/devtest/mods/unittests/init.lua @@ -175,6 +175,7 @@ dofile(modpath .. "/misc.lua") dofile(modpath .. "/player.lua") dofile(modpath .. "/crafting.lua") dofile(modpath .. "/itemdescription.lua") +dofile(modpath .. "/async_env.lua") -------------- diff --git a/games/devtest/mods/unittests/inside_async_env.lua b/games/devtest/mods/unittests/inside_async_env.lua new file mode 100644 index 000000000..9774771f9 --- /dev/null +++ b/games/devtest/mods/unittests/inside_async_env.lua @@ -0,0 +1,15 @@ +unittests = {} + +core.log("info", "Hello World") + +function unittests.async_test() + assert(core == minetest) + -- stuff that should not be here + assert(not core.get_player_by_name) + assert(not core.set_node) + assert(not core.object_refs) + -- stuff that should be here + assert(ItemStack) + assert(core.registered_items[""]) + return true +end diff --git a/src/map.cpp b/src/map.cpp index 9c9324f5f..5153dcaa9 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -1896,6 +1896,7 @@ MMVManip::MMVManip(Map *map): VoxelManipulator(), m_map(map) { + assert(map); } void MMVManip::initialEmerge(v3s16 blockpos_min, v3s16 blockpos_max, @@ -1903,6 +1904,8 @@ void MMVManip::initialEmerge(v3s16 blockpos_min, v3s16 blockpos_max, { TimeTaker timer1("initialEmerge", &emerge_time); + assert(m_map); + // Units of these are MapBlocks v3s16 p_min = blockpos_min; v3s16 p_max = blockpos_max; @@ -1986,6 +1989,7 @@ void MMVManip::blitBackAll(std::map *modified_blocks, { if(m_area.getExtent() == v3s16(0,0,0)) return; + assert(m_map); /* Copy data of all blocks @@ -2006,4 +2010,33 @@ void MMVManip::blitBackAll(std::map *modified_blocks, } } +MMVManip *MMVManip::clone() const +{ + MMVManip *ret = new MMVManip(); + + const s32 size = m_area.getVolume(); + ret->m_area = m_area; + if (m_data) { + ret->m_data = new MapNode[size]; + memcpy(ret->m_data, m_data, size * sizeof(MapNode)); + } + if (m_flags) { + ret->m_flags = new u8[size]; + memcpy(ret->m_flags, m_flags, size * sizeof(u8)); + } + + ret->m_is_dirty = m_is_dirty; + // Even if the copy is disconnected from a map object keep the information + // needed to write it back to one + ret->m_loaded_blocks = m_loaded_blocks; + + return ret; +} + +void MMVManip::reparent(Map *map) +{ + assert(map && !m_map); + m_map = map; +} + //END diff --git a/src/map.h b/src/map.h index d8ed29106..21e3dbd6c 100644 --- a/src/map.h +++ b/src/map.h @@ -446,10 +446,25 @@ public: void blitBackAll(std::map * modified_blocks, bool overwrite_generated = true); + /* + Creates a copy of this VManip including contents, the copy will not be + associated with a Map. + */ + MMVManip *clone() const; + + // Reassociates a copied VManip to a map + void reparent(Map *map); + + // Is it impossible to call initialEmerge / blitBackAll? + inline bool isOrphan() const { return !m_map; } + bool m_is_dirty = false; protected: - Map *m_map; + MMVManip() {}; + + // may be null + Map *m_map = nullptr; /* key = blockpos value = flags describing the block diff --git a/src/script/common/CMakeLists.txt b/src/script/common/CMakeLists.txt index d07f6ab1b..3e84b46c7 100644 --- a/src/script/common/CMakeLists.txt +++ b/src/script/common/CMakeLists.txt @@ -3,6 +3,7 @@ set(common_SCRIPT_COMMON_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/c_converter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/c_types.cpp ${CMAKE_CURRENT_SOURCE_DIR}/c_internal.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/c_packer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/helper.cpp PARENT_SCOPE) diff --git a/src/script/common/c_internal.cpp b/src/script/common/c_internal.cpp index df82dba14..ddd2d184c 100644 --- a/src/script/common/c_internal.cpp +++ b/src/script/common/c_internal.cpp @@ -166,3 +166,17 @@ void log_deprecated(lua_State *L, std::string message, int stack_depth) infostream << script_get_backtrace(L) << std::endl; } +void call_string_dump(lua_State *L, int idx) +{ + // Retrieve string.dump from insecure env to avoid it being tampered with + lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_GLOBALS_BACKUP); + if (!lua_isnil(L, -1)) + lua_getfield(L, -1, "string"); + else + lua_getglobal(L, "string"); + lua_getfield(L, -1, "dump"); + lua_remove(L, -2); // remove _G + lua_remove(L, -2); // remove 'string' table + lua_pushvalue(L, idx); + lua_call(L, 1, 1); +} diff --git a/src/script/common/c_internal.h b/src/script/common/c_internal.h index c43db34aa..272a39941 100644 --- a/src/script/common/c_internal.h +++ b/src/script/common/c_internal.h @@ -56,6 +56,7 @@ extern "C" { #define CUSTOM_RIDX_BACKTRACE (CUSTOM_RIDX_BASE + 3) #define CUSTOM_RIDX_HTTP_API_LUA (CUSTOM_RIDX_BASE + 4) #define CUSTOM_RIDX_VECTOR_METATABLE (CUSTOM_RIDX_BASE + 5) +#define CUSTOM_RIDX_METATABLE_MAP (CUSTOM_RIDX_BASE + 6) // Determine if CUSTOM_RIDX_SCRIPTAPI will hold a light or full userdata @@ -139,3 +140,7 @@ DeprecatedHandlingMode get_deprecated_handling_mode(); * @param stack_depth How far on the stack to the first user function (ie: not builtin or core) */ void log_deprecated(lua_State *L, std::string message, int stack_depth = 1); + +// Safely call string.dump on a function value +// (does not pop, leaves one value on stack) +void call_string_dump(lua_State *L, int idx); diff --git a/src/script/common/c_packer.cpp b/src/script/common/c_packer.cpp new file mode 100644 index 000000000..fc5277330 --- /dev/null +++ b/src/script/common/c_packer.cpp @@ -0,0 +1,583 @@ +/* +Minetest +Copyright (C) 2022 sfan5 + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include +#include +#include +#include +#include +#include +#include "c_packer.h" +#include "c_internal.h" +#include "log.h" +#include "debug.h" +#include "threading/mutex_auto_lock.h" + +extern "C" { +#include +} + +// +// Helpers +// + +// convert negative index to absolute position on Lua stack +static inline int absidx(lua_State *L, int idx) +{ + assert(idx < 0); + return lua_gettop(L) + idx + 1; +} + +// does the type put anything into PackedInstr::sdata? +static inline bool uses_sdata(int type) +{ + switch (type) { + case LUA_TSTRING: + case LUA_TFUNCTION: + case LUA_TUSERDATA: + return true; + default: + return false; + } +} + +// does the type put anything into PackedInstr::? +static inline bool uses_union(int type) +{ + switch (type) { + case LUA_TNIL: + case LUA_TSTRING: + case LUA_TFUNCTION: + return false; + default: + return true; + } +} + +static inline bool can_set_into(int ktype, int vtype) +{ + switch (ktype) { + case LUA_TNUMBER: + return !uses_union(vtype); + case LUA_TSTRING: + return !uses_sdata(vtype); + default: + return false; + } +} + +// is the key suitable for use with set_into? +static inline bool suitable_key(lua_State *L, int idx) +{ + if (lua_type(L, idx) == LUA_TSTRING) { + // strings may not have a NULL byte (-> lua_setfield) + size_t len; + const char *str = lua_tolstring(L, idx, &len); + return strlen(str) == len; + } else { + assert(lua_type(L, idx) == LUA_TNUMBER); + // numbers must fit into an s32 and be integers (-> lua_rawseti) + lua_Number n = lua_tonumber(L, idx); + return std::floor(n) == n && n >= S32_MIN && n <= S32_MAX; + } +} + +namespace { + // checks if you left any values on the stack, for debugging + class StackChecker { + lua_State *L; + int top; + public: + StackChecker(lua_State *L) : L(L), top(lua_gettop(L)) {} + ~StackChecker() { + assert(lua_gettop(L) >= top); + if (lua_gettop(L) > top) { + rawstream << "Lua stack not cleaned up: " + << lua_gettop(L) << " != " << top + << " (false-positive if exception thrown)" << std::endl; + } + } + }; + + // Since an std::vector may reallocate, this is the only safe way to keep + // a reference to a particular element. + template + class VectorRef { + std::vector *vec; + size_t idx; + VectorRef(std::vector *vec, size_t idx) : vec(vec), idx(idx) {} + public: + static VectorRef front(std::vector &vec) { + return VectorRef(&vec, 0); + } + static VectorRef back(std::vector &vec) { + return VectorRef(&vec, vec.size() - 1); + } + T &operator*() { return (*vec)[idx]; } + T *operator->() { return &(*vec)[idx]; } + }; + + struct Packer { + PackInFunc fin; + PackOutFunc fout; + }; + + typedef std::pair PackerTuple; +} + +static inline auto emplace(PackedValue &pv, s16 type) +{ + pv.i.emplace_back(); + auto ref = VectorRef::back(pv.i); + ref->type = type; + // Initialize fields that may be left untouched + if (type == LUA_TTABLE) { + ref->uidata1 = 0; + ref->uidata2 = 0; + } else if (type == LUA_TUSERDATA) { + ref->ptrdata = nullptr; + } else if (type == INSTR_POP) { + ref->sidata2 = 0; + } + return ref; +} + +// +// Management of registered packers +// + +static std::unordered_map g_packers; +static std::mutex g_packers_lock; + +void script_register_packer(lua_State *L, const char *regname, + PackInFunc fin, PackOutFunc fout) +{ + // Store away callbacks + { + MutexAutoLock autolock(g_packers_lock); + auto it = g_packers.find(regname); + if (it == g_packers.end()) { + auto &ref = g_packers[regname]; + ref.fin = fin; + ref.fout = fout; + } else { + FATAL_ERROR_IF(it->second.fin != fin || it->second.fout != fout, + "Packer registered twice with mismatching callbacks"); + } + } + + // Save metatable so we can identify instances later + lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_METATABLE_MAP); + if (lua_isnil(L, -1)) { + lua_newtable(L); + lua_pushvalue(L, -1); + lua_rawseti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_METATABLE_MAP); + } + + luaL_getmetatable(L, regname); + FATAL_ERROR_IF(lua_isnil(L, -1), "No metatable registered with that name"); + + // CUSTOM_RIDX_METATABLE_MAP contains { [metatable] = "regname", ... } + // check first + lua_pushstring(L, regname); + lua_rawget(L, -3); + if (!lua_isnil(L, -1)) { + FATAL_ERROR_IF(lua_topointer(L, -1) != lua_topointer(L, -2), + "Packer registered twice with inconsistent metatable"); + } + lua_pop(L, 1); + // then set + lua_pushstring(L, regname); + lua_rawset(L, -3); + + lua_pop(L, 1); +} + +static bool find_packer(const char *regname, PackerTuple &out) +{ + MutexAutoLock autolock(g_packers_lock); + auto it = g_packers.find(regname); + if (it == g_packers.end()) + return false; + // copy data for thread safety + out.first = it->first; + out.second = it->second; + return true; +} + +static bool find_packer(lua_State *L, int idx, PackerTuple &out) +{ +#ifndef NDEBUG + StackChecker checker(L); +#endif + + // retrieve metatable of the object + if (lua_getmetatable(L, idx) != 1) + return false; + + // use our global table to map it to the registry name + lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_METATABLE_MAP); + assert(lua_istable(L, -1)); + lua_pushvalue(L, -2); + lua_rawget(L, -2); + if (lua_isnil(L, -1)) { + lua_pop(L, 3); + return false; + } + + // load the associated data + bool found = find_packer(lua_tostring(L, -1), out); + FATAL_ERROR_IF(!found, "Inconsistent internal state"); + lua_pop(L, 3); + return true; +} + +// +// Packing implementation +// + +// recursively goes through the structure and ensures there are no circular references +static void pack_validate(lua_State *L, int idx, std::unordered_set &seen) +{ +#ifndef NDEBUG + StackChecker checker(L); + assert(idx > 0); +#endif + + if (lua_type(L, idx) != LUA_TTABLE) + return; + + const void *ptr = lua_topointer(L, idx); + assert(ptr); + + if (seen.find(ptr) != seen.end()) + throw LuaError("Circular references cannot be packed (yet)"); + seen.insert(ptr); + + lua_checkstack(L, 5); + lua_pushnil(L); + while (lua_next(L, idx) != 0) { + // key at -2, value at -1 + pack_validate(L, absidx(L, -2), seen); + pack_validate(L, absidx(L, -1), seen); + + lua_pop(L, 1); + } + + seen.erase(ptr); +} + +static VectorRef pack_inner(lua_State *L, int idx, int vidx, PackedValue &pv) +{ +#ifndef NDEBUG + StackChecker checker(L); + assert(idx > 0); + assert(vidx > 0); +#endif + + switch (lua_type(L, idx)) { + case LUA_TNONE: + case LUA_TNIL: + return emplace(pv, LUA_TNIL); + case LUA_TBOOLEAN: { + auto r = emplace(pv, LUA_TBOOLEAN); + r->bdata = lua_toboolean(L, idx); + return r; + } + case LUA_TNUMBER: { + auto r = emplace(pv, LUA_TNUMBER); + r->ndata = lua_tonumber(L, idx); + return r; + } + case LUA_TSTRING: { + auto r = emplace(pv, LUA_TSTRING); + size_t len; + const char *str = lua_tolstring(L, idx, &len); + assert(str); + r->sdata.assign(str, len); + return r; + } + case LUA_TTABLE: + break; // execution continues + case LUA_TFUNCTION: { + auto r = emplace(pv, LUA_TFUNCTION); + call_string_dump(L, idx); + size_t len; + const char *str = lua_tolstring(L, -1, &len); + assert(str); + r->sdata.assign(str, len); + lua_pop(L, 1); + return r; + } + case LUA_TUSERDATA: { + PackerTuple ser; + if (!find_packer(L, idx, ser)) + throw LuaError("Cannot serialize unsupported userdata"); + pv.contains_userdata = true; + auto r = emplace(pv, LUA_TUSERDATA); + r->sdata = ser.first; + r->ptrdata = ser.second.fin(L, idx); + return r; + } + default: { + std::string err = "Cannot serialize type "; + err += lua_typename(L, lua_type(L, idx)); + throw LuaError(err); + } + } + + // LUA_TTABLE + lua_checkstack(L, 5); + + auto rtable = emplace(pv, LUA_TTABLE); + const int vi_table = vidx++; + + lua_pushnil(L); + while (lua_next(L, idx) != 0) { + // key at -2, value at -1 + const int ktype = lua_type(L, -2), vtype = lua_type(L, -1); + if (ktype == LUA_TNUMBER) + rtable->uidata1++; // narr + else + rtable->uidata2++; // nrec + + // check if we can use a shortcut + if (can_set_into(ktype, vtype) && suitable_key(L, -2)) { + // push only the value + auto rval = pack_inner(L, absidx(L, -1), vidx, pv); + rval->pop = vtype != LUA_TTABLE; + // and where to put it: + rval->set_into = vi_table; + if (ktype == LUA_TSTRING) + rval->sdata = lua_tostring(L, -2); + else + rval->sidata1 = lua_tointeger(L, -2); + // pop tables after the fact + if (!rval->pop) { + auto ri1 = emplace(pv, INSTR_POP); + ri1->sidata1 = vidx; + } + } else { + // push the key and value + pack_inner(L, absidx(L, -2), vidx, pv); + vidx++; + pack_inner(L, absidx(L, -1), vidx, pv); + vidx++; + // push an instruction to set them + auto ri1 = emplace(pv, INSTR_SETTABLE); + ri1->set_into = vi_table; + ri1->sidata1 = vidx - 2; + ri1->sidata2 = vidx - 1; + ri1->pop = true; + vidx -= 2; + } + + lua_pop(L, 1); + } + + assert(vidx == vi_table + 1); + return rtable; +} + +PackedValue *script_pack(lua_State *L, int idx) +{ + if (idx < 0) + idx = absidx(L, idx); + + std::unordered_set seen; + pack_validate(L, idx, seen); + assert(seen.size() == 0); + + // Actual serialization + PackedValue pv; + pack_inner(L, idx, 1, pv); + + return new PackedValue(std::move(pv)); +} + +// +// Unpacking implementation +// + +void script_unpack(lua_State *L, PackedValue *pv) +{ + const int top = lua_gettop(L); + int ctr = 0; + + for (auto &i : pv->i) { + // If leaving values on stack make sure there's space (every 5th iteration) + if (!i.pop && (ctr++) >= 5) { + lua_checkstack(L, 5); + ctr = 0; + } + + /* Instructions */ + switch (i.type) { + case INSTR_SETTABLE: + lua_pushvalue(L, top + i.sidata1); // key + lua_pushvalue(L, top + i.sidata2); // value + lua_rawset(L, top + i.set_into); + if (i.pop) { + if (i.sidata1 != i.sidata2) { + // removing moves indices so pop higher index first + lua_remove(L, top + std::max(i.sidata1, i.sidata2)); + lua_remove(L, top + std::min(i.sidata1, i.sidata2)); + } else { + lua_remove(L, top + i.sidata1); + } + } + continue; + case INSTR_POP: + lua_remove(L, top + i.sidata1); + if (i.sidata2 > 0) + lua_remove(L, top + i.sidata2); + continue; + default: + break; + } + + /* Lua types */ + switch (i.type) { + case LUA_TNIL: + lua_pushnil(L); + break; + case LUA_TBOOLEAN: + lua_pushboolean(L, i.bdata); + break; + case LUA_TNUMBER: + lua_pushnumber(L, i.ndata); + break; + case LUA_TSTRING: + lua_pushlstring(L, i.sdata.data(), i.sdata.size()); + break; + case LUA_TTABLE: + lua_createtable(L, i.uidata1, i.uidata2); + break; + case LUA_TFUNCTION: + luaL_loadbuffer(L, i.sdata.data(), i.sdata.size(), nullptr); + break; + case LUA_TUSERDATA: { + PackerTuple ser; + sanity_check(find_packer(i.sdata.c_str(), ser)); + ser.second.fout(L, i.ptrdata); + i.ptrdata = nullptr; // ownership taken by callback + break; + } + default: + assert(0); + break; + } + + if (i.set_into) { + if (!i.pop) + lua_pushvalue(L, -1); + if (uses_sdata(i.type)) + lua_rawseti(L, top + i.set_into, i.sidata1); + else + lua_setfield(L, top + i.set_into, i.sdata.c_str()); + } else { + if (i.pop) + lua_pop(L, 1); + } + } + + // as part of the unpacking process we take ownership of all userdata + pv->contains_userdata = false; + // leave exactly one value on the stack + lua_settop(L, top+1); +} + +// +// PackedValue +// + +PackedValue::~PackedValue() +{ + if (!contains_userdata) + return; + for (auto &i : this->i) { + if (i.type == LUA_TUSERDATA && i.ptrdata) { + PackerTuple ser; + if (find_packer(i.sdata.c_str(), ser)) { + // tell it to deallocate object + ser.second.fout(nullptr, i.ptrdata); + } else { + assert(false); + } + } + } +} + +// +// script_dump_packed +// + +#ifndef NDEBUG +void script_dump_packed(const PackedValue *val) +{ + printf("instruction stream: [\n"); + for (const auto &i : val->i) { + printf("\t("); + switch (i.type) { + case INSTR_SETTABLE: + printf("SETTABLE(%d, %d)", i.sidata1, i.sidata2); + break; + case INSTR_POP: + printf(i.sidata2 ? "POP(%d, %d)" : "POP(%d)", i.sidata1, i.sidata2); + break; + case LUA_TNIL: + printf("nil"); + break; + case LUA_TBOOLEAN: + printf(i.bdata ? "true" : "false"); + break; + case LUA_TNUMBER: + printf("%f", i.ndata); + break; + case LUA_TSTRING: + printf("\"%s\"", i.sdata.c_str()); + break; + case LUA_TTABLE: + printf("table(%d, %d)", i.uidata1, i.uidata2); + break; + case LUA_TFUNCTION: + printf("function(%d byte)", i.sdata.size()); + break; + case LUA_TUSERDATA: + printf("userdata %s %p", i.sdata.c_str(), i.ptrdata); + break; + default: + printf("!!UNKNOWN!!"); + break; + } + if (i.set_into) { + if (i.type >= 0 && uses_sdata(i.type)) + printf(", k=%d, into=%d", i.sidata1, i.set_into); + else if (i.type >= 0) + printf(", k=\"%s\", into=%d", i.sdata.c_str(), i.set_into); + else + printf(", into=%d", i.set_into); + } + if (i.pop) + printf(", pop"); + printf(")\n"); + } + printf("]\n"); +} +#endif diff --git a/src/script/common/c_packer.h b/src/script/common/c_packer.h new file mode 100644 index 000000000..8bccca98d --- /dev/null +++ b/src/script/common/c_packer.h @@ -0,0 +1,123 @@ +/* +Minetest +Copyright (C) 2022 sfan5 + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include +#include +#include "irrlichttypes.h" +#include "util/basic_macros.h" + +extern "C" { +#include +} + +/* + This file defines an in-memory representation of Lua objects including + support for functions and userdata. It it used to move data between Lua + states and cannot be used for persistence or network transfer. +*/ + +#define INSTR_SETTABLE (-10) +#define INSTR_POP (-11) + +/** + * Represents a single instruction that pushes a new value or works with existing ones. + */ +struct PackedInstr +{ + s16 type; // LUA_T* or INSTR_* + u16 set_into; // set into table on stack + bool pop; // remove from stack? + union { + bool bdata; // boolean: value + lua_Number ndata; // number: value + struct { + u16 uidata1, uidata2; // table: narr, nrec + }; + struct { + /* + SETTABLE: key index, value index + POP: indices to remove + otherwise w/ set_into: numeric key, - + */ + s32 sidata1, sidata2; + }; + void *ptrdata; // userdata: implementation defined + }; + /* + - string: value + - function: buffer + - w/ set_into: string key (no null bytes!) + - userdata: name in registry + */ + std::string sdata; + + PackedInstr() : type(0), set_into(0), pop(false) {} +}; + +/** + * A packed value can be a primitive like a string or number but also a table + * including all of its contents. It is made up of a linear stream of + * 'instructions' that build the final value when executed. + */ +struct PackedValue +{ + std::vector i; + // Indicates whether there are any userdata pointers that need to be deallocated + bool contains_userdata = false; + + PackedValue() = default; + ~PackedValue(); + + DISABLE_CLASS_COPY(PackedValue) + + ALLOW_CLASS_MOVE(PackedValue) +}; + +/* + * Packing callback: Turns a Lua value at given index into a void* + */ +typedef void *(*PackInFunc)(lua_State *L, int idx); +/* + * Unpacking callback: Turns a void* back into the Lua value (left on top of stack) + * + * Note that this function must take ownership of the pointer, so make sure + * to free or keep the memory. + * `L` can be nullptr to indicate that data should just be discarded. + */ +typedef void (*PackOutFunc)(lua_State *L, void *ptr); +/* + * Register a packable type with the name of its metatable. + * + * Even though the callbacks are global this must be called for every Lua state + * that supports objects of this type. + * This function is thread-safe. + */ +void script_register_packer(lua_State *L, const char *regname, + PackInFunc fin, PackOutFunc fout); + +// Pack a Lua value +PackedValue *script_pack(lua_State *L, int idx); +// Unpack a Lua value (left on top of stack) +// Note that this may modify the PackedValue, you can't reuse it! +void script_unpack(lua_State *L, PackedValue *val); + +// Dump contents of PackedValue to stdout for debugging +void script_dump_packed(const PackedValue *val); diff --git a/src/script/cpp_api/s_async.cpp b/src/script/cpp_api/s_async.cpp index dacdcd75a..42a794ceb 100644 --- a/src/script/cpp_api/s_async.cpp +++ b/src/script/cpp_api/s_async.cpp @@ -21,9 +21,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include extern "C" { -#include "lua.h" -#include "lauxlib.h" -#include "lualib.h" +#include +#include +#include } #include "server.h" @@ -32,6 +32,7 @@ extern "C" { #include "filesys.h" #include "porting.h" #include "common/c_internal.h" +#include "common/c_packer.h" #include "lua_api/l_base.h" /******************************************************************************/ @@ -76,19 +77,34 @@ void AsyncEngine::initialize(unsigned int numEngines) { initDone = true; - for (unsigned int i = 0; i < numEngines; i++) { - AsyncWorkerThread *toAdd = new AsyncWorkerThread(this, - std::string("AsyncWorker-") + itos(i)); - workerThreads.push_back(toAdd); - toAdd->start(); + if (numEngines == 0) { + // Leave one core for the main thread and one for whatever else + autoscaleMaxWorkers = Thread::getNumberOfProcessors(); + if (autoscaleMaxWorkers >= 2) + autoscaleMaxWorkers -= 2; + infostream << "AsyncEngine: using at most " << autoscaleMaxWorkers + << " threads with automatic scaling" << std::endl; + + addWorkerThread(); + } else { + for (unsigned int i = 0; i < numEngines; i++) + addWorkerThread(); } } +void AsyncEngine::addWorkerThread() +{ + AsyncWorkerThread *toAdd = new AsyncWorkerThread(this, + std::string("AsyncWorker-") + itos(workerThreads.size())); + workerThreads.push_back(toAdd); + toAdd->start(); +} + /******************************************************************************/ u32 AsyncEngine::queueAsyncJob(std::string &&func, std::string &¶ms, const std::string &mod_origin) { - jobQueueMutex.lock(); + MutexAutoLock autolock(jobQueueMutex); u32 jobId = jobIdCounter++; jobQueue.emplace_back(); @@ -99,7 +115,23 @@ u32 AsyncEngine::queueAsyncJob(std::string &&func, std::string &¶ms, to_add.mod_origin = mod_origin; jobQueueCounter.post(); - jobQueueMutex.unlock(); + return jobId; +} + +u32 AsyncEngine::queueAsyncJob(std::string &&func, PackedValue *params, + const std::string &mod_origin) +{ + MutexAutoLock autolock(jobQueueMutex); + u32 jobId = jobIdCounter++; + + jobQueue.emplace_back(); + auto &to_add = jobQueue.back(); + to_add.id = jobId; + to_add.function = std::move(func); + to_add.params_ext.reset(params); + to_add.mod_origin = mod_origin; + + jobQueueCounter.post(); return jobId; } @@ -131,6 +163,12 @@ void AsyncEngine::putJobResult(LuaJobInfo &&result) /******************************************************************************/ void AsyncEngine::step(lua_State *L) +{ + stepJobResults(L); + stepAutoscale(); +} + +void AsyncEngine::stepJobResults(lua_State *L) { int error_handler = PUSH_ERROR_HANDLER(L); lua_getglobal(L, "core"); @@ -148,7 +186,10 @@ void AsyncEngine::step(lua_State *L) luaL_checktype(L, -1, LUA_TFUNCTION); lua_pushinteger(L, j.id); - lua_pushlstring(L, j.result.data(), j.result.size()); + if (j.result_ext) + script_unpack(L, j.result_ext.get()); + else + lua_pushlstring(L, j.result.data(), j.result.size()); // Call handler const char *origin = j.mod_origin.empty() ? nullptr : j.mod_origin.c_str(); @@ -161,12 +202,71 @@ void AsyncEngine::step(lua_State *L) lua_pop(L, 2); // Pop core and error handler } +void AsyncEngine::stepAutoscale() +{ + if (workerThreads.size() >= autoscaleMaxWorkers) + return; + + MutexAutoLock autolock(jobQueueMutex); + + // 2) If the timer elapsed, check again + if (autoscaleTimer && porting::getTimeMs() >= autoscaleTimer) { + autoscaleTimer = 0; + // Determine overlap with previous snapshot + unsigned int n = 0; + for (const auto &it : jobQueue) + n += autoscaleSeenJobs.count(it.id); + autoscaleSeenJobs.clear(); + infostream << "AsyncEngine: " << n << " jobs were still waiting after 1s" << std::endl; + // Start this many new threads + while (workerThreads.size() < autoscaleMaxWorkers && n > 0) { + addWorkerThread(); + n--; + } + return; + } + + // 1) Check if there's anything in the queue + if (!autoscaleTimer && !jobQueue.empty()) { + // Take a snapshot of all jobs we have seen + for (const auto &it : jobQueue) + autoscaleSeenJobs.emplace(it.id); + // and set a timer for 1 second + autoscaleTimer = porting::getTimeMs() + 1000; + } +} + /******************************************************************************/ -void AsyncEngine::prepareEnvironment(lua_State* L, int top) +bool AsyncEngine::prepareEnvironment(lua_State* L, int top) { for (StateInitializer &stateInitializer : stateInitializers) { stateInitializer(L, top); } + + auto *script = ModApiBase::getScriptApiBase(L); + try { + script->loadMod(Server::getBuiltinLuaPath() + DIR_DELIM + "init.lua", + BUILTIN_MOD_NAME); + } catch (const ModError &e) { + errorstream << "Execution of async base environment failed: " + << e.what() << std::endl; + FATAL_ERROR("Execution of async base environment failed"); + } + + // Load per mod stuff + if (server) { + const auto &list = server->m_async_init_files; + try { + for (auto &it : list) + script->loadMod(it.second, it.first); + } catch (const ModError &e) { + errorstream << "Failed to load mod script inside async environment." << std::endl; + server->setAsyncFatalError(e.what()); + return false; + } + } + + return true; } /******************************************************************************/ @@ -178,15 +278,25 @@ AsyncWorkerThread::AsyncWorkerThread(AsyncEngine* jobDispatcher, { lua_State *L = getStack(); + if (jobDispatcher->server) { + setGameDef(jobDispatcher->server); + + if (g_settings->getBool("secure.enable_security")) + initializeSecurity(); + } + // Prepare job lua environment lua_getglobal(L, "core"); int top = lua_gettop(L); // Push builtin initialization type - lua_pushstring(L, "async"); + lua_pushstring(L, jobDispatcher->server ? "async_game" : "async"); lua_setglobal(L, "INIT"); - jobDispatcher->prepareEnvironment(L, top); + if (!jobDispatcher->prepareEnvironment(L, top)) { + // can't throw from here so we're stuck with this + isErrored = true; + } } /******************************************************************************/ @@ -198,19 +308,20 @@ AsyncWorkerThread::~AsyncWorkerThread() /******************************************************************************/ void* AsyncWorkerThread::run() { - lua_State *L = getStack(); + if (isErrored) + return nullptr; - try { - loadMod(getServer()->getBuiltinLuaPath() + DIR_DELIM + "init.lua", - BUILTIN_MOD_NAME); - } catch (const ModError &e) { - errorstream << "Execution of async base environment failed: " - << e.what() << std::endl; - FATAL_ERROR("Execution of async base environment failed"); - } + lua_State *L = getStack(); int error_handler = PUSH_ERROR_HANDLER(L); + auto report_error = [this] (const ModError &e) { + if (jobDispatcher->server) + jobDispatcher->server->setAsyncFatalError(e.what()); + else + errorstream << e.what() << std::endl; + }; + lua_getglobal(L, "core"); if (lua_isnil(L, -1)) { FATAL_ERROR("Unable to find core within async environment!"); @@ -223,6 +334,8 @@ void* AsyncWorkerThread::run() if (!jobDispatcher->getJob(&j) || stopRequested()) continue; + const bool use_ext = !!j.params_ext; + lua_getfield(L, -1, "job_processor"); if (lua_isnil(L, -1)) FATAL_ERROR("Unable to get async job processor!"); @@ -232,7 +345,10 @@ void* AsyncWorkerThread::run() errorstream << "ASYNC WORKER: Unable to deserialize function" << std::endl; lua_pushnil(L); } - lua_pushlstring(L, j.params.data(), j.params.size()); + if (use_ext) + script_unpack(L, j.params_ext.get()); + else + lua_pushlstring(L, j.params.data(), j.params.size()); // Call it setOriginDirect(j.mod_origin.empty() ? nullptr : j.mod_origin.c_str()); @@ -241,19 +357,28 @@ void* AsyncWorkerThread::run() try { scriptError(result, ""); } catch (const ModError &e) { - errorstream << e.what() << std::endl; + report_error(e); } } else { // Fetch result - size_t length; - const char *retval = lua_tolstring(L, -1, &length); - j.result.assign(retval, length); + if (use_ext) { + try { + j.result_ext.reset(script_pack(L, -1)); + } catch (const ModError &e) { + report_error(e); + result = LUA_ERRERR; + } + } else { + size_t length; + const char *retval = lua_tolstring(L, -1, &length); + j.result.assign(retval, length); + } } lua_pop(L, 1); // Pop retval // Put job result - if (!j.result.empty()) + if (result == 0) jobDispatcher->putJobResult(std::move(j)); } diff --git a/src/script/cpp_api/s_async.h b/src/script/cpp_api/s_async.h index 697cb0221..1e34e40ea 100644 --- a/src/script/cpp_api/s_async.h +++ b/src/script/cpp_api/s_async.h @@ -21,11 +21,15 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include +#include +#include +#include #include "threading/semaphore.h" #include "threading/thread.h" -#include "lua.h" +#include "common/c_packer.h" #include "cpp_api/s_base.h" +#include "cpp_api/s_security.h" // Forward declarations class AsyncEngine; @@ -42,8 +46,12 @@ struct LuaJobInfo std::string function; // Parameter to be passed to function (serialized) std::string params; + // Alternative parameters + std::unique_ptr params_ext; // Result of function call (serialized) std::string result; + // Alternative result + std::unique_ptr result_ext; // Name of the mod who invoked this call std::string mod_origin; // JobID used to identify a job and match it to callback @@ -51,7 +59,8 @@ struct LuaJobInfo }; // Asynchronous working environment -class AsyncWorkerThread : public Thread, virtual public ScriptApiBase { +class AsyncWorkerThread : public Thread, + virtual public ScriptApiBase, public ScriptApiSecurity { friend class AsyncEngine; public: virtual ~AsyncWorkerThread(); @@ -63,6 +72,7 @@ protected: private: AsyncEngine *jobDispatcher = nullptr; + bool isErrored = false; }; // Asynchornous thread and job management @@ -71,6 +81,7 @@ class AsyncEngine { typedef void (*StateInitializer)(lua_State *L, int top); public: AsyncEngine() = default; + AsyncEngine(Server *server) : server(server) {}; ~AsyncEngine(); /** @@ -81,7 +92,7 @@ public: /** * Create async engine tasks and lock function registration - * @param numEngines Number of async threads to be started + * @param numEngines Number of worker threads, 0 for automatic scaling */ void initialize(unsigned int numEngines); @@ -94,9 +105,17 @@ public: u32 queueAsyncJob(std::string &&func, std::string &¶ms, const std::string &mod_origin = ""); + /** + * Queue an async job + * @param func Serialized lua function + * @param params Serialized parameters (takes ownership!) + * @return ID of queued job + */ + u32 queueAsyncJob(std::string &&func, PackedValue *params, + const std::string &mod_origin = ""); + /** * Engine step to process finished jobs - * the engine step is one way to pass events back, PushFinishedJobs another * @param L The Lua stack */ void step(lua_State *L); @@ -116,19 +135,44 @@ protected: */ void putJobResult(LuaJobInfo &&result); + /** + * Start an additional worker thread + */ + void addWorkerThread(); + + /** + * Process finished jobs callbacks + */ + void stepJobResults(lua_State *L); + + /** + * Handle automatic scaling of worker threads + */ + void stepAutoscale(); + /** * Initialize environment with current registred functions * this function adds all functions registred by registerFunction to the * passed lua stack * @param L Lua stack to initialize * @param top Stack position + * @return false if a mod error ocurred */ - void prepareEnvironment(lua_State* L, int top); + bool prepareEnvironment(lua_State* L, int top); private: // Variable locking the engine against further modification bool initDone = false; + // Maximum number of worker threads for automatic scaling + // 0 if disabled + unsigned int autoscaleMaxWorkers = 0; + u64 autoscaleTimer = 0; + std::unordered_set autoscaleSeenJobs; + + // Only set for the server async environment (duh) + Server *server = nullptr; + // Internal store for registred state initializers std::vector stateInitializers; diff --git a/src/script/lua_api/l_craft.cpp b/src/script/lua_api/l_craft.cpp index c2c5a5551..137b210be 100644 --- a/src/script/lua_api/l_craft.cpp +++ b/src/script/lua_api/l_craft.cpp @@ -525,3 +525,11 @@ void ModApiCraft::Initialize(lua_State *L, int top) API_FCT(register_craft); API_FCT(clear_craft); } + +void ModApiCraft::InitializeAsync(lua_State *L, int top) +{ + // all read-only functions + API_FCT(get_all_craft_recipes); + API_FCT(get_craft_recipe); + API_FCT(get_craft_result); +} diff --git a/src/script/lua_api/l_craft.h b/src/script/lua_api/l_craft.h index 9002b23ef..5234af56f 100644 --- a/src/script/lua_api/l_craft.h +++ b/src/script/lua_api/l_craft.h @@ -45,4 +45,5 @@ private: 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_internal.h b/src/script/lua_api/l_internal.h index 672e535ca..de73ff42a 100644 --- a/src/script/lua_api/l_internal.h +++ b/src/script/lua_api/l_internal.h @@ -69,7 +69,7 @@ with this program; if not, write to the Free Software Foundation, Inc., // Retrieve Environment pointer as `env` (no map lock) #define GET_PLAIN_ENV_PTR_NO_MAP_LOCK \ - Environment *env = (Environment *)getEnv(L); \ + Environment *env = getEnv(L); \ if (env == NULL) \ return 0 diff --git a/src/script/lua_api/l_item.cpp b/src/script/lua_api/l_item.cpp index fc97a1736..b58b994d9 100644 --- a/src/script/lua_api/l_item.cpp +++ b/src/script/lua_api/l_item.cpp @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_internal.h" #include "common/c_converter.h" #include "common/c_content.h" +#include "common/c_packer.h" #include "itemdef.h" #include "nodedef.h" #include "server.h" @@ -441,6 +442,7 @@ int LuaItemStack::create_object(lua_State *L) lua_setmetatable(L, -2); return 1; } + // Not callable from Lua int LuaItemStack::create(lua_State *L, const ItemStack &item) { @@ -457,6 +459,20 @@ LuaItemStack *LuaItemStack::checkobject(lua_State *L, int narg) return *(LuaItemStack **)luaL_checkudata(L, narg, className); } +void *LuaItemStack::packIn(lua_State *L, int idx) +{ + LuaItemStack *o = checkobject(L, idx); + return new ItemStack(o->getItem()); +} + +void LuaItemStack::packOut(lua_State *L, void *ptr) +{ + ItemStack *stack = reinterpret_cast(ptr); + if (L) + create(L, *stack); + delete stack; +} + void LuaItemStack::Register(lua_State *L) { lua_newtable(L); @@ -488,6 +504,8 @@ void LuaItemStack::Register(lua_State *L) // Can be created from Lua (ItemStack(itemstack or itemstring or table or nil)) lua_register(L, className, create_object); + + script_register_packer(L, className, packIn, packOut); } const char LuaItemStack::className[] = "ItemStack"; @@ -673,3 +691,10 @@ void ModApiItemMod::Initialize(lua_State *L, int top) API_FCT(get_content_id); API_FCT(get_name_from_content_id); } + +void ModApiItemMod::InitializeAsync(lua_State *L, int top) +{ + // all read-only functions + API_FCT(get_content_id); + API_FCT(get_name_from_content_id); +} diff --git a/src/script/lua_api/l_item.h b/src/script/lua_api/l_item.h index 16878c101..180975061 100644 --- a/src/script/lua_api/l_item.h +++ b/src/script/lua_api/l_item.h @@ -141,8 +141,11 @@ public: // Not callable from Lua static int create(lua_State *L, const ItemStack &item); static LuaItemStack* checkobject(lua_State *L, int narg); - static void Register(lua_State *L); + static void *packIn(lua_State *L, int idx); + static void packOut(lua_State *L, void *ptr); + + static void Register(lua_State *L); }; class ModApiItemMod : public ModApiBase { @@ -152,6 +155,8 @@ private: static int l_register_alias_raw(lua_State *L); static int l_get_content_id(lua_State *L); static int l_get_name_from_content_id(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_noise.cpp b/src/script/lua_api/l_noise.cpp index 0eee49b7d..5561eaebf 100644 --- a/src/script/lua_api/l_noise.cpp +++ b/src/script/lua_api/l_noise.cpp @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_internal.h" #include "common/c_converter.h" #include "common/c_content.h" +#include "common/c_packer.h" #include "log.h" #include "porting.h" #include "util/numeric.h" @@ -101,6 +102,25 @@ LuaPerlinNoise *LuaPerlinNoise::checkobject(lua_State *L, int narg) } +void *LuaPerlinNoise::packIn(lua_State *L, int idx) +{ + LuaPerlinNoise *o = checkobject(L, idx); + return new NoiseParams(o->np); +} + +void LuaPerlinNoise::packOut(lua_State *L, void *ptr) +{ + NoiseParams *np = reinterpret_cast(ptr); + if (L) { + LuaPerlinNoise *o = new LuaPerlinNoise(np); + *(void **)(lua_newuserdata(L, sizeof(void *))) = o; + luaL_getmetatable(L, className); + lua_setmetatable(L, -2); + } + delete np; +} + + void LuaPerlinNoise::Register(lua_State *L) { lua_newtable(L); @@ -126,6 +146,8 @@ void LuaPerlinNoise::Register(lua_State *L) lua_pop(L, 1); lua_register(L, className, create_object); + + script_register_packer(L, className, packIn, packOut); } @@ -357,6 +379,35 @@ LuaPerlinNoiseMap *LuaPerlinNoiseMap::checkobject(lua_State *L, int narg) } +struct NoiseMapParams { + NoiseParams np; + s32 seed; + v3s16 size; +}; + +void *LuaPerlinNoiseMap::packIn(lua_State *L, int idx) +{ + LuaPerlinNoiseMap *o = checkobject(L, idx); + NoiseMapParams *ret = new NoiseMapParams(); + ret->np = o->noise->np; + ret->seed = o->noise->seed; + ret->size = v3s16(o->noise->sx, o->noise->sy, o->noise->sz); + return ret; +} + +void LuaPerlinNoiseMap::packOut(lua_State *L, void *ptr) +{ + NoiseMapParams *p = reinterpret_cast(ptr); + if (L) { + LuaPerlinNoiseMap *o = new LuaPerlinNoiseMap(&p->np, p->seed, p->size); + *(void **)(lua_newuserdata(L, sizeof(void *))) = o; + luaL_getmetatable(L, className); + lua_setmetatable(L, -2); + } + delete p; +} + + void LuaPerlinNoiseMap::Register(lua_State *L) { lua_newtable(L); @@ -382,6 +433,8 @@ void LuaPerlinNoiseMap::Register(lua_State *L) lua_pop(L, 1); lua_register(L, className, create_object); + + script_register_packer(L, className, packIn, packOut); } diff --git a/src/script/lua_api/l_noise.h b/src/script/lua_api/l_noise.h index 29ab41a31..5d34a479b 100644 --- a/src/script/lua_api/l_noise.h +++ b/src/script/lua_api/l_noise.h @@ -52,6 +52,9 @@ public: static LuaPerlinNoise *checkobject(lua_State *L, int narg); + static void *packIn(lua_State *L, int idx); + static void packOut(lua_State *L, void *ptr); + static void Register(lua_State *L); }; @@ -91,6 +94,9 @@ public: static LuaPerlinNoiseMap *checkobject(lua_State *L, int narg); + static void *packIn(lua_State *L, int idx); + static void packOut(lua_State *L, void *ptr); + static void Register(lua_State *L); }; diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index 42725e5d2..4b0b45887 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_internal.h" #include "common/c_converter.h" #include "common/c_content.h" +#include "common/c_packer.h" #include "cpp_api/s_base.h" #include "cpp_api/s_security.h" #include "scripting_server.h" @@ -526,6 +527,76 @@ int ModApiServer::l_notify_authentication_modified(lua_State *L) return 0; } +// do_async_callback(func, params, mod_origin) +int ModApiServer::l_do_async_callback(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ServerScripting *script = getScriptApi(L); + + luaL_checktype(L, 1, LUA_TFUNCTION); + luaL_checktype(L, 2, LUA_TTABLE); + luaL_checktype(L, 3, LUA_TSTRING); + + call_string_dump(L, 1); + size_t func_length; + const char *serialized_func_raw = lua_tolstring(L, -1, &func_length); + + PackedValue *param = script_pack(L, 2); + + std::string mod_origin = readParam(L, 3); + + u32 jobId = script->queueAsync( + std::string(serialized_func_raw, func_length), + param, mod_origin); + + lua_settop(L, 0); + lua_pushinteger(L, jobId); + return 1; +} + +// register_async_dofile(path) +int ModApiServer::l_register_async_dofile(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + std::string path = readParam(L, 1); + CHECK_SECURE_PATH(L, path.c_str(), false); + + // Find currently running mod name (only at init time) + lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); + if (!lua_isstring(L, -1)) + return 0; + std::string modname = readParam(L, -1); + + getServer(L)->m_async_init_files.emplace_back(modname, path); + lua_pushboolean(L, true); + return 1; +} + +// serialize_roundtrip(value) +// Meant for unit testing the packer from Lua +int ModApiServer::l_serialize_roundtrip(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + int top = lua_gettop(L); + auto *pv = script_pack(L, 1); + if (top != lua_gettop(L)) + throw LuaError("stack values leaked"); + +#ifndef NDEBUG + script_dump_packed(pv); +#endif + + top = lua_gettop(L); + script_unpack(L, pv); + delete pv; + if (top + 1 != lua_gettop(L)) + throw LuaError("stack values leaked"); + + return 1; +} + void ModApiServer::Initialize(lua_State *L, int top) { API_FCT(request_shutdown); @@ -559,4 +630,18 @@ void ModApiServer::Initialize(lua_State *L, int top) API_FCT(remove_player); API_FCT(unban_player_or_ip); API_FCT(notify_authentication_modified); + + API_FCT(do_async_callback); + API_FCT(register_async_dofile); + API_FCT(serialize_roundtrip); +} + +void ModApiServer::InitializeAsync(lua_State *L, int top) +{ + API_FCT(get_worldpath); + API_FCT(is_singleplayer); + + API_FCT(get_current_modname); + API_FCT(get_modpath); + API_FCT(get_modnames); } diff --git a/src/script/lua_api/l_server.h b/src/script/lua_api/l_server.h index f05c0b7c9..a4f38c34e 100644 --- a/src/script/lua_api/l_server.h +++ b/src/script/lua_api/l_server.h @@ -106,6 +106,16 @@ private: // notify_authentication_modified(name) static int l_notify_authentication_modified(lua_State *L); + // do_async_callback(func, params, mod_origin) + static int l_do_async_callback(lua_State *L); + + // register_async_dofile(path) + static int l_register_async_dofile(lua_State *L); + + // serialize_roundtrip(obj) + static int l_serialize_roundtrip(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_util.cpp b/src/script/lua_api/l_util.cpp index b04f26fda..97068ce4c 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -671,6 +671,9 @@ void ModApiUtil::InitializeAsync(lua_State *L, int top) API_FCT(cpdir); API_FCT(mvdir); API_FCT(get_dir_list); + API_FCT(safe_file_write); + + API_FCT(request_insecure_environment); API_FCT(encode_base64); API_FCT(decode_base64); @@ -680,6 +683,8 @@ void ModApiUtil::InitializeAsync(lua_State *L, int top) API_FCT(colorspec_to_colorstring); API_FCT(colorspec_to_bytes); + API_FCT(encode_png); + API_FCT(get_last_run_mod); API_FCT(set_last_run_mod); diff --git a/src/script/lua_api/l_util.h b/src/script/lua_api/l_util.h index fcf8a1057..cc5563577 100644 --- a/src/script/lua_api/l_util.h +++ b/src/script/lua_api/l_util.h @@ -129,6 +129,4 @@ public: static void Initialize(lua_State *L, int top); static void InitializeAsync(lua_State *L, int top); static void InitializeClient(lua_State *L, int top); - - static void InitializeAsync(AsyncEngine &engine); }; diff --git a/src/script/lua_api/l_vmanip.cpp b/src/script/lua_api/l_vmanip.cpp index a3ece627c..6187a47db 100644 --- a/src/script/lua_api/l_vmanip.cpp +++ b/src/script/lua_api/l_vmanip.cpp @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_internal.h" #include "common/c_content.h" #include "common/c_converter.h" +#include "common/c_packer.h" #include "emerge.h" #include "environment.h" #include "map.h" @@ -45,6 +46,8 @@ int LuaVoxelManip::l_read_from_map(lua_State *L) LuaVoxelManip *o = checkobject(L, 1); MMVManip *vm = o->vm; + if (vm->isOrphan()) + return 0; v3s16 bp1 = getNodeBlockPos(check_v3s16(L, 2)); v3s16 bp2 = getNodeBlockPos(check_v3s16(L, 3)); @@ -429,6 +432,34 @@ LuaVoxelManip *LuaVoxelManip::checkobject(lua_State *L, int narg) return *(LuaVoxelManip **)ud; // unbox pointer } +void *LuaVoxelManip::packIn(lua_State *L, int idx) +{ + LuaVoxelManip *o = checkobject(L, idx); + + if (o->is_mapgen_vm) + throw LuaError("nope"); + return o->vm->clone(); +} + +void LuaVoxelManip::packOut(lua_State *L, void *ptr) +{ + MMVManip *vm = reinterpret_cast(ptr); + if (!L) { + delete vm; + return; + } + + // Associate vmanip with map if the Lua env has one + Environment *env = getEnv(L); + if (env) + vm->reparent(&(env->getMap())); + + LuaVoxelManip *o = new LuaVoxelManip(vm, false); + *(void **)(lua_newuserdata(L, sizeof(void *))) = o; + luaL_getmetatable(L, className); + lua_setmetatable(L, -2); +} + void LuaVoxelManip::Register(lua_State *L) { lua_newtable(L); @@ -455,6 +486,8 @@ void LuaVoxelManip::Register(lua_State *L) // Can be created from Lua (VoxelManip()) lua_register(L, className, create_object); + + script_register_packer(L, className, packIn, packOut); } const char LuaVoxelManip::className[] = "VoxelManip"; diff --git a/src/script/lua_api/l_vmanip.h b/src/script/lua_api/l_vmanip.h index 5113070dc..005133335 100644 --- a/src/script/lua_api/l_vmanip.h +++ b/src/script/lua_api/l_vmanip.h @@ -75,5 +75,8 @@ public: static LuaVoxelManip *checkobject(lua_State *L, int narg); + static void *packIn(lua_State *L, int idx); + static void packOut(lua_State *L, void *ptr); + static void Register(lua_State *L); }; diff --git a/src/script/scripting_server.cpp b/src/script/scripting_server.cpp index 85411ded4..5b99468dc 100644 --- a/src/script/scripting_server.cpp +++ b/src/script/scripting_server.cpp @@ -47,11 +47,12 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_storage.h" extern "C" { -#include "lualib.h" +#include } ServerScripting::ServerScripting(Server* server): - ScriptApiBase(ScriptingType::Server) + ScriptApiBase(ScriptingType::Server), + asyncEngine(server) { setGameDef(server); @@ -88,6 +89,47 @@ ServerScripting::ServerScripting(Server* server): infostream << "SCRIPTAPI: Initialized game modules" << std::endl; } +void ServerScripting::initAsync() +{ + // Save globals to transfer + { + lua_State *L = getStack(); + lua_getglobal(L, "core"); + luaL_checktype(L, -1, LUA_TTABLE); + lua_getfield(L, -1, "get_globals_to_transfer"); + lua_call(L, 0, 1); + luaL_checktype(L, -1, LUA_TSTRING); + getServer()->m_async_globals_data.set(readParam(L, -1)); + lua_pushnil(L); + lua_setfield(L, -3, "get_globals_to_transfer"); // unset function too + lua_pop(L, 2); // pop 'core', return value + } + + infostream << "SCRIPTAPI: Initializing async engine" << std::endl; + asyncEngine.registerStateInitializer(InitializeAsync); + asyncEngine.registerStateInitializer(ModApiUtil::InitializeAsync); + asyncEngine.registerStateInitializer(ModApiCraft::InitializeAsync); + asyncEngine.registerStateInitializer(ModApiItemMod::InitializeAsync); + asyncEngine.registerStateInitializer(ModApiServer::InitializeAsync); + // not added: ModApiMapgen is a minefield for thread safety + // not added: ModApiHttp async api can't really work together with our jobs + // not added: ModApiStorage is probably not thread safe(?) + + asyncEngine.initialize(0); +} + +void ServerScripting::stepAsync() +{ + asyncEngine.step(getStack()); +} + +u32 ServerScripting::queueAsync(std::string &&serialized_func, + PackedValue *param, const std::string &mod_origin) +{ + return asyncEngine.queueAsyncJob(std::move(serialized_func), + param, mod_origin); +} + void ServerScripting::InitializeModApi(lua_State *L, int top) { // Register reference classes (userdata) @@ -125,3 +167,24 @@ void ServerScripting::InitializeModApi(lua_State *L, int top) ModApiStorage::Initialize(L, top); ModApiChannels::Initialize(L, top); } + +void ServerScripting::InitializeAsync(lua_State *L, int top) +{ + // classes + LuaItemStack::Register(L); + LuaPerlinNoise::Register(L); + LuaPerlinNoiseMap::Register(L); + LuaPseudoRandom::Register(L); + LuaPcgRandom::Register(L); + LuaSecureRandom::Register(L); + LuaVoxelManip::Register(L); + LuaSettings::Register(L); + + // globals data + lua_getglobal(L, "core"); + luaL_checktype(L, -1, LUA_TTABLE); + std::string s = ModApiBase::getServer(L)->m_async_globals_data.get(); + lua_pushlstring(L, s.c_str(), s.size()); + lua_setfield(L, -2, "transferred_globals"); + lua_pop(L, 1); // pop 'core' +} diff --git a/src/script/scripting_server.h b/src/script/scripting_server.h index bf06ab197..9803397c5 100644 --- a/src/script/scripting_server.h +++ b/src/script/scripting_server.h @@ -27,6 +27,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "cpp_api/s_player.h" #include "cpp_api/s_server.h" #include "cpp_api/s_security.h" +#include "cpp_api/s_async.h" + +struct PackedValue; /*****************************************************************************/ /* Scripting <-> Server Game Interface */ @@ -48,6 +51,20 @@ public: // use ScriptApiBase::loadMod() to load mods + // Initialize async engine, call this AFTER loading all mods + void initAsync(); + + // Global step handler to collect async results + void stepAsync(); + + // Pass job to async threads + u32 queueAsync(std::string &&serialized_func, + PackedValue *param, const std::string &mod_origin); + private: void InitializeModApi(lua_State *L, int top); + + static void InitializeAsync(lua_State *L, int top); + + AsyncEngine asyncEngine; }; diff --git a/src/server.cpp b/src/server.cpp index dec6cf44c..c9cd4e398 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -243,6 +243,7 @@ Server::Server( m_clients(m_con), m_admin_chat(iface), m_on_shutdown_errmsg(on_shutdown_errmsg), + m_async_globals_data(""), m_modchannel_mgr(new ModChannelMgr()) { if (m_path_world.empty()) @@ -480,6 +481,9 @@ void Server::init() // Give environment reference to scripting api m_script->initializeEnvironment(m_env); + // Do this after regular script init is done + m_script->initAsync(); + // Register us to receive map edit events servermap->addEventReceiver(this); diff --git a/src/server.h b/src/server.h index bd799c313..5090a3579 100644 --- a/src/server.h +++ b/src/server.h @@ -292,7 +292,7 @@ public: virtual const std::vector &getMods() const; virtual const ModSpec* getModSpec(const std::string &modname) const; - std::string getBuiltinLuaPath(); + static std::string getBuiltinLuaPath(); virtual std::string getWorldPath() const { return m_path_world; } inline bool isSingleplayer() const @@ -385,6 +385,12 @@ public: static bool migrateModStorageDatabase(const GameParams &game_params, const Settings &cmd_args); + // Lua files registered for init of async env, pair of modname + path + std::vector> m_async_init_files; + + // Serialized data transferred into async envs at init time + MutexedVariable m_async_globals_data; + // Bind address Address m_bind_addr; diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index f8d84604b..493210744 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -1481,6 +1481,8 @@ void ServerEnvironment::step(float dtime) */ m_script->environment_Step(dtime); + m_script->stepAsync(); + /* Step active objects */ diff --git a/src/util/basic_macros.h b/src/util/basic_macros.h index 334e342e0..3910c6185 100644 --- a/src/util/basic_macros.h +++ b/src/util/basic_macros.h @@ -29,13 +29,19 @@ with this program; if not, write to the Free Software Foundation, Inc., #define CONTAINS(c, v) (std::find((c).begin(), (c).end(), (v)) != (c).end()) // To disable copy constructors and assignment operations for some class -// 'Foobar', add the macro DISABLE_CLASS_COPY(Foobar) as a private member. +// 'Foobar', add the macro DISABLE_CLASS_COPY(Foobar) in the class definition. // Note this also disables copying for any classes derived from 'Foobar' as well // as classes having a 'Foobar' member. #define DISABLE_CLASS_COPY(C) \ C(const C &) = delete; \ C &operator=(const C &) = delete; +// If you have used DISABLE_CLASS_COPY with a class but still want to permit moving +// use this macro to add the default move constructors back. +#define ALLOW_CLASS_MOVE(C) \ + C(C &&other) = default; \ + C &operator=(C &&) = default; + #ifndef _MSC_VER #define UNUSED_ATTRIBUTE __attribute__ ((unused)) #else -- cgit v1.2.3