From 53dca4f95fb48d2a16e1f26f01515d4811aab78d Mon Sep 17 00:00:00 2001 From: Dmitry Marakasov Date: Sat, 15 May 2021 11:15:03 +0300 Subject: Use --image-base instead of -Ttext-segment for lld linker on FreeBSD (#9367) (#11263) --- src/CMakeLists.txt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9526e88f9..2a2adfaf0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -758,7 +758,16 @@ else() check_c_source_compiles("#ifndef __aarch64__\n#error\n#endif\nint main(){}" IS_AARCH64) if(IS_AARCH64) # Move text segment below LuaJIT's 47-bit limit (see issue #9367) - SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-Ttext-segment=0x200000000") + 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 + # 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 + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--image-base=0x200000000") + else() + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-Ttext-segment=0x200000000") + endif() endif() endif() -- cgit v1.2.3 From b56a028d6bc101cb01f5bdfe07053ac1af9fb016 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 16 May 2021 14:05:43 +0200 Subject: Fix curl_timeout being ignored for Lua HTTP fetches --- src/script/lua_api/l_http.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/script/lua_api/l_http.cpp b/src/script/lua_api/l_http.cpp index 5ea3b3f99..751ec9837 100644 --- a/src/script/lua_api/l_http.cpp +++ b/src/script/lua_api/l_http.cpp @@ -42,12 +42,10 @@ void ModApiHttp::read_http_fetch_request(lua_State *L, HTTPFetchRequest &req) req.caller = httpfetch_caller_alloc_secure(); getstringfield(L, 1, "url", req.url); - lua_getfield(L, 1, "user_agent"); - if (lua_isstring(L, -1)) - req.useragent = getstringfield_default(L, 1, "user_agent", ""); - lua_pop(L, 1); + getstringfield(L, 1, "user_agent", req.useragent); req.multipart = getboolfield_default(L, 1, "multipart", false); - req.timeout = getintfield_default(L, 1, "timeout", 3) * 1000; + if (getintfield(L, 1, "timeout", req.timeout)) + req.timeout *= 1000; lua_getfield(L, 1, "method"); if (lua_isstring(L, -1)) { -- cgit v1.2.3 From 93f43c890bf53dcdfccdd87601bea60e43862861 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Fri, 21 May 2021 17:26:02 +0200 Subject: GUIEditBox: Allow selecting and copying read-only texts --- src/gui/guiEditBox.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/gui/guiEditBox.cpp b/src/gui/guiEditBox.cpp index ba548aa2d..43afb6e3e 100644 --- a/src/gui/guiEditBox.cpp +++ b/src/gui/guiEditBox.cpp @@ -232,10 +232,6 @@ bool GUIEditBox::OnEvent(const SEvent &event) bool GUIEditBox::processKey(const SEvent &event) { - if (!m_writable) { - return false; - } - if (!event.KeyInput.PressedDown) return false; @@ -531,6 +527,9 @@ bool GUIEditBox::onKeyControlX(const SEvent &event, s32 &mark_begin, s32 &mark_e // First copy to clipboard onKeyControlC(event); + if (!m_writable) + return false; + if (m_passwordbox || !m_operator || m_mark_begin == m_mark_end) return false; @@ -556,7 +555,7 @@ bool GUIEditBox::onKeyControlX(const SEvent &event, s32 &mark_begin, s32 &mark_e bool GUIEditBox::onKeyControlV(const SEvent &event, s32 &mark_begin, s32 &mark_end) { - if (!isEnabled()) + if (!isEnabled() || !m_writable) return false; // paste from the clipboard @@ -602,7 +601,7 @@ bool GUIEditBox::onKeyControlV(const SEvent &event, s32 &mark_begin, s32 &mark_e bool GUIEditBox::onKeyBack(const SEvent &event, s32 &mark_begin, s32 &mark_end) { - if (!isEnabled() || Text.empty()) + if (!isEnabled() || Text.empty() || !m_writable) return false; core::stringw s; @@ -640,7 +639,7 @@ bool GUIEditBox::onKeyBack(const SEvent &event, s32 &mark_begin, s32 &mark_end) bool GUIEditBox::onKeyDelete(const SEvent &event, s32 &mark_begin, s32 &mark_end) { - if (!isEnabled() || Text.empty()) + if (!isEnabled() || Text.empty() || !m_writable) return false; core::stringw s; -- cgit v1.2.3 From 673c29f7ea6735c15596a118fe7e6100d4a466e0 Mon Sep 17 00:00:00 2001 From: savilli <78875209+savilli@users.noreply.github.com> Date: Mon, 24 May 2021 19:40:35 +0200 Subject: Fix client crash on when con::PeerNotFoundException is thrown (#11286) --- src/client/clientlauncher.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp index 6db5f2e70..dbf1d1cd1 100644 --- a/src/client/clientlauncher.cpp +++ b/src/client/clientlauncher.cpp @@ -277,14 +277,6 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args) chat_backend, &reconnect_requested ); - m_rendering_engine->get_scene_manager()->clear(); - -#ifdef HAVE_TOUCHSCREENGUI - delete g_touchscreengui; - g_touchscreengui = NULL; - receiver->m_touchscreengui = NULL; -#endif - } //try catch (con::PeerNotFoundException &e) { error_message = gettext("Connection error (timed out?)"); @@ -300,6 +292,14 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args) } #endif + m_rendering_engine->get_scene_manager()->clear(); + +#ifdef HAVE_TOUCHSCREENGUI + delete g_touchscreengui; + g_touchscreengui = NULL; + receiver->m_touchscreengui = NULL; +#endif + // If no main menu, show error and exit if (skip_main_menu) { if (!error_message.empty()) { -- cgit v1.2.3 From ff48619a857da2158768314f08191994b33ffee9 Mon Sep 17 00:00:00 2001 From: Wuzzy Date: Tue, 25 May 2021 23:44:41 +0200 Subject: Fix cloud fog being broken for high clouds --- src/client/clouds.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/clouds.cpp b/src/client/clouds.cpp index 5008047af..383a1d799 100644 --- a/src/client/clouds.cpp +++ b/src/client/clouds.cpp @@ -352,7 +352,7 @@ void Clouds::update(const v3f &camera_p, const video::SColorf &color_diffuse) // is the camera inside the cloud mesh? m_camera_inside_cloud = false; // default if (m_enable_3d) { - float camera_height = camera_p.Y; + float camera_height = camera_p.Y - BS * m_camera_offset.Y; if (camera_height >= m_box.MinEdge.Y && camera_height <= m_box.MaxEdge.Y) { v2f camera_in_noise; -- cgit v1.2.3 From 5bf72468f3a0925a9fc3c9acacf3f6e138bff35e Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Sat, 29 May 2021 11:44:48 +0200 Subject: UnitSAO: Prevent circular attachments --- doc/lua_api.txt | 2 ++ src/server/unit_sao.cpp | 13 +++++++++++++ 2 files changed, 15 insertions(+) (limited to 'src') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index ef86efcc1..6c7ae0fb5 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -6314,6 +6314,8 @@ object you are working with still exists. Default `{x=0, y=0, z=0}` * `forced_visible`: Boolean to control whether the attached entity should appear in first person. Default `false`. + * This command may fail silently (do nothing) when it would result + in circular attachments. * `get_attach()`: returns parent, bone, position, rotation, forced_visible, or nil if it isn't attached. * `get_children()`: returns a list of ObjectRefs that are attached to the diff --git a/src/server/unit_sao.cpp b/src/server/unit_sao.cpp index fa6c8f0f4..acbdd478a 100644 --- a/src/server/unit_sao.cpp +++ b/src/server/unit_sao.cpp @@ -124,6 +124,19 @@ void UnitSAO::sendOutdatedData() void UnitSAO::setAttachment(int parent_id, const std::string &bone, v3f position, v3f rotation, bool force_visible) { + auto *obj = parent_id ? m_env->getActiveObject(parent_id) : nullptr; + if (obj) { + // Do checks to avoid circular references + // The chain of wanted parent must not refer or contain "this" + for (obj = obj->getParent(); obj; obj = obj->getParent()) { + if (obj == this) { + warningstream << "Mod bug: Attempted to attach object " << m_id << " to parent " + << parent_id << " but former is an (in)direct parent of latter." << std::endl; + return; + } + } + } + // Attachments need to be handled on both the server and client. // If we just attach on the server, we can only copy the position of the parent. // Attachments are still sent to clients at an interval so players might see them -- cgit v1.2.3 From a12017c564782b2645b3020a6b998b116789c7cc Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 25 May 2021 20:03:05 +0200 Subject: Provide exact error message if postgres connection string missing --- src/database/database-postgresql.cpp | 27 ++++++++++++++++----------- src/database/database-postgresql.h | 2 +- 2 files changed, 17 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/database/database-postgresql.cpp b/src/database/database-postgresql.cpp index e1bb39928..29ecd4223 100644 --- a/src/database/database-postgresql.cpp +++ b/src/database/database-postgresql.cpp @@ -39,20 +39,24 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "remoteplayer.h" #include "server/player_sao.h" -Database_PostgreSQL::Database_PostgreSQL(const std::string &connect_string) : +Database_PostgreSQL::Database_PostgreSQL(const std::string &connect_string, + const char *type) : m_connect_string(connect_string) { if (m_connect_string.empty()) { - throw SettingNotFoundException( - "Set pgsql_connection string in world.mt to " + // Use given type to reference the exact setting in the error message + std::string s = type; + std::string msg = + "Set pgsql" + s + "_connection string in world.mt to " "use the postgresql backend\n" "Notes:\n" - "pgsql_connection has the following form: \n" - "\tpgsql_connection = host=127.0.0.1 port=5432 user=mt_user " - "password=mt_password dbname=minetest_world\n" + "pgsql" + s + "_connection has the following form: \n" + "\tpgsql" + s + "_connection = host=127.0.0.1 port=5432 " + "user=mt_user password=mt_password dbname=minetest" + s + "\n" "mt_user should have CREATE TABLE, INSERT, SELECT, UPDATE and " - "DELETE rights on the database.\n" - "Don't create mt_user as a SUPERUSER!"); + "DELETE rights on the database. " + "Don't create mt_user as a SUPERUSER!"; + throw SettingNotFoundException(msg); } } @@ -166,7 +170,7 @@ void Database_PostgreSQL::rollback() } MapDatabasePostgreSQL::MapDatabasePostgreSQL(const std::string &connect_string): - Database_PostgreSQL(connect_string), + Database_PostgreSQL(connect_string, ""), MapDatabase() { connectToDatabase(); @@ -315,7 +319,7 @@ void MapDatabasePostgreSQL::listAllLoadableBlocks(std::vector &dst) * Player Database */ PlayerDatabasePostgreSQL::PlayerDatabasePostgreSQL(const std::string &connect_string): - Database_PostgreSQL(connect_string), + Database_PostgreSQL(connect_string, "_player"), PlayerDatabase() { connectToDatabase(); @@ -637,7 +641,8 @@ void PlayerDatabasePostgreSQL::listPlayers(std::vector &res) } AuthDatabasePostgreSQL::AuthDatabasePostgreSQL(const std::string &connect_string) : - Database_PostgreSQL(connect_string), AuthDatabase() + Database_PostgreSQL(connect_string, "_auth"), + AuthDatabase() { connectToDatabase(); } diff --git a/src/database/database-postgresql.h b/src/database/database-postgresql.h index f47deda33..81b4a2b10 100644 --- a/src/database/database-postgresql.h +++ b/src/database/database-postgresql.h @@ -29,7 +29,7 @@ class Settings; class Database_PostgreSQL: public Database { public: - Database_PostgreSQL(const std::string &connect_string); + Database_PostgreSQL(const std::string &connect_string, const char *type); ~Database_PostgreSQL(); void beginSave(); -- cgit v1.2.3 From a0047d6edcb300afeca16a2a63c5a112082736f3 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 25 May 2021 20:20:16 +0200 Subject: script: Replace calls to depreated luaL_openlib --- src/script/lua_api/l_areastore.cpp | 2 +- src/script/lua_api/l_camera.cpp | 2 +- src/script/lua_api/l_env.cpp | 2 +- src/script/lua_api/l_inventory.cpp | 2 +- src/script/lua_api/l_item.cpp | 2 +- src/script/lua_api/l_itemstackmeta.cpp | 2 +- src/script/lua_api/l_localplayer.cpp | 2 +- src/script/lua_api/l_minimap.cpp | 2 +- src/script/lua_api/l_modchannels.cpp | 2 +- src/script/lua_api/l_nodemeta.cpp | 4 ++-- src/script/lua_api/l_nodetimer.cpp | 2 +- src/script/lua_api/l_noise.cpp | 10 +++++----- src/script/lua_api/l_object.cpp | 2 +- src/script/lua_api/l_playermeta.cpp | 2 +- src/script/lua_api/l_settings.cpp | 2 +- src/script/lua_api/l_storage.cpp | 2 +- src/script/lua_api/l_vmanip.cpp | 2 +- 17 files changed, 22 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/script/lua_api/l_areastore.cpp b/src/script/lua_api/l_areastore.cpp index 908c766b0..45724e604 100644 --- a/src/script/lua_api/l_areastore.cpp +++ b/src/script/lua_api/l_areastore.cpp @@ -372,7 +372,7 @@ void LuaAreaStore::Register(lua_State *L) lua_pop(L, 1); // drop metatable - luaL_openlib(L, 0, methods, 0); // fill methodtable + luaL_register(L, nullptr, methods); // fill methodtable lua_pop(L, 1); // drop methodtable // Can be created from Lua (AreaStore()) diff --git a/src/script/lua_api/l_camera.cpp b/src/script/lua_api/l_camera.cpp index 40251154c..d85d16283 100644 --- a/src/script/lua_api/l_camera.cpp +++ b/src/script/lua_api/l_camera.cpp @@ -219,7 +219,7 @@ void LuaCamera::Register(lua_State *L) lua_pop(L, 1); - luaL_openlib(L, 0, methods, 0); + luaL_register(L, nullptr, methods); lua_pop(L, 1); } diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index c75fc8dc7..39c229ee4 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -228,7 +228,7 @@ void LuaRaycast::Register(lua_State *L) lua_pop(L, 1); - luaL_openlib(L, 0, methods, 0); + luaL_register(L, nullptr, methods); lua_pop(L, 1); lua_register(L, className, create_object); diff --git a/src/script/lua_api/l_inventory.cpp b/src/script/lua_api/l_inventory.cpp index 434d0a76c..0dd418462 100644 --- a/src/script/lua_api/l_inventory.cpp +++ b/src/script/lua_api/l_inventory.cpp @@ -456,7 +456,7 @@ void InvRef::Register(lua_State *L) lua_pop(L, 1); // drop metatable - luaL_openlib(L, 0, methods, 0); // fill methodtable + luaL_register(L, nullptr, methods); // fill methodtable lua_pop(L, 1); // drop methodtable // Cannot be created from Lua diff --git a/src/script/lua_api/l_item.cpp b/src/script/lua_api/l_item.cpp index 9e0da4034..794d8a6e5 100644 --- a/src/script/lua_api/l_item.cpp +++ b/src/script/lua_api/l_item.cpp @@ -483,7 +483,7 @@ void LuaItemStack::Register(lua_State *L) lua_pop(L, 1); // drop metatable - luaL_openlib(L, 0, methods, 0); // fill methodtable + luaL_register(L, nullptr, methods); // fill methodtable lua_pop(L, 1); // drop methodtable // Can be created from Lua (ItemStack(itemstack or itemstring or table or nil)) diff --git a/src/script/lua_api/l_itemstackmeta.cpp b/src/script/lua_api/l_itemstackmeta.cpp index d1ba1bda4..739fb9221 100644 --- a/src/script/lua_api/l_itemstackmeta.cpp +++ b/src/script/lua_api/l_itemstackmeta.cpp @@ -114,7 +114,7 @@ void ItemStackMetaRef::Register(lua_State *L) lua_pop(L, 1); // drop metatable - luaL_openlib(L, 0, methods, 0); // fill methodtable + luaL_register(L, nullptr, methods); // fill methodtable lua_pop(L, 1); // drop methodtable // Cannot be created from Lua diff --git a/src/script/lua_api/l_localplayer.cpp b/src/script/lua_api/l_localplayer.cpp index 33fa27c8b..59d9ea5f8 100644 --- a/src/script/lua_api/l_localplayer.cpp +++ b/src/script/lua_api/l_localplayer.cpp @@ -446,7 +446,7 @@ void LuaLocalPlayer::Register(lua_State *L) lua_pop(L, 1); // Drop metatable - luaL_openlib(L, 0, methods, 0); // fill methodtable + luaL_register(L, nullptr, methods); // fill methodtable lua_pop(L, 1); // Drop methodtable } diff --git a/src/script/lua_api/l_minimap.cpp b/src/script/lua_api/l_minimap.cpp index 3bbb6e5e3..a135e0bd5 100644 --- a/src/script/lua_api/l_minimap.cpp +++ b/src/script/lua_api/l_minimap.cpp @@ -211,7 +211,7 @@ void LuaMinimap::Register(lua_State *L) lua_pop(L, 1); // drop metatable - luaL_openlib(L, 0, methods, 0); // fill methodtable + luaL_register(L, nullptr, methods); // fill methodtable lua_pop(L, 1); // drop methodtable } diff --git a/src/script/lua_api/l_modchannels.cpp b/src/script/lua_api/l_modchannels.cpp index 0485b276a..931c2749c 100644 --- a/src/script/lua_api/l_modchannels.cpp +++ b/src/script/lua_api/l_modchannels.cpp @@ -107,7 +107,7 @@ void ModChannelRef::Register(lua_State *L) lua_pop(L, 1); // Drop metatable - luaL_openlib(L, 0, methods, 0); // fill methodtable + luaL_register(L, nullptr, methods); // fill methodtable lua_pop(L, 1); // Drop methodtable } diff --git a/src/script/lua_api/l_nodemeta.cpp b/src/script/lua_api/l_nodemeta.cpp index 57052cb42..60d14f8f2 100644 --- a/src/script/lua_api/l_nodemeta.cpp +++ b/src/script/lua_api/l_nodemeta.cpp @@ -234,7 +234,7 @@ void NodeMetaRef::RegisterCommon(lua_State *L) void NodeMetaRef::Register(lua_State *L) { RegisterCommon(L); - luaL_openlib(L, 0, methodsServer, 0); // fill methodtable + luaL_register(L, nullptr, methodsServer); // fill methodtable lua_pop(L, 1); // drop methodtable } @@ -260,7 +260,7 @@ const luaL_Reg NodeMetaRef::methodsServer[] = { void NodeMetaRef::RegisterClient(lua_State *L) { RegisterCommon(L); - luaL_openlib(L, 0, methodsClient, 0); // fill methodtable + luaL_register(L, nullptr, methodsClient); // fill methodtable lua_pop(L, 1); // drop methodtable } diff --git a/src/script/lua_api/l_nodetimer.cpp b/src/script/lua_api/l_nodetimer.cpp index c2df52c05..8a302149f 100644 --- a/src/script/lua_api/l_nodetimer.cpp +++ b/src/script/lua_api/l_nodetimer.cpp @@ -122,7 +122,7 @@ void NodeTimerRef::Register(lua_State *L) lua_pop(L, 1); // drop metatable - luaL_openlib(L, 0, methods, 0); // fill methodtable + luaL_register(L, nullptr, methods); // fill methodtable lua_pop(L, 1); // drop methodtable // Cannot be created from Lua diff --git a/src/script/lua_api/l_noise.cpp b/src/script/lua_api/l_noise.cpp index e0861126a..f43ba837a 100644 --- a/src/script/lua_api/l_noise.cpp +++ b/src/script/lua_api/l_noise.cpp @@ -122,7 +122,7 @@ void LuaPerlinNoise::Register(lua_State *L) lua_pop(L, 1); - luaL_openlib(L, 0, methods, 0); + luaL_register(L, nullptr, methods); lua_pop(L, 1); lua_register(L, className, create_object); @@ -380,7 +380,7 @@ void LuaPerlinNoiseMap::Register(lua_State *L) lua_pop(L, 1); - luaL_openlib(L, 0, methods, 0); + luaL_register(L, nullptr, methods); lua_pop(L, 1); lua_register(L, className, create_object); @@ -485,7 +485,7 @@ void LuaPseudoRandom::Register(lua_State *L) lua_pop(L, 1); - luaL_openlib(L, 0, methods, 0); + luaL_register(L, nullptr, methods); lua_pop(L, 1); lua_register(L, className, create_object); @@ -584,7 +584,7 @@ void LuaPcgRandom::Register(lua_State *L) lua_pop(L, 1); - luaL_openlib(L, 0, methods, 0); + luaL_register(L, nullptr, methods); lua_pop(L, 1); lua_register(L, className, create_object); @@ -699,7 +699,7 @@ void LuaSecureRandom::Register(lua_State *L) lua_pop(L, 1); - luaL_openlib(L, 0, methods, 0); + luaL_register(L, nullptr, methods); lua_pop(L, 1); lua_register(L, className, create_object); diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index 8ae99b929..8e308cd9e 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -2311,7 +2311,7 @@ void ObjectRef::Register(lua_State *L) lua_pop(L, 1); // drop metatable - luaL_openlib(L, 0, methods, 0); // fill methodtable + luaL_register(L, nullptr, methods); // fill methodtable lua_pop(L, 1); // drop methodtable } diff --git a/src/script/lua_api/l_playermeta.cpp b/src/script/lua_api/l_playermeta.cpp index 558672e38..2706c99df 100644 --- a/src/script/lua_api/l_playermeta.cpp +++ b/src/script/lua_api/l_playermeta.cpp @@ -97,7 +97,7 @@ void PlayerMetaRef::Register(lua_State *L) lua_pop(L, 1); // drop metatable - luaL_openlib(L, 0, methods, 0); + luaL_register(L, nullptr, methods); lua_pop(L, 1); // Cannot be created from Lua diff --git a/src/script/lua_api/l_settings.cpp b/src/script/lua_api/l_settings.cpp index a82073ed4..14398dda2 100644 --- a/src/script/lua_api/l_settings.cpp +++ b/src/script/lua_api/l_settings.cpp @@ -334,7 +334,7 @@ void LuaSettings::Register(lua_State* L) lua_pop(L, 1); // drop metatable - luaL_openlib(L, 0, methods, 0); // fill methodtable + luaL_register(L, nullptr, methods); // fill methodtable lua_pop(L, 1); // drop methodtable // Can be created from Lua (Settings(filename)) diff --git a/src/script/lua_api/l_storage.cpp b/src/script/lua_api/l_storage.cpp index cba34fb63..978b315d5 100644 --- a/src/script/lua_api/l_storage.cpp +++ b/src/script/lua_api/l_storage.cpp @@ -110,7 +110,7 @@ void StorageRef::Register(lua_State *L) lua_pop(L, 1); // drop metatable - luaL_openlib(L, 0, methods, 0); // fill methodtable + luaL_register(L, nullptr, methods); // fill methodtable lua_pop(L, 1); // drop methodtable } diff --git a/src/script/lua_api/l_vmanip.cpp b/src/script/lua_api/l_vmanip.cpp index b99b1d98c..e040e545b 100644 --- a/src/script/lua_api/l_vmanip.cpp +++ b/src/script/lua_api/l_vmanip.cpp @@ -450,7 +450,7 @@ void LuaVoxelManip::Register(lua_State *L) lua_pop(L, 1); // drop metatable - luaL_openlib(L, 0, methods, 0); // fill methodtable + luaL_register(L, nullptr, methods); // fill methodtable lua_pop(L, 1); // drop methodtable // Can be created from Lua (VoxelManip()) -- cgit v1.2.3 From 758e3aa1ca541c99e8c7824a2465b3bda7394d20 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 25 May 2021 21:03:51 +0200 Subject: Fix background color of formspec text fields --- src/gui/guiFormSpecMenu.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index c6435804f..09b004f8f 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -1577,11 +1577,10 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec, } e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); - e->setDrawBorder(style.getBool(StyleSpec::BORDER, true)); e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); - if (style.get(StyleSpec::BGCOLOR, "") == "transparent") { - e->setDrawBackground(false); - } + bool border = style.getBool(StyleSpec::BORDER, true); + e->setDrawBorder(border); + e->setDrawBackground(border); e->setOverrideFont(style.getFont()); e->drop(); -- cgit v1.2.3 From f30dcdb504adb02724e3a5faa30a951eb907b33f Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sat, 29 May 2021 19:08:16 +0200 Subject: Fix procession ordering issue in content_cao --- src/client/content_cao.cpp | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index 6c7559364..2e58e19cf 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -346,18 +346,6 @@ void GenericCAO::initialize(const std::string &data) infostream<<"GenericCAO: Got init data"<getLocalPlayer(); - if (player && strcmp(player->getName(), m_name.c_str()) == 0) { - m_is_local_player = true; - m_is_visible = false; - player->setCAO(this); - - m_prop.show_on_minimap = false; - } - } - m_enable_shaders = g_settings->getBool("enable_shaders"); } @@ -380,6 +368,16 @@ void GenericCAO::processInitData(const std::string &data) m_rotation = readV3F32(is); m_hp = readU16(is); + if (m_is_player) { + // Check if it's the current player + LocalPlayer *player = m_env->getLocalPlayer(); + if (player && strcmp(player->getName(), m_name.c_str()) == 0) { + m_is_local_player = true; + m_is_visible = false; + player->setCAO(this); + } + } + const u8 num_messages = readU8(is); for (int i = 0; i < num_messages; i++) { -- cgit v1.2.3 From 89f3991351185b365ccd10525e74d35d7bb2da46 Mon Sep 17 00:00:00 2001 From: Lars Müller <34514239+appgurueu@users.noreply.github.com> Date: Sun, 30 May 2021 20:23:12 +0200 Subject: Fix base64 validation and add unittests (#10515) Implement proper padding character checks --- doc/client_lua_api.txt | 4 +- doc/lua_api.txt | 4 +- src/unittest/test_utilities.cpp | 93 +++++++++++++++++++++++++++++++++++++++++ src/util/base64.cpp | 29 +++++++++++-- 4 files changed, 124 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/doc/client_lua_api.txt b/doc/client_lua_api.txt index 1e8015f7b..d239594f7 100644 --- a/doc/client_lua_api.txt +++ b/doc/client_lua_api.txt @@ -910,7 +910,9 @@ Call these functions only at load time! * Example: `minetest.rgba(10, 20, 30, 40)`, returns `"#0A141E28"` * `minetest.encode_base64(string)`: returns string encoded in base64 * Encodes a string in base64. -* `minetest.decode_base64(string)`: returns string +* `minetest.decode_base64(string)`: returns string or nil on failure + * Padding characters are only supported starting at version 5.4.0, where + 5.5.0 and newer perform proper checks. * Decodes a string encoded in base64. * `minetest.gettext(string)` : returns string * look up the translation of a string in the gettext message catalog diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 6c7ae0fb5..956919c89 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -5773,7 +5773,9 @@ Misc. * Example: `minetest.rgba(10, 20, 30, 40)`, returns `"#0A141E28"` * `minetest.encode_base64(string)`: returns string encoded in base64 * Encodes a string in base64. -* `minetest.decode_base64(string)`: returns string or nil for invalid base64 +* `minetest.decode_base64(string)`: returns string or nil on failure + * Padding characters are only supported starting at version 5.4.0, where + 5.5.0 and newer perform proper checks. * Decodes a string encoded in base64. * `minetest.is_protected(pos, name)`: returns boolean * Returning `true` restricts the player `name` from modifying (i.e. digging, diff --git a/src/unittest/test_utilities.cpp b/src/unittest/test_utilities.cpp index 93ba3f844..039110d54 100644 --- a/src/unittest/test_utilities.cpp +++ b/src/unittest/test_utilities.cpp @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/enriched_string.h" #include "util/numeric.h" #include "util/string.h" +#include "util/base64.h" class TestUtilities : public TestBase { public: @@ -56,6 +57,7 @@ public: void testMyround(); void testStringJoin(); void testEulerConversion(); + void testBase64(); }; static TestUtilities g_test_instance; @@ -87,6 +89,7 @@ void TestUtilities::runTests(IGameDef *gamedef) TEST(testMyround); TEST(testStringJoin); TEST(testEulerConversion); + TEST(testBase64); } //////////////////////////////////////////////////////////////////////////////// @@ -537,3 +540,93 @@ void TestUtilities::testEulerConversion() setPitchYawRoll(m2, v2); UASSERT(within(m1, m2, tolL)); } + +void TestUtilities::testBase64() +{ + // Test character set + UASSERT(base64_is_valid("ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/") == true); + UASSERT(base64_is_valid("/+9876543210" + "zyxwvutsrqponmlkjihgfedcba" + "ZYXWVUTSRQPONMLKJIHGFEDCBA") == true); + UASSERT(base64_is_valid("ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+.") == false); + UASSERT(base64_is_valid("ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789 /") == false); + + // Test empty string + UASSERT(base64_is_valid("") == true); + + // Test different lengths, with and without padding, + // with correct and incorrect padding + UASSERT(base64_is_valid("A") == false); + UASSERT(base64_is_valid("AA") == true); + UASSERT(base64_is_valid("AAA") == true); + UASSERT(base64_is_valid("AAAA") == true); + UASSERT(base64_is_valid("AAAAA") == false); + UASSERT(base64_is_valid("AAAAAA") == true); + UASSERT(base64_is_valid("AAAAAAA") == true); + UASSERT(base64_is_valid("AAAAAAAA") == true); + UASSERT(base64_is_valid("A===") == false); + UASSERT(base64_is_valid("AA==") == true); + UASSERT(base64_is_valid("AAA=") == true); + UASSERT(base64_is_valid("AAAA") == true); + UASSERT(base64_is_valid("AAAA====") == false); + UASSERT(base64_is_valid("AAAAA===") == false); + UASSERT(base64_is_valid("AAAAAA==") == true); + UASSERT(base64_is_valid("AAAAAAA=") == true); + UASSERT(base64_is_valid("AAAAAAA==") == false); + UASSERT(base64_is_valid("AAAAAAA===") == false); + UASSERT(base64_is_valid("AAAAAAA====") == false); + UASSERT(base64_is_valid("AAAAAAAA") == true); + UASSERT(base64_is_valid("AAAAAAAA=") == false); + UASSERT(base64_is_valid("AAAAAAAA==") == false); + UASSERT(base64_is_valid("AAAAAAAA===") == false); + UASSERT(base64_is_valid("AAAAAAAA====") == false); + + // Test if canonical encoding + // Last character limitations, length % 4 == 3 + UASSERT(base64_is_valid("AAB") == false); + UASSERT(base64_is_valid("AAE") == true); + UASSERT(base64_is_valid("AAQ") == true); + UASSERT(base64_is_valid("AAB=") == false); + UASSERT(base64_is_valid("AAE=") == true); + UASSERT(base64_is_valid("AAQ=") == true); + UASSERT(base64_is_valid("AAAAAAB=") == false); + UASSERT(base64_is_valid("AAAAAAE=") == true); + UASSERT(base64_is_valid("AAAAAAQ=") == true); + // Last character limitations, length % 4 == 2 + UASSERT(base64_is_valid("AB") == false); + UASSERT(base64_is_valid("AE") == false); + UASSERT(base64_is_valid("AQ") == true); + UASSERT(base64_is_valid("AB==") == false); + UASSERT(base64_is_valid("AE==") == false); + UASSERT(base64_is_valid("AQ==") == true); + UASSERT(base64_is_valid("AAAAAB==") == false); + UASSERT(base64_is_valid("AAAAAE==") == false); + UASSERT(base64_is_valid("AAAAAQ==") == true); + + // Extraneous character present + UASSERT(base64_is_valid(".") == false); + UASSERT(base64_is_valid("A.") == false); + UASSERT(base64_is_valid("AA.") == false); + UASSERT(base64_is_valid("AAA.") == false); + UASSERT(base64_is_valid("AAAA.") == false); + UASSERT(base64_is_valid("AAAAA.") == false); + UASSERT(base64_is_valid("A.A") == false); + UASSERT(base64_is_valid("AA.A") == false); + UASSERT(base64_is_valid("AAA.A") == false); + UASSERT(base64_is_valid("AAAA.A") == false); + UASSERT(base64_is_valid("AAAAA.A") == false); + UASSERT(base64_is_valid("\xE1""AAA") == false); + + // Padding in wrong position + UASSERT(base64_is_valid("A=A") == false); + UASSERT(base64_is_valid("AA=A") == false); + UASSERT(base64_is_valid("AAA=A") == false); + UASSERT(base64_is_valid("AAAA=A") == false); + UASSERT(base64_is_valid("AAAAA=A") == false); +} \ No newline at end of file diff --git a/src/util/base64.cpp b/src/util/base64.cpp index 6e1584410..0c2455222 100644 --- a/src/util/base64.cpp +++ b/src/util/base64.cpp @@ -33,18 +33,39 @@ static const std::string base64_chars = "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; +static const std::string base64_chars_padding_1 = "AEIMQUYcgkosw048"; +static const std::string base64_chars_padding_2 = "AQgw"; static inline bool is_base64(unsigned char c) { - return isalnum(c) || c == '+' || c == '/' || c == '='; + return (c >= '0' && c <= '9') + || (c >= 'A' && c <= 'Z') + || (c >= 'a' && c <= 'z') + || c == '+' || c == '/'; } bool base64_is_valid(std::string const& s) { - for (char i : s) - if (!is_base64(i)) + size_t i = 0; + for (; i < s.size(); ++i) + if (!is_base64(s[i])) + break; + unsigned char padding = 3 - ((i + 3) % 4); + if ((padding == 1 && base64_chars_padding_1.find(s[i - 1]) == std::string::npos) + || (padding == 2 && base64_chars_padding_2.find(s[i - 1]) == std::string::npos) + || padding == 3) + return false; + int actual_padding = s.size() - i; + // omission of padding characters is allowed + if (actual_padding == 0) + return true; + + // remaining characters (max. 2) may only be padding + for (; i < s.size(); ++i) + if (s[i] != '=') return false; - return true; + // number of padding characters needs to match + return padding == actual_padding; } std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) { -- cgit v1.2.3 From c9144ae5e22ee041fed2512cd3055608c6e9a4bc Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Sun, 30 May 2021 20:24:12 +0200 Subject: Add core.compare_block_status function (#11247) Makes it possible to check the status of the mapblock in a future-extensible way. --- doc/lua_api.txt | 13 +++++++++++++ src/emerge.cpp | 7 +++++++ src/emerge.h | 2 ++ src/map.cpp | 5 +++++ src/map.h | 2 ++ src/mapblock.h | 2 +- src/script/lua_api/l_env.cpp | 30 +++++++++++++++++++++++++++++- src/script/lua_api/l_env.h | 6 +++++- src/serverenvironment.cpp | 15 +++++++++++++++ src/serverenvironment.h | 11 ++++++++++- 10 files changed, 89 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 956919c89..0f57f1f28 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -5863,6 +5863,19 @@ Misc. * If `transient` is `false` or absent, frees a persistent forceload. If `true`, frees a transient forceload. +* `minetest.compare_block_status(pos, condition)` + * Checks whether the mapblock at positition `pos` is in the wanted condition. + * `condition` may be one of the following values: + * `"unknown"`: not in memory + * `"emerging"`: in the queue for loading from disk or generating + * `"loaded"`: in memory but inactive (no ABMs are executed) + * `"active"`: in memory and active + * Other values are reserved for future functionality extensions + * Return value, the comparison status: + * `false`: Mapblock does not fulfil the wanted condition + * `true`: Mapblock meets the requirement + * `nil`: Unsupported `condition` value + * `minetest.request_insecure_environment()`: returns an environment containing insecure functions if the calling mod has been listed as trusted in the `secure.trusted_mods` setting or security is disabled, otherwise returns diff --git a/src/emerge.cpp b/src/emerge.cpp index 32e7d9f24..3a2244d7e 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -358,6 +358,13 @@ bool EmergeManager::enqueueBlockEmergeEx( } +bool EmergeManager::isBlockInQueue(v3s16 pos) +{ + MutexAutoLock queuelock(m_queue_mutex); + return m_blocks_enqueued.find(pos) != m_blocks_enqueued.end(); +} + + // // Mapgen-related helper functions // diff --git a/src/emerge.h b/src/emerge.h index aac3e7dd3..b060226f8 100644 --- a/src/emerge.h +++ b/src/emerge.h @@ -174,6 +174,8 @@ public: EmergeCompletionCallback callback, void *callback_param); + bool isBlockInQueue(v3s16 pos); + v3s16 getContainingChunk(v3s16 blockpos); Mapgen *getCurrentMapgen(); diff --git a/src/map.cpp b/src/map.cpp index 7c59edbaa..641287c3d 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -1549,6 +1549,11 @@ MapBlock *ServerMap::getBlockOrEmerge(v3s16 p3d) return block; } +bool ServerMap::isBlockInQueue(v3s16 pos) +{ + return m_emerge && m_emerge->isBlockInQueue(pos); +} + // 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 e68795c4a..8d20c4a44 100644 --- a/src/map.h +++ b/src/map.h @@ -365,6 +365,8 @@ public: */ MapBlock *getBlockOrEmerge(v3s16 p3d); + bool isBlockInQueue(v3s16 pos); + /* Database functions */ diff --git a/src/mapblock.h b/src/mapblock.h index 7b82301e9..2e3eb0d76 100644 --- a/src/mapblock.h +++ b/src/mapblock.h @@ -140,7 +140,7 @@ public: //// Flags //// - inline bool isDummy() + inline bool isDummy() const { return !data; } diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index 39c229ee4..98f8861fa 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -46,13 +46,22 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/client.h" #endif -struct EnumString ModApiEnvMod::es_ClearObjectsMode[] = +const EnumString ModApiEnvMod::es_ClearObjectsMode[] = { {CLEAR_OBJECTS_MODE_FULL, "full"}, {CLEAR_OBJECTS_MODE_QUICK, "quick"}, {0, NULL}, }; +const EnumString ModApiEnvMod::es_BlockStatusType[] = +{ + {ServerEnvironment::BS_UNKNOWN, "unknown"}, + {ServerEnvironment::BS_EMERGING, "emerging"}, + {ServerEnvironment::BS_LOADED, "loaded"}, + {ServerEnvironment::BS_ACTIVE, "active"}, + {0, NULL}, +}; + /////////////////////////////////////////////////////////////////////////////// @@ -1389,6 +1398,24 @@ int ModApiEnvMod::l_forceload_block(lua_State *L) return 0; } +// compare_block_status(nodepos) +int ModApiEnvMod::l_compare_block_status(lua_State *L) +{ + GET_ENV_PTR; + + 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 + + lua_pushboolean(L, status >= condition_i); + return 1; +} + + // forceload_free_block(blockpos) // blockpos = {x=num, y=num, z=num} int ModApiEnvMod::l_forceload_free_block(lua_State *L) @@ -1462,6 +1489,7 @@ void ModApiEnvMod::Initialize(lua_State *L, int top) API_FCT(transforming_liquid_add); API_FCT(forceload_block); API_FCT(forceload_free_block); + API_FCT(compare_block_status); API_FCT(get_translated_string); } diff --git a/src/script/lua_api/l_env.h b/src/script/lua_api/l_env.h index 42c2d64f8..979b13c40 100644 --- a/src/script/lua_api/l_env.h +++ b/src/script/lua_api/l_env.h @@ -195,6 +195,9 @@ private: // stops forceloading a position static int l_forceload_free_block(lua_State *L); + // compare_block_status(nodepos) + static int l_compare_block_status(lua_State *L); + // Get a string translated server side static int l_get_translated_string(lua_State * L); @@ -207,7 +210,8 @@ public: static void Initialize(lua_State *L, int top); static void InitializeClient(lua_State *L, int top); - static struct EnumString es_ClearObjectsMode[]; + static const EnumString es_ClearObjectsMode[]; + static const EnumString es_BlockStatusType[]; }; class LuaABM : public ActiveBlockModifier { diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index 3d9ba132b..413a785e6 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -1542,6 +1542,21 @@ void ServerEnvironment::step(float dtime) m_server->sendDetachedInventories(PEER_ID_INEXISTENT, true); } +ServerEnvironment::BlockStatus ServerEnvironment::getBlockStatus(v3s16 blockpos) +{ + if (m_active_blocks.contains(blockpos)) + return BS_ACTIVE; + + const MapBlock *block = m_map->getBlockNoCreateNoEx(blockpos); + if (block && !block->isDummy()) + return BS_LOADED; + + if (m_map->isBlockInQueue(blockpos)) + return BS_EMERGING; + + return BS_UNKNOWN; +} + u32 ServerEnvironment::addParticleSpawner(float exptime) { // Timers with lifetime 0 do not expire diff --git a/src/serverenvironment.h b/src/serverenvironment.h index a11c814ed..c5ca463ee 100644 --- a/src/serverenvironment.h +++ b/src/serverenvironment.h @@ -342,7 +342,16 @@ public: void reportMaxLagEstimate(float f) { m_max_lag_estimate = f; } float getMaxLagEstimate() { return m_max_lag_estimate; } - std::set* getForceloadedBlocks() { return &m_active_blocks.m_forceloaded_list; }; + std::set* getForceloadedBlocks() { return &m_active_blocks.m_forceloaded_list; } + + // Sorted by how ready a mapblock is + enum BlockStatus { + BS_UNKNOWN, + BS_EMERGING, + BS_LOADED, + BS_ACTIVE // always highest value + }; + BlockStatus getBlockStatus(v3s16 blockpos); // Sets the static object status all the active objects in the specified block // This is only really needed for deleting blocks from the map -- cgit v1.2.3 From e15cae9fa0f99f597f349a7ba07d149cd91cc2d8 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 1 Jun 2021 19:45:45 +0200 Subject: fontengine: Fix crash loading PNG/XML fonts from paths without dot fixes #11096 --- src/client/fontengine.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/fontengine.cpp b/src/client/fontengine.cpp index 0382c2f18..f64315db4 100644 --- a/src/client/fontengine.cpp +++ b/src/client/fontengine.cpp @@ -342,8 +342,9 @@ gui::IGUIFont *FontEngine::initSimpleFont(const FontSpec &spec) (spec.mode == FM_SimpleMono) ? "mono_font_path" : "font_path"); size_t pos_dot = font_path.find_last_of('.'); - std::string basename = font_path; - std::string ending = lowercase(font_path.substr(pos_dot)); + std::string basename = font_path, ending; + if (pos_dot != std::string::npos) + ending = lowercase(font_path.substr(pos_dot)); if (ending == ".ttf") { errorstream << "FontEngine: Found font \"" << font_path -- cgit v1.2.3 From 8f085e02a107dd8092393935bfa1bba71d2546d2 Mon Sep 17 00:00:00 2001 From: DS Date: Fri, 4 Jun 2021 21:22:33 +0200 Subject: Add metatables to lua vectors (#11039) Add backwards-compatible metatable functions for vectors. --- builtin/client/init.lua | 1 - builtin/common/misc_helpers.lua | 28 ++- builtin/common/tests/misc_helpers_spec.lua | 5 +- builtin/common/tests/serialize_spec.lua | 13 ++ builtin/common/tests/vector_spec.lua | 248 +++++++++++++++++++++++++- builtin/common/vector.lua | 212 +++++++++++++++------- builtin/game/chat.lua | 8 +- builtin/game/falling.lua | 53 +++--- builtin/game/init.lua | 2 - builtin/game/item.lua | 70 ++++---- builtin/game/misc.lua | 11 +- builtin/game/voxelarea.lua | 14 +- builtin/init.lua | 1 + builtin/mainmenu/tests/serverlistmgr_spec.lua | 1 + doc/lua_api.txt | 59 +++++- src/script/common/c_converter.cpp | 25 +++ 16 files changed, 571 insertions(+), 180 deletions(-) (limited to 'src') diff --git a/builtin/client/init.lua b/builtin/client/init.lua index 9633a7c71..589fe8f24 100644 --- a/builtin/client/init.lua +++ b/builtin/client/init.lua @@ -7,5 +7,4 @@ dofile(clientpath .. "register.lua") dofile(commonpath .. "after.lua") dofile(commonpath .. "chatcommands.lua") dofile(clientpath .. "chatcommands.lua") -dofile(commonpath .. "vector.lua") dofile(clientpath .. "death_formspec.lua") diff --git a/builtin/common/misc_helpers.lua b/builtin/common/misc_helpers.lua index d5f25f2fe..64c8c9a67 100644 --- a/builtin/common/misc_helpers.lua +++ b/builtin/common/misc_helpers.lua @@ -432,21 +432,19 @@ function core.string_to_pos(value) return nil end - local p = {} - p.x, p.y, p.z = string.match(value, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$") - if p.x and p.y and p.z then - p.x = tonumber(p.x) - p.y = tonumber(p.y) - p.z = tonumber(p.z) - return p - end - p = {} - p.x, p.y, p.z = string.match(value, "^%( *([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+) *%)$") - if p.x and p.y and p.z then - p.x = tonumber(p.x) - p.y = tonumber(p.y) - p.z = tonumber(p.z) - return p + local x, y, z = string.match(value, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$") + if x and y and z then + x = tonumber(x) + y = tonumber(y) + z = tonumber(z) + return vector.new(x, y, z) + end + x, y, z = string.match(value, "^%( *([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+) *%)$") + if x and y and z then + x = tonumber(x) + y = tonumber(y) + z = tonumber(z) + return vector.new(x, y, z) end return nil end diff --git a/builtin/common/tests/misc_helpers_spec.lua b/builtin/common/tests/misc_helpers_spec.lua index bb9d13e7f..b16987f0b 100644 --- a/builtin/common/tests/misc_helpers_spec.lua +++ b/builtin/common/tests/misc_helpers_spec.lua @@ -1,4 +1,5 @@ _G.core = {} +dofile("builtin/common/vector.lua") dofile("builtin/common/misc_helpers.lua") describe("string", function() @@ -55,8 +56,8 @@ end) describe("pos", function() it("from string", function() - assert.same({ x = 10, y = 5.1, z = -2}, core.string_to_pos("10.0, 5.1, -2")) - assert.same({ x = 10, y = 5.1, z = -2}, core.string_to_pos("( 10.0, 5.1, -2)")) + assert.equal(vector.new(10, 5.1, -2), core.string_to_pos("10.0, 5.1, -2")) + assert.equal(vector.new(10, 5.1, -2), core.string_to_pos("( 10.0, 5.1, -2)")) assert.is_nil(core.string_to_pos("asd, 5, -2)")) end) diff --git a/builtin/common/tests/serialize_spec.lua b/builtin/common/tests/serialize_spec.lua index 17c6a60f7..e46b7dcc5 100644 --- a/builtin/common/tests/serialize_spec.lua +++ b/builtin/common/tests/serialize_spec.lua @@ -3,6 +3,7 @@ _G.core = {} _G.setfenv = require 'busted.compatibility'.setfenv dofile("builtin/common/serialize.lua") +dofile("builtin/common/vector.lua") describe("serialize", function() it("works", function() @@ -53,4 +54,16 @@ describe("serialize", function() assert.is_nil(test_out.func) assert.equals(test_out.foo, "bar") end) + + it("vectors work", function() + local v = vector.new(1, 2, 3) + assert.same({{x = 1, y = 2, z = 3}}, core.deserialize(core.serialize({v}))) + assert.same({x = 1, y = 2, z = 3}, core.deserialize(core.serialize(v))) + + -- abuse + v = vector.new(1, 2, 3) + v.a = "bla" + assert.same({x = 1, y = 2, z = 3, a = "bla"}, + core.deserialize(core.serialize(v))) + end) end) diff --git a/builtin/common/tests/vector_spec.lua b/builtin/common/tests/vector_spec.lua index 104c656e9..9ebe69056 100644 --- a/builtin/common/tests/vector_spec.lua +++ b/builtin/common/tests/vector_spec.lua @@ -4,14 +4,20 @@ dofile("builtin/common/vector.lua") describe("vector", function() describe("new()", function() it("constructs", function() - assert.same({ x = 0, y = 0, z = 0 }, vector.new()) - assert.same({ x = 1, y = 2, z = 3 }, vector.new(1, 2, 3)) - assert.same({ x = 3, y = 2, z = 1 }, vector.new({ x = 3, y = 2, z = 1 })) + assert.same({x = 0, y = 0, z = 0}, vector.new()) + assert.same({x = 1, y = 2, z = 3}, vector.new(1, 2, 3)) + assert.same({x = 3, y = 2, z = 1}, vector.new({x = 3, y = 2, z = 1})) + + assert.is_true(vector.check(vector.new())) + assert.is_true(vector.check(vector.new(1, 2, 3))) + assert.is_true(vector.check(vector.new({x = 3, y = 2, z = 1}))) local input = vector.new({ x = 3, y = 2, z = 1 }) local output = vector.new(input) assert.same(input, output) - assert.are_not.equal(input, output) + assert.equal(input, output) + assert.is_false(rawequal(input, output)) + assert.equal(input, input:new()) end) it("throws on invalid input", function() @@ -25,7 +31,89 @@ describe("vector", function() end) end) - it("equal()", function() + it("indexes", function() + local some_vector = vector.new(24, 42, 13) + assert.equal(24, some_vector[1]) + assert.equal(24, some_vector.x) + assert.equal(42, some_vector[2]) + assert.equal(42, some_vector.y) + assert.equal(13, some_vector[3]) + assert.equal(13, some_vector.z) + + some_vector[1] = 100 + assert.equal(100, some_vector.x) + some_vector.x = 101 + assert.equal(101, some_vector[1]) + + some_vector[2] = 100 + assert.equal(100, some_vector.y) + some_vector.y = 102 + assert.equal(102, some_vector[2]) + + some_vector[3] = 100 + assert.equal(100, some_vector.z) + some_vector.z = 103 + assert.equal(103, some_vector[3]) + end) + + it("direction()", function() + local a = vector.new(1, 0, 0) + local b = vector.new(1, 42, 0) + assert.equal(vector.new(0, 1, 0), vector.direction(a, b)) + assert.equal(vector.new(0, 1, 0), a:direction(b)) + end) + + it("distance()", function() + local a = vector.new(1, 0, 0) + local b = vector.new(3, 42, 9) + assert.is_true(math.abs(43 - vector.distance(a, b)) < 1.0e-12) + assert.is_true(math.abs(43 - a:distance(b)) < 1.0e-12) + assert.equal(0, vector.distance(a, a)) + assert.equal(0, b:distance(b)) + end) + + it("length()", function() + local a = vector.new(0, 0, -23) + assert.equal(0, vector.length(vector.new())) + assert.equal(23, vector.length(a)) + assert.equal(23, a:length()) + end) + + it("normalize()", function() + local a = vector.new(0, 0, -23) + assert.equal(vector.new(0, 0, -1), vector.normalize(a)) + assert.equal(vector.new(0, 0, -1), a:normalize()) + assert.equal(vector.new(), vector.normalize(vector.new())) + end) + + it("floor()", function() + local a = vector.new(0.1, 0.9, -0.5) + assert.equal(vector.new(0, 0, -1), vector.floor(a)) + assert.equal(vector.new(0, 0, -1), a:floor()) + end) + + it("round()", function() + local a = vector.new(0.1, 0.9, -0.5) + assert.equal(vector.new(0, 1, -1), vector.round(a)) + assert.equal(vector.new(0, 1, -1), a:round()) + end) + + it("apply()", function() + local i = 0 + local f = function(x) + i = i + 1 + return x + i + end + local a = vector.new(0.1, 0.9, -0.5) + assert.equal(vector.new(1, 1, 0), vector.apply(a, math.ceil)) + assert.equal(vector.new(1, 1, 0), a:apply(math.ceil)) + assert.equal(vector.new(0.1, 0.9, 0.5), vector.apply(a, math.abs)) + assert.equal(vector.new(0.1, 0.9, 0.5), a:apply(math.abs)) + assert.equal(vector.new(1.1, 2.9, 2.5), vector.apply(a, f)) + assert.equal(vector.new(4.1, 5.9, 5.5), a:apply(f)) + end) + + it("equals()", function() local function assertE(a, b) assert.is_true(vector.equals(a, b)) end @@ -35,22 +123,164 @@ describe("vector", function() assertE({x = 0, y = 0, z = 0}, {x = 0, y = 0, z = 0}) assertE({x = -1, y = 0, z = 1}, {x = -1, y = 0, z = 1}) - local a = { x = 2, y = 4, z = -10 } + assertE({x = -1, y = 0, z = 1}, vector.new(-1, 0, 1)) + local a = {x = 2, y = 4, z = -10} assertE(a, a) assertNE({x = -1, y = 0, z = 1}, a) + + assert.equal(vector.new(1, 2, 3), vector.new(1, 2, 3)) + assert.is_true(vector.new(1, 2, 3):equals(vector.new(1, 2, 3))) + assert.not_equal(vector.new(1, 2, 3), vector.new(1, 2, 4)) + assert.is_true(vector.new(1, 2, 3) == vector.new(1, 2, 3)) + assert.is_false(vector.new(1, 2, 3) == vector.new(1, 3, 3)) end) - it("add()", function() - assert.same({ x = 2, y = 4, z = 6 }, vector.add(vector.new(1, 2, 3), { x = 1, y = 2, z = 3 })) + it("metatable is same", function() + local a = vector.new() + local b = vector.new(1, 2, 3) + + assert.equal(true, vector.check(a)) + assert.equal(true, vector.check(b)) + + assert.equal(vector.metatable, getmetatable(a)) + assert.equal(vector.metatable, getmetatable(b)) + assert.equal(vector.metatable, a.metatable) + end) + + it("sort()", function() + local a = vector.new(1, 2, 3) + local b = vector.new(0.5, 232, -2) + local sorted = {vector.new(0.5, 2, -2), vector.new(1, 232, 3)} + assert.same(sorted, {vector.sort(a, b)}) + assert.same(sorted, {a:sort(b)}) + end) + + it("angle()", function() + assert.equal(math.pi, vector.angle(vector.new(-1, -2, -3), vector.new(1, 2, 3))) + assert.equal(math.pi/2, vector.new(0, 1, 0):angle(vector.new(1, 0, 0))) + end) + + it("dot()", function() + assert.equal(-14, vector.dot(vector.new(-1, -2, -3), vector.new(1, 2, 3))) + assert.equal(0, vector.new():dot(vector.new(1, 2, 3))) + end) + + it("cross()", function() + local a = vector.new(-1, -2, 0) + local b = vector.new(1, 2, 3) + assert.equal(vector.new(-6, 3, 0), vector.cross(a, b)) + assert.equal(vector.new(-6, 3, 0), a:cross(b)) end) it("offset()", function() - assert.same({ x = 41, y = 52, z = 63 }, vector.offset(vector.new(1, 2, 3), 40, 50, 60)) + assert.same({x = 41, y = 52, z = 63}, vector.offset(vector.new(1, 2, 3), 40, 50, 60)) + assert.equal(vector.new(41, 52, 63), vector.offset(vector.new(1, 2, 3), 40, 50, 60)) + assert.equal(vector.new(41, 52, 63), vector.new(1, 2, 3):offset(40, 50, 60)) + end) + + it("is()", function() + local some_table1 = {foo = 13, [42] = 1, "bar", 2} + local some_table2 = {1, 2, 3} + local some_table3 = {x = 1, 2, 3} + local some_table4 = {1, 2, z = 3} + local old = {x = 1, y = 2, z = 3} + local real = vector.new(1, 2, 3) + + assert.is_false(vector.check(nil)) + assert.is_false(vector.check(1)) + assert.is_false(vector.check(true)) + assert.is_false(vector.check("foo")) + assert.is_false(vector.check(some_table1)) + assert.is_false(vector.check(some_table2)) + assert.is_false(vector.check(some_table3)) + assert.is_false(vector.check(some_table4)) + assert.is_false(vector.check(old)) + assert.is_true(vector.check(real)) + assert.is_true(real:check()) + end) + + it("global pairs", function() + local out = {} + local vec = vector.new(10, 20, 30) + for k, v in pairs(vec) do + out[k] = v + end + assert.same({x = 10, y = 20, z = 30}, out) + end) + + it("abusing works", function() + local v = vector.new(1, 2, 3) + v.a = 1 + assert.equal(1, v.a) + + local a_is_there = false + for key, value in pairs(v) do + if key == "a" then + a_is_there = true + assert.equal(value, 1) + break + end + end + assert.is_true(a_is_there) + end) + + it("add()", function() + local a = vector.new(1, 2, 3) + local b = vector.new(1, 4, 3) + local c = vector.new(2, 6, 6) + assert.equal(c, vector.add(a, {x = 1, y = 4, z = 3})) + assert.equal(c, vector.add(a, b)) + assert.equal(c, a:add(b)) + assert.equal(c, a + b) + assert.equal(c, b + a) + end) + + it("subtract()", function() + local a = vector.new(1, 2, 3) + local b = vector.new(2, 4, 3) + local c = vector.new(-1, -2, 0) + assert.equal(c, vector.subtract(a, {x = 2, y = 4, z = 3})) + assert.equal(c, vector.subtract(a, b)) + assert.equal(c, a:subtract(b)) + assert.equal(c, a - b) + assert.equal(c, -b + a) + end) + + it("multiply()", function() + local a = vector.new(1, 2, 3) + local b = vector.new(2, 4, 3) + local c = vector.new(2, 8, 9) + local s = 2 + local d = vector.new(2, 4, 6) + assert.equal(c, vector.multiply(a, {x = 2, y = 4, z = 3})) + assert.equal(c, vector.multiply(a, b)) + assert.equal(d, vector.multiply(a, s)) + assert.equal(d, a:multiply(s)) + assert.equal(d, a * s) + assert.equal(d, s * a) + assert.equal(-a, -1 * a) + end) + + it("divide()", function() + local a = vector.new(1, 2, 3) + local b = vector.new(2, 4, 3) + local c = vector.new(0.5, 0.5, 1) + local s = 2 + local d = vector.new(0.5, 1, 1.5) + assert.equal(c, vector.divide(a, {x = 2, y = 4, z = 3})) + assert.equal(c, vector.divide(a, b)) + assert.equal(d, vector.divide(a, s)) + assert.equal(d, a:divide(s)) + assert.equal(d, a / s) + assert.equal(d, 1/s * a) + assert.equal(-a, a / -1) end) it("to_string()", function() local v = vector.new(1, 2, 3.14) assert.same("(1, 2, 3.14)", vector.to_string(v)) + assert.same("(1, 2, 3.14)", v:to_string()) + assert.same("(1, 2, 3.14)", tostring(v)) end) it("from_string()", function() diff --git a/builtin/common/vector.lua b/builtin/common/vector.lua index 2ef8fc617..cbaa872dc 100644 --- a/builtin/common/vector.lua +++ b/builtin/common/vector.lua @@ -1,15 +1,43 @@ +--[[ +Vector helpers +Note: The vector.*-functions must be able to accept old vectors that had no metatables +]] + +-- localize functions +local setmetatable = setmetatable vector = {} +local metatable = {} +vector.metatable = metatable + +local xyz = {"x", "y", "z"} + +-- only called when rawget(v, key) returns nil +function metatable.__index(v, key) + return rawget(v, xyz[key]) or vector[key] +end + +-- only called when rawget(v, key) returns nil +function metatable.__newindex(v, key, value) + rawset(v, xyz[key] or key, value) +end + +-- constructors + +local function fast_new(x, y, z) + return setmetatable({x = x, y = y, z = z}, metatable) +end + function vector.new(a, b, c) if type(a) == "table" then assert(a.x and a.y and a.z, "Invalid vector passed to vector.new()") - return {x=a.x, y=a.y, z=a.z} + return fast_new(a.x, a.y, a.z) elseif a then assert(b and c, "Invalid arguments for vector.new()") - return {x=a, y=b, z=c} + return fast_new(a, b, c) end - return {x=0, y=0, z=0} + return fast_new(0, 0, 0) end function vector.from_string(s, init) @@ -27,48 +55,49 @@ end function vector.to_string(v) return string.format("(%g, %g, %g)", v.x, v.y, v.z) end +metatable.__tostring = vector.to_string function vector.equals(a, b) return a.x == b.x and a.y == b.y and a.z == b.z end +metatable.__eq = vector.equals + +-- unary operations function vector.length(v) return math.hypot(v.x, math.hypot(v.y, v.z)) end +-- Note: we can not use __len because it is already used for primitive table length function vector.normalize(v) local len = vector.length(v) if len == 0 then - return {x=0, y=0, z=0} + return fast_new(0, 0, 0) else return vector.divide(v, len) end end function vector.floor(v) - return { - x = math.floor(v.x), - y = math.floor(v.y), - z = math.floor(v.z) - } + return vector.apply(v, math.floor) end function vector.round(v) - return { - x = math.round(v.x), - y = math.round(v.y), - z = math.round(v.z) - } + return fast_new( + math.round(v.x), + math.round(v.y), + math.round(v.z) + ) end function vector.apply(v, func) - return { - x = func(v.x), - y = func(v.y), - z = func(v.z) - } + return fast_new( + func(v.x), + func(v.y), + func(v.z) + ) end function vector.distance(a, b) @@ -79,11 +108,7 @@ function vector.distance(a, b) end function vector.direction(pos1, pos2) - return vector.normalize({ - x = pos2.x - pos1.x, - y = pos2.y - pos1.y, - z = pos2.z - pos1.z - }) + return vector.subtract(pos2, pos1):normalize() end function vector.angle(a, b) @@ -98,70 +123,137 @@ function vector.dot(a, b) end function vector.cross(a, b) - return { - x = a.y * b.z - a.z * b.y, - y = a.z * b.x - a.x * b.z, - z = a.x * b.y - a.y * b.x - } + return fast_new( + a.y * b.z - a.z * b.y, + a.z * b.x - a.x * b.z, + a.x * b.y - a.y * b.x + ) end +function metatable.__unm(v) + return fast_new(-v.x, -v.y, -v.z) +end + +-- add, sub, mul, div operations + function vector.add(a, b) if type(b) == "table" then - return {x = a.x + b.x, - y = a.y + b.y, - z = a.z + b.z} + return fast_new( + a.x + b.x, + a.y + b.y, + a.z + b.z + ) else - return {x = a.x + b, - y = a.y + b, - z = a.z + b} + return fast_new( + a.x + b, + a.y + b, + a.z + b + ) end end +function metatable.__add(a, b) + return fast_new( + a.x + b.x, + a.y + b.y, + a.z + b.z + ) +end function vector.subtract(a, b) if type(b) == "table" then - return {x = a.x - b.x, - y = a.y - b.y, - z = a.z - b.z} + return fast_new( + a.x - b.x, + a.y - b.y, + a.z - b.z + ) else - return {x = a.x - b, - y = a.y - b, - z = a.z - b} + return fast_new( + a.x - b, + a.y - b, + a.z - b + ) end end +function metatable.__sub(a, b) + return fast_new( + a.x - b.x, + a.y - b.y, + a.z - b.z + ) +end function vector.multiply(a, b) if type(b) == "table" then - return {x = a.x * b.x, - y = a.y * b.y, - z = a.z * b.z} + return fast_new( + a.x * b.x, + a.y * b.y, + a.z * b.z + ) else - return {x = a.x * b, - y = a.y * b, - z = a.z * b} + return fast_new( + a.x * b, + a.y * b, + a.z * b + ) + end +end +function metatable.__mul(a, b) + if type(a) == "table" then + return fast_new( + a.x * b, + a.y * b, + a.z * b + ) + else + return fast_new( + a * b.x, + a * b.y, + a * b.z + ) end end function vector.divide(a, b) if type(b) == "table" then - return {x = a.x / b.x, - y = a.y / b.y, - z = a.z / b.z} + return fast_new( + a.x / b.x, + a.y / b.y, + a.z / b.z + ) else - return {x = a.x / b, - y = a.y / b, - z = a.z / b} + return fast_new( + a.x / b, + a.y / b, + a.z / b + ) end end +function metatable.__div(a, b) + -- scalar/vector makes no sense + return fast_new( + a.x / b, + a.y / b, + a.z / b + ) +end + +-- misc stuff function vector.offset(v, x, y, z) - return {x = v.x + x, - y = v.y + y, - z = v.z + z} + return fast_new( + v.x + x, + v.y + y, + v.z + z + ) end function vector.sort(a, b) - return {x = math.min(a.x, b.x), y = math.min(a.y, b.y), z = math.min(a.z, b.z)}, - {x = math.max(a.x, b.x), y = math.max(a.y, b.y), z = math.max(a.z, b.z)} + return fast_new(math.min(a.x, b.x), math.min(a.y, b.y), math.min(a.z, b.z)), + fast_new(math.max(a.x, b.x), math.max(a.y, b.y), math.max(a.z, b.z)) +end + +function vector.check(v) + return getmetatable(v) == metatable end local function sin(x) @@ -229,7 +321,7 @@ end function vector.dir_to_rotation(forward, up) forward = vector.normalize(forward) - local rot = {x = math.asin(forward.y), y = -math.atan2(forward.x, forward.z), z = 0} + local rot = vector.new(math.asin(forward.y), -math.atan2(forward.x, forward.z), 0) if not up then return rot end @@ -237,7 +329,7 @@ function vector.dir_to_rotation(forward, up) "Invalid vectors passed to vector.dir_to_rotation().") up = vector.normalize(up) -- Calculate vector pointing up with roll = 0, just based on forward vector. - local forwup = vector.rotate({x = 0, y = 1, z = 0}, rot) + local forwup = vector.rotate(vector.new(0, 1, 0), rot) -- 'forwup' and 'up' are now in a plane with 'forward' as normal. -- The angle between them is the absolute of the roll value we're looking for. rot.z = vector.angle(forwup, up) diff --git a/builtin/game/chat.lua b/builtin/game/chat.lua index 4dbcff1e2..e155fba70 100644 --- a/builtin/game/chat.lua +++ b/builtin/game/chat.lua @@ -499,10 +499,10 @@ core.register_chatcommand("remove_player", { -- pos may be a non-integer position local function find_free_position_near(pos) local tries = { - {x=1, y=0, z=0}, - {x=-1, y=0, z=0}, - {x=0, y=0, z=1}, - {x=0, y=0, z=-1}, + vector.new( 1, 0, 0), + vector.new(-1, 0, 0), + vector.new( 0, 0, 1), + vector.new( 0, 0, -1), } for _, d in ipairs(tries) do local p = vector.add(pos, d) diff --git a/builtin/game/falling.lua b/builtin/game/falling.lua index 2cc0d8fac..a9dbc6ed5 100644 --- a/builtin/game/falling.lua +++ b/builtin/game/falling.lua @@ -39,7 +39,7 @@ local gravity = tonumber(core.settings:get("movement_gravity")) or 9.81 core.register_entity(":__builtin:falling_node", { initial_properties = { visual = "item", - visual_size = {x = SCALE, y = SCALE, z = SCALE}, + visual_size = vector.new(SCALE, SCALE, SCALE), textures = {}, physical = true, is_visible = false, @@ -96,7 +96,7 @@ core.register_entity(":__builtin:falling_node", { local vsize if def.visual_scale then local s = def.visual_scale - vsize = {x = s, y = s, z = s} + vsize = vector.new(s, s, s) end self.object:set_properties({ is_visible = true, @@ -114,7 +114,7 @@ core.register_entity(":__builtin:falling_node", { local vsize if def.visual_scale then local s = def.visual_scale * SCALE - vsize = {x = s, y = s, z = s} + vsize = vector.new(s, s, s) end self.object:set_properties({ is_visible = true, @@ -227,7 +227,7 @@ core.register_entity(":__builtin:falling_node", { on_activate = function(self, staticdata) self.object:set_armor_groups({immortal = 1}) - self.object:set_acceleration({x = 0, y = -gravity, z = 0}) + self.object:set_acceleration(vector.new(0, -gravity, 0)) local ds = core.deserialize(staticdata) if ds and ds.node then @@ -303,7 +303,7 @@ core.register_entity(":__builtin:falling_node", { if self.floats then local pos = self.object:get_pos() - local bcp = vector.round({x = pos.x, y = pos.y - 0.7, z = pos.z}) + local bcp = pos:offset(0, -0.7, 0):round() local bcn = core.get_node(bcp) local bcd = core.registered_nodes[bcn.name] @@ -344,13 +344,12 @@ core.register_entity(":__builtin:falling_node", { -- TODO: this hack could be avoided in the future if objects -- could choose who to collide with local vel = self.object:get_velocity() - self.object:set_velocity({ - x = vel.x, - y = player_collision.old_velocity.y, - z = vel.z - }) - self.object:set_pos(vector.add(self.object:get_pos(), - {x = 0, y = -0.5, z = 0})) + self.object:set_velocity(vector.new( + vel.x, + player_collision.old_velocity.y, + vel.z + )) + self.object:set_pos(self.object:get_pos():offset(0, -0.5, 0)) end return elseif bcn.name == "ignore" then @@ -430,7 +429,7 @@ local function drop_attached_node(p) if def and def.preserve_metadata then local oldmeta = core.get_meta(p):to_table().fields -- Copy pos and node because the callback can modify them. - local pos_copy = {x=p.x, y=p.y, z=p.z} + local pos_copy = vector.new(p) local node_copy = {name=n.name, param1=n.param1, param2=n.param2} local drop_stacks = {} for k, v in pairs(drops) do @@ -455,14 +454,14 @@ end function builtin_shared.check_attached_node(p, n) local def = core.registered_nodes[n.name] - local d = {x = 0, y = 0, z = 0} + local d = vector.new() if def.paramtype2 == "wallmounted" or def.paramtype2 == "colorwallmounted" then -- The fallback vector here is in case 'wallmounted to dir' is nil due -- to voxelmanip placing a wallmounted node without resetting a -- pre-existing param2 value that is out-of-range for wallmounted. -- The fallback vector corresponds to param2 = 0. - d = core.wallmounted_to_dir(n.param2) or {x = 0, y = 1, z = 0} + d = core.wallmounted_to_dir(n.param2) or vector.new(0, 1, 0) else d.y = -1 end @@ -482,7 +481,7 @@ end function core.check_single_for_falling(p) local n = core.get_node(p) if core.get_item_group(n.name, "falling_node") ~= 0 then - local p_bottom = {x = p.x, y = p.y - 1, z = p.z} + local p_bottom = vector.offset(p, 0, -1, 0) -- Only spawn falling node if node below is loaded local n_bottom = core.get_node_or_nil(p_bottom) local d_bottom = n_bottom and core.registered_nodes[n_bottom.name] @@ -521,17 +520,17 @@ end -- Down first as likely case, but always before self. The same with sides. -- Up must come last, so that things above self will also fall all at once. local check_for_falling_neighbors = { - {x = -1, y = -1, z = 0}, - {x = 1, y = -1, z = 0}, - {x = 0, y = -1, z = -1}, - {x = 0, y = -1, z = 1}, - {x = 0, y = -1, z = 0}, - {x = -1, y = 0, z = 0}, - {x = 1, y = 0, z = 0}, - {x = 0, y = 0, z = 1}, - {x = 0, y = 0, z = -1}, - {x = 0, y = 0, z = 0}, - {x = 0, y = 1, z = 0}, + vector.new(-1, -1, 0), + vector.new( 1, -1, 0), + vector.new( 0, -1, -1), + vector.new( 0, -1, 1), + vector.new( 0, -1, 0), + vector.new(-1, 0, 0), + vector.new( 1, 0, 0), + vector.new( 0, 0, 1), + vector.new( 0, 0, -1), + vector.new( 0, 0, 0), + vector.new( 0, 1, 0), } function core.check_for_falling(p) diff --git a/builtin/game/init.lua b/builtin/game/init.lua index 1d62be019..bb007fabd 100644 --- a/builtin/game/init.lua +++ b/builtin/game/init.lua @@ -7,8 +7,6 @@ local gamepath = scriptpath .. "game".. DIR_DELIM -- not exposed to outer context local builtin_shared = {} -dofile(commonpath .. "vector.lua") - dofile(gamepath .. "constants.lua") assert(loadfile(gamepath .. "item.lua"))(builtin_shared) dofile(gamepath .. "register.lua") diff --git a/builtin/game/item.lua b/builtin/game/item.lua index 17746e9a8..99465e099 100644 --- a/builtin/game/item.lua +++ b/builtin/game/item.lua @@ -92,12 +92,12 @@ end -- Table of possible dirs local facedir_to_dir = { - {x= 0, y=0, z= 1}, - {x= 1, y=0, z= 0}, - {x= 0, y=0, z=-1}, - {x=-1, y=0, z= 0}, - {x= 0, y=-1, z= 0}, - {x= 0, y=1, z= 0}, + vector.new( 0, 0, 1), + vector.new( 1, 0, 0), + vector.new( 0, 0, -1), + vector.new(-1, 0, 0), + vector.new( 0, -1, 0), + vector.new( 0, 1, 0), } -- Mapping from facedir value to index in facedir_to_dir. local facedir_to_dir_map = { @@ -136,12 +136,12 @@ end -- table of dirs in wallmounted order local wallmounted_to_dir = { - [0] = {x = 0, y = 1, z = 0}, - {x = 0, y = -1, z = 0}, - {x = 1, y = 0, z = 0}, - {x = -1, y = 0, z = 0}, - {x = 0, y = 0, z = 1}, - {x = 0, y = 0, z = -1}, + [0] = vector.new( 0, 1, 0), + vector.new( 0, -1, 0), + vector.new( 1, 0, 0), + vector.new(-1, 0, 0), + vector.new( 0, 0, 1), + vector.new( 0, 0, -1), } function core.wallmounted_to_dir(wallmounted) return wallmounted_to_dir[wallmounted % 8] @@ -152,7 +152,7 @@ function core.dir_to_yaw(dir) end function core.yaw_to_dir(yaw) - return {x = -math.sin(yaw), y = 0, z = math.cos(yaw)} + return vector.new(-math.sin(yaw), 0, math.cos(yaw)) end function core.is_colored_paramtype(ptype) @@ -290,12 +290,12 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2, end -- Place above pointed node - local place_to = {x = above.x, y = above.y, z = above.z} + local place_to = vector.new(above) -- If node under is buildable_to, place into it instead (eg. snow) if olddef_under.buildable_to then log("info", "node under is buildable to") - place_to = {x = under.x, y = under.y, z = under.z} + place_to = vector.new(under) end if core.is_protected(place_to, playername) then @@ -315,22 +315,14 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2, newnode.param2 = def.place_param2 elseif (def.paramtype2 == "wallmounted" or def.paramtype2 == "colorwallmounted") and not param2 then - local dir = { - x = under.x - above.x, - y = under.y - above.y, - z = under.z - above.z - } + local dir = vector.subtract(under, above) newnode.param2 = core.dir_to_wallmounted(dir) -- Calculate the direction for furnaces and chests and stuff elseif (def.paramtype2 == "facedir" or def.paramtype2 == "colorfacedir") and not param2 then local placer_pos = placer and placer:get_pos() if placer_pos then - local dir = { - x = above.x - placer_pos.x, - y = above.y - placer_pos.y, - z = above.z - placer_pos.z - } + local dir = vector.subtract(above, placer_pos) newnode.param2 = core.dir_to_facedir(dir) log("info", "facedir: " .. newnode.param2) end @@ -384,7 +376,7 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2, -- Run callback if def.after_place_node and not prevent_after_place then -- Deepcopy place_to and pointed_thing because callback can modify it - local place_to_copy = {x=place_to.x, y=place_to.y, z=place_to.z} + local place_to_copy = vector.new(place_to) local pointed_thing_copy = copy_pointed_thing(pointed_thing) if def.after_place_node(place_to_copy, placer, itemstack, pointed_thing_copy) then @@ -395,7 +387,7 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2, -- Run script hook for _, callback in ipairs(core.registered_on_placenodes) do -- Deepcopy pos, node and pointed_thing because callback can modify them - local place_to_copy = {x=place_to.x, y=place_to.y, z=place_to.z} + local place_to_copy = vector.new(place_to) local newnode_copy = {name=newnode.name, param1=newnode.param1, param2=newnode.param2} local oldnode_copy = {name=oldnode.name, param1=oldnode.param1, param2=oldnode.param2} local pointed_thing_copy = copy_pointed_thing(pointed_thing) @@ -541,11 +533,11 @@ function core.handle_node_drops(pos, drops, digger) for _, dropped_item in pairs(drops) do local left = give_item(dropped_item) if not left:is_empty() then - local p = { - x = pos.x + math.random()/2-0.25, - y = pos.y + math.random()/2-0.25, - z = pos.z + math.random()/2-0.25, - } + local p = vector.offset(pos, + math.random()/2-0.25, + math.random()/2-0.25, + math.random()/2-0.25 + ) core.add_item(p, left) end end @@ -604,7 +596,7 @@ function core.node_dig(pos, node, digger) if def and def.preserve_metadata then local oldmeta = core.get_meta(pos):to_table().fields -- Copy pos and node because the callback can modify them. - local pos_copy = {x=pos.x, y=pos.y, z=pos.z} + local pos_copy = vector.new(pos) local node_copy = {name=node.name, param1=node.param1, param2=node.param2} local drop_stacks = {} for k, v in pairs(drops) do @@ -636,7 +628,7 @@ function core.node_dig(pos, node, digger) -- Run callback if def and def.after_dig_node then -- Copy pos and node because callback can modify them - local pos_copy = {x=pos.x, y=pos.y, z=pos.z} + local pos_copy = vector.new(pos) local node_copy = {name=node.name, param1=node.param1, param2=node.param2} def.after_dig_node(pos_copy, node_copy, oldmetadata, digger) end @@ -649,7 +641,7 @@ function core.node_dig(pos, node, digger) end -- Copy pos and node because callback can modify them - local pos_copy = {x=pos.x, y=pos.y, z=pos.z} + local pos_copy = vector.new(pos) local node_copy = {name=node.name, param1=node.param1, param2=node.param2} callback(pos_copy, node_copy, digger) end @@ -692,7 +684,7 @@ core.nodedef_default = { groups = {}, inventory_image = "", wield_image = "", - wield_scale = {x=1,y=1,z=1}, + wield_scale = vector.new(1, 1, 1), stack_max = default_stack_max, usable = false, liquids_pointable = false, @@ -751,7 +743,7 @@ core.craftitemdef_default = { groups = {}, inventory_image = "", wield_image = "", - wield_scale = {x=1,y=1,z=1}, + wield_scale = vector.new(1, 1, 1), stack_max = default_stack_max, liquids_pointable = false, tool_capabilities = nil, @@ -770,7 +762,7 @@ core.tooldef_default = { groups = {}, inventory_image = "", wield_image = "", - wield_scale = {x=1,y=1,z=1}, + wield_scale = vector.new(1, 1, 1), stack_max = 1, liquids_pointable = false, tool_capabilities = nil, @@ -789,7 +781,7 @@ core.noneitemdef_default = { -- This is used for the hand and unknown items groups = {}, inventory_image = "", wield_image = "", - wield_scale = {x=1,y=1,z=1}, + wield_scale = vector.new(1, 1, 1), stack_max = default_stack_max, liquids_pointable = false, tool_capabilities = nil, diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua index fcb86146d..c13a583f0 100644 --- a/builtin/game/misc.lua +++ b/builtin/game/misc.lua @@ -119,13 +119,12 @@ end function core.get_position_from_hash(hash) - local pos = {} - pos.x = (hash % 65536) - 32768 + local x = (hash % 65536) - 32768 hash = math.floor(hash / 65536) - pos.y = (hash % 65536) - 32768 + local y = (hash % 65536) - 32768 hash = math.floor(hash / 65536) - pos.z = (hash % 65536) - 32768 - return pos + local z = (hash % 65536) - 32768 + return vector.new(x, y, z) end @@ -215,7 +214,7 @@ function core.is_area_protected(minp, maxp, player_name, interval) local y = math.floor(yf + 0.5) for xf = minp.x, maxp.x, d.x do local x = math.floor(xf + 0.5) - local pos = {x = x, y = y, z = z} + local pos = vector.new(x, y, z) if core.is_protected(pos, player_name) then return pos end diff --git a/builtin/game/voxelarea.lua b/builtin/game/voxelarea.lua index 724761414..64436bf1a 100644 --- a/builtin/game/voxelarea.lua +++ b/builtin/game/voxelarea.lua @@ -1,6 +1,6 @@ VoxelArea = { - MinEdge = {x=1, y=1, z=1}, - MaxEdge = {x=0, y=0, z=0}, + MinEdge = vector.new(1, 1, 1), + MaxEdge = vector.new(0, 0, 0), ystride = 0, zstride = 0, } @@ -19,11 +19,11 @@ end function VoxelArea:getExtent() local MaxEdge, MinEdge = self.MaxEdge, self.MinEdge - return { - x = MaxEdge.x - MinEdge.x + 1, - y = MaxEdge.y - MinEdge.y + 1, - z = MaxEdge.z - MinEdge.z + 1, - } + return vector.new( + MaxEdge.x - MinEdge.x + 1, + MaxEdge.y - MinEdge.y + 1, + MaxEdge.z - MinEdge.z + 1 + ) end function VoxelArea:getVolume() diff --git a/builtin/init.lua b/builtin/init.lua index 89b1fdc64..7a9b5c427 100644 --- a/builtin/init.lua +++ b/builtin/init.lua @@ -30,6 +30,7 @@ local clientpath = scriptdir .. "client" .. DIR_DELIM local commonpath = scriptdir .. "common" .. DIR_DELIM local asyncpath = scriptdir .. "async" .. DIR_DELIM +dofile(commonpath .. "vector.lua") dofile(commonpath .. "strict.lua") dofile(commonpath .. "serialize.lua") dofile(commonpath .. "misc_helpers.lua") diff --git a/builtin/mainmenu/tests/serverlistmgr_spec.lua b/builtin/mainmenu/tests/serverlistmgr_spec.lua index 148e9b794..a091959fb 100644 --- a/builtin/mainmenu/tests/serverlistmgr_spec.lua +++ b/builtin/mainmenu/tests/serverlistmgr_spec.lua @@ -2,6 +2,7 @@ _G.core = {} _G.unpack = table.unpack _G.serverlistmgr = {} +dofile("builtin/common/vector.lua") dofile("builtin/common/misc_helpers.lua") dofile("builtin/mainmenu/serverlistmgr.lua") diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 0f57f1f28..0c81ca911 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -1505,6 +1505,9 @@ Position/vector {x=num, y=num, z=num} + Note: it is highly recommended to construct a vector using the helper function: + vector.new(num, num, num) + For helper functions see [Spatial Vectors]. `pointed_thing` @@ -3168,15 +3171,35 @@ no particular point. Internally, it is implemented as a table with the 3 fields `x`, `y` and `z`. Example: `{x = 0, y = 1, z = 0}`. +However, one should *never* create a vector manually as above, such misbehavior +is deprecated. The vector helpers set a metatable for the created vectors which +allows indexing with numbers, calling functions directly on vectors and using +operators (like `+`). Furthermore, the internal implementation might change in +the future. +Old code might still use vectors without metatables, be aware of this! + +All these forms of addressing a vector `v` are valid: +`v[1]`, `v[3]`, `v.x`, `v[1] = 42`, `v.y = 13` + +Where `v` is a vector and `foo` stands for any function name, `v:foo(...)` does +the same as `vector.foo(v, ...)`, apart from deprecated functionality. + +The metatable that is used for vectors can be accessed via `vector.metatable`. +Do not modify it! + +All `vector.*` functions allow vectors `{x = X, y = Y, z = Z}` without metatables. +Returned vectors always have a metatable set. For the following functions, `v`, `v1`, `v2` are vectors, `p1`, `p2` are positions, -`s` is a scalar (a number): +`s` is a scalar (a number), +vectors are written like this: `(x, y, z)`: -* `vector.new(a[, b, c])`: +* `vector.new([a[, b, c]])`: * Returns a vector. * A copy of `a` if `a` is a vector. - * `{x = a, y = b, z = c}`, if all of `a`, `b`, `c` are defined numbers. + * `(a, b, c)`, if all of `a`, `b`, `c` are defined numbers. + * `(0, 0, 0)`, if no arguments are given. * `vector.from_string(s[, init])`: * Returns `v, np`, where `v` is a vector read from the given string `s` and `np` is the next position in the string after the vector. @@ -3189,14 +3212,14 @@ For the following functions, `v`, `v1`, `v2` are vectors, * Returns a string of the form `"(x, y, z)"`. * `vector.direction(p1, p2)`: * Returns a vector of length 1 with direction `p1` to `p2`. - * If `p1` and `p2` are identical, returns `{x = 0, y = 0, z = 0}`. + * If `p1` and `p2` are identical, returns `(0, 0, 0)`. * `vector.distance(p1, p2)`: * Returns zero or a positive number, the distance between `p1` and `p2`. * `vector.length(v)`: * Returns zero or a positive number, the length of vector `v`. * `vector.normalize(v)`: * Returns a vector of length 1 with direction of vector `v`. - * If `v` has zero length, returns `{x = 0, y = 0, z = 0}`. + * If `v` has zero length, returns `(0, 0, 0)`. * `vector.floor(v)`: * Returns a vector, each dimension rounded down. * `vector.round(v)`: @@ -3216,7 +3239,11 @@ For the following functions, `v`, `v1`, `v2` are vectors, * `vector.cross(v1, v2)`: * Returns the cross product of `v1` and `v2`. * `vector.offset(v, x, y, z)`: - * Returns the sum of the vectors `v` and `{x = x, y = y, z = z}`. + * Returns the sum of the vectors `v` and `(x, y, z)`. +* `vector.check()`: + * Returns a boolean value indicating whether `v` is a real vector, eg. created + by a `vector.*` function. + * Returns `false` for anything else, including tables like `{x=3,y=1,z=4}`. For the following functions `x` can be either a vector or a number: @@ -3235,14 +3262,30 @@ For the following functions `x` can be either a vector or a number: * Returns a scaled vector. * Deprecated: If `s` is a vector: Returns the Schur quotient. +Operators can be used if all of the involved vectors have metatables: +* `v1 == v2`: + * Returns whether `v1` and `v2` are identical. +* `-v`: + * Returns the additive inverse of v. +* `v1 + v2`: + * Returns the sum of both vectors. + * Note: `+` can not be used together with scalars. +* `v1 - v2`: + * Returns the difference of `v1` subtracted by `v2`. + * Note: `-` can not be used together with scalars. +* `v * s` or `s * v`: + * Returns `v` scaled by `s`. +* `v / s`: + * Returns `v` scaled by `1 / s`. + For the following functions `a` is an angle in radians and `r` is a rotation vector ({x = , y = , z = }) where pitch, yaw and roll are angles in radians. * `vector.rotate(v, r)`: * Applies the rotation `r` to `v` and returns the result. - * `vector.rotate({x = 0, y = 0, z = 1}, r)` and - `vector.rotate({x = 0, y = 1, z = 0}, r)` return vectors pointing + * `vector.rotate(vector.new(0, 0, 1), r)` and + `vector.rotate(vector.new(0, 1, 0), r)` return vectors pointing forward and up relative to an entity's rotation `r`. * `vector.rotate_around_axis(v1, v2, a)`: * Returns `v1` rotated around axis `v2` by `a` radians according to diff --git a/src/script/common/c_converter.cpp b/src/script/common/c_converter.cpp index c00401b58..d848b75b8 100644 --- a/src/script/common/c_converter.cpp +++ b/src/script/common/c_converter.cpp @@ -51,6 +51,29 @@ if (value < F1000_MIN || value > F1000_MAX) { \ #define CHECK_POS_TAB(index) CHECK_TYPE(index, "position", LUA_TTABLE) +/** + * A helper which sets (if available) the vector metatable from builtin as metatable + * for the table on top of the stack + */ +static void set_vector_metatable(lua_State *L) +{ + // get vector.metatable + lua_getglobal(L, "vector"); + if (!lua_istable(L, -1)) { + // there is no global vector table + lua_pop(L, 1); + errorstream << "set_vector_metatable in c_converter.cpp: " << + "missing global vector table" << std::endl; + return; + } + lua_getfield(L, -1, "metatable"); + // set the metatable + lua_setmetatable(L, -3); + // pop vector global + lua_pop(L, 1); +} + + void push_float_string(lua_State *L, float value) { std::stringstream ss; @@ -69,6 +92,7 @@ void push_v3f(lua_State *L, v3f p) lua_setfield(L, -2, "y"); lua_pushnumber(L, p.Z); lua_setfield(L, -2, "z"); + set_vector_metatable(L); } void push_v2f(lua_State *L, v2f p) @@ -281,6 +305,7 @@ void push_v3s16(lua_State *L, v3s16 p) lua_setfield(L, -2, "y"); lua_pushinteger(L, p.Z); lua_setfield(L, -2, "z"); + set_vector_metatable(L); } v3s16 read_v3s16(lua_State *L, int index) -- cgit v1.2.3 From c47313db65f968559711ac1b505ef341a9872017 Mon Sep 17 00:00:00 2001 From: Liso Date: Sun, 6 Jun 2021 18:51:21 +0200 Subject: Shadow mapping render pass (#11244) Co-authored-by: x2048 --- builtin/mainmenu/tab_settings.lua | 58 ++- builtin/settingtypes.txt | 52 ++ client/shaders/nodes_shader/opengl_fragment.glsl | 431 +++++++++++++++- client/shaders/nodes_shader/opengl_vertex.glsl | 59 ++- client/shaders/object_shader/opengl_fragment.glsl | 297 +++++++++++- client/shaders/object_shader/opengl_vertex.glsl | 46 ++ client/shaders/shadow_shaders/pass1_fragment.glsl | 13 + .../shadow_shaders/pass1_trans_fragment.glsl | 38 ++ .../shaders/shadow_shaders/pass1_trans_vertex.glsl | 26 + client/shaders/shadow_shaders/pass1_vertex.glsl | 26 + client/shaders/shadow_shaders/pass2_fragment.glsl | 23 + client/shaders/shadow_shaders/pass2_vertex.glsl | 9 + src/client/CMakeLists.txt | 4 + src/client/clientmap.cpp | 218 ++++++++- src/client/clientmap.h | 11 +- src/client/content_cao.cpp | 13 +- src/client/game.cpp | 37 +- src/client/mapblock_mesh.cpp | 6 +- src/client/render/core.cpp | 21 +- src/client/render/core.h | 5 + src/client/renderingengine.h | 11 + src/client/shader.cpp | 64 +++ src/client/shadows/dynamicshadows.cpp | 145 ++++++ src/client/shadows/dynamicshadows.h | 102 ++++ src/client/shadows/dynamicshadowsrender.cpp | 539 +++++++++++++++++++++ src/client/shadows/dynamicshadowsrender.h | 146 ++++++ src/client/shadows/shadowsScreenQuad.cpp | 67 +++ src/client/shadows/shadowsScreenQuad.h | 45 ++ src/client/shadows/shadowsshadercallbacks.cpp | 44 ++ src/client/shadows/shadowsshadercallbacks.h | 34 ++ src/client/sky.cpp | 38 +- src/client/sky.h | 7 + src/client/wieldmesh.cpp | 12 + src/client/wieldmesh.h | 3 + src/defaultsettings.cpp | 12 + 35 files changed, 2624 insertions(+), 38 deletions(-) create mode 100644 client/shaders/shadow_shaders/pass1_fragment.glsl create mode 100644 client/shaders/shadow_shaders/pass1_trans_fragment.glsl create mode 100644 client/shaders/shadow_shaders/pass1_trans_vertex.glsl create mode 100644 client/shaders/shadow_shaders/pass1_vertex.glsl create mode 100644 client/shaders/shadow_shaders/pass2_fragment.glsl create mode 100644 client/shaders/shadow_shaders/pass2_vertex.glsl create mode 100644 src/client/shadows/dynamicshadows.cpp create mode 100644 src/client/shadows/dynamicshadows.h create mode 100644 src/client/shadows/dynamicshadowsrender.cpp create mode 100644 src/client/shadows/dynamicshadowsrender.h create mode 100644 src/client/shadows/shadowsScreenQuad.cpp create mode 100644 src/client/shadows/shadowsScreenQuad.h create mode 100644 src/client/shadows/shadowsshadercallbacks.cpp create mode 100644 src/client/shadows/shadowsshadercallbacks.h (limited to 'src') diff --git a/builtin/mainmenu/tab_settings.lua b/builtin/mainmenu/tab_settings.lua index 29744048a..8bc5bf8b6 100644 --- a/builtin/mainmenu/tab_settings.lua +++ b/builtin/mainmenu/tab_settings.lua @@ -43,6 +43,14 @@ local labels = { fgettext("2x"), fgettext("4x"), fgettext("8x") + }, + shadow_levels = { + fgettext("Disabled"), + fgettext("Very Low"), + fgettext("Low"), + fgettext("Medium"), + fgettext("High"), + fgettext("Ultra High") } } @@ -66,6 +74,10 @@ local dd_options = { antialiasing = { table.concat(labels.antialiasing, ","), {"0", "2", "4", "8"} + }, + shadow_levels = { + table.concat(labels.shadow_levels, ","), + { "0", "1", "2", "3", "4", "5" } } } @@ -110,6 +122,15 @@ local getSettingIndex = { end end return 1 + end, + ShadowMapping = function() + local shadow_setting = core.settings:get("shadow_levels") + for i = 1, #dd_options.shadow_levels[2] do + if shadow_setting == dd_options.shadow_levels[2][i] then + return i + end + end + return 1 end } @@ -197,7 +218,10 @@ local function formspec(tabview, name, tabdata) "checkbox[8.25,1.5;cb_waving_leaves;" .. fgettext("Waving Leaves") .. ";" .. dump(core.settings:get_bool("enable_waving_leaves")) .. "]" .. "checkbox[8.25,2;cb_waving_plants;" .. fgettext("Waving Plants") .. ";" - .. dump(core.settings:get_bool("enable_waving_plants")) .. "]" + .. dump(core.settings:get_bool("enable_waving_plants")) .. "]".. + "label[8.25,3.0;" .. fgettext("Dynamic shadows: ") .. "]" .. + "dropdown[8.25,3.5;3.5;dd_shadows;" .. dd_options.shadow_levels[1] .. ";" + .. getSettingIndex.ShadowMapping() .. "]" else tab_string = tab_string .. "label[8.38,0.7;" .. core.colorize("#888888", @@ -207,7 +231,9 @@ local function formspec(tabview, name, tabdata) "label[8.38,1.7;" .. core.colorize("#888888", fgettext("Waving Leaves")) .. "]" .. "label[8.38,2.2;" .. core.colorize("#888888", - fgettext("Waving Plants")) .. "]" + fgettext("Waving Plants")) .. "]".. + "label[8.38,2.7;" .. core.colorize("#888888", + fgettext("Dynamic shadows")) .. "]" end return tab_string @@ -333,6 +359,34 @@ local function handle_settings_buttons(this, fields, tabname, tabdata) ddhandled = true end + for i = 1, #labels.shadow_levels do + if fields["dd_shadows"] == labels.shadow_levels[i] then + core.settings:set("shadow_levels", dd_options.shadow_levels[2][i]) + ddhandled = true + end + end + + if fields["dd_shadows"] == labels.shadow_levels[1] then + core.settings:set("enable_dynamic_shadows", "false") + else + core.settings:set("enable_dynamic_shadows", "true") + local shadow_presets = { + [2] = { 80, 512, "true", 0, "false" }, + [3] = { 120, 1024, "true", 1, "false" }, + [4] = { 350, 2048, "true", 1, "false" }, + [5] = { 350, 2048, "true", 2, "true" }, + [6] = { 450, 4096, "true", 2, "true" }, + } + local s = shadow_presets[table.indexof(labels.shadow_levels, fields["dd_shadows"])] + if s then + core.settings:set("shadow_map_max_distance", s[1]) + core.settings:set("shadow_map_texture_size", s[2]) + core.settings:set("shadow_map_texture_32bit", s[3]) + core.settings:set("shadow_filters", s[4]) + core.settings:set("shadow_map_color", s[5]) + end + end + return ddhandled end diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index d13bac91b..ae421de2c 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -582,6 +582,58 @@ enable_waving_leaves (Waving leaves) bool false # Requires shaders to be enabled. enable_waving_plants (Waving plants) bool false +[***Dynamic shadows] + +# Set to true to enable Shadow Mapping. +# Requires shaders to be enabled. +enable_dynamic_shadows (Dynamic shadows) bool false + +# Set the shadow strength. +# Lower value means lighter shadows, higher value means darker shadows. +shadow_strength (Shadow strength) float 0.2 0.05 1.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 + +# Texture size to render the shadow map on. +# This must be a power of two. +# Bigger numbers create better shadowsbut it is also more expensive. +shadow_map_texture_size (Shadow map texture size) int 1024 128 8192 + +# Sets shadow texture quality to 32 bits. +# On false, 16 bits texture will be used. +# This can cause much more artifacts in the shadow. +shadow_map_texture_32bit (Shadow map texture in 32 bits) bool true + +# Enable poisson disk filtering. +# On true uses poisson disk to make "soft shadows". Otherwise uses PCF filtering. +shadow_poisson_filter (Poisson filtering) bool true + +# Define shadow filtering quality +# This simulates the soft shadows effect by applying a PCF or poisson disk +# but also uses more resources. +shadow_filters (Shadow filter quality) enum 1 0,1,2 + +# Enable colored shadows. +# On true translucent nodes cast colored shadows. This is expensive. +shadow_map_color (Colored shadows) bool false + + +# Set the shadow update time. +# Lower value means shadows and map updates faster, but it consume more resources. +# Minimun value 0.001 seconds max value 0.2 seconds +shadow_update_time (Map update time) float 0.2 0.001 0.2 + +# Set the soft shadow radius size. +# Lower values mean sharper shadows bigger values softer. +# Minimun value 1.0 and max value 10.0 +shadow_soft_radius (Soft shadow radius) float 1.0 1.0 10.0 + +# Set the tilt of Sun/Moon orbit in degrees +# Value of 0 means no tilt / vertical orbit. +# Minimun value 0.0 and max value 60.0 +shadow_sky_body_orbit_tilt (Sky Body Orbit Tilt) float 0.0 0.0 60.0 + [**Advanced] # Arm inertia, gives a more realistic movement of diff --git a/client/shaders/nodes_shader/opengl_fragment.glsl b/client/shaders/nodes_shader/opengl_fragment.glsl index b58095063..43a8b1f25 100644 --- a/client/shaders/nodes_shader/opengl_fragment.glsl +++ b/client/shaders/nodes_shader/opengl_fragment.glsl @@ -7,7 +7,22 @@ uniform vec3 eyePosition; // The cameraOffset is the current center of the visible world. uniform vec3 cameraOffset; uniform float animationTimer; +#ifdef ENABLE_DYNAMIC_SHADOWS + // shadow texture + uniform sampler2D ShadowMapSampler; + // shadow uniforms + uniform vec3 v_LightDirection; + uniform float f_textureresolution; + uniform mat4 m_ShadowViewProj; + uniform float f_shadowfar; + varying float normalOffsetScale; + varying float adj_shadow_strength; + varying float cosLight; + varying float f_normal_length; +#endif + +varying vec3 vNormal; varying vec3 vPosition; // World position in the visible world (i.e. relative to the cameraOffset.) // This can be used for many shader effects without loss of precision. @@ -22,10 +37,388 @@ varying mediump vec2 varTexCoord; centroid varying vec2 varTexCoord; #endif varying vec3 eyeVec; +varying float nightRatio; const float fogStart = FOG_START; const float fogShadingParameter = 1.0 / ( 1.0 - fogStart); + + +#ifdef ENABLE_DYNAMIC_SHADOWS +const float bias0 = 0.9; +const float zPersFactor = 0.5; +const float bias1 = 1.0 - bias0 + 1e-6; + +vec4 getPerspectiveFactor(in vec4 shadowPosition) +{ + + float pDistance = length(shadowPosition.xy); + float pFactor = pDistance * bias0 + bias1; + + shadowPosition.xyz *= vec3(vec2(1.0 / pFactor), zPersFactor); + + return shadowPosition; +} + +// assuming near is always 1.0 +float getLinearDepth() +{ + return 2.0 * f_shadowfar / (f_shadowfar + 1.0 - (2.0 * gl_FragCoord.z - 1.0) * (f_shadowfar - 1.0)); +} + +vec3 getLightSpacePosition() +{ + vec4 pLightSpace; + // some drawtypes have zero normals, so we need to handle it :( + #if DRAW_TYPE == NDT_PLANTLIKE + pLightSpace = m_ShadowViewProj * vec4(worldPosition, 1.0); + #else + float offsetScale = (0.0057 * getLinearDepth() + normalOffsetScale); + pLightSpace = m_ShadowViewProj * vec4(worldPosition + offsetScale * normalize(vNormal), 1.0); + #endif + pLightSpace = getPerspectiveFactor(pLightSpace); + return pLightSpace.xyz * 0.5 + 0.5; +} +// custom smoothstep implementation because it's not defined in glsl1.2 +// https://docs.gl/sl4/smoothstep +float mtsmoothstep(in float edge0, in float edge1, in float x) +{ + float t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0); + return t * t * (3.0 - 2.0 * t); +} + +#ifdef COLORED_SHADOWS + +// c_precision of 128 fits within 7 base-10 digits +const float c_precision = 128.0; +const float c_precisionp1 = c_precision + 1.0; + +float packColor(vec3 color) +{ + return floor(color.b * c_precision + 0.5) + + floor(color.g * c_precision + 0.5) * c_precisionp1 + + floor(color.r * c_precision + 0.5) * c_precisionp1 * c_precisionp1; +} + +vec3 unpackColor(float value) +{ + vec3 color; + color.b = mod(value, c_precisionp1) / c_precision; + color.g = mod(floor(value / c_precisionp1), c_precisionp1) / c_precision; + color.r = floor(value / (c_precisionp1 * c_precisionp1)) / c_precision; + return color; +} + +vec4 getHardShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) +{ + vec4 texDepth = texture2D(shadowsampler, smTexCoord.xy).rgba; + + float visibility = step(0.0, realDistance - texDepth.r); + vec4 result = vec4(visibility, vec3(0.0,0.0,0.0));//unpackColor(texDepth.g)); + if (visibility < 0.1) { + visibility = step(0.0, realDistance - texDepth.b); + result = vec4(visibility, unpackColor(texDepth.a)); + } + return result; +} + +#else + +float getHardShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) +{ + float texDepth = texture2D(shadowsampler, smTexCoord.xy).r; + float visibility = step(0.0, realDistance - texDepth); + return visibility; +} + +#endif + + +#if SHADOW_FILTER == 2 + #define PCFBOUND 3.5 + #define PCFSAMPLES 64.0 +#elif SHADOW_FILTER == 1 + #define PCFBOUND 1.5 + #if defined(POISSON_FILTER) + #define PCFSAMPLES 32.0 + #else + #define PCFSAMPLES 16.0 + #endif +#else + #define PCFBOUND 0.0 + #if defined(POISSON_FILTER) + #define PCFSAMPLES 4.0 + #else + #define PCFSAMPLES 1.0 + #endif +#endif +#ifdef COLORED_SHADOWS +float getHardShadowDepth(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) +{ + vec4 texDepth = texture2D(shadowsampler, smTexCoord.xy); + float depth = max(realDistance - texDepth.r, realDistance - texDepth.b); + return depth; +} +#else +float getHardShadowDepth(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) +{ + float texDepth = texture2D(shadowsampler, smTexCoord.xy).r; + float depth = realDistance - texDepth; + return depth; +} +#endif + +float getBaseLength(vec2 smTexCoord) +{ + float l = length(2.0 * smTexCoord.xy - 1.0); // length in texture coords + return bias1 / (1.0 / l - bias0); // return to undistorted coords +} + +float getDeltaPerspectiveFactor(float l) +{ + return 0.1 / (bias0 * l + bias1); // original distortion factor, divided by 10 +} + +float getPenumbraRadius(sampler2D shadowsampler, vec2 smTexCoord, float realDistance, float multiplier) +{ + // Return fast if sharp shadows are requested + if (SOFTSHADOWRADIUS <= 1.0) + return SOFTSHADOWRADIUS; + + vec2 clampedpos; + float texture_size = 1.0 / (2048 /*f_textureresolution*/ * 0.5); + float y, x; + float depth = 0.0; + float pointDepth; + float maxRadius = SOFTSHADOWRADIUS * 5.0 * multiplier; + + float baseLength = getBaseLength(smTexCoord); + float perspectiveFactor; + float bound = clamp(PCFBOUND * (1 - baseLength), 0.5, PCFBOUND); + int n = 0; + + for (y = -bound; y <= bound; y += 1.0) + for (x = -bound; x <= bound; x += 1.0) { + clampedpos = vec2(x,y); + perspectiveFactor = getDeltaPerspectiveFactor(baseLength + length(clampedpos) * texture_size * maxRadius); + clampedpos = clampedpos * texture_size * perspectiveFactor * maxRadius * perspectiveFactor + smTexCoord.xy; + + pointDepth = getHardShadowDepth(shadowsampler, clampedpos.xy, realDistance); + if (pointDepth > -0.01) { + depth += pointDepth; + n += 1; + } + } + + depth = depth / n; + + depth = pow(clamp(depth, 0.0, 1000.0), 1.6) / 0.001; + return max(0.5, depth * maxRadius); +} + +#ifdef POISSON_FILTER +const vec2[64] poissonDisk = vec2[64]( + vec2(0.170019, -0.040254), + vec2(-0.299417, 0.791925), + vec2(0.645680, 0.493210), + vec2(-0.651784, 0.717887), + vec2(0.421003, 0.027070), + vec2(-0.817194, -0.271096), + vec2(-0.705374, -0.668203), + vec2(0.977050, -0.108615), + vec2(0.063326, 0.142369), + vec2(0.203528, 0.214331), + vec2(-0.667531, 0.326090), + vec2(-0.098422, -0.295755), + vec2(-0.885922, 0.215369), + vec2(0.566637, 0.605213), + vec2(0.039766, -0.396100), + vec2(0.751946, 0.453352), + vec2(0.078707, -0.715323), + vec2(-0.075838, -0.529344), + vec2(0.724479, -0.580798), + vec2(0.222999, -0.215125), + vec2(-0.467574, -0.405438), + vec2(-0.248268, -0.814753), + vec2(0.354411, -0.887570), + vec2(0.175817, 0.382366), + vec2(0.487472, -0.063082), + vec2(0.355476, 0.025357), + vec2(-0.084078, 0.898312), + vec2(0.488876, -0.783441), + vec2(0.470016, 0.217933), + vec2(-0.696890, -0.549791), + vec2(-0.149693, 0.605762), + vec2(0.034211, 0.979980), + vec2(0.503098, -0.308878), + vec2(-0.016205, -0.872921), + vec2(0.385784, -0.393902), + vec2(-0.146886, -0.859249), + vec2(0.643361, 0.164098), + vec2(0.634388, -0.049471), + vec2(-0.688894, 0.007843), + vec2(0.464034, -0.188818), + vec2(-0.440840, 0.137486), + vec2(0.364483, 0.511704), + vec2(0.034028, 0.325968), + vec2(0.099094, -0.308023), + vec2(0.693960, -0.366253), + vec2(0.678884, -0.204688), + vec2(0.001801, 0.780328), + vec2(0.145177, -0.898984), + vec2(0.062655, -0.611866), + vec2(0.315226, -0.604297), + vec2(-0.780145, 0.486251), + vec2(-0.371868, 0.882138), + vec2(0.200476, 0.494430), + vec2(-0.494552, -0.711051), + vec2(0.612476, 0.705252), + vec2(-0.578845, -0.768792), + vec2(-0.772454, -0.090976), + vec2(0.504440, 0.372295), + vec2(0.155736, 0.065157), + vec2(0.391522, 0.849605), + vec2(-0.620106, -0.328104), + vec2(0.789239, -0.419965), + vec2(-0.545396, 0.538133), + vec2(-0.178564, -0.596057) +); + +#ifdef COLORED_SHADOWS + +vec4 getShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) +{ + vec2 clampedpos; + vec4 visibility = vec4(0.0); + float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance, 1.5); // scale to align with PCF + if (radius < 0.1) { + // we are in the middle of even brightness, no need for filtering + return getHardShadowColor(shadowsampler, smTexCoord.xy, realDistance); + } + + float baseLength = getBaseLength(smTexCoord); + float perspectiveFactor; + + float texture_size = 1.0 / (f_textureresolution * 0.5); + int samples = int(clamp(PCFSAMPLES * (1 - baseLength) * (1 - baseLength), 1, PCFSAMPLES)); + int init_offset = int(floor(mod(((smTexCoord.x * 34.0) + 1.0) * smTexCoord.y, 64.0-samples))); + int end_offset = int(samples) + init_offset; + + for (int x = init_offset; x < end_offset; x++) { + clampedpos = poissonDisk[x]; + perspectiveFactor = getDeltaPerspectiveFactor(baseLength + length(clampedpos) * texture_size * radius); + clampedpos = clampedpos * texture_size * perspectiveFactor * radius * perspectiveFactor + smTexCoord.xy; + visibility += getHardShadowColor(shadowsampler, clampedpos.xy, realDistance); + } + + return visibility / samples; +} + +#else + +float getShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) +{ + vec2 clampedpos; + float visibility = 0.0; + float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance, 1.5); // scale to align with PCF + if (radius < 0.1) { + // we are in the middle of even brightness, no need for filtering + return getHardShadow(shadowsampler, smTexCoord.xy, realDistance); + } + + float baseLength = getBaseLength(smTexCoord); + float perspectiveFactor; + + float texture_size = 1.0 / (f_textureresolution * 0.5); + int samples = int(clamp(PCFSAMPLES * (1 - baseLength) * (1 - baseLength), 1, PCFSAMPLES)); + int init_offset = int(floor(mod(((smTexCoord.x * 34.0) + 1.0) * smTexCoord.y, 64.0-samples))); + int end_offset = int(samples) + init_offset; + + for (int x = init_offset; x < end_offset; x++) { + clampedpos = poissonDisk[x]; + perspectiveFactor = getDeltaPerspectiveFactor(baseLength + length(clampedpos) * texture_size * radius); + clampedpos = clampedpos * texture_size * perspectiveFactor * radius * perspectiveFactor + smTexCoord.xy; + visibility += getHardShadow(shadowsampler, clampedpos.xy, realDistance); + } + + return visibility / samples; +} + +#endif + +#else +/* poisson filter disabled */ + +#ifdef COLORED_SHADOWS + +vec4 getShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) +{ + vec2 clampedpos; + vec4 visibility = vec4(0.0); + float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance, 1.0); + if (radius < 0.1) { + // we are in the middle of even brightness, no need for filtering + return getHardShadowColor(shadowsampler, smTexCoord.xy, realDistance); + } + + float baseLength = getBaseLength(smTexCoord); + float perspectiveFactor; + + float texture_size = 1.0 / (f_textureresolution * 0.5); + float y, x; + float bound = clamp(PCFBOUND * (1 - baseLength), 0.5, PCFBOUND); + int n = 0; + + // basic PCF filter + for (y = -bound; y <= bound; y += 1.0) + for (x = -bound; x <= bound; x += 1.0) { + clampedpos = vec2(x,y); // screen offset + perspectiveFactor = getDeltaPerspectiveFactor(baseLength + length(clampedpos) * texture_size * radius / bound); + clampedpos = clampedpos * texture_size * perspectiveFactor * radius * perspectiveFactor / bound + smTexCoord.xy; // both dx,dy and radius are adjusted + visibility += getHardShadowColor(shadowsampler, clampedpos.xy, realDistance); + n += 1; + } + + return visibility / n; +} + +#else +float getShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) +{ + vec2 clampedpos; + float visibility = 0.0; + float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance, 1.0); + if (radius < 0.1) { + // we are in the middle of even brightness, no need for filtering + return getHardShadow(shadowsampler, smTexCoord.xy, realDistance); + } + + float baseLength = getBaseLength(smTexCoord); + float perspectiveFactor; + + float texture_size = 1.0 / (f_textureresolution * 0.5); + float y, x; + float bound = clamp(PCFBOUND * (1 - baseLength), 0.5, PCFBOUND); + int n = 0; + + // basic PCF filter + for (y = -bound; y <= bound; y += 1.0) + for (x = -bound; x <= bound; x += 1.0) { + clampedpos = vec2(x,y); // screen offset + perspectiveFactor = getDeltaPerspectiveFactor(baseLength + length(clampedpos) * texture_size * radius / bound); + clampedpos = clampedpos * texture_size * perspectiveFactor * radius * perspectiveFactor / bound + smTexCoord.xy; // both dx,dy and radius are adjusted + visibility += getHardShadow(shadowsampler, clampedpos.xy, realDistance); + n += 1; + } + + return visibility / n; +} + +#endif + +#endif +#endif + #if ENABLE_TONE_MAPPING /* Hable's UC2 Tone mapping parameters @@ -58,6 +451,8 @@ vec4 applyToneMapping(vec4 color) } #endif + + void main(void) { vec3 color; @@ -74,9 +469,41 @@ void main(void) #endif color = base.rgb; - 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(); + + 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) { + +#ifdef COLORED_SHADOWS + vec4 visibility = getShadowColor(ShadowMapSampler, posLightSpace.xy, posLightSpace.z); + shadow_int = visibility.r; + shadow_color = visibility.gba; +#else + shadow_int = getShadow(ShadowMapSampler, posLightSpace.xy, posLightSpace.z); +#endif + shadow_int *= distance_rate; + shadow_int *= 1.0 - nightRatio; + + + } + + if (f_normal_length != 0 && cosLight < 0.0) { + shadow_int = clamp(1.0-nightRatio, 0.0, 1.0); + } + + shadow_int = 1.0 - (shadow_int * f_adj_shadow_strength); + + col.rgb = mix(shadow_color,col.rgb,shadow_int)*shadow_int; + // col.r = 0.5 * clamp(getPenumbraRadius(ShadowMapSampler, posLightSpace.xy, posLightSpace.z, 1.0) / SOFTSHADOWRADIUS, 0.0, 1.0) + 0.5 * col.r; +#endif + #if ENABLE_TONE_MAPPING col = applyToneMapping(col); #endif @@ -94,6 +521,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 1a4840d35..d316930b2 100644 --- a/client/shaders/nodes_shader/opengl_vertex.glsl +++ b/client/shaders/nodes_shader/opengl_vertex.glsl @@ -1,5 +1,4 @@ uniform mat4 mWorld; - // Color of the light emitted by the sun. uniform vec3 dayLight; uniform vec3 eyePosition; @@ -8,6 +7,7 @@ uniform vec3 eyePosition; uniform vec3 cameraOffset; uniform float animationTimer; +varying vec3 vNormal; varying vec3 vPosition; // World position in the visible world (i.e. relative to the cameraOffset.) // This can be used for many shader effects without loss of precision. @@ -24,13 +24,38 @@ varying mediump vec2 varTexCoord; #else centroid varying vec2 varTexCoord; #endif -varying vec3 eyeVec; +#ifdef ENABLE_DYNAMIC_SHADOWS + // shadow uniforms + uniform vec3 v_LightDirection; + uniform float f_textureresolution; + uniform mat4 m_ShadowViewProj; + uniform float f_shadowfar; + uniform float f_shadow_strength; + uniform float f_timeofday; + varying float cosLight; + varying float normalOffsetScale; + varying float adj_shadow_strength; + varying float f_normal_length; +#endif + +varying vec3 eyeVec; +varying float nightRatio; // Color of the light emitted by the light sources. const vec3 artificialLight = vec3(1.04, 1.04, 1.04); const float e = 2.718281828459; const float BS = 10.0; +#ifdef ENABLE_DYNAMIC_SHADOWS +// custom smoothstep implementation because it's not defined in glsl1.2 +// https://docs.gl/sl4/smoothstep +float mtsmoothstep(in float edge0, in float edge1, in float x) +{ + float t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0); + return t * t * (3.0 - 2.0 * t); +} +#endif + float smoothCurve(float x) { @@ -86,6 +111,9 @@ float snoise(vec3 p) #endif + + + void main(void) { varTexCoord = inTexCoord0.st; @@ -136,10 +164,9 @@ void main(void) gl_Position = mWorldViewProj * inVertexPosition; #endif - vPosition = gl_Position.xyz; - eyeVec = -(mWorldView * inVertexPosition).xyz; + vNormal = inVertexNormal; // Calculate color. // Red, green and blue components are pre-multiplied with @@ -152,7 +179,7 @@ void main(void) vec4 color = inVertexColor; #endif // The alpha gives the ratio of sunlight in the incoming light. - float nightRatio = 1.0 - color.a; + nightRatio = 1.0 - color.a; color.rgb = color.rgb * (color.a * dayLight.rgb + nightRatio * artificialLight.rgb) * 2.0; color.a = 1.0; @@ -164,4 +191,26 @@ void main(void) 0.07 * brightness); varColor = clamp(color, 0.0, 1.0); + +#ifdef ENABLE_DYNAMIC_SHADOWS + vec3 nNormal = normalize(vNormal); + cosLight = dot(nNormal, -v_LightDirection); + float texelSize = 767.0 / f_textureresolution; + float slopeScale = clamp(1.0 - abs(cosLight), 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); +#endif + } diff --git a/client/shaders/object_shader/opengl_fragment.glsl b/client/shaders/object_shader/opengl_fragment.glsl index 9a81d8185..8d6f57a44 100644 --- a/client/shaders/object_shader/opengl_fragment.glsl +++ b/client/shaders/object_shader/opengl_fragment.glsl @@ -23,8 +23,22 @@ const float BS = 10.0; const float fogStart = FOG_START; const float fogShadingParameter = 1.0 / (1.0 - fogStart); -#if ENABLE_TONE_MAPPING +#ifdef ENABLE_DYNAMIC_SHADOWS + // shadow texture + uniform sampler2D ShadowMapSampler; + // shadow uniforms + uniform vec3 v_LightDirection; + uniform float f_textureresolution; + uniform mat4 m_ShadowViewProj; + uniform float f_shadowfar; + uniform float f_timeofday; + varying float normalOffsetScale; + varying float adj_shadow_strength; + varying float cosLight; + varying float f_normal_length; +#endif +#if ENABLE_TONE_MAPPING /* Hable's UC2 Tone mapping parameters A = 0.22; B = 0.30; @@ -55,11 +69,263 @@ vec4 applyToneMapping(vec4 color) } #endif +#ifdef ENABLE_DYNAMIC_SHADOWS +const float bias0 = 0.9; +const float zPersFactor = 0.5; +const float bias1 = 1.0 - bias0; + +vec4 getPerspectiveFactor(in vec4 shadowPosition) +{ + float pDistance = length(shadowPosition.xy); + float pFactor = pDistance * bias0 + bias1; + shadowPosition.xyz *= vec3(vec2(1.0 / pFactor), zPersFactor); + + return shadowPosition; +} + +// assuming near is always 1.0 +float getLinearDepth() +{ + return 2.0 * f_shadowfar / (f_shadowfar + 1.0 - (2.0 * gl_FragCoord.z - 1.0) * (f_shadowfar - 1.0)); +} + +vec3 getLightSpacePosition() +{ + vec4 pLightSpace; + float normalBias = 0.0005 * getLinearDepth() * cosLight + normalOffsetScale; + pLightSpace = m_ShadowViewProj * vec4(worldPosition + normalBias * normalize(vNormal), 1.0); + pLightSpace = getPerspectiveFactor(pLightSpace); + return pLightSpace.xyz * 0.5 + 0.5; +} + +#ifdef COLORED_SHADOWS + +// c_precision of 128 fits within 7 base-10 digits +const float c_precision = 128.0; +const float c_precisionp1 = c_precision + 1.0; + +float packColor(vec3 color) +{ + return floor(color.b * c_precision + 0.5) + + floor(color.g * c_precision + 0.5) * c_precisionp1 + + floor(color.r * c_precision + 0.5) * c_precisionp1 * c_precisionp1; +} + +vec3 unpackColor(float value) +{ + vec3 color; + color.b = mod(value, c_precisionp1) / c_precision; + color.g = mod(floor(value / c_precisionp1), c_precisionp1) / c_precision; + color.r = floor(value / (c_precisionp1 * c_precisionp1)) / c_precision; + return color; +} + +vec4 getHardShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) +{ + vec4 texDepth = texture2D(shadowsampler, smTexCoord.xy).rgba; + + float visibility = step(0.0, (realDistance-2e-5) - texDepth.r); + vec4 result = vec4(visibility, vec3(0.0,0.0,0.0));//unpackColor(texDepth.g)); + if (visibility < 0.1) { + visibility = step(0.0, (realDistance-2e-5) - texDepth.r); + result = vec4(visibility, unpackColor(texDepth.a)); + } + return result; +} + +#else + +float getHardShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) +{ + float texDepth = texture2D(shadowsampler, smTexCoord.xy).r; + float visibility = step(0.0, (realDistance-2e-5) - texDepth); + + return visibility; +} + +#endif + +#if SHADOW_FILTER == 2 + #define PCFBOUND 3.5 + #define PCFSAMPLES 64.0 +#elif SHADOW_FILTER == 1 + #define PCFBOUND 1.5 + #if defined(POISSON_FILTER) + #define PCFSAMPLES 32.0 + #else + #define PCFSAMPLES 16.0 + #endif +#else + #define PCFBOUND 0.0 + #if defined(POISSON_FILTER) + #define PCFSAMPLES 4.0 + #else + #define PCFSAMPLES 1.0 + #endif +#endif + +#ifdef POISSON_FILTER +const vec2[64] poissonDisk = vec2[64]( + vec2(0.170019, -0.040254), + vec2(-0.299417, 0.791925), + vec2(0.645680, 0.493210), + vec2(-0.651784, 0.717887), + vec2(0.421003, 0.027070), + vec2(-0.817194, -0.271096), + vec2(-0.705374, -0.668203), + vec2(0.977050, -0.108615), + vec2(0.063326, 0.142369), + vec2(0.203528, 0.214331), + vec2(-0.667531, 0.326090), + vec2(-0.098422, -0.295755), + vec2(-0.885922, 0.215369), + vec2(0.566637, 0.605213), + vec2(0.039766, -0.396100), + vec2(0.751946, 0.453352), + vec2(0.078707, -0.715323), + vec2(-0.075838, -0.529344), + vec2(0.724479, -0.580798), + vec2(0.222999, -0.215125), + vec2(-0.467574, -0.405438), + vec2(-0.248268, -0.814753), + vec2(0.354411, -0.887570), + vec2(0.175817, 0.382366), + vec2(0.487472, -0.063082), + vec2(0.355476, 0.025357), + vec2(-0.084078, 0.898312), + vec2(0.488876, -0.783441), + vec2(0.470016, 0.217933), + vec2(-0.696890, -0.549791), + vec2(-0.149693, 0.605762), + vec2(0.034211, 0.979980), + vec2(0.503098, -0.308878), + vec2(-0.016205, -0.872921), + vec2(0.385784, -0.393902), + vec2(-0.146886, -0.859249), + vec2(0.643361, 0.164098), + vec2(0.634388, -0.049471), + vec2(-0.688894, 0.007843), + vec2(0.464034, -0.188818), + vec2(-0.440840, 0.137486), + vec2(0.364483, 0.511704), + vec2(0.034028, 0.325968), + vec2(0.099094, -0.308023), + vec2(0.693960, -0.366253), + vec2(0.678884, -0.204688), + vec2(0.001801, 0.780328), + vec2(0.145177, -0.898984), + vec2(0.062655, -0.611866), + vec2(0.315226, -0.604297), + vec2(-0.780145, 0.486251), + vec2(-0.371868, 0.882138), + vec2(0.200476, 0.494430), + vec2(-0.494552, -0.711051), + vec2(0.612476, 0.705252), + vec2(-0.578845, -0.768792), + vec2(-0.772454, -0.090976), + vec2(0.504440, 0.372295), + vec2(0.155736, 0.065157), + vec2(0.391522, 0.849605), + vec2(-0.620106, -0.328104), + vec2(0.789239, -0.419965), + vec2(-0.545396, 0.538133), + vec2(-0.178564, -0.596057) +); + +#ifdef COLORED_SHADOWS + +vec4 getShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) +{ + vec2 clampedpos; + vec4 visibility = vec4(0.0); + + float texture_size = 1.0 / (f_textureresolution * 0.5); + int init_offset = int(floor(mod(((smTexCoord.x * 34.0) + 1.0) * smTexCoord.y, 64.0-PCFSAMPLES))); + int end_offset = int(PCFSAMPLES) + init_offset; + + for (int x = init_offset; x < end_offset; x++) { + clampedpos = poissonDisk[x] * texture_size * SOFTSHADOWRADIUS + smTexCoord.xy; + visibility += getHardShadowColor(shadowsampler, clampedpos.xy, realDistance); + } + + return visibility / PCFSAMPLES; +} + +#else + +float getShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) +{ + vec2 clampedpos; + float visibility = 0.0; + + float texture_size = 1.0 / (f_textureresolution * 0.5); + int init_offset = int(floor(mod(((smTexCoord.x * 34.0) + 1.0) * smTexCoord.y, 64.0-PCFSAMPLES))); + int end_offset = int(PCFSAMPLES) + init_offset; + + for (int x = init_offset; x < end_offset; x++) { + clampedpos = poissonDisk[x] * texture_size * SOFTSHADOWRADIUS + smTexCoord.xy; + visibility += getHardShadow(shadowsampler, clampedpos.xy, realDistance); + } + + return visibility / PCFSAMPLES; +} + +#endif + +#else +/* poisson filter disabled */ + +#ifdef COLORED_SHADOWS + +vec4 getShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) +{ + vec2 clampedpos; + vec4 visibility = vec4(0.0); + float sradius=0.0; + if( PCFBOUND>0) + sradius = SOFTSHADOWRADIUS / PCFBOUND; + float texture_size = 1.0 / (f_textureresolution * 0.5); + float y, x; + // basic PCF filter + for (y = -PCFBOUND; y <= PCFBOUND; y += 1.0) + for (x = -PCFBOUND; x <= PCFBOUND; x += 1.0) { + clampedpos = vec2(x,y) * texture_size* sradius + smTexCoord.xy; + visibility += getHardShadowColor(shadowsampler, clampedpos.xy, realDistance); + } + + return visibility / PCFSAMPLES; +} + +#else +float getShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) +{ + vec2 clampedpos; + float visibility = 0.0; + float sradius=0.0; + if( PCFBOUND>0) + sradius = SOFTSHADOWRADIUS / PCFBOUND; + + float texture_size = 1.0 / (f_textureresolution * 0.5); + float y, x; + // basic PCF filter + for (y = -PCFBOUND; y <= PCFBOUND; y += 1.0) + for (x = -PCFBOUND; x <= PCFBOUND; x += 1.0) { + clampedpos = vec2(x,y) * texture_size * sradius + smTexCoord.xy; + visibility += getHardShadow(shadowsampler, clampedpos.xy, realDistance); + } + + return visibility / PCFSAMPLES; +} + +#endif + +#endif +#endif + void main(void) { vec3 color; vec2 uv = varTexCoord.st; - vec4 base = texture2D(baseTexture, uv).rgba; #ifdef USE_DISCARD @@ -72,13 +338,34 @@ void main(void) #endif color = base.rgb; - vec4 col = vec4(color.rgb, base.a); - col.rgb *= varColor.rgb; - col.rgb *= emissiveColor.rgb * vIDiff; +#ifdef ENABLE_DYNAMIC_SHADOWS + float shadow_int = 0.0; + vec3 shadow_color = vec3(0.0, 0.0, 0.0); + vec3 posLightSpace = getLightSpacePosition(); + +#ifdef COLORED_SHADOWS + vec4 visibility = getShadowColor(ShadowMapSampler, posLightSpace.xy, posLightSpace.z); + shadow_int = visibility.r; + shadow_color = visibility.gba; +#else + shadow_int = getShadow(ShadowMapSampler, posLightSpace.xy, posLightSpace.z); +#endif + + if (f_normal_length != 0 && cosLight <= 0.001) { + shadow_int = clamp(shadow_int + 0.5 * abs(cosLight), 0.0, 1.0); + } + + shadow_int = 1.0 - (shadow_int * adj_shadow_strength); + + col.rgb = mix(shadow_color, col.rgb, shadow_int) * shadow_int; +#endif + + + #if ENABLE_TONE_MAPPING col = applyToneMapping(col); #endif diff --git a/client/shaders/object_shader/opengl_vertex.glsl b/client/shaders/object_shader/opengl_vertex.glsl index f26224e82..f135ab9dc 100644 --- a/client/shaders/object_shader/opengl_vertex.glsl +++ b/client/shaders/object_shader/opengl_vertex.glsl @@ -13,12 +13,37 @@ varying mediump vec2 varTexCoord; centroid varying vec2 varTexCoord; #endif +#ifdef ENABLE_DYNAMIC_SHADOWS + // shadow uniforms + uniform vec3 v_LightDirection; + uniform float f_textureresolution; + uniform mat4 m_ShadowViewProj; + uniform float f_shadowfar; + uniform float f_shadow_strength; + uniform float f_timeofday; + varying float cosLight; + varying float normalOffsetScale; + varying float adj_shadow_strength; + varying float f_normal_length; +#endif + varying vec3 eyeVec; varying float vIDiff; const float e = 2.718281828459; const float BS = 10.0; +#ifdef ENABLE_DYNAMIC_SHADOWS +// custom smoothstep implementation because it's not defined in glsl1.2 +// https://docs.gl/sl4/smoothstep +float mtsmoothstep(in float edge0, in float edge1, in float x) +{ + float t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0); + return t * t * (3.0 - 2.0 * t); +} +#endif + + float directional_ambient(vec3 normal) { vec3 v = normal * normal; @@ -54,4 +79,25 @@ void main(void) #else varColor = inVertexColor; #endif + +#ifdef ENABLE_DYNAMIC_SHADOWS + + cosLight = max(0.0, dot(vNormal, -v_LightDirection)); + float texelSize = 0.51; + float slopeScale = clamp(1.0 - cosLight, 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); + +#endif } diff --git a/client/shaders/shadow_shaders/pass1_fragment.glsl b/client/shaders/shadow_shaders/pass1_fragment.glsl new file mode 100644 index 000000000..2105def96 --- /dev/null +++ b/client/shaders/shadow_shaders/pass1_fragment.glsl @@ -0,0 +1,13 @@ +uniform sampler2D ColorMapSampler; +varying vec4 tPos; + +void main() +{ + vec4 col = texture2D(ColorMapSampler, gl_TexCoord[0].st); + + if (col.a < 0.70) + discard; + + float depth = 0.5 + tPos.z * 0.5; + gl_FragColor = vec4(depth, 0.0, 0.0, 1.0); +} diff --git a/client/shaders/shadow_shaders/pass1_trans_fragment.glsl b/client/shaders/shadow_shaders/pass1_trans_fragment.glsl new file mode 100644 index 000000000..9f9e5be8c --- /dev/null +++ b/client/shaders/shadow_shaders/pass1_trans_fragment.glsl @@ -0,0 +1,38 @@ +uniform sampler2D ColorMapSampler; +varying vec4 tPos; + +#ifdef COLORED_SHADOWS +// c_precision of 128 fits within 7 base-10 digits +const float c_precision = 128.0; +const float c_precisionp1 = c_precision + 1.0; + +float packColor(vec3 color) +{ + return floor(color.b * c_precision + 0.5) + + floor(color.g * c_precision + 0.5) * c_precisionp1 + + floor(color.r * c_precision + 0.5) * c_precisionp1 * c_precisionp1; +} + +const vec3 black = vec3(0.0); +#endif + +void main() +{ + vec4 col = texture2D(ColorMapSampler, gl_TexCoord[0].st); +#ifndef COLORED_SHADOWS + if (col.a < 0.5) + discard; +#endif + + float depth = 0.5 + tPos.z * 0.5; + // ToDo: Liso: Apply movement on waving plants + // depth in [0, 1] for texture + + //col.rgb = col.a == 1.0 ? vec3(1.0) : col.rgb; +#ifdef COLORED_SHADOWS + float packedColor = packColor(mix(col.rgb, black, col.a)); + gl_FragColor = vec4(depth, packedColor, 0.0,1.0); +#else + gl_FragColor = vec4(depth, 0.0, 0.0, 1.0); +#endif +} diff --git a/client/shaders/shadow_shaders/pass1_trans_vertex.glsl b/client/shaders/shadow_shaders/pass1_trans_vertex.glsl new file mode 100644 index 000000000..ca59f2796 --- /dev/null +++ b/client/shaders/shadow_shaders/pass1_trans_vertex.glsl @@ -0,0 +1,26 @@ +uniform mat4 LightMVP; // world matrix +varying vec4 tPos; + +const float bias0 = 0.9; +const float zPersFactor = 0.5; +const float bias1 = 1.0 - bias0 + 1e-6; + +vec4 getPerspectiveFactor(in vec4 shadowPosition) +{ + float pDistance = length(shadowPosition.xy); + float pFactor = pDistance * bias0 + bias1; + shadowPosition.xyz *= vec3(vec2(1.0 / pFactor), zPersFactor); + + return shadowPosition; +} + + +void main() +{ + vec4 pos = LightMVP * gl_Vertex; + + tPos = getPerspectiveFactor(LightMVP * gl_Vertex); + + gl_Position = vec4(tPos.xyz, 1.0); + gl_TexCoord[0].st = gl_MultiTexCoord0.st; +} diff --git a/client/shaders/shadow_shaders/pass1_vertex.glsl b/client/shaders/shadow_shaders/pass1_vertex.glsl new file mode 100644 index 000000000..a6d8b3db8 --- /dev/null +++ b/client/shaders/shadow_shaders/pass1_vertex.glsl @@ -0,0 +1,26 @@ +uniform mat4 LightMVP; // world matrix +varying vec4 tPos; + +const float bias0 = 0.9; +const float zPersFactor = 0.5; +const float bias1 = 1.0 - bias0 + 1e-6; + +vec4 getPerspectiveFactor(in vec4 shadowPosition) +{ + float pDistance = length(shadowPosition.xy); + float pFactor = pDistance * bias0 + bias1; + shadowPosition.xyz *= vec3(vec2(1.0 / pFactor), zPersFactor); + + return shadowPosition; +} + + +void main() +{ + vec4 pos = LightMVP * gl_Vertex; + + tPos = getPerspectiveFactor(pos); + + gl_Position = vec4(tPos.xyz, 1.0); + gl_TexCoord[0].st = gl_MultiTexCoord0.st; +} diff --git a/client/shaders/shadow_shaders/pass2_fragment.glsl b/client/shaders/shadow_shaders/pass2_fragment.glsl new file mode 100644 index 000000000..00b4f9f6c --- /dev/null +++ b/client/shaders/shadow_shaders/pass2_fragment.glsl @@ -0,0 +1,23 @@ +uniform sampler2D ShadowMapClientMap; +#ifdef COLORED_SHADOWS +uniform sampler2D ShadowMapClientMapTraslucent; +#endif +uniform sampler2D ShadowMapSamplerdynamic; + +void main() { + +#ifdef COLORED_SHADOWS + vec2 first_depth = texture2D(ShadowMapClientMap, gl_TexCoord[0].st).rg; + vec2 depth_splitdynamics = vec2(texture2D(ShadowMapSamplerdynamic, gl_TexCoord[2].st).r, 0.0); + if (first_depth.r > depth_splitdynamics.r) + first_depth = depth_splitdynamics; + vec2 depth_color = texture2D(ShadowMapClientMapTraslucent, gl_TexCoord[1].st).rg; + gl_FragColor = vec4(first_depth.r, first_depth.g, depth_color.r, depth_color.g); +#else + float first_depth = texture2D(ShadowMapClientMap, gl_TexCoord[0].st).r; + float depth_splitdynamics = texture2D(ShadowMapSamplerdynamic, gl_TexCoord[2].st).r; + first_depth = min(first_depth, depth_splitdynamics); + gl_FragColor = vec4(first_depth, 0.0, 0.0, 1.0); +#endif + +} diff --git a/client/shaders/shadow_shaders/pass2_vertex.glsl b/client/shaders/shadow_shaders/pass2_vertex.glsl new file mode 100644 index 000000000..ac445c9c7 --- /dev/null +++ b/client/shaders/shadow_shaders/pass2_vertex.glsl @@ -0,0 +1,9 @@ + +void main() +{ + vec4 uv = vec4(gl_Vertex.xyz, 1.0) * 0.5 + 0.5; + gl_TexCoord[0] = uv; + gl_TexCoord[1] = uv; + gl_TexCoord[2] = uv; + gl_Position = vec4(gl_Vertex.xyz, 1.0); +} diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt index 140814911..8d058852a 100644 --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -58,5 +58,9 @@ set(client_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/sky.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tile.cpp ${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 PARENT_SCOPE ) diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp index 6dc535898..8b09eade1 100644 --- a/src/client/clientmap.cpp +++ b/src/client/clientmap.cpp @@ -72,8 +72,15 @@ ClientMap::ClientMap( scene::ISceneNode(rendering_engine->get_scene_manager()->getRootSceneNode(), rendering_engine->get_scene_manager(), id), m_client(client), + m_rendering_engine(rendering_engine), m_control(control) { + + /* + * @Liso: Sadly C++ doesn't have introspection, so the only way we have to know + * the class is whith a name ;) Name property cames from ISceneNode base class. + */ + Name = "ClientMap"; m_box = aabb3f(-BS*1000000,-BS*1000000,-BS*1000000, BS*1000000,BS*1000000,BS*1000000); @@ -115,12 +122,21 @@ void ClientMap::OnRegisterSceneNode() } ISceneNode::OnRegisterSceneNode(); + + if (!m_added_to_shadow_renderer) { + m_added_to_shadow_renderer = true; + if (auto shadows = m_rendering_engine->get_shadow_renderer()) + shadows->addNodeToShadowList(this); + } } void ClientMap::getBlocksInViewRange(v3s16 cam_pos_nodes, - v3s16 *p_blocks_min, v3s16 *p_blocks_max) + v3s16 *p_blocks_min, v3s16 *p_blocks_max, float range) { - v3s16 box_nodes_d = m_control.wanted_range * v3s16(1, 1, 1); + if (range <= 0.0f) + range = m_control.wanted_range; + + v3s16 box_nodes_d = range * v3s16(1, 1, 1); // Define p_nodes_min/max as v3s32 because 'cam_pos_nodes -/+ box_nodes_d' // can exceed the range of v3s16 when a large view range is used near the // world edges. @@ -321,7 +337,6 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) // Mesh animation if (pass == scene::ESNRP_SOLID) { - //MutexAutoLock lock(block->mesh_mutex); MapBlockMesh *mapBlockMesh = block->mesh; assert(mapBlockMesh); // Pretty random but this should work somewhat nicely @@ -342,8 +357,6 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) Get the meshbuffers of the block */ { - //MutexAutoLock lock(block->mesh_mutex); - MapBlockMesh *mapBlockMesh = block->mesh; assert(mapBlockMesh); @@ -394,6 +407,17 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) "returning." << std::endl; return; } + + // pass the shadow map texture to the buffer texture + ShadowRenderer *shadow = m_rendering_engine->get_shadow_renderer(); + if (shadow && shadow->is_active()) { + auto &layer = list.m.TextureLayer[3]; + layer.Texture = shadow->get_texture(); + layer.TextureWrapU = video::E_TEXTURE_CLAMP::ETC_CLAMP_TO_EDGE; + layer.TextureWrapV = video::E_TEXTURE_CLAMP::ETC_CLAMP_TO_EDGE; + layer.TrilinearFilter = true; + } + driver->setMaterial(list.m); drawcall_count += list.bufs.size(); @@ -610,3 +634,187 @@ void ClientMap::PrintInfo(std::ostream &out) { out<<"ClientMap: "; } + +void ClientMap::renderMapShadows(video::IVideoDriver *driver, + const video::SMaterial &material, s32 pass) +{ + bool is_transparent_pass = pass != scene::ESNRP_SOLID; + std::string prefix; + if (is_transparent_pass) + prefix = "renderMap(SHADOW TRANS): "; + else + prefix = "renderMap(SHADOW SOLID): "; + + u32 drawcall_count = 0; + u32 vertex_count = 0; + + MeshBufListList drawbufs; + + for (auto &i : m_drawlist_shadow) { + v3s16 block_pos = i.first; + MapBlock *block = i.second; + + // If the mesh of the block happened to get deleted, ignore it + if (!block->mesh) + continue; + + /* + Get the meshbuffers of the block + */ + { + MapBlockMesh *mapBlockMesh = block->mesh; + assert(mapBlockMesh); + + for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) { + scene::IMesh *mesh = mapBlockMesh->getMesh(layer); + assert(mesh); + + u32 c = mesh->getMeshBufferCount(); + for (u32 i = 0; i < c; i++) { + scene::IMeshBuffer *buf = mesh->getMeshBuffer(i); + + video::SMaterial &mat = buf->getMaterial(); + auto rnd = driver->getMaterialRenderer(mat.MaterialType); + bool transparent = rnd && rnd->isTransparent(); + if (transparent == is_transparent_pass) + drawbufs.add(buf, block_pos, layer); + } + } + } + } + + TimeTaker draw("Drawing shadow mesh buffers"); + + core::matrix4 m; // Model matrix + v3f offset = intToFloat(m_camera_offset, BS); + + // Render all layers in order + for (auto &lists : drawbufs.lists) { + for (MeshBufList &list : lists) { + // Check and abort if the machine is swapping a lot + if (draw.getTimerTime() > 1000) { + infostream << "ClientMap::renderMapShadows(): Rendering " + "took >1s, returning." << std::endl; + break; + } + for (auto &pair : list.bufs) { + scene::IMeshBuffer *buf = pair.second; + + // override some material properties + video::SMaterial local_material = buf->getMaterial(); + local_material.MaterialType = material.MaterialType; + local_material.BackfaceCulling = material.BackfaceCulling; + local_material.FrontfaceCulling = material.FrontfaceCulling; + local_material.Lighting = false; + driver->setMaterial(local_material); + + v3f block_wpos = intToFloat(pair.first * MAP_BLOCKSIZE, BS); + m.setTranslation(block_wpos - offset); + + driver->setTransform(video::ETS_WORLD, m); + driver->drawMeshBuffer(buf); + vertex_count += buf->getVertexCount(); + } + + drawcall_count += list.bufs.size(); + } + } + + 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); +} + +/* + Custom update draw list for the pov of shadow light. +*/ +void ClientMap::updateDrawListShadow(const v3f &shadow_light_pos, const v3f &shadow_light_dir, float shadow_range) +{ + ScopeProfiler sp(g_profiler, "CM::updateDrawListShadow()", SPT_AVG); + + const v3f camera_position = shadow_light_pos; + const v3f camera_direction = shadow_light_dir; + // I "fake" fov just to avoid creating a new function to handle orthographic + // projection. + const f32 camera_fov = m_camera_fov * 1.9f; + + v3s16 cam_pos_nodes = floatToInt(camera_position, BS); + v3s16 p_blocks_min; + v3s16 p_blocks_max; + getBlocksInViewRange(cam_pos_nodes, &p_blocks_min, &p_blocks_max, shadow_range); + + std::vector blocks_in_range; + + for (auto &i : m_drawlist_shadow) { + MapBlock *block = i.second; + block->refDrop(); + } + m_drawlist_shadow.clear(); + + // We need to append the blocks from the camera POV because sometimes + // they are not inside the light frustum and it creates glitches. + // FIXME: This could be removed if we figure out why they are missing + // from the light frustum. + for (auto &i : m_drawlist) { + i.second->refGrab(); + m_drawlist_shadow[i.first] = i.second; + } + + // Number of blocks currently loaded by the client + u32 blocks_loaded = 0; + // Number of blocks with mesh in rendering range + u32 blocks_in_range_with_mesh = 0; + // Number of blocks occlusion culled + u32 blocks_occlusion_culled = 0; + + for (auto §or_it : m_sectors) { + MapSector *sector = sector_it.second; + if (!sector) + continue; + blocks_loaded += sector->size(); + + MapBlockVect sectorblocks; + sector->getBlocks(sectorblocks); + + /* + Loop through blocks in sector + */ + for (MapBlock *block : sectorblocks) { + if (!block->mesh) { + // Ignore if mesh doesn't exist + continue; + } + + float range = shadow_range; + + float d = 0.0; + if (!isBlockInSight(block->getPos(), camera_position, + camera_direction, camera_fov, range, &d)) + continue; + + blocks_in_range_with_mesh++; + + /* + Occlusion culling + */ + if (isBlockOccluded(block, cam_pos_nodes)) { + blocks_occlusion_culled++; + continue; + } + + // This block is in range. Reset usage timer. + block->resetUsageTimer(); + + // Add to set + if (m_drawlist_shadow.find(block->getPos()) == m_drawlist_shadow.end()) { + block->refGrab(); + m_drawlist_shadow[block->getPos()] = block; + } + } + } + + g_profiler->avg("SHADOW MapBlock meshes in range [#]", blocks_in_range_with_mesh); + g_profiler->avg("SHADOW MapBlocks occlusion culled [#]", blocks_occlusion_culled); + g_profiler->avg("SHADOW MapBlocks drawn [#]", m_drawlist_shadow.size()); + g_profiler->avg("SHADOW MapBlocks loaded [#]", blocks_loaded); +} diff --git a/src/client/clientmap.h b/src/client/clientmap.h index 80add4a44..93ade4c15 100644 --- a/src/client/clientmap.h +++ b/src/client/clientmap.h @@ -119,10 +119,14 @@ public: } void getBlocksInViewRange(v3s16 cam_pos_nodes, - v3s16 *p_blocks_min, v3s16 *p_blocks_max); + v3s16 *p_blocks_min, v3s16 *p_blocks_max, float range=-1.0f); void updateDrawList(); + void updateDrawListShadow(const v3f &shadow_light_pos, const v3f &shadow_light_dir, float shadow_range); void renderMap(video::IVideoDriver* driver, s32 pass); + void renderMapShadows(video::IVideoDriver *driver, + const video::SMaterial &material, s32 pass); + int getBackgroundBrightness(float max_d, u32 daylight_factor, int oldvalue, bool *sunlight_seen_result); @@ -132,9 +136,12 @@ public: virtual void PrintInfo(std::ostream &out); const MapDrawControl & getControl() const { return m_control; } + f32 getWantedRange() const { return m_control.wanted_range; } f32 getCameraFov() const { return m_camera_fov; } + private: Client *m_client; + RenderingEngine *m_rendering_engine; aabb3f m_box = aabb3f(-BS * 1000000, -BS * 1000000, -BS * 1000000, BS * 1000000, BS * 1000000, BS * 1000000); @@ -147,10 +154,12 @@ private: v3s16 m_camera_offset; std::map m_drawlist; + std::map m_drawlist_shadow; std::set m_last_drawn_sectors; bool m_cache_trilinear_filter; bool m_cache_bilinear_filter; bool m_cache_anistropic_filter; + bool m_added_to_shadow_renderer{false}; }; diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index 2e58e19cf..9216f0010 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include "client/client.h" +#include "client/renderingengine.h" #include "client/sound.h" #include "client/tile.h" #include "util/basic_macros.h" @@ -555,6 +556,9 @@ void GenericCAO::removeFromScene(bool permanent) clearParentAttachment(); } + if (auto shadow = RenderingEngine::get_shadow_renderer()) + shadow->removeNodeFromShadowList(getSceneNode()); + if (m_meshnode) { m_meshnode->remove(); m_meshnode->drop(); @@ -803,10 +807,13 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) if (m_reset_textures_timer < 0) updateTextures(m_current_texture_modifier); - scene::ISceneNode *node = getSceneNode(); + if (scene::ISceneNode *node = getSceneNode()) { + if (m_matrixnode) + node->setParent(m_matrixnode); - if (node && m_matrixnode) - node->setParent(m_matrixnode); + if (auto shadow = RenderingEngine::get_shadow_renderer()) + shadow->addNodeToShadowList(node); + } updateNametag(); updateMarker(); diff --git a/src/client/game.cpp b/src/client/game.cpp index eb1dbb5a3..d240ebc0f 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -738,6 +738,7 @@ protected: const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime); void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, const CameraOrientation &cam); + void updateShadows(); // Misc void limitFps(FpsControl *fps_timings, f32 *dtime); @@ -3831,13 +3832,20 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, */ runData.update_draw_list_timer += dtime; + float update_draw_list_delta = 0.2f; + if (ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer()) + update_draw_list_delta = shadow->getUpdateDelta(); + v3f camera_direction = camera->getDirection(); - if (runData.update_draw_list_timer >= 0.2 + if (runData.update_draw_list_timer >= update_draw_list_delta || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2 || m_camera_offset_changed) { + runData.update_draw_list_timer = 0; client->getEnv().getClientMap().updateDrawList(); runData.update_draw_list_last_cam_dir = camera_direction; + + updateShadows(); } m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime); @@ -3968,7 +3976,34 @@ inline void Game::updateProfilerGraphs(ProfilerGraph *graph) graph->put(values); } +/**************************************************************************** + * Shadows + *****************************************************************************/ +void Game::updateShadows() +{ + ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer(); + if (!shadow) + return; + + float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f); + + float timeoftheday = fmod(getWickedTimeOfDay(in_timeofday) + 0.75f, 0.5f) + 0.25f; + const float offset_constant = 10000.0f; + + v3f light(0.0f, 0.0f, -1.0f); + light.rotateXZBy(90); + light.rotateXYBy(timeoftheday * 360 - 90); + light.rotateYZBy(sky->getSkyBodyOrbitTilt()); + v3f sun_pos = light * offset_constant; + + if (shadow->getDirectionalLightCount() == 0) + shadow->addDirectionalLight(); + shadow->getDirectionalLight().setDirection(sun_pos); + shadow->setTimeOfDay(in_timeofday); + + shadow->getDirectionalLight().update_frustum(camera, client); +} /**************************************************************************** Misc diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index 72e68fe97..402217066 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -860,6 +860,9 @@ static void updateFastFaceRow( g_settings->getBool("enable_shaders") && g_settings->getBool("enable_waving_water"); + static thread_local const bool force_not_tiling = + g_settings->getBool("enable_dynamic_shadows"); + v3s16 p = startpos; u16 continuous_tiles_count = 1; @@ -898,7 +901,8 @@ static void updateFastFaceRow( waving, next_tile); - if (next_makes_face == makes_face + if (!force_not_tiling + && next_makes_face == makes_face && next_p_corrected == p_corrected + translate_dir && next_face_dir_corrected == face_dir_corrected && memcmp(next_lights, lights, sizeof(lights)) == 0 diff --git a/src/client/render/core.cpp b/src/client/render/core.cpp index 3c4583623..4a820f583 100644 --- a/src/client/render/core.cpp +++ b/src/client/render/core.cpp @@ -24,25 +24,35 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/clientmap.h" #include "client/hud.h" #include "client/minimap.h" +#include "client/shadows/dynamicshadowsrender.h" RenderingCore::RenderingCore(IrrlichtDevice *_device, Client *_client, Hud *_hud) : device(_device), driver(device->getVideoDriver()), smgr(device->getSceneManager()), guienv(device->getGUIEnvironment()), client(_client), camera(client->getCamera()), - mapper(client->getMinimap()), hud(_hud) + mapper(client->getMinimap()), hud(_hud), + shadow_renderer(nullptr) { screensize = driver->getScreenSize(); virtual_size = screensize; + + if (g_settings->getBool("enable_shaders") && + g_settings->getBool("enable_dynamic_shadows")) { + shadow_renderer = new ShadowRenderer(device, client); + } } RenderingCore::~RenderingCore() { clearTextures(); + delete shadow_renderer; } void RenderingCore::initialize() { // have to be called late as the VMT is not ready in the constructor: initTextures(); + if (shadow_renderer) + shadow_renderer->initialize(); } void RenderingCore::updateScreenSize() @@ -72,7 +82,14 @@ void RenderingCore::draw(video::SColor _skycolor, bool _show_hud, bool _show_min void RenderingCore::draw3D() { - smgr->drawAll(); + if (shadow_renderer) { + // Shadow renderer will handle the draw stage + shadow_renderer->setClearColor(skycolor); + shadow_renderer->update(); + } else { + smgr->drawAll(); + } + driver->setTransform(video::ETS_WORLD, core::IdentityMatrix); if (!show_hud) return; diff --git a/src/client/render/core.h b/src/client/render/core.h index 52ea8f99f..cabfbbfad 100644 --- a/src/client/render/core.h +++ b/src/client/render/core.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include "irrlichttypes_extrabloated.h" +class ShadowRenderer; class Camera; class Client; class Hud; @@ -47,6 +48,8 @@ protected: Minimap *mapper; Hud *hud; + ShadowRenderer *shadow_renderer; + void updateScreenSize(); virtual void initTextures() {} virtual void clearTextures() {} @@ -72,4 +75,6 @@ public: bool _draw_wield_tool, bool _draw_crosshair); inline v2u32 getVirtualSize() const { return virtual_size; } + + ShadowRenderer *get_shadow_renderer() { return shadow_renderer; }; }; diff --git a/src/client/renderingengine.h b/src/client/renderingengine.h index 28ddc8652..6d42221d6 100644 --- a/src/client/renderingengine.h +++ b/src/client/renderingengine.h @@ -25,6 +25,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include "irrlichttypes_extrabloated.h" #include "debug.h" +#include "client/render/core.h" +// include the shadow mapper classes too +#include "client/shadows/dynamicshadowsrender.h" + class ITextureSource; class Camera; @@ -113,6 +117,13 @@ public: return m_device->run(); } + // FIXME: this is still global when it shouldn't be + static ShadowRenderer *get_shadow_renderer() + { + if (s_singleton && s_singleton->core) + return s_singleton->core->get_shadow_renderer(); + return nullptr; + } static std::vector> getSupportedVideoModes(); static std::vector getSupportedVideoDrivers(); diff --git a/src/client/shader.cpp b/src/client/shader.cpp index 58946b90f..355366bd3 100644 --- a/src/client/shader.cpp +++ b/src/client/shader.cpp @@ -225,6 +225,16 @@ class MainShaderConstantSetter : public IShaderConstantSetter { CachedVertexShaderSetting m_world_view_proj; CachedVertexShaderSetting m_world; + + // Shadow-related + CachedPixelShaderSetting m_shadow_view_proj; + CachedPixelShaderSetting m_light_direction; + CachedPixelShaderSetting m_texture_res; + CachedPixelShaderSetting m_shadow_strength; + CachedPixelShaderSetting m_time_of_day; + CachedPixelShaderSetting m_shadowfar; + CachedPixelShaderSetting m_shadow_texture; + #if ENABLE_GLES // Modelview matrix CachedVertexShaderSetting m_world_view; @@ -243,6 +253,13 @@ public: , m_texture("mTexture") , m_normal("mNormal") #endif + , m_shadow_view_proj("m_ShadowViewProj") + , m_light_direction("v_LightDirection") + , m_texture_res("f_textureresolution") + , m_shadow_strength("f_shadow_strength") + , m_time_of_day("f_timeofday") + , m_shadowfar("f_shadowfar") + , m_shadow_texture("ShadowMapSampler") {} ~MainShaderConstantSetter() = default; @@ -280,6 +297,36 @@ public: }; m_normal.set(m, services); #endif + + // Set uniforms for Shadow shader + if (ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer()) { + const auto &light = shadow->getDirectionalLight(); + + core::matrix4 shadowViewProj = light.getProjectionMatrix(); + shadowViewProj *= light.getViewMatrix(); + m_shadow_view_proj.set(shadowViewProj.pointer(), services); + + float v_LightDirection[3]; + light.getDirection().getAs3Values(v_LightDirection); + m_light_direction.set(v_LightDirection, services); + + float TextureResolution = light.getMapResolution(); + m_texture_res.set(&TextureResolution, services); + + float ShadowStrength = shadow->getShadowStrength(); + m_shadow_strength.set(&ShadowStrength, services); + + float timeOfDay = shadow->getTimeOfDay(); + m_time_of_day.set(&timeOfDay, services); + + float shadowFar = shadow->getMaxShadowFar(); + m_shadowfar.set(&shadowFar, services); + + // I dont like using this hardcoded value. maybe something like + // MAX_TEXTURE - 1 or somthing like that?? + s32 TextureLayerID = 3; + m_shadow_texture.set(&TextureLayerID, services); + } } }; @@ -682,6 +729,23 @@ ShaderInfo ShaderSource::generateShader(const std::string &name, shaders_header << "#define FOG_START " << core::clamp(g_settings->getFloat("fog_start"), 0.0f, 0.99f) << "\n"; + if (g_settings->getBool("enable_dynamic_shadows")) { + shaders_header << "#define ENABLE_DYNAMIC_SHADOWS 1\n"; + if (g_settings->getBool("shadow_map_color")) + shaders_header << "#define COLORED_SHADOWS 1\n"; + + if (g_settings->getBool("shadow_poisson_filter")) + shaders_header << "#define POISSON_FILTER 1\n"; + + s32 shadow_filter = g_settings->getS32("shadow_filters"); + shaders_header << "#define SHADOW_FILTER " << shadow_filter << "\n"; + + float shadow_soft_radius = g_settings->getS32("shadow_soft_radius"); + if (shadow_soft_radius < 1.0f) + shadow_soft_radius = 1.0f; + shaders_header << "#define SOFTSHADOWRADIUS " << shadow_soft_radius << "\n"; + } + std::string common_header = shaders_header.str(); std::string vertex_shader = m_sourcecache.getOrLoad(name, "opengl_vertex.glsl"); diff --git a/src/client/shadows/dynamicshadows.cpp b/src/client/shadows/dynamicshadows.cpp new file mode 100644 index 000000000..775cdebce --- /dev/null +++ b/src/client/shadows/dynamicshadows.cpp @@ -0,0 +1,145 @@ +/* +Minetest +Copyright (C) 2021 Liso + +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 "client/shadows/dynamicshadows.h" +#include "client/client.h" +#include "client/clientenvironment.h" +#include "client/clientmap.h" +#include "client/camera.h" + +using m4f = core::matrix4; + +void DirectionalLight::createSplitMatrices(const Camera *cam) +{ + float radius; + v3f newCenter; + v3f look = cam->getDirection(); + + v3f camPos2 = cam->getPosition(); + v3f camPos = v3f(camPos2.X - cam->getOffset().X * BS, + camPos2.Y - cam->getOffset().Y * BS, + camPos2.Z - cam->getOffset().Z * BS); + camPos += look * shadow_frustum.zNear; + camPos2 += look * shadow_frustum.zNear; + float end = shadow_frustum.zNear + shadow_frustum.zFar; + newCenter = camPos + look * (shadow_frustum.zNear + 0.05f * end); + v3f world_center = camPos2 + look * (shadow_frustum.zNear + 0.05f * end); + // Create a vector to the frustum far corner + // @Liso: move all vars we can outside the loop. + float tanFovY = tanf(cam->getFovY() * 0.5f); + float tanFovX = tanf(cam->getFovX() * 0.5f); + + const v3f &viewUp = cam->getCameraNode()->getUpVector(); + // viewUp.normalize(); + + v3f viewRight = look.crossProduct(viewUp); + // viewRight.normalize(); + + v3f farCorner = look + viewRight * tanFovX + viewUp * tanFovY; + // Compute the frustumBoundingSphere radius + v3f boundVec = (camPos + farCorner * shadow_frustum.zFar) - newCenter; + radius = boundVec.getLength() * 2.0f; + // boundVec.getLength(); + float vvolume = radius * 2.0f; + + float texelsPerUnit = getMapResolution() / vvolume; + m4f mTexelScaling; + mTexelScaling.setScale(texelsPerUnit); + + m4f mLookAt, mLookAtInv; + + mLookAt.buildCameraLookAtMatrixLH(v3f(0.0f, 0.0f, 0.0f), -direction, v3f(0.0f, 1.0f, 0.0f)); + + mLookAt *= mTexelScaling; + mLookAtInv = mLookAt; + mLookAtInv.makeInverse(); + + v3f frustumCenter = newCenter; + mLookAt.transformVect(frustumCenter); + frustumCenter.X = floorf(frustumCenter.X); // clamp to texel increment + frustumCenter.Y = floorf(frustumCenter.Y); // clamp to texel increment + frustumCenter.Z = floorf(frustumCenter.Z); + mLookAtInv.transformVect(frustumCenter); + // probar radius multipliacdor en funcion del I, a menor I mas multiplicador + v3f eye_displacement = direction * vvolume; + + // we must compute the viewmat with the position - the camera offset + // but the shadow_frustum position must be the actual world position + v3f eye = frustumCenter - eye_displacement; + shadow_frustum.position = world_center - eye_displacement; + shadow_frustum.length = vvolume; + shadow_frustum.ViewMat.buildCameraLookAtMatrixLH(eye, frustumCenter, v3f(0.0f, 1.0f, 0.0f)); + shadow_frustum.ProjOrthMat.buildProjectionMatrixOrthoLH(shadow_frustum.length, + shadow_frustum.length, -shadow_frustum.length, + shadow_frustum.length,false); +} + +DirectionalLight::DirectionalLight(const u32 shadowMapResolution, + const v3f &position, video::SColorf lightColor, + f32 farValue) : + diffuseColor(lightColor), + farPlane(farValue), mapRes(shadowMapResolution), pos(position) +{} + +void DirectionalLight::update_frustum(const Camera *cam, Client *client) +{ + should_update_map_shadow = true; + float zNear = cam->getCameraNode()->getNearValue(); + float zFar = getMaxFarValue(); + + /////////////////////////////////// + // update splits near and fars + shadow_frustum.zNear = zNear; + shadow_frustum.zFar = zFar; + + // update shadow frustum + createSplitMatrices(cam); + // get the draw list for shadows + client->getEnv().getClientMap().updateDrawListShadow( + getPosition(), getDirection(), shadow_frustum.length); + should_update_map_shadow = true; +} + +void DirectionalLight::setDirection(v3f dir) +{ + direction = -dir; + direction.normalize(); +} + +v3f DirectionalLight::getPosition() const +{ + return shadow_frustum.position; +} + +const m4f &DirectionalLight::getViewMatrix() const +{ + return shadow_frustum.ViewMat; +} + +const m4f &DirectionalLight::getProjectionMatrix() const +{ + return shadow_frustum.ProjOrthMat; +} + +m4f DirectionalLight::getViewProjMatrix() +{ + return shadow_frustum.ProjOrthMat * shadow_frustum.ViewMat; +} diff --git a/src/client/shadows/dynamicshadows.h b/src/client/shadows/dynamicshadows.h new file mode 100644 index 000000000..a53612f7c --- /dev/null +++ b/src/client/shadows/dynamicshadows.h @@ -0,0 +1,102 @@ +/* +Minetest +Copyright (C) 2021 Liso + +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 "irrlichttypes_bloated.h" +#include +#include "util/basic_macros.h" + +class Camera; +class Client; + +struct shadowFrustum +{ + float zNear{0.0f}; + float zFar{0.0f}; + float length{0.0f}; + core::matrix4 ProjOrthMat; + core::matrix4 ViewMat; + v3f position; +}; + +class DirectionalLight +{ +public: + DirectionalLight(const u32 shadowMapResolution, + const v3f &position, + video::SColorf lightColor = video::SColor(0xffffffff), + f32 farValue = 100.0f); + ~DirectionalLight() = default; + + //DISABLE_CLASS_COPY(DirectionalLight) + + void update_frustum(const Camera *cam, Client *client); + + // when set direction is updated to negative normalized(direction) + void setDirection(v3f dir); + v3f getDirection() const{ + return direction; + }; + v3f getPosition() const; + + /// Gets the light's matrices. + const core::matrix4 &getViewMatrix() const; + const core::matrix4 &getProjectionMatrix() const; + core::matrix4 getViewProjMatrix(); + + /// Gets the light's far value. + f32 getMaxFarValue() const + { + return farPlane; + } + + + /// Gets the light's color. + const video::SColorf &getLightColor() const + { + return diffuseColor; + } + + /// Sets the light's color. + void setLightColor(const video::SColorf &lightColor) + { + diffuseColor = lightColor; + } + + /// Gets the shadow map resolution for this light. + u32 getMapResolution() const + { + return mapRes; + } + + bool should_update_map_shadow{true}; + +private: + void createSplitMatrices(const Camera *cam); + + video::SColorf diffuseColor; + + f32 farPlane; + u32 mapRes; + + v3f pos; + v3f direction{0}; + shadowFrustum shadow_frustum; +}; diff --git a/src/client/shadows/dynamicshadowsrender.cpp b/src/client/shadows/dynamicshadowsrender.cpp new file mode 100644 index 000000000..135c9f895 --- /dev/null +++ b/src/client/shadows/dynamicshadowsrender.cpp @@ -0,0 +1,539 @@ +/* +Minetest +Copyright (C) 2021 Liso + +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 "client/shadows/dynamicshadowsrender.h" +#include "client/shadows/shadowsScreenQuad.h" +#include "client/shadows/shadowsshadercallbacks.h" +#include "settings.h" +#include "filesys.h" +#include "util/string.h" +#include "client/shader.h" +#include "client/client.h" +#include "client/clientmap.h" + +ShadowRenderer::ShadowRenderer(IrrlichtDevice *device, Client *client) : + m_device(device), m_smgr(device->getSceneManager()), + m_driver(device->getVideoDriver()), m_client(client) +{ + m_shadows_enabled = true; + + m_shadow_strength = g_settings->getFloat("shadow_strength"); + + m_shadow_map_max_distance = g_settings->getFloat("shadow_map_max_distance"); + + m_shadow_map_texture_size = g_settings->getFloat("shadow_map_texture_size"); + + m_shadow_map_texture_32bit = g_settings->getBool("shadow_map_texture_32bit"); + m_shadow_map_colored = g_settings->getBool("shadow_map_color"); + m_shadow_samples = g_settings->getS32("shadow_filters"); + m_update_delta = g_settings->getFloat("shadow_update_time"); +} + +ShadowRenderer::~ShadowRenderer() +{ + if (m_shadow_depth_cb) + delete m_shadow_depth_cb; + if (m_shadow_mix_cb) + delete m_shadow_mix_cb; + m_shadow_node_array.clear(); + m_light_list.clear(); + + if (shadowMapTextureDynamicObjects) + m_driver->removeTexture(shadowMapTextureDynamicObjects); + + if (shadowMapTextureFinal) + m_driver->removeTexture(shadowMapTextureFinal); + + if (shadowMapTextureColors) + m_driver->removeTexture(shadowMapTextureColors); + + if (shadowMapClientMap) + m_driver->removeTexture(shadowMapClientMap); +} + +void ShadowRenderer::initialize() +{ + auto *gpu = m_driver->getGPUProgrammingServices(); + + // we need glsl + if (m_shadows_enabled && gpu && m_driver->queryFeature(video::EVDF_ARB_GLSL)) { + createShaders(); + } else { + m_shadows_enabled = false; + + warningstream << "Shadows: GLSL Shader not supported on this system." + << std::endl; + return; + } + + m_texture_format = m_shadow_map_texture_32bit + ? video::ECOLOR_FORMAT::ECF_R32F + : video::ECOLOR_FORMAT::ECF_R16F; + + m_texture_format_color = m_shadow_map_texture_32bit + ? video::ECOLOR_FORMAT::ECF_G32R32F + : video::ECOLOR_FORMAT::ECF_G16R16F; +} + + +float ShadowRenderer::getUpdateDelta() const +{ + return m_update_delta; +} + +size_t ShadowRenderer::addDirectionalLight() +{ + m_light_list.emplace_back(m_shadow_map_texture_size, + v3f(0.f, 0.f, 0.f), + video::SColor(255, 255, 255, 255), m_shadow_map_max_distance); + return m_light_list.size() - 1; +} + +DirectionalLight &ShadowRenderer::getDirectionalLight(u32 index) +{ + return m_light_list[index]; +} + +size_t ShadowRenderer::getDirectionalLightCount() const +{ + return m_light_list.size(); +} + +f32 ShadowRenderer::getMaxShadowFar() const +{ + if (!m_light_list.empty()) { + float wanted_range = m_client->getEnv().getClientMap().getWantedRange(); + + float zMax = m_light_list[0].getMaxFarValue() > wanted_range + ? wanted_range + : m_light_list[0].getMaxFarValue(); + return zMax * MAP_BLOCKSIZE; + } + return 0.0f; +} + +void ShadowRenderer::addNodeToShadowList( + scene::ISceneNode *node, E_SHADOW_MODE shadowMode) +{ + m_shadow_node_array.emplace_back(NodeToApply(node, shadowMode)); +} + +void ShadowRenderer::removeNodeFromShadowList(scene::ISceneNode *node) +{ + for (auto it = m_shadow_node_array.begin(); it != m_shadow_node_array.end();) { + if (it->node == node) { + it = m_shadow_node_array.erase(it); + break; + } else { + ++it; + } + } +} + +void ShadowRenderer::setClearColor(video::SColor ClearColor) +{ + m_clear_color = ClearColor; +} + +void ShadowRenderer::update(video::ITexture *outputTarget) +{ + if (!m_shadows_enabled || m_smgr->getActiveCamera() == nullptr) { + m_smgr->drawAll(); + return; + } + + if (!shadowMapTextureDynamicObjects) { + + shadowMapTextureDynamicObjects = getSMTexture( + std::string("shadow_dynamic_") + itos(m_shadow_map_texture_size), + m_texture_format, true); + } + + if (!shadowMapClientMap) { + + shadowMapClientMap = getSMTexture( + std::string("shadow_clientmap_") + itos(m_shadow_map_texture_size), + m_shadow_map_colored ? m_texture_format_color : m_texture_format, + true); + } + + if (m_shadow_map_colored && !shadowMapTextureColors) { + shadowMapTextureColors = getSMTexture( + std::string("shadow_colored_") + itos(m_shadow_map_texture_size), + m_shadow_map_colored ? m_texture_format_color : m_texture_format, + true); + } + + // The merge all shadowmaps texture + if (!shadowMapTextureFinal) { + video::ECOLOR_FORMAT frt; + if (m_shadow_map_texture_32bit) { + if (m_shadow_map_colored) + frt = video::ECOLOR_FORMAT::ECF_A32B32G32R32F; + else + frt = video::ECOLOR_FORMAT::ECF_R32F; + } else { + if (m_shadow_map_colored) + frt = video::ECOLOR_FORMAT::ECF_A16B16G16R16F; + else + frt = video::ECOLOR_FORMAT::ECF_R16F; + } + shadowMapTextureFinal = getSMTexture( + std::string("shadowmap_final_") + itos(m_shadow_map_texture_size), + frt, true); + } + + if (!m_shadow_node_array.empty() && !m_light_list.empty()) { + // for every directional light: + for (DirectionalLight &light : m_light_list) { + // Static shader values. + m_shadow_depth_cb->MapRes = (f32)m_shadow_map_texture_size; + m_shadow_depth_cb->MaxFar = (f32)m_shadow_map_max_distance * BS; + + // set the Render Target + // right now we can only render in usual RTT, not + // Depth texture is available in irrlicth maybe we + // should put some gl* fn here + + if (light.should_update_map_shadow) { + light.should_update_map_shadow = false; + + m_driver->setRenderTarget(shadowMapClientMap, true, true, + video::SColor(255, 255, 255, 255)); + renderShadowMap(shadowMapClientMap, light); + + if (m_shadow_map_colored) { + m_driver->setRenderTarget(shadowMapTextureColors, + true, false, video::SColor(255, 255, 255, 255)); + } + renderShadowMap(shadowMapTextureColors, light, + scene::ESNRP_TRANSPARENT); + m_driver->setRenderTarget(0, false, false); + } + + // render shadows for the n0n-map objects. + m_driver->setRenderTarget(shadowMapTextureDynamicObjects, true, + true, video::SColor(255, 255, 255, 255)); + renderShadowObjects(shadowMapTextureDynamicObjects, light); + // clear the Render Target + m_driver->setRenderTarget(0, false, false); + + // in order to avoid too many map shadow renders, + // we should make a second pass to mix clientmap shadows and + // entities shadows :( + m_screen_quad->getMaterial().setTexture(0, shadowMapClientMap); + // dynamic objs shadow texture. + if (m_shadow_map_colored) + m_screen_quad->getMaterial().setTexture(1, shadowMapTextureColors); + m_screen_quad->getMaterial().setTexture(2, shadowMapTextureDynamicObjects); + + m_driver->setRenderTarget(shadowMapTextureFinal, false, false, + video::SColor(255, 255, 255, 255)); + m_screen_quad->render(m_driver); + m_driver->setRenderTarget(0, false, false); + + } // end for lights + + // now render the actual MT render pass + m_driver->setRenderTarget(outputTarget, true, true, m_clear_color); + m_smgr->drawAll(); + + /* 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())); + + 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 (m_shadow_map_colored) { + + m_driver->draw2DImage(shadowMapTextureColors, + core::rect(128,128 + 50 + 128 + 128, + 128 + 128, 128 + 50 + 128 + 128 + 128), + core::rect({0, 0}, shadowMapTextureColors->getSize())); + } + #endif + m_driver->setRenderTarget(0, false, false); + } +} + + +video::ITexture *ShadowRenderer::getSMTexture(const std::string &shadow_map_name, + video::ECOLOR_FORMAT texture_format, bool force_creation) +{ + if (force_creation) { + return m_driver->addRenderTargetTexture( + core::dimension2du(m_shadow_map_texture_size, + m_shadow_map_texture_size), + shadow_map_name.c_str(), texture_format); + } + + return m_driver->getTexture(shadow_map_name.c_str()); +} + +void ShadowRenderer::renderShadowMap(video::ITexture *target, + DirectionalLight &light, scene::E_SCENE_NODE_RENDER_PASS pass) +{ + m_driver->setTransform(video::ETS_VIEW, light.getViewMatrix()); + m_driver->setTransform(video::ETS_PROJECTION, light.getProjectionMatrix()); + + // Operate on the client map + for (const auto &shadow_node : m_shadow_node_array) { + if (strcmp(shadow_node.node->getName(), "ClientMap") != 0) + continue; + + ClientMap *map_node = static_cast(shadow_node.node); + + video::SMaterial material; + if (map_node->getMaterialCount() > 0) { + // we only want the first material, which is the one with the albedo info + material = map_node->getMaterial(0); + } + + material.BackfaceCulling = false; + material.FrontfaceCulling = true; + material.PolygonOffsetFactor = 4.0f; + material.PolygonOffsetDirection = video::EPO_BACK; + //material.PolygonOffsetDepthBias = 1.0f/4.0f; + //material.PolygonOffsetSlopeScale = -1.f; + + if (m_shadow_map_colored && pass != scene::ESNRP_SOLID) + material.MaterialType = (video::E_MATERIAL_TYPE) depth_shader_trans; + else + material.MaterialType = (video::E_MATERIAL_TYPE) depth_shader; + + // FIXME: I don't think this is needed here + map_node->OnAnimate(m_device->getTimer()->getTime()); + + m_driver->setTransform(video::ETS_WORLD, + map_node->getAbsoluteTransformation()); + + map_node->renderMapShadows(m_driver, material, pass); + break; + } +} + +void ShadowRenderer::renderShadowObjects( + video::ITexture *target, DirectionalLight &light) +{ + m_driver->setTransform(video::ETS_VIEW, light.getViewMatrix()); + m_driver->setTransform(video::ETS_PROJECTION, light.getProjectionMatrix()); + + for (const auto &shadow_node : m_shadow_node_array) { + // we only take care of the shadow casters + if (shadow_node.shadowMode == ESM_RECEIVE || + strcmp(shadow_node.node->getName(), "ClientMap") == 0) + continue; + + // render other objects + u32 n_node_materials = shadow_node.node->getMaterialCount(); + std::vector BufferMaterialList; + std::vector> BufferMaterialCullingList; + BufferMaterialList.reserve(n_node_materials); + BufferMaterialCullingList.reserve(n_node_materials); + + // backup materialtype for each material + // (aka shader) + // and replace it by our "depth" shader + for (u32 m = 0; m < n_node_materials; m++) { + auto ¤t_mat = shadow_node.node->getMaterial(m); + + BufferMaterialList.push_back(current_mat.MaterialType); + current_mat.MaterialType = + (video::E_MATERIAL_TYPE)depth_shader; + + current_mat.setTexture(3, shadowMapTextureFinal); + + BufferMaterialCullingList.emplace_back( + (bool)current_mat.BackfaceCulling, (bool)current_mat.FrontfaceCulling); + + current_mat.BackfaceCulling = true; + current_mat.FrontfaceCulling = false; + current_mat.PolygonOffsetFactor = 1.0f/2048.0f; + current_mat.PolygonOffsetDirection = video::EPO_BACK; + //current_mat.PolygonOffsetDepthBias = 1.0 * 2.8e-6; + //current_mat.PolygonOffsetSlopeScale = -1.f; + } + + m_driver->setTransform(video::ETS_WORLD, + shadow_node.node->getAbsoluteTransformation()); + shadow_node.node->render(); + + // restore the material. + + for (u32 m = 0; m < n_node_materials; m++) { + auto ¤t_mat = shadow_node.node->getMaterial(m); + + current_mat.MaterialType = (video::E_MATERIAL_TYPE) BufferMaterialList[m]; + + current_mat.BackfaceCulling = BufferMaterialCullingList[m].first; + current_mat.FrontfaceCulling = BufferMaterialCullingList[m].second; + } + + } // end for caster shadow nodes +} + +void ShadowRenderer::mixShadowsQuad() +{ +} + +/* + * @Liso's disclaimer ;) This function loads the Shadow Mapping Shaders. + * I used a custom loader because I couldn't figure out how to use the base + * Shaders system with custom IShaderConstantSetCallBack without messing up the + * code too much. If anyone knows how to integrate this with the standard MT + * shaders, please feel free to change it. + */ + +void ShadowRenderer::createShaders() +{ + video::IGPUProgrammingServices *gpu = m_driver->getGPUProgrammingServices(); + + if (depth_shader == -1) { + std::string depth_shader_vs = getShaderPath("shadow_shaders", "pass1_vertex.glsl"); + if (depth_shader_vs.empty()) { + m_shadows_enabled = 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; + errorstream << "Error shadow mapping fs shader not found." << std::endl; + return; + } + m_shadow_depth_cb = new ShadowDepthShaderCB(); + + depth_shader = gpu->addHighLevelShaderMaterial( + readShaderFile(depth_shader_vs).c_str(), "vertexMain", + video::EVST_VS_1_1, + readShaderFile(depth_shader_fs).c_str(), "pixelMain", + video::EPST_PS_1_2, m_shadow_depth_cb); + + if (depth_shader == -1) { + // upsi, something went wrong loading shader. + delete m_shadow_depth_cb; + m_shadows_enabled = false; + errorstream << "Error compiling shadow mapping shader." << std::endl; + return; + } + + // HACK, TODO: investigate this better + // Grab the material renderer once more so minetest doesn't crash + // on exit + m_driver->getMaterialRenderer(depth_shader)->grab(); + } + + if (mixcsm_shader == -1) { + std::string depth_shader_vs = getShaderPath("shadow_shaders", "pass2_vertex.glsl"); + if (depth_shader_vs.empty()) { + m_shadows_enabled = 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; + errorstream << "Error cascade shadow mapping fs shader not found." << std::endl; + return; + } + m_shadow_mix_cb = new shadowScreenQuadCB(); + m_screen_quad = new shadowScreenQuad(); + mixcsm_shader = gpu->addHighLevelShaderMaterial( + readShaderFile(depth_shader_vs).c_str(), "vertexMain", + video::EVST_VS_1_1, + readShaderFile(depth_shader_fs).c_str(), "pixelMain", + video::EPST_PS_1_2, m_shadow_mix_cb); + + m_screen_quad->getMaterial().MaterialType = + (video::E_MATERIAL_TYPE)mixcsm_shader; + + if (mixcsm_shader == -1) { + // upsi, something went wrong loading shader. + delete m_shadow_mix_cb; + delete m_screen_quad; + m_shadows_enabled = false; + errorstream << "Error compiling cascade shadow mapping shader." << std::endl; + return; + } + + // HACK, TODO: investigate this better + // Grab the material renderer once more so minetest doesn't crash + // on exit + m_driver->getMaterialRenderer(mixcsm_shader)->grab(); + } + + 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; + 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; + errorstream << "Error shadow mapping fs shader not found." << std::endl; + return; + } + m_shadow_depth_trans_cb = new ShadowDepthShaderCB(); + + depth_shader_trans = gpu->addHighLevelShaderMaterial( + readShaderFile(depth_shader_vs).c_str(), "vertexMain", + video::EVST_VS_1_1, + readShaderFile(depth_shader_fs).c_str(), "pixelMain", + video::EPST_PS_1_2, m_shadow_depth_trans_cb); + + if (depth_shader_trans == -1) { + // upsi, something went wrong loading shader. + delete m_shadow_depth_trans_cb; + m_shadow_map_colored = false; + m_shadows_enabled = false; + errorstream << "Error compiling colored shadow mapping shader." << std::endl; + return; + } + + // HACK, TODO: investigate this better + // Grab the material renderer once more so minetest doesn't crash + // on exit + m_driver->getMaterialRenderer(depth_shader_trans)->grab(); + } +} + +std::string ShadowRenderer::readShaderFile(const std::string &path) +{ + std::string prefix; + if (m_shadow_map_colored) + prefix.append("#define COLORED_SHADOWS 1\n"); + + std::string content; + fs::ReadFile(path, content); + + return prefix + content; +} diff --git a/src/client/shadows/dynamicshadowsrender.h b/src/client/shadows/dynamicshadowsrender.h new file mode 100644 index 000000000..e633bd4f7 --- /dev/null +++ b/src/client/shadows/dynamicshadowsrender.h @@ -0,0 +1,146 @@ +/* +Minetest +Copyright (C) 2021 Liso + +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_extrabloated.h" +#include "client/shadows/dynamicshadows.h" + +class ShadowDepthShaderCB; +class shadowScreenQuad; +class shadowScreenQuadCB; + +enum E_SHADOW_MODE : u8 +{ + ESM_RECEIVE = 0, + ESM_BOTH, +}; + +struct NodeToApply +{ + NodeToApply(scene::ISceneNode *n, + E_SHADOW_MODE m = E_SHADOW_MODE::ESM_BOTH) : + node(n), + shadowMode(m){}; + bool operator<(const NodeToApply &other) const { return node < other.node; }; + + scene::ISceneNode *node; + + E_SHADOW_MODE shadowMode{E_SHADOW_MODE::ESM_BOTH}; + bool dirty{false}; +}; + +class ShadowRenderer +{ +public: + ShadowRenderer(IrrlichtDevice *device, Client *client); + + ~ShadowRenderer(); + + void initialize(); + + /// Adds a directional light shadow map (Usually just one (the sun) except in + /// Tattoine ). + size_t addDirectionalLight(); + DirectionalLight &getDirectionalLight(u32 index = 0); + size_t getDirectionalLightCount() const; + f32 getMaxShadowFar() const; + + float getUpdateDelta() const; + /// Adds a shadow to the scene node. + /// The shadow mode can be ESM_BOTH, or ESM_RECEIVE. + /// ESM_BOTH casts and receives shadows + /// ESM_RECEIVE only receives but does not cast shadows. + /// + void addNodeToShadowList(scene::ISceneNode *node, + E_SHADOW_MODE shadowMode = ESM_BOTH); + void removeNodeFromShadowList(scene::ISceneNode *node); + + void setClearColor(video::SColor ClearColor); + + void update(video::ITexture *outputTarget = nullptr); + + video::ITexture *get_texture() + { + return shadowMapTextureFinal; + } + + + bool is_active() const { return m_shadows_enabled; } + void setTimeOfDay(float isDay) { m_time_day = isDay; }; + + s32 getShadowSamples() const { return m_shadow_samples; } + float getShadowStrength() const { return m_shadow_strength; } + float getTimeOfDay() const { return m_time_day; } + +private: + video::ITexture *getSMTexture(const std::string &shadow_map_name, + video::ECOLOR_FORMAT texture_format, + bool force_creation = false); + + void renderShadowMap(video::ITexture *target, DirectionalLight &light, + scene::E_SCENE_NODE_RENDER_PASS pass = + scene::ESNRP_SOLID); + void renderShadowObjects(video::ITexture *target, DirectionalLight &light); + void mixShadowsQuad(); + + // a bunch of variables + IrrlichtDevice *m_device{nullptr}; + scene::ISceneManager *m_smgr{nullptr}; + video::IVideoDriver *m_driver{nullptr}; + Client *m_client{nullptr}; + video::ITexture *shadowMapClientMap{nullptr}; + video::ITexture *shadowMapTextureFinal{nullptr}; + video::ITexture *shadowMapTextureDynamicObjects{nullptr}; + video::ITexture *shadowMapTextureColors{nullptr}; + video::SColor m_clear_color{0x0}; + + std::vector m_light_list; + std::vector m_shadow_node_array; + + float m_shadow_strength; + float m_shadow_map_max_distance; + float m_shadow_map_texture_size; + float m_time_day{0.0f}; + float m_update_delta; + int m_shadow_samples; + bool m_shadow_map_texture_32bit; + bool m_shadows_enabled; + bool m_shadow_map_colored; + + video::ECOLOR_FORMAT m_texture_format{video::ECOLOR_FORMAT::ECF_R16F}; + video::ECOLOR_FORMAT m_texture_format_color{video::ECOLOR_FORMAT::ECF_R16G16}; + + // Shadow Shader stuff + + void createShaders(); + std::string readShaderFile(const std::string &path); + + s32 depth_shader{-1}; + s32 depth_shader_trans{-1}; + s32 mixcsm_shader{-1}; + + ShadowDepthShaderCB *m_shadow_depth_cb{nullptr}; + ShadowDepthShaderCB *m_shadow_depth_trans_cb{nullptr}; + + shadowScreenQuad *m_screen_quad{nullptr}; + shadowScreenQuadCB *m_shadow_mix_cb{nullptr}; +}; diff --git a/src/client/shadows/shadowsScreenQuad.cpp b/src/client/shadows/shadowsScreenQuad.cpp new file mode 100644 index 000000000..c36ee0d60 --- /dev/null +++ b/src/client/shadows/shadowsScreenQuad.cpp @@ -0,0 +1,67 @@ +/* +Minetest +Copyright (C) 2021 Liso + +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 "shadowsScreenQuad.h" + +shadowScreenQuad::shadowScreenQuad() +{ + Material.Wireframe = false; + Material.Lighting = false; + + video::SColor color(0x0); + Vertices[0] = video::S3DVertex( + -1.0f, -1.0f, 0.0f, 0, 0, 1, color, 0.0f, 1.0f); + Vertices[1] = video::S3DVertex( + -1.0f, 1.0f, 0.0f, 0, 0, 1, color, 0.0f, 0.0f); + Vertices[2] = video::S3DVertex( + 1.0f, 1.0f, 0.0f, 0, 0, 1, color, 1.0f, 0.0f); + Vertices[3] = video::S3DVertex( + 1.0f, -1.0f, 0.0f, 0, 0, 1, color, 1.0f, 1.0f); + Vertices[4] = video::S3DVertex( + -1.0f, -1.0f, 0.0f, 0, 0, 1, color, 0.0f, 1.0f); + Vertices[5] = video::S3DVertex( + 1.0f, 1.0f, 0.0f, 0, 0, 1, color, 1.0f, 0.0f); +} + +void shadowScreenQuad::render(video::IVideoDriver *driver) +{ + u16 indices[6] = {0, 1, 2, 3, 4, 5}; + driver->setMaterial(Material); + driver->setTransform(video::ETS_WORLD, core::matrix4()); + driver->drawIndexedTriangleList(&Vertices[0], 6, &indices[0], 2); +} + +void shadowScreenQuadCB::OnSetConstants( + video::IMaterialRendererServices *services, s32 userData) +{ + s32 TextureId = 0; + services->setPixelShaderConstant( + services->getPixelShaderConstantID("ShadowMapClientMap"), + &TextureId, 1); + + TextureId = 1; + services->setPixelShaderConstant( + services->getPixelShaderConstantID("ShadowMapClientMapTraslucent"), + &TextureId, 1); + + TextureId = 2; + services->setPixelShaderConstant( + services->getPixelShaderConstantID("ShadowMapSamplerdynamic"), + &TextureId, 1); +} diff --git a/src/client/shadows/shadowsScreenQuad.h b/src/client/shadows/shadowsScreenQuad.h new file mode 100644 index 000000000..e6cc95957 --- /dev/null +++ b/src/client/shadows/shadowsScreenQuad.h @@ -0,0 +1,45 @@ +/* +Minetest +Copyright (C) 2021 Liso + +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 "irrlichttypes_extrabloated.h" +#include +#include + +class shadowScreenQuad +{ +public: + shadowScreenQuad(); + + void render(video::IVideoDriver *driver); + video::SMaterial &getMaterial() { return Material; } + +private: + video::S3DVertex Vertices[6]; + video::SMaterial Material; +}; + +class shadowScreenQuadCB : public video::IShaderConstantSetCallBack +{ +public: + shadowScreenQuadCB(){}; + + virtual void OnSetConstants(video::IMaterialRendererServices *services, + s32 userData); +}; diff --git a/src/client/shadows/shadowsshadercallbacks.cpp b/src/client/shadows/shadowsshadercallbacks.cpp new file mode 100644 index 000000000..2f5797084 --- /dev/null +++ b/src/client/shadows/shadowsshadercallbacks.cpp @@ -0,0 +1,44 @@ +/* +Minetest +Copyright (C) 2021 Liso + +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 "client/shadows/shadowsshadercallbacks.h" + +void ShadowDepthShaderCB::OnSetConstants( + video::IMaterialRendererServices *services, s32 userData) +{ + video::IVideoDriver *driver = services->getVideoDriver(); + + core::matrix4 lightMVP = driver->getTransform(video::ETS_PROJECTION); + lightMVP *= driver->getTransform(video::ETS_VIEW); + lightMVP *= driver->getTransform(video::ETS_WORLD); + + services->setVertexShaderConstant( + services->getPixelShaderConstantID("LightMVP"), + lightMVP.pointer(), 16); + + services->setVertexShaderConstant( + services->getPixelShaderConstantID("MapResolution"), &MapRes, 1); + services->setVertexShaderConstant( + services->getPixelShaderConstantID("MaxFar"), &MaxFar, 1); + + s32 TextureId = 0; + services->setPixelShaderConstant( + services->getPixelShaderConstantID("ColorMapSampler"), &TextureId, + 1); +} diff --git a/src/client/shadows/shadowsshadercallbacks.h b/src/client/shadows/shadowsshadercallbacks.h new file mode 100644 index 000000000..43ad489f2 --- /dev/null +++ b/src/client/shadows/shadowsshadercallbacks.h @@ -0,0 +1,34 @@ +/* +Minetest +Copyright (C) 2021 Liso + +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 "irrlichttypes_extrabloated.h" +#include +#include + +class ShadowDepthShaderCB : public video::IShaderConstantSetCallBack +{ +public: + void OnSetMaterial(const video::SMaterial &material) override {} + + void OnSetConstants(video::IMaterialRendererServices *services, + s32 userData) override; + + f32 MaxFar{2048.0f}, MapRes{1024.0f}; +}; diff --git a/src/client/sky.cpp b/src/client/sky.cpp index 47296a7a5..1cf9a4afc 100644 --- a/src/client/sky.cpp +++ b/src/client/sky.cpp @@ -122,7 +122,14 @@ Sky::Sky(s32 id, RenderingEngine *rendering_engine, ITextureSource *tsrc, IShade m_materials[i].Lighting = true; m_materials[i].MaterialType = video::EMT_SOLID; } + m_directional_colored_fog = g_settings->getBool("directional_colored_fog"); + + if (g_settings->getBool("enable_dynamic_shadows")) { + float val = g_settings->getFloat("shadow_sky_body_orbit_tilt"); + m_sky_body_orbit_tilt = rangelim(val, 0.0f, 60.0f); + } + setStarCount(1000, true); } @@ -175,17 +182,7 @@ void Sky::render() video::SColorf mooncolor_f(0.50, 0.57, 0.65, 1); video::SColorf mooncolor2_f(0.85, 0.875, 0.9, 1); - float nightlength = 0.415; - float wn = nightlength / 2; - float wicked_time_of_day = 0; - if (m_time_of_day > wn && m_time_of_day < 1.0 - wn) - wicked_time_of_day = (m_time_of_day - wn) / (1.0 - wn * 2) * 0.5 + 0.25; - else if (m_time_of_day < 0.5) - wicked_time_of_day = m_time_of_day / wn * 0.25; - else - wicked_time_of_day = 1.0 - ((1.0 - m_time_of_day) / wn * 0.25); - /*std::cerr<<"time_of_day="< " - <<"wicked_time_of_day="< wn && time_of_day < 1.0f - wn) + wicked_time_of_day = (time_of_day - wn) / (1.0f - wn * 2) * 0.5f + 0.25f; + else if (time_of_day < 0.5f) + wicked_time_of_day = time_of_day / wn * 0.25f; + else + wicked_time_of_day = 1.0f - ((1.0f - time_of_day) / wn * 0.25f); + return wicked_time_of_day; +} diff --git a/src/client/sky.h b/src/client/sky.h index 121a16bb7..83106453b 100644 --- a/src/client/sky.h +++ b/src/client/sky.h @@ -105,6 +105,8 @@ public: ITextureSource *tsrc); const video::SColorf &getCurrentStarColor() const { return m_star_color; } + float getSkyBodyOrbitTilt() const { return m_sky_body_orbit_tilt; } + private: aabb3f m_box; video::SMaterial m_materials[SKY_MATERIAL_COUNT]; @@ -159,6 +161,7 @@ private: bool m_directional_colored_fog; bool m_in_clouds = true; // Prevent duplicating bools to remember old values bool m_enable_shaders = false; + float m_sky_body_orbit_tilt = 0.0f; video::SColorf m_bgcolor_bright_f = video::SColorf(1.0f, 1.0f, 1.0f, 1.0f); video::SColorf m_skycolor_bright_f = video::SColorf(1.0f, 1.0f, 1.0f, 1.0f); @@ -205,3 +208,7 @@ private: float horizon_position, float day_position); void setSkyDefaults(); }; + +// calculates value for sky body positions for the given observed time of day +// this is used to draw both Sun/Moon and shadows +float getWickedTimeOfDay(float time_of_day); diff --git a/src/client/wieldmesh.cpp b/src/client/wieldmesh.cpp index 08fd49fc0..7597aaa88 100644 --- a/src/client/wieldmesh.cpp +++ b/src/client/wieldmesh.cpp @@ -33,6 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/numeric.h" #include #include +#include "client/renderingengine.h" #define WIELD_SCALE_FACTOR 30.0 #define WIELD_SCALE_FACTOR_EXTRUDED 40.0 @@ -220,11 +221,18 @@ WieldMeshSceneNode::WieldMeshSceneNode(scene::ISceneManager *mgr, s32 id, bool l m_meshnode->setReadOnlyMaterials(false); m_meshnode->setVisible(false); dummymesh->drop(); // m_meshnode grabbed it + + m_shadow = RenderingEngine::get_shadow_renderer(); } WieldMeshSceneNode::~WieldMeshSceneNode() { sanity_check(g_extrusion_mesh_cache); + + // Remove node from shadow casters + if (m_shadow) + m_shadow->removeNodeFromShadowList(m_meshnode); + if (g_extrusion_mesh_cache->drop()) g_extrusion_mesh_cache = nullptr; } @@ -527,6 +535,10 @@ void WieldMeshSceneNode::changeToMesh(scene::IMesh *mesh) // need to normalize normals when lighting is enabled (because of setScale()) m_meshnode->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, m_lighting); m_meshnode->setVisible(true); + + // Add mesh to shadow caster + if (m_shadow) + m_shadow->addNodeToShadowList(m_meshnode); } void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) diff --git a/src/client/wieldmesh.h b/src/client/wieldmesh.h index 933097230..d1eeb64f5 100644 --- a/src/client/wieldmesh.h +++ b/src/client/wieldmesh.h @@ -27,6 +27,7 @@ struct ItemStack; class Client; class ITextureSource; struct ContentFeatures; +class ShadowRenderer; /*! * Holds color information of an item mesh's buffer. @@ -124,6 +125,8 @@ private: // so this variable is just required so we can implement // getBoundingBox() and is set to an empty box. aabb3f m_bounding_box; + + ShadowRenderer *m_shadow; }; void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result); diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 871290944..38cade80b 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -262,6 +262,18 @@ void set_default_settings() settings->setDefault("enable_waving_leaves", "false"); settings->setDefault("enable_waving_plants", "false"); + // Effects Shadows + settings->setDefault("enable_dynamic_shadows", "false"); + settings->setDefault("shadow_strength", "0.2"); + settings->setDefault("shadow_map_max_distance", "200.0"); + settings->setDefault("shadow_map_texture_size", "2048"); + settings->setDefault("shadow_map_texture_32bit", "true"); + settings->setDefault("shadow_map_color", "false"); + settings->setDefault("shadow_filters", "1"); + settings->setDefault("shadow_poisson_filter", "true"); + settings->setDefault("shadow_update_time", "0.2"); + settings->setDefault("shadow_soft_radius", "1.0"); + settings->setDefault("shadow_sky_body_orbit_tilt", "0.0"); // Input settings->setDefault("invert_mouse", "false"); -- cgit v1.2.3 From edf098db637bf74d4675ce900dc9c0b8ee528a03 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 15 Jun 2021 19:11:56 +0200 Subject: Drop --videomodes, fullscreen_bpp and high_precision_fpu settings These have been pointless for a while. --- builtin/settingtypes.txt | 10 +---- src/client/clientlauncher.cpp | 6 --- src/client/clientlauncher.h | 1 - src/client/renderingengine.cpp | 78 +-------------------------------------- src/client/renderingengine.h | 1 - src/defaultsettings.cpp | 2 - src/main.cpp | 2 - src/script/lua_api/l_mainmenu.cpp | 23 ------------ src/script/lua_api/l_mainmenu.h | 2 - 9 files changed, 3 insertions(+), 122 deletions(-) (limited to 'src') diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index ae421de2c..57857cabb 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -660,10 +660,10 @@ viewing_range (Viewing range) int 190 20 4000 # 0.1 = Default, 0.25 = Good value for weaker tablets. near_plane (Near plane) float 0.1 0 0.25 -# Width component of the initial window size. +# Width component of the initial window size. Ignored in fullscreen mode. screen_w (Screen width) int 1024 1 -# Height component of the initial window size. +# Height component of the initial window size. Ignored in fullscreen mode. screen_h (Screen height) int 600 1 # Save window size automatically when modified. @@ -672,9 +672,6 @@ autosave_screensize (Autosave screen size) bool true # Fullscreen mode. fullscreen (Full screen) bool false -# Bits per pixel (aka color depth) in fullscreen mode. -fullscreen_bpp (Full screen BPP) int 24 - # Vertical screen synchronization. vsync (VSync) bool false @@ -1477,9 +1474,6 @@ curl_parallel_limit (cURL parallel limit) int 8 # Maximum time a file download (e.g. a mod download) may take, stated in milliseconds. curl_file_download_timeout (cURL file download timeout) int 300000 -# Makes DirectX work with LuaJIT. Disable if it causes troubles. -high_precision_fpu (High-precision FPU) bool true - # Replaces the default main menu with a custom one. main_menu_script (Main menu script) string diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp index dbf1d1cd1..13e7aefcf 100644 --- a/src/client/clientlauncher.cpp +++ b/src/client/clientlauncher.cpp @@ -99,10 +99,6 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args) init_args(start_data, cmd_args); - // List video modes if requested - if (list_video_modes) - return m_rendering_engine->print_video_modes(); - #if USE_SOUND if (g_settings->getBool("enable_sound")) g_sound_manager_singleton = createSoundManagerSingleton(); @@ -336,8 +332,6 @@ void ClientLauncher::init_args(GameStartData &start_data, const Settings &cmd_ar if (cmd_args.exists("name")) start_data.name = cmd_args.get("name"); - list_video_modes = cmd_args.getFlag("videomodes"); - random_input = g_settings->getBool("random_input") || cmd_args.getFlag("random-input"); } diff --git a/src/client/clientlauncher.h b/src/client/clientlauncher.h index 79727e1fe..d1fd9a258 100644 --- a/src/client/clientlauncher.h +++ b/src/client/clientlauncher.h @@ -46,7 +46,6 @@ private: void speed_tests(); - bool list_video_modes = false; bool skip_main_menu = false; bool random_input = false; RenderingEngine *m_rendering_engine = nullptr; diff --git a/src/client/renderingengine.cpp b/src/client/renderingengine.cpp index 4f96a6e37..9015fb82a 100644 --- a/src/client/renderingengine.cpp +++ b/src/client/renderingengine.cpp @@ -92,7 +92,6 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver) // bpp, fsaa, vsync bool vsync = g_settings->getBool("vsync"); - u16 bits = g_settings->getU16("fullscreen_bpp"); u16 fsaa = g_settings->getU16("fsaa"); // stereo buffer required for pageflip stereo @@ -122,15 +121,13 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver) params.LoggingLevel = irr::ELL_DEBUG; params.DriverType = driverType; params.WindowSize = core::dimension2d(screen_w, screen_h); - params.Bits = bits; params.AntiAlias = fsaa; params.Fullscreen = fullscreen; params.Stencilbuffer = false; params.Stereobuffer = stereo_buffer; params.Vsync = vsync; params.EventReceiver = receiver; - params.HighPrecisionFPU = g_settings->getBool("high_precision_fpu"); - params.ZBufferBits = 24; + params.HighPrecisionFPU = true; #ifdef __ANDROID__ params.PrivateData = porting::app_global; #endif @@ -171,60 +168,6 @@ void RenderingEngine::setResizable(bool resize) m_device->setResizable(resize); } -bool RenderingEngine::print_video_modes() -{ - IrrlichtDevice *nulldevice; - - bool vsync = g_settings->getBool("vsync"); - u16 fsaa = g_settings->getU16("fsaa"); - MyEventReceiver *receiver = new MyEventReceiver(); - - SIrrlichtCreationParameters params = SIrrlichtCreationParameters(); - params.DriverType = video::EDT_NULL; - params.WindowSize = core::dimension2d(640, 480); - params.Bits = 24; - params.AntiAlias = fsaa; - params.Fullscreen = false; - params.Stencilbuffer = false; - params.Vsync = vsync; - params.EventReceiver = receiver; - params.HighPrecisionFPU = g_settings->getBool("high_precision_fpu"); - - nulldevice = createDeviceEx(params); - - if (!nulldevice) { - delete receiver; - return false; - } - - std::cout << _("Available video modes (WxHxD):") << std::endl; - - video::IVideoModeList *videomode_list = nulldevice->getVideoModeList(); - - if (videomode_list != NULL) { - s32 videomode_count = videomode_list->getVideoModeCount(); - core::dimension2d videomode_res; - s32 videomode_depth; - for (s32 i = 0; i < videomode_count; ++i) { - videomode_res = videomode_list->getVideoModeResolution(i); - videomode_depth = videomode_list->getVideoModeDepth(i); - std::cout << videomode_res.Width << "x" << videomode_res.Height - << "x" << videomode_depth << std::endl; - } - - std::cout << _("Active video mode (WxHxD):") << std::endl; - videomode_res = videomode_list->getDesktopResolution(); - videomode_depth = videomode_list->getDesktopDepth(); - std::cout << videomode_res.Width << "x" << videomode_res.Height << "x" - << videomode_depth << std::endl; - } - - nulldevice->drop(); - delete receiver; - - return videomode_list != NULL; -} - void RenderingEngine::removeMesh(const scene::IMesh* mesh) { m_device->getSceneManager()->getMeshCache()->removeMesh(mesh); @@ -582,25 +525,6 @@ void RenderingEngine::draw_menu_scene(gui::IGUIEnvironment *guienv, get_video_driver()->endScene(); } -std::vector> RenderingEngine::getSupportedVideoModes() -{ - IrrlichtDevice *nulldevice = createDevice(video::EDT_NULL); - sanity_check(nulldevice); - - std::vector> mlist; - video::IVideoModeList *modelist = nulldevice->getVideoModeList(); - - s32 num_modes = modelist->getVideoModeCount(); - for (s32 i = 0; i != num_modes; i++) { - core::dimension2d mode_res = modelist->getVideoModeResolution(i); - u32 mode_depth = (u32)modelist->getVideoModeDepth(i); - mlist.emplace_back(mode_res.Width, mode_res.Height, mode_depth); - } - - nulldevice->drop(); - return mlist; -} - std::vector RenderingEngine::getSupportedVideoDrivers() { std::vector drivers; diff --git a/src/client/renderingengine.h b/src/client/renderingengine.h index 6d42221d6..5299222e2 100644 --- a/src/client/renderingengine.h +++ b/src/client/renderingengine.h @@ -124,7 +124,6 @@ public: return s_singleton->core->get_shadow_renderer(); return nullptr; } - static std::vector> getSupportedVideoModes(); static std::vector getSupportedVideoDrivers(); private: diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 38cade80b..0895bf898 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -177,7 +177,6 @@ void set_default_settings() settings->setDefault("screen_h", "600"); settings->setDefault("autosave_screensize", "true"); settings->setDefault("fullscreen", "false"); - settings->setDefault("fullscreen_bpp", "24"); settings->setDefault("vsync", "false"); settings->setDefault("fov", "72"); settings->setDefault("leaves_style", "fancy"); @@ -454,7 +453,6 @@ void set_default_settings() settings->setDefault("server_name", ""); settings->setDefault("server_description", ""); - settings->setDefault("high_precision_fpu", "true"); settings->setDefault("enable_console", "false"); settings->setDefault("screen_dpi", "72"); diff --git a/src/main.cpp b/src/main.cpp index 7f96836b5..4a69f83b5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -304,8 +304,6 @@ static void set_allowed_options(OptionList *allowed_options) allowed_options->insert(std::make_pair("terminal", ValueSpec(VALUETYPE_FLAG, _("Feature an interactive terminal (Only works when using minetestserver or with --server)")))); #ifndef SERVER - allowed_options->insert(std::make_pair("videomodes", ValueSpec(VALUETYPE_FLAG, - _("Show available video modes")))); allowed_options->insert(std::make_pair("speedtests", ValueSpec(VALUETYPE_FLAG, _("Run speed tests")))); allowed_options->insert(std::make_pair("address", ValueSpec(VALUETYPE_STRING, diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index ee3e72dea..788557460 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -752,28 +752,6 @@ int ModApiMainMenu::l_get_video_drivers(lua_State *L) return 1; } -/******************************************************************************/ -int ModApiMainMenu::l_get_video_modes(lua_State *L) -{ - std::vector > videomodes - = RenderingEngine::getSupportedVideoModes(); - - lua_newtable(L); - for (u32 i = 0; i != videomodes.size(); i++) { - lua_newtable(L); - lua_pushnumber(L, videomodes[i].X); - lua_setfield(L, -2, "w"); - lua_pushnumber(L, videomodes[i].Y); - lua_setfield(L, -2, "h"); - lua_pushnumber(L, videomodes[i].Z); - lua_setfield(L, -2, "depth"); - - lua_rawseti(L, -2, i + 1); - } - - return 1; -} - /******************************************************************************/ int ModApiMainMenu::l_gettext(lua_State *L) { @@ -895,7 +873,6 @@ void ModApiMainMenu::Initialize(lua_State *L, int top) API_FCT(download_file); API_FCT(gettext); API_FCT(get_video_drivers); - API_FCT(get_video_modes); API_FCT(get_screen_info); API_FCT(get_min_supp_proto); API_FCT(get_max_supp_proto); diff --git a/src/script/lua_api/l_mainmenu.h b/src/script/lua_api/l_mainmenu.h index 33ac9e721..ec2d20da2 100644 --- a/src/script/lua_api/l_mainmenu.h +++ b/src/script/lua_api/l_mainmenu.h @@ -140,8 +140,6 @@ private: static int l_get_video_drivers(lua_State *L); - static int l_get_video_modes(lua_State *L); - //version compatibility static int l_get_min_supp_proto(lua_State *L); -- cgit v1.2.3 From cb5dd0dae4a0a3c403aba6cab9198a12efbc876b Mon Sep 17 00:00:00 2001 From: "updatepo.sh" Date: Wed, 16 Jun 2021 18:27:45 +0200 Subject: Update minetest.conf.example and dummy translation file --- minetest.conf.example | 129 +++++++++++++++++++++++++------------- src/settings_translation_file.cpp | 65 +++++++++++-------- 2 files changed, 127 insertions(+), 67 deletions(-) (limited to 'src') diff --git a/minetest.conf.example b/minetest.conf.example index 6343c8234..718cb0c75 100644 --- a/minetest.conf.example +++ b/minetest.conf.example @@ -30,7 +30,7 @@ # type: bool # pitch_move = false -# Fast movement (via the "special" key). +# Fast movement (via the "Aux1" key). # This requires the "fast" privilege on the server. # type: bool # fast_move = false @@ -61,7 +61,7 @@ # type: float # mouse_sensitivity = 0.2 -# If enabled, "special" key instead of "sneak" key is used for climbing down and +# If enabled, "Aux1" key instead of "Sneak" key is used for climbing down and # descending. # type: bool # aux1_descends = false @@ -70,7 +70,7 @@ # type: bool # doubletap_jump = false -# If disabled, "special" key is used to fly fast if both fly and fast mode are +# If disabled, "Aux1" key is used to fly fast if both fly and fast mode are # enabled. # type: bool # always_fly_fast = true @@ -107,10 +107,10 @@ # type: bool # fixed_virtual_joystick = false -# (Android) Use virtual joystick to trigger "aux" button. -# If enabled, virtual joystick will also tap "aux" button when out of main circle. +# (Android) Use virtual joystick to trigger "Aux1" button. +# If enabled, virtual joystick will also tap "Aux1" button when out of main circle. # type: bool -# virtual_joystick_triggers_aux = false +# virtual_joystick_triggers_aux1 = false # Enable joysticks # type: bool @@ -188,7 +188,7 @@ # Key for moving fast in fast mode. # See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3 # type: key -# keymap_special1 = KEY_KEY_E +# keymap_aux1 = KEY_KEY_E # Key for opening the chat window. # See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3 @@ -570,9 +570,9 @@ # trilinear_filter = false # Filtered textures can blend RGB values with fully-transparent neighbors, -# which PNG optimizers usually discard, sometimes resulting in a dark or -# light edge to transparent textures. Apply this filter to clean that up -# at texture load time. +# which PNG optimizers usually discard, often resulting in dark or +# light edges to transparent textures. Apply a filter to clean that up +# at texture load time. This is automatically enabled if mipmapping is enabled. # type: bool # texture_clean_transparent = false @@ -580,9 +580,8 @@ # can be blurred, so automatically upscale them with nearest-neighbor # interpolation to preserve crisp pixels. This sets the minimum texture size # for the upscaled textures; higher values look sharper, but require more -# memory. Powers of 2 are recommended. Setting this higher than 1 may not -# have a visible effect unless bilinear/trilinear/anisotropic filtering is -# enabled. +# memory. Powers of 2 are recommended. This setting is ONLY applies if +# bilinear/trilinear/anisotropic filtering is enabled. # This is also used as the base node texture size for world-aligned # texture autoscaling. # type: int @@ -662,6 +661,68 @@ # type: bool # enable_waving_plants = false +#### Dynamic shadows + +# Set to true to enable Shadow Mapping. +# Requires shaders to be enabled. +# type: bool +# enable_dynamic_shadows = false + +# Set the shadow strength. +# Lower value means lighter shadows, higher value means darker shadows. +# type: float min: 0.05 max: 1 +# shadow_strength = 0.2 + +# Maximum distance to render shadows. +# type: float min: 10 max: 1000 +# shadow_map_max_distance = 120.0 + +# Texture size to render the shadow map on. +# This must be a power of two. +# Bigger numbers create better shadowsbut it is also more expensive. +# type: int min: 128 max: 8192 +# shadow_map_texture_size = 1024 + +# Sets shadow texture quality to 32 bits. +# On false, 16 bits texture will be used. +# This can cause much more artifacts in the shadow. +# type: bool +# shadow_map_texture_32bit = true + +# Enable poisson disk filtering. +# On true uses poisson disk to make "soft shadows". Otherwise uses PCF filtering. +# type: bool +# shadow_poisson_filter = true + +# Define shadow filtering quality +# This simulates the soft shadows effect by applying a PCF or poisson disk +# but also uses more resources. +# type: enum values: 0, 1, 2 +# shadow_filters = 1 + +# Enable colored shadows. +# On true translucent nodes cast colored shadows. This is expensive. +# type: bool +# shadow_map_color = false + +# Set the shadow update time. +# Lower value means shadows and map updates faster, but it consume more resources. +# Minimun value 0.001 seconds max value 0.2 seconds +# type: float min: 0.001 max: 0.2 +# shadow_update_time = 0.2 + +# Set the soft shadow radius size. +# Lower values mean sharper shadows bigger values softer. +# Minimun value 1.0 and max value 10.0 +# type: float min: 1 max: 10 +# shadow_soft_radius = 1.0 + +# Set the tilt of Sun/Moon orbit in degrees +# Value of 0 means no tilt / vertical orbit. +# Minimun value 0.0 and max value 60.0 +# type: float min: 0 max: 60 +# shadow_sky_body_orbit_tilt = 0.0 + ### Advanced # Arm inertia, gives a more realistic movement of @@ -694,11 +755,11 @@ # type: float min: 0 max: 0.25 # near_plane = 0.1 -# Width component of the initial window size. +# Width component of the initial window size. Ignored in fullscreen mode. # type: int min: 1 # screen_w = 1024 -# Height component of the initial window size. +# Height component of the initial window size. Ignored in fullscreen mode. # type: int min: 1 # screen_h = 600 @@ -710,10 +771,6 @@ # type: bool # fullscreen = false -# Bits per pixel (aka color depth) in fullscreen mode. -# type: int -# fullscreen_bpp = 24 - # Vertical screen synchronization. # type: bool # vsync = false @@ -1011,7 +1068,7 @@ # font_path_italic = fonts/Arimo-Italic.ttf # type: filepath -# font_path_bolditalic = fonts/Arimo-BoldItalic.ttf +# font_path_bold_italic = fonts/Arimo-BoldItalic.ttf # Font size of the monospace font in point (pt). # type: int min: 1 @@ -1031,19 +1088,7 @@ # mono_font_path_italic = fonts/Cousine-Italic.ttf # type: filepath -# mono_font_path_bolditalic = fonts/Cousine-BoldItalic.ttf - -# Font size of the fallback font in point (pt). -# type: int min: 1 -# fallback_font_size = 15 - -# Shadow offset (in pixels) of the fallback font. If 0, then shadow will not be drawn. -# type: int -# fallback_font_shadow = 1 - -# Opaqueness (alpha) of the shadow behind the fallback font, between 0 and 255. -# type: int min: 0 max: 255 -# fallback_font_shadow_alpha = 128 +# mono_font_path_bold_italic = fonts/Cousine-BoldItalic.ttf # Path of the fallback font. # If “freetype” setting is enabled: Must be a TrueType font. @@ -1364,6 +1409,11 @@ # type: string # chat_message_format = <@name> @message +# If the execution of a chat command takes longer than this specified time in +# seconds, add the time information to the chat command message +# type: float +# chatcommand_msg_time_threshold = 0.1 + # A message to be displayed to all clients when the server shuts down. # type: string # kick_msg_shutdown = Server shutting down. @@ -1682,7 +1732,7 @@ # Set the language. Leave empty to use the system language. # A restart is required after changing this. -# type: enum values: , ar, ca, cs, da, de, dv, el, en, eo, es, et, eu, fil, fr, hu, id, it, ja, ja_KS, jbo, kk, kn, lo, lt, ms, my, nb, nl, nn, pl, pt, pt_BR, ro, ru, sl, sr_Cyrl, sv, sw, th, tr, uk, vi +# type: enum values: , be, bg, ca, cs, da, de, el, en, eo, es, et, eu, fi, fr, gd, gl, hu, id, it, ja, jbo, kk, ko, lt, lv, ms, nb, nl, nn, pl, pt, pt_BR, ro, ru, sk, sl, sr_Cyrl, sr_Latn, sv, sw, tr, uk, vi, zh_CN, zh_TW # language = # Level of logging to be written to debug.txt: @@ -1714,10 +1764,9 @@ ## Advanced -# Default timeout for cURL, stated in milliseconds. -# Only has an effect if compiled with cURL. +# Maximum time an interactive request (e.g. server list fetch) may take, stated in milliseconds. # type: int -# curl_timeout = 5000 +# curl_timeout = 20000 # Limits number of parallel HTTP requests. Affects: # - Media fetch if server uses remote_media setting. @@ -1727,14 +1776,10 @@ # type: int # curl_parallel_limit = 8 -# Maximum time in ms a file download (e.g. a mod download) may take. +# Maximum time a file download (e.g. a mod download) may take, stated in milliseconds. # type: int # curl_file_download_timeout = 300000 -# Makes DirectX work with LuaJIT. Disable if it causes troubles. -# type: bool -# high_precision_fpu = true - # Replaces the default main menu with a custom one. # type: string # main_menu_script = diff --git a/src/settings_translation_file.cpp b/src/settings_translation_file.cpp index 317186e94..49591d1ee 100644 --- a/src/settings_translation_file.cpp +++ b/src/settings_translation_file.cpp @@ -11,7 +11,7 @@ fake_function() { gettext("Pitch move mode"); gettext("If enabled, makes move directions relative to the player's pitch when flying or swimming."); gettext("Fast movement"); - gettext("Fast movement (via the \"special\" key).\nThis requires the \"fast\" privilege on the server."); + gettext("Fast movement (via the \"Aux1\" key).\nThis requires the \"fast\" privilege on the server."); gettext("Noclip"); gettext("If enabled together with fly mode, player is able to fly through solid nodes.\nThis requires the \"noclip\" privilege on the server."); gettext("Cinematic mode"); @@ -24,12 +24,12 @@ fake_function() { gettext("Invert vertical mouse movement."); gettext("Mouse sensitivity"); gettext("Mouse sensitivity multiplier."); - gettext("Special key for climbing/descending"); - gettext("If enabled, \"special\" key instead of \"sneak\" key is used for climbing down and\ndescending."); + gettext("Aux1 key for climbing/descending"); + gettext("If enabled, \"Aux1\" key instead of \"Sneak\" key is used for climbing down and\ndescending."); gettext("Double tap jump for fly"); gettext("Double-tapping the jump key toggles fly mode."); gettext("Always fly and fast"); - gettext("If disabled, \"special\" key is used to fly fast if both fly and fast mode are\nenabled."); + gettext("If disabled, \"Aux1\" key is used to fly fast if both fly and fast mode are\nenabled."); gettext("Place repetition interval"); gettext("The time in seconds it takes between repeated node placements when holding\nthe place button."); gettext("Automatic jumping"); @@ -44,8 +44,8 @@ fake_function() { gettext("The length in pixels it takes for touch screen interaction to start."); gettext("Fixed virtual joystick"); gettext("(Android) Fixes the position of virtual joystick.\nIf disabled, virtual joystick will center to first-touch's position."); - gettext("Virtual joystick triggers aux button"); - gettext("(Android) Use virtual joystick to trigger \"aux\" button.\nIf enabled, virtual joystick will also tap \"aux\" button when out of main circle."); + gettext("Virtual joystick triggers Aux1 button"); + gettext("(Android) Use virtual joystick to trigger \"Aux1\" button.\nIf enabled, virtual joystick will also tap \"Aux1\" button when out of main circle."); gettext("Enable joysticks"); gettext("Enable joysticks"); gettext("Joystick ID"); @@ -76,7 +76,7 @@ fake_function() { gettext("Key for placing.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); gettext("Inventory key"); gettext("Key for opening the inventory.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Special key"); + gettext("Aux1 key"); gettext("Key for moving fast in fast mode.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); gettext("Chat key"); gettext("Key for opening the chat window.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); @@ -233,9 +233,9 @@ fake_function() { gettext("Trilinear filtering"); gettext("Use trilinear filtering when scaling textures."); gettext("Clean transparent textures"); - gettext("Filtered textures can blend RGB values with fully-transparent neighbors,\nwhich PNG optimizers usually discard, sometimes resulting in a dark or\nlight edge to transparent textures. Apply this filter to clean that up\nat texture load time."); + gettext("Filtered textures can blend RGB values with fully-transparent neighbors,\nwhich PNG optimizers usually discard, often resulting in dark or\nlight edges to transparent textures. Apply a filter to clean that up\nat texture load time. This is automatically enabled if mipmapping is enabled."); gettext("Minimum texture size"); - gettext("When using bilinear/trilinear/anisotropic filters, low-resolution textures\ncan be blurred, so automatically upscale them with nearest-neighbor\ninterpolation to preserve crisp pixels. This sets the minimum texture size\nfor the upscaled textures; higher values look sharper, but require more\nmemory. Powers of 2 are recommended. Setting this higher than 1 may not\nhave a visible effect unless bilinear/trilinear/anisotropic filtering is\nenabled.\nThis is also used as the base node texture size for world-aligned\ntexture autoscaling."); + gettext("When using bilinear/trilinear/anisotropic filters, low-resolution textures\ncan be blurred, so automatically upscale them with nearest-neighbor\ninterpolation to preserve crisp pixels. This sets the minimum texture size\nfor the upscaled textures; higher values look sharper, but require more\nmemory. Powers of 2 are recommended. This setting is ONLY applies if\nbilinear/trilinear/anisotropic filtering is enabled.\nThis is also used as the base node texture size for world-aligned\ntexture autoscaling."); gettext("FSAA"); gettext("Use multi-sample antialiasing (MSAA) to smooth out block edges.\nThis algorithm smooths out the 3D viewport while keeping the image sharp,\nbut it doesn't affect the insides of textures\n(which is especially noticeable with transparent textures).\nVisible spaces appear between nodes when shaders are disabled.\nIf set to 0, MSAA is disabled.\nA restart is required after changing this option."); gettext("Undersampling"); @@ -261,6 +261,29 @@ fake_function() { gettext("Set to true to enable waving leaves.\nRequires shaders to be enabled."); gettext("Waving plants"); gettext("Set to true to enable waving plants.\nRequires shaders to be enabled."); + gettext("Dynamic shadows"); + gettext("Dynamic shadows"); + gettext("Set to true to enable Shadow Mapping.\nRequires shaders to be enabled."); + gettext("Shadow strength"); + gettext("Set the shadow strength.\nLower value means lighter shadows, higher value means darker shadows."); + gettext("Shadow map max distance in nodes to render shadows"); + gettext("Maximum distance to render shadows."); + gettext("Shadow map texture size"); + gettext("Texture size to render the shadow map on.\nThis must be a power of two.\nBigger numbers create better shadowsbut it is also more expensive."); + gettext("Shadow map texture in 32 bits"); + gettext("Sets shadow texture quality to 32 bits.\nOn false, 16 bits texture will be used.\nThis can cause much more artifacts in the shadow."); + gettext("Poisson filtering"); + gettext("Enable poisson disk filtering.\nOn true uses poisson disk to make \"soft shadows\". Otherwise uses PCF filtering."); + gettext("Shadow filter quality"); + gettext("Define shadow filtering quality\nThis simulates the soft shadows effect by applying a PCF or poisson disk\nbut also uses more resources."); + gettext("Colored shadows"); + gettext("Enable colored shadows. \nOn true translucent nodes cast colored shadows. This is expensive."); + gettext("Map update time"); + gettext("Set the shadow update time.\nLower value means shadows and map updates faster, but it consume more resources.\nMinimun value 0.001 seconds max value 0.2 seconds"); + gettext("Soft shadow radius"); + gettext("Set the soft shadow radius size.\nLower values mean sharper shadows bigger values softer.\nMinimun value 1.0 and max value 10.0"); + gettext("Sky Body Orbit Tilt"); + gettext("Set the tilt of Sun/Moon orbit in degrees\nValue of 0 means no tilt / vertical orbit.\nMinimun value 0.0 and max value 60.0"); gettext("Advanced"); gettext("Arm inertia"); gettext("Arm inertia, gives a more realistic movement of\nthe arm when the camera moves."); @@ -275,15 +298,13 @@ fake_function() { gettext("Near plane"); gettext("Camera 'near clipping plane' distance in nodes, between 0 and 0.25\nOnly works on GLES platforms. Most users will not need to change this.\nIncreasing can reduce artifacting on weaker GPUs.\n0.1 = Default, 0.25 = Good value for weaker tablets."); gettext("Screen width"); - gettext("Width component of the initial window size."); + gettext("Width component of the initial window size. Ignored in fullscreen mode."); gettext("Screen height"); - gettext("Height component of the initial window size."); + gettext("Height component of the initial window size. Ignored in fullscreen mode."); gettext("Autosave screen size"); gettext("Save window size automatically when modified."); gettext("Full screen"); gettext("Fullscreen mode."); - gettext("Full screen BPP"); - gettext("Bits per pixel (aka color depth) in fullscreen mode."); gettext("VSync"); gettext("Vertical screen synchronization."); gettext("Field of view"); @@ -303,7 +324,7 @@ fake_function() { gettext("Texture path"); gettext("Path to texture directory. All textures are first searched from here."); gettext("Video driver"); - gettext("The rendering back-end for Irrlicht.\nA restart is required after changing this.\nNote: On Android, stick with OGLES1 if unsure! App may fail to start otherwise.\nOn other platforms, OpenGL is recommended.\nShaders are supported by OpenGL (desktop only) and OGLES2 (experimental)"); + gettext("The rendering back-end.\nA restart is required after changing this.\nNote: On Android, stick with OGLES1 if unsure! App may fail to start otherwise.\nOn other platforms, OpenGL is recommended.\nShaders are supported by OpenGL (desktop only) and OGLES2 (experimental)"); gettext("Cloud radius"); gettext("Radius of cloud area stated in number of 64 node cloud squares.\nValues larger than 26 will start to produce sharp cutoffs at cloud area corners."); gettext("View bobbing factor"); @@ -407,12 +428,6 @@ fake_function() { gettext("Bold monospace font path"); gettext("Italic monospace font path"); gettext("Bold and italic monospace font path"); - gettext("Fallback font size"); - gettext("Font size of the fallback font in point (pt)."); - gettext("Fallback font shadow"); - gettext("Shadow offset (in pixels) of the fallback font. If 0, then shadow will not be drawn."); - gettext("Fallback font shadow alpha"); - gettext("Opaqueness (alpha) of the shadow behind the fallback font, between 0 and 255."); gettext("Fallback font path"); gettext("Path of the fallback font.\nIf “freetype” setting is enabled: Must be a TrueType font.\nIf “freetype” setting is disabled: Must be a bitmap or XML vectors font.\nThis font will be used for certain languages or if the default font is unavailable."); gettext("Chat font size"); @@ -542,6 +557,8 @@ fake_function() { gettext("If enabled, actions are recorded for rollback.\nThis option is only read when server starts."); gettext("Chat message format"); gettext("Format of player chat messages. The following strings are valid placeholders:\n@name, @message, @timestamp (optional)"); + gettext("Chat command time message threshold"); + gettext("If the execution of a chat command takes longer than this specified time in\nseconds, add the time information to the chat command message"); gettext("Shutdown message"); gettext("A message to be displayed to all clients when the server shuts down."); gettext("Crash message"); @@ -679,14 +696,12 @@ fake_function() { gettext("IPv6"); gettext("Enable IPv6 support (for both client and server).\nRequired for IPv6 connections to work at all."); gettext("Advanced"); - gettext("cURL timeout"); - gettext("Default timeout for cURL, stated in milliseconds.\nOnly has an effect if compiled with cURL."); + gettext("cURL interactive timeout"); + gettext("Maximum time an interactive request (e.g. server list fetch) may take, stated in milliseconds."); gettext("cURL parallel limit"); gettext("Limits number of parallel HTTP requests. Affects:\n- Media fetch if server uses remote_media setting.\n- Serverlist download and server announcement.\n- Downloads performed by main menu (e.g. mod manager).\nOnly has an effect if compiled with cURL."); gettext("cURL file download timeout"); - gettext("Maximum time in ms a file download (e.g. a mod download) may take."); - gettext("High-precision FPU"); - gettext("Makes DirectX work with LuaJIT. Disable if it causes troubles."); + gettext("Maximum time a file download (e.g. a mod download) may take, stated in milliseconds."); gettext("Main menu script"); gettext("Replaces the default main menu with a custom one."); gettext("Engine profiling data print interval"); -- cgit v1.2.3 From 1805775f3d54043c3b1e75e47b9b85e3b12bab00 Mon Sep 17 00:00:00 2001 From: pecksin <78765996+pecksin@users.noreply.github.com> Date: Sun, 20 Jun 2021 11:20:24 -0400 Subject: Make chat web links clickable (#11092) If enabled in minetest.conf, provides colored, clickable (middle-mouse or ctrl-left-mouse) weblinks in chat output, to open the OS' default web browser. --- builtin/settingtypes.txt | 6 ++ minetest.conf.example | 8 +++ po/minetest.pot | 9 +++ src/chat.cpp | 133 +++++++++++++++++++++++++++++++++++---------- src/chat.h | 8 +++ src/defaultsettings.cpp | 2 + src/gui/guiChatConsole.cpp | 98 ++++++++++++++++++++++++++++++++- src/gui/guiChatConsole.h | 8 +++ 8 files changed, 242 insertions(+), 30 deletions(-) (limited to 'src') diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 57857cabb..fd7d8b9b9 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -973,6 +973,12 @@ mute_sound (Mute sound) bool false [Client] +# Clickable weblinks (middle-click or ctrl-left-click) enabled in chat console output. +clickable_chat_weblinks (Chat weblinks) bool false + +# Optional override for chat weblink color. +chat_weblink_color (Weblink color) string + [*Network] # Address to connect to. diff --git a/minetest.conf.example b/minetest.conf.example index 718cb0c75..b252f4f70 100644 --- a/minetest.conf.example +++ b/minetest.conf.example @@ -1155,6 +1155,14 @@ # Client # +# If enabled, http links in chat can be middle-clicked or ctrl-left-clicked to open the link in the OS's default web browser. +# type: bool +# clickable_chat_weblinks = false + +# If clickable_chat_weblinks is enabled, specify the color (as 24-bit hexadecimal) of weblinks in chat. +# type: string +# chat_weblink_color = #8888FF + ## Network # Address to connect to. diff --git a/po/minetest.pot b/po/minetest.pot index 4ed1e2434..53b706f5f 100644 --- a/po/minetest.pot +++ b/po/minetest.pot @@ -6551,3 +6551,12 @@ msgid "" "be queued.\n" "This should be lower than curl_parallel_limit." msgstr "" + +#: src/gui/guiChatConsole.cpp +msgid "Opening webpage" +msgstr "" + +#: src/gui/guiChatConsole.cpp +msgid "Failed to open webpage" +msgstr "" + diff --git a/src/chat.cpp b/src/chat.cpp index c9317a079..e44d73ac0 100644 --- a/src/chat.cpp +++ b/src/chat.cpp @@ -35,6 +35,17 @@ ChatBuffer::ChatBuffer(u32 scrollback): if (m_scrollback == 0) m_scrollback = 1; m_empty_formatted_line.first = true; + + m_cache_clickable_chat_weblinks = false; + // Curses mode cannot access g_settings here + if (g_settings != nullptr) { + m_cache_clickable_chat_weblinks = g_settings->getBool("clickable_chat_weblinks"); + if (m_cache_clickable_chat_weblinks) { + std::string colorval = g_settings->get("chat_weblink_color"); + parseColorString(colorval, m_cache_chat_weblink_color, false, 255); + m_cache_chat_weblink_color.setAlpha(255); + } + } } void ChatBuffer::addLine(const std::wstring &name, const std::wstring &text) @@ -263,78 +274,144 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols, //EnrichedString line_text(line.text); next_line.first = true; - bool text_processing = false; + // Set/use forced newline after the last frag in each line + bool mark_newline = false; // Produce fragments and layout them into lines - while (!next_frags.empty() || in_pos < line.text.size()) - { + while (!next_frags.empty() || in_pos < line.text.size()) { + mark_newline = false; // now using this to USE line-end frag + // Layout fragments into lines - while (!next_frags.empty()) - { + while (!next_frags.empty()) { ChatFormattedFragment& frag = next_frags[0]; - if (frag.text.size() <= cols - out_column) - { + + // Force newline after this frag, if marked + if (frag.column == INT_MAX) + mark_newline = true; + + if (frag.text.size() <= cols - out_column) { // Fragment fits into current line frag.column = out_column; next_line.fragments.push_back(frag); out_column += frag.text.size(); next_frags.erase(next_frags.begin()); - } - else - { + } else { // Fragment does not fit into current line // So split it up temp_frag.text = frag.text.substr(0, cols - out_column); temp_frag.column = out_column; - //temp_frag.bold = frag.bold; + temp_frag.weblink = frag.weblink; + next_line.fragments.push_back(temp_frag); frag.text = frag.text.substr(cols - out_column); + frag.column = 0; out_column = cols; } - if (out_column == cols || text_processing) - { + + if (out_column == cols || mark_newline) { // End the current line destination.push_back(next_line); num_added++; next_line.fragments.clear(); next_line.first = false; - out_column = text_processing ? hanging_indentation : 0; + out_column = hanging_indentation; + mark_newline = false; } } - // Produce fragment - if (in_pos < line.text.size()) - { - u32 remaining_in_input = line.text.size() - in_pos; - u32 remaining_in_output = cols - out_column; + // Produce fragment(s) for next formatted line + if (!(in_pos < line.text.size())) + continue; + const std::wstring &linestring = line.text.getString(); + u32 remaining_in_output = cols - out_column; + size_t http_pos = std::wstring::npos; + mark_newline = false; // now using this to SET line-end frag + + // Construct all frags for next output line + while (!mark_newline) { // Determine a fragment length <= the minimum of // remaining_in_{in,out}put. Try to end the fragment // on a word boundary. - u32 frag_length = 1, space_pos = 0; + u32 frag_length = 0, space_pos = 0; + u32 remaining_in_input = line.text.size() - in_pos; + + if (m_cache_clickable_chat_weblinks) { + // Note: unsigned(-1) on fail + http_pos = linestring.find(L"https://", in_pos); + if (http_pos == std::wstring::npos) + http_pos = linestring.find(L"http://", in_pos); + if (http_pos != std::wstring::npos) + http_pos -= in_pos; + } + while (frag_length < remaining_in_input && - frag_length < remaining_in_output) - { - if (iswspace(line.text.getString()[in_pos + frag_length])) + frag_length < remaining_in_output) { + if (iswspace(linestring[in_pos + frag_length])) space_pos = frag_length; ++frag_length; } + + if (http_pos >= remaining_in_output) { + // Http not in range, grab until space or EOL, halt as normal. + // Note this works because (http_pos = npos) is unsigned(-1) + + mark_newline = true; + } else if (http_pos == 0) { + // At http, grab ALL until FIRST whitespace or end marker. loop. + // If at end of string, next loop will be empty string to mark end of weblink. + + frag_length = 6; // Frag is at least "http://" + + // Chars to mark end of weblink + // TODO? replace this with a safer (slower) regex whitelist? + static const std::wstring delim_chars = L"\'\");,"; + wchar_t tempchar = linestring[in_pos+frag_length]; + while (frag_length < remaining_in_input && + !iswspace(tempchar) && + delim_chars.find(tempchar) == std::wstring::npos) { + ++frag_length; + tempchar = linestring[in_pos+frag_length]; + } + + space_pos = frag_length - 1; + // This frag may need to be force-split. That's ok, urls aren't "words" + if (frag_length >= remaining_in_output) { + mark_newline = true; + } + } else { + // Http in range, grab until http, loop + + space_pos = http_pos - 1; + frag_length = http_pos; + } + + // Include trailing space in current frag if (space_pos != 0 && frag_length < remaining_in_input) frag_length = space_pos + 1; temp_frag.text = line.text.substr(in_pos, frag_length); - temp_frag.column = 0; - //temp_frag.bold = 0; + // A hack so this frag remembers mark_newline for the layout phase + temp_frag.column = mark_newline ? INT_MAX : 0; + + if (http_pos == 0) { + // Discard color stuff from the source frag + temp_frag.text = EnrichedString(temp_frag.text.getString()); + temp_frag.text.setDefaultColor(m_cache_chat_weblink_color); + // Set weblink in the frag meta + temp_frag.weblink = wide_to_utf8(temp_frag.text.getString()); + } else { + temp_frag.weblink.clear(); + } next_frags.push_back(temp_frag); in_pos += frag_length; - text_processing = true; + remaining_in_output -= std::min(frag_length, remaining_in_output); } } // End the last line - if (num_added == 0 || !next_line.fragments.empty()) - { + if (num_added == 0 || !next_line.fragments.empty()) { destination.push_back(next_line); num_added++; } diff --git a/src/chat.h b/src/chat.h index 0b98e4d3c..aabb0821e 100644 --- a/src/chat.h +++ b/src/chat.h @@ -57,6 +57,8 @@ struct ChatFormattedFragment EnrichedString text; // starting column u32 column; + // web link is empty for most frags + std::string weblink; // formatting //u8 bold:1; }; @@ -118,6 +120,7 @@ public: std::vector& destination) const; void resize(u32 scrollback); + protected: s32 getTopScrollPos() const; s32 getBottomScrollPos() const; @@ -138,6 +141,11 @@ private: std::vector m_formatted; // Empty formatted line, for error returns ChatFormattedLine m_empty_formatted_line; + + // Enable clickable chat weblinks + bool m_cache_clickable_chat_weblinks; + // Color of clickable chat weblinks + irr::video::SColor m_cache_chat_weblink_color; }; class ChatPrompt diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 0895bf898..6791fccf5 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -65,6 +65,8 @@ void set_default_settings() settings->setDefault("max_out_chat_queue_size", "20"); settings->setDefault("pause_on_lost_focus", "false"); settings->setDefault("enable_register_confirmation", "true"); + settings->setDefault("clickable_chat_weblinks", "false"); + settings->setDefault("chat_weblink_color", "#8888FF"); // Keymap settings->setDefault("remote_port", "30000"); diff --git a/src/gui/guiChatConsole.cpp b/src/gui/guiChatConsole.cpp index baaaea5e8..85617d862 100644 --- a/src/gui/guiChatConsole.cpp +++ b/src/gui/guiChatConsole.cpp @@ -41,6 +41,10 @@ inline u32 clamp_u8(s32 value) return (u32) MYMIN(MYMAX(value, 0), 255); } +inline bool isInCtrlKeys(const irr::EKEY_CODE& kc) +{ + return kc == KEY_LCONTROL || kc == KEY_RCONTROL || kc == KEY_CONTROL; +} GUIChatConsole::GUIChatConsole( gui::IGUIEnvironment* env, @@ -91,6 +95,10 @@ GUIChatConsole::GUIChatConsole( // set default cursor options setCursor(true, true, 2.0, 0.1); + + // track ctrl keys for mouse event + m_is_ctrl_down = false; + m_cache_clickable_chat_weblinks = g_settings->getBool("clickable_chat_weblinks"); } GUIChatConsole::~GUIChatConsole() @@ -405,8 +413,21 @@ bool GUIChatConsole::OnEvent(const SEvent& event) ChatPrompt &prompt = m_chat_backend->getPrompt(); - if(event.EventType == EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown) + if (event.EventType == EET_KEY_INPUT_EVENT && !event.KeyInput.PressedDown) + { + // CTRL up + if (isInCtrlKeys(event.KeyInput.Key)) + { + m_is_ctrl_down = false; + } + } + else if(event.EventType == EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown) { + // CTRL down + if (isInCtrlKeys(event.KeyInput.Key)) { + m_is_ctrl_down = true; + } + // Key input if (KeyPress(event.KeyInput) == getKeySetting("keymap_console")) { closeConsole(); @@ -613,11 +634,24 @@ bool GUIChatConsole::OnEvent(const SEvent& event) } else if(event.EventType == EET_MOUSE_INPUT_EVENT) { - if(event.MouseInput.Event == EMIE_MOUSE_WHEEL) + if (event.MouseInput.Event == EMIE_MOUSE_WHEEL) { s32 rows = myround(-3.0 * event.MouseInput.Wheel); m_chat_backend->scroll(rows); } + // Middle click or ctrl-click opens weblink, if enabled in config + else if(m_cache_clickable_chat_weblinks && ( + event.MouseInput.Event == EMIE_MMOUSE_PRESSED_DOWN || + (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN && m_is_ctrl_down) + )) + { + // If clicked within console output region + if (event.MouseInput.Y / m_fontsize.Y < (m_height / m_fontsize.Y) - 1 ) + { + // Translate pixel position to font position + middleClick(event.MouseInput.X / m_fontsize.X, event.MouseInput.Y / m_fontsize.Y); + } + } } #if (IRRLICHT_VERSION_MT_REVISION >= 2) else if(event.EventType == EET_STRING_INPUT_EVENT) @@ -640,3 +674,63 @@ void GUIChatConsole::setVisible(bool visible) } } +void GUIChatConsole::middleClick(s32 col, s32 row) +{ + // Prevent accidental rapid clicking + static u64 s_oldtime = 0; + u64 newtime = porting::getTimeMs(); + + // 0.6 seconds should suffice + if (newtime - s_oldtime < 600) + return; + s_oldtime = newtime; + + const std::vector & + frags = m_chat_backend->getConsoleBuffer().getFormattedLine(row).fragments; + std::string weblink = ""; // from frag meta + + // Identify targetted fragment, if exists + int indx = frags.size() - 1; + if (indx < 0) { + // Invalid row, frags is empty + return; + } + // Scan from right to left, offset by 1 font space because left margin + while (indx > -1 && (u32)col < frags[indx].column + 1) { + --indx; + } + if (indx > -1) { + weblink = frags[indx].weblink; + // Note if(indx < 0) then a frag somehow had a corrupt column field + } + + /* + // Debug help. Please keep this in case adjustments are made later. + std::string ws; + ws = "Middleclick: (" + std::to_string(col) + ',' + std::to_string(row) + ')' + " frags:"; + // show all frags () for the clicked row + for (u32 i=0;iaddUnparsedMessage(utf8_to_wide(msg.str())); + } +} diff --git a/src/gui/guiChatConsole.h b/src/gui/guiChatConsole.h index 1152f2b2d..32628f0d8 100644 --- a/src/gui/guiChatConsole.h +++ b/src/gui/guiChatConsole.h @@ -84,6 +84,9 @@ private: void drawText(); void drawPrompt(); + // If clicked fragment has a web url, send it to the system default web browser + void middleClick(s32 col, s32 row); + private: ChatBackend* m_chat_backend; Client* m_client; @@ -126,4 +129,9 @@ private: // font gui::IGUIFont *m_font = nullptr; v2u32 m_fontsize; + + // Enable clickable chat weblinks + bool m_cache_clickable_chat_weblinks; + // Track if a ctrl key is currently held down + bool m_is_ctrl_down; }; -- cgit v1.2.3 From b10091be9b6b6c74a170b9444f856f83dd3fe952 Mon Sep 17 00:00:00 2001 From: sfence Date: Sun, 20 Jun 2021 17:21:35 +0200 Subject: Add min_y and max_y checks for Active Block Modifiers (ABM) (#11333) This check can be used by ABM to reduce CPU usage. --- builtin/game/features.lua | 1 + doc/lua_api.txt | 7 +++++++ src/script/cpp_api/s_env.cpp | 8 +++++++- src/script/lua_api/l_env.h | 16 ++++++++++++++-- src/serverenvironment.cpp | 8 ++++++++ src/serverenvironment.h | 4 ++++ 6 files changed, 41 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/builtin/game/features.lua b/builtin/game/features.lua index 8f0604448..b50a05989 100644 --- a/builtin/game/features.lua +++ b/builtin/game/features.lua @@ -20,6 +20,7 @@ core.features = { direct_velocity_on_players = true, use_texture_alpha_string_modes = true, degrotate_240_steps = true, + abm_min_max_y = true, } function core.has_feature(arg) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 0c81ca911..ded416333 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -4484,6 +4484,8 @@ Utilities -- degrotate param2 rotates in units of 1.5° instead of 2° -- thus changing the range of values from 0-179 to 0-240 (5.5.0) degrotate_240_steps = true, + -- ABM supports min_y and max_y fields in definition (5.5.0) + abm_min_max_y = true, } * `minetest.has_feature(arg)`: returns `boolean, missing_features` @@ -7187,6 +7189,11 @@ Used by `minetest.register_abm`. chance = 1, -- Chance of triggering `action` per-node per-interval is 1.0 / this -- value + + min_y = -32768, + max_y = 32767, + -- min and max height levels where ABM will be processed + -- can be used to reduce CPU usage catch_up = true, -- If true, catch-up behaviour is enabled: The `chance` value is diff --git a/src/script/cpp_api/s_env.cpp b/src/script/cpp_api/s_env.cpp index 8da5debaa..c4a39a869 100644 --- a/src/script/cpp_api/s_env.cpp +++ b/src/script/cpp_api/s_env.cpp @@ -150,13 +150,19 @@ 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); lua_getfield(L, current_abm, "action"); luaL_checktype(L, current_abm + 1, LUA_TFUNCTION); lua_pop(L, 1); LuaABM *abm = new LuaABM(L, id, trigger_contents, required_neighbors, - trigger_interval, trigger_chance, simple_catch_up); + trigger_interval, trigger_chance, simple_catch_up, min_y, max_y); env->addActiveBlockModifier(abm); diff --git a/src/script/lua_api/l_env.h b/src/script/lua_api/l_env.h index 979b13c40..67c76faae 100644 --- a/src/script/lua_api/l_env.h +++ b/src/script/lua_api/l_env.h @@ -223,17 +223,21 @@ private: float m_trigger_interval; u32 m_trigger_chance; bool m_simple_catch_up; + s16 m_min_y; + s16 m_max_y; public: LuaABM(lua_State *L, int id, const std::vector &trigger_contents, const std::vector &required_neighbors, - float trigger_interval, u32 trigger_chance, bool simple_catch_up): + float trigger_interval, u32 trigger_chance, bool simple_catch_up, s16 min_y, s16 max_y): m_id(id), m_trigger_contents(trigger_contents), m_required_neighbors(required_neighbors), m_trigger_interval(trigger_interval), m_trigger_chance(trigger_chance), - m_simple_catch_up(simple_catch_up) + m_simple_catch_up(simple_catch_up), + m_min_y(min_y), + m_max_y(max_y) { } virtual const std::vector &getTriggerContents() const @@ -256,6 +260,14 @@ public: { return m_simple_catch_up; } + virtual s16 getMinY() + { + return m_min_y; + } + virtual s16 getMaxY() + { + return m_max_y; + } virtual void trigger(ServerEnvironment *env, v3s16 p, MapNode n, u32 active_object_count, u32 active_object_count_wider); }; diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index 413a785e6..f3711652c 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -729,6 +729,8 @@ struct ActiveABM int chance; std::vector required_neighbors; bool check_required_neighbors; // false if required_neighbors is known to be empty + s16 min_y; + s16 max_y; }; class ABMHandler @@ -773,6 +775,9 @@ public: } else { aabm.chance = chance; } + // y limits + aabm.min_y = abm->getMinY(); + aabm.max_y = abm->getMaxY(); // Trigger neighbors const std::vector &required_neighbors_s = @@ -885,6 +890,9 @@ public: v3s16 p = p0 + block->getPosRelative(); 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/serverenvironment.h b/src/serverenvironment.h index c5ca463ee..8733c2dd2 100644 --- a/src/serverenvironment.h +++ b/src/serverenvironment.h @@ -67,6 +67,10 @@ public: virtual u32 getTriggerChance() = 0; // Whether to modify chance to simulate time lost by an unnattended block virtual bool getSimpleCatchUp() = 0; + // get min Y for apply abm + virtual s16 getMinY() = 0; + // get max Y for apply abm + virtual s16 getMaxY() = 0; // This is called usually at interval for 1/chance of the nodes virtual void trigger(ServerEnvironment *env, v3s16 p, MapNode n){}; virtual void trigger(ServerEnvironment *env, v3s16 p, MapNode n, -- cgit v1.2.3 From 2db6b07de1e45c56f6135363939dbb3781336514 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Sun, 20 Jun 2021 17:21:50 +0200 Subject: Inventory: show error on invalid list names (#11368) --- src/inventory.h | 1 + src/script/common/c_content.cpp | 26 ++++++++++++++------------ 2 files changed, 15 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/inventory.h b/src/inventory.h index f36bc57cf..fbf995fab 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -298,6 +298,7 @@ public: void serialize(std::ostream &os, bool incremental = false) const; void deSerialize(std::istream &is); + // Adds a new list or clears and resizes an existing one InventoryList * addList(const std::string &name, u32 size); InventoryList * getList(const std::string &name); const InventoryList * getList(const std::string &name) const; diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 52baeae9d..f8cc40927 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -1350,26 +1350,28 @@ void read_inventory_list(lua_State *L, int tableindex, { if(tableindex < 0) tableindex = lua_gettop(L) + 1 + tableindex; + // If nil, delete list if(lua_isnil(L, tableindex)){ inv->deleteList(name); return; } - // Otherwise set list + + // Get Lua-specified items to insert into the list std::vector items = read_items(L, tableindex,srv); - int listsize = (forcesize != -1) ? forcesize : items.size(); + size_t listsize = (forcesize > 0) ? forcesize : items.size(); + + // Create or clear list InventoryList *invlist = inv->addList(name, listsize); - int index = 0; - for(std::vector::const_iterator - i = items.begin(); i != items.end(); ++i){ - if(forcesize != -1 && index == forcesize) - break; - invlist->changeItem(index, *i); - index++; + if (!invlist) { + luaL_error(L, "inventory list: cannot create list named '%s'", name); + return; } - while(forcesize != -1 && index < forcesize){ - invlist->deleteItem(index); - index++; + + for (size_t i = 0; i < items.size(); ++i) { + if (i == listsize) + break; // Truncate provided list of items + invlist->changeItem(i, items[i]); } } -- cgit v1.2.3 From a8b7c8ff38d305f1cde045564779e287adfca98b Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Mon, 21 Jun 2021 19:04:25 +0200 Subject: Server: Ignore whitespace-only chat messages --- src/server.cpp | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src') diff --git a/src/server.cpp b/src/server.cpp index a8d452783..c47596a97 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -3001,6 +3001,9 @@ std::wstring Server::handleChat(const std::string &name, } auto message = trim(wide_to_utf8(wmessage)); + if (message.empty()) + return L""; + if (message.find_first_of("\n\r") != std::wstring::npos) { return L"Newlines are not permitted in chat messages"; } -- cgit v1.2.3 From c60a146e2291f7a55a3e5fd0447bd393b063ab1c Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 23 Jun 2021 15:22:31 +0200 Subject: Rework Settings to support arbitrary hierarchies (#11352) --- src/httpfetch.cpp | 10 +-- src/main.cpp | 21 ++++-- src/map_settings_manager.cpp | 35 +++++----- src/map_settings_manager.h | 6 +- src/settings.cpp | 106 ++++++++++++++++++++--------- src/settings.h | 44 ++++++++++-- src/unittest/test_map_settings_manager.cpp | 5 ++ 7 files changed, 160 insertions(+), 67 deletions(-) (limited to 'src') diff --git a/src/httpfetch.cpp b/src/httpfetch.cpp index 6137782ff..1307bfec3 100644 --- a/src/httpfetch.cpp +++ b/src/httpfetch.cpp @@ -761,10 +761,12 @@ void httpfetch_cleanup() { verbosestream<<"httpfetch_cleanup: cleaning up"<stop(); - g_httpfetch_thread->requestWakeUp(); - g_httpfetch_thread->wait(); - delete g_httpfetch_thread; + if (g_httpfetch_thread) { + g_httpfetch_thread->stop(); + g_httpfetch_thread->requestWakeUp(); + g_httpfetch_thread->wait(); + delete g_httpfetch_thread; + } curl_global_cleanup(); } diff --git a/src/main.cpp b/src/main.cpp index 4a69f83b5..ffbdb7b5b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -91,6 +91,7 @@ static void list_worlds(bool print_name, bool print_path); static bool setup_log_params(const Settings &cmd_args); static bool create_userdata_path(); static bool init_common(const Settings &cmd_args, int argc, char *argv[]); +static void uninit_common(); static void startup_message(); static bool read_config_file(const Settings &cmd_args); static void init_log_streams(const Settings &cmd_args); @@ -201,6 +202,7 @@ int main(int argc, char *argv[]) errorstream << "Unittest support is not enabled in this binary. " << "If you want to enable it, compile project with BUILD_UNITTESTS=1 flag." << std::endl; + return 1; #endif } #endif @@ -236,9 +238,6 @@ int main(int argc, char *argv[]) print_modified_quicktune_values(); - // Stop httpfetch thread (if started) - httpfetch_cleanup(); - END_DEBUG_EXCEPTION_HANDLER return retval; @@ -486,13 +485,14 @@ static bool init_common(const Settings &cmd_args, int argc, char *argv[]) startup_message(); set_default_settings(); - // Initialize sockets sockets_init(); - atexit(sockets_cleanup); // Initialize g_settings Settings::createLayer(SL_GLOBAL); + // Set cleanup callback(s) to run at process exit + atexit(uninit_common); + if (!read_config_file(cmd_args)) return false; @@ -511,6 +511,17 @@ static bool init_common(const Settings &cmd_args, int argc, char *argv[]) return true; } +static void uninit_common() +{ + httpfetch_cleanup(); + + sockets_cleanup(); + + // It'd actually be okay to leak these but we want to please valgrind... + for (int i = 0; i < (int)SL_TOTAL_COUNT; i++) + delete Settings::getLayer((SettingsLayer)i); +} + static void startup_message() { infostream << PROJECT_NAME << " " << _("with") diff --git a/src/map_settings_manager.cpp b/src/map_settings_manager.cpp index 99e3cb0e6..7e86a9937 100644 --- a/src/map_settings_manager.cpp +++ b/src/map_settings_manager.cpp @@ -26,15 +26,24 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "map_settings_manager.h" MapSettingsManager::MapSettingsManager(const std::string &map_meta_path): - m_map_meta_path(map_meta_path) + m_map_meta_path(map_meta_path), + m_hierarchy(g_settings) { - m_map_settings = Settings::createLayer(SL_MAP, "[end_of_params]"); - Mapgen::setDefaultSettings(Settings::getLayer(SL_DEFAULTS)); + /* + * We build our own hierarchy which falls back to the global one. + * It looks as follows: (lowest prio first) + * 0: whatever is picked up from g_settings (incl. engine defaults) + * 1: defaults set by scripts (override_meta = false) + * 2: settings present in map_meta.txt or overriden by scripts + */ + m_defaults = new Settings("", &m_hierarchy, 1); + m_map_settings = new Settings("[end_of_params]", &m_hierarchy, 2); } MapSettingsManager::~MapSettingsManager() { + delete m_defaults; delete m_map_settings; delete mapgen_params; } @@ -43,14 +52,13 @@ MapSettingsManager::~MapSettingsManager() bool MapSettingsManager::getMapSetting( const std::string &name, std::string *value_out) { - // Get from map_meta.txt, then try from all other sources + // Try getting it normally first if (m_map_settings->getNoEx(name, *value_out)) return true; - // Compatibility kludge + // 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; } @@ -72,7 +80,7 @@ bool MapSettingsManager::setMapSetting( if (override_meta) m_map_settings->set(name, value); else - Settings::getLayer(SL_GLOBAL)->set(name, value); + m_defaults->set(name, value); return true; } @@ -87,7 +95,7 @@ bool MapSettingsManager::setMapSettingNoiseParams( if (override_meta) m_map_settings->setNoiseParams(name, *value); else - Settings::getLayer(SL_GLOBAL)->setNoiseParams(name, *value); + m_defaults->setNoiseParams(name, *value); return true; } @@ -146,15 +154,8 @@ MapgenParams *MapSettingsManager::makeMapgenParams() if (mapgen_params) return mapgen_params; - assert(m_map_settings != NULL); - - // At this point, we have (in order of precedence): - // 1). SL_MAP containing map_meta.txt settings or - // explicit overrides from scripts - // 2). SL_GLOBAL containing all user-specified config file - // settings - // 3). SL_DEFAULTS containing any low-priority settings from - // scripts, e.g. mods using Lua as an enhanced config file) + assert(m_map_settings); + assert(m_defaults); // Now, get the mapgen type so we can create the appropriate MapgenParams std::string mg_name; diff --git a/src/map_settings_manager.h b/src/map_settings_manager.h index 9258d3032..fa271268d 100644 --- a/src/map_settings_manager.h +++ b/src/map_settings_manager.h @@ -20,8 +20,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include +#include "settings.h" -class Settings; struct NoiseParams; struct MapgenParams; @@ -70,6 +70,8 @@ public: private: std::string m_map_meta_path; - // TODO: Rename to "m_settings" + + SettingsHierarchy m_hierarchy; + Settings *m_defaults; Settings *m_map_settings; }; diff --git a/src/settings.cpp b/src/settings.cpp index cff393e5f..0a9424994 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -33,35 +33,90 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include -Settings *g_settings = nullptr; // Populated in main() +Settings *g_settings = nullptr; +static SettingsHierarchy g_hierarchy; std::string g_settings_path; -Settings *Settings::s_layers[SL_TOTAL_COUNT] = {0}; // Zeroed by compiler std::unordered_map Settings::s_flags; +/* Settings hierarchy implementation */ -Settings *Settings::createLayer(SettingsLayer sl, const std::string &end_tag) +SettingsHierarchy::SettingsHierarchy(Settings *fallback) +{ + layers.push_back(fallback); +} + + +Settings *SettingsHierarchy::getLayer(int layer) const { - if ((int)sl < 0 || sl >= SL_TOTAL_COUNT) + if (layer < 0 || layer >= layers.size()) throw BaseException("Invalid settings layer"); + return layers[layer]; +} + - Settings *&pos = s_layers[(size_t)sl]; +Settings *SettingsHierarchy::getParent(int layer) const +{ + assert(layer >= 0 && layer < layers.size()); + // iterate towards the origin (0) to find the next fallback layer + for (int i = layer - 1; i >= 0; --i) { + if (layers[i]) + return layers[i]; + } + + return nullptr; +} + + +void SettingsHierarchy::onLayerCreated(int layer, Settings *obj) +{ + if (layer < 0) + throw BaseException("Invalid settings layer"); + if (layers.size() < layer+1) + layers.resize(layer+1); + + Settings *&pos = layers[layer]; if (pos) - throw BaseException("Setting layer " + std::to_string(sl) + " already exists"); + throw BaseException("Setting layer " + itos(layer) + " already exists"); + + pos = obj; + // This feels bad + if (this == &g_hierarchy && layer == (int)SL_GLOBAL) + g_settings = obj; +} + + +void SettingsHierarchy::onLayerRemoved(int layer) +{ + assert(layer >= 0 && layer < layers.size()); + layers[layer] = nullptr; + if (this == &g_hierarchy && layer == (int)SL_GLOBAL) + g_settings = nullptr; +} - pos = new Settings(end_tag); - pos->m_settingslayer = sl; +/* Settings implementation */ - if (sl == SL_GLOBAL) - g_settings = pos; - return pos; +Settings *Settings::createLayer(SettingsLayer sl, const std::string &end_tag) +{ + return new Settings(end_tag, &g_hierarchy, (int)sl); } Settings *Settings::getLayer(SettingsLayer sl) { sanity_check((int)sl >= 0 && sl < SL_TOTAL_COUNT); - return s_layers[(size_t)sl]; + return g_hierarchy.layers[(int)sl]; +} + + +Settings::Settings(const std::string &end_tag, SettingsHierarchy *h, + int settings_layer) : + m_end_tag(end_tag), + m_hierarchy(h), + m_settingslayer(settings_layer) +{ + if (m_hierarchy) + m_hierarchy->onLayerCreated(m_settingslayer, this); } @@ -69,12 +124,8 @@ Settings::~Settings() { MutexAutoLock lock(m_mutex); - if (m_settingslayer < SL_TOTAL_COUNT) - s_layers[(size_t)m_settingslayer] = nullptr; - - // Compatibility - if (m_settingslayer == SL_GLOBAL) - g_settings = nullptr; + if (m_hierarchy) + m_hierarchy->onLayerRemoved(m_settingslayer); clearNoLock(); } @@ -86,8 +137,8 @@ Settings & Settings::operator = (const Settings &other) return *this; // TODO: Avoid copying Settings objects. Make this private. - FATAL_ERROR_IF(m_settingslayer != SL_TOTAL_COUNT && other.m_settingslayer != SL_TOTAL_COUNT, - ("Tried to copy unique Setting layer " + std::to_string(m_settingslayer)).c_str()); + FATAL_ERROR_IF(m_hierarchy || other.m_hierarchy, + "Cannot copy or overwrite Settings object that belongs to a hierarchy"); MutexAutoLock lock(m_mutex); MutexAutoLock lock2(other.m_mutex); @@ -410,18 +461,7 @@ bool Settings::parseCommandLine(int argc, char *argv[], Settings *Settings::getParent() const { - // If the Settings object is within the hierarchy structure, - // iterate towards the origin (0) to find the next fallback layer - if (m_settingslayer >= SL_TOTAL_COUNT) - return nullptr; - - for (int i = (int)m_settingslayer - 1; i >= 0; --i) { - if (s_layers[i]) - return s_layers[i]; - } - - // No parent - return nullptr; + return m_hierarchy ? m_hierarchy->getParent(m_settingslayer) : nullptr; } @@ -823,6 +863,8 @@ bool Settings::set(const std::string &name, const std::string &value) // TODO: Remove this function bool Settings::setDefault(const std::string &name, const std::string &value) { + FATAL_ERROR_IF(m_hierarchy != &g_hierarchy, "setDefault is only valid on " + "global settings"); return getLayer(SL_DEFAULTS)->set(name, value); } diff --git a/src/settings.h b/src/settings.h index e22d949d3..7791413b9 100644 --- a/src/settings.h +++ b/src/settings.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_bloated.h" #include "util/string.h" +#include "util/basic_macros.h" #include #include #include @@ -60,14 +61,36 @@ enum SettingsParseEvent { SPE_MULTILINE, }; +// Describes the global setting layers, SL_GLOBAL is where settings are read from enum SettingsLayer { SL_DEFAULTS, SL_GAME, SL_GLOBAL, - SL_MAP, SL_TOTAL_COUNT }; +// Implements the hierarchy a settings object may be part of +class SettingsHierarchy { +public: + /* + * A settings object that may be part of another hierarchy can + * occupy the index 0 as a fallback. If not set you can use 0 on your own. + */ + SettingsHierarchy(Settings *fallback = nullptr); + + DISABLE_CLASS_COPY(SettingsHierarchy) + + Settings *getLayer(int layer) const; + +private: + friend class Settings; + Settings *getParent(int layer) const; + void onLayerCreated(int layer, Settings *obj); + void onLayerRemoved(int layer); + + std::vector layers; +}; + struct ValueSpec { ValueSpec(ValueType a_type, const char *a_help=NULL) { @@ -100,13 +123,15 @@ typedef std::unordered_map SettingEntries; class Settings { public: + /* These functions operate on the global hierarchy! */ static Settings *createLayer(SettingsLayer sl, const std::string &end_tag = ""); static Settings *getLayer(SettingsLayer sl); - SettingsLayer getLayerType() const { return m_settingslayer; } + /**/ Settings(const std::string &end_tag = "") : m_end_tag(end_tag) {} + Settings(const std::string &end_tag, SettingsHierarchy *h, int settings_layer); ~Settings(); Settings & operator += (const Settings &other); @@ -200,9 +225,9 @@ public: // remove a setting bool remove(const std::string &name); - /************** - * Miscellany * - **************/ + /***************** + * Miscellaneous * + *****************/ void setDefault(const std::string &name, const FlagDesc *flagdesc, u32 flags); const FlagDesc *getFlagDescFallback(const std::string &name) const; @@ -214,6 +239,10 @@ public: void removeSecureSettings(); + // Returns the settings layer this object is. + // If within the global hierarchy you can cast this to enum SettingsLayer + inline int getLayer() const { return m_settingslayer; } + private: /*********************** * Reading and writing * @@ -257,7 +286,8 @@ private: // All methods that access m_settings/m_defaults directly should lock this. mutable std::mutex m_mutex; - static Settings *s_layers[SL_TOTAL_COUNT]; - SettingsLayer m_settingslayer = SL_TOTAL_COUNT; + SettingsHierarchy *m_hierarchy = nullptr; + int m_settingslayer = -1; + static std::unordered_map s_flags; }; diff --git a/src/unittest/test_map_settings_manager.cpp b/src/unittest/test_map_settings_manager.cpp index 81ca68705..17c31fe79 100644 --- a/src/unittest/test_map_settings_manager.cpp +++ b/src/unittest/test_map_settings_manager.cpp @@ -148,6 +148,11 @@ void TestMapSettingsManager::testMapSettingsManager() check_noise_params(&dummy, &script_np_factor); } + // The settings manager MUST leave user settings alone + mgr.setMapSetting("testname", "1"); + mgr.setMapSetting("testname", "1", true); + UASSERT(!Settings::getLayer(SL_GLOBAL)->exists("testname")); + // Now make our Params and see if the values are correctly sourced MapgenParams *params = mgr.makeMapgenParams(); UASSERT(params->mgtype == MAPGEN_V5); -- cgit v1.2.3 From 51bf4a6e26f9eca461ae88181b06b517afc4d656 Mon Sep 17 00:00:00 2001 From: Wuzzy Date: Wed, 23 Jun 2021 16:35:50 +0000 Subject: Perform some quality assurance for translation strings (#11375) --- src/client/clientlauncher.cpp | 4 ++-- src/gui/guiVolumeChange.cpp | 15 +++++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp index 13e7aefcf..6ab610670 100644 --- a/src/client/clientlauncher.cpp +++ b/src/client/clientlauncher.cpp @@ -512,8 +512,8 @@ bool ClientLauncher::launch_game(std::string &error_message, // Load gamespec for required game start_data.game_spec = findWorldSubgame(worldspec.path); if (!start_data.game_spec.isValid()) { - error_message = gettext("Could not find or load game \"") - + worldspec.gameid + "\""; + error_message = gettext("Could not find or load game: ") + + worldspec.gameid; errorstream << error_message << std::endl; return false; } diff --git a/src/gui/guiVolumeChange.cpp b/src/gui/guiVolumeChange.cpp index f17cfa986..61ab758a1 100644 --- a/src/gui/guiVolumeChange.cpp +++ b/src/gui/guiVolumeChange.cpp @@ -93,11 +93,12 @@ void GUIVolumeChange::regenerateGui(v2u32 screensize) core::rect rect(0, 0, 160 * s, 20 * s); rect = rect + v2s32(size.X / 2 - 80 * s, size.Y / 2 - 70 * s); - const wchar_t *text = wgettext("Sound Volume: "); + wchar_t text[100]; + const wchar_t *str = wgettext("Sound Volume: %d%%"); + swprintf(text, sizeof(text) / sizeof(wchar_t), str, volume); + delete[] str; core::stringw volume_text = text; - delete [] text; - volume_text += core::stringw(volume) + core::stringw("%"); Environment->addStaticText(volume_text.c_str(), rect, false, true, this, ID_soundText); } @@ -183,11 +184,13 @@ bool GUIVolumeChange::OnEvent(const SEvent& event) g_settings->setFloat("sound_volume", (float) pos / 100); gui::IGUIElement *e = getElementFromId(ID_soundText); - const wchar_t *text = wgettext("Sound Volume: "); + wchar_t text[100]; + const wchar_t *str = wgettext("Sound Volume: %d%%"); + swprintf(text, sizeof(text) / sizeof(wchar_t), str, pos); + delete[] str; + core::stringw volume_text = text; - delete [] text; - volume_text += core::stringw(pos) + core::stringw("%"); e->setText(volume_text.c_str()); return true; } -- cgit v1.2.3 From 63fc728a84a5ba97240233ad1c5d94f1ade2deb1 Mon Sep 17 00:00:00 2001 From: Wuzzy Date: Thu, 24 Jun 2021 18:21:19 +0000 Subject: Require 'basic_debug' priv to view gameplay-relevant debug info, require 'debug' priv to view wireframe (#9315) Fixes #7245. --- builtin/game/privileges.lua | 7 +++-- src/client/game.cpp | 59 ++++++++++++++++++++++++++++++++----- src/client/gameui.cpp | 19 +++++++----- src/client/gameui.h | 3 +- src/client/hud.cpp | 5 ++++ src/client/hud.h | 1 + src/network/clientpackethandler.cpp | 5 ++++ src/network/networkprotocol.h | 4 ++- 8 files changed, 84 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/builtin/game/privileges.lua b/builtin/game/privileges.lua index 1d3efb525..97681655e 100644 --- a/builtin/game/privileges.lua +++ b/builtin/game/privileges.lua @@ -97,10 +97,13 @@ core.register_privilege("rollback", { description = S("Can use the rollback functionality"), give_to_singleplayer = false, }) +core.register_privilege("basic_debug", { + description = S("Can view more debug info that might give a gameplay advantage"), + give_to_singleplayer = false, +}) core.register_privilege("debug", { - description = S("Allows enabling various debug options that may affect gameplay"), + description = S("Can enable wireframe"), give_to_singleplayer = false, - give_to_admin = true, }) core.register_can_bypass_userlimit(function(name, ip) diff --git a/src/client/game.cpp b/src/client/game.cpp index d240ebc0f..9f643e611 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -676,6 +676,7 @@ protected: bool handleCallbacks(); void processQueues(); void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime); + void updateBasicDebugState(); void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime); void updateProfilerGraphs(ProfilerGraph *graph); @@ -693,6 +694,7 @@ protected: void toggleFast(); void toggleNoClip(); void toggleCinematic(); + void toggleBlockBounds(); void toggleAutoforward(); void toggleMinimap(bool shift_pressed); @@ -1108,6 +1110,7 @@ void Game::run() m_game_ui->clearInfoText(); hud->resizeHotbar(); + updateProfilers(stats, draw_times, dtime); processUserInput(dtime); // Update camera before player movement to avoid camera lag of one frame @@ -1119,10 +1122,11 @@ void Game::run() updatePlayerControl(cam_view); step(&dtime); processClientEvents(&cam_view_target); + updateBasicDebugState(); updateCamera(draw_times.busy_time, dtime); updateSound(dtime); processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud, - m_game_ui->m_flags.show_debug); + m_game_ui->m_flags.show_basic_debug); updateFrame(&graph, &stats, dtime, cam_view); updateProfilerGraphs(&graph); @@ -1723,6 +1727,19 @@ void Game::processQueues() shader_src->processQueue(); } +void Game::updateBasicDebugState() +{ + if (m_game_ui->m_flags.show_basic_debug) { + if (!client->checkPrivilege("basic_debug")) { + m_game_ui->m_flags.show_basic_debug = false; + hud->disableBlockBounds(); + } + } else if (m_game_ui->m_flags.show_minimal_debug) { + if (client->checkPrivilege("basic_debug")) { + m_game_ui->m_flags.show_basic_debug = true; + } + } +} void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime) @@ -1935,7 +1952,7 @@ void Game::processKeyInput() } else if (wasKeyDown(KeyType::SCREENSHOT)) { client->makeScreenshot(); } else if (wasKeyDown(KeyType::TOGGLE_BLOCK_BOUNDS)) { - hud->toggleBlockBounds(); + toggleBlockBounds(); } else if (wasKeyDown(KeyType::TOGGLE_HUD)) { m_game_ui->toggleHud(); } else if (wasKeyDown(KeyType::MINIMAP)) { @@ -2173,6 +2190,15 @@ void Game::toggleCinematic() m_game_ui->showTranslatedStatusText("Cinematic mode disabled"); } +void Game::toggleBlockBounds() +{ + if (client->checkPrivilege("basic_debug")) { + hud->toggleBlockBounds(); + } else { + m_game_ui->showTranslatedStatusText("Can't show block bounds (need 'basic_debug' privilege)"); + } +} + // Autoforward by toggling continuous forward. void Game::toggleAutoforward() { @@ -2236,24 +2262,41 @@ void Game::toggleFog() void Game::toggleDebug() { - // Initial / 4x toggle: Chat only - // 1x toggle: Debug text with chat + // Initial: No debug info + // 1x toggle: Debug text // 2x toggle: Debug text with profiler graph - // 3x toggle: Debug text and wireframe - if (!m_game_ui->m_flags.show_debug) { - m_game_ui->m_flags.show_debug = true; + // 3x toggle: Debug text and wireframe (needs "debug" priv) + // Next toggle: Back to initial + // + // 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 used when player has "basic_debug" priv, + // 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 (client->checkPrivilege("basic_debug")) { + m_game_ui->m_flags.show_basic_debug = true; + } m_game_ui->m_flags.show_profiler_graph = false; draw_control->show_wireframe = false; m_game_ui->showTranslatedStatusText("Debug info shown"); } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) { + if (client->checkPrivilege("basic_debug")) { + m_game_ui->m_flags.show_basic_debug = true; + } m_game_ui->m_flags.show_profiler_graph = true; m_game_ui->showTranslatedStatusText("Profiler graph shown"); } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) { + if (client->checkPrivilege("basic_debug")) { + m_game_ui->m_flags.show_basic_debug = true; + } m_game_ui->m_flags.show_profiler_graph = false; draw_control->show_wireframe = true; m_game_ui->showTranslatedStatusText("Wireframe shown"); } else { - m_game_ui->m_flags.show_debug = false; + m_game_ui->m_flags.show_minimal_debug = false; + 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")) { diff --git a/src/client/gameui.cpp b/src/client/gameui.cpp index ebc6b108c..323967550 100644 --- a/src/client/gameui.cpp +++ b/src/client/gameui.cpp @@ -99,7 +99,8 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_ { v2u32 screensize = RenderingEngine::getWindowSize(); - if (m_flags.show_debug) { + // Minimal debug text must only contain info that can't give a gameplay advantage + if (m_flags.show_minimal_debug) { static float drawtime_avg = 0; drawtime_avg = drawtime_avg * 0.95 + stats.drawtime * 0.05; u16 fps = 1.0 / stats.dtime_jitter.avg; @@ -125,9 +126,10 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_ } // Finally set the guitext visible depending on the flag - m_guitext->setVisible(m_flags.show_debug); + m_guitext->setVisible(m_flags.show_minimal_debug); - if (m_flags.show_debug) { + // Basic debug text also shows info that might give a gameplay advantage + if (m_flags.show_basic_debug) { LocalPlayer *player = client->getEnv().getLocalPlayer(); v3f player_position = player->getPosition(); @@ -160,7 +162,7 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_ )); } - m_guitext2->setVisible(m_flags.show_debug); + m_guitext2->setVisible(m_flags.show_basic_debug); setStaticText(m_guitext_info, m_infotext.c_str()); m_guitext_info->setVisible(m_flags.show_hud && g_menumgr.menuCount() == 0); @@ -204,7 +206,8 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_ void GameUI::initFlags() { m_flags = GameUI::Flags(); - m_flags.show_debug = g_settings->getBool("show_debug"); + m_flags.show_minimal_debug = g_settings->getBool("show_debug"); + m_flags.show_basic_debug = false; } void GameUI::showMinimap(bool show) @@ -225,8 +228,10 @@ void GameUI::setChatText(const EnrichedString &chat_text, u32 recent_chat_count) // Update gui element size and position s32 chat_y = 5; - if (m_flags.show_debug) - chat_y += 2 * g_fontengine->getLineHeight(); + if (m_flags.show_minimal_debug) + chat_y += g_fontengine->getLineHeight(); + if (m_flags.show_basic_debug) + chat_y += g_fontengine->getLineHeight(); const v2u32 &window_size = RenderingEngine::getWindowSize(); diff --git a/src/client/gameui.h b/src/client/gameui.h index b6c8a224d..cb460b1c3 100644 --- a/src/client/gameui.h +++ b/src/client/gameui.h @@ -58,7 +58,8 @@ public: bool show_chat = true; bool show_hud = true; bool show_minimap = false; - bool show_debug = true; + bool show_minimal_debug = false; + bool show_basic_debug = false; bool show_profiler_graph = false; }; diff --git a/src/client/hud.cpp b/src/client/hud.cpp index 0bfdd5af0..fbfc886d2 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -870,6 +870,11 @@ void Hud::toggleBlockBounds() } } +void Hud::disableBlockBounds() +{ + m_block_bounds_mode = BLOCK_BOUNDS_OFF; +} + void Hud::drawBlockBounds() { if (m_block_bounds_mode == BLOCK_BOUNDS_OFF) { diff --git a/src/client/hud.h b/src/client/hud.h index d341105d2..e228c1d52 100644 --- a/src/client/hud.h +++ b/src/client/hud.h @@ -52,6 +52,7 @@ public: ~Hud(); void toggleBlockBounds(); + void disableBlockBounds(); void drawBlockBounds(); void drawHotbar(u16 playeritem); diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index c8a160732..b86bdac18 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -896,6 +896,11 @@ void Client::handleCommand_Privileges(NetworkPacket* pkt) m_privileges.insert(priv); infostream << priv << " "; } + + // Enable basic_debug on server versions before it was added + if (m_proto_ver < 40) + m_privileges.insert("basic_debug"); + infostream << std::endl; } diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index 838bf0b2c..b647aab1a 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -205,9 +205,11 @@ with this program; if not, write to the Free Software Foundation, Inc., Updated set_sky packet Adds new sun, moon and stars packets Minimap modes + PROTOCOL VERSION 40: + Added 'basic_debug' privilege */ -#define LATEST_PROTOCOL_VERSION 39 +#define LATEST_PROTOCOL_VERSION 40 #define LATEST_PROTOCOL_VERSION_STRING TOSTRING(LATEST_PROTOCOL_VERSION) // Server's supported network protocol range -- cgit v1.2.3 From f2fd4432625ee5cf0380bdd006cd1f15d053b12f Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Wed, 30 Jun 2021 20:39:38 +0200 Subject: Inventory: Make addList() consistent (#11382) Fixes list clearing for inv:set_list() using same size, since 2db6b07. addList() now clears the list in all cases. Use setSize() to resize without clearing. --- src/inventory.cpp | 15 ++++++--------- src/inventory.h | 2 +- src/script/common/c_content.cpp | 4 ++-- 3 files changed, 9 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/inventory.cpp b/src/inventory.cpp index fc1aaf371..b3bed623a 100644 --- a/src/inventory.cpp +++ b/src/inventory.cpp @@ -938,19 +938,16 @@ void Inventory::deSerialize(std::istream &is) InventoryList * Inventory::addList(const std::string &name, u32 size) { setModified(); + + // Remove existing lists s32 i = getListIndex(name); - if(i != -1) - { - if(m_lists[i]->getSize() != size) - { - delete m_lists[i]; - m_lists[i] = new InventoryList(name, size, m_itemdef); - m_lists[i]->setModified(); - } + if (i != -1) { + delete m_lists[i]; + m_lists[i] = new InventoryList(name, size, m_itemdef); + m_lists[i]->setModified(); return m_lists[i]; } - //don't create list with invalid name if (name.find(' ') != std::string::npos) return nullptr; diff --git a/src/inventory.h b/src/inventory.h index fbf995fab..6c84f5fd1 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -298,7 +298,7 @@ public: void serialize(std::ostream &os, bool incremental = false) const; void deSerialize(std::istream &is); - // Adds a new list or clears and resizes an existing one + // Creates a new list if none exists or truncates existing lists InventoryList * addList(const std::string &name, u32 size); InventoryList * getList(const std::string &name); const InventoryList * getList(const std::string &name) const; diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index f8cc40927..a0b45982a 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -1359,9 +1359,9 @@ 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); - size_t listsize = (forcesize > 0) ? forcesize : items.size(); + size_t listsize = (forcesize >= 0) ? forcesize : items.size(); - // Create or clear list + // Create or resize/clear list InventoryList *invlist = inv->addList(name, listsize); if (!invlist) { luaL_error(L, "inventory list: cannot create list named '%s'", name); -- cgit v1.2.3 From 827a7852e2ac4abfe548fe193b8ed380d5c46d86 Mon Sep 17 00:00:00 2001 From: hecks <42101236+hecktest@users.noreply.github.com> Date: Wed, 30 Jun 2021 20:42:15 +0200 Subject: Remove unsupported video drivers (#11395) This completely removes any mention of the software and D3D drivers from MT, preventing the user from accidentally attempting to use them. Users who need a software renderer should be asked to install Mesa drivers which offer superior fidelity and performance over the 'burningsvideo' driver. --- builtin/settingtypes.txt | 2 +- src/client/renderingengine.cpp | 54 ++++++++++++++++++------------------------ 2 files changed, 24 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 648c8c674..17843fac8 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -715,7 +715,7 @@ texture_path (Texture path) path # Note: On Android, stick with OGLES1 if unsure! App may fail to start otherwise. # On other platforms, OpenGL is recommended. # Shaders are supported by OpenGL (desktop only) and OGLES2 (experimental) -video_driver (Video driver) enum opengl null,software,burningsvideo,direct3d8,direct3d9,opengl,ogles1,ogles2 +video_driver (Video driver) enum opengl opengl,ogles1,ogles2 # Radius of cloud area stated in number of 64 node cloud squares. # Values larger than 26 will start to produce sharp cutoffs at cloud area corners. diff --git a/src/client/renderingengine.cpp b/src/client/renderingengine.cpp index 9015fb82a..558a9dd7a 100644 --- a/src/client/renderingengine.cpp +++ b/src/client/renderingengine.cpp @@ -284,14 +284,6 @@ static bool getWindowHandle(irr::video::IVideoDriver *driver, HWND &hWnd) const video::SExposedVideoData exposedData = driver->getExposedVideoData(); switch (driver->getDriverType()) { -#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9 - case video::EDT_DIRECT3D8: - hWnd = reinterpret_cast(exposedData.D3D8.HWnd); - break; -#endif - case video::EDT_DIRECT3D9: - hWnd = reinterpret_cast(exposedData.D3D9.HWnd); - break; #if ENABLE_GLES case video::EDT_OGLES1: case video::EDT_OGLES2: @@ -527,11 +519,19 @@ void RenderingEngine::draw_menu_scene(gui::IGUIEnvironment *guienv, std::vector RenderingEngine::getSupportedVideoDrivers() { + // Only check these drivers. + // We do not support software and D3D in any capacity. + static const irr::video::E_DRIVER_TYPE glDrivers[4] = { + irr::video::EDT_NULL, + irr::video::EDT_OPENGL, + irr::video::EDT_OGLES1, + irr::video::EDT_OGLES2, + }; std::vector drivers; - for (int i = 0; i != irr::video::EDT_COUNT; i++) { - if (irr::IrrlichtDevice::isDriverSupported((irr::video::E_DRIVER_TYPE)i)) - drivers.push_back((irr::video::E_DRIVER_TYPE)i); + for (int i = 0; i < 4; i++) { + if (irr::IrrlichtDevice::isDriverSupported(glDrivers[i])) + drivers.push_back(glDrivers[i]); } return drivers; @@ -557,34 +557,26 @@ void RenderingEngine::draw_scene(video::SColor skycolor, bool show_hud, const char *RenderingEngine::getVideoDriverName(irr::video::E_DRIVER_TYPE type) { - static const char *driver_ids[] = { - "null", - "software", - "burningsvideo", - "direct3d8", - "direct3d9", - "opengl", - "ogles1", - "ogles2", + static const std::unordered_map driver_ids = { + {irr::video::EDT_NULL, "null"}, + {irr::video::EDT_OPENGL, "opengl"}, + {irr::video::EDT_OGLES1, "ogles1"}, + {irr::video::EDT_OGLES2, "ogles2"}, }; - return driver_ids[type]; + return driver_ids.at(type).c_str(); } const char *RenderingEngine::getVideoDriverFriendlyName(irr::video::E_DRIVER_TYPE type) { - static const char *driver_names[] = { - "NULL Driver", - "Software Renderer", - "Burning's Video", - "Direct3D 8", - "Direct3D 9", - "OpenGL", - "OpenGL ES1", - "OpenGL ES2", + static const std::unordered_map driver_names = { + {irr::video::EDT_NULL, "NULL Driver"}, + {irr::video::EDT_OPENGL, "OpenGL"}, + {irr::video::EDT_OGLES1, "OpenGL ES1"}, + {irr::video::EDT_OGLES2, "OpenGL ES2"}, }; - return driver_names[type]; + return driver_names.at(type).c_str(); } #ifndef __ANDROID__ -- cgit v1.2.3 From 062fd2190e0ac617499b19c51db609d3a923347e Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Wed, 30 Jun 2021 20:42:26 +0200 Subject: Auth API: Error when accessed prior to ServerEnv init (#11398) --- src/script/lua_api/l_auth.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/script/lua_api/l_auth.cpp b/src/script/lua_api/l_auth.cpp index 0fc57ba3a..32d8a7411 100644 --- a/src/script/lua_api/l_auth.cpp +++ b/src/script/lua_api/l_auth.cpp @@ -32,8 +32,11 @@ AuthDatabase *ModApiAuth::getAuthDb(lua_State *L) { ServerEnvironment *server_environment = dynamic_cast(getEnv(L)); - if (!server_environment) + if (!server_environment) { + luaL_error(L, "Attempt to access an auth function but the auth" + " system is yet not initialized. This causes bugs."); return nullptr; + } return server_environment->getAuthDatabase(); } -- cgit v1.2.3 From 52128ae11e8b1a7ce66a87c53f1b15f3aabe69f4 Mon Sep 17 00:00:00 2001 From: Warr1024 Date: Fri, 9 Jul 2021 09:08:40 -0400 Subject: Add API for mods to hook liquid transformation events (#11405) Add API for mods to hook liquid transformation events Without this API, there is no reliable way for mods to be notified when liquid transform modifies nodes and mods are forced to poll for changes. This allows mods to detect changes to flowing liquid nodes and liquid renewal using event-driven logic. --- builtin/game/register.lua | 1 + doc/lua_api.txt | 6 ++++++ src/map.cpp | 2 +- src/script/cpp_api/s_env.cpp | 34 ++++++++++++++++++++++++++++++++++ src/script/cpp_api/s_env.h | 5 +++++ 5 files changed, 47 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/builtin/game/register.lua b/builtin/game/register.lua index c07535855..56e40c75c 100644 --- a/builtin/game/register.lua +++ b/builtin/game/register.lua @@ -610,6 +610,7 @@ core.registered_on_modchannel_message, core.register_on_modchannel_message = mak core.registered_on_player_inventory_actions, core.register_on_player_inventory_action = make_registration() core.registered_allow_player_inventory_actions, core.register_allow_player_inventory_action = make_registration() core.registered_on_rightclickplayers, core.register_on_rightclickplayer = make_registration() +core.registered_on_liquid_transformed, core.register_on_liquid_transformed = make_registration() -- -- Compatibility for on_mapgen_init() diff --git a/doc/lua_api.txt b/doc/lua_api.txt index aa59898d7..fc6d077e1 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -4859,6 +4859,12 @@ Call these functions only at load time! * Called when an incoming mod channel message is received * You should have joined some channels to receive events. * If message comes from a server mod, `sender` field is an empty string. +* `minetest.register_on_liquid_transformed(function(post_list, node_list))` + * Called after liquid nodes are modified by the engine's liquid transformation + process. + * `pos_list` is an array of all modified positions. + * `node_list` is an array of the old node that was previously at the position + with the corresponding index in pos_list. Setting-related --------------- diff --git a/src/map.cpp b/src/map.cpp index 641287c3d..30ce064d6 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -829,7 +829,7 @@ void Map::transformLiquids(std::map &modified_blocks, m_transforming_liquid.push_back(iter); voxalgo::update_lighting_nodes(this, changed_nodes, modified_blocks); - + env->getScriptIface()->on_liquid_transformed(changed_nodes); /* ---------------------------------------------------------------------- * Manage the queue so that it does not grow indefinately diff --git a/src/script/cpp_api/s_env.cpp b/src/script/cpp_api/s_env.cpp index c4a39a869..c11de3757 100644 --- a/src/script/cpp_api/s_env.cpp +++ b/src/script/cpp_api/s_env.cpp @@ -25,6 +25,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mapgen/mapgen.h" #include "lua_api/l_env.h" #include "server.h" +#include "script/common/c_content.h" + void ScriptApiEnv::environment_OnGenerated(v3s16 minp, v3s16 maxp, u32 blockseed) @@ -267,3 +269,35 @@ void ScriptApiEnv::on_emerge_area_completion( luaL_unref(L, LUA_REGISTRYINDEX, state->args_ref); } } + +void ScriptApiEnv::on_liquid_transformed( + const std::vector> &list) +{ + SCRIPTAPI_PRECHECKHEADER + + // Get core.registered_on_liquid_transformed + lua_getglobal(L, "core"); + lua_getfield(L, -1, "registered_on_liquid_transformed"); + luaL_checktype(L, -1, LUA_TTABLE); + lua_remove(L, -2); + + // Skip converting list and calling hook if there are + // no registered callbacks. + if(lua_objlen(L, -1) < 1) return; + + // Convert the list to a pos array and a node array for lua + int index = 1; + const NodeDefManager *ndef = getEnv()->getGameDef()->ndef(); + lua_createtable(L, list.size(), 0); + lua_createtable(L, list.size(), 0); + for(std::pair p : list) { + lua_pushnumber(L, index); + push_v3s16(L, p.first); + lua_rawset(L, -4); + lua_pushnumber(L, index++); + pushnode(L, p.second, ndef); + lua_rawset(L, -3); + } + + runCallbacks(2, RUN_CALLBACKS_MODE_FIRST); +} \ No newline at end of file diff --git a/src/script/cpp_api/s_env.h b/src/script/cpp_api/s_env.h index 232a08aaf..090858f17 100644 --- a/src/script/cpp_api/s_env.h +++ b/src/script/cpp_api/s_env.h @@ -21,6 +21,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "cpp_api/s_base.h" #include "irr_v3d.h" +#include "mapnode.h" +#include class ServerEnvironment; struct ScriptCallbackState; @@ -41,5 +43,8 @@ public: void on_emerge_area_completion(v3s16 blockpos, int action, ScriptCallbackState *state); + // Called after liquid transform changes + void on_liquid_transformed(const std::vector> &list); + void initializeEnvironment(ServerEnvironment *env); }; -- cgit v1.2.3 From b93bbfde2c0f6f6217ed3e358ed898049f98e448 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Sat, 10 Jul 2021 14:18:35 +0200 Subject: Script API: Fix segfault in remove_detached_inventory when minetest.remove_detached_inventory is called on script init, the environment is yet not set up, hence m_env is still nullptr until all scripts are loaded --- src/server/serverinventorymgr.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/server/serverinventorymgr.cpp b/src/server/serverinventorymgr.cpp index 2a80c9bbe..3aee003b4 100644 --- a/src/server/serverinventorymgr.cpp +++ b/src/server/serverinventorymgr.cpp @@ -157,8 +157,8 @@ bool ServerInventoryManager::removeDetachedInventory(const std::string &name) m_env->getGameDef()->sendDetachedInventory( nullptr, name, player->getPeerId()); - } else { - // Notify all players about the change + } else if (m_env) { + // Notify all players about the change as soon ServerEnv exists m_env->getGameDef()->sendDetachedInventory( nullptr, name, PEER_ID_INEXISTENT); } -- cgit v1.2.3 From 1d25d1f7ad35f739e8a64c2bdb44105998aed19b Mon Sep 17 00:00:00 2001 From: hecks <42101236+hecktest@users.noreply.github.com> Date: Sun, 11 Jul 2021 09:50:34 +0200 Subject: Refactor video driver name retrieval (#11413) Co-authored-by: hecktest <> --- src/client/renderingengine.cpp | 29 ++++++++--------------------- src/client/renderingengine.h | 7 +++++-- src/script/lua_api/l_mainmenu.cpp | 7 +++---- 3 files changed, 16 insertions(+), 27 deletions(-) (limited to 'src') diff --git a/src/client/renderingengine.cpp b/src/client/renderingengine.cpp index 558a9dd7a..037ede074 100644 --- a/src/client/renderingengine.cpp +++ b/src/client/renderingengine.cpp @@ -105,7 +105,7 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver) u32 i; for (i = 0; i != drivers.size(); i++) { if (!strcasecmp(driverstring.c_str(), - RenderingEngine::getVideoDriverName(drivers[i]))) { + RenderingEngine::getVideoDriverInfo(drivers[i]).name.c_str())) { driverType = drivers[i]; break; } @@ -555,28 +555,15 @@ void RenderingEngine::draw_scene(video::SColor skycolor, bool show_hud, core->draw(skycolor, show_hud, show_minimap, draw_wield_tool, draw_crosshair); } -const char *RenderingEngine::getVideoDriverName(irr::video::E_DRIVER_TYPE type) +const VideoDriverInfo &RenderingEngine::getVideoDriverInfo(irr::video::E_DRIVER_TYPE type) { - static const std::unordered_map driver_ids = { - {irr::video::EDT_NULL, "null"}, - {irr::video::EDT_OPENGL, "opengl"}, - {irr::video::EDT_OGLES1, "ogles1"}, - {irr::video::EDT_OGLES2, "ogles2"}, + static const std::unordered_map driver_info_map = { + {irr::video::EDT_NULL, {"null", "NULL Driver"}}, + {irr::video::EDT_OPENGL, {"opengl", "OpenGL"}}, + {irr::video::EDT_OGLES1, {"ogles1", "OpenGL ES1"}}, + {irr::video::EDT_OGLES2, {"ogles2", "OpenGL ES2"}}, }; - - return driver_ids.at(type).c_str(); -} - -const char *RenderingEngine::getVideoDriverFriendlyName(irr::video::E_DRIVER_TYPE type) -{ - static const std::unordered_map driver_names = { - {irr::video::EDT_NULL, "NULL Driver"}, - {irr::video::EDT_OPENGL, "OpenGL"}, - {irr::video::EDT_OGLES1, "OpenGL ES1"}, - {irr::video::EDT_OGLES2, "OpenGL ES2"}, - }; - - return driver_names.at(type).c_str(); + return driver_info_map.at(type); } #ifndef __ANDROID__ diff --git a/src/client/renderingengine.h b/src/client/renderingengine.h index 5299222e2..6f104bba9 100644 --- a/src/client/renderingengine.h +++ b/src/client/renderingengine.h @@ -29,6 +29,10 @@ with this program; if not, write to the Free Software Foundation, Inc., // include the shadow mapper classes too #include "client/shadows/dynamicshadowsrender.h" +struct VideoDriverInfo { + std::string name; + std::string friendly_name; +}; class ITextureSource; class Camera; @@ -49,8 +53,7 @@ public: video::IVideoDriver *getVideoDriver() { return driver; } - static const char *getVideoDriverName(irr::video::E_DRIVER_TYPE type); - static const char *getVideoDriverFriendlyName(irr::video::E_DRIVER_TYPE type); + static const VideoDriverInfo &getVideoDriverInfo(irr::video::E_DRIVER_TYPE type); static float getDisplayDensity(); static v2u32 getDisplaySize(); diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index 788557460..ad00de1c4 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -737,13 +737,12 @@ int ModApiMainMenu::l_get_video_drivers(lua_State *L) lua_newtable(L); for (u32 i = 0; i != drivers.size(); i++) { - const char *name = RenderingEngine::getVideoDriverName(drivers[i]); - const char *fname = RenderingEngine::getVideoDriverFriendlyName(drivers[i]); + auto &info = RenderingEngine::getVideoDriverInfo(drivers[i]); lua_newtable(L); - lua_pushstring(L, name); + lua_pushstring(L, info.name.c_str()); lua_setfield(L, -2, "name"); - lua_pushstring(L, fname); + lua_pushstring(L, info.friendly_name.c_str()); lua_setfield(L, -2, "friendly_name"); lua_rawseti(L, -2, i + 1); -- cgit v1.2.3 From f5706d444b02ccc1fcd854968087172d50cfcca2 Mon Sep 17 00:00:00 2001 From: x2048 Date: Sun, 11 Jul 2021 17:15:19 +0200 Subject: Improve shadow rendering with non-default camera FOV (#11385) * Adjust minimum filter radius for perspective * Expand shadow frustum when camera FOV changes, reuse FOV distance adjustment from numeric.cpp * Read shadow_soft_radius setting as float * Use adaptive filter radius to accomodate for PSM distortion * Adjust filter radius for texture resolution --- client/shaders/nodes_shader/opengl_fragment.glsl | 16 +++++++----- src/client/shader.cpp | 2 +- src/client/shadows/dynamicshadows.cpp | 31 ++++++++++++++---------- src/util/numeric.cpp | 11 ++++++--- 4 files changed, 37 insertions(+), 23 deletions(-) (limited to 'src') diff --git a/client/shaders/nodes_shader/opengl_fragment.glsl b/client/shaders/nodes_shader/opengl_fragment.glsl index 43a8b1f25..9f8a21d09 100644 --- a/client/shaders/nodes_shader/opengl_fragment.glsl +++ b/client/shaders/nodes_shader/opengl_fragment.glsl @@ -181,9 +181,14 @@ float getDeltaPerspectiveFactor(float l) float getPenumbraRadius(sampler2D shadowsampler, vec2 smTexCoord, float realDistance, float multiplier) { + float baseLength = getBaseLength(smTexCoord); + float perspectiveFactor; + // Return fast if sharp shadows are requested - if (SOFTSHADOWRADIUS <= 1.0) - return SOFTSHADOWRADIUS; + if (SOFTSHADOWRADIUS <= 1.0) { + perspectiveFactor = getDeltaPerspectiveFactor(baseLength); + return max(2 * length(smTexCoord.xy) * 2048 / f_textureresolution / pow(perspectiveFactor, 3), SOFTSHADOWRADIUS); + } vec2 clampedpos; float texture_size = 1.0 / (2048 /*f_textureresolution*/ * 0.5); @@ -192,8 +197,6 @@ float getPenumbraRadius(sampler2D shadowsampler, vec2 smTexCoord, float realDist float pointDepth; float maxRadius = SOFTSHADOWRADIUS * 5.0 * multiplier; - float baseLength = getBaseLength(smTexCoord); - float perspectiveFactor; float bound = clamp(PCFBOUND * (1 - baseLength), 0.5, PCFBOUND); int n = 0; @@ -211,9 +214,10 @@ float getPenumbraRadius(sampler2D shadowsampler, vec2 smTexCoord, float realDist } depth = depth / n; - depth = pow(clamp(depth, 0.0, 1000.0), 1.6) / 0.001; - return max(0.5, depth * maxRadius); + + perspectiveFactor = getDeltaPerspectiveFactor(baseLength); + return max(length(smTexCoord.xy) * 2 * 2048 / f_textureresolution / pow(perspectiveFactor, 3), depth * maxRadius); } #ifdef POISSON_FILTER diff --git a/src/client/shader.cpp b/src/client/shader.cpp index 355366bd3..0b35c37af 100644 --- a/src/client/shader.cpp +++ b/src/client/shader.cpp @@ -740,7 +740,7 @@ ShaderInfo ShaderSource::generateShader(const std::string &name, s32 shadow_filter = g_settings->getS32("shadow_filters"); shaders_header << "#define SHADOW_FILTER " << shadow_filter << "\n"; - float shadow_soft_radius = g_settings->getS32("shadow_soft_radius"); + float shadow_soft_radius = g_settings->getFloat("shadow_soft_radius"); if (shadow_soft_radius < 1.0f) shadow_soft_radius = 1.0f; shaders_header << "#define SOFTSHADOWRADIUS " << shadow_soft_radius << "\n"; diff --git a/src/client/shadows/dynamicshadows.cpp b/src/client/shadows/dynamicshadows.cpp index 775cdebce..17b711a61 100644 --- a/src/client/shadows/dynamicshadows.cpp +++ b/src/client/shadows/dynamicshadows.cpp @@ -33,29 +33,34 @@ void DirectionalLight::createSplitMatrices(const Camera *cam) v3f newCenter; v3f look = cam->getDirection(); + // camera view tangents + float tanFovY = tanf(cam->getFovY() * 0.5f); + float tanFovX = tanf(cam->getFovX() * 0.5f); + + // adjusted frustum boundaries + float sfNear = shadow_frustum.zNear; + float sfFar = adjustDist(shadow_frustum.zFar, cam->getFovY()); + + // adjusted camera positions v3f camPos2 = cam->getPosition(); v3f camPos = v3f(camPos2.X - cam->getOffset().X * BS, camPos2.Y - cam->getOffset().Y * BS, camPos2.Z - cam->getOffset().Z * BS); - camPos += look * shadow_frustum.zNear; - camPos2 += look * shadow_frustum.zNear; - float end = shadow_frustum.zNear + shadow_frustum.zFar; - newCenter = camPos + look * (shadow_frustum.zNear + 0.05f * end); - v3f world_center = camPos2 + look * (shadow_frustum.zNear + 0.05f * end); - // Create a vector to the frustum far corner - // @Liso: move all vars we can outside the loop. - float tanFovY = tanf(cam->getFovY() * 0.5f); - float tanFovX = tanf(cam->getFovX() * 0.5f); + camPos += look * sfNear; + camPos2 += look * sfNear; - const v3f &viewUp = cam->getCameraNode()->getUpVector(); - // viewUp.normalize(); + // center point of light frustum + float end = sfNear + sfFar; + newCenter = camPos + look * (sfNear + 0.05f * end); + v3f world_center = camPos2 + look * (sfNear + 0.05f * end); + // Create a vector to the frustum far corner + const v3f &viewUp = cam->getCameraNode()->getUpVector(); v3f viewRight = look.crossProduct(viewUp); - // viewRight.normalize(); v3f farCorner = look + viewRight * tanFovX + viewUp * tanFovY; // Compute the frustumBoundingSphere radius - v3f boundVec = (camPos + farCorner * shadow_frustum.zFar) - newCenter; + v3f boundVec = (camPos + farCorner * sfFar) - newCenter; radius = boundVec.getLength() * 2.0f; // boundVec.getLength(); float vvolume = radius * 2.0f; diff --git a/src/util/numeric.cpp b/src/util/numeric.cpp index 99e4cfb5c..702ddce95 100644 --- a/src/util/numeric.cpp +++ b/src/util/numeric.cpp @@ -159,7 +159,7 @@ bool isBlockInSight(v3s16 blockpos_b, v3f camera_pos, v3f camera_dir, return true; } -s16 adjustDist(s16 dist, float zoom_fov) +inline float adjustDist(float dist, float zoom_fov) { // 1.775 ~= 72 * PI / 180 * 1.4, the default FOV on the client. // The heuristic threshold for zooming is half of that. @@ -167,8 +167,13 @@ s16 adjustDist(s16 dist, float zoom_fov) if (zoom_fov < 0.001f || zoom_fov > threshold_fov) return dist; - return std::round(dist * std::cbrt((1.0f - std::cos(threshold_fov)) / - (1.0f - std::cos(zoom_fov / 2.0f)))); + return dist * std::cbrt((1.0f - std::cos(threshold_fov)) / + (1.0f - std::cos(zoom_fov / 2.0f))); +} + +s16 adjustDist(s16 dist, float zoom_fov) +{ + return std::round(adjustDist((float)dist, zoom_fov)); } void setPitchYawRollRad(core::matrix4 &m, const v3f &rot) -- cgit v1.2.3 From effb5356caee01c8bfa63e98974347aab01f96ef Mon Sep 17 00:00:00 2001 From: x2048 Date: Sun, 11 Jul 2021 19:57:29 +0200 Subject: Avoid draw list and shadow map update in the same frame to reduce dtime jitter (#11393) * Separate draw list and shadows update to reduce jitter * Avoid draw list update and shadow update in the same frame * Force-update shadows when camera offset changes --- src/client/game.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/game.cpp b/src/client/game.cpp index 9f643e611..134c74d5d 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -609,6 +609,7 @@ struct GameRunData { float jump_timer; float damage_flash; float update_draw_list_timer; + float update_shadows_timer; f32 fog_range; @@ -3874,10 +3875,10 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, changed much */ runData.update_draw_list_timer += dtime; + runData.update_shadows_timer += dtime; float update_draw_list_delta = 0.2f; - if (ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer()) - update_draw_list_delta = shadow->getUpdateDelta(); + bool draw_list_updated = false; v3f camera_direction = camera->getDirection(); if (runData.update_draw_list_timer >= update_draw_list_delta @@ -3887,8 +3888,18 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, runData.update_draw_list_timer = 0; client->getEnv().getClientMap().updateDrawList(); runData.update_draw_list_last_cam_dir = camera_direction; + draw_list_updated = true; + } - updateShadows(); + if (ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer()) { + update_draw_list_delta = shadow->getUpdateDelta(); + + if (m_camera_offset_changed || + (runData.update_shadows_timer > update_draw_list_delta && + (!draw_list_updated || shadow->getDirectionalLightCount() == 0))) { + runData.update_shadows_timer = 0; + updateShadows(); + } } m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime); -- cgit v1.2.3 From 5c89a0e12a1e679180b14bf92bdcdb1614e3982e Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 12 Jul 2021 11:53:05 +0200 Subject: Fix build on Ubuntu 16.04 and macOS Apparently the C++ standard library is supposed to provide specializations of std::hash for enums (even in C++11) but those don't always work for whatever reason. --- src/client/renderingengine.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/client/renderingengine.cpp b/src/client/renderingengine.cpp index 037ede074..ead4c7e21 100644 --- a/src/client/renderingengine.cpp +++ b/src/client/renderingengine.cpp @@ -557,13 +557,13 @@ void RenderingEngine::draw_scene(video::SColor skycolor, bool show_hud, const VideoDriverInfo &RenderingEngine::getVideoDriverInfo(irr::video::E_DRIVER_TYPE type) { - static const std::unordered_map driver_info_map = { - {irr::video::EDT_NULL, {"null", "NULL Driver"}}, - {irr::video::EDT_OPENGL, {"opengl", "OpenGL"}}, - {irr::video::EDT_OGLES1, {"ogles1", "OpenGL ES1"}}, - {irr::video::EDT_OGLES2, {"ogles2", "OpenGL ES2"}}, + static const std::unordered_map driver_info_map = { + {(int)video::EDT_NULL, {"null", "NULL Driver"}}, + {(int)video::EDT_OPENGL, {"opengl", "OpenGL"}}, + {(int)video::EDT_OGLES1, {"ogles1", "OpenGL ES1"}}, + {(int)video::EDT_OGLES2, {"ogles2", "OpenGL ES2"}}, }; - return driver_info_map.at(type); + return driver_info_map.at((int)type); } #ifndef __ANDROID__ -- cgit v1.2.3 From b7b5aad02758ae897fc0239f50f93e04d085ceed Mon Sep 17 00:00:00 2001 From: Wuzzy Date: Mon, 12 Jul 2021 18:32:18 +0000 Subject: Fix revoke debug privs not reliably turn off stuff (#11409) --- src/client/game.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/client/game.cpp b/src/client/game.cpp index 134c74d5d..85dd8f4bb 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -677,7 +677,7 @@ protected: bool handleCallbacks(); void processQueues(); void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime); - void updateBasicDebugState(); + void updateDebugState(); void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime); void updateProfilerGraphs(ProfilerGraph *graph); @@ -1123,7 +1123,7 @@ void Game::run() updatePlayerControl(cam_view); step(&dtime); processClientEvents(&cam_view_target); - updateBasicDebugState(); + updateDebugState(); updateCamera(draw_times.busy_time, dtime); updateSound(dtime); processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud, @@ -1728,18 +1728,24 @@ void Game::processQueues() shader_src->processQueue(); } -void Game::updateBasicDebugState() +void Game::updateDebugState() { + bool has_basic_debug = client->checkPrivilege("basic_debug"); + bool has_debug = client->checkPrivilege("debug"); + if (m_game_ui->m_flags.show_basic_debug) { - if (!client->checkPrivilege("basic_debug")) { + if (!has_basic_debug) { m_game_ui->m_flags.show_basic_debug = false; - hud->disableBlockBounds(); } } else if (m_game_ui->m_flags.show_minimal_debug) { - if (client->checkPrivilege("basic_debug")) { + if (has_basic_debug) { m_game_ui->m_flags.show_basic_debug = true; } } + if (!has_basic_debug) + hud->disableBlockBounds(); + if (!has_debug) + draw_control->show_wireframe = false; } void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times, -- cgit v1.2.3 From f4d8cc0f0bf33a998aa0d6d95de4b34f69fb31db Mon Sep 17 00:00:00 2001 From: Wuzzy Date: Thu, 15 Jul 2021 19:19:59 +0000 Subject: Add wallmounted support for plantlike and plantlike_rooted nodes (#11379) --- builtin/game/falling.lua | 3 +- doc/lua_api.txt | 10 ++++- games/devtest/mods/testnodes/drawtypes.lua | 30 +++++++++++++ ...odes_plantlike_rooted_base_side_wallmounted.png | Bin 0 -> 224 bytes .../testnodes_plantlike_rooted_wallmounted.png | Bin 0 -> 268 bytes .../textures/testnodes_plantlike_wallmounted.png | Bin 0 -> 268 bytes src/client/content_mapblock.cpp | 48 ++++++++++++++++++++- src/client/content_mapblock.h | 2 +- src/mapnode.cpp | 4 +- 9 files changed, 90 insertions(+), 7 deletions(-) create mode 100644 games/devtest/mods/testnodes/textures/testnodes_plantlike_rooted_base_side_wallmounted.png create mode 100644 games/devtest/mods/testnodes/textures/testnodes_plantlike_rooted_wallmounted.png create mode 100644 games/devtest/mods/testnodes/textures/testnodes_plantlike_wallmounted.png (limited to 'src') diff --git a/builtin/game/falling.lua b/builtin/game/falling.lua index 4a13b0776..6db563534 100644 --- a/builtin/game/falling.lua +++ b/builtin/game/falling.lua @@ -157,7 +157,8 @@ core.register_entity(":__builtin:falling_node", { if euler then self.object:set_rotation(euler) end - elseif (def.paramtype2 == "wallmounted" or def.paramtype2 == "colorwallmounted" or def.drawtype == "signlike") then + elseif (def.drawtype ~= "plantlike" and def.drawtype ~= "plantlike_rooted" and + (def.paramtype2 == "wallmounted" or def.paramtype2 == "colorwallmounted" or def.drawtype == "signlike")) then local rot = node.param2 % 8 if (def.drawtype == "signlike" and def.paramtype2 ~= "wallmounted" and def.paramtype2 ~= "colorwallmounted") then -- Change rotation to "floor" by default for non-wallmounted paramtype2 diff --git a/doc/lua_api.txt b/doc/lua_api.txt index ad11d7235..3c96d0b36 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -1022,7 +1022,8 @@ The function of `param2` is determined by `paramtype2` in node definition. to access/manipulate the content of this field * Bit 3: If set, liquid is flowing downwards (no graphical effect) * `paramtype2 = "wallmounted"` - * Supported drawtypes: "torchlike", "signlike", "normal", "nodebox", "mesh" + * Supported drawtypes: "torchlike", "signlike", "plantlike", + "plantlike_rooted", "normal", "nodebox", "mesh" * The rotation of the node is stored in `param2` * You can make this value by using `minetest.dir_to_wallmounted()` * Values range 0 - 5 @@ -1198,7 +1199,12 @@ Look for examples in `games/devtest` or `games/minetest_game`. * `plantlike_rooted` * Enables underwater `plantlike` without air bubbles around the nodes. * Consists of a base cube at the co-ordinates of the node plus a - `plantlike` extension above with a height of `param2 / 16` nodes. + `plantlike` extension above + * If `paramtype2="leveled", the `plantlike` extension has a height + of `param2 / 16` nodes, otherwise it's the height of 1 node + * If `paramtype2="wallmounted"`, the `plantlike` extension + will be at one of the corresponding 6 sides of the base cube. + Also, the base cube rotates like a `normal` cube would * The `plantlike` extension visually passes through any nodes above the base cube without affecting them. * The base cube texture tiles are defined as normal, the `plantlike` diff --git a/games/devtest/mods/testnodes/drawtypes.lua b/games/devtest/mods/testnodes/drawtypes.lua index 2bc7ec2e3..208774f6c 100644 --- a/games/devtest/mods/testnodes/drawtypes.lua +++ b/games/devtest/mods/testnodes/drawtypes.lua @@ -208,6 +208,19 @@ minetest.register_node("testnodes:plantlike_waving", { groups = { dig_immediate = 3 }, }) +minetest.register_node("testnodes:plantlike_wallmounted", { + description = S("Wallmounted Plantlike Drawtype Test Node"), + drawtype = "plantlike", + paramtype = "light", + paramtype2 = "wallmounted", + tiles = { "testnodes_plantlike_wallmounted.png" }, + leveled = 1, + + + walkable = false, + sunlight_propagates = true, + groups = { dig_immediate = 3 }, +}) -- param2 will rotate @@ -320,6 +333,20 @@ minetest.register_node("testnodes:plantlike_rooted", { groups = { dig_immediate = 3 }, }) +minetest.register_node("testnodes:plantlike_rooted_wallmounted", { + description = S("Wallmounted Rooted Plantlike Drawtype Test Node"), + drawtype = "plantlike_rooted", + paramtype = "light", + paramtype2 = "wallmounted", + tiles = { + "testnodes_plantlike_rooted_base.png", + "testnodes_plantlike_rooted_base.png", + "testnodes_plantlike_rooted_base_side_wallmounted.png" }, + special_tiles = { "testnodes_plantlike_rooted_wallmounted.png" }, + + groups = { dig_immediate = 3 }, +}) + minetest.register_node("testnodes:plantlike_rooted_waving", { description = S("Waving Rooted Plantlike Drawtype Test Node"), drawtype = "plantlike_rooted", @@ -588,6 +615,9 @@ scale("allfaces_optional_waving", scale("plantlike", S("Double-sized Plantlike Drawtype Test Node"), S("Half-sized Plantlike Drawtype Test Node")) +scale("plantlike_wallmounted", + S("Double-sized Wallmounted Plantlike Drawtype Test Node"), + S("Half-sized Wallmounted Plantlike Drawtype Test Node")) scale("torchlike_wallmounted", S("Double-sized Wallmounted Torchlike Drawtype Test Node"), S("Half-sized Wallmounted Torchlike Drawtype Test Node")) diff --git a/games/devtest/mods/testnodes/textures/testnodes_plantlike_rooted_base_side_wallmounted.png b/games/devtest/mods/testnodes/textures/testnodes_plantlike_rooted_base_side_wallmounted.png new file mode 100644 index 000000000..b0be8d077 Binary files /dev/null and b/games/devtest/mods/testnodes/textures/testnodes_plantlike_rooted_base_side_wallmounted.png differ diff --git a/games/devtest/mods/testnodes/textures/testnodes_plantlike_rooted_wallmounted.png b/games/devtest/mods/testnodes/textures/testnodes_plantlike_rooted_wallmounted.png new file mode 100644 index 000000000..421466407 Binary files /dev/null and b/games/devtest/mods/testnodes/textures/testnodes_plantlike_rooted_wallmounted.png differ diff --git a/games/devtest/mods/testnodes/textures/testnodes_plantlike_wallmounted.png b/games/devtest/mods/testnodes/textures/testnodes_plantlike_wallmounted.png new file mode 100644 index 000000000..c89b29e30 Binary files /dev/null and b/games/devtest/mods/testnodes/textures/testnodes_plantlike_wallmounted.png differ diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp index 810c57138..bb2d6398f 100644 --- a/src/client/content_mapblock.cpp +++ b/src/client/content_mapblock.cpp @@ -958,10 +958,38 @@ void MapblockMeshGenerator::drawPlantlikeQuad(float rotation, float quad_offset, vertex.rotateXZBy(rotation + rotate_degree); vertex += offset; } + + u8 wall = n.getWallMounted(nodedef); + if (wall != DWM_YN) { + for (v3f &vertex : vertices) { + switch (wall) { + case DWM_YP: + vertex.rotateYZBy(180); + vertex.rotateXZBy(180); + break; + case DWM_XP: + vertex.rotateXYBy(90); + break; + case DWM_XN: + vertex.rotateXYBy(-90); + vertex.rotateYZBy(180); + break; + case DWM_ZP: + vertex.rotateYZBy(-90); + vertex.rotateXYBy(90); + break; + case DWM_ZN: + vertex.rotateYZBy(90); + vertex.rotateXYBy(90); + break; + } + } + } + drawQuad(vertices, v3s16(0, 0, 0), plant_height); } -void MapblockMeshGenerator::drawPlantlike() +void MapblockMeshGenerator::drawPlantlike(bool is_rooted) { draw_style = PLANT_STYLE_CROSS; scale = BS / 2 * f->visual_scale; @@ -998,6 +1026,22 @@ void MapblockMeshGenerator::drawPlantlike() break; } + if (is_rooted) { + u8 wall = n.getWallMounted(nodedef); + switch (wall) { + case DWM_YP: + offset.Y += BS*2; + break; + case DWM_XN: + case DWM_XP: + case DWM_ZN: + case DWM_ZP: + offset.X += -BS; + offset.Y += BS; + break; + } + } + switch (draw_style) { case PLANT_STYLE_CROSS: drawPlantlikeQuad(46); @@ -1048,7 +1092,7 @@ void MapblockMeshGenerator::drawPlantlikeRootedNode() MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + p); light = LightPair(getInteriorLight(ntop, 1, nodedef)); } - drawPlantlike(); + drawPlantlike(true); p.Y--; } diff --git a/src/client/content_mapblock.h b/src/client/content_mapblock.h index 237cc7847..7344f05ee 100644 --- a/src/client/content_mapblock.h +++ b/src/client/content_mapblock.h @@ -146,7 +146,7 @@ public: void drawPlantlikeQuad(float rotation, float quad_offset = 0, bool offset_top_only = false); - void drawPlantlike(); + void drawPlantlike(bool is_rooted = false); // firelike-specific void drawFirelikeQuad(float rotation, float opening_angle, diff --git a/src/mapnode.cpp b/src/mapnode.cpp index c885bfe1d..f212ea8c9 100644 --- a/src/mapnode.cpp +++ b/src/mapnode.cpp @@ -161,7 +161,9 @@ u8 MapNode::getWallMounted(const NodeDefManager *nodemgr) const if (f.param_type_2 == CPT2_WALLMOUNTED || f.param_type_2 == CPT2_COLORED_WALLMOUNTED) { return getParam2() & 0x07; - } else if (f.drawtype == NDT_SIGNLIKE || f.drawtype == NDT_TORCHLIKE) { + } else if (f.drawtype == NDT_SIGNLIKE || f.drawtype == NDT_TORCHLIKE || + f.drawtype == NDT_PLANTLIKE || + f.drawtype == NDT_PLANTLIKE_ROOTED) { return 1; } return 0; -- cgit v1.2.3 From 40bee27e5603a2ed1aff3646fa62661aef55c08a Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Sat, 17 Jul 2021 16:44:06 +0200 Subject: CSM: Do not index files within hidden directories CSM would previously scan for files within .git or .svn directories, and also special files such as .gitignore --- src/client/client.cpp | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src') diff --git a/src/client/client.cpp b/src/client/client.cpp index 00ae8f6b8..17661c242 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -210,6 +210,9 @@ void Client::scanModSubfolder(const std::string &mod_name, const std::string &mo std::string full_path = mod_path + DIR_DELIM + mod_subpath; std::vector mod = fs::GetDirListing(full_path); for (const fs::DirListNode &j : mod) { + if (j.name[0] == '.') + continue; + if (j.dir) { scanModSubfolder(mod_name, mod_path, mod_subpath + j.name + DIR_DELIM); continue; -- cgit v1.2.3 From 6caed7073c5b50b5502edcb10f072adc3d59f84b Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 20 Jul 2021 17:53:28 +0200 Subject: Fix no locales being generated when APPLY_LOCALE_BLACKLIST=0 Also enable `ky` which appears to work fine. --- src/CMakeLists.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2a2adfaf0..ac460883a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -685,12 +685,11 @@ set(GETTEXT_BLACKLISTED_LOCALES he hi kn - ky ms_Arab th ) -option(APPLY_LOCALE_BLACKLIST "Use a blacklist to avoid broken locales" TRUE) +option(APPLY_LOCALE_BLACKLIST "Use a blacklist to avoid known broken locales" TRUE) if (GETTEXTLIB_FOUND AND APPLY_LOCALE_BLACKLIST) set(GETTEXT_USED_LOCALES "") @@ -700,6 +699,8 @@ if (GETTEXTLIB_FOUND AND APPLY_LOCALE_BLACKLIST) endif() endforeach() message(STATUS "Locale blacklist applied; Locales used: ${GETTEXT_USED_LOCALES}") +elseif (GETTEXTLIB_FOUND) + set(GETTEXT_USED_LOCALES ${GETTEXT_AVAILABLE_LOCALES}) endif() # Set some optimizations and tweaks -- cgit v1.2.3 From 850293bae6013fe020c454541d61af2c816b3204 Mon Sep 17 00:00:00 2001 From: hecks <42101236+hecktest@users.noreply.github.com> Date: Wed, 21 Jul 2021 22:07:13 +0200 Subject: Remove unused header includes --- src/client/renderingengine.cpp | 1 - src/gui/guiScene.cpp | 1 - src/gui/touchscreengui.cpp | 2 -- 3 files changed, 4 deletions(-) (limited to 'src') diff --git a/src/client/renderingengine.cpp b/src/client/renderingengine.cpp index ead4c7e21..8491dda04 100644 --- a/src/client/renderingengine.cpp +++ b/src/client/renderingengine.cpp @@ -19,7 +19,6 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include -#include #include "fontengine.h" #include "client.h" #include "clouds.h" diff --git a/src/gui/guiScene.cpp b/src/gui/guiScene.cpp index f0cfbec5e..ee2556b03 100644 --- a/src/gui/guiScene.cpp +++ b/src/gui/guiScene.cpp @@ -21,7 +21,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include -#include #include "porting.h" GUIScene::GUIScene(gui::IGUIEnvironment *env, scene::ISceneManager *smgr, diff --git a/src/gui/touchscreengui.cpp b/src/gui/touchscreengui.cpp index 78b18c2d9..eb20b7e70 100644 --- a/src/gui/touchscreengui.cpp +++ b/src/gui/touchscreengui.cpp @@ -32,8 +32,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include -#include - using namespace irr::core; const char **button_imagenames = (const char *[]) { -- cgit v1.2.3 From a049e8267fabd101cb5c6528b3270214cb0647f0 Mon Sep 17 00:00:00 2001 From: hecks <42101236+hecktest@users.noreply.github.com> Date: Thu, 22 Jul 2021 00:55:20 +0200 Subject: Remove unused ITextSceneNode header (#11476) Co-authored-by: hecktest <> --- src/client/content_cao.cpp | 1 - 1 file changed, 1 deletion(-) (limited to 'src') diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index 9216f0010..c3ac613a5 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -20,7 +20,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "content_cao.h" #include #include -#include #include #include #include "client/client.h" -- cgit v1.2.3 From bf3acbf388406f736286d990adb5f35a9023c390 Mon Sep 17 00:00:00 2001 From: x2048 Date: Sun, 25 Jul 2021 12:36:23 +0200 Subject: Distribute shadow map update over multiple frames to reduce stutter (#11422) Reduces stutter and freezes when playing. * Maintains double SM and SM Color textures * Light frustum update triggers incremental generation of shadow map into secondary 'future' textures. * Every incremental update renders a portion of the shadow draw list (split equally). * After defined number of frames (currently, 4), 'future' and 'current' textures are swapped, and DirectionalLight 'commits' the new frustum to use when rendering shadows on screen. Co-authored-by: sfan5 --- builtin/settingtypes.txt | 10 +- client/shaders/nodes_shader/opengl_fragment.glsl | 10 +- src/client/clientmap.cpp | 25 +++- src/client/clientmap.h | 2 +- src/client/game.cpp | 17 +-- src/client/shadows/dynamicshadows.cpp | 60 +++++++-- src/client/shadows/dynamicshadows.h | 9 +- src/client/shadows/dynamicshadowsrender.cpp | 156 ++++++++++++++++++----- src/client/shadows/dynamicshadowsrender.h | 7 +- src/defaultsettings.cpp | 2 +- 10 files changed, 225 insertions(+), 73 deletions(-) (limited to 'src') diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 17843fac8..420e9d49c 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -618,11 +618,11 @@ shadow_filters (Shadow filter quality) enum 1 0,1,2 # On true translucent nodes cast colored shadows. This is expensive. shadow_map_color (Colored shadows) bool false - -# Set the shadow update time, in seconds. -# Lower value means shadows and map updates faster, but it consumes more resources. -# Minimum value: 0.001; maximum value: 0.2 -shadow_update_time (Map update time) float 0.2 0.001 0.2 +# Spread a complete update of shadow map over given amount of frames. +# Higher values might make shadows laggy, lower values +# will consume more resources. +# Minimum value: 1; maximum value: 16 +shadow_update_frames (Map shadows update frames) int 8 1 16 # Set the soft shadow radius size. # Lower values mean sharper shadows, bigger values mean softer shadows. diff --git a/client/shaders/nodes_shader/opengl_fragment.glsl b/client/shaders/nodes_shader/opengl_fragment.glsl index 64a88ebbb..f85ca7b48 100644 --- a/client/shaders/nodes_shader/opengl_fragment.glsl +++ b/client/shaders/nodes_shader/opengl_fragment.glsl @@ -197,7 +197,7 @@ float getPenumbraRadius(sampler2D shadowsampler, vec2 smTexCoord, float realDist float pointDepth; float maxRadius = SOFTSHADOWRADIUS * 5.0 * multiplier; - float bound = clamp(PCFBOUND * (1 - baseLength), 0.5, PCFBOUND); + float bound = clamp(PCFBOUND * (1 - baseLength), 0.0, PCFBOUND); int n = 0; for (y = -bound; y <= bound; y += 1.0) @@ -304,7 +304,7 @@ vec4 getShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDistance float perspectiveFactor; float texture_size = 1.0 / (f_textureresolution * 0.5); - int samples = int(clamp(PCFSAMPLES * (1 - baseLength) * (1 - baseLength), 1, PCFSAMPLES)); + int samples = int(clamp(PCFSAMPLES * (1 - baseLength) * (1 - baseLength), PCFSAMPLES / 4, PCFSAMPLES)); int init_offset = int(floor(mod(((smTexCoord.x * 34.0) + 1.0) * smTexCoord.y, 64.0-samples))); int end_offset = int(samples) + init_offset; @@ -334,7 +334,7 @@ float getShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) float perspectiveFactor; float texture_size = 1.0 / (f_textureresolution * 0.5); - int samples = int(clamp(PCFSAMPLES * (1 - baseLength) * (1 - baseLength), 1, PCFSAMPLES)); + int samples = int(clamp(PCFSAMPLES * (1 - baseLength) * (1 - baseLength), PCFSAMPLES / 4, PCFSAMPLES)); int init_offset = int(floor(mod(((smTexCoord.x * 34.0) + 1.0) * smTexCoord.y, 64.0-samples))); int end_offset = int(samples) + init_offset; @@ -370,7 +370,7 @@ vec4 getShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDistance float texture_size = 1.0 / (f_textureresolution * 0.5); float y, x; - float bound = clamp(PCFBOUND * (1 - baseLength), 0.5, PCFBOUND); + float bound = clamp(PCFBOUND * (1 - baseLength), PCFBOUND / 2, PCFBOUND); int n = 0; // basic PCF filter @@ -402,7 +402,7 @@ float getShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) float texture_size = 1.0 / (f_textureresolution * 0.5); float y, x; - float bound = clamp(PCFBOUND * (1 - baseLength), 0.5, PCFBOUND); + float bound = clamp(PCFBOUND * (1 - baseLength), PCFBOUND / 2, PCFBOUND); int n = 0; // basic PCF filter diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp index 8b09eade1..77f3b9fe8 100644 --- a/src/client/clientmap.cpp +++ b/src/client/clientmap.cpp @@ -636,7 +636,7 @@ void ClientMap::PrintInfo(std::ostream &out) } void ClientMap::renderMapShadows(video::IVideoDriver *driver, - const video::SMaterial &material, s32 pass) + const video::SMaterial &material, s32 pass, int frame, int total_frames) { bool is_transparent_pass = pass != scene::ESNRP_SOLID; std::string prefix; @@ -650,7 +650,23 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver, MeshBufListList drawbufs; + int count = 0; + int low_bound = is_transparent_pass ? 0 : m_drawlist_shadow.size() / total_frames * frame; + int high_bound = is_transparent_pass ? m_drawlist_shadow.size() : m_drawlist_shadow.size() / total_frames * (frame + 1); + + // transparent pass should be rendered in one go + if (is_transparent_pass && frame != total_frames - 1) { + return; + } + for (auto &i : m_drawlist_shadow) { + // only process specific part of the list & break early + ++count; + if (count <= low_bound) + continue; + if (count > high_bound) + break; + v3s16 block_pos = i.first; MapBlock *block = i.second; @@ -705,6 +721,7 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver, local_material.MaterialType = material.MaterialType; local_material.BackfaceCulling = material.BackfaceCulling; local_material.FrontfaceCulling = material.FrontfaceCulling; + local_material.BlendOperation = material.BlendOperation; local_material.Lighting = false; driver->setMaterial(local_material); @@ -720,6 +737,12 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver, } } + // 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/clientmap.h b/src/client/clientmap.h index 93ade4c15..97ce8d355 100644 --- a/src/client/clientmap.h +++ b/src/client/clientmap.h @@ -125,7 +125,7 @@ public: void renderMap(video::IVideoDriver* driver, s32 pass); void renderMapShadows(video::IVideoDriver *driver, - const video::SMaterial &material, s32 pass); + const video::SMaterial &material, s32 pass, int frame, int total_frames); int getBackgroundBrightness(float max_d, u32 daylight_factor, int oldvalue, bool *sunlight_seen_result); diff --git a/src/client/game.cpp b/src/client/game.cpp index 85dd8f4bb..6fc57c8cc 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -609,7 +609,6 @@ struct GameRunData { float jump_timer; float damage_flash; float update_draw_list_timer; - float update_shadows_timer; f32 fog_range; @@ -3881,10 +3880,8 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, changed much */ runData.update_draw_list_timer += dtime; - runData.update_shadows_timer += dtime; float update_draw_list_delta = 0.2f; - bool draw_list_updated = false; v3f camera_direction = camera->getDirection(); if (runData.update_draw_list_timer >= update_draw_list_delta @@ -3894,18 +3891,10 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, runData.update_draw_list_timer = 0; client->getEnv().getClientMap().updateDrawList(); runData.update_draw_list_last_cam_dir = camera_direction; - draw_list_updated = true; } - if (ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer()) { - update_draw_list_delta = shadow->getUpdateDelta(); - - if (m_camera_offset_changed || - (runData.update_shadows_timer > update_draw_list_delta && - (!draw_list_updated || shadow->getDirectionalLightCount() == 0))) { - runData.update_shadows_timer = 0; - updateShadows(); - } + if (RenderingEngine::get_shadow_renderer()) { + updateShadows(); } m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime); @@ -4062,7 +4051,7 @@ void Game::updateShadows() shadow->getDirectionalLight().setDirection(sun_pos); shadow->setTimeOfDay(in_timeofday); - shadow->getDirectionalLight().update_frustum(camera, client); + shadow->getDirectionalLight().update_frustum(camera, client, m_camera_offset_changed); } /**************************************************************************** diff --git a/src/client/shadows/dynamicshadows.cpp b/src/client/shadows/dynamicshadows.cpp index 17b711a61..0c7eea0e7 100644 --- a/src/client/shadows/dynamicshadows.cpp +++ b/src/client/shadows/dynamicshadows.cpp @@ -38,8 +38,8 @@ void DirectionalLight::createSplitMatrices(const Camera *cam) float tanFovX = tanf(cam->getFovX() * 0.5f); // adjusted frustum boundaries - float sfNear = shadow_frustum.zNear; - float sfFar = adjustDist(shadow_frustum.zFar, cam->getFovY()); + float sfNear = future_frustum.zNear; + float sfFar = adjustDist(future_frustum.zFar, cam->getFovY()); // adjusted camera positions v3f camPos2 = cam->getPosition(); @@ -87,14 +87,15 @@ void DirectionalLight::createSplitMatrices(const Camera *cam) v3f eye_displacement = direction * vvolume; // we must compute the viewmat with the position - the camera offset - // but the shadow_frustum position must be the actual world position + // but the future_frustum position must be the actual world position v3f eye = frustumCenter - eye_displacement; - shadow_frustum.position = world_center - eye_displacement; - shadow_frustum.length = vvolume; - shadow_frustum.ViewMat.buildCameraLookAtMatrixLH(eye, frustumCenter, v3f(0.0f, 1.0f, 0.0f)); - shadow_frustum.ProjOrthMat.buildProjectionMatrixOrthoLH(shadow_frustum.length, - shadow_frustum.length, -shadow_frustum.length, - shadow_frustum.length,false); + future_frustum.position = world_center - eye_displacement; + future_frustum.length = vvolume; + future_frustum.ViewMat.buildCameraLookAtMatrixLH(eye, frustumCenter, v3f(0.0f, 1.0f, 0.0f)); + future_frustum.ProjOrthMat.buildProjectionMatrixOrthoLH(future_frustum.length, + future_frustum.length, -future_frustum.length, + future_frustum.length,false); + future_frustum.camera_offset = cam->getOffset(); } DirectionalLight::DirectionalLight(const u32 shadowMapResolution, @@ -104,23 +105,44 @@ DirectionalLight::DirectionalLight(const u32 shadowMapResolution, farPlane(farValue), mapRes(shadowMapResolution), pos(position) {} -void DirectionalLight::update_frustum(const Camera *cam, Client *client) +void DirectionalLight::update_frustum(const Camera *cam, Client *client, bool force) { - should_update_map_shadow = true; + if (dirty && !force) + return; + float zNear = cam->getCameraNode()->getNearValue(); float zFar = getMaxFarValue(); /////////////////////////////////// // update splits near and fars - shadow_frustum.zNear = zNear; - shadow_frustum.zFar = zFar; + future_frustum.zNear = zNear; + future_frustum.zFar = zFar; // update shadow frustum createSplitMatrices(cam); // get the draw list for shadows client->getEnv().getClientMap().updateDrawListShadow( - getPosition(), getDirection(), shadow_frustum.length); + getPosition(), getDirection(), future_frustum.length); should_update_map_shadow = true; + dirty = true; + + // when camera offset changes, adjust the current frustum view matrix to avoid flicker + v3s16 cam_offset = cam->getOffset(); + if (cam_offset != shadow_frustum.camera_offset) { + v3f rotated_offset; + shadow_frustum.ViewMat.rotateVect(rotated_offset, intToFloat(cam_offset - shadow_frustum.camera_offset, BS)); + shadow_frustum.ViewMat.setTranslation(shadow_frustum.ViewMat.getTranslation() + rotated_offset); + shadow_frustum.camera_offset = cam_offset; + } +} + +void DirectionalLight::commitFrustum() +{ + if (!dirty) + return; + + shadow_frustum = future_frustum; + dirty = false; } void DirectionalLight::setDirection(v3f dir) @@ -144,6 +166,16 @@ const m4f &DirectionalLight::getProjectionMatrix() const return shadow_frustum.ProjOrthMat; } +const m4f &DirectionalLight::getFutureViewMatrix() const +{ + return future_frustum.ViewMat; +} + +const m4f &DirectionalLight::getFutureProjectionMatrix() const +{ + return future_frustum.ProjOrthMat; +} + m4f DirectionalLight::getViewProjMatrix() { return shadow_frustum.ProjOrthMat * shadow_frustum.ViewMat; diff --git a/src/client/shadows/dynamicshadows.h b/src/client/shadows/dynamicshadows.h index a53612f7c..d8be66be8 100644 --- a/src/client/shadows/dynamicshadows.h +++ b/src/client/shadows/dynamicshadows.h @@ -34,6 +34,7 @@ struct shadowFrustum core::matrix4 ProjOrthMat; core::matrix4 ViewMat; v3f position; + v3s16 camera_offset; }; class DirectionalLight @@ -47,7 +48,7 @@ public: //DISABLE_CLASS_COPY(DirectionalLight) - void update_frustum(const Camera *cam, Client *client); + void update_frustum(const Camera *cam, Client *client, bool force = false); // when set direction is updated to negative normalized(direction) void setDirection(v3f dir); @@ -59,6 +60,8 @@ public: /// Gets the light's matrices. const core::matrix4 &getViewMatrix() const; const core::matrix4 &getProjectionMatrix() const; + const core::matrix4 &getFutureViewMatrix() const; + const core::matrix4 &getFutureProjectionMatrix() const; core::matrix4 getViewProjMatrix(); /// Gets the light's far value. @@ -88,6 +91,8 @@ public: bool should_update_map_shadow{true}; + void commitFrustum(); + private: void createSplitMatrices(const Camera *cam); @@ -99,4 +104,6 @@ private: v3f pos; v3f direction{0}; shadowFrustum shadow_frustum; + shadowFrustum future_frustum; + bool dirty{false}; }; diff --git a/src/client/shadows/dynamicshadowsrender.cpp b/src/client/shadows/dynamicshadowsrender.cpp index 135c9f895..350586225 100644 --- a/src/client/shadows/dynamicshadowsrender.cpp +++ b/src/client/shadows/dynamicshadowsrender.cpp @@ -27,10 +27,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/shader.h" #include "client/client.h" #include "client/clientmap.h" +#include "profiler.h" ShadowRenderer::ShadowRenderer(IrrlichtDevice *device, Client *client) : m_device(device), m_smgr(device->getSceneManager()), - m_driver(device->getVideoDriver()), m_client(client) + m_driver(device->getVideoDriver()), m_client(client), m_current_frame(0) { m_shadows_enabled = true; @@ -43,7 +44,7 @@ ShadowRenderer::ShadowRenderer(IrrlichtDevice *device, Client *client) : m_shadow_map_texture_32bit = g_settings->getBool("shadow_map_texture_32bit"); m_shadow_map_colored = g_settings->getBool("shadow_map_color"); m_shadow_samples = g_settings->getS32("shadow_filters"); - m_update_delta = g_settings->getFloat("shadow_update_time"); + m_map_shadow_update_frames = g_settings->getS16("shadow_update_frames"); } ShadowRenderer::~ShadowRenderer() @@ -66,6 +67,9 @@ ShadowRenderer::~ShadowRenderer() if (shadowMapClientMap) m_driver->removeTexture(shadowMapClientMap); + + if (shadowMapClientMapFuture) + m_driver->removeTexture(shadowMapClientMapFuture); } void ShadowRenderer::initialize() @@ -93,11 +97,6 @@ void ShadowRenderer::initialize() } -float ShadowRenderer::getUpdateDelta() const -{ - return m_update_delta; -} - size_t ShadowRenderer::addDirectionalLight() { m_light_list.emplace_back(m_shadow_map_texture_size, @@ -152,10 +151,9 @@ void ShadowRenderer::setClearColor(video::SColor ClearColor) m_clear_color = ClearColor; } -void ShadowRenderer::update(video::ITexture *outputTarget) +void ShadowRenderer::updateSMTextures() { if (!m_shadows_enabled || m_smgr->getActiveCamera() == nullptr) { - m_smgr->drawAll(); return; } @@ -174,6 +172,13 @@ void ShadowRenderer::update(video::ITexture *outputTarget) true); } + if (!shadowMapClientMapFuture && m_map_shadow_update_frames > 1) { + shadowMapClientMapFuture = getSMTexture( + std::string("shadow_clientmap_bb_") + itos(m_shadow_map_texture_size), + m_shadow_map_colored ? m_texture_format_color : m_texture_format, + true); + } + if (m_shadow_map_colored && !shadowMapTextureColors) { shadowMapTextureColors = getSMTexture( std::string("shadow_colored_") + itos(m_shadow_map_texture_size), @@ -201,7 +206,22 @@ void ShadowRenderer::update(video::ITexture *outputTarget) } if (!m_shadow_node_array.empty() && !m_light_list.empty()) { - // for every directional light: + bool reset_sm_texture = false; + + // detect if SM should be regenerated + for (DirectionalLight &light : m_light_list) { + if (light.should_update_map_shadow) { + light.should_update_map_shadow = false; + m_current_frame = 0; + reset_sm_texture = true; + } + } + + video::ITexture* shadowMapTargetTexture = shadowMapClientMapFuture; + if (shadowMapTargetTexture == nullptr) + shadowMapTargetTexture = shadowMapClientMap; + + // Update SM incrementally: for (DirectionalLight &light : m_light_list) { // Static shader values. m_shadow_depth_cb->MapRes = (f32)m_shadow_map_texture_size; @@ -212,22 +232,60 @@ void ShadowRenderer::update(video::ITexture *outputTarget) // Depth texture is available in irrlicth maybe we // should put some gl* fn here - if (light.should_update_map_shadow) { - light.should_update_map_shadow = false; - m_driver->setRenderTarget(shadowMapClientMap, true, true, + if (m_current_frame < m_map_shadow_update_frames) { + m_driver->setRenderTarget(shadowMapTargetTexture, reset_sm_texture, true, video::SColor(255, 255, 255, 255)); - renderShadowMap(shadowMapClientMap, light); - - if (m_shadow_map_colored) { - m_driver->setRenderTarget(shadowMapTextureColors, - true, false, video::SColor(255, 255, 255, 255)); + renderShadowMap(shadowMapTargetTexture, light); + + // Render transparent part in one pass. + // This is also handled in ClientMap. + if (m_current_frame == m_map_shadow_update_frames - 1) { + if (m_shadow_map_colored) { + m_driver->setRenderTarget(shadowMapTextureColors, + true, false, video::SColor(255, 255, 255, 255)); + } + renderShadowMap(shadowMapTextureColors, light, + scene::ESNRP_TRANSPARENT); } - renderShadowMap(shadowMapTextureColors, light, - scene::ESNRP_TRANSPARENT); m_driver->setRenderTarget(0, false, false); } + reset_sm_texture = false; + } // end for lights + + // move to the next section + if (m_current_frame <= m_map_shadow_update_frames) + ++m_current_frame; + + // pass finished, swap textures and commit light changes + if (m_current_frame == m_map_shadow_update_frames) { + if (shadowMapClientMapFuture != nullptr) + std::swap(shadowMapClientMapFuture, shadowMapClientMap); + + // Let all lights know that maps are updated + for (DirectionalLight &light : m_light_list) + light.commitFrustum(); + } + } +} + +void ShadowRenderer::update(video::ITexture *outputTarget) +{ + if (!m_shadows_enabled || m_smgr->getActiveCamera() == nullptr) { + m_smgr->drawAll(); + return; + } + + updateSMTextures(); + + if (!m_shadow_node_array.empty() && !m_light_list.empty()) { + + for (DirectionalLight &light : m_light_list) { + // Static shader values. + m_shadow_depth_cb->MapRes = (f32)m_shadow_map_texture_size; + m_shadow_depth_cb->MaxFar = (f32)m_shadow_map_max_distance * BS; + // render shadows for the n0n-map objects. m_driver->setRenderTarget(shadowMapTextureDynamicObjects, true, true, video::SColor(255, 255, 255, 255)); @@ -299,8 +357,8 @@ video::ITexture *ShadowRenderer::getSMTexture(const std::string &shadow_map_name void ShadowRenderer::renderShadowMap(video::ITexture *target, DirectionalLight &light, scene::E_SCENE_NODE_RENDER_PASS pass) { - m_driver->setTransform(video::ETS_VIEW, light.getViewMatrix()); - m_driver->setTransform(video::ETS_PROJECTION, light.getProjectionMatrix()); + m_driver->setTransform(video::ETS_VIEW, light.getFutureViewMatrix()); + m_driver->setTransform(video::ETS_PROJECTION, light.getFutureProjectionMatrix()); // Operate on the client map for (const auto &shadow_node : m_shadow_node_array) { @@ -322,10 +380,13 @@ void ShadowRenderer::renderShadowMap(video::ITexture *target, //material.PolygonOffsetDepthBias = 1.0f/4.0f; //material.PolygonOffsetSlopeScale = -1.f; - if (m_shadow_map_colored && pass != scene::ESNRP_SOLID) + if (m_shadow_map_colored && pass != scene::ESNRP_SOLID) { material.MaterialType = (video::E_MATERIAL_TYPE) depth_shader_trans; - else + } + else { material.MaterialType = (video::E_MATERIAL_TYPE) depth_shader; + material.BlendOperation = video::EBO_MIN; + } // FIXME: I don't think this is needed here map_node->OnAnimate(m_device->getTimer()->getTime()); @@ -333,7 +394,7 @@ void ShadowRenderer::renderShadowMap(video::ITexture *target, m_driver->setTransform(video::ETS_WORLD, map_node->getAbsoluteTransformation()); - map_node->renderMapShadows(m_driver, material, pass); + map_node->renderMapShadows(m_driver, material, pass, m_current_frame, m_map_shadow_update_frames); break; } } @@ -354,8 +415,10 @@ void ShadowRenderer::renderShadowObjects( u32 n_node_materials = shadow_node.node->getMaterialCount(); std::vector BufferMaterialList; std::vector> BufferMaterialCullingList; + std::vector BufferBlendOperationList; BufferMaterialList.reserve(n_node_materials); BufferMaterialCullingList.reserve(n_node_materials); + BufferBlendOperationList.reserve(n_node_materials); // backup materialtype for each material // (aka shader) @@ -365,12 +428,11 @@ void ShadowRenderer::renderShadowObjects( BufferMaterialList.push_back(current_mat.MaterialType); current_mat.MaterialType = - (video::E_MATERIAL_TYPE)depth_shader; - - current_mat.setTexture(3, shadowMapTextureFinal); + (video::E_MATERIAL_TYPE)depth_shader_entities; BufferMaterialCullingList.emplace_back( (bool)current_mat.BackfaceCulling, (bool)current_mat.FrontfaceCulling); + BufferBlendOperationList.push_back(current_mat.BlendOperation); current_mat.BackfaceCulling = true; current_mat.FrontfaceCulling = false; @@ -393,6 +455,7 @@ void ShadowRenderer::renderShadowObjects( current_mat.BackfaceCulling = BufferMaterialCullingList[m].first; current_mat.FrontfaceCulling = BufferMaterialCullingList[m].second; + current_mat.BlendOperation = BufferBlendOperationList[m]; } } // end for caster shadow nodes @@ -433,7 +496,7 @@ void ShadowRenderer::createShaders() readShaderFile(depth_shader_vs).c_str(), "vertexMain", video::EVST_VS_1_1, readShaderFile(depth_shader_fs).c_str(), "pixelMain", - video::EPST_PS_1_2, m_shadow_depth_cb); + video::EPST_PS_1_2, m_shadow_depth_cb, video::EMT_ONETEXTURE_BLEND); if (depth_shader == -1) { // upsi, something went wrong loading shader. @@ -449,6 +512,41 @@ void ShadowRenderer::createShaders() m_driver->getMaterialRenderer(depth_shader)->grab(); } + // This creates a clone of depth_shader with base material set to EMT_SOLID, + // because entities won't render shadows with base material EMP_ONETEXTURE_BLEND + 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; + 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; + errorstream << "Error shadow mapping fs shader not found." << std::endl; + return; + } + + depth_shader_entities = gpu->addHighLevelShaderMaterial( + readShaderFile(depth_shader_vs).c_str(), "vertexMain", + video::EVST_VS_1_1, + readShaderFile(depth_shader_fs).c_str(), "pixelMain", + video::EPST_PS_1_2, m_shadow_depth_cb); + + if (depth_shader_entities == -1) { + // upsi, something went wrong loading shader. + m_shadows_enabled = false; + errorstream << "Error compiling shadow mapping shader (dynamic)." << std::endl; + return; + } + + // HACK, TODO: investigate this better + // Grab the material renderer once more so minetest doesn't crash + // on exit + m_driver->getMaterialRenderer(depth_shader_entities)->grab(); + } + if (mixcsm_shader == -1) { std::string depth_shader_vs = getShaderPath("shadow_shaders", "pass2_vertex.glsl"); if (depth_shader_vs.empty()) { diff --git a/src/client/shadows/dynamicshadowsrender.h b/src/client/shadows/dynamicshadowsrender.h index e633bd4f7..52b24a18f 100644 --- a/src/client/shadows/dynamicshadowsrender.h +++ b/src/client/shadows/dynamicshadowsrender.h @@ -64,7 +64,6 @@ public: size_t getDirectionalLightCount() const; f32 getMaxShadowFar() const; - float getUpdateDelta() const; /// Adds a shadow to the scene node. /// The shadow mode can be ESM_BOTH, or ESM_RECEIVE. /// ESM_BOTH casts and receives shadows @@ -101,6 +100,7 @@ private: scene::ESNRP_SOLID); void renderShadowObjects(video::ITexture *target, DirectionalLight &light); void mixShadowsQuad(); + void updateSMTextures(); // a bunch of variables IrrlichtDevice *m_device{nullptr}; @@ -108,6 +108,7 @@ private: video::IVideoDriver *m_driver{nullptr}; Client *m_client{nullptr}; video::ITexture *shadowMapClientMap{nullptr}; + video::ITexture *shadowMapClientMapFuture{nullptr}; video::ITexture *shadowMapTextureFinal{nullptr}; video::ITexture *shadowMapTextureDynamicObjects{nullptr}; video::ITexture *shadowMapTextureColors{nullptr}; @@ -120,11 +121,12 @@ private: float m_shadow_map_max_distance; float m_shadow_map_texture_size; float m_time_day{0.0f}; - float m_update_delta; int m_shadow_samples; bool m_shadow_map_texture_32bit; bool m_shadows_enabled; 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 */ video::ECOLOR_FORMAT m_texture_format{video::ECOLOR_FORMAT::ECF_R16F}; video::ECOLOR_FORMAT m_texture_format_color{video::ECOLOR_FORMAT::ECF_R16G16}; @@ -135,6 +137,7 @@ private: std::string readShaderFile(const std::string &path); s32 depth_shader{-1}; + s32 depth_shader_entities{-1}; s32 depth_shader_trans{-1}; s32 mixcsm_shader{-1}; diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 6791fccf5..faf839b3a 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -272,7 +272,7 @@ void set_default_settings() settings->setDefault("shadow_map_color", "false"); settings->setDefault("shadow_filters", "1"); settings->setDefault("shadow_poisson_filter", "true"); - settings->setDefault("shadow_update_time", "0.2"); + settings->setDefault("shadow_update_frames", "8"); settings->setDefault("shadow_soft_radius", "1.0"); settings->setDefault("shadow_sky_body_orbit_tilt", "0.0"); -- cgit v1.2.3 From cf136914cf421ee52f6806eda2fa97740d0ee552 Mon Sep 17 00:00:00 2001 From: JosiahWI <41302989+JosiahWI@users.noreply.github.com> Date: Tue, 27 Jul 2021 12:11:27 -0500 Subject: Take advantage of IrrlichtMt CMake target (#11287) With the CMake changes to IrrlichtMt, it's now possible to use a target for IrrlichtMt. Besides greatly improving the ease of setting up IrrlichtMt for users building the client, it removes the need for Minetest's CMake to include transitive dependencies such as image libraries, cleaning it up a tiny bit. The PR works by finding the IrrlichtMt package and linking to the target it provides. If the package isn't found and it isn't building the client, it will still fall back to using just the headers of old Irrlicht or IrrlichtMt. --- .gitlab-ci.yml | 10 ++-- CMakeLists.txt | 41 +++++++---------- README.md | 3 ++ cmake/Modules/FindIrrlicht.cmake | 61 ------------------------- cmake/Modules/MinetestFindIrrlichtHeaders.cmake | 26 +++++++++++ src/CMakeLists.txt | 41 +++-------------- util/buildbot/buildwin32.sh | 8 ++-- util/buildbot/buildwin64.sh | 8 ++-- util/ci/common.sh | 4 +- 9 files changed, 62 insertions(+), 140 deletions(-) delete mode 100644 cmake/Modules/FindIrrlicht.cmake create mode 100644 cmake/Modules/MinetestFindIrrlichtHeaders.cmake (limited to 'src') diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b5c4695bb..d335285d5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -9,7 +9,7 @@ stages: - deploy variables: - IRRLICHT_TAG: "1.9.0mt1" + IRRLICHT_TAG: "1.9.0mt2" MINETEST_GAME_REPO: "https://github.com/minetest/minetest_game.git" CONTAINER_IMAGE: registry.gitlab.com/$CI_PROJECT_PATH @@ -19,14 +19,10 @@ variables: - apt-get update - apt-get -y install build-essential git cmake libpng-dev libjpeg-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libleveldb-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev script: - - git clone https://github.com/minetest/irrlicht -b $IRRLICHT_TAG - - cd irrlicht - - cmake . -DBUILD_SHARED_LIBS=OFF - - make -j2 - - cd .. + - git clone https://github.com/minetest/irrlicht -b $IRRLICHT_TAG lib/irrlichtmt - mkdir cmakebuild - cd cmakebuild - - cmake -DIRRLICHT_LIBRARY=$PWD/../irrlicht/lib/Linux/libIrrlichtMt.a -DIRRLICHT_INCLUDE_DIR=$PWD/../irrlicht/include -DCMAKE_INSTALL_PREFIX=../artifact/minetest/usr/ -DCMAKE_BUILD_TYPE=Release -DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE -DBUILD_SERVER=TRUE .. + - cmake -DCMAKE_INSTALL_PREFIX=../artifact/minetest/usr/ -DCMAKE_BUILD_TYPE=Release -DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE -DBUILD_SERVER=TRUE .. - make -j2 - make install artifacts: diff --git a/CMakeLists.txt b/CMakeLists.txt index 42b343540..fe508ffdb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,34 +68,25 @@ if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/lib/irrlichtmt") if(NOT TARGET IrrlichtMt) message(FATAL_ERROR "IrrlichtMt project is missing a CMake target?!") endif() - - # set include dir the way it would normally be - set(IRRLICHT_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/lib/irrlichtmt/include") - set(IRRLICHT_LIBRARY IrrlichtMt) else() - find_package(Irrlicht) - if(BUILD_CLIENT AND NOT IRRLICHT_FOUND) - message(FATAL_ERROR "IrrlichtMt is required to build the client, but it was not found.") - elseif(NOT IRRLICHT_INCLUDE_DIR) - message(FATAL_ERROR "Irrlicht or IrrlichtMt headers are required to build the server, but none found.") - endif() -endif() + find_package(IrrlichtMt QUIET) + if(NOT TARGET IrrlichtMt::IrrlichtMt) + string(CONCAT explanation_msg + "The Minetest team has forked Irrlicht to make their own customizations. " + "It can be found here: https://github.com/minetest/irrlicht") + if(BUILD_CLIENT) + message(FATAL_ERROR "IrrlichtMt is required to build the client, but it was not found.\n${explanation_msg}") + endif() -include(CheckSymbolExists) -set(CMAKE_REQUIRED_INCLUDES ${IRRLICHT_INCLUDE_DIR}) -unset(HAS_FORKED_IRRLICHT CACHE) -check_symbol_exists(IRRLICHT_VERSION_MT "IrrCompileConfig.h" HAS_FORKED_IRRLICHT) -if(NOT HAS_FORKED_IRRLICHT) - string(CONCAT EXPLANATION_MSG - "Irrlicht found, but it is not IrrlichtMt (Minetest's Irrlicht fork). " - "The Minetest team has forked Irrlicht to make their own customizations. " - "It can be found here: https://github.com/minetest/irrlicht") - if(BUILD_CLIENT) - message(FATAL_ERROR "${EXPLANATION_MSG}\n" - "Building the client with upstream Irrlicht is no longer possible.") + include(MinetestFindIrrlichtHeaders) + if(NOT IRRLICHT_INCLUDE_DIR) + message(FATAL_ERROR "Irrlicht or IrrlichtMt headers are required to build the server, but none found.\n${explanation_msg}") + endif() + message(STATUS "Found Irrlicht headers: ${IRRLICHT_INCLUDE_DIR}") + add_library(IrrlichtMt::IrrlichtMt INTERFACE IMPORTED) + target_include_directories(IrrlichtMt::IrrlichtMt INTERFACE "${IRRLICHT_INCLUDE_DIR}") else() - message(WARNING "${EXPLANATION_MSG}\n" - "The server can still be built with upstream Irrlicht but this is DISCOURAGED.") + message(STATUS "Found IrrlichtMt ${IrrlichtMt_VERSION}") endif() endif() diff --git a/README.md b/README.md index 0cd134f27..1774d1ea3 100644 --- a/README.md +++ b/README.md @@ -223,6 +223,9 @@ Run it: - Debug build is slower, but gives much more useful output in a debugger. - If you build a bare server you don't need to have the Irrlicht or IrrlichtMt library installed. - In that case use `-DIRRLICHT_INCLUDE_DIR=/some/where/irrlicht/include`. +- IrrlichtMt can also be installed somewhere that is not a standard install path. + - In that case use `-DCMAKE_PREFIX_PATH=/path/to/install_prefix` + - The path must be set so that `$(CMAKE_PREFIX_PATH)/lib/cmake/IrrlichtMt` exists. ### CMake options diff --git a/cmake/Modules/FindIrrlicht.cmake b/cmake/Modules/FindIrrlicht.cmake deleted file mode 100644 index 1e334652b..000000000 --- a/cmake/Modules/FindIrrlicht.cmake +++ /dev/null @@ -1,61 +0,0 @@ - -mark_as_advanced(IRRLICHT_DLL) - -# Find include directory and libraries - -# find our fork first, then upstream (TODO: remove this?) -foreach(libname IN ITEMS IrrlichtMt Irrlicht) - string(TOLOWER "${libname}" libname2) - - find_path(IRRLICHT_INCLUDE_DIR NAMES irrlicht.h - DOC "Path to the directory with IrrlichtMt includes" - PATHS - /usr/local/include/${libname2} - /usr/include/${libname2} - /system/develop/headers/${libname2} #Haiku - PATH_SUFFIXES "include/${libname2}" - ) - - find_library(IRRLICHT_LIBRARY NAMES lib${libname} ${libname} - DOC "Path to the IrrlichtMt library file" - PATHS - /usr/local/lib - /usr/lib - /system/develop/lib # Haiku - ) - - if(IRRLICHT_INCLUDE_DIR OR IRRLICHT_LIBRARY) - break() - endif() -endforeach() - -# Handholding for users -if(IRRLICHT_INCLUDE_DIR AND (NOT IS_DIRECTORY "${IRRLICHT_INCLUDE_DIR}" OR - NOT EXISTS "${IRRLICHT_INCLUDE_DIR}/irrlicht.h")) - message(WARNING "IRRLICHT_INCLUDE_DIR was set to ${IRRLICHT_INCLUDE_DIR} " - "but irrlicht.h does not exist inside. The path will not be used.") - unset(IRRLICHT_INCLUDE_DIR CACHE) -endif() -if(WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Linux" OR APPLE) - # (only on systems where we're sure how a valid library looks like) - if(IRRLICHT_LIBRARY AND (NOT EXISTS "${IRRLICHT_LIBRARY}" OR - NOT IRRLICHT_LIBRARY MATCHES "\\.(a|so|dylib|lib)([.0-9]+)?$")) - message(WARNING "IRRLICHT_LIBRARY was set to ${IRRLICHT_LIBRARY} " - "but is not a valid library file. The path will not be used.") - unset(IRRLICHT_LIBRARY CACHE) - endif() -endif() - -# On Windows, find the DLL for installation -if(WIN32) - # If VCPKG_APPLOCAL_DEPS is ON, dll's are automatically handled by VCPKG - if(NOT VCPKG_APPLOCAL_DEPS) - find_file(IRRLICHT_DLL NAMES IrrlichtMt.dll - DOC "Path of the IrrlichtMt dll (for installation)" - ) - endif() -endif(WIN32) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Irrlicht DEFAULT_MSG IRRLICHT_LIBRARY IRRLICHT_INCLUDE_DIR) - diff --git a/cmake/Modules/MinetestFindIrrlichtHeaders.cmake b/cmake/Modules/MinetestFindIrrlichtHeaders.cmake new file mode 100644 index 000000000..d33b296d0 --- /dev/null +++ b/cmake/Modules/MinetestFindIrrlichtHeaders.cmake @@ -0,0 +1,26 @@ +# Locate Irrlicht or IrrlichtMt headers on system. + +foreach(libname IN ITEMS IrrlichtMt Irrlicht) + string(TOLOWER "${libname}" libname2) + + find_path(IRRLICHT_INCLUDE_DIR NAMES irrlicht.h + DOC "Path to the directory with IrrlichtMt includes" + PATHS + /usr/local/include/${libname2} + /usr/include/${libname2} + /system/develop/headers/${libname2} #Haiku + PATH_SUFFIXES "include/${libname2}" + ) + + if(IRRLICHT_INCLUDE_DIR) + break() + endif() +endforeach() + +# Handholding for users +if(IRRLICHT_INCLUDE_DIR AND (NOT IS_DIRECTORY "${IRRLICHT_INCLUDE_DIR}" OR + NOT EXISTS "${IRRLICHT_INCLUDE_DIR}/irrlicht.h")) + message(WARNING "IRRLICHT_INCLUDE_DIR was set to ${IRRLICHT_INCLUDE_DIR} " + "but irrlicht.h does not exist inside. The path will not be used.") + unset(IRRLICHT_INCLUDE_DIR CACHE) +endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ac460883a..7a5e48b49 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -293,33 +293,7 @@ else() if(NOT HAIKU AND NOT APPLE) find_package(X11 REQUIRED) endif(NOT HAIKU AND NOT APPLE) - - ## - # The following dependencies are transitive dependencies from Irrlicht. - # Minetest itself does not use them, but we link them so that statically - # linking Irrlicht works. - if(NOT HAIKU AND NOT APPLE) - # This way Xxf86vm is found on OpenBSD too - find_library(XXF86VM_LIBRARY Xxf86vm) - mark_as_advanced(XXF86VM_LIBRARY) - set(CLIENT_PLATFORM_LIBS ${CLIENT_PLATFORM_LIBS} ${XXF86VM_LIBRARY}) - endif(NOT HAIKU AND NOT APPLE) - - find_package(JPEG REQUIRED) - find_package(PNG REQUIRED) - if(APPLE) - find_library(CARBON_LIB Carbon REQUIRED) - find_library(COCOA_LIB Cocoa REQUIRED) - find_library(IOKIT_LIB IOKit REQUIRED) - mark_as_advanced( - CARBON_LIB - COCOA_LIB - IOKIT_LIB - ) - SET(CLIENT_PLATFORM_LIBS ${CLIENT_PLATFORM_LIBS} ${CARBON_LIB} ${COCOA_LIB} ${IOKIT_LIB}) - endif(APPLE) - ## - endif(BUILD_CLIENT) + endif() find_package(ZLIB REQUIRED) set(PLATFORM_LIBS -lpthread ${CMAKE_DL_LIBS}) @@ -511,9 +485,7 @@ endif() include_directories( ${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR} - ${IRRLICHT_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR} - ${PNG_INCLUDE_DIR} ${SOUND_INCLUDE_DIRS} ${SQLITE3_INCLUDE_DIR} ${LUA_INCLUDE_DIR} @@ -548,10 +520,7 @@ if(BUILD_CLIENT) target_link_libraries( ${PROJECT_NAME} ${ZLIB_LIBRARIES} - ${IRRLICHT_LIBRARY} - ${JPEG_LIBRARIES} - ${BZIP2_LIBRARIES} - ${PNG_LIBRARIES} + IrrlichtMt::IrrlichtMt ${X11_LIBRARIES} ${SOUND_LIBRARIES} ${SQLITE3_LIBRARY} @@ -559,7 +528,6 @@ if(BUILD_CLIENT) ${GMP_LIBRARY} ${JSON_LIBRARY} ${PLATFORM_LIBS} - ${CLIENT_PLATFORM_LIBS} ) if(NOT USE_LUAJIT) set_target_properties(${PROJECT_NAME} PROPERTIES @@ -629,6 +597,11 @@ endif(BUILD_CLIENT) if(BUILD_SERVER) add_executable(${PROJECT_NAME}server ${server_SRCS} ${extra_windows_SRCS}) add_dependencies(${PROJECT_NAME}server GenerateVersion) + + get_target_property( + IRRLICHT_INCLUDES IrrlichtMt::IrrlichtMt INTERFACE_INCLUDE_DIRECTORIES) + # Doesn't work without PRIVATE/PUBLIC/INTERFACE mode specified. + target_include_directories(${PROJECT_NAME}server PRIVATE ${IRRLICHT_INCLUDES}) target_link_libraries( ${PROJECT_NAME}server ${ZLIB_LIBRARIES} diff --git a/util/buildbot/buildwin32.sh b/util/buildbot/buildwin32.sh index 468df05a9..40c205250 100755 --- a/util/buildbot/buildwin32.sh +++ b/util/buildbot/buildwin32.sh @@ -30,7 +30,7 @@ if [ -z "$toolchain_file" ]; then fi echo "Using $toolchain_file" -irrlicht_version=1.9.0mt1 +irrlicht_version=1.9.0mt2 ogg_version=1.3.4 vorbis_version=1.3.7 curl_version=7.76.1 @@ -97,7 +97,7 @@ cd $builddir mkdir build cd build -irr_dlls=$(echo $libdir/irrlicht/bin/*.dll | tr ' ' ';') +irr_dlls=$(echo $libdir/irrlicht/lib/*.dll | tr ' ' ';') vorbis_dlls=$(echo $libdir/libvorbis/bin/libvorbis{,file}-*.dll | tr ' ' ';') gettext_dlls=$(echo $libdir/gettext/bin/lib{intl,iconv}-*.dll | tr ' ' ';') @@ -113,9 +113,7 @@ cmake -S $sourcedir -B . \ -DENABLE_FREETYPE=1 \ -DENABLE_LEVELDB=1 \ \ - -DIRRLICHT_INCLUDE_DIR=$libdir/irrlicht/include/irrlichtmt \ - -DIRRLICHT_LIBRARY=$libdir/irrlicht/lib/libIrrlichtMt.dll.a \ - -DIRRLICHT_DLL="$irr_dlls" \ + -DCMAKE_PREFIX_PATH=$libdir/irrlicht \ \ -DZLIB_INCLUDE_DIR=$libdir/zlib/include \ -DZLIB_LIBRARIES=$libdir/zlib/lib/libz.dll.a \ diff --git a/util/buildbot/buildwin64.sh b/util/buildbot/buildwin64.sh index 3b5d61c96..6d3deceae 100755 --- a/util/buildbot/buildwin64.sh +++ b/util/buildbot/buildwin64.sh @@ -30,7 +30,7 @@ if [ -z "$toolchain_file" ]; then fi echo "Using $toolchain_file" -irrlicht_version=1.9.0mt1 +irrlicht_version=1.9.0mt2 ogg_version=1.3.4 vorbis_version=1.3.7 curl_version=7.76.1 @@ -97,7 +97,7 @@ cd $builddir mkdir build cd build -irr_dlls=$(echo $libdir/irrlicht/bin/*.dll | tr ' ' ';') +irr_dlls=$(echo $libdir/irrlicht/lib/*.dll | tr ' ' ';') vorbis_dlls=$(echo $libdir/libvorbis/bin/libvorbis{,file}-*.dll | tr ' ' ';') gettext_dlls=$(echo $libdir/gettext/bin/lib{intl,iconv}-*.dll | tr ' ' ';') @@ -113,9 +113,7 @@ cmake -S $sourcedir -B . \ -DENABLE_FREETYPE=1 \ -DENABLE_LEVELDB=1 \ \ - -DIRRLICHT_INCLUDE_DIR=$libdir/irrlicht/include/irrlichtmt \ - -DIRRLICHT_LIBRARY=$libdir/irrlicht/lib/libIrrlichtMt.dll.a \ - -DIRRLICHT_DLL="$irr_dlls" \ + -DCMAKE_PREFIX_PATH=$libdir/irrlicht \ \ -DZLIB_INCLUDE_DIR=$libdir/zlib/include \ -DZLIB_LIBRARIES=$libdir/zlib/lib/libz.dll.a \ diff --git a/util/ci/common.sh b/util/ci/common.sh index 6a28482fd..70a1bedaf 100644 --- a/util/ci/common.sh +++ b/util/ci/common.sh @@ -11,9 +11,7 @@ install_linux_deps() { shift pkgs+=(libirrlicht-dev) else - # TODO: return old URL when IrrlichtMt 1.9.0mt2 is tagged - #wget "https://github.com/minetest/irrlicht/releases/download/1.9.0mt1/ubuntu-bionic.tar.gz" - wget "http://minetest.kitsunemimi.pw/irrlichtmt-patched-temporary.tgz" -O ubuntu-bionic.tar.gz + wget "https://github.com/minetest/irrlicht/releases/download/1.9.0mt2/ubuntu-bionic.tar.gz" sudo tar -xaf ubuntu-bionic.tar.gz -C /usr/local fi -- cgit v1.2.3 From 6e8aebf4327ed43ade17cbe8e6cf652edc099651 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 27 Jul 2021 19:11:46 +0200 Subject: Add bold, italic and monospace font styling for HUD text elements (#11478) Co-authored-by: Elias Fleckenstein --- doc/client_lua_api.txt | 2 + doc/lua_api.txt | 3 ++ games/devtest/mods/testhud/init.lua | 81 +++++++++++++++++++++++++++++++++++++ games/devtest/mods/testhud/mod.conf | 2 + src/client/clientevent.h | 2 +- src/client/game.cpp | 3 ++ src/client/hud.cpp | 30 ++++++-------- src/hud.cpp | 1 + src/hud.h | 6 +++ src/network/clientpackethandler.cpp | 5 ++- src/script/common/c_content.cpp | 9 +++++ src/server.cpp | 7 +--- 12 files changed, 127 insertions(+), 24 deletions(-) create mode 100644 games/devtest/mods/testhud/init.lua create mode 100644 games/devtest/mods/testhud/mod.conf (limited to 'src') diff --git a/doc/client_lua_api.txt b/doc/client_lua_api.txt index d239594f7..24d23b0b5 100644 --- a/doc/client_lua_api.txt +++ b/doc/client_lua_api.txt @@ -1314,6 +1314,8 @@ It can be created via `Raycast(pos1, pos2, objects, liquids)` or -- ^ See "HUD Element Types" size = { x=100, y=100 }, -- default {x=0, y=0} -- ^ Size of element in pixels + style = 0, + -- ^ For "text" elements sets font style: bitfield with 1 = bold, 2 = italic, 4 = monospace } ``` diff --git a/doc/lua_api.txt b/doc/lua_api.txt index bb94829a5..7ee9a3f2c 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -8447,6 +8447,9 @@ Used by `Player:hud_add`. Returned by `Player:hud_get`. z_index = 0, -- Z index : lower z-index HUDs are displayed behind higher z-index HUDs + + style = 0, + -- For "text" elements sets font style: bitfield with 1 = bold, 2 = italic, 4 = monospace } Particle definition diff --git a/games/devtest/mods/testhud/init.lua b/games/devtest/mods/testhud/init.lua new file mode 100644 index 000000000..2fa12fd71 --- /dev/null +++ b/games/devtest/mods/testhud/init.lua @@ -0,0 +1,81 @@ +local player_huds = {} + +local states = { + {0, "Normal font"}, + {1, "Bold font"}, + {2, "Italic font"}, + {3, "Bold and italic font"}, + {4, "Monospace font"}, + {5, "Bold and monospace font"}, + {7, "ZOMG all the font styles"}, +} + + +local default_def = { + hud_elem_type = "text", + position = {x = 0.5, y = 0.5}, + scale = {x = 2, y = 2}, + alignment = { x = 0, y = 0 }, +} + +local function add_hud(player, state) + local def = table.copy(default_def) + local statetbl = states[state] + def.offset = {x = 0, y = 32 * state} + def.style = statetbl[1] + def.text = statetbl[2] + return player:hud_add(def) +end + +minetest.register_on_leaveplayer(function(player) + player_huds[player:get_player_name()] = nil +end) + +local etime = 0 +local state = 0 + +minetest.register_globalstep(function(dtime) + etime = etime + dtime + if etime < 1 then + return + end + etime = 0 + for _, player in ipairs(minetest.get_connected_players()) do + local huds = player_huds[player:get_player_name()] + if huds then + for i, hud_id in ipairs(huds) do + local statetbl = states[(state + i) % #states + 1] + player:hud_change(hud_id, "style", statetbl[1]) + player:hud_change(hud_id, "text", statetbl[2]) + end + end + end + state = state + 1 +end) + +minetest.register_chatcommand("hudfonts", { + params = "", + description = "Show/Hide some text on the HUD with various font options", + func = function(name, param) + local player = minetest.get_player_by_name(name) + local param = tonumber(param) or 0 + param = math.min(math.max(param, 1), #states) + if player_huds[name] == nil then + player_huds[name] = {} + for i = 1, param do + table.insert(player_huds[name], add_hud(player, i)) + end + minetest.chat_send_player(name, ("%d HUD element(s) added."):format(param)) + else + local huds = player_huds[name] + if huds then + for _, hud_id in ipairs(huds) do + player:hud_remove(hud_id) + end + minetest.chat_send_player(name, "All HUD elements removed.") + end + player_huds[name] = nil + end + return true + end, +}) diff --git a/games/devtest/mods/testhud/mod.conf b/games/devtest/mods/testhud/mod.conf new file mode 100644 index 000000000..ed9f65c59 --- /dev/null +++ b/games/devtest/mods/testhud/mod.conf @@ -0,0 +1,2 @@ +name = testhud +description = For testing HUD functionality diff --git a/src/client/clientevent.h b/src/client/clientevent.h index 2215aecbd..17d3aedd6 100644 --- a/src/client/clientevent.h +++ b/src/client/clientevent.h @@ -59,7 +59,7 @@ struct ClientEventHudAdd v2f pos, scale; std::string name; std::string text, text2; - u32 number, item, dir; + u32 number, item, dir, style; v2f align, offset; v3f world_pos; v2s32 size; diff --git a/src/client/game.cpp b/src/client/game.cpp index 6fc57c8cc..73ad73e0e 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -2730,6 +2730,7 @@ void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam) e->size = event->hudadd->size; e->z_index = event->hudadd->z_index; e->text2 = event->hudadd->text2; + e->style = event->hudadd->style; m_hud_server_to_client[server_id] = player->addHud(e); delete event->hudadd; @@ -2795,6 +2796,8 @@ void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *ca CASE_SET(HUD_STAT_Z_INDEX, z_index, data); CASE_SET(HUD_STAT_TEXT2, text2, sdata); + + CASE_SET(HUD_STAT_STYLE, style, data); } #undef CASE_SET diff --git a/src/client/hud.cpp b/src/client/hud.cpp index fbfc886d2..c5bf0f2f8 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -331,8 +331,8 @@ bool Hud::calculateScreenPos(const v3s16 &camera_offset, HudElement *e, v2s32 *p void Hud::drawLuaElements(const v3s16 &camera_offset) { - u32 text_height = g_fontengine->getTextHeight(); - irr::gui::IGUIFont* font = g_fontengine->getFont(); + const u32 text_height = g_fontengine->getTextHeight(); + gui::IGUIFont *const font = g_fontengine->getFont(); // Reorder elements by z_index std::vector elems; @@ -356,38 +356,34 @@ void Hud::drawLuaElements(const v3s16 &camera_offset) floor(e->pos.Y * (float) m_screensize.Y + 0.5)); switch (e->type) { case HUD_ELEM_TEXT: { - irr::gui::IGUIFont *textfont = font; unsigned int font_size = g_fontengine->getDefaultFontSize(); if (e->size.X > 0) font_size *= e->size.X; - if (font_size != g_fontengine->getDefaultFontSize()) - textfont = g_fontengine->getFont(font_size); +#ifdef __ANDROID__ + // The text size on Android is not proportional with the actual scaling + // FIXME: why do we have such a weird unportable hack?? + if (font_size > 3 && e->offset.X < -20) + font_size -= 3; +#endif + auto textfont = g_fontengine->getFont(FontSpec(font_size, + (e->style & HUD_STYLE_MONO) ? FM_Mono : FM_Unspecified, + e->style & HUD_STYLE_BOLD, e->style & HUD_STYLE_ITALIC)); video::SColor color(255, (e->number >> 16) & 0xFF, (e->number >> 8) & 0xFF, (e->number >> 0) & 0xFF); std::wstring text = unescape_translate(utf8_to_wide(e->text)); core::dimension2d textsize = textfont->getDimension(text.c_str()); -#ifdef __ANDROID__ - // The text size on Android is not proportional with the actual scaling - irr::gui::IGUIFont *font_scaled = font_size <= 3 ? - textfont : g_fontengine->getFont(font_size - 3); - if (e->offset.X < -20) - textsize = font_scaled->getDimension(text.c_str()); -#endif + v2s32 offset((e->align.X - 1.0) * (textsize.Width / 2), (e->align.Y - 1.0) * (textsize.Height / 2)); core::rect size(0, 0, e->scale.X * m_scale_factor, text_height * e->scale.Y * m_scale_factor); v2s32 offs(e->offset.X * m_scale_factor, e->offset.Y * m_scale_factor); -#ifdef __ANDROID__ - if (e->offset.X < -20) - font_scaled->draw(text.c_str(), size + pos + offset + offs, color); - else -#endif + { textfont->draw(text.c_str(), size + pos + offset + offs, color); } diff --git a/src/hud.cpp b/src/hud.cpp index 1791e04df..e4ad7940f 100644 --- a/src/hud.cpp +++ b/src/hud.cpp @@ -50,6 +50,7 @@ const struct EnumString es_HudElementStat[] = {HUD_STAT_SIZE, "size"}, {HUD_STAT_Z_INDEX, "z_index"}, {HUD_STAT_TEXT2, "text2"}, + {HUD_STAT_STYLE, "style"}, {0, NULL}, }; diff --git a/src/hud.h b/src/hud.h index a0613ae98..769966688 100644 --- a/src/hud.h +++ b/src/hud.h @@ -33,6 +33,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #define HUD_CORNER_LOWER 1 #define HUD_CORNER_CENTER 2 +#define HUD_STYLE_BOLD 1 +#define HUD_STYLE_ITALIC 2 +#define HUD_STYLE_MONO 4 + // Note that these visibility flags do not determine if the hud items are // actually drawn, but rather, whether to draw the item should the rest // of the game state permit it. @@ -78,6 +82,7 @@ enum HudElementStat { HUD_STAT_SIZE, HUD_STAT_Z_INDEX, HUD_STAT_TEXT2, + HUD_STAT_STYLE, }; enum HudCompassDir { @@ -102,6 +107,7 @@ struct HudElement { v2s32 size; s16 z_index = 0; std::string text2; + u32 style; }; extern const EnumString es_HudElementType[]; diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index b86bdac18..50f497959 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -1061,6 +1061,7 @@ void Client::handleCommand_HudAdd(NetworkPacket* pkt) v2s32 size; s16 z_index = 0; std::string text2; + u32 style = 0; *pkt >> server_id >> type >> pos >> name >> scale >> text >> number >> item >> dir >> align >> offset; @@ -1069,6 +1070,7 @@ void Client::handleCommand_HudAdd(NetworkPacket* pkt) *pkt >> size; *pkt >> z_index; *pkt >> text2; + *pkt >> style; } catch(PacketError &e) {}; ClientEvent *event = new ClientEvent(); @@ -1089,6 +1091,7 @@ void Client::handleCommand_HudAdd(NetworkPacket* pkt) event->hudadd->size = size; event->hudadd->z_index = z_index; event->hudadd->text2 = text2; + event->hudadd->style = style; m_client_event_queue.push(event); } @@ -1123,7 +1126,7 @@ void Client::handleCommand_HudChange(NetworkPacket* pkt) *pkt >> sdata; else if (stat == HUD_STAT_WORLD_POS) *pkt >> v3fdata; - else if (stat == HUD_STAT_SIZE ) + else if (stat == HUD_STAT_SIZE) *pkt >> v2s32data; else *pkt >> intdata; diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index a0b45982a..235016be0 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -1928,6 +1928,8 @@ void read_hud_element(lua_State *L, HudElement *elem) elem->world_pos = lua_istable(L, -1) ? read_v3f(L, -1) : v3f(); lua_pop(L, 1); + elem->style = getintfield_default(L, 2, "style", 0); + /* check for known deprecated element usage */ if ((elem->type == HUD_ELEM_STATBAR) && (elem->size == v2s32())) log_deprecated(L,"Deprecated usage of statbar without size!"); @@ -1982,6 +1984,9 @@ void push_hud_element(lua_State *L, HudElement *elem) lua_pushstring(L, elem->text2.c_str()); lua_setfield(L, -2, "text2"); + + lua_pushinteger(L, elem->style); + lua_setfield(L, -2, "style"); } HudElementStat read_hud_change(lua_State *L, HudElement *elem, void **value) @@ -2050,6 +2055,10 @@ HudElementStat read_hud_change(lua_State *L, HudElement *elem, void **value) elem->text2 = luaL_checkstring(L, 4); *value = &elem->text2; break; + case HUD_STAT_STYLE: + elem->style = luaL_checknumber(L, 4); + *value = &elem->style; + break; } return stat; } diff --git a/src/server.cpp b/src/server.cpp index c47596a97..4d2cd8e55 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1638,7 +1638,7 @@ void Server::SendHUDAdd(session_t peer_id, u32 id, HudElement *form) pkt << id << (u8) form->type << form->pos << form->name << form->scale << form->text << form->number << form->item << form->dir << form->align << form->offset << form->world_pos << form->size - << form->z_index << form->text2; + << form->z_index << form->text2 << form->style; Send(&pkt); } @@ -1673,10 +1673,7 @@ void Server::SendHUDChange(session_t peer_id, u32 id, HudElementStat stat, void case HUD_STAT_SIZE: pkt << *(v2s32 *) value; break; - case HUD_STAT_NUMBER: - case HUD_STAT_ITEM: - case HUD_STAT_DIR: - default: + default: // all other types pkt << *(u32 *) value; break; } -- cgit v1.2.3 From 80d12dbedb67191a5eb3e4f3c36b04baed1f8afb Mon Sep 17 00:00:00 2001 From: hecks <42101236+hecktest@users.noreply.github.com> Date: Thu, 29 Jul 2021 05:10:10 +0200 Subject: Add a simple PNG image encoder with Lua API (#11485) * Add a simple PNG image encoder with Lua API Add ColorSpec to RGBA converter Make a safety wrapper for the encoder Create devtest examples Co-authored-by: hecktest <> Co-authored-by: sfan5 --- .gitignore | 1 + builtin/game/misc.lua | 39 ++++++++++++++++ doc/lua_api.txt | 19 +++++++- games/devtest/mods/testnodes/textures.lua | 75 +++++++++++++++++++++++++++++++ src/script/lua_api/l_util.cpp | 43 ++++++++++++++++++ src/script/lua_api/l_util.h | 6 +++ src/util/CMakeLists.txt | 1 + src/util/png.cpp | 68 ++++++++++++++++++++++++++++ src/util/png.h | 27 +++++++++++ 9 files changed, 278 insertions(+), 1 deletion(-) create mode 100755 src/util/png.cpp create mode 100755 src/util/png.h (limited to 'src') diff --git a/.gitignore b/.gitignore index df1386bce..a83a3718f 100644 --- a/.gitignore +++ b/.gitignore @@ -87,6 +87,7 @@ src/test_config.h src/cmake_config.h src/cmake_config_githash.h src/unittest/test_world/world.mt +games/devtest/mods/testnodes/textures/testnodes_generated_*.png /locale/ .directory *.cbp diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua index c13a583f0..cee95dd23 100644 --- a/builtin/game/misc.lua +++ b/builtin/game/misc.lua @@ -290,3 +290,42 @@ function core.dynamic_add_media(filepath, callback) end return true end + + +-- PNG encoder safety wrapper + +local o_encode_png = core.encode_png +function core.encode_png(width, height, data, compression) + if type(width) ~= "number" then + error("Incorrect type for 'width', expected number, got " .. type(width)) + end + if type(height) ~= "number" then + error("Incorrect type for 'height', expected number, got " .. type(height)) + end + + local expected_byte_count = width * height * 4; + + if type(data) ~= "table" and type(data) ~= "string" then + error("Incorrect type for 'height', expected table or string, got " .. type(height)); + end + + local data_length = type(data) == "table" and #data * 4 or string.len(data); + + if data_length ~= expected_byte_count then + error(string.format( + "Incorrect length of 'data', width and height imply %d bytes but %d were provided", + expected_byte_count, + data_length + )) + end + + if type(data) == "table" then + local dataBuf = {} + for i = 1, #data do + dataBuf[i] = core.colorspec_to_bytes(data[i]) + end + data = table.concat(dataBuf) + end + + return o_encode_png(width, height, data, compression or 6) +end diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 7ee9a3f2c..21e34b1ec 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -4611,6 +4611,23 @@ Utilities * `minetest.colorspec_to_colorstring(colorspec)`: Converts a ColorSpec to a ColorString. If the ColorSpec is invalid, returns `nil`. * `colorspec`: The ColorSpec to convert +* `minetest.colorspec_to_bytes(colorspec)`: Converts a ColorSpec to a raw + string of four bytes in an RGBA layout, returned as a string. + * `colorspec`: The ColorSpec to convert +* `minetest.encode_png(width, height, data, [compression])`: Encode a PNG + image and return it in string form. + * `width`: Width of the image + * `height`: Height of the image + * `data`: Image data, one of: + * array table of ColorSpec, length must be width*height + * string with raw RGBA pixels, length must be width*height*4 + * `compression`: Optional zlib compression level, number in range 0 to 9. + The data is one-dimensional, starting in the upper left corner of the image + and laid out in scanlines going from left to right, then top to bottom. + Please note that it's not safe to use string.char to generate raw data, + use `colorspec_to_bytes` to generate raw RGBA values in a predictable way. + The resulting PNG image is always 32-bit. Palettes are not supported at the moment. + You may use this to procedurally generate textures during server init. Logging ------- @@ -7631,7 +7648,7 @@ Used by `minetest.register_node`. leveled_max = 127, -- Maximum value for `leveled` (0-127), enforced in -- `minetest.set_node_level` and `minetest.add_node_level`. - -- Values above 124 might causes collision detection issues. + -- Values above 124 might causes collision detection issues. liquid_range = 8, -- Maximum distance that flowing liquid nodes can spread around diff --git a/games/devtest/mods/testnodes/textures.lua b/games/devtest/mods/testnodes/textures.lua index f6e6a0c2a..4652007d9 100644 --- a/games/devtest/mods/testnodes/textures.lua +++ b/games/devtest/mods/testnodes/textures.lua @@ -65,3 +65,78 @@ for a=1,#alphas do }) end +-- Generate PNG textures + +local function mandelbrot(w, h, iterations) + local r = {} + for y=0, h-1 do + for x=0, w-1 do + local re = (x - w/2) * 4/w + local im = (y - h/2) * 4/h + -- zoom in on a nice view + re = re / 128 - 0.23 + im = im / 128 - 0.82 + + local px, py = 0, 0 + local i = 0 + while px*px + py*py <= 4 and i < iterations do + px, py = px*px - py*py + re, 2 * px * py + im + i = i + 1 + end + r[w*y+x+1] = i / iterations + end + end + return r +end + +local function gen_checkers(w, h, tile) + local r = {} + for y=0, h-1 do + for x=0, w-1 do + local hori = math.floor(x / tile) % 2 == 0 + local vert = math.floor(y / tile) % 2 == 0 + r[w*y+x+1] = hori ~= vert and 1 or 0 + end + end + return r +end + +local fractal = mandelbrot(512, 512, 128) +local checker = gen_checkers(512, 512, 32) + +local floor = math.floor +local abs = math.abs +local data_mb = {} +local data_ck = {} +for i=1, #fractal do + data_mb[i] = { + r = floor(fractal[i] * 255), + g = floor(abs(fractal[i] * 2 - 1) * 255), + b = floor(abs(1 - fractal[i]) * 255), + a = 255, + } + data_ck[i] = checker[i] > 0 and "#F80" or "#000" +end + +local textures_path = minetest.get_modpath( minetest.get_current_modname() ) .. "/textures/" +minetest.safe_file_write( + textures_path .. "testnodes_generated_mb.png", + minetest.encode_png(512,512,data_mb) +) +minetest.safe_file_write( + textures_path .. "testnodes_generated_ck.png", + minetest.encode_png(512,512,data_ck) +) + +minetest.register_node("testnodes:generated_png_mb", { + description = S("Generated Mandelbrot PNG Test Node"), + tiles = { "testnodes_generated_mb.png" }, + + groups = { dig_immediate = 2 }, +}) +minetest.register_node("testnodes:generated_png_ck", { + description = S("Generated Checker PNG Test Node"), + tiles = { "testnodes_generated_ck.png" }, + + groups = { dig_immediate = 2 }, +}) diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index 8de2d67c8..87436fce0 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -40,6 +40,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "version.h" #include "util/hex.h" #include "util/sha1.h" +#include "util/png.h" #include #include @@ -497,6 +498,43 @@ int ModApiUtil::l_colorspec_to_colorstring(lua_State *L) return 0; } +// colorspec_to_bytes(colorspec) +int ModApiUtil::l_colorspec_to_bytes(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + video::SColor color(0); + if (read_color(L, 1, &color)) { + u8 colorbytes[4] = { + (u8) color.getRed(), + (u8) color.getGreen(), + (u8) color.getBlue(), + (u8) color.getAlpha(), + }; + lua_pushlstring(L, (const char*) colorbytes, 4); + return 1; + } + + return 0; +} + +// encode_png(w, h, data, level) +int ModApiUtil::l_encode_png(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + // The args are already pre-validated on the lua side. + u32 width = readParam(L, 1); + u32 height = readParam(L, 2); + const char *data = luaL_checklstring(L, 3, NULL); + s32 compression = readParam(L, 4); + + std::string out = encodePNG((const u8*)data, width, height, compression); + + lua_pushlstring(L, out.data(), out.size()); + return 1; +} + void ModApiUtil::Initialize(lua_State *L, int top) { API_FCT(log); @@ -532,6 +570,9 @@ void ModApiUtil::Initialize(lua_State *L, int top) API_FCT(get_version); API_FCT(sha1); API_FCT(colorspec_to_colorstring); + API_FCT(colorspec_to_bytes); + + API_FCT(encode_png); LuaSettings::create(L, g_settings, g_settings_path); lua_setfield(L, top, "settings"); @@ -557,6 +598,7 @@ void ModApiUtil::InitializeClient(lua_State *L, int top) API_FCT(get_version); API_FCT(sha1); API_FCT(colorspec_to_colorstring); + API_FCT(colorspec_to_bytes); } void ModApiUtil::InitializeAsync(lua_State *L, int top) @@ -585,6 +627,7 @@ void ModApiUtil::InitializeAsync(lua_State *L, int top) API_FCT(get_version); API_FCT(sha1); API_FCT(colorspec_to_colorstring); + API_FCT(colorspec_to_bytes); LuaSettings::create(L, g_settings, g_settings_path); lua_setfield(L, top, "settings"); diff --git a/src/script/lua_api/l_util.h b/src/script/lua_api/l_util.h index 6943a6afb..54d2be619 100644 --- a/src/script/lua_api/l_util.h +++ b/src/script/lua_api/l_util.h @@ -104,6 +104,12 @@ private: // colorspec_to_colorstring(colorspec) static int l_colorspec_to_colorstring(lua_State *L); + // colorspec_to_bytes(colorspec) + static int l_colorspec_to_bytes(lua_State *L); + + // encode_png(w, h, data, level) + static int l_encode_png(lua_State *L); + public: static void Initialize(lua_State *L, int top); static void InitializeAsync(lua_State *L, int top); diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index cd2e468d1..6bc97915f 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -15,4 +15,5 @@ set(UTIL_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/string.cpp ${CMAKE_CURRENT_SOURCE_DIR}/srp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/timetaker.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/png.cpp PARENT_SCOPE) diff --git a/src/util/png.cpp b/src/util/png.cpp new file mode 100755 index 000000000..7ac2e94a1 --- /dev/null +++ b/src/util/png.cpp @@ -0,0 +1,68 @@ +/* +Minetest +Copyright (C) 2021 hecks + +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 "png.h" +#include +#include +#include +#include +#include "util/serialize.h" +#include "serialization.h" +#include "irrlichttypes.h" + +static void writeChunk(std::ostringstream &target, const std::string &chunk_str) +{ + assert(chunk_str.size() >= 4); + assert(chunk_str.size() - 4 < U32_MAX); + writeU32(target, chunk_str.size() - 4); // Write length minus the identifier + target << chunk_str; + writeU32(target, crc32(0,(const u8*)chunk_str.data(), chunk_str.size())); +} + +std::string encodePNG(const u8 *data, u32 width, u32 height, s32 compression) +{ + auto file = std::ostringstream(std::ios::binary); + file << "\x89PNG\r\n\x1a\n"; + + { + auto IHDR = std::ostringstream(std::ios::binary); + IHDR << "IHDR"; + writeU32(IHDR, width); + writeU32(IHDR, height); + // 8 bpp, color type 6 (RGBA) + IHDR.write("\x08\x06\x00\x00\x00", 5); + writeChunk(file, IHDR.str()); + } + + { + auto IDAT = std::ostringstream(std::ios::binary); + IDAT << "IDAT"; + auto scanlines = std::ostringstream(std::ios::binary); + for(u32 i = 0; i < height; i++) { + scanlines.write("\x00", 1); // Null predictor + scanlines.write((const char*) data + width * 4 * i, width * 4); + } + compressZlib(scanlines.str(), IDAT, compression); + writeChunk(file, IDAT.str()); + } + + file.write("\x00\x00\x00\x00IEND\xae\x42\x60\x82", 12); + + return file.str(); +} diff --git a/src/util/png.h b/src/util/png.h new file mode 100755 index 000000000..92387aef0 --- /dev/null +++ b/src/util/png.h @@ -0,0 +1,27 @@ +/* +Minetest +Copyright (C) 2021 hecks + +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 "irrlichttypes.h" + +/* Simple PNG encoder. Encodes an RGBA image with no predictors. + Returns a binary string. */ +std::string encodePNG(const u8 *data, u32 width, u32 height, s32 compression); -- cgit v1.2.3 From 28c98f9fa54ea64d094b530f2f87c4e5e1c19bd6 Mon Sep 17 00:00:00 2001 From: hecktest <> Date: Thu, 29 Jul 2021 21:47:08 +0200 Subject: Remove unsupported extensions from list in tile.cpp --- src/client/tile.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/client/tile.cpp b/src/client/tile.cpp index 96312ea27..15ae5472d 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -81,12 +81,8 @@ static bool replace_ext(std::string &path, const char *ext) std::string getImagePath(std::string path) { // A NULL-ended list of possible image extensions - const char *extensions[] = { - "png", "jpg", "bmp", "tga", - "pcx", "ppm", "psd", "wal", "rgb", - NULL - }; - // If there is no extension, add one + const char *extensions[] = { "png", "jpg", "bmp", NULL }; + // If there is no extension, assume PNG if (removeStringEnd(path, extensions).empty()) path = path + ".png"; // Check paths until something is found to exist -- cgit v1.2.3 From 1e2b6388818fec0d4cdc52f796850bfb7ec3a22e Mon Sep 17 00:00:00 2001 From: hecks <42101236+hecktest@users.noreply.github.com> Date: Thu, 29 Jul 2021 22:36:25 +0200 Subject: Remove unsupported formats from the media enumerator --- src/server.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/server.cpp b/src/server.cpp index 4d2cd8e55..8339faa76 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -2438,10 +2438,9 @@ bool Server::addMediaFile(const std::string &filename, } // If name is not in a supported format, ignore it const char *supported_ext[] = { - ".png", ".jpg", ".bmp", ".tga", - ".pcx", ".ppm", ".psd", ".wal", ".rgb", + ".png", ".jpg", ".bmp", ".ogg", - ".x", ".b3d", ".md2", ".obj", + ".x", ".b3d", ".obj", // Custom translation file format ".tr", NULL -- cgit v1.2.3 From e7cd4cfa25485610c05a906859e8365158a13f69 Mon Sep 17 00:00:00 2001 From: Wuzzy Date: Sat, 31 Jul 2021 17:54:40 +0000 Subject: Fix /emergeblocks crashing in debug builds (#11461) The reason for the bug was an u16 overflow, thus failing the assert. This only happened in Debug build but not in Release builds. --- builtin/settingtypes.txt | 6 +++--- src/emerge.cpp | 29 ++++++++++++----------------- src/emerge.h | 8 ++++---- src/settings.cpp | 9 +++++++++ src/settings.h | 1 + 5 files changed, 29 insertions(+), 24 deletions(-) (limited to 'src') diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 420e9d49c..25a51b888 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -2218,15 +2218,15 @@ chunksize (Chunk size) int 5 enable_mapgen_debug_info (Mapgen debug) bool false # Maximum number of blocks that can be queued for loading. -emergequeue_limit_total (Absolute limit of queued blocks to emerge) int 1024 +emergequeue_limit_total (Absolute limit of queued blocks to emerge) int 1024 1 1000000 # Maximum number of blocks to be queued that are to be loaded from file. # This limit is enforced per player. -emergequeue_limit_diskonly (Per-player limit of queued blocks load from disk) int 128 +emergequeue_limit_diskonly (Per-player limit of queued blocks load from disk) int 128 1 1000000 # Maximum number of blocks to be queued that are to be generated. # This limit is enforced per player. -emergequeue_limit_generate (Per-player limit of queued blocks to generate) int 128 +emergequeue_limit_generate (Per-player limit of queued blocks to generate) int 128 1 1000000 # Number of emerge threads to use. # Value 0: diff --git a/src/emerge.cpp b/src/emerge.cpp index 3a2244d7e..bd1c1726d 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -165,20 +165,17 @@ EmergeManager::EmergeManager(Server *server) if (nthreads < 1) nthreads = 1; - m_qlimit_total = g_settings->getU16("emergequeue_limit_total"); + m_qlimit_total = g_settings->getU32("emergequeue_limit_total"); // FIXME: these fallback values are probably not good - if (!g_settings->getU16NoEx("emergequeue_limit_diskonly", m_qlimit_diskonly)) + if (!g_settings->getU32NoEx("emergequeue_limit_diskonly", m_qlimit_diskonly)) m_qlimit_diskonly = nthreads * 5 + 1; - if (!g_settings->getU16NoEx("emergequeue_limit_generate", m_qlimit_generate)) + if (!g_settings->getU32NoEx("emergequeue_limit_generate", m_qlimit_generate)) m_qlimit_generate = nthreads + 1; // don't trust user input for something very important like this - if (m_qlimit_total < 1) - m_qlimit_total = 1; - if (m_qlimit_diskonly < 1) - m_qlimit_diskonly = 1; - if (m_qlimit_generate < 1) - m_qlimit_generate = 1; + m_qlimit_total = rangelim(m_qlimit_total, 1, 1000000); + m_qlimit_diskonly = rangelim(m_qlimit_diskonly, 1, 1000000); + m_qlimit_generate = rangelim(m_qlimit_generate, 1, 1000000); for (s16 i = 0; i < nthreads; i++) m_threads.push_back(new EmergeThread(server, i)); @@ -425,14 +422,14 @@ bool EmergeManager::pushBlockEmergeData( void *callback_param, bool *entry_already_exists) { - u16 &count_peer = m_peer_queue_count[peer_requested]; + u32 &count_peer = m_peer_queue_count[peer_requested]; if ((flags & BLOCK_EMERGE_FORCE_QUEUE) == 0) { if (m_blocks_enqueued.size() >= m_qlimit_total) return false; if (peer_requested != PEER_ID_INEXISTENT) { - u16 qlimit_peer = (flags & BLOCK_EMERGE_ALLOW_GEN) ? + u32 qlimit_peer = (flags & BLOCK_EMERGE_ALLOW_GEN) ? m_qlimit_generate : m_qlimit_diskonly; if (count_peer >= qlimit_peer) return false; @@ -467,20 +464,18 @@ bool EmergeManager::pushBlockEmergeData( bool EmergeManager::popBlockEmergeData(v3s16 pos, BlockEmergeData *bedata) { - std::map::iterator it; - std::unordered_map::iterator it2; - - it = m_blocks_enqueued.find(pos); + auto it = m_blocks_enqueued.find(pos); if (it == m_blocks_enqueued.end()) return false; *bedata = it->second; - it2 = m_peer_queue_count.find(bedata->peer_requested); + auto it2 = m_peer_queue_count.find(bedata->peer_requested); if (it2 == m_peer_queue_count.end()) return false; - u16 &count_peer = it2->second; + u32 &count_peer = it2->second; + assert(count_peer != 0); count_peer--; diff --git a/src/emerge.h b/src/emerge.h index b060226f8..e2d727973 100644 --- a/src/emerge.h +++ b/src/emerge.h @@ -194,11 +194,11 @@ private: std::mutex m_queue_mutex; std::map m_blocks_enqueued; - std::unordered_map m_peer_queue_count; + std::unordered_map m_peer_queue_count; - u16 m_qlimit_total; - u16 m_qlimit_diskonly; - u16 m_qlimit_generate; + u32 m_qlimit_total; + u32 m_qlimit_diskonly; + u32 m_qlimit_generate; // Managers of various map generation-related components // Note that each Mapgen gets a copy(!) of these to work with diff --git a/src/settings.cpp b/src/settings.cpp index 0a9424994..ba4629a6f 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -755,6 +755,15 @@ bool Settings::getS16NoEx(const std::string &name, s16 &val) const } } +bool Settings::getU32NoEx(const std::string &name, u32 &val) const +{ + try { + val = getU32(name); + return true; + } catch (SettingNotFoundException &e) { + return false; + } +} bool Settings::getS32NoEx(const std::string &name, s32 &val) const { diff --git a/src/settings.h b/src/settings.h index 7791413b9..4e32a3488 100644 --- a/src/settings.h +++ b/src/settings.h @@ -186,6 +186,7 @@ public: bool getFlag(const std::string &name) const; bool getU16NoEx(const std::string &name, u16 &val) const; bool getS16NoEx(const std::string &name, s16 &val) const; + bool getU32NoEx(const std::string &name, u32 &val) const; bool getS32NoEx(const std::string &name, s32 &val) const; bool getU64NoEx(const std::string &name, u64 &val) const; bool getFloatNoEx(const std::string &name, float &val) const; -- cgit v1.2.3 From 32cb9d0828828da3068259c9e0a3c0f5da170439 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Sat, 31 Jul 2021 19:54:52 +0200 Subject: Mods: Combine mod loading checks and deprection logging (#11503) This limits the logged deprecation messages to the mods that are loaded Unifies the mod naming convention check for CSM & SSM --- src/client/client.cpp | 6 +----- src/content/mods.cpp | 44 ++++++++++++++++++++++++++------------------ src/content/mods.h | 5 +++++ src/server/mods.cpp | 8 ++------ 4 files changed, 34 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/client/client.cpp b/src/client/client.cpp index 17661c242..923369afe 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -177,11 +177,7 @@ void Client::loadMods() // Load "mod" scripts for (const ModSpec &mod : m_mods) { - if (!string_allowed(mod.name, MODNAME_ALLOWED_CHARS)) { - throw ModError("Error loading mod \"" + mod.name + - "\": Mod name does not follow naming conventions: " - "Only characters [a-z0-9_] are allowed."); - } + mod.checkAndLog(); scanModIntoMemory(mod.name, mod.path); } diff --git a/src/content/mods.cpp b/src/content/mods.cpp index 434004b29..6f088a5b3 100644 --- a/src/content/mods.cpp +++ b/src/content/mods.cpp @@ -30,6 +30,29 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "convert_json.h" #include "script/common/c_internal.h" +void ModSpec::checkAndLog() const +{ + if (!string_allowed(name, MODNAME_ALLOWED_CHARS)) { + throw ModError("Error loading mod \"" + name + + "\": Mod name does not follow naming conventions: " + "Only characters [a-z0-9_] are allowed."); + } + + // Log deprecation messages + auto handling_mode = get_deprecated_handling_mode(); + if (!deprecation_msgs.empty() && handling_mode != DeprecatedHandlingMode::Ignore) { + std::ostringstream os; + os << "Mod " << name << " at " << path << ":" << std::endl; + for (auto msg : deprecation_msgs) + os << "\t" << msg << std::endl; + + if (handling_mode == DeprecatedHandlingMode::Error) + throw ModError(os.str()); + else + warningstream << os.str(); + } +} + bool parseDependsString(std::string &dep, std::unordered_set &symbols) { dep = trim(dep); @@ -45,21 +68,6 @@ bool parseDependsString(std::string &dep, std::unordered_set &symbols) return !dep.empty(); } -static void log_mod_deprecation(const ModSpec &spec, const std::string &warning) -{ - auto handling_mode = get_deprecated_handling_mode(); - if (handling_mode != DeprecatedHandlingMode::Ignore) { - std::ostringstream os; - os << warning << " (" << spec.name << " at " << spec.path << ")" << std::endl; - - if (handling_mode == DeprecatedHandlingMode::Error) { - throw ModError(os.str()); - } else { - warningstream << os.str(); - } - } -} - void parseModContents(ModSpec &spec) { // NOTE: this function works in mutual recursion with getModsInPath @@ -89,7 +97,7 @@ void parseModContents(ModSpec &spec) if (info.exists("name")) spec.name = info.get("name"); else - log_mod_deprecation(spec, "Mods not having a mod.conf file with the name is deprecated."); + spec.deprecation_msgs.push_back("Mods not having a mod.conf file with the name is deprecated."); if (info.exists("author")) spec.author = info.get("author"); @@ -130,7 +138,7 @@ void parseModContents(ModSpec &spec) std::ifstream is((spec.path + DIR_DELIM + "depends.txt").c_str()); if (is.good()) - log_mod_deprecation(spec, "depends.txt is deprecated, please use mod.conf instead."); + spec.deprecation_msgs.push_back("depends.txt is deprecated, please use mod.conf instead."); while (is.good()) { std::string dep; @@ -153,7 +161,7 @@ void parseModContents(ModSpec &spec) if (info.exists("description")) spec.desc = info.get("description"); else if (fs::ReadFile(spec.path + DIR_DELIM + "description.txt", spec.desc)) - log_mod_deprecation(spec, "description.txt is deprecated, please use mod.conf instead."); + spec.deprecation_msgs.push_back("description.txt is deprecated, please use mod.conf instead."); } } diff --git a/src/content/mods.h b/src/content/mods.h index b3500fbc8..b56a97edb 100644 --- a/src/content/mods.h +++ b/src/content/mods.h @@ -49,6 +49,9 @@ struct ModSpec bool part_of_modpack = false; bool is_modpack = false; + // For logging purposes + std::vector deprecation_msgs; + // if modpack: std::map modpack_content; ModSpec(const std::string &name = "", const std::string &path = "") : @@ -59,6 +62,8 @@ struct ModSpec name(name), path(path), part_of_modpack(part_of_modpack) { } + + void checkAndLog() const; }; // Retrieves depends, optdepends, is_modpack and modpack_content diff --git a/src/server/mods.cpp b/src/server/mods.cpp index 83fa12da9..609d8c346 100644 --- a/src/server/mods.cpp +++ b/src/server/mods.cpp @@ -61,12 +61,8 @@ void ServerModManager::loadMods(ServerScripting *script) infostream << std::endl; // Load and run "mod" scripts for (const ModSpec &mod : m_sorted_mods) { - if (!string_allowed(mod.name, MODNAME_ALLOWED_CHARS)) { - throw ModError("Error loading mod \"" + mod.name + - "\": Mod name does not follow naming " - "conventions: " - "Only characters [a-z0-9_] are allowed."); - } + mod.checkAndLog(); + std::string script_path = mod.path + DIR_DELIM + "init.lua"; auto t = porting::getTimeMs(); script->loadMod(script_path, mod.name); -- cgit v1.2.3 From 4a3728d828fa8896b49e80fdc68f5d7647bf45b7 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Tue, 3 Aug 2021 20:26:00 +0200 Subject: OpenAL: Free buffers on quit --- src/client/sound_openal.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'src') diff --git a/src/client/sound_openal.cpp b/src/client/sound_openal.cpp index 8dceeede6..0eda8842b 100644 --- a/src/client/sound_openal.cpp +++ b/src/client/sound_openal.cpp @@ -362,6 +362,14 @@ public: for (auto &buffer : m_buffers) { for (SoundBuffer *sb : buffer.second) { + alDeleteBuffers(1, &sb->buffer_id); + + ALenum error = alGetError(); + if (error != AL_NO_ERROR) { + warningstream << "Audio: Failed to free stream for " + << buffer.first << ": " << alErrorString(error) << std::endl; + } + delete sb; } buffer.second.clear(); -- cgit v1.2.3 From c6eddb0bae32c43ffff46e9c1e3f293d0fd9ed73 Mon Sep 17 00:00:00 2001 From: Pevernow <3450354617@qq.com> Date: Mon, 9 Aug 2021 00:59:07 +0800 Subject: Gettext support on Android (#11435) Co-authored-by: sfan5 Co-authored-by: =?UTF-8?q?Olivier=20Samyn=20=F0=9F=8E=BB?= --- .github/workflows/android.yml | 2 ++ android/app/build.gradle | 11 +++++++---- android/native/jni/Android.mk | 9 ++++++++- src/gettext.cpp | 4 ++++ src/gui/modalMenu.cpp | 2 +- src/porting_android.cpp | 1 + 6 files changed, 23 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 47ab64d11..660b5c8df 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -28,6 +28,8 @@ jobs: uses: actions/setup-java@v1 with: java-version: 1.8 + - name: Install deps + run: sudo apt-get update; sudo apt-get install -y --no-install-recommends gettext - name: Build with Gradle run: cd android; ./gradlew assemblerelease - name: Save armeabi artifact diff --git a/android/app/build.gradle b/android/app/build.gradle index b7d93ef0f..53fe85910 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -76,10 +76,13 @@ task prepareAssets() { copy { from "${projRoot}/games/${gameToCopy}" into "${assetsFolder}/games/${gameToCopy}" } - /*copy { - // ToDo: fix broken locales - from "${projRoot}/po" into "${assetsFolder}/po" - }*/ + fileTree("${projRoot}/po").include("**/*.po").forEach { poFile -> + def moPath = "${assetsFolder}/locale/${poFile.parentFile.name}/LC_MESSAGES/" + file(moPath).mkdirs() + exec { + commandLine 'msgfmt', '-o', "${moPath}/minetest.mo", poFile + } + } copy { from "${projRoot}/textures" into "${assetsFolder}/textures" } diff --git a/android/native/jni/Android.mk b/android/native/jni/Android.mk index 5039f325e..f92ac1d60 100644 --- a/android/native/jni/Android.mk +++ b/android/native/jni/Android.mk @@ -47,6 +47,11 @@ LOCAL_MODULE := OpenAL LOCAL_SRC_FILES := deps/Android/OpenAL-Soft/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libopenal.a include $(PREBUILT_STATIC_LIBRARY) +include $(CLEAR_VARS) +LOCAL_MODULE := GetText +LOCAL_SRC_FILES := deps/Android/GetText/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libintl.a +include $(PREBUILT_STATIC_LIBRARY) + include $(CLEAR_VARS) LOCAL_MODULE := Vorbis LOCAL_SRC_FILES := deps/Android/Vorbis/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libvorbis.a @@ -64,6 +69,7 @@ LOCAL_CFLAGS += \ -DUSE_FREETYPE=1 \ -DUSE_LEVELDB=0 \ -DUSE_LUAJIT=1 \ + -DUSE_GETTEXT=1 \ -DVERSION_MAJOR=${versionMajor} \ -DVERSION_MINOR=${versionMinor} \ -DVERSION_PATCH=${versionPatch} \ @@ -89,6 +95,7 @@ LOCAL_C_INCLUDES := \ deps/Android/Freetype/include \ deps/Android/Irrlicht/include \ deps/Android/LevelDB/include \ + deps/Android/GetText/include \ deps/Android/libiconv/include \ deps/Android/libiconv/libcharset/include \ deps/Android/LuaJIT/src \ @@ -194,7 +201,7 @@ LOCAL_SRC_FILES += \ # SQLite3 LOCAL_SRC_FILES += deps/Android/sqlite/sqlite3.c -LOCAL_STATIC_LIBRARIES += Curl Freetype Irrlicht OpenAL mbedTLS mbedx509 mbedcrypto Vorbis LuaJIT android_native_app_glue $(PROFILER_LIBS) #LevelDB +LOCAL_STATIC_LIBRARIES += Curl Freetype Irrlicht OpenAL mbedTLS mbedx509 mbedcrypto Vorbis LuaJIT GetText android_native_app_glue $(PROFILER_LIBS) #LevelDB LOCAL_LDLIBS := -lEGL -lGLESv1_CM -lGLESv2 -landroid -lOpenSLES diff --git a/src/gettext.cpp b/src/gettext.cpp index 6818004df..de042cf35 100644 --- a/src/gettext.cpp +++ b/src/gettext.cpp @@ -127,6 +127,10 @@ void init_gettext(const char *path, const std::string &configured_language, // Add user specified locale to environment setenv("LANGUAGE", configured_language.c_str(), 1); +#ifdef __ANDROID__ + setenv("LANG", configured_language.c_str(), 1); +#endif + // Reload locale with changed environment setlocale(LC_ALL, ""); #elif defined(_MSC_VER) diff --git a/src/gui/modalMenu.cpp b/src/gui/modalMenu.cpp index 0d3fb55f0..1016de389 100644 --- a/src/gui/modalMenu.cpp +++ b/src/gui/modalMenu.cpp @@ -268,7 +268,7 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event) std::string label = wide_to_utf8(getLabelByID(hovered->getID())); if (label.empty()) label = "text"; - message += gettext(label) + ":"; + message += strgettext(label) + ":"; // single line text input int type = 2; diff --git a/src/porting_android.cpp b/src/porting_android.cpp index f5870c174..29e95b8ca 100644 --- a/src/porting_android.cpp +++ b/src/porting_android.cpp @@ -190,6 +190,7 @@ void initializePathsAndroid() path_user = path_storage + DIR_DELIM + PROJECT_NAME_C; path_share = path_storage + DIR_DELIM + PROJECT_NAME_C; + path_locale = path_share + DIR_DELIM + "locale"; path_cache = getAndroidPath(nativeActivity, app_global->activity->clazz, mt_getAbsPath, "getCacheDir"); migrateCachePath(); -- cgit v1.2.3 From 1ab29f1716e51bccd405e6f6e04bad64712cc018 Mon Sep 17 00:00:00 2001 From: DS Date: Sun, 8 Aug 2021 18:59:45 +0200 Subject: Fix GUIEditBoxWithScrollBar using a smaller steps than intlGUIEditBox (#11519) --- src/gui/guiEditBoxWithScrollbar.cpp | 15 +++++++++++++-- util/ci/clang-format-whitelist.txt | 2 -- 2 files changed, 13 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/gui/guiEditBoxWithScrollbar.cpp b/src/gui/guiEditBoxWithScrollbar.cpp index c72070787..fb4bc2a0b 100644 --- a/src/gui/guiEditBoxWithScrollbar.cpp +++ b/src/gui/guiEditBoxWithScrollbar.cpp @@ -620,6 +620,17 @@ void GUIEditBoxWithScrollBar::createVScrollBar() if (Environment) skin = Environment->getSkin(); + s32 fontHeight = 1; + + if (m_override_font) { + fontHeight = m_override_font->getDimension(L"Ay").Height; + } else { + IGUIFont *font; + if (skin && (font = skin->getFont())) { + fontHeight = font->getDimension(L"Ay").Height; + } + } + m_scrollbar_width = skin ? skin->getSize(gui::EGDS_SCROLLBAR_SIZE) : 16; irr::core::rect scrollbarrect = m_frame_rect; @@ -628,8 +639,8 @@ void GUIEditBoxWithScrollBar::createVScrollBar() scrollbarrect, false, true); m_vscrollbar->setVisible(false); - m_vscrollbar->setSmallStep(1); - m_vscrollbar->setLargeStep(1); + m_vscrollbar->setSmallStep(3 * fontHeight); + m_vscrollbar->setLargeStep(10 * fontHeight); } diff --git a/util/ci/clang-format-whitelist.txt b/util/ci/clang-format-whitelist.txt index 75d99f4cd..5cbc262ef 100644 --- a/util/ci/clang-format-whitelist.txt +++ b/util/ci/clang-format-whitelist.txt @@ -192,8 +192,6 @@ src/gui/guiTable.cpp src/gui/guiTable.h src/gui/guiVolumeChange.cpp src/gui/guiVolumeChange.h -src/gui/intlGUIEditBox.cpp -src/gui/intlGUIEditBox.h src/gui/mainmenumanager.h src/gui/modalMenu.h src/guiscalingfilter.cpp -- cgit v1.2.3 From 0709946c75ae6f2257d368714185bed7bee538ba Mon Sep 17 00:00:00 2001 From: DS Date: Thu, 12 Aug 2021 20:06:18 +0200 Subject: Fix a segfault caused by wrong textdomain lines in translation files (#11530) * The problem were lines like these: "# textdomain:" * str_split does not add an empty last part if there is a delimiter at the end, but this was probably assumed here. --- src/translation.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/translation.cpp b/src/translation.cpp index 55c958fa2..1e43b0894 100644 --- a/src/translation.cpp +++ b/src/translation.cpp @@ -64,7 +64,13 @@ void Translations::loadTranslation(const std::string &data) line.resize(line.length() - 1); if (str_starts_with(line, "# textdomain:")) { - textdomain = utf8_to_wide(trim(str_split(line, ':')[1])); + auto parts = str_split(line, ':'); + if (parts.size() < 2) { + errorstream << "Invalid textdomain translation line \"" << line + << "\"" << std::endl; + continue; + } + textdomain = utf8_to_wide(trim(parts[1])); } if (line.empty() || line[0] == '#') continue; -- cgit v1.2.3 From 442e48b84fea511badf108cedc2a6b43d79fd852 Mon Sep 17 00:00:00 2001 From: x2048 Date: Thu, 12 Aug 2021 20:07:09 +0200 Subject: Move updating shadows outside of RenderingCore::drawAll. (#11491) Fixes indirect rendering modes such as some 3D modes mentioned in #11437 and undersampled rendering. Does not fully fix anaglyph 3d mode. --- src/client/render/core.cpp | 13 +++--- src/client/shadows/dynamicshadowsrender.cpp | 61 ++++++++++++--------------- src/client/shadows/dynamicshadowsrender.h | 4 +- src/client/shadows/shadowsScreenQuad.cpp | 12 ++---- src/client/shadows/shadowsScreenQuad.h | 11 ++++- src/client/shadows/shadowsshadercallbacks.cpp | 16 ++----- src/client/shadows/shadowsshadercallbacks.h | 14 ++++++ 7 files changed, 65 insertions(+), 66 deletions(-) (limited to 'src') diff --git a/src/client/render/core.cpp b/src/client/render/core.cpp index 4a820f583..f151832f3 100644 --- a/src/client/render/core.cpp +++ b/src/client/render/core.cpp @@ -76,19 +76,18 @@ void RenderingCore::draw(video::SColor _skycolor, bool _show_hud, bool _show_min draw_wield_tool = _draw_wield_tool; draw_crosshair = _draw_crosshair; + if (shadow_renderer) + shadow_renderer->update(); + beforeDraw(); drawAll(); } void RenderingCore::draw3D() { - if (shadow_renderer) { - // Shadow renderer will handle the draw stage - shadow_renderer->setClearColor(skycolor); - shadow_renderer->update(); - } else { - smgr->drawAll(); - } + smgr->drawAll(); + if (shadow_renderer) + shadow_renderer->drawDebug(); driver->setTransform(video::ETS_WORLD, core::IdentityMatrix); if (!show_hud) diff --git a/src/client/shadows/dynamicshadowsrender.cpp b/src/client/shadows/dynamicshadowsrender.cpp index 350586225..a913a9290 100644 --- a/src/client/shadows/dynamicshadowsrender.cpp +++ b/src/client/shadows/dynamicshadowsrender.cpp @@ -146,11 +146,6 @@ void ShadowRenderer::removeNodeFromShadowList(scene::ISceneNode *node) } } -void ShadowRenderer::setClearColor(video::SColor ClearColor) -{ - m_clear_color = ClearColor; -} - void ShadowRenderer::updateSMTextures() { if (!m_shadows_enabled || m_smgr->getActiveCamera() == nullptr) { @@ -242,6 +237,7 @@ void ShadowRenderer::updateSMTextures() // This is also handled in ClientMap. if (m_current_frame == m_map_shadow_update_frames - 1) { if (m_shadow_map_colored) { + m_driver->setRenderTarget(0, false, false); m_driver->setRenderTarget(shadowMapTextureColors, true, false, video::SColor(255, 255, 255, 255)); } @@ -273,7 +269,6 @@ void ShadowRenderer::updateSMTextures() void ShadowRenderer::update(video::ITexture *outputTarget) { if (!m_shadows_enabled || m_smgr->getActiveCamera() == nullptr) { - m_smgr->drawAll(); return; } @@ -308,36 +303,34 @@ void ShadowRenderer::update(video::ITexture *outputTarget) m_driver->setRenderTarget(0, false, false); } // end for lights + } +} - // now render the actual MT render pass - m_driver->setRenderTarget(outputTarget, true, true, m_clear_color); - m_smgr->drawAll(); - - /* 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())); - - 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 (m_shadow_map_colored) { - - m_driver->draw2DImage(shadowMapTextureColors, - core::rect(128,128 + 50 + 128 + 128, - 128 + 128, 128 + 50 + 128 + 128 + 128), - core::rect({0, 0}, shadowMapTextureColors->getSize())); - } - #endif - m_driver->setRenderTarget(0, false, false); +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())); + + 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 (m_shadow_map_colored) { + + m_driver->draw2DImage(shadowMapTextureColors, + core::rect(128,128 + 50 + 128 + 128, + 128 + 128, 128 + 50 + 128 + 128 + 128), + core::rect({0, 0}, shadowMapTextureColors->getSize())); } + #endif } diff --git a/src/client/shadows/dynamicshadowsrender.h b/src/client/shadows/dynamicshadowsrender.h index 52b24a18f..e4b3c3e22 100644 --- a/src/client/shadows/dynamicshadowsrender.h +++ b/src/client/shadows/dynamicshadowsrender.h @@ -73,9 +73,8 @@ public: E_SHADOW_MODE shadowMode = ESM_BOTH); void removeNodeFromShadowList(scene::ISceneNode *node); - void setClearColor(video::SColor ClearColor); - void update(video::ITexture *outputTarget = nullptr); + void drawDebug(); video::ITexture *get_texture() { @@ -112,7 +111,6 @@ private: video::ITexture *shadowMapTextureFinal{nullptr}; video::ITexture *shadowMapTextureDynamicObjects{nullptr}; video::ITexture *shadowMapTextureColors{nullptr}; - video::SColor m_clear_color{0x0}; std::vector m_light_list; std::vector m_shadow_node_array; diff --git a/src/client/shadows/shadowsScreenQuad.cpp b/src/client/shadows/shadowsScreenQuad.cpp index c36ee0d60..5f6d38157 100644 --- a/src/client/shadows/shadowsScreenQuad.cpp +++ b/src/client/shadows/shadowsScreenQuad.cpp @@ -51,17 +51,11 @@ void shadowScreenQuadCB::OnSetConstants( video::IMaterialRendererServices *services, s32 userData) { s32 TextureId = 0; - services->setPixelShaderConstant( - services->getPixelShaderConstantID("ShadowMapClientMap"), - &TextureId, 1); + m_sm_client_map_setting.set(&TextureId, services); TextureId = 1; - services->setPixelShaderConstant( - services->getPixelShaderConstantID("ShadowMapClientMapTraslucent"), - &TextureId, 1); + m_sm_client_map_trans_setting.set(&TextureId, services); TextureId = 2; - services->setPixelShaderConstant( - services->getPixelShaderConstantID("ShadowMapSamplerdynamic"), - &TextureId, 1); + m_sm_dynamic_sampler_setting.set(&TextureId, services); } diff --git a/src/client/shadows/shadowsScreenQuad.h b/src/client/shadows/shadowsScreenQuad.h index e6cc95957..c18be9a2b 100644 --- a/src/client/shadows/shadowsScreenQuad.h +++ b/src/client/shadows/shadowsScreenQuad.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_extrabloated.h" #include #include +#include "client/shader.h" class shadowScreenQuad { @@ -38,8 +39,16 @@ private: class shadowScreenQuadCB : public video::IShaderConstantSetCallBack { public: - shadowScreenQuadCB(){}; + shadowScreenQuadCB() : + m_sm_client_map_setting("ShadowMapClientMap"), + m_sm_client_map_trans_setting("ShadowMapClientMapTraslucent"), + m_sm_dynamic_sampler_setting("ShadowMapSamplerdynamic") + {} virtual void OnSetConstants(video::IMaterialRendererServices *services, s32 userData); +private: + CachedPixelShaderSetting m_sm_client_map_setting; + CachedPixelShaderSetting m_sm_client_map_trans_setting; + CachedPixelShaderSetting m_sm_dynamic_sampler_setting; }; diff --git a/src/client/shadows/shadowsshadercallbacks.cpp b/src/client/shadows/shadowsshadercallbacks.cpp index 2f5797084..65a63f49c 100644 --- a/src/client/shadows/shadowsshadercallbacks.cpp +++ b/src/client/shadows/shadowsshadercallbacks.cpp @@ -28,17 +28,9 @@ void ShadowDepthShaderCB::OnSetConstants( lightMVP *= driver->getTransform(video::ETS_VIEW); lightMVP *= driver->getTransform(video::ETS_WORLD); - services->setVertexShaderConstant( - services->getPixelShaderConstantID("LightMVP"), - lightMVP.pointer(), 16); - - services->setVertexShaderConstant( - services->getPixelShaderConstantID("MapResolution"), &MapRes, 1); - services->setVertexShaderConstant( - services->getPixelShaderConstantID("MaxFar"), &MaxFar, 1); - + m_light_mvp_setting.set(lightMVP.pointer(), services); + m_map_resolution_setting.set(&MapRes, services); + m_max_far_setting.set(&MaxFar, services); s32 TextureId = 0; - services->setPixelShaderConstant( - services->getPixelShaderConstantID("ColorMapSampler"), &TextureId, - 1); + m_color_map_sampler_setting.set(&TextureId, services); } diff --git a/src/client/shadows/shadowsshadercallbacks.h b/src/client/shadows/shadowsshadercallbacks.h index 43ad489f2..3549567c3 100644 --- a/src/client/shadows/shadowsshadercallbacks.h +++ b/src/client/shadows/shadowsshadercallbacks.h @@ -21,14 +21,28 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_extrabloated.h" #include #include +#include "client/shader.h" class ShadowDepthShaderCB : public video::IShaderConstantSetCallBack { public: + ShadowDepthShaderCB() : + m_light_mvp_setting("LightMVP"), + m_map_resolution_setting("MapResolution"), + m_max_far_setting("MaxFar"), + m_color_map_sampler_setting("ColorMapSampler") + {} + void OnSetMaterial(const video::SMaterial &material) override {} void OnSetConstants(video::IMaterialRendererServices *services, s32 userData) override; f32 MaxFar{2048.0f}, MapRes{1024.0f}; + +private: + CachedVertexShaderSetting m_light_mvp_setting; + CachedVertexShaderSetting m_map_resolution_setting; + CachedVertexShaderSetting m_max_far_setting; + CachedPixelShaderSetting m_color_map_sampler_setting; }; -- cgit v1.2.3 From b3b075ea02034306256b486dd45410aa765f035a Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Thu, 12 Aug 2021 20:03:25 +0200 Subject: Fix segfault caused by shadow map on exit --- src/client/wieldmesh.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/wieldmesh.cpp b/src/client/wieldmesh.cpp index 7597aaa88..6beed3f3a 100644 --- a/src/client/wieldmesh.cpp +++ b/src/client/wieldmesh.cpp @@ -229,9 +229,9 @@ WieldMeshSceneNode::~WieldMeshSceneNode() { sanity_check(g_extrusion_mesh_cache); - // Remove node from shadow casters - if (m_shadow) - m_shadow->removeNodeFromShadowList(m_meshnode); + // Remove node from shadow casters. m_shadow might be an invalid pointer! + if (auto shadow = RenderingEngine::get_shadow_renderer()) + shadow->removeNodeFromShadowList(m_meshnode); if (g_extrusion_mesh_cache->drop()) g_extrusion_mesh_cache = nullptr; -- cgit v1.2.3 From 963fbd1572e805ae5205381ba6cb372699d6bd30 Mon Sep 17 00:00:00 2001 From: Treer Date: Tue, 17 Aug 2021 01:55:35 +1000 Subject: Fix access violation in create_schematic() (#11534) fixes #11533 Schematics saved from y locations greater than 0 would cause an access violation if layer probabilities were specified --- src/mapgen/mg_schematic.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/mapgen/mg_schematic.cpp b/src/mapgen/mg_schematic.cpp index 653bad4fe..848a43626 100644 --- a/src/mapgen/mg_schematic.cpp +++ b/src/mapgen/mg_schematic.cpp @@ -598,8 +598,9 @@ void Schematic::applyProbabilities(v3s16 p0, } for (size_t i = 0; i != splist->size(); i++) { - s16 y = (*splist)[i].first - p0.Y; - slice_probs[y] = (*splist)[i].second; + s16 slice = (*splist)[i].first; + if (slice < size.Y) + slice_probs[slice] = (*splist)[i].second; } } -- cgit v1.2.3 From 4419e311a96821d12e64073f342877327c655dca Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 15 Aug 2021 14:46:45 +0200 Subject: Cap iterations of imageCleanTransparent sanely fixes #11513 performance regression with 256x textures --- src/client/imagefilters.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/imagefilters.cpp b/src/client/imagefilters.cpp index 7b2ef9822..9c7d0035e 100644 --- a/src/client/imagefilters.cpp +++ b/src/client/imagefilters.cpp @@ -95,9 +95,14 @@ void imageCleanTransparent(video::IImage *src, u32 threshold) Bitmap newmap = bitmap; + // Cap iterations to keep runtime reasonable, for higher-res textures we can + // get away with filling less pixels. + int iter_max = 11 - std::max(dim.Width, dim.Height) / 16; + iter_max = std::max(iter_max, 2); + // Then repeatedly look for transparent pixels, filling them in until - // we're finished (capped at 50 iterations). - for (u32 iter = 0; iter < 50; iter++) { + // we're finished. + for (u32 iter = 0; iter < iter_max; iter++) { for (u32 ctry = 0; ctry < dim.Height; ctry++) for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) { -- cgit v1.2.3 From 328d9492256e600159d2e04987e493b3a269a067 Mon Sep 17 00:00:00 2001 From: Lean Rada Date: Mon, 16 Aug 2021 23:56:38 +0800 Subject: Start sprite animation at the beginning (#11509) When setting a sprite animation, do not keep the last animation's frame number. Setting a new animation should start the animation at the start of the new animation. --- src/client/content_cao.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index c3ac613a5..83c8e15d4 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -1722,6 +1722,7 @@ void GenericCAO::processMessage(const std::string &data) m_tx_basepos = p; m_anim_num_frames = num_frames; + m_anim_frame = 0; m_anim_framelength = framelength; m_tx_select_horiz_by_yawpitch = select_horiz_by_yawpitch; -- cgit v1.2.3 From 3b842a7e021f61c119f060df5de035b71df1fe83 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Tue, 17 Aug 2021 20:00:47 +0200 Subject: Fix inconsistent integer comparison warnings --- src/client/imagefilters.cpp | 2 +- src/settings.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/imagefilters.cpp b/src/client/imagefilters.cpp index 9c7d0035e..97ad094e5 100644 --- a/src/client/imagefilters.cpp +++ b/src/client/imagefilters.cpp @@ -102,7 +102,7 @@ void imageCleanTransparent(video::IImage *src, u32 threshold) // Then repeatedly look for transparent pixels, filling them in until // we're finished. - for (u32 iter = 0; iter < iter_max; iter++) { + for (int iter = 0; iter < iter_max; iter++) { for (u32 ctry = 0; ctry < dim.Height; ctry++) for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) { diff --git a/src/settings.cpp b/src/settings.cpp index ba4629a6f..4def46112 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -49,7 +49,7 @@ SettingsHierarchy::SettingsHierarchy(Settings *fallback) Settings *SettingsHierarchy::getLayer(int layer) const { - if (layer < 0 || layer >= layers.size()) + if (layer < 0 || layer >= (int)layers.size()) throw BaseException("Invalid settings layer"); return layers[layer]; } @@ -57,7 +57,7 @@ Settings *SettingsHierarchy::getLayer(int layer) const Settings *SettingsHierarchy::getParent(int layer) const { - assert(layer >= 0 && layer < layers.size()); + assert(layer >= 0 && layer < (int)layers.size()); // iterate towards the origin (0) to find the next fallback layer for (int i = layer - 1; i >= 0; --i) { if (layers[i]) @@ -72,8 +72,8 @@ void SettingsHierarchy::onLayerCreated(int layer, Settings *obj) { if (layer < 0) throw BaseException("Invalid settings layer"); - if (layers.size() < layer+1) - layers.resize(layer+1); + if ((int)layers.size() < layer + 1) + layers.resize(layer + 1); Settings *&pos = layers[layer]; if (pos) -- cgit v1.2.3 From 24b66dede00c8a5336adc6c1fafc837ee688c9ad Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Thu, 19 Aug 2021 19:13:25 +0100 Subject: Add fwgettext util function --- src/client/client.cpp | 2 +- src/client/game.cpp | 43 ++++++++++++------------------------------- src/client/gameui.cpp | 9 +++------ src/gettext.h | 18 ++++++++++++++++++ util/updatepo.sh | 1 + 5 files changed, 35 insertions(+), 38 deletions(-) (limited to 'src') diff --git a/src/client/client.cpp b/src/client/client.cpp index 923369afe..3c5559fca 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -1735,7 +1735,7 @@ void Client::afterContentReceived() tu_args.guienv = guienv; tu_args.last_time_ms = porting::getTimeMs(); tu_args.last_percent = 0; - tu_args.text_base = wgettext("Initializing nodes"); + tu_args.text_base = wgettext("Initializing nodes"); tu_args.tsrc = m_tsrc; m_nodedef->updateTextures(this, &tu_args); delete[] tu_args.text_base; diff --git a/src/client/game.cpp b/src/client/game.cpp index 73ad73e0e..90bfab2a3 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -1927,24 +1927,18 @@ void Game::processKeyInput() } else if (wasKeyDown(KeyType::INC_VOLUME)) { if (g_settings->getBool("enable_sound")) { float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f); - wchar_t buf[100]; g_settings->setFloat("sound_volume", new_volume); - const wchar_t *str = wgettext("Volume changed to %d%%"); - swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100)); - delete[] str; - m_game_ui->showStatusText(buf); + std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100)); + m_game_ui->showStatusText(msg); } else { m_game_ui->showTranslatedStatusText("Sound system is disabled"); } } else if (wasKeyDown(KeyType::DEC_VOLUME)) { if (g_settings->getBool("enable_sound")) { float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f); - wchar_t buf[100]; g_settings->setFloat("sound_volume", new_volume); - const wchar_t *str = wgettext("Volume changed to %d%%"); - swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100)); - delete[] str; - m_game_ui->showStatusText(buf); + std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100)); + m_game_ui->showStatusText(msg); } else { m_game_ui->showTranslatedStatusText("Sound system is disabled"); } @@ -2329,20 +2323,13 @@ void Game::increaseViewRange() s16 range = g_settings->getS16("viewing_range"); s16 range_new = range + 10; - wchar_t buf[255]; - const wchar_t *str; if (range_new > 4000) { range_new = 4000; - str = wgettext("Viewing range is at maximum: %d"); - swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new); - delete[] str; - m_game_ui->showStatusText(buf); - + std::wstring msg = fwgettext("Viewing range is at maximum: %d", range_new); + m_game_ui->showStatusText(msg); } else { - str = wgettext("Viewing range changed to %d"); - swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new); - delete[] str; - m_game_ui->showStatusText(buf); + std::wstring msg = fwgettext("Viewing range changed to %d", range_new); + m_game_ui->showStatusText(msg); } g_settings->set("viewing_range", itos(range_new)); } @@ -2353,19 +2340,13 @@ void Game::decreaseViewRange() s16 range = g_settings->getS16("viewing_range"); s16 range_new = range - 10; - wchar_t buf[255]; - const wchar_t *str; if (range_new < 20) { range_new = 20; - str = wgettext("Viewing range is at minimum: %d"); - swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new); - delete[] str; - m_game_ui->showStatusText(buf); + std::wstring msg = fwgettext("Viewing range is at minimum: %d", range_new); + m_game_ui->showStatusText(msg); } else { - str = wgettext("Viewing range changed to %d"); - swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new); - delete[] str; - m_game_ui->showStatusText(buf); + std::wstring msg = fwgettext("Viewing range changed to %d", range_new); + m_game_ui->showStatusText(msg); } g_settings->set("viewing_range", itos(range_new)); } diff --git a/src/client/gameui.cpp b/src/client/gameui.cpp index 323967550..028052fe6 100644 --- a/src/client/gameui.cpp +++ b/src/client/gameui.cpp @@ -299,12 +299,9 @@ void GameUI::toggleProfiler() updateProfiler(); if (m_profiler_current_page != 0) { - wchar_t buf[255]; - const wchar_t* str = wgettext("Profiler shown (page %d of %d)"); - swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, - m_profiler_current_page, m_profiler_max_page); - delete[] str; - showStatusText(buf); + std::wstring msg = fwgettext("Profiler shown (page %d of %d)", + m_profiler_current_page, m_profiler_max_page); + showStatusText(msg); } else { showTranslatedStatusText("Profiler hidden"); } diff --git a/src/gettext.h b/src/gettext.h index 42b375d86..5a3654be4 100644 --- a/src/gettext.h +++ b/src/gettext.h @@ -59,3 +59,21 @@ inline std::string strgettext(const std::string &text) { return text.empty() ? "" : gettext(text.c_str()); } + +/** + * Returns translated string with format args applied + * + * @tparam Args Template parameter for format args + * @param src Translation source string + * @param args Variable format args + * @return translated string + */ +template +inline std::wstring fwgettext(const char *src, Args&&... args) +{ + wchar_t buf[255]; + const wchar_t* str = wgettext(src); + swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, std::forward(args)...); + delete[] str; + return std::wstring(buf); +} diff --git a/util/updatepo.sh b/util/updatepo.sh index 95acb01ea..dbcb16fde 100755 --- a/util/updatepo.sh +++ b/util/updatepo.sh @@ -54,6 +54,7 @@ xgettext --package-name=minetest \ --add-location=file \ --keyword=N_ \ --keyword=wgettext \ + --keyword=fwgettext \ --keyword=fgettext \ --keyword=fgettext_ne \ --keyword=strgettext \ -- cgit v1.2.3 From 1320c51d8e15409544cba970a97b167a37513bae Mon Sep 17 00:00:00 2001 From: Wuzzy Date: Thu, 19 Aug 2021 18:14:04 +0000 Subject: Fix scaled world-aligned textures being aligned inconsistently for non-normal drawtypes --- src/client/mapblock_mesh.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index 402217066..03522eca9 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -407,20 +407,20 @@ static void getNodeTextureCoords(v3f base, const v3f &scale, const v3s16 &dir, f if (dir.X > 0 || dir.Y != 0 || dir.Z < 0) base -= scale; if (dir == v3s16(0,0,1)) { - *u = -base.X - 1; - *v = -base.Y - 1; + *u = -base.X; + *v = -base.Y; } else if (dir == v3s16(0,0,-1)) { *u = base.X + 1; - *v = -base.Y - 2; + *v = -base.Y - 1; } else if (dir == v3s16(1,0,0)) { *u = base.Z + 1; - *v = -base.Y - 2; - } else if (dir == v3s16(-1,0,0)) { - *u = -base.Z - 1; *v = -base.Y - 1; + } else if (dir == v3s16(-1,0,0)) { + *u = -base.Z; + *v = -base.Y; } else if (dir == v3s16(0,1,0)) { *u = base.X + 1; - *v = -base.Z - 2; + *v = -base.Z - 1; } else if (dir == v3s16(0,-1,0)) { *u = base.X + 1; *v = base.Z + 1; -- cgit v1.2.3 From e7b05beb7d90b4ea53ef13da86ff8b8ccde1193b Mon Sep 17 00:00:00 2001 From: sfan5 Date: Thu, 19 Aug 2021 20:14:22 +0200 Subject: Validate staticdata and object property length limits (#11511) Some games provide users with enough freedom to create items with metadata longer than 64KB, preventing this from causing issues is on them but we'll still do the minimum not to abort the server if this happens. --- src/object_properties.cpp | 34 +++++++++++++++++++++++++++++++++- src/object_properties.h | 2 ++ src/script/lua_api/l_object.cpp | 2 ++ src/staticobject.cpp | 24 ++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/object_properties.cpp b/src/object_properties.cpp index 2eebc27d6..db06f8930 100644 --- a/src/object_properties.cpp +++ b/src/object_properties.cpp @@ -83,6 +83,39 @@ std::string ObjectProperties::dump() return os.str(); } +bool ObjectProperties::validate() +{ + const char *func = "ObjectProperties::validate(): "; + bool ret = true; + + // cf. where serializeString16 is used below + for (u32 i = 0; i < textures.size(); i++) { + if (textures[i].size() > U16_MAX) { + warningstream << func << "texture " << (i+1) << " has excessive length, " + "clearing it." << std::endl; + textures[i].clear(); + ret = false; + } + } + if (nametag.length() > U16_MAX) { + warningstream << func << "nametag has excessive length, clearing it." << std::endl; + nametag.clear(); + ret = false; + } + if (infotext.length() > U16_MAX) { + warningstream << func << "infotext has excessive length, clearing it." << std::endl; + infotext.clear(); + ret = false; + } + if (wield_item.length() > U16_MAX) { + warningstream << func << "wield_item has excessive length, clearing it." << std::endl; + wield_item.clear(); + ret = false; + } + + return ret; +} + void ObjectProperties::serialize(std::ostream &os) const { writeU8(os, 4); // PROTOCOL_VERSION >= 37 @@ -105,7 +138,6 @@ void ObjectProperties::serialize(std::ostream &os) const writeU8(os, is_visible); writeU8(os, makes_footstep_sound); writeF32(os, automatic_rotate); - // Added in protocol version 14 os << serializeString16(mesh); writeU16(os, colors.size()); for (video::SColor color : colors) { diff --git a/src/object_properties.h b/src/object_properties.h index db28eebfd..79866a22c 100644 --- a/src/object_properties.h +++ b/src/object_properties.h @@ -68,6 +68,8 @@ struct ObjectProperties ObjectProperties(); std::string dump(); + // check limits of some important properties (strings) that'd cause exceptions later on + bool validate(); void serialize(std::ostream &os) const; void deSerialize(std::istream &is); }; diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index 8e308cd9e..c404cb63c 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -685,6 +685,7 @@ int ObjectRef::l_set_properties(lua_State *L) return 0; read_object_properties(L, 2, sao, prop, getServer(L)->idef()); + prop->validate(); sao->notifyObjectPropertiesModified(); return 0; } @@ -752,6 +753,7 @@ int ObjectRef::l_set_nametag_attributes(lua_State *L) std::string nametag = getstringfield_default(L, 2, "text", ""); prop->nametag = nametag; + prop->validate(); sao->notifyObjectPropertiesModified(); lua_pushboolean(L, true); return 1; diff --git a/src/staticobject.cpp b/src/staticobject.cpp index 86e455b9f..1160ec68f 100644 --- a/src/staticobject.cpp +++ b/src/staticobject.cpp @@ -37,6 +37,7 @@ void StaticObject::serialize(std::ostream &os) // data os< bool { + if (obj.data.size() > U16_MAX) { + errorstream << "StaticObjectList::serialize(): " + "object has excessive static data (" << obj.data.size() << + "), deleting it." << std::endl; + return true; + } + return false; + }; + for (auto it = m_stored.begin(); it != m_stored.end(); ) { + if (problematic(*it)) + it = m_stored.erase(it); + else + it++; + } + for (auto it = m_active.begin(); it != m_active.end(); ) { + if (problematic(it->second)) + it = m_active.erase(it); + else + it++; + } + // version u8 version = 0; writeU8(os, version); -- cgit v1.2.3 From 6fd8aede48e357524ea0723fd0e8836697ece11e Mon Sep 17 00:00:00 2001 From: Wuzzy Date: Sat, 21 Aug 2021 11:53:49 +0000 Subject: Show status message when changing block bounds (#11556) --- src/client/game.cpp | 19 ++++++++++++++++++- src/client/hud.cpp | 5 +++-- src/client/hud.h | 18 +++++++++--------- 3 files changed, 30 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/client/game.cpp b/src/client/game.cpp index 90bfab2a3..011875e4a 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -2193,7 +2193,24 @@ void Game::toggleCinematic() void Game::toggleBlockBounds() { if (client->checkPrivilege("basic_debug")) { - hud->toggleBlockBounds(); + 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)"); } diff --git a/src/client/hud.cpp b/src/client/hud.cpp index c5bf0f2f8..e92f5a73d 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -857,13 +857,14 @@ void Hud::drawSelectionMesh() } } -void Hud::toggleBlockBounds() +enum Hud::BlockBoundsMode Hud::toggleBlockBounds() { m_block_bounds_mode = static_cast(m_block_bounds_mode + 1); if (m_block_bounds_mode >= BLOCK_BOUNDS_MAX) { m_block_bounds_mode = BLOCK_BOUNDS_OFF; } + return m_block_bounds_mode; } void Hud::disableBlockBounds() @@ -890,7 +891,7 @@ void Hud::drawBlockBounds() v3f offset = intToFloat(client->getCamera()->getOffset(), BS); - s8 radius = m_block_bounds_mode == BLOCK_BOUNDS_ALL ? 2 : 0; + s8 radius = m_block_bounds_mode == BLOCK_BOUNDS_NEAR ? 2 : 0; v3f halfNode = v3f(BS, BS, BS) / 2.0f; diff --git a/src/client/hud.h b/src/client/hud.h index e228c1d52..fd79183a0 100644 --- a/src/client/hud.h +++ b/src/client/hud.h @@ -35,6 +35,14 @@ struct ItemStack; class Hud { public: + enum BlockBoundsMode + { + BLOCK_BOUNDS_OFF, + BLOCK_BOUNDS_CURRENT, + BLOCK_BOUNDS_NEAR, + BLOCK_BOUNDS_MAX + } m_block_bounds_mode = BLOCK_BOUNDS_OFF; + video::SColor crosshair_argb; video::SColor selectionbox_argb; @@ -51,7 +59,7 @@ public: Inventory *inventory); ~Hud(); - void toggleBlockBounds(); + enum BlockBoundsMode toggleBlockBounds(); void disableBlockBounds(); void drawBlockBounds(); @@ -127,14 +135,6 @@ private: scene::SMeshBuffer m_rotation_mesh_buffer; - enum BlockBoundsMode - { - BLOCK_BOUNDS_OFF, - BLOCK_BOUNDS_CURRENT, - BLOCK_BOUNDS_ALL, - BLOCK_BOUNDS_MAX - } m_block_bounds_mode = BLOCK_BOUNDS_OFF; - enum { HIGHLIGHT_BOX, -- cgit v1.2.3 From 0c1e9603db32b4281974fdd8b8e3b505148be47e Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Sat, 21 Aug 2021 20:04:04 +0200 Subject: HUD: Reject and warn on invalid stat types (#11548) This comes into play on older servers which do not know the "stat" type. Warnings are only logged once to avoid spam within globalstep callbacks --- src/network/clientpackethandler.cpp | 34 ++++++++++++++++--------- src/script/common/c_content.cpp | 19 ++++++++------ src/script/common/c_content.h | 10 ++++---- src/script/common/c_internal.cpp | 48 ++++++++++++++++++++++++++---------- src/script/common/c_internal.h | 6 +++-- src/script/lua_api/l_localplayer.cpp | 5 ++-- src/script/lua_api/l_object.cpp | 8 +++--- 7 files changed, 86 insertions(+), 44 deletions(-) (limited to 'src') diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 50f497959..a631a3178 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -1119,17 +1119,29 @@ void Client::handleCommand_HudChange(NetworkPacket* pkt) *pkt >> server_id >> stat; - if (stat == HUD_STAT_POS || stat == HUD_STAT_SCALE || - stat == HUD_STAT_ALIGN || stat == HUD_STAT_OFFSET) - *pkt >> v2fdata; - else if (stat == HUD_STAT_NAME || stat == HUD_STAT_TEXT || stat == HUD_STAT_TEXT2) - *pkt >> sdata; - else if (stat == HUD_STAT_WORLD_POS) - *pkt >> v3fdata; - else if (stat == HUD_STAT_SIZE) - *pkt >> v2s32data; - else - *pkt >> intdata; + // Keep in sync with:server.cpp -> SendHUDChange + switch ((HudElementStat)stat) { + case HUD_STAT_POS: + case HUD_STAT_SCALE: + case HUD_STAT_ALIGN: + case HUD_STAT_OFFSET: + *pkt >> v2fdata; + break; + case HUD_STAT_NAME: + case HUD_STAT_TEXT: + case HUD_STAT_TEXT2: + *pkt >> sdata; + break; + case HUD_STAT_WORLD_POS: + *pkt >> v3fdata; + break; + case HUD_STAT_SIZE: + *pkt >> v2s32data; + break; + default: + *pkt >> intdata; + break; + } ClientEvent *event = new ClientEvent(); event->type = CE_HUDCHANGE; diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 235016be0..f13287375 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -1989,15 +1989,17 @@ void push_hud_element(lua_State *L, HudElement *elem) lua_setfield(L, -2, "style"); } -HudElementStat read_hud_change(lua_State *L, HudElement *elem, void **value) +bool read_hud_change(lua_State *L, HudElementStat &stat, HudElement *elem, void **value) { - HudElementStat stat = HUD_STAT_NUMBER; - std::string statstr; - if (lua_isstring(L, 3)) { + std::string statstr = lua_tostring(L, 3); + { int statint; - statstr = lua_tostring(L, 3); - stat = string_to_enum(es_HudElementStat, statint, statstr) ? - (HudElementStat)statint : stat; + if (!string_to_enum(es_HudElementStat, statint, statstr)) { + script_log_unique(L, "Unknown HUD stat type: " + statstr, warningstream); + return false; + } + + stat = (HudElementStat)statint; } switch (stat) { @@ -2060,7 +2062,8 @@ HudElementStat read_hud_change(lua_State *L, HudElement *elem, void **value) *value = &elem->style; break; } - return stat; + + return true; } /******************************************************************************/ diff --git a/src/script/common/c_content.h b/src/script/common/c_content.h index 4dc614706..e762604a4 100644 --- a/src/script/common/c_content.h +++ b/src/script/common/c_content.h @@ -193,12 +193,12 @@ void read_json_value (lua_State *L, Json::Value &root, void push_pointed_thing(lua_State *L, const PointedThing &pointed, bool csm = false, bool hitpoint = false); -void push_objectRef (lua_State *L, const u16 id); +void push_objectRef (lua_State *L, const u16 id); -void read_hud_element (lua_State *L, HudElement *elem); +void read_hud_element (lua_State *L, HudElement *elem); -void push_hud_element (lua_State *L, HudElement *elem); +void push_hud_element (lua_State *L, HudElement *elem); -HudElementStat read_hud_change (lua_State *L, HudElement *elem, void **value); +bool read_hud_change (lua_State *L, HudElementStat &stat, HudElement *elem, void **value); -void push_collision_move_result(lua_State *L, const collisionMoveResult &res); +void push_collision_move_result(lua_State *L, const collisionMoveResult &res); diff --git a/src/script/common/c_internal.cpp b/src/script/common/c_internal.cpp index ad5f836c5..66f6a9b98 100644 --- a/src/script/common/c_internal.cpp +++ b/src/script/common/c_internal.cpp @@ -18,10 +18,12 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "common/c_internal.h" +#include "util/numeric.h" #include "debug.h" #include "log.h" #include "porting.h" #include "settings.h" +#include // std::find std::string script_get_backtrace(lua_State *L) { @@ -135,24 +137,35 @@ void script_run_callbacks_f(lua_State *L, int nargs, lua_remove(L, error_handler); } -static void script_log(lua_State *L, const std::string &message, - std::ostream &log_to, bool do_error, int stack_depth) +static void script_log_add_source(lua_State *L, std::string &message, int stack_depth) { lua_Debug ar; - log_to << message << " "; if (lua_getstack(L, stack_depth, &ar)) { FATAL_ERROR_IF(!lua_getinfo(L, "Sl", &ar), "lua_getinfo() failed"); - log_to << "(at " << ar.short_src << ":" << ar.currentline << ")"; + message.append(" (at " + std::string(ar.short_src) + ":" + + std::to_string(ar.currentline) + ")"); } else { - log_to << "(at ?:?)"; + message.append(" (at ?:?)"); } - log_to << std::endl; +} - if (do_error) - script_error(L, LUA_ERRRUN, NULL, NULL); - else - infostream << script_get_backtrace(L) << std::endl; +bool script_log_unique(lua_State *L, std::string message, std::ostream &log_to, + int stack_depth) +{ + thread_local std::vector logged_messages; + + script_log_add_source(L, message, stack_depth); + u64 hash = murmur_hash_64_ua(message.data(), message.length(), 0xBADBABE); + + if (std::find(logged_messages.begin(), logged_messages.end(), hash) + == logged_messages.end()) { + + logged_messages.emplace_back(hash); + log_to << message << std::endl; + return true; + } + return false; } DeprecatedHandlingMode get_deprecated_handling_mode() @@ -174,9 +187,18 @@ DeprecatedHandlingMode get_deprecated_handling_mode() return ret; } -void log_deprecated(lua_State *L, const std::string &message, int stack_depth) +void log_deprecated(lua_State *L, std::string message, int stack_depth) { DeprecatedHandlingMode mode = get_deprecated_handling_mode(); - if (mode != DeprecatedHandlingMode::Ignore) - script_log(L, message, warningstream, mode == DeprecatedHandlingMode::Error, stack_depth); + if (mode == DeprecatedHandlingMode::Ignore) + return; + + script_log_add_source(L, message, stack_depth); + warningstream << message << std::endl; + + if (mode == DeprecatedHandlingMode::Error) + script_error(L, LUA_ERRRUN, NULL, NULL); + else + infostream << script_get_backtrace(L) << std::endl; } + diff --git a/src/script/common/c_internal.h b/src/script/common/c_internal.h index 452c2dd5e..4ddbed232 100644 --- a/src/script/common/c_internal.h +++ b/src/script/common/c_internal.h @@ -114,6 +114,9 @@ void script_error(lua_State *L, int pcall_result, const char *mod, const char *f void script_run_callbacks_f(lua_State *L, int nargs, RunCallbacksMode mode, const char *fxn); +bool script_log_unique(lua_State *L, std::string message, std::ostream &log_to, + int stack_depth = 1); + enum class DeprecatedHandlingMode { Ignore, Log, @@ -134,5 +137,4 @@ DeprecatedHandlingMode get_deprecated_handling_mode(); * @param message The deprecation method * @param stack_depth How far on the stack to the first user function (ie: not builtin or core) */ -void log_deprecated(lua_State *L, const std::string &message, - int stack_depth=1); +void log_deprecated(lua_State *L, std::string message, int stack_depth = 1); diff --git a/src/script/lua_api/l_localplayer.cpp b/src/script/lua_api/l_localplayer.cpp index 59d9ea5f8..77a692f08 100644 --- a/src/script/lua_api/l_localplayer.cpp +++ b/src/script/lua_api/l_localplayer.cpp @@ -369,10 +369,11 @@ int LuaLocalPlayer::l_hud_change(lua_State *L) if (!element) return 0; + HudElementStat stat; void *unused; - read_hud_change(L, element, &unused); + bool ok = read_hud_change(L, stat, element, &unused); - lua_pushboolean(L, true); + lua_pushboolean(L, ok); return 1; } diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index c404cb63c..c915fa9e1 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -1555,12 +1555,14 @@ int ObjectRef::l_hud_change(lua_State *L) if (elem == nullptr) return 0; + HudElementStat stat; void *value = nullptr; - HudElementStat stat = read_hud_change(L, elem, &value); + bool ok = read_hud_change(L, stat, elem, &value); - getServer(L)->hudChange(player, id, stat, value); + if (ok) + getServer(L)->hudChange(player, id, stat, value); - lua_pushboolean(L, true); + lua_pushboolean(L, ok); return 1; } -- cgit v1.2.3 From dad87a360bdd99595ea9061f9c06bbacb4aceb9d Mon Sep 17 00:00:00 2001 From: DS Date: Mon, 23 Aug 2021 14:09:50 +0200 Subject: Use utf-8 for the Irrlicht clipboard (#11538) --- src/gui/guiChatConsole.cpp | 5 ++--- src/gui/guiEditBox.cpp | 15 +++++++-------- 2 files changed, 9 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/gui/guiChatConsole.cpp b/src/gui/guiChatConsole.cpp index 85617d862..049e21a16 100644 --- a/src/gui/guiChatConsole.cpp +++ b/src/gui/guiChatConsole.cpp @@ -338,7 +338,7 @@ void GUIChatConsole::drawText() false, false, &AbsoluteClippingRect); - } else + } else #endif { // Otherwise use standard text @@ -580,8 +580,7 @@ bool GUIChatConsole::OnEvent(const SEvent& event) const c8 *text = os_operator->getTextFromClipboard(); if (!text) return true; - std::basic_string str((const unsigned char*)text); - prompt.input(std::wstring(str.begin(), str.end())); + prompt.input(utf8_to_wide(text)); return true; } else if(event.KeyInput.Key == KEY_KEY_X && event.KeyInput.Control) diff --git a/src/gui/guiEditBox.cpp b/src/gui/guiEditBox.cpp index 43afb6e3e..8459107cd 100644 --- a/src/gui/guiEditBox.cpp +++ b/src/gui/guiEditBox.cpp @@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "IGUIFont.h" #include "porting.h" +#include "util/string.h" GUIEditBox::~GUIEditBox() { @@ -517,8 +518,7 @@ void GUIEditBox::onKeyControlC(const SEvent &event) const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; - core::stringc s; - s = Text.subString(realmbgn, realmend - realmbgn).c_str(); + std::string s = stringw_to_utf8(Text.subString(realmbgn, realmend - realmbgn)); m_operator->copyToClipboard(s.c_str()); } @@ -567,29 +567,28 @@ bool GUIEditBox::onKeyControlV(const SEvent &event, s32 &mark_begin, s32 &mark_e // add new character if (const c8 *p = m_operator->getTextFromClipboard()) { + core::stringw inserted_text = utf8_to_stringw(p); if (m_mark_begin == m_mark_end) { // insert text core::stringw s = Text.subString(0, m_cursor_pos); - s.append(p); + s.append(inserted_text); s.append(Text.subString( m_cursor_pos, Text.size() - m_cursor_pos)); if (!m_max || s.size() <= m_max) { Text = s; - s = p; - m_cursor_pos += s.size(); + m_cursor_pos += inserted_text.size(); } } else { // replace text core::stringw s = Text.subString(0, realmbgn); - s.append(p); + s.append(inserted_text); s.append(Text.subString(realmend, Text.size() - realmend)); if (!m_max || s.size() <= m_max) { Text = s; - s = p; - m_cursor_pos = realmbgn + s.size(); + m_cursor_pos = realmbgn + inserted_text.size(); } } } -- cgit v1.2.3 From eea488ed75c9a158a398a971a16d5f7226b02f35 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Mon, 23 Aug 2021 14:10:17 +0200 Subject: Inventory: Fix rare out-of-bounds access Co-authored-by: Thomas--S --- src/inventorymanager.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/inventorymanager.cpp b/src/inventorymanager.cpp index 1e81c1dbc..a159bf786 100644 --- a/src/inventorymanager.cpp +++ b/src/inventorymanager.cpp @@ -273,7 +273,7 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame } if (!list_to) { infostream << "IMoveAction::apply(): FAIL: destination list not found: " - << "to_inv=\""< list_to->getSize()) { + if (from_i < 0 || list_from->getSize() <= (u32) from_i) { + infostream << "IMoveAction::apply(): FAIL: source index out of bounds: " + << "size of from_list=\"" << list_from->getSize() << "\"" + << ", from_index=\"" << from_i << "\"" << std::endl; + return; + } + + if (to_i < 0 || list_to->getSize() <= (u32) to_i) { infostream << "IMoveAction::apply(): FAIL: destination index out of bounds: " - << "to_i=" << to_i - << ", size=" << list_to->getSize() << std::endl; + << "size of to_list=\"" << list_to->getSize() << "\"" + << ", to_index=\"" << to_i << "\"" << std::endl; return; } + /* Do not handle rollback if both inventories are that of the same player */ -- cgit v1.2.3 From 63e8224636cec3ede1dbb8b78954a8e3a82407a5 Mon Sep 17 00:00:00 2001 From: Wuzzy Date: Mon, 23 Aug 2021 20:13:17 +0000 Subject: Fix 6th line of infotext being cut off in half (#11456) --- doc/lua_api.txt | 6 ++++-- src/client/gameui.cpp | 9 ++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 1aece31f7..327f64b21 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2100,7 +2100,9 @@ Some of the values in the key-value store are handled specially: * `formspec`: Defines an inventory menu that is opened with the 'place/use' key. Only works if no `on_rightclick` was defined for the node. See also [Formspec]. -* `infotext`: Text shown on the screen when the node is pointed at +* `infotext`: Text shown on the screen when the node is pointed at. + Line-breaks will be applied automatically. + If the infotext is very long, it will be truncated. Example: @@ -7189,7 +7191,7 @@ Player properties need to be saved manually. -- Default: false infotext = "", - -- By default empty, text to be shown when pointed at object + -- Same as infotext for nodes. Empty by default static_save = true, -- If false, never save this object statically. It will simply be diff --git a/src/client/gameui.cpp b/src/client/gameui.cpp index 028052fe6..9b77cf6ff 100644 --- a/src/client/gameui.cpp +++ b/src/client/gameui.cpp @@ -71,11 +71,14 @@ void GameUI::init() chat_font_size, FM_Unspecified)); } - // At the middle of the screen - // Object infos are shown in this + + // Infotext of nodes and objects. + // If in debug mode, object debug infos shown here, too. + // Located on the left on the screen, below chat. u32 chat_font_height = m_guitext_chat->getActiveFont()->getDimension(L"Ay").Height; m_guitext_info = gui::StaticText::add(guienv, L"", - core::rect(0, 0, 400, g_fontengine->getTextHeight() * 5 + 5) + + // Size is limited; text will be truncated after 6 lines. + core::rect(0, 0, 400, g_fontengine->getTextHeight() * 6) + v2s32(100, chat_font_height * (g_settings->getU16("recent_chat_messages") + 3)), false, true, guiroot); -- cgit v1.2.3 From 1d69a23ba48d99b051dcf2a6be225edd7c644c7b Mon Sep 17 00:00:00 2001 From: NeroBurner Date: Fri, 27 Aug 2021 20:24:24 +0200 Subject: Joystick sensitivity for player movement (#11262) This commit deprecates the forward, backward, left, and right binary inputs currently used for player movement in the PlayerControl struct. In their place, it adds the movement_speed and movement_direction values, which represents the player movement is a polar coordinate system. movement_speed is a scalar from 0.0 to 1.0. movement_direction is an angle from 0 to +-Pi: FWD 0 _ LFT / \ RGT -Pi/2 | | +Pi/2 \_/ +-Pi BCK Boolean movement bits will still be set for server telegrams and Lua script invocations to provide full backward compatibility. When generating these values from an analog input, a direction is considered active when it is 22.5 degrees away from either orthogonal axis. Co-authored-by: Markus Koch Co-authored-by: sfan5 --- src/client/content_cao.cpp | 7 ++-- src/client/game.cpp | 77 +++++++++++++++++++----------------- src/client/inputhandler.h | 43 ++++++++++++++++++++ src/client/joystick_controller.cpp | 24 +++++++++-- src/client/joystick_controller.h | 5 ++- src/client/localplayer.cpp | 24 ++--------- src/network/serverpackethandler.cpp | 4 -- src/player.h | 25 ++++-------- src/script/lua_api/l_localplayer.cpp | 20 ++++++---- src/script/lua_api/l_object.cpp | 8 ++-- 10 files changed, 138 insertions(+), 99 deletions(-) (limited to 'src') diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index 83c8e15d4..da78cae7c 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -998,9 +998,7 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) const PlayerControl &controls = player->getPlayerControl(); bool walking = false; - if (controls.up || controls.down || controls.left || controls.right || - controls.forw_move_joystick_axis != 0.f || - controls.sidew_move_joystick_axis != 0.f) + if (controls.movement_speed > 0.001f) walking = true; f32 new_speed = player->local_animation_speed; @@ -1015,9 +1013,10 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) g_settings->getBool("free_move") && m_client->checkLocalPrivilege("fly")))) new_speed *= 1.5; - // slowdown speed if sneeking + // slowdown speed if sneaking if (controls.sneak && walking) new_speed /= 2; + new_speed *= controls.movement_speed; if (walking && (controls.dig || controls.place)) { new_anim = player->local_animations[3]; diff --git a/src/client/game.cpp b/src/client/game.cpp index 011875e4a..18df5cc58 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -2460,7 +2460,7 @@ void Game::updateCameraOrientation(CameraOrientation *cam, float dtime) if (m_cache_enable_joysticks) { f32 sens_scale = getSensitivityScaleFactor(); - f32 c = m_cache_joystick_frustum_sensitivity * (1.f / 32767.f) * dtime * sens_scale; + f32 c = m_cache_joystick_frustum_sensitivity * dtime * sens_scale; cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c; cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c; } @@ -2471,18 +2471,12 @@ void Game::updateCameraOrientation(CameraOrientation *cam, float dtime) void Game::updatePlayerControl(const CameraOrientation &cam) { - //TimeTaker tt("update player control", NULL, PRECISION_NANO); + LocalPlayer *player = client->getEnv().getLocalPlayer(); - // DO NOT use the isKeyDown method for the forward, backward, left, right - // buttons, as the code that uses the controls needs to be able to - // distinguish between the two in order to know when to use joysticks. + //TimeTaker tt("update player control", NULL, PRECISION_NANO); PlayerControl control( - input->isKeyDown(KeyType::FORWARD), - input->isKeyDown(KeyType::BACKWARD), - input->isKeyDown(KeyType::LEFT), - input->isKeyDown(KeyType::RIGHT), - isKeyDown(KeyType::JUMP), + isKeyDown(KeyType::JUMP) || player->getAutojump(), isKeyDown(KeyType::AUX1), isKeyDown(KeyType::SNEAK), isKeyDown(KeyType::ZOOM), @@ -2490,22 +2484,16 @@ void Game::updatePlayerControl(const CameraOrientation &cam) isKeyDown(KeyType::PLACE), cam.camera_pitch, cam.camera_yaw, - input->joystick.getAxisWithoutDead(JA_SIDEWARD_MOVE), - input->joystick.getAxisWithoutDead(JA_FORWARD_MOVE) + input->getMovementSpeed(), + input->getMovementDirection() ); - u32 keypress_bits = ( - ( (u32)(isKeyDown(KeyType::FORWARD) & 0x1) << 0) | - ( (u32)(isKeyDown(KeyType::BACKWARD) & 0x1) << 1) | - ( (u32)(isKeyDown(KeyType::LEFT) & 0x1) << 2) | - ( (u32)(isKeyDown(KeyType::RIGHT) & 0x1) << 3) | - ( (u32)(isKeyDown(KeyType::JUMP) & 0x1) << 4) | - ( (u32)(isKeyDown(KeyType::AUX1) & 0x1) << 5) | - ( (u32)(isKeyDown(KeyType::SNEAK) & 0x1) << 6) | - ( (u32)(isKeyDown(KeyType::DIG) & 0x1) << 7) | - ( (u32)(isKeyDown(KeyType::PLACE) & 0x1) << 8) | - ( (u32)(isKeyDown(KeyType::ZOOM) & 0x1) << 9) - ); + // autoforward if set: move towards pointed position at maximum speed + if (player->getPlayerSettings().continuous_forward && + client->activeObjectsReceived() && !player->isDead()) { + control.movement_speed = 1.0f; + control.movement_direction = 0.0f; + } #ifdef ANDROID /* For Android, simulate holding down AUX1 (fast move) if the user has @@ -2515,23 +2503,38 @@ void Game::updatePlayerControl(const CameraOrientation &cam) */ if (m_cache_hold_aux1) { control.aux1 = control.aux1 ^ true; - keypress_bits ^= ((u32)(1U << 5)); } #endif - LocalPlayer *player = client->getEnv().getLocalPlayer(); - - // autojump if set: simulate "jump" key - if (player->getAutojump()) { - control.jump = true; - keypress_bits |= 1U << 4; - } + 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) + ); - // autoforward if set: simulate "up" key - if (player->getPlayerSettings().continuous_forward && - client->activeObjectsReceived() && !player->isDead()) { - control.up = true; - keypress_bits |= 1U << 0; + // 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); diff --git a/src/client/inputhandler.h b/src/client/inputhandler.h index 1fb4cf0ec..76e3c7b5b 100644 --- a/src/client/inputhandler.h +++ b/src/client/inputhandler.h @@ -240,6 +240,9 @@ public: virtual bool wasKeyReleased(GameKeyType k) = 0; virtual bool cancelPressed() = 0; + virtual float getMovementSpeed() = 0; + virtual float getMovementDirection() = 0; + virtual void clearWasKeyPressed() {} virtual void clearWasKeyReleased() {} @@ -285,6 +288,44 @@ public: { return m_receiver->WasKeyReleased(keycache.key[k]) || joystick.wasKeyReleased(k); } + virtual float getMovementSpeed() + { + bool f = m_receiver->IsKeyDown(keycache.key[KeyType::FORWARD]), + b = m_receiver->IsKeyDown(keycache.key[KeyType::BACKWARD]), + l = m_receiver->IsKeyDown(keycache.key[KeyType::LEFT]), + r = m_receiver->IsKeyDown(keycache.key[KeyType::RIGHT]); + if (f || b || l || r) + { + // if contradictory keys pressed, stay still + if (f && b && l && r) + return 0.0f; + else if (f && b && !l && !r) + return 0.0f; + else if (!f && !b && l && r) + return 0.0f; + return 1.0f; // If there is a keyboard event, assume maximum speed + } + return joystick.getMovementSpeed(); + } + virtual float getMovementDirection() + { + float x = 0, z = 0; + + /* Check keyboard for input */ + if (m_receiver->IsKeyDown(keycache.key[KeyType::FORWARD])) + z += 1; + if (m_receiver->IsKeyDown(keycache.key[KeyType::BACKWARD])) + z -= 1; + if (m_receiver->IsKeyDown(keycache.key[KeyType::RIGHT])) + x += 1; + if (m_receiver->IsKeyDown(keycache.key[KeyType::LEFT])) + x -= 1; + + if (x != 0 || z != 0) /* If there is a keyboard event, it takes priority */ + return atan2(x, z); + else + return joystick.getMovementDirection(); + } virtual bool cancelPressed() { return wasKeyDown(KeyType::ESC) || m_receiver->WasKeyDown(CancelKey); @@ -352,6 +393,8 @@ public: virtual bool wasKeyPressed(GameKeyType k) { return false; } virtual bool wasKeyReleased(GameKeyType k) { return false; } virtual bool cancelPressed() { return false; } + virtual float getMovementSpeed() {return 0.0f;} + virtual float getMovementDirection() {return 0.0f;} virtual v2s32 getMousePos() { return mousepos; } virtual void setMousePos(s32 x, s32 y) { mousepos = v2s32(x, y); } diff --git a/src/client/joystick_controller.cpp b/src/client/joystick_controller.cpp index 919db5315..630565d8d 100644 --- a/src/client/joystick_controller.cpp +++ b/src/client/joystick_controller.cpp @@ -160,6 +160,7 @@ JoystickController::JoystickController() : for (float &i : m_past_pressed_time) { i = 0; } + m_layout.axes_deadzone = 0; clear(); } @@ -251,10 +252,27 @@ void JoystickController::clear() memset(m_axes_vals, 0, sizeof(m_axes_vals)); } -s16 JoystickController::getAxisWithoutDead(JoystickAxis axis) +float JoystickController::getAxisWithoutDead(JoystickAxis axis) { s16 v = m_axes_vals[axis]; + if (abs(v) < m_layout.axes_deadzone) - return 0; - return v; + return 0.0f; + + v += (v < 0 ? m_layout.axes_deadzone : -m_layout.axes_deadzone); + + return (float)v / ((float)(INT16_MAX - m_layout.axes_deadzone)); +} + +float JoystickController::getMovementDirection() +{ + return atan2(getAxisWithoutDead(JA_SIDEWARD_MOVE), -getAxisWithoutDead(JA_FORWARD_MOVE)); +} + +float JoystickController::getMovementSpeed() +{ + float speed = sqrt(pow(getAxisWithoutDead(JA_FORWARD_MOVE), 2) + pow(getAxisWithoutDead(JA_SIDEWARD_MOVE), 2)); + if (speed > 1.0f) + speed = 1.0f; + return speed; } diff --git a/src/client/joystick_controller.h b/src/client/joystick_controller.h index 3f361e4ef..cbc60886c 100644 --- a/src/client/joystick_controller.h +++ b/src/client/joystick_controller.h @@ -144,7 +144,10 @@ public: return m_axes_vals[axis]; } - s16 getAxisWithoutDead(JoystickAxis axis); + float getAxisWithoutDead(JoystickAxis axis); + + float getMovementDirection(); + float getMovementSpeed(); f32 doubling_dtime; diff --git a/src/client/localplayer.cpp b/src/client/localplayer.cpp index f3eb1a2dd..2d4f7305a 100644 --- a/src/client/localplayer.cpp +++ b/src/client/localplayer.cpp @@ -566,23 +566,7 @@ void LocalPlayer::applyControl(float dtime, Environment *env) } } - if (control.up) - speedH += v3f(0.0f, 0.0f, 1.0f); - - if (control.down) - speedH -= v3f(0.0f, 0.0f, 1.0f); - - if (!control.up && !control.down) - speedH -= v3f(0.0f, 0.0f, 1.0f) * (control.forw_move_joystick_axis / 32767.f); - - if (control.left) - speedH += v3f(-1.0f, 0.0f, 0.0f); - - if (control.right) - speedH += v3f(1.0f, 0.0f, 0.0f); - - if (!control.left && !control.right) - speedH += v3f(1.0f, 0.0f, 0.0f) * (control.sidew_move_joystick_axis / 32767.f); + speedH = v3f(sin(control.movement_direction), 0.0f, cos(control.movement_direction)); if (m_autojump) { // release autojump after a given time @@ -639,6 +623,8 @@ void LocalPlayer::applyControl(float dtime, Environment *env) else speedH = speedH.normalize() * movement_speed_walk; + speedH *= control.movement_speed; /* Apply analog input */ + // Acceleration increase f32 incH = 0.0f; // Horizontal (X, Z) f32 incV = 0.0f; // Vertical (Y) @@ -1106,9 +1092,7 @@ void LocalPlayer::handleAutojump(f32 dtime, Environment *env, if (m_autojump) return; - bool control_forward = control.up || - (!control.up && !control.down && - control.forw_move_joystick_axis < -0.05f); + bool control_forward = keyPressed & (1 << 0); bool could_autojump = m_can_jump && !control.jump && !control.sneak && control_forward; diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index 708ddbf20..dc5864be3 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -510,10 +510,6 @@ void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao, playersao->setWantedRange(wanted_range); player->keyPressed = keyPressed; - player->control.up = (keyPressed & (0x1 << 0)); - player->control.down = (keyPressed & (0x1 << 1)); - player->control.left = (keyPressed & (0x1 << 2)); - player->control.right = (keyPressed & (0x1 << 3)); player->control.jump = (keyPressed & (0x1 << 4)); player->control.aux1 = (keyPressed & (0x1 << 5)); player->control.sneak = (keyPressed & (0x1 << 6)); diff --git a/src/player.h b/src/player.h index ec068a8b1..3800e1a33 100644 --- a/src/player.h +++ b/src/player.h @@ -49,10 +49,6 @@ struct PlayerControl PlayerControl() = default; PlayerControl( - bool a_up, - bool a_down, - bool a_left, - bool a_right, bool a_jump, bool a_aux1, bool a_sneak, @@ -61,14 +57,10 @@ struct PlayerControl bool a_place, float a_pitch, float a_yaw, - float a_sidew_move_joystick_axis, - float a_forw_move_joystick_axis + float a_movement_speed, + float a_movement_direction ) { - up = a_up; - down = a_down; - left = a_left; - right = a_right; jump = a_jump; aux1 = a_aux1; sneak = a_sneak; @@ -77,13 +69,9 @@ struct PlayerControl place = a_place; pitch = a_pitch; yaw = a_yaw; - sidew_move_joystick_axis = a_sidew_move_joystick_axis; - forw_move_joystick_axis = a_forw_move_joystick_axis; + movement_speed = a_movement_speed; + movement_direction = a_movement_direction; } - bool up = false; - bool down = false; - bool left = false; - bool right = false; bool jump = false; bool aux1 = false; bool sneak = false; @@ -92,8 +80,9 @@ struct PlayerControl bool place = false; float pitch = 0.0f; float yaw = 0.0f; - float sidew_move_joystick_axis = 0.0f; - float forw_move_joystick_axis = 0.0f; + // Note: These two are NOT available on the server + float movement_speed = 0.0f; + float movement_direction = 0.0f; }; struct PlayerSettings diff --git a/src/script/lua_api/l_localplayer.cpp b/src/script/lua_api/l_localplayer.cpp index 77a692f08..9f3569ecc 100644 --- a/src/script/lua_api/l_localplayer.cpp +++ b/src/script/lua_api/l_localplayer.cpp @@ -223,16 +223,20 @@ int LuaLocalPlayer::l_get_control(lua_State *L) }; lua_createtable(L, 0, 12); - set("up", c.up); - set("down", c.down); - set("left", c.left); - set("right", c.right); - set("jump", c.jump); - set("aux1", c.aux1); + set("jump", c.jump); + set("aux1", c.aux1); set("sneak", c.sneak); - set("zoom", c.zoom); - set("dig", c.dig); + set("zoom", c.zoom); + 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); + // 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. return 1; } diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index c915fa9e1..c8fa7d806 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -1392,13 +1392,13 @@ int ObjectRef::l_get_player_control(lua_State *L) const PlayerControl &control = player->getPlayerControl(); lua_newtable(L); - lua_pushboolean(L, control.up); + lua_pushboolean(L, player->keyPressed & (1 << 0)); lua_setfield(L, -2, "up"); - lua_pushboolean(L, control.down); + lua_pushboolean(L, player->keyPressed & (1 << 1)); lua_setfield(L, -2, "down"); - lua_pushboolean(L, control.left); + lua_pushboolean(L, player->keyPressed & (1 << 2)); lua_setfield(L, -2, "left"); - lua_pushboolean(L, control.right); + lua_pushboolean(L, player->keyPressed & (1 << 3)); lua_setfield(L, -2, "right"); lua_pushboolean(L, control.jump); lua_setfield(L, -2, "jump"); -- cgit v1.2.3 From 3f1adb49ae8da5bb02bea52609524d3645b6a665 Mon Sep 17 00:00:00 2001 From: savilli <78875209+savilli@users.noreply.github.com> Date: Sat, 28 Aug 2021 12:14:16 +0200 Subject: Remove redundant on_dieplayer calls --- src/network/serverpackethandler.cpp | 16 ---------------- src/script/common/c_content.cpp | 2 -- src/script/lua_api/l_object.cpp | 18 ------------------ src/server.cpp | 3 +-- src/server/player_sao.cpp | 4 ++-- 5 files changed, 3 insertions(+), 40 deletions(-) (limited to 'src') diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index dc5864be3..77fde2a66 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -828,7 +828,6 @@ void Server::handleCommand_Damage(NetworkPacket* pkt) PlayerHPChangeReason reason(PlayerHPChangeReason::FALL); playersao->setHP((s32)playersao->getHP() - (s32)damage, reason); - SendPlayerHPOrDie(playersao, reason); } } @@ -1113,9 +1112,6 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) float time_from_last_punch = playersao->resetTimeFromLastPunch(); - u16 src_original_hp = pointed_object->getHP(); - u16 dst_origin_hp = playersao->getHP(); - u16 wear = pointed_object->punch(dir, &toolcap, playersao, time_from_last_punch); @@ -1125,18 +1121,6 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) if (changed) playersao->setWieldedItem(selected_item); - // If the object is a player and its HP changed - if (src_original_hp != pointed_object->getHP() && - pointed_object->getType() == ACTIVEOBJECT_TYPE_PLAYER) { - SendPlayerHPOrDie((PlayerSAO *)pointed_object, - PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, playersao)); - } - - // If the puncher is a player and its HP changed - if (dst_origin_hp != playersao->getHP()) - SendPlayerHPOrDie(playersao, - PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, pointed_object)); - return; } // action == INTERACT_START_DIGGING diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index f13287375..5a095fd8f 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -200,8 +200,6 @@ void read_object_properties(lua_State *L, int index, if (prop->hp_max < sao->getHP()) { PlayerHPChangeReason reason(PlayerHPChangeReason::SET_HP); sao->setHP(prop->hp_max, reason); - if (sao->getType() == ACTIVEOBJECT_TYPE_PLAYER) - sao->getEnv()->getGameDef()->SendPlayerHPOrDie((PlayerSAO *)sao, reason); } } diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index c8fa7d806..b7185f7ec 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -172,27 +172,11 @@ int ObjectRef::l_punch(lua_State *L) float time_from_last_punch = readParam(L, 3, 1000000.0f); ToolCapabilities toolcap = read_tool_capabilities(L, 4); v3f dir = readParam(L, 5, sao->getBasePosition() - puncher->getBasePosition()); - dir.normalize(); - u16 src_original_hp = sao->getHP(); - u16 dst_origin_hp = puncher->getHP(); u16 wear = sao->punch(dir, &toolcap, puncher, time_from_last_punch); lua_pushnumber(L, wear); - // If the punched is a player, and its HP changed - if (src_original_hp != sao->getHP() && - sao->getType() == ACTIVEOBJECT_TYPE_PLAYER) { - getServer(L)->SendPlayerHPOrDie((PlayerSAO *)sao, - PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher)); - } - - // If the puncher is a player, and its HP changed - if (dst_origin_hp != puncher->getHP() && - puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) { - getServer(L)->SendPlayerHPOrDie((PlayerSAO *)puncher, - PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, sao)); - } return 1; } @@ -238,8 +222,6 @@ int ObjectRef::l_set_hp(lua_State *L) } sao->setHP(hp, reason); - if (sao->getType() == ACTIVEOBJECT_TYPE_PLAYER) - getServer(L)->SendPlayerHPOrDie((PlayerSAO *)sao, reason); if (reason.hasLuaReference()) luaL_unref(L, LUA_REGISTRYINDEX, reason.lua_reference); return 0; diff --git a/src/server.cpp b/src/server.cpp index 8339faa76..b96db1209 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1079,8 +1079,7 @@ PlayerSAO* Server::StageTwoClientInit(session_t peer_id) if (playersao->isDead()) SendDeathscreen(peer_id, false, v3f(0,0,0)); else - SendPlayerHPOrDie(playersao, - PlayerHPChangeReason(PlayerHPChangeReason::SET_HP)); + SendPlayerHP(peer_id); // Send Breath SendPlayerBreath(playersao); diff --git a/src/server/player_sao.cpp b/src/server/player_sao.cpp index 0d31f2e0b..d4d036726 100644 --- a/src/server/player_sao.cpp +++ b/src/server/player_sao.cpp @@ -167,7 +167,6 @@ void PlayerSAO::step(float dtime, bool send_recommended) if (m_breath == 0) { PlayerHPChangeReason reason(PlayerHPChangeReason::DROWNING); setHP(m_hp - c.drowning, reason); - m_env->getGameDef()->SendPlayerHPOrDie(this, reason); } } } @@ -216,7 +215,6 @@ void PlayerSAO::step(float dtime, bool send_recommended) s32 newhp = (s32)m_hp - (s32)damage_per_second; PlayerHPChangeReason reason(PlayerHPChangeReason::NODE_DAMAGE, nodename); setHP(newhp, reason); - m_env->getGameDef()->SendPlayerHPOrDie(this, reason); } } @@ -491,6 +489,8 @@ void PlayerSAO::setHP(s32 hp, const PlayerHPChangeReason &reason) // Update properties on death if ((hp == 0) != (oldhp == 0)) m_properties_sent = false; + + m_env->getGameDef()->SendPlayerHPOrDie(this, reason); } void PlayerSAO::setBreath(const u16 breath, bool send) -- cgit v1.2.3 From 6a1424f2b18520f40ba8cfd12f7988f6b33db9a6 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sat, 28 Aug 2021 12:15:12 +0200 Subject: Async-related script cleanups --- builtin/async/init.lua | 10 +--- src/gui/guiEngine.cpp | 7 --- src/gui/guiEngine.h | 4 -- src/script/cpp_api/s_async.cpp | 108 ++++++++++++++++++++------------------ src/script/cpp_api/s_async.h | 39 +++++++------- src/script/cpp_api/s_base.cpp | 4 -- src/script/cpp_api/s_base.h | 5 +- src/script/cpp_api/s_security.cpp | 21 ++------ src/script/lua_api/l_mainmenu.cpp | 11 ++-- src/script/lua_api/l_server.cpp | 29 +--------- src/script/lua_api/l_server.h | 6 --- src/script/lua_api/l_util.cpp | 30 +++++++++++ src/script/lua_api/l_util.h | 6 +++ src/script/scripting_mainmenu.cpp | 6 +-- src/script/scripting_mainmenu.h | 5 +- 15 files changed, 135 insertions(+), 156 deletions(-) (limited to 'src') diff --git a/builtin/async/init.lua b/builtin/async/init.lua index 1b2549685..3803994d6 100644 --- a/builtin/async/init.lua +++ b/builtin/async/init.lua @@ -1,16 +1,10 @@ core.log("info", "Initializing Asynchronous environment") -function core.job_processor(serialized_func, serialized_param) - local func = loadstring(serialized_func) +function core.job_processor(func, serialized_param) local param = core.deserialize(serialized_param) - local retval = nil - if type(func) == "function" then - retval = core.serialize(func(param)) - else - core.log("error", "ASYNC WORKER: Unable to deserialize function") - end + local retval = core.serialize(func(param)) return retval or core.serialize(nil) end diff --git a/src/gui/guiEngine.cpp b/src/gui/guiEngine.cpp index 694baf482..b3808535c 100644 --- a/src/gui/guiEngine.cpp +++ b/src/gui/guiEngine.cpp @@ -614,10 +614,3 @@ void GUIEngine::stopSound(s32 handle) { m_sound_manager->stopSound(handle); } - -/******************************************************************************/ -unsigned int GUIEngine::queueAsync(const std::string &serialized_func, - const std::string &serialized_params) -{ - return m_script->queueAsync(serialized_func, serialized_params); -} diff --git a/src/gui/guiEngine.h b/src/gui/guiEngine.h index 70abce181..d7e6485ef 100644 --- a/src/gui/guiEngine.h +++ b/src/gui/guiEngine.h @@ -175,10 +175,6 @@ public: return m_scriptdir; } - /** pass async callback to scriptengine **/ - unsigned int queueAsync(const std::string &serialized_fct, - const std::string &serialized_params); - private: /** find and run the main menu script */ diff --git a/src/script/cpp_api/s_async.cpp b/src/script/cpp_api/s_async.cpp index 0619b32c0..dacdcd75a 100644 --- a/src/script/cpp_api/s_async.cpp +++ b/src/script/cpp_api/s_async.cpp @@ -32,20 +32,19 @@ extern "C" { #include "filesys.h" #include "porting.h" #include "common/c_internal.h" +#include "lua_api/l_base.h" /******************************************************************************/ AsyncEngine::~AsyncEngine() { - // Request all threads to stop for (AsyncWorkerThread *workerThread : workerThreads) { workerThread->stop(); } - // Wake up all threads - for (std::vector::iterator it = workerThreads.begin(); - it != workerThreads.end(); ++it) { + for (auto it : workerThreads) { + (void)it; jobQueueCounter.post(); } @@ -68,6 +67,7 @@ AsyncEngine::~AsyncEngine() /******************************************************************************/ void AsyncEngine::registerStateInitializer(StateInitializer func) { + FATAL_ERROR_IF(initDone, "Initializer may not be registered after init"); stateInitializers.push_back(func); } @@ -85,36 +85,36 @@ void AsyncEngine::initialize(unsigned int numEngines) } /******************************************************************************/ -unsigned int AsyncEngine::queueAsyncJob(const std::string &func, - const std::string ¶ms) +u32 AsyncEngine::queueAsyncJob(std::string &&func, std::string &¶ms, + const std::string &mod_origin) { jobQueueMutex.lock(); - LuaJobInfo toAdd; - toAdd.id = jobIdCounter++; - toAdd.serializedFunction = func; - toAdd.serializedParams = params; + u32 jobId = jobIdCounter++; - jobQueue.push_back(toAdd); + jobQueue.emplace_back(); + auto &to_add = jobQueue.back(); + to_add.id = jobId; + to_add.function = std::move(func); + to_add.params = std::move(params); + to_add.mod_origin = mod_origin; jobQueueCounter.post(); - jobQueueMutex.unlock(); - - return toAdd.id; + return jobId; } /******************************************************************************/ -LuaJobInfo AsyncEngine::getJob() +bool AsyncEngine::getJob(LuaJobInfo *job) { jobQueueCounter.wait(); jobQueueMutex.lock(); - LuaJobInfo retval; + bool retval = false; if (!jobQueue.empty()) { - retval = jobQueue.front(); + *job = std::move(jobQueue.front()); jobQueue.pop_front(); - retval.valid = true; + retval = true; } jobQueueMutex.unlock(); @@ -122,10 +122,10 @@ LuaJobInfo AsyncEngine::getJob() } /******************************************************************************/ -void AsyncEngine::putJobResult(const LuaJobInfo &result) +void AsyncEngine::putJobResult(LuaJobInfo &&result) { resultQueueMutex.lock(); - resultQueue.push_back(result); + resultQueue.emplace_back(std::move(result)); resultQueueMutex.unlock(); } @@ -134,26 +134,30 @@ void AsyncEngine::step(lua_State *L) { int error_handler = PUSH_ERROR_HANDLER(L); lua_getglobal(L, "core"); - resultQueueMutex.lock(); + + ScriptApiBase *script = ModApiBase::getScriptApiBase(L); + + MutexAutoLock autolock(resultQueueMutex); while (!resultQueue.empty()) { - LuaJobInfo jobDone = resultQueue.front(); + LuaJobInfo j = std::move(resultQueue.front()); resultQueue.pop_front(); lua_getfield(L, -1, "async_event_handler"); - - if (lua_isnil(L, -1)) { + if (lua_isnil(L, -1)) FATAL_ERROR("Async event handler does not exist!"); - } - luaL_checktype(L, -1, LUA_TFUNCTION); - lua_pushinteger(L, jobDone.id); - lua_pushlstring(L, jobDone.serializedResult.data(), - jobDone.serializedResult.size()); + lua_pushinteger(L, j.id); + lua_pushlstring(L, j.result.data(), j.result.size()); - PCALL_RESL(L, lua_pcall(L, 2, 0, error_handler)); + // Call handler + const char *origin = j.mod_origin.empty() ? nullptr : j.mod_origin.c_str(); + script->setOriginDirect(origin); + int result = lua_pcall(L, 2, 0, error_handler); + if (result) + script_error(L, result, origin, ""); } - resultQueueMutex.unlock(); + lua_pop(L, 2); // Pop core and error handler } @@ -168,8 +172,8 @@ void AsyncEngine::prepareEnvironment(lua_State* L, int top) /******************************************************************************/ AsyncWorkerThread::AsyncWorkerThread(AsyncEngine* jobDispatcher, const std::string &name) : - Thread(name), ScriptApiBase(ScriptingType::Async), + Thread(name), jobDispatcher(jobDispatcher) { lua_State *L = getStack(); @@ -196,9 +200,9 @@ void* AsyncWorkerThread::run() { lua_State *L = getStack(); - std::string script = getServer()->getBuiltinLuaPath() + DIR_DELIM + "init.lua"; try { - loadScript(script); + 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; @@ -213,44 +217,44 @@ void* AsyncWorkerThread::run() } // Main loop + LuaJobInfo j; while (!stopRequested()) { // Wait for job - LuaJobInfo toProcess = jobDispatcher->getJob(); - - if (!toProcess.valid || stopRequested()) { + if (!jobDispatcher->getJob(&j) || stopRequested()) continue; - } lua_getfield(L, -1, "job_processor"); - if (lua_isnil(L, -1)) { + if (lua_isnil(L, -1)) FATAL_ERROR("Unable to get async job processor!"); - } - luaL_checktype(L, -1, LUA_TFUNCTION); - // Call it - lua_pushlstring(L, - toProcess.serializedFunction.data(), - toProcess.serializedFunction.size()); - lua_pushlstring(L, - toProcess.serializedParams.data(), - toProcess.serializedParams.size()); + if (luaL_loadbuffer(L, j.function.data(), j.function.size(), "=(async)")) { + errorstream << "ASYNC WORKER: Unable to deserialize function" << std::endl; + lua_pushnil(L); + } + lua_pushlstring(L, j.params.data(), j.params.size()); + // Call it + setOriginDirect(j.mod_origin.empty() ? nullptr : j.mod_origin.c_str()); int result = lua_pcall(L, 2, 1, error_handler); if (result) { - PCALL_RES(result); - toProcess.serializedResult = ""; + try { + scriptError(result, ""); + } catch (const ModError &e) { + errorstream << e.what() << std::endl; + } } else { // Fetch result size_t length; const char *retval = lua_tolstring(L, -1, &length); - toProcess.serializedResult = std::string(retval, length); + j.result.assign(retval, length); } lua_pop(L, 1); // Pop retval // Put job result - jobDispatcher->putJobResult(toProcess); + if (!j.result.empty()) + jobDispatcher->putJobResult(std::move(j)); } lua_pop(L, 2); // Pop core and error handler diff --git a/src/script/cpp_api/s_async.h b/src/script/cpp_api/s_async.h index 99a4f891c..697cb0221 100644 --- a/src/script/cpp_api/s_async.h +++ b/src/script/cpp_api/s_async.h @@ -21,7 +21,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include -#include #include "threading/semaphore.h" #include "threading/thread.h" @@ -39,26 +38,29 @@ struct LuaJobInfo { LuaJobInfo() = default; - // Function to be called in async environment - std::string serializedFunction = ""; - // Parameter to be passed to function - std::string serializedParams = ""; - // Result of function call - std::string serializedResult = ""; + // Function to be called in async environment (from string.dump) + std::string function; + // Parameter to be passed to function (serialized) + std::string params; + // Result of function call (serialized) + std::string result; + // Name of the mod who invoked this call + std::string mod_origin; // JobID used to identify a job and match it to callback - unsigned int id = 0; - - bool valid = false; + u32 id; }; // Asynchronous working environment -class AsyncWorkerThread : public Thread, public ScriptApiBase { +class AsyncWorkerThread : public Thread, virtual public ScriptApiBase { + friend class AsyncEngine; public: - AsyncWorkerThread(AsyncEngine* jobDispatcher, const std::string &name); virtual ~AsyncWorkerThread(); void *run(); +protected: + AsyncWorkerThread(AsyncEngine* jobDispatcher, const std::string &name); + private: AsyncEngine *jobDispatcher = nullptr; }; @@ -89,7 +91,8 @@ public: * @param params Serialized parameters * @return jobid The job is queued */ - unsigned int queueAsyncJob(const std::string &func, const std::string ¶ms); + u32 queueAsyncJob(std::string &&func, std::string &¶ms, + const std::string &mod_origin = ""); /** * Engine step to process finished jobs @@ -102,15 +105,16 @@ protected: /** * Get a Job from queue to be processed * this function blocks until a job is ready - * @return a job to be processed + * @param job a job to be processed + * @return whether a job was available */ - LuaJobInfo getJob(); + bool getJob(LuaJobInfo *job); /** * Put a Job result back to result queue * @param result result of completed job */ - void putJobResult(const LuaJobInfo &result); + void putJobResult(LuaJobInfo &&result); /** * Initialize environment with current registred functions @@ -129,11 +133,10 @@ private: std::vector stateInitializers; // Internal counter to create job IDs - unsigned int jobIdCounter = 0; + u32 jobIdCounter = 0; // Mutex to protect job queue std::mutex jobQueueMutex; - // Job queue std::deque jobQueue; diff --git a/src/script/cpp_api/s_base.cpp b/src/script/cpp_api/s_base.cpp index f965975a3..921f713c0 100644 --- a/src/script/cpp_api/s_base.cpp +++ b/src/script/cpp_api/s_base.cpp @@ -331,13 +331,9 @@ void ScriptApiBase::setOriginDirect(const char *origin) void ScriptApiBase::setOriginFromTableRaw(int index, const char *fxn) { -#ifdef SCRIPTAPI_DEBUG lua_State *L = getStack(); - m_last_run_mod = lua_istable(L, index) ? getstringfield_default(L, index, "mod_origin", "") : ""; - //printf(">>>> running %s for mod: %s\n", fxn, m_last_run_mod.c_str()); -#endif } /* diff --git a/src/script/cpp_api/s_base.h b/src/script/cpp_api/s_base.h index 86f7f7bac..7a8ebc85a 100644 --- a/src/script/cpp_api/s_base.h +++ b/src/script/cpp_api/s_base.h @@ -39,7 +39,6 @@ extern "C" { #include "config.h" #define SCRIPTAPI_LOCK_DEBUG -#define SCRIPTAPI_DEBUG // MUST be an invalid mod name so that mods can't // use that name to bypass security! @@ -108,7 +107,9 @@ public: Client* getClient(); #endif - std::string getOrigin() { return m_last_run_mod; } + // IMPORTANT: these cannot be used for any security-related uses, they exist + // only to enrich error messages + const std::string &getOrigin() { return m_last_run_mod; } void setOriginDirect(const char *origin); void setOriginFromTableRaw(int index, const char *fxn); diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index add7b1658..580042ec2 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -18,7 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "cpp_api/s_security.h" - +#include "lua_api/l_base.h" #include "filesys.h" #include "porting.h" #include "server.h" @@ -538,15 +538,8 @@ bool ScriptApiSecurity::checkPath(lua_State *L, const char *path, if (!removed.empty()) abs_path += DIR_DELIM + removed; - // Get server from registry - lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_SCRIPTAPI); - ScriptApiBase *script; -#if INDIRECT_SCRIPTAPI_RIDX - script = (ScriptApiBase *) *(void**)(lua_touserdata(L, -1)); -#else - script = (ScriptApiBase *) lua_touserdata(L, -1); -#endif - lua_pop(L, 1); + // Get gamedef from registry + ScriptApiBase *script = ModApiBase::getScriptApiBase(L); const IGameDef *gamedef = script->getGameDef(); if (!gamedef) return false; @@ -669,13 +662,7 @@ int ScriptApiSecurity::sl_g_load(lua_State *L) int ScriptApiSecurity::sl_g_loadfile(lua_State *L) { #ifndef SERVER - lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_SCRIPTAPI); -#if INDIRECT_SCRIPTAPI_RIDX - ScriptApiBase *script = (ScriptApiBase *) *(void**)(lua_touserdata(L, -1)); -#else - ScriptApiBase *script = (ScriptApiBase *) lua_touserdata(L, -1); -#endif - lua_pop(L, 1); + ScriptApiBase *script = ModApiBase::getScriptApiBase(L); // Client implementation if (script->getType() == ScriptingType::Client) { diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index ad00de1c4..6e9a5c34f 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.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_content.h" #include "cpp_api/s_async.h" +#include "scripting_mainmenu.h" #include "gui/guiEngine.h" #include "gui/guiMainMenu.h" #include "gui/guiKeyChangeMenu.h" @@ -816,20 +817,20 @@ int ModApiMainMenu::l_open_dir(lua_State *L) /******************************************************************************/ int ModApiMainMenu::l_do_async_callback(lua_State *L) { - GUIEngine* engine = getGuiEngine(L); + MainMenuScripting *script = getScriptApi(L); size_t func_length, param_length; const char* serialized_func_raw = luaL_checklstring(L, 1, &func_length); - const char* serialized_param_raw = luaL_checklstring(L, 2, ¶m_length); sanity_check(serialized_func_raw != NULL); sanity_check(serialized_param_raw != NULL); - std::string serialized_func = std::string(serialized_func_raw, func_length); - std::string serialized_param = std::string(serialized_param_raw, param_length); + u32 jobId = script->queueAsync( + std::string(serialized_func_raw, func_length), + std::string(serialized_param_raw, param_length)); - lua_pushinteger(L, engine->queueAsync(serialized_func, serialized_param)); + lua_pushinteger(L, jobId); return 1; } diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index bf5292521..9866e0bc8 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "common/c_content.h" #include "cpp_api/s_base.h" #include "cpp_api/s_security.h" +#include "scripting_server.h" #include "server.h" #include "environment.h" #include "remoteplayer.h" @@ -498,31 +499,6 @@ int ModApiServer::l_notify_authentication_modified(lua_State *L) return 0; } -// get_last_run_mod() -int ModApiServer::l_get_last_run_mod(lua_State *L) -{ - NO_MAP_LOCK_REQUIRED; - lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); - std::string current_mod = readParam(L, -1, ""); - if (current_mod.empty()) { - lua_pop(L, 1); - lua_pushstring(L, getScriptApiBase(L)->getOrigin().c_str()); - } - return 1; -} - -// set_last_run_mod(modname) -int ModApiServer::l_set_last_run_mod(lua_State *L) -{ - NO_MAP_LOCK_REQUIRED; -#ifdef SCRIPTAPI_DEBUG - const char *mod = lua_tostring(L, 1); - getScriptApiBase(L)->setOriginDirect(mod); - //printf(">>>> last mod set from Lua: %s\n", mod); -#endif - return 0; -} - void ModApiServer::Initialize(lua_State *L, int top) { API_FCT(request_shutdown); @@ -555,7 +531,4 @@ 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(get_last_run_mod); - API_FCT(set_last_run_mod); } diff --git a/src/script/lua_api/l_server.h b/src/script/lua_api/l_server.h index 2df180b17..fb7a851f4 100644 --- a/src/script/lua_api/l_server.h +++ b/src/script/lua_api/l_server.h @@ -103,12 +103,6 @@ private: // notify_authentication_modified(name) static int l_notify_authentication_modified(lua_State *L); - // get_last_run_mod() - static int l_get_last_run_mod(lua_State *L); - - // set_last_run_mod(modname) - static int l_set_last_run_mod(lua_State *L); - public: static void Initialize(lua_State *L, int top); }; diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index 87436fce0..9152b5f7f 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -535,6 +535,30 @@ int ModApiUtil::l_encode_png(lua_State *L) return 1; } +// get_last_run_mod() +int ModApiUtil::l_get_last_run_mod(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); + std::string current_mod = readParam(L, -1, ""); + if (current_mod.empty()) { + lua_pop(L, 1); + lua_pushstring(L, getScriptApiBase(L)->getOrigin().c_str()); + } + return 1; +} + +// set_last_run_mod(modname) +int ModApiUtil::l_set_last_run_mod(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + const char *mod = luaL_checkstring(L, 1); + getScriptApiBase(L)->setOriginDirect(mod); + return 0; +} + void ModApiUtil::Initialize(lua_State *L, int top) { API_FCT(log); @@ -574,6 +598,9 @@ void ModApiUtil::Initialize(lua_State *L, int top) API_FCT(encode_png); + API_FCT(get_last_run_mod); + API_FCT(set_last_run_mod); + LuaSettings::create(L, g_settings, g_settings_path); lua_setfield(L, top, "settings"); } @@ -629,6 +656,9 @@ void ModApiUtil::InitializeAsync(lua_State *L, int top) API_FCT(colorspec_to_colorstring); API_FCT(colorspec_to_bytes); + API_FCT(get_last_run_mod); + API_FCT(set_last_run_mod); + LuaSettings::create(L, g_settings, g_settings_path); lua_setfield(L, top, "settings"); } diff --git a/src/script/lua_api/l_util.h b/src/script/lua_api/l_util.h index 54d2be619..cc91e8d39 100644 --- a/src/script/lua_api/l_util.h +++ b/src/script/lua_api/l_util.h @@ -110,6 +110,12 @@ private: // encode_png(w, h, data, level) static int l_encode_png(lua_State *L); + // get_last_run_mod() + static int l_get_last_run_mod(lua_State *L); + + // set_last_run_mod(modname) + static int l_set_last_run_mod(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/scripting_mainmenu.cpp b/src/script/scripting_mainmenu.cpp index b102a66a1..2a0cadb23 100644 --- a/src/script/scripting_mainmenu.cpp +++ b/src/script/scripting_mainmenu.cpp @@ -92,9 +92,9 @@ void MainMenuScripting::step() } /******************************************************************************/ -unsigned int MainMenuScripting::queueAsync(const std::string &serialized_func, - const std::string &serialized_param) +u32 MainMenuScripting::queueAsync(std::string &&serialized_func, + std::string &&serialized_param) { - return asyncEngine.queueAsyncJob(serialized_func, serialized_param); + return asyncEngine.queueAsyncJob(std::move(serialized_func), std::move(serialized_param)); } diff --git a/src/script/scripting_mainmenu.h b/src/script/scripting_mainmenu.h index 9e23bdc1b..3c329654a 100644 --- a/src/script/scripting_mainmenu.h +++ b/src/script/scripting_mainmenu.h @@ -38,8 +38,9 @@ public: void step(); // Pass async events from engine to async threads - unsigned int queueAsync(const std::string &serialized_func, - const std::string &serialized_params); + u32 queueAsync(std::string &&serialized_func, + std::string &&serialized_param); + private: void initializeModApi(lua_State *L, int top); static void registerLuaClasses(lua_State *L, int top); -- cgit v1.2.3 From 040aed37abcc929137fb9729f2526d6a15d2d51a Mon Sep 17 00:00:00 2001 From: pecksin <78765996+pecksin@users.noreply.github.com> Date: Sun, 29 Aug 2021 13:14:06 -0400 Subject: Remove closing paren as weblink delimiter --- src/chat.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/chat.cpp b/src/chat.cpp index e44d73ac0..162622abe 100644 --- a/src/chat.cpp +++ b/src/chat.cpp @@ -366,7 +366,7 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols, // Chars to mark end of weblink // TODO? replace this with a safer (slower) regex whitelist? - static const std::wstring delim_chars = L"\'\");,"; + static const std::wstring delim_chars = L"\'\";,"; wchar_t tempchar = linestring[in_pos+frag_length]; while (frag_length < remaining_in_input && !iswspace(tempchar) && -- cgit v1.2.3 From d1624a552151bcb152b7abf63df6501b63458d78 Mon Sep 17 00:00:00 2001 From: lhofhansl Date: Tue, 31 Aug 2021 17:32:31 -0700 Subject: Switch MapBlock compression to zstd (#10788) * Add zstd support. * Rearrange serialization order * Compress entire mapblock Co-authored-by: sfan5 --- .github/workflows/build.yml | 2 +- .github/workflows/macos.yml | 2 +- .gitlab-ci.yml | 4 +- Dockerfile | 2 +- android/native/jni/Android.mk | 10 ++- builtin/settingtypes.txt | 16 ++-- cmake/Modules/FindZstd.cmake | 9 +++ doc/world_format.txt | 91 ++++++++++++--------- misc/debpkg-control | 2 +- src/CMakeLists.txt | 11 +++ src/defaultsettings.cpp | 4 +- src/main.cpp | 73 ++++++++++++++++- src/mapblock.cpp | 166 ++++++++++++++++++++++++++------------ src/mapblock.h | 2 +- src/mapgen/mg_schematic.cpp | 9 ++- src/mapgen/mg_schematic.h | 1 + src/mapnode.cpp | 30 +++---- src/mapnode.h | 5 +- src/serialization.cpp | 135 +++++++++++++++++++++++++++++-- src/serialization.h | 14 +++- src/unittest/test_compression.cpp | 42 +++++++++- util/buildbot/buildwin32.sh | 6 ++ util/buildbot/buildwin64.sh | 6 ++ util/ci/common.sh | 2 +- 24 files changed, 493 insertions(+), 151 deletions(-) create mode 100644 cmake/Modules/FindZstd.cmake (limited to 'src') diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d268aa0cb..98b1ffe8a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -227,7 +227,7 @@ jobs: env: VCPKG_VERSION: 0bf3923f9fab4001c00f0f429682a0853b5749e0 # 2020.11 - vcpkg_packages: irrlicht zlib curl[winssl] openal-soft libvorbis libogg sqlite3 freetype luajit + vcpkg_packages: irrlicht zlib zstd curl[winssl] openal-soft libvorbis libogg sqlite3 freetype luajit strategy: fail-fast: false matrix: diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 3ec157d0e..d97cff1aa 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -34,7 +34,7 @@ jobs: - uses: actions/checkout@v2 - name: Install deps run: | - pkgs=(cmake freetype gettext gmp hiredis jpeg jsoncpp leveldb libogg libpng libvorbis luajit) + pkgs=(cmake freetype gettext gmp hiredis jpeg jsoncpp leveldb libogg libpng libvorbis luajit zstd) brew update brew install ${pkgs[@]} brew unlink $(brew ls --formula) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cabce627f..252ed8a5b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -17,7 +17,7 @@ variables: stage: build before_script: - apt-get update - - apt-get -y install build-essential git cmake libpng-dev libjpeg-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libleveldb-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev + - apt-get -y install build-essential git cmake libpng-dev libjpeg-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libleveldb-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev libzstd-dev script: - git clone https://github.com/minetest/irrlicht -b $IRRLICHT_TAG lib/irrlichtmt - mkdir cmakebuild @@ -187,7 +187,7 @@ build:fedora-28: extends: .build_template image: fedora:28 before_script: - - dnf -y install make git gcc gcc-c++ kernel-devel cmake libjpeg-devel libpng-devel libcurl-devel openal-soft-devel libvorbis-devel libXxf86vm-devel libogg-devel freetype-devel mesa-libGL-devel zlib-devel jsoncpp-devel gmp-devel sqlite-devel luajit-devel leveldb-devel ncurses-devel spatialindex-devel + - dnf -y install make git gcc gcc-c++ kernel-devel cmake libjpeg-devel libpng-devel libcurl-devel openal-soft-devel libvorbis-devel libXxf86vm-devel libogg-devel freetype-devel mesa-libGL-devel zlib-devel jsoncpp-devel gmp-devel sqlite-devel luajit-devel leveldb-devel ncurses-devel spatialindex-devel libzstd-devel ## ## MinGW for Windows diff --git a/Dockerfile b/Dockerfile index 8843e4bbc..481dab237 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,7 +20,7 @@ COPY textures /usr/src/minetest/textures WORKDIR /usr/src/minetest -RUN apk add --no-cache git build-base cmake sqlite-dev curl-dev zlib-dev \ +RUN apk add --no-cache git build-base cmake sqlite-dev curl-dev zlib-dev zstd-dev \ gmp-dev jsoncpp-dev postgresql-dev ninja luajit-dev ca-certificates && \ git clone --depth=1 -b ${MINETEST_GAME_VERSION} https://github.com/minetest/minetest_game.git ./games/minetest_game && \ rm -fr ./games/minetest_game/.git diff --git a/android/native/jni/Android.mk b/android/native/jni/Android.mk index f92ac1d60..26e9b058b 100644 --- a/android/native/jni/Android.mk +++ b/android/native/jni/Android.mk @@ -57,6 +57,11 @@ LOCAL_MODULE := Vorbis LOCAL_SRC_FILES := deps/Android/Vorbis/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libvorbis.a include $(PREBUILT_STATIC_LIBRARY) +include $(CLEAR_VARS) +LOCAL_MODULE := Zstd +LOCAL_SRC_FILES := deps/Android/Zstd/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libzstd.a +include $(PREBUILT_STATIC_LIBRARY) + include $(CLEAR_VARS) LOCAL_MODULE := Minetest @@ -101,7 +106,8 @@ LOCAL_C_INCLUDES := \ deps/Android/LuaJIT/src \ deps/Android/OpenAL-Soft/include \ deps/Android/sqlite \ - deps/Android/Vorbis/include + deps/Android/Vorbis/include \ + deps/Android/Zstd/include LOCAL_SRC_FILES := \ $(wildcard ../../src/client/*.cpp) \ @@ -201,7 +207,7 @@ LOCAL_SRC_FILES += \ # SQLite3 LOCAL_SRC_FILES += deps/Android/sqlite/sqlite3.c -LOCAL_STATIC_LIBRARIES += Curl Freetype Irrlicht OpenAL mbedTLS mbedx509 mbedcrypto Vorbis LuaJIT GetText android_native_app_glue $(PROFILER_LIBS) #LevelDB +LOCAL_STATIC_LIBRARIES += Curl Freetype Irrlicht OpenAL mbedTLS mbedx509 mbedcrypto Vorbis LuaJIT GetText Zstd android_native_app_glue $(PROFILER_LIBS) #LevelDB LOCAL_LDLIBS := -lEGL -lGLESv1_CM -lGLESv2 -landroid -lOpenSLES diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 25a51b888..43e70e052 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -1100,11 +1100,10 @@ full_block_send_enable_min_time_from_building (Delay in sending blocks after bui # client number. max_packets_per_iteration (Max. packets per iteration) int 1024 -# ZLib compression level to use when sending mapblocks to the client. -# -1 - Zlib's default compression level -# 0 - no compresson, fastest +# Compression level to use when sending mapblocks to the client. +# -1 - use default compression level +# 0 - least compresson, fastest # 9 - best compression, slowest -# (levels 1-3 use Zlib's "fast" method, 4-9 use the normal method) map_compression_level_net (Map Compression Level for Network Transfer) int -1 -1 9 [*Game] @@ -1303,12 +1302,11 @@ max_objects_per_block (Maximum objects per block) int 64 # See https://www.sqlite.org/pragma.html#pragma_synchronous sqlite_synchronous (Synchronous SQLite) enum 2 0,1,2 -# ZLib compression level to use when saving mapblocks to disk. -# -1 - Zlib's default compression level -# 0 - no compresson, fastest +# Compression level to use when saving mapblocks to disk. +# -1 - use default compression level +# 0 - least compresson, fastest # 9 - best compression, slowest -# (levels 1-3 use Zlib's "fast" method, 4-9 use the normal method) -map_compression_level_disk (Map Compression Level for Disk Storage) int 3 -1 9 +map_compression_level_disk (Map Compression Level for Disk Storage) int -1 -1 9 # Length of a server tick and the interval at which objects are generally updated over # network. diff --git a/cmake/Modules/FindZstd.cmake b/cmake/Modules/FindZstd.cmake new file mode 100644 index 000000000..461e4d5a6 --- /dev/null +++ b/cmake/Modules/FindZstd.cmake @@ -0,0 +1,9 @@ +mark_as_advanced(ZSTD_LIBRARY ZSTD_INCLUDE_DIR) + +find_path(ZSTD_INCLUDE_DIR NAMES zstd.h) + +find_library(ZSTD_LIBRARY NAMES zstd) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Zstd DEFAULT_MSG ZSTD_LIBRARY ZSTD_INCLUDE_DIR) + diff --git a/doc/world_format.txt b/doc/world_format.txt index a8a9e463e..eb1d7f728 100644 --- a/doc/world_format.txt +++ b/doc/world_format.txt @@ -1,5 +1,5 @@ ============================= -Minetest World Format 22...27 +Minetest World Format 22...29 ============================= This applies to a world format carrying the block serialization version @@ -8,6 +8,7 @@ This applies to a world format carrying the block serialization version - 0.4.0 (23) - 24 was never released as stable and existed for ~2 days - 27 was added in 0.4.15-dev +- 29 was added in 5.5.0-dev The block serialization version does not fully specify every aspect of this format; if compliance with this format is to be checked, it needs to be @@ -281,6 +282,8 @@ MapBlock serialization format NOTE: Byte order is MSB first (big-endian). NOTE: Zlib data is in such a format that Python's zlib at least can directly decompress. +NOTE: Since version 29 zstd is used instead of zlib. In addition the entire + block is first serialized and then compressed (except the version byte). u8 version - map format version number, see serialisation.h for the latest number @@ -324,6 +327,20 @@ u16 lighting_complete then Minetest will correct lighting in the day light bank when the block at (1, 0, 0) is also loaded. +if map format version >= 29: + u32 timestamp + - Timestamp when last saved, as seconds from starting the game. + - 0xffffffff = invalid/unknown timestamp, nothing should be done with the time + difference when loaded + + u16 num_name_id_mappings + foreach num_name_id_mappings + u16 id + u16 name_len + u8[name_len] name +if map format version < 29: + -- Nothing right here, timpstamp and node id mappings are serialized later + u8 content_width - Number of bytes in the content (param0) fields of nodes if map format version <= 23: @@ -335,7 +352,7 @@ u8 params_width - Number of bytes used for parameters per node - Always 2 -zlib-compressed node data: +node data (zlib-compressed if version < 29): if content_width == 1: - content: u8[4096]: param0 fields @@ -348,31 +365,31 @@ if content_width == 2: u8[4096]: param2 fields - The location of a node in each of those arrays is (z*16*16 + y*16 + x). -zlib-compressed node metadata list +node metadata list (zlib-compressed if version < 29): - content: -if map format version <= 22: - u16 version (=1) - u16 count of metadata - foreach count: - u16 position (p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X) - u16 type_id - u16 content_size - u8[content_size] content of metadata. Format depends on type_id, see below. -if map format version >= 23: - u8 version -- Note: type was u16 for map format version <= 22 - -- = 1 for map format version < 28 - -- = 2 since map format version 28 - u16 count of metadata - foreach count: - u16 position (p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X) - u32 num_vars - foreach num_vars: - u16 key_len - u8[key_len] key - u32 val_len - u8[val_len] value - u8 is_private -- only for version >= 2. 0 = not private, 1 = private - serialized inventory + if map format version <= 22: + u16 version (=1) + u16 count of metadata + foreach count: + u16 position (p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X) + u16 type_id + u16 content_size + u8[content_size] content of metadata. Format depends on type_id, see below. + if map format version >= 23: + u8 version -- Note: type was u16 for map format version <= 22 + -- = 1 for map format version < 28 + -- = 2 since map format version 28 + u16 count of metadata + foreach count: + u16 position (p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X) + u32 num_vars + foreach num_vars: + u16 key_len + u8[key_len] key + u32 val_len + u8[val_len] value + u8 is_private -- only for version >= 2. 0 = not private, 1 = private + serialized inventory - Node timers if map format version == 23: @@ -403,20 +420,18 @@ foreach static_object_count: u16 data_size u8[data_size] data -u32 timestamp -- Timestamp when last saved, as seconds from starting the game. -- 0xffffffff = invalid/unknown timestamp, nothing should be done with the time - difference when loaded - -u8 name-id-mapping version -- Always 0 +if map format version < 29: + u32 timestamp + - Same meaning as the timestamp further up -u16 num_name_id_mappings + u8 name-id-mapping version + - Always 0 -foreach num_name_id_mappings - u16 id - u16 name_len - u8[name_len] name + u16 num_name_id_mappings + foreach num_name_id_mappings + u16 id + u16 name_len + u8[name_len] name - Node timers if map format version == 25: diff --git a/misc/debpkg-control b/misc/debpkg-control index 7c0134bb0..e867f3eb9 100644 --- a/misc/debpkg-control +++ b/misc/debpkg-control @@ -3,7 +3,7 @@ Priority: extra Standards-Version: 3.6.2 Package: minetest-staging Version: 5.4.0-DATEPLACEHOLDER -Depends: libc6, libcurl3-gnutls, libfreetype6, libgl1, JPEG_PLACEHOLDER, JSONCPP_PLACEHOLDER, LEVELDB_PLACEHOLDER, libopenal1, libpng16-16, libsqlite3-0, libstdc++6, libvorbisfile3, libx11-6, libxxf86vm1, zlib1g +Depends: libc6, libcurl3-gnutls, libfreetype6, libgl1, JPEG_PLACEHOLDER, JSONCPP_PLACEHOLDER, LEVELDB_PLACEHOLDER, libopenal1, libpng16-16, libsqlite3-0, libstdc++6, libvorbisfile3, libx11-6, libxxf86vm1, libzstd1, zlib1g Maintainer: Loic Blot Homepage: https://www.minetest.net/ Vcs-Git: https://github.com/minetest/minetest.git diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7a5e48b49..addb0af3f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -271,9 +271,13 @@ if(WIN32) find_path(ZLIB_INCLUDE_DIR "zlib.h" DOC "Zlib include directory") find_library(ZLIB_LIBRARIES "zlib" DOC "Path to zlib library") + find_path(ZSTD_INCLUDE_DIR "zstd.h" DOC "Zstd include directory") + find_library(ZSTD_LIBRARY "zstd" DOC "Path to zstd library") + # Dll's are automatically copied to the output directory by vcpkg when VCPKG_APPLOCAL_DEPS=ON if(NOT VCPKG_APPLOCAL_DEPS) find_file(ZLIB_DLL NAMES "zlib.dll" "zlib1.dll" DOC "Path to zlib.dll for installation (optional)") + find_file(ZSTD_DLL NAMES "zstd.dll" DOC "Path to zstd.dll for installation (optional)") if(ENABLE_SOUND) set(OPENAL_DLL "" CACHE FILEPATH "Path to OpenAL32.dll for installation (optional)") set(OGG_DLL "" CACHE FILEPATH "Path to libogg.dll for installation (optional)") @@ -296,6 +300,7 @@ else() endif() find_package(ZLIB REQUIRED) + find_package(Zstd REQUIRED) set(PLATFORM_LIBS -lpthread ${CMAKE_DL_LIBS}) if(APPLE) set(PLATFORM_LIBS "-framework CoreFoundation" ${PLATFORM_LIBS}) @@ -486,6 +491,7 @@ include_directories( ${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR} ${ZLIB_INCLUDE_DIR} + ${ZSTD_INCLUDE_DIR} ${SOUND_INCLUDE_DIRS} ${SQLITE3_INCLUDE_DIR} ${LUA_INCLUDE_DIR} @@ -521,6 +527,7 @@ if(BUILD_CLIENT) ${PROJECT_NAME} ${ZLIB_LIBRARIES} IrrlichtMt::IrrlichtMt + ${ZSTD_LIBRARY} ${X11_LIBRARIES} ${SOUND_LIBRARIES} ${SQLITE3_LIBRARY} @@ -605,6 +612,7 @@ if(BUILD_SERVER) target_link_libraries( ${PROJECT_NAME}server ${ZLIB_LIBRARIES} + ${ZSTD_LIBRARY} ${SQLITE3_LIBRARY} ${JSON_LIBRARY} ${LUA_LIBRARY} @@ -821,6 +829,9 @@ if(WIN32) if(ZLIB_DLL) install(FILES ${ZLIB_DLL} DESTINATION ${BINDIR}) endif() + if(ZSTD_DLL) + install(FILES ${ZSTD_DLL} DESTINATION ${BINDIR}) + endif() if(BUILD_CLIENT AND FREETYPE_DLL) install(FILES ${FREETYPE_DLL} DESTINATION ${BINDIR}) endif() diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index faf839b3a..2cb345ba7 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -398,7 +398,7 @@ void set_default_settings() settings->setDefault("chat_message_limit_per_10sec", "8.0"); settings->setDefault("chat_message_limit_trigger_kick", "50"); settings->setDefault("sqlite_synchronous", "2"); - settings->setDefault("map_compression_level_disk", "3"); + settings->setDefault("map_compression_level_disk", "-1"); settings->setDefault("map_compression_level_net", "-1"); settings->setDefault("full_block_send_enable_min_time_from_building", "2.0"); settings->setDefault("dedicated_server_step", "0.09"); @@ -484,7 +484,7 @@ void set_default_settings() settings->setDefault("max_objects_per_block", "20"); settings->setDefault("sqlite_synchronous", "1"); settings->setDefault("map_compression_level_disk", "-1"); - settings->setDefault("map_compression_level_net", "3"); + settings->setDefault("map_compression_level_net", "-1"); settings->setDefault("server_map_save_interval", "15"); settings->setDefault("client_mapblock_limit", "1000"); settings->setDefault("active_block_range", "2"); diff --git a/src/main.cpp b/src/main.cpp index ffbdb7b5b..543b70333 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -38,6 +38,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "player.h" #include "porting.h" #include "network/socket.h" +#include "mapblock.h" #if USE_CURSES #include "terminal_chat_console.h" #endif @@ -111,6 +112,7 @@ static bool determine_subgame(GameParams *game_params); static bool run_dedicated_server(const GameParams &game_params, const Settings &cmd_args); static bool migrate_map_database(const GameParams &game_params, const Settings &cmd_args); +static bool recompress_map_database(const GameParams &game_params, const Settings &cmd_args, const Address &addr); /**********************************************************************/ @@ -302,6 +304,8 @@ static void set_allowed_options(OptionList *allowed_options) _("Migrate from current auth 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, + _("Recompress the blocks of the given map database.")))); #ifndef SERVER allowed_options->insert(std::make_pair("speedtests", ValueSpec(VALUETYPE_FLAG, _("Run speed tests")))); @@ -875,7 +879,7 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings & return false; } - // Database migration + // Database migration/compression if (cmd_args.exists("migrate")) return migrate_map_database(game_params, cmd_args); @@ -885,6 +889,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.getFlag("recompress")) + return recompress_map_database(game_params, cmd_args, bind_addr); + if (cmd_args.exists("terminal")) { #if USE_CURSES bool name_ok = true; @@ -1034,3 +1041,67 @@ static bool migrate_map_database(const GameParams &game_params, const Settings & return true; } + +static bool recompress_map_database(const GameParams &game_params, const Settings &cmd_args, const Address &addr) +{ + Settings world_mt; + const 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 at " << world_mt_path << std::endl; + return false; + } + const std::string &backend = world_mt.get("backend"); + Server server(game_params.world_path, game_params.game_spec, false, addr, false); + MapDatabase *db = ServerMap::createDatabase(backend, game_params.world_path, world_mt); + + u32 count = 0; + u64 last_update_time = 0; + bool &kill = *porting::signal_handler_killstatus(); + const u8 serialize_as_ver = SER_FMT_VER_HIGHEST_WRITE; + + // This is ok because the server doesn't actually run + std::vector blocks; + db->listAllLoadableBlocks(blocks); + db->beginSave(); + std::istringstream iss(std::ios_base::binary); + std::ostringstream oss(std::ios_base::binary); + for (auto it = blocks.begin(); it != blocks.end(); ++it) { + if (kill) return false; + + std::string data; + db->loadBlock(*it, &data); + if (data.empty()) { + errorstream << "Failed to load block " << PP(*it) << std::endl; + return false; + } + + iss.str(data); + iss.clear(); + + MapBlock mb(nullptr, v3s16(0,0,0), &server); + u8 ver = readU8(iss); + mb.deSerialize(iss, ver, true); + + oss.str(""); + oss.clear(); + writeU8(oss, serialize_as_ver); + mb.serialize(oss, serialize_as_ver, true, -1); + + db->saveBlock(*it, oss.str()); + + count++; + if (count % 0xFF == 0 && porting::getTimeS() - last_update_time >= 1) { + std::cerr << " Recompressed " << count << " blocks, " + << (100.0f * count / blocks.size()) << "% completed.\r"; + db->endSave(); + db->beginSave(); + last_update_time = porting::getTimeS(); + } + } + std::cerr << std::endl; + db->endSave(); + + actionstream << "Done, " << count << " blocks were recompressed." << std::endl; + return true; +} diff --git a/src/mapblock.cpp b/src/mapblock.cpp index 0ca71e643..4958d3a65 100644 --- a/src/mapblock.cpp +++ b/src/mapblock.cpp @@ -355,7 +355,7 @@ static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes, } } -void MapBlock::serialize(std::ostream &os, u8 version, bool disk, int compression_level) +void MapBlock::serialize(std::ostream &os_compressed, u8 version, bool disk, int compression_level) { if(!ser_ver_supported(version)) throw VersionMismatchException("ERROR: MapBlock format not supported"); @@ -365,6 +365,9 @@ void MapBlock::serialize(std::ostream &os, u8 version, bool disk, int compressio FATAL_ERROR_IF(version < SER_FMT_VER_LOWEST_WRITE, "Serialisation version error"); + std::ostringstream os_raw(std::ios_base::binary); + std::ostream &os = version >= 29 ? os_raw : os_compressed; + // First byte u8 flags = 0; if(is_underground) @@ -382,37 +385,52 @@ void MapBlock::serialize(std::ostream &os, u8 version, bool disk, int compressio Bulk node data */ NameIdMapping nimap; - if(disk) + SharedBuffer buf; + const u8 content_width = 2; + const u8 params_width = 2; + if(disk) { MapNode *tmp_nodes = new MapNode[nodecount]; - for(u32 i=0; indef()); - u8 content_width = 2; - u8 params_width = 2; - writeU8(os, content_width); - writeU8(os, params_width); - MapNode::serializeBulk(os, version, tmp_nodes, nodecount, - content_width, params_width, compression_level); + buf = MapNode::serializeBulk(version, tmp_nodes, nodecount, + content_width, params_width); delete[] tmp_nodes; + + // write timestamp and node/id mapping first + if (version >= 29) { + writeU32(os, getTimestamp()); + + nimap.serialize(os); + } } else { - u8 content_width = 2; - u8 params_width = 2; - writeU8(os, content_width); - writeU8(os, params_width); - MapNode::serializeBulk(os, version, data, nodecount, - content_width, params_width, compression_level); + buf = MapNode::serializeBulk(version, data, nodecount, + content_width, params_width); + } + + writeU8(os, content_width); + writeU8(os, params_width); + if (version >= 29) { + os.write(reinterpret_cast(*buf), buf.getSize()); + } else { + // prior to 29 node data was compressed individually + compress(buf, os, version, compression_level); } /* Node metadata */ - std::ostringstream oss(std::ios_base::binary); - m_node_metadata.serialize(oss, version, disk); - compressZlib(oss.str(), os, compression_level); + if (version >= 29) { + m_node_metadata.serialize(os, version, disk); + } else { + // use os_raw from above to avoid allocating another stream object + m_node_metadata.serialize(os_raw, version, disk); + // prior to 29 node data was compressed individually + compress(os_raw.str(), os, version, compression_level); + } /* Data that goes to disk, but not the network @@ -427,17 +445,24 @@ void MapBlock::serialize(std::ostream &os, u8 version, bool disk, int compressio // Static objects m_static_objects.serialize(os); - // Timestamp - writeU32(os, getTimestamp()); + if(version < 29){ + // Timestamp + writeU32(os, getTimestamp()); - // Write block-specific node definition id mapping - nimap.serialize(os); + // Write block-specific node definition id mapping + nimap.serialize(os); + } if(version >= 25){ // Node timers m_node_timers.serialize(os, version); } } + + if (version >= 29) { + // now compress the whole thing + compress(os_raw.str(), os_compressed, version, compression_level); + } } void MapBlock::serializeNetworkSpecific(std::ostream &os) @@ -449,7 +474,7 @@ void MapBlock::serializeNetworkSpecific(std::ostream &os) writeU8(os, 2); // version } -void MapBlock::deSerialize(std::istream &is, u8 version, bool disk) +void MapBlock::deSerialize(std::istream &in_compressed, u8 version, bool disk) { if(!ser_ver_supported(version)) throw VersionMismatchException("ERROR: MapBlock format not supported"); @@ -460,10 +485,16 @@ void MapBlock::deSerialize(std::istream &is, u8 version, bool disk) if(version <= 21) { - deSerialize_pre22(is, version, disk); + deSerialize_pre22(in_compressed, version, disk); return; } + // Decompress the whole block (version >= 29) + std::stringstream in_raw(std::ios_base::binary | std::ios_base::in | std::ios_base::out); + if (version >= 29) + decompress(in_compressed, in_raw, version); + std::istream &is = version >= 29 ? in_raw : in_compressed; + u8 flags = readU8(is); is_underground = (flags & 0x01) != 0; m_day_night_differs = (flags & 0x02) != 0; @@ -473,9 +504,20 @@ void MapBlock::deSerialize(std::istream &is, u8 version, bool disk) m_lighting_complete = readU16(is); m_generated = (flags & 0x08) == 0; - /* - Bulk node data - */ + NameIdMapping nimap; + if (disk && version >= 29) { + // Timestamp + TRACESTREAM(<<"MapBlock::deSerialize "<= 29) { + MapNode::deSerializeBulk(is, version, data, nodecount, content_width, params_width); + } else { + // use in_raw from above to avoid allocating another stream object + decompress(is, in_raw, version); + MapNode::deSerializeBulk(in_raw, version, data, nodecount, + content_width, params_width); + } /* NodeMetadata */ TRACESTREAM(<<"MapBlock::deSerialize "<= 23) - m_node_metadata.deSerialize(iss, m_gamedef->idef()); - else - content_nodemeta_deserialize_legacy(iss, - &m_node_metadata, &m_node_timers, - m_gamedef->idef()); - } catch(SerializationError &e) { - warningstream<<"MapBlock::deSerialize(): Ignoring an error" - <<" while deserializing node metadata at (" - <= 29) { + m_node_metadata.deSerialize(is, m_gamedef->idef()); + } else { + try { + // reuse in_raw + in_raw.str(""); + in_raw.clear(); + decompress(is, in_raw, version); + if (version >= 23) + m_node_metadata.deSerialize(in_raw, m_gamedef->idef()); + else + content_nodemeta_deserialize_legacy(in_raw, + &m_node_metadata, &m_node_timers, + m_gamedef->idef()); + } catch(SerializationError &e) { + warningstream<<"MapBlock::deSerialize(): Ignoring an error" + <<" while deserializing node metadata at (" + <= 25){ diff --git a/src/mapblock.h b/src/mapblock.h index 2e3eb0d76..8de631a29 100644 --- a/src/mapblock.h +++ b/src/mapblock.h @@ -473,7 +473,7 @@ public: // These don't write or read version by itself // Set disk to true for on-disk format, false for over-the-network format // Precondition: version >= SER_FMT_VER_LOWEST_WRITE - void serialize(std::ostream &os, u8 version, bool disk, int compression_level); + void serialize(std::ostream &result, u8 version, bool disk, int compression_level); // If disk == true: In addition to doing other things, will add // unknown blocks from id-name mapping to wndef void deSerialize(std::istream &is, u8 version, bool disk); diff --git a/src/mapgen/mg_schematic.cpp b/src/mapgen/mg_schematic.cpp index 848a43626..b9ba70302 100644 --- a/src/mapgen/mg_schematic.cpp +++ b/src/mapgen/mg_schematic.cpp @@ -339,7 +339,9 @@ bool Schematic::deserializeFromMts(std::istream *is) delete []schemdata; schemdata = new MapNode[nodecount]; - MapNode::deSerializeBulk(ss, SER_FMT_VER_HIGHEST_READ, schemdata, + std::stringstream d_ss(std::ios_base::binary | std::ios_base::in | std::ios_base::out); + decompress(ss, d_ss, MTSCHEM_MAPNODE_SER_FMT_VER); + MapNode::deSerializeBulk(d_ss, MTSCHEM_MAPNODE_SER_FMT_VER, schemdata, nodecount, 2, 2); // Fix probability values for nodes that were ignore; removed in v2 @@ -384,8 +386,9 @@ bool Schematic::serializeToMts(std::ostream *os) const } // compressed bulk node data - MapNode::serializeBulk(ss, SER_FMT_VER_HIGHEST_WRITE, - schemdata, size.X * size.Y * size.Z, 2, 2, -1); + SharedBuffer buf = MapNode::serializeBulk(MTSCHEM_MAPNODE_SER_FMT_VER, + schemdata, size.X * size.Y * size.Z, 2, 2); + compress(buf, ss, MTSCHEM_MAPNODE_SER_FMT_VER); return true; } diff --git a/src/mapgen/mg_schematic.h b/src/mapgen/mg_schematic.h index 5f64ea280..9189bb3a7 100644 --- a/src/mapgen/mg_schematic.h +++ b/src/mapgen/mg_schematic.h @@ -70,6 +70,7 @@ class Server; #define MTSCHEM_FILE_SIGNATURE 0x4d54534d // 'MTSM' #define MTSCHEM_FILE_VER_HIGHEST_READ 4 #define MTSCHEM_FILE_VER_HIGHEST_WRITE 4 +#define MTSCHEM_MAPNODE_SER_FMT_VER 28 // Fixed serialization version for schematics since these still need to use Zlib #define MTSCHEM_PROB_MASK 0x7F diff --git a/src/mapnode.cpp b/src/mapnode.cpp index f212ea8c9..73bd620fb 100644 --- a/src/mapnode.cpp +++ b/src/mapnode.cpp @@ -730,9 +730,10 @@ void MapNode::deSerialize(u8 *source, u8 version) } } } -void MapNode::serializeBulk(std::ostream &os, int version, + +SharedBuffer MapNode::serializeBulk(int version, const MapNode *nodes, u32 nodecount, - u8 content_width, u8 params_width, int compression_level) + u8 content_width, u8 params_width) { if (!ser_ver_supported(version)) throw VersionMismatchException("ERROR: MapNode format not supported"); @@ -746,8 +747,7 @@ void MapNode::serializeBulk(std::ostream &os, int version, throw SerializationError("MapNode::serializeBulk: serialization to " "version < 24 not possible"); - size_t databuf_size = nodecount * (content_width + params_width); - u8 *databuf = new u8[databuf_size]; + SharedBuffer databuf(nodecount * (content_width + params_width)); u32 start1 = content_width * nodecount; u32 start2 = (content_width + 1) * nodecount; @@ -758,14 +758,7 @@ void MapNode::serializeBulk(std::ostream &os, int version, writeU8(&databuf[start1 + i], nodes[i].param1); writeU8(&databuf[start2 + i], nodes[i].param2); } - - /* - Compress data to output stream - */ - - compressZlib(databuf, databuf_size, os, compression_level); - - delete [] databuf; + return databuf; } // Deserialize bulk node data @@ -781,15 +774,10 @@ void MapNode::deSerializeBulk(std::istream &is, int version, || params_width != 2) FATAL_ERROR("Deserialize bulk node data error"); - // Uncompress or read data - u32 len = nodecount * (content_width + params_width); - std::ostringstream os(std::ios_base::binary); - decompressZlib(is, os); - std::string s = os.str(); - if(s.size() != len) - throw SerializationError("deSerializeBulkNodes: " - "decompress resulted in invalid size"); - const u8 *databuf = reinterpret_cast(s.c_str()); + // read data + const u32 len = nodecount * (content_width + params_width); + Buffer databuf(len); + is.read(reinterpret_cast(*databuf), len); // Deserialize content if(content_width == 1) diff --git a/src/mapnode.h b/src/mapnode.h index 28ff9e43d..afd3a96be 100644 --- a/src/mapnode.h +++ b/src/mapnode.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_bloated.h" #include "light.h" +#include "util/pointer.h" #include #include @@ -293,9 +294,9 @@ struct MapNode // content_width = the number of bytes of content per node // params_width = the number of bytes of params per node // compressed = true to zlib-compress output - static void serializeBulk(std::ostream &os, int version, + static SharedBuffer serializeBulk(int version, const MapNode *nodes, u32 nodecount, - u8 content_width, u8 params_width, int compression_level); + u8 content_width, u8 params_width); static void deSerializeBulk(std::istream &is, int version, MapNode *nodes, u32 nodecount, u8 content_width, u8 params_width); diff --git a/src/serialization.cpp b/src/serialization.cpp index 310604f54..b6ce3b37f 100644 --- a/src/serialization.cpp +++ b/src/serialization.cpp @@ -21,7 +21,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/serialize.h" -#include "zlib.h" +#include +#include /* report a zlib or i/o error */ void zerr(int ret) @@ -197,27 +198,133 @@ void decompressZlib(std::istream &is, std::ostream &os, size_t limit) inflateEnd(&z); } -void compress(const SharedBuffer &data, std::ostream &os, u8 version) +struct ZSTD_Deleter { + void operator() (ZSTD_CStream* cstream) { + ZSTD_freeCStream(cstream); + } + + void operator() (ZSTD_DStream* dstream) { + ZSTD_freeDStream(dstream); + } +}; + +void compressZstd(const u8 *data, size_t data_size, std::ostream &os, int level) +{ + // reusing the context is recommended for performance + // it will destroyed when the thread ends + thread_local std::unique_ptr stream(ZSTD_createCStream()); + + ZSTD_initCStream(stream.get(), level); + + const size_t bufsize = 16384; + char output_buffer[bufsize]; + + ZSTD_inBuffer input = { data, data_size, 0 }; + ZSTD_outBuffer output = { output_buffer, bufsize, 0 }; + + while (input.pos < input.size) { + size_t ret = ZSTD_compressStream(stream.get(), &output, &input); + if (ZSTD_isError(ret)) { + dstream << ZSTD_getErrorName(ret) << std::endl; + throw SerializationError("compressZstd: failed"); + } + if (output.pos) { + os.write(output_buffer, output.pos); + output.pos = 0; + } + } + + size_t ret; + do { + ret = ZSTD_endStream(stream.get(), &output); + if (ZSTD_isError(ret)) { + dstream << ZSTD_getErrorName(ret) << std::endl; + throw SerializationError("compressZstd: failed"); + } + if (output.pos) { + os.write(output_buffer, output.pos); + output.pos = 0; + } + } while (ret != 0); + +} + +void compressZstd(const std::string &data, std::ostream &os, int level) { + compressZstd((u8*)data.c_str(), data.size(), os, level); +} + +void decompressZstd(std::istream &is, std::ostream &os) +{ + // reusing the context is recommended for performance + // it will destroyed when the thread ends + thread_local std::unique_ptr stream(ZSTD_createDStream()); + + ZSTD_initDStream(stream.get()); + + const size_t bufsize = 16384; + char output_buffer[bufsize]; + char input_buffer[bufsize]; + + ZSTD_outBuffer output = { output_buffer, bufsize, 0 }; + ZSTD_inBuffer input = { input_buffer, 0, 0 }; + size_t ret; + do + { + if (input.size == input.pos) { + is.read(input_buffer, bufsize); + input.size = is.gcount(); + input.pos = 0; + } + + ret = ZSTD_decompressStream(stream.get(), &output, &input); + if (ZSTD_isError(ret)) { + dstream << ZSTD_getErrorName(ret) << std::endl; + throw SerializationError("decompressZstd: failed"); + } + if (output.pos) { + os.write(output_buffer, output.pos); + output.pos = 0; + } + } while (ret != 0); + + // Unget all the data that ZSTD_decompressStream didn't take + is.clear(); // Just in case EOF is set + for (u32 i = 0; i < input.size - input.pos; i++) { + is.unget(); + if (is.fail() || is.bad()) + throw SerializationError("decompressZstd: unget failed"); + } +} + +void compress(u8 *data, u32 size, std::ostream &os, u8 version, int level) +{ + if(version >= 29) + { + // map the zlib levels [0,9] to [1,10]. -1 becomes 0 which indicates the default (currently 3) + compressZstd(data, size, os, level + 1); + return; + } + if(version >= 11) { - compressZlib(*data ,data.getSize(), os); + compressZlib(data, size, os, level); return; } - if(data.getSize() == 0) + if(size == 0) return; // Write length (u32) u8 tmp[4]; - writeU32(tmp, data.getSize()); + writeU32(tmp, size); os.write((char*)tmp, 4); // We will be writing 8-bit pairs of more_count and byte u8 more_count = 0; u8 current_byte = data[0]; - for(u32 i=1; i &data, std::ostream &os, u8 version) os.write((char*)¤t_byte, 1); } +void compress(const SharedBuffer &data, std::ostream &os, u8 version, int level) +{ + compress(*data, data.getSize(), os, version, level); +} + +void compress(const std::string &data, std::ostream &os, u8 version, int level) +{ + compress((u8*)data.c_str(), data.size(), os, version, level); +} + void decompress(std::istream &is, std::ostream &os, u8 version) { + if(version >= 29) + { + decompressZstd(is, os); + return; + } + if(version >= 11) { decompressZlib(is, os); diff --git a/src/serialization.h b/src/serialization.h index f399983c4..e83a8c179 100644 --- a/src/serialization.h +++ b/src/serialization.h @@ -63,13 +63,14 @@ with this program; if not, write to the Free Software Foundation, Inc., 26: Never written; read the same as 25 27: Added light spreading flags to blocks 28: Added "private" flag to NodeMetadata + 29: Switched compression to zstd, a bit of reorganization */ // This represents an uninitialized or invalid format #define SER_FMT_VER_INVALID 255 // Highest supported serialization version -#define SER_FMT_VER_HIGHEST_READ 28 +#define SER_FMT_VER_HIGHEST_READ 29 // Saved on disk version -#define SER_FMT_VER_HIGHEST_WRITE 28 +#define SER_FMT_VER_HIGHEST_WRITE 29 // Lowest supported serialization version #define SER_FMT_VER_LOWEST_READ 0 // Lowest serialization version for writing @@ -89,7 +90,12 @@ void compressZlib(const u8 *data, size_t data_size, std::ostream &os, int level void compressZlib(const std::string &data, std::ostream &os, int level = -1); void decompressZlib(std::istream &is, std::ostream &os, size_t limit = 0); +void compressZstd(const u8 *data, size_t data_size, std::ostream &os, int level = 0); +void compressZstd(const std::string &data, std::ostream &os, int level = 0); +void decompressZstd(std::istream &is, std::ostream &os); + // These choose between zlib and a self-made one according to version -void compress(const SharedBuffer &data, std::ostream &os, u8 version); -//void compress(const std::string &data, std::ostream &os, u8 version); +void compress(const SharedBuffer &data, std::ostream &os, u8 version, int level = -1); +void compress(const std::string &data, std::ostream &os, u8 version, int level = -1); +void compress(u8 *data, u32 size, std::ostream &os, u8 version, int level = -1); void decompress(std::istream &is, std::ostream &os, u8 version); diff --git a/src/unittest/test_compression.cpp b/src/unittest/test_compression.cpp index dfcadd4b2..a96282f58 100644 --- a/src/unittest/test_compression.cpp +++ b/src/unittest/test_compression.cpp @@ -37,6 +37,7 @@ public: void testRLECompression(); void testZlibCompression(); void testZlibLargeData(); + void testZstdLargeData(); void testZlibLimit(); void _testZlibLimit(u32 size, u32 limit); }; @@ -48,6 +49,7 @@ void TestCompression::runTests(IGameDef *gamedef) TEST(testRLECompression); TEST(testZlibCompression); TEST(testZlibLargeData); + TEST(testZstdLargeData); TEST(testZlibLimit); } @@ -111,7 +113,7 @@ void TestCompression::testZlibCompression() fromdata[3]=1; std::ostringstream os(std::ios_base::binary); - compress(fromdata, os, SER_FMT_VER_HIGHEST_READ); + compressZlib(*fromdata, fromdata.getSize(), os); std::string str_out = os.str(); @@ -124,7 +126,7 @@ void TestCompression::testZlibCompression() std::istringstream is(str_out, std::ios_base::binary); std::ostringstream os2(std::ios_base::binary); - decompress(is, os2, SER_FMT_VER_HIGHEST_READ); + decompressZlib(is, os2); std::string str_out2 = os2.str(); infostream << "decompress: "; @@ -174,6 +176,42 @@ void TestCompression::testZlibLargeData() } } +void TestCompression::testZstdLargeData() +{ + infostream << "Test: Testing zstd wrappers with a large amount " + "of pseudorandom data" << std::endl; + + u32 size = 500000; + infostream << "Test: Input size of large compressZstd is " + << size << std::endl; + + std::string data_in; + data_in.resize(size); + PseudoRandom pseudorandom(9420); + for (u32 i = 0; i < size; i++) + data_in[i] = pseudorandom.range(0, 255); + + std::ostringstream os_compressed(std::ios::binary); + compressZstd(data_in, os_compressed, 0); + infostream << "Test: Output size of large compressZstd is " + << os_compressed.str().size()< Date: Wed, 1 Sep 2021 10:48:54 +0200 Subject: Don't look for zlib and zstd manually on Windows --- src/CMakeLists.txt | 20 ++++++++------------ util/buildbot/buildwin32.sh | 2 +- util/buildbot/buildwin64.sh | 2 +- 3 files changed, 10 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index addb0af3f..dc2072d11 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -203,6 +203,7 @@ endif(ENABLE_REDIS) find_package(SQLite3 REQUIRED) + OPTION(ENABLE_PROMETHEUS "Enable prometheus client support" FALSE) set(USE_PROMETHEUS FALSE) @@ -239,6 +240,10 @@ if(ENABLE_SPATIAL) endif(ENABLE_SPATIAL) +find_package(ZLIB REQUIRED) +find_package(Zstd REQUIRED) + + if(NOT MSVC) set(USE_GPROF FALSE CACHE BOOL "Use -pg flag for g++") endif() @@ -267,17 +272,10 @@ if(WIN32) endif() set(PLATFORM_LIBS ws2_32.lib version.lib shlwapi.lib ${PLATFORM_LIBS}) - # Zlib stuff - find_path(ZLIB_INCLUDE_DIR "zlib.h" DOC "Zlib include directory") - find_library(ZLIB_LIBRARIES "zlib" DOC "Path to zlib library") - - find_path(ZSTD_INCLUDE_DIR "zstd.h" DOC "Zstd include directory") - find_library(ZSTD_LIBRARY "zstd" DOC "Path to zstd library") - - # Dll's are automatically copied to the output directory by vcpkg when VCPKG_APPLOCAL_DEPS=ON + # DLLs are automatically copied to the output directory by vcpkg when VCPKG_APPLOCAL_DEPS=ON if(NOT VCPKG_APPLOCAL_DEPS) - find_file(ZLIB_DLL NAMES "zlib.dll" "zlib1.dll" DOC "Path to zlib.dll for installation (optional)") - find_file(ZSTD_DLL NAMES "zstd.dll" DOC "Path to zstd.dll for installation (optional)") + find_file(ZLIB_DLL NAMES "" DOC "Path to Zlib DLL for installation (optional)") + find_file(ZSTD_DLL NAMES "" DOC "Path to Zstd DLL for installation (optional)") if(ENABLE_SOUND) set(OPENAL_DLL "" CACHE FILEPATH "Path to OpenAL32.dll for installation (optional)") set(OGG_DLL "" CACHE FILEPATH "Path to libogg.dll for installation (optional)") @@ -299,8 +297,6 @@ else() endif(NOT HAIKU AND NOT APPLE) endif() - find_package(ZLIB REQUIRED) - find_package(Zstd REQUIRED) set(PLATFORM_LIBS -lpthread ${CMAKE_DL_LIBS}) if(APPLE) set(PLATFORM_LIBS "-framework CoreFoundation" ${PLATFORM_LIBS}) diff --git a/util/buildbot/buildwin32.sh b/util/buildbot/buildwin32.sh index b6c188739..eceb5b788 100755 --- a/util/buildbot/buildwin32.sh +++ b/util/buildbot/buildwin32.sh @@ -119,7 +119,7 @@ cmake -S $sourcedir -B . \ -DIRRLICHT_DLL="$irr_dlls" \ \ -DZLIB_INCLUDE_DIR=$libdir/zlib/include \ - -DZLIB_LIBRARIES=$libdir/zlib/lib/libz.dll.a \ + -DZLIB_LIBRARY=$libdir/zlib/lib/libz.dll.a \ -DZLIB_DLL=$libdir/zlib/bin/zlib1.dll \ \ -DZSTD_INCLUDE_DIR=$libdir/zstd/include \ diff --git a/util/buildbot/buildwin64.sh b/util/buildbot/buildwin64.sh index c6cb80cda..68d2ebf0c 100755 --- a/util/buildbot/buildwin64.sh +++ b/util/buildbot/buildwin64.sh @@ -119,7 +119,7 @@ cmake -S $sourcedir -B . \ -DIRRLICHT_DLL="$irr_dlls" \ \ -DZLIB_INCLUDE_DIR=$libdir/zlib/include \ - -DZLIB_LIBRARIES=$libdir/zlib/lib/libz.dll.a \ + -DZLIB_LIBRARY=$libdir/zlib/lib/libz.dll.a \ -DZLIB_DLL=$libdir/zlib/bin/zlib1.dll \ \ -DZSTD_INCLUDE_DIR=$libdir/zstd/include \ -- cgit v1.2.3 From 7f3401412eedc2a54b161f892b29d0a109b3a07b Mon Sep 17 00:00:00 2001 From: NeroBurner Date: Sun, 5 Sep 2021 19:58:50 +0200 Subject: Fix movement in random_input mode (#11592) --- src/client/inputhandler.cpp | 42 +++++++++++++++++++++++++++++++++++++----- src/client/inputhandler.h | 6 ++++-- 2 files changed, 41 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/client/inputhandler.cpp b/src/client/inputhandler.cpp index 980765efa..a6ba87e8d 100644 --- a/src/client/inputhandler.cpp +++ b/src/client/inputhandler.cpp @@ -138,11 +138,8 @@ bool MyEventReceiver::OnEvent(const SEvent &event) #endif } else if (event.EventType == irr::EET_JOYSTICK_INPUT_EVENT) { - /* TODO add a check like: - if (event.JoystickEvent != joystick_we_listen_for) - return false; - */ - return joystick->handleEvent(event.JoystickEvent); + // joystick may be nullptr if game is launched with '--random-input' parameter + return joystick && joystick->handleEvent(event.JoystickEvent); } else if (event.EventType == irr::EET_MOUSE_INPUT_EVENT) { // Handle mouse events KeyPress key; @@ -243,4 +240,39 @@ void RandomInputHandler::step(float dtime) } } mousepos += mousespeed; + static bool useJoystick = false; + { + static float counterUseJoystick = 0; + counterUseJoystick -= dtime; + if (counterUseJoystick < 0.0) { + counterUseJoystick = 5.0; // switch between joystick and keyboard direction input + useJoystick = !useJoystick; + } + } + if (useJoystick) { + static float counterMovement = 0; + counterMovement -= dtime; + if (counterMovement < 0.0) { + counterMovement = 0.1 * Rand(1, 40); + movementSpeed = Rand(0,100)*0.01; + movementDirection = Rand(-100, 100)*0.01 * M_PI; + } + } else { + bool f = keydown[keycache.key[KeyType::FORWARD]], + l = keydown[keycache.key[KeyType::LEFT]]; + if (f || l) { + movementSpeed = 1.0f; + if (f && !l) + movementDirection = 0.0; + else if (!f && l) + movementDirection = -M_PI_2; + else if (f && l) + movementDirection = -M_PI_4; + else + movementDirection = 0.0; + } else { + movementSpeed = 0.0; + movementDirection = 0.0; + } + } } diff --git a/src/client/inputhandler.h b/src/client/inputhandler.h index 76e3c7b5b..e630b860e 100644 --- a/src/client/inputhandler.h +++ b/src/client/inputhandler.h @@ -393,8 +393,8 @@ public: virtual bool wasKeyPressed(GameKeyType k) { return false; } virtual bool wasKeyReleased(GameKeyType k) { return false; } virtual bool cancelPressed() { return false; } - virtual float getMovementSpeed() {return 0.0f;} - virtual float getMovementDirection() {return 0.0f;} + virtual float getMovementSpeed() { return movementSpeed; } + virtual float getMovementDirection() { return movementDirection; } virtual v2s32 getMousePos() { return mousepos; } virtual void setMousePos(s32 x, s32 y) { mousepos = v2s32(x, y); } @@ -408,4 +408,6 @@ private: KeyList keydown; v2s32 mousepos; v2s32 mousespeed; + float movementSpeed; + float movementDirection; }; -- cgit v1.2.3 From bbfae0cc673d3abdc21224c53e09b209ee4688a2 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Thu, 9 Sep 2021 16:51:35 +0200 Subject: Dynamic_Add_Media v2 (#11550) --- builtin/game/misc.lua | 23 +--- doc/lua_api.txt | 37 ++++-- src/client/client.cpp | 39 +++++- src/client/client.h | 6 + src/client/clientmedia.cpp | 252 +++++++++++++++++++++++++++++------- src/client/clientmedia.h | 159 ++++++++++++++++++----- src/filesys.cpp | 12 ++ src/filesys.h | 4 + src/network/clientopcodes.cpp | 2 +- src/network/clientpackethandler.cpp | 129 +++++++++++------- src/network/networkprotocol.h | 12 +- src/network/serveropcodes.cpp | 4 +- src/network/serverpackethandler.cpp | 34 ++++- src/script/cpp_api/s_server.cpp | 66 ++++++++++ src/script/cpp_api/s_server.h | 6 + src/script/lua_api/l_server.cpp | 38 +++--- src/script/lua_api/l_server.h | 2 +- src/server.cpp | 193 +++++++++++++++++++-------- src/server.h | 22 +++- 19 files changed, 795 insertions(+), 245 deletions(-) (limited to 'src') diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua index aac6c2d18..63d64817c 100644 --- a/builtin/game/misc.lua +++ b/builtin/game/misc.lua @@ -269,27 +269,8 @@ function core.cancel_shutdown_requests() end --- Callback handling for dynamic_add_media - -local dynamic_add_media_raw = core.dynamic_add_media_raw -core.dynamic_add_media_raw = nil -function core.dynamic_add_media(filepath, callback) - local ret = dynamic_add_media_raw(filepath) - if ret == false then - return ret - end - if callback == nil then - core.log("deprecated", "Calling minetest.dynamic_add_media without ".. - "a callback is deprecated and will stop working in future versions.") - else - -- At the moment async loading is not actually implemented, so we - -- immediately call the callback ourselves - for _, name in ipairs(ret) do - callback(name) - end - end - return true -end +-- Used for callback handling with dynamic_add_media +core.dynamic_media_callbacks = {} -- PNG encoder safety wrapper diff --git a/doc/lua_api.txt b/doc/lua_api.txt index e99c1d1e6..3a1a3f02f 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -5649,22 +5649,33 @@ Server * Returns a code (0: successful, 1: no such player, 2: player is connected) * `minetest.remove_player_auth(name)`: remove player authentication data * Returns boolean indicating success (false if player nonexistant) -* `minetest.dynamic_add_media(filepath, callback)` - * `filepath`: path to a media file on the filesystem - * `callback`: function with arguments `name`, where name is a player name - (previously there was no callback argument; omitting it is deprecated) - * Adds the file to the media sent to clients by the server on startup - and also pushes this file to already connected clients. - The file must be a supported image, sound or model format. It must not be - modified, deleted, moved or renamed after calling this function. - The list of dynamically added media is not persisted. +* `minetest.dynamic_add_media(options, callback)` + * `options`: table containing the following parameters + * `filepath`: path to a media file on the filesystem + * `to_player`: name of the player the media should be sent to instead of + all players (optional) + * `ephemeral`: boolean that marks the media as ephemeral, + it will not be cached on the client (optional, default false) + * `callback`: function with arguments `name`, which is a player name + * Pushes the specified media file to client(s). (details below) + The file must be a supported image, sound or model format. + Dynamically added media is not persisted between server restarts. * Returns false on error, true if the request was accepted * The given callback will be called for every player as soon as the media is available on the client. - Old clients that lack support for this feature will not see the media - unless they reconnect to the server. (callback won't be called) - * Since media transferred this way currently does not use client caching - or HTTP transfers, dynamic media should not be used with big files. + * Details/Notes: + * If `ephemeral`=false and `to_player` is unset the file is added to the media + sent to clients on startup, this means the media will appear even on + old clients if they rejoin the server. + * If `ephemeral`=false the file must not be modified, deleted, moved or + renamed after calling this function. + * Regardless of any use of `ephemeral`, adding media files with the same + name twice is not possible/guaranteed to work. An exception to this is the + use of `to_player` to send the same, already existent file to multiple + chosen players. + * Clients will attempt to fetch files added this way via remote media, + this can make transfer of bigger files painless (if set up). Nevertheless + it is advised not to use dynamic media for big media files. Bans ---- diff --git a/src/client/client.cpp b/src/client/client.cpp index 3c5559fca..13ff22e8e 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -555,6 +555,29 @@ void Client::step(float dtime) m_media_downloader = NULL; } } + { + // Acknowledge dynamic media downloads to server + std::vector done; + for (auto it = m_pending_media_downloads.begin(); + it != m_pending_media_downloads.end();) { + assert(it->second->isStarted()); + it->second->step(this); + if (it->second->isDone()) { + done.emplace_back(it->first); + + it = m_pending_media_downloads.erase(it); + } else { + it++; + } + + if (done.size() == 255) { // maximum in one packet + sendHaveMedia(done); + done.clear(); + } + } + if (!done.empty()) + sendHaveMedia(done); + } /* If the server didn't update the inventory in a while, revert @@ -770,7 +793,8 @@ void Client::request_media(const std::vector &file_requests) Send(&pkt); infostream << "Client: Sending media request list to server (" - << file_requests.size() << " files. packet size)" << std::endl; + << file_requests.size() << " files, packet size " + << pkt.getSize() << ")" << std::endl; } void Client::initLocalMapSaving(const Address &address, @@ -1295,6 +1319,19 @@ void Client::sendPlayerPos() Send(&pkt); } +void Client::sendHaveMedia(const std::vector &tokens) +{ + NetworkPacket pkt(TOSERVER_HAVE_MEDIA, 1 + tokens.size() * 4); + + sanity_check(tokens.size() < 256); + + pkt << static_cast(tokens.size()); + for (u32 token : tokens) + pkt << token; + + Send(&pkt); +} + void Client::removeNode(v3s16 p) { std::map modified_blocks; diff --git a/src/client/client.h b/src/client/client.h index 85ca24049..c1a38ba48 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -53,6 +53,7 @@ class ISoundManager; class NodeDefManager; //class IWritableCraftDefManager; class ClientMediaDownloader; +class SingleMediaDownloader; struct MapDrawControl; class ModChannelMgr; class MtEventManager; @@ -245,6 +246,7 @@ public: void sendDamage(u16 damage); void sendRespawn(); void sendReady(); + void sendHaveMedia(const std::vector &tokens); ClientEnvironment& getEnv() { return m_env; } ITextureSource *tsrc() { return getTextureSource(); } @@ -536,9 +538,13 @@ private: bool m_activeobjects_received = false; bool m_mods_loaded = false; + std::vector m_remote_media_servers; + // Media downloader, only exists during init ClientMediaDownloader *m_media_downloader; // Set of media filenames pushed by server at runtime std::unordered_set m_media_pushed_files; + // Pending downloads of dynamic media (key: token) + std::vector>> m_pending_media_downloads; // time_of_day speed approximation for old protocol bool m_time_of_day_set = false; diff --git a/src/client/clientmedia.cpp b/src/client/clientmedia.cpp index 0f9ba5356..6c5d4a8bf 100644 --- a/src/client/clientmedia.cpp +++ b/src/client/clientmedia.cpp @@ -49,7 +49,6 @@ bool clientMediaUpdateCache(const std::string &raw_hash, const std::string &file */ ClientMediaDownloader::ClientMediaDownloader(): - m_media_cache(getMediaCacheDir()), m_httpfetch_caller(HTTPFETCH_DISCARD) { } @@ -66,6 +65,12 @@ ClientMediaDownloader::~ClientMediaDownloader() delete remote; } +bool ClientMediaDownloader::loadMedia(Client *client, const std::string &data, + const std::string &name) +{ + return client->loadMedia(data, name); +} + void ClientMediaDownloader::addFile(const std::string &name, const std::string &sha1) { assert(!m_initial_step_done); // pre-condition @@ -105,7 +110,7 @@ void ClientMediaDownloader::addRemoteServer(const std::string &baseurl) { assert(!m_initial_step_done); // pre-condition - #ifdef USE_CURL +#ifdef USE_CURL if (g_settings->getBool("enable_remote_media_server")) { infostream << "Client: Adding remote server \"" @@ -117,13 +122,13 @@ void ClientMediaDownloader::addRemoteServer(const std::string &baseurl) m_remotes.push_back(remote); } - #else +#else infostream << "Client: Ignoring remote server \"" << baseurl << "\" because cURL support is not compiled in" << std::endl; - #endif +#endif } void ClientMediaDownloader::step(Client *client) @@ -172,36 +177,21 @@ void ClientMediaDownloader::initialStep(Client *client) // Check media cache m_uncached_count = m_files.size(); for (auto &file_it : m_files) { - std::string name = file_it.first; + const std::string &name = file_it.first; FileStatus *filestatus = file_it.second; const std::string &sha1 = filestatus->sha1; - std::ostringstream tmp_os(std::ios_base::binary); - bool found_in_cache = m_media_cache.load(hex_encode(sha1), tmp_os); - - // If found in cache, try to load it from there - if (found_in_cache) { - bool success = checkAndLoad(name, sha1, - tmp_os.str(), true, client); - if (success) { - filestatus->received = true; - m_uncached_count--; - } + if (tryLoadFromCache(name, sha1, client)) { + filestatus->received = true; + m_uncached_count--; } } assert(m_uncached_received_count == 0); // Create the media cache dir if we are likely to write to it - if (m_uncached_count != 0) { - bool did = fs::CreateAllDirs(getMediaCacheDir()); - if (!did) { - errorstream << "Client: " - << "Could not create media cache directory: " - << getMediaCacheDir() - << std::endl; - } - } + if (m_uncached_count != 0) + createCacheDirs(); // If we found all files in the cache, report this fact to the server. // If the server reported no remote servers, immediately start @@ -301,8 +291,7 @@ void ClientMediaDownloader::remoteHashSetReceived( // available on this server, add this server // to the available_remotes array - for(std::map::iterator - it = m_files.upper_bound(m_name_bound); + for(auto it = m_files.upper_bound(m_name_bound); it != m_files.end(); ++it) { FileStatus *f = it->second; if (!f->received && sha1_set.count(f->sha1)) @@ -328,8 +317,7 @@ void ClientMediaDownloader::remoteMediaReceived( std::string name; { - std::unordered_map::iterator it = - m_remote_file_transfers.find(fetch_result.request_id); + auto it = m_remote_file_transfers.find(fetch_result.request_id); assert(it != m_remote_file_transfers.end()); name = it->second; m_remote_file_transfers.erase(it); @@ -398,8 +386,7 @@ void ClientMediaDownloader::startRemoteMediaTransfers() { bool changing_name_bound = true; - for (std::map::iterator - files_iter = m_files.upper_bound(m_name_bound); + for (auto files_iter = m_files.upper_bound(m_name_bound); files_iter != m_files.end(); ++files_iter) { // Abort if active fetch limit is exceeded @@ -477,19 +464,18 @@ void ClientMediaDownloader::startConventionalTransfers(Client *client) } } -void ClientMediaDownloader::conventionalTransferDone( +bool ClientMediaDownloader::conventionalTransferDone( const std::string &name, const std::string &data, Client *client) { // Check that file was announced - std::map::iterator - file_iter = m_files.find(name); + auto file_iter = m_files.find(name); if (file_iter == m_files.end()) { errorstream << "Client: server sent media file that was" << "not announced, ignoring it: \"" << name << "\"" << std::endl; - return; + return false; } FileStatus *filestatus = file_iter->second; assert(filestatus != NULL); @@ -499,7 +485,7 @@ void ClientMediaDownloader::conventionalTransferDone( errorstream << "Client: server sent media file that we already" << "received, ignoring it: \"" << name << "\"" << std::endl; - return; + return true; } // Mark file as received, regardless of whether loading it works and @@ -512,9 +498,45 @@ void ClientMediaDownloader::conventionalTransferDone( // Check that received file matches announced checksum // If so, load it checkAndLoad(name, filestatus->sha1, data, false, client); + + return true; +} + +/* + IClientMediaDownloader +*/ + +IClientMediaDownloader::IClientMediaDownloader(): + m_media_cache(getMediaCacheDir()), m_write_to_cache(true) +{ } -bool ClientMediaDownloader::checkAndLoad( +void IClientMediaDownloader::createCacheDirs() +{ + if (!m_write_to_cache) + return; + + std::string path = getMediaCacheDir(); + if (!fs::CreateAllDirs(path)) { + errorstream << "Client: Could not create media cache directory: " + << path << std::endl; + } +} + +bool IClientMediaDownloader::tryLoadFromCache(const std::string &name, + const std::string &sha1, Client *client) +{ + std::ostringstream tmp_os(std::ios_base::binary); + bool found_in_cache = m_media_cache.load(hex_encode(sha1), tmp_os); + + // If found in cache, try to load it from there + if (found_in_cache) + return checkAndLoad(name, sha1, tmp_os.str(), true, client); + + return false; +} + +bool IClientMediaDownloader::checkAndLoad( const std::string &name, const std::string &sha1, const std::string &data, bool is_from_cache, Client *client) { @@ -544,7 +566,7 @@ bool ClientMediaDownloader::checkAndLoad( } // Checksum is ok, try loading the file - bool success = client->loadMedia(data, name); + bool success = loadMedia(client, data, name); if (!success) { infostream << "Client: " << "Failed to load " << cached_or_received << " media: " @@ -559,7 +581,7 @@ bool ClientMediaDownloader::checkAndLoad( << std::endl; // Update cache (unless we just loaded the file from the cache) - if (!is_from_cache) + if (!is_from_cache && m_write_to_cache) m_media_cache.update(sha1_hex, data); return true; @@ -587,12 +609,10 @@ std::string ClientMediaDownloader::serializeRequiredHashSet() // Write list of hashes of files that have not been // received (found in cache) yet - for (std::map::iterator - it = m_files.begin(); - it != m_files.end(); ++it) { - if (!it->second->received) { - FATAL_ERROR_IF(it->second->sha1.size() != 20, "Invalid SHA1 size"); - os << it->second->sha1; + for (const auto &it : m_files) { + if (!it.second->received) { + FATAL_ERROR_IF(it.second->sha1.size() != 20, "Invalid SHA1 size"); + os << it.second->sha1; } } @@ -628,3 +648,145 @@ void ClientMediaDownloader::deSerializeHashSet(const std::string &data, result.insert(data.substr(pos, 20)); } } + +/* + SingleMediaDownloader +*/ + +SingleMediaDownloader::SingleMediaDownloader(bool write_to_cache): + m_httpfetch_caller(HTTPFETCH_DISCARD) +{ + m_write_to_cache = write_to_cache; +} + +SingleMediaDownloader::~SingleMediaDownloader() +{ + if (m_httpfetch_caller != HTTPFETCH_DISCARD) + httpfetch_caller_free(m_httpfetch_caller); +} + +bool SingleMediaDownloader::loadMedia(Client *client, const std::string &data, + const std::string &name) +{ + return client->loadMedia(data, name, true); +} + +void SingleMediaDownloader::addFile(const std::string &name, const std::string &sha1) +{ + assert(m_stage == STAGE_INIT); // pre-condition + + assert(!name.empty()); + assert(sha1.size() == 20); + + FATAL_ERROR_IF(!m_file_name.empty(), "Cannot add a second file"); + m_file_name = name; + m_file_sha1 = sha1; +} + +void SingleMediaDownloader::addRemoteServer(const std::string &baseurl) +{ + assert(m_stage == STAGE_INIT); // pre-condition + + if (g_settings->getBool("enable_remote_media_server")) + m_remotes.emplace_back(baseurl); +} + +void SingleMediaDownloader::step(Client *client) +{ + if (m_stage == STAGE_INIT) { + m_stage = STAGE_CACHE_CHECKED; + initialStep(client); + } + + // Remote media: check for completion of fetches + if (m_httpfetch_caller != HTTPFETCH_DISCARD) { + HTTPFetchResult fetch_result; + while (httpfetch_async_get(m_httpfetch_caller, fetch_result)) { + remoteMediaReceived(fetch_result, client); + } + } +} + +bool SingleMediaDownloader::conventionalTransferDone(const std::string &name, + const std::string &data, Client *client) +{ + if (name != m_file_name) + return false; + + // Mark file as received unconditionally and try to load it + m_stage = STAGE_DONE; + checkAndLoad(name, m_file_sha1, data, false, client); + return true; +} + +void SingleMediaDownloader::initialStep(Client *client) +{ + if (tryLoadFromCache(m_file_name, m_file_sha1, client)) + m_stage = STAGE_DONE; + if (isDone()) + return; + + createCacheDirs(); + + // If the server reported no remote servers, immediately fall back to + // conventional transfer. + if (!USE_CURL || m_remotes.empty()) { + startConventionalTransfer(client); + } else { + // Otherwise start by requesting the file from the first remote media server + m_httpfetch_caller = httpfetch_caller_alloc(); + m_current_remote = 0; + startRemoteMediaTransfer(); + } +} + +void SingleMediaDownloader::remoteMediaReceived( + const HTTPFetchResult &fetch_result, Client *client) +{ + sanity_check(!isDone()); + sanity_check(m_current_remote >= 0); + + // If fetch succeeded, try to load it + if (fetch_result.succeeded) { + bool success = checkAndLoad(m_file_name, m_file_sha1, + fetch_result.data, false, client); + if (success) { + m_stage = STAGE_DONE; + return; + } + } + + // Otherwise try the next remote server or fall back to conventional transfer + m_current_remote++; + if (m_current_remote >= (int)m_remotes.size()) { + infostream << "Client: Failed to remote-fetch \"" << m_file_name + << "\". Requesting it the usual way." << std::endl; + m_current_remote = -1; + startConventionalTransfer(client); + } else { + startRemoteMediaTransfer(); + } +} + +void SingleMediaDownloader::startRemoteMediaTransfer() +{ + std::string url = m_remotes.at(m_current_remote) + hex_encode(m_file_sha1); + verbosestream << "Client: Requesting remote media file " + << "\"" << m_file_name << "\" " << "\"" << url << "\"" << std::endl; + + HTTPFetchRequest fetch_request; + fetch_request.url = url; + fetch_request.caller = m_httpfetch_caller; + fetch_request.request_id = m_httpfetch_next_id; + fetch_request.timeout = g_settings->getS32("curl_file_download_timeout"); + httpfetch_async(fetch_request); + + m_httpfetch_next_id++; +} + +void SingleMediaDownloader::startConventionalTransfer(Client *client) +{ + std::vector requests; + requests.emplace_back(m_file_name); + client->request_media(requests); +} diff --git a/src/client/clientmedia.h b/src/client/clientmedia.h index e97a0f24b..aa7b0f398 100644 --- a/src/client/clientmedia.h +++ b/src/client/clientmedia.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes.h" #include "filecache.h" +#include "util/basic_macros.h" #include #include #include @@ -38,7 +39,62 @@ struct HTTPFetchResult; bool clientMediaUpdateCache(const std::string &raw_hash, const std::string &filedata); -class ClientMediaDownloader +// more of a base class than an interface but this name was most convenient... +class IClientMediaDownloader +{ +public: + DISABLE_CLASS_COPY(IClientMediaDownloader) + + virtual bool isStarted() const = 0; + + // If this returns true, the downloader is done and can be deleted + virtual bool isDone() const = 0; + + // Add a file to the list of required file (but don't fetch it yet) + virtual void addFile(const std::string &name, const std::string &sha1) = 0; + + // Add a remote server to the list; ignored if not built with cURL + virtual void addRemoteServer(const std::string &baseurl) = 0; + + // Steps the media downloader: + // - May load media into client by calling client->loadMedia() + // - May check media cache for files + // - May add files to media cache + // - May start remote transfers by calling httpfetch_async + // - May check for completion of current remote transfers + // - May start conventional transfers by calling client->request_media() + // - May inform server that all media has been loaded + // by calling client->received_media() + // After step has been called once, don't call addFile/addRemoteServer. + virtual void step(Client *client) = 0; + + // Must be called for each file received through TOCLIENT_MEDIA + // returns true if this file belongs to this downloader + virtual bool conventionalTransferDone(const std::string &name, + const std::string &data, Client *client) = 0; + +protected: + IClientMediaDownloader(); + virtual ~IClientMediaDownloader() = default; + + // Forwards the call to the appropriate Client method + virtual bool loadMedia(Client *client, const std::string &data, + const std::string &name) = 0; + + void createCacheDirs(); + + bool tryLoadFromCache(const std::string &name, const std::string &sha1, + Client *client); + + bool checkAndLoad(const std::string &name, const std::string &sha1, + const std::string &data, bool is_from_cache, Client *client); + + // Filesystem-based media cache + FileCache m_media_cache; + bool m_write_to_cache; +}; + +class ClientMediaDownloader : public IClientMediaDownloader { public: ClientMediaDownloader(); @@ -52,39 +108,29 @@ public: return 0.0f; } - bool isStarted() const { + bool isStarted() const override { return m_initial_step_done; } - // If this returns true, the downloader is done and can be deleted - bool isDone() const { + bool isDone() const override { return m_initial_step_done && m_uncached_received_count == m_uncached_count; } - // Add a file to the list of required file (but don't fetch it yet) - void addFile(const std::string &name, const std::string &sha1); + void addFile(const std::string &name, const std::string &sha1) override; - // Add a remote server to the list; ignored if not built with cURL - void addRemoteServer(const std::string &baseurl); + void addRemoteServer(const std::string &baseurl) override; - // Steps the media downloader: - // - May load media into client by calling client->loadMedia() - // - May check media cache for files - // - May add files to media cache - // - May start remote transfers by calling httpfetch_async - // - May check for completion of current remote transfers - // - May start conventional transfers by calling client->request_media() - // - May inform server that all media has been loaded - // by calling client->received_media() - // After step has been called once, don't call addFile/addRemoteServer. - void step(Client *client); + void step(Client *client) override; - // Must be called for each file received through TOCLIENT_MEDIA - void conventionalTransferDone( + bool conventionalTransferDone( const std::string &name, const std::string &data, - Client *client); + Client *client) override; + +protected: + bool loadMedia(Client *client, const std::string &data, + const std::string &name) override; private: struct FileStatus { @@ -107,13 +153,9 @@ private: void startRemoteMediaTransfers(); void startConventionalTransfers(Client *client); - bool checkAndLoad(const std::string &name, const std::string &sha1, - const std::string &data, bool is_from_cache, - Client *client); - - std::string serializeRequiredHashSet(); static void deSerializeHashSet(const std::string &data, std::set &result); + std::string serializeRequiredHashSet(); // Maps filename to file status std::map m_files; @@ -121,9 +163,6 @@ private: // Array of remote media servers std::vector m_remotes; - // Filesystem-based media cache - FileCache m_media_cache; - // Has an attempt been made to load media files from the file cache? // Have hash sets been requested from remote servers? bool m_initial_step_done = false; @@ -149,3 +188,63 @@ private: std::string m_name_bound = ""; }; + +// A media downloader that only downloads a single file. +// It does/doesn't do several things the normal downloader does: +// - won't fetch hash sets from remote servers +// - will mark loaded media as coming from file push +// - writing to file cache is optional +class SingleMediaDownloader : public IClientMediaDownloader +{ +public: + SingleMediaDownloader(bool write_to_cache); + ~SingleMediaDownloader(); + + bool isStarted() const override { + return m_stage > STAGE_INIT; + } + + bool isDone() const override { + return m_stage >= STAGE_DONE; + } + + void addFile(const std::string &name, const std::string &sha1) override; + + void addRemoteServer(const std::string &baseurl) override; + + void step(Client *client) override; + + bool conventionalTransferDone(const std::string &name, + const std::string &data, Client *client) override; + +protected: + bool loadMedia(Client *client, const std::string &data, + const std::string &name) override; + +private: + void initialStep(Client *client); + void remoteMediaReceived(const HTTPFetchResult &fetch_result, Client *client); + void startRemoteMediaTransfer(); + void startConventionalTransfer(Client *client); + + enum Stage { + STAGE_INIT, + STAGE_CACHE_CHECKED, // we have tried to load the file from cache + STAGE_DONE + }; + + // Information about the one file we want to fetch + std::string m_file_name; + std::string m_file_sha1; + s32 m_current_remote; + + // Array of remote media servers + std::vector m_remotes; + + enum Stage m_stage = STAGE_INIT; + + // Status of remote transfers + unsigned long m_httpfetch_caller; + unsigned long m_httpfetch_next_id = 0; + +}; diff --git a/src/filesys.cpp b/src/filesys.cpp index 99b030624..0941739b8 100644 --- a/src/filesys.cpp +++ b/src/filesys.cpp @@ -21,8 +21,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/string.h" #include #include +#include #include #include +#include #include #include "log.h" #include "config.h" @@ -811,5 +813,15 @@ bool Rename(const std::string &from, const std::string &to) return rename(from.c_str(), to.c_str()) == 0; } +std::string CreateTempFile() +{ + std::string path = TempPath() + DIR_DELIM "MT_XXXXXX"; + int fd = mkstemp(&path[0]); // modifies path + if (fd == -1) + return ""; + close(fd); + return path; +} + } // namespace fs diff --git a/src/filesys.h b/src/filesys.h index a9584b036..f72cb0ba2 100644 --- a/src/filesys.h +++ b/src/filesys.h @@ -71,6 +71,10 @@ bool DeleteSingleFileOrEmptyDirectory(const std::string &path); // Returns path to temp directory, can return "" on error std::string TempPath(); +// Returns path to securely-created temporary file (will already exist when this function returns) +// can return "" on error +std::string CreateTempFile(); + /* Returns a list of subdirectories, including the path itself, but excluding hidden directories (whose names start with . or _) */ diff --git a/src/network/clientopcodes.cpp b/src/network/clientopcodes.cpp index 55cfdd4dc..a98a5e7d1 100644 --- a/src/network/clientopcodes.cpp +++ b/src/network/clientopcodes.cpp @@ -204,7 +204,7 @@ const ServerCommandFactory serverCommandFactoryTable[TOSERVER_NUM_MSG_TYPES] = null_command_factory, // 0x3e null_command_factory, // 0x3f { "TOSERVER_REQUEST_MEDIA", 1, true }, // 0x40 - null_command_factory, // 0x41 + { "TOSERVER_HAVE_MEDIA", 2, true }, // 0x41 null_command_factory, // 0x42 { "TOSERVER_CLIENT_READY", 1, true }, // 0x43 null_command_factory, // 0x44 diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index a631a3178..9c9c59d13 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -670,21 +670,19 @@ void Client::handleCommand_AnnounceMedia(NetworkPacket* pkt) m_media_downloader->addFile(name, sha1_raw); } - try { + { std::string str; - *pkt >> str; Strfnd sf(str); - while(!sf.at_end()) { + while (!sf.at_end()) { std::string baseurl = trim(sf.next(",")); - if (!baseurl.empty()) + if (!baseurl.empty()) { + m_remote_media_servers.emplace_back(baseurl); m_media_downloader->addRemoteServer(baseurl); + } } } - catch(SerializationError& e) { - // not supported by server or turned off - } m_media_downloader->step(this); } @@ -716,31 +714,38 @@ void Client::handleCommand_Media(NetworkPacket* pkt) if (num_files == 0) return; - if (!m_media_downloader || !m_media_downloader->isStarted()) { - const char *problem = m_media_downloader ? - "media has not been requested" : - "all media has been received already"; - errorstream << "Client: Received media but " - << problem << "! " - << " bunch " << bunch_i << "/" << num_bunches - << " files=" << num_files - << " size=" << pkt->getSize() << std::endl; - return; - } + bool init_phase = m_media_downloader && m_media_downloader->isStarted(); - // Mesh update thread must be stopped while - // updating content definitions - sanity_check(!m_mesh_update_thread.isRunning()); + if (init_phase) { + // Mesh update thread must be stopped while + // updating content definitions + sanity_check(!m_mesh_update_thread.isRunning()); + } - for (u32 i=0; i < num_files; i++) { - std::string name; + for (u32 i = 0; i < num_files; i++) { + std::string name, data; *pkt >> name; + data = pkt->readLongString(); - std::string data = pkt->readLongString(); - - m_media_downloader->conventionalTransferDone( - name, data, this); + bool ok = false; + if (init_phase) { + ok = m_media_downloader->conventionalTransferDone(name, data, this); + } else { + // Check pending dynamic transfers, one of them must be it + for (const auto &it : m_pending_media_downloads) { + if (it.second->conventionalTransferDone(name, data, this)) { + ok = true; + break; + } + } + } + if (!ok) { + errorstream << "Client: Received media \"" << name + << "\" but no downloads pending. " << num_bunches << " bunches, " + << num_files << " in this one. (init_phase=" << init_phase + << ")" << std::endl; + } } } @@ -1497,46 +1502,72 @@ void Client::handleCommand_PlayerSpeed(NetworkPacket *pkt) void Client::handleCommand_MediaPush(NetworkPacket *pkt) { std::string raw_hash, filename, filedata; + u32 token; bool cached; *pkt >> raw_hash >> filename >> cached; - filedata = pkt->readLongString(); + if (m_proto_ver >= 40) + *pkt >> token; + else + filedata = pkt->readLongString(); - if (raw_hash.size() != 20 || filedata.empty() || filename.empty() || + if (raw_hash.size() != 20 || filename.empty() || + (m_proto_ver < 40 && filedata.empty()) || !string_allowed(filename, TEXTURENAME_ALLOWED_CHARS)) { throw PacketError("Illegal filename, data or hash"); } - verbosestream << "Server pushes media file \"" << filename << "\" with " - << filedata.size() << " bytes of data (cached=" << cached - << ")" << std::endl; + verbosestream << "Server pushes media file \"" << filename << "\" "; + if (filedata.empty()) + verbosestream << "to be fetched "; + else + verbosestream << "with " << filedata.size() << " bytes "; + verbosestream << "(cached=" << cached << ")" << std::endl; if (m_media_pushed_files.count(filename) != 0) { - // Silently ignore for synchronization purposes + // Ignore (but acknowledge). Previously this was for sync purposes, + // but even in new versions media cannot be replaced at runtime. + if (m_proto_ver >= 40) + sendHaveMedia({ token }); return; } - // Compute and check checksum of data - std::string computed_hash; - { - SHA1 ctx; - ctx.addBytes(filedata.c_str(), filedata.size()); - unsigned char *buf = ctx.getDigest(); - computed_hash.assign((char*) buf, 20); - free(buf); - } - if (raw_hash != computed_hash) { - verbosestream << "Hash of file data mismatches, ignoring." << std::endl; + if (!filedata.empty()) { + // LEGACY CODEPATH + // Compute and check checksum of data + std::string computed_hash; + { + SHA1 ctx; + ctx.addBytes(filedata.c_str(), filedata.size()); + unsigned char *buf = ctx.getDigest(); + computed_hash.assign((char*) buf, 20); + free(buf); + } + if (raw_hash != computed_hash) { + verbosestream << "Hash of file data mismatches, ignoring." << std::endl; + return; + } + + // Actually load media + loadMedia(filedata, filename, true); + m_media_pushed_files.insert(filename); + + // Cache file for the next time when this client joins the same server + if (cached) + clientMediaUpdateCache(raw_hash, filedata); return; } - // Actually load media - loadMedia(filedata, filename, true); m_media_pushed_files.insert(filename); - // Cache file for the next time when this client joins the same server - if (cached) - clientMediaUpdateCache(raw_hash, filedata); + // create a downloader for this file + auto downloader = new SingleMediaDownloader(cached); + m_pending_media_downloads.emplace_back(token, downloader); + downloader->addFile(filename, raw_hash); + for (const auto &baseurl : m_remote_media_servers) + downloader->addRemoteServer(baseurl); + + downloader->step(this); } /* diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index b647aab1a..8214cc5b1 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -207,6 +207,7 @@ with this program; if not, write to the Free Software Foundation, Inc., Minimap modes PROTOCOL VERSION 40: Added 'basic_debug' privilege + TOCLIENT_MEDIA_PUSH changed, TOSERVER_HAVE_MEDIA added */ #define LATEST_PROTOCOL_VERSION 40 @@ -317,9 +318,8 @@ enum ToClientCommand /* std::string raw_hash std::string filename + u32 callback_token bool should_be_cached - u32 len - char filedata[len] */ // (oops, there is some gap here) @@ -938,7 +938,13 @@ enum ToServerCommand } */ - TOSERVER_RECEIVED_MEDIA = 0x41, // Obsolete + TOSERVER_HAVE_MEDIA = 0x41, + /* + u8 number of callback tokens + for each: + u32 token + */ + TOSERVER_BREATH = 0x42, // Obsolete TOSERVER_CLIENT_READY = 0x43, diff --git a/src/network/serveropcodes.cpp b/src/network/serveropcodes.cpp index aea5d7174..44b65e8da 100644 --- a/src/network/serveropcodes.cpp +++ b/src/network/serveropcodes.cpp @@ -89,7 +89,7 @@ const ToServerCommandHandler toServerCommandTable[TOSERVER_NUM_MSG_TYPES] = null_command_handler, // 0x3e null_command_handler, // 0x3f { "TOSERVER_REQUEST_MEDIA", TOSERVER_STATE_STARTUP, &Server::handleCommand_RequestMedia }, // 0x40 - null_command_handler, // 0x41 + { "TOSERVER_HAVE_MEDIA", TOSERVER_STATE_INGAME, &Server::handleCommand_HaveMedia }, // 0x41 null_command_handler, // 0x42 { "TOSERVER_CLIENT_READY", TOSERVER_STATE_STARTUP, &Server::handleCommand_ClientReady }, // 0x43 null_command_handler, // 0x44 @@ -167,7 +167,7 @@ const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] = { "TOCLIENT_TIME_OF_DAY", 0, true }, // 0x29 { "TOCLIENT_CSM_RESTRICTION_FLAGS", 0, true }, // 0x2A { "TOCLIENT_PLAYER_SPEED", 0, true }, // 0x2B - { "TOCLIENT_MEDIA_PUSH", 0, true }, // 0x2C (sent over channel 1 too) + { "TOCLIENT_MEDIA_PUSH", 0, true }, // 0x2C (sent over channel 1 too if legacy) null_command_factory, // 0x2D null_command_factory, // 0x2E { "TOCLIENT_CHAT_MESSAGE", 0, true }, // 0x2F diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index 77fde2a66..4c609644f 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -362,16 +362,15 @@ void Server::handleCommand_RequestMedia(NetworkPacket* pkt) session_t peer_id = pkt->getPeerId(); infostream << "Sending " << numfiles << " files to " << getPlayerName(peer_id) << std::endl; - verbosestream << "TOSERVER_REQUEST_MEDIA: " << std::endl; + verbosestream << "TOSERVER_REQUEST_MEDIA: requested file(s)" << std::endl; for (u16 i = 0; i < numfiles; i++) { std::string name; *pkt >> name; - tosend.push_back(name); - verbosestream << "TOSERVER_REQUEST_MEDIA: requested file " - << name << std::endl; + tosend.emplace_back(name); + verbosestream << " " << name << std::endl; } sendRequestedMedia(peer_id, tosend); @@ -1801,3 +1800,30 @@ void Server::handleCommand_ModChannelMsg(NetworkPacket *pkt) broadcastModChannelMessage(channel_name, channel_msg, peer_id); } + +void Server::handleCommand_HaveMedia(NetworkPacket *pkt) +{ + std::vector tokens; + u8 numtokens; + + *pkt >> numtokens; + for (u16 i = 0; i < numtokens; i++) { + u32 n; + *pkt >> n; + tokens.emplace_back(n); + } + + const session_t peer_id = pkt->getPeerId(); + auto player = m_env->getPlayer(peer_id); + + for (const u32 token : tokens) { + auto it = m_pending_dyn_media.find(token); + if (it == m_pending_dyn_media.end()) + continue; + if (it->second.waiting_players.count(peer_id)) { + it->second.waiting_players.erase(peer_id); + if (player) + getScriptIface()->on_dynamic_media_added(token, player->getName()); + } + } +} diff --git a/src/script/cpp_api/s_server.cpp b/src/script/cpp_api/s_server.cpp index 96cb28b28..6ddb2630d 100644 --- a/src/script/cpp_api/s_server.cpp +++ b/src/script/cpp_api/s_server.cpp @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "cpp_api/s_server.h" #include "cpp_api/s_internal.h" #include "common/c_converter.h" +#include "util/numeric.h" // myrand bool ScriptApiServer::getAuth(const std::string &playername, std::string *dst_password, @@ -196,3 +197,68 @@ std::string ScriptApiServer::formatChatMessage(const std::string &name, return ret; } + +u32 ScriptApiServer::allocateDynamicMediaCallback(int f_idx) +{ + lua_State *L = getStack(); + + if (f_idx < 0) + f_idx = lua_gettop(L) + f_idx + 1; + + lua_getglobal(L, "core"); + lua_getfield(L, -1, "dynamic_media_callbacks"); + luaL_checktype(L, -1, LUA_TTABLE); + + // Find a randomly generated token that doesn't exist yet + int tries = 100; + u32 token; + while (1) { + token = myrand(); + lua_rawgeti(L, -2, token); + bool is_free = lua_isnil(L, -1); + lua_pop(L, 1); + if (is_free) + break; + if (--tries < 0) + FATAL_ERROR("Ran out of callbacks IDs?!"); + } + + // core.dynamic_media_callbacks[token] = callback_func + lua_pushvalue(L, f_idx); + lua_rawseti(L, -2, token); + + lua_pop(L, 2); + + verbosestream << "allocateDynamicMediaCallback() = " << token << std::endl; + return token; +} + +void ScriptApiServer::freeDynamicMediaCallback(u32 token) +{ + lua_State *L = getStack(); + + verbosestream << "freeDynamicMediaCallback(" << token << ")" << std::endl; + + // core.dynamic_media_callbacks[token] = nil + lua_getglobal(L, "core"); + lua_getfield(L, -1, "dynamic_media_callbacks"); + luaL_checktype(L, -1, LUA_TTABLE); + lua_pushnil(L); + lua_rawseti(L, -2, token); + lua_pop(L, 2); +} + +void ScriptApiServer::on_dynamic_media_added(u32 token, const char *playername) +{ + SCRIPTAPI_PRECHECKHEADER + + int error_handler = PUSH_ERROR_HANDLER(L); + lua_getglobal(L, "core"); + lua_getfield(L, -1, "dynamic_media_callbacks"); + luaL_checktype(L, -1, LUA_TTABLE); + lua_rawgeti(L, -1, token); + luaL_checktype(L, -1, LUA_TFUNCTION); + + lua_pushstring(L, playername); + PCALL_RES(lua_pcall(L, 1, 0, error_handler)); +} diff --git a/src/script/cpp_api/s_server.h b/src/script/cpp_api/s_server.h index d8639cba7..c5c3d5596 100644 --- a/src/script/cpp_api/s_server.h +++ b/src/script/cpp_api/s_server.h @@ -49,6 +49,12 @@ public: const std::string &password); bool setPassword(const std::string &playername, const std::string &password); + + /* dynamic media handling */ + u32 allocateDynamicMediaCallback(int f_idx); + void freeDynamicMediaCallback(u32 token); + void on_dynamic_media_added(u32 token, const char *playername); + private: void getAuthHandler(); void readPrivileges(int index, std::set &result); diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index 9866e0bc8..473faaa14 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -453,29 +453,37 @@ int ModApiServer::l_sound_fade(lua_State *L) } // dynamic_add_media(filepath) -int ModApiServer::l_dynamic_add_media_raw(lua_State *L) +int ModApiServer::l_dynamic_add_media(lua_State *L) { NO_MAP_LOCK_REQUIRED; if (!getEnv(L)) throw LuaError("Dynamic media cannot be added before server has started up"); + Server *server = getServer(L); - std::string filepath = readParam(L, 1); - CHECK_SECURE_PATH(L, filepath.c_str(), false); + std::string filepath; + std::string to_player; + bool ephemeral = false; - std::vector sent_to; - bool ok = getServer(L)->dynamicAddMedia(filepath, sent_to); - if (ok) { - // (see wrapper code in builtin) - lua_createtable(L, sent_to.size(), 0); - int i = 0; - for (RemotePlayer *player : sent_to) { - lua_pushstring(L, player->getName()); - lua_rawseti(L, -2, ++i); - } + if (lua_istable(L, 1)) { + getstringfield(L, 1, "filepath", filepath); + getstringfield(L, 1, "to_player", to_player); + getboolfield(L, 1, "ephemeral", ephemeral); } else { - lua_pushboolean(L, false); + filepath = readParam(L, 1); } + if (filepath.empty()) + luaL_typerror(L, 1, "non-empty string"); + luaL_checktype(L, 2, LUA_TFUNCTION); + + CHECK_SECURE_PATH(L, filepath.c_str(), false); + + u32 token = server->getScriptIface()->allocateDynamicMediaCallback(2); + + bool ok = server->dynamicAddMedia(filepath, token, to_player, ephemeral); + if (!ok) + server->getScriptIface()->freeDynamicMediaCallback(token); + lua_pushboolean(L, ok); return 1; } @@ -519,7 +527,7 @@ void ModApiServer::Initialize(lua_State *L, int top) API_FCT(sound_play); API_FCT(sound_stop); API_FCT(sound_fade); - API_FCT(dynamic_add_media_raw); + API_FCT(dynamic_add_media); API_FCT(get_player_information); API_FCT(get_player_privs); diff --git a/src/script/lua_api/l_server.h b/src/script/lua_api/l_server.h index fb7a851f4..c688e494b 100644 --- a/src/script/lua_api/l_server.h +++ b/src/script/lua_api/l_server.h @@ -71,7 +71,7 @@ private: static int l_sound_fade(lua_State *L); // dynamic_add_media(filepath) - static int l_dynamic_add_media_raw(lua_State *L); + static int l_dynamic_add_media(lua_State *L); // get_player_privs(name, text) static int l_get_player_privs(lua_State *L); diff --git a/src/server.cpp b/src/server.cpp index b96db1209..1b5cbe395 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -665,6 +665,17 @@ void Server::AsyncRunStep(bool initial_step) } else { m_lag_gauge->increment(dtime/100); } + + { + float &counter = m_step_pending_dyn_media_timer; + counter += dtime; + if (counter >= 5.0f) { + stepPendingDynMediaCallbacks(counter); + counter = 0; + } + } + + #if USE_CURL // send masterserver announce { @@ -2527,6 +2538,8 @@ void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_co std::string lang_suffix; lang_suffix.append(".").append(lang_code).append(".tr"); for (const auto &i : m_media) { + if (i.second.no_announce) + continue; if (str_ends_with(i.first, ".tr") && !str_ends_with(i.first, lang_suffix)) continue; media_sent++; @@ -2535,6 +2548,8 @@ void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_co pkt << media_sent; for (const auto &i : m_media) { + if (i.second.no_announce) + continue; if (str_ends_with(i.first, ".tr") && !str_ends_with(i.first, lang_suffix)) continue; pkt << i.first << i.second.sha1_digest; @@ -2553,11 +2568,9 @@ struct SendableMedia std::string path; std::string data; - SendableMedia(const std::string &name_="", const std::string &path_="", - const std::string &data_=""): - name(name_), - path(path_), - data(data_) + SendableMedia(const std::string &name, const std::string &path, + std::string &&data): + name(name), path(path), data(std::move(data)) {} }; @@ -2584,40 +2597,19 @@ void Server::sendRequestedMedia(session_t peer_id, continue; } - //TODO get path + name - std::string tpath = m_media[name].path; + const auto &m = m_media[name]; // Read data - std::ifstream fis(tpath.c_str(), std::ios_base::binary); - if(!fis.good()){ - errorstream<<"Server::sendRequestedMedia(): Could not open \"" - <= bytes_per_bunch) { @@ -2660,6 +2652,33 @@ void Server::sendRequestedMedia(session_t peer_id, } } +void Server::stepPendingDynMediaCallbacks(float dtime) +{ + MutexAutoLock lock(m_env_mutex); + + for (auto it = m_pending_dyn_media.begin(); it != m_pending_dyn_media.end();) { + it->second.expiry_timer -= dtime; + bool del = it->second.waiting_players.empty() || it->second.expiry_timer < 0; + + if (!del) { + it++; + continue; + } + + const auto &name = it->second.filename; + if (!name.empty()) { + assert(m_media.count(name)); + // if no_announce isn't set we're definitely deleting the wrong file! + sanity_check(m_media[name].no_announce); + + fs::DeleteSingleFileOrEmptyDirectory(m_media[name].path); + m_media.erase(name); + } + getScriptIface()->freeDynamicMediaCallback(it->first); + it = m_pending_dyn_media.erase(it); + } +} + void Server::SendMinimapModes(session_t peer_id, std::vector &modes, size_t wanted_mode) { @@ -3457,14 +3476,18 @@ void Server::deleteParticleSpawner(const std::string &playername, u32 id) SendDeleteParticleSpawner(peer_id, id); } -bool Server::dynamicAddMedia(const std::string &filepath, - std::vector &sent_to) +bool Server::dynamicAddMedia(std::string filepath, + const u32 token, const std::string &to_player, bool ephemeral) { std::string filename = fs::GetFilenameFromPath(filepath.c_str()); - if (m_media.find(filename) != m_media.end()) { - errorstream << "Server::dynamicAddMedia(): file \"" << filename - << "\" already exists in media cache" << std::endl; - return false; + auto it = m_media.find(filename); + if (it != m_media.end()) { + // Allow the same path to be "added" again in certain conditions + if (ephemeral || it->second.path != filepath) { + errorstream << "Server::dynamicAddMedia(): file \"" << filename + << "\" already exists in media cache" << std::endl; + return false; + } } // Load the file and add it to our media cache @@ -3473,35 +3496,91 @@ bool Server::dynamicAddMedia(const std::string &filepath, if (!ok) return false; + if (ephemeral) { + // Create a copy of the file and swap out the path, this removes the + // requirement that mods keep the file accessible at the original path. + filepath = fs::CreateTempFile(); + bool ok = ([&] () -> bool { + if (filepath.empty()) + return false; + std::ofstream os(filepath.c_str(), std::ios::binary); + if (!os.good()) + return false; + os << filedata; + os.close(); + return !os.fail(); + })(); + if (!ok) { + errorstream << "Server: failed to create a copy of media file " + << "\"" << filename << "\"" << std::endl; + m_media.erase(filename); + return false; + } + verbosestream << "Server: \"" << filename << "\" temporarily copied to " + << filepath << std::endl; + + m_media[filename].path = filepath; + m_media[filename].no_announce = true; + // stepPendingDynMediaCallbacks will clean this up later. + } else if (!to_player.empty()) { + m_media[filename].no_announce = true; + } + // Push file to existing clients NetworkPacket pkt(TOCLIENT_MEDIA_PUSH, 0); - pkt << raw_hash << filename << (bool) true; - pkt.putLongString(filedata); + pkt << raw_hash << filename << (bool)ephemeral; + + NetworkPacket legacy_pkt = pkt; + // Newer clients get asked to fetch the file (asynchronous) + pkt << token; + // Older clients have an awful hack that just throws the data at them + legacy_pkt.putLongString(filedata); + + std::unordered_set delivered, waiting; m_clients.lock(); for (auto &pair : m_clients.getClientList()) { if (pair.second->getState() < CS_DefinitionsSent) continue; - if (pair.second->net_proto_version < 39) + const auto proto_ver = pair.second->net_proto_version; + if (proto_ver < 39) continue; - if (auto player = m_env->getPlayer(pair.second->peer_id)) - sent_to.emplace_back(player); - /* - FIXME: this is a very awful hack - The network layer only guarantees ordered delivery inside a channel. - Since the very next packet could be one that uses the media, we have - to push the media over ALL channels to ensure it is processed before - it is used. - In practice this means we have to send it twice: - - channel 1 (HUD) - - channel 0 (everything else: e.g. play_sound, object messages) - */ - m_clients.send(pair.second->peer_id, 1, &pkt, true); - m_clients.send(pair.second->peer_id, 0, &pkt, true); + const session_t peer_id = pair.second->peer_id; + if (!to_player.empty() && getPlayerName(peer_id) != to_player) + continue; + + if (proto_ver < 40) { + delivered.emplace(peer_id); + /* + The network layer only guarantees ordered delivery inside a channel. + Since the very next packet could be one that uses the media, we have + to push the media over ALL channels to ensure it is processed before + it is used. In practice this means channels 1 and 0. + */ + m_clients.send(peer_id, 1, &legacy_pkt, true); + m_clients.send(peer_id, 0, &legacy_pkt, true); + } else { + waiting.emplace(peer_id); + Send(peer_id, &pkt); + } } m_clients.unlock(); + // Run callback for players that already had the file delivered (legacy-only) + for (session_t peer_id : delivered) { + if (auto player = m_env->getPlayer(peer_id)) + getScriptIface()->on_dynamic_media_added(token, player->getName()); + } + + // Save all others in our pending state + auto &state = m_pending_dyn_media[token]; + state.waiting_players = std::move(waiting); + // regardless of success throw away the callback after a while + state.expiry_timer = 60.0f; + if (ephemeral) + state.filename = filename; + return true; } diff --git a/src/server.h b/src/server.h index 9857215d0..7b16845af 100644 --- a/src/server.h +++ b/src/server.h @@ -43,6 +43,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include +#include class ChatEvent; struct ChatEventChat; @@ -81,12 +82,14 @@ enum ClientDeletionReason { struct MediaInfo { std::string path; - std::string sha1_digest; + std::string sha1_digest; // base64-encoded + bool no_announce; // true: not announced in TOCLIENT_ANNOUNCE_MEDIA (at player join) MediaInfo(const std::string &path_="", const std::string &sha1_digest_=""): path(path_), - sha1_digest(sha1_digest_) + sha1_digest(sha1_digest_), + no_announce(false) { } }; @@ -197,6 +200,7 @@ public: void handleCommand_FirstSrp(NetworkPacket* pkt); void handleCommand_SrpBytesA(NetworkPacket* pkt); void handleCommand_SrpBytesM(NetworkPacket* pkt); + void handleCommand_HaveMedia(NetworkPacket *pkt); void ProcessData(NetworkPacket *pkt); @@ -257,7 +261,8 @@ public: void deleteParticleSpawner(const std::string &playername, u32 id); - bool dynamicAddMedia(const std::string &filepath, std::vector &sent_to); + bool dynamicAddMedia(std::string filepath, u32 token, + const std::string &to_player, bool ephemeral); ServerInventoryManager *getInventoryMgr() const { return m_inventory_mgr.get(); } void sendDetachedInventory(Inventory *inventory, const std::string &name, session_t peer_id); @@ -395,6 +400,12 @@ private: float m_timer = 0.0f; }; + struct PendingDynamicMediaCallback { + std::string filename; // only set if media entry and file is to be deleted + float expiry_timer; + std::unordered_set waiting_players; + }; + void init(); void SendMovement(session_t peer_id); @@ -466,6 +477,7 @@ private: void sendMediaAnnouncement(session_t peer_id, const std::string &lang_code); void sendRequestedMedia(session_t peer_id, const std::vector &tosend); + void stepPendingDynMediaCallbacks(float dtime); // Adds a ParticleSpawner on peer with peer_id (PEER_ID_INEXISTENT == all) void SendAddParticleSpawner(session_t peer_id, u16 protocol_version, @@ -650,6 +662,10 @@ private: // media files known to server std::unordered_map m_media; + // pending dynamic media callbacks, clients inform the server when they have a file fetched + std::unordered_map m_pending_dyn_media; + float m_step_pending_dyn_media_timer = 0.0f; + /* Sounds */ -- cgit v1.2.3 From 7423c4c11e01edecd8db18b147bab4d2f3eeb471 Mon Sep 17 00:00:00 2001 From: Jude Melton-Houghton Date: Fri, 10 Sep 2021 17:16:34 -0400 Subject: Send to clients node metadata that changed to become empty (#11597) --- src/nodemetadata.cpp | 6 +++--- src/nodemetadata.h | 2 +- src/server.cpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nodemetadata.cpp b/src/nodemetadata.cpp index f98732385..b5052c3b8 100644 --- a/src/nodemetadata.cpp +++ b/src/nodemetadata.cpp @@ -113,13 +113,13 @@ int NodeMetadata::countNonPrivate() const */ void NodeMetadataList::serialize(std::ostream &os, u8 blockver, bool disk, - bool absolute_pos) const + bool absolute_pos, bool include_empty) const { /* Version 0 is a placeholder for "nothing to see here; go away." */ - u16 count = countNonEmpty(); + u16 count = include_empty ? m_data.size() : countNonEmpty(); if (count == 0) { writeU8(os, 0); // version return; @@ -134,7 +134,7 @@ void NodeMetadataList::serialize(std::ostream &os, u8 blockver, bool disk, i != m_data.end(); ++i) { v3s16 p = i->first; NodeMetadata *data = i->second; - if (data->empty()) + if (!include_empty && data->empty()) continue; if (absolute_pos) { diff --git a/src/nodemetadata.h b/src/nodemetadata.h index c028caf88..4b5b4d887 100644 --- a/src/nodemetadata.h +++ b/src/nodemetadata.h @@ -82,7 +82,7 @@ public: ~NodeMetadataList(); void serialize(std::ostream &os, u8 blockver, bool disk = true, - bool absolute_pos = false) const; + bool absolute_pos = false, bool include_empty = false) const; void deSerialize(std::istream &is, IItemDefManager *item_def_mgr, bool absolute_pos = false); diff --git a/src/server.cpp b/src/server.cpp index 1b5cbe395..46b497d6e 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -2320,7 +2320,7 @@ void Server::sendMetadataChanged(const std::list &meta_updates, float far // Send the meta changes std::ostringstream os(std::ios::binary); - meta_updates_list.serialize(os, client->net_proto_version, false, true); + meta_updates_list.serialize(os, client->serialization_version, false, true, true); std::ostringstream oss(std::ios::binary); compressZlib(os.str(), oss); -- cgit v1.2.3 From 766e885a1b1c5afb7a62f11b427b6d135adeab87 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Fri, 10 Sep 2021 23:16:46 +0200 Subject: Clean up/improve some scriptapi error handling code --- src/client/client.h | 4 ++ src/emerge.cpp | 2 +- src/script/common/c_internal.cpp | 36 ---------------- src/script/common/c_internal.h | 10 ++--- src/script/cpp_api/s_base.h | 3 ++ src/script/cpp_api/s_client.cpp | 88 ++++++++++++++++++++++++++++++++------- src/script/cpp_api/s_env.cpp | 23 +++------- src/script/cpp_api/s_item.cpp | 15 ++++--- src/script/cpp_api/s_nodemeta.cpp | 18 ++++---- src/server.cpp | 11 +++-- src/server.h | 4 ++ 11 files changed, 120 insertions(+), 94 deletions(-) (limited to 'src') diff --git a/src/client/client.h b/src/client/client.h index c1a38ba48..f6030b022 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -325,6 +325,10 @@ public: m_access_denied = true; m_access_denied_reason = reason; } + inline void setFatalError(const LuaError &e) + { + setFatalError(std::string("Lua :") + e.what()); + } // Renaming accessDeniedReason to better name could be good as it's used to // disconnect client when CSM failed. diff --git a/src/emerge.cpp b/src/emerge.cpp index bd1c1726d..9234fe6d3 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -647,7 +647,7 @@ MapBlock *EmergeThread::finishGen(v3s16 pos, BlockMakeData *bmdata, m_server->getScriptIface()->environment_OnGenerated( minp, maxp, m_mapgen->blockseed); } catch (LuaError &e) { - m_server->setAsyncFatalError("Lua: finishGen" + std::string(e.what())); + m_server->setAsyncFatalError(e); } /* diff --git a/src/script/common/c_internal.cpp b/src/script/common/c_internal.cpp index 66f6a9b98..df82dba14 100644 --- a/src/script/common/c_internal.cpp +++ b/src/script/common/c_internal.cpp @@ -101,42 +101,6 @@ void script_error(lua_State *L, int pcall_result, const char *mod, const char *f throw LuaError(err_msg); } -// Push the list of callbacks (a lua table). -// Then push nargs arguments. -// Then call this function, which -// - runs the callbacks -// - replaces the table and arguments with the return value, -// computed depending on mode -void script_run_callbacks_f(lua_State *L, int nargs, - RunCallbacksMode mode, const char *fxn) -{ - FATAL_ERROR_IF(lua_gettop(L) < nargs + 1, "Not enough arguments"); - - // Insert error handler - PUSH_ERROR_HANDLER(L); - int error_handler = lua_gettop(L) - nargs - 1; - lua_insert(L, error_handler); - - // Insert run_callbacks between error handler and table - lua_getglobal(L, "core"); - lua_getfield(L, -1, "run_callbacks"); - lua_remove(L, -2); - lua_insert(L, error_handler + 1); - - // Insert mode after table - lua_pushnumber(L, (int) mode); - lua_insert(L, error_handler + 3); - - // Stack now looks like this: - // ... ... - - int result = lua_pcall(L, nargs + 2, 1, error_handler); - if (result != 0) - script_error(L, result, NULL, fxn); - - lua_remove(L, error_handler); -} - static void script_log_add_source(lua_State *L, std::string &message, int stack_depth) { lua_Debug ar; diff --git a/src/script/common/c_internal.h b/src/script/common/c_internal.h index 4ddbed232..ab2d7b975 100644 --- a/src/script/common/c_internal.h +++ b/src/script/common/c_internal.h @@ -75,9 +75,6 @@ extern "C" { } \ } -#define script_run_callbacks(L, nargs, mode) \ - script_run_callbacks_f((L), (nargs), (mode), __FUNCTION__) - // What script_run_callbacks does with the return values of callbacks. // Regardless of the mode, if only one callback is defined, // its return value is the total return value. @@ -108,16 +105,17 @@ enum RunCallbacksMode // are converted by readParam to true or false, respectively. }; +// Gets a backtrace of the current execution point std::string script_get_backtrace(lua_State *L); +// Wrapper for CFunction calls that converts C++ exceptions to Lua errors int script_exception_wrapper(lua_State *L, lua_CFunction f); +// Takes an error from lua_pcall and throws it as a LuaError void script_error(lua_State *L, int pcall_result, const char *mod, const char *fxn); -void script_run_callbacks_f(lua_State *L, int nargs, - RunCallbacksMode mode, const char *fxn); bool script_log_unique(lua_State *L, std::string message, std::ostream &log_to, int stack_depth = 1); -enum class DeprecatedHandlingMode { +enum DeprecatedHandlingMode { Ignore, Log, Error diff --git a/src/script/cpp_api/s_base.h b/src/script/cpp_api/s_base.h index 7a8ebc85a..06df2abe3 100644 --- a/src/script/cpp_api/s_base.h +++ b/src/script/cpp_api/s_base.h @@ -128,8 +128,11 @@ protected: lua_State* getStack() { return m_luastack; } + // Checks that stack size is sane void realityCheck(); + // Takes an error from lua_pcall and throws it as a LuaError void scriptError(int result, const char *fxn); + // Dumps stack contents for debugging void stackDump(std::ostream &o); void setGameDef(IGameDef* gamedef) { m_gamedef = gamedef; } diff --git a/src/script/cpp_api/s_client.cpp b/src/script/cpp_api/s_client.cpp index f2cc9730b..c889fffa0 100644 --- a/src/script/cpp_api/s_client.cpp +++ b/src/script/cpp_api/s_client.cpp @@ -33,7 +33,11 @@ void ScriptApiClient::on_mods_loaded() lua_getglobal(L, "core"); lua_getfield(L, -1, "registered_on_mods_loaded"); // Call callbacks - runCallbacks(0, RUN_CALLBACKS_MODE_FIRST); + try { + runCallbacks(0, RUN_CALLBACKS_MODE_FIRST); + } catch (LuaError &e) { + getClient()->setFatalError(e); + } } void ScriptApiClient::on_shutdown() @@ -44,7 +48,11 @@ void ScriptApiClient::on_shutdown() lua_getglobal(L, "core"); lua_getfield(L, -1, "registered_on_shutdown"); // Call callbacks - runCallbacks(0, RUN_CALLBACKS_MODE_FIRST); + try { + runCallbacks(0, RUN_CALLBACKS_MODE_FIRST); + } catch (LuaError &e) { + getClient()->setFatalError(e); + } } bool ScriptApiClient::on_sending_message(const std::string &message) @@ -56,7 +64,12 @@ bool ScriptApiClient::on_sending_message(const std::string &message) lua_getfield(L, -1, "registered_on_sending_chat_message"); // Call callbacks lua_pushstring(L, message.c_str()); - runCallbacks(1, RUN_CALLBACKS_MODE_OR_SC); + try { + runCallbacks(1, RUN_CALLBACKS_MODE_OR_SC); + } catch (LuaError &e) { + getClient()->setFatalError(e); + return true; + } return readParam(L, -1); } @@ -69,7 +82,12 @@ bool ScriptApiClient::on_receiving_message(const std::string &message) lua_getfield(L, -1, "registered_on_receiving_chat_message"); // Call callbacks lua_pushstring(L, message.c_str()); - runCallbacks(1, RUN_CALLBACKS_MODE_OR_SC); + try { + runCallbacks(1, RUN_CALLBACKS_MODE_OR_SC); + } catch (LuaError &e) { + getClient()->setFatalError(e); + return true; + } return readParam(L, -1); } @@ -82,7 +100,11 @@ void ScriptApiClient::on_damage_taken(int32_t damage_amount) lua_getfield(L, -1, "registered_on_damage_taken"); // Call callbacks lua_pushinteger(L, damage_amount); - runCallbacks(1, RUN_CALLBACKS_MODE_OR_SC); + try { + runCallbacks(1, RUN_CALLBACKS_MODE_OR_SC); + } catch (LuaError &e) { + getClient()->setFatalError(e); + } } void ScriptApiClient::on_hp_modification(int32_t newhp) @@ -94,7 +116,11 @@ void ScriptApiClient::on_hp_modification(int32_t newhp) lua_getfield(L, -1, "registered_on_hp_modification"); // Call callbacks lua_pushinteger(L, newhp); - runCallbacks(1, RUN_CALLBACKS_MODE_OR_SC); + try { + runCallbacks(1, RUN_CALLBACKS_MODE_OR_SC); + } catch (LuaError &e) { + getClient()->setFatalError(e); + } } void ScriptApiClient::on_death() @@ -105,7 +131,11 @@ void ScriptApiClient::on_death() lua_getglobal(L, "core"); lua_getfield(L, -1, "registered_on_death"); // Call callbacks - runCallbacks(0, RUN_CALLBACKS_MODE_FIRST); + try { + runCallbacks(0, RUN_CALLBACKS_MODE_FIRST); + } catch (LuaError &e) { + getClient()->setFatalError(e); + } } void ScriptApiClient::environment_step(float dtime) @@ -120,8 +150,7 @@ void ScriptApiClient::environment_step(float dtime) try { runCallbacks(1, RUN_CALLBACKS_MODE_FIRST); } catch (LuaError &e) { - getClient()->setFatalError(std::string("Client environment_step: ") + e.what() + "\n" - + script_get_backtrace(L)); + getClient()->setFatalError(e); } } @@ -146,7 +175,11 @@ void ScriptApiClient::on_formspec_input(const std::string &formname, lua_pushlstring(L, value.c_str(), value.size()); lua_settable(L, -3); } - runCallbacks(2, RUN_CALLBACKS_MODE_OR_SC); + try { + runCallbacks(2, RUN_CALLBACKS_MODE_OR_SC); + } catch (LuaError &e) { + getClient()->setFatalError(e); + } } bool ScriptApiClient::on_dignode(v3s16 p, MapNode node) @@ -164,7 +197,12 @@ bool ScriptApiClient::on_dignode(v3s16 p, MapNode node) pushnode(L, node, ndef); // Call functions - runCallbacks(2, RUN_CALLBACKS_MODE_OR); + try { + runCallbacks(2, RUN_CALLBACKS_MODE_OR); + } catch (LuaError &e) { + getClient()->setFatalError(e); + return true; + } return lua_toboolean(L, -1); } @@ -183,7 +221,12 @@ bool ScriptApiClient::on_punchnode(v3s16 p, MapNode node) pushnode(L, node, ndef); // Call functions - runCallbacks(2, RUN_CALLBACKS_MODE_OR); + try { + runCallbacks(2, RUN_CALLBACKS_MODE_OR); + } catch (LuaError &e) { + getClient()->setFatalError(e); + return true; + } return readParam(L, -1); } @@ -200,7 +243,12 @@ bool ScriptApiClient::on_placenode(const PointedThing &pointed, const ItemDefini push_item_definition(L, item); // Call functions - runCallbacks(2, RUN_CALLBACKS_MODE_OR); + try { + runCallbacks(2, RUN_CALLBACKS_MODE_OR); + } catch (LuaError &e) { + getClient()->setFatalError(e); + return true; + } return readParam(L, -1); } @@ -217,7 +265,12 @@ bool ScriptApiClient::on_item_use(const ItemStack &item, const PointedThing &poi push_pointed_thing(L, pointed, true); // Call functions - runCallbacks(2, RUN_CALLBACKS_MODE_OR); + try { + runCallbacks(2, RUN_CALLBACKS_MODE_OR); + } catch (LuaError &e) { + getClient()->setFatalError(e); + return true; + } return readParam(L, -1); } @@ -238,7 +291,12 @@ bool ScriptApiClient::on_inventory_open(Inventory *inventory) lua_rawset(L, -3); } - runCallbacks(1, RUN_CALLBACKS_MODE_OR); + try { + runCallbacks(1, RUN_CALLBACKS_MODE_OR); + } catch (LuaError &e) { + getClient()->setFatalError(e); + return true; + } return readParam(L, -1); } diff --git a/src/script/cpp_api/s_env.cpp b/src/script/cpp_api/s_env.cpp index c11de3757..874c37b6e 100644 --- a/src/script/cpp_api/s_env.cpp +++ b/src/script/cpp_api/s_env.cpp @@ -53,13 +53,7 @@ void ScriptApiEnv::environment_Step(float dtime) lua_getfield(L, -1, "registered_globalsteps"); // Call callbacks lua_pushnumber(L, dtime); - try { - runCallbacks(1, RUN_CALLBACKS_MODE_FIRST); - } catch (LuaError &e) { - getServer()->setAsyncFatalError( - std::string("environment_Step: ") + e.what() + "\n" - + script_get_backtrace(L)); - } + runCallbacks(1, RUN_CALLBACKS_MODE_FIRST); } void ScriptApiEnv::player_event(ServerActiveObject *player, const std::string &type) @@ -76,13 +70,7 @@ void ScriptApiEnv::player_event(ServerActiveObject *player, const std::string &t // Call callbacks objectrefGetOrCreate(L, player); // player lua_pushstring(L,type.c_str()); // event type - try { - runCallbacks(2, RUN_CALLBACKS_MODE_FIRST); - } catch (LuaError &e) { - getServer()->setAsyncFatalError( - std::string("player_event: ") + e.what() + "\n" - + script_get_backtrace(L) ); - } + runCallbacks(2, RUN_CALLBACKS_MODE_FIRST); } void ScriptApiEnv::initializeEnvironment(ServerEnvironment *env) @@ -257,9 +245,8 @@ void ScriptApiEnv::on_emerge_area_completion( try { PCALL_RES(lua_pcall(L, 4, 0, error_handler)); } catch (LuaError &e) { - server->setAsyncFatalError( - std::string("on_emerge_area_completion: ") + e.what() + "\n" - + script_get_backtrace(L)); + // Note: don't throw here, we still need to run the cleanup code below + server->setAsyncFatalError(e); } lua_pop(L, 1); // Pop error handler @@ -300,4 +287,4 @@ void ScriptApiEnv::on_liquid_transformed( } runCallbacks(2, RUN_CALLBACKS_MODE_FIRST); -} \ No newline at end of file +} diff --git a/src/script/cpp_api/s_item.cpp b/src/script/cpp_api/s_item.cpp index 24955cefc..48dce14f3 100644 --- a/src/script/cpp_api/s_item.cpp +++ b/src/script/cpp_api/s_item.cpp @@ -29,6 +29,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "inventory.h" #include "inventorymanager.h" +#define WRAP_LUAERROR(e, detail) \ + LuaError(std::string(__FUNCTION__) + ": " + (e).what() + ". " detail) + bool ScriptApiItem::item_OnDrop(ItemStack &item, ServerActiveObject *dropper, v3f pos) { @@ -49,7 +52,7 @@ bool ScriptApiItem::item_OnDrop(ItemStack &item, try { item = read_item(L, -1, getServer()->idef()); } catch (LuaError &e) { - throw LuaError(std::string(e.what()) + ". item=" + item.name); + throw WRAP_LUAERROR(e, "item=" + item.name); } } lua_pop(L, 2); // Pop item and error handler @@ -81,7 +84,7 @@ bool ScriptApiItem::item_OnPlace(ItemStack &item, try { item = read_item(L, -1, getServer()->idef()); } catch (LuaError &e) { - throw LuaError(std::string(e.what()) + ". item=" + item.name); + throw WRAP_LUAERROR(e, "item=" + item.name); } } lua_pop(L, 2); // Pop item and error handler @@ -108,7 +111,7 @@ bool ScriptApiItem::item_OnUse(ItemStack &item, try { item = read_item(L, -1, getServer()->idef()); } catch (LuaError &e) { - throw LuaError(std::string(e.what()) + ". item=" + item.name); + throw WRAP_LUAERROR(e, "item=" + item.name); } } lua_pop(L, 2); // Pop item and error handler @@ -133,7 +136,7 @@ bool ScriptApiItem::item_OnSecondaryUse(ItemStack &item, try { item = read_item(L, -1, getServer()->idef()); } catch (LuaError &e) { - throw LuaError(std::string(e.what()) + ". item=" + item.name); + throw WRAP_LUAERROR(e, "item=" + item.name); } } lua_pop(L, 2); // Pop item and error handler @@ -165,7 +168,7 @@ bool ScriptApiItem::item_OnCraft(ItemStack &item, ServerActiveObject *user, try { item = read_item(L, -1, getServer()->idef()); } catch (LuaError &e) { - throw LuaError(std::string(e.what()) + ". item=" + item.name); + throw WRAP_LUAERROR(e, "item=" + item.name); } } lua_pop(L, 2); // Pop item and error handler @@ -197,7 +200,7 @@ bool ScriptApiItem::item_CraftPredict(ItemStack &item, ServerActiveObject *user, try { item = read_item(L, -1, getServer()->idef()); } catch (LuaError &e) { - throw LuaError(std::string(e.what()) + ". item=" + item.name); + throw WRAP_LUAERROR(e, "item=" + item.name); } } lua_pop(L, 2); // Pop item and error handler diff --git a/src/script/cpp_api/s_nodemeta.cpp b/src/script/cpp_api/s_nodemeta.cpp index c081e9fc4..7ab3757f3 100644 --- a/src/script/cpp_api/s_nodemeta.cpp +++ b/src/script/cpp_api/s_nodemeta.cpp @@ -43,7 +43,7 @@ int ScriptApiNodemeta::nodemeta_inventory_AllowMove( return 0; // Push callback function on stack - std::string nodename = ndef->get(node).name; + const auto &nodename = ndef->get(node).name; if (!getItemCallback(nodename.c_str(), "allow_metadata_inventory_move", &ma.to_inv.p)) return count; @@ -58,7 +58,7 @@ int ScriptApiNodemeta::nodemeta_inventory_AllowMove( PCALL_RES(lua_pcall(L, 7, 1, error_handler)); if (!lua_isnumber(L, -1)) throw LuaError("allow_metadata_inventory_move should" - " return a number, guilty node: " + nodename); + " return a number. node=" + nodename); int num = luaL_checkinteger(L, -1); lua_pop(L, 2); // Pop integer and error handler return num; @@ -81,7 +81,7 @@ int ScriptApiNodemeta::nodemeta_inventory_AllowPut( return 0; // Push callback function on stack - std::string nodename = ndef->get(node).name; + const auto &nodename = ndef->get(node).name; if (!getItemCallback(nodename.c_str(), "allow_metadata_inventory_put", &ma.to_inv.p)) return stack.count; @@ -94,7 +94,7 @@ int ScriptApiNodemeta::nodemeta_inventory_AllowPut( PCALL_RES(lua_pcall(L, 5, 1, error_handler)); if(!lua_isnumber(L, -1)) throw LuaError("allow_metadata_inventory_put should" - " return a number, guilty node: " + nodename); + " return a number. node=" + nodename); int num = luaL_checkinteger(L, -1); lua_pop(L, 2); // Pop integer and error handler return num; @@ -117,7 +117,7 @@ int ScriptApiNodemeta::nodemeta_inventory_AllowTake( return 0; // Push callback function on stack - std::string nodename = ndef->get(node).name; + const auto &nodename = ndef->get(node).name; if (!getItemCallback(nodename.c_str(), "allow_metadata_inventory_take", &ma.from_inv.p)) return stack.count; @@ -130,7 +130,7 @@ int ScriptApiNodemeta::nodemeta_inventory_AllowTake( PCALL_RES(lua_pcall(L, 5, 1, error_handler)); if (!lua_isnumber(L, -1)) throw LuaError("allow_metadata_inventory_take should" - " return a number, guilty node: " + nodename); + " return a number. node=" + nodename); int num = luaL_checkinteger(L, -1); lua_pop(L, 2); // Pop integer and error handler return num; @@ -153,7 +153,7 @@ void ScriptApiNodemeta::nodemeta_inventory_OnMove( return; // Push callback function on stack - std::string nodename = ndef->get(node).name; + const auto &nodename = ndef->get(node).name; if (!getItemCallback(nodename.c_str(), "on_metadata_inventory_move", &ma.from_inv.p)) return; @@ -186,7 +186,7 @@ void ScriptApiNodemeta::nodemeta_inventory_OnPut( return; // Push callback function on stack - std::string nodename = ndef->get(node).name; + const auto &nodename = ndef->get(node).name; if (!getItemCallback(nodename.c_str(), "on_metadata_inventory_put", &ma.to_inv.p)) return; @@ -217,7 +217,7 @@ void ScriptApiNodemeta::nodemeta_inventory_OnTake( return; // Push callback function on stack - std::string nodename = ndef->get(node).name; + const auto &nodename = ndef->get(node).name; if (!getItemCallback(nodename.c_str(), "on_metadata_inventory_take", &ma.from_inv.p)) return; diff --git a/src/server.cpp b/src/server.cpp index 46b497d6e..8474bc6f1 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -103,7 +103,13 @@ void *ServerThread::run() * doesn't busy wait) and will process any remaining packets. */ - m_server->AsyncRunStep(true); + try { + m_server->AsyncRunStep(true); + } catch (con::ConnectionBindFailed &e) { + m_server->setAsyncFatalError(e.what()); + } catch (LuaError &e) { + m_server->setAsyncFatalError(e); + } while (!stopRequested()) { try { @@ -117,8 +123,7 @@ void *ServerThread::run() } catch (con::ConnectionBindFailed &e) { m_server->setAsyncFatalError(e.what()); } catch (LuaError &e) { - m_server->setAsyncFatalError( - "ServerThread::run Lua: " + std::string(e.what())); + m_server->setAsyncFatalError(e); } } diff --git a/src/server.h b/src/server.h index 7b16845af..c5db0fdfb 100644 --- a/src/server.h +++ b/src/server.h @@ -300,6 +300,10 @@ public: inline void setAsyncFatalError(const std::string &error) { m_async_fatal_error.set(error); } + inline void setAsyncFatalError(const LuaError &e) + { + setAsyncFatalError(std::string("Lua: ") + e.what()); + } bool showFormspec(const char *name, const std::string &formspec, const std::string &formname); Map & getMap() { return m_env->getMap(); } -- cgit v1.2.3 From 75bf9b75caba5fc876f20eabea3fabc142d1b51e Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sat, 11 Sep 2021 21:06:57 +0200 Subject: Make sure relevant std::stringstreams are set to binary --- src/client/client.cpp | 8 ++++---- src/client/game.cpp | 2 +- src/database/database-leveldb.cpp | 16 ++++++++-------- src/database/database-postgresql.cpp | 14 ++++++++------ src/database/database-redis.cpp | 6 ++---- src/database/database-sqlite3.cpp | 13 +++++++++---- src/gui/guiChatConsole.cpp | 1 - src/gui/guiFormSpecMenu.cpp | 10 +++------- src/inventory.cpp | 1 - src/itemstackmetadata.cpp | 2 +- src/network/clientpackethandler.cpp | 12 +++++------- src/script/common/c_converter.cpp | 5 +---- src/script/lua_api/l_mapgen.cpp | 4 +--- src/script/lua_api/l_util.cpp | 12 ++++++------ src/settings.cpp | 5 +---- src/unittest/test_areastore.cpp | 4 ++-- src/util/serialize.cpp | 5 ++--- 17 files changed, 54 insertions(+), 66 deletions(-) (limited to 'src') diff --git a/src/client/client.cpp b/src/client/client.cpp index 13ff22e8e..45cc62a33 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -1693,13 +1693,13 @@ float Client::mediaReceiveProgress() return 1.0; // downloader only exists when not yet done } -typedef struct TextureUpdateArgs { +struct TextureUpdateArgs { gui::IGUIEnvironment *guienv; u64 last_time_ms; u16 last_percent; const wchar_t* text_base; ITextureSource *tsrc; -} TextureUpdateArgs; +}; void Client::showUpdateProgressTexture(void *args, u32 progress, u32 max_progress) { @@ -1718,8 +1718,8 @@ void Client::showUpdateProgressTexture(void *args, u32 progress, u32 max_progres if (do_draw) { targs->last_time_ms = time_ms; - std::basic_stringstream strm; - strm << targs->text_base << " " << targs->last_percent << "%..."; + std::wostringstream strm; + strm << targs->text_base << L" " << targs->last_percent << L"%..."; m_rendering_engine->draw_load_screen(strm.str(), targs->guienv, targs->tsrc, 0, 72 + (u16) ((18. / 100.) * (double) targs->last_percent), true); } diff --git a/src/client/game.cpp b/src/client/game.cpp index 18df5cc58..a24ded844 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -1618,7 +1618,7 @@ bool Game::getServerContent(bool *aborted) dtime, progress); delete[] text; } else { - std::stringstream message; + std::ostringstream message; std::fixed(message); message.precision(0); float receive = client->mediaReceiveProgress() * 100; diff --git a/src/database/database-leveldb.cpp b/src/database/database-leveldb.cpp index 73cd63f6d..39f4c8442 100644 --- a/src/database/database-leveldb.cpp +++ b/src/database/database-leveldb.cpp @@ -70,11 +70,11 @@ bool Database_LevelDB::saveBlock(const v3s16 &pos, const std::string &data) void Database_LevelDB::loadBlock(const v3s16 &pos, std::string *block) { - std::string datastr; leveldb::Status status = m_database->Get(leveldb::ReadOptions(), - i64tos(getBlockAsInteger(pos)), &datastr); + i64tos(getBlockAsInteger(pos)), block); - *block = (status.ok()) ? datastr : ""; + if (!status.ok()) + block->clear(); } bool Database_LevelDB::deleteBlock(const v3s16 &pos) @@ -131,7 +131,7 @@ void PlayerDatabaseLevelDB::savePlayer(RemotePlayer *player) std::string (long) serialized_inventory */ - std::ostringstream os; + std::ostringstream os(std::ios_base::binary); writeU8(os, 1); PlayerSAO *sao = player->getPlayerSAO(); @@ -142,7 +142,7 @@ void PlayerDatabaseLevelDB::savePlayer(RemotePlayer *player) writeF32(os, sao->getRotation().Y); writeU16(os, sao->getBreath()); - StringMap stringvars = sao->getMeta().getStrings(); + const auto &stringvars = sao->getMeta().getStrings(); writeU32(os, stringvars.size()); for (const auto &it : stringvars) { os << serializeString16(it.first); @@ -170,7 +170,7 @@ bool PlayerDatabaseLevelDB::loadPlayer(RemotePlayer *player, PlayerSAO *sao) player->getName(), &raw); if (!s.ok()) return false; - std::istringstream is(raw); + std::istringstream is(raw, std::ios_base::binary); if (readU8(is) > 1) return false; @@ -230,7 +230,7 @@ bool AuthDatabaseLevelDB::getAuth(const std::string &name, AuthEntry &res) leveldb::Status s = m_database->Get(leveldb::ReadOptions(), name, &raw); if (!s.ok()) return false; - std::istringstream is(raw); + std::istringstream is(raw, std::ios_base::binary); /* u8 version = 1 @@ -262,7 +262,7 @@ bool AuthDatabaseLevelDB::getAuth(const std::string &name, AuthEntry &res) bool AuthDatabaseLevelDB::saveAuth(const AuthEntry &authEntry) { - std::ostringstream os; + std::ostringstream os(std::ios_base::binary); writeU8(os, 1); os << serializeString16(authEntry.password); diff --git a/src/database/database-postgresql.cpp b/src/database/database-postgresql.cpp index 29ecd4223..3469f4242 100644 --- a/src/database/database-postgresql.cpp +++ b/src/database/database-postgresql.cpp @@ -274,10 +274,10 @@ void MapDatabasePostgreSQL::loadBlock(const v3s16 &pos, std::string *block) PGresult *results = execPrepared("read_block", ARRLEN(args), args, argLen, argFmt, false); - *block = ""; - if (PQntuples(results)) - *block = std::string(PQgetvalue(results, 0, 0), PQgetlength(results, 0, 0)); + block->assign(PQgetvalue(results, 0, 0), PQgetlength(results, 0, 0)); + else + block->clear(); PQclear(results); } @@ -496,6 +496,7 @@ void PlayerDatabasePostgreSQL::savePlayer(RemotePlayer *player) execPrepared("remove_player_inventory_items", 1, rmvalues); std::vector inventory_lists = sao->getInventory()->getLists(); + std::ostringstream oss; for (u16 i = 0; i < inventory_lists.size(); i++) { const InventoryList* list = inventory_lists[i]; const std::string &name = list->getName(); @@ -512,9 +513,10 @@ void PlayerDatabasePostgreSQL::savePlayer(RemotePlayer *player) execPrepared("add_player_inventory", 5, inv_values); for (u32 j = 0; j < list->getSize(); j++) { - std::ostringstream os; - list->getItem(j).serialize(os); - std::string itemStr = os.str(), slotId = itos(j); + oss.str(""); + oss.clear(); + list->getItem(j).serialize(oss); + std::string itemStr = oss.str(), slotId = itos(j); const char* invitem_values[] = { player->getName(), diff --git a/src/database/database-redis.cpp b/src/database/database-redis.cpp index 096ea504d..5ffff67b7 100644 --- a/src/database/database-redis.cpp +++ b/src/database/database-redis.cpp @@ -127,8 +127,7 @@ void Database_Redis::loadBlock(const v3s16 &pos, std::string *block) switch (reply->type) { case REDIS_REPLY_STRING: { - *block = std::string(reply->str, reply->len); - // std::string copies the memory so this won't cause any problems + block->assign(reply->str, reply->len); freeReplyObject(reply); return; } @@ -141,8 +140,7 @@ void Database_Redis::loadBlock(const v3s16 &pos, std::string *block) "Redis command 'HGET %s %s' errored: ") + errstr); } case REDIS_REPLY_NIL: { - *block = ""; - // block not found in database + block->clear(); freeReplyObject(reply); return; } diff --git a/src/database/database-sqlite3.cpp b/src/database/database-sqlite3.cpp index 4560743b9..898acc265 100644 --- a/src/database/database-sqlite3.cpp +++ b/src/database/database-sqlite3.cpp @@ -302,7 +302,10 @@ void MapDatabaseSQLite3::loadBlock(const v3s16 &pos, std::string *block) const char *data = (const char *) sqlite3_column_blob(m_stmt_read, 0); size_t len = sqlite3_column_bytes(m_stmt_read, 0); - *block = (data) ? std::string(data, len) : ""; + if (data) + block->assign(data, len); + else + block->clear(); sqlite3_step(m_stmt_read); // We should never get more than 1 row, so ok to reset @@ -491,6 +494,7 @@ void PlayerDatabaseSQLite3::savePlayer(RemotePlayer *player) sqlite3_reset(m_stmt_player_remove_inventory_items); std::vector inventory_lists = sao->getInventory()->getLists(); + std::ostringstream oss; for (u16 i = 0; i < inventory_lists.size(); i++) { const InventoryList* list = inventory_lists[i]; @@ -503,9 +507,10 @@ void PlayerDatabaseSQLite3::savePlayer(RemotePlayer *player) sqlite3_reset(m_stmt_player_add_inventory); for (u32 j = 0; j < list->getSize(); j++) { - std::ostringstream os; - list->getItem(j).serialize(os); - std::string itemStr = os.str(); + oss.str(""); + oss.clear(); + list->getItem(j).serialize(oss); + std::string itemStr = oss.str(); str_to_sqlite(m_stmt_player_add_inventory_items, 1, player->getName()); int_to_sqlite(m_stmt_player_add_inventory_items, 2, i); diff --git a/src/gui/guiChatConsole.cpp b/src/gui/guiChatConsole.cpp index 049e21a16..0610c85cc 100644 --- a/src/gui/guiChatConsole.cpp +++ b/src/gui/guiChatConsole.cpp @@ -729,7 +729,6 @@ void GUIChatConsole::middleClick(s32 col, s32 row) msg << gettext("Failed to open webpage"); } msg << " '" << weblink << "'"; - msg.flush(); m_chat_backend->addUnparsedMessage(utf8_to_wide(msg.str())); } } diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 09b004f8f..797fd3ff6 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -3933,9 +3933,7 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode) } if (e != 0) { - std::stringstream ss; - ss << (e->getActiveTab() +1); - fields[name] = ss.str(); + fields[name] = itos(e->getActiveTab() + 1); } } else if (s.ftype == f_CheckBox) { // No dynamic cast possible due to some distributions shipped @@ -3961,12 +3959,10 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode) e = static_cast(element); if (e) { - std::stringstream os; - os << e->getPos(); if (s.fdefault == L"Changed") - fields[name] = "CHG:" + os.str(); + fields[name] = "CHG:" + itos(e->getPos()); else - fields[name] = "VAL:" + os.str(); + fields[name] = "VAL:" + itos(e->getPos()); } } else if (s.ftype == f_AnimatedImage) { // No dynamic cast possible due to some distributions shipped diff --git a/src/inventory.cpp b/src/inventory.cpp index b3bed623a..da6517e62 100644 --- a/src/inventory.cpp +++ b/src/inventory.cpp @@ -460,7 +460,6 @@ void InventoryList::deSerialize(std::istream &is) std::getline(is, line, '\n'); std::istringstream iss(line); - //iss.imbue(std::locale("C")); std::string name; std::getline(iss, name, ' '); diff --git a/src/itemstackmetadata.cpp b/src/itemstackmetadata.cpp index 7a26fbb0e..529e0149f 100644 --- a/src/itemstackmetadata.cpp +++ b/src/itemstackmetadata.cpp @@ -60,7 +60,7 @@ bool ItemStackMetadata::setString(const std::string &name, const std::string &va void ItemStackMetadata::serialize(std::ostream &os) const { - std::ostringstream os2; + std::ostringstream os2(std::ios_base::binary); os2 << DESERIALIZE_START; for (const auto &stringvar : m_stringvars) { if (!stringvar.first.empty() || !stringvar.second.empty()) diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 9c9c59d13..128240c02 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -261,7 +261,7 @@ void Client::handleCommand_NodemetaChanged(NetworkPacket *pkt) return; std::istringstream is(pkt->readLongString(), std::ios::binary); - std::stringstream sstr; + std::stringstream sstr(std::ios::binary); decompressZlib(is, sstr); NodeMetadataList meta_updates_list(false); @@ -760,12 +760,11 @@ void Client::handleCommand_NodeDef(NetworkPacket* pkt) // Decompress node definitions std::istringstream tmp_is(pkt->readLongString(), std::ios::binary); - std::ostringstream tmp_os; + std::stringstream tmp_os(std::ios::binary | std::ios::in | std::ios::out); decompressZlib(tmp_is, tmp_os); // Deserialize node definitions - std::istringstream tmp_is2(tmp_os.str()); - m_nodedef->deSerialize(tmp_is2); + m_nodedef->deSerialize(tmp_os); m_nodedef_received = true; } @@ -780,12 +779,11 @@ void Client::handleCommand_ItemDef(NetworkPacket* pkt) // Decompress item definitions std::istringstream tmp_is(pkt->readLongString(), std::ios::binary); - std::ostringstream tmp_os; + std::stringstream tmp_os(std::ios::binary | std::ios::in | std::ios::out); decompressZlib(tmp_is, tmp_os); // Deserialize node definitions - std::istringstream tmp_is2(tmp_os.str()); - m_itemdef->deSerialize(tmp_is2); + m_itemdef->deSerialize(tmp_os); m_itemdef_received = true; } diff --git a/src/script/common/c_converter.cpp b/src/script/common/c_converter.cpp index d848b75b8..19734b913 100644 --- a/src/script/common/c_converter.cpp +++ b/src/script/common/c_converter.cpp @@ -76,10 +76,7 @@ static void set_vector_metatable(lua_State *L) void push_float_string(lua_State *L, float value) { - std::stringstream ss; - std::string str; - ss << value; - str = ss.str(); + auto str = ftos(value); lua_pushstring(L, str.c_str()); } diff --git a/src/script/lua_api/l_mapgen.cpp b/src/script/lua_api/l_mapgen.cpp index eb3d49a5e..f173bd162 100644 --- a/src/script/lua_api/l_mapgen.cpp +++ b/src/script/lua_api/l_mapgen.cpp @@ -752,9 +752,7 @@ int ModApiMapgen::l_get_mapgen_params(lua_State *L) lua_setfield(L, -2, "mgname"); settingsmgr->getMapSetting("seed", &value); - std::istringstream ss(value); - u64 seed; - ss >> seed; + u64 seed = from_string(value); lua_pushinteger(L, seed); lua_setfield(L, -2, "seed"); diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index 9152b5f7f..2405cd90d 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -272,11 +272,11 @@ int ModApiUtil::l_compress(lua_State *L) const char *data = luaL_checklstring(L, 1, &size); int level = -1; - if (!lua_isnone(L, 3) && !lua_isnil(L, 3)) - level = readParam(L, 3); + if (!lua_isnoneornil(L, 3)) + level = readParam(L, 3); - std::ostringstream os; - compressZlib(std::string(data, size), os, level); + std::ostringstream os(std::ios_base::binary); + compressZlib(reinterpret_cast(data), size, os, level); std::string out = os.str(); @@ -292,8 +292,8 @@ int ModApiUtil::l_decompress(lua_State *L) size_t size; const char *data = luaL_checklstring(L, 1, &size); - std::istringstream is(std::string(data, size)); - std::ostringstream os; + std::istringstream is(std::string(data, size), std::ios_base::binary); + std::ostringstream os(std::ios_base::binary); decompressZlib(is, os); std::string out = os.str(); diff --git a/src/settings.cpp b/src/settings.cpp index 4def46112..f4de5bec9 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -537,11 +537,8 @@ float Settings::getFloat(const std::string &name) const u64 Settings::getU64(const std::string &name) const { - u64 value = 0; std::string s = get(name); - std::istringstream ss(s); - ss >> value; - return value; + return from_string(s); } diff --git a/src/unittest/test_areastore.cpp b/src/unittest/test_areastore.cpp index 691cd69d2..2af3ca90c 100644 --- a/src/unittest/test_areastore.cpp +++ b/src/unittest/test_areastore.cpp @@ -135,7 +135,7 @@ void TestAreaStore::testSerialization() b.data = "Area BB"; store.insertArea(&b); - std::ostringstream os; + std::ostringstream os(std::ios_base::binary); store.serialize(os); std::string str = os.str(); @@ -157,7 +157,7 @@ void TestAreaStore::testSerialization() UASSERTEQ(const std::string &, str, str_wanted); - std::istringstream is(str); + std::istringstream is(str, std::ios_base::binary); store.deserialize(is); // deserialize() doesn't clear the store diff --git a/src/util/serialize.cpp b/src/util/serialize.cpp index d770101f2..281061229 100644 --- a/src/util/serialize.cpp +++ b/src/util/serialize.cpp @@ -248,7 +248,7 @@ std::string serializeJsonStringIfNeeded(const std::string &s) std::string deSerializeJsonStringIfNeeded(std::istream &is) { - std::ostringstream tmp_os; + std::stringstream tmp_os(std::ios_base::binary | std::ios_base::in | std::ios_base::out); bool expect_initial_quote = true; bool is_json = false; bool was_backslash = false; @@ -280,8 +280,7 @@ std::string deSerializeJsonStringIfNeeded(std::istream &is) expect_initial_quote = false; } if (is_json) { - std::istringstream tmp_is(tmp_os.str(), std::ios::binary); - return deSerializeJsonString(tmp_is); + return deSerializeJsonString(tmp_os); } return tmp_os.str(); -- cgit v1.2.3 From b480a3e9fd7036208068c4fad410f0cae8fc9c4f Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 12 Sep 2021 14:35:52 +0200 Subject: Fix broken handling of NodemetaChanged packets fixes #11610 --- src/network/clientpackethandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 128240c02..b2965c23d 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -261,7 +261,7 @@ void Client::handleCommand_NodemetaChanged(NetworkPacket *pkt) return; std::istringstream is(pkt->readLongString(), std::ios::binary); - std::stringstream sstr(std::ios::binary); + std::stringstream sstr(std::ios::binary | std::ios::in | std::ios::out); decompressZlib(is, sstr); NodeMetadataList meta_updates_list(false); -- cgit v1.2.3 From 4feb799b7e33c1a544036a830faf00eb33d3eaf5 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 13 Sep 2021 19:40:53 +0200 Subject: Add Windows-specific CreateTempFile() implementation Once again MSVC is the only compiler not supporting basic POSIX functionality. --- src/filesys.cpp | 40 +++++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/filesys.cpp b/src/filesys.cpp index 0941739b8..a07370c0e 100644 --- a/src/filesys.cpp +++ b/src/filesys.cpp @@ -24,7 +24,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include -#include #include #include "log.h" #include "config.h" @@ -38,6 +37,7 @@ namespace fs #define _WIN32_WINNT 0x0501 #include #include +#include std::vector GetDirListing(const std::string &pathstring) { @@ -178,13 +178,27 @@ std::string TempPath() errorstream<<"GetTempPath failed, error = "< buf(bufsize); + std::string buf; + buf.resize(bufsize); DWORD len = GetTempPath(bufsize, &buf[0]); if(len == 0 || len > bufsize){ errorstream<<"GetTempPath failed, error = "< &dirs, const std::string &dir) @@ -813,15 +837,5 @@ bool Rename(const std::string &from, const std::string &to) return rename(from.c_str(), to.c_str()) == 0; } -std::string CreateTempFile() -{ - std::string path = TempPath() + DIR_DELIM "MT_XXXXXX"; - int fd = mkstemp(&path[0]); // modifies path - if (fd == -1) - return ""; - close(fd); - return path; -} - } // namespace fs -- cgit v1.2.3 From 719a12ecac1c5363612e0c230eae411bdb3fe058 Mon Sep 17 00:00:00 2001 From: Lars Müller <34514239+appgurueu@users.noreply.github.com> Date: Tue, 14 Sep 2021 20:46:02 +0200 Subject: Chop game background in mainmenu (#10796) --- games/devtest/menu/background.png | Bin 152 -> 160 bytes src/gui/guiEngine.cpp | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/games/devtest/menu/background.png b/games/devtest/menu/background.png index 415bb3d14..89c45fcd5 100644 Binary files a/games/devtest/menu/background.png and b/games/devtest/menu/background.png differ diff --git a/src/gui/guiEngine.cpp b/src/gui/guiEngine.cpp index b3808535c..c39c3ee0d 100644 --- a/src/gui/guiEngine.cpp +++ b/src/gui/guiEngine.cpp @@ -437,9 +437,22 @@ void GUIEngine::drawBackground(video::IVideoDriver *driver) return; } + // Chop background image to the smaller screen dimension + v2u32 bg_size = screensize; + v2f32 scale( + (f32) bg_size.X / sourcesize.X, + (f32) bg_size.Y / sourcesize.Y); + if (scale.X < scale.Y) + bg_size.X = (int) (scale.Y * sourcesize.X); + else + bg_size.Y = (int) (scale.X * sourcesize.Y); + v2s32 offset = v2s32( + (s32) screensize.X - (s32) bg_size.X, + (s32) screensize.Y - (s32) bg_size.Y + ) / 2; /* Draw background texture */ draw2DImageFilterScaled(driver, texture, - core::rect(0, 0, screensize.X, screensize.Y), + core::rect(offset.X, offset.Y, bg_size.X + offset.X, bg_size.Y + offset.Y), core::rect(0, 0, sourcesize.X, sourcesize.Y), NULL, NULL, true); } -- cgit v1.2.3 From 6fedee16f098549ffaee188b02b777239513abc3 Mon Sep 17 00:00:00 2001 From: ROllerozxa Date: Wed, 15 Sep 2021 12:12:24 +0200 Subject: Readd TGA to the list of valid texture formats. (#11598) --- src/client/tile.cpp | 2 +- src/server.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/tile.cpp b/src/client/tile.cpp index 15ae5472d..a31e3aca1 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -81,7 +81,7 @@ static bool replace_ext(std::string &path, const char *ext) std::string getImagePath(std::string path) { // A NULL-ended list of possible image extensions - const char *extensions[] = { "png", "jpg", "bmp", NULL }; + const char *extensions[] = { "png", "jpg", "bmp", "tga", NULL }; // If there is no extension, assume PNG if (removeStringEnd(path, extensions).empty()) path = path + ".png"; diff --git a/src/server.cpp b/src/server.cpp index 8474bc6f1..7fb9a78e9 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -2453,7 +2453,7 @@ bool Server::addMediaFile(const std::string &filename, } // If name is not in a supported format, ignore it const char *supported_ext[] = { - ".png", ".jpg", ".bmp", + ".png", ".jpg", ".bmp", ".tga", ".ogg", ".x", ".b3d", ".obj", // Custom translation file format -- cgit v1.2.3 From d1e0f73b770165fbd889b8298dec79c83107862e Mon Sep 17 00:00:00 2001 From: HybridDog Date: Thu, 5 Mar 2020 14:11:58 +0100 Subject: Hide Wself-assign-overloaded and Wself-move unittest compilation warnings The warnings occured with the clang compiler --- src/unittest/test_irrptr.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'src') diff --git a/src/unittest/test_irrptr.cpp b/src/unittest/test_irrptr.cpp index aa857ff46..3484f1514 100644 --- a/src/unittest/test_irrptr.cpp +++ b/src/unittest/test_irrptr.cpp @@ -91,6 +91,12 @@ void TestIrrPtr::testRefCounting() obj->getReferenceCount()); } +#if defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wself-assign-overloaded" + #pragma GCC diagnostic ignored "-Wself-move" +#endif + void TestIrrPtr::testSelfAssignment() { irr_ptr p1{new IReferenceCounted()}; @@ -129,3 +135,7 @@ void TestIrrPtr::testNullHandling() UASSERT(!p2); UASSERT(!p3); } + +#if defined(__clang__) + #pragma GCC diagnostic pop +#endif -- cgit v1.2.3 From ea250ff5c57301b6ea3e529c811484c743c1fde1 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Fri, 10 Sep 2021 21:59:29 +0200 Subject: Fix GLES2 discard behaviour (texture transparency) --- client/shaders/nodes_shader/opengl_fragment.glsl | 9 ++++++--- client/shaders/object_shader/opengl_fragment.glsl | 9 ++++++--- src/client/shader.cpp | 8 ++++++-- 3 files changed, 18 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/client/shaders/nodes_shader/opengl_fragment.glsl b/client/shaders/nodes_shader/opengl_fragment.glsl index f85ca7b48..87ef9af7d 100644 --- a/client/shaders/nodes_shader/opengl_fragment.glsl +++ b/client/shaders/nodes_shader/opengl_fragment.glsl @@ -463,13 +463,16 @@ void main(void) vec2 uv = varTexCoord.st; vec4 base = texture2D(baseTexture, uv).rgba; -#ifdef USE_DISCARD // If alpha is zero, we can just discard the pixel. This fixes transparency // on GPUs like GC7000L, where GL_ALPHA_TEST is not implemented in mesa, // and also on GLES 2, where GL_ALPHA_TEST is missing entirely. - if (base.a == 0.0) { +#ifdef USE_DISCARD + if (base.a == 0.0) + discard; +#endif +#ifdef USE_DISCARD_REF + if (base.a < 0.5) discard; - } #endif color = base.rgb; diff --git a/client/shaders/object_shader/opengl_fragment.glsl b/client/shaders/object_shader/opengl_fragment.glsl index 8d6f57a44..9a0b90f15 100644 --- a/client/shaders/object_shader/opengl_fragment.glsl +++ b/client/shaders/object_shader/opengl_fragment.glsl @@ -328,13 +328,16 @@ void main(void) vec2 uv = varTexCoord.st; vec4 base = texture2D(baseTexture, uv).rgba; -#ifdef USE_DISCARD // If alpha is zero, we can just discard the pixel. This fixes transparency // on GPUs like GC7000L, where GL_ALPHA_TEST is not implemented in mesa, // and also on GLES 2, where GL_ALPHA_TEST is missing entirely. - if (base.a == 0.0) { +#ifdef USE_DISCARD + if (base.a == 0.0) + discard; +#endif +#ifdef USE_DISCARD_REF + if (base.a < 0.5) discard; - } #endif color = base.rgb; diff --git a/src/client/shader.cpp b/src/client/shader.cpp index 0b35c37af..dc9e9ae6d 100644 --- a/src/client/shader.cpp +++ b/src/client/shader.cpp @@ -674,8 +674,12 @@ ShaderInfo ShaderSource::generateShader(const std::string &name, if (strstr(gl_renderer, "GC7000")) use_discard = true; #endif - if (use_discard && shaderinfo.base_material != video::EMT_SOLID) - shaders_header << "#define USE_DISCARD 1\n"; + if (use_discard) { + if (shaderinfo.base_material == video::EMT_TRANSPARENT_ALPHA_CHANNEL) + shaders_header << "#define USE_DISCARD 1\n"; + else if (shaderinfo.base_material == video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF) + shaders_header << "#define USE_DISCARD_REF 1\n"; + } #define PROVIDE(constant) shaders_header << "#define " #constant " " << (int)constant << "\n" -- cgit v1.2.3 From fd8a8501bc26dfca2a93d51000867b8592210040 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Fri, 17 Sep 2021 18:14:25 +0200 Subject: Shave off buffer copies in networking code (#11607) --- src/network/connection.cpp | 72 +++++++++++++++++++++------------------ src/network/connection.h | 40 +++++++++------------- src/network/connectionthreads.cpp | 41 +++++++++++----------- src/network/networkpacket.cpp | 9 ++--- src/network/networkpacket.h | 3 +- src/unittest/test_connection.cpp | 12 +++---- src/util/container.h | 7 ++++ src/util/pointer.h | 34 ++++++++++++++++++ 8 files changed, 126 insertions(+), 92 deletions(-) (limited to 'src') diff --git a/src/network/connection.cpp b/src/network/connection.cpp index 0ba8c36b2..a4970954f 100644 --- a/src/network/connection.cpp +++ b/src/network/connection.cpp @@ -200,17 +200,12 @@ RPBSearchResult ReliablePacketBuffer::findPacket(u16 seqnum) return i; } -RPBSearchResult ReliablePacketBuffer::notFound() -{ - return m_list.end(); -} - bool ReliablePacketBuffer::getFirstSeqnum(u16& result) { MutexAutoLock listlock(m_list_mutex); if (m_list.empty()) return false; - const BufferedPacket &p = *m_list.begin(); + const BufferedPacket &p = m_list.front(); result = readU16(&p.data[BASE_HEADER_SIZE + 1]); return true; } @@ -220,14 +215,14 @@ BufferedPacket ReliablePacketBuffer::popFirst() MutexAutoLock listlock(m_list_mutex); if (m_list.empty()) throw NotFoundException("Buffer is empty"); - BufferedPacket p = *m_list.begin(); - m_list.erase(m_list.begin()); + BufferedPacket p = std::move(m_list.front()); + m_list.pop_front(); if (m_list.empty()) { m_oldest_non_answered_ack = 0; } else { m_oldest_non_answered_ack = - readU16(&m_list.begin()->data[BASE_HEADER_SIZE + 1]); + readU16(&m_list.front().data[BASE_HEADER_SIZE + 1]); } return p; } @@ -241,15 +236,7 @@ BufferedPacket ReliablePacketBuffer::popSeqnum(u16 seqnum) << " not found in reliable buffer"<data[BASE_HEADER_SIZE+1])); - m_oldest_non_answered_ack = s; - } + BufferedPacket p = std::move(*r); m_list.erase(r); @@ -257,12 +244,12 @@ BufferedPacket ReliablePacketBuffer::popSeqnum(u16 seqnum) m_oldest_non_answered_ack = 0; } else { m_oldest_non_answered_ack = - readU16(&m_list.begin()->data[BASE_HEADER_SIZE + 1]); + readU16(&m_list.front().data[BASE_HEADER_SIZE + 1]); } return p; } -void ReliablePacketBuffer::insert(BufferedPacket &p, u16 next_expected) +void ReliablePacketBuffer::insert(const BufferedPacket &p, u16 next_expected) { MutexAutoLock listlock(m_list_mutex); if (p.data.getSize() < BASE_HEADER_SIZE + 3) { @@ -355,7 +342,7 @@ void ReliablePacketBuffer::insert(BufferedPacket &p, u16 next_expected) } /* update last packet number */ - m_oldest_non_answered_ack = readU16(&(*m_list.begin()).data[BASE_HEADER_SIZE+1]); + m_oldest_non_answered_ack = readU16(&m_list.front().data[BASE_HEADER_SIZE+1]); } void ReliablePacketBuffer::incrementTimeouts(float dtime) @@ -367,17 +354,19 @@ void ReliablePacketBuffer::incrementTimeouts(float dtime) } } -std::list ReliablePacketBuffer::getTimedOuts(float timeout, - unsigned int max_packets) +std::list + ReliablePacketBuffer::getTimedOuts(float timeout, u32 max_packets) { MutexAutoLock listlock(m_list_mutex); std::list timed_outs; for (BufferedPacket &bufferedPacket : m_list) { if (bufferedPacket.time >= timeout) { + // caller will resend packet so reset time and increase counter + bufferedPacket.time = 0.0f; + bufferedPacket.resend_count++; + timed_outs.push_back(bufferedPacket); - //this packet will be sent right afterwards reset timeout here - bufferedPacket.time = 0.0f; if (timed_outs.size() >= max_packets) break; } @@ -1051,20 +1040,20 @@ bool UDPPeer::processReliableSendCommand( m_connection->GetProtocolID(), m_connection->GetPeerID(), c.channelnum); - toadd.push(p); + toadd.push(std::move(p)); } if (have_sequence_number) { volatile u16 pcount = 0; while (!toadd.empty()) { - BufferedPacket p = toadd.front(); + BufferedPacket p = std::move(toadd.front()); toadd.pop(); // LOG(dout_con<getDesc() // << " queuing reliable packet for peer_id: " << c.peer_id // << " channel: " << (c.channelnum&0xFF) // << " seqnum: " << readU16(&p.data[BASE_HEADER_SIZE+1]) // << std::endl) - chan.queued_reliables.push(p); + chan.queued_reliables.push(std::move(p)); pcount++; } sanity_check(chan.queued_reliables.size() < 0xFFFF); @@ -1208,12 +1197,19 @@ Connection::~Connection() } /* Internal stuff */ -void Connection::putEvent(ConnectionEvent &e) + +void Connection::putEvent(const ConnectionEvent &e) { assert(e.type != CONNEVENT_NONE); // Pre-condition m_event_queue.push_back(e); } +void Connection::putEvent(ConnectionEvent &&e) +{ + assert(e.type != CONNEVENT_NONE); // Pre-condition + m_event_queue.push_back(std::move(e)); +} + void Connection::TriggerSend() { m_sendThread->Trigger(); @@ -1299,7 +1295,7 @@ ConnectionEvent Connection::waitEvent(u32 timeout_ms) } } -void Connection::putCommand(ConnectionCommand &c) +void Connection::putCommand(const ConnectionCommand &c) { if (!m_shutting_down) { m_command_queue.push_back(c); @@ -1307,6 +1303,14 @@ void Connection::putCommand(ConnectionCommand &c) } } +void Connection::putCommand(ConnectionCommand &&c) +{ + if (!m_shutting_down) { + m_command_queue.push_back(std::move(c)); + m_sendThread->Trigger(); + } +} + void Connection::Serve(Address bind_addr) { ConnectionCommand c; @@ -1408,7 +1412,7 @@ void Connection::Send(session_t peer_id, u8 channelnum, ConnectionCommand c; c.send(peer_id, channelnum, pkt, reliable); - putCommand(c); + putCommand(std::move(c)); } Address Connection::GetPeerAddress(session_t peer_id) @@ -1508,12 +1512,12 @@ u16 Connection::createPeer(Address& sender, MTProtocols protocol, int fd) << "createPeer(): giving peer_id=" << peer_id_new << std::endl); ConnectionCommand cmd; - SharedBuffer reply(4); + Buffer reply(4); writeU8(&reply[0], PACKET_TYPE_CONTROL); writeU8(&reply[1], CONTROLTYPE_SET_PEER_ID); writeU16(&reply[2], peer_id_new); cmd.createPeer(peer_id_new,reply); - putCommand(cmd); + putCommand(std::move(cmd)); // Create peer addition event ConnectionEvent e; @@ -1560,7 +1564,7 @@ void Connection::sendAck(session_t peer_id, u8 channelnum, u16 seqnum) writeU16(&ack[2], seqnum); c.ack(peer_id, channelnum, ack); - putCommand(c); + putCommand(std::move(c)); m_sendThread->Trigger(); } diff --git a/src/network/connection.h b/src/network/connection.h index 24cd4fe4a..49bb65c3e 100644 --- a/src/network/connection.h +++ b/src/network/connection.h @@ -19,7 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once -#include "irrlichttypes_bloated.h" +#include "irrlichttypes.h" #include "peerhandler.h" #include "socket.h" #include "constants.h" @@ -29,7 +29,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/numeric.h" #include "networkprotocol.h" #include -#include #include #include @@ -242,20 +241,19 @@ public: BufferedPacket popFirst(); BufferedPacket popSeqnum(u16 seqnum); - void insert(BufferedPacket &p, u16 next_expected); + void insert(const BufferedPacket &p, u16 next_expected); void incrementTimeouts(float dtime); - std::list getTimedOuts(float timeout, - unsigned int max_packets); + std::list getTimedOuts(float timeout, u32 max_packets); void print(); bool empty(); - RPBSearchResult notFound(); u32 size(); private: RPBSearchResult findPacket(u16 seqnum); // does not perform locking + inline RPBSearchResult notFound() { return m_list.end(); } std::list m_list; @@ -329,18 +327,6 @@ struct ConnectionCommand bool raw = false; ConnectionCommand() = default; - ConnectionCommand &operator=(const ConnectionCommand &other) - { - type = other.type; - address = other.address; - peer_id = other.peer_id; - channelnum = other.channelnum; - // We must copy the buffer here to prevent race condition - data = SharedBuffer(*other.data, other.data.getSize()); - reliable = other.reliable; - raw = other.raw; - return *this; - } void serve(Address address_) { @@ -364,7 +350,7 @@ struct ConnectionCommand void send(session_t peer_id_, u8 channelnum_, NetworkPacket *pkt, bool reliable_); - void ack(session_t peer_id_, u8 channelnum_, const SharedBuffer &data_) + void ack(session_t peer_id_, u8 channelnum_, const Buffer &data_) { type = CONCMD_ACK; peer_id = peer_id_; @@ -373,7 +359,7 @@ struct ConnectionCommand reliable = false; } - void createPeer(session_t peer_id_, const SharedBuffer &data_) + void createPeer(session_t peer_id_, const Buffer &data_) { type = CONCMD_CREATE_PEER; peer_id = peer_id_; @@ -707,7 +693,7 @@ struct ConnectionEvent ConnectionEvent() = default; - std::string describe() + const char *describe() const { switch(type) { case CONNEVENT_NONE: @@ -724,7 +710,7 @@ struct ConnectionEvent return "Invalid ConnectionEvent"; } - void dataReceived(session_t peer_id_, const SharedBuffer &data_) + void dataReceived(session_t peer_id_, const Buffer &data_) { type = CONNEVENT_DATA_RECEIVED; peer_id = peer_id_; @@ -763,7 +749,9 @@ public: /* Interface */ ConnectionEvent waitEvent(u32 timeout_ms); - void putCommand(ConnectionCommand &c); + // Warning: creates an unnecessary copy, prefer putCommand(T&&) if possible + void putCommand(const ConnectionCommand &c); + void putCommand(ConnectionCommand &&c); void SetTimeoutMs(u32 timeout) { m_bc_receive_timeout = timeout; } void Serve(Address bind_addr); @@ -802,11 +790,14 @@ protected: } UDPSocket m_udpSocket; + // Command queue: user -> SendThread MutexedQueue m_command_queue; bool Receive(NetworkPacket *pkt, u32 timeout); - void putEvent(ConnectionEvent &e); + // Warning: creates an unnecessary copy, prefer putEvent(T&&) if possible + void putEvent(const ConnectionEvent &e); + void putEvent(ConnectionEvent &&e); void TriggerSend(); @@ -815,6 +806,7 @@ protected: return getPeerNoEx(PEER_ID_SERVER) != nullptr; } private: + // Event queue: ReceiveThread -> user MutexedQueue m_event_queue; session_t m_peer_id = 0; diff --git a/src/network/connectionthreads.cpp b/src/network/connectionthreads.cpp index 7b62bc792..47678dac5 100644 --- a/src/network/connectionthreads.cpp +++ b/src/network/connectionthreads.cpp @@ -174,6 +174,11 @@ void ConnectionSendThread::runTimeouts(float dtime) std::vector timeouted_peers; std::vector peerIds = m_connection->getPeerIDs(); + const u32 numpeers = m_connection->m_peers.size(); + + if (numpeers == 0) + return; + for (session_t &peerId : peerIds) { PeerHelper peer = m_connection->getPeerNoEx(peerId); @@ -209,7 +214,6 @@ void ConnectionSendThread::runTimeouts(float dtime) float resend_timeout = udpPeer->getResendTimeout(); bool retry_count_exceeded = false; for (Channel &channel : udpPeer->channels) { - std::list timed_outs; // Remove timed out incomplete unreliable split packets channel.incoming_splits.removeUnreliableTimedOuts(dtime, m_timeout); @@ -217,13 +221,8 @@ void ConnectionSendThread::runTimeouts(float dtime) // Increment reliable packet times channel.outgoing_reliables_sent.incrementTimeouts(dtime); - unsigned int numpeers = m_connection->m_peers.size(); - - if (numpeers == 0) - return; - // Re-send timed out outgoing reliables - timed_outs = channel.outgoing_reliables_sent.getTimedOuts(resend_timeout, + auto timed_outs = channel.outgoing_reliables_sent.getTimedOuts(resend_timeout, (m_max_data_packets_per_iteration / numpeers)); channel.UpdatePacketLossCounter(timed_outs.size()); @@ -231,16 +230,14 @@ void ConnectionSendThread::runTimeouts(float dtime) m_iteration_packets_avaialble -= timed_outs.size(); - for (std::list::iterator k = timed_outs.begin(); - k != timed_outs.end(); ++k) { - session_t peer_id = readPeerId(*(k->data)); - u8 channelnum = readChannel(*(k->data)); - u16 seqnum = readU16(&(k->data[BASE_HEADER_SIZE + 1])); + for (const auto &k : timed_outs) { + session_t peer_id = readPeerId(*k.data); + u8 channelnum = readChannel(*k.data); + u16 seqnum = readU16(&(k.data[BASE_HEADER_SIZE + 1])); - channel.UpdateBytesLost(k->data.getSize()); - k->resend_count++; + channel.UpdateBytesLost(k.data.getSize()); - if (k->resend_count > MAX_RELIABLE_RETRY) { + if (k.resend_count > MAX_RELIABLE_RETRY) { retry_count_exceeded = true; timeouted_peers.push_back(peer->id); /* no need to check additional packets if a single one did timeout*/ @@ -249,14 +246,14 @@ void ConnectionSendThread::runTimeouts(float dtime) LOG(derr_con << m_connection->getDesc() << "RE-SENDING timed-out RELIABLE to " - << k->address.serializeString() + << k.address.serializeString() << "(t/o=" << resend_timeout << "): " << "from_peer_id=" << peer_id << ", channel=" << ((int) channelnum & 0xff) << ", seqnum=" << seqnum << std::endl); - rawSend(*k); + rawSend(k); // do not handle rtt here as we can't decide if this packet was // lost or really takes more time to transmit @@ -375,7 +372,7 @@ bool ConnectionSendThread::rawSendAsPacket(session_t peer_id, u8 channelnum, << " INFO: queueing reliable packet for peer_id: " << peer_id << " channel: " << (u32)channelnum << " seqnum: " << seqnum << std::endl); - channel->queued_reliables.push(p); + channel->queued_reliables.push(std::move(p)); return false; } @@ -717,13 +714,15 @@ void ConnectionSendThread::sendPackets(float dtime) channel.outgoing_reliables_sent.size() < channel.getWindowSize() && peer->m_increment_packets_remaining > 0) { - BufferedPacket p = channel.queued_reliables.front(); + BufferedPacket p = std::move(channel.queued_reliables.front()); channel.queued_reliables.pop(); + LOG(dout_con << m_connection->getDesc() << " INFO: sending a queued reliable packet " << " channel: " << i << ", seqnum: " << readU16(&p.data[BASE_HEADER_SIZE + 1]) << std::endl); + sendAsPacketReliable(p, &channel); peer->m_increment_packets_remaining--; } @@ -911,7 +910,7 @@ void ConnectionReceiveThread::receive(SharedBuffer &packetdata, if (data_left) { ConnectionEvent e; e.dataReceived(peer_id, resultdata); - m_connection->putEvent(e); + m_connection->putEvent(std::move(e)); } } catch (ProcessedSilentlyException &e) { @@ -1022,7 +1021,7 @@ void ConnectionReceiveThread::receive(SharedBuffer &packetdata, ConnectionEvent e; e.dataReceived(peer_id, resultdata); - m_connection->putEvent(e); + m_connection->putEvent(std::move(e)); } catch (ProcessedSilentlyException &e) { } diff --git a/src/network/networkpacket.cpp b/src/network/networkpacket.cpp index a71e26572..6b8b0f703 100644 --- a/src/network/networkpacket.cpp +++ b/src/network/networkpacket.cpp @@ -549,14 +549,11 @@ NetworkPacket& NetworkPacket::operator<<(video::SColor src) return *this; } -SharedBuffer NetworkPacket::oldForgePacket() +Buffer NetworkPacket::oldForgePacket() { - SharedBuffer sb(m_datasize + 2); + Buffer sb(m_datasize + 2); writeU16(&sb[0], m_command); + memcpy(&sb[2], m_data.data(), m_datasize); - u8* datas = getU8Ptr(0); - - if (datas != NULL) - memcpy(&sb[2], datas, m_datasize); return sb; } diff --git a/src/network/networkpacket.h b/src/network/networkpacket.h index c7ff03b8e..b1c44f055 100644 --- a/src/network/networkpacket.h +++ b/src/network/networkpacket.h @@ -115,7 +115,8 @@ public: NetworkPacket &operator<<(video::SColor src); // Temp, we remove SharedBuffer when migration finished - SharedBuffer oldForgePacket(); + // ^ this comment has been here for 4 years + Buffer oldForgePacket(); private: void checkReadOffset(u32 from_offset, u32 field_size); diff --git a/src/unittest/test_connection.cpp b/src/unittest/test_connection.cpp index c3aacc536..23b7e9105 100644 --- a/src/unittest/test_connection.cpp +++ b/src/unittest/test_connection.cpp @@ -88,7 +88,7 @@ void TestConnection::testNetworkPacketSerialize() }; if (sizeof(wchar_t) == 2) - warningstream << __func__ << " may fail on this platform." << std::endl; + warningstream << __FUNCTION__ << " may fail on this platform." << std::endl; { NetworkPacket pkt(123, 0); @@ -96,7 +96,7 @@ void TestConnection::testNetworkPacketSerialize() // serializing wide strings should do surrogate encoding, we test that here pkt << std::wstring(L"\U00020b9a"); - SharedBuffer buf = pkt.oldForgePacket(); + auto buf = pkt.oldForgePacket(); UASSERTEQ(int, buf.getSize(), sizeof(expected)); UASSERT(!memcmp(expected, &buf[0], buf.getSize())); } @@ -280,7 +280,7 @@ void TestConnection::testConnectSendReceive() NetworkPacket pkt; pkt.putRawPacket((u8*) "Hello World !", 14, 0); - SharedBuffer sentdata = pkt.oldForgePacket(); + auto sentdata = pkt.oldForgePacket(); infostream<<"** running client.Send()"< sentdata = pkt.oldForgePacket(); + auto sentdata = pkt.oldForgePacket(); server.Send(peer_id_client, 0, &pkt, true); //sleep_ms(3000); - SharedBuffer recvdata; + Buffer recvdata; infostream << "** running client.Receive()" << std::endl; session_t peer_id = 132; u16 size = 0; diff --git a/src/util/container.h b/src/util/container.h index 1c4a219f0..001066563 100644 --- a/src/util/container.h +++ b/src/util/container.h @@ -140,6 +140,13 @@ public: m_signal.post(); } + void push_back(T &&t) + { + MutexAutoLock lock(m_mutex); + m_queue.push_back(std::move(t)); + m_signal.post(); + } + /* this version of pop_front returns a empty element of T on timeout. * Make sure default constructor of T creates a recognizable "empty" element */ diff --git a/src/util/pointer.h b/src/util/pointer.h index d29ec8739..7fc5de551 100644 --- a/src/util/pointer.h +++ b/src/util/pointer.h @@ -51,6 +51,19 @@ public: else data = NULL; } + Buffer(Buffer &&buffer) + { + m_size = buffer.m_size; + if(m_size != 0) + { + data = buffer.data; + buffer.data = nullptr; + buffer.m_size = 0; + } + else + data = nullptr; + } + // Copies whole buffer Buffer(const T *t, unsigned int size) { m_size = size; @@ -62,10 +75,12 @@ public: else data = NULL; } + ~Buffer() { drop(); } + Buffer& operator=(const Buffer &buffer) { if(this == &buffer) @@ -81,6 +96,23 @@ public: data = NULL; return *this; } + Buffer& operator=(Buffer &&buffer) + { + if(this == &buffer) + return *this; + drop(); + m_size = buffer.m_size; + if(m_size != 0) + { + data = buffer.data; + buffer.data = nullptr; + buffer.m_size = 0; + } + else + data = nullptr; + return *this; + } + T & operator[](unsigned int i) const { return data[i]; @@ -89,10 +121,12 @@ public: { return data; } + unsigned int getSize() const { return m_size; } + private: void drop() { -- cgit v1.2.3 From ad076ede852e5ee8fc3b89e0de95da6c9be42cf5 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 13 Sep 2021 17:38:19 +0200 Subject: Add preprocessor check for weird (incorrect) build configurations --- src/main.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 543b70333..1044b327a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -61,11 +61,8 @@ extern "C" { #endif } -#if !defined(SERVER) && \ - (IRRLICHT_VERSION_MAJOR == 1) && \ - (IRRLICHT_VERSION_MINOR == 8) && \ - (IRRLICHT_VERSION_REVISION == 2) - #error "Irrlicht 1.8.2 is known to be broken - please update Irrlicht to version >= 1.8.3" +#if !defined(__cpp_rtti) || !defined(__cpp_exceptions) +#error Minetest cannot be built without exceptions or RTTI #endif #define DEBUGFILE "debug.txt" -- cgit v1.2.3 From e0529da5c84f224c380e6d5e063392cb01f85683 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 19 Sep 2021 13:46:56 +0200 Subject: Fix trivial typos --- src/client/client.h | 2 +- src/client/renderingengine.cpp | 2 +- src/network/clientpackethandler.cpp | 4 ++-- src/script/cpp_api/s_mainmenu.h | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/client.h b/src/client/client.h index f6030b022..b0324ee90 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -327,7 +327,7 @@ public: } inline void setFatalError(const LuaError &e) { - setFatalError(std::string("Lua :") + e.what()); + setFatalError(std::string("Lua: ") + e.what()); } // Renaming accessDeniedReason to better name could be good as it's used to diff --git a/src/client/renderingengine.cpp b/src/client/renderingengine.cpp index 8491dda04..0fdbc95dc 100644 --- a/src/client/renderingengine.cpp +++ b/src/client/renderingengine.cpp @@ -252,7 +252,7 @@ void RenderingEngine::setupTopLevelXorgWindow(const std::string &name) // force a shutdown of an application if it doesn't respond to the destroy // window message. - verbosestream << "Client: Setting Xorg _NET_WM_PID extened window manager property" + verbosestream << "Client: Setting Xorg _NET_WM_PID extended window manager property" << std::endl; Atom NET_WM_PID = XInternAtom(x11_dpl, "_NET_WM_PID", false); diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index b2965c23d..d20a80fb7 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -88,7 +88,7 @@ void Client::handleCommand_Hello(NetworkPacket* pkt) // This is only neccessary though when we actually want to add casing support if (m_chosen_auth_mech != AUTH_MECHANISM_NONE) { - // we recieved a TOCLIENT_HELLO while auth was already going on + // we received a TOCLIENT_HELLO while auth was already going on errorstream << "Client: TOCLIENT_HELLO while auth was already going on" << "(chosen_mech=" << m_chosen_auth_mech << ")." << std::endl; if (m_chosen_auth_mech == AUTH_MECHANISM_SRP || @@ -156,7 +156,7 @@ void Client::handleCommand_AcceptSudoMode(NetworkPacket* pkt) m_password = m_new_password; - verbosestream << "Client: Recieved TOCLIENT_ACCEPT_SUDO_MODE." << std::endl; + verbosestream << "Client: Received TOCLIENT_ACCEPT_SUDO_MODE." << std::endl; // send packet to actually set the password startAuth(AUTH_MECHANISM_FIRST_SRP); diff --git a/src/script/cpp_api/s_mainmenu.h b/src/script/cpp_api/s_mainmenu.h index aef36ce39..470577a29 100644 --- a/src/script/cpp_api/s_mainmenu.h +++ b/src/script/cpp_api/s_mainmenu.h @@ -38,7 +38,7 @@ public: void handleMainMenuEvent(std::string text); /** - * process field data recieved from formspec + * process field data received from formspec * @param fields data in field format */ void handleMainMenuButtons(const StringMap &fields); -- cgit v1.2.3