diff options
author | Elias Fleckenstein <eliasfleckenstein@web.de> | 2022-05-17 22:12:00 +0200 |
---|---|---|
committer | Elias Fleckenstein <eliasfleckenstein@web.de> | 2022-05-17 22:12:00 +0200 |
commit | 21df26984da91143c15587f5a03c98d68c3adc4e (patch) | |
tree | aaa707a628ad331f67890023dffe1b4f60dd01d3 /src | |
parent | b09fc5de5cdb021f43ad32b7e3f50dc75c0bc622 (diff) | |
parent | eabf05758e3ba5f6f4bb1b8d1d1f02179b84e410 (diff) | |
download | dragonfireclient-21df26984da91143c15587f5a03c98d68c3adc4e.tar.xz |
Merge branch 'master' of https://github.com/minetest/minetest
Diffstat (limited to 'src')
279 files changed, 9695 insertions, 6529 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index dc2072d11..7bba68a64 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -98,34 +98,17 @@ if(BUILD_CLIENT AND ENABLE_SOUND) endif() endif() - -option(ENABLE_GLES "Use OpenGL ES instead of OpenGL" FALSE) +# TODO: this should be removed one day, we can enable it unconditionally +option(ENABLE_GLES "Enable extra support code for OpenGL ES" FALSE) mark_as_advanced(ENABLE_GLES) -if(BUILD_CLIENT) - # transitive dependency from Irrlicht (see longer explanation below) - if(NOT WIN32) - if(ENABLE_GLES) - find_package(OpenGLES2 REQUIRED) - else() - set(OPENGL_GL_PREFERENCE "LEGACY" CACHE STRING - "See CMake Policy CMP0072 for reference. GLVND is broken on some nvidia setups") - set(OpenGL_GL_PREFERENCE ${OPENGL_GL_PREFERENCE}) - find_package(OpenGL REQUIRED) - endif() - endif() +option(ENABLE_TOUCH "Enable Touchscreen support" FALSE) +if(ENABLE_TOUCH) + add_definitions(-DHAVE_TOUCHSCREENGUI) endif() - -option(ENABLE_FREETYPE "Enable FreeType2 (TrueType fonts and basic unicode support)" TRUE) -set(USE_FREETYPE FALSE) - -if(BUILD_CLIENT AND ENABLE_FREETYPE) - find_package(Freetype) - if(FREETYPE_FOUND) - message(STATUS "Freetype enabled.") - set(USE_FREETYPE TRUE) - endif() +if(BUILD_CLIENT) + find_package(Freetype REQUIRED) endif() option(ENABLE_CURSES "Enable ncurses console" TRUE) @@ -270,12 +253,14 @@ if(WIN32) else() # Probably MinGW = GCC set(PLATFORM_LIBS "") endif() - set(PLATFORM_LIBS ws2_32.lib version.lib shlwapi.lib ${PLATFORM_LIBS}) + set(PLATFORM_LIBS ws2_32.lib version.lib shlwapi.lib winmm.lib ${PLATFORM_LIBS}) + + set(EXTRA_DLL "" CACHE FILEPATH "Optional paths to additional DLLs that should be packaged") # 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 "" DOC "Path to Zlib DLL for installation (optional)") - find_file(ZSTD_DLL NAMES "" DOC "Path to Zstd DLL for installation (optional)") + set(ZLIB_DLL "" CACHE FILEPATH "Path to Zlib DLL for installation (optional)") + set(ZSTD_DLL "" CACHE FILEPATH "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)") @@ -288,7 +273,6 @@ if(WIN32) set(LUA_DLL "" CACHE FILEPATH "Path to luajit-5.1.dll for installation (optional)") endif() endif() - else() # Unix probably if(BUILD_CLIENT) @@ -346,6 +330,7 @@ add_subdirectory(mapgen) add_subdirectory(network) add_subdirectory(script) add_subdirectory(unittest) +add_subdirectory(benchmark) add_subdirectory(util) add_subdirectory(irrlicht_changes) add_subdirectory(server) @@ -428,6 +413,9 @@ if(BUILD_UNITTESTS) set(common_SRCS ${common_SRCS} ${UNITTEST_SRCS}) endif() +if(BUILD_BENCHMARKS) + set(common_SRCS ${common_SRCS} ${BENCHMARK_SRCS}) +endif() # This gives us the icon and file version information if(WIN32) @@ -468,6 +456,10 @@ if(BUILD_UNITTESTS) set(client_SRCS ${client_SRCS} ${UNITTEST_CLIENT_SRCS}) endif() +if(BUILD_BENCHMARKS) + set(client_SRCS ${client_SRCS} ${BENCHMARK_CLIENT_SRCS}) +endif() + list(SORT client_SRCS) # Server sources @@ -486,23 +478,28 @@ endif() include_directories( ${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR} + ${PROJECT_SOURCE_DIR}/script +) +include_directories(SYSTEM ${ZLIB_INCLUDE_DIR} ${ZSTD_INCLUDE_DIR} - ${SOUND_INCLUDE_DIRS} ${SQLITE3_INCLUDE_DIR} ${LUA_INCLUDE_DIR} ${GMP_INCLUDE_DIR} ${JSON_INCLUDE_DIR} - ${X11_INCLUDE_DIR} - ${PROJECT_SOURCE_DIR}/script + ${LUA_BIT_INCLUDE_DIR} ) if(USE_GETTEXT) include_directories(${GETTEXT_INCLUDE_DIR}) endif() -if(USE_FREETYPE) - include_directories(${FREETYPE_INCLUDE_DIRS}) +if(BUILD_CLIENT) + include_directories(SYSTEM + ${FREETYPE_INCLUDE_DIRS} + ${SOUND_INCLUDE_DIRS} + ${X11_INCLUDE_DIR} + ) endif() if(USE_CURL) @@ -530,6 +527,8 @@ if(BUILD_CLIENT) ${LUA_LIBRARY} ${GMP_LIBRARY} ${JSON_LIBRARY} + ${LUA_BIT_LIBRARY} + ${FREETYPE_LIBRARY} ${PLATFORM_LIBS} ) if(NOT USE_LUAJIT) @@ -540,18 +539,6 @@ if(BUILD_CLIENT) ) endif() - if(ENABLE_GLES) - target_link_libraries( - ${PROJECT_NAME} - ${OPENGLES2_LIBRARIES} - ${EGL_LIBRARIES} - ) - else() - target_link_libraries( - ${PROJECT_NAME} - ${OPENGL_LIBRARIES} - ) - endif() if(USE_GETTEXT) target_link_libraries( ${PROJECT_NAME} @@ -564,17 +551,11 @@ if(BUILD_CLIENT) ${CURL_LIBRARY} ) endif() - if(USE_FREETYPE) - if(FREETYPE_PKGCONFIG_FOUND) - set_target_properties(${PROJECT_NAME} - PROPERTIES - COMPILE_FLAGS "${FREETYPE_CFLAGS_STR}" - ) - endif() - target_link_libraries( - ${PROJECT_NAME} - ${FREETYPE_LIBRARY} - ) + if(FREETYPE_PKGCONFIG_FOUND) + set_target_properties(${PROJECT_NAME} + PROPERTIES + COMPILE_FLAGS "${FREETYPE_CFLAGS_STR}" + ) endif() if (USE_CURSES) target_link_libraries(${PROJECT_NAME} ${CURSES_LIBRARIES}) @@ -594,6 +575,9 @@ if(BUILD_CLIENT) if (USE_SPATIAL) target_link_libraries(${PROJECT_NAME} ${SPATIAL_LIBRARY}) endif() + if(BUILD_BENCHMARKS) + target_link_libraries(${PROJECT_NAME} catch2) + endif() endif(BUILD_CLIENT) @@ -612,6 +596,7 @@ if(BUILD_SERVER) ${SQLITE3_LIBRARY} ${JSON_LIBRARY} ${LUA_LIBRARY} + ${LUA_BIT_LIBRARY} ${GMP_LIBRARY} ${PLATFORM_LIBS} ) @@ -652,6 +637,9 @@ if(BUILD_SERVER) ${CURL_LIBRARY} ) endif() + if(BUILD_BENCHMARKS) + target_link_libraries(${PROJECT_NAME}server catch2) + endif() endif(BUILD_SERVER) # Blacklisted locales that don't work. @@ -719,15 +707,13 @@ if(MSVC) endif() else() # GCC or compatible compilers such as Clang - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + set(WARNING_FLAGS "-Wall -Wextra") + set(WARNING_FLAGS "${WARNING_FLAGS} -Wno-unused-parameter -Wno-implicit-fallthrough") if(WARN_ALL) - set(RELEASE_WARNING_FLAGS "-Wall") + set(RELEASE_WARNING_FLAGS "${WARNING_FLAGS}") else() set(RELEASE_WARNING_FLAGS "") endif() - if(CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?Clang") - set(WARNING_FLAGS "${WARNING_FLAGS} -Wsign-compare") - endif() if(APPLE AND USE_LUAJIT) # required per http://luajit.org/install.html @@ -738,7 +724,7 @@ else() # Move text segment below LuaJIT's 47-bit limit (see issue #9367) if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD") # FreeBSD uses lld, and lld does not support -Ttext-segment, suggesting - # --image-base instead. Not sure if it's equivalent change for the purpose + # --image-base instead. Not sure if it's equivalent change for the purpose # but at least if fixes build on FreeBSD/aarch64 # XXX: the condition should also be changed to check for lld regardless of # os, bit CMake doesn't have anything like CMAKE_LINKER_IS_LLD yet @@ -759,7 +745,17 @@ else() # - we don't deal with Inf/NaN or signed zero set(MATH_FLAGS "-fno-math-errno -fno-trapping-math -ffinite-math-only -fno-signed-zeros") - set(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG ${RELEASE_WARNING_FLAGS} ${WARNING_FLAGS} ${OTHER_FLAGS} -Wall -pipe -funroll-loops") + # Enable SSE for floating point math on 32-bit x86 by default + # reasoning see minetest issue #11810 and https://gcc.gnu.org/wiki/FloatingPointMath + if(CMAKE_SIZEOF_VOID_P EQUAL 4) + check_c_source_compiles("#ifndef __i686__\n#error\n#endif\nint main(){}" IS_I686) + if(IS_I686) + message(STATUS "Detected Intel x86: using SSE instead of x87 FPU") + set(OTHER_FLAGS "${OTHER_FLAGS} -mfpmath=sse -msse") + endif() + endif() + + set(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG ${RELEASE_WARNING_FLAGS} ${OTHER_FLAGS} -pipe -funroll-loops") if(CMAKE_SYSTEM_NAME MATCHES "(Darwin|BSD|DragonFly)") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Os") else() @@ -772,8 +768,9 @@ else() set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${MATH_FLAGS}") endif() endif() - set(CMAKE_CXX_FLAGS_SEMIDEBUG "-g -O1 -Wall ${WARNING_FLAGS} ${OTHER_FLAGS}") - set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -Wall ${WARNING_FLAGS} ${OTHER_FLAGS}") + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELEASE} -g") + set(CMAKE_CXX_FLAGS_SEMIDEBUG "-g -O1 ${WARNING_FLAGS} ${OTHER_FLAGS}") + set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 ${WARNING_FLAGS} ${OTHER_FLAGS}") if(USE_GPROF) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -pg") @@ -788,6 +785,9 @@ endif() # Installation if(WIN32) + if(EXTRA_DLL) + install(FILES ${EXTRA_DLL} DESTINATION ${BINDIR}) + endif() if(VCPKG_APPLOCAL_DEPS) # Collect the dll's from the output path install(DIRECTORY ${EXECUTABLE_OUTPUT_PATH}/Release/ @@ -840,13 +840,14 @@ if(WIN32) if(LUA_DLL) install(FILES ${LUA_DLL} DESTINATION ${BINDIR}) endif() - if(BUILD_CLIENT AND IRRLICHT_DLL) - install(FILES ${IRRLICHT_DLL} DESTINATION ${BINDIR}) - endif() if(BUILD_CLIENT AND USE_GETTEXT AND GETTEXT_DLL) install(FILES ${GETTEXT_DLL} DESTINATION ${BINDIR}) endif() endif() + + if(BUILD_CLIENT AND IRRLICHT_DLL) + install(FILES ${IRRLICHT_DLL} DESTINATION ${BINDIR}) + endif() endif() if(BUILD_CLIENT) @@ -873,14 +874,8 @@ if(BUILD_CLIENT) endforeach() endif() - # Install necessary fonts depending on configuration - if(USE_FREETYPE) - install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../fonts" DESTINATION "${SHAREDIR}" - FILES_MATCHING PATTERN "*.ttf" PATTERN "*.txt") - else() - install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../fonts" DESTINATION "${SHAREDIR}" - FILES_MATCHING PATTERN "*.png" PATTERN "*.xml") - endif() + install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../fonts" DESTINATION "${SHAREDIR}" + FILES_MATCHING PATTERN "*.ttf" PATTERN "*.txt") endif(BUILD_CLIENT) if(BUILD_SERVER) diff --git a/src/benchmark/CMakeLists.txt b/src/benchmark/CMakeLists.txt new file mode 100644 index 000000000..5feba345b --- /dev/null +++ b/src/benchmark/CMakeLists.txt @@ -0,0 +1,7 @@ +set (BENCHMARK_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/benchmark.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/benchmark_serialize.cpp + PARENT_SCOPE) + +set (BENCHMARK_CLIENT_SRCS + PARENT_SCOPE) diff --git a/src/unittest/test_player.cpp b/src/benchmark/benchmark.cpp index 6990b4016..0bc2af368 100644 --- a/src/unittest/test_player.cpp +++ b/src/benchmark/benchmark.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2016 nerzhul, Loic Blot <loic.blot@unix-experience.fr> +Copyright (C) 2022 Minetest Authors 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 @@ -17,23 +17,16 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "test.h" +#include "benchmark/benchmark.h" -#include "exceptions.h" -#include "remoteplayer.h" -#include "server.h" +// This must be set in just this file +#define CATCH_CONFIG_RUNNER +#include "benchmark_setup.h" -class TestPlayer : public TestBase -{ -public: - TestPlayer() { TestManager::registerTestModule(this); } - const char *getName() { return "TestPlayer"; } - - void runTests(IGameDef *gamedef); -}; - -static TestPlayer g_test_instance; - -void TestPlayer::runTests(IGameDef *gamedef) +int run_benchmarks() { + int argc = 1; + const char *argv[] = { "MinetestBenchmark", NULL }; + int errCount = Catch::Session().run(argc, argv); + return errCount ? 1 : 0; } diff --git a/src/benchmark/benchmark.h b/src/benchmark/benchmark.h new file mode 100644 index 000000000..45dd9b6a4 --- /dev/null +++ b/src/benchmark/benchmark.h @@ -0,0 +1,26 @@ +/* +Minetest +Copyright (C) 2022 Minetest Authors + +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 "config.h" + +#if BUILD_BENCHMARKS +extern int run_benchmarks(); +#endif diff --git a/src/benchmark/benchmark_serialize.cpp b/src/benchmark/benchmark_serialize.cpp new file mode 100644 index 000000000..97cc7d59d --- /dev/null +++ b/src/benchmark/benchmark_serialize.cpp @@ -0,0 +1,71 @@ +/* +Minetest +Copyright (C) 2022 Minetest Authors + +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 "benchmark_setup.h" +#include "util/serialize.h" +#include <sstream> +#include <ios> + +// Builds a string of exactly `length` characters by repeating `s` (rest cut off) +static std::string makeRepeatTo(const std::string &s, size_t length) +{ + std::string v; + v.reserve(length + s.size()); + for (size_t i = 0; i < length; i += s.size()) { + v += s; + } + v.resize(length); + return v; +} + +#define BENCH3(_label, _chars, _length, _lengthlabel) \ + BENCHMARK_ADVANCED("serializeJsonStringIfNeeded_" _lengthlabel "_" _label)(Catch::Benchmark::Chronometer meter) { \ + std::string s = makeRepeatTo(_chars, _length); \ + meter.measure([&] { return serializeJsonStringIfNeeded(s); }); \ + }; \ + BENCHMARK_ADVANCED("deSerializeJsonStringIfNeeded_" _lengthlabel "_" _label)(Catch::Benchmark::Chronometer meter) { \ + std::string s = makeRepeatTo(_chars, _length); \ + std::string serialized = serializeJsonStringIfNeeded(s); \ + std::istringstream is(serialized, std::ios::binary); \ + meter.measure([&] { \ + is.clear(); \ + is.seekg(0, std::ios::beg); \ + return deSerializeJsonStringIfNeeded(is); \ + }); \ + }; + +/* Both with and without a space character (' ') */ +#define BENCH2(_label, _chars, _length, _lengthlabel) \ + BENCH3(_label, _chars, _length, _lengthlabel) \ + BENCH3(_label "_with_space", " " _chars, _length, _lengthlabel) \ + +/* Iterate over input lengths */ +#define BENCH1(_label, _chars) \ + BENCH2(_label, _chars, 10, "small") \ + BENCH2(_label, _chars, 10000, "large") + +/* Iterate over character sets */ +#define BENCH_ALL() \ + BENCH1("alpha", "abcdefghijklmnopqrstuvwxyz") \ + BENCH1("escaped", "\"\\/\b\f\n\r\t") \ + BENCH1("nonascii", "\xf0\xff") + +TEST_CASE("benchmark_serialize") { + BENCH_ALL() +} diff --git a/src/benchmark/benchmark_setup.h b/src/benchmark/benchmark_setup.h new file mode 100644 index 000000000..34a4eca4c --- /dev/null +++ b/src/benchmark/benchmark_setup.h @@ -0,0 +1,22 @@ +/* +Minetest +Copyright (C) 2022 Minetest Authors + +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. +*/ + +#define CATCH_CONFIG_ENABLE_BENCHMARKING +#define CATCH_CONFIG_CONSOLE_WIDTH 160 +#include <catch.hpp> diff --git a/src/chat.cpp b/src/chat.cpp index 162622abe..92df038e8 100644 --- a/src/chat.cpp +++ b/src/chat.cpp @@ -50,6 +50,8 @@ ChatBuffer::ChatBuffer(u32 scrollback): void ChatBuffer::addLine(const std::wstring &name, const std::wstring &text) { + m_lines_modified = true; + ChatLine line(name, text); m_unformatted.push_back(line); @@ -72,6 +74,7 @@ void ChatBuffer::clear() m_unformatted.clear(); m_formatted.clear(); m_scroll = 0; + m_lines_modified = true; } u32 ChatBuffer::getLineCount() const @@ -99,14 +102,11 @@ void ChatBuffer::deleteOldest(u32 count) u32 del_unformatted = 0; u32 del_formatted = 0; - while (count > 0 && del_unformatted < m_unformatted.size()) - { + while (count > 0 && del_unformatted < m_unformatted.size()) { ++del_unformatted; // keep m_formatted in sync - if (del_formatted < m_formatted.size()) - { - + if (del_formatted < m_formatted.size()) { sanity_check(m_formatted[del_formatted].first); ++del_formatted; while (del_formatted < m_formatted.size() && @@ -120,6 +120,9 @@ void ChatBuffer::deleteOldest(u32 count) m_unformatted.erase(m_unformatted.begin(), m_unformatted.begin() + del_unformatted); m_formatted.erase(m_formatted.begin(), m_formatted.begin() + del_formatted); + if (del_unformatted > 0) + m_lines_modified = true; + if (at_bottom) m_scroll = getBottomScrollPos(); else @@ -139,11 +142,6 @@ u32 ChatBuffer::getRows() const return m_rows; } -void ChatBuffer::scrollTop() -{ - m_scroll = getTopScrollPos(); -} - void ChatBuffer::reformat(u32 cols, u32 rows) { if (cols == 0 || rows == 0) diff --git a/src/chat.h b/src/chat.h index aabb0821e..fc080f64b 100644 --- a/src/chat.h +++ b/src/chat.h @@ -110,8 +110,13 @@ public: void scrollAbsolute(s32 scroll); // Scroll to bottom of buffer (newest) void scrollBottom(); - // Scroll to top of buffer (oldest) - void scrollTop(); + + // Functions for keeping track of whether the lines were modified by any + // preceding operations + // If they were not changed, getLineCount() and getLine() output the same as + // before + bool getLinesModified() const { return m_lines_modified; } + void resetLinesModified() { m_lines_modified = false; } // Format a chat line for the given number of columns. // Appends the formatted lines to the destination array and @@ -146,6 +151,11 @@ private: bool m_cache_clickable_chat_weblinks; // Color of clickable chat weblinks irr::video::SColor m_cache_chat_weblink_color; + + // Whether the lines were modified since last markLinesUnchanged() + // Is always set to true when m_unformatted is modified, because that's what + // determines the output of getLineCount() and getLine() + bool m_lines_modified = true; }; class ChatPrompt diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt index 8d058852a..656ad45ce 100644 --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -60,7 +60,7 @@ set(client_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/wieldmesh.cpp ${CMAKE_CURRENT_SOURCE_DIR}/shadows/dynamicshadows.cpp ${CMAKE_CURRENT_SOURCE_DIR}/shadows/dynamicshadowsrender.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/shadows/shadowsshadercallbacks.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/shadows/shadowsScreenQuad.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/shadows/shadowsshadercallbacks.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/shadows/shadowsScreenQuad.cpp PARENT_SCOPE ) diff --git a/src/client/camera.cpp b/src/client/camera.cpp index 52bedcba9..0c387262e 100644 --- a/src/client/camera.cpp +++ b/src/client/camera.cpp @@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "player.h" #include <cmath> #include "client/renderingengine.h" +#include "client/content_cao.h" #include "settings.h" #include "wieldmesh.h" #include "noise.h" // easeCurve @@ -37,6 +38,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "fontengine.h" #include "guiscalingfilter.h" #include "script/scripting_client.h" +#include "gettext.h" #define CAMERA_OFFSET_STEP 200 #define WIELDMESH_OFFSET_X 55.0f @@ -133,28 +135,6 @@ void Camera::notifyFovChange() } } -bool Camera::successfullyCreated(std::string &error_message) -{ - if (!m_playernode) { - error_message = "Failed to create the player scene node"; - } else if (!m_headnode) { - error_message = "Failed to create the head scene node"; - } else if (!m_cameranode) { - error_message = "Failed to create the camera scene node"; - } else if (!m_wieldmgr) { - error_message = "Failed to create the wielded item scene manager"; - } else if (!m_wieldnode) { - error_message = "Failed to create the wielded item scene node"; - } else { - error_message.clear(); - } - - if (m_client->modsLoaded()) - m_client->getScript()->on_camera_ready(this); - - return error_message.empty(); -} - // Returns the fractional part of x inline f32 my_modf(f32 x) { @@ -187,9 +167,7 @@ void Camera::step(f32 dtime) m_view_bobbing_anim -= offset; } else if (m_view_bobbing_anim > 0.75) { m_view_bobbing_anim += offset; - } - - if (m_view_bobbing_anim < 0.5) { + } else if (m_view_bobbing_anim < 0.5) { m_view_bobbing_anim += offset; if (m_view_bobbing_anim > 0.5) m_view_bobbing_anim = 0.5; @@ -331,7 +309,7 @@ void Camera::addArmInertia(f32 player_yaw) } } -void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_reload_ratio) +void Camera::update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio) { // Get player position // Smooth the movement when walking up stairs @@ -344,13 +322,16 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r if (player->getParent()) player_position = player->getParent()->getPosition() + v3f(0, g_settings->getBool("float_above_parent") ? BS : 0, 0); - // Smooth the camera movement when the player instantly moves upward due to stepheight. - // To smooth the 'not touching_ground' stepheight, smoothing is necessary when jumping - // or swimming (for when moving from liquid to land). - // Disable smoothing if climbing or flying, to avoid upwards offset of player model - // when seen in 3rd person view. - bool flying = (g_settings->getBool("free_move") && m_client->checkLocalPrivilege("fly")) || g_settings->getBool("freecam"); - if (player_position.Y > old_player_position.Y && !player->is_climbing && !flying) { + // Smooth the camera movement after the player instantly moves upward due to stepheight. + // The smoothing usually continues until the camera position reaches the player position. + float player_stepheight = player->getCAO() ? player->getCAO()->getStepHeight() : HUGE_VALF; + float upward_movement = player_position.Y - old_player_position.Y; + if (upward_movement < 0.01f || upward_movement > player_stepheight) { + m_stepheight_smooth_active = false; + } else if (player->touching_ground) { + m_stepheight_smooth_active = true; + } + if (m_stepheight_smooth_active) { f32 oldy = old_player_position.Y; f32 newy = player_position.Y; f32 t = std::exp(-23 * frametime); @@ -379,7 +360,8 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r // Smoothen and invert the above fall_bobbing = sin(fall_bobbing * 0.5 * M_PI) * -1; // Amplify according to the intensity of the impact - fall_bobbing *= (1 - rangelim(50 / player->camera_impact, 0, 1)) * 5; + if (player->camera_impact > 0.0f) + fall_bobbing *= (1 - rangelim(50 / player->camera_impact, 0, 1)) * 5; fall_bobbing *= m_cache_fall_bobbing_amount; } @@ -410,41 +392,17 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r f32 bobfrac = my_modf(m_view_bobbing_anim * 2); f32 bobdir = (m_view_bobbing_anim < 0.5) ? 1.0 : -1.0; - #if 1 f32 bobknob = 1.2; f32 bobtmp = sin(pow(bobfrac, bobknob) * M_PI); - //f32 bobtmp2 = cos(pow(bobfrac, bobknob) * M_PI); v3f bobvec = v3f( 0.3 * bobdir * sin(bobfrac * M_PI), -0.28 * bobtmp * bobtmp, 0.); - //rel_cam_pos += 0.2 * bobvec; - //rel_cam_target += 0.03 * bobvec; - //rel_cam_up.rotateXYBy(0.02 * bobdir * bobtmp * M_PI); - float f = 1.0; - f *= m_cache_view_bobbing_amount; - rel_cam_pos += bobvec * f; - //rel_cam_target += 0.995 * bobvec * f; - rel_cam_target += bobvec * f; - rel_cam_target.Z -= 0.005 * bobvec.Z * f; - //rel_cam_target.X -= 0.005 * bobvec.X * f; - //rel_cam_target.Y -= 0.005 * bobvec.Y * f; - rel_cam_up.rotateXYBy(-0.03 * bobdir * bobtmp * M_PI * f); - #else - f32 angle_deg = 1 * bobdir * sin(bobfrac * M_PI); - f32 angle_rad = angle_deg * M_PI / 180; - f32 r = 0.05; - v3f off = v3f( - r * sin(angle_rad), - r * (cos(angle_rad) - 1), - 0); - rel_cam_pos += off; - //rel_cam_target += off; - rel_cam_up.rotateXYBy(angle_deg); - #endif - + rel_cam_pos += bobvec * m_cache_view_bobbing_amount; + rel_cam_target += bobvec * m_cache_view_bobbing_amount; + rel_cam_up.rotateXYBy(-0.03 * bobdir * bobtmp * M_PI * m_cache_view_bobbing_amount); } // Compute absolute camera position and target @@ -613,6 +571,8 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r const bool walking = movement_XZ && player->touching_ground; const bool swimming = (movement_XZ || player->swimming_vertical) && player->in_liquid; const bool climbing = movement_Y && player->is_climbing; + const bool flying = g_settings->getBool("free_move") + && m_client->checkLocalPrivilege("fly"); if ((walking || swimming || climbing) && !flying) { // Start animation m_view_bobbing_state = 1; diff --git a/src/client/camera.h b/src/client/camera.h index 30fac5289..ecd71f1e7 100644 --- a/src/client/camera.h +++ b/src/client/camera.h @@ -162,16 +162,11 @@ public: // Notify about new server-sent FOV and initialize smooth FOV transition void notifyFovChange(); - // Checks if the constructor was able to create the scene nodes - bool successfullyCreated(std::string &error_message); - // Step the camera: updates the viewing range and view bobbing. void step(f32 dtime); // Update the camera from the local player's position. - // busytime is used to adjust the viewing range. - void update(LocalPlayer* player, f32 frametime, f32 busytime, - f32 tool_reload_ratio); + void update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio); // Update render distance void updateViewingRange(); @@ -245,6 +240,8 @@ private: // Camera offset v3s16 m_camera_offset; + bool m_stepheight_smooth_active = false; + // Server-sent FOV variables bool m_server_sent_fov = false; f32 m_curr_fov_degrees, m_old_fov_degrees, m_target_fov_degrees; diff --git a/src/client/client.cpp b/src/client/client.cpp index 3c4ea5f95..4e4bb8a97 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -51,6 +51,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "clientmap.h" #include "clientmedia.h" #include "version.h" +#include "database/database-files.h" #include "database/database-sqlite3.h" #include "serialization.h" #include "guiscalingfilter.h" @@ -129,6 +130,11 @@ Client::Client( // Add local player m_env.setLocalPlayer(new LocalPlayer(this, playername)); + // Make the mod storage database and begin the save for later + m_mod_storage_database = + new ModMetadataDatabaseSQLite3(porting::path_user + DIR_DELIM + "client"); + m_mod_storage_database->beginSave(); + if (g_settings->getBool("enable_minimap")) { m_minimap = new Minimap(this); } @@ -136,6 +142,33 @@ Client::Client( m_cache_save_interval = g_settings->getU16("server_map_save_interval"); } +void Client::migrateModStorage() +{ + std::string mod_storage_dir = porting::path_user + DIR_DELIM + "client"; + std::string old_mod_storage = mod_storage_dir + DIR_DELIM + "mod_storage"; + if (fs::IsDir(old_mod_storage)) { + infostream << "Migrating client mod storage to SQLite3 database" << std::endl; + { + ModMetadataDatabaseFiles files_db(mod_storage_dir); + std::vector<std::string> mod_list; + files_db.listMods(&mod_list); + for (const std::string &modname : mod_list) { + infostream << "Migrating client mod storage for mod " << modname << std::endl; + StringMap meta; + files_db.getModEntries(modname, &meta); + for (const auto &pair : meta) { + m_mod_storage_database->setModEntry(modname, pair.first, pair.second); + } + } + } + if (!fs::Rename(old_mod_storage, old_mod_storage + ".bak")) { + // Execution cannot move forward if the migration does not complete. + throw BaseException("Could not finish migrating client mod storage"); + } + infostream << "Finished migration of client mod storage" << std::endl; + } +} + void Client::loadMods() { // Don't load mods twice. @@ -304,10 +337,17 @@ Client::~Client() // cleanup 3d model meshes on client shutdown m_rendering_engine->cleanupMeshCache(); + guiScalingCacheClear(); + delete m_minimap; m_minimap = nullptr; delete m_media_downloader; + + // Write the changes and delete + if (m_mod_storage_database) + m_mod_storage_database->endSave(); + delete m_mod_storage_database; } void Client::connect(Address address, bool is_local_server) @@ -644,19 +684,12 @@ void Client::step(float dtime) } } + // Write changes to the mod storage m_mod_storage_save_timer -= dtime; if (m_mod_storage_save_timer <= 0.0f) { m_mod_storage_save_timer = g_settings->getFloat("server_map_save_interval"); - int n = 0; - for (std::unordered_map<std::string, ModMetadata *>::const_iterator - it = m_mod_storages.begin(); it != m_mod_storages.end(); ++it) { - if (it->second->isModified()) { - it->second->save(getModStoragePath()); - n++; - } - } - if (n > 0) - infostream << "Saved " << n << " modified mod storages." << std::endl; + m_mod_storage_database->endSave(); + m_mod_storage_database->beginSave(); } // Write server map @@ -880,7 +913,7 @@ void Client::ProcessData(NetworkPacket *pkt) */ if(sender_peer_id != PEER_ID_SERVER) { infostream << "Client::ProcessData(): Discarding data not " - "coming from server: peer_id=" << sender_peer_id + "coming from server: peer_id=" << sender_peer_id << " command=" << pkt->getCommand() << std::endl; return; } @@ -931,7 +964,7 @@ void writePlayerPos(LocalPlayer *myplayer, ClientMap *clientMap, NetworkPacket * v3f sf = myplayer->getSendSpeed() * 100; s32 pitch = myplayer->getPitch() * 100; s32 yaw = myplayer->getYaw() * 100; - u32 keyPressed = myplayer->keyPressed; + u32 keyPressed = myplayer->control.getKeysPressed(); // scaled by 80, so that pi can fit into a u8 u8 fov = clientMap->getCameraFov() * 80; u8 wanted_range = MYMIN(255, @@ -1287,22 +1320,24 @@ void Client::sendPlayerPos(v3f pos) if (!player) return; - ClientMap &map = m_env.getClientMap(); - u8 camera_fov = map.getCameraFov(); - u8 wanted_range = map.getControl().wanted_range; - // Save bandwidth by only updating position when // player is not dead and something changed if (m_activeobjects_received && player->isDead()) return; + ClientMap &map = m_env.getClientMap(); + u8 camera_fov = map.getCameraFov(); + u8 wanted_range = map.getControl().wanted_range; + + u32 keyPressed = player->control.getKeysPressed(); + if ( player->last_position == pos && player->last_speed == player->getSendSpeed() && player->last_pitch == player->getPitch() && player->last_yaw == player->getYaw() && - player->last_keyPressed == player->keyPressed && + player->last_keyPressed == keyPressed && player->last_camera_fov == camera_fov && player->last_wanted_range == wanted_range) return; @@ -1311,7 +1346,7 @@ void Client::sendPlayerPos(v3f pos) player->last_speed = player->getSendSpeed(); player->last_pitch = player->getPitch(); player->last_yaw = player->getYaw(); - player->last_keyPressed = player->keyPressed; + player->last_keyPressed = keyPressed; player->last_camera_fov = camera_fov; player->last_wanted_range = wanted_range; @@ -1614,20 +1649,7 @@ void Client::addUpdateMeshTask(v3s16 p, bool ack_to_server, bool urgent) void Client::addUpdateMeshTaskWithEdge(v3s16 blockpos, bool ack_to_server, bool urgent) { - try{ - addUpdateMeshTask(blockpos, ack_to_server, urgent); - } - catch(InvalidPositionException &e){} - - // Leading edge - for (int i=0;i<6;i++) - { - try{ - v3s16 p = blockpos + g_6dirs[i]; - addUpdateMeshTask(p, false, urgent); - } - catch(InvalidPositionException &e){} - } + m_mesh_update_thread.updateBlock(&m_env.getMap(), blockpos, ack_to_server, urgent, true); } void Client::addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server, bool urgent) @@ -1639,38 +1661,16 @@ void Client::addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server, bool ur <<std::endl; } - v3s16 blockpos = getNodeBlockPos(nodepos); + v3s16 blockpos = getNodeBlockPos(nodepos); v3s16 blockpos_relative = blockpos * MAP_BLOCKSIZE; - - try{ - addUpdateMeshTask(blockpos, ack_to_server, urgent); - } - catch(InvalidPositionException &e) {} - + m_mesh_update_thread.updateBlock(&m_env.getMap(), blockpos, ack_to_server, urgent, false); // Leading edge - if(nodepos.X == blockpos_relative.X){ - try{ - v3s16 p = blockpos + v3s16(-1,0,0); - addUpdateMeshTask(p, false, urgent); - } - catch(InvalidPositionException &e){} - } - - if(nodepos.Y == blockpos_relative.Y){ - try{ - v3s16 p = blockpos + v3s16(0,-1,0); - addUpdateMeshTask(p, false, urgent); - } - catch(InvalidPositionException &e){} - } - - if(nodepos.Z == blockpos_relative.Z){ - try{ - v3s16 p = blockpos + v3s16(0,0,-1); - addUpdateMeshTask(p, false, urgent); - } - catch(InvalidPositionException &e){} - } + if (nodepos.X == blockpos_relative.X) + addUpdateMeshTask(blockpos + v3s16(-1, 0, 0), false, urgent); + if (nodepos.Y == blockpos_relative.Y) + addUpdateMeshTask(blockpos + v3s16(0, -1, 0), false, urgent); + if (nodepos.Z == blockpos_relative.Z) + addUpdateMeshTask(blockpos + v3s16(0, 0, -1), false, urgent); } void Client::updateAllMapBlocks() @@ -1834,11 +1834,10 @@ void Client::makeScreenshot() if (!raw_image) return; - time_t t = time(NULL); - struct tm *tm = localtime(&t); + const struct tm tm = mt_localtime(); char timetstamp_c[64]; - strftime(timetstamp_c, sizeof(timetstamp_c), "%Y%m%d_%H%M%S", tm); + strftime(timetstamp_c, sizeof(timetstamp_c), "%Y%m%d_%H%M%S", &tm); std::string screenshot_dir; @@ -2025,16 +2024,8 @@ void Client::unregisterModStorage(const std::string &name) { std::unordered_map<std::string, ModMetadata *>::const_iterator it = m_mod_storages.find(name); - if (it != m_mod_storages.end()) { - // Save unconditionaly on unregistration - it->second->save(getModStoragePath()); + if (it != m_mod_storages.end()) m_mod_storages.erase(name); - } -} - -std::string Client::getModStoragePath() const -{ - return porting::path_user + DIR_DELIM + "client" + DIR_DELIM + "mod_storage"; } /* diff --git a/src/client/client.h b/src/client/client.h index 1493d3ce3..d49f2f9ad 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -226,6 +226,7 @@ public: void handleCommand_PlayerSpeed(NetworkPacket *pkt); void handleCommand_MediaPush(NetworkPacket *pkt); void handleCommand_MinimapModes(NetworkPacket *pkt); + void handleCommand_SetLighting(NetworkPacket *pkt); void ProcessData(NetworkPacket *pkt); @@ -335,13 +336,13 @@ public: // disconnect client when CSM failed. const std::string &accessDeniedReason() const { return m_access_denied_reason; } - const bool itemdefReceived() const + bool itemdefReceived() const { return m_itemdef_received; } - const bool nodedefReceived() const + bool nodedefReceived() const { return m_nodedef_received; } - const bool mediaReceived() const + bool mediaReceived() const { return !m_media_downloader; } - const bool activeObjectsReceived() const + bool activeObjectsReceived() const { return m_activeobjects_received; } u16 getProtoVersion() @@ -382,11 +383,14 @@ public: bool checkLocalPrivilege(const std::string &priv){ return checkPrivilege(priv); } virtual scene::IAnimatedMesh* getMesh(const std::string &filename, bool cache = false); const std::string* getModFile(std::string filename); + ModMetadataDatabase *getModStorageDatabase() override { return m_mod_storage_database; } - std::string getModStoragePath() const override; bool registerModStorage(ModMetadata *meta) override; void unregisterModStorage(const std::string &name) override; + // Migrates away old files-based mod storage if necessary + void migrateModStorage(); + // The following set of functions is used by ClientMediaDownloader // Insert a media file appropriately into the appropriate manager bool loadMedia(const std::string &data, const std::string &filename, @@ -405,7 +409,7 @@ public: } ClientScripting *getScript() { return m_script; } - const bool modsLoaded() const { return m_mods_loaded; } + bool modsLoaded() const { return m_mods_loaded; } void pushToEventQueue(ClientEvent *event); @@ -554,7 +558,7 @@ private: // Set of media filenames pushed by server at runtime std::unordered_set<std::string> m_media_pushed_files; // Pending downloads of dynamic media (key: token) - std::vector<std::pair<u32, std::unique_ptr<SingleMediaDownloader>>> m_pending_media_downloads; + std::vector<std::pair<u32, std::shared_ptr<SingleMediaDownloader>>> m_pending_media_downloads; // time_of_day speed approximation for old protocol bool m_time_of_day_set = false; @@ -596,6 +600,7 @@ private: // Client modding ClientScripting *m_script = nullptr; std::unordered_map<std::string, ModMetadata *> m_mod_storages; + ModMetadataDatabase *m_mod_storage_database = nullptr; float m_mod_storage_save_timer = 10.0f; std::vector<ModSpec> m_mods; StringMap m_mod_vfs; diff --git a/src/client/clientenvironment.cpp b/src/client/clientenvironment.cpp index f9c20b2df..01aaa0408 100644 --- a/src/client/clientenvironment.cpp +++ b/src/client/clientenvironment.cpp @@ -193,33 +193,41 @@ void ClientEnvironment::step(float dtime) // Control local player lplayer->applyControl(dtime_part, this); - // Apply physics - if (!free_move && !is_climbing && !g_settings->getBool("freecam")) { + if (!free_move && !g_settings->getBool("freecam")) { // Gravity v3f speed = lplayer->getSpeed(); - if (!lplayer->in_liquid) + if (!is_climbing && !lplayer->in_liquid) speed.Y -= lplayer->movement_gravity * lplayer->physics_override_gravity * dtime_part * 2.0f; // Liquid floating / sinking - if (lplayer->in_liquid && !lplayer->swimming_vertical && + if (!is_climbing && lplayer->in_liquid && + !lplayer->swimming_vertical && !lplayer->swimming_pitch) speed.Y -= lplayer->movement_liquid_sink * dtime_part * 2.0f; - // Liquid resistance - if (lplayer->in_liquid_stable || lplayer->in_liquid) { - // How much the node's viscosity blocks movement, ranges - // between 0 and 1. Should match the scale at which viscosity + // Movement resistance + if (lplayer->move_resistance > 0) { + // How much the node's move_resistance blocks movement, ranges + // between 0 and 1. Should match the scale at which liquid_viscosity // increase affects other liquid attributes. - static const f32 viscosity_factor = 0.3f; - - v3f d_wanted = -speed / lplayer->movement_liquid_fluidity; + static const f32 resistance_factor = 0.3f; + + v3f d_wanted; + bool in_liquid_stable = lplayer->in_liquid_stable || lplayer->in_liquid; + if (in_liquid_stable) { + d_wanted = -speed / lplayer->movement_liquid_fluidity; + } else { + d_wanted = -speed / BS; + } f32 dl = d_wanted.getLength(); - if (dl > lplayer->movement_liquid_fluidity_smooth) - dl = lplayer->movement_liquid_fluidity_smooth; + if (in_liquid_stable) { + if (dl > lplayer->movement_liquid_fluidity_smooth) + dl = lplayer->movement_liquid_fluidity_smooth; + } - dl *= (lplayer->liquid_viscosity * viscosity_factor) + - (1 - viscosity_factor); + dl *= (lplayer->move_resistance * resistance_factor) + + (1 - resistance_factor); v3f d = d_wanted.normalize() * (dl * dtime_part * 100.0f); speed += d; } diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp index 6ab610670..54c561d11 100644 --- a/src/client/clientlauncher.cpp +++ b/src/client/clientlauncher.cpp @@ -38,9 +38,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #if USE_SOUND #include "sound_openal.h" #endif -#ifdef __ANDROID__ - #include "porting.h" -#endif /* mainmenumanager.h */ @@ -73,10 +70,10 @@ static void dump_start_data(const GameStartData &data) ClientLauncher::~ClientLauncher() { - delete receiver; - delete input; + delete receiver; + delete g_fontengine; delete g_gamecallback; @@ -147,8 +144,8 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args) skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255, 0, 0, 0)); skin->setColor(gui::EGDC_HIGH_LIGHT, video::SColor(255, 70, 120, 50)); skin->setColor(gui::EGDC_HIGH_LIGHT_TEXT, video::SColor(255, 255, 255, 255)); -#ifdef __ANDROID__ - float density = porting::getDisplayDensity(); +#ifdef HAVE_TOUCHSCREENGUI + float density = RenderingEngine::getDisplayDensity(); skin->setSize(gui::EGDS_CHECK_BOX_WIDTH, (s32)(17.0f * density)); skin->setSize(gui::EGDS_SCROLLBAR_SIZE, (s32)(14.0f * density)); skin->setSize(gui::EGDS_WINDOW_BUTTON_WIDTH, (s32)(15.0f * density)); @@ -567,6 +564,8 @@ void ClientLauncher::speed_tests() // volatile to avoid some potential compiler optimisations volatile static s16 temp16; volatile static f32 tempf; + // Silence compiler warning + (void)temp16; static v3f tempv3f1; static v3f tempv3f2; static std::string tempstring; diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp index 4a4784f91..51f0f6896 100644 --- a/src/client/clientmap.cpp +++ b/src/client/clientmap.cpp @@ -73,7 +73,8 @@ ClientMap::ClientMap( rendering_engine->get_scene_manager(), id), m_client(client), m_rendering_engine(rendering_engine), - m_control(control) + m_control(control), + m_drawlist(MapBlockComparer(v3s16(0,0,0))) { /* @@ -96,9 +97,32 @@ ClientMap::ClientMap( m_cache_trilinear_filter = g_settings->getBool("trilinear_filter"); m_cache_bilinear_filter = g_settings->getBool("bilinear_filter"); m_cache_anistropic_filter = g_settings->getBool("anisotropic_filter"); + m_cache_transparency_sorting_distance = g_settings->getU16("transparency_sorting_distance"); } +void ClientMap::updateCamera(v3f pos, v3f dir, f32 fov, v3s16 offset) +{ + v3s16 previous_node = floatToInt(m_camera_position, BS) + m_camera_offset; + v3s16 previous_block = getContainerPos(previous_node, MAP_BLOCKSIZE); + + m_camera_position = pos; + m_camera_direction = dir; + m_camera_fov = fov; + m_camera_offset = offset; + + v3s16 current_node = floatToInt(m_camera_position, BS) + m_camera_offset; + v3s16 current_block = getContainerPos(current_node, MAP_BLOCKSIZE); + + // reorder the blocks when camera crosses block boundary + if (previous_block != current_block) + m_needs_update_drawlist = true; + + // reorder transparent meshes when camera crosses node boundary + if (previous_node != current_node) + m_needs_update_transparent_meshes = true; +} + MapSector * ClientMap::emergeSector(v2s16 p2d) { // Check that it doesn't exist already @@ -164,6 +188,8 @@ void ClientMap::updateDrawList() { ScopeProfiler sp(g_profiler, "CM::updateDrawList()", SPT_AVG); + m_needs_update_drawlist = false; + for (auto &i : m_drawlist) { MapBlock *block = i.second; block->refDrop(); @@ -178,6 +204,7 @@ void ClientMap::updateDrawList() const f32 camera_fov = m_camera_fov * 1.1f; 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); @@ -202,6 +229,8 @@ void ClientMap::updateDrawList() occlusion_culling_enabled = false; } + v3s16 camera_block = getContainerPos(cam_pos_nodes, MAP_BLOCKSIZE); + m_drawlist = std::map<v3s16, MapBlock*, MapBlockComparer>(MapBlockComparer(camera_block)); // Uncomment to debug occluded blocks in the wireframe mode // TODO: Include this as a flag for an extended debugging setting @@ -318,10 +347,18 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) //u32 mesh_animate_count_far = 0; /* + Update transparent meshes + */ + if (is_transparent_pass) + updateTransparentMeshBuffers(); + + /* Draw the selected MapBlocks */ - MeshBufListList drawbufs; + MeshBufListList grouped_buffers; + std::vector<DrawDescriptor> draw_order; + video::SMaterial previous_material; for (auto &i : m_drawlist) { v3s16 block_pos = i.first; @@ -356,7 +393,15 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) /* Get the meshbuffers of the block */ - { + if (is_transparent_pass) { + // In transparent pass, the mesh will give us + // the partial buffers in the correct order + for (auto &buffer : block->mesh->getTransparentBuffers()) + draw_order.emplace_back(block_pos, &buffer); + } + else { + // otherwise, group buffers across meshes + // using MeshBufListList MapBlockMesh *mapBlockMesh = block->mesh; assert(mapBlockMesh); @@ -370,69 +415,94 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) video::SMaterial& material = buf->getMaterial(); video::IMaterialRenderer* rnd = - driver->getMaterialRenderer(material.MaterialType); + driver->getMaterialRenderer(material.MaterialType); bool transparent = (rnd && rnd->isTransparent()); - if (transparent == is_transparent_pass) { + if (!transparent) { if (buf->getVertexCount() == 0) errorstream << "Block [" << analyze_block(block) - << "] contains an empty meshbuf" << std::endl; - - material.setFlag(video::EMF_TRILINEAR_FILTER, - m_cache_trilinear_filter); - material.setFlag(video::EMF_BILINEAR_FILTER, - m_cache_bilinear_filter); - material.setFlag(video::EMF_ANISOTROPIC_FILTER, - m_cache_anistropic_filter); - material.setFlag(video::EMF_WIREFRAME, - m_control.show_wireframe); - - drawbufs.add(buf, block_pos, layer); + << "] contains an empty meshbuf" << std::endl; + + grouped_buffers.add(buf, block_pos, layer); } } } } } + // Capture draw order for all solid meshes + for (auto &lists : grouped_buffers.lists) { + for (MeshBufList &list : lists) { + // iterate in reverse to draw closest blocks first + for (auto it = list.bufs.rbegin(); it != list.bufs.rend(); ++it) { + draw_order.emplace_back(it->first, it->second, it != list.bufs.rbegin()); + } + } + } + TimeTaker draw("Drawing mesh buffers"); core::matrix4 m; // Model matrix v3f offset = intToFloat(m_camera_offset, BS); + u32 material_swaps = 0; - // 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() > 2000) { - infostream << "ClientMap::renderMap(): Rendering took >2s, " << - "returning." << std::endl; - return; - } + // Render all mesh buffers in order + drawcall_count += draw_order.size(); + + for (auto &descriptor : draw_order) { + scene::IMeshBuffer *buf; + + if (descriptor.m_use_partial_buffer) { + descriptor.m_partial_buffer->beforeDraw(); + buf = descriptor.m_partial_buffer->getBuffer(); + } + else { + buf = descriptor.m_buffer; + } + + // Check and abort if the machine is swapping a lot + if (draw.getTimerTime() > 2000) { + infostream << "ClientMap::renderMap(): Rendering took >2s, " << + "returning." << std::endl; + return; + } + + if (!descriptor.m_reuse_material) { + auto &material = buf->getMaterial(); + + // Apply filter settings + material.setFlag(video::EMF_TRILINEAR_FILTER, + m_cache_trilinear_filter); + material.setFlag(video::EMF_BILINEAR_FILTER, + m_cache_bilinear_filter); + material.setFlag(video::EMF_ANISOTROPIC_FILTER, + m_cache_anistropic_filter); + material.setFlag(video::EMF_WIREFRAME, + m_control.show_wireframe); // 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]; + auto &layer = material.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; + // Do not enable filter on shadow texture to avoid visual artifacts + // with colored shadows. + // Filtering is done in shader code anyway + layer.TrilinearFilter = false; } + driver->setMaterial(material); + ++material_swaps; + } - driver->setMaterial(list.m); - - drawcall_count += list.bufs.size(); - for (auto &pair : list.bufs) { - scene::IMeshBuffer *buf = pair.second; - - v3f block_wpos = intToFloat(pair.first * MAP_BLOCKSIZE, BS); - m.setTranslation(block_wpos - offset); + v3f block_wpos = intToFloat(descriptor.m_pos * MAP_BLOCKSIZE, BS); + m.setTranslation(block_wpos - offset); - driver->setTransform(video::ETS_WORLD, m); - driver->drawMeshBuffer(buf); - vertex_count += buf->getVertexCount(); - } - } + driver->setTransform(video::ETS_WORLD, m); + driver->drawMeshBuffer(buf); + vertex_count += buf->getIndexCount(); } + g_profiler->avg(prefix + "draw meshes [ms]", draw.stop(true)); // Log only on solid pass because values are the same @@ -440,8 +510,13 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) g_profiler->avg("renderMap(): animated meshes [#]", mesh_animate_count); } + if (pass == scene::ESNRP_TRANSPARENT) { + g_profiler->avg("renderMap(): transparent buffers [#]", draw_order.size()); + } + g_profiler->avg(prefix + "vertices drawn [#]", vertex_count); g_profiler->avg(prefix + "drawcalls [#]", drawcall_count); + g_profiler->avg(prefix + "material swaps [#]", material_swaps); } static bool getVisibleBrightness(Map *map, const v3f &p0, v3f dir, float step, @@ -648,7 +723,9 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver, u32 drawcall_count = 0; u32 vertex_count = 0; - MeshBufListList drawbufs; + MeshBufListList grouped_buffers; + std::vector<DrawDescriptor> draw_order; + int count = 0; int low_bound = is_transparent_pass ? 0 : m_drawlist_shadow.size() / total_frames * frame; @@ -677,7 +754,15 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver, /* Get the meshbuffers of the block */ - { + if (is_transparent_pass) { + // In transparent pass, the mesh will give us + // the partial buffers in the correct order + for (auto &buffer : block->mesh->getTransparentBuffers()) + draw_order.emplace_back(block_pos, &buffer); + } + else { + // otherwise, group buffers across meshes + // using MeshBufListList MapBlockMesh *mapBlockMesh = block->mesh; assert(mapBlockMesh); @@ -692,79 +777,99 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver, 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); + if (!transparent) + grouped_buffers.add(buf, block_pos, layer); } } } } + u32 buffer_count = 0; + for (auto &lists : grouped_buffers.lists) + for (MeshBufList &list : lists) + buffer_count += list.bufs.size(); + + draw_order.reserve(draw_order.size() + buffer_count); + + // Capture draw order for all solid meshes + for (auto &lists : grouped_buffers.lists) { + for (MeshBufList &list : lists) { + // iterate in reverse to draw closest blocks first + for (auto it = list.bufs.rbegin(); it != list.bufs.rend(); ++it) + draw_order.emplace_back(it->first, it->second, it != list.bufs.rbegin()); + } + } + TimeTaker draw("Drawing shadow mesh buffers"); core::matrix4 m; // Model matrix v3f offset = intToFloat(m_camera_offset, BS); + u32 material_swaps = 0; - // 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.BlendOperation = material.BlendOperation; - 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(); - } + // Render all mesh buffers in order + drawcall_count += draw_order.size(); + + for (auto &descriptor : draw_order) { + scene::IMeshBuffer *buf; + + if (descriptor.m_use_partial_buffer) { + descriptor.m_partial_buffer->beforeDraw(); + buf = descriptor.m_partial_buffer->getBuffer(); + } + else { + buf = descriptor.m_buffer; + } + + // Check and abort if the machine is swapping a lot + if (draw.getTimerTime() > 1000) { + infostream << "ClientMap::renderMapShadows(): Rendering " + "took >1s, returning." << std::endl; + break; + } - drawcall_count += list.bufs.size(); + if (!descriptor.m_reuse_material) { + // 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.BlendOperation = material.BlendOperation; + local_material.Lighting = false; + driver->setMaterial(local_material); + ++material_swaps; } + + v3f block_wpos = intToFloat(descriptor.m_pos * MAP_BLOCKSIZE, BS); + m.setTranslation(block_wpos - offset); + + driver->setTransform(video::ETS_WORLD, m); + driver->drawMeshBuffer(buf); + vertex_count += buf->getIndexCount(); } - // restore the driver material state + // restore the driver material state video::SMaterial clean; clean.BlendOperation = video::EBO_ADD; driver->setMaterial(clean); // reset material to defaults driver->draw3DLine(v3f(), v3f(), video::SColor(0)); - + g_profiler->avg(prefix + "draw meshes [ms]", draw.stop(true)); g_profiler->avg(prefix + "vertices drawn [#]", vertex_count); g_profiler->avg(prefix + "drawcalls [#]", drawcall_count); + g_profiler->avg(prefix + "material swaps [#]", material_swaps); } /* 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) +void ClientMap::updateDrawListShadow(v3f shadow_light_pos, v3f shadow_light_dir, float radius, float length) { 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 cam_pos_nodes = floatToInt(shadow_light_pos, BS); v3s16 p_blocks_min; v3s16 p_blocks_max; - getBlocksInViewRange(cam_pos_nodes, &p_blocks_min, &p_blocks_max, shadow_range); + getBlocksInViewRange(cam_pos_nodes, &p_blocks_min, &p_blocks_max, radius + length); std::vector<v2s16> blocks_in_range; @@ -774,15 +879,6 @@ void ClientMap::updateDrawListShadow(const v3f &shadow_light_pos, const v3f &sha } 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 @@ -808,23 +904,13 @@ void ClientMap::updateDrawListShadow(const v3f &shadow_light_pos, const v3f &sha continue; } - float range = shadow_range; - - float d = 0.0; - if (!isBlockInSight(block->getPos(), camera_position, - camera_direction, camera_fov, range, &d)) + v3f block_pos = intToFloat(block->getPos() * MAP_BLOCKSIZE, BS); + v3f projection = shadow_light_pos + shadow_light_dir * shadow_light_dir.dotProduct(block_pos - shadow_light_pos); + if (projection.getDistanceFrom(block_pos) > radius) 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(); @@ -841,3 +927,40 @@ void ClientMap::updateDrawListShadow(const v3f &shadow_light_pos, const v3f &sha g_profiler->avg("SHADOW MapBlocks drawn [#]", m_drawlist_shadow.size()); g_profiler->avg("SHADOW MapBlocks loaded [#]", blocks_loaded); } + +void ClientMap::updateTransparentMeshBuffers() +{ + ScopeProfiler sp(g_profiler, "CM::updateTransparentMeshBuffers", SPT_AVG); + u32 sorted_blocks = 0; + u32 unsorted_blocks = 0; + f32 sorting_distance_sq = pow(m_cache_transparency_sorting_distance * BS, 2.0f); + + + // Update the order of transparent mesh buffers in each mesh + for (auto it = m_drawlist.begin(); it != m_drawlist.end(); it++) { + MapBlock* block = it->second; + if (!block->mesh) + continue; + + if (m_needs_update_transparent_meshes || + block->mesh->getTransparentBuffers().size() == 0) { + + v3s16 block_pos = block->getPos(); + v3f block_pos_f = intToFloat(block_pos * MAP_BLOCKSIZE + MAP_BLOCKSIZE / 2, BS); + f32 distance = m_camera_position.getDistanceFromSQ(block_pos_f); + if (distance <= sorting_distance_sq) { + block->mesh->updateTransparentBuffers(m_camera_position, block_pos); + ++sorted_blocks; + } + else { + block->mesh->consolidateTransparentBuffers(); + ++unsorted_blocks; + } + } + } + + g_profiler->avg("CM::Transparent Buffers - Sorted", sorted_blocks); + g_profiler->avg("CM::Transparent Buffers - Unsorted", unsorted_blocks); + m_needs_update_transparent_meshes = false; +} + diff --git a/src/client/clientmap.h b/src/client/clientmap.h index 97ce8d355..6d57f1911 100644 --- a/src/client/clientmap.h +++ b/src/client/clientmap.h @@ -56,6 +56,7 @@ struct MeshBufListList class Client; class ITextureSource; +class PartialMeshBuffer; /* ClientMap @@ -75,45 +76,37 @@ public: virtual ~ClientMap() = default; - s32 mapType() const + bool maySaveBlocks() override { - return MAPTYPE_CLIENT; + return false; } - void drop() + void drop() override { - ISceneNode::drop(); + ISceneNode::drop(); // calls destructor } - void updateCamera(const v3f &pos, const v3f &dir, f32 fov, const v3s16 &offset) - { - m_camera_position = pos; - m_camera_direction = dir; - m_camera_fov = fov; - m_camera_offset = offset; - } + void updateCamera(v3f pos, v3f dir, f32 fov, v3s16 offset); /* Forcefully get a sector from somewhere */ - MapSector * emergeSector(v2s16 p); - - //void deSerializeSector(v2s16 p2d, std::istream &is); + MapSector * emergeSector(v2s16 p) override; /* ISceneNode methods */ - virtual void OnRegisterSceneNode(); + virtual void OnRegisterSceneNode() override; - virtual void render() + virtual void render() override { video::IVideoDriver* driver = SceneManager->getVideoDriver(); driver->setTransform(video::ETS_WORLD, AbsoluteTransformation); renderMap(driver, SceneManager->getSceneNodeRenderPass()); } - virtual const aabb3f &getBoundingBox() const + virtual const aabb3f &getBoundingBox() const override { return m_box; } @@ -121,7 +114,9 @@ public: void getBlocksInViewRange(v3s16 cam_pos_nodes, 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 updateDrawListShadow(v3f shadow_light_pos, v3f shadow_light_dir, float radius, float length); + // Returns true if draw list needs updating before drawing the next frame. + bool needsUpdateDrawList() { return m_needs_update_drawlist; } void renderMap(video::IVideoDriver* driver, s32 pass); void renderMapShadows(video::IVideoDriver *driver, @@ -133,13 +128,54 @@ public: void renderPostFx(CameraMode cam_mode); // For debug printing - virtual void PrintInfo(std::ostream &out); + void PrintInfo(std::ostream &out) override; const MapDrawControl & getControl() const { return m_control; } f32 getWantedRange() const { return m_control.wanted_range; } f32 getCameraFov() const { return m_camera_fov; } private: + + // update the vertex order in transparent mesh buffers + void updateTransparentMeshBuffers(); + + // Orders blocks by distance to the camera + class MapBlockComparer + { + public: + MapBlockComparer(const v3s16 &camera_block) : m_camera_block(camera_block) {} + + bool operator() (const v3s16 &left, const v3s16 &right) const + { + auto distance_left = left.getDistanceFromSQ(m_camera_block); + auto distance_right = right.getDistanceFromSQ(m_camera_block); + return distance_left > distance_right || (distance_left == distance_right && left > right); + } + + private: + v3s16 m_camera_block; + }; + + + // reference to a mesh buffer used when rendering the map. + struct DrawDescriptor { + v3s16 m_pos; + union { + scene::IMeshBuffer *m_buffer; + const PartialMeshBuffer *m_partial_buffer; + }; + bool m_reuse_material:1; + bool m_use_partial_buffer:1; + + DrawDescriptor(v3s16 pos, scene::IMeshBuffer *buffer, bool reuse_material) : + m_pos(pos), m_buffer(buffer), m_reuse_material(reuse_material), m_use_partial_buffer(false) + {} + + DrawDescriptor(v3s16 pos, const PartialMeshBuffer *buffer) : + m_pos(pos), m_partial_buffer(buffer), m_reuse_material(false), m_use_partial_buffer(true) + {} + }; + Client *m_client; RenderingEngine *m_rendering_engine; @@ -152,9 +188,11 @@ private: v3f m_camera_direction = v3f(0,0,1); f32 m_camera_fov = M_PI; v3s16 m_camera_offset; + bool m_needs_update_transparent_meshes = true; - std::map<v3s16, MapBlock*> m_drawlist; + std::map<v3s16, MapBlock*, MapBlockComparer> m_drawlist; std::map<v3s16, MapBlock*> m_drawlist_shadow; + bool m_needs_update_drawlist; std::set<v2s16> m_last_drawn_sectors; @@ -162,4 +200,5 @@ private: bool m_cache_bilinear_filter; bool m_cache_anistropic_filter; bool m_added_to_shadow_renderer{false}; + u16 m_cache_transparency_sorting_distance; }; diff --git a/src/client/clientmedia.h b/src/client/clientmedia.h index aa7b0f398..c297d737f 100644 --- a/src/client/clientmedia.h +++ b/src/client/clientmedia.h @@ -174,12 +174,12 @@ private: s32 m_uncached_received_count = 0; // Status of remote transfers - unsigned long m_httpfetch_caller; - unsigned long m_httpfetch_next_id = 0; + u64 m_httpfetch_caller; + u64 m_httpfetch_next_id = 0; s32 m_httpfetch_active = 0; s32 m_httpfetch_active_limit = 0; s32 m_outstanding_hash_sets = 0; - std::unordered_map<unsigned long, std::string> m_remote_file_transfers; + std::unordered_map<u64, std::string> m_remote_file_transfers; // All files up to this name have either been received from a // remote server or failed on all remote servers, so those files diff --git a/src/client/clouds.h b/src/client/clouds.h index c009a05b7..6db88d93c 100644 --- a/src/client/clouds.h +++ b/src/client/clouds.h @@ -22,7 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_extrabloated.h" #include <iostream> #include "constants.h" -#include "cloudparams.h" +#include "skyparams.h" // Menu clouds class Clouds; diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index 5d8a597a2..ec1fd1c2a 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -27,7 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/sound.h" #include "client/tile.h" #include "util/basic_macros.h" -#include "util/numeric.h" // For IntervalLimiter & setPitchYawRoll +#include "util/numeric.h" #include "util/serialize.h" #include "camera.h" // CameraModes #include "collision.h" @@ -172,6 +172,20 @@ static void updatePositionRecursive(scene::ISceneNode *node) node->updateAbsolutePosition(); } +static bool logOnce(const std::ostringstream &from, std::ostream &log_to) +{ + thread_local std::vector<u64> logged; + + std::string message = from.str(); + u64 hash = murmur_hash_64_ua(message.data(), message.length(), 0xBADBABE); + + if (std::find(logged.begin(), logged.end(), hash) != logged.end()) + return false; + logged.push_back(hash); + log_to << message << std::endl; + return true; +} + /* TestCAO */ @@ -422,7 +436,7 @@ const v3f GenericCAO::getPosition() const return m_position; } -const bool GenericCAO::isImmortal() +bool GenericCAO::isImmortal() const { return itemgroup_get(getGroups(), "immortal"); } @@ -651,7 +665,7 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) m_matrixnode, v2f(1, 1), v3f(0,0,0), -1); m_spritenode->grab(); m_spritenode->setMaterialTexture(0, - tsrc->getTextureForMesh("unknown_node.png")); + tsrc->getTextureForMesh("no_texture.png")); setSceneNodeMaterial(m_spritenode); @@ -735,9 +749,6 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) m_meshnode = m_smgr->addMeshSceneNode(mesh, m_matrixnode); m_meshnode->grab(); mesh->drop(); - // Set it to use the materials of the meshbuffers directly. - // This is needed for changing the texture in the future - m_meshnode->setReadOnlyMaterials(true); } else if (m_prop.visual == "cube") { grabMatrixNode(); scene::IMesh *mesh = createCubeMesh(v3f(BS,BS,BS)); @@ -754,10 +765,6 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) grabMatrixNode(); scene::IAnimatedMesh *mesh = m_client->getMesh(m_prop.mesh, true); if (mesh) { - m_animated_meshnode = m_smgr->addAnimatedMeshSceneNode(mesh, m_matrixnode); - m_animated_meshnode->grab(); - mesh->drop(); // The scene node took hold of it - if (!checkMeshNormals(mesh)) { infostream << "GenericCAO: recalculating normals for mesh " << m_prop.mesh << std::endl; @@ -765,6 +772,9 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) recalculateNormals(mesh, true, false); } + m_animated_meshnode = m_smgr->addAnimatedMeshSceneNode(mesh, m_matrixnode); + m_animated_meshnode->grab(); + mesh->drop(); // The scene node took hold of it m_animated_meshnode->animateJoints(); // Needed for some animations m_animated_meshnode->setScale(m_prop.visual_size); @@ -827,12 +837,33 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) setNodeLight(m_last_light); updateMeshCulling(); + if (m_animated_meshnode) { + u32 mat_count = m_animated_meshnode->getMaterialCount(); + if (mat_count == 0 || m_prop.textures.empty()) { + // nothing + } else if (mat_count > m_prop.textures.size()) { + std::ostringstream oss; + oss << "GenericCAO::addToScene(): Model " + << m_prop.mesh << " loaded with " << mat_count + << " mesh buffers but only " << m_prop.textures.size() + << " texture(s) specifed, this is deprecated."; + logOnce(oss, warningstream); + + video::ITexture *last = m_animated_meshnode->getMaterial(0).TextureLayer[0].Texture; + for (u32 i = 1; i < mat_count; i++) { + auto &layer = m_animated_meshnode->getMaterial(i).TextureLayer[0]; + if (!layer.Texture) + layer.Texture = last; + last = layer.Texture; + } + } + } + if (m_client->modsLoaded() && m_client->getScript()->on_object_add(m_id)) { removeFromScene(false); return; } - if (m_client->modsLoaded()) m_client->getScript()->on_object_properties_change(m_id); } @@ -842,7 +873,8 @@ void GenericCAO::updateLight(u32 day_night_ratio) if (m_glow < 0) return; - u8 light_at_pos = 0; + u16 light_at_pos = 0; + u8 light_at_pos_intensity = 0; bool pos_ok = false; v3s16 pos[3]; @@ -851,30 +883,36 @@ void GenericCAO::updateLight(u32 day_night_ratio) bool this_ok; MapNode n = m_env->getMap().getNode(pos[i], &this_ok); if (this_ok) { - u8 this_light = n.getLightBlend(day_night_ratio, m_client->ndef()); - light_at_pos = MYMAX(light_at_pos, this_light); + u16 this_light = getInteriorLight(n, 0, m_client->ndef()); + u8 this_light_intensity = MYMAX(this_light & 0xFF, (this_light >> 8) && 0xFF); + if (this_light_intensity > light_at_pos_intensity) { + light_at_pos = this_light; + light_at_pos_intensity = this_light_intensity; + } pos_ok = true; } } if (!pos_ok) - light_at_pos = blend_light(day_night_ratio, LIGHT_SUN, 0); + light_at_pos = LIGHT_SUN; + + video::SColor light = encode_light(light_at_pos, m_glow); + if (!m_enable_shaders) + final_color_blend(&light, light_at_pos, day_night_ratio); - u8 light = decode_light(light_at_pos + m_glow); if (g_settings->getBool("fullbright")) - light = 255; + light = video::SColor(0xFFFFFFFF); + if (light != m_last_light) { m_last_light = light; setNodeLight(light); } } -void GenericCAO::setNodeLight(u8 light) +void GenericCAO::setNodeLight(const video::SColor &light_color) { - video::SColor color(255, light, light, light); - if (m_prop.visual == "wielditem" || m_prop.visual == "item") { if (m_wield_meshnode) - m_wield_meshnode->setNodeLightColor(color); + m_wield_meshnode->setNodeLightColor(light_color); return; } @@ -886,7 +924,7 @@ void GenericCAO::setNodeLight(u8 light) scene::IMesh *mesh = m_meshnode->getMesh(); for (u32 i = 0; i < mesh->getMeshBufferCount(); ++i) { scene::IMeshBuffer *buf = mesh->getMeshBuffer(i); - buf->getMaterial().EmissiveColor = color; + buf->getMaterial().EmissiveColor = light_color; } } else { scene::ISceneNode *node = getSceneNode(); @@ -895,16 +933,16 @@ void GenericCAO::setNodeLight(u8 light) for (u32 i = 0; i < node->getMaterialCount(); ++i) { video::SMaterial &material = node->getMaterial(i); - material.EmissiveColor = color; + material.EmissiveColor = light_color; } } } else { if (m_meshnode) { - setMeshColor(m_meshnode->getMesh(), color); + setMeshColor(m_meshnode->getMesh(), light_color); } else if (m_animated_meshnode) { - setAnimatedMeshColor(m_animated_meshnode, color); + setAnimatedMeshColor(m_animated_meshnode, light_color); } else if (m_spritenode) { - m_spritenode->setColor(color); + m_spritenode->setColor(light_color); } } } @@ -1014,12 +1052,14 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) m_velocity = v3f(0,0,0); m_acceleration = v3f(0,0,0); const PlayerControl &controls = player->getPlayerControl(); + f32 new_speed = player->local_animation_speed; bool walking = false; - if (controls.movement_speed > 0.001f && ! g_settings->getBool("freecam")) + if (controls.movement_speed > 0.001f && ! g_settings->getBool("freecam")) { + new_speed *= controls.movement_speed; walking = true; + } - f32 new_speed = player->local_animation_speed; v2s32 new_anim = v2s32(0,0); bool allow_update = false; @@ -1034,7 +1074,6 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) // slowdown speed if sneaking if (controls.sneak && walking && ! g_settings->getBool("no_slow")) new_speed /= 2; - new_speed *= controls.movement_speed; if (walking && (controls.dig || controls.place)) { new_anim = player->local_animations[3]; @@ -1304,9 +1343,15 @@ void GenericCAO::updateTextures(std::string mod) m_current_texture_modifier = mod; m_glow = m_prop.glow; + video::ITexture *shadow_texture = nullptr; + if (auto shadow = RenderingEngine::get_shadow_renderer()) + shadow_texture = shadow->get_texture(); + + const u32 TEXTURE_LAYER_SHADOW = 3; + if (m_spritenode) { if (m_prop.visual == "sprite") { - std::string texturestring = "unknown_node.png"; + std::string texturestring = "no_texture.png"; if (!m_prop.textures.empty()) texturestring = m_prop.textures[0]; texturestring += mod; @@ -1314,6 +1359,7 @@ void GenericCAO::updateTextures(std::string mod) m_spritenode->getMaterial(0).MaterialTypeParam = 0.5f; m_spritenode->setMaterialTexture(0, tsrc->getTextureForMesh(texturestring)); + m_spritenode->setMaterialTexture(TEXTURE_LAYER_SHADOW, shadow_texture); // This allows setting per-material colors. However, until a real lighting // system is added, the code below will have no effect. Once MineTest @@ -1349,6 +1395,7 @@ void GenericCAO::updateTextures(std::string mod) material.MaterialType = m_material_type; material.MaterialTypeParam = 0.5f; material.TextureLayer[0].Texture = texture; + material.TextureLayer[TEXTURE_LAYER_SHADOW].Texture = shadow_texture; material.setFlag(video::EMF_LIGHTING, true); material.setFlag(video::EMF_BILINEAR_FILTER, false); material.setFlag(video::EMF_BACK_FACE_CULLING, m_prop.backface_culling); @@ -1385,7 +1432,7 @@ void GenericCAO::updateTextures(std::string mod) { for (u32 i = 0; i < 6; ++i) { - std::string texturestring = "unknown_node.png"; + std::string texturestring = "no_texture.png"; if(m_prop.textures.size() > i) texturestring = m_prop.textures[i]; texturestring += mod; @@ -1399,6 +1446,7 @@ void GenericCAO::updateTextures(std::string mod) material.setFlag(video::EMF_BILINEAR_FILTER, false); material.setTexture(0, tsrc->getTextureForMesh(texturestring)); + material.setTexture(TEXTURE_LAYER_SHADOW, shadow_texture); material.getTextureMatrix(0).makeIdentity(); // This allows setting per-material colors. However, until a real lighting @@ -1418,54 +1466,56 @@ void GenericCAO::updateTextures(std::string mod) } else if (m_prop.visual == "upright_sprite") { scene::IMesh *mesh = m_meshnode->getMesh(); { - std::string tname = "unknown_object.png"; + std::string tname = "no_texture.png"; if (!m_prop.textures.empty()) tname = m_prop.textures[0]; tname += mod; - scene::IMeshBuffer *buf = mesh->getMeshBuffer(0); - buf->getMaterial().setTexture(0, + auto& material = m_meshnode->getMaterial(0); + material.setTexture(0, tsrc->getTextureForMesh(tname)); + material.setTexture(TEXTURE_LAYER_SHADOW, shadow_texture); // This allows setting per-material colors. However, until a real lighting // system is added, the code below will have no effect. Once MineTest // has directional lighting, it should work automatically. if(!m_prop.colors.empty()) { - buf->getMaterial().AmbientColor = m_prop.colors[0]; - buf->getMaterial().DiffuseColor = m_prop.colors[0]; - buf->getMaterial().SpecularColor = m_prop.colors[0]; + material.AmbientColor = m_prop.colors[0]; + material.DiffuseColor = m_prop.colors[0]; + material.SpecularColor = m_prop.colors[0]; } - buf->getMaterial().setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); - buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); - buf->getMaterial().setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); + material.setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); + material.setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); + material.setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); } { - std::string tname = "unknown_object.png"; + std::string tname = "no_texture.png"; if (m_prop.textures.size() >= 2) tname = m_prop.textures[1]; else if (!m_prop.textures.empty()) tname = m_prop.textures[0]; tname += mod; - scene::IMeshBuffer *buf = mesh->getMeshBuffer(1); - buf->getMaterial().setTexture(0, + auto& material = m_meshnode->getMaterial(1); + material.setTexture(0, tsrc->getTextureForMesh(tname)); + material.setTexture(TEXTURE_LAYER_SHADOW, shadow_texture); // This allows setting per-material colors. However, until a real lighting // system is added, the code below will have no effect. Once MineTest // has directional lighting, it should work automatically. if (m_prop.colors.size() >= 2) { - buf->getMaterial().AmbientColor = m_prop.colors[1]; - buf->getMaterial().DiffuseColor = m_prop.colors[1]; - buf->getMaterial().SpecularColor = m_prop.colors[1]; + material.AmbientColor = m_prop.colors[1]; + material.DiffuseColor = m_prop.colors[1]; + material.SpecularColor = m_prop.colors[1]; } else if (!m_prop.colors.empty()) { - buf->getMaterial().AmbientColor = m_prop.colors[0]; - buf->getMaterial().DiffuseColor = m_prop.colors[0]; - buf->getMaterial().SpecularColor = m_prop.colors[0]; + material.AmbientColor = m_prop.colors[0]; + material.DiffuseColor = m_prop.colors[0]; + material.SpecularColor = m_prop.colors[0]; } - buf->getMaterial().setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); - buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); - buf->getMaterial().setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); + material.setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); + material.setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); + material.setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); } // Set mesh color (only if lighting is disabled) if (!m_prop.colors.empty() && m_glow < 0) @@ -1811,6 +1861,7 @@ void GenericCAO::processMessage(const std::string &data) { updateAnimation(); } + // FIXME: ^ This code is trash. It's also broken. } } else if (cmd == AO_CMD_SET_ANIMATION_SPEED) { m_animation_speed = readF32(is); @@ -1855,6 +1906,8 @@ void GenericCAO::processMessage(const std::string &data) m_reset_textures_timer = 0.05; if(damage >= 2) m_reset_textures_timer += 0.05 * damage; + // Cap damage overlay to 1 second + m_reset_textures_timer = std::min(m_reset_textures_timer, 1.0f); updateTextures(m_current_texture_modifier + m_prop.damage_texture_modifier); } } @@ -1905,7 +1958,8 @@ bool GenericCAO::directReportPunch(v3f dir, const ItemStack *punchitem, m_armor_groups, toolcap, punchitem, - time_from_last_punch); + time_from_last_punch, + punchitem->wear); if(result.did_punch && result.damage != 0) { @@ -1925,6 +1979,8 @@ bool GenericCAO::directReportPunch(v3f dir, const ItemStack *punchitem, m_reset_textures_timer = 0.05; if (result.damage >= 2) m_reset_textures_timer += 0.05 * result.damage; + // Cap damage overlay to 1 second + m_reset_textures_timer = std::min(m_reset_textures_timer, 1.0f); updateTextures(m_current_texture_modifier + m_prop.damage_texture_modifier); } } @@ -1953,20 +2009,17 @@ void GenericCAO::updateMeshCulling() const bool hidden = m_client->getCamera()->getCameraMode() == CAMERA_MODE_FIRST && ! g_settings->getBool("freecam"); - if (m_meshnode && m_prop.visual == "upright_sprite") { - u32 buffers = m_meshnode->getMesh()->getMeshBufferCount(); - for (u32 i = 0; i < buffers; i++) { - video::SMaterial &mat = m_meshnode->getMesh()->getMeshBuffer(i)->getMaterial(); - // upright sprite has no backface culling - mat.setFlag(video::EMF_FRONT_FACE_CULLING, hidden); - } - return; - } - scene::ISceneNode *node = getSceneNode(); + if (!node) return; + if (m_prop.visual == "upright_sprite") { + // upright sprite has no backface culling + node->setMaterialFlag(video::EMF_FRONT_FACE_CULLING, hidden); + return; + } + if (hidden) { // Hide the mesh by culling both front and // back faces. Serious hackyness but it works for our diff --git a/src/client/content_cao.h b/src/client/content_cao.h index 7e9bb6671..8e5d04bfa 100644 --- a/src/client/content_cao.h +++ b/src/client/content_cao.h @@ -125,7 +125,7 @@ private: std::string m_current_texture_modifier = ""; bool m_visuals_expired = false; float m_step_distance_counter = 0.0f; - u8 m_last_light = 255; + video::SColor m_last_light = video::SColor(0xFFFFFFFF); bool m_is_visible = false; s8 m_glow = 0; // Material @@ -182,12 +182,12 @@ public: return m_velocity; } - inline const u16 getHp() const + inline u16 getHp() const { return m_hp; } - const bool isImmortal(); + bool isImmortal() const; inline const ObjectProperties &getProperties() const { return m_prop; } @@ -271,7 +271,7 @@ public: void updateLight(u32 day_night_ratio); - void setNodeLight(u8 light); + void setNodeLight(const video::SColor &light); /* Get light position(s). * returns number of positions written into pos[], which must have space diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp index bb2d6398f..8675275aa 100644 --- a/src/client/content_mapblock.cpp +++ b/src/client/content_mapblock.cpp @@ -150,8 +150,10 @@ void MapblockMeshGenerator::drawQuad(v3f *coords, const v3s16 &normal, // should be (2+2)*6=24 values in the list. The order of // the faces in the list is up-down-right-left-back-front // (compatible with ContentFeatures). +// mask - a bit mask that suppresses drawing of tiles. +// tile i will not be drawn if mask & (1 << i) is 1 void MapblockMeshGenerator::drawCuboid(const aabb3f &box, - TileSpec *tiles, int tilecount, const LightInfo *lights, const f32 *txc) + TileSpec *tiles, int tilecount, const LightInfo *lights, const f32 *txc, u8 mask) { assert(tilecount >= 1 && tilecount <= 6); // pre-condition @@ -274,6 +276,8 @@ void MapblockMeshGenerator::drawCuboid(const aabb3f &box, // Add to mesh collector for (int k = 0; k < 6; ++k) { + if (mask & (1 << k)) + continue; int tileindex = MYMIN(k, tilecount - 1); collector->append(tiles[tileindex], vertices + 4 * k, 4, quad_indices, 6); } @@ -363,7 +367,7 @@ void MapblockMeshGenerator::generateCuboidTextureCoords(const aabb3f &box, f32 * } void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc, - TileSpec *tiles, int tile_count) + TileSpec *tiles, int tile_count, u8 mask) { bool scale = std::fabs(f->visual_scale - 1.0f) > 1e-3f; f32 texture_coord_buf[24]; @@ -373,6 +377,10 @@ void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc, f32 dx2 = box.MaxEdge.X; f32 dy2 = box.MaxEdge.Y; f32 dz2 = box.MaxEdge.Z; + + box.MinEdge += origin; + box.MaxEdge += origin; + if (scale) { if (!txc) { // generate texture coords before scaling generateCuboidTextureCoords(box, texture_coord_buf); @@ -381,12 +389,11 @@ void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc, box.MinEdge *= f->visual_scale; box.MaxEdge *= f->visual_scale; } - box.MinEdge += origin; - box.MaxEdge += origin; if (!txc) { generateCuboidTextureCoords(box, texture_coord_buf); txc = texture_coord_buf; } + if (!tiles) { tiles = &tile; tile_count = 1; @@ -400,12 +407,49 @@ void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc, d.Z = (j & 1) ? dz2 : dz1; lights[j] = blendLight(d); } - drawCuboid(box, tiles, tile_count, lights, txc); + drawCuboid(box, tiles, tile_count, lights, txc, mask); } else { - drawCuboid(box, tiles, tile_count, nullptr, txc); + drawCuboid(box, tiles, tile_count, nullptr, txc, mask); } } +u8 MapblockMeshGenerator::getNodeBoxMask(aabb3f box, u8 solid_neighbors, u8 sametype_neighbors) const +{ + const f32 NODE_BOUNDARY = 0.5 * BS; + + // For an oversized nodebox, return immediately + if (box.MaxEdge.X > NODE_BOUNDARY || + box.MinEdge.X < -NODE_BOUNDARY || + box.MaxEdge.Y > NODE_BOUNDARY || + box.MinEdge.Y < -NODE_BOUNDARY || + box.MaxEdge.Z > NODE_BOUNDARY || + box.MinEdge.Z < -NODE_BOUNDARY) + return 0; + + // We can skip faces at node boundary if the matching neighbor is solid + u8 solid_mask = + (box.MaxEdge.Y == NODE_BOUNDARY ? 1 : 0) | + (box.MinEdge.Y == -NODE_BOUNDARY ? 2 : 0) | + (box.MaxEdge.X == NODE_BOUNDARY ? 4 : 0) | + (box.MinEdge.X == -NODE_BOUNDARY ? 8 : 0) | + (box.MaxEdge.Z == NODE_BOUNDARY ? 16 : 0) | + (box.MinEdge.Z == -NODE_BOUNDARY ? 32 : 0); + + u8 sametype_mask = 0; + if (f->alpha == AlphaMode::ALPHAMODE_OPAQUE) { + // In opaque nodeboxes, faces on opposite sides can cancel + // each other out if there is a matching neighbor of the same type + sametype_mask = + ((solid_mask & 3) == 3 ? 3 : 0) | + ((solid_mask & 12) == 12 ? 12 : 0) | + ((solid_mask & 48) == 48 ? 48 : 0); + } + + // Combine masks with actual neighbors to get the faces to be skipped + return (solid_mask & solid_neighbors) | (sametype_mask & sametype_neighbors); +} + + void MapblockMeshGenerator::prepareLiquidNodeDrawing() { getSpecialTile(0, &tile_liquid_top); @@ -1363,13 +1407,38 @@ void MapblockMeshGenerator::drawNodeboxNode() getTile(nodebox_tile_dirs[face], &tiles[face]); } + bool param2_is_rotation = + f->param_type_2 == CPT2_COLORED_FACEDIR || + f->param_type_2 == CPT2_COLORED_WALLMOUNTED || + f->param_type_2 == CPT2_FACEDIR || + f->param_type_2 == CPT2_WALLMOUNTED; + + bool param2_is_level = + f->param_type_2 == CPT2_LEVELED; + // locate possible neighboring nodes to connect to u8 neighbors_set = 0; - if (f->node_box.type == NODEBOX_CONNECTED) { - for (int dir = 0; dir != 6; dir++) { - u8 flag = 1 << dir; - v3s16 p2 = blockpos_nodes + p + nodebox_connection_dirs[dir]; - MapNode n2 = data->m_vmanip.getNodeNoEx(p2); + u8 solid_neighbors = 0; + u8 sametype_neighbors = 0; + for (int dir = 0; dir != 6; dir++) { + u8 flag = 1 << dir; + v3s16 p2 = blockpos_nodes + p + nodebox_tile_dirs[dir]; + MapNode n2 = data->m_vmanip.getNodeNoEx(p2); + + // mark neighbors that are the same node type + // and have the same rotation or higher level stored as param2 + if (n2.param0 == n.param0 && + (!param2_is_rotation || n.param2 == n2.param2) && + (!param2_is_level || n.param2 <= n2.param2)) + sametype_neighbors |= flag; + + // mark neighbors that are simple solid blocks + if (nodedef->get(n2).drawtype == NDT_NORMAL) + solid_neighbors |= flag; + + if (f->node_box.type == NODEBOX_CONNECTED) { + p2 = blockpos_nodes + p + nodebox_connection_dirs[dir]; + n2 = data->m_vmanip.getNodeNoEx(p2); if (nodedef->nodeboxConnects(n, n2, flag)) neighbors_set |= flag; } @@ -1377,8 +1446,63 @@ void MapblockMeshGenerator::drawNodeboxNode() std::vector<aabb3f> boxes; n.getNodeBoxes(nodedef, &boxes, neighbors_set); - for (auto &box : boxes) - drawAutoLightedCuboid(box, nullptr, tiles, 6); + + bool isTransparent = false; + + for (const TileSpec &tile : tiles) { + if (tile.layers[0].isTransparent()) { + isTransparent = true; + break; + } + } + + if (isTransparent) { + std::vector<float> sections; + // Preallocate 8 default splits + Min&Max for each nodebox + sections.reserve(8 + 2 * boxes.size()); + + for (int axis = 0; axis < 3; axis++) { + // identify sections + + if (axis == 0) { + // Default split at node bounds, up to 3 nodes in each direction + for (float s = -3.5f * BS; s < 4.0f * BS; s += 1.0f * BS) + sections.push_back(s); + } + else { + // Avoid readding the same 8 default splits for Y and Z + sections.resize(8); + } + + // Add edges of existing node boxes, rounded to 1E-3 + for (size_t i = 0; i < boxes.size(); i++) { + sections.push_back(std::floor(boxes[i].MinEdge[axis] * 1E3) * 1E-3); + sections.push_back(std::floor(boxes[i].MaxEdge[axis] * 1E3) * 1E-3); + } + + // split the boxes at recorded sections + // limit splits to avoid runaway crash if inner loop adds infinite splits + // due to e.g. precision problems. + // 100 is just an arbitrary, reasonably high number. + for (size_t i = 0; i < boxes.size() && i < 100; i++) { + aabb3f *box = &boxes[i]; + for (float section : sections) { + if (box->MinEdge[axis] < section && box->MaxEdge[axis] > section) { + aabb3f copy(*box); + copy.MinEdge[axis] = section; + box->MaxEdge[axis] = section; + boxes.push_back(copy); + box = &boxes[i]; // find new address of the box in case of reallocation + } + } + } + } + } + + for (auto &box : boxes) { + u8 mask = getNodeBoxMask(box, solid_neighbors, sametype_neighbors); + drawAutoLightedCuboid(box, nullptr, tiles, 6, mask); + } } void MapblockMeshGenerator::drawMeshNode() diff --git a/src/client/content_mapblock.h b/src/client/content_mapblock.h index 7344f05ee..b13748cbc 100644 --- a/src/client/content_mapblock.h +++ b/src/client/content_mapblock.h @@ -100,10 +100,11 @@ public: // cuboid drawing! void drawCuboid(const aabb3f &box, TileSpec *tiles, int tilecount, - const LightInfo *lights , const f32 *txc); + const LightInfo *lights , const f32 *txc, u8 mask = 0); void generateCuboidTextureCoords(aabb3f const &box, f32 *coords); void drawAutoLightedCuboid(aabb3f box, const f32 *txc = NULL, - TileSpec *tiles = NULL, int tile_count = 0); + TileSpec *tiles = NULL, int tile_count = 0, u8 mask = 0); + u8 getNodeBoxMask(aabb3f box, u8 solid_neighbors, u8 sametype_neighbors) const; // liquid-specific bool top_is_same_liquid; diff --git a/src/client/fontengine.cpp b/src/client/fontengine.cpp index f64315db4..ad8305b45 100644 --- a/src/client/fontengine.cpp +++ b/src/client/fontengine.cpp @@ -24,10 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "porting.h" #include "filesys.h" #include "gettext.h" - -#if USE_FREETYPE #include "irrlicht_changes/CGUITTFont.h" -#endif /** maximum size distance for getting a "similar" font size */ #define MAX_FONT_SIZE_OFFSET 10 @@ -45,9 +42,8 @@ static void font_setting_changed(const std::string &name, void *userdata) FontEngine::FontEngine(gui::IGUIEnvironment* env) : m_env(env) { - for (u32 &i : m_default_size) { - i = (FontMode) FONT_SIZE_UNSPECIFIED; + i = FONT_SIZE_UNSPECIFIED; } assert(g_settings != NULL); // pre-condition @@ -56,23 +52,19 @@ FontEngine::FontEngine(gui::IGUIEnvironment* env) : readSettings(); - if (m_currentMode != FM_Simple) { - g_settings->registerChangedCallback("font_size", font_setting_changed, NULL); - g_settings->registerChangedCallback("font_bold", font_setting_changed, NULL); - g_settings->registerChangedCallback("font_italic", font_setting_changed, NULL); - g_settings->registerChangedCallback("font_path", font_setting_changed, NULL); - g_settings->registerChangedCallback("font_path_bold", font_setting_changed, NULL); - g_settings->registerChangedCallback("font_path_italic", font_setting_changed, NULL); - g_settings->registerChangedCallback("font_path_bolditalic", font_setting_changed, NULL); - g_settings->registerChangedCallback("font_shadow", font_setting_changed, NULL); - g_settings->registerChangedCallback("font_shadow_alpha", font_setting_changed, NULL); - g_settings->registerChangedCallback("fallback_font_path", font_setting_changed, NULL); - } + const char *settings[] = { + "font_size", "font_bold", "font_italic", "font_size_divisible_by", + "mono_font_size", "mono_font_size_divisible_by", + "font_shadow", "font_shadow_alpha", + "font_path", "font_path_bold", "font_path_italic", "font_path_bold_italic", + "mono_font_path", "mono_font_path_bold", "mono_font_path_italic", + "mono_font_path_bold_italic", + "fallback_font_path", + "screen_dpi", "gui_scaling", + }; - g_settings->registerChangedCallback("mono_font_path", font_setting_changed, NULL); - g_settings->registerChangedCallback("mono_font_size", font_setting_changed, NULL); - g_settings->registerChangedCallback("screen_dpi", font_setting_changed, NULL); - g_settings->registerChangedCallback("gui_scaling", font_setting_changed, NULL); + for (auto name : settings) + g_settings->registerChangedCallback(name, font_setting_changed, NULL); } /******************************************************************************/ @@ -84,11 +76,13 @@ FontEngine::~FontEngine() /******************************************************************************/ void FontEngine::cleanCache() { + RecursiveMutexAutoLock l(m_font_mutex); + for (auto &font_cache_it : m_font_cache) { for (auto &font_it : font_cache_it) { font_it.second->drop(); - font_it.second = NULL; + font_it.second = nullptr; } font_cache_it.clear(); } @@ -104,16 +98,8 @@ irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec, bool may_fail) { if (spec.mode == FM_Unspecified) { spec.mode = m_currentMode; - } else if (m_currentMode == FM_Simple) { - // Freetype disabled -> Force simple mode - spec.mode = (spec.mode == FM_Mono || - spec.mode == FM_SimpleMono) ? - FM_SimpleMono : FM_Simple; - // Support for those could be added, but who cares? - spec.bold = false; - spec.italic = false; } else if (spec.mode == _FM_Fallback) { - // Fallback font doesn't support these either + // Fallback font doesn't support these spec.bold = false; spec.italic = false; } @@ -122,17 +108,15 @@ irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec, bool may_fail) if (spec.size == FONT_SIZE_UNSPECIFIED) spec.size = m_default_size[spec.mode]; + RecursiveMutexAutoLock l(m_font_mutex); + const auto &cache = m_font_cache[spec.getHash()]; auto it = cache.find(spec.size); if (it != cache.end()) return it->second; // Font does not yet exist - gui::IGUIFont *font = nullptr; - if (spec.mode == FM_Simple || spec.mode == FM_SimpleMono) - font = initSimpleFont(spec); - else - font = initFont(spec); + gui::IGUIFont *font = initFont(spec); if (!font && !may_fail) { errorstream << "Minetest cannot continue without a valid font. " @@ -149,13 +133,7 @@ irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec, bool may_fail) /******************************************************************************/ unsigned int FontEngine::getTextHeight(const FontSpec &spec) { - irr::gui::IGUIFont *font = getFont(spec); - - // use current skin font as fallback - if (font == NULL) { - font = m_env->getSkin()->getFont(); - } - FATAL_ERROR_IF(font == NULL, "Could not get skin font"); + gui::IGUIFont *font = getFont(spec); return font->getDimension(L"Some unimportant example String").Height; } @@ -163,28 +141,15 @@ unsigned int FontEngine::getTextHeight(const FontSpec &spec) /******************************************************************************/ unsigned int FontEngine::getTextWidth(const std::wstring &text, const FontSpec &spec) { - irr::gui::IGUIFont *font = getFont(spec); - - // use current skin font as fallback - if (font == NULL) { - font = m_env->getSkin()->getFont(); - } - FATAL_ERROR_IF(font == NULL, "Could not get font"); + gui::IGUIFont *font = getFont(spec); return font->getDimension(text.c_str()).Width; } - /** get line height for a specific font (including empty room between lines) */ unsigned int FontEngine::getLineHeight(const FontSpec &spec) { - irr::gui::IGUIFont *font = getFont(spec); - - // use current skin font as fallback - if (font == NULL) { - font = m_env->getSkin()->getFont(); - } - FATAL_ERROR_IF(font == NULL, "Could not get font"); + gui::IGUIFont *font = getFont(spec); return font->getDimension(L"Some unimportant example String").Height + font->getKerningHeight(); @@ -198,13 +163,6 @@ unsigned int FontEngine::getDefaultFontSize() unsigned int FontEngine::getFontSize(FontMode mode) { - if (m_currentMode == FM_Simple) { - if (mode == FM_Mono || mode == FM_SimpleMono) - return m_default_size[FM_SimpleMono]; - else - return m_default_size[FM_Simple]; - } - if (mode == FM_Unspecified) return m_default_size[FM_Standard]; @@ -214,20 +172,12 @@ unsigned int FontEngine::getFontSize(FontMode mode) /******************************************************************************/ void FontEngine::readSettings() { - if (USE_FREETYPE && g_settings->getBool("freetype")) { - m_default_size[FM_Standard] = g_settings->getU16("font_size"); - m_default_size[_FM_Fallback] = g_settings->getU16("font_size"); - m_default_size[FM_Mono] = g_settings->getU16("mono_font_size"); - - m_default_bold = g_settings->getBool("font_bold"); - m_default_italic = g_settings->getBool("font_italic"); + m_default_size[FM_Standard] = g_settings->getU16("font_size"); + m_default_size[_FM_Fallback] = g_settings->getU16("font_size"); + m_default_size[FM_Mono] = g_settings->getU16("mono_font_size"); - } else { - m_currentMode = FM_Simple; - } - - m_default_size[FM_Simple] = g_settings->getU16("font_size"); - m_default_size[FM_SimpleMono] = g_settings->getU16("mono_font_size"); + m_default_bold = g_settings->getBool("font_bold"); + m_default_italic = g_settings->getBool("font_italic"); cleanCache(); updateFontCache(); @@ -238,22 +188,9 @@ void FontEngine::readSettings() void FontEngine::updateSkin() { gui::IGUIFont *font = getFont(); + assert(font); - if (font) - m_env->getSkin()->setFont(font); - else - errorstream << "FontEngine: Default font file: " << - "\n\t\"" << g_settings->get("font_path") << "\"" << - "\n\trequired for current screen configuration was not found" << - " or was invalid file format." << - "\n\tUsing irrlicht default font." << std::endl; - - // If we did fail to create a font our own make irrlicht find a default one - font = m_env->getSkin()->getFont(); - FATAL_ERROR_IF(font == NULL, "Could not create/get font"); - - u32 text_height = font->getDimension(L"Hello, world!").Height; - infostream << "FontEngine: measured text_height=" << text_height << std::endl; + m_env->getSkin()->setFont(font); } /******************************************************************************/ @@ -280,15 +217,18 @@ gui::IGUIFont *FontEngine::initFont(const FontSpec &spec) if (spec.italic) setting_suffix.append("_italic"); - u32 size = std::floor(RenderingEngine::getDisplayDensity() * - g_settings->getFloat("gui_scaling") * spec.size); + u32 size = std::max<u32>(spec.size * RenderingEngine::getDisplayDensity() * + g_settings->getFloat("gui_scaling"), 1); - if (size == 0) { - errorstream << "FontEngine: attempt to use font size 0" << std::endl; - errorstream << " display density: " << RenderingEngine::getDisplayDensity() << std::endl; - abort(); + // Constrain the font size to a certain multiple, if necessary + u16 divisible_by = g_settings->getU16(setting_prefix + "font_size_divisible_by"); + if (divisible_by > 1) { + size = std::max<u32>( + std::round((double)size / divisible_by) * divisible_by, divisible_by); } + sanity_check(size != 0); + u16 font_shadow = 0; u16 font_shadow_alpha = 0; g_settings->getU16NoEx(setting_prefix + "font_shadow", font_shadow); @@ -306,7 +246,6 @@ gui::IGUIFont *FontEngine::initFont(const FontSpec &spec) Settings::getLayer(SL_DEFAULTS)->get(path_setting) }; -#if USE_FREETYPE for (const std::string &font_path : fallback_settings) { gui::CGUITTFont *font = gui::CGUITTFont::createTTFont(m_env, font_path.c_str(), size, true, true, font_shadow, @@ -325,80 +264,5 @@ gui::IGUIFont *FontEngine::initFont(const FontSpec &spec) } return font; } -#else - errorstream << "FontEngine: Tried to load TTF font but Minetest was" - " compiled without Freetype." << std::endl; -#endif return nullptr; } - -/** initialize a font without freetype */ -gui::IGUIFont *FontEngine::initSimpleFont(const FontSpec &spec) -{ - assert(spec.mode == FM_Simple || spec.mode == FM_SimpleMono); - assert(spec.size != FONT_SIZE_UNSPECIFIED); - - const std::string &font_path = g_settings->get( - (spec.mode == FM_SimpleMono) ? "mono_font_path" : "font_path"); - - size_t pos_dot = font_path.find_last_of('.'); - 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 - << "\" but freetype is not available." << std::endl; - return nullptr; - } - - if (ending == ".xml" || ending == ".png") - basename = font_path.substr(0, pos_dot); - - u32 size = std::floor( - RenderingEngine::getDisplayDensity() * - g_settings->getFloat("gui_scaling") * - spec.size); - - irr::gui::IGUIFont *font = nullptr; - std::string font_extensions[] = { ".png", ".xml" }; - - // Find nearest matching font scale - // Does a "zig-zag motion" (positibe/negative), from 0 to MAX_FONT_SIZE_OFFSET - for (s32 zoffset = 0; zoffset < MAX_FONT_SIZE_OFFSET * 2; zoffset++) { - std::stringstream path; - - // LSB to sign - s32 sign = (zoffset & 1) ? -1 : 1; - s32 offset = zoffset >> 1; - - for (const std::string &ext : font_extensions) { - path.str(""); // Clear - path << basename << "_" << (size + offset * sign) << ext; - - if (!fs::PathExists(path.str())) - continue; - - font = m_env->getFont(path.str().c_str()); - - if (font) { - verbosestream << "FontEngine: found font: " << path.str() << std::endl; - break; - } - } - - if (font) - break; - } - - // try name direct - if (font == NULL) { - if (fs::PathExists(font_path)) { - font = m_env->getFont(font_path.c_str()); - if (font) - verbosestream << "FontEngine: found font: " << font_path << std::endl; - } - } - - return font; -} diff --git a/src/client/fontengine.h b/src/client/fontengine.h index 3d389ea48..78608e517 100644 --- a/src/client/fontengine.h +++ b/src/client/fontengine.h @@ -20,13 +20,13 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include <map> -#include <vector> #include "util/basic_macros.h" #include "irrlichttypes.h" #include <IGUIFont.h> #include <IGUISkin.h> #include <IGUIEnvironment.h> #include "settings.h" +#include "threading/mutex_auto_lock.h" #define FONT_SIZE_UNSPECIFIED 0xFFFFFFFF @@ -34,8 +34,6 @@ enum FontMode : u8 { FM_Standard = 0, FM_Mono, _FM_Fallback, // do not use directly - FM_Simple, - FM_SimpleMono, FM_MaxMode, FM_Unspecified }; @@ -140,9 +138,6 @@ private: /** initialize a new TTF font */ gui::IGUIFont *initFont(const FontSpec &spec); - /** initialize a font without freetype */ - gui::IGUIFont *initSimpleFont(const FontSpec &spec); - /** update current minetest skin with font changes */ void updateSkin(); @@ -152,6 +147,9 @@ private: /** pointer to irrlicht gui environment */ gui::IGUIEnvironment* m_env = nullptr; + /** mutex used to protect font init and cache */ + std::recursive_mutex m_font_mutex; + /** internal storage for caching fonts of different size */ std::map<unsigned int, irr::gui::IGUIFont*> m_font_cache[FM_MaxMode << 2]; @@ -162,8 +160,8 @@ private: bool m_default_bold = false; bool m_default_italic = false; - /** current font engine mode */ - FontMode m_currentMode = FM_Standard; + /** default font engine mode (fixed) */ + static const FontMode m_currentMode = FM_Standard; DISABLE_CLASS_COPY(FontEngine); }; diff --git a/src/client/game.cpp b/src/client/game.cpp index d2a751040..e439d0e32 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -122,7 +122,7 @@ Game::Game() : readSettings(); -#ifdef __ANDROID__ +#ifdef HAVE_TOUCHSCREENGUI m_cache_hold_aux1 = false; // This is initialised properly later #endif @@ -243,19 +243,19 @@ bool Game::startup(bool *kill, void Game::run() { ProfilerGraph graph; - RunStats stats = { 0 }; - FpsControl draw_times = { 0 }; + RunStats stats = {}; + FpsControl draw_times; f32 dtime; // in seconds /* Clear the profiler */ Profiler::GraphValues dummyvalues; g_profiler->graphGet(dummyvalues); - draw_times.last_time = m_rendering_engine->get_timer_time(); + draw_times.reset(); set_light_table(g_settings->getFloat("display_gamma")); -#ifdef __ANDROID__ +#ifdef HAVE_TOUCHSCREENGUI m_cache_hold_aux1 = g_settings->getBool("fast_move") && client->checkPrivilege("fast"); #endif @@ -283,7 +283,7 @@ void Game::run() // Calculate dtime = // m_rendering_engine->run() from this iteration // + Sleep time until the wanted FPS are reached - limitFps(&draw_times, &dtime); + draw_times.limit(device, &dtime); // Prepare render data for next iteration @@ -313,10 +313,9 @@ void Game::run() step(&dtime); processClientEvents(&cam_view_target); updateDebugState(); - updateCamera(draw_times.busy_time, dtime); + updateCamera(dtime); updateSound(dtime); - processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud, - m_game_ui->m_flags.show_basic_debug); + processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud); updateFrame(&graph, &stats, dtime, cam_view); updateProfilerGraphs(&graph); @@ -473,9 +472,8 @@ bool Game::createSingleplayerServer(const std::string &map_dir, } if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) { - *error_message = "Unable to listen on " + - bind_addr.serializeString() + - " because IPv6 is disabled"; + *error_message = fmtgettext("Unable to listen on %s because IPv6 is disabled", + bind_addr.serializeString().c_str()); errorstream << *error_message << std::endl; return false; } @@ -508,7 +506,7 @@ bool Game::createClient(const GameStartData &start_data) if (!could_connect) { if (error_message->empty() && !connect_aborted) { // Should not happen if error messages are set properly - *error_message = "Connection failed for unknown reason"; + *error_message = gettext("Connection failed for unknown reason"); errorstream << *error_message << std::endl; } return false; @@ -517,7 +515,7 @@ bool Game::createClient(const GameStartData &start_data) if (!getServerContent(&connect_aborted)) { if (error_message->empty() && !connect_aborted) { // Should not happen if error messages are set properly - *error_message = "Connection failed for unknown reason"; + *error_message = gettext("Connection failed for unknown reason"); errorstream << *error_message << std::endl; } return false; @@ -533,8 +531,8 @@ bool Game::createClient(const GameStartData &start_data) /* Camera */ camera = new Camera(*draw_control, client, m_rendering_engine); - if (!camera->successfullyCreated(*error_message)) - return false; + if (client->modsLoaded()) + client->getScript()->on_camera_ready(camera); client->setCamera(camera); /* Clouds @@ -575,7 +573,7 @@ bool Game::createClient(const GameStartData &start_data) str += L" ["; str += text; str += L"]"; - delete text; + delete[] text; } str += L" ["; str += L"Minetest Hackclient"; @@ -650,7 +648,6 @@ bool Game::connectToServer(const GameStartData &start_data, connect_address.Resolve(start_data.address.c_str()); if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY - //connect_address.Resolve("localhost"); if (connect_address.isIPv6()) { IPv6AddressBytes addr_bytes; addr_bytes.bytes[15] = 1; @@ -661,29 +658,35 @@ bool Game::connectToServer(const GameStartData &start_data, local_server_mode = true; } } catch (ResolveError &e) { - *error_message = std::string("Couldn't resolve address: ") + e.what(); + *error_message = fmtgettext("Couldn't resolve address: %s", e.what()); + errorstream << *error_message << std::endl; return false; } if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) { - *error_message = "Unable to connect to " + - connect_address.serializeString() + - " because IPv6 is disabled"; + *error_message = fmtgettext("Unable to connect to %s because IPv6 is disabled", connect_address.serializeString().c_str()); errorstream << *error_message << std::endl; return false; } - client = new Client(start_data.name.c_str(), - start_data.password, start_data.address, - *draw_control, texture_src, shader_src, - itemdef_manager, nodedef_manager, sound, eventmgr, - m_rendering_engine, connect_address.isIPv6(), m_game_ui.get()); + try { + client = new Client(start_data.name.c_str(), + start_data.password, start_data.address, + *draw_control, texture_src, shader_src, + itemdef_manager, nodedef_manager, sound, eventmgr, + m_rendering_engine, connect_address.isIPv6(), m_game_ui.get()); + client->migrateModStorage(); + } catch (const BaseException &e) { + *error_message = fmtgettext("Error creating client: %s", e.what()); + errorstream << *error_message << std::endl; + return false; + } client->m_simple_singleplayer_mode = simple_singleplayer_mode; infostream << "Connecting to server at "; - connect_address.print(&infostream); + connect_address.print(infostream); infostream << std::endl; client->connect(connect_address, @@ -696,15 +699,15 @@ bool Game::connectToServer(const GameStartData &start_data, try { input->clear(); - FpsControl fps_control = { 0 }; + FpsControl fps_control; f32 dtime; f32 wait_time = 0; // in seconds - fps_control.last_time = m_rendering_engine->get_timer_time(); + fps_control.reset(); while (m_rendering_engine->run()) { - limitFps(&fps_control, &dtime); + fps_control.limit(device, &dtime); // Update client and server client->step(dtime); @@ -723,8 +726,7 @@ bool Game::connectToServer(const GameStartData &start_data, break; if (client->accessDenied()) { - *error_message = "Access denied. Reason: " - + client->accessDeniedReason(); + *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str()); *reconnect_requested = client->reconnectRequested(); errorstream << *error_message << std::endl; break; @@ -750,7 +752,7 @@ bool Game::connectToServer(const GameStartData &start_data, wait_time += dtime; // Only time out if we aren't waiting for the server we started if (!start_data.address.empty() && wait_time > 10) { - *error_message = "Connection timed out."; + *error_message = gettext("Connection timed out."); errorstream << *error_message << std::endl; break; } @@ -772,14 +774,14 @@ bool Game::getServerContent(bool *aborted) { input->clear(); - FpsControl fps_control = { 0 }; + FpsControl fps_control; f32 dtime; // in seconds - fps_control.last_time = m_rendering_engine->get_timer_time(); + fps_control.reset(); while (m_rendering_engine->run()) { - limitFps(&fps_control, &dtime); + fps_control.limit(device, &dtime); // Update client and server client->step(dtime); @@ -798,7 +800,7 @@ bool Game::getServerContent(bool *aborted) return false; if (client->getState() < LC_Init) { - *error_message = "Client disconnected"; + *error_message = gettext("Client disconnected"); errorstream << *error_message << std::endl; return false; } @@ -880,8 +882,7 @@ inline void Game::updateInteractTimers(f32 dtime) inline bool Game::checkConnection() { if (client->accessDenied()) { - *error_message = "Access denied. Reason: " - + client->accessDeniedReason(); + *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str()); *reconnect_requested = client->reconnectRequested(); errorstream << *error_message << std::endl; return false; @@ -936,17 +937,16 @@ void Game::processQueues() void Game::updateDebugState() { - bool has_basic_debug = client->checkPrivilege("basic_debug"); + LocalPlayer *player = client->getEnv().getLocalPlayer(); bool has_debug = client->checkPrivilege("debug"); + bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG); if (m_game_ui->m_flags.show_basic_debug) { - if (!has_basic_debug) { + if (!has_basic_debug) m_game_ui->m_flags.show_basic_debug = false; - } } else if (m_game_ui->m_flags.show_minimal_debug) { - if (has_basic_debug) { + if (has_basic_debug) m_game_ui->m_flags.show_basic_debug = true; - } } if (!has_basic_debug) hud->disableBlockBounds(); @@ -977,10 +977,10 @@ void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times, } // Update update graphs - g_profiler->graphAdd("Time non-rendering [ms]", + g_profiler->graphAdd("Time non-rendering [us]", draw_times.busy_time - stats.drawtime); - g_profiler->graphAdd("Sleep [ms]", draw_times.sleep_time); + g_profiler->graphAdd("Sleep [us]", draw_times.sleep_time); g_profiler->graphAdd("FPS", 1.0f / dtime); } @@ -1013,9 +1013,9 @@ void Game::updateStats(RunStats *stats, const FpsControl &draw_times, /* Busytime average and jitter calculation */ jp = &stats->busy_time_jitter; - jp->avg = jp->avg + draw_times.busy_time * 0.02; + jp->avg = jp->avg + draw_times.getBusyMs() * 0.02; - jitter = draw_times.busy_time - jp->avg; + jitter = draw_times.getBusyMs() - jp->avg; if (jitter > jp->max) jp->max = jitter; @@ -1052,6 +1052,7 @@ void Game::processUserInput(f32 dtime) else if (g_touchscreengui) { /* on touchscreengui step may generate own input events which ain't * what we want in case we just did clear them */ + g_touchscreengui->show(); g_touchscreengui->step(dtime); } #endif @@ -1290,15 +1291,22 @@ void Game::openInventory() InventoryLocation inventoryloc; inventoryloc.setCurrentPlayer(); - if (!client->modsLoaded() - || !client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) { - TextDest *txt_dst = new TextDestPlayerInventory(client); - auto *&formspec = m_game_ui->updateFormspec(""); - GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(), - &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound); + if (client->modsLoaded() && client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) { + delete fs_src; + return; + } - formspec->setFormSpec(fs_src->getForm(), inventoryloc); + if (fs_src->getForm().empty()) { + delete fs_src; + return; } + + TextDest *txt_dst = new TextDestPlayerInventory(client); + auto *&formspec = m_game_ui->updateFormspec(""); + GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(), + &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound); + + formspec->setFormSpec(fs_src->getForm(), inventoryloc); } void Game::openEnderchest() @@ -1398,7 +1406,7 @@ void Game::toggleFast() m_game_ui->showTranslatedStatusText("Fast mode disabled"); } -#ifdef __ANDROID__ +#ifdef HAVE_TOUCHSCREENGUI m_cache_hold_aux1 = fast_move && has_fast_privs; #endif } @@ -1469,27 +1477,27 @@ void Game::toggleCinematic() void Game::toggleBlockBounds() { - if (client->checkPrivilege("basic_debug")) { - enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds(); - switch (newmode) { - case Hud::BLOCK_BOUNDS_OFF: - m_game_ui->showTranslatedStatusText("Block bounds hidden"); - break; - case Hud::BLOCK_BOUNDS_CURRENT: - m_game_ui->showTranslatedStatusText("Block bounds shown for current block"); - break; - case Hud::BLOCK_BOUNDS_NEAR: - m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks"); - break; - case Hud::BLOCK_BOUNDS_MAX: - m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks"); - break; - default: - break; - } - - } else { - m_game_ui->showTranslatedStatusText("Can't show block bounds (need 'basic_debug' privilege)"); + LocalPlayer *player = client->getEnv().getLocalPlayer(); + if (!(client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG))) { + m_game_ui->showTranslatedStatusText("Can't show block bounds (disabled by mod or game)"); + return; + } + enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds(); + switch (newmode) { + case Hud::BLOCK_BOUNDS_OFF: + m_game_ui->showTranslatedStatusText("Block bounds hidden"); + break; + case Hud::BLOCK_BOUNDS_CURRENT: + m_game_ui->showTranslatedStatusText("Block bounds shown for current block"); + break; + case Hud::BLOCK_BOUNDS_NEAR: + m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks"); + break; + case Hud::BLOCK_BOUNDS_MAX: + m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks"); + break; + default: + break; } } @@ -1556,6 +1564,9 @@ void Game::toggleFog() void Game::toggleDebug() { + LocalPlayer *player = client->getEnv().getLocalPlayer(); + bool has_debug = client->checkPrivilege("debug"); + bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG); // Initial: No debug info // 1x toggle: Debug text // 2x toggle: Debug text with profiler graph @@ -1565,26 +1576,23 @@ void Game::toggleDebug() // The debug text can be in 2 modes: minimal and basic. // * Minimal: Only technical client info that not gameplay-relevant // * Basic: Info that might give gameplay advantage, e.g. pos, angle - // Basic mode is used when player has "basic_debug" priv, + // Basic mode is used when player has the debug HUD flag set, // otherwise the Minimal mode is used. if (!m_game_ui->m_flags.show_minimal_debug) { m_game_ui->m_flags.show_minimal_debug = true; - if (client->checkPrivilege("basic_debug")) { + if (has_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")) { + if (has_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")) { + if (has_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"); @@ -1593,7 +1601,7 @@ void Game::toggleDebug() m_game_ui->m_flags.show_basic_debug = false; m_game_ui->m_flags.show_profiler_graph = false; draw_control->show_wireframe = false; - if (client->checkPrivilege("debug")) { + if (has_debug) { m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden"); } else { m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden"); @@ -1755,6 +1763,10 @@ void Game::updatePlayerControl(const CameraOrientation &cam) //TimeTaker tt("update player control", NULL, PRECISION_NANO); PlayerControl control( + isKeyDown(KeyType::FORWARD), + isKeyDown(KeyType::BACKWARD), + isKeyDown(KeyType::LEFT), + isKeyDown(KeyType::RIGHT), isKeyDown(KeyType::JUMP) || player->getAutojump(), isKeyDown(KeyType::AUX1), isKeyDown(KeyType::SNEAK), @@ -1774,10 +1786,10 @@ void Game::updatePlayerControl(const CameraOrientation &cam) control.movement_direction = 0.0f; } -#ifdef ANDROID - /* For Android, simulate holding down AUX1 (fast move) if the user has +#ifdef HAVE_TOUCHSCREENGUI + /* For touch, simulate holding down AUX1 (fast move) if the user has * the fast_move setting toggled on. If there is an aux1 key defined for - * Android then its meaning is inverted (i.e. holding aux1 means walk and + * touch then its meaning is inverted (i.e. holding aux1 means walk and * not fast) */ if (m_cache_hold_aux1) { @@ -1785,39 +1797,7 @@ void Game::updatePlayerControl(const CameraOrientation &cam) } #endif - u32 keypress_bits = ( - ( (u32)(control.jump & 0x1) << 4) | - ( (u32)(control.aux1 & 0x1) << 5) | - ( (u32)(control.sneak & 0x1) << 6) | - ( (u32)(control.dig & 0x1) << 7) | - ( (u32)(control.place & 0x1) << 8) | - ( (u32)(control.zoom & 0x1) << 9) - ); - - // Set direction keys to ensure mod compatibility - if (control.movement_speed > 0.001f) { - float absolute_direction; - - // Check in original orientation (absolute value indicates forward / backward) - absolute_direction = abs(control.movement_direction); - if (absolute_direction < (3.0f / 8.0f * M_PI)) - keypress_bits |= (u32)(0x1 << 0); // Forward - if (absolute_direction > (5.0f / 8.0f * M_PI)) - keypress_bits |= (u32)(0x1 << 1); // Backward - - // Rotate entire coordinate system by 90 degrees (absolute value indicates left / right) - absolute_direction = control.movement_direction + M_PI_2; - if (absolute_direction >= M_PI) - absolute_direction -= 2 * M_PI; - absolute_direction = abs(absolute_direction); - if (absolute_direction < (3.0f / 8.0f * M_PI)) - keypress_bits |= (u32)(0x1 << 2); // Left - if (absolute_direction > (5.0f / 8.0f * M_PI)) - keypress_bits |= (u32)(0x1 << 3); // Right - } - client->setPlayerControl(control); - player->keyPressed = keypress_bits; //tt.stop(); } @@ -2172,7 +2152,7 @@ void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam) void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam) { sky->setStarsVisible(event->star_params->visible); - sky->setStarCount(event->star_params->count, false); + sky->setStarCount(event->star_params->count); sky->setStarColor(event->star_params->starcolor); sky->setStarScale(event->star_params->scale); delete event->star_params; @@ -2209,7 +2189,7 @@ void Game::processClientEvents(CameraOrientation *cam) } } -void Game::updateChat(f32 dtime, const v2u32 &screensize) +void Game::updateChat(f32 dtime) { // Get new messages from error log buffer while (!m_chat_log_buf.empty()) @@ -2225,11 +2205,17 @@ void Game::updateChat(f32 dtime, const v2u32 &screensize) chat_backend->step(dtime); // Display all messages in a static text element - m_game_ui->setChatText(chat_backend->getRecentChat(), - chat_backend->getRecentBuffer().getLineCount()); + auto &buf = chat_backend->getRecentBuffer(); + if (buf.getLinesModified()) { + buf.resetLinesModified(); + m_game_ui->setChatText(chat_backend->getRecentChat(), buf.getLineCount()); + } + + // Make sure that the size is still correct + m_game_ui->updateChatSize(); } -void Game::updateCamera(u32 busy_time, f32 dtime) +void Game::updateCamera(f32 dtime) { LocalPlayer *player = client->getEnv().getLocalPlayer(); @@ -2259,7 +2245,7 @@ void Game::updateCamera(u32 busy_time, f32 dtime) float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval; tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0); - camera->update(player, dtime, busy_time / 1000.0f, tool_reload_ratio); + camera->update(player, dtime, tool_reload_ratio); camera->step(dtime); v3f camera_position = camera->getPosition(); @@ -2333,7 +2319,7 @@ void Game::updateSound(f32 dtime) } -void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug) +void Game::processPlayerInteraction(f32 dtime, bool show_hud) { LocalPlayer *player = client->getEnv().getLocalPlayer(); @@ -2391,10 +2377,12 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug) !runData.btn_down_for_dig, camera_offset); - if (pointed != runData.pointed_old) { + if (pointed != runData.pointed_old) infostream << "Pointing at " << pointed.dump() << std::endl; - hud->updateSelectionMesh(camera_offset); - } + + // Note that updating the selection mesh every frame is not particularly efficient, + // but the halo rendering code is already inefficient so there's no point in optimizing it here + hud->updateSelectionMesh(camera_offset); // Allow digging again if button is not pressed if (runData.digging_blocked && !isKeyDown(KeyType::DIG)) @@ -2455,7 +2443,9 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug) handlePointingAtNode(pointed, selected_item, hand_item, dtime); } else if (pointed.type == POINTEDTHING_OBJECT) { v3f player_position = player->getPosition(); - handlePointingAtObject(pointed, tool_item, player_position, show_debug); + bool basic_debug_allowed = client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG); + handlePointingAtObject(pointed, tool_item, player_position, + m_game_ui->m_flags.show_basic_debug && basic_debug_allowed); } else if (isKeyDown(KeyType::DIG)) { // When button is held down in air, show continuous animation runData.punching = true; @@ -2614,9 +2604,8 @@ void Game::handlePointingAtNode(const PointedThing &pointed, } else { MapNode n = map.getNode(nodepos); - if (nodedef_manager->get(n).tiledef[0].name == "unknown_node.png") { - m_game_ui->setInfoText(L"Unknown node: " + - utf8_to_wide(nodedef_manager->get(n).name)); + if (nodedef_manager->get(n).name == "unknown") { + m_game_ui->setInfoText(L"Unknown node"); } } @@ -2904,7 +2893,8 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos, // cheat detection. // Get digging parameters DigParams params = getDigParams(nodedef_manager->get(n).groups, - &selected_item.getToolCapabilities(itemdef_manager)); + &selected_item.getToolCapabilities(itemdef_manager), + selected_item.wear); // If can't dig, try hand if (!params.diggable) { @@ -3155,12 +3145,28 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, } /* - Get chat messages from client + Damage camera tilt */ + if (player->hurt_tilt_timer > 0.0f) { + player->hurt_tilt_timer -= dtime * 6.0f; - v2u32 screensize = driver->getScreenSize(); + if (player->hurt_tilt_timer < 0.0f || g_settings->getBool("no_hurt_cam")) + player->hurt_tilt_strength = 0.0f; + } + + /* + Update minimap pos and rotation + */ + if (mapper && m_game_ui->m_flags.show_hud) { + mapper->setPos(floatToInt(player->getPosition(), BS)); + mapper->setAngle(player->getYaw()); + } + + /* + Get chat messages from client + */ - updateChat(dtime, screensize); + updateChat(dtime); /* Inventory @@ -3187,8 +3193,8 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, v3f camera_direction = camera->getDirection(); 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) { - + || m_camera_offset_changed + || client->getEnv().getClientMap().needsUpdateDrawList()) { runData.update_draw_list_timer = 0; client->getEnv().getClientMap().updateDrawList(); runData.update_draw_list_last_cam_dir = camera_direction; @@ -3229,11 +3235,11 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, } while (false); /* - Drawing begins + ==================== Drawing begins ==================== */ - const video::SColor &skycolor = sky->getSkyColor(); + const video::SColor skycolor = sky->getSkyColor(); - TimeTaker tt_draw("Draw scene"); + TimeTaker tt_draw("Draw scene", nullptr, PRECISION_MICRO); driver->beginScene(true, true, skycolor); bool draw_wield_tool = (m_game_ui->m_flags.show_hud && @@ -3254,6 +3260,8 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, /* Profiler graph */ + v2u32 screensize = driver->getScreenSize(); + if (m_game_ui->m_flags.show_profiler_graph) graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont()); @@ -3279,26 +3287,9 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, } /* - Damage camera tilt - */ - if (player->hurt_tilt_timer > 0.0f) { - player->hurt_tilt_timer -= dtime * 6.0f; - - if (player->hurt_tilt_timer < 0.0f || g_settings->getBool("no_hurt_cam")) - player->hurt_tilt_strength = 0.0f; - } - - /* - Update minimap pos and rotation - */ - if (mapper && m_game_ui->m_flags.show_hud) { - mapper->setPos(floatToInt(player->getPosition(), BS)); - mapper->setAngle(player->getYaw()); - } - - /* End scene */ +#if IRRLICHT_VERSION_MT_REVISION < 5 if (++m_reset_HW_buffer_counter > 500) { /* Periodically remove all mesh HW buffers. @@ -3320,11 +3311,13 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, driver->removeAllHardwareBuffers(); m_reset_HW_buffer_counter = 0; } +#endif + driver->endScene(); stats->drawtime = tt_draw.stop(true); - g_profiler->avg("Game::updateFrame(): draw scene [ms]", stats->drawtime); - g_profiler->graphAdd("Update frame [ms]", tt_update.stop(true)); + g_profiler->graphAdd("Draw scene [us]", stats->drawtime); + g_profiler->avg("Game::updateFrame(): update frame [ms]", tt_update.stop(true)); } /* Log times and stuff for visualization */ @@ -3346,7 +3339,12 @@ void Game::updateShadows() float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f); - float timeoftheday = fmod(getWickedTimeOfDay(in_timeofday) + 0.75f, 0.5f) + 0.25f; + float timeoftheday = getWickedTimeOfDay(in_timeofday); + bool is_day = timeoftheday > 0.25 && timeoftheday < 0.75; + bool is_shadow_visible = is_day ? sky->getSunVisible() : sky->getMoonVisible(); + shadow->setShadowIntensity(is_shadow_visible ? client->getEnv().getLocalPlayer()->getLighting().shadow_intensity : 0.0f); + + timeoftheday = fmod(timeoftheday + 0.75f, 0.5f) + 0.25f; const float offset_constant = 10000.0f; v3f light(0.0f, 0.0f, -1.0f); @@ -3368,47 +3366,46 @@ void Game::updateShadows() Misc ****************************************************************************/ -/* On some computers framerate doesn't seem to be automatically limited - */ -inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime) +void FpsControl::reset() { - // not using getRealTime is necessary for wine - device->getTimer()->tick(); // Maker sure device time is up-to-date - u32 time = device->getTimer()->getTime(); - u32 last_time = fps_timings->last_time; - - if (time > last_time) // Make sure time hasn't overflowed - fps_timings->busy_time = time - last_time; - else - fps_timings->busy_time = 0; + last_time = porting::getTimeUs(); +} - u32 frametime_min = 1000 / ( +/* + * On some computers framerate doesn't seem to be automatically limited + */ +void FpsControl::limit(IrrlichtDevice *device, f32 *dtime) +{ + const u64 frametime_min = 1000000.0f / ( device->isWindowFocused() && !g_menumgr.pausesGame() ? g_settings->getFloat("fps_max") : g_settings->getFloat("fps_max_unfocused")); - if (fps_timings->busy_time < frametime_min) { - fps_timings->sleep_time = frametime_min - fps_timings->busy_time; - device->sleep(fps_timings->sleep_time); + u64 time = porting::getTimeUs(); + + if (time > last_time) // Make sure time hasn't overflowed + busy_time = time - last_time; + else + busy_time = 0; + + if (busy_time < frametime_min) { + sleep_time = frametime_min - busy_time; + if (sleep_time > 1000) + sleep_ms(sleep_time / 1000); } else { - fps_timings->sleep_time = 0; + sleep_time = 0; } - /* Get the new value of the device timer. Note that device->sleep() may - * not sleep for the entire requested time as sleep may be interrupted and - * therefore it is arguably more accurate to get the new time from the - * device rather than calculating it by adding sleep_time to time. - */ - - device->getTimer()->tick(); // Update device timer - time = device->getTimer()->getTime(); + // Read the timer again to accurately determine how long we actually slept, + // rather than calculating it by adding sleep_time to time. + time = porting::getTimeUs(); - if (time > last_time) // Make sure last_time hasn't overflowed - *dtime = (time - last_time) / 1000.0; + if (time > last_time) // Make sure last_time hasn't overflowed + *dtime = (time - last_time) / 1000000.0f; else *dtime = 0; - fps_timings->last_time = time; + last_time = time; } void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds) @@ -3522,7 +3519,7 @@ void Game::showDeathFormspec() #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name()) void Game::showPauseMenu() { -#ifdef __ANDROID__ +#ifdef HAVE_TOUCHSCREENGUI static const std::string control_text = strgettext("Default Controls:\n" "No menu visible:\n" "- single tap: button activate\n" @@ -3630,16 +3627,18 @@ void Game::showPauseMenu() if (simple_singleplayer_mode || address.empty()) { static const std::string on = strgettext("On"); static const std::string off = strgettext("Off"); - const std::string &damage = g_settings->getBool("enable_damage") ? on : off; - const std::string &creative = g_settings->getBool("creative_mode") ? on : off; + // Note: Status of enable_damage and creative_mode settings is intentionally + // NOT shown here because the game might roll its own damage system and/or do + // a per-player Creative Mode, in which case writing it here would mislead. + bool damage = g_settings->getBool("enable_damage"); const std::string &announced = g_settings->getBool("server_announce") ? on : off; - os << strgettext("- Damage: ") << damage << "\n" - << strgettext("- Creative Mode: ") << creative << "\n"; if (!simple_singleplayer_mode) { - const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off; - //~ PvP = Player versus Player - os << strgettext("- PvP: ") << pvp << "\n" - << strgettext("- Public: ") << announced << "\n"; + if (damage) { + const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off; + //~ PvP = Player versus Player + os << strgettext("- PvP: ") << pvp << "\n"; + } + os << strgettext("- Public: ") << announced << "\n"; std::string server_name = g_settings->get("server_name"); str_formspec_escape(server_name); if (announced == on && !server_name.empty()) @@ -3698,14 +3697,15 @@ void the_game(bool *kill, } } catch (SerializationError &e) { - error_message = std::string("A serialization error occurred:\n") - + e.what() + "\n\nThe server is probably " - " running a different version of " PROJECT_NAME_C "."; + const std::string ver_err = fmtgettext("The server is probably running a different version of %s.", PROJECT_NAME_C); + error_message = strgettext("A serialization error occurred:") +"\n" + + e.what() + "\n\n" + ver_err; errorstream << error_message << std::endl; } catch (ServerError &e) { error_message = e.what(); errorstream << "ServerError: " << error_message << std::endl; } catch (ModError &e) { + // DO NOT TRANSLATE the `ModError`, it's used by ui.lua error_message = std::string("ModError: ") + e.what() + strgettext("\nCheck debug.txt for details."); errorstream << error_message << std::endl; diff --git a/src/client/game.h b/src/client/game.h index cb40d4890..0e5d0550d 100644 --- a/src/client/game.h +++ b/src/client/game.h @@ -86,7 +86,7 @@ struct Jitter { }; struct RunStats { - u32 drawtime; + u64 drawtime; // (us) Jitter dtime_jitter, busy_time_jitter; }; @@ -584,7 +584,7 @@ public: } }; -#ifdef __ANDROID__ +#ifdef HAVE_TOUCHSCREENGUI #define SIZE_TAG "size[11,5.5]" #else #define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop @@ -596,7 +596,16 @@ public: const float object_hit_delay = 0.2; struct FpsControl { - u32 last_time, busy_time, sleep_time; + FpsControl() : last_time(0), busy_time(0), sleep_time(0) {} + + void reset(); + + void limit(IrrlichtDevice *device, f32 *dtime); + + u32 getBusyMs() const { return busy_time / 1000; } + + // all values in microseconds (us) + u64 last_time, busy_time, sleep_time; }; @@ -726,9 +735,9 @@ public: void updatePlayerControl(const CameraOrientation &cam); void step(f32 *dtime); void processClientEvents(CameraOrientation *cam); - void updateCamera(u32 busy_time, f32 dtime); + void updateCamera(f32 dtime); void updateSound(f32 dtime); - void processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug); + void processPlayerInteraction(f32 dtime, bool show_hud); /*! * Returns the object or node the player is pointing at. * Also updates the selected thing in the Hud. @@ -757,8 +766,6 @@ public: void updateShadows(); // Misc - void limitFps(FpsControl *fps_timings, f32 *dtime); - void showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds = true); @@ -807,7 +814,7 @@ public: CameraOrientation *cam); void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam); - void updateChat(f32 dtime, const v2u32 &screensize); + void updateChat(f32 dtime); bool nodePlacement(const ItemDefinition &selected_def, const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos, const PointedThing &pointed, @@ -904,12 +911,17 @@ public: bool m_does_lost_focus_pause_game = false; - CameraOrientation cam_view_target = { 0 }; - CameraOrientation cam_view = { 0 }; + CameraOrientation cam_view_target = {}; // added by dragonfireclient + CameraOrientation cam_view = {}; // added by dragonfireclient +#if IRRLICHT_VERSION_MT_REVISION < 5 int m_reset_HW_buffer_counter = 0; -#ifdef __ANDROID__ +#endif + +#ifdef HAVE_TOUCHSCREENGUI bool m_cache_hold_aux1; +#endif +#ifdef __ANDROID__ bool m_android_chat_open; #endif }; diff --git a/src/client/gameui.cpp b/src/client/gameui.cpp index 66a006ea1..54be24ae2 100644 --- a/src/client/gameui.cpp +++ b/src/client/gameui.cpp @@ -124,16 +124,16 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_ // 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; + const u16 fps = 1.0 / stats.dtime_jitter.avg; + m_drawtime_avg *= 0.95f; + m_drawtime_avg += 0.05f * (stats.drawtime / 1000); std::ostringstream os(std::ios_base::binary); os << std::fixed << PROJECT_NAME_C " " << g_version_hash << " | FPS: " << fps << std::setprecision(0) - << " | drawtime: " << drawtime_avg << "ms" + << " | drawtime: " << m_drawtime_avg << "ms" << std::setprecision(1) << " | dtime jitter: " << (stats.dtime_jitter.max_fraction * 100.0) << "%" @@ -171,9 +171,13 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_ const NodeDefManager *nodedef = client->getNodeDefManager(); MapNode n = map.getNode(pointed_old.node_undersurface); - if (n.getContent() != CONTENT_IGNORE && nodedef->get(n).name != "unknown") { - os << ", pointed: " << nodedef->get(n).name - << ", param2: " << (u64) n.getParam2(); + if (n.getContent() != CONTENT_IGNORE) { + if (nodedef->get(n).name == "unknown") { + os << ", pointed: <unknown node>"; + } else { + os << ", pointed: " << nodedef->get(n).name; + } + os << ", param2: " << (u64) n.getParam2(); } } @@ -229,7 +233,6 @@ void GameUI::initFlags() { m_flags = GameUI::Flags(); m_flags.show_minimal_debug = g_settings->getBool("show_debug"); - m_flags.show_basic_debug = false; } void GameUI::showMinimap(bool show) @@ -246,7 +249,13 @@ void GameUI::showTranslatedStatusText(const char *str) void GameUI::setChatText(const EnrichedString &chat_text, u32 recent_chat_count) { + setStaticText(m_guitext_chat, chat_text); + m_recent_chat_count = recent_chat_count; +} + +void GameUI::updateChatSize() +{ // Update gui element size and position const v2u32 &window_size = RenderingEngine::getWindowSize(); @@ -258,15 +267,15 @@ void GameUI::setChatText(const EnrichedString &chat_text, u32 recent_chat_count) if (m_flags.show_basic_debug) chat_y += g_fontengine->getLineHeight(); - core::rect<s32> chat_size(10, chat_y, - window_size.X - 20, 0); + core::rect<s32> chat_size(10, chat_y, window_size.X - 20, 0); chat_size.LowerRightCorner.Y = std::min((s32)window_size.Y, - m_guitext_chat->getTextHeight() + chat_y); + m_guitext_chat->getTextHeight() + chat_y); - m_guitext_chat->setRelativePosition(chat_size); - setStaticText(m_guitext_chat, chat_text); + if (chat_size == m_current_chat_size) + return; + m_current_chat_size = chat_size; - m_recent_chat_count = recent_chat_count; + m_guitext_chat->setRelativePosition(chat_size); } void GameUI::updateProfiler() diff --git a/src/client/gameui.h b/src/client/gameui.h index 5404643e2..e22be068b 100644 --- a/src/client/gameui.h +++ b/src/client/gameui.h @@ -85,11 +85,12 @@ public: void showTranslatedStatusText(const char *str); inline void clearStatusText() { m_statustext.clear(); } - const bool isChatVisible() + bool isChatVisible() { return m_flags.show_chat && m_recent_chat_count != 0 && m_profiler_current_page == 0; } void setChatText(const EnrichedString &chat_text, u32 recent_chat_count); + void updateChatSize(); void updateProfiler(); @@ -111,6 +112,8 @@ public: private: Flags m_flags; + float m_drawtime_avg = 0; + gui::IGUIStaticText *m_guitext = nullptr; // First line of debug text gui::IGUIStaticText *m_guitext2 = nullptr; // Second line of debug text gui::IGUIStaticText *m_guitext_coords = nullptr; @@ -125,6 +128,7 @@ private: gui::IGUIStaticText *m_guitext_chat = nullptr; // Chat text u32 m_recent_chat_count = 0; + core::rect<s32> m_current_chat_size{0, 0, 0, 0}; gui::IGUIStaticText *m_guitext_profiler = nullptr; // Profiler text u8 m_profiler_current_page = 0; diff --git a/src/client/guiscalingfilter.cpp b/src/client/guiscalingfilter.cpp index 232219237..de122becf 100644 --- a/src/client/guiscalingfilter.cpp +++ b/src/client/guiscalingfilter.cpp @@ -43,6 +43,10 @@ void guiScalingCache(const io::path &key, video::IVideoDriver *driver, video::II { if (!g_settings->getBool("gui_scaling_filter")) return; + + if (g_imgCache.find(key) != g_imgCache.end()) + return; // Already cached. + video::IImage *copied = driver->createImage(value->getColorFormat(), value->getDimension()); value->copyTo(copied); @@ -90,14 +94,16 @@ video::ITexture *guiScalingResizeCached(video::IVideoDriver *driver, io::path scalename = origname + "@guiScalingFilter:" + rectstr; // Search for existing scaled texture. - video::ITexture *scaled = g_txrCache[scalename]; + auto it_txr = g_txrCache.find(scalename); + video::ITexture *scaled = (it_txr != g_txrCache.end()) ? it_txr->second : nullptr; if (scaled) return scaled; // Try to find the texture converted to an image in the cache. // If the image was not found, try to extract it from the texture. - video::IImage* srcimg = g_imgCache[origname]; - if (srcimg == NULL) { + auto it_img = g_imgCache.find(origname); + video::IImage *srcimg = (it_img != g_imgCache.end()) ? it_img->second : nullptr; + if (!srcimg) { if (!g_settings->getBool("gui_scaling_filter_txr2img")) return src; srcimg = driver->createImageFromData(src->getColorFormat(), diff --git a/src/client/hud.cpp b/src/client/hud.cpp index 3f687b698..d5debecfb 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -20,6 +20,8 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "client/hud.h" +#include <string> +#include <iostream> #include <cmath> #include "settings.h" #include "util/numeric.h" @@ -222,6 +224,7 @@ void Hud::drawItem(const ItemStack &item, const core::rect<s32>& rect, } //NOTE: selectitem = 0 -> no selected; selectitem 1-based +// mainlist can be NULL, but draw the frame anyway. void Hud::drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount, s32 inv_offset, InventoryList *mainlist, u16 selectitem, u16 direction) { @@ -269,7 +272,8 @@ void Hud::drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount, // Draw items core::rect<s32> imgrect(0, 0, m_hotbar_imagesize, m_hotbar_imagesize); - for (s32 i = inv_offset; i < itemcount && (size_t)i < mainlist->getSize(); i++) { + const s32 list_size = mainlist ? mainlist->getSize() : 0; + for (s32 i = inv_offset; i < itemcount && i < list_size; i++) { s32 fullimglen = m_hotbar_imagesize + m_padding * 2; v2s32 steppos; @@ -377,15 +381,19 @@ void Hud::drawLuaElements(const v3s16 &camera_offset) std::wstring text = unescape_translate(utf8_to_wide(e->text)); core::dimension2d<u32> textsize = textfont->getDimension(text.c_str()); - v2s32 offset((e->align.X - 1.0) * (textsize.Width / 2), - (e->align.Y - 1.0) * (textsize.Height / 2)); + v2s32 offset(0, (e->align.Y - 1.0) * (textsize.Height / 2)); core::rect<s32> size(0, 0, e->scale.X * m_scale_factor, - text_height * e->scale.Y * 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); - + e->offset.Y * m_scale_factor); + std::wstringstream wss(text); + std::wstring line; + while (std::getline(wss, line, L'\n')) { - textfont->draw(text.c_str(), size + pos + offset + offs, color); + core::dimension2d<u32> linesize = textfont->getDimension(line.c_str()); + v2s32 line_offset((e->align.X - 1.0) * (linesize.Width / 2), 0); + textfont->draw(line.c_str(), size + pos + offset + offs + line_offset, color); + offset.Y += linesize.Height; } break; } case HUD_ELEM_STATBAR: { @@ -395,6 +403,8 @@ void Hud::drawLuaElements(const v3s16 &camera_offset) break; } case HUD_ELEM_INVENTORY: { InventoryList *inv = inventory->getList(e->text); + if (!inv) + warningstream << "HUD: Unknown inventory list. name=" << e->text << std::endl; drawItems(pos, v2s32(e->offset.X, e->offset.Y), e->number, 0, inv, e->item, e->dir); break; } @@ -666,7 +676,7 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, // Rectangles for 1/2 the "off state" texture core::rect<s32> srchalfrect2, dsthalfrect2; - if (count % 2 == 1) { + if (count % 2 == 1 || maxcount % 2 == 1) { // Need to draw halves: Calculate rectangles srchalfrect = calculate_clipping_rect(srcd, steppos); dsthalfrect = calculate_clipping_rect(dstd, steppos); @@ -701,7 +711,7 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, } } - if (stat_texture_bg && maxcount > count / 2) { + if (stat_texture_bg && maxcount > count) { // Draw "off state" textures s32 start_offset; if (count % 2 == 1) @@ -721,8 +731,7 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, if (maxcount % 2 == 1) { draw2DImageFilterScaled(driver, stat_texture_bg, - dsthalfrect + p, srchalfrect, - NULL, colors, true); + dsthalfrect + p, srchalfrect, NULL, colors, true); } } } @@ -1001,11 +1010,19 @@ void drawItemStack( bool draw_overlay = false; + bool has_mesh = false; + ItemMesh *imesh; + + core::rect<s32> viewrect = rect; + if (clip != nullptr) + viewrect.clipAgainst(*clip); + // Render as mesh if animated or no inventory image if ((enable_animations && rotation_kind < IT_ROT_NONE) || def.inventory_image.empty()) { - ItemMesh *imesh = client->idef()->getWieldMesh(def.name, client); - if (!imesh || !imesh->mesh) - return; + imesh = client->idef()->getWieldMesh(def.name, client); + has_mesh = imesh && imesh->mesh; + } + if (has_mesh) { scene::IMesh *mesh = imesh->mesh; driver->clearBuffers(video::ECBF_DEPTH); s32 delta = 0; @@ -1021,9 +1038,6 @@ void drawItemStack( core::rect<s32> oldViewPort = driver->getViewPort(); core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION); core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW); - core::rect<s32> viewrect = rect; - if (clip) - viewrect.clipAgainst(*clip); core::matrix4 ProjMatrix; ProjMatrix.buildProjectionMatrixOrthoLH(2.0f, 2.0f, -1.0f, 100.0f); @@ -1097,10 +1111,17 @@ void drawItemStack( draw_overlay = def.type == ITEM_NODE && def.inventory_image.empty(); } else { // Otherwise just draw as 2D video::ITexture *texture = client->idef()->getInventoryTexture(def.name, client); - if (!texture) - return; - video::SColor color = - client->idef()->getItemstackColor(item, client); + video::SColor color; + if (texture) { + color = client->idef()->getItemstackColor(item, client); + } else { + color = video::SColor(255, 255, 255, 255); + ITextureSource *tsrc = client->getTextureSource(); + texture = tsrc->getTexture("no_texture.png"); + if (!texture) + return; + } + const video::SColor colors[] = { color, color, color, color }; draw2DImageFilterScaled(driver, texture, rect, @@ -1160,24 +1181,68 @@ void drawItemStack( driver->draw2DRectangle(color, progressrect2, clip); } - if (font != NULL && item.count >= 2) { + const std::string &count_text = item.metadata.getString("count_meta"); + if (font != nullptr && (item.count >= 2 || !count_text.empty())) { // Get the item count as a string - std::string text = itos(item.count); - v2u32 dim = font->getDimension(utf8_to_wide(text).c_str()); + std::string text = count_text.empty() ? itos(item.count) : count_text; + v2u32 dim = font->getDimension(utf8_to_wide(unescape_enriched(text)).c_str()); v2s32 sdim(dim.X, dim.Y); core::rect<s32> rect2( - /*rect.UpperLeftCorner, - core::dimension2d<u32>(rect.getWidth(), 15)*/ rect.LowerRightCorner - sdim, - sdim + rect.LowerRightCorner ); - video::SColor bgcolor(128, 0, 0, 0); - driver->draw2DRectangle(bgcolor, rect2, clip); + // get the count alignment + s32 count_alignment = stoi(item.metadata.getString("count_alignment")); + if (count_alignment != 0) { + s32 a_x = count_alignment & 3; + s32 a_y = (count_alignment >> 2) & 3; + + s32 x1, x2, y1, y2; + switch (a_x) { + case 1: // left + x1 = rect.UpperLeftCorner.X; + x2 = x1 + sdim.X; + break; + case 2: // middle + x1 = (rect.UpperLeftCorner.X + rect.LowerRightCorner.X - sdim.X) / 2; + x2 = x1 + sdim.X; + break; + case 3: // right + x2 = rect.LowerRightCorner.X; + x1 = x2 - sdim.X; + break; + default: // 0 = default + x1 = rect2.UpperLeftCorner.X; + x2 = rect2.LowerRightCorner.X; + break; + } + + switch (a_y) { + case 1: // up + y1 = rect.UpperLeftCorner.Y; + y2 = y1 + sdim.Y; + break; + case 2: // middle + y1 = (rect.UpperLeftCorner.Y + rect.LowerRightCorner.Y - sdim.Y) / 2; + y2 = y1 + sdim.Y; + break; + case 3: // down + y2 = rect.LowerRightCorner.Y; + y1 = y2 - sdim.Y; + break; + default: // 0 = default + y1 = rect2.UpperLeftCorner.Y; + y2 = rect2.LowerRightCorner.Y; + break; + } + + rect2 = core::rect<s32>(x1, y1, x2, y2); + } video::SColor color(255, 255, 255, 255); - font->draw(text.c_str(), rect2, color, false, false, clip); + font->draw(utf8_to_wide(text).c_str(), rect2, color, false, false, &viewrect); } } diff --git a/src/client/imagefilters.cpp b/src/client/imagefilters.cpp index 97ad094e5..c9d1504ad 100644 --- a/src/client/imagefilters.cpp +++ b/src/client/imagefilters.cpp @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <cmath> #include <cassert> #include <vector> +#include <algorithm> // Simple 2D bitmap class with just the functionality needed here class Bitmap { @@ -123,7 +124,7 @@ void imageCleanTransparent(video::IImage *src, u32 threshold) // Ignore pixels we haven't processed if (!bitmap.get(sx, sy)) continue; - + // Add RGB values weighted by alpha IF the pixel is opaque, otherwise // use full weight since we want to propagate colors. video::SColor d = src->getPixel(sx, sy); diff --git a/src/client/inputhandler.h b/src/client/inputhandler.h index 951ec8884..47a61d4b8 100644 --- a/src/client/inputhandler.h +++ b/src/client/inputhandler.h @@ -153,8 +153,14 @@ public: // in the subsequent iteration of Game::processPlayerInteraction bool WasKeyReleased(const KeyPress &keycode) const { return keyWasReleased[keycode]; } - void listenForKey(const KeyPress &keyCode) { keysListenedFor.set(keyCode); } - void dontListenForKeys() { keysListenedFor.clear(); } + void listenForKey(const KeyPress &keyCode) + { + keysListenedFor.set(keyCode); + } + void dontListenForKeys() + { + keysListenedFor.clear(); + } s32 getMouseWheel() { @@ -190,14 +196,14 @@ public: #endif } - s32 mouse_wheel = 0; - JoystickController *joystick = nullptr; #ifdef HAVE_TOUCHSCREENGUI TouchScreenGUI *m_touchscreengui; #endif + s32 mouse_wheel = 0; + // The current state of keys KeyList keyIsDown; @@ -274,6 +280,12 @@ public: { m_receiver->joystick = &joystick; } + + virtual ~RealInputHandler() + { + m_receiver->joystick = nullptr; + } + virtual bool isKeyDown(GameKeyType k) { return m_receiver->IsKeyDown(keycache.key[k]) || joystick.isKeyDown(k); @@ -299,6 +311,7 @@ public: { return m_receiver->WasKeyReleased(keycache.key[k]) || joystick.wasKeyReleased(k); } + virtual float getMovementSpeed() { bool f = m_receiver->IsKeyDown(keycache.key[KeyType::FORWARD]), @@ -318,6 +331,7 @@ public: } return joystick.getMovementSpeed(); } + virtual float getMovementDirection() { float x = 0, z = 0; @@ -337,10 +351,12 @@ public: else return joystick.getMovementDirection(); } + virtual bool cancelPressed() { return wasKeyDown(KeyType::ESC) || m_receiver->WasKeyDown(CancelKey); } + virtual void clearWasKeyPressed() { m_receiver->clearWasKeyPressed(); @@ -349,17 +365,21 @@ public: { m_receiver->clearWasKeyReleased(); } + virtual void listenForKey(const KeyPress &keyCode) { m_receiver->listenForKey(keyCode); } - virtual void dontListenForKeys() { m_receiver->dontListenForKeys(); } + virtual void dontListenForKeys() + { + m_receiver->dontListenForKeys(); + } + virtual v2s32 getMousePos() { - if (RenderingEngine::get_raw_device()->getCursorControl()) { - return RenderingEngine::get_raw_device() - ->getCursorControl() - ->getPosition(); + auto control = RenderingEngine::get_raw_device()->getCursorControl(); + if (control) { + return control->getPosition(); } return m_mousepos; @@ -367,16 +387,18 @@ public: virtual void setMousePos(s32 x, s32 y) { - if (RenderingEngine::get_raw_device()->getCursorControl()) { - RenderingEngine::get_raw_device() - ->getCursorControl() - ->setPosition(x, y); + auto control = RenderingEngine::get_raw_device()->getCursorControl(); + if (control) { + control->setPosition(x, y); } else { m_mousepos = v2s32(x, y); } } - virtual s32 getMouseWheel() { return m_receiver->getMouseWheel(); } + virtual s32 getMouseWheel() + { + return m_receiver->getMouseWheel(); + } void clear() { @@ -384,7 +406,7 @@ public: m_receiver->clearInput(); } - private: +private: MyEventReceiver *m_receiver = nullptr; v2s32 m_mousepos; }; diff --git a/src/client/joystick_controller.cpp b/src/client/joystick_controller.cpp index 630565d8d..aae73c62d 100644 --- a/src/client/joystick_controller.cpp +++ b/src/client/joystick_controller.cpp @@ -154,6 +154,54 @@ JoystickLayout create_xbox_layout() return jlo; } +JoystickLayout create_dragonrise_gamecube_layout() +{ + JoystickLayout jlo; + + jlo.axes_deadzone = 7000; + + const JoystickAxisLayout axes[JA_COUNT] = { + // Control Stick + {0, 1}, // JA_SIDEWARD_MOVE + {1, 1}, // JA_FORWARD_MOVE + + // C-Stick + {3, 1}, // JA_FRUSTUM_HORIZONTAL + {4, 1}, // JA_FRUSTUM_VERTICAL + }; + memcpy(jlo.axes, axes, sizeof(jlo.axes)); + + // The center button + JLO_B_PB(KeyType::ESC, 1 << 9, 1 << 9); // Start/Pause Button + + // Front right buttons + JLO_B_PB(KeyType::JUMP, 1 << 2, 1 << 2); // A Button + JLO_B_PB(KeyType::SNEAK, 1 << 3, 1 << 3); // B Button + JLO_B_PB(KeyType::DROP, 1 << 0, 1 << 0); // Y Button + JLO_B_PB(KeyType::AUX1, 1 << 1, 1 << 1); // X Button + + // Triggers + JLO_B_PB(KeyType::DIG, 1 << 4, 1 << 4); // L Trigger + JLO_B_PB(KeyType::PLACE, 1 << 5, 1 << 5); // R Trigger + JLO_B_PB(KeyType::INVENTORY, 1 << 6, 1 << 6); // Z Button + + // D-Pad + JLO_A_PB(KeyType::HOTBAR_PREV, 5, 1, jlo.axes_deadzone); // left + JLO_A_PB(KeyType::HOTBAR_NEXT, 5, -1, jlo.axes_deadzone); // right + // Axis are hard to actuate independantly, best to leave up and down unused. + //JLO_A_PB(0, 6, 1, jlo.axes_deadzone); // up + //JLO_A_PB(0, 6, -1, jlo.axes_deadzone); // down + + // Movements tied to Control Stick, important for vessels + JLO_A_PB(KeyType::LEFT, 0, 1, jlo.axes_deadzone); + JLO_A_PB(KeyType::RIGHT, 0, -1, jlo.axes_deadzone); + JLO_A_PB(KeyType::FORWARD, 1, 1, jlo.axes_deadzone); + JLO_A_PB(KeyType::BACKWARD, 1, -1, jlo.axes_deadzone); + + return jlo; +} + + JoystickController::JoystickController() : doubling_dtime(g_settings->getFloat("repeat_joystick_button_time")) { @@ -188,6 +236,8 @@ void JoystickController::setLayoutFromControllerName(const std::string &name) { if (lowercase(name).find("xbox") != std::string::npos) { m_layout = create_xbox_layout(); + } else if (lowercase(name).find("dragonrise_gamecube") != std::string::npos) { + m_layout = create_dragonrise_gamecube_layout(); } else { m_layout = create_default_layout(); } diff --git a/src/client/localplayer.cpp b/src/client/localplayer.cpp index ca61f3a11..c4d9b9845 100644 --- a/src/client/localplayer.cpp +++ b/src/client/localplayer.cpp @@ -232,8 +232,9 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, pp = floatToInt(position + v3f(0.0f, BS * 0.1f, 0.0f), BS); node = map->getNode(pp, &is_valid_position); if (is_valid_position) { - in_liquid = nodemgr->get(node.getContent()).isLiquid(); - liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity; + const ContentFeatures &cf = nodemgr->get(node.getContent()); + in_liquid = cf.liquid_move_physics; + move_resistance = cf.move_resistance; } else { in_liquid = false; } @@ -243,8 +244,9 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, pp = floatToInt(position + v3f(0.0f, BS * 0.5f, 0.0f), BS); node = map->getNode(pp, &is_valid_position); if (is_valid_position) { - in_liquid = nodemgr->get(node.getContent()).isLiquid(); - liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity; + const ContentFeatures &cf = nodemgr->get(node.getContent()); + in_liquid = cf.liquid_move_physics; + move_resistance = cf.move_resistance; } else { in_liquid = false; } @@ -257,7 +259,7 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, pp = floatToInt(position + v3f(0.0f), BS); node = map->getNode(pp, &is_valid_position); if (is_valid_position) { - in_liquid_stable = nodemgr->get(node.getContent()).isLiquid(); + in_liquid_stable = nodemgr->get(node.getContent()).liquid_move_physics; } else { in_liquid_stable = false; } @@ -694,19 +696,21 @@ v3s16 LocalPlayer::getStandingNodePos() v3s16 LocalPlayer::getFootstepNodePos() { + v3f feet_pos = getPosition() + v3f(0.0f, m_collisionbox.MinEdge.Y, 0.0f); + // Emit swimming sound if the player is in liquid if (in_liquid_stable) - return floatToInt(getPosition(), BS); + return floatToInt(feet_pos, BS); // BS * 0.05 below the player's feet ensures a 1/16th height // nodebox is detected instead of the node below it. if (touching_ground) - return floatToInt(getPosition() - v3f(0.0f, BS * 0.05f, 0.0f), BS); + return floatToInt(feet_pos - v3f(0.0f, BS * 0.05f, 0.0f), BS); // A larger distance below is necessary for a footstep sound // when landing after a jump or fall. BS * 0.5 ensures water // sounds when swimming in 1 node deep water. - return floatToInt(getPosition() - v3f(0.0f, BS * 0.5f, 0.0f), BS); + return floatToInt(feet_pos - v3f(0.0f, BS * 0.5f, 0.0f), BS); } v3s16 LocalPlayer::getLightPosition() const @@ -846,8 +850,9 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d, pp = floatToInt(position + v3f(0.0f, BS * 0.1f, 0.0f), BS); node = map->getNode(pp, &is_valid_position); if (is_valid_position) { - in_liquid = nodemgr->get(node.getContent()).isLiquid(); - liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity; + const ContentFeatures &cf = nodemgr->get(node.getContent()); + in_liquid = cf.liquid_move_physics; + move_resistance = cf.move_resistance; } else { in_liquid = false; } @@ -856,8 +861,9 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d, pp = floatToInt(position + v3f(0.0f, BS * 0.5f, 0.0f), BS); node = map->getNode(pp, &is_valid_position); if (is_valid_position) { - in_liquid = nodemgr->get(node.getContent()).isLiquid(); - liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity; + const ContentFeatures &cf = nodemgr->get(node.getContent()); + in_liquid = cf.liquid_move_physics; + move_resistance = cf.move_resistance; } else { in_liquid = false; } @@ -869,7 +875,7 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d, pp = floatToInt(position + v3f(0.0f), BS); node = map->getNode(pp, &is_valid_position); if (is_valid_position) - in_liquid_stable = nodemgr->get(node.getContent()).isLiquid(); + in_liquid_stable = nodemgr->get(node.getContent()).liquid_move_physics; else in_liquid_stable = false; @@ -1138,10 +1144,8 @@ void LocalPlayer::handleAutojump(f32 dtime, Environment *env, if (m_autojump) return; - bool control_forward = keyPressed & (1 << 0); - bool could_autojump = - m_can_jump && !control.jump && !control.sneak && control_forward; + m_can_jump && !control.jump && !control.sneak && control.isMoving(); if (!could_autojump) return; diff --git a/src/client/localplayer.h b/src/client/localplayer.h index eaac216d3..ebc67c4f8 100644 --- a/src/client/localplayer.h +++ b/src/client/localplayer.h @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "environment.h" #include "constants.h" #include "settings.h" +#include "lighting.h" #include <list> class Client; @@ -56,8 +57,8 @@ public: bool in_liquid = false; // This is more stable and defines the maximum speed of the player bool in_liquid_stable = false; - // Gets the viscosity of water to calculate friction - u8 liquid_viscosity = 0; + // Slows down the player when moving through + u8 move_resistance = 0; bool is_climbing = false; bool swimming_vertical = false; bool swimming_pitch = false; @@ -87,7 +88,7 @@ public: v3f last_speed; float last_pitch = 0.0f; float last_yaw = 0.0f; - unsigned int last_keyPressed = 0; + u32 last_keyPressed = 0; u8 last_camera_fov = 0; u8 last_wanted_range = 0; @@ -187,6 +188,8 @@ public: added_velocity += vel; } + inline Lighting& getLighting() { return m_lighting; } + void tryReattach(int id); bool isWaitingForReattach() const; @@ -247,4 +250,5 @@ private: GenericCAO *m_cao = nullptr; Client *m_client; + Lighting m_lighting; }; diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index 52729632a..f0d43ec7e 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -30,6 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/meshgen/collector.h" #include "client/renderingengine.h" #include <array> +#include <algorithm> /* MeshMakeData @@ -1046,6 +1047,173 @@ static void applyTileColor(PreMeshBuffer &pmb) } /* + MapBlockBspTree +*/ + +void MapBlockBspTree::buildTree(const std::vector<MeshTriangle> *triangles) +{ + this->triangles = triangles; + + nodes.clear(); + + // assert that triangle index can fit into s32 + assert(triangles->size() <= 0x7FFFFFFFL); + std::vector<s32> indexes; + indexes.reserve(triangles->size()); + for (u32 i = 0; i < triangles->size(); i++) + indexes.push_back(i); + + root = buildTree(v3f(1, 0, 0), v3f(85, 85, 85), 40, indexes, 0); +} + +/** + * @brief Find a candidate plane to split a set of triangles in two + * + * The candidate plane is represented by one of the triangles from the set. + * + * @param list Vector of indexes of the triangles in the set + * @param triangles Vector of all triangles in the BSP tree + * @return Address of the triangle that represents the proposed split plane + */ +static const MeshTriangle *findSplitCandidate(const std::vector<s32> &list, const std::vector<MeshTriangle> &triangles) +{ + // find the center of the cluster. + v3f center(0, 0, 0); + size_t n = list.size(); + for (s32 i : list) { + center += triangles[i].centroid / n; + } + + // find the triangle with the largest area and closest to the center + const MeshTriangle *candidate_triangle = &triangles[list[0]]; + const MeshTriangle *ith_triangle; + for (s32 i : list) { + ith_triangle = &triangles[i]; + if (ith_triangle->areaSQ > candidate_triangle->areaSQ || + (ith_triangle->areaSQ == candidate_triangle->areaSQ && + ith_triangle->centroid.getDistanceFromSQ(center) < candidate_triangle->centroid.getDistanceFromSQ(center))) { + candidate_triangle = ith_triangle; + } + } + return candidate_triangle; +} + +s32 MapBlockBspTree::buildTree(v3f normal, v3f origin, float delta, const std::vector<s32> &list, u32 depth) +{ + // if the list is empty, don't bother + if (list.empty()) + return -1; + + // if there is only one triangle, or the delta is insanely small, this is a leaf node + if (list.size() == 1 || delta < 0.01) { + nodes.emplace_back(normal, origin, list, -1, -1); + return nodes.size() - 1; + } + + std::vector<s32> front_list; + std::vector<s32> back_list; + std::vector<s32> node_list; + + // split the list + for (s32 i : list) { + const MeshTriangle &triangle = (*triangles)[i]; + float factor = normal.dotProduct(triangle.centroid - origin); + if (factor == 0) + node_list.push_back(i); + else if (factor > 0) + front_list.push_back(i); + else + back_list.push_back(i); + } + + // define the new split-plane + v3f candidate_normal(normal.Z, normal.X, normal.Y); + float candidate_delta = delta; + if (depth % 3 == 2) + candidate_delta /= 2; + + s32 front_index = -1; + s32 back_index = -1; + + if (!front_list.empty()) { + v3f next_normal = candidate_normal; + v3f next_origin = origin + delta * normal; + float next_delta = candidate_delta; + if (next_delta < 10) { + const MeshTriangle *candidate = findSplitCandidate(front_list, *triangles); + next_normal = candidate->getNormal(); + next_origin = candidate->centroid; + } + front_index = buildTree(next_normal, next_origin, next_delta, front_list, depth + 1); + + // if there are no other triangles, don't create a new node + if (back_list.empty() && node_list.empty()) + return front_index; + } + + if (!back_list.empty()) { + v3f next_normal = candidate_normal; + v3f next_origin = origin - delta * normal; + float next_delta = candidate_delta; + if (next_delta < 10) { + const MeshTriangle *candidate = findSplitCandidate(back_list, *triangles); + next_normal = candidate->getNormal(); + next_origin = candidate->centroid; + } + + back_index = buildTree(next_normal, next_origin, next_delta, back_list, depth + 1); + + // if there are no other triangles, don't create a new node + if (front_list.empty() && node_list.empty()) + return back_index; + } + + nodes.emplace_back(normal, origin, node_list, front_index, back_index); + + return nodes.size() - 1; +} + +void MapBlockBspTree::traverse(s32 node, v3f viewpoint, std::vector<s32> &output) const +{ + if (node < 0) return; // recursion break; + + const TreeNode &n = nodes[node]; + float factor = n.normal.dotProduct(viewpoint - n.origin); + + if (factor > 0) + traverse(n.back_ref, viewpoint, output); + else + traverse(n.front_ref, viewpoint, output); + + if (factor != 0) + for (s32 i : n.triangle_refs) + output.push_back(i); + + if (factor > 0) + traverse(n.front_ref, viewpoint, output); + else + traverse(n.back_ref, viewpoint, output); +} + + + +/* + PartialMeshBuffer +*/ + +void PartialMeshBuffer::beforeDraw() const +{ + // Patch the indexes in the mesh buffer before draw + + m_buffer->Indices.clear(); + if (!m_vertex_indexes.empty()) { + for (auto index : m_vertex_indexes) + m_buffer->Indices.push_back(index); + } + m_buffer->setDirty(scene::EBT_INDEX); +} + +/* MapBlockMesh */ @@ -1152,6 +1320,9 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): Convert MeshCollector to SMesh */ + const bool desync_animations = g_settings->getBool( + "desynchronize_mapblock_texture_animation"); + for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) { for(u32 i = 0; i < collector.prebuffers[layer].size(); i++) { @@ -1181,18 +1352,18 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): // - Texture animation if (p.layer.material_flags & MATERIAL_FLAG_ANIMATION) { // Add to MapBlockMesh in order to animate these tiles - m_animation_tiles[std::pair<u8, u32>(layer, i)] = p.layer; - m_animation_frames[std::pair<u8, u32>(layer, i)] = 0; - if (g_settings->getBool( - "desynchronize_mapblock_texture_animation")) { + auto &info = m_animation_info[{layer, i}]; + info.tile = p.layer; + info.frame = 0; + if (desync_animations) { // Get starting position from noise - m_animation_frame_offsets[std::pair<u8, u32>(layer, i)] = + info.frame_offset = 100000 * (2.0 + noise3d( data->m_blockpos.X, data->m_blockpos.Y, data->m_blockpos.Z, 0)); } else { // Play all synchronized - m_animation_frame_offsets[std::pair<u8, u32>(layer, i)] = 0; + info.frame_offset = 0; } // Replace tile texture with the first animation frame p.layer.texture = (*p.layer.frames)[0].texture; @@ -1203,19 +1374,23 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): // Dummy sunlight to handle non-sunlit areas video::SColorf sunlight; get_sunlight_color(&sunlight, 0); - u32 vertex_count = p.vertices.size(); + + std::map<u32, video::SColor> colors; + const u32 vertex_count = p.vertices.size(); for (u32 j = 0; j < vertex_count; j++) { video::SColor *vc = &p.vertices[j].Color; video::SColor copy = *vc; if (vc->getAlpha() == 0) // No sunlight - no need to animate final_color_blend(vc, copy, sunlight); // Finalize color else // Record color to animate - m_daynight_diffs[std::pair<u8, u32>(layer, i)][j] = copy; + colors[j] = copy; // The sunlight ratio has been stored, // delete alpha (for the final rendering). vc->setAlpha(255); } + if (!colors.empty()) + m_daynight_diffs[{layer, i}] = std::move(colors); } // Create material @@ -1241,8 +1416,31 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): scene::SMeshBuffer *buf = new scene::SMeshBuffer(); buf->Material = material; - buf->append(&p.vertices[0], p.vertices.size(), - &p.indices[0], p.indices.size()); + switch (p.layer.material_type) { + // list of transparent materials taken from tile.h + case TILE_MATERIAL_ALPHA: + case TILE_MATERIAL_LIQUID_TRANSPARENT: + case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT: + { + buf->append(&p.vertices[0], p.vertices.size(), + &p.indices[0], 0); + + MeshTriangle t; + t.buffer = buf; + for (u32 i = 0; i < p.indices.size(); i += 3) { + t.p1 = p.indices[i]; + t.p2 = p.indices[i + 1]; + t.p3 = p.indices[i + 2]; + t.updateAttributes(); + m_transparent_triangles.push_back(t); + } + } + break; + default: + buf->append(&p.vertices[0], p.vertices.size(), + &p.indices[0], p.indices.size()); + break; + } mesh->addMeshBuffer(buf); buf->drop(); } @@ -1255,23 +1453,26 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): } //std::cout<<"added "<<fastfaces.getSize()<<" faces."<<std::endl; + m_bsp_tree.buildTree(&m_transparent_triangles); // Check if animation is required for this mesh m_has_animation = !m_crack_materials.empty() || !m_daynight_diffs.empty() || - !m_animation_tiles.empty(); + !m_animation_info.empty(); } MapBlockMesh::~MapBlockMesh() { for (scene::IMesh *m : m_mesh) { +#if IRRLICHT_VERSION_MT_REVISION < 5 if (m_enable_vbo) { for (u32 i = 0; i < m->getMeshBufferCount(); i++) { scene::IMeshBuffer *buf = m->getMeshBuffer(i); RenderingEngine::get_video_driver()->removeHardwareBuffer(buf); } } +#endif m->drop(); } delete m_minimap_mapblock; @@ -1292,25 +1493,22 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, for (auto &crack_material : m_crack_materials) { scene::IMeshBuffer *buf = m_mesh[crack_material.first.first]-> getMeshBuffer(crack_material.first.second); - std::string basename = crack_material.second; // Create new texture name from original - std::ostringstream os; - os << basename << crack; + std::string s = crack_material.second + itos(crack); u32 new_texture_id = 0; video::ITexture *new_texture = - m_tsrc->getTextureForMesh(os.str(), &new_texture_id); + m_tsrc->getTextureForMesh(s, &new_texture_id); buf->getMaterial().setTexture(0, new_texture); - // If the current material is also animated, - // update animation info - auto anim_iter = m_animation_tiles.find(crack_material.first); - if (anim_iter != m_animation_tiles.end()) { - TileLayer &tile = anim_iter->second; + // If the current material is also animated, update animation info + auto anim_it = m_animation_info.find(crack_material.first); + if (anim_it != m_animation_info.end()) { + TileLayer &tile = anim_it->second.tile; tile.texture = new_texture; tile.texture_id = new_texture_id; // force animation update - m_animation_frames[crack_material.first] = -1; + anim_it->second.frame = -1; } } @@ -1318,28 +1516,25 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, } // Texture animation - for (auto &animation_tile : m_animation_tiles) { - const TileLayer &tile = animation_tile.second; + for (auto &it : m_animation_info) { + const TileLayer &tile = it.second.tile; // Figure out current frame - int frameoffset = m_animation_frame_offsets[animation_tile.first]; - int frame = (int)(time * 1000 / tile.animation_frame_length_ms - + frameoffset) % tile.animation_frame_count; + int frameno = (int)(time * 1000 / tile.animation_frame_length_ms + + it.second.frame_offset) % tile.animation_frame_count; // If frame doesn't change, skip - if (frame == m_animation_frames[animation_tile.first]) + if (frameno == it.second.frame) continue; - m_animation_frames[animation_tile.first] = frame; + it.second.frame = frameno; - scene::IMeshBuffer *buf = m_mesh[animation_tile.first.first]-> - getMeshBuffer(animation_tile.first.second); + scene::IMeshBuffer *buf = m_mesh[it.first.first]->getMeshBuffer(it.first.second); - const FrameSpec &animation_frame = (*tile.frames)[frame]; - buf->getMaterial().setTexture(0, animation_frame.texture); + const FrameSpec &frame = (*tile.frames)[frameno]; + buf->getMaterial().setTexture(0, frame.texture); if (m_enable_shaders) { - if (animation_frame.normal_texture) - buf->getMaterial().setTexture(1, - animation_frame.normal_texture); - buf->getMaterial().setTexture(2, animation_frame.flags_texture); + if (frame.normal_texture) + buf->getMaterial().setTexture(1, frame.normal_texture); + buf->getMaterial().setTexture(2, frame.flags_texture); } } @@ -1366,6 +1561,67 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, return true; } +void MapBlockMesh::updateTransparentBuffers(v3f camera_pos, v3s16 block_pos) +{ + // nothing to do if the entire block is opaque + if (m_transparent_triangles.empty()) + return; + + v3f block_posf = intToFloat(block_pos * MAP_BLOCKSIZE, BS); + v3f rel_camera_pos = camera_pos - block_posf; + + std::vector<s32> triangle_refs; + m_bsp_tree.traverse(rel_camera_pos, triangle_refs); + + // arrange index sequences into partial buffers + m_transparent_buffers.clear(); + + scene::SMeshBuffer *current_buffer = nullptr; + std::vector<u16> current_strain; + for (auto i : triangle_refs) { + const auto &t = m_transparent_triangles[i]; + if (current_buffer != t.buffer) { + if (current_buffer) { + m_transparent_buffers.emplace_back(current_buffer, current_strain); + current_strain.clear(); + } + current_buffer = t.buffer; + } + current_strain.push_back(t.p1); + current_strain.push_back(t.p2); + current_strain.push_back(t.p3); + } + + if (!current_strain.empty()) + m_transparent_buffers.emplace_back(current_buffer, current_strain); +} + +void MapBlockMesh::consolidateTransparentBuffers() +{ + m_transparent_buffers.clear(); + + scene::SMeshBuffer *current_buffer = nullptr; + std::vector<u16> current_strain; + + // use the fact that m_transparent_triangles is already arranged by buffer + for (const auto &t : m_transparent_triangles) { + if (current_buffer != t.buffer) { + if (current_buffer != nullptr) { + this->m_transparent_buffers.emplace_back(current_buffer, current_strain); + current_strain.clear(); + } + current_buffer = t.buffer; + } + current_strain.push_back(t.p1); + current_strain.push_back(t.p2); + current_strain.push_back(t.p3); + } + + if (!current_strain.empty()) { + this->m_transparent_buffers.emplace_back(current_buffer, current_strain); + } +} + video::SColor encode_light(u16 light, u8 emissive_light) { // Get components diff --git a/src/client/mapblock_mesh.h b/src/client/mapblock_mesh.h index 80075fce2..5e2d70b75 100644 --- a/src/client/mapblock_mesh.h +++ b/src/client/mapblock_mesh.h @@ -71,6 +71,91 @@ struct MeshMakeData void setSmoothLighting(bool smooth_lighting); }; +// represents a triangle as indexes into the vertex buffer in SMeshBuffer +class MeshTriangle +{ +public: + scene::SMeshBuffer *buffer; + u16 p1, p2, p3; + v3f centroid; + float areaSQ; + + void updateAttributes() + { + v3f v1 = buffer->getPosition(p1); + v3f v2 = buffer->getPosition(p2); + v3f v3 = buffer->getPosition(p3); + + centroid = (v1 + v2 + v3) / 3; + areaSQ = (v2-v1).crossProduct(v3-v1).getLengthSQ() / 4; + } + + v3f getNormal() const { + v3f v1 = buffer->getPosition(p1); + v3f v2 = buffer->getPosition(p2); + v3f v3 = buffer->getPosition(p3); + + return (v2-v1).crossProduct(v3-v1); + } +}; + +/** + * Implements a binary space partitioning tree + * See also: https://en.wikipedia.org/wiki/Binary_space_partitioning + */ +class MapBlockBspTree +{ +public: + MapBlockBspTree() {} + + void buildTree(const std::vector<MeshTriangle> *triangles); + + void traverse(v3f viewpoint, std::vector<s32> &output) const + { + traverse(root, viewpoint, output); + } + +private: + // Tree node definition; + struct TreeNode + { + v3f normal; + v3f origin; + std::vector<s32> triangle_refs; + s32 front_ref; + s32 back_ref; + + TreeNode() = default; + TreeNode(v3f normal, v3f origin, const std::vector<s32> &triangle_refs, s32 front_ref, s32 back_ref) : + normal(normal), origin(origin), triangle_refs(triangle_refs), front_ref(front_ref), back_ref(back_ref) + {} + }; + + + s32 buildTree(v3f normal, v3f origin, float delta, const std::vector<s32> &list, u32 depth); + void traverse(s32 node, v3f viewpoint, std::vector<s32> &output) const; + + const std::vector<MeshTriangle> *triangles = nullptr; // this reference is managed externally + std::vector<TreeNode> nodes; // list of nodes + s32 root = -1; // index of the root node +}; + +class PartialMeshBuffer +{ +public: + PartialMeshBuffer(scene::SMeshBuffer *buffer, const std::vector<u16> &vertex_indexes) : + m_buffer(buffer), m_vertex_indexes(vertex_indexes) + {} + + scene::IMeshBuffer *getBuffer() const { return m_buffer; } + const std::vector<u16> &getVertexIndexes() const { return m_vertex_indexes; } + + void beforeDraw() const; +private: + scene::SMeshBuffer *m_buffer; + std::vector<u16> m_vertex_indexes; +}; + /* Holds a mesh for a mapblock. @@ -125,9 +210,25 @@ public: m_animation_force_timer--; } + /// update transparent buffers to render towards the camera + void updateTransparentBuffers(v3f camera_pos, v3s16 block_pos); + void consolidateTransparentBuffers(); + + /// get the list of transparent buffers + const std::vector<PartialMeshBuffer> &getTransparentBuffers() const + { + return this->m_transparent_buffers; + } + std::set<v3s16> esp_nodes; private: + struct AnimationInfo { + int frame; // last animation frame + int frame_offset; + TileLayer tile; + }; + scene::IMesh *m_mesh[MAX_TILE_LAYERS]; MinimapMapblock *m_minimap_mapblock; ITextureSource *m_tsrc; @@ -146,12 +247,10 @@ private: // Maps mesh and mesh buffer (i.e. material) indices to base texture names std::map<std::pair<u8, u32>, std::string> m_crack_materials; - // Animation info: texture animationi + // Animation info: texture animation // Maps mesh and mesh buffer indices to TileSpecs // Keys are pairs of (mesh index, buffer index in the mesh) - std::map<std::pair<u8, u32>, TileLayer> m_animation_tiles; - std::map<std::pair<u8, u32>, int> m_animation_frames; // last animation frame - std::map<std::pair<u8, u32>, int> m_animation_frame_offsets; + std::map<std::pair<u8, u32>, AnimationInfo> m_animation_info; // Animation info: day/night transitions // Last daynight_ratio value passed to animate() @@ -160,6 +259,13 @@ private: // of sunlit vertices // Keys are pairs of (mesh index, buffer index in the mesh) std::map<std::pair<u8, u32>, std::map<u32, video::SColor > > m_daynight_diffs; + + // list of all semitransparent triangles in the mapblock + std::vector<MeshTriangle> m_transparent_triangles; + // Binary Space Partitioning tree for the block + MapBlockBspTree m_bsp_tree; + // Ordered list of references to parts of transparent buffers to draw + std::vector<PartialMeshBuffer> m_transparent_buffers; }; /*! diff --git a/src/client/mesh.cpp b/src/client/mesh.cpp index e43139218..bec72fb5e 100644 --- a/src/client/mesh.cpp +++ b/src/client/mesh.cpp @@ -331,6 +331,9 @@ void recalculateBoundingBox(scene::IMesh *src_mesh) bool checkMeshNormals(scene::IMesh *mesh) { + // Assume correct normals if this many first faces get it right. + static const u16 MAX_FACES_TO_CHECK = 9; + u32 buffer_count = mesh->getMeshBufferCount(); for (u32 i = 0; i < buffer_count; i++) { @@ -344,6 +347,19 @@ bool checkMeshNormals(scene::IMesh *mesh) if (!std::isfinite(length) || length < 1e-10f) return false; + + const u16 count = MYMIN(MAX_FACES_TO_CHECK * 3, buffer->getIndexCount() - 3); + for (u16 i = 0; i < count; i += 3) { + + core::plane3df plane(buffer->getPosition(buffer->getIndices()[i]), + buffer->getPosition(buffer->getIndices()[i+1]), + buffer->getPosition(buffer->getIndices()[i+2])); + + for (u16 j = 0; j < 3; j++) + if (plane.Normal.dotProduct(buffer->getNormal(buffer->getIndices()[i+j])) <= 0) + return false; + } + } return true; @@ -498,592 +514,3 @@ scene::IMesh* convertNodeboxesToMesh(const std::vector<aabb3f> &boxes, } return dst_mesh; } - -struct vcache -{ - core::array<u32> tris; - float score; - s16 cachepos; - u16 NumActiveTris; -}; - -struct tcache -{ - u16 ind[3]; - float score; - bool drawn; -}; - -const u16 cachesize = 32; - -float FindVertexScore(vcache *v) -{ - const float CacheDecayPower = 1.5f; - const float LastTriScore = 0.75f; - const float ValenceBoostScale = 2.0f; - const float ValenceBoostPower = 0.5f; - const float MaxSizeVertexCache = 32.0f; - - if (v->NumActiveTris == 0) - { - // No tri needs this vertex! - return -1.0f; - } - - float Score = 0.0f; - int CachePosition = v->cachepos; - if (CachePosition < 0) - { - // Vertex is not in FIFO cache - no score. - } - else - { - if (CachePosition < 3) - { - // This vertex was used in the last triangle, - // so it has a fixed score. - Score = LastTriScore; - } - else - { - // Points for being high in the cache. - const float Scaler = 1.0f / (MaxSizeVertexCache - 3); - Score = 1.0f - (CachePosition - 3) * Scaler; - Score = powf(Score, CacheDecayPower); - } - } - - // Bonus points for having a low number of tris still to - // use the vert, so we get rid of lone verts quickly. - float ValenceBoost = powf(v->NumActiveTris, - -ValenceBoostPower); - Score += ValenceBoostScale * ValenceBoost; - - return Score; -} - -/* - A specialized LRU cache for the Forsyth algorithm. -*/ - -class f_lru -{ - -public: - f_lru(vcache *v, tcache *t): vc(v), tc(t) - { - for (int &i : cache) { - i = -1; - } - } - - // Adds this vertex index and returns the highest-scoring triangle index - u32 add(u16 vert, bool updatetris = false) - { - bool found = false; - - // Mark existing pos as empty - for (u16 i = 0; i < cachesize; i++) - { - if (cache[i] == vert) - { - // Move everything down - for (u16 j = i; j; j--) - { - cache[j] = cache[j - 1]; - } - - found = true; - break; - } - } - - if (!found) - { - if (cache[cachesize-1] != -1) - vc[cache[cachesize-1]].cachepos = -1; - - // Move everything down - for (u16 i = cachesize - 1; i; i--) - { - cache[i] = cache[i - 1]; - } - } - - cache[0] = vert; - - u32 highest = 0; - float hiscore = 0; - - if (updatetris) - { - // Update cache positions - for (u16 i = 0; i < cachesize; i++) - { - if (cache[i] == -1) - break; - - vc[cache[i]].cachepos = i; - vc[cache[i]].score = FindVertexScore(&vc[cache[i]]); - } - - // Update triangle scores - for (int i : cache) { - if (i == -1) - break; - - const u16 trisize = vc[i].tris.size(); - for (u16 t = 0; t < trisize; t++) - { - tcache *tri = &tc[vc[i].tris[t]]; - - tri->score = - vc[tri->ind[0]].score + - vc[tri->ind[1]].score + - vc[tri->ind[2]].score; - - if (tri->score > hiscore) - { - hiscore = tri->score; - highest = vc[i].tris[t]; - } - } - } - } - - return highest; - } - -private: - s32 cache[cachesize]; - vcache *vc; - tcache *tc; -}; - -/** -Vertex cache optimization according to the Forsyth paper: -http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html - -The function is thread-safe (read: you can optimize several meshes in different threads) - -\param mesh Source mesh for the operation. */ -scene::IMesh* createForsythOptimizedMesh(const scene::IMesh *mesh) -{ - if (!mesh) - return 0; - - scene::SMesh *newmesh = new scene::SMesh(); - newmesh->BoundingBox = mesh->getBoundingBox(); - - const u32 mbcount = mesh->getMeshBufferCount(); - - for (u32 b = 0; b < mbcount; ++b) - { - const scene::IMeshBuffer *mb = mesh->getMeshBuffer(b); - - if (mb->getIndexType() != video::EIT_16BIT) - { - //os::Printer::log("Cannot optimize a mesh with 32bit indices", ELL_ERROR); - newmesh->drop(); - return 0; - } - - const u32 icount = mb->getIndexCount(); - const u32 tcount = icount / 3; - const u32 vcount = mb->getVertexCount(); - const u16 *ind = mb->getIndices(); - - vcache *vc = new vcache[vcount]; - tcache *tc = new tcache[tcount]; - - f_lru lru(vc, tc); - - // init - for (u16 i = 0; i < vcount; i++) - { - vc[i].score = 0; - vc[i].cachepos = -1; - vc[i].NumActiveTris = 0; - } - - // First pass: count how many times a vert is used - for (u32 i = 0; i < icount; i += 3) - { - vc[ind[i]].NumActiveTris++; - vc[ind[i + 1]].NumActiveTris++; - vc[ind[i + 2]].NumActiveTris++; - - const u32 tri_ind = i/3; - tc[tri_ind].ind[0] = ind[i]; - tc[tri_ind].ind[1] = ind[i + 1]; - tc[tri_ind].ind[2] = ind[i + 2]; - } - - // Second pass: list of each triangle - for (u32 i = 0; i < tcount; i++) - { - vc[tc[i].ind[0]].tris.push_back(i); - vc[tc[i].ind[1]].tris.push_back(i); - vc[tc[i].ind[2]].tris.push_back(i); - - tc[i].drawn = false; - } - - // Give initial scores - for (u16 i = 0; i < vcount; i++) - { - vc[i].score = FindVertexScore(&vc[i]); - } - for (u32 i = 0; i < tcount; i++) - { - tc[i].score = - vc[tc[i].ind[0]].score + - vc[tc[i].ind[1]].score + - vc[tc[i].ind[2]].score; - } - - switch(mb->getVertexType()) - { - case video::EVT_STANDARD: - { - video::S3DVertex *v = (video::S3DVertex *) mb->getVertices(); - - scene::SMeshBuffer *buf = new scene::SMeshBuffer(); - buf->Material = mb->getMaterial(); - - buf->Vertices.reallocate(vcount); - buf->Indices.reallocate(icount); - - core::map<const video::S3DVertex, const u16> sind; // search index for fast operation - typedef core::map<const video::S3DVertex, const u16>::Node snode; - - // Main algorithm - u32 highest = 0; - u32 drawcalls = 0; - for (;;) - { - if (tc[highest].drawn) - { - bool found = false; - float hiscore = 0; - for (u32 t = 0; t < tcount; t++) - { - if (!tc[t].drawn) - { - if (tc[t].score > hiscore) - { - highest = t; - hiscore = tc[t].score; - found = true; - } - } - } - if (!found) - break; - } - - // Output the best triangle - u16 newind = buf->Vertices.size(); - - snode *s = sind.find(v[tc[highest].ind[0]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[0]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[0]], newind); - newind++; - } - else - { - buf->Indices.push_back(s->getValue()); - } - - s = sind.find(v[tc[highest].ind[1]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[1]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[1]], newind); - newind++; - } - else - { - buf->Indices.push_back(s->getValue()); - } - - s = sind.find(v[tc[highest].ind[2]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[2]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[2]], newind); - } - else - { - buf->Indices.push_back(s->getValue()); - } - - vc[tc[highest].ind[0]].NumActiveTris--; - vc[tc[highest].ind[1]].NumActiveTris--; - vc[tc[highest].ind[2]].NumActiveTris--; - - tc[highest].drawn = true; - - for (u16 j : tc[highest].ind) { - vcache *vert = &vc[j]; - for (u16 t = 0; t < vert->tris.size(); t++) - { - if (highest == vert->tris[t]) - { - vert->tris.erase(t); - break; - } - } - } - - lru.add(tc[highest].ind[0]); - lru.add(tc[highest].ind[1]); - highest = lru.add(tc[highest].ind[2], true); - drawcalls++; - } - - buf->setBoundingBox(mb->getBoundingBox()); - newmesh->addMeshBuffer(buf); - buf->drop(); - } - break; - case video::EVT_2TCOORDS: - { - video::S3DVertex2TCoords *v = (video::S3DVertex2TCoords *) mb->getVertices(); - - scene::SMeshBufferLightMap *buf = new scene::SMeshBufferLightMap(); - buf->Material = mb->getMaterial(); - - buf->Vertices.reallocate(vcount); - buf->Indices.reallocate(icount); - - core::map<const video::S3DVertex2TCoords, const u16> sind; // search index for fast operation - typedef core::map<const video::S3DVertex2TCoords, const u16>::Node snode; - - // Main algorithm - u32 highest = 0; - u32 drawcalls = 0; - for (;;) - { - if (tc[highest].drawn) - { - bool found = false; - float hiscore = 0; - for (u32 t = 0; t < tcount; t++) - { - if (!tc[t].drawn) - { - if (tc[t].score > hiscore) - { - highest = t; - hiscore = tc[t].score; - found = true; - } - } - } - if (!found) - break; - } - - // Output the best triangle - u16 newind = buf->Vertices.size(); - - snode *s = sind.find(v[tc[highest].ind[0]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[0]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[0]], newind); - newind++; - } - else - { - buf->Indices.push_back(s->getValue()); - } - - s = sind.find(v[tc[highest].ind[1]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[1]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[1]], newind); - newind++; - } - else - { - buf->Indices.push_back(s->getValue()); - } - - s = sind.find(v[tc[highest].ind[2]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[2]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[2]], newind); - } - else - { - buf->Indices.push_back(s->getValue()); - } - - vc[tc[highest].ind[0]].NumActiveTris--; - vc[tc[highest].ind[1]].NumActiveTris--; - vc[tc[highest].ind[2]].NumActiveTris--; - - tc[highest].drawn = true; - - for (u16 j : tc[highest].ind) { - vcache *vert = &vc[j]; - for (u16 t = 0; t < vert->tris.size(); t++) - { - if (highest == vert->tris[t]) - { - vert->tris.erase(t); - break; - } - } - } - - lru.add(tc[highest].ind[0]); - lru.add(tc[highest].ind[1]); - highest = lru.add(tc[highest].ind[2]); - drawcalls++; - } - - buf->setBoundingBox(mb->getBoundingBox()); - newmesh->addMeshBuffer(buf); - buf->drop(); - - } - break; - case video::EVT_TANGENTS: - { - video::S3DVertexTangents *v = (video::S3DVertexTangents *) mb->getVertices(); - - scene::SMeshBufferTangents *buf = new scene::SMeshBufferTangents(); - buf->Material = mb->getMaterial(); - - buf->Vertices.reallocate(vcount); - buf->Indices.reallocate(icount); - - core::map<const video::S3DVertexTangents, const u16> sind; // search index for fast operation - typedef core::map<const video::S3DVertexTangents, const u16>::Node snode; - - // Main algorithm - u32 highest = 0; - u32 drawcalls = 0; - for (;;) - { - if (tc[highest].drawn) - { - bool found = false; - float hiscore = 0; - for (u32 t = 0; t < tcount; t++) - { - if (!tc[t].drawn) - { - if (tc[t].score > hiscore) - { - highest = t; - hiscore = tc[t].score; - found = true; - } - } - } - if (!found) - break; - } - - // Output the best triangle - u16 newind = buf->Vertices.size(); - - snode *s = sind.find(v[tc[highest].ind[0]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[0]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[0]], newind); - newind++; - } - else - { - buf->Indices.push_back(s->getValue()); - } - - s = sind.find(v[tc[highest].ind[1]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[1]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[1]], newind); - newind++; - } - else - { - buf->Indices.push_back(s->getValue()); - } - - s = sind.find(v[tc[highest].ind[2]]); - - if (!s) - { - buf->Vertices.push_back(v[tc[highest].ind[2]]); - buf->Indices.push_back(newind); - sind.insert(v[tc[highest].ind[2]], newind); - } - else - { - buf->Indices.push_back(s->getValue()); - } - - vc[tc[highest].ind[0]].NumActiveTris--; - vc[tc[highest].ind[1]].NumActiveTris--; - vc[tc[highest].ind[2]].NumActiveTris--; - - tc[highest].drawn = true; - - for (u16 j : tc[highest].ind) { - vcache *vert = &vc[j]; - for (u16 t = 0; t < vert->tris.size(); t++) - { - if (highest == vert->tris[t]) - { - vert->tris.erase(t); - break; - } - } - } - - lru.add(tc[highest].ind[0]); - lru.add(tc[highest].ind[1]); - highest = lru.add(tc[highest].ind[2]); - drawcalls++; - } - - buf->setBoundingBox(mb->getBoundingBox()); - newmesh->addMeshBuffer(buf); - buf->drop(); - } - break; - } - - delete [] vc; - delete [] tc; - - } // for each meshbuffer - - return newmesh; -} diff --git a/src/client/mesh.h b/src/client/mesh.h index dbc091a06..1ed753c01 100644 --- a/src/client/mesh.h +++ b/src/client/mesh.h @@ -133,10 +133,3 @@ void recalculateBoundingBox(scene::IMesh *src_mesh); We assume normal to be valid when it's 0 < length < Inf. and not NaN */ bool checkMeshNormals(scene::IMesh *mesh); - -/* - Vertex cache optimization according to the Forsyth paper: - http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html - Ported from irrlicht 1.8 -*/ -scene::IMesh* createForsythOptimizedMesh(const scene::IMesh *mesh); diff --git a/src/client/mesh_generator_thread.cpp b/src/client/mesh_generator_thread.cpp index c8d1cba26..5c3f4180b 100644 --- a/src/client/mesh_generator_thread.cpp +++ b/src/client/mesh_generator_thread.cpp @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client.h" #include "mapblock.h" #include "map.h" +#include "util/directiontables.h" /* CachedMapBlockData @@ -69,7 +70,7 @@ MeshUpdateQueue::~MeshUpdateQueue() } } -void MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent) +bool MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent) { MutexAutoLock lock(m_mutex); @@ -81,20 +82,15 @@ void MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool */ std::vector<CachedMapBlockData*> cached_blocks; size_t cache_hit_counter = 0; + CachedMapBlockData *cached_block = cacheBlock(map, p, FORCE_UPDATE); + if (!cached_block->data) + return false; // nothing to update cached_blocks.reserve(3*3*3); - v3s16 dp; - for (dp.X = -1; dp.X <= 1; dp.X++) - for (dp.Y = -1; dp.Y <= 1; dp.Y++) - for (dp.Z = -1; dp.Z <= 1; dp.Z++) { - v3s16 p1 = p + dp; - CachedMapBlockData *cached_block; - if (dp == v3s16(0, 0, 0)) - cached_block = cacheBlock(map, p1, FORCE_UPDATE); - else - cached_block = cacheBlock(map, p1, SKIP_UPDATE_IF_ALREADY_CACHED, - &cache_hit_counter); - cached_blocks.push_back(cached_block); - } + cached_blocks.push_back(cached_block); + for (v3s16 dp : g_26dirs) + cached_blocks.push_back(cacheBlock(map, p + dp, + SKIP_UPDATE_IF_ALREADY_CACHED, + &cache_hit_counter)); g_profiler->avg("MeshUpdateQueue: MapBlocks from cache [%]", 100.0f * cache_hit_counter / cached_blocks.size()); @@ -116,7 +112,7 @@ void MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool q->ack_block_to_server = true; q->crack_level = m_client->getCrackLevel(); q->crack_pos = m_client->getCrackPos(); - return; + return true; } } @@ -134,6 +130,7 @@ void MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool for (CachedMapBlockData *cached_block : cached_blocks) { cached_block->refcount_from_queue++; } + return true; } // Returned pointer must be deleted @@ -212,10 +209,7 @@ void MeshUpdateQueue::fillDataFromMapBlockCache(QueuedMeshUpdate *q) std::time_t t_now = std::time(0); // Collect data for 3*3*3 blocks from cache - v3s16 dp; - for (dp.X = -1; dp.X <= 1; dp.X++) - for (dp.Y = -1; dp.Y <= 1; dp.Y++) - for (dp.Z = -1; dp.Z <= 1; dp.Z++) { + for (v3s16 dp : g_27dirs) { v3s16 p = q->p + dp; CachedMapBlockData *cached_block = getCachedBlock(p); if (cached_block) { @@ -272,10 +266,25 @@ MeshUpdateThread::MeshUpdateThread(Client *client): } void MeshUpdateThread::updateBlock(Map *map, v3s16 p, bool ack_block_to_server, - bool urgent) + bool urgent, bool update_neighbors) { - // Allow the MeshUpdateQueue to do whatever it wants - m_queue_in.addBlock(map, p, ack_block_to_server, urgent); + static thread_local const bool many_neighbors = + g_settings->getBool("smooth_lighting") + && !g_settings->getFlag("performance_tradeoffs"); + if (!m_queue_in.addBlock(map, p, ack_block_to_server, urgent)) { + warningstream << "Update requested for non-existent block at (" + << p.X << ", " << p.Y << ", " << p.Z << ")" << std::endl; + return; + } + if (update_neighbors) { + if (many_neighbors) { + for (v3s16 dp : g_26dirs) + m_queue_in.addBlock(map, p + dp, false, urgent); + } else { + for (v3s16 dp : g_6dirs) + m_queue_in.addBlock(map, p + dp, false, urgent); + } + } deferUpdate(); } diff --git a/src/client/mesh_generator_thread.h b/src/client/mesh_generator_thread.h index f393e2368..2ef695954 100644 --- a/src/client/mesh_generator_thread.h +++ b/src/client/mesh_generator_thread.h @@ -66,7 +66,7 @@ public: // Caches the block at p and its neighbors (if needed) and queues a mesh // update for the block at p - void addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent); + bool addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent); // Returned pointer must be deleted // Returns NULL if queue is empty @@ -113,7 +113,8 @@ public: // Caches the block at p and its neighbors (if needed) and queues a mesh // update for the block at p - void updateBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent); + void updateBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent, + bool update_neighbors = false); v3s16 m_camera_offset; MutexedQueue<MeshUpdateResult> m_queue_out; diff --git a/src/client/minimap.cpp b/src/client/minimap.cpp index 3013e1406..9bb9d14e0 100644 --- a/src/client/minimap.cpp +++ b/src/client/minimap.cpp @@ -304,7 +304,7 @@ void Minimap::setModeIndex(size_t index) data->mode = m_modes[index]; m_current_mode_index = index; } else { - data->mode = MinimapModeDef{MINIMAP_TYPE_OFF, gettext("Minimap hidden"), 0, 0, ""}; + data->mode = {MINIMAP_TYPE_OFF, gettext("Minimap hidden"), 0, 0, "", 0}; m_current_mode_index = 0; } diff --git a/src/client/render/anaglyph.cpp b/src/client/render/anaglyph.cpp index 153e77400..2571f7333 100644 --- a/src/client/render/anaglyph.cpp +++ b/src/client/render/anaglyph.cpp @@ -30,6 +30,7 @@ void RenderingCoreAnaglyph::drawAll() void RenderingCoreAnaglyph::setupMaterial(int color_mask) { video::SOverrideMaterial &mat = driver->getOverrideMaterial(); + mat.reset(); mat.Material.ColorMask = color_mask; mat.EnableFlags = video::EMF_COLOR_MASK; mat.EnablePasses = scene::ESNRP_SKY_BOX | scene::ESNRP_SOLID | diff --git a/src/client/render/core.cpp b/src/client/render/core.cpp index 1028a96e1..9927e2589 100644 --- a/src/client/render/core.cpp +++ b/src/client/render/core.cpp @@ -90,8 +90,11 @@ void RenderingCore::draw(video::SColor _skycolor, bool _show_hud, bool _show_min entity_esp_color = video::SColor(255, entity_color.X, entity_color.Y, entity_color.Z); player_esp_color = video::SColor(255, player_color.X, player_color.Y, player_color.Z); - if (shadow_renderer) + if (shadow_renderer) { + // This is necessary to render shadows for animations correctly + smgr->getRootSceneNode()->OnAnimate(device->getTimer()->getTime()); shadow_renderer->update(); + } beforeDraw(); drawAll(); diff --git a/src/client/renderingengine.cpp b/src/client/renderingengine.cpp index 2e4994a40..6ebcc784d 100644 --- a/src/client/renderingengine.cpp +++ b/src/client/renderingengine.cpp @@ -116,7 +116,7 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver) } SIrrlichtCreationParameters params = SIrrlichtCreationParameters(); - if (g_logger.getTraceEnabled()) + if (tracestream) params.LoggingLevel = irr::ELL_DEBUG; params.DriverType = driverType; params.WindowSize = core::dimension2d<u32>(screen_w, screen_h); @@ -597,7 +597,7 @@ static float calcDisplayDensity() float RenderingEngine::getDisplayDensity() { static float cached_display_density = calcDisplayDensity(); - return cached_display_density; + return cached_display_density * g_settings->getFloat("display_density_factor"); } #elif defined(_WIN32) @@ -625,14 +625,14 @@ float RenderingEngine::getDisplayDensity() display_density = calcDisplayDensity(get_video_driver()); cached = true; } - return display_density; + return display_density * g_settings->getFloat("display_density_factor"); } #else float RenderingEngine::getDisplayDensity() { - return g_settings->getFloat("screen_dpi") / 96.0; + return (g_settings->getFloat("screen_dpi") / 96.0) * g_settings->getFloat("display_density_factor"); } #endif diff --git a/src/client/shader.cpp b/src/client/shader.cpp index dc9e9ae6d..bbb872761 100644 --- a/src/client/shader.cpp +++ b/src/client/shader.cpp @@ -40,20 +40,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/tile.h" #include "config.h" -#if ENABLE_GLES -#ifdef _IRR_COMPILE_WITH_OGLES1_ -#include <GLES/gl.h> -#else -#include <GLES2/gl2.h> -#endif -#else -#ifndef __APPLE__ -#include <GL/gl.h> -#else -#define GL_SILENCE_DEPRECATION -#include <OpenGL/gl.h> -#endif -#endif +#include <mt_opengl.h> /* A cache from shader name to shader path @@ -223,17 +210,24 @@ public: class MainShaderConstantSetter : public IShaderConstantSetter { - CachedVertexShaderSetting<float, 16> m_world_view_proj; - CachedVertexShaderSetting<float, 16> m_world; + CachedVertexShaderSetting<f32, 16> m_world_view_proj; + CachedVertexShaderSetting<f32, 16> m_world; // Shadow-related - CachedPixelShaderSetting<float, 16> m_shadow_view_proj; - CachedPixelShaderSetting<float, 3> m_light_direction; - CachedPixelShaderSetting<float> m_texture_res; - CachedPixelShaderSetting<float> m_shadow_strength; - CachedPixelShaderSetting<float> m_time_of_day; - CachedPixelShaderSetting<float> m_shadowfar; + CachedPixelShaderSetting<f32, 16> m_shadow_view_proj; + CachedPixelShaderSetting<f32, 3> m_light_direction; + CachedPixelShaderSetting<f32> m_texture_res; + CachedPixelShaderSetting<f32> m_shadow_strength; + CachedPixelShaderSetting<f32> m_time_of_day; + CachedPixelShaderSetting<f32> m_shadowfar; + CachedPixelShaderSetting<f32, 4> m_camera_pos; CachedPixelShaderSetting<s32> m_shadow_texture; + CachedVertexShaderSetting<f32> m_perspective_bias0_vertex; + CachedPixelShaderSetting<f32> m_perspective_bias0_pixel; + CachedVertexShaderSetting<f32> m_perspective_bias1_vertex; + CachedPixelShaderSetting<f32> m_perspective_bias1_pixel; + CachedVertexShaderSetting<f32> m_perspective_zbias_vertex; + CachedPixelShaderSetting<f32> m_perspective_zbias_pixel; #if ENABLE_GLES // Modelview matrix @@ -248,18 +242,25 @@ public: MainShaderConstantSetter() : m_world_view_proj("mWorldViewProj") , m_world("mWorld") -#if ENABLE_GLES - , m_world_view("mWorldView") - , 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_camera_pos("CameraPos") , m_shadow_texture("ShadowMapSampler") + , m_perspective_bias0_vertex("xyPerspectiveBias0") + , m_perspective_bias0_pixel("xyPerspectiveBias0") + , m_perspective_bias1_vertex("xyPerspectiveBias1") + , m_perspective_bias1_pixel("xyPerspectiveBias1") + , m_perspective_zbias_vertex("zPerspectiveBias") + , m_perspective_zbias_pixel("zPerspectiveBias") +#if ENABLE_GLES + , m_world_view("mWorldView") + , m_texture("mTexture") + , m_normal("mNormal") +#endif {} ~MainShaderConstantSetter() = default; @@ -306,26 +307,40 @@ public: shadowViewProj *= light.getViewMatrix(); m_shadow_view_proj.set(shadowViewProj.pointer(), services); - float v_LightDirection[3]; + f32 v_LightDirection[3]; light.getDirection().getAs3Values(v_LightDirection); m_light_direction.set(v_LightDirection, services); - float TextureResolution = light.getMapResolution(); + f32 TextureResolution = light.getMapResolution(); m_texture_res.set(&TextureResolution, services); - float ShadowStrength = shadow->getShadowStrength(); + f32 ShadowStrength = shadow->getShadowStrength(); m_shadow_strength.set(&ShadowStrength, services); - float timeOfDay = shadow->getTimeOfDay(); + f32 timeOfDay = shadow->getTimeOfDay(); m_time_of_day.set(&timeOfDay, services); - float shadowFar = shadow->getMaxShadowFar(); + f32 shadowFar = shadow->getMaxShadowFar(); m_shadowfar.set(&shadowFar, services); + f32 cam_pos[4]; + shadowViewProj.transformVect(cam_pos, light.getPlayerPos()); + m_camera_pos.set(cam_pos, 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); + + f32 bias0 = shadow->getPerspectiveBiasXY(); + m_perspective_bias0_vertex.set(&bias0, services); + m_perspective_bias0_pixel.set(&bias0, services); + f32 bias1 = 1.0f - bias0 + 1e-5f; + m_perspective_bias1_vertex.set(&bias1, services); + m_perspective_bias1_pixel.set(&bias1, services); + f32 zbias = shadow->getPerspectiveBiasZ(); + m_perspective_zbias_vertex.set(&zbias, services); + m_perspective_zbias_pixel.set(&zbias, services); } } }; @@ -667,13 +682,19 @@ ShaderInfo ShaderSource::generateShader(const std::string &name, )"; } + // Since this is the first time we're using the GL bindings be extra careful. + // This should be removed before 5.6.0 or similar. + if (!GL.GetString) { + errorstream << "OpenGL procedures were not loaded correctly, " + "please open a bug report with details about your platform/OS." << std::endl; + abort(); + } + bool use_discard = use_gles; -#ifdef __unix__ // For renderers that should use discard instead of GL_ALPHA_TEST - const char* gl_renderer = (const char*)glGetString(GL_RENDERER); - if (strstr(gl_renderer, "GC7000")) + const char *renderer = reinterpret_cast<const char*>(GL.GetString(GL.RENDERER)); + if (strstr(renderer, "GC7000")) use_discard = true; -#endif if (use_discard) { if (shaderinfo.base_material == video::EMT_TRANSPARENT_ALPHA_CHANNEL) shaders_header << "#define USE_DISCARD 1\n"; diff --git a/src/client/shadows/dynamicshadows.cpp b/src/client/shadows/dynamicshadows.cpp index 0c7eea0e7..ca2d3ce37 100644 --- a/src/client/shadows/dynamicshadows.cpp +++ b/src/client/shadows/dynamicshadows.cpp @@ -29,7 +29,6 @@ using m4f = core::matrix4; void DirectionalLight::createSplitMatrices(const Camera *cam) { - float radius; v3f newCenter; v3f look = cam->getDirection(); @@ -42,59 +41,38 @@ void DirectionalLight::createSplitMatrices(const Camera *cam) float sfFar = adjustDist(future_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 * sfNear; - camPos2 += look * sfNear; + v3f cam_pos_world = cam->getPosition(); + v3f cam_pos_scene = v3f(cam_pos_world.X - cam->getOffset().X * BS, + cam_pos_world.Y - cam->getOffset().Y * BS, + cam_pos_world.Z - cam->getOffset().Z * BS); + cam_pos_scene += look * sfNear; + cam_pos_world += look * sfNear; // 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); + v3f center_scene = cam_pos_scene + look * 0.35 * (sfFar - sfNear); + v3f center_world = cam_pos_world + look * 0.35 * (sfFar - sfNear); // Create a vector to the frustum far corner const v3f &viewUp = cam->getCameraNode()->getUpVector(); v3f viewRight = look.crossProduct(viewUp); - v3f farCorner = look + viewRight * tanFovX + viewUp * tanFovY; + v3f farCorner = (look + viewRight * tanFovX + viewUp * tanFovY).normalize(); // Compute the frustumBoundingSphere radius - v3f boundVec = (camPos + farCorner * sfFar) - 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; + v3f boundVec = (cam_pos_scene + farCorner * sfFar) - center_scene; + float radius = boundVec.getLength(); + float length = radius * 3.0f; + v3f eye_displacement = direction * length; // we must compute the viewmat with the position - the camera offset // but the future_frustum position must be the actual world position - v3f eye = frustumCenter - eye_displacement; - 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); + v3f eye = center_scene - eye_displacement; + future_frustum.player = cam_pos_scene; + future_frustum.position = center_world - eye_displacement; + future_frustum.length = length; + future_frustum.radius = radius; + future_frustum.ViewMat.buildCameraLookAtMatrixLH(eye, center_scene, v3f(0.0f, 1.0f, 0.0f)); + future_frustum.ProjOrthMat.buildProjectionMatrixOrthoLH(radius, radius, + 0.0f, length, false); future_frustum.camera_offset = cam->getOffset(); } @@ -112,6 +90,8 @@ void DirectionalLight::update_frustum(const Camera *cam, Client *client, bool fo float zNear = cam->getCameraNode()->getNearValue(); float zFar = getMaxFarValue(); + if (!client->getEnv().getClientMap().getControl().range_all) + zFar = MYMIN(zFar, client->getEnv().getClientMap().getControl().wanted_range * BS); /////////////////////////////////// // update splits near and fars @@ -122,7 +102,7 @@ void DirectionalLight::update_frustum(const Camera *cam, Client *client, bool fo createSplitMatrices(cam); // get the draw list for shadows client->getEnv().getClientMap().updateDrawListShadow( - getPosition(), getDirection(), future_frustum.length); + getPosition(), getDirection(), future_frustum.radius, future_frustum.length); should_update_map_shadow = true; dirty = true; @@ -132,6 +112,7 @@ void DirectionalLight::update_frustum(const Camera *cam, Client *client, bool fo 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.player += intToFloat(shadow_frustum.camera_offset - cam->getOffset(), BS); shadow_frustum.camera_offset = cam_offset; } } @@ -156,6 +137,16 @@ v3f DirectionalLight::getPosition() const return shadow_frustum.position; } +v3f DirectionalLight::getPlayerPos() const +{ + return shadow_frustum.player; +} + +v3f DirectionalLight::getFuturePlayerPos() const +{ + return future_frustum.player; +} + const m4f &DirectionalLight::getViewMatrix() const { return shadow_frustum.ViewMat; diff --git a/src/client/shadows/dynamicshadows.h b/src/client/shadows/dynamicshadows.h index d8be66be8..6e9d96b15 100644 --- a/src/client/shadows/dynamicshadows.h +++ b/src/client/shadows/dynamicshadows.h @@ -22,18 +22,21 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_bloated.h" #include <matrix4.h> #include "util/basic_macros.h" +#include "constants.h" class Camera; class Client; struct shadowFrustum { - float zNear{0.0f}; - float zFar{0.0f}; - float length{0.0f}; + f32 zNear{0.0f}; + f32 zFar{0.0f}; + f32 length{0.0f}; + f32 radius{0.0f}; core::matrix4 ProjOrthMat; core::matrix4 ViewMat; v3f position; + v3f player; v3s16 camera_offset; }; @@ -56,6 +59,8 @@ public: return direction; }; v3f getPosition() const; + v3f getPlayerPos() const; + v3f getFuturePlayerPos() const; /// Gets the light's matrices. const core::matrix4 &getViewMatrix() const; @@ -64,10 +69,16 @@ public: const core::matrix4 &getFutureProjectionMatrix() const; core::matrix4 getViewProjMatrix(); - /// Gets the light's far value. + /// Gets the light's maximum far value, i.e. the shadow boundary f32 getMaxFarValue() const { - return farPlane; + return farPlane * BS; + } + + /// Gets the current far value of the light + f32 getFarValue() const + { + return shadow_frustum.zFar; } diff --git a/src/client/shadows/dynamicshadowsrender.cpp b/src/client/shadows/dynamicshadowsrender.cpp index a913a9290..07dc6daf2 100644 --- a/src/client/shadows/dynamicshadowsrender.cpp +++ b/src/client/shadows/dynamicshadowsrender.cpp @@ -18,6 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include <cstring> +#include <cmath> #include "client/shadows/dynamicshadowsrender.h" #include "client/shadows/shadowsScreenQuad.h" #include "client/shadows/shadowsshadercallbacks.h" @@ -30,12 +31,19 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "profiler.h" ShadowRenderer::ShadowRenderer(IrrlichtDevice *device, Client *client) : - m_device(device), m_smgr(device->getSceneManager()), - m_driver(device->getVideoDriver()), m_client(client), m_current_frame(0) + m_smgr(device->getSceneManager()), m_driver(device->getVideoDriver()), + m_client(client), m_current_frame(0), + m_perspective_bias_xy(0.8), m_perspective_bias_z(0.5) { + (void) m_client; + + m_shadows_supported = true; // assume shadows supported. We will check actual support in initialize m_shadows_enabled = true; - m_shadow_strength = g_settings->getFloat("shadow_strength"); + m_shadow_strength_gamma = g_settings->getFloat("shadow_strength_gamma"); + if (std::isnan(m_shadow_strength_gamma)) + m_shadow_strength_gamma = 1.0f; + m_shadow_strength_gamma = core::clamp(m_shadow_strength_gamma, 0.1f, 10.0f); m_shadow_map_max_distance = g_settings->getFloat("shadow_map_max_distance"); @@ -49,8 +57,15 @@ ShadowRenderer::ShadowRenderer(IrrlichtDevice *device, Client *client) : ShadowRenderer::~ShadowRenderer() { + // call to disable releases dynamically allocated resources + disable(); + if (m_shadow_depth_cb) delete m_shadow_depth_cb; + if (m_shadow_depth_entity_cb) + delete m_shadow_depth_entity_cb; + if (m_shadow_depth_trans_cb) + delete m_shadow_depth_trans_cb; if (m_shadow_mix_cb) delete m_shadow_mix_cb; m_shadow_node_array.clear(); @@ -72,15 +87,25 @@ ShadowRenderer::~ShadowRenderer() m_driver->removeTexture(shadowMapClientMapFuture); } +void ShadowRenderer::disable() +{ + m_shadows_enabled = false; + if (shadowMapTextureFinal) { + m_driver->setRenderTarget(shadowMapTextureFinal, true, true, + video::SColor(255, 255, 255, 255)); + m_driver->setRenderTarget(0, true, true); + } +} + void ShadowRenderer::initialize() { auto *gpu = m_driver->getGPUProgrammingServices(); // we need glsl - if (m_shadows_enabled && gpu && m_driver->queryFeature(video::EVDF_ARB_GLSL)) { + if (m_shadows_supported && gpu && m_driver->queryFeature(video::EVDF_ARB_GLSL)) { createShaders(); } else { - m_shadows_enabled = false; + m_shadows_supported = false; warningstream << "Shadows: GLSL Shader not supported on this system." << std::endl; @@ -94,6 +119,8 @@ void ShadowRenderer::initialize() m_texture_format_color = m_shadow_map_texture_32bit ? video::ECOLOR_FORMAT::ECF_G32R32F : video::ECOLOR_FORMAT::ECF_G16R16F; + + m_shadows_enabled &= m_shadows_supported; } @@ -118,16 +145,21 @@ size_t ShadowRenderer::getDirectionalLightCount() const 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; + float zMax = m_light_list[0].getFarValue(); + return zMax; } return 0.0f; } +void ShadowRenderer::setShadowIntensity(float shadow_intensity) +{ + m_shadow_strength = pow(shadow_intensity, 1.0f / m_shadow_strength_gamma); + if (m_shadow_strength > 1E-2) + enable(); + else + disable(); +} + void ShadowRenderer::addNodeToShadowList( scene::ISceneNode *node, E_SHADOW_MODE shadowMode) { @@ -157,6 +189,7 @@ void ShadowRenderer::updateSMTextures() shadowMapTextureDynamicObjects = getSMTexture( std::string("shadow_dynamic_") + itos(m_shadow_map_texture_size), m_texture_format, true); + assert(shadowMapTextureDynamicObjects != nullptr); } if (!shadowMapClientMap) { @@ -165,6 +198,7 @@ void ShadowRenderer::updateSMTextures() std::string("shadow_clientmap_") + itos(m_shadow_map_texture_size), m_shadow_map_colored ? m_texture_format_color : m_texture_format, true); + assert(shadowMapClientMap != nullptr); } if (!shadowMapClientMapFuture && m_map_shadow_update_frames > 1) { @@ -172,6 +206,7 @@ void ShadowRenderer::updateSMTextures() std::string("shadow_clientmap_bb_") + itos(m_shadow_map_texture_size), m_shadow_map_colored ? m_texture_format_color : m_texture_format, true); + assert(shadowMapClientMapFuture != nullptr); } if (m_shadow_map_colored && !shadowMapTextureColors) { @@ -179,6 +214,7 @@ void ShadowRenderer::updateSMTextures() std::string("shadow_colored_") + itos(m_shadow_map_texture_size), m_shadow_map_colored ? m_texture_format_color : m_texture_format, true); + assert(shadowMapTextureColors != nullptr); } // The merge all shadowmaps texture @@ -198,6 +234,7 @@ void ShadowRenderer::updateSMTextures() shadowMapTextureFinal = getSMTexture( std::string("shadowmap_final_") + itos(m_shadow_map_texture_size), frt, true); + assert(shadowMapTextureFinal != nullptr); } if (!m_shadow_node_array.empty() && !m_light_list.empty()) { @@ -219,9 +256,15 @@ void ShadowRenderer::updateSMTextures() // Update SM incrementally: 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; - + for (auto cb : {m_shadow_depth_cb, m_shadow_depth_entity_cb, m_shadow_depth_trans_cb}) + if (cb) { + cb->MapRes = (f32)m_shadow_map_texture_size; + cb->MaxFar = (f32)m_shadow_map_max_distance * BS; + cb->PerspectiveBiasXY = getPerspectiveBiasXY(); + cb->PerspectiveBiasZ = getPerspectiveBiasZ(); + cb->CameraPos = light.getFuturePlayerPos(); + } + // set the Render Target // right now we can only render in usual RTT, not // Depth texture is available in irrlicth maybe we @@ -274,12 +317,17 @@ void ShadowRenderer::update(video::ITexture *outputTarget) updateSMTextures(); + if (shadowMapTextureFinal == nullptr) { + return; + } + if (!m_shadow_node_array.empty() && !m_light_list.empty()) { for (DirectionalLight &light : m_light_list) { - // 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; + // Static shader values for entities are set in updateSMTextures + // SM texture for entities is not updated incrementally and + // must by updated using current player position. + m_shadow_depth_entity_cb->CameraPos = light.getPlayerPos(); // render shadows for the n0n-map objects. m_driver->setRenderTarget(shadowMapTextureDynamicObjects, true, @@ -311,19 +359,23 @@ void ShadowRenderer::drawDebug() /* this code just shows shadows textures in screen and in ONLY for debugging*/ #if 0 // this is debug, ignore for now. - m_driver->draw2DImage(shadowMapTextureFinal, - core::rect<s32>(0, 50, 128, 128 + 50), - core::rect<s32>({0, 0}, shadowMapTextureFinal->getSize())); + if (shadowMapTextureFinal) + m_driver->draw2DImage(shadowMapTextureFinal, + core::rect<s32>(0, 50, 128, 128 + 50), + core::rect<s32>({0, 0}, shadowMapTextureFinal->getSize())); - m_driver->draw2DImage(shadowMapClientMap, - core::rect<s32>(0, 50 + 128, 128, 128 + 50 + 128), - core::rect<s32>({0, 0}, shadowMapTextureFinal->getSize())); - m_driver->draw2DImage(shadowMapTextureDynamicObjects, - core::rect<s32>(0, 128 + 50 + 128, 128, - 128 + 50 + 128 + 128), - core::rect<s32>({0, 0}, shadowMapTextureDynamicObjects->getSize())); + if (shadowMapClientMap) + m_driver->draw2DImage(shadowMapClientMap, + core::rect<s32>(0, 50 + 128, 128, 128 + 50 + 128), + core::rect<s32>({0, 0}, shadowMapTextureFinal->getSize())); + + if (shadowMapTextureDynamicObjects) + m_driver->draw2DImage(shadowMapTextureDynamicObjects, + core::rect<s32>(0, 128 + 50 + 128, 128, + 128 + 50 + 128 + 128), + core::rect<s32>({0, 0}, shadowMapTextureDynamicObjects->getSize())); - if (m_shadow_map_colored) { + if (m_shadow_map_colored && shadowMapTextureColors) { m_driver->draw2DImage(shadowMapTextureColors, core::rect<s32>(128,128 + 50 + 128 + 128, @@ -368,10 +420,6 @@ void ShadowRenderer::renderShadowMap(video::ITexture *target, 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; @@ -381,9 +429,6 @@ void ShadowRenderer::renderShadowMap(video::ITexture *target, material.BlendOperation = video::EBO_MIN; } - // 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()); @@ -429,10 +474,6 @@ void ShadowRenderer::renderShadowObjects( 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, @@ -473,13 +514,13 @@ void ShadowRenderer::createShaders() if (depth_shader == -1) { std::string depth_shader_vs = getShaderPath("shadow_shaders", "pass1_vertex.glsl"); if (depth_shader_vs.empty()) { - m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error shadow mapping vs shader not found." << std::endl; return; } std::string depth_shader_fs = getShaderPath("shadow_shaders", "pass1_fragment.glsl"); if (depth_shader_fs.empty()) { - m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error shadow mapping fs shader not found." << std::endl; return; } @@ -494,7 +535,9 @@ void ShadowRenderer::createShaders() if (depth_shader == -1) { // upsi, something went wrong loading shader. delete m_shadow_depth_cb; + m_shadow_depth_cb = nullptr; m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error compiling shadow mapping shader." << std::endl; return; } @@ -510,26 +553,30 @@ void ShadowRenderer::createShaders() if (depth_shader_entities == -1) { std::string depth_shader_vs = getShaderPath("shadow_shaders", "pass1_vertex.glsl"); if (depth_shader_vs.empty()) { - m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error shadow mapping vs shader not found." << std::endl; return; } std::string depth_shader_fs = getShaderPath("shadow_shaders", "pass1_fragment.glsl"); if (depth_shader_fs.empty()) { - m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error shadow mapping fs shader not found." << std::endl; return; } + m_shadow_depth_entity_cb = new ShadowDepthShaderCB(); 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); + video::EPST_PS_1_2, m_shadow_depth_entity_cb); if (depth_shader_entities == -1) { // upsi, something went wrong loading shader. + delete m_shadow_depth_entity_cb; + m_shadow_depth_entity_cb = nullptr; m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error compiling shadow mapping shader (dynamic)." << std::endl; return; } @@ -543,14 +590,14 @@ void ShadowRenderer::createShaders() if (mixcsm_shader == -1) { std::string depth_shader_vs = getShaderPath("shadow_shaders", "pass2_vertex.glsl"); if (depth_shader_vs.empty()) { - m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error cascade shadow mapping fs shader not found." << std::endl; return; } std::string depth_shader_fs = getShaderPath("shadow_shaders", "pass2_fragment.glsl"); if (depth_shader_fs.empty()) { - m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error cascade shadow mapping fs shader not found." << std::endl; return; } @@ -569,7 +616,7 @@ void ShadowRenderer::createShaders() // upsi, something went wrong loading shader. delete m_shadow_mix_cb; delete m_screen_quad; - m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error compiling cascade shadow mapping shader." << std::endl; return; } @@ -583,13 +630,13 @@ void ShadowRenderer::createShaders() if (m_shadow_map_colored && depth_shader_trans == -1) { std::string depth_shader_vs = getShaderPath("shadow_shaders", "pass1_trans_vertex.glsl"); if (depth_shader_vs.empty()) { - m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error shadow mapping vs shader not found." << std::endl; return; } std::string depth_shader_fs = getShaderPath("shadow_shaders", "pass1_trans_fragment.glsl"); if (depth_shader_fs.empty()) { - m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error shadow mapping fs shader not found." << std::endl; return; } @@ -604,8 +651,9 @@ void ShadowRenderer::createShaders() if (depth_shader_trans == -1) { // upsi, something went wrong loading shader. delete m_shadow_depth_trans_cb; + m_shadow_depth_trans_cb = nullptr; m_shadow_map_colored = false; - m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error compiling colored shadow mapping shader." << std::endl; return; } diff --git a/src/client/shadows/dynamicshadowsrender.h b/src/client/shadows/dynamicshadowsrender.h index e4b3c3e22..0e4ef6b70 100644 --- a/src/client/shadows/dynamicshadowsrender.h +++ b/src/client/shadows/dynamicshadowsrender.h @@ -82,13 +82,17 @@ public: } - bool is_active() const { return m_shadows_enabled; } + bool is_active() const { return m_shadows_enabled && shadowMapTextureFinal != nullptr; } void setTimeOfDay(float isDay) { m_time_day = isDay; }; + void setShadowIntensity(float shadow_intensity); s32 getShadowSamples() const { return m_shadow_samples; } - float getShadowStrength() const { return m_shadow_strength; } + float getShadowStrength() const { return m_shadows_enabled ? m_shadow_strength : 0.0f; } float getTimeOfDay() const { return m_time_day; } + f32 getPerspectiveBiasXY() { return m_perspective_bias_xy; } + f32 getPerspectiveBiasZ() { return m_perspective_bias_z; } + private: video::ITexture *getSMTexture(const std::string &shadow_map_name, video::ECOLOR_FORMAT texture_format, @@ -101,8 +105,10 @@ private: void mixShadowsQuad(); void updateSMTextures(); + void disable(); + void enable() { m_shadows_enabled = m_shadows_supported; } + // a bunch of variables - IrrlichtDevice *m_device{nullptr}; scene::ISceneManager *m_smgr{nullptr}; video::IVideoDriver *m_driver{nullptr}; Client *m_client{nullptr}; @@ -116,15 +122,19 @@ private: std::vector<NodeToApply> m_shadow_node_array; float m_shadow_strength; + float m_shadow_strength_gamma; float m_shadow_map_max_distance; float m_shadow_map_texture_size; float m_time_day{0.0f}; int m_shadow_samples; bool m_shadow_map_texture_32bit; bool m_shadows_enabled; + bool m_shadows_supported; bool m_shadow_map_colored; u8 m_map_shadow_update_frames; /* Use this number of frames to update map shaodw */ u8 m_current_frame{0}; /* Current frame */ + f32 m_perspective_bias_xy; + f32 m_perspective_bias_z; video::ECOLOR_FORMAT m_texture_format{video::ECOLOR_FORMAT::ECF_R16F}; video::ECOLOR_FORMAT m_texture_format_color{video::ECOLOR_FORMAT::ECF_R16G16}; @@ -140,6 +150,7 @@ private: s32 mixcsm_shader{-1}; ShadowDepthShaderCB *m_shadow_depth_cb{nullptr}; + ShadowDepthShaderCB *m_shadow_depth_entity_cb{nullptr}; ShadowDepthShaderCB *m_shadow_depth_trans_cb{nullptr}; shadowScreenQuad *m_screen_quad{nullptr}; diff --git a/src/client/shadows/shadowsshadercallbacks.cpp b/src/client/shadows/shadowsshadercallbacks.cpp index 65a63f49c..b571ea939 100644 --- a/src/client/shadows/shadowsshadercallbacks.cpp +++ b/src/client/shadows/shadowsshadercallbacks.cpp @@ -26,6 +26,10 @@ void ShadowDepthShaderCB::OnSetConstants( core::matrix4 lightMVP = driver->getTransform(video::ETS_PROJECTION); lightMVP *= driver->getTransform(video::ETS_VIEW); + + f32 cam_pos[4]; + lightMVP.transformVect(cam_pos, CameraPos); + lightMVP *= driver->getTransform(video::ETS_WORLD); m_light_mvp_setting.set(lightMVP.pointer(), services); @@ -33,4 +37,12 @@ void ShadowDepthShaderCB::OnSetConstants( m_max_far_setting.set(&MaxFar, services); s32 TextureId = 0; m_color_map_sampler_setting.set(&TextureId, services); + f32 bias0 = PerspectiveBiasXY; + m_perspective_bias0.set(&bias0, services); + f32 bias1 = 1.0f - bias0 + 1e-5f; + m_perspective_bias1.set(&bias1, services); + f32 zbias = PerspectiveBiasZ; + m_perspective_zbias.set(&zbias, services); + + m_cam_pos_setting.set(cam_pos, services); } diff --git a/src/client/shadows/shadowsshadercallbacks.h b/src/client/shadows/shadowsshadercallbacks.h index 3549567c3..87833c0ec 100644 --- a/src/client/shadows/shadowsshadercallbacks.h +++ b/src/client/shadows/shadowsshadercallbacks.h @@ -30,7 +30,11 @@ public: m_light_mvp_setting("LightMVP"), m_map_resolution_setting("MapResolution"), m_max_far_setting("MaxFar"), - m_color_map_sampler_setting("ColorMapSampler") + m_color_map_sampler_setting("ColorMapSampler"), + m_perspective_bias0("xyPerspectiveBias0"), + m_perspective_bias1("xyPerspectiveBias1"), + m_perspective_zbias("zPerspectiveBias"), + m_cam_pos_setting("CameraPos") {} void OnSetMaterial(const video::SMaterial &material) override {} @@ -39,10 +43,16 @@ public: s32 userData) override; f32 MaxFar{2048.0f}, MapRes{1024.0f}; + f32 PerspectiveBiasXY {0.9f}, PerspectiveBiasZ {0.5f}; + v3f CameraPos; private: CachedVertexShaderSetting<f32, 16> m_light_mvp_setting; CachedVertexShaderSetting<f32> m_map_resolution_setting; CachedVertexShaderSetting<f32> m_max_far_setting; CachedPixelShaderSetting<s32> m_color_map_sampler_setting; + CachedVertexShaderSetting<f32> m_perspective_bias0; + CachedVertexShaderSetting<f32> m_perspective_bias1; + CachedVertexShaderSetting<f32> m_perspective_zbias; + CachedVertexShaderSetting<f32, 4> m_cam_pos_setting; }; diff --git a/src/client/sky.cpp b/src/client/sky.cpp index 1cf9a4afc..0ab710eee 100644 --- a/src/client/sky.cpp +++ b/src/client/sky.cpp @@ -18,21 +18,21 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include <cmath> #include "sky.h" -#include "ITexture.h" -#include "IVideoDriver.h" -#include "ISceneManager.h" -#include "ICameraSceneNode.h" -#include "S3DVertex.h" +#include <ITexture.h> +#include <IVideoDriver.h> +#include <ISceneManager.h> +#include <ICameraSceneNode.h> +#include <S3DVertex.h> #include "client/tile.h" #include "noise.h" // easeCurve #include "profiler.h" #include "util/numeric.h" -#include <cmath> #include "client/renderingengine.h" #include "settings.h" #include "camera.h" // CameraModes -#include "config.h" + using namespace irr::core; static video::SMaterial baseMaterial() @@ -51,7 +51,14 @@ static video::SMaterial baseMaterial() mat.TextureLayer[0].TextureWrapV = video::ETC_CLAMP_TO_EDGE; mat.BackfaceCulling = false; return mat; -}; +} + +static inline void disableTextureFiltering(video::SMaterial &mat) +{ + mat.setFlag(video::E_MATERIAL_FLAG::EMF_BILINEAR_FILTER, false); + mat.setFlag(video::E_MATERIAL_FLAG::EMF_TRILINEAR_FILTER, false); + mat.setFlag(video::E_MATERIAL_FLAG::EMF_ANISOTROPIC_FILTER, false); +} Sky::Sky(s32 id, RenderingEngine *rendering_engine, ITextureSource *tsrc, IShaderSource *ssrc) : scene::ISceneNode(rendering_engine->get_scene_manager()->getRootSceneNode(), @@ -65,6 +72,11 @@ Sky::Sky(s32 id, RenderingEngine *rendering_engine, ITextureSource *tsrc, IShade m_enable_shaders = g_settings->getBool("enable_shaders"); + m_sky_params = SkyboxDefaults::getSkyDefaults(); + m_sun_params = SkyboxDefaults::getSunDefaults(); + m_moon_params = SkyboxDefaults::getMoonDefaults(); + m_star_params = SkyboxDefaults::getStarDefaults(); + // Create materials m_materials[0] = baseMaterial(); @@ -73,49 +85,15 @@ Sky::Sky(s32 id, RenderingEngine *rendering_engine, ITextureSource *tsrc, IShade m_materials[0].ColorMaterial = video::ECM_NONE; m_materials[1] = baseMaterial(); - //m_materials[1].MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA; m_materials[1].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; m_materials[2] = baseMaterial(); m_materials[2].setTexture(0, tsrc->getTextureForMesh("sunrisebg.png")); m_materials[2].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; - //m_materials[2].MaterialType = video::EMT_TRANSPARENT_ADD_COLOR; - - // Ensures that sun and moon textures and tonemaps are correct. - setSkyDefaults(); - m_sun_texture = tsrc->isKnownSourceImage(m_sun_params.texture) ? - tsrc->getTextureForMesh(m_sun_params.texture) : nullptr; - m_moon_texture = tsrc->isKnownSourceImage(m_moon_params.texture) ? - tsrc->getTextureForMesh(m_moon_params.texture) : nullptr; - m_sun_tonemap = tsrc->isKnownSourceImage(m_sun_params.tonemap) ? - tsrc->getTexture(m_sun_params.tonemap) : nullptr; - m_moon_tonemap = tsrc->isKnownSourceImage(m_moon_params.tonemap) ? - tsrc->getTexture(m_moon_params.tonemap) : nullptr; - if (m_sun_texture) { - m_materials[3] = baseMaterial(); - m_materials[3].setTexture(0, m_sun_texture); - m_materials[3].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; - // Disables texture filtering - m_materials[3].setFlag(video::E_MATERIAL_FLAG::EMF_BILINEAR_FILTER, false); - m_materials[3].setFlag(video::E_MATERIAL_FLAG::EMF_TRILINEAR_FILTER, false); - m_materials[3].setFlag(video::E_MATERIAL_FLAG::EMF_ANISOTROPIC_FILTER, false); - // Use tonemaps if available - if (m_sun_tonemap) - m_materials[3].Lighting = true; - } - if (m_moon_texture) { - m_materials[4] = baseMaterial(); - m_materials[4].setTexture(0, m_moon_texture); - m_materials[4].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; - // Disables texture filtering - m_materials[4].setFlag(video::E_MATERIAL_FLAG::EMF_BILINEAR_FILTER, false); - m_materials[4].setFlag(video::E_MATERIAL_FLAG::EMF_TRILINEAR_FILTER, false); - m_materials[4].setFlag(video::E_MATERIAL_FLAG::EMF_ANISOTROPIC_FILTER, false); - // Use tonemaps if available - if (m_moon_tonemap) - m_materials[4].Lighting = true; - } + setSunTexture(m_sun_params.texture, m_sun_params.tonemap, tsrc); + + setMoonTexture(m_moon_params.texture, m_moon_params.tonemap, tsrc); for (int i = 5; i < 11; i++) { m_materials[i] = baseMaterial(); @@ -130,7 +108,7 @@ Sky::Sky(s32 id, RenderingEngine *rendering_engine, ITextureSource *tsrc, IShade m_sky_body_orbit_tilt = rangelim(val, 0.0f, 60.0f); } - setStarCount(1000, true); + setStarCount(1000); } void Sky::OnRegisterSceneNode() @@ -386,20 +364,6 @@ void Sky::update(float time_of_day, float time_brightness, bool is_dawn = (time_brightness >= 0.20 && time_brightness < 0.35); - /* - Development colours - - video::SColorf bgcolor_bright_normal_f(170. / 255, 200. / 255, 230. / 255, 1.0); - video::SColorf bgcolor_bright_dawn_f(0.666, 200. / 255 * 0.7, 230. / 255 * 0.5, 1.0); - video::SColorf bgcolor_bright_dawn_f(0.666, 0.549, 0.220, 1.0); - video::SColorf bgcolor_bright_dawn_f(0.666 * 1.2, 0.549 * 1.0, 0.220 * 1.0, 1.0); - video::SColorf bgcolor_bright_dawn_f(0.666 * 1.2, 0.549 * 1.0, 0.220 * 1.2, 1.0); - - video::SColorf cloudcolor_bright_dawn_f(1.0, 0.591, 0.4); - video::SColorf cloudcolor_bright_dawn_f(1.0, 0.65, 0.44); - video::SColorf cloudcolor_bright_dawn_f(1.0, 0.7, 0.5); - */ - video::SColorf bgcolor_bright_normal_f = m_sky_params.sky_color.day_horizon; video::SColorf bgcolor_bright_indoor_f = m_sky_params.sky_color.indoors; video::SColorf bgcolor_bright_dawn_f = m_sky_params.sky_color.dawn_horizon; @@ -754,33 +718,29 @@ void Sky::setSunTexture(const std::string &sun_texture, // Ignore matching textures (with modifiers) entirely, // but lets at least update the tonemap before hand. m_sun_params.tonemap = sun_tonemap; - m_sun_tonemap = tsrc->isKnownSourceImage(m_sun_params.tonemap) ? - tsrc->getTexture(m_sun_params.tonemap) : nullptr; + m_sun_tonemap = tsrc->isKnownSourceImage(sun_tonemap) ? + tsrc->getTexture(sun_tonemap) : nullptr; m_materials[3].Lighting = !!m_sun_tonemap; - if (m_sun_params.texture == sun_texture) + if (m_sun_params.texture == sun_texture && !m_first_update) return; m_sun_params.texture = sun_texture; - if (sun_texture != "") { - // We want to ensure the texture exists first. - m_sun_texture = tsrc->getTextureForMesh(m_sun_params.texture); - - if (m_sun_texture) { - m_materials[3] = baseMaterial(); - m_materials[3].setTexture(0, m_sun_texture); - m_materials[3].MaterialType = video:: - EMT_TRANSPARENT_ALPHA_CHANNEL; - // Disables texture filtering - m_materials[3].setFlag( - video::E_MATERIAL_FLAG::EMF_BILINEAR_FILTER, false); - m_materials[3].setFlag( - video::E_MATERIAL_FLAG::EMF_TRILINEAR_FILTER, false); - m_materials[3].setFlag( - video::E_MATERIAL_FLAG::EMF_ANISOTROPIC_FILTER, false); - } - } else { - m_sun_texture = nullptr; + m_sun_texture = nullptr; + if (sun_texture == "sun.png") { + // Dumb compatibility fix: sun.png transparently falls back to no texture + m_sun_texture = tsrc->isKnownSourceImage(sun_texture) ? + tsrc->getTexture(sun_texture) : nullptr; + } else if (!sun_texture.empty()) { + m_sun_texture = tsrc->getTextureForMesh(sun_texture); + } + + if (m_sun_texture) { + m_materials[3] = baseMaterial(); + m_materials[3].setTexture(0, m_sun_texture); + m_materials[3].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + disableTextureFiltering(m_materials[3]); + m_materials[3].Lighting = !!m_sun_tonemap; } } @@ -802,40 +762,36 @@ void Sky::setMoonTexture(const std::string &moon_texture, // Ignore matching textures (with modifiers) entirely, // but lets at least update the tonemap before hand. m_moon_params.tonemap = moon_tonemap; - m_moon_tonemap = tsrc->isKnownSourceImage(m_moon_params.tonemap) ? - tsrc->getTexture(m_moon_params.tonemap) : nullptr; + m_moon_tonemap = tsrc->isKnownSourceImage(moon_tonemap) ? + tsrc->getTexture(moon_tonemap) : nullptr; m_materials[4].Lighting = !!m_moon_tonemap; - if (m_moon_params.texture == moon_texture) + if (m_moon_params.texture == moon_texture && !m_first_update) return; m_moon_params.texture = moon_texture; - if (moon_texture != "") { - // We want to ensure the texture exists first. - m_moon_texture = tsrc->getTextureForMesh(m_moon_params.texture); - - if (m_moon_texture) { - m_materials[4] = baseMaterial(); - m_materials[4].setTexture(0, m_moon_texture); - m_materials[4].MaterialType = video:: - EMT_TRANSPARENT_ALPHA_CHANNEL; - // Disables texture filtering - m_materials[4].setFlag( - video::E_MATERIAL_FLAG::EMF_BILINEAR_FILTER, false); - m_materials[4].setFlag( - video::E_MATERIAL_FLAG::EMF_TRILINEAR_FILTER, false); - m_materials[4].setFlag( - video::E_MATERIAL_FLAG::EMF_ANISOTROPIC_FILTER, false); - } - } else { - m_moon_texture = nullptr; + m_moon_texture = nullptr; + if (moon_texture == "moon.png") { + // Dumb compatibility fix: moon.png transparently falls back to no texture + m_moon_texture = tsrc->isKnownSourceImage(moon_texture) ? + tsrc->getTexture(moon_texture) : nullptr; + } else if (!moon_texture.empty()) { + m_moon_texture = tsrc->getTextureForMesh(moon_texture); + } + + if (m_moon_texture) { + m_materials[4] = baseMaterial(); + m_materials[4].setTexture(0, m_moon_texture); + m_materials[4].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + disableTextureFiltering(m_materials[4]); + m_materials[4].Lighting = !!m_moon_tonemap; } } -void Sky::setStarCount(u16 star_count, bool force_update) +void Sky::setStarCount(u16 star_count) { // Allow force updating star count at game init. - if (m_star_params.count != star_count || force_update) { + if (m_star_params.count != star_count || m_first_update) { m_star_params.count = star_count; updateStars(); } @@ -924,16 +880,6 @@ void Sky::addTextureToSkybox(const std::string &texture, int material_id, m_materials[material_id+5].MaterialType = video::EMT_SOLID; } -// To be called once at game init to setup default values. -void Sky::setSkyDefaults() -{ - SkyboxDefaults sky_defaults; - m_sky_params.sky_color = sky_defaults.getSkyColorDefaults(); - m_sun_params = sky_defaults.getSunDefaults(); - m_moon_params = sky_defaults.getMoonDefaults(); - m_star_params = sky_defaults.getStarDefaults(); -} - float getWickedTimeOfDay(float time_of_day) { float nightlength = 0.415f; diff --git a/src/client/sky.h b/src/client/sky.h index 83106453b..e03683f12 100644 --- a/src/client/sky.h +++ b/src/client/sky.h @@ -17,6 +17,8 @@ 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 <ISceneNode.h> #include <array> @@ -25,8 +27,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "shader.h" #include "skyparams.h" -#pragma once - #define SKY_MATERIAL_COUNT 12 class ITextureSource; @@ -65,6 +65,7 @@ public: } void setSunVisible(bool sun_visible) { m_sun_params.visible = sun_visible; } + bool getSunVisible() const { return m_sun_params.visible; } void setSunTexture(const std::string &sun_texture, const std::string &sun_tonemap, ITextureSource *tsrc); void setSunScale(f32 sun_scale) { m_sun_params.scale = sun_scale; } @@ -72,12 +73,13 @@ public: void setSunriseTexture(const std::string &sunglow_texture, ITextureSource* tsrc); void setMoonVisible(bool moon_visible) { m_moon_params.visible = moon_visible; } + bool getMoonVisible() const { return m_moon_params.visible; } void setMoonTexture(const std::string &moon_texture, const std::string &moon_tonemap, ITextureSource *tsrc); void setMoonScale(f32 moon_scale) { m_moon_params.scale = moon_scale; } void setStarsVisible(bool stars_visible) { m_star_params.visible = stars_visible; } - void setStarCount(u16 star_count, bool force_update); + void setStarCount(u16 star_count); void setStarColor(video::SColor star_color) { m_star_params.starcolor = star_color; } void setStarScale(f32 star_scale) { m_star_params.scale = star_scale; updateStars(); } @@ -150,7 +152,7 @@ private: bool m_visible = true; // Used when m_visible=false video::SColor m_fallback_bg_color = video::SColor(255, 255, 255, 255); - bool m_first_update = true; + bool m_first_update = true; // Set before the sky is updated for the first time float m_time_of_day; float m_time_brightness; bool m_sunlight_seen; @@ -206,7 +208,6 @@ private: void draw_stars(video::IVideoDriver *driver, float wicked_time_of_day); void place_sky_body(std::array<video::S3DVertex, 4> &vertices, float horizon_position, float day_position); - void setSkyDefaults(); }; // calculates value for sky body positions for the given observed time of day diff --git a/src/client/tile.cpp b/src/client/tile.cpp index a31e3aca1..aa78c50f0 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -33,6 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "imagefilters.h" #include "guiscalingfilter.h" #include "renderingengine.h" +#include "util/base64.h" /* A cache from texture name to texture path @@ -762,6 +763,9 @@ void TextureSource::rebuildImagesAndTextures() // Recreate textures for (TextureInfo &ti : m_textureinfo_cache) { + if (ti.name.empty()) + continue; // Skip dummy entry + video::IImage *img = generateImage(ti.name); #if ENABLE_GLES img = Align2Npot2(img, driver); @@ -1056,6 +1060,45 @@ static std::string unescape_string(const std::string &str, const char esc = '\\' return out; } +void blitBaseImage(video::IImage* &src, video::IImage* &dst) +{ + //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl; + // Size of the copied area + core::dimension2d<u32> dim = src->getDimension(); + //core::dimension2d<u32> dim(16,16); + // Position to copy the blitted to in the base image + core::position2d<s32> pos_to(0,0); + // Position to copy the blitted from in the blitted image + core::position2d<s32> pos_from(0,0); + // Blit + /*image->copyToWithAlpha(baseimg, pos_to, + core::rect<s32>(pos_from, dim), + video::SColor(255,255,255,255), + NULL);*/ + + core::dimension2d<u32> dim_dst = dst->getDimension(); + if (dim == dim_dst) { + blit_with_alpha(src, dst, pos_from, pos_to, dim); + } else if (dim.Width * dim.Height < dim_dst.Width * dim_dst.Height) { + // Upscale overlying image + video::IImage *scaled_image = RenderingEngine::get_video_driver()-> + createImage(video::ECF_A8R8G8B8, dim_dst); + src->copyToScaling(scaled_image); + + blit_with_alpha(scaled_image, dst, pos_from, pos_to, dim_dst); + scaled_image->drop(); + } else { + // Upscale base image + video::IImage *scaled_base = RenderingEngine::get_video_driver()-> + createImage(video::ECF_A8R8G8B8, dim); + dst->copyToScaling(scaled_base); + dst->drop(); + dst = scaled_base; + + blit_with_alpha(src, dst, pos_from, pos_to, dim); + } +} + bool TextureSource::generateImagePart(std::string part_of_name, video::IImage *& baseimg) { @@ -1066,9 +1109,6 @@ bool TextureSource::generateImagePart(std::string part_of_name, // Stuff starting with [ are special commands if (part_of_name.empty() || part_of_name[0] != '[') { video::IImage *image = m_sourcecache.getOrLoad(part_of_name); -#if ENABLE_GLES - image = Align2Npot2(image, driver); -#endif if (image == NULL) { if (!part_of_name.empty()) { @@ -1119,41 +1159,7 @@ bool TextureSource::generateImagePart(std::string part_of_name, // Else blit on base. else { - //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl; - // Size of the copied area - core::dimension2d<u32> dim = image->getDimension(); - //core::dimension2d<u32> dim(16,16); - // Position to copy the blitted to in the base image - core::position2d<s32> pos_to(0,0); - // Position to copy the blitted from in the blitted image - core::position2d<s32> pos_from(0,0); - // Blit - /*image->copyToWithAlpha(baseimg, pos_to, - core::rect<s32>(pos_from, dim), - video::SColor(255,255,255,255), - NULL);*/ - - core::dimension2d<u32> dim_dst = baseimg->getDimension(); - if (dim == dim_dst) { - blit_with_alpha(image, baseimg, pos_from, pos_to, dim); - } else if (dim.Width * dim.Height < dim_dst.Width * dim_dst.Height) { - // Upscale overlying image - video::IImage *scaled_image = RenderingEngine::get_video_driver()-> - createImage(video::ECF_A8R8G8B8, dim_dst); - image->copyToScaling(scaled_image); - - blit_with_alpha(scaled_image, baseimg, pos_from, pos_to, dim_dst); - scaled_image->drop(); - } else { - // Upscale base image - video::IImage *scaled_base = RenderingEngine::get_video_driver()-> - createImage(video::ECF_A8R8G8B8, dim); - baseimg->copyToScaling(scaled_base); - baseimg->drop(); - baseimg = scaled_base; - - blit_with_alpha(image, baseimg, pos_from, pos_to, dim); - } + blitBaseImage(image, baseimg); } //cleanup image->drop(); @@ -1781,6 +1787,43 @@ bool TextureSource::generateImagePart(std::string part_of_name, baseimg->drop(); baseimg = img; } + /* + [png:base64 + Decodes a PNG image in base64 form. + Use minetest.encode_png and minetest.encode_base64 + to produce a valid string. + */ + else if (str_starts_with(part_of_name, "[png:")) { + Strfnd sf(part_of_name); + sf.next(":"); + std::string png; + { + std::string blob = sf.next(""); + if (!base64_is_valid(blob)) { + errorstream << "generateImagePart(): " + << "malformed base64 in '[png'" + << std::endl; + return false; + } + png = base64_decode(blob); + } + + auto *device = RenderingEngine::get_raw_device(); + auto *fs = device->getFileSystem(); + auto *vd = device->getVideoDriver(); + auto *memfile = fs->createMemoryReadFile(png.data(), png.size(), "__temp_png"); + video::IImage* pngimg = vd->createImageFromFile(memfile); + memfile->drop(); + + if (baseimg) { + blitBaseImage(pngimg, baseimg); + } else { + core::dimension2d<u32> dim = pngimg->getDimension(); + baseimg = driver->createImage(video::ECF_A8R8G8B8, dim); + pngimg->copyTo(baseimg); + } + pngimg->drop(); + } else { errorstream << "generateImagePart(): Invalid " @@ -2183,6 +2226,48 @@ video::ITexture* TextureSource::getNormalTexture(const std::string &name) return NULL; } +namespace { + // For more colourspace transformations, see for example + // https://github.com/tobspr/GLSL-Color-Spaces/blob/master/ColorSpaces.inc.glsl + + inline float linear_to_srgb_component(float v) + { + if (v > 0.0031308f) + return 1.055f * powf(v, 1.0f / 2.4f) - 0.055f; + return 12.92f * v; + } + inline float srgb_to_linear_component(float v) + { + if (v > 0.04045f) + return powf((v + 0.055f) / 1.055f, 2.4f); + return v / 12.92f; + } + + v3f srgb_to_linear(const video::SColor &col_srgb) + { + v3f col(col_srgb.getRed(), col_srgb.getGreen(), col_srgb.getBlue()); + col /= 255.0f; + col.X = srgb_to_linear_component(col.X); + col.Y = srgb_to_linear_component(col.Y); + col.Z = srgb_to_linear_component(col.Z); + return col; + } + + video::SColor linear_to_srgb(const v3f &col_linear) + { + v3f col; + col.X = linear_to_srgb_component(col_linear.X); + col.Y = linear_to_srgb_component(col_linear.Y); + col.Z = linear_to_srgb_component(col_linear.Z); + col *= 255.0f; + col.X = core::clamp<float>(col.X, 0.0f, 255.0f); + col.Y = core::clamp<float>(col.Y, 0.0f, 255.0f); + col.Z = core::clamp<float>(col.Z, 0.0f, 255.0f); + return video::SColor(0xff, myround(col.X), myround(col.Y), + myround(col.Z)); + } +} + video::SColor TextureSource::getTextureAverageColor(const std::string &name) { video::IVideoDriver *driver = RenderingEngine::get_video_driver(); @@ -2197,9 +2282,7 @@ video::SColor TextureSource::getTextureAverageColor(const std::string &name) return c; u32 total = 0; - u32 tR = 0; - u32 tG = 0; - u32 tB = 0; + v3f col_acc(0, 0, 0); core::dimension2d<u32> dim = image->getDimension(); u16 step = 1; if (dim.Width > 16) @@ -2209,17 +2292,14 @@ video::SColor TextureSource::getTextureAverageColor(const std::string &name) c = image->getPixel(x,y); if (c.getAlpha() > 0) { total++; - tR += c.getRed(); - tG += c.getGreen(); - tB += c.getBlue(); + col_acc += srgb_to_linear(c); } } } image->drop(); if (total > 0) { - c.setRed(tR / total); - c.setGreen(tG / total); - c.setBlue(tB / total); + col_acc /= total; + c = linear_to_srgb(col_acc); } c.setAlpha(255); return c; diff --git a/src/client/tile.h b/src/client/tile.h index fcdc46460..e55a26e56 100644 --- a/src/client/tile.h +++ b/src/client/tile.h @@ -195,6 +195,7 @@ struct TileLayer texture_id == other.texture_id && material_type == other.material_type && material_flags == other.material_flags && + has_color == other.has_color && color == other.color && scale == other.scale; } @@ -259,6 +260,17 @@ struct TileLayer && (material_flags & MATERIAL_FLAG_TILEABLE_VERTICAL); } + bool isTransparent() const + { + switch (material_type) { + case TILE_MATERIAL_ALPHA: + case TILE_MATERIAL_LIQUID_TRANSPARENT: + case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT: + return true; + } + return false; + } + // Ordered for size, please do not reorder video::ITexture *texture = nullptr; @@ -288,9 +300,9 @@ struct TileLayer * The color of the tile, or if the tile does not own * a color then the color of the node owning this tile. */ - video::SColor color; + video::SColor color = video::SColor(0, 0, 0, 0); - u8 scale; + u8 scale = 1; }; /*! @@ -307,7 +319,8 @@ struct TileSpec for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) { if (layers[layer] != other.layers[layer]) return false; - if (!layers[layer].isTileable()) + // Only non-transparent tiles can be merged into fast faces + if (layers[layer].isTransparent() || !layers[layer].isTileable()) return false; } return rotation == 0 diff --git a/src/client/wieldmesh.cpp b/src/client/wieldmesh.cpp index 6beed3f3a..25b343573 100644 --- a/src/client/wieldmesh.cpp +++ b/src/client/wieldmesh.cpp @@ -386,6 +386,9 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che m_colors.emplace_back(); // overlay is white, if present m_colors.emplace_back(true, video::SColor(0xFFFFFFFF)); + // initialize the color + if (!m_lighting) + setColor(video::SColor(0xFFFFFFFF)); return; } @@ -457,13 +460,26 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che material.setFlag(video::EMF_BILINEAR_FILTER, m_bilinear_filter); material.setFlag(video::EMF_TRILINEAR_FILTER, m_trilinear_filter); } + + // initialize the color + if (!m_lighting) + setColor(video::SColor(0xFFFFFFFF)); return; - } else if (!def.inventory_image.empty()) { - setExtruded(def.inventory_image, def.inventory_overlay, def.wield_scale, - tsrc, 1); + } else { + if (!def.inventory_image.empty()) { + setExtruded(def.inventory_image, def.inventory_overlay, def.wield_scale, + tsrc, 1); + } else { + setExtruded("no_texture.png", "", def.wield_scale, tsrc, 1); + } + m_colors.emplace_back(); // overlay is white, if present m_colors.emplace_back(true, video::SColor(0xFFFFFFFF)); + + // initialize the color + if (!m_lighting) + setColor(video::SColor(0xFFFFFFFF)); return; } @@ -510,8 +526,9 @@ void WieldMeshSceneNode::setNodeLightColor(video::SColor color) material.EmissiveColor = color; } } - - setColor(color); + else { + setColor(color); + } } void WieldMeshSceneNode::render() @@ -536,9 +553,14 @@ void WieldMeshSceneNode::changeToMesh(scene::IMesh *mesh) m_meshnode->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, m_lighting); m_meshnode->setVisible(true); - // Add mesh to shadow caster - if (m_shadow) + if (m_shadow) { + // Add mesh to shadow caster m_shadow->addNodeToShadowList(m_meshnode); + + // Set shadow texture + for (u32 i = 0; i < m_meshnode->getMaterialCount(); i++) + m_meshnode->setMaterialTexture(3, m_shadow->get_texture()); + } } void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) diff --git a/src/clientiface.cpp b/src/clientiface.cpp index f35dcd0eb..5733b05de 100644 --- a/src/clientiface.cpp +++ b/src/clientiface.cpp @@ -472,20 +472,14 @@ void RemoteClient::notifyEvent(ClientStateEvent event) { case CSE_AuthAccept: m_state = CS_AwaitingInit2; - if (chosen_mech == AUTH_MECHANISM_SRP || - chosen_mech == AUTH_MECHANISM_LEGACY_PASSWORD) - srp_verifier_delete((SRPVerifier *) auth_data); - chosen_mech = AUTH_MECHANISM_NONE; + resetChosenMech(); break; case CSE_Disconnect: m_state = CS_Disconnecting; break; case CSE_SetDenied: m_state = CS_Denied; - if (chosen_mech == AUTH_MECHANISM_SRP || - chosen_mech == AUTH_MECHANISM_LEGACY_PASSWORD) - srp_verifier_delete((SRPVerifier *) auth_data); - chosen_mech = AUTH_MECHANISM_NONE; + resetChosenMech(); break; default: myerror << "HelloSent: Invalid client state transition! " << event; @@ -561,9 +555,7 @@ void RemoteClient::notifyEvent(ClientStateEvent event) break; case CSE_SudoSuccess: m_state = CS_SudoMode; - if (chosen_mech == AUTH_MECHANISM_SRP) - srp_verifier_delete((SRPVerifier *) auth_data); - chosen_mech = AUTH_MECHANISM_NONE; + resetChosenMech(); break; /* Init GotInit2 SetDefinitionsSent SetMediaSent SetDenied */ default: @@ -596,6 +588,15 @@ void RemoteClient::notifyEvent(ClientStateEvent event) } } +void RemoteClient::resetChosenMech() +{ + if (auth_data) { + srp_verifier_delete((SRPVerifier *) auth_data); + auth_data = nullptr; + } + chosen_mech = AUTH_MECHANISM_NONE; +} + u64 RemoteClient::uptime() const { return porting::getTimeS() - m_connection_time; @@ -714,31 +715,6 @@ void ClientInterface::sendToAll(NetworkPacket *pkt) } } -void ClientInterface::sendToAllCompat(NetworkPacket *pkt, NetworkPacket *legacypkt, - u16 min_proto_ver) -{ - RecursiveMutexAutoLock clientslock(m_clients_mutex); - for (auto &client_it : m_clients) { - RemoteClient *client = client_it.second; - NetworkPacket *pkt_to_send = nullptr; - - if (client->net_proto_version >= min_proto_ver) { - pkt_to_send = pkt; - } else if (client->net_proto_version != 0) { - pkt_to_send = legacypkt; - } else { - warningstream << "Client with unhandled version to handle: '" - << client->net_proto_version << "'"; - continue; - } - - m_con->Send(client->peer_id, - clientCommandFactoryTable[pkt_to_send->getCommand()].channel, - pkt_to_send, - clientCommandFactoryTable[pkt_to_send->getCommand()].reliable); - } -} - RemoteClient* ClientInterface::getClientNoEx(session_t peer_id, ClientState state_min) { RecursiveMutexAutoLock clientslock(m_clients_mutex); diff --git a/src/clientiface.h b/src/clientiface.h index dfd976741..3e7ba4793 100644 --- a/src/clientiface.h +++ b/src/clientiface.h @@ -27,6 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "network/networkprotocol.h" #include "network/address.h" #include "porting.h" +#include "threading/mutex_auto_lock.h" #include <list> #include <vector> @@ -242,6 +243,8 @@ public: u32 allowed_auth_mechs = 0; u32 allowed_sudo_mechs = 0; + void resetChosenMech(); + bool isSudoMechAllowed(AuthMechanism mech) { return allowed_sudo_mechs & mech; } bool isMechAllowed(AuthMechanism mech) @@ -340,7 +343,7 @@ public: u8 getMinor() const { return m_version_minor; } u8 getPatch() const { return m_version_patch; } const std::string &getFullVer() const { return m_full_version; } - + void setLangCode(const std::string &code) { m_lang_code = code; } const std::string &getLangCode() const { return m_lang_code; } @@ -465,7 +468,6 @@ public: /* send to all clients */ void sendToAll(NetworkPacket *pkt); - void sendToAllCompat(NetworkPacket *pkt, NetworkPacket *legacypkt, u16 min_proto_ver); /* delete a client */ void DeleteClient(session_t peer_id); @@ -504,9 +506,13 @@ public: static std::string state2Name(ClientState state); protected: - //TODO find way to avoid this functions - void lock() { m_clients_mutex.lock(); } - void unlock() { m_clients_mutex.unlock(); } + class AutoLock { + public: + AutoLock(ClientInterface &iface): m_lock(iface.m_clients_mutex) {} + + private: + RecursiveMutexAutoLock m_lock; + }; RemoteClientMap& getClientList() { return m_clients; } diff --git a/src/cmake_config.h.in b/src/cmake_config.h.in index cfcee4b58..b1298165e 100644 --- a/src/cmake_config.h.in +++ b/src/cmake_config.h.in @@ -18,7 +18,6 @@ #cmakedefine01 USE_GETTEXT #cmakedefine01 USE_CURL #cmakedefine01 USE_SOUND -#cmakedefine01 USE_FREETYPE #cmakedefine01 USE_CURSES #cmakedefine01 USE_LEVELDB #cmakedefine01 USE_LUAJIT @@ -36,3 +35,4 @@ #cmakedefine01 CURSES_HAVE_NCURSESW_NCURSES_H #cmakedefine01 CURSES_HAVE_NCURSESW_CURSES_H #cmakedefine01 BUILD_UNITTESTS +#cmakedefine01 BUILD_BENCHMARKS diff --git a/src/collision.cpp b/src/collision.cpp index 2e788956d..4f2cba263 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -153,7 +153,7 @@ CollisionAxis axisAlignedCollision( (std::max(movingbox.MaxEdge.Z + speed.Z * time, staticbox.MaxEdge.Z) - std::min(movingbox.MinEdge.Z + speed.Z * time, staticbox.MinEdge.Z) - relbox.MinEdge.Z < 0) - ) + ) return COLLISION_AXIS_X; } } else { @@ -180,7 +180,7 @@ CollisionAxis axisAlignedCollision( (std::max(movingbox.MaxEdge.Y + speed.Y * time, staticbox.MaxEdge.Y) - std::min(movingbox.MinEdge.Y + speed.Y * time, staticbox.MinEdge.Y) - relbox.MinEdge.Y < 0) - ) + ) return COLLISION_AXIS_Z; } } @@ -304,7 +304,8 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, if (!(f.walkable || (jesus && f.isLiquid()))) continue; - int n_bouncy_value = itemgroup_get(f.groups, "bouncy"); + // Negative bouncy may have a meaning, but we need +value here. + int n_bouncy_value = abs(itemgroup_get(f.groups, "bouncy")); int neighbors = 0; if (f.drawtype == NDT_NODEBOX && diff --git a/src/config.h b/src/config.h index 5e1164642..50e118428 100644 --- a/src/config.h +++ b/src/config.h @@ -11,17 +11,13 @@ #if defined USE_CMAKE_CONFIG_H #include "cmake_config.h" -#elif defined (__ANDROID__) - #define PROJECT_NAME "minetest" - #define PROJECT_NAME_C "Minetest" - #define STATIC_SHAREDIR "" - #define VERSION_STRING STR(VERSION_MAJOR) "." STR(VERSION_MINOR) "." STR(VERSION_PATCH) STR(VERSION_EXTRA) -#ifdef NDEBUG - #define BUILD_TYPE "Release" - #else - #define BUILD_TYPE "Debug" - #endif #else + #if defined (__ANDROID__) + #define PROJECT_NAME "minetest" + #define PROJECT_NAME_C "Minetest" + #define STATIC_SHAREDIR "" + #define VERSION_STRING STR(VERSION_MAJOR) "." STR(VERSION_MINOR) "." STR(VERSION_PATCH) STR(VERSION_EXTRA) + #endif #ifdef NDEBUG #define BUILD_TYPE "Release" #else diff --git a/src/constants.h b/src/constants.h index 3cc3af094..b9d4f8d70 100644 --- a/src/constants.h +++ b/src/constants.h @@ -64,7 +64,7 @@ with this program; if not, write to the Free Software Foundation, Inc., // I really don't want to make every algorithm to check if it's going near // the limit or not, so this is lower. // This is the maximum value the setting map_generation_limit can be -#define MAX_MAP_GENERATION_LIMIT (31000) +#define MAX_MAP_GENERATION_LIMIT (31007) // Size of node in floating-point units // The original idea behind this is to disallow plain casts between @@ -111,4 +111,3 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #define TTF_DEFAULT_FONT_SIZE (16) -#define DEFAULT_FONT_SIZE (10) diff --git a/src/content/mods.cpp b/src/content/mods.cpp index 6f088a5b3..f75119bbb 100644 --- a/src/content/mods.cpp +++ b/src/content/mods.cpp @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <json/json.h> #include <algorithm> #include "content/mods.h" +#include "database/database.h" #include "filesys.h" #include "log.h" #include "content/subgames.h" @@ -88,7 +89,7 @@ void parseModContents(ModSpec &spec) modpack2_is.close(); spec.is_modpack = true; - spec.modpack_content = getModsInPath(spec.path, true); + spec.modpack_content = getModsInPath(spec.path, spec.virtual_path, true); } else { Settings info; @@ -166,13 +167,14 @@ void parseModContents(ModSpec &spec) } std::map<std::string, ModSpec> getModsInPath( - const std::string &path, bool part_of_modpack) + const std::string &path, const std::string &virtual_path, bool part_of_modpack) { // NOTE: this function works in mutual recursion with parseModContents std::map<std::string, ModSpec> result; std::vector<fs::DirListNode> dirlist = fs::GetDirListing(path); - std::string modpath; + std::string mod_path; + std::string mod_virtual_path; for (const fs::DirListNode &dln : dirlist) { if (!dln.dir) @@ -184,10 +186,14 @@ std::map<std::string, ModSpec> getModsInPath( if (modname[0] == '.') continue; - modpath.clear(); - modpath.append(path).append(DIR_DELIM).append(modname); + mod_path.clear(); + mod_path.append(path).append(DIR_DELIM).append(modname); - ModSpec spec(modname, modpath, part_of_modpack); + mod_virtual_path.clear(); + // Intentionally uses / to keep paths same on different platforms + mod_virtual_path.append(virtual_path).append("/").append(modname); + + ModSpec spec(modname, mod_path, part_of_modpack, mod_virtual_path); parseModContents(spec); result.insert(std::make_pair(modname, spec)); } @@ -227,9 +233,9 @@ void ModConfiguration::printUnsatisfiedModsError() const } } -void ModConfiguration::addModsInPath(const std::string &path) +void ModConfiguration::addModsInPath(const std::string &path, const std::string &virtual_path) { - addMods(flattenMods(getModsInPath(path))); + addMods(flattenMods(getModsInPath(path, virtual_path))); } void ModConfiguration::addMods(const std::vector<ModSpec> &new_mods) @@ -293,29 +299,39 @@ void ModConfiguration::addMods(const std::vector<ModSpec> &new_mods) } void ModConfiguration::addModsFromConfig( - const std::string &settings_path, const std::set<std::string> &mods) + const std::string &settings_path, + const std::unordered_map<std::string, std::string> &modPaths) { Settings conf; - std::set<std::string> load_mod_names; + std::unordered_map<std::string, std::string> load_mod_names; conf.readConfigFile(settings_path.c_str()); std::vector<std::string> names = conf.getNames(); for (const std::string &name : names) { - if (name.compare(0, 9, "load_mod_") == 0 && conf.get(name) != "false" && - conf.get(name) != "nil") - load_mod_names.insert(name.substr(9)); + const auto &value = conf.get(name); + if (name.compare(0, 9, "load_mod_") == 0 && value != "false" && + value != "nil") + load_mod_names[name.substr(9)] = value; } std::vector<ModSpec> addon_mods; - for (const std::string &i : mods) { - std::vector<ModSpec> addon_mods_in_path = flattenMods(getModsInPath(i)); + std::unordered_map<std::string, std::vector<std::string>> candidates; + + for (const auto &modPath : modPaths) { + std::vector<ModSpec> addon_mods_in_path = flattenMods(getModsInPath(modPath.second, modPath.first)); for (std::vector<ModSpec>::const_iterator it = addon_mods_in_path.begin(); it != addon_mods_in_path.end(); ++it) { const ModSpec &mod = *it; - if (load_mod_names.count(mod.name) != 0) - addon_mods.push_back(mod); - else + const auto &pair = load_mod_names.find(mod.name); + if (pair != load_mod_names.end()) { + if (is_yes(pair->second) || pair->second == mod.virtual_path) { + addon_mods.push_back(mod); + } else { + candidates[pair->first].emplace_back(mod.virtual_path); + } + } else { conf.setBool("load_mod_" + mod.name, false); + } } } conf.updateConfigFile(settings_path.c_str()); @@ -334,9 +350,22 @@ void ModConfiguration::addModsFromConfig( if (!load_mod_names.empty()) { errorstream << "The following mods could not be found:"; - for (const std::string &mod : load_mod_names) - errorstream << " \"" << mod << "\""; + for (const auto &pair : load_mod_names) + errorstream << " \"" << pair.first << "\""; errorstream << std::endl; + + for (const auto &pair : load_mod_names) { + const auto &candidate = candidates.find(pair.first); + if (candidate != candidates.end()) { + errorstream << "Unable to load " << pair.first << " as the specified path " + << pair.second << " could not be found. " + << "However, it is available in the following locations:" + << std::endl; + for (const auto &path : candidate->second) { + errorstream << " - " << path << std::endl; + } + } + } } } @@ -412,93 +441,41 @@ void ModConfiguration::resolveDependencies() ClientModConfiguration::ClientModConfiguration(const std::string &path) : ModConfiguration(path) { - std::set<std::string> paths; + std::unordered_map<std::string, std::string> paths; std::string path_user = porting::path_user + DIR_DELIM + "clientmods"; - paths.insert(path); - paths.insert(path_user); + if (path != path_user) { + paths["share"] = path; + } + paths["mods"] = path_user; std::string settings_path = path_user + DIR_DELIM + "mods.conf"; addModsFromConfig(settings_path, paths); } #endif -ModMetadata::ModMetadata(const std::string &mod_name) : m_mod_name(mod_name) +ModMetadata::ModMetadata(const std::string &mod_name, ModMetadataDatabase *database): + m_mod_name(mod_name), m_database(database) { + m_database->getModEntries(m_mod_name, &m_stringvars); } void ModMetadata::clear() { + for (const auto &pair : m_stringvars) { + m_database->removeModEntry(m_mod_name, pair.first); + } Metadata::clear(); - m_modified = true; } -bool ModMetadata::save(const std::string &root_path) +bool ModMetadata::setString(const std::string &name, const std::string &var) { - Json::Value json; - for (StringMap::const_iterator it = m_stringvars.begin(); - it != m_stringvars.end(); ++it) { - json[it->first] = it->second; - } - - if (!fs::PathExists(root_path)) { - if (!fs::CreateAllDirs(root_path)) { - errorstream << "ModMetadata[" << m_mod_name - << "]: Unable to save. '" << root_path - << "' tree cannot be created." << std::endl; - return false; + if (Metadata::setString(name, var)) { + if (var.empty()) { + m_database->removeModEntry(m_mod_name, name); + } else { + m_database->setModEntry(m_mod_name, name, var); } - } else if (!fs::IsDir(root_path)) { - errorstream << "ModMetadata[" << m_mod_name << "]: Unable to save. '" - << root_path << "' is not a directory." << std::endl; - return false; - } - - bool w_ok = fs::safeWriteToFile( - root_path + DIR_DELIM + m_mod_name, fastWriteJson(json)); - - if (w_ok) { - m_modified = false; - } else { - errorstream << "ModMetadata[" << m_mod_name << "]: failed write file." - << std::endl; - } - return w_ok; -} - -bool ModMetadata::load(const std::string &root_path) -{ - m_stringvars.clear(); - - std::ifstream is((root_path + DIR_DELIM + m_mod_name).c_str(), - std::ios_base::binary); - if (!is.good()) { - return false; - } - - Json::Value root; - Json::CharReaderBuilder builder; - builder.settings_["collectComments"] = false; - std::string errs; - - if (!Json::parseFromStream(builder, is, &root, &errs)) { - errorstream << "ModMetadata[" << m_mod_name - << "]: failed read data " - "(Json decoding failure). Message: " - << errs << std::endl; - return false; - } - - const Json::Value::Members attr_list = root.getMemberNames(); - for (const auto &it : attr_list) { - Json::Value attr_value = root[it]; - m_stringvars[it] = attr_value.asString(); + return true; } - - return true; -} - -bool ModMetadata::setString(const std::string &name, const std::string &var) -{ - m_modified = Metadata::setString(name, var); - return m_modified; + return false; } diff --git a/src/content/mods.h b/src/content/mods.h index b56a97edb..ab0a9300e 100644 --- a/src/content/mods.h +++ b/src/content/mods.h @@ -31,6 +31,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "config.h" #include "metadata.h" +class ModMetadataDatabase; + #define MODNAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_" struct ModSpec @@ -49,17 +51,36 @@ struct ModSpec bool part_of_modpack = false; bool is_modpack = false; + /** + * A constructed canonical path to represent this mod's location. + * This intended to be used as an identifier for a modpath that tolerates file movement, + * and cannot be used to read the mod files. + * + * Note that `mymod` is the directory name, not the mod name specified in mod.conf. + * + * Ex: + * + * - mods/mymod + * - mods/mymod (1) + * (^ this would have name=mymod in mod.conf) + * - mods/modpack1/mymod + * - games/mygame/mods/mymod + * - worldmods/mymod + */ + std::string virtual_path; + // For logging purposes std::vector<const char *> deprecation_msgs; // if modpack: std::map<std::string, ModSpec> modpack_content; - ModSpec(const std::string &name = "", const std::string &path = "") : - name(name), path(path) + + ModSpec() { } - ModSpec(const std::string &name, const std::string &path, bool part_of_modpack) : - name(name), path(path), part_of_modpack(part_of_modpack) + + ModSpec(const std::string &name, const std::string &path, bool part_of_modpack, const std::string &virtual_path) : + name(name), path(path), part_of_modpack(part_of_modpack), virtual_path(virtual_path) { } @@ -69,8 +90,16 @@ struct ModSpec // Retrieves depends, optdepends, is_modpack and modpack_content void parseModContents(ModSpec &mod); -std::map<std::string, ModSpec> getModsInPath( - const std::string &path, bool part_of_modpack = false); +/** + * Gets a list of all mods and modpacks in path + * + * @param Path to search, should be absolute + * @param part_of_modpack Is this searching within a modpack? + * @param virtual_path Virtual path for this directory, see comment in ModSpec + * @returns map of mods + */ +std::map<std::string, ModSpec> getModsInPath(const std::string &path, + const std::string &virtual_path, bool part_of_modpack = false); // replaces modpack Modspecs with their content std::vector<ModSpec> flattenMods(const std::map<std::string, ModSpec> &mods); @@ -95,15 +124,25 @@ public: protected: ModConfiguration(const std::string &worldpath); - // adds all mods in the given path. used for games, modpacks - // and world-specific mods (worldmods-folders) - void addModsInPath(const std::string &path); + + /** + * adds all mods in the given path. used for games, modpacks + * and world-specific mods (worldmods-folders) + * + * @param path To search, should be absolute + * @param virtual_path Virtual path for this directory, see comment in ModSpec + */ + void addModsInPath(const std::string &path, const std::string &virtual_path); // adds all mods in the set. void addMods(const std::vector<ModSpec> &new_mods); + /** + * @param settings_path Path to world.mt + * @param modPaths Map from virtual name to mod path + */ void addModsFromConfig(const std::string &settings_path, - const std::set<std::string> &mods); + const std::unordered_map<std::string, std::string> &modPaths); void checkConflictsAndDeps(); @@ -149,20 +188,16 @@ class ModMetadata : public Metadata { public: ModMetadata() = delete; - ModMetadata(const std::string &mod_name); + ModMetadata(const std::string &mod_name, ModMetadataDatabase *database); ~ModMetadata() = default; virtual void clear(); - bool save(const std::string &root_path); - bool load(const std::string &root_path); - - bool isModified() const { return m_modified; } const std::string &getModName() const { return m_mod_name; } virtual bool setString(const std::string &name, const std::string &var); private: std::string m_mod_name; - bool m_modified = false; + ModMetadataDatabase *m_database; }; diff --git a/src/content/subgames.cpp b/src/content/subgames.cpp index e9dc609b0..23355990e 100644 --- a/src/content/subgames.cpp +++ b/src/content/subgames.cpp @@ -24,7 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "log.h" #include "util/strfnd.h" #include "defaultsettings.h" // for set_default_settings -#include "mapgen/mapgen.h" // for MapgenParams +#include "map_settings_manager.h" #include "util/string.h" #ifndef SERVER @@ -107,11 +107,14 @@ SubgameSpec findSubgame(const std::string &id) std::string gamemod_path = game_path + DIR_DELIM + "mods"; // Find mod directories - std::set<std::string> mods_paths; - if (!user_game) - mods_paths.insert(share + DIR_DELIM + "mods"); - if (user != share || user_game) - mods_paths.insert(user + DIR_DELIM + "mods"); + std::unordered_map<std::string, std::string> mods_paths; + mods_paths["mods"] = user + DIR_DELIM + "mods"; + if (!user_game && user != share) + mods_paths["share"] = share + DIR_DELIM + "mods"; + + for (const std::string &mod_path : getEnvModPaths()) { + mods_paths[fs::AbsolutePath(mod_path)] = mod_path; + } // Get meta std::string conf_path = game_path + DIR_DELIM + "game.conf"; @@ -354,6 +357,7 @@ void loadGameConfAndInitWorld(const std::string &path, const std::string &name, conf.set("backend", "sqlite3"); conf.set("player_backend", "sqlite3"); conf.set("auth_backend", "sqlite3"); + conf.set("mod_storage_backend", "sqlite3"); conf.setBool("creative_mode", g_settings->getBool("creative_mode")); conf.setBool("enable_damage", g_settings->getBool("enable_damage")); @@ -365,22 +369,25 @@ void loadGameConfAndInitWorld(const std::string &path, const std::string &name, // Create map_meta.txt if does not already exist std::string map_meta_path = final_path + DIR_DELIM + "map_meta.txt"; if (!fs::PathExists(map_meta_path)) { - verbosestream << "Creating map_meta.txt (" << map_meta_path << ")" - << std::endl; - std::ostringstream oss(std::ios_base::binary); - - Settings conf; - MapgenParams params; + MapSettingsManager mgr(map_meta_path); - params.readParams(g_settings); - params.writeParams(&conf); - conf.writeLines(oss); - oss << "[end_of_params]\n"; + mgr.setMapSetting("seed", g_settings->get("fixed_map_seed")); - fs::safeWriteToFile(map_meta_path, oss.str()); + mgr.makeMapgenParams(); + mgr.saveMapMeta(); } // The Settings object is no longer needed for created worlds if (new_game_settings) delete game_settings; } + +std::vector<std::string> getEnvModPaths() +{ + const char *c_mod_path = getenv("MINETEST_MOD_PATH"); + std::vector<std::string> paths; + Strfnd search_paths(c_mod_path ? c_mod_path : ""); + while (!search_paths.at_end()) + paths.push_back(search_paths.next(PATH_DELIM)); + return paths; +} diff --git a/src/content/subgames.h b/src/content/subgames.h index 60392639b..d36b4952f 100644 --- a/src/content/subgames.h +++ b/src/content/subgames.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <string> #include <set> +#include <unordered_map> #include <vector> class Settings; @@ -33,13 +34,16 @@ struct SubgameSpec int release; std::string path; std::string gamemods_path; - std::set<std::string> addon_mods_paths; + + /** + * Map from virtual path to mods path + */ + std::unordered_map<std::string, std::string> addon_mods_paths; std::string menuicon_path; SubgameSpec(const std::string &id = "", const std::string &path = "", const std::string &gamemods_path = "", - const std::set<std::string> &addon_mods_paths = - std::set<std::string>(), + const std::unordered_map<std::string, std::string> &addon_mods_paths = {}, const std::string &name = "", const std::string &menuicon_path = "", const std::string &author = "", int release = 0) : @@ -58,6 +62,8 @@ SubgameSpec findWorldSubgame(const std::string &world_path); std::set<std::string> getAvailableGameIds(); std::vector<SubgameSpec> getAvailableGames(); +// Get the list of paths to mods in the environment variable $MINETEST_MOD_PATH +std::vector<std::string> getEnvModPaths(); bool getWorldExists(const std::string &world_path); //! Try to get the displayed name of a world diff --git a/src/craftdef.cpp b/src/craftdef.cpp index 210605198..c05a0cfb7 100644 --- a/src/craftdef.cpp +++ b/src/craftdef.cpp @@ -734,7 +734,8 @@ bool CraftDefinitionCooking::check(const CraftInput &input, IGameDef *gamedef) c } // Check the single input item - return inputItemMatchesRecipe(input_filtered[0], recipe, gamedef->idef()); + std::string rec_name = craftGetItemName(recipe, gamedef); + return inputItemMatchesRecipe(input_filtered[0], rec_name, gamedef->idef()); } CraftOutput CraftDefinitionCooking::getOutput(const CraftInput &input, IGameDef *gamedef) const @@ -836,7 +837,8 @@ bool CraftDefinitionFuel::check(const CraftInput &input, IGameDef *gamedef) cons } // Check the single input item - return inputItemMatchesRecipe(input_filtered[0], recipe, gamedef->idef()); + std::string rec_name = craftGetItemName(recipe, gamedef); + return inputItemMatchesRecipe(input_filtered[0], rec_name, gamedef->idef()); } CraftOutput CraftDefinitionFuel::getOutput(const CraftInput &input, IGameDef *gamedef) const diff --git a/src/database/database-dummy.cpp b/src/database/database-dummy.cpp index b56f341c5..629b2fb04 100644 --- a/src/database/database-dummy.cpp +++ b/src/database/database-dummy.cpp @@ -80,3 +80,41 @@ void Database_Dummy::listPlayers(std::vector<std::string> &res) res.emplace_back(player); } } + +bool Database_Dummy::getModEntries(const std::string &modname, StringMap *storage) +{ + const auto mod_pair = m_mod_meta_database.find(modname); + if (mod_pair != m_mod_meta_database.cend()) { + for (const auto &pair : mod_pair->second) { + (*storage)[pair.first] = pair.second; + } + } + return true; +} + +bool Database_Dummy::setModEntry(const std::string &modname, + const std::string &key, const std::string &value) +{ + auto mod_pair = m_mod_meta_database.find(modname); + if (mod_pair == m_mod_meta_database.end()) { + m_mod_meta_database[modname] = StringMap({{key, value}}); + } else { + mod_pair->second[key] = value; + } + return true; +} + +bool Database_Dummy::removeModEntry(const std::string &modname, const std::string &key) +{ + auto mod_pair = m_mod_meta_database.find(modname); + if (mod_pair != m_mod_meta_database.end()) + return mod_pair->second.erase(key) > 0; + return false; +} + +void Database_Dummy::listMods(std::vector<std::string> *res) +{ + for (const auto &pair : m_mod_meta_database) { + res->push_back(pair.first); + } +} diff --git a/src/database/database-dummy.h b/src/database/database-dummy.h index b69919f84..44b9e8d68 100644 --- a/src/database/database-dummy.h +++ b/src/database/database-dummy.h @@ -24,7 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "database.h" #include "irrlichttypes.h" -class Database_Dummy : public MapDatabase, public PlayerDatabase +class Database_Dummy : public MapDatabase, public PlayerDatabase, public ModMetadataDatabase { public: bool saveBlock(const v3s16 &pos, const std::string &data); @@ -37,10 +37,17 @@ public: bool removePlayer(const std::string &name); void listPlayers(std::vector<std::string> &res); + bool getModEntries(const std::string &modname, StringMap *storage); + bool setModEntry(const std::string &modname, + const std::string &key, const std::string &value); + bool removeModEntry(const std::string &modname, const std::string &key); + void listMods(std::vector<std::string> *res); + void beginSave() {} void endSave() {} private: std::map<s64, std::string> m_database; std::set<std::string> m_player_database; + std::unordered_map<std::string, StringMap> m_mod_meta_database; }; diff --git a/src/database/database-files.cpp b/src/database/database-files.cpp index d9d113b4e..7c0dbac28 100644 --- a/src/database/database-files.cpp +++ b/src/database/database-files.cpp @@ -18,7 +18,6 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include <cassert> -#include <json/json.h> #include "convert_json.h" #include "database-files.h" #include "remoteplayer.h" @@ -376,3 +375,127 @@ bool AuthDatabaseFiles::writeAuthFile() } return true; } + +ModMetadataDatabaseFiles::ModMetadataDatabaseFiles(const std::string &savedir): + m_storage_dir(savedir + DIR_DELIM + "mod_storage") +{ +} + +bool ModMetadataDatabaseFiles::getModEntries(const std::string &modname, StringMap *storage) +{ + Json::Value *meta = getOrCreateJson(modname); + if (!meta) + return false; + + const Json::Value::Members attr_list = meta->getMemberNames(); + for (const auto &it : attr_list) { + Json::Value attr_value = (*meta)[it]; + (*storage)[it] = attr_value.asString(); + } + + return true; +} + +bool ModMetadataDatabaseFiles::setModEntry(const std::string &modname, + const std::string &key, const std::string &value) +{ + Json::Value *meta = getOrCreateJson(modname); + if (!meta) + return false; + + (*meta)[key] = Json::Value(value); + m_modified.insert(modname); + + return true; +} + +bool ModMetadataDatabaseFiles::removeModEntry(const std::string &modname, + const std::string &key) +{ + Json::Value *meta = getOrCreateJson(modname); + if (!meta) + return false; + + Json::Value removed; + if (meta->removeMember(key, &removed)) { + m_modified.insert(modname); + return true; + } + return false; +} + +void ModMetadataDatabaseFiles::beginSave() +{ +} + +void ModMetadataDatabaseFiles::endSave() +{ + if (m_modified.empty()) + return; + + if (!fs::CreateAllDirs(m_storage_dir)) { + errorstream << "ModMetadataDatabaseFiles: Unable to save. '" + << m_storage_dir << "' cannot be created." << std::endl; + return; + } + if (!fs::IsDir(m_storage_dir)) { + errorstream << "ModMetadataDatabaseFiles: Unable to save. '" + << m_storage_dir << "' is not a directory." << std::endl; + return; + } + + for (auto it = m_modified.begin(); it != m_modified.end();) { + const std::string &modname = *it; + + const Json::Value &json = m_mod_meta[modname]; + + if (!fs::safeWriteToFile(m_storage_dir + DIR_DELIM + modname, fastWriteJson(json))) { + errorstream << "ModMetadataDatabaseFiles[" << modname + << "]: failed to write file." << std::endl; + ++it; + continue; + } + + it = m_modified.erase(it); + } +} + +void ModMetadataDatabaseFiles::listMods(std::vector<std::string> *res) +{ + // List in-memory metadata first. + for (const auto &pair : m_mod_meta) { + res->push_back(pair.first); + } + + // List other metadata present in the filesystem. + for (const auto &entry : fs::GetDirListing(m_storage_dir)) { + if (!entry.dir && m_mod_meta.count(entry.name) == 0) + res->push_back(entry.name); + } +} + +Json::Value *ModMetadataDatabaseFiles::getOrCreateJson(const std::string &modname) +{ + auto found = m_mod_meta.find(modname); + if (found != m_mod_meta.end()) + return &found->second; + + Json::Value meta(Json::objectValue); + + std::string path = m_storage_dir + DIR_DELIM + modname; + if (fs::PathExists(path)) { + std::ifstream is(path.c_str(), std::ios_base::binary); + + Json::CharReaderBuilder builder; + builder.settings_["collectComments"] = false; + std::string errs; + + if (!Json::parseFromStream(builder, is, &meta, &errs)) { + errorstream << "ModMetadataDatabaseFiles[" << modname + << "]: failed to decode data: " << errs << std::endl; + return nullptr; + } + } + + return &(m_mod_meta[modname] = meta); +} diff --git a/src/database/database-files.h b/src/database/database-files.h index e647a2e24..962e4d7bb 100644 --- a/src/database/database-files.h +++ b/src/database/database-files.h @@ -25,6 +25,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "database.h" #include <unordered_map> +#include <unordered_set> +#include <json/json.h> class PlayerDatabaseFiles : public PlayerDatabase { @@ -69,3 +71,27 @@ private: bool readAuthFile(); bool writeAuthFile(); }; + +class ModMetadataDatabaseFiles : public ModMetadataDatabase +{ +public: + ModMetadataDatabaseFiles(const std::string &savedir); + virtual ~ModMetadataDatabaseFiles() = default; + + virtual bool getModEntries(const std::string &modname, StringMap *storage); + virtual bool setModEntry(const std::string &modname, + const std::string &key, const std::string &value); + virtual bool removeModEntry(const std::string &modname, const std::string &key); + virtual void listMods(std::vector<std::string> *res); + + virtual void beginSave(); + virtual void endSave(); + +private: + Json::Value *getOrCreateJson(const std::string &modname); + bool writeJson(const std::string &modname, const Json::Value &json); + + std::string m_storage_dir; + std::unordered_map<std::string, Json::Value> m_mod_meta; + std::unordered_set<std::string> m_modified; +}; diff --git a/src/database/database-leveldb.cpp b/src/database/database-leveldb.cpp index 39f4c8442..6e59daab3 100644 --- a/src/database/database-leveldb.cpp +++ b/src/database/database-leveldb.cpp @@ -74,7 +74,7 @@ void Database_LevelDB::loadBlock(const v3s16 &pos, std::string *block) i64tos(getBlockAsInteger(pos)), block); if (!status.ok()) - block->clear(); + block->clear(); } bool Database_LevelDB::deleteBlock(const v3s16 &pos) diff --git a/src/database/database-postgresql.cpp b/src/database/database-postgresql.cpp index 3469f4242..9d6501e68 100644 --- a/src/database/database-postgresql.cpp +++ b/src/database/database-postgresql.cpp @@ -495,7 +495,7 @@ void PlayerDatabasePostgreSQL::savePlayer(RemotePlayer *player) execPrepared("remove_player_inventories", 1, rmvalues); execPrepared("remove_player_inventory_items", 1, rmvalues); - std::vector<const InventoryList*> inventory_lists = sao->getInventory()->getLists(); + const auto &inventory_lists = sao->getInventory()->getLists(); std::ostringstream oss; for (u16 i = 0; i < inventory_lists.size(); i++) { const InventoryList* list = inventory_lists[i]; diff --git a/src/database/database-postgresql.h b/src/database/database-postgresql.h index 81b4a2b10..0a9ead01e 100644 --- a/src/database/database-postgresql.h +++ b/src/database/database-postgresql.h @@ -94,7 +94,8 @@ protected: checkResults(PQprepare(m_conn, name.c_str(), sql.c_str(), 0, NULL)); } - const int getPGVersion() const { return m_pgversion; } + int getPGVersion() const { return m_pgversion; } + private: // Database connectivity checks void ping(); diff --git a/src/database/database-sqlite3.cpp b/src/database/database-sqlite3.cpp index 898acc265..9521085e9 100644 --- a/src/database/database-sqlite3.cpp +++ b/src/database/database-sqlite3.cpp @@ -228,11 +228,7 @@ void MapDatabaseSQLite3::createDatabase() void MapDatabaseSQLite3::initStatements() { PREPARE_STATEMENT(read, "SELECT `data` FROM `blocks` WHERE `pos` = ? LIMIT 1"); -#ifdef __ANDROID__ - PREPARE_STATEMENT(write, "INSERT INTO `blocks` (`pos`, `data`) VALUES (?, ?)"); -#else PREPARE_STATEMENT(write, "REPLACE INTO `blocks` (`pos`, `data`) VALUES (?, ?)"); -#endif PREPARE_STATEMENT(delete, "DELETE FROM `blocks` WHERE `pos` = ?"); PREPARE_STATEMENT(list, "SELECT `pos` FROM `blocks`"); @@ -265,19 +261,6 @@ bool MapDatabaseSQLite3::saveBlock(const v3s16 &pos, const std::string &data) { verifyDatabase(); -#ifdef __ANDROID__ - /** - * Note: For some unknown reason SQLite3 fails to REPLACE blocks on Android, - * deleting them and then inserting works. - */ - bindPos(m_stmt_read, pos); - - if (sqlite3_step(m_stmt_read) == SQLITE_ROW) { - deleteBlock(pos); - } - sqlite3_reset(m_stmt_read); -#endif - bindPos(m_stmt_write, pos); SQLOK(sqlite3_bind_blob(m_stmt_write, 2, data.data(), data.size(), NULL), "Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__)); @@ -493,10 +476,10 @@ void PlayerDatabaseSQLite3::savePlayer(RemotePlayer *player) sqlite3_vrfy(sqlite3_step(m_stmt_player_remove_inventory_items), SQLITE_DONE); sqlite3_reset(m_stmt_player_remove_inventory_items); - std::vector<const InventoryList*> inventory_lists = sao->getInventory()->getLists(); + const auto &inventory_lists = sao->getInventory()->getLists(); std::ostringstream oss; for (u16 i = 0; i < inventory_lists.size(); i++) { - const InventoryList* list = inventory_lists[i]; + const InventoryList *list = inventory_lists[i]; str_to_sqlite(m_stmt_player_add_inventory, 1, player->getName()); int_to_sqlite(m_stmt_player_add_inventory, 2, i); @@ -779,3 +762,108 @@ void AuthDatabaseSQLite3::writePrivileges(const AuthEntry &authEntry) sqlite3_reset(m_stmt_write_privs); } } + +ModMetadataDatabaseSQLite3::ModMetadataDatabaseSQLite3(const std::string &savedir): + Database_SQLite3(savedir, "mod_storage"), ModMetadataDatabase() +{ +} + +ModMetadataDatabaseSQLite3::~ModMetadataDatabaseSQLite3() +{ + FINALIZE_STATEMENT(m_stmt_remove) + FINALIZE_STATEMENT(m_stmt_set) + FINALIZE_STATEMENT(m_stmt_get) +} + +void ModMetadataDatabaseSQLite3::createDatabase() +{ + assert(m_database); // Pre-condition + + SQLOK(sqlite3_exec(m_database, + "CREATE TABLE IF NOT EXISTS `entries` (\n" + " `modname` TEXT NOT NULL,\n" + " `key` BLOB NOT NULL,\n" + " `value` BLOB NOT NULL,\n" + " PRIMARY KEY (`modname`, `key`)\n" + ");\n", + NULL, NULL, NULL), + "Failed to create database table"); +} + +void ModMetadataDatabaseSQLite3::initStatements() +{ + PREPARE_STATEMENT(get, "SELECT `key`, `value` FROM `entries` WHERE `modname` = ?"); + PREPARE_STATEMENT(set, + "REPLACE INTO `entries` (`modname`, `key`, `value`) VALUES (?, ?, ?)"); + PREPARE_STATEMENT(remove, "DELETE FROM `entries` WHERE `modname` = ? AND `key` = ?"); +} + +bool ModMetadataDatabaseSQLite3::getModEntries(const std::string &modname, StringMap *storage) +{ + verifyDatabase(); + + str_to_sqlite(m_stmt_get, 1, modname); + while (sqlite3_step(m_stmt_get) == SQLITE_ROW) { + const char *key_data = (const char *) sqlite3_column_blob(m_stmt_get, 0); + size_t key_len = sqlite3_column_bytes(m_stmt_get, 0); + const char *value_data = (const char *) sqlite3_column_blob(m_stmt_get, 1); + size_t value_len = sqlite3_column_bytes(m_stmt_get, 1); + (*storage)[std::string(key_data, key_len)] = std::string(value_data, value_len); + } + sqlite3_vrfy(sqlite3_errcode(m_database), SQLITE_DONE); + + sqlite3_reset(m_stmt_get); + + return true; +} + +bool ModMetadataDatabaseSQLite3::setModEntry(const std::string &modname, + const std::string &key, const std::string &value) +{ + verifyDatabase(); + + str_to_sqlite(m_stmt_set, 1, modname); + SQLOK(sqlite3_bind_blob(m_stmt_set, 2, key.data(), key.size(), NULL), + "Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__)); + SQLOK(sqlite3_bind_blob(m_stmt_set, 3, value.data(), value.size(), NULL), + "Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__)); + SQLRES(sqlite3_step(m_stmt_set), SQLITE_DONE, "Failed to set mod entry") + + sqlite3_reset(m_stmt_set); + + return true; +} + +bool ModMetadataDatabaseSQLite3::removeModEntry(const std::string &modname, + const std::string &key) +{ + verifyDatabase(); + + str_to_sqlite(m_stmt_remove, 1, modname); + SQLOK(sqlite3_bind_blob(m_stmt_remove, 2, key.data(), key.size(), NULL), + "Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__)); + sqlite3_vrfy(sqlite3_step(m_stmt_remove), SQLITE_DONE); + int changes = sqlite3_changes(m_database); + + sqlite3_reset(m_stmt_remove); + + return changes > 0; +} + +void ModMetadataDatabaseSQLite3::listMods(std::vector<std::string> *res) +{ + verifyDatabase(); + + char *errmsg; + int status = sqlite3_exec(m_database, + "SELECT `modname` FROM `entries` GROUP BY `modname`;", + [](void *res_vp, int n_col, char **cols, char **col_names) -> int { + ((decltype(res)) res_vp)->emplace_back(cols[0]); + return 0; + }, (void *) res, &errmsg); + if (status != SQLITE_OK) { + DatabaseException e(std::string("Error trying to list mods with metadata: ") + errmsg); + sqlite3_free(errmsg); + throw e; + } +} diff --git a/src/database/database-sqlite3.h b/src/database/database-sqlite3.h index d7202a918..5e3d7c96c 100644 --- a/src/database/database-sqlite3.h +++ b/src/database/database-sqlite3.h @@ -232,3 +232,28 @@ private: sqlite3_stmt *m_stmt_delete_privs = nullptr; sqlite3_stmt *m_stmt_last_insert_rowid = nullptr; }; + +class ModMetadataDatabaseSQLite3 : private Database_SQLite3, public ModMetadataDatabase +{ +public: + ModMetadataDatabaseSQLite3(const std::string &savedir); + virtual ~ModMetadataDatabaseSQLite3(); + + virtual bool getModEntries(const std::string &modname, StringMap *storage); + virtual bool setModEntry(const std::string &modname, + const std::string &key, const std::string &value); + virtual bool removeModEntry(const std::string &modname, const std::string &key); + virtual void listMods(std::vector<std::string> *res); + + virtual void beginSave() { Database_SQLite3::beginSave(); } + virtual void endSave() { Database_SQLite3::endSave(); } + +protected: + virtual void createDatabase(); + virtual void initStatements(); + +private: + sqlite3_stmt *m_stmt_get = nullptr; + sqlite3_stmt *m_stmt_set = nullptr; + sqlite3_stmt *m_stmt_remove = nullptr; +}; diff --git a/src/database/database.h b/src/database/database.h index b7d551935..fbb5befea 100644 --- a/src/database/database.h +++ b/src/database/database.h @@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irr_v3d.h" #include "irrlichttypes.h" #include "util/basic_macros.h" +#include "util/string.h" class Database { @@ -84,3 +85,15 @@ public: virtual void listNames(std::vector<std::string> &res) = 0; virtual void reload() = 0; }; + +class ModMetadataDatabase : public Database +{ +public: + virtual ~ModMetadataDatabase() = default; + + virtual bool getModEntries(const std::string &modname, StringMap *storage) = 0; + virtual bool setModEntry(const std::string &modname, + const std::string &key, const std::string &value) = 0; + virtual bool removeModEntry(const std::string &modname, const std::string &key) = 0; + virtual void listMods(std::vector<std::string> *res) = 0; +}; diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 0d509752b..ef2f8724d 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -65,7 +65,6 @@ 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"); // Cheat Menu @@ -254,6 +253,7 @@ void set_default_settings() settings->setDefault("leaves_style", "fancy"); settings->setDefault("connected_glass", "false"); settings->setDefault("smooth_lighting", "true"); + settings->setDefault("performance_tradeoffs", "false"); settings->setDefault("lighting_alpha", "0.0"); settings->setDefault("lighting_beta", "1.5"); settings->setDefault("display_gamma", "1.0"); @@ -313,6 +313,7 @@ void set_default_settings() settings->setDefault("enable_particles", "true"); settings->setDefault("arm_inertia", "true"); settings->setDefault("show_nametag_backgrounds", "true"); + settings->setDefault("transparency_sorting_distance", "16"); settings->setDefault("enable_minimap", "true"); settings->setDefault("minimap_shape_round", "false"); @@ -335,7 +336,7 @@ void set_default_settings() // Effects Shadows settings->setDefault("enable_dynamic_shadows", "false"); - settings->setDefault("shadow_strength", "0.2"); + settings->setDefault("shadow_strength_gamma", "1.0"); settings->setDefault("shadow_map_max_distance", "200.0"); settings->setDefault("shadow_map_texture_size", "2048"); settings->setDefault("shadow_map_texture_32bit", "true"); @@ -355,7 +356,7 @@ void set_default_settings() settings->setDefault("aux1_descends", "false"); settings->setDefault("doubletap_jump", "false"); settings->setDefault("always_fly_fast", "true"); -#ifdef __ANDROID__ +#ifdef HAVE_TOUCHSCREENGUI settings->setDefault("autojump", "true"); #else settings->setDefault("autojump", "false"); @@ -372,8 +373,7 @@ void set_default_settings() settings->setDefault("main_menu_path", ""); settings->setDefault("serverlist_file", "favoriteservers.json"); -#if USE_FREETYPE - settings->setDefault("freetype", "true"); + // General font settings settings->setDefault("font_path", porting::getDataPath("fonts" DIR_DELIM "Arimo-Regular.ttf")); settings->setDefault("font_path_italic", porting::getDataPath("fonts" DIR_DELIM "Arimo-Italic.ttf")); settings->setDefault("font_path_bold", porting::getDataPath("fonts" DIR_DELIM "Arimo-Bold.ttf")); @@ -382,21 +382,15 @@ void set_default_settings() settings->setDefault("font_italic", "false"); settings->setDefault("font_shadow", "1"); settings->setDefault("font_shadow_alpha", "127"); + settings->setDefault("font_size_divisible_by", "1"); settings->setDefault("mono_font_path", porting::getDataPath("fonts" DIR_DELIM "Cousine-Regular.ttf")); settings->setDefault("mono_font_path_italic", porting::getDataPath("fonts" DIR_DELIM "Cousine-Italic.ttf")); settings->setDefault("mono_font_path_bold", porting::getDataPath("fonts" DIR_DELIM "Cousine-Bold.ttf")); settings->setDefault("mono_font_path_bold_italic", porting::getDataPath("fonts" DIR_DELIM "Cousine-BoldItalic.ttf")); + settings->setDefault("mono_font_size_divisible_by", "1"); settings->setDefault("fallback_font_path", porting::getDataPath("fonts" DIR_DELIM "DroidSansFallbackFull.ttf")); std::string font_size_str = std::to_string(TTF_DEFAULT_FONT_SIZE); -#else - settings->setDefault("freetype", "false"); - settings->setDefault("font_path", porting::getDataPath("fonts" DIR_DELIM "mono_dejavu_sans")); - settings->setDefault("mono_font_path", porting::getDataPath("fonts" DIR_DELIM "mono_dejavu_sans")); - - std::string font_size_str = std::to_string(DEFAULT_FONT_SIZE); -#endif - // General font settings settings->setDefault("font_size", font_size_str); settings->setDefault("mono_font_size", font_size_str); settings->setDefault("chat_font_size", "0"); // Default "font_size" @@ -462,7 +456,7 @@ void set_default_settings() settings->setDefault("time_speed", "72"); settings->setDefault("world_start_time", "6125"); settings->setDefault("server_unload_unused_data_timeout", "29"); - settings->setDefault("max_objects_per_block", "64"); + settings->setDefault("max_objects_per_block", "256"); settings->setDefault("server_map_save_interval", "5.3"); settings->setDefault("chat_message_max_size", "500"); settings->setDefault("chat_message_limit_per_10sec", "8.0"); @@ -511,7 +505,7 @@ void set_default_settings() // Mapgen settings->setDefault("mg_name", "v7"); settings->setDefault("water_level", "1"); - settings->setDefault("mapgen_limit", "31000"); + settings->setDefault("mapgen_limit", "31007"); settings->setDefault("chunksize", "5"); settings->setDefault("fixed_map_seed", ""); settings->setDefault("max_block_generate_distance", "10"); @@ -527,6 +521,7 @@ void set_default_settings() settings->setDefault("enable_console", "false"); settings->setDefault("screen_dpi", "72"); + settings->setDefault("display_density_factor", "1"); // Altered settings for macOS #if defined(__MACH__) && defined(__APPLE__) @@ -534,16 +529,22 @@ void set_default_settings() settings->setDefault("fps_max", "0"); #endif +#ifdef HAVE_TOUCHSCREENGUI + settings->setDefault("touchtarget", "true"); + settings->setDefault("touchscreen_threshold","20"); + settings->setDefault("fixed_virtual_joystick", "false"); + settings->setDefault("virtual_joystick_triggers_aux1", "false"); + settings->setDefault("clickable_chat_weblinks", "false"); +#else + settings->setDefault("clickable_chat_weblinks", "true"); +#endif // Altered settings for Android #ifdef __ANDROID__ settings->setDefault("screen_w", "0"); settings->setDefault("screen_h", "0"); settings->setDefault("fullscreen", "true"); - settings->setDefault("touchtarget", "true"); - settings->setDefault("touchscreen_threshold","20"); - settings->setDefault("fixed_virtual_joystick", "false"); - settings->setDefault("virtual_joystick_triggers_aux1", "false"); settings->setDefault("smooth_lighting", "false"); + settings->setDefault("performance_tradeoffs", "true"); settings->setDefault("max_simultaneous_block_sends_per_client", "10"); settings->setDefault("emergequeue_limit_diskonly", "16"); settings->setDefault("emergequeue_limit_generate", "16"); @@ -551,7 +552,6 @@ void set_default_settings() settings->setDefault("enable_3d_clouds", "false"); settings->setDefault("fps_max", "30"); settings->setDefault("fps_max_unfocused", "10"); - 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", "-1"); diff --git a/src/emerge.cpp b/src/emerge.cpp index 9234fe6d3..5c1569f04 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -61,7 +61,9 @@ public: void cancelPendingItems(); - static void runCompletionCallbacks( +protected: + + void runCompletionCallbacks( const v3s16 &pos, EmergeAction action, const EmergeCallbackList &callbacks); @@ -138,7 +140,7 @@ EmergeParams::EmergeParams(EmergeManager *parent, const BiomeGen *biomegen, //// EmergeManager //// -EmergeManager::EmergeManager(Server *server) +EmergeManager::EmergeManager(Server *server, MetricsBackend *mb) { this->ndef = server->getNodeDefManager(); this->biomemgr = new BiomeManager(server); @@ -156,6 +158,17 @@ EmergeManager::EmergeManager(Server *server) enable_mapgen_debug_info = g_settings->getBool("enable_mapgen_debug_info"); + STATIC_ASSERT(ARRLEN(emergeActionStrs) == ARRLEN(m_completed_emerge_counter), + enum_size_mismatches); + for (u32 i = 0; i < ARRLEN(m_completed_emerge_counter); i++) { + std::string help_str("Number of completed emerges with status "); + help_str.append(emergeActionStrs[i]); + m_completed_emerge_counter[i] = mb->addCounter( + "minetest_emerge_completed", help_str, + {{"status", emergeActionStrs[i]}} + ); + } + s16 nthreads = 1; g_settings->getS16NoEx("num_emerge_threads", nthreads); // If automatic, leave a proc for the main thread and one for @@ -202,6 +215,7 @@ EmergeManager::~EmergeManager() delete m_mapgens[i]; } + delete biomegen; delete biomemgr; delete oremgr; delete decomgr; @@ -368,12 +382,6 @@ bool EmergeManager::isBlockInQueue(v3s16 pos) // TODO(hmmmm): Move this to ServerMap -v3s16 EmergeManager::getContainingChunk(v3s16 blockpos) -{ - return getContainingChunk(blockpos, mgparams->chunksize); -} - -// TODO(hmmmm): Move this to ServerMap v3s16 EmergeManager::getContainingChunk(v3s16 blockpos, s16 chunksize) { s16 coff = -chunksize / 2; @@ -396,17 +404,6 @@ int EmergeManager::getSpawnLevelAtPoint(v2s16 p) } -int EmergeManager::getGroundLevelAtPoint(v2s16 p) -{ - if (m_mapgens.empty() || !m_mapgens[0]) { - errorstream << "EmergeManager: getGroundLevelAtPoint() called" - " before mapgen init" << std::endl; - return 0; - } - - return m_mapgens[0]->getGroundLevelAtPoint(p); -} - // TODO(hmmmm): Move this to ServerMap bool EmergeManager::isBlockUnderground(v3s16 blockpos) { @@ -505,6 +502,12 @@ EmergeThread *EmergeManager::getOptimalThread() return m_threads[index]; } +void EmergeManager::reportCompletedEmerge(EmergeAction action) +{ + assert((int)action < ARRLEN(m_completed_emerge_counter)); + m_completed_emerge_counter[(int)action]->increment(); +} + //// //// EmergeThread @@ -556,6 +559,8 @@ void EmergeThread::cancelPendingItems() void EmergeThread::runCompletionCallbacks(const v3s16 &pos, EmergeAction action, const EmergeCallbackList &callbacks) { + m_emerge->reportCompletedEmerge(action); + for (size_t i = 0; i != callbacks.size(); i++) { EmergeCompletionCallback callback; void *param; @@ -650,12 +655,14 @@ MapBlock *EmergeThread::finishGen(v3s16 pos, BlockMakeData *bmdata, m_server->setAsyncFatalError(e); } + EMERGE_DBG_OUT("ended up with: " << analyze_block(block)); + /* - Clear generate notifier events + Clear mapgen state */ + assert(!m_mapgen->generating); m_mapgen->gennotify.clearEvents(); - - EMERGE_DBG_OUT("ended up with: " << analyze_block(block)); + m_mapgen->vm = nullptr; /* Activate the block @@ -671,19 +678,19 @@ void *EmergeThread::run() BEGIN_DEBUG_EXCEPTION_HANDLER v3s16 pos; + std::map<v3s16, MapBlock *> modified_blocks; - m_map = (ServerMap *)&(m_server->m_env->getMap()); + m_map = &m_server->m_env->getServerMap(); m_emerge = m_server->m_emerge; m_mapgen = m_emerge->m_mapgens[id]; enable_mapgen_debug_info = m_emerge->enable_mapgen_debug_info; try { while (!stopRequested()) { - std::map<v3s16, MapBlock *> modified_blocks; BlockEmergeData bedata; BlockMakeData bmdata; EmergeAction action; - MapBlock *block; + MapBlock *block = nullptr; if (!popBlockEmerge(&pos, &bedata)) { m_queue_event.wait(); @@ -706,6 +713,8 @@ void *EmergeThread::run() } block = finishGen(pos, &bmdata, &modified_blocks); + if (!block) + action = EMERGE_ERRORED; } runCompletionCallbacks(pos, action, bedata.callbacks); @@ -715,6 +724,7 @@ void *EmergeThread::run() if (!modified_blocks.empty()) m_server->SetBlocksNotSent(modified_blocks); + modified_blocks.clear(); } } catch (VersionMismatchException &e) { std::ostringstream err; @@ -736,6 +746,8 @@ void *EmergeThread::run() m_server->setAsyncFatalError(err.str()); } + cancelPendingItems(); + END_DEBUG_EXCEPTION_HANDLER return NULL; } diff --git a/src/emerge.h b/src/emerge.h index e2d727973..1bac4b708 100644 --- a/src/emerge.h +++ b/src/emerge.h @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "network/networkprotocol.h" #include "irr_v3d.h" #include "util/container.h" +#include "util/metricsbackend.h" #include "mapgen/mapgen.h" // for MapgenParams #include "map.h" @@ -69,6 +70,14 @@ enum EmergeAction { EMERGE_GENERATED, }; +const static std::string emergeActionStrs[] = { + "cancelled", + "errored", + "from_memory", + "from_disk", + "generated", +}; + // Callback typedef void (*EmergeCompletionCallback)( v3s16 blockpos, EmergeAction action, void *param); @@ -138,7 +147,7 @@ public: MapSettingsManager *map_settings_mgr; // Methods - EmergeManager(Server *server); + EmergeManager(Server *server, MetricsBackend *mb); ~EmergeManager(); DISABLE_CLASS_COPY(EmergeManager); @@ -176,13 +185,10 @@ public: bool isBlockInQueue(v3s16 pos); - v3s16 getContainingChunk(v3s16 blockpos); - Mapgen *getCurrentMapgen(); // Mapgen helpers methods int getSpawnLevelAtPoint(v2s16 p); - int getGroundLevelAtPoint(v2s16 p); bool isBlockUnderground(v3s16 blockpos); static v3s16 getContainingChunk(v3s16 blockpos, s16 chunksize); @@ -200,6 +206,9 @@ private: u32 m_qlimit_diskonly; u32 m_qlimit_generate; + // Emerge metrics + MetricCounterPtr m_completed_emerge_counter[5]; + // Managers of various map generation-related components // Note that each Mapgen gets a copy(!) of these to work with BiomeGen *biomegen; @@ -221,5 +230,7 @@ private: bool popBlockEmergeData(v3s16 pos, BlockEmergeData *bedata); + void reportCompletedEmerge(EmergeAction action); + friend class EmergeThread; }; diff --git a/src/environment.cpp b/src/environment.cpp index f10f773cf..547b3567e 100644 --- a/src/environment.cpp +++ b/src/environment.cpp @@ -173,6 +173,12 @@ void Environment::continueRaycast(RaycastState *state, PointedThing *result) new_nodes.MaxEdge.Z = new_nodes.MinEdge.Z; } + if (new_nodes.MaxEdge.X == S16_MAX || + new_nodes.MaxEdge.Y == S16_MAX || + new_nodes.MaxEdge.Z == S16_MAX) { + break; // About to go out of bounds + } + // For each untested node for (s16 x = new_nodes.MinEdge.X; x <= new_nodes.MaxEdge.X; x++) for (s16 y = new_nodes.MinEdge.Y; y <= new_nodes.MaxEdge.Y; y++) diff --git a/src/filesys.cpp b/src/filesys.cpp index a07370c0e..ea00def6a 100644 --- a/src/filesys.cpp +++ b/src/filesys.cpp @@ -28,16 +28,26 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "log.h" #include "config.h" #include "porting.h" +#ifndef SERVER +#include "irr_ptr.h" +#endif namespace fs { -#ifdef _WIN32 // WINDOWS +#ifdef _WIN32 + +/*********** + * Windows * + ***********/ +#ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0501 +#endif #include <windows.h> #include <shlwapi.h> #include <io.h> +#include <direct.h> std::vector<DirListNode> GetDirListing(const std::string &pathstring) { @@ -201,7 +211,11 @@ std::string CreateTempFile() return path; } -#else // POSIX +#else + +/********* + * POSIX * + *********/ #include <sys/types.h> #include <dirent.h> @@ -392,6 +406,10 @@ std::string CreateTempFile() #endif +/**************************** + * portable implementations * + ****************************/ + void GetRecursiveDirs(std::vector<std::string> &dirs, const std::string &dir) { static const std::set<char> chars_to_ignore = { '_', '.' }; @@ -543,6 +561,30 @@ bool CopyDir(const std::string &source, const std::string &target) return false; } +bool MoveDir(const std::string &source, const std::string &target) +{ + infostream << "Moving \"" << source << "\" to \"" << target << "\"" << std::endl; + + // If target exists as empty folder delete, otherwise error + if (fs::PathExists(target)) { + if (rmdir(target.c_str()) != 0) { + errorstream << "MoveDir: target \"" << target + << "\" exists as file or non-empty folder" << std::endl; + return false; + } + } + + // Try renaming first which is instant + if (fs::Rename(source, target)) + return true; + + infostream << "MoveDir: rename not possible, will copy instead" << std::endl; + bool retval = fs::CopyDir(source, target); + if (retval) + retval &= fs::RecursiveDelete(source); + return retval; +} + bool PathStartsWith(const std::string &path, const std::string &prefix) { size_t pathsize = path.size(); @@ -753,69 +795,66 @@ bool safeWriteToFile(const std::string &path, const std::string &content) return true; } +#ifndef SERVER bool extractZipFile(io::IFileSystem *fs, const char *filename, const std::string &destination) { - if (!fs->addFileArchive(filename, false, false, io::EFAT_ZIP)) { + // Be careful here not to touch the global file hierarchy in Irrlicht + // since this function needs to be thread-safe! + + io::IArchiveLoader *zip_loader = nullptr; + for (u32 i = 0; i < fs->getArchiveLoaderCount(); i++) { + if (fs->getArchiveLoader(i)->isALoadableFileFormat(io::EFAT_ZIP)) { + zip_loader = fs->getArchiveLoader(i); + break; + } + } + if (!zip_loader) { + warningstream << "fs::extractZipFile(): Irrlicht said it doesn't support ZIPs." << std::endl; return false; } - sanity_check(fs->getFileArchiveCount() > 0); - - /**********************************************************************/ - /* WARNING this is not threadsafe!! */ - /**********************************************************************/ - io::IFileArchive* opened_zip = fs->getFileArchive(fs->getFileArchiveCount() - 1); - + irr_ptr<io::IFileArchive> opened_zip(zip_loader->createArchive(filename, false, false)); const io::IFileList* files_in_zip = opened_zip->getFileList(); - unsigned int number_of_files = files_in_zip->getFileCount(); - - for (unsigned int i=0; i < number_of_files; i++) { - std::string fullpath = destination; - fullpath += DIR_DELIM; + for (u32 i = 0; i < files_in_zip->getFileCount(); i++) { + std::string fullpath = destination + DIR_DELIM; fullpath += files_in_zip->getFullFileName(i).c_str(); std::string fullpath_dir = fs::RemoveLastPathComponent(fullpath); - if (!files_in_zip->isDirectory(i)) { - if (!fs::PathExists(fullpath_dir) && !fs::CreateAllDirs(fullpath_dir)) { - fs->removeFileArchive(fs->getFileArchiveCount()-1); - return false; - } - - io::IReadFile* toread = opened_zip->createAndOpenFile(i); + if (files_in_zip->isDirectory(i)) + continue; // ignore, we create dirs as necessary - FILE *targetfile = fopen(fullpath.c_str(),"wb"); + if (!fs::PathExists(fullpath_dir) && !fs::CreateAllDirs(fullpath_dir)) + return false; - if (targetfile == NULL) { - fs->removeFileArchive(fs->getFileArchiveCount()-1); - return false; - } + irr_ptr<io::IReadFile> toread(opened_zip->createAndOpenFile(i)); - char read_buffer[1024]; - long total_read = 0; + std::ofstream os(fullpath.c_str(), std::ios::binary); + if (!os.good()) + return false; - while (total_read < toread->getSize()) { + char buffer[4096]; + long total_read = 0; - unsigned int bytes_read = - toread->read(read_buffer,sizeof(read_buffer)); - if ((bytes_read == 0 ) || - (fwrite(read_buffer, 1, bytes_read, targetfile) != bytes_read)) - { - fclose(targetfile); - fs->removeFileArchive(fs->getFileArchiveCount() - 1); - return false; - } - total_read += bytes_read; + while (total_read < toread->getSize()) { + long bytes_read = toread->read(buffer, sizeof(buffer)); + bool error = true; + if (bytes_read != 0) { + os.write(buffer, bytes_read); + error = os.fail(); } - - fclose(targetfile); + if (error) { + os.close(); + remove(fullpath.c_str()); + return false; + } + total_read += bytes_read; } - } - fs->removeFileArchive(fs->getFileArchiveCount() - 1); return true; } +#endif bool ReadFile(const std::string &path, std::string &out) { @@ -829,7 +868,7 @@ bool ReadFile(const std::string &path, std::string &out) is.seekg(0); is.read(&out[0], size); - return true; + return !is.fail(); } bool Rename(const std::string &from, const std::string &to) diff --git a/src/filesys.h b/src/filesys.h index f72cb0ba2..3fa2524c3 100644 --- a/src/filesys.h +++ b/src/filesys.h @@ -24,12 +24,12 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <vector> #include "exceptions.h" -#ifdef _WIN32 // WINDOWS +#ifdef _WIN32 #define DIR_DELIM "\\" #define DIR_DELIM_CHAR '\\' #define FILESYS_CASE_INSENSITIVE true #define PATH_DELIM ";" -#else // POSIX +#else #define DIR_DELIM "/" #define DIR_DELIM_CHAR '/' #define FILESYS_CASE_INSENSITIVE false @@ -106,6 +106,10 @@ bool CopyFileContents(const std::string &source, const std::string &target); // Omits files and subdirectories that start with a period bool CopyDir(const std::string &source, const std::string &target); +// Move directory and all subdirectories +// Behavior with files/subdirs that start with a period is undefined +bool MoveDir(const std::string &source, const std::string &target); + // Check if one path is prefix of another // For example, "/tmp" is a prefix of "/tmp" and "/tmp/file" but not "/tmp2" // Ignores case differences and '/' vs. '\\' on Windows @@ -133,7 +137,9 @@ const char *GetFilenameFromPath(const char *path); bool safeWriteToFile(const std::string &path, const std::string &content); +#ifndef SERVER bool extractZipFile(irr::io::IFileSystem *fs, const char *filename, const std::string &destination); +#endif bool ReadFile(const std::string &path, std::string &out); diff --git a/src/gamedef.h b/src/gamedef.h index 723404106..4434da369 100644 --- a/src/gamedef.h +++ b/src/gamedef.h @@ -34,6 +34,7 @@ class EmergeManager; class Camera; class ModChannel; class ModMetadata; +class ModMetadataDatabase; namespace irr { namespace scene { class IAnimatedMesh; @@ -65,6 +66,8 @@ public: virtual IRollbackManager* getRollbackManager() { return NULL; } // Shorthands + // TODO: these should be made const-safe so that a const IGameDef* is + // actually usable IItemDefManager *idef() { return getItemDefManager(); } const NodeDefManager *ndef() { return getNodeDefManager(); } ICraftDefManager *cdef() { return getCraftDefManager(); } @@ -73,9 +76,9 @@ public: virtual const std::vector<ModSpec> &getMods() const = 0; virtual const ModSpec* getModSpec(const std::string &modname) const = 0; virtual std::string getWorldPath() const { return ""; } - virtual std::string getModStoragePath() const = 0; virtual bool registerModStorage(ModMetadata *storage) = 0; virtual void unregisterModStorage(const std::string &name) = 0; + virtual ModMetadataDatabase *getModStorageDatabase() = 0; virtual bool joinModChannel(const std::string &channel) = 0; virtual bool leaveModChannel(const std::string &channel) = 0; diff --git a/src/gettext.h b/src/gettext.h index 5a3654be4..6225fef93 100644 --- a/src/gettext.h +++ b/src/gettext.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "config.h" // for USE_GETTEXT #include <string> +#include "porting.h" #if USE_GETTEXT #include <libintl.h> @@ -47,8 +48,7 @@ void init_gettext(const char *path, const std::string &configured_language, extern wchar_t *utf8_to_wide_c(const char *str); -// You must free the returned string! -// The returned string is allocated using new +// The returned string must be freed using delete[] inline const wchar_t *wgettext(const char *str) { // We must check here that is not an empty string to avoid trying to translate it @@ -77,3 +77,31 @@ inline std::wstring fwgettext(const char *src, Args&&... args) delete[] str; return std::wstring(buf); } + +/** + * Returns translated string with format args applied + * + * @tparam Args Template parameter for format args + * @param format Translation source string + * @param args Variable format args + * @return translated string. + */ +template <typename ...Args> +inline std::string fmtgettext(const char *format, Args&&... args) +{ + std::string buf; + std::size_t buf_size = 256; + buf.resize(buf_size); + + format = gettext(format); + + int len = porting::mt_snprintf(&buf[0], buf_size, format, std::forward<Args>(args)...); + if (len <= 0) throw std::runtime_error("gettext format error: " + std::string(format)); + if ((size_t)len >= buf.size()) { + buf.resize(len+1); // extra null byte + porting::mt_snprintf(&buf[0], buf.size(), format, std::forward<Args>(args)...); + } + buf.resize(len); // remove null bytes + + return buf; +} diff --git a/src/gettime.h b/src/gettime.h index 66efef1d7..772ff9b50 100644 --- a/src/gettime.h +++ b/src/gettime.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <ctime> #include <string> +#include <mutex> enum TimePrecision { @@ -30,13 +31,34 @@ enum TimePrecision PRECISION_NANO }; -inline std::string getTimestamp() +inline struct tm mt_localtime() { + // initialize the time zone on first invocation + static std::once_flag tz_init; + std::call_once(tz_init, [] { +#ifdef _WIN32 + _tzset(); +#else + tzset(); +#endif + }); + + struct tm ret; time_t t = time(NULL); - // This is not really thread-safe but it won't break anything - // except its own output, so just go with it. - struct tm *tm = localtime(&t); + // TODO we should check if the function returns NULL, which would mean error +#ifdef _WIN32 + localtime_s(&ret, &t); +#else + localtime_r(&t, &ret); +#endif + return ret; +} + + +inline std::string getTimestamp() +{ + const struct tm tm = mt_localtime(); char cs[20]; // YYYY-MM-DD HH:MM:SS + '\0' - strftime(cs, 20, "%Y-%m-%d %H:%M:%S", tm); + strftime(cs, 20, "%Y-%m-%d %H:%M:%S", &tm); return cs; } diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index ea6e44ab7..a5f25c0f3 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -1,3 +1,8 @@ +set(extra_gui_SRCS "") +if(ENABLE_TOUCH) + set(extra_gui_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/touchscreengui.cpp) +endif() + set(gui_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/cheatMenu.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiAnimatedImage.cpp @@ -26,5 +31,6 @@ set(gui_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/guiVolumeChange.cpp ${CMAKE_CURRENT_SOURCE_DIR}/modalMenu.cpp ${CMAKE_CURRENT_SOURCE_DIR}/profilergraph.cpp + ${extra_gui_SRCS} PARENT_SCOPE ) diff --git a/src/gui/cheatMenu.cpp b/src/gui/cheatMenu.cpp index 31acfb780..2be82f148 100644 --- a/src/gui/cheatMenu.cpp +++ b/src/gui/cheatMenu.cpp @@ -31,10 +31,6 @@ FontMode CheatMenu::fontStringToEnum(std::string str) return FM_Mono; else if (str == "FM_Fallback") return _FM_Fallback; - else if (str == "FM_Simple") - return FM_Simple; - else if (str == "FM_SimpleMono") - return FM_SimpleMono; else if (str == "FM_MaxMode") return FM_MaxMode; else if (str == "FM_Unspecified") diff --git a/src/gui/guiBackgroundImage.cpp b/src/gui/guiBackgroundImage.cpp index 21c1e88cf..85e870771 100644 --- a/src/gui/guiBackgroundImage.cpp +++ b/src/gui/guiBackgroundImage.cpp @@ -44,7 +44,7 @@ void GUIBackgroundImage::draw() core::rect<s32> rect = AbsoluteRect; if (m_autoclip) - rect.LowerRightCorner += Parent->getAbsolutePosition().getSize(); + rect.LowerRightCorner += Parent->getAbsoluteClippingRect().getSize(); video::IVideoDriver *driver = Environment->getVideoDriver(); diff --git a/src/gui/guiButton.cpp b/src/gui/guiButton.cpp index d6dbddf54..ba95b81c3 100644 --- a/src/gui/guiButton.cpp +++ b/src/gui/guiButton.cpp @@ -632,85 +632,6 @@ bool GUIButton::isDrawingBorder() const }
-//! Writes attributes of the element.
-void GUIButton::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0) const
-{
- IGUIButton::serializeAttributes(out,options);
-
- out->addBool ("PushButton", IsPushButton );
- if (IsPushButton)
- out->addBool("Pressed", Pressed);
-
- for ( u32 i=0; i<(u32)EGBIS_COUNT; ++i )
- {
- if ( ButtonImages[i].Texture )
- {
- core::stringc name( GUIButtonImageStateNames[i] );
- out->addTexture(name.c_str(), ButtonImages[i].Texture);
- name += "Rect";
- out->addRect(name.c_str(), ButtonImages[i].SourceRect);
- }
- }
-
- out->addBool ("UseAlphaChannel", UseAlphaChannel);
- out->addBool ("Border", DrawBorder);
- out->addBool ("ScaleImage", ScaleImage);
-
- for ( u32 i=0; i<(u32)EGBS_COUNT; ++i )
- {
- if ( ButtonSprites[i].Index >= 0 )
- {
- core::stringc nameIndex( GUIButtonStateNames[i] );
- nameIndex += "Index";
- out->addInt(nameIndex.c_str(), ButtonSprites[i].Index );
-
- core::stringc nameColor( GUIButtonStateNames[i] );
- nameColor += "Color";
- out->addColor(nameColor.c_str(), ButtonSprites[i].Color );
-
- core::stringc nameLoop( GUIButtonStateNames[i] );
- nameLoop += "Loop";
- out->addBool(nameLoop.c_str(), ButtonSprites[i].Loop );
-
- core::stringc nameScale( GUIButtonStateNames[i] );
- nameScale += "Scale";
- out->addBool(nameScale.c_str(), ButtonSprites[i].Scale );
- }
- }
-
- // out->addString ("OverrideFont", OverrideFont);
-}
-
-
-//! Reads attributes of the element
-void GUIButton::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0)
-{
- IGUIButton::deserializeAttributes(in,options);
-
- IsPushButton = in->getAttributeAsBool("PushButton");
- Pressed = IsPushButton ? in->getAttributeAsBool("Pressed") : false;
-
- core::rect<s32> rec = in->getAttributeAsRect("ImageRect");
- if (rec.isValid())
- setImage( in->getAttributeAsTexture("Image"), rec);
- else
- setImage( in->getAttributeAsTexture("Image") );
-
- rec = in->getAttributeAsRect("PressedImageRect");
- if (rec.isValid())
- setPressedImage( in->getAttributeAsTexture("PressedImage"), rec);
- else
- setPressedImage( in->getAttributeAsTexture("PressedImage") );
-
- setDrawBorder(in->getAttributeAsBool("Border"));
- setUseAlphaChannel(in->getAttributeAsBool("UseAlphaChannel"));
- setScaleImage(in->getAttributeAsBool("ScaleImage"));
-
- // setOverrideFont(in->getAttributeAsString("OverrideFont"));
-
- updateAbsolutePosition();
-}
-
// PATCH
GUIButton* GUIButton::addButton(IGUIEnvironment *environment,
const core::rect<s32>& rectangle, ISimpleTextureSource *tsrc,
diff --git a/src/gui/guiButton.h b/src/gui/guiButton.h index 834405f51..ee9bb6f21 100644 --- a/src/gui/guiButton.h +++ b/src/gui/guiButton.h @@ -221,14 +221,6 @@ public: return ClickControlState;
}
- //! Writes attributes of the element.
- virtual void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const override;
-
- //! Reads attributes of the element
- virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options) override;
-
-
-
void setColor(video::SColor color);
// PATCH
//! Set element properties from a StyleSpec corresponding to the button state
diff --git a/src/gui/guiChatConsole.cpp b/src/gui/guiChatConsole.cpp index 0610c85cc..01e10ea2e 100644 --- a/src/gui/guiChatConsole.cpp +++ b/src/gui/guiChatConsole.cpp @@ -30,12 +30,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/fontengine.h" #include "log.h" #include "gettext.h" +#include "irrlicht_changes/CGUITTFont.h" #include <string> -#if USE_FREETYPE - #include "irrlicht_changes/CGUITTFont.h" -#endif - inline u32 clamp_u8(s32 value) { return (u32) MYMIN(MYMAX(value, 0), 255); @@ -328,19 +325,16 @@ void GUIChatConsole::drawText() core::rect<s32> destrect( x, y, x + m_fontsize.X * fragment.text.size(), y + m_fontsize.Y); -#if USE_FREETYPE if (m_font->getType() == irr::gui::EGFT_CUSTOM) { - // Draw colored text if FreeType is enabled - irr::gui::CGUITTFont *tmp = dynamic_cast<irr::gui::CGUITTFont *>(m_font); + // Draw colored text if possible + gui::CGUITTFont *tmp = static_cast<gui::CGUITTFont*>(m_font); tmp->draw( fragment.text, destrect, false, false, &AbsoluteClippingRect); - } else -#endif - { + } else { // Otherwise use standard text m_font->draw( fragment.text.c_str(), diff --git a/src/gui/guiConfirmRegistration.cpp b/src/gui/guiConfirmRegistration.cpp index 4ca9a64ed..b8887a4af 100644 --- a/src/gui/guiConfirmRegistration.cpp +++ b/src/gui/guiConfirmRegistration.cpp @@ -28,6 +28,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiEditBoxWithScrollbar.h" #include "porting.h" +#ifdef HAVE_TOUCHSCREENGUI + #include "client/renderingengine.h" +#endif + #include "gettext.h" // Continuing from guiPasswordChange.cpp @@ -45,7 +49,7 @@ GUIConfirmRegistration::GUIConfirmRegistration(gui::IGUIEnvironment *env, m_client(client), m_playername(playername), m_password(password), m_aborted(aborted), m_tsrc(tsrc) { -#ifdef __ANDROID__ +#ifdef HAVE_TOUCHSCREENGUI m_touchscreen_visible = false; #endif } @@ -73,8 +77,8 @@ void GUIConfirmRegistration::regenerateGui(v2u32 screensize) /* Calculate new sizes and positions */ -#ifdef __ANDROID__ - const float s = m_gui_scale * porting::getDisplayDensity() / 2; +#ifdef HAVE_TOUCHSCREENGUI + const float s = m_gui_scale * RenderingEngine::getDisplayDensity() / 2; #else const float s = m_gui_scale; #endif diff --git a/src/gui/guiEditBox.cpp b/src/gui/guiEditBox.cpp index 8459107cd..4a0f5013d 100644 --- a/src/gui/guiEditBox.cpp +++ b/src/gui/guiEditBox.cpp @@ -846,54 +846,3 @@ void GUIEditBox::updateVScrollBar() } } } - -void GUIEditBox::deserializeAttributes( - io::IAttributes *in, io::SAttributeReadWriteOptions *options = 0) -{ - IGUIEditBox::deserializeAttributes(in, options); - - setOverrideColor(in->getAttributeAsColor("OverrideColor")); - enableOverrideColor(in->getAttributeAsBool("OverrideColorEnabled")); - setMax(in->getAttributeAsInt("MaxChars")); - setWordWrap(in->getAttributeAsBool("WordWrap")); - setMultiLine(in->getAttributeAsBool("MultiLine")); - setAutoScroll(in->getAttributeAsBool("AutoScroll")); - core::stringw ch = in->getAttributeAsStringW("PasswordChar"); - - if (ch.empty()) - setPasswordBox(in->getAttributeAsBool("PasswordBox")); - else - setPasswordBox(in->getAttributeAsBool("PasswordBox"), ch[0]); - - setTextAlignment((EGUI_ALIGNMENT)in->getAttributeAsEnumeration( - "HTextAlign", GUIAlignmentNames), - (EGUI_ALIGNMENT)in->getAttributeAsEnumeration( - "VTextAlign", GUIAlignmentNames)); - - setWritable(in->getAttributeAsBool("Writable")); - // setOverrideFont(in->getAttributeAsFont("OverrideFont")); -} - -//! Writes attributes of the element. -void GUIEditBox::serializeAttributes( - io::IAttributes *out, io::SAttributeReadWriteOptions *options = 0) const -{ - // IGUIEditBox::serializeAttributes(out,options); - - out->addBool("OverrideColorEnabled", m_override_color_enabled); - out->addColor("OverrideColor", m_override_color); - // out->addFont("OverrideFont",m_override_font); - out->addInt("MaxChars", m_max); - out->addBool("WordWrap", m_word_wrap); - out->addBool("MultiLine", m_multiline); - out->addBool("AutoScroll", m_autoscroll); - out->addBool("PasswordBox", m_passwordbox); - core::stringw ch = L" "; - ch[0] = m_passwordchar; - out->addString("PasswordChar", ch.c_str()); - out->addEnum("HTextAlign", m_halign, GUIAlignmentNames); - out->addEnum("VTextAlign", m_valign, GUIAlignmentNames); - out->addBool("Writable", m_writable); - - IGUIEditBox::serializeAttributes(out, options); -} diff --git a/src/gui/guiEditBox.h b/src/gui/guiEditBox.h index 2a5c911bc..4c7413f54 100644 --- a/src/gui/guiEditBox.h +++ b/src/gui/guiEditBox.h @@ -130,14 +130,6 @@ public: //! called if an event happened. virtual bool OnEvent(const SEvent &event); - //! Writes attributes of the element. - virtual void serializeAttributes(io::IAttributes *out, - io::SAttributeReadWriteOptions *options) const; - - //! Reads attributes of the element - virtual void deserializeAttributes( - io::IAttributes *in, io::SAttributeReadWriteOptions *options); - virtual bool acceptsIME() { return isEnabled() && m_writable; }; protected: diff --git a/src/gui/guiEditBoxWithScrollbar.cpp b/src/gui/guiEditBoxWithScrollbar.cpp index fb4bc2a0b..1b7f7832a 100644 --- a/src/gui/guiEditBoxWithScrollbar.cpp +++ b/src/gui/guiEditBoxWithScrollbar.cpp @@ -652,26 +652,6 @@ void GUIEditBoxWithScrollBar::setBackgroundColor(const video::SColor &bg_color) m_bg_color_used = true; } -//! Writes attributes of the element. -void GUIEditBoxWithScrollBar::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options = 0) const -{ - out->addBool("Border", m_border); - out->addBool("Background", m_background); - // out->addFont("OverrideFont", OverrideFont); - - GUIEditBox::serializeAttributes(out, options); -} - - -//! Reads attributes of the element -void GUIEditBoxWithScrollBar::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options = 0) -{ - GUIEditBox::deserializeAttributes(in, options); - - setDrawBorder(in->getAttributeAsBool("Border")); - setDrawBackground(in->getAttributeAsBool("Background")); -} - bool GUIEditBoxWithScrollBar::isDrawBackgroundEnabled() const { return false; } bool GUIEditBoxWithScrollBar::isDrawBorderEnabled() const { return false; } void GUIEditBoxWithScrollBar::setCursorChar(const wchar_t cursorChar) { } diff --git a/src/gui/guiEditBoxWithScrollbar.h b/src/gui/guiEditBoxWithScrollbar.h index 3f7450dcb..cea482fc2 100644 --- a/src/gui/guiEditBoxWithScrollbar.h +++ b/src/gui/guiEditBoxWithScrollbar.h @@ -31,12 +31,6 @@ public: //! Change the background color virtual void setBackgroundColor(const video::SColor &bg_color); - //! Writes attributes of the element. - virtual void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const; - - //! Reads attributes of the element - virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options); - virtual bool isDrawBackgroundEnabled() const; virtual bool isDrawBorderEnabled() const; virtual void setCursorChar(const wchar_t cursorChar); diff --git a/src/gui/guiEngine.cpp b/src/gui/guiEngine.cpp index c39c3ee0d..b65b31304 100644 --- a/src/gui/guiEngine.cpp +++ b/src/gui/guiEngine.cpp @@ -104,16 +104,22 @@ void MenuMusicFetcher::fetchSounds(const std::string &name, if(m_fetched.count(name)) return; m_fetched.insert(name); - std::string base; - base = porting::path_share + DIR_DELIM + "sounds"; - dst_paths.insert(base + DIR_DELIM + name + ".ogg"); - int i; - for(i=0; i<10; i++) - dst_paths.insert(base + DIR_DELIM + name + "."+itos(i)+".ogg"); - base = porting::path_user + DIR_DELIM + "sounds"; - dst_paths.insert(base + DIR_DELIM + name + ".ogg"); - for(i=0; i<10; i++) - dst_paths.insert(base + DIR_DELIM + name + "."+itos(i)+".ogg"); + std::vector<fs::DirListNode> list; + // Reusable local function + auto add_paths = [&dst_paths](const std::string name, const std::string base = "") { + dst_paths.insert(base + name + ".ogg"); + for (int i = 0; i < 10; i++) + dst_paths.insert(base + name + "." + itos(i) + ".ogg"); + }; + // Allow full paths + if (name.find(DIR_DELIM_CHAR) != std::string::npos) { + add_paths(name); + } else { + std::string share_prefix = porting::path_share + DIR_DELIM; + add_paths(name, share_prefix + "sounds" + DIR_DELIM); + std::string user_prefix = porting::path_user + DIR_DELIM; + add_paths(name, user_prefix + "sounds" + DIR_DELIM); + } } /******************************************************************************/ diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 797fd3ff6..f3570ccaf 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -81,6 +81,13 @@ with this program; if not, write to the Free Software Foundation, Inc., " specified: \"" << parts[b] << "\"" << std::endl; \ return; \ } + +#define MY_CHECKCLIENT(a) \ + if (!m_client) { \ + errorstream << "Attempted to use element " << a << " with m_client == nullptr." << std::endl; \ + return; \ + } + /* GUIFormSpecMenu */ @@ -130,8 +137,6 @@ GUIFormSpecMenu::~GUIFormSpecMenu() checkbox_it.second->drop(); for (auto &scrollbar_it : m_scrollbars) scrollbar_it.second->drop(); - for (auto &background_it : m_backgrounds) - background_it->drop(); for (auto &tooltip_rect_it : m_tooltip_rects) tooltip_rect_it.first->drop(); for (auto &clickthrough_it : m_clickthrough_elements) @@ -294,8 +299,20 @@ v2s32 GUIFormSpecMenu::getRealCoordinateGeometry(const std::vector<std::string> return v2s32(stof(v_geom[0]) * imgsize.X, stof(v_geom[1]) * imgsize.Y); } +bool GUIFormSpecMenu::precheckElement(const std::string &name, const std::string &element, + size_t args_min, size_t args_max, std::vector<std::string> &parts) +{ + parts = split(element, ';'); + if (parts.size() >= args_min && (parts.size() <= args_max || m_formspec_version > FORMSPEC_API_VERSION)) + return true; + + errorstream << "Invalid " << name << " element(" << parts.size() << "): '" << element << "'" << std::endl; + return false; +} + void GUIFormSpecMenu::parseSize(parserData* data, const std::string &element) { + // Note: do not use precheckElement due to "," separator. std::vector<std::string> parts = split(element,','); if (((parts.size() == 2) || parts.size() == 3) || @@ -308,7 +325,7 @@ void GUIFormSpecMenu::parseSize(parserData* data, const std::string &element) data->invsize.Y = MYMAX(0, stof(parts[1])); lockSize(false); -#ifndef __ANDROID__ +#ifndef HAVE_TOUCHSCREENGUI if (parts.size() == 3) { if (parts[2] == "true") { lockSize(true,v2u32(800,600)); @@ -349,14 +366,9 @@ void GUIFormSpecMenu::parseContainerEnd(parserData* data) void GUIFormSpecMenu::parseScrollContainer(parserData *data, const std::string &element) { - std::vector<std::string> parts = split(element, ';'); - - if (parts.size() < 4 || - (parts.size() > 5 && m_formspec_version <= FORMSPEC_API_VERSION)) { - errorstream << "Invalid scroll_container start element (" << parts.size() - << "): '" << element << "'" << std::endl; + std::vector<std::string> parts; + if (!precheckElement("scroll_container start", element, 4, 5, parts)) return; - } std::vector<std::string> v_pos = split(parts[0], ','); std::vector<std::string> v_geom = split(parts[1], ','); @@ -445,105 +457,95 @@ void GUIFormSpecMenu::parseScrollContainerEnd(parserData *data) void GUIFormSpecMenu::parseList(parserData *data, const std::string &element) { - if (m_client == 0) { - warningstream<<"invalid use of 'list' with m_client==0"<<std::endl; - return; - } + MY_CHECKCLIENT("list"); - std::vector<std::string> parts = split(element,';'); + std::vector<std::string> parts; + if (!precheckElement("list", element, 4, 5, parts)) + return; - if (((parts.size() == 4) || (parts.size() == 5)) || - ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::string location = parts[0]; - std::string listname = parts[1]; - std::vector<std::string> v_pos = split(parts[2],','); - std::vector<std::string> v_geom = split(parts[3],','); - std::string startindex; - if (parts.size() == 5) - startindex = parts[4]; + std::string location = parts[0]; + std::string listname = parts[1]; + std::vector<std::string> v_pos = split(parts[2],','); + std::vector<std::string> v_geom = split(parts[3],','); + std::string startindex; + if (parts.size() == 5) + startindex = parts[4]; - MY_CHECKPOS("list",2); - MY_CHECKGEOM("list",3); + MY_CHECKPOS("list",2); + MY_CHECKGEOM("list",3); - InventoryLocation loc; + InventoryLocation loc; - if (location == "context" || location == "current_name") - loc = m_current_inventory_location; - else - loc.deSerialize(location); + if (location == "context" || location == "current_name") + loc = m_current_inventory_location; + else + loc.deSerialize(location); - v2s32 geom; - geom.X = stoi(v_geom[0]); - geom.Y = stoi(v_geom[1]); + v2s32 geom; + geom.X = stoi(v_geom[0]); + geom.Y = stoi(v_geom[1]); - s32 start_i = 0; - if (!startindex.empty()) - start_i = stoi(startindex); + s32 start_i = 0; + if (!startindex.empty()) + start_i = stoi(startindex); - if (geom.X < 0 || geom.Y < 0 || start_i < 0) { - errorstream << "Invalid list element: '" << element << "'" << std::endl; - return; - } + if (geom.X < 0 || geom.Y < 0 || start_i < 0) { + errorstream << "Invalid list element: '" << element << "'" << std::endl; + return; + } - if (!data->explicit_size) - warningstream << "invalid use of list without a size[] element" << std::endl; + if (!data->explicit_size) + warningstream << "invalid use of list without a size[] element" << std::endl; - FieldSpec spec( - "", - L"", - L"", - 258 + m_fields.size(), - 3 - ); + FieldSpec spec( + "", + L"", + L"", + 258 + m_fields.size(), + 3 + ); - auto style = getDefaultStyleForElement("list", spec.fname); + auto style = getDefaultStyleForElement("list", spec.fname); - v2f32 slot_scale = style.getVector2f(StyleSpec::SIZE, v2f32(0, 0)); - v2f32 slot_size( - slot_scale.X <= 0 ? imgsize.X : std::max<f32>(slot_scale.X * imgsize.X, 1), - slot_scale.Y <= 0 ? imgsize.Y : std::max<f32>(slot_scale.Y * imgsize.Y, 1) - ); + v2f32 slot_scale = style.getVector2f(StyleSpec::SIZE, v2f32(0, 0)); + v2f32 slot_size( + slot_scale.X <= 0 ? imgsize.X : std::max<f32>(slot_scale.X * imgsize.X, 1), + slot_scale.Y <= 0 ? imgsize.Y : std::max<f32>(slot_scale.Y * imgsize.Y, 1) + ); - v2f32 slot_spacing = style.getVector2f(StyleSpec::SPACING, v2f32(-1, -1)); - v2f32 default_spacing = data->real_coordinates ? - v2f32(imgsize.X * 0.25f, imgsize.Y * 0.25f) : - v2f32(spacing.X - imgsize.X, spacing.Y - imgsize.Y); + v2f32 slot_spacing = style.getVector2f(StyleSpec::SPACING, v2f32(-1, -1)); + v2f32 default_spacing = data->real_coordinates ? + v2f32(imgsize.X * 0.25f, imgsize.Y * 0.25f) : + v2f32(spacing.X - imgsize.X, spacing.Y - imgsize.Y); - slot_spacing.X = slot_spacing.X < 0 ? default_spacing.X : - imgsize.X * slot_spacing.X; - slot_spacing.Y = slot_spacing.Y < 0 ? default_spacing.Y : - imgsize.Y * slot_spacing.Y; + slot_spacing.X = slot_spacing.X < 0 ? default_spacing.X : + imgsize.X * slot_spacing.X; + slot_spacing.Y = slot_spacing.Y < 0 ? default_spacing.Y : + imgsize.Y * slot_spacing.Y; - slot_spacing += slot_size; + slot_spacing += slot_size; - v2s32 pos = data->real_coordinates ? getRealCoordinateBasePos(v_pos) : - getElementBasePos(&v_pos); + v2s32 pos = data->real_coordinates ? getRealCoordinateBasePos(v_pos) : + getElementBasePos(&v_pos); - core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, - pos.X + (geom.X - 1) * slot_spacing.X + slot_size.X, - pos.Y + (geom.Y - 1) * slot_spacing.Y + slot_size.Y); + core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, + pos.X + (geom.X - 1) * slot_spacing.X + slot_size.X, + pos.Y + (geom.Y - 1) * slot_spacing.Y + slot_size.Y); - GUIInventoryList *e = new GUIInventoryList(Environment, data->current_parent, - spec.fid, rect, m_invmgr, loc, listname, geom, start_i, - v2s32(slot_size.X, slot_size.Y), slot_spacing, this, - data->inventorylist_options, m_font); + GUIInventoryList *e = new GUIInventoryList(Environment, data->current_parent, + spec.fid, rect, m_invmgr, loc, listname, geom, start_i, + v2s32(slot_size.X, slot_size.Y), slot_spacing, this, + data->inventorylist_options, m_font); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); - m_inventorylists.push_back(e); - m_fields.push_back(spec); - return; - } - errorstream<< "Invalid list element(" << parts.size() << "): '" << element << "'" << std::endl; + m_inventorylists.push_back(e); + m_fields.push_back(spec); } void GUIFormSpecMenu::parseListRing(parserData *data, const std::string &element) { - if (m_client == 0) { - errorstream << "WARNING: invalid use of 'listring' with m_client==0" << std::endl; - return; - } + MY_CHECKCLIENT("listring"); std::vector<std::string> parts = split(element, ';'); @@ -578,157 +580,150 @@ void GUIFormSpecMenu::parseListRing(parserData *data, const std::string &element void GUIFormSpecMenu::parseCheckbox(parserData* data, const std::string &element) { - std::vector<std::string> parts = split(element,';'); - - if (((parts.size() >= 3) && (parts.size() <= 4)) || - ((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector<std::string> v_pos = split(parts[0],','); - std::string name = parts[1]; - std::string label = parts[2]; - std::string selected; + std::vector<std::string> parts; + if (!precheckElement("checkbox", element, 3, 4, parts)) + return; - if (parts.size() >= 4) - selected = parts[3]; + std::vector<std::string> v_pos = split(parts[0],','); + std::string name = parts[1]; + std::string label = parts[2]; + std::string selected; - MY_CHECKPOS("checkbox",0); + if (parts.size() >= 4) + selected = parts[3]; - bool fselected = false; + MY_CHECKPOS("checkbox",0); - if (selected == "true") - fselected = true; + bool fselected = false; - std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label))); - const core::dimension2d<u32> label_size = m_font->getDimension(wlabel.c_str()); - s32 cb_size = Environment->getSkin()->getSize(gui::EGDS_CHECK_BOX_WIDTH); - s32 y_center = (std::max(label_size.Height, (u32)cb_size) + 1) / 2; + if (selected == "true") + fselected = true; - v2s32 pos; - core::rect<s32> rect; + std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label))); + const core::dimension2d<u32> label_size = m_font->getDimension(wlabel.c_str()); + s32 cb_size = Environment->getSkin()->getSize(gui::EGDS_CHECK_BOX_WIDTH); + s32 y_center = (std::max(label_size.Height, (u32)cb_size) + 1) / 2; - if (data->real_coordinates) { - pos = getRealCoordinateBasePos(v_pos); + v2s32 pos; + core::rect<s32> rect; - rect = core::rect<s32>( - pos.X, - pos.Y - y_center, - pos.X + label_size.Width + cb_size + 7, - pos.Y + y_center - ); - } else { - pos = getElementBasePos(&v_pos); - rect = core::rect<s32>( - pos.X, - pos.Y + imgsize.Y / 2 - y_center, - pos.X + label_size.Width + cb_size + 7, - pos.Y + imgsize.Y / 2 + y_center - ); - } + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(v_pos); - FieldSpec spec( - name, - wlabel, //Needed for displaying text on MSVC - wlabel, - 258+m_fields.size() + rect = core::rect<s32>( + pos.X, + pos.Y - y_center, + pos.X + label_size.Width + cb_size + 7, + pos.Y + y_center + ); + } else { + pos = getElementBasePos(&v_pos); + rect = core::rect<s32>( + pos.X, + pos.Y + imgsize.Y / 2 - y_center, + pos.X + label_size.Width + cb_size + 7, + pos.Y + imgsize.Y / 2 + y_center ); + } - spec.ftype = f_CheckBox; + FieldSpec spec( + name, + wlabel, //Needed for displaying text on MSVC + wlabel, + 258+m_fields.size() + ); - gui::IGUICheckBox *e = Environment->addCheckBox(fselected, rect, - data->current_parent, spec.fid, spec.flabel.c_str()); + spec.ftype = f_CheckBox; - auto style = getDefaultStyleForElement("checkbox", name); + gui::IGUICheckBox *e = Environment->addCheckBox(fselected, rect, + data->current_parent, spec.fid, spec.flabel.c_str()); - spec.sound = style.get(StyleSpec::Property::SOUND, ""); + auto style = getDefaultStyleForElement("checkbox", name); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); + spec.sound = style.get(StyleSpec::Property::SOUND, ""); - if (spec.fname == m_focused_element) { - Environment->setFocus(e); - } + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); - e->grab(); - m_checkboxes.emplace_back(spec, e); - m_fields.push_back(spec); - return; + if (spec.fname == m_focused_element) { + Environment->setFocus(e); } - errorstream<< "Invalid checkbox element(" << parts.size() << "): '" << element << "'" << std::endl; + + e->grab(); + m_checkboxes.emplace_back(spec, e); + m_fields.push_back(spec); } void GUIFormSpecMenu::parseScrollBar(parserData* data, const std::string &element) { - std::vector<std::string> parts = split(element,';'); - - if (parts.size() >= 5) { - std::vector<std::string> v_pos = split(parts[0],','); - std::vector<std::string> v_geom = split(parts[1],','); - std::string name = parts[3]; - std::string value = parts[4]; + std::vector<std::string> parts; + if (!precheckElement("scrollbar", element, 5, 5, parts)) + return; - MY_CHECKPOS("scrollbar",0); - MY_CHECKGEOM("scrollbar",1); + std::vector<std::string> v_pos = split(parts[0],','); + std::vector<std::string> v_geom = split(parts[1],','); + std::string name = parts[3]; + std::string value = parts[4]; - v2s32 pos; - v2s32 dim; + MY_CHECKPOS("scrollbar",0); + MY_CHECKGEOM("scrollbar",1); - if (data->real_coordinates) { - pos = getRealCoordinateBasePos(v_pos); - dim = getRealCoordinateGeometry(v_geom); - } else { - pos = getElementBasePos(&v_pos); - dim.X = stof(v_geom[0]) * spacing.X; - dim.Y = stof(v_geom[1]) * spacing.Y; - } + v2s32 pos; + v2s32 dim; - core::rect<s32> rect = - core::rect<s32>(pos.X, pos.Y, pos.X + dim.X, pos.Y + dim.Y); + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(v_pos); + dim = getRealCoordinateGeometry(v_geom); + } else { + pos = getElementBasePos(&v_pos); + dim.X = stof(v_geom[0]) * spacing.X; + dim.Y = stof(v_geom[1]) * spacing.Y; + } - FieldSpec spec( - name, - L"", - L"", - 258+m_fields.size() - ); + core::rect<s32> rect = + core::rect<s32>(pos.X, pos.Y, pos.X + dim.X, pos.Y + dim.Y); - bool is_horizontal = true; + FieldSpec spec( + name, + L"", + L"", + 258+m_fields.size() + ); - if (parts[2] == "vertical") - is_horizontal = false; + bool is_horizontal = true; - spec.ftype = f_ScrollBar; - spec.send = true; - GUIScrollBar *e = new GUIScrollBar(Environment, data->current_parent, - spec.fid, rect, is_horizontal, true); + if (parts[2] == "vertical") + is_horizontal = false; - auto style = getDefaultStyleForElement("scrollbar", name); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); - e->setArrowsVisible(data->scrollbar_options.arrow_visiblity); + spec.ftype = f_ScrollBar; + spec.send = true; + GUIScrollBar *e = new GUIScrollBar(Environment, data->current_parent, + spec.fid, rect, is_horizontal, true); - s32 max = data->scrollbar_options.max; - s32 min = data->scrollbar_options.min; + auto style = getDefaultStyleForElement("scrollbar", name); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); + e->setArrowsVisible(data->scrollbar_options.arrow_visiblity); - e->setMax(max); - e->setMin(min); + s32 max = data->scrollbar_options.max; + s32 min = data->scrollbar_options.min; - e->setPos(stoi(parts[4])); + e->setMax(max); + e->setMin(min); - e->setSmallStep(data->scrollbar_options.small_step); - e->setLargeStep(data->scrollbar_options.large_step); + e->setPos(stoi(parts[4])); - s32 scrollbar_size = is_horizontal ? dim.X : dim.Y; + e->setSmallStep(data->scrollbar_options.small_step); + e->setLargeStep(data->scrollbar_options.large_step); - e->setPageSize(scrollbar_size * (max - min + 1) / data->scrollbar_options.thumb_size); + s32 scrollbar_size = is_horizontal ? dim.X : dim.Y; - if (spec.fname == m_focused_element) { - Environment->setFocus(e); - } + e->setPageSize(scrollbar_size * (max - min + 1) / data->scrollbar_options.thumb_size); - m_scrollbars.emplace_back(spec,e); - m_fields.push_back(spec); - return; + if (spec.fname == m_focused_element) { + Environment->setFocus(e); } - errorstream << "Invalid scrollbar element(" << parts.size() << "): '" << element - << "'" << std::endl; + + m_scrollbars.emplace_back(spec,e); + m_fields.push_back(spec); } void GUIFormSpecMenu::parseScrollBarOptions(parserData* data, const std::string &element) @@ -786,11 +781,11 @@ void GUIFormSpecMenu::parseScrollBarOptions(parserData* data, const std::string void GUIFormSpecMenu::parseImage(parserData* data, const std::string &element) { - std::vector<std::string> parts = split(element,';'); + std::vector<std::string> parts; + if (!precheckElement("image", element, 2, 3, parts)) + return; - if ((parts.size() == 3) || - ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION))) - { + if (parts.size() >= 3) { std::vector<std::string> v_pos = split(parts[0],','); std::vector<std::string> v_geom = split(parts[1],','); std::string name = unescape_string(parts[2]); @@ -842,54 +837,47 @@ void GUIFormSpecMenu::parseImage(parserData* data, const std::string &element) return; } - if (parts.size() == 2) { - std::vector<std::string> v_pos = split(parts[0],','); - std::string name = unescape_string(parts[1]); - - MY_CHECKPOS("image", 0); + // Else: 2 arguments in "parts" - v2s32 pos = getElementBasePos(&v_pos); + std::vector<std::string> v_pos = split(parts[0],','); + std::string name = unescape_string(parts[1]); - if (!data->explicit_size) - warningstream<<"invalid use of image without a size[] element"<<std::endl; + MY_CHECKPOS("image", 0); - video::ITexture *texture = m_tsrc->getTexture(name); - if (!texture) { - errorstream << "GUIFormSpecMenu::parseImage() Unable to load texture:" - << std::endl << "\t" << name << std::endl; - return; - } + v2s32 pos = getElementBasePos(&v_pos); - FieldSpec spec( - name, - L"", - L"", - 258 + m_fields.size() - ); - gui::IGUIImage *e = Environment->addImage(texture, pos, true, - data->current_parent, spec.fid, 0); - auto style = getDefaultStyleForElement("image", spec.fname); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3)); - m_fields.push_back(spec); + if (!data->explicit_size) + warningstream<<"invalid use of image without a size[] element"<<std::endl; - // images should let events through - e->grab(); - m_clickthrough_elements.push_back(e); + video::ITexture *texture = m_tsrc->getTexture(name); + if (!texture) { + errorstream << "GUIFormSpecMenu::parseImage() Unable to load texture:" + << std::endl << "\t" << name << std::endl; return; } - errorstream<< "Invalid image element(" << parts.size() << "): '" << element << "'" << std::endl; + + FieldSpec spec( + name, + L"", + L"", + 258 + m_fields.size() + ); + gui::IGUIImage *e = Environment->addImage(texture, pos, true, + data->current_parent, spec.fid, 0); + auto style = getDefaultStyleForElement("image", spec.fname); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3)); + m_fields.push_back(spec); + + // images should let events through + e->grab(); + m_clickthrough_elements.push_back(e); } void GUIFormSpecMenu::parseAnimatedImage(parserData *data, const std::string &element) { - std::vector<std::string> parts = split(element, ';'); - - if (parts.size() != 6 && parts.size() != 7 && - !(parts.size() > 7 && m_formspec_version > FORMSPEC_API_VERSION)) { - errorstream << "Invalid animated_image element(" << parts.size() - << "): '" << element << "'" << std::endl; + std::vector<std::string> parts; + if (!precheckElement("animated_image", element, 6, 7, parts)) return; - } std::vector<std::string> v_pos = split(parts[0], ','); std::vector<std::string> v_geom = split(parts[1], ','); @@ -944,218 +932,205 @@ void GUIFormSpecMenu::parseAnimatedImage(parserData *data, const std::string &el void GUIFormSpecMenu::parseItemImage(parserData* data, const std::string &element) { - std::vector<std::string> parts = split(element,';'); + std::vector<std::string> parts; + if (!precheckElement("item_image", element, 3, 3, parts)) + return; - if ((parts.size() == 3) || - ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector<std::string> v_pos = split(parts[0],','); - std::vector<std::string> v_geom = split(parts[1],','); - std::string name = parts[2]; + std::vector<std::string> v_pos = split(parts[0],','); + std::vector<std::string> v_geom = split(parts[1],','); + std::string name = parts[2]; - MY_CHECKPOS("itemimage",0); - MY_CHECKGEOM("itemimage",1); + MY_CHECKPOS("item_image",0); + MY_CHECKGEOM("item_image",1); - v2s32 pos; - v2s32 geom; + v2s32 pos; + v2s32 geom; - if (data->real_coordinates) { - pos = getRealCoordinateBasePos(v_pos); - geom = getRealCoordinateGeometry(v_geom); - } else { - pos = getElementBasePos(&v_pos); - geom.X = stof(v_geom[0]) * (float)imgsize.X; - geom.Y = stof(v_geom[1]) * (float)imgsize.Y; - } + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(v_pos); + geom = getRealCoordinateGeometry(v_geom); + } else { + pos = getElementBasePos(&v_pos); + geom.X = stof(v_geom[0]) * (float)imgsize.X; + geom.Y = stof(v_geom[1]) * (float)imgsize.Y; + } - if(!data->explicit_size) - warningstream<<"invalid use of item_image without a size[] element"<<std::endl; + if(!data->explicit_size) + warningstream<<"invalid use of item_image without a size[] element"<<std::endl; - FieldSpec spec( - "", - L"", - L"", - 258 + m_fields.size(), - 2 - ); - spec.ftype = f_ItemImage; + FieldSpec spec( + "", + L"", + L"", + 258 + m_fields.size(), + 2 + ); + spec.ftype = f_ItemImage; - GUIItemImage *e = new GUIItemImage(Environment, data->current_parent, spec.fid, - core::rect<s32>(pos, pos + geom), name, m_font, m_client); - auto style = getDefaultStyleForElement("item_image", spec.fname); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); + GUIItemImage *e = new GUIItemImage(Environment, data->current_parent, spec.fid, + core::rect<s32>(pos, pos + geom), name, m_font, m_client); + auto style = getDefaultStyleForElement("item_image", spec.fname); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); - // item images should let events through - m_clickthrough_elements.push_back(e); + // item images should let events through + m_clickthrough_elements.push_back(e); - m_fields.push_back(spec); - return; - } - errorstream<< "Invalid ItemImage element(" << parts.size() << "): '" << element << "'" << std::endl; + m_fields.push_back(spec); } void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element, const std::string &type) { - std::vector<std::string> parts = split(element,';'); - - if ((parts.size() == 4) || - ((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector<std::string> v_pos = split(parts[0],','); - std::vector<std::string> v_geom = split(parts[1],','); - std::string name = parts[2]; - std::string label = parts[3]; + std::vector<std::string> parts; + if (!precheckElement("button", element, 4, 4, parts)) + return; - MY_CHECKPOS("button",0); - MY_CHECKGEOM("button",1); + std::vector<std::string> v_pos = split(parts[0],','); + std::vector<std::string> v_geom = split(parts[1],','); + std::string name = parts[2]; + std::string label = parts[3]; - v2s32 pos; - v2s32 geom; - core::rect<s32> rect; + MY_CHECKPOS("button",0); + MY_CHECKGEOM("button",1); - if (data->real_coordinates) { - pos = getRealCoordinateBasePos(v_pos); - geom = getRealCoordinateGeometry(v_geom); - rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, - pos.Y+geom.Y); - } else { - pos = getElementBasePos(&v_pos); - geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X); - pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2; + v2s32 pos; + v2s32 geom; + core::rect<s32> rect; - rect = core::rect<s32>(pos.X, pos.Y - m_btn_height, - pos.X + geom.X, pos.Y + m_btn_height); - } + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(v_pos); + geom = getRealCoordinateGeometry(v_geom); + rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, + pos.Y+geom.Y); + } else { + pos = getElementBasePos(&v_pos); + geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X); + pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2; - if(!data->explicit_size) - warningstream<<"invalid use of button without a size[] element"<<std::endl; + rect = core::rect<s32>(pos.X, pos.Y - m_btn_height, + pos.X + geom.X, pos.Y + m_btn_height); + } - std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label))); + if(!data->explicit_size) + warningstream<<"invalid use of button without a size[] element"<<std::endl; - FieldSpec spec( - name, - wlabel, - L"", - 258 + m_fields.size() - ); - spec.ftype = f_Button; - if(type == "button_exit") - spec.is_exit = true; + std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label))); - GUIButton *e = GUIButton::addButton(Environment, rect, m_tsrc, - data->current_parent, spec.fid, spec.flabel.c_str()); + FieldSpec spec( + name, + wlabel, + L"", + 258 + m_fields.size() + ); + spec.ftype = f_Button; + if(type == "button_exit") + spec.is_exit = true; - auto style = getStyleForElement(type, name, (type != "button") ? "button" : ""); + GUIButton *e = GUIButton::addButton(Environment, rect, m_tsrc, + data->current_parent, spec.fid, spec.flabel.c_str()); - spec.sound = style[StyleSpec::STATE_DEFAULT].get(StyleSpec::Property::SOUND, ""); + auto style = getStyleForElement(type, name, (type != "button") ? "button" : ""); - e->setStyles(style); + spec.sound = style[StyleSpec::STATE_DEFAULT].get(StyleSpec::Property::SOUND, ""); - if (spec.fname == m_focused_element) { - Environment->setFocus(e); - } + e->setStyles(style); - m_fields.push_back(spec); - return; + if (spec.fname == m_focused_element) { + Environment->setFocus(e); } - errorstream<< "Invalid button element(" << parts.size() << "): '" << element << "'" << std::endl; + + m_fields.push_back(spec); } void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &element) { - std::vector<std::string> parts = split(element,';'); + std::vector<std::string> parts; + if (!precheckElement("background", element, 3, 5, parts)) + return; - if ((parts.size() >= 3 && parts.size() <= 5) || - (parts.size() > 5 && m_formspec_version > FORMSPEC_API_VERSION)) { - std::vector<std::string> v_pos = split(parts[0],','); - std::vector<std::string> v_geom = split(parts[1],','); - std::string name = unescape_string(parts[2]); + std::vector<std::string> v_pos = split(parts[0],','); + std::vector<std::string> v_geom = split(parts[1],','); + std::string name = unescape_string(parts[2]); - MY_CHECKPOS("background",0); - MY_CHECKGEOM("background",1); + MY_CHECKPOS("background",0); + MY_CHECKGEOM("background",1); - v2s32 pos; - v2s32 geom; + v2s32 pos; + v2s32 geom; - if (data->real_coordinates) { - pos = getRealCoordinateBasePos(v_pos); - geom = getRealCoordinateGeometry(v_geom); - } else { - pos = getElementBasePos(&v_pos); - pos.X -= (spacing.X - (float)imgsize.X) / 2; - pos.Y -= (spacing.Y - (float)imgsize.Y) / 2; + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(v_pos); + geom = getRealCoordinateGeometry(v_geom); + } else { + pos = getElementBasePos(&v_pos); + pos.X -= (spacing.X - (float)imgsize.X) / 2; + pos.Y -= (spacing.Y - (float)imgsize.Y) / 2; - geom.X = stof(v_geom[0]) * spacing.X; - geom.Y = stof(v_geom[1]) * spacing.Y; - } + geom.X = stof(v_geom[0]) * spacing.X; + geom.Y = stof(v_geom[1]) * spacing.Y; + } - bool clip = false; - if (parts.size() >= 4 && is_yes(parts[3])) { - if (data->real_coordinates) { - pos = getRealCoordinateBasePos(v_pos) * -1; - geom = v2s32(0, 0); - } else { - pos.X = stoi(v_pos[0]); //acts as offset - pos.Y = stoi(v_pos[1]); - } - clip = true; + bool clip = false; + if (parts.size() >= 4 && is_yes(parts[3])) { + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(v_pos) * -1; + geom = v2s32(0, 0); + } else { + pos.X = stoi(v_pos[0]); //acts as offset + pos.Y = stoi(v_pos[1]); } + clip = true; + } - core::rect<s32> middle; - if (parts.size() >= 5) { - std::vector<std::string> v_middle = split(parts[4], ','); - if (v_middle.size() == 1) { - s32 x = stoi(v_middle[0]); - middle.UpperLeftCorner = core::vector2di(x, x); - middle.LowerRightCorner = core::vector2di(-x, -x); - } else if (v_middle.size() == 2) { - s32 x = stoi(v_middle[0]); - s32 y = stoi(v_middle[1]); - middle.UpperLeftCorner = core::vector2di(x, y); - middle.LowerRightCorner = core::vector2di(-x, -y); - // `-x` is interpreted as `w - x` - } else if (v_middle.size() == 4) { - middle.UpperLeftCorner = core::vector2di(stoi(v_middle[0]), stoi(v_middle[1])); - middle.LowerRightCorner = core::vector2di(stoi(v_middle[2]), stoi(v_middle[3])); - } else { - warningstream << "Invalid rectangle given to middle param of background[] element" << std::endl; - } + core::rect<s32> middle; + if (parts.size() >= 5) { + std::vector<std::string> v_middle = split(parts[4], ','); + if (v_middle.size() == 1) { + s32 x = stoi(v_middle[0]); + middle.UpperLeftCorner = core::vector2di(x, x); + middle.LowerRightCorner = core::vector2di(-x, -x); + } else if (v_middle.size() == 2) { + s32 x = stoi(v_middle[0]); + s32 y = stoi(v_middle[1]); + middle.UpperLeftCorner = core::vector2di(x, y); + middle.LowerRightCorner = core::vector2di(-x, -y); + // `-x` is interpreted as `w - x` + } else if (v_middle.size() == 4) { + middle.UpperLeftCorner = core::vector2di(stoi(v_middle[0]), stoi(v_middle[1])); + middle.LowerRightCorner = core::vector2di(stoi(v_middle[2]), stoi(v_middle[3])); + } else { + warningstream << "Invalid rectangle given to middle param of background[] element" << std::endl; } + } - if (!data->explicit_size && !clip) - warningstream << "invalid use of unclipped background without a size[] element" << std::endl; - - FieldSpec spec( - name, - L"", - L"", - 258 + m_fields.size() - ); + if (!data->explicit_size && !clip) + warningstream << "invalid use of unclipped background without a size[] element" << std::endl; - core::rect<s32> rect; - if (!clip) { - // no auto_clip => position like normal image - rect = core::rect<s32>(pos, pos + geom); - } else { - // it will be auto-clipped when drawing - rect = core::rect<s32>(-pos, pos); - } + FieldSpec spec( + name, + L"", + L"", + 258 + m_fields.size() + ); - GUIBackgroundImage *e = new GUIBackgroundImage(Environment, this, spec.fid, - rect, name, middle, m_tsrc, clip); + core::rect<s32> rect; + if (!clip) { + // no auto_clip => position like normal image + rect = core::rect<s32>(pos, pos + geom); + } else { + // it will be auto-clipped when drawing + rect = core::rect<s32>(-pos, pos); + } - FATAL_ERROR_IF(!e, "Failed to create background formspec element"); + GUIBackgroundImage *e = new GUIBackgroundImage(Environment, data->background_parent.get(), + spec.fid, rect, name, middle, m_tsrc, clip); - e->setNotClipped(true); + FATAL_ERROR_IF(!e, "Failed to create background formspec element"); - e->setVisible(false); // the element is drawn manually before all others + e->setNotClipped(true); - m_backgrounds.push_back(e); - m_fields.push_back(spec); - return; - } - errorstream<< "Invalid background element(" << parts.size() << "): '" << element << "'" << std::endl; + m_fields.push_back(spec); + e->drop(); } void GUIFormSpecMenu::parseTableOptions(parserData* data, const std::string &element) @@ -1192,338 +1167,320 @@ void GUIFormSpecMenu::parseTableColumns(parserData* data, const std::string &ele void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element) { - std::vector<std::string> parts = split(element,';'); + std::vector<std::string> parts; + if (!precheckElement("table", element, 4, 5, parts)) + return; - if (((parts.size() == 4) || (parts.size() == 5)) || - ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector<std::string> v_pos = split(parts[0],','); - std::vector<std::string> v_geom = split(parts[1],','); - std::string name = parts[2]; - std::vector<std::string> items = split(parts[3],','); - std::string str_initial_selection; - std::string str_transparent = "false"; + std::vector<std::string> v_pos = split(parts[0],','); + std::vector<std::string> v_geom = split(parts[1],','); + std::string name = parts[2]; + std::vector<std::string> items = split(parts[3],','); + std::string str_initial_selection; + std::string str_transparent = "false"; - if (parts.size() >= 5) - str_initial_selection = parts[4]; + if (parts.size() >= 5) + str_initial_selection = parts[4]; - MY_CHECKPOS("table",0); - MY_CHECKGEOM("table",1); + MY_CHECKPOS("table",0); + MY_CHECKGEOM("table",1); - v2s32 pos; - v2s32 geom; + v2s32 pos; + v2s32 geom; - if (data->real_coordinates) { - pos = getRealCoordinateBasePos(v_pos); - geom = getRealCoordinateGeometry(v_geom); - } else { - pos = getElementBasePos(&v_pos); - geom.X = stof(v_geom[0]) * spacing.X; - geom.Y = stof(v_geom[1]) * spacing.Y; - } + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(v_pos); + geom = getRealCoordinateGeometry(v_geom); + } else { + pos = getElementBasePos(&v_pos); + geom.X = stof(v_geom[0]) * spacing.X; + geom.Y = stof(v_geom[1]) * spacing.Y; + } - core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); + core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); - FieldSpec spec( - name, - L"", - L"", - 258 + m_fields.size() - ); + FieldSpec spec( + name, + L"", + L"", + 258 + m_fields.size() + ); - spec.ftype = f_Table; + spec.ftype = f_Table; - for (std::string &item : items) { - item = wide_to_utf8(unescape_translate(utf8_to_wide(unescape_string(item)))); - } + for (std::string &item : items) { + item = wide_to_utf8(unescape_translate(utf8_to_wide(unescape_string(item)))); + } - //now really show table - GUITable *e = new GUITable(Environment, data->current_parent, spec.fid, - rect, m_tsrc); + //now really show table + GUITable *e = new GUITable(Environment, data->current_parent, spec.fid, + rect, m_tsrc); - if (spec.fname == m_focused_element) { - Environment->setFocus(e); - } + if (spec.fname == m_focused_element) { + Environment->setFocus(e); + } - e->setTable(data->table_options, data->table_columns, items); + e->setTable(data->table_options, data->table_columns, items); - if (data->table_dyndata.find(name) != data->table_dyndata.end()) { - e->setDynamicData(data->table_dyndata[name]); - } + if (data->table_dyndata.find(name) != data->table_dyndata.end()) { + e->setDynamicData(data->table_dyndata[name]); + } - if (!str_initial_selection.empty() && str_initial_selection != "0") - e->setSelected(stoi(str_initial_selection)); + if (!str_initial_selection.empty() && str_initial_selection != "0") + e->setSelected(stoi(str_initial_selection)); - auto style = getDefaultStyleForElement("table", name); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); - e->setOverrideFont(style.getFont()); + auto style = getDefaultStyleForElement("table", name); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); + e->setOverrideFont(style.getFont()); - m_tables.emplace_back(spec, e); - m_fields.push_back(spec); - return; - } - errorstream<< "Invalid table element(" << parts.size() << "): '" << element << "'" << std::endl; + m_tables.emplace_back(spec, e); + m_fields.push_back(spec); } void GUIFormSpecMenu::parseTextList(parserData* data, const std::string &element) { - std::vector<std::string> parts = split(element,';'); + std::vector<std::string> parts; + if (!precheckElement("textlist", element, 4, 6, parts)) + return; - if (((parts.size() == 4) || (parts.size() == 5) || (parts.size() == 6)) || - ((parts.size() > 6) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector<std::string> v_pos = split(parts[0],','); - std::vector<std::string> v_geom = split(parts[1],','); - std::string name = parts[2]; - std::vector<std::string> items = split(parts[3],','); - std::string str_initial_selection; - std::string str_transparent = "false"; + std::vector<std::string> v_pos = split(parts[0],','); + std::vector<std::string> v_geom = split(parts[1],','); + std::string name = parts[2]; + std::vector<std::string> items = split(parts[3],','); + std::string str_initial_selection; + std::string str_transparent = "false"; - if (parts.size() >= 5) - str_initial_selection = parts[4]; + if (parts.size() >= 5) + str_initial_selection = parts[4]; - if (parts.size() >= 6) - str_transparent = parts[5]; + if (parts.size() >= 6) + str_transparent = parts[5]; - MY_CHECKPOS("textlist",0); - MY_CHECKGEOM("textlist",1); + MY_CHECKPOS("textlist",0); + MY_CHECKGEOM("textlist",1); - v2s32 pos; - v2s32 geom; + v2s32 pos; + v2s32 geom; - if (data->real_coordinates) { - pos = getRealCoordinateBasePos(v_pos); - geom = getRealCoordinateGeometry(v_geom); - } else { - pos = getElementBasePos(&v_pos); - geom.X = stof(v_geom[0]) * spacing.X; - geom.Y = stof(v_geom[1]) * spacing.Y; - } + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(v_pos); + geom = getRealCoordinateGeometry(v_geom); + } else { + pos = getElementBasePos(&v_pos); + geom.X = stof(v_geom[0]) * spacing.X; + geom.Y = stof(v_geom[1]) * spacing.Y; + } - core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); + core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); - FieldSpec spec( - name, - L"", - L"", - 258 + m_fields.size() - ); + FieldSpec spec( + name, + L"", + L"", + 258 + m_fields.size() + ); - spec.ftype = f_Table; + spec.ftype = f_Table; - for (std::string &item : items) { - item = wide_to_utf8(unescape_translate(utf8_to_wide(unescape_string(item)))); - } + for (std::string &item : items) { + item = wide_to_utf8(unescape_translate(utf8_to_wide(unescape_string(item)))); + } - //now really show list - GUITable *e = new GUITable(Environment, data->current_parent, spec.fid, - rect, m_tsrc); + //now really show list + GUITable *e = new GUITable(Environment, data->current_parent, spec.fid, + rect, m_tsrc); - if (spec.fname == m_focused_element) { - Environment->setFocus(e); - } + if (spec.fname == m_focused_element) { + Environment->setFocus(e); + } - e->setTextList(items, is_yes(str_transparent)); + e->setTextList(items, is_yes(str_transparent)); - if (data->table_dyndata.find(name) != data->table_dyndata.end()) { - e->setDynamicData(data->table_dyndata[name]); - } + if (data->table_dyndata.find(name) != data->table_dyndata.end()) { + e->setDynamicData(data->table_dyndata[name]); + } - if (!str_initial_selection.empty() && str_initial_selection != "0") - e->setSelected(stoi(str_initial_selection)); + if (!str_initial_selection.empty() && str_initial_selection != "0") + e->setSelected(stoi(str_initial_selection)); - auto style = getDefaultStyleForElement("textlist", name); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); - e->setOverrideFont(style.getFont()); + auto style = getDefaultStyleForElement("textlist", name); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); + e->setOverrideFont(style.getFont()); - m_tables.emplace_back(spec, e); - m_fields.push_back(spec); - return; - } - errorstream<< "Invalid textlist element(" << parts.size() << "): '" << element << "'" << std::endl; + m_tables.emplace_back(spec, e); + m_fields.push_back(spec); } void GUIFormSpecMenu::parseDropDown(parserData* data, const std::string &element) { - std::vector<std::string> parts = split(element, ';'); - - if (parts.size() == 5 || parts.size() == 6 || - (parts.size() > 6 && m_formspec_version > FORMSPEC_API_VERSION)) - { - std::vector<std::string> v_pos = split(parts[0], ','); - std::string name = parts[2]; - std::vector<std::string> items = split(parts[3], ','); - std::string str_initial_selection = parts[4]; + std::vector<std::string> parts; + if (!precheckElement("dropdown", element, 5, 6, parts)) + return; - if (parts.size() >= 6 && is_yes(parts[5])) - m_dropdown_index_event[name] = true; + std::vector<std::string> v_pos = split(parts[0], ','); + std::string name = parts[2]; + std::vector<std::string> items = split(parts[3], ','); + std::string str_initial_selection = parts[4]; - MY_CHECKPOS("dropdown",0); + if (parts.size() >= 6 && is_yes(parts[5])) + m_dropdown_index_event[name] = true; - v2s32 pos; - v2s32 geom; - core::rect<s32> rect; + MY_CHECKPOS("dropdown",0); - if (data->real_coordinates) { - std::vector<std::string> v_geom = split(parts[1],','); + v2s32 pos; + v2s32 geom; + core::rect<s32> rect; - if (v_geom.size() == 1) - v_geom.emplace_back("1"); + if (data->real_coordinates) { + std::vector<std::string> v_geom = split(parts[1],','); - MY_CHECKGEOM("dropdown",1); + if (v_geom.size() == 1) + v_geom.emplace_back("1"); - pos = getRealCoordinateBasePos(v_pos); - geom = getRealCoordinateGeometry(v_geom); - rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); - } else { - pos = getElementBasePos(&v_pos); + MY_CHECKGEOM("dropdown",1); - s32 width = stof(parts[1]) * spacing.Y; + pos = getRealCoordinateBasePos(v_pos); + geom = getRealCoordinateGeometry(v_geom); + rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); + } else { + pos = getElementBasePos(&v_pos); - rect = core::rect<s32>(pos.X, pos.Y, - pos.X + width, pos.Y + (m_btn_height * 2)); - } + s32 width = stof(parts[1]) * spacing.Y; - FieldSpec spec( - name, - L"", - L"", - 258 + m_fields.size() - ); + rect = core::rect<s32>(pos.X, pos.Y, + pos.X + width, pos.Y + (m_btn_height * 2)); + } - spec.ftype = f_DropDown; - spec.send = true; + FieldSpec spec( + name, + L"", + L"", + 258 + m_fields.size() + ); - //now really show list - gui::IGUIComboBox *e = Environment->addComboBox(rect, data->current_parent, - spec.fid); + spec.ftype = f_DropDown; + spec.send = true; - if (spec.fname == m_focused_element) { - Environment->setFocus(e); - } + //now really show list + gui::IGUIComboBox *e = Environment->addComboBox(rect, data->current_parent, + spec.fid); - for (const std::string &item : items) { - e->addItem(unescape_translate(unescape_string( - utf8_to_wide(item))).c_str()); - } + if (spec.fname == m_focused_element) { + Environment->setFocus(e); + } - if (!str_initial_selection.empty()) - e->setSelected(stoi(str_initial_selection)-1); + for (const std::string &item : items) { + e->addItem(unescape_translate(unescape_string( + utf8_to_wide(item))).c_str()); + } - auto style = getDefaultStyleForElement("dropdown", name); + if (!str_initial_selection.empty()) + e->setSelected(stoi(str_initial_selection)-1); - spec.sound = style.get(StyleSpec::Property::SOUND, ""); + auto style = getDefaultStyleForElement("dropdown", name); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); + spec.sound = style.get(StyleSpec::Property::SOUND, ""); - m_fields.push_back(spec); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); - m_dropdowns.emplace_back(spec, std::vector<std::string>()); - std::vector<std::string> &values = m_dropdowns.back().second; - for (const std::string &item : items) { - values.push_back(unescape_string(item)); - } + m_fields.push_back(spec); - return; + m_dropdowns.emplace_back(spec, std::vector<std::string>()); + std::vector<std::string> &values = m_dropdowns.back().second; + for (const std::string &item : items) { + values.push_back(unescape_string(item)); } - errorstream << "Invalid dropdown element(" << parts.size() << "): '" << element - << "'" << std::endl; } void GUIFormSpecMenu::parseFieldCloseOnEnter(parserData *data, const std::string &element) { - std::vector<std::string> parts = split(element,';'); - if (parts.size() == 2 || - (parts.size() > 2 && m_formspec_version > FORMSPEC_API_VERSION)) { - field_close_on_enter[parts[0]] = is_yes(parts[1]); - } + std::vector<std::string> parts; + if (!precheckElement("field_close_on_enter", element, 2, 2, parts)) + return; + + field_close_on_enter[parts[0]] = is_yes(parts[1]); } void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element) { - std::vector<std::string> parts = split(element,';'); - - if (parts.size() == 4 || - (parts.size() > 4 && m_formspec_version > FORMSPEC_API_VERSION)) - { - std::vector<std::string> v_pos = split(parts[0],','); - std::vector<std::string> v_geom = split(parts[1],','); - std::string name = parts[2]; - std::string label = parts[3]; - - MY_CHECKPOS("pwdfield",0); - MY_CHECKGEOM("pwdfield",1); + std::vector<std::string> parts; + if (!precheckElement("pwdfield", element, 4, 4, parts)) + return; - v2s32 pos; - v2s32 geom; + std::vector<std::string> v_pos = split(parts[0],','); + std::vector<std::string> v_geom = split(parts[1],','); + std::string name = parts[2]; + std::string label = parts[3]; - if (data->real_coordinates) { - pos = getRealCoordinateBasePos(v_pos); - geom = getRealCoordinateGeometry(v_geom); - } else { - pos = getElementBasePos(&v_pos); - pos -= padding; + MY_CHECKPOS("pwdfield",0); + MY_CHECKGEOM("pwdfield",1); - geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X); + v2s32 pos; + v2s32 geom; - pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2; - pos.Y -= m_btn_height; - geom.Y = m_btn_height*2; - } + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(v_pos); + geom = getRealCoordinateGeometry(v_geom); + } else { + pos = getElementBasePos(&v_pos); + pos -= padding; - core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); + geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X); - std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label))); + pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2; + pos.Y -= m_btn_height; + geom.Y = m_btn_height*2; + } - FieldSpec spec( - name, - wlabel, - L"", - 258 + m_fields.size(), - 0, - ECI_IBEAM - ); + core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); - spec.send = true; - gui::IGUIEditBox *e = Environment->addEditBox(0, rect, true, - data->current_parent, spec.fid); + std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label))); - if (spec.fname == m_focused_element) { - Environment->setFocus(e); - } + FieldSpec spec( + name, + wlabel, + L"", + 258 + m_fields.size(), + 0, + ECI_IBEAM + ); - if (label.length() >= 1) { - int font_height = g_fontengine->getTextHeight(); - rect.UpperLeftCorner.Y -= font_height; - rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height; - gui::StaticText::add(Environment, spec.flabel.c_str(), rect, false, true, - data->current_parent, 0); - } + spec.send = true; + gui::IGUIEditBox *e = Environment->addEditBox(0, rect, true, + data->current_parent, spec.fid); - e->setPasswordBox(true,L'*'); + if (spec.fname == m_focused_element) { + Environment->setFocus(e); + } - auto style = getDefaultStyleForElement("pwdfield", name, "field"); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); - e->setDrawBorder(style.getBool(StyleSpec::BORDER, true)); - e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); - e->setOverrideFont(style.getFont()); + if (label.length() >= 1) { + int font_height = g_fontengine->getTextHeight(); + rect.UpperLeftCorner.Y -= font_height; + rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height; + gui::StaticText::add(Environment, spec.flabel.c_str(), rect, false, true, + data->current_parent, 0); + } - irr::SEvent evt; - evt.EventType = EET_KEY_INPUT_EVENT; - evt.KeyInput.Key = KEY_END; - evt.KeyInput.Char = 0; - evt.KeyInput.Control = false; - evt.KeyInput.Shift = false; - evt.KeyInput.PressedDown = true; - e->OnEvent(evt); + e->setPasswordBox(true,L'*'); - // Note: Before 5.2.0 "parts.size() >= 5" resulted in a - // warning referring to field_close_on_enter[]! + auto style = getDefaultStyleForElement("pwdfield", name, "field"); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); + e->setDrawBorder(style.getBool(StyleSpec::BORDER, true)); + e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); + e->setOverrideFont(style.getFont()); + + irr::SEvent evt; + evt.EventType = EET_KEY_INPUT_EVENT; + evt.KeyInput.Key = KEY_END; + evt.KeyInput.Char = 0; + evt.KeyInput.Control = false; + evt.KeyInput.Shift = false; + evt.KeyInput.PressedDown = true; + e->OnEvent(evt); + + // Note: Before 5.2.0 "parts.size() >= 5" resulted in a + // warning referring to field_close_on_enter[]! - m_fields.push_back(spec); - return; - } - errorstream<< "Invalid pwdfield element(" << parts.size() << "): '" << element << "'" << std::endl; + m_fields.push_back(spec); } void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec, @@ -1710,30 +1667,26 @@ void GUIFormSpecMenu::parseTextArea(parserData* data, std::vector<std::string>& void GUIFormSpecMenu::parseField(parserData* data, const std::string &element, const std::string &type) { - std::vector<std::string> parts = split(element,';'); + std::vector<std::string> parts; + if (!precheckElement(type, element, 3, 5, parts)) + return; if (parts.size() == 3 || parts.size() == 4) { - parseSimpleField(data,parts); + parseSimpleField(data, parts); return; } - if ((parts.size() == 5) || - ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - parseTextArea(data,parts,type); - return; - } - errorstream<< "Invalid field element(" << parts.size() << "): '" << element << "'" << std::endl; + // Else: >= 5 arguments in "parts" + parseTextArea(data, parts, type); } void GUIFormSpecMenu::parseHyperText(parserData *data, const std::string &element) { - std::vector<std::string> parts = split(element, ';'); + MY_CHECKCLIENT("list"); - if (parts.size() != 4 && m_formspec_version < FORMSPEC_API_VERSION) { - errorstream << "Invalid text element(" << parts.size() << "): '" << element << "'" << std::endl; + std::vector<std::string> parts; + if (!precheckElement("hypertext", element, 4, 4, parts)) return; - } std::vector<std::string> v_pos = split(parts[0], ','); std::vector<std::string> v_geom = split(parts[1], ','); @@ -1784,539 +1737,521 @@ void GUIFormSpecMenu::parseHyperText(parserData *data, const std::string &elemen void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element) { - std::vector<std::string> parts = split(element,';'); + std::vector<std::string> parts; + if (!precheckElement("label", element, 2, 2, parts)) + return; - if ((parts.size() == 2) || - ((parts.size() > 2) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector<std::string> v_pos = split(parts[0],','); - std::string text = parts[1]; + std::vector<std::string> v_pos = split(parts[0],','); + std::string text = parts[1]; - MY_CHECKPOS("label",0); + MY_CHECKPOS("label",0); - if(!data->explicit_size) - warningstream<<"invalid use of label without a size[] element"<<std::endl; + if(!data->explicit_size) + warningstream<<"invalid use of label without a size[] element"<<std::endl; - std::vector<std::string> lines = split(text, '\n'); + std::vector<std::string> lines = split(text, '\n'); - auto style = getDefaultStyleForElement("label", ""); - gui::IGUIFont *font = style.getFont(); - if (!font) - font = m_font; + auto style = getDefaultStyleForElement("label", ""); + gui::IGUIFont *font = style.getFont(); + if (!font) + font = m_font; - for (unsigned int i = 0; i != lines.size(); i++) { - std::wstring wlabel_colors = translate_string( - utf8_to_wide(unescape_string(lines[i]))); - // Without color escapes to get the font dimensions - std::wstring wlabel_plain = unescape_enriched(wlabel_colors); + for (unsigned int i = 0; i != lines.size(); i++) { + std::wstring wlabel_colors = translate_string( + utf8_to_wide(unescape_string(lines[i]))); + // Without color escapes to get the font dimensions + std::wstring wlabel_plain = unescape_enriched(wlabel_colors); - core::rect<s32> rect; + core::rect<s32> rect; - if (data->real_coordinates) { - // Lines are spaced at the distance of 1/2 imgsize. - // This alows lines that line up with the new elements - // easily without sacrificing good line distance. If - // it was one whole imgsize, it would have too much - // spacing. - v2s32 pos = getRealCoordinateBasePos(v_pos); + if (data->real_coordinates) { + // Lines are spaced at the distance of 1/2 imgsize. + // This alows lines that line up with the new elements + // easily without sacrificing good line distance. If + // it was one whole imgsize, it would have too much + // spacing. + v2s32 pos = getRealCoordinateBasePos(v_pos); - // Labels are positioned by their center, not their top. - pos.Y += (((float) imgsize.Y) / -2) + (((float) imgsize.Y) * i / 2); + // Labels are positioned by their center, not their top. + pos.Y += (((float) imgsize.Y) / -2) + (((float) imgsize.Y) * i / 2); - rect = core::rect<s32>( - pos.X, pos.Y, - pos.X + font->getDimension(wlabel_plain.c_str()).Width, - pos.Y + imgsize.Y); + rect = core::rect<s32>( + pos.X, pos.Y, + pos.X + font->getDimension(wlabel_plain.c_str()).Width, + pos.Y + imgsize.Y); - } else { - // Lines are spaced at the nominal distance of - // 2/5 inventory slot, even if the font doesn't - // quite match that. This provides consistent - // form layout, at the expense of sometimes - // having sub-optimal spacing for the font. - // We multiply by 2 and then divide by 5, rather - // than multiply by 0.4, to get exact results - // in the integer cases: 0.4 is not exactly - // representable in binary floating point. - - v2s32 pos = getElementBasePos(nullptr); - pos.X += stof(v_pos[0]) * spacing.X; - pos.Y += (stof(v_pos[1]) + 7.0f / 30.0f) * spacing.Y; - - pos.Y += ((float) i) * spacing.Y * 2.0 / 5.0; - - rect = core::rect<s32>( - pos.X, pos.Y - m_btn_height, - pos.X + font->getDimension(wlabel_plain.c_str()).Width, - pos.Y + m_btn_height); - } + } else { + // Lines are spaced at the nominal distance of + // 2/5 inventory slot, even if the font doesn't + // quite match that. This provides consistent + // form layout, at the expense of sometimes + // having sub-optimal spacing for the font. + // We multiply by 2 and then divide by 5, rather + // than multiply by 0.4, to get exact results + // in the integer cases: 0.4 is not exactly + // representable in binary floating point. + + v2s32 pos = getElementBasePos(nullptr); + pos.X += stof(v_pos[0]) * spacing.X; + pos.Y += (stof(v_pos[1]) + 7.0f / 30.0f) * spacing.Y; + + pos.Y += ((float) i) * spacing.Y * 2.0 / 5.0; - FieldSpec spec( - "", - wlabel_colors, - L"", - 258 + m_fields.size(), - 4 - ); - gui::IGUIStaticText *e = gui::StaticText::add(Environment, - spec.flabel.c_str(), rect, false, false, data->current_parent, - spec.fid); - e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_CENTER); + rect = core::rect<s32>( + pos.X, pos.Y - m_btn_height, + pos.X + font->getDimension(wlabel_plain.c_str()).Width, + pos.Y + m_btn_height); + } - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); - e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); - e->setOverrideFont(font); + FieldSpec spec( + "", + wlabel_colors, + L"", + 258 + m_fields.size(), + 4 + ); + gui::IGUIStaticText *e = gui::StaticText::add(Environment, + spec.flabel.c_str(), rect, false, false, data->current_parent, + spec.fid); + e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_CENTER); - m_fields.push_back(spec); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); + e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); + e->setOverrideFont(font); - // labels should let events through - e->grab(); - m_clickthrough_elements.push_back(e); - } + m_fields.push_back(spec); - return; + // labels should let events through + e->grab(); + m_clickthrough_elements.push_back(e); } - errorstream << "Invalid label element(" << parts.size() << "): '" << element - << "'" << std::endl; } void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &element) { - std::vector<std::string> parts = split(element,';'); + std::vector<std::string> parts; + if (!precheckElement("vertlabel", element, 2, 2, parts)) + return; - if ((parts.size() == 2) || - ((parts.size() > 2) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector<std::string> v_pos = split(parts[0],','); - std::wstring text = unescape_translate( - unescape_string(utf8_to_wide(parts[1]))); + std::vector<std::string> v_pos = split(parts[0],','); + std::wstring text = unescape_translate( + unescape_string(utf8_to_wide(parts[1]))); - MY_CHECKPOS("vertlabel",1); + MY_CHECKPOS("vertlabel",1); - auto style = getDefaultStyleForElement("vertlabel", "", "label"); - gui::IGUIFont *font = style.getFont(); - if (!font) - font = m_font; + auto style = getDefaultStyleForElement("vertlabel", "", "label"); + gui::IGUIFont *font = style.getFont(); + if (!font) + font = m_font; - v2s32 pos; - core::rect<s32> rect; + v2s32 pos; + core::rect<s32> rect; - if (data->real_coordinates) { - pos = getRealCoordinateBasePos(v_pos); + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(v_pos); - // Vertlabels are positioned by center, not left. - pos.X -= imgsize.X / 2; + // Vertlabels are positioned by center, not left. + pos.X -= imgsize.X / 2; - // We use text.length + 1 because without it, the rect - // isn't quite tall enough and cuts off the text. - rect = core::rect<s32>(pos.X, pos.Y, - pos.X + imgsize.X, - pos.Y + font_line_height(font) * - (text.length() + 1)); + // We use text.length + 1 because without it, the rect + // isn't quite tall enough and cuts off the text. + rect = core::rect<s32>(pos.X, pos.Y, + pos.X + imgsize.X, + pos.Y + font_line_height(font) * + (text.length() + 1)); - } else { - pos = getElementBasePos(&v_pos); + } else { + pos = getElementBasePos(&v_pos); - // As above, the length must be one longer. The width of - // the rect (15 pixels) seems rather arbitrary, but - // changing it might break something. - rect = core::rect<s32>( - pos.X, pos.Y+((imgsize.Y/2) - m_btn_height), - pos.X+15, pos.Y + - font_line_height(font) * - (text.length() + 1) + - ((imgsize.Y/2) - m_btn_height)); - } + // As above, the length must be one longer. The width of + // the rect (15 pixels) seems rather arbitrary, but + // changing it might break something. + rect = core::rect<s32>( + pos.X, pos.Y+((imgsize.Y/2) - m_btn_height), + pos.X+15, pos.Y + + font_line_height(font) * + (text.length() + 1) + + ((imgsize.Y/2) - m_btn_height)); + } - if(!data->explicit_size) - warningstream<<"invalid use of label without a size[] element"<<std::endl; + if(!data->explicit_size) + warningstream<<"invalid use of label without a size[] element"<<std::endl; - std::wstring label; + std::wstring label; - for (wchar_t i : text) { - label += i; - label += L"\n"; - } + for (wchar_t i : text) { + label += i; + label += L"\n"; + } - FieldSpec spec( - "", - label, - L"", - 258 + m_fields.size() - ); - gui::IGUIStaticText *e = gui::StaticText::add(Environment, spec.flabel.c_str(), - rect, false, false, data->current_parent, spec.fid); - e->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER); + FieldSpec spec( + "", + label, + L"", + 258 + m_fields.size() + ); + gui::IGUIStaticText *e = gui::StaticText::add(Environment, spec.flabel.c_str(), + rect, false, false, data->current_parent, spec.fid); + e->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); - e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); - e->setOverrideFont(font); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); + e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); + e->setOverrideFont(font); - m_fields.push_back(spec); + m_fields.push_back(spec); - // vertlabels should let events through - e->grab(); - m_clickthrough_elements.push_back(e); - return; - } - errorstream<< "Invalid vertlabel element(" << parts.size() << "): '" << element << "'" << std::endl; + // vertlabels should let events through + e->grab(); + m_clickthrough_elements.push_back(e); } void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &element, const std::string &type) { - std::vector<std::string> parts = split(element,';'); - - if ((((parts.size() >= 5) && (parts.size() <= 8)) && (parts.size() != 6)) || - ((parts.size() > 8) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector<std::string> v_pos = split(parts[0],','); - std::vector<std::string> v_geom = split(parts[1],','); - std::string image_name = parts[2]; - std::string name = parts[3]; - std::string label = parts[4]; + std::vector<std::string> parts; + if (!precheckElement("image_button", element, 5, 8, parts)) + return; - MY_CHECKPOS("imagebutton",0); - MY_CHECKGEOM("imagebutton",1); + if (parts.size() == 6) { + // Invalid argument count. + errorstream << "Invalid image_button element(" << parts.size() << "): '" << element << "'" << std::endl; + return; + } - std::string pressed_image_name; + std::vector<std::string> v_pos = split(parts[0],','); + std::vector<std::string> v_geom = split(parts[1],','); + std::string image_name = parts[2]; + std::string name = parts[3]; + std::string label = parts[4]; - if (parts.size() >= 8) { - pressed_image_name = parts[7]; - } + MY_CHECKPOS("image_button",0); + MY_CHECKGEOM("image_button",1); - v2s32 pos; - v2s32 geom; + std::string pressed_image_name; - if (data->real_coordinates) { - pos = getRealCoordinateBasePos(v_pos); - geom = getRealCoordinateGeometry(v_geom); - } else { - pos = getElementBasePos(&v_pos); - geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X); - geom.Y = (stof(v_geom[1]) * spacing.Y) - (spacing.Y - imgsize.Y); - } + if (parts.size() >= 8) { + pressed_image_name = parts[7]; + } - core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, - pos.Y+geom.Y); + v2s32 pos; + v2s32 geom; - if (!data->explicit_size) - warningstream<<"invalid use of image_button without a size[] element"<<std::endl; + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(v_pos); + geom = getRealCoordinateGeometry(v_geom); + } else { + pos = getElementBasePos(&v_pos); + geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X); + geom.Y = (stof(v_geom[1]) * spacing.Y) - (spacing.Y - imgsize.Y); + } - image_name = unescape_string(image_name); - pressed_image_name = unescape_string(pressed_image_name); + core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, + pos.Y+geom.Y); - std::wstring wlabel = utf8_to_wide(unescape_string(label)); + if (!data->explicit_size) + warningstream<<"invalid use of image_button without a size[] element"<<std::endl; - FieldSpec spec( - name, - wlabel, - utf8_to_wide(image_name), - 258 + m_fields.size() - ); - spec.ftype = f_Button; - if (type == "image_button_exit") - spec.is_exit = true; + image_name = unescape_string(image_name); + pressed_image_name = unescape_string(pressed_image_name); - GUIButtonImage *e = GUIButtonImage::addButton(Environment, rect, m_tsrc, - data->current_parent, spec.fid, spec.flabel.c_str()); + std::wstring wlabel = utf8_to_wide(unescape_string(label)); - if (spec.fname == m_focused_element) { - Environment->setFocus(e); - } + FieldSpec spec( + name, + wlabel, + utf8_to_wide(image_name), + 258 + m_fields.size() + ); + spec.ftype = f_Button; + if (type == "image_button_exit") + spec.is_exit = true; - auto style = getStyleForElement("image_button", spec.fname); + GUIButtonImage *e = GUIButtonImage::addButton(Environment, rect, m_tsrc, + data->current_parent, spec.fid, spec.flabel.c_str()); - spec.sound = style[StyleSpec::STATE_DEFAULT].get(StyleSpec::Property::SOUND, ""); + if (spec.fname == m_focused_element) { + Environment->setFocus(e); + } - // Override style properties with values specified directly in the element - if (!image_name.empty()) - style[StyleSpec::STATE_DEFAULT].set(StyleSpec::FGIMG, image_name); + auto style = getStyleForElement("image_button", spec.fname); - if (!pressed_image_name.empty()) - style[StyleSpec::STATE_PRESSED].set(StyleSpec::FGIMG, pressed_image_name); + spec.sound = style[StyleSpec::STATE_DEFAULT].get(StyleSpec::Property::SOUND, ""); - if (parts.size() >= 7) { - style[StyleSpec::STATE_DEFAULT].set(StyleSpec::NOCLIP, parts[5]); - style[StyleSpec::STATE_DEFAULT].set(StyleSpec::BORDER, parts[6]); - } + // Override style properties with values specified directly in the element + if (!image_name.empty()) + style[StyleSpec::STATE_DEFAULT].set(StyleSpec::FGIMG, image_name); - e->setStyles(style); - e->setScaleImage(true); + if (!pressed_image_name.empty()) + style[StyleSpec::STATE_PRESSED].set(StyleSpec::FGIMG, pressed_image_name); - m_fields.push_back(spec); - return; + if (parts.size() >= 7) { + style[StyleSpec::STATE_DEFAULT].set(StyleSpec::NOCLIP, parts[5]); + style[StyleSpec::STATE_DEFAULT].set(StyleSpec::BORDER, parts[6]); } - errorstream<< "Invalid imagebutton element(" << parts.size() << "): '" << element << "'" << std::endl; + e->setStyles(style); + e->setScaleImage(true); + + m_fields.push_back(spec); } void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &element) { - std::vector<std::string> parts = split(element, ';'); - - if (((parts.size() == 4) || (parts.size() == 6)) || (parts.size() == 7 && - data->real_coordinates) || ((parts.size() > 6) && - (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector<std::string> v_pos = split(parts[0],','); + std::vector<std::string> parts; + if (!precheckElement("tabheader", element, 4, 7, parts)) + return; - // If we're using real coordinates, add an extra field for height. - // Width is not here because tabs are the width of the text, and - // there's no reason to change that. - unsigned int i = 0; - std::vector<std::string> v_geom = {"1", "1"}; // Dummy width and height - bool auto_width = true; - if (parts.size() == 7) { - i++; + // Length 7: Additional "height" parameter after "pos". Only valid with real_coordinates. + // Note: New arguments for the "height" syntax cannot be added without breaking older clients. + if (parts.size() == 5 || (parts.size() == 7 && !data->real_coordinates)) { + errorstream << "Invalid tabheader element(" << parts.size() << "): '" + << element << "'" << std::endl; + return; + } - v_geom = split(parts[1], ','); - if (v_geom.size() == 1) - v_geom.insert(v_geom.begin(), "1"); // Dummy value - else - auto_width = false; - } + std::vector<std::string> v_pos = split(parts[0],','); - std::string name = parts[i+1]; - std::vector<std::string> buttons = split(parts[i+2], ','); - std::string str_index = parts[i+3]; - bool show_background = true; - bool show_border = true; - int tab_index = stoi(str_index) - 1; + // If we're using real coordinates, add an extra field for height. + // Width is not here because tabs are the width of the text, and + // there's no reason to change that. + unsigned int i = 0; + std::vector<std::string> v_geom = {"1", "1"}; // Dummy width and height + bool auto_width = true; + if (parts.size() == 7) { + i++; + + v_geom = split(parts[1], ','); + if (v_geom.size() == 1) + v_geom.insert(v_geom.begin(), "1"); // Dummy value + else + auto_width = false; + } - MY_CHECKPOS("tabheader", 0); + std::string name = parts[i+1]; + std::vector<std::string> buttons = split(parts[i+2], ','); + std::string str_index = parts[i+3]; + bool show_background = true; + bool show_border = true; + int tab_index = stoi(str_index) - 1; - if (parts.size() == 6 + i) { - if (parts[4+i] == "true") - show_background = false; - if (parts[5+i] == "false") - show_border = false; - } + MY_CHECKPOS("tabheader", 0); - FieldSpec spec( - name, - L"", - L"", - 258 + m_fields.size() - ); + if (parts.size() == 6 + i) { + if (parts[4+i] == "true") + show_background = false; + if (parts[5+i] == "false") + show_border = false; + } - spec.ftype = f_TabHeader; + FieldSpec spec( + name, + L"", + L"", + 258 + m_fields.size() + ); - v2s32 pos; - v2s32 geom; + spec.ftype = f_TabHeader; - if (data->real_coordinates) { - pos = getRealCoordinateBasePos(v_pos); + v2s32 pos; + v2s32 geom; - geom = getRealCoordinateGeometry(v_geom); - // Set default height - if (parts.size() <= 6) - geom.Y = m_btn_height * 2; - pos.Y -= geom.Y; // TabHeader base pos is the bottom, not the top. - if (auto_width) - geom.X = DesiredRect.getWidth(); // Set automatic width - - MY_CHECKGEOM("tabheader", 1); - } else { - v2f32 pos_f = pos_offset * spacing; - pos_f.X += stof(v_pos[0]) * spacing.X; - pos_f.Y += stof(v_pos[1]) * spacing.Y - m_btn_height * 2; - pos = v2s32(pos_f.X, pos_f.Y); + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(v_pos); + geom = getRealCoordinateGeometry(v_geom); + // Set default height + if (parts.size() <= 6) geom.Y = m_btn_height * 2; - geom.X = DesiredRect.getWidth(); - } + pos.Y -= geom.Y; // TabHeader base pos is the bottom, not the top. + if (auto_width) + geom.X = DesiredRect.getWidth(); // Set automatic width - core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, - pos.Y+geom.Y); + MY_CHECKGEOM("tabheader", 1); + } else { + v2f32 pos_f = pos_offset * spacing; + pos_f.X += stof(v_pos[0]) * spacing.X; + pos_f.Y += stof(v_pos[1]) * spacing.Y - m_btn_height * 2; + pos = v2s32(pos_f.X, pos_f.Y); - gui::IGUITabControl *e = Environment->addTabControl(rect, - data->current_parent, show_background, show_border, spec.fid); - e->setAlignment(irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_UPPERLEFT, - irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_LOWERRIGHT); - e->setTabHeight(geom.Y); + geom.Y = m_btn_height * 2; + geom.X = DesiredRect.getWidth(); + } - auto style = getDefaultStyleForElement("tabheader", name); + core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, + pos.Y+geom.Y); - spec.sound = style.get(StyleSpec::Property::SOUND, ""); + gui::IGUITabControl *e = Environment->addTabControl(rect, + data->current_parent, show_background, show_border, spec.fid); + e->setAlignment(irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_UPPERLEFT, + irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_LOWERRIGHT); + e->setTabHeight(geom.Y); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, true)); + auto style = getDefaultStyleForElement("tabheader", name); - for (const std::string &button : buttons) { - auto tab = e->addTab(unescape_translate(unescape_string( - utf8_to_wide(button))).c_str(), -1); - if (style.isNotDefault(StyleSpec::BGCOLOR)) - tab->setBackgroundColor(style.getColor(StyleSpec::BGCOLOR)); + spec.sound = style.get(StyleSpec::Property::SOUND, ""); - tab->setTextColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); - } + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, true)); - if ((tab_index >= 0) && - (buttons.size() < INT_MAX) && - (tab_index < (int) buttons.size())) - e->setActiveTab(tab_index); + for (const std::string &button : buttons) { + auto tab = e->addTab(unescape_translate(unescape_string( + utf8_to_wide(button))).c_str(), -1); + if (style.isNotDefault(StyleSpec::BGCOLOR)) + tab->setBackgroundColor(style.getColor(StyleSpec::BGCOLOR)); - m_fields.push_back(spec); - return; + tab->setTextColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); } - errorstream << "Invalid TabHeader element(" << parts.size() << "): '" - << element << "'" << std::endl; + + if ((tab_index >= 0) && + (buttons.size() < INT_MAX) && + (tab_index < (int) buttons.size())) + e->setActiveTab(tab_index); + + m_fields.push_back(spec); } void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string &element) { - if (m_client == 0) { - warningstream << "invalid use of item_image_button with m_client==0" - << std::endl; - return; - } - - std::vector<std::string> parts = split(element,';'); + MY_CHECKCLIENT("item_image_button"); - if ((parts.size() == 5) || - ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector<std::string> v_pos = split(parts[0],','); - std::vector<std::string> v_geom = split(parts[1],','); - std::string item_name = parts[2]; - std::string name = parts[3]; - std::string label = parts[4]; + std::vector<std::string> parts; + if (!precheckElement("item_image_button", element, 5, 5, parts)) + return; - label = unescape_string(label); - item_name = unescape_string(item_name); + std::vector<std::string> v_pos = split(parts[0],','); + std::vector<std::string> v_geom = split(parts[1],','); + std::string item_name = parts[2]; + std::string name = parts[3]; + std::string label = parts[4]; - MY_CHECKPOS("itemimagebutton",0); - MY_CHECKGEOM("itemimagebutton",1); + label = unescape_string(label); + item_name = unescape_string(item_name); - v2s32 pos; - v2s32 geom; + MY_CHECKPOS("item_image_button",0); + MY_CHECKGEOM("item_image_button",1); - if (data->real_coordinates) { - pos = getRealCoordinateBasePos(v_pos); - geom = getRealCoordinateGeometry(v_geom); - } else { - pos = getElementBasePos(&v_pos); - geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X); - geom.Y = (stof(v_geom[1]) * spacing.Y) - (spacing.Y - imgsize.Y); - } + v2s32 pos; + v2s32 geom; - core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(v_pos); + geom = getRealCoordinateGeometry(v_geom); + } else { + pos = getElementBasePos(&v_pos); + geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X); + geom.Y = (stof(v_geom[1]) * spacing.Y) - (spacing.Y - imgsize.Y); + } - if(!data->explicit_size) - warningstream<<"invalid use of item_image_button without a size[] element"<<std::endl; + core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); - IItemDefManager *idef = m_client->idef(); - ItemStack item; - item.deSerialize(item_name, idef); + if(!data->explicit_size) + warningstream<<"invalid use of item_image_button without a size[] element"<<std::endl; - m_tooltips[name] = - TooltipSpec(utf8_to_wide(item.getDefinition(idef).description), - m_default_tooltip_bgcolor, - m_default_tooltip_color); + IItemDefManager *idef = m_client->idef(); + ItemStack item; + item.deSerialize(item_name, idef); - // the spec for the button - FieldSpec spec_btn( - name, - utf8_to_wide(label), - utf8_to_wide(item_name), - 258 + m_fields.size(), - 2 - ); + m_tooltips[name] = + TooltipSpec(utf8_to_wide(item.getDefinition(idef).description), + m_default_tooltip_bgcolor, + m_default_tooltip_color); - GUIButtonItemImage *e_btn = GUIButtonItemImage::addButton(Environment, - rect, m_tsrc, data->current_parent, spec_btn.fid, spec_btn.flabel.c_str(), - item_name, m_client); + // the spec for the button + FieldSpec spec_btn( + name, + utf8_to_wide(label), + utf8_to_wide(item_name), + 258 + m_fields.size(), + 2 + ); - auto style = getStyleForElement("item_image_button", spec_btn.fname, "image_button"); + GUIButtonItemImage *e_btn = GUIButtonItemImage::addButton(Environment, + rect, m_tsrc, data->current_parent, spec_btn.fid, spec_btn.flabel.c_str(), + item_name, m_client); - spec_btn.sound = style[StyleSpec::STATE_DEFAULT].get(StyleSpec::Property::SOUND, ""); + auto style = getStyleForElement("item_image_button", spec_btn.fname, "image_button"); - e_btn->setStyles(style); + spec_btn.sound = style[StyleSpec::STATE_DEFAULT].get(StyleSpec::Property::SOUND, ""); - if (spec_btn.fname == m_focused_element) { - Environment->setFocus(e_btn); - } + e_btn->setStyles(style); - spec_btn.ftype = f_Button; - rect += data->basepos-padding; - spec_btn.rect = rect; - m_fields.push_back(spec_btn); - return; + if (spec_btn.fname == m_focused_element) { + Environment->setFocus(e_btn); } - errorstream<< "Invalid ItemImagebutton element(" << parts.size() << "): '" << element << "'" << std::endl; + + spec_btn.ftype = f_Button; + rect += data->basepos-padding; + spec_btn.rect = rect; + m_fields.push_back(spec_btn); } void GUIFormSpecMenu::parseBox(parserData* data, const std::string &element) { - std::vector<std::string> parts = split(element, ';'); + std::vector<std::string> parts; + if (!precheckElement("box", element, 3, 3, parts)) + return; - if ((parts.size() == 3) || - ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - std::vector<std::string> v_pos = split(parts[0], ','); - std::vector<std::string> v_geom = split(parts[1], ','); + std::vector<std::string> v_pos = split(parts[0], ','); + std::vector<std::string> v_geom = split(parts[1], ','); - MY_CHECKPOS("box", 0); - MY_CHECKGEOM("box", 1); + MY_CHECKPOS("box", 0); + MY_CHECKGEOM("box", 1); - v2s32 pos; - v2s32 geom; + v2s32 pos; + v2s32 geom; - if (data->real_coordinates) { - pos = getRealCoordinateBasePos(v_pos); - geom = getRealCoordinateGeometry(v_geom); - } else { - pos = getElementBasePos(&v_pos); - geom.X = stof(v_geom[0]) * spacing.X; - geom.Y = stof(v_geom[1]) * spacing.Y; - } + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(v_pos); + geom = getRealCoordinateGeometry(v_geom); + } else { + pos = getElementBasePos(&v_pos); + geom.X = stof(v_geom[0]) * spacing.X; + geom.Y = stof(v_geom[1]) * spacing.Y; + } - FieldSpec spec( - "", - L"", - L"", - 258 + m_fields.size(), - -2 - ); - spec.ftype = f_Box; + FieldSpec spec( + "", + L"", + L"", + 258 + m_fields.size(), + -2 + ); + spec.ftype = f_Box; - auto style = getDefaultStyleForElement("box", spec.fname); + auto style = getDefaultStyleForElement("box", spec.fname); - video::SColor tmp_color; - std::array<video::SColor, 4> colors; - std::array<video::SColor, 4> bordercolors = {0x0, 0x0, 0x0, 0x0}; - std::array<s32, 4> borderwidths = {0, 0, 0, 0}; + video::SColor tmp_color; + std::array<video::SColor, 4> colors; + std::array<video::SColor, 4> bordercolors = {0x0, 0x0, 0x0, 0x0}; + std::array<s32, 4> borderwidths = {0, 0, 0, 0}; - if (parseColorString(parts[2], tmp_color, true, 0x8C)) { - colors = {tmp_color, tmp_color, tmp_color, tmp_color}; - } else { - colors = style.getColorArray(StyleSpec::COLORS, {0x0, 0x0, 0x0, 0x0}); - bordercolors = style.getColorArray(StyleSpec::BORDERCOLORS, - {0x0, 0x0, 0x0, 0x0}); - borderwidths = style.getIntArray(StyleSpec::BORDERWIDTHS, {0, 0, 0, 0}); - } + if (parseColorString(parts[2], tmp_color, true, 0x8C)) { + colors = {tmp_color, tmp_color, tmp_color, tmp_color}; + } else { + colors = style.getColorArray(StyleSpec::COLORS, {0x0, 0x0, 0x0, 0x0}); + bordercolors = style.getColorArray(StyleSpec::BORDERCOLORS, + {0x0, 0x0, 0x0, 0x0}); + borderwidths = style.getIntArray(StyleSpec::BORDERWIDTHS, {0, 0, 0, 0}); + } - core::rect<s32> rect(pos, pos + geom); + core::rect<s32> rect(pos, pos + geom); - GUIBox *e = new GUIBox(Environment, data->current_parent, spec.fid, rect, - colors, bordercolors, borderwidths); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3)); - e->drop(); + GUIBox *e = new GUIBox(Environment, data->current_parent, spec.fid, rect, + colors, bordercolors, borderwidths); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3)); + e->drop(); - m_fields.push_back(spec); - return; - } - errorstream << "Invalid Box element(" << parts.size() << "): '" << element - << "'" << std::endl; + m_fields.push_back(spec); } void GUIFormSpecMenu::parseBackgroundColor(parserData* data, const std::string &element) { - std::vector<std::string> parts = split(element,';'); + std::vector<std::string> parts; + if (!precheckElement("bgcolor", element, 1, 3, parts)) + return; + const u32 parameter_count = parts.size(); - if ((parameter_count > 2 && m_formspec_version < 3) || - (parameter_count > 3 && m_formspec_version <= FORMSPEC_API_VERSION)) { + if (parameter_count > 2 && m_formspec_version < 3) { errorstream << "Invalid bgcolor element(" << parameter_count << "): '" << element << "'" << std::endl; return; @@ -2347,49 +2282,51 @@ void GUIFormSpecMenu::parseBackgroundColor(parserData* data, const std::string & void GUIFormSpecMenu::parseListColors(parserData* data, const std::string &element) { - std::vector<std::string> parts = split(element,';'); + std::vector<std::string> parts; + // Legacy Note: If clients older than 5.5.0-dev are supplied with additional arguments, + // the tooltip colors will be ignored. + if (!precheckElement("listcolors", element, 2, 5, parts)) + return; - if (((parts.size() == 2) || (parts.size() == 3) || (parts.size() == 5)) || - ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION))) - { - parseColorString(parts[0], data->inventorylist_options.slotbg_n, false); - parseColorString(parts[1], data->inventorylist_options.slotbg_h, false); + if (parts.size() == 4) { + // Invalid argument combination + errorstream << "Invalid listcolors element(" << parts.size() << "): '" + << element << "'" << std::endl; + return; + } - if (parts.size() >= 3) { - if (parseColorString(parts[2], data->inventorylist_options.slotbordercolor, - false)) { - data->inventorylist_options.slotborder = true; - } - } - if (parts.size() == 5) { - video::SColor tmp_color; + parseColorString(parts[0], data->inventorylist_options.slotbg_n, false); + parseColorString(parts[1], data->inventorylist_options.slotbg_h, false); - if (parseColorString(parts[3], tmp_color, false)) - m_default_tooltip_bgcolor = tmp_color; - if (parseColorString(parts[4], tmp_color, false)) - m_default_tooltip_color = tmp_color; + if (parts.size() >= 3) { + if (parseColorString(parts[2], data->inventorylist_options.slotbordercolor, + false)) { + data->inventorylist_options.slotborder = true; } + } + if (parts.size() >= 5) { + video::SColor tmp_color; - // update all already parsed inventorylists - for (GUIInventoryList *e : m_inventorylists) { - e->setSlotBGColors(data->inventorylist_options.slotbg_n, - data->inventorylist_options.slotbg_h); - e->setSlotBorders(data->inventorylist_options.slotborder, - data->inventorylist_options.slotbordercolor); - } - return; + if (parseColorString(parts[3], tmp_color, false)) + m_default_tooltip_bgcolor = tmp_color; + if (parseColorString(parts[4], tmp_color, false)) + m_default_tooltip_color = tmp_color; + } + + // update all already parsed inventorylists + for (GUIInventoryList *e : m_inventorylists) { + e->setSlotBGColors(data->inventorylist_options.slotbg_n, + data->inventorylist_options.slotbg_h); + e->setSlotBorders(data->inventorylist_options.slotborder, + data->inventorylist_options.slotbordercolor); } - errorstream<< "Invalid listcolors element(" << parts.size() << "): '" << element << "'" << std::endl; } void GUIFormSpecMenu::parseTooltip(parserData* data, const std::string &element) { - std::vector<std::string> parts = split(element,';'); - if (parts.size() < 2) { - errorstream << "Invalid tooltip element(" << parts.size() << "): '" - << element << "'" << std::endl; + std::vector<std::string> parts; + if (!precheckElement("tooltip", element, 2, 5, parts)) return; - } // Get mode and check size bool rect_mode = parts[0].find(',') != std::string::npos; @@ -2529,11 +2466,16 @@ bool GUIFormSpecMenu::parsePositionDirect(parserData *data, const std::string &e void GUIFormSpecMenu::parsePosition(parserData *data, const std::string &element) { - std::vector<std::string> parts = split(element, ','); + std::vector<std::string> parts = split(element, ';'); - if (parts.size() == 2) { - data->offset.X = stof(parts[0]); - data->offset.Y = stof(parts[1]); + if (parts.size() == 1 || + (parts.size() > 1 && m_formspec_version > FORMSPEC_API_VERSION)) { + std::vector<std::string> v_geom = split(parts[0], ','); + + MY_CHECKGEOM("position", 0); + + data->offset.X = stof(v_geom[0]); + data->offset.Y = stof(v_geom[1]); return; } @@ -2563,11 +2505,16 @@ bool GUIFormSpecMenu::parseAnchorDirect(parserData *data, const std::string &ele void GUIFormSpecMenu::parseAnchor(parserData *data, const std::string &element) { - std::vector<std::string> parts = split(element, ','); + std::vector<std::string> parts = split(element, ';'); - if (parts.size() == 2) { - data->anchor.X = stof(parts[0]); - data->anchor.Y = stof(parts[1]); + if (parts.size() == 1 || + (parts.size() > 1 && m_formspec_version > FORMSPEC_API_VERSION)) { + std::vector<std::string> v_geom = split(parts[0], ','); + + MY_CHECKGEOM("anchor", 0); + + data->anchor.X = stof(v_geom[0]); + data->anchor.Y = stof(v_geom[1]); return; } @@ -2575,6 +2522,46 @@ void GUIFormSpecMenu::parseAnchor(parserData *data, const std::string &element) << "'" << std::endl; } +bool GUIFormSpecMenu::parsePaddingDirect(parserData *data, const std::string &element) +{ + if (element.empty()) + return false; + + std::vector<std::string> parts = split(element, '['); + + if (parts.size() != 2) + return false; + + std::string type = trim(parts[0]); + std::string description = trim(parts[1]); + + if (type != "padding") + return false; + + parsePadding(data, description); + + return true; +} + +void GUIFormSpecMenu::parsePadding(parserData *data, const std::string &element) +{ + std::vector<std::string> parts = split(element, ';'); + + if (parts.size() == 1 || + (parts.size() > 1 && m_formspec_version > FORMSPEC_API_VERSION)) { + std::vector<std::string> v_geom = split(parts[0], ','); + + MY_CHECKGEOM("padding", 0); + + data->padding.X = stof(v_geom[0]); + data->padding.Y = stof(v_geom[1]); + return; + } + + errorstream << "Invalid padding element (" << parts.size() << "): '" << element + << "'" << std::endl; +} + bool GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element, bool style_type) { std::vector<std::string> parts = split(element, ';'); @@ -2713,35 +2700,25 @@ bool GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element, b void GUIFormSpecMenu::parseSetFocus(const std::string &element) { - std::vector<std::string> parts = split(element, ';'); - - if (parts.size() <= 2 || - (parts.size() > 2 && m_formspec_version > FORMSPEC_API_VERSION)) - { - if (m_is_form_regenerated) - return; // Never focus on resizing - - bool force_focus = parts.size() >= 2 && is_yes(parts[1]); - if (force_focus || m_text_dst->m_formname != m_last_formname) - setFocus(parts[0]); - + std::vector<std::string> parts; + if (!precheckElement("set_focus", element, 1, 2, parts)) return; - } - errorstream << "Invalid set_focus element (" << parts.size() << "): '" << element - << "'" << std::endl; + if (m_is_form_regenerated) + return; // Never focus on resizing + + bool force_focus = parts.size() >= 2 && is_yes(parts[1]); + if (force_focus || m_text_dst->m_formname != m_last_formname) + setFocus(parts[0]); } void GUIFormSpecMenu::parseModel(parserData *data, const std::string &element) { - std::vector<std::string> parts = split(element, ';'); + MY_CHECKCLIENT("model"); - if (parts.size() < 5 || (parts.size() > 10 && - m_formspec_version <= FORMSPEC_API_VERSION)) { - errorstream << "Invalid model element (" << parts.size() << "): '" << element - << "'" << std::endl; + std::vector<std::string> parts; + if (!precheckElement("model", element, 5, 10, parts)) return; - } // Avoid length checks by resizing if (parts.size() < 10) @@ -3078,8 +3055,6 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) checkbox_it.second->drop(); for (auto &scrollbar_it : m_scrollbars) scrollbar_it.second->drop(); - for (auto &background_it : m_backgrounds) - background_it->drop(); for (auto &tooltip_rect_it : m_tooltip_rects) tooltip_rect_it.first->drop(); for (auto &clickthrough_it : m_clickthrough_elements) @@ -3091,6 +3066,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) mydata.screensize = screensize; mydata.offset = v2f32(0.5f, 0.5f); mydata.anchor = v2f32(0.5f, 0.5f); + mydata.padding = v2f32(0.05f, 0.05f); mydata.simple_field_count = 0; // Base position of contents of form @@ -3100,7 +3076,6 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) mydata.current_parent = this; m_inventorylists.clear(); - m_backgrounds.clear(); m_tables.clear(); m_checkboxes.clear(); m_scrollbars.clear(); @@ -3193,7 +3168,14 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) } } - /* "no_prepend" element is always after "position" (or "size" element) if it used */ + /* "padding" element is always after "anchor" and previous if it is used */ + for (; i < elements.size(); i++) { + if (!parsePaddingDirect(&mydata, elements[i])) { + break; + } + } + + /* "no_prepend" element is always after "padding" and previous if it used */ bool enable_prepends = true; for (; i < elements.size(); i++) { if (elements[i].empty()) @@ -3258,11 +3240,9 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) double fitx_imgsize; double fity_imgsize; - // Pad the screensize with 5% of the screensize on all sides to ensure - // that even the largest formspecs don't touch the screen borders. v2f padded_screensize( - mydata.screensize.X * 0.9f, - mydata.screensize.Y * 0.9f + mydata.screensize.X * (1.0f - mydata.padding.X * 2.0f), + mydata.screensize.Y * (1.0f - mydata.padding.Y * 2.0f) ); if (mydata.real_coordinates) { @@ -3278,13 +3258,15 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) ((15.0 / 13.0) * (0.85 + mydata.invsize.Y)); } -#ifdef __ANDROID__ + s32 min_screen_dim = std::min(padded_screensize.X, padded_screensize.Y); + +#ifdef HAVE_TOUCHSCREENGUI // In Android, the preferred imgsize should be larger to accommodate the // smaller screensize. - double prefer_imgsize = padded_screensize.Y / 10 * gui_scaling; + double prefer_imgsize = min_screen_dim / 10 * gui_scaling; #else // Desktop computers have more space, so try to fit 15 coordinates. - double prefer_imgsize = padded_screensize.Y / 15 * gui_scaling; + double prefer_imgsize = min_screen_dim / 15 * gui_scaling; #endif // Try to use the preferred imgsize, but if that's bigger than the maximum // size, use the maximum size. @@ -3347,6 +3329,15 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) gui::IGUIFont *old_font = skin->getFont(); skin->setFont(m_font); + // Add a new element that will hold all the background elements as its children. + // Because it is the first added element, all backgrounds will be behind all + // the other elements. + // (We use an arbitrarily big rect. The actual size is determined later by + // clipping to `this`.) + core::rect<s32> background_parent_rect(0, 0, 100000, 100000); + mydata.background_parent.reset(new gui::IGUIElement(EGUIET_ELEMENT, Environment, + this, -1, background_parent_rect)); + pos_offset = v2f32(); // used for formspec versions < 3 @@ -3602,15 +3593,6 @@ void GUIFormSpecMenu::drawMenu() } } - /* - Draw backgrounds - */ - for (gui::IGUIElement *e : m_backgrounds) { - e->setVisible(true); - e->draw(); - e->setVisible(false); - } - // Some elements are only visible while being drawn for (gui::IGUIElement *e : m_clickthrough_elements) e->setVisible(true); @@ -3741,7 +3723,7 @@ void GUIFormSpecMenu::showTooltip(const std::wstring &text, v2u32 screenSize = Environment->getVideoDriver()->getScreenSize(); int tooltip_offset_x = m_btn_height; int tooltip_offset_y = m_btn_height; -#ifdef __ANDROID__ +#ifdef HAVE_TOUCHSCREENGUI tooltip_offset_x *= 3; tooltip_offset_y = 0; if (m_pointer.X > (s32)screenSize.X / 2) diff --git a/src/gui/guiFormSpecMenu.h b/src/gui/guiFormSpecMenu.h index 926de66d5..3fedb3b78 100644 --- a/src/gui/guiFormSpecMenu.h +++ b/src/gui/guiFormSpecMenu.h @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <unordered_set> #include "irrlichttypes_extrabloated.h" +#include "irr_ptr.h" #include "inventorymanager.h" #include "modalMenu.h" #include "guiInventoryList.h" @@ -229,7 +230,7 @@ public: return m_selected_item; } - const u16 getSelectedAmount() const + u16 getSelectedAmount() const { return m_selected_amount; } @@ -279,6 +280,8 @@ protected: v2s32 getElementBasePos(const std::vector<std::string> *v_pos); v2s32 getRealCoordinateBasePos(const std::vector<std::string> &v_pos); v2s32 getRealCoordinateGeometry(const std::vector<std::string> &v_geom); + bool precheckElement(const std::string &name, const std::string &element, + size_t args_min, size_t args_max, std::vector<std::string> &parts); std::unordered_map<std::string, std::vector<StyleSpec>> theme_by_type; std::unordered_map<std::string, std::vector<StyleSpec>> theme_by_name; @@ -311,7 +314,6 @@ protected: std::vector<GUIInventoryList *> m_inventorylists; std::vector<ListRingSpec> m_inventory_rings; - std::vector<gui::IGUIElement *> m_backgrounds; std::unordered_map<std::string, bool> field_close_on_enter; std::unordered_map<std::string, bool> m_dropdown_index_event; std::vector<FieldSpec> m_fields; @@ -366,12 +368,14 @@ private: v2s32 size; v2f32 offset; v2f32 anchor; + v2f32 padding; core::rect<s32> rect; v2s32 basepos; v2u32 screensize; GUITable::TableOptions table_options; GUITable::TableColumns table_columns; gui::IGUIElement *current_parent = nullptr; + irr_ptr<gui::IGUIElement> background_parent; GUIInventoryList::Options inventorylist_options; @@ -447,6 +451,8 @@ private: void parsePosition(parserData *data, const std::string &element); bool parseAnchorDirect(parserData *data, const std::string &element); void parseAnchor(parserData *data, const std::string &element); + bool parsePaddingDirect(parserData *data, const std::string &element); + void parsePadding(parserData *data, const std::string &element); bool parseStyle(parserData *data, const std::string &element, bool style_type); void parseSetFocus(const std::string &element); void parseModel(parserData *data, const std::string &element); diff --git a/src/gui/guiHyperText.cpp b/src/gui/guiHyperText.cpp index ccfdcb81d..40450ce5f 100644 --- a/src/gui/guiHyperText.cpp +++ b/src/gui/guiHyperText.cpp @@ -17,31 +17,26 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "IGUIEnvironment.h" -#include "IGUIElement.h" +#include "guiHyperText.h" #include "guiScrollBar.h" -#include "IGUIFont.h" -#include <vector> -#include <list> -#include <unordered_map> -using namespace irr::gui; #include "client/fontengine.h" -#include <SColor.h> #include "client/tile.h" #include "IVideoDriver.h" #include "client/client.h" #include "client/renderingengine.h" #include "hud.h" -#include "guiHyperText.h" #include "util/string.h" +#include "irrlicht_changes/CGUITTFont.h" -bool check_color(const std::string &str) +using namespace irr::gui; + +static bool check_color(const std::string &str) { irr::video::SColor color; return parseColorString(str, color, false); } -bool check_integer(const std::string &str) +static bool check_integer(const std::string &str) { if (str.empty()) return false; @@ -616,12 +611,10 @@ TextDrawer::TextDrawer(const wchar_t *text, Client *client, if (e.font) { e.dim.Width = e.font->getDimension(e.text.c_str()).Width; e.dim.Height = e.font->getDimension(L"Yy").Height; -#if USE_FREETYPE if (e.font->getType() == irr::gui::EGFT_CUSTOM) { - e.baseline = e.dim.Height - 1 - - ((irr::gui::CGUITTFont *)e.font)->getAscender() / 64; + CGUITTFont *tmp = static_cast<CGUITTFont*>(e.font); + e.baseline = e.dim.Height - 1 - tmp->getAscender() / 64; } -#endif } else { e.dim = {0, 0}; } diff --git a/src/gui/guiHyperText.h b/src/gui/guiHyperText.h index 5b936262e..04c664df5 100644 --- a/src/gui/guiHyperText.h +++ b/src/gui/guiHyperText.h @@ -19,16 +19,17 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once -#include "config.h" // for USE_FREETYPE +#include <vector> +#include <list> +#include <unordered_map> +#include <string> +#include "irrlichttypes_extrabloated.h" using namespace irr; class ISimpleTextureSource; class Client; - -#if USE_FREETYPE -#include "irrlicht_changes/CGUITTFont.h" -#endif +class GUIScrollBar; class ParsedText { diff --git a/src/gui/guiPasswordChange.cpp b/src/gui/guiPasswordChange.cpp index 74cd62f5b..c983260f6 100644 --- a/src/gui/guiPasswordChange.cpp +++ b/src/gui/guiPasswordChange.cpp @@ -25,6 +25,10 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include <IGUIStaticText.h> #include <IGUIFont.h> +#ifdef HAVE_TOUCHSCREENGUI + #include "client/renderingengine.h" +#endif + #include "porting.h" #include "gettext.h" @@ -79,8 +83,8 @@ void GUIPasswordChange::regenerateGui(v2u32 screensize) /* Calculate new sizes and positions */ -#ifdef __ANDROID__ - const float s = m_gui_scale * porting::getDisplayDensity() / 2; +#ifdef HAVE_TOUCHSCREENGUI + const float s = m_gui_scale * RenderingEngine::getDisplayDensity() / 2; #else const float s = m_gui_scale; #endif diff --git a/src/gui/guiSkin.cpp b/src/gui/guiSkin.cpp index e09209bd9..ca692f6cb 100644 --- a/src/gui/guiSkin.cpp +++ b/src/gui/guiSkin.cpp @@ -1024,48 +1024,6 @@ void GUISkin::draw2DRectangle(IGUIElement* element, }
-//! Writes attributes of the object.
-//! Implement this to expose the attributes of your scene node animator for
-//! scripting languages, editors, debuggers or xml serialization purposes.
-void GUISkin::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const
-{
- u32 i;
- for (i=0; i<EGDC_COUNT; ++i)
- out->addColor(GUISkinColorNames[i], Colors[i]);
-
- for (i=0; i<EGDS_COUNT; ++i)
- out->addInt(GUISkinSizeNames[i], Sizes[i]);
-
- for (i=0; i<EGDT_COUNT; ++i)
- out->addString(GUISkinTextNames[i], Texts[i].c_str());
-
- for (i=0; i<EGDI_COUNT; ++i)
- out->addInt(GUISkinIconNames[i], Icons[i]);
-}
-
-
-//! Reads attributes of the object.
-//! Implement this to set the attributes of your scene node animator for
-//! scripting languages, editors, debuggers or xml deserialization purposes.
-void GUISkin::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options)
-{
- // TODO: This is not nice code for downward compatibility, whenever new values are added and users
- // load an old skin the corresponding values will be set to 0.
- u32 i;
- for (i=0; i<EGDC_COUNT; ++i)
- Colors[i] = in->getAttributeAsColor(GUISkinColorNames[i]);
-
- for (i=0; i<EGDS_COUNT; ++i)
- Sizes[i] = in->getAttributeAsInt(GUISkinSizeNames[i]);
-
- for (i=0; i<EGDT_COUNT; ++i)
- Texts[i] = in->getAttributeAsStringW(GUISkinTextNames[i]);
-
- for (i=0; i<EGDI_COUNT; ++i)
- Icons[i] = in->getAttributeAsInt(GUISkinIconNames[i]);
-}
-
-
//! gets the colors
// PATCH
void GUISkin::getColors(video::SColor* colors)
diff --git a/src/gui/guiSkin.h b/src/gui/guiSkin.h index bbb900f9f..fa9b27bdd 100644 --- a/src/gui/guiSkin.h +++ b/src/gui/guiSkin.h @@ -290,16 +290,6 @@ namespace gui //! get the type of this skin
virtual EGUI_SKIN_TYPE getType() const;
- //! Writes attributes of the object.
- //! Implement this to expose the attributes of your scene node animator for
- //! scripting languages, editors, debuggers or xml serialization purposes.
- virtual void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0) const;
-
- //! Reads attributes of the object.
- //! Implement this to set the attributes of your scene node animator for
- //! scripting languages, editors, debuggers or xml deserialization purposes.
- virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0);
-
//! gets the colors
virtual void getColors(video::SColor* colors); // ::PATCH:
diff --git a/src/gui/guiTable.cpp b/src/gui/guiTable.cpp index cab2e19fd..79ae1aea3 100644 --- a/src/gui/guiTable.cpp +++ b/src/gui/guiTable.cpp @@ -77,9 +77,10 @@ GUITable::GUITable(gui::IGUIEnvironment *env, setTabStop(true); setTabOrder(-1); updateAbsolutePosition(); +#ifdef HAVE_TOUCHSCREENGUI + float density = 1; // dp scaling is applied by the skin +#else float density = RenderingEngine::getDisplayDensity(); -#ifdef __ANDROID__ - density = 1; // dp scaling is applied by the skin #endif core::rect<s32> relative_rect = m_scrollbar->getRelativePosition(); s32 width = (relative_rect.getWidth() / (2.0 / 3.0)) * density * diff --git a/src/gui/modalMenu.cpp b/src/gui/modalMenu.cpp index 1016de389..56a5d2cb9 100644 --- a/src/gui/modalMenu.cpp +++ b/src/gui/modalMenu.cpp @@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #ifdef HAVE_TOUCHSCREENGUI #include "touchscreengui.h" +#include "client/renderingengine.h" #endif // clang-format off @@ -40,8 +41,8 @@ GUIModalMenu::GUIModalMenu(gui::IGUIEnvironment* env, gui::IGUIElement* parent, m_remap_dbl_click(remap_dbl_click) { m_gui_scale = g_settings->getFloat("gui_scaling"); -#ifdef __ANDROID__ - float d = porting::getDisplayDensity(); +#ifdef HAVE_TOUCHSCREENGUI + float d = RenderingEngine::getDisplayDensity(); m_gui_scale *= 1.1 - 0.3 * d + 0.2 * d * d; #endif setVisible(true); @@ -183,7 +184,7 @@ static bool isChild(gui::IGUIElement *tocheck, gui::IGUIElement *parent) return false; } -#ifdef __ANDROID__ +#ifdef HAVE_TOUCHSCREENGUI bool GUIModalMenu::simulateMouseEvent( gui::IGUIElement *target, ETOUCH_INPUT_EVENT touch_event) @@ -217,6 +218,8 @@ bool GUIModalMenu::simulateMouseEvent( void GUIModalMenu::enter(gui::IGUIElement *hovered) { + if (!hovered) + return; sanity_check(!m_hovered); m_hovered.grab(hovered); SEvent gui_event{}; @@ -286,7 +289,9 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event) return retval; } } +#endif +#ifdef HAVE_TOUCHSCREENGUI if (event.EventType == EET_TOUCH_INPUT_EVENT) { irr_ptr<GUIModalMenu> holder; holder.grab(this); // keep this alive until return (it might be dropped downstream [?]) diff --git a/src/gui/modalMenu.h b/src/gui/modalMenu.h index ed0da3205..06e78f06b 100644 --- a/src/gui/modalMenu.h +++ b/src/gui/modalMenu.h @@ -75,10 +75,10 @@ protected: v2u32 m_screensize_old; float m_gui_scale; #ifdef __ANDROID__ - v2s32 m_down_pos; std::string m_jni_field_name; #endif #ifdef HAVE_TOUCHSCREENGUI + v2s32 m_down_pos; bool m_touchscreen_visible = true; #endif @@ -102,7 +102,7 @@ private: // wants to launch other menus bool m_allow_focus_removal = false; -#ifdef __ANDROID__ +#ifdef HAVE_TOUCHSCREENGUI irr_ptr<gui::IGUIElement> m_hovered; bool simulateMouseEvent(gui::IGUIElement *target, ETOUCH_INPUT_EVENT touch_event); diff --git a/src/gui/profilergraph.cpp b/src/gui/profilergraph.cpp index b29285e2f..f71ef3799 100644 --- a/src/gui/profilergraph.cpp +++ b/src/gui/profilergraph.cpp @@ -94,20 +94,29 @@ void ProfilerGraph::draw(s32 x_left, s32 y_bottom, video::IVideoDriver *driver, show_min = 0; } - s32 texth = 15; + const s32 texth = 15; char buf[10]; - porting::mt_snprintf(buf, sizeof(buf), "%.3g", show_max); + if (floorf(show_max) == show_max) + porting::mt_snprintf(buf, sizeof(buf), "%.5g", show_max); + else + porting::mt_snprintf(buf, sizeof(buf), "%.3g", show_max); font->draw(utf8_to_wide(buf).c_str(), core::rect<s32>(textx, y - graphh, textx2, y - graphh + texth), meta.color); - porting::mt_snprintf(buf, sizeof(buf), "%.3g", show_min); + + if (floorf(show_min) == show_min) + porting::mt_snprintf(buf, sizeof(buf), "%.5g", show_min); + else + porting::mt_snprintf(buf, sizeof(buf), "%.3g", show_min); font->draw(utf8_to_wide(buf).c_str(), core::rect<s32>(textx, y - texth, textx2, y), meta.color); + font->draw(utf8_to_wide(id).c_str(), core::rect<s32>(textx, y - graphh / 2 - texth / 2, textx2, y - graphh / 2 + texth / 2), meta.color); + s32 graph1y = y; s32 graph1h = graphh; bool relativegraph = (show_min != 0 && show_min != show_max); diff --git a/src/gui/touchscreengui.cpp b/src/gui/touchscreengui.cpp index eb20b7e70..ebe1a6325 100644 --- a/src/gui/touchscreengui.cpp +++ b/src/gui/touchscreengui.cpp @@ -28,6 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/numeric.h" #include "porting.h" #include "client/guiscalingfilter.h" +#include "client/renderingengine.h" #include <iostream> #include <algorithm> @@ -426,7 +427,7 @@ TouchScreenGUI::TouchScreenGUI(IrrlichtDevice *device, IEventReceiver *receiver) m_joystick_triggers_aux1 = g_settings->getBool("virtual_joystick_triggers_aux1"); m_screensize = m_device->getVideoDriver()->getScreenSize(); button_size = MYMIN(m_screensize.Y / 4.5f, - porting::getDisplayDensity() * + RenderingEngine::getDisplayDensity() * g_settings->getFloat("hud_scaling") * 65.0f); } @@ -668,9 +669,9 @@ void TouchScreenGUI::handleReleaseEvent(size_t evt_id) if (button != after_last_element_id) { // handle button events handleButtonEvent(button, evt_id, false); - } else if (evt_id == m_move_id) { + } else if (m_has_move_id && evt_id == m_move_id) { // handle the point used for moving view - m_move_id = -1; + m_has_move_id = false; // if this pointer issued a mouse event issue symmetric release here if (m_move_sent_as_mouse_event) { @@ -692,8 +693,8 @@ void TouchScreenGUI::handleReleaseEvent(size_t evt_id) } // handle joystick - else if (evt_id == m_joystick_id) { - m_joystick_id = -1; + else if (m_has_joystick_id && evt_id == m_joystick_id) { + m_has_joystick_id = false; // reset joystick for (unsigned int i = 0; i < 4; i++) @@ -776,7 +777,8 @@ void TouchScreenGUI::translateEvent(const SEvent &event) if ((m_fixed_joystick && dxj * dxj + dyj * dyj <= button_size * button_size * 1.5 * 1.5) || (!m_fixed_joystick && event.TouchInput.X < m_screensize.X / 3.0f)) { // If we don't already have a starting point for joystick make this the one. - if (m_joystick_id == -1) { + if (!m_has_joystick_id) { + m_has_joystick_id = true; m_joystick_id = event.TouchInput.ID; m_joystick_has_really_moved = false; @@ -796,7 +798,8 @@ void TouchScreenGUI::translateEvent(const SEvent &event) } } else { // If we don't already have a moving point make this the moving one. - if (m_move_id == -1) { + if (!m_has_move_id) { + m_has_move_id = true; m_move_id = event.TouchInput.ID; m_move_has_really_moved = false; m_move_downtime = porting::getTimeMs(); @@ -819,7 +822,7 @@ void TouchScreenGUI::translateEvent(const SEvent &event) v2s32(event.TouchInput.X, event.TouchInput.Y)) return; - if (m_move_id != -1) { + if (m_has_move_id) { if ((event.TouchInput.ID == m_move_id) && (!m_move_sent_as_mouse_event)) { @@ -862,7 +865,7 @@ void TouchScreenGUI::translateEvent(const SEvent &event) } } - if (m_joystick_id != -1 && event.TouchInput.ID == m_joystick_id) { + if (m_has_joystick_id && event.TouchInput.ID == m_joystick_id) { s32 X = event.TouchInput.X; s32 Y = event.TouchInput.Y; @@ -941,7 +944,7 @@ void TouchScreenGUI::translateEvent(const SEvent &event) } } - if (m_move_id == -1 && m_joystick_id == -1) + if (!m_has_move_id && !m_has_joystick_id) handleChangedButton(event); } } @@ -1086,7 +1089,7 @@ void TouchScreenGUI::step(float dtime) button.repeatcounter += dtime; // in case we're moving around digging does not happen - if (m_move_id != -1) + if (m_has_move_id) m_move_has_really_moved = true; if (button.repeatcounter < button.repeatdelay) @@ -1114,7 +1117,7 @@ void TouchScreenGUI::step(float dtime) } // if a new placed pointer isn't moved for some time start digging - if ((m_move_id != -1) && + if (m_has_move_id && (!m_move_has_really_moved) && (!m_move_sent_as_mouse_event)) { diff --git a/src/gui/touchscreengui.h b/src/gui/touchscreengui.h index ad5abae87..6b36c0d59 100644 --- a/src/gui/touchscreengui.h +++ b/src/gui/touchscreengui.h @@ -228,13 +228,15 @@ private: */ line3d<f32> m_shootline; - int m_move_id = -1; + bool m_has_move_id = false; + size_t m_move_id; bool m_move_has_really_moved = false; u64 m_move_downtime = 0; bool m_move_sent_as_mouse_event = false; v2s32 m_move_downlocation = v2s32(-10000, -10000); - int m_joystick_id = -1; + bool m_has_joystick_id = false; + size_t m_joystick_id; bool m_joystick_has_really_moved = false; bool m_fixed_joystick = false; bool m_joystick_triggers_aux1 = false; diff --git a/src/httpfetch.cpp b/src/httpfetch.cpp index 1307bfec3..16f0791c9 100644 --- a/src/httpfetch.cpp +++ b/src/httpfetch.cpp @@ -38,7 +38,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "noise.h" static std::mutex g_httpfetch_mutex; -static std::unordered_map<unsigned long, std::queue<HTTPFetchResult>> +static std::unordered_map<u64, std::queue<HTTPFetchResult>> g_httpfetch_results; static PcgRandom g_callerid_randomness; @@ -52,22 +52,21 @@ HTTPFetchRequest::HTTPFetchRequest() : static void httpfetch_deliver_result(const HTTPFetchResult &fetch_result) { - unsigned long caller = fetch_result.caller; + u64 caller = fetch_result.caller; if (caller != HTTPFETCH_DISCARD) { MutexAutoLock lock(g_httpfetch_mutex); g_httpfetch_results[caller].emplace(fetch_result); } } -static void httpfetch_request_clear(unsigned long caller); +static void httpfetch_request_clear(u64 caller); -unsigned long httpfetch_caller_alloc() +u64 httpfetch_caller_alloc() { MutexAutoLock lock(g_httpfetch_mutex); - // Check each caller ID except HTTPFETCH_DISCARD - const unsigned long discard = HTTPFETCH_DISCARD; - for (unsigned long caller = discard + 1; caller != discard; ++caller) { + // Check each caller ID except reserved ones + for (u64 caller = HTTPFETCH_CID_START; caller != 0; ++caller) { auto it = g_httpfetch_results.find(caller); if (it == g_httpfetch_results.end()) { verbosestream << "httpfetch_caller_alloc: allocating " @@ -79,18 +78,17 @@ unsigned long httpfetch_caller_alloc() } FATAL_ERROR("httpfetch_caller_alloc: ran out of caller IDs"); - return discard; } -unsigned long httpfetch_caller_alloc_secure() +u64 httpfetch_caller_alloc_secure() { MutexAutoLock lock(g_httpfetch_mutex); // Generate random caller IDs and make sure they're not - // already used or equal to HTTPFETCH_DISCARD + // already used or reserved. // Give up after 100 tries to prevent infinite loop - u8 tries = 100; - unsigned long caller; + size_t tries = 100; + u64 caller; do { caller = (((u64) g_callerid_randomness.next()) << 32) | @@ -100,7 +98,8 @@ unsigned long httpfetch_caller_alloc_secure() FATAL_ERROR("httpfetch_caller_alloc_secure: ran out of caller IDs"); return HTTPFETCH_DISCARD; } - } while (g_httpfetch_results.find(caller) != g_httpfetch_results.end()); + } while (caller >= HTTPFETCH_CID_START && + g_httpfetch_results.find(caller) != g_httpfetch_results.end()); verbosestream << "httpfetch_caller_alloc_secure: allocating " << caller << std::endl; @@ -110,7 +109,7 @@ unsigned long httpfetch_caller_alloc_secure() return caller; } -void httpfetch_caller_free(unsigned long caller) +void httpfetch_caller_free(u64 caller) { verbosestream<<"httpfetch_caller_free: freeing " <<caller<<std::endl; @@ -122,7 +121,7 @@ void httpfetch_caller_free(unsigned long caller) } } -bool httpfetch_async_get(unsigned long caller, HTTPFetchResult &fetch_result) +bool httpfetch_async_get(u64 caller, HTTPFetchResult &fetch_result) { MutexAutoLock lock(g_httpfetch_mutex); @@ -242,7 +241,6 @@ HTTPFetchOngoing::HTTPFetchOngoing(const HTTPFetchRequest &request_, // Set static cURL options curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); - curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 3); curl_easy_setopt(curl, CURLOPT_ENCODING, "gzip"); @@ -393,10 +391,17 @@ const HTTPFetchResult * HTTPFetchOngoing::complete(CURLcode res) } if (res != CURLE_OK) { - errorstream << request.url << " not found (" - << curl_easy_strerror(res) << ")" - << " (response code " << result.response_code << ")" + errorstream << "HTTPFetch for " << request.url << " failed (" + << curl_easy_strerror(res) << ")" << std::endl; + } else if (result.response_code >= 400) { + errorstream << "HTTPFetch for " << request.url + << " returned response code " << result.response_code << std::endl; + if (result.caller == HTTPFETCH_PRINT_ERR && !result.data.empty()) { + errorstream << "Response body:" << std::endl; + safe_print_string(errorstream, result.data); + errorstream << std::endl; + } } return &result; @@ -474,7 +479,7 @@ public: m_requests.push_back(req); } - void requestClear(unsigned long caller, Event *event) + void requestClear(u64 caller, Event *event) { Request req; req.type = RT_CLEAR; @@ -505,7 +510,7 @@ protected: } else if (req.type == RT_CLEAR) { - unsigned long caller = req.fetch_request.caller; + u64 caller = req.fetch_request.caller; // Abort all ongoing fetches for the caller for (std::vector<HTTPFetchOngoing*>::iterator @@ -778,7 +783,7 @@ void httpfetch_async(const HTTPFetchRequest &fetch_request) g_httpfetch_thread->start(); } -static void httpfetch_request_clear(unsigned long caller) +static void httpfetch_request_clear(u64 caller) { if (g_httpfetch_thread->isRunning()) { Event event; @@ -827,7 +832,7 @@ void httpfetch_async(const HTTPFetchRequest &fetch_request) httpfetch_deliver_result(fetch_result); } -static void httpfetch_request_clear(unsigned long caller) +static void httpfetch_request_clear(u64 caller) { } diff --git a/src/httpfetch.h b/src/httpfetch.h index 3b9f17f0a..a4901e63b 100644 --- a/src/httpfetch.h +++ b/src/httpfetch.h @@ -23,10 +23,17 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/string.h" #include "config.h" -// Can be used in place of "caller" in asynchronous transfers to discard result -// (used as default value of "caller") +// These can be used in place of "caller" in to specify special handling. +// Discard result (used as default value of "caller"). #define HTTPFETCH_DISCARD 0 +// Indicates that the result should not be discarded when performing a +// synchronous request (since a real caller ID is not needed for synchronous +// requests because the result does not have to be retrieved later). #define HTTPFETCH_SYNC 1 +// Print response body to console if the server returns an error code. +#define HTTPFETCH_PRINT_ERR 2 +// Start of regular allocated caller IDs. +#define HTTPFETCH_CID_START 3 // Methods enum HttpMethod : u8 @@ -43,11 +50,11 @@ struct HTTPFetchRequest // Identifies the caller (for asynchronous requests) // Ignored by httpfetch_sync - unsigned long caller = HTTPFETCH_DISCARD; + u64 caller = HTTPFETCH_DISCARD; // Some number that identifies the request // (when the same caller issues multiple httpfetch_async calls) - unsigned long request_id = 0; + u64 request_id = 0; // Timeout for the whole transfer, in milliseconds long timeout; @@ -85,8 +92,8 @@ struct HTTPFetchResult long response_code = 0; std::string data = ""; // The caller and request_id from the corresponding HTTPFetchRequest. - unsigned long caller = HTTPFETCH_DISCARD; - unsigned long request_id = 0; + u64 caller = HTTPFETCH_DISCARD; + u64 request_id = 0; HTTPFetchResult() = default; @@ -107,19 +114,19 @@ void httpfetch_async(const HTTPFetchRequest &fetch_request); // If any fetch for the given caller ID is complete, removes it from the // result queue, sets the fetch result and returns true. Otherwise returns false. -bool httpfetch_async_get(unsigned long caller, HTTPFetchResult &fetch_result); +bool httpfetch_async_get(u64 caller, HTTPFetchResult &fetch_result); // Allocates a caller ID for httpfetch_async // Not required if you want to set caller = HTTPFETCH_DISCARD -unsigned long httpfetch_caller_alloc(); +u64 httpfetch_caller_alloc(); // Allocates a non-predictable caller ID for httpfetch_async -unsigned long httpfetch_caller_alloc_secure(); +u64 httpfetch_caller_alloc_secure(); // Frees a caller ID allocated with httpfetch_caller_alloc // Note: This can be expensive, because the httpfetch thread is told // to stop any ongoing fetches for the given caller. -void httpfetch_caller_free(unsigned long caller); +void httpfetch_caller_free(u64 caller); // Performs a synchronous HTTP request. This blocks and therefore should // only be used from background threads. diff --git a/src/hud.cpp b/src/hud.cpp index e4ad7940f..841c90758 100644 --- a/src/hud.cpp +++ b/src/hud.cpp @@ -63,5 +63,6 @@ const struct EnumString es_HudBuiltinElement[] = {HUD_FLAG_BREATHBAR_VISIBLE, "breathbar"}, {HUD_FLAG_MINIMAP_VISIBLE, "minimap"}, {HUD_FLAG_MINIMAP_RADAR_VISIBLE, "minimap_radar"}, + {HUD_FLAG_BASIC_DEBUG, "basic_debug"}, {0, NULL}, }; @@ -47,6 +47,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #define HUD_FLAG_BREATHBAR_VISIBLE (1 << 4) #define HUD_FLAG_MINIMAP_VISIBLE (1 << 5) #define HUD_FLAG_MINIMAP_RADAR_VISIBLE (1 << 6) +#define HUD_FLAG_BASIC_DEBUG (1 << 7) #define HUD_PARAM_HOTBAR_ITEMCOUNT 1 #define HUD_PARAM_HOTBAR_IMAGE 2 diff --git a/src/inventory.cpp b/src/inventory.cpp index da6517e62..6d2b7fba3 100644 --- a/src/inventory.cpp +++ b/src/inventory.cpp @@ -503,11 +503,6 @@ void InventoryList::deSerialize(std::istream &is) throw SerializationError(ss.str()); } -InventoryList::InventoryList(const InventoryList &other) -{ - *this = other; -} - InventoryList & InventoryList::operator = (const InventoryList &other) { m_items = other.m_items; @@ -535,21 +530,6 @@ bool InventoryList::operator == (const InventoryList &other) const return true; } -const std::string &InventoryList::getName() const -{ - return m_name; -} - -u32 InventoryList::getSize() const -{ - return m_items.size(); -} - -u32 InventoryList::getWidth() const -{ - return m_width; -} - u32 InventoryList::getUsedSlots() const { u32 num = 0; @@ -560,23 +540,6 @@ u32 InventoryList::getUsedSlots() const return num; } -u32 InventoryList::getFreeSlots() const -{ - return getSize() - getUsedSlots(); -} - -const ItemStack& InventoryList::getItem(u32 i) const -{ - assert(i < m_size); // Pre-condition - return m_items[i]; -} - -ItemStack& InventoryList::getItem(u32 i) -{ - assert(i < m_size); // Pre-condition - return m_items[i]; -} - ItemStack InventoryList::changeItem(u32 i, const ItemStack &newitem) { if(i >= m_items.size()) @@ -965,16 +928,6 @@ InventoryList * Inventory::getList(const std::string &name) return m_lists[i]; } -std::vector<const InventoryList*> Inventory::getLists() -{ - std::vector<const InventoryList*> lists; - lists.reserve(m_lists.size()); - for (auto list : m_lists) { - lists.push_back(list); - } - return lists; -} - bool Inventory::deleteList(const std::string &name) { s32 i = getListIndex(name); @@ -995,7 +948,7 @@ const InventoryList *Inventory::getList(const std::string &name) const return m_lists[i]; } -const s32 Inventory::getListIndex(const std::string &name) const +s32 Inventory::getListIndex(const std::string &name) const { for(u32 i=0; i<m_lists.size(); i++) { diff --git a/src/inventory.h b/src/inventory.h index 6c84f5fd1..8b31de3a8 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -198,7 +198,7 @@ public: void serialize(std::ostream &os, bool incremental) const; void deSerialize(std::istream &is); - InventoryList(const InventoryList &other); + InventoryList(const InventoryList &other) { *this = other; } InventoryList & operator = (const InventoryList &other); bool operator == (const InventoryList &other) const; bool operator != (const InventoryList &other) const @@ -206,16 +206,25 @@ public: return !(*this == other); } - const std::string &getName() const; - u32 getSize() const; - u32 getWidth() const; + const std::string &getName() const { return m_name; } + u32 getSize() const { return static_cast<u32>(m_items.size()); } + u32 getWidth() const { return m_width; } // Count used slots u32 getUsedSlots() const; - u32 getFreeSlots() const; // Get reference to item - const ItemStack& getItem(u32 i) const; - ItemStack& getItem(u32 i); + const ItemStack &getItem(u32 i) const + { + assert(i < m_size); // Pre-condition + return m_items[i]; + } + ItemStack &getItem(u32 i) + { + assert(i < m_size); // Pre-condition + return m_items[i]; + } + // Get reference to all items + const std::vector<ItemStack> &getItems() const { return m_items; } // Returns old item. Parameter can be an empty item. ItemStack changeItem(u32 i, const ItemStack &newitem); // Delete item @@ -272,7 +281,7 @@ public: private: std::vector<ItemStack> m_items; std::string m_name; - u32 m_size; + u32 m_size; // always the same as m_items.size() u32 m_width = 0; IItemDefManager *m_itemdef; bool m_dirty = true; @@ -302,7 +311,7 @@ public: InventoryList * addList(const std::string &name, u32 size); InventoryList * getList(const std::string &name); const InventoryList * getList(const std::string &name) const; - std::vector<const InventoryList*> getLists(); + const std::vector<InventoryList *> &getLists() const { return m_lists; } bool deleteList(const std::string &name); // A shorthand for adding items. Returns leftover item (possibly empty). ItemStack addItem(const std::string &listname, const ItemStack &newitem) @@ -336,7 +345,7 @@ public: } private: // -1 if not found - const s32 getListIndex(const std::string &name) const; + s32 getListIndex(const std::string &name) const; std::vector<InventoryList*> m_lists; IItemDefManager *m_itemdef; diff --git a/src/inventorymanager.cpp b/src/inventorymanager.cpp index a159bf786..ecdb56a97 100644 --- a/src/inventorymanager.cpp +++ b/src/inventorymanager.cpp @@ -172,7 +172,7 @@ void IMoveAction::onPutAndOnTake(const ItemStack &src_item, ServerActiveObject * sa->player_inventory_OnPut(*this, src_item, player); else assert(false); - + if (from_inv.type == InventoryLocation::DETACHED) sa->detached_inventory_OnTake(*this, src_item, player); else if (from_inv.type == InventoryLocation::NODEMETA) diff --git a/src/irrlicht_changes/CGUITTFont.cpp b/src/irrlicht_changes/CGUITTFont.cpp index e785ea837..787f4cd5a 100644 --- a/src/irrlicht_changes/CGUITTFont.cpp +++ b/src/irrlicht_changes/CGUITTFont.cpp @@ -478,7 +478,7 @@ CGUITTGlyphPage* CGUITTFont::getLastGlyphPage() const CGUITTGlyphPage* CGUITTFont::createGlyphPage(const u8& pixel_mode) { CGUITTGlyphPage* page = 0; - + // Name of our page. io::path name("TTFontGlyphPage_"); name += tt_face->family_name; diff --git a/src/irrlicht_changes/CMakeLists.txt b/src/irrlicht_changes/CMakeLists.txt index 87c88f7e8..19f431af3 100644 --- a/src/irrlicht_changes/CMakeLists.txt +++ b/src/irrlicht_changes/CMakeLists.txt @@ -1,14 +1,9 @@ if (BUILD_CLIENT) set(client_irrlicht_changes_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/static_text.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/CGUITTFont.cpp ) - if (USE_FREETYPE) - set(client_irrlicht_changes_SRCS ${client_irrlicht_changes_SRCS} - ${CMAKE_CURRENT_SOURCE_DIR}/CGUITTFont.cpp - ) - endif() - # CMake require us to set a local scope and then parent scope # Else the last set win in parent scope set(client_irrlicht_changes_SRCS ${client_irrlicht_changes_SRCS} PARENT_SCOPE) diff --git a/src/irrlicht_changes/static_text.cpp b/src/irrlicht_changes/static_text.cpp index 8908a91f7..baf0ea626 100644 --- a/src/irrlicht_changes/static_text.cpp +++ b/src/irrlicht_changes/static_text.cpp @@ -12,17 +12,12 @@ #include <rect.h> #include <SColor.h> -#if USE_FREETYPE - #include "CGUITTFont.h" -#endif - +#include "CGUITTFont.h" #include "util/string.h" namespace irr { -#if USE_FREETYPE - namespace gui { //! constructor @@ -108,14 +103,12 @@ void StaticText::draw() font->getDimension(str.c_str()).Width; } -#if USE_FREETYPE if (font->getType() == irr::gui::EGFT_CUSTOM) { - irr::gui::CGUITTFont *tmp = static_cast<irr::gui::CGUITTFont*>(font); + CGUITTFont *tmp = static_cast<CGUITTFont*>(font); tmp->draw(str, r, HAlign == EGUIA_CENTER, VAlign == EGUIA_CENTER, (RestrainTextInside ? &AbsoluteClippingRect : NULL)); } else -#endif { // Draw non-colored text font->draw(str.c_str(), @@ -588,54 +581,8 @@ s32 StaticText::getTextWidth() const } -//! Writes attributes of the element. -//! Implement this to expose the attributes of your element for -//! scripting languages, editors, debuggers or xml serialization purposes. -void StaticText::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0) const -{ - IGUIStaticText::serializeAttributes(out,options); - - out->addBool ("Border", Border); - out->addBool ("OverrideColorEnabled",true); - out->addBool ("OverrideBGColorEnabled",ColoredText.hasBackground()); - out->addBool ("WordWrap", WordWrap); - out->addBool ("Background", Background); - out->addBool ("RightToLeft", RightToLeft); - out->addBool ("RestrainTextInside", RestrainTextInside); - out->addColor ("OverrideColor", ColoredText.getDefaultColor()); - out->addColor ("BGColor", ColoredText.getBackground()); - out->addEnum ("HTextAlign", HAlign, GUIAlignmentNames); - out->addEnum ("VTextAlign", VAlign, GUIAlignmentNames); - - // out->addFont ("OverrideFont", OverrideFont); -} - - -//! Reads attributes of the element -void StaticText::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0) -{ - IGUIStaticText::deserializeAttributes(in,options); - - Border = in->getAttributeAsBool("Border"); - setWordWrap(in->getAttributeAsBool("WordWrap")); - Background = in->getAttributeAsBool("Background"); - RightToLeft = in->getAttributeAsBool("RightToLeft"); - RestrainTextInside = in->getAttributeAsBool("RestrainTextInside"); - if (in->getAttributeAsBool("OverrideColorEnabled")) - ColoredText.setDefaultColor(in->getAttributeAsColor("OverrideColor")); - if (in->getAttributeAsBool("OverrideBGColorEnabled")) - ColoredText.setBackground(in->getAttributeAsColor("BGColor")); - - setTextAlignment( (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("HTextAlign", GUIAlignmentNames), - (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("VTextAlign", GUIAlignmentNames)); - - // OverrideFont = in->getAttributeAsFont("OverrideFont"); -} - } // end namespace gui -#endif // USE_FREETYPE - } // end namespace irr diff --git a/src/irrlicht_changes/static_text.h b/src/irrlicht_changes/static_text.h index 83bbf4c3d..74ef62008 100644 --- a/src/irrlicht_changes/static_text.h +++ b/src/irrlicht_changes/static_text.h @@ -20,7 +20,6 @@ #include "config.h" #include <IGUIEnvironment.h> -#if USE_FREETYPE namespace irr { @@ -184,12 +183,6 @@ namespace gui //! Checks if the text should be interpreted as right-to-left text virtual bool isRightToLeft() const; - //! Writes attributes of the element. - virtual void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const; - - //! Reads attributes of the element - virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options); - virtual bool hasType(EGUI_ELEMENT_TYPE t) const { return (t == EGUIET_ENRICHED_STATIC_TEXT) || (t == EGUIET_STATIC_TEXT); }; @@ -236,41 +229,6 @@ inline void setStaticText(irr::gui::IGUIStaticText *static_text, const EnrichedS } } -#else // USE_FREETYPE - -namespace irr -{ -namespace gui -{ - -class StaticText -{ -public: - static irr::gui::IGUIStaticText *add( - irr::gui::IGUIEnvironment *guienv, - const EnrichedString &text, - const core::rect< s32 > &rectangle, - bool border = false, - bool wordWrap = true, - irr::gui::IGUIElement *parent = NULL, - s32 id = -1, - bool fillBackground = false) - { - return guienv->addStaticText(text.c_str(), rectangle, border, wordWrap, parent, id, fillBackground); - } -}; - -} // end namespace gui - -} // end namespace irr - -inline void setStaticText(irr::gui::IGUIStaticText *static_text, const EnrichedString &text) -{ - static_text->setText(text.c_str()); -} - -#endif - inline void setStaticText(irr::gui::IGUIStaticText *static_text, const wchar_t *text) { setStaticText(static_text, EnrichedString(text, static_text->getOverrideColor())); diff --git a/src/cloudparams.h b/src/lighting.h index 88b5760ee..e0d9cee09 100644 --- a/src/cloudparams.h +++ b/src/lighting.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2017 bendeutsch, Ben Deutsch <ben@bendeutsch.de> +Copyright (C) 2021 x2048, Dmitry Kostenko <codeforsmile@gmail.com> 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 @@ -19,12 +19,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once -struct CloudParams +/** Describes ambient light settings for a player + */ +struct Lighting { - float density; - video::SColor color_bright; - video::SColor color_ambient; - float thickness; - float height; - v2f speed; + float shadow_intensity {0.0f}; }; diff --git a/src/log.cpp b/src/log.cpp index 3c61414e9..51652fe0a 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -35,43 +35,30 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <cerrno> #include <cstring> -const int BUFFER_LENGTH = 256; - -class StringBuffer : public std::streambuf { +class LevelTarget : public LogTarget { public: - StringBuffer() { - buffer_index = 0; - } - - int overflow(int c); - virtual void flush(const std::string &buf) = 0; - std::streamsize xsputn(const char *s, std::streamsize n); - void push_back(char c); - -private: - char buffer[BUFFER_LENGTH]; - int buffer_index; -}; - - -class LogBuffer : public StringBuffer { -public: - LogBuffer(Logger &logger, LogLevel lev) : - logger(logger), - level(lev) + LevelTarget(Logger &logger, LogLevel level, bool raw = false) : + m_logger(logger), + m_level(level), + m_raw(raw) {} - void flush(const std::string &buffer); - -private: - Logger &logger; - LogLevel level; -}; + virtual bool hasOutput() override { + return m_logger.hasOutput(m_level); + } + virtual void log(const std::string &buf) override { + if (!m_raw) { + m_logger.log(m_level, buf); + } else { + m_logger.logRaw(m_level, buf); + } + } -class RawLogBuffer : public StringBuffer { -public: - void flush(const std::string &buffer); +private: + Logger &m_logger; + LogLevel m_level; + bool m_raw; }; //// @@ -80,31 +67,33 @@ public: Logger g_logger; +#ifdef __ANDROID__ +AndroidLogOutput stdout_output; +AndroidLogOutput stderr_output; +#else StreamLogOutput stdout_output(std::cout); StreamLogOutput stderr_output(std::cerr); -std::ostream null_stream(NULL); - -RawLogBuffer raw_buf; - -LogBuffer none_buf(g_logger, LL_NONE); -LogBuffer error_buf(g_logger, LL_ERROR); -LogBuffer warning_buf(g_logger, LL_WARNING); -LogBuffer action_buf(g_logger, LL_ACTION); -LogBuffer info_buf(g_logger, LL_INFO); -LogBuffer verbose_buf(g_logger, LL_VERBOSE); - -// Connection -std::ostream *dout_con_ptr = &null_stream; -std::ostream *derr_con_ptr = &verbosestream; - -// Common streams -std::ostream rawstream(&raw_buf); -std::ostream dstream(&none_buf); -std::ostream errorstream(&error_buf); -std::ostream warningstream(&warning_buf); -std::ostream actionstream(&action_buf); -std::ostream infostream(&info_buf); -std::ostream verbosestream(&verbose_buf); +#endif + +LevelTarget none_target_raw(g_logger, LL_NONE, true); +LevelTarget none_target(g_logger, LL_NONE); +LevelTarget error_target(g_logger, LL_ERROR); +LevelTarget warning_target(g_logger, LL_WARNING); +LevelTarget action_target(g_logger, LL_ACTION); +LevelTarget info_target(g_logger, LL_INFO); +LevelTarget verbose_target(g_logger, LL_VERBOSE); +LevelTarget trace_target(g_logger, LL_TRACE); + +thread_local LogStream dstream(none_target); +thread_local LogStream rawstream(none_target_raw); +thread_local LogStream errorstream(error_target); +thread_local LogStream warningstream(warning_target); +thread_local LogStream actionstream(action_target); +thread_local LogStream infostream(info_target); +thread_local LogStream verbosestream(verbose_target); +thread_local LogStream tracestream(trace_target); +thread_local LogStream derr_con(verbose_target); +thread_local LogStream dout_con(trace_target); // Android #ifdef __ANDROID__ @@ -118,29 +107,15 @@ static unsigned int g_level_to_android[] = { //ANDROID_LOG_INFO, ANDROID_LOG_DEBUG, // LL_INFO ANDROID_LOG_VERBOSE, // LL_VERBOSE + ANDROID_LOG_VERBOSE, // LL_TRACE }; -class AndroidSystemLogOutput : public ICombinedLogOutput { - public: - AndroidSystemLogOutput() - { - g_logger.addOutput(this); - } - ~AndroidSystemLogOutput() - { - g_logger.removeOutput(this); - } - void logRaw(LogLevel lev, const std::string &line) - { - STATIC_ASSERT(ARRLEN(g_level_to_android) == LL_MAX, - mismatch_between_android_and_internal_loglevels); - __android_log_print(g_level_to_android[lev], - PROJECT_NAME_C, "%s", line.c_str()); - } -}; - -AndroidSystemLogOutput g_android_log_output; - +void AndroidLogOutput::logRaw(LogLevel lev, const std::string &line) { + STATIC_ASSERT(ARRLEN(g_level_to_android) == LL_MAX, + mismatch_between_android_and_internal_loglevels); + __android_log_print(g_level_to_android[lev], + PROJECT_NAME_C, "%s", line.c_str()); +} #endif /////////////////////////////////////////////////////////////////////////////// @@ -164,6 +139,8 @@ LogLevel Logger::stringToLevel(const std::string &name) return LL_INFO; else if (name == "verbose") return LL_VERBOSE; + else if (name == "trace") + return LL_TRACE; else return LL_MAX; } @@ -176,21 +153,26 @@ void Logger::addOutput(ILogOutput *out) void Logger::addOutput(ILogOutput *out, LogLevel lev) { m_outputs[lev].push_back(out); + m_has_outputs[lev] = true; } void Logger::addOutputMasked(ILogOutput *out, LogLevelMask mask) { for (size_t i = 0; i < LL_MAX; i++) { - if (mask & LOGLEVEL_TO_MASKLEVEL(i)) + if (mask & LOGLEVEL_TO_MASKLEVEL(i)) { m_outputs[i].push_back(out); + m_has_outputs[i] = true; + } } } void Logger::addOutputMaxLevel(ILogOutput *out, LogLevel lev) { assert(lev < LL_MAX); - for (size_t i = 0; i <= lev; i++) + for (size_t i = 0; i <= lev; i++) { m_outputs[i].push_back(out); + m_has_outputs[i] = true; + } } LogLevelMask Logger::removeOutput(ILogOutput *out) @@ -203,6 +185,7 @@ LogLevelMask Logger::removeOutput(ILogOutput *out) if (it != m_outputs[i].end()) { ret_mask |= LOGLEVEL_TO_MASKLEVEL(i); m_outputs[i].erase(it); + m_has_outputs[i] = !m_outputs[i].empty(); } } return ret_mask; @@ -236,6 +219,7 @@ const std::string Logger::getLevelLabel(LogLevel lev) "ACTION", "INFO", "VERBOSE", + "TRACE", }; assert(lev < LL_MAX && lev >= 0); STATIC_ASSERT(ARRLEN(names) == LL_MAX, @@ -297,7 +281,6 @@ void Logger::logToOutputs(LogLevel lev, const std::string &combined, m_outputs[lev][i]->log(lev, combined, time, thread_name, payload_text); } - //// //// *LogOutput methods //// @@ -349,6 +332,7 @@ void StreamLogOutput::logRaw(LogLevel lev, const std::string &line) m_stream << "\033[37m"; break; case LL_VERBOSE: + case LL_TRACE: // verbose is darker than info m_stream << "\033[2m"; break; @@ -396,6 +380,7 @@ void LogOutputBuffer::logRaw(LogLevel lev, const std::string &line) color = "\x1b(c@#BBB)"; break; case LL_VERBOSE: // dark grey + case LL_TRACE: color = "\x1b(c@#888)"; break; default: break; @@ -404,47 +389,3 @@ void LogOutputBuffer::logRaw(LogLevel lev, const std::string &line) m_buffer.push(color.append(line)); } - -//// -//// *Buffer methods -//// - -int StringBuffer::overflow(int c) -{ - push_back(c); - return c; -} - - -std::streamsize StringBuffer::xsputn(const char *s, std::streamsize n) -{ - for (int i = 0; i < n; ++i) - push_back(s[i]); - return n; -} - -void StringBuffer::push_back(char c) -{ - if (c == '\n' || c == '\r') { - if (buffer_index) - flush(std::string(buffer, buffer_index)); - buffer_index = 0; - } else { - buffer[buffer_index++] = c; - if (buffer_index >= BUFFER_LENGTH) { - flush(std::string(buffer, buffer_index)); - buffer_index = 0; - } - } -} - - -void LogBuffer::flush(const std::string &buffer) -{ - logger.log(level, buffer); -} - -void RawLogBuffer::flush(const std::string &buffer) -{ - g_logger.logRaw(LL_NONE, buffer); -} @@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once +#include <atomic> #include <map> #include <queue> #include <string> @@ -28,6 +29,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #if !defined(_WIN32) // POSIX #include <unistd.h> #endif +#include "util/basic_macros.h" +#include "util/stream.h" #include "irrlichttypes.h" class ILogOutput; @@ -39,6 +42,7 @@ enum LogLevel { LL_ACTION, // In-game actions LL_INFO, LL_VERBOSE, + LL_TRACE, LL_MAX, }; @@ -67,12 +71,13 @@ public: // Logs without a prefix void logRaw(LogLevel lev, const std::string &text); - void setTraceEnabled(bool enable) { m_trace_enabled = enable; } - bool getTraceEnabled() { return m_trace_enabled; } - static LogLevel stringToLevel(const std::string &name); static const std::string getLevelLabel(LogLevel lev); + bool hasOutput(LogLevel level) { + return m_has_outputs[level].load(std::memory_order_relaxed); + } + static LogColor color_mode; private: @@ -84,6 +89,7 @@ private: const std::string getThreadName(); std::vector<ILogOutput *> m_outputs[LL_MAX]; + std::atomic<bool> m_has_outputs[LL_MAX]; // Should implement atomic loads and stores (even though it's only // written to when one thread has access currently). @@ -91,7 +97,6 @@ private: volatile bool m_silenced_levels[LL_MAX]; std::map<std::thread::id, std::string> m_thread_names; mutable std::mutex m_mutex; - bool m_trace_enabled; }; class ILogOutput { @@ -185,35 +190,178 @@ private: Logger &m_logger; }; +#ifdef __ANDROID__ +class AndroidLogOutput : public ICombinedLogOutput { +public: + void logRaw(LogLevel lev, const std::string &line); +}; +#endif -extern StreamLogOutput stdout_output; -extern StreamLogOutput stderr_output; -extern std::ostream null_stream; +/* + * LogTarget + * + * This is the interface that sits between the LogStreams and the global logger. + * Primarily used to route streams to log levels, but could also enable other + * custom behavior. + * + */ +class LogTarget { +public: + // Must be thread-safe. These can be called from any thread. + virtual bool hasOutput() = 0; + virtual void log(const std::string &buf) = 0; +}; -extern std::ostream *dout_con_ptr; -extern std::ostream *derr_con_ptr; -extern std::ostream *derr_server_ptr; -extern Logger g_logger; +/* + * StreamProxy + * + * An ostream-like object that can proxy to a real ostream or do nothing, + * depending on how it is configured. See LogStream below. + * + */ +class StreamProxy { +public: + StreamProxy(std::ostream *os) : m_os(os) { } + + template<typename T> + StreamProxy& operator<<(T&& arg) { + if (m_os) { + *m_os << std::forward<T>(arg); + } + return *this; + } -// Writes directly to all LL_NONE log outputs for g_logger with no prefix. -extern std::ostream rawstream; + StreamProxy& operator<<(std::ostream& (*manip)(std::ostream&)) { + if (m_os) { + *m_os << manip; + } + return *this; + } -extern std::ostream errorstream; -extern std::ostream warningstream; -extern std::ostream actionstream; -extern std::ostream infostream; -extern std::ostream verbosestream; -extern std::ostream dstream; +private: + std::ostream *m_os; +}; -#define TRACEDO(x) do { \ - if (g_logger.getTraceEnabled()) { \ - x; \ - } \ -} while (0) -#define TRACESTREAM(x) TRACEDO(verbosestream x) +/* + * LogStream + * + * The public interface for log streams (infostream, verbosestream, etc). + * + * LogStream minimizes the work done when a given stream is off. (meaning + * it has no output targets, so it goes to /dev/null) + * + * For example, consider: + * + * verbosestream << "hello world" << 123 << std::endl; + * + * The compiler evaluates this as: + * + * (((verbosestream << "hello world") << 123) << std::endl) + * ^ ^ + * + * If `verbosestream` is on, the innermost expression (marked by ^) will return + * a StreamProxy that forwards to a real ostream, that feeds into the logger. + * However, if `verbosestream` is off, it will return a StreamProxy that does + * nothing on all later operations. Specifically, CPU time won't be wasted + * writing "hello world" and 123 into a buffer, or formatting the log entry. + * + * It is also possible to directly check if the stream is on/off: + * + * if (verbosestream) { + * auto data = ComputeExpensiveDataForTheLog(); + * verbosestream << data << endl; + * } + * +*/ -#define dout_con (*dout_con_ptr) -#define derr_con (*derr_con_ptr) +class LogStream { +public: + LogStream() = delete; + DISABLE_CLASS_COPY(LogStream); + + LogStream(LogTarget &target) : + m_target(target), + m_buffer(std::bind(&LogStream::internalFlush, this, std::placeholders::_1)), + m_dummy_buffer(), + m_stream(&m_buffer), + m_dummy_stream(&m_dummy_buffer), + m_proxy(&m_stream), + m_dummy_proxy(nullptr) { } + + template<typename T> + StreamProxy& operator<<(T&& arg) { + StreamProxy& sp = m_target.hasOutput() ? m_proxy : m_dummy_proxy; + sp << std::forward<T>(arg); + return sp; + } + StreamProxy& operator<<(std::ostream& (*manip)(std::ostream&)) { + StreamProxy& sp = m_target.hasOutput() ? m_proxy : m_dummy_proxy; + sp << manip; + return sp; + } + + operator bool() { + return m_target.hasOutput(); + } + + void internalFlush(const std::string &buf) { + m_target.log(buf); + } + + operator std::ostream&() { + return m_target.hasOutput() ? m_stream : m_dummy_stream; + } + +private: + // 10 streams per thread x (256 + overhead) ~ 3K per thread + static const int BUFFER_LENGTH = 256; + LogTarget &m_target; + StringStreamBuffer<BUFFER_LENGTH> m_buffer; + DummyStreamBuffer m_dummy_buffer; + std::ostream m_stream; + std::ostream m_dummy_stream; + StreamProxy m_proxy; + StreamProxy m_dummy_proxy; + +}; + +#ifdef __ANDROID__ +extern AndroidLogOutput stdout_output; +extern AndroidLogOutput stderr_output; +#else +extern StreamLogOutput stdout_output; +extern StreamLogOutput stderr_output; +#endif + +extern Logger g_logger; + +/* + * By making the streams thread_local, each thread has its own + * private buffer. Two or more threads can write to the same stream + * simultaneously (lock-free), and there won't be any interference. + * + * The finished lines are sent to a LogTarget which is a global (not thread-local) + * object, and from there relayed to g_logger. The final writes are serialized + * by the mutex in g_logger. +*/ + +extern thread_local LogStream dstream; +extern thread_local LogStream rawstream; // Writes directly to all LL_NONE log outputs with no prefix. +extern thread_local LogStream errorstream; +extern thread_local LogStream warningstream; +extern thread_local LogStream actionstream; +extern thread_local LogStream infostream; +extern thread_local LogStream verbosestream; +extern thread_local LogStream tracestream; +// TODO: Search/replace these with verbose/tracestream +extern thread_local LogStream derr_con; +extern thread_local LogStream dout_con; + +#define TRACESTREAM(x) do { \ + if (tracestream) { \ + tracestream x; \ + } \ +} while (0) diff --git a/src/main.cpp b/src/main.cpp index 1044b327a..ebd1f740e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes.h" // must be included before anything irrlicht, see comment in the file #include "irrlicht.h" // createDevice #include "irrlichttypes_extrabloated.h" +#include "benchmark/benchmark.h" #include "chat_interface.h" #include "debug.h" #include "unittest/test.h" @@ -65,6 +66,14 @@ extern "C" { #error Minetest cannot be built without exceptions or RTTI #endif +#if defined(__MINGW32__) && !defined(__MINGW64__) && !defined(__clang__) && \ + (__GNUC__ < 11 || (__GNUC__ == 11 && __GNUC_MINOR__ < 1)) +// see e.g. https://github.com/minetest/minetest/issues/10137 +#warning ================================== +#warning 32-bit MinGW gcc before 11.1 has known issues with crashes on thread exit, you should upgrade. +#warning ================================== +#endif + #define DEBUGFILE "debug.txt" #define DEFAULT_SERVER_PORT 30000 @@ -204,7 +213,19 @@ int main(int argc, char *argv[]) return 1; #endif } + + // Run benchmarks + if (cmd_args.getFlag("run-benchmarks")) { +#if BUILD_BENCHMARKS + return run_benchmarks(); +#else + errorstream << "Benchmark support is not enabled in this binary. " + << "If you want to enable it, compile project with BUILD_BENCHMARKS=1 flag." + << std::endl; + return 1; #endif + } +#endif // __ANDROID__ GameStartData game_params; #ifdef SERVER @@ -269,6 +290,8 @@ static void set_allowed_options(OptionList *allowed_options) _("Set network port (UDP)")))); allowed_options->insert(std::make_pair("run-unittests", ValueSpec(VALUETYPE_FLAG, _("Run the unit tests and exit")))); + allowed_options->insert(std::make_pair("run-benchmarks", ValueSpec(VALUETYPE_FLAG, + _("Run the benchmarks and exit")))); allowed_options->insert(std::make_pair("map-dir", ValueSpec(VALUETYPE_STRING, _("Same as --world (deprecated)")))); allowed_options->insert(std::make_pair("world", ValueSpec(VALUETYPE_STRING, @@ -299,6 +322,8 @@ static void set_allowed_options(OptionList *allowed_options) _("Migrate from current players backend to another (Only works when using minetestserver or with --server)")))); allowed_options->insert(std::make_pair("migrate-auth", ValueSpec(VALUETYPE_STRING, _("Migrate from current auth backend to another (Only works when using minetestserver or with --server)")))); + allowed_options->insert(std::make_pair("migrate-mod-storage", ValueSpec(VALUETYPE_STRING, + _("Migrate from current mod storage backend to another (Only works when using minetestserver or with --server)")))); allowed_options->insert(std::make_pair("terminal", ValueSpec(VALUETYPE_FLAG, _("Feature an interactive terminal (Only works when using minetestserver or with --server)")))); allowed_options->insert(std::make_pair("recompress", ValueSpec(VALUETYPE_FLAG, @@ -443,14 +468,6 @@ static bool setup_log_params(const Settings &cmd_args) } } - // If trace is enabled, enable logging of certain things - if (cmd_args.getFlag("trace")) { - dstream << _("Enabling trace level debug output") << std::endl; - g_logger.setTraceEnabled(true); - dout_con_ptr = &verbosestream; // This is somewhat old - socket_enable_debug_output = true; // Sockets doesn't use log.h - } - // In certain cases, output info level on stderr if (cmd_args.getFlag("info") || cmd_args.getFlag("verbose") || cmd_args.getFlag("trace") || cmd_args.getFlag("speedtests")) @@ -460,6 +477,12 @@ static bool setup_log_params(const Settings &cmd_args) if (cmd_args.getFlag("verbose") || cmd_args.getFlag("trace")) g_logger.addOutput(&stderr_output, LL_VERBOSE); + if (cmd_args.getFlag("trace")) { + dstream << _("Enabling trace level debug output") << std::endl; + g_logger.addOutput(&stderr_output, LL_TRACE); + socket_enable_debug_output = true; + } + return true; } @@ -589,7 +612,7 @@ static void init_log_streams(const Settings &cmd_args) warningstream << "Deprecated use of debug_log_level with an " "integer value; please update your configuration." << std::endl; static const char *lev_name[] = - {"", "error", "action", "info", "verbose"}; + {"", "error", "action", "info", "verbose", "trace"}; int lev_i = atoi(conf_loglev.c_str()); if (lev_i < 0 || lev_i >= (int)ARRLEN(lev_name)) { warningstream << "Supplied invalid debug_log_level!" @@ -886,6 +909,9 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings & if (cmd_args.exists("migrate-auth")) return ServerEnvironment::migrateAuthDatabase(game_params, cmd_args); + if (cmd_args.exists("migrate-mod-storage")) + return Server::migrateModStorageDatabase(game_params, cmd_args); + if (cmd_args.getFlag("recompress")) return recompress_map_database(game_params, cmd_args, bind_addr); diff --git a/src/map.cpp b/src/map.cpp index 1648adec3..7bc1334b0 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -154,13 +154,6 @@ void Map::listAllLoadedBlocks(std::vector<v3s16> &dst) } } -bool Map::isNodeUnderground(v3s16 p) -{ - v3s16 blockpos = getNodeBlockPos(p); - MapBlock *block = getBlockNoCreateNoEx(blockpos); - return block && block->getIsUnderground(); -} - bool Map::isValidPosition(v3s16 p) { v3s16 blockpos = getNodeBlockPos(p); @@ -187,24 +180,32 @@ MapNode Map::getNode(v3s16 p, bool *is_valid_position) return node; } -// throws InvalidPositionException if not found -void Map::setNode(v3s16 p, MapNode & n) +static void set_node_in_block(MapBlock *block, v3s16 relpos, MapNode n) { - v3s16 blockpos = getNodeBlockPos(p); - MapBlock *block = getBlockNoCreate(blockpos); - v3s16 relpos = p - blockpos*MAP_BLOCKSIZE; // Never allow placing CONTENT_IGNORE, it causes problems if(n.getContent() == CONTENT_IGNORE){ + const NodeDefManager *nodedef = block->getParent()->getNodeDefManager(); + v3s16 blockpos = block->getPos(); + v3s16 p = blockpos * MAP_BLOCKSIZE + relpos; bool temp_bool; - errorstream<<"Map::setNode(): Not allowing to place CONTENT_IGNORE" + errorstream<<"Not allowing to place CONTENT_IGNORE" <<" while trying to replace \"" - <<m_nodedef->get(block->getNodeNoCheck(relpos, &temp_bool)).name + <<nodedef->get(block->getNodeNoCheck(relpos, &temp_bool)).name <<"\" at "<<PP(p)<<" (block "<<PP(blockpos)<<")"<<std::endl; return; } block->setNodeNoCheck(relpos, n); } +// throws InvalidPositionException if not found +void Map::setNode(v3s16 p, MapNode & n) +{ + v3s16 blockpos = getNodeBlockPos(p); + MapBlock *block = getBlockNoCreate(blockpos); + v3s16 relpos = p - blockpos*MAP_BLOCKSIZE; + set_node_in_block(block, relpos, n); +} + void Map::addNodeAndUpdate(v3s16 p, MapNode n, std::map<v3s16, MapBlock*> &modified_blocks, bool remove_metadata) @@ -212,8 +213,14 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n, // Collect old node for rollback RollbackNode rollback_oldnode(this, p, m_gamedef); + v3s16 blockpos = getNodeBlockPos(p); + MapBlock *block = getBlockNoCreate(blockpos); + if (block->isDummy()) + throw InvalidPositionException(); + v3s16 relpos = p - blockpos * MAP_BLOCKSIZE; + // This is needed for updating the lighting - MapNode oldnode = getNode(p); + MapNode oldnode = block->getNodeUnsafe(relpos); // Remove node metadata if (remove_metadata) { @@ -221,18 +228,29 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n, } // Set the node on the map - // Ignore light (because calling voxalgo::update_lighting_nodes) - n.setLight(LIGHTBANK_DAY, 0, m_nodedef); - n.setLight(LIGHTBANK_NIGHT, 0, m_nodedef); - setNode(p, n); + const ContentFeatures &cf = m_nodedef->get(n); + const ContentFeatures &oldcf = m_nodedef->get(oldnode); + if (cf.lightingEquivalent(oldcf)) { + // No light update needed, just copy over the old light. + n.setLight(LIGHTBANK_DAY, oldnode.getLightRaw(LIGHTBANK_DAY, oldcf), cf); + n.setLight(LIGHTBANK_NIGHT, oldnode.getLightRaw(LIGHTBANK_NIGHT, oldcf), cf); + set_node_in_block(block, relpos, n); + + modified_blocks[blockpos] = block; + } else { + // Ignore light (because calling voxalgo::update_lighting_nodes) + n.setLight(LIGHTBANK_DAY, 0, cf); + n.setLight(LIGHTBANK_NIGHT, 0, cf); + set_node_in_block(block, relpos, n); - // Update lighting - std::vector<std::pair<v3s16, MapNode> > oldnodes; - oldnodes.emplace_back(p, oldnode); - voxalgo::update_lighting_nodes(this, oldnodes, modified_blocks); + // Update lighting + std::vector<std::pair<v3s16, MapNode> > oldnodes; + oldnodes.emplace_back(p, oldnode); + voxalgo::update_lighting_nodes(this, oldnodes, modified_blocks); - for (auto &modified_block : modified_blocks) { - modified_block.second->expireDayNightDiff(); + for (auto &modified_block : modified_blocks) { + modified_block.second->expireDayNightDiff(); + } } // Report for rollback @@ -243,22 +261,6 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n, action.setSetNode(p, rollback_oldnode, rollback_newnode); m_gamedef->rollback()->reportAction(action); } - - /* - Add neighboring liquid nodes and this node to transform queue. - (it's vital for the node itself to get updated last, if it was removed.) - */ - - for (const v3s16 &dir : g_7dirs) { - v3s16 p2 = p + dir; - - bool is_valid_position; - MapNode n2 = getNode(p2, &is_valid_position); - if(is_valid_position && - (m_nodedef->get(n2).isLiquid() || - n2.getContent() == CONTENT_AIR)) - m_transforming_liquid.push_back(p2); - } } void Map::removeNodeAndUpdate(v3s16 p, @@ -339,7 +341,7 @@ struct TimeOrderedMapBlock { void Map::timerUpdate(float dtime, float unload_timeout, u32 max_loaded_blocks, std::vector<v3s16> *unloaded_blocks) { - bool save_before_unloading = (mapType() == MAPTYPE_SERVER); + bool save_before_unloading = maySaveBlocks(); // Profile modified reasons Profiler modprofiler; @@ -349,6 +351,7 @@ void Map::timerUpdate(float dtime, float unload_timeout, u32 max_loaded_blocks, u32 saved_blocks_count = 0; u32 block_count_all = 0; + const auto start_time = porting::getTimeUs(); beginSave(); // If there is no practical limit, we spare creation of mapblock_queue @@ -390,6 +393,7 @@ void Map::timerUpdate(float dtime, float unload_timeout, u32 max_loaded_blocks, } } + // Delete sector if we emptied it if (all_blocks_deleted) { sector_deletion_queue.push_back(sector_it.first); } @@ -408,6 +412,7 @@ void Map::timerUpdate(float dtime, float unload_timeout, u32 max_loaded_blocks, } } block_count_all = mapblock_queue.size(); + // Delete old blocks, and blocks over the limit from the memory while (!mapblock_queue.empty() && (mapblock_queue.size() > max_loaded_blocks || mapblock_queue.top().block->getUsageTimer() > unload_timeout)) { @@ -438,6 +443,7 @@ void Map::timerUpdate(float dtime, float unload_timeout, u32 max_loaded_blocks, deleted_blocks_count++; block_count_all--; } + // Delete empty sectors for (auto §or_it : m_sectors) { if (sector_it.second->empty()) { @@ -445,7 +451,11 @@ void Map::timerUpdate(float dtime, float unload_timeout, u32 max_loaded_blocks, } } } + endSave(); + const auto end_time = porting::getTimeUs(); + + reportMetrics(end_time - start_time, saved_blocks_count, block_count_all); // Finally delete the empty sectors deleteSectors(sector_deletion_queue); @@ -524,11 +534,11 @@ struct NodeNeighbor { { } }; -void Map::transforming_liquid_add(v3s16 p) { +void ServerMap::transforming_liquid_add(v3s16 p) { m_transforming_liquid.push_back(p); } -void Map::transformLiquids(std::map<v3s16, MapBlock*> &modified_blocks, +void ServerMap::transformLiquids(std::map<v3s16, MapBlock*> &modified_blocks, ServerEnvironment *env) { u32 loopcount = 0; @@ -1231,7 +1241,12 @@ ServerMap::ServerMap(const std::string &savedir, IGameDef *gamedef, m_savedir = savedir; m_map_saving_enabled = false; - m_save_time_counter = mb->addCounter("minetest_core_map_save_time", "Map save time (in nanoseconds)"); + m_save_time_counter = mb->addCounter( + "minetest_map_save_time", "Time spent saving blocks (in microseconds)"); + m_save_count_counter = mb->addCounter( + "minetest_map_saved_blocks", "Number of blocks saved"); + m_loaded_blocks_gauge = mb->addGauge( + "minetest_map_loaded_blocks", "Number of loaded blocks"); m_map_compression_level = rangelim(g_settings->getS16("map_compression_level_disk"), -1, 9); @@ -1466,11 +1481,7 @@ MapSector *ServerMap::createSector(v2s16 p2d) /* Do not create over max mapgen limit */ - const s16 max_limit_bp = MAX_MAP_GENERATION_LIMIT / MAP_BLOCKSIZE; - if (p2d.X < -max_limit_bp || - p2d.X > max_limit_bp || - p2d.Y < -max_limit_bp || - p2d.Y > max_limit_bp) + if (blockpos_over_max_limit(v3s16(p2d.X, 0, p2d.Y))) throw InvalidPositionException("createSector(): pos. over max mapgen limit"); /* @@ -1479,9 +1490,6 @@ MapSector *ServerMap::createSector(v2s16 p2d) sector = new MapSector(this, p2d, m_gamedef); - // Sector position on map in nodes - //v2s16 nodepos2d = p2d * MAP_BLOCKSIZE; - /* Insert to container */ @@ -1569,6 +1577,29 @@ bool ServerMap::isBlockInQueue(v3s16 pos) return m_emerge && m_emerge->isBlockInQueue(pos); } +void ServerMap::addNodeAndUpdate(v3s16 p, MapNode n, + std::map<v3s16, MapBlock*> &modified_blocks, + bool remove_metadata) +{ + Map::addNodeAndUpdate(p, n, modified_blocks, remove_metadata); + + /* + Add neighboring liquid nodes and this node to transform queue. + (it's vital for the node itself to get updated last, if it was removed.) + */ + + for (const v3s16 &dir : g_7dirs) { + v3s16 p2 = p + dir; + + bool is_valid_position; + MapNode n2 = getNode(p2, &is_valid_position); + if(is_valid_position && + (m_nodedef->get(n2).isLiquid() || + n2.getContent() == CONTENT_AIR)) + m_transforming_liquid.push_back(p2); + } +} + // N.B. This requires no synchronization, since data will not be modified unless // the VoxelManipulator being updated belongs to the same thread. void ServerMap::updateVManip(v3s16 pos) @@ -1591,6 +1622,13 @@ void ServerMap::updateVManip(v3s16 pos) vm->m_is_dirty = true; } +void ServerMap::reportMetrics(u64 save_time_us, u32 saved_blocks, u32 all_blocks) +{ + m_loaded_blocks_gauge->set(all_blocks); + m_save_time_counter->increment(save_time_us); + m_save_count_counter->increment(saved_blocks); +} + void ServerMap::save(ModifiedState save_level) { if (!m_map_saving_enabled) { @@ -1598,7 +1636,7 @@ void ServerMap::save(ModifiedState save_level) return; } - u64 start_time = porting::getTimeNs(); + const auto start_time = porting::getTimeUs(); if(save_level == MOD_STATE_CLEAN) infostream<<"ServerMap: Saving whole map, this can take time." @@ -1659,8 +1697,8 @@ void ServerMap::save(ModifiedState save_level) modprofiler.print(infostream); } - auto end_time = porting::getTimeNs(); - m_save_time_counter->increment(end_time - start_time); + const auto end_time = porting::getTimeUs(); + reportMetrics(end_time - start_time, block_count, block_count_all); } void ServerMap::listAllLoadableBlocks(std::vector<v3s16> &dst) @@ -1878,6 +1916,7 @@ MMVManip::MMVManip(Map *map): VoxelManipulator(), m_map(map) { + assert(map); } void MMVManip::initialEmerge(v3s16 blockpos_min, v3s16 blockpos_max, @@ -1885,6 +1924,8 @@ void MMVManip::initialEmerge(v3s16 blockpos_min, v3s16 blockpos_max, { TimeTaker timer1("initialEmerge", &emerge_time); + assert(m_map); + // Units of these are MapBlocks v3s16 p_min = blockpos_min; v3s16 p_max = blockpos_max; @@ -1968,6 +2009,7 @@ void MMVManip::blitBackAll(std::map<v3s16, MapBlock*> *modified_blocks, { if(m_area.getExtent() == v3s16(0,0,0)) return; + assert(m_map); /* Copy data of all blocks @@ -1988,4 +2030,33 @@ void MMVManip::blitBackAll(std::map<v3s16, MapBlock*> *modified_blocks, } } +MMVManip *MMVManip::clone() const +{ + MMVManip *ret = new MMVManip(); + + const s32 size = m_area.getVolume(); + ret->m_area = m_area; + if (m_data) { + ret->m_data = new MapNode[size]; + memcpy(ret->m_data, m_data, size * sizeof(MapNode)); + } + if (m_flags) { + ret->m_flags = new u8[size]; + memcpy(ret->m_flags, m_flags, size * sizeof(u8)); + } + + ret->m_is_dirty = m_is_dirty; + // Even if the copy is disconnected from a map object keep the information + // needed to write it back to one + ret->m_loaded_blocks = m_loaded_blocks; + + return ret; +} + +void MMVManip::reparent(Map *map) +{ + assert(map && !m_map); + m_map = map; +} + //END @@ -54,10 +54,6 @@ struct BlockMakeData; MapEditEvent */ -#define MAPTYPE_BASE 0 -#define MAPTYPE_SERVER 1 -#define MAPTYPE_CLIENT 2 - enum MapEditEventType{ // Node added (changed from air or something else to something) MEET_ADDNODE, @@ -127,11 +123,6 @@ public: virtual ~Map(); DISABLE_CLASS_COPY(Map); - virtual s32 mapType() const - { - return MAPTYPE_BASE; - } - /* Drop (client) or delete (server) the map. */ @@ -169,9 +160,6 @@ public: inline const NodeDefManager * getNodeDefManager() { return m_nodedef; } - // Returns InvalidPositionException if not found - bool isNodeUnderground(v3s16 p); - bool isValidPosition(v3s16 p); // throws InvalidPositionException if not found @@ -185,7 +173,7 @@ public: /* These handle lighting but not faces. */ - void addNodeAndUpdate(v3s16 p, MapNode n, + virtual void addNodeAndUpdate(v3s16 p, MapNode n, std::map<v3s16, MapBlock*> &modified_blocks, bool remove_metadata = true); void removeNodeAndUpdate(v3s16 p, @@ -205,6 +193,11 @@ public: virtual void save(ModifiedState save_level) { FATAL_ERROR("FIXME"); } + /* + Return true unless the map definitely cannot save blocks. + */ + virtual bool maySaveBlocks() { return true; } + // Server implements these. // Client leaves them as no-op. virtual bool saveBlock(MapBlock *block) { return false; } @@ -212,14 +205,14 @@ public: /* Updates usage timers and unloads unused blocks and sectors. - Saves modified blocks before unloading on MAPTYPE_SERVER. + Saves modified blocks before unloading if possible. */ void timerUpdate(float dtime, float unload_timeout, u32 max_loaded_blocks, std::vector<v3s16> *unloaded_blocks=NULL); /* Unloads all blocks with a zero refCount(). - Saves modified blocks before unloading on MAPTYPE_SERVER. + Saves modified blocks before unloading if possible. */ void unloadUnreferencedBlocks(std::vector<v3s16> *unloaded_blocks=NULL); @@ -231,9 +224,6 @@ public: // For debug printing. Prints "Map: ", "ServerMap: " or "ClientMap: " virtual void PrintInfo(std::ostream &out); - void transformLiquids(std::map<v3s16, MapBlock*> & modified_blocks, - ServerEnvironment *env); - /* Node metadata These are basically coordinate wrappers to MapBlock @@ -272,12 +262,8 @@ public: Variables */ - void transforming_liquid_add(v3s16 p); - bool isBlockOccluded(MapBlock *block, v3s16 cam_pos_nodes); protected: - friend class LuaVoxelManip; - IGameDef *m_gamedef; std::set<MapEventReceiver*> m_event_receivers; @@ -288,23 +274,17 @@ protected: MapSector *m_sector_cache = nullptr; v2s16 m_sector_cache_p; - // Queued transforming water nodes - UniqueQueue<v3s16> m_transforming_liquid; - // This stores the properties of the nodes on the map. const NodeDefManager *m_nodedef; + // Can be implemented by child class + virtual void reportMetrics(u64 save_time_us, u32 saved_blocks, u32 all_blocks) {} + bool determineAdditionalOcclusionCheck(const v3s16 &pos_camera, const core::aabbox3d<s16> &block_bounds, v3s16 &check); bool isOccluded(const v3s16 &pos_camera, const v3s16 &pos_target, float step, float stepfac, float start_offset, float end_offset, u32 needed_count); - -private: - f32 m_transforming_liquid_loop_count_multiplier = 1.0f; - u32 m_unprocessed_count = 0; - u64 m_inc_trending_up_start_time = 0; // milliseconds - bool m_queue_size_timer_started = false; }; /* @@ -322,11 +302,6 @@ public: ServerMap(const std::string &savedir, IGameDef *gamedef, EmergeManager *emerge, MetricsBackend *mb); ~ServerMap(); - s32 mapType() const - { - return MAPTYPE_SERVER; - } - /* Get a sector from somewhere. - Check memory @@ -357,7 +332,7 @@ public: - Create blank filled with CONTENT_IGNORE */ - MapBlock *emergeBlock(v3s16 p, bool create_blank=true); + MapBlock *emergeBlock(v3s16 p, bool create_blank=true) override; /* Try to get a block. @@ -369,32 +344,36 @@ public: bool isBlockInQueue(v3s16 pos); + void addNodeAndUpdate(v3s16 p, MapNode n, + std::map<v3s16, MapBlock*> &modified_blocks, + bool remove_metadata) override; + /* Database functions */ static MapDatabase *createDatabase(const std::string &name, const std::string &savedir, Settings &conf); // Call these before and after saving of blocks - void beginSave(); - void endSave(); + void beginSave() override; + void endSave() override; - void save(ModifiedState save_level); + void save(ModifiedState save_level) override; void listAllLoadableBlocks(std::vector<v3s16> &dst); MapgenParams *getMapgenParams(); - bool saveBlock(MapBlock *block); + bool saveBlock(MapBlock *block) override; static bool saveBlock(MapBlock *block, MapDatabase *db, int compression_level = -1); MapBlock* loadBlock(v3s16 p); // Database version void loadBlock(std::string *blob, v3s16 p3d, MapSector *sector, bool save_after_load=false); - bool deleteBlock(v3s16 blockpos); + bool deleteBlock(v3s16 blockpos) override; void updateVManip(v3s16 pos); // For debug printing - virtual void PrintInfo(std::ostream &out); + void PrintInfo(std::ostream &out) override; bool isSavingEnabled(){ return m_map_saving_enabled; } @@ -410,9 +389,20 @@ public: bool repairBlockLight(v3s16 blockpos, std::map<v3s16, MapBlock *> *modified_blocks); + void transformLiquids(std::map<v3s16, MapBlock*> & modified_blocks, + ServerEnvironment *env); + + void transforming_liquid_add(v3s16 p); + MapSettingsManager settings_mgr; +protected: + + void reportMetrics(u64 save_time_us, u32 saved_blocks, u32 all_blocks) override; + private: + friend class LuaVoxelManip; + // Emerge manager EmergeManager *m_emerge; @@ -423,6 +413,13 @@ private: std::set<v3s16> m_chunks_in_progress; + // Queued transforming water nodes + UniqueQueue<v3s16> m_transforming_liquid; + f32 m_transforming_liquid_loop_count_multiplier = 1.0f; + u32 m_unprocessed_count = 0; + u64 m_inc_trending_up_start_time = 0; // milliseconds + bool m_queue_size_timer_started = false; + /* Metadata is re-written on disk only if this is true. This is reset to false when written on disk. @@ -431,7 +428,10 @@ private: MapDatabase *dbase = nullptr; MapDatabase *dbase_ro = nullptr; + // Map metrics + MetricGaugePtr m_loaded_blocks_gauge; MetricCounterPtr m_save_time_counter; + MetricCounterPtr m_save_count_counter; }; @@ -457,10 +457,25 @@ public: void blitBackAll(std::map<v3s16, MapBlock*> * modified_blocks, bool overwrite_generated = true); + /* + Creates a copy of this VManip including contents, the copy will not be + associated with a Map. + */ + MMVManip *clone() const; + + // Reassociates a copied VManip to a map + void reparent(Map *map); + + // Is it impossible to call initialEmerge / blitBackAll? + inline bool isOrphan() const { return !m_map; } + bool m_is_dirty = false; protected: - Map *m_map; + MMVManip() {}; + + // may be null + Map *m_map = nullptr; /* key = blockpos value = flags describing the block diff --git a/src/map_settings_manager.cpp b/src/map_settings_manager.cpp index 7e86a9937..c75483edb 100644 --- a/src/map_settings_manager.cpp +++ b/src/map_settings_manager.cpp @@ -52,14 +52,7 @@ MapSettingsManager::~MapSettingsManager() bool MapSettingsManager::getMapSetting( const std::string &name, std::string *value_out) { - // Try getting it normally first - if (m_map_settings->getNoEx(name, *value_out)) - return true; - - // If not we may have to resolve some compatibility kludges - if (name == "seed") - return Settings::getLayer(SL_GLOBAL)->getNoEx("fixed_map_seed", *value_out); - return false; + return m_map_settings->getNoEx(name, *value_out); } diff --git a/src/mapblock.cpp b/src/mapblock.cpp index 4958d3a65..e3a6caa19 100644 --- a/src/mapblock.cpp +++ b/src/mapblock.cpp @@ -218,31 +218,6 @@ void MapBlock::expireDayNightDiff() m_day_night_differs_expired = true; } -s16 MapBlock::getGroundLevel(v2s16 p2d) -{ - if(isDummy()) - return -3; - try - { - s16 y = MAP_BLOCKSIZE-1; - for(; y>=0; y--) - { - MapNode n = getNodeRef(p2d.X, y, p2d.Y); - if (m_gamedef->ndef()->get(n).walkable) { - if(y == MAP_BLOCKSIZE-1) - return -2; - - return y; - } - } - return -1; - } - catch(InvalidPositionException &e) - { - return -3; - } -} - /* Serialization */ diff --git a/src/mapblock.h b/src/mapblock.h index 8de631a29..a86db7b70 100644 --- a/src/mapblock.h +++ b/src/mapblock.h @@ -364,20 +364,6 @@ public: } //// - //// Miscellaneous stuff - //// - - /* - Tries to measure ground level. - Return value: - -1 = only air - -2 = only ground - -3 = random fail - 0...MAP_BLOCKSIZE-1 = ground level - */ - s16 getGroundLevel(v2s16 p2d); - - //// //// Timestamp (see m_timestamp) //// @@ -615,7 +601,7 @@ typedef std::vector<MapBlock*> MapBlockVect; inline bool objectpos_over_limit(v3f p) { - const float max_limit_bs = MAX_MAP_GENERATION_LIMIT * BS; + const float max_limit_bs = (MAX_MAP_GENERATION_LIMIT + 0.5f) * BS; return p.X < -max_limit_bs || p.X > max_limit_bs || p.Y < -max_limit_bs || diff --git a/src/mapgen/dungeongen.cpp b/src/mapgen/dungeongen.cpp index acdb1a0f0..1d439abeb 100644 --- a/src/mapgen/dungeongen.cpp +++ b/src/mapgen/dungeongen.cpp @@ -71,7 +71,7 @@ DungeonGen::DungeonGen(const NodeDefManager *ndef, dp.num_dungeons = 1; dp.notifytype = GENNOTIFY_DUNGEON; - dp.np_alt_wall = + dp.np_alt_wall = NoiseParams(-0.4, 1.0, v3f(40.0, 40.0, 40.0), 32474, 6, 1.1, 2.0); } } diff --git a/src/mapgen/mapgen.cpp b/src/mapgen/mapgen.cpp index 7984ff609..d767bd264 100644 --- a/src/mapgen/mapgen.cpp +++ b/src/mapgen/mapgen.cpp @@ -1018,10 +1018,11 @@ MapgenParams::~MapgenParams() void MapgenParams::readParams(const Settings *settings) { - std::string seed_str; - const char *seed_name = (settings == g_settings) ? "fixed_map_seed" : "seed"; + // should always be used via MapSettingsManager + assert(settings != g_settings); - if (settings->getNoEx(seed_name, seed_str)) { + std::string seed_str; + if (settings->getNoEx("seed", seed_str)) { if (!seed_str.empty()) seed = read_seed(seed_str.c_str()); else diff --git a/src/mapgen/mapgen_flat.cpp b/src/mapgen/mapgen_flat.cpp index 342455029..6b249ea1f 100644 --- a/src/mapgen/mapgen_flat.cpp +++ b/src/mapgen/mapgen_flat.cpp @@ -177,7 +177,7 @@ void MapgenFlatParams::setDefaultSettings(Settings *settings) int MapgenFlat::getSpawnLevelAtPoint(v2s16 p) { s16 stone_level = ground_level; - float n_terrain = + float n_terrain = ((spflags & MGFLAT_LAKES) || (spflags & MGFLAT_HILLS)) ? NoisePerlin2D(&noise_terrain->np, p.X, p.Y, seed) : 0.0f; diff --git a/src/mapgen/mapgen_v6.cpp b/src/mapgen/mapgen_v6.cpp index bce9cee81..a418acace 100644 --- a/src/mapgen/mapgen_v6.cpp +++ b/src/mapgen/mapgen_v6.cpp @@ -360,19 +360,6 @@ int MapgenV6::getSpawnLevelAtPoint(v2s16 p) //////////////////////// Noise functions -float MapgenV6::getMudAmount(v2s16 p) -{ - int index = (p.Y - node_min.Z) * ystride + (p.X - node_min.X); - return getMudAmount(index); -} - - -bool MapgenV6::getHaveBeach(v2s16 p) -{ - int index = (p.Y - node_min.Z) * ystride + (p.X - node_min.X); - return getHaveBeach(index); -} - BiomeV6Type MapgenV6::getBiome(v2s16 p) { diff --git a/src/mapgen/mapgen_v6.h b/src/mapgen/mapgen_v6.h index a6e6da8c6..b0eb67893 100644 --- a/src/mapgen/mapgen_v6.h +++ b/src/mapgen/mapgen_v6.h @@ -154,9 +154,7 @@ public: float getHumidity(v2s16 p); float getTreeAmount(v2s16 p); bool getHaveAppleTree(v2s16 p); - float getMudAmount(v2s16 p); - virtual float getMudAmount(int index); - bool getHaveBeach(v2s16 p); + float getMudAmount(int index); bool getHaveBeach(int index); BiomeV6Type getBiome(v2s16 p); BiomeV6Type getBiome(int index, v2s16 p); diff --git a/src/mapgen/mg_biome.cpp b/src/mapgen/mg_biome.cpp index f08cc190f..8b4c96cd5 100644 --- a/src/mapgen/mg_biome.cpp +++ b/src/mapgen/mg_biome.cpp @@ -273,7 +273,7 @@ Biome *BiomeGenOriginal::calcBiomeFromNoise(float heat, float humidity, v3s16 po pos.Y - biome_closest_blend->max_pos.Y) return biome_closest_blend; - return (biome_closest) ? biome_closest : (Biome *)m_bmgr->getRaw(BIOME_NONE); + return (biome_closest) ? biome_closest : (Biome *)m_bmgr->getRaw(BIOME_NONE); } diff --git a/src/mapgen/mg_ore.cpp b/src/mapgen/mg_ore.cpp index 5814f433a..4f0c35548 100644 --- a/src/mapgen/mg_ore.cpp +++ b/src/mapgen/mg_ore.cpp @@ -498,8 +498,8 @@ void OreVein::generate(MMVManip *vm, int mapseed, u32 blockseed, } // randval ranges from -1..1 - /* - Note: can generate values slightly larger than 1 + /* + Note: can generate values slightly larger than 1 but this can't be changed as mapgen must be deterministic accross versions. */ float randval = (float)pr.next() / float(pr.RANDOM_RANGE / 2) - 1.f; diff --git a/src/modchannels.cpp b/src/modchannels.cpp index 301dcb092..9626e8e0c 100644 --- a/src/modchannels.cpp +++ b/src/modchannels.cpp @@ -88,8 +88,7 @@ bool ModChannelMgr::canWriteOnChannel(const std::string &channel) const void ModChannelMgr::registerChannel(const std::string &channel) { - m_registered_channels[channel] = - std::unique_ptr<ModChannel>(new ModChannel(channel)); + m_registered_channels[channel] = std::make_unique<ModChannel>(channel); } bool ModChannelMgr::setChannelState(const std::string &channel, ModChannelState state) diff --git a/src/network/address.cpp b/src/network/address.cpp index 05678aa62..cf2e6208d 100644 --- a/src/network/address.cpp +++ b/src/network/address.cpp @@ -87,38 +87,31 @@ Address::Address(const IPv6AddressBytes *ipv6_bytes, u16 port) setPort(port); } -// Equality (address family, address and port must be equal) -bool Address::operator==(const Address &address) +// Equality (address family, IP and port must be equal) +bool Address::operator==(const Address &other) { - if (address.m_addr_family != m_addr_family || address.m_port != m_port) + if (other.m_addr_family != m_addr_family || other.m_port != m_port) return false; if (m_addr_family == AF_INET) { - return m_address.ipv4.sin_addr.s_addr == - address.m_address.ipv4.sin_addr.s_addr; + return m_address.ipv4.s_addr == other.m_address.ipv4.s_addr; } if (m_addr_family == AF_INET6) { - return memcmp(m_address.ipv6.sin6_addr.s6_addr, - address.m_address.ipv6.sin6_addr.s6_addr, 16) == 0; + return memcmp(m_address.ipv6.s6_addr, + other.m_address.ipv6.s6_addr, 16) == 0; } return false; } -bool Address::operator!=(const Address &address) -{ - return !(*this == address); -} - void Address::Resolve(const char *name) { if (!name || name[0] == 0) { - if (m_addr_family == AF_INET) { - setAddress((u32)0); - } else if (m_addr_family == AF_INET6) { - setAddress((IPv6AddressBytes *)0); - } + if (m_addr_family == AF_INET) + setAddress(static_cast<u32>(0)); + else if (m_addr_family == AF_INET6) + setAddress(static_cast<IPv6AddressBytes*>(nullptr)); return; } @@ -126,9 +119,6 @@ void Address::Resolve(const char *name) memset(&hints, 0, sizeof(hints)); // Setup hints - hints.ai_socktype = 0; - hints.ai_protocol = 0; - hints.ai_flags = 0; if (g_settings->getBool("enable_ipv6")) { // AF_UNSPEC allows both IPv6 and IPv4 addresses to be returned hints.ai_family = AF_UNSPEC; @@ -145,14 +135,13 @@ void Address::Resolve(const char *name) if (resolved->ai_family == AF_INET) { struct sockaddr_in *t = (struct sockaddr_in *)resolved->ai_addr; m_addr_family = AF_INET; - m_address.ipv4 = *t; + m_address.ipv4 = t->sin_addr; } else if (resolved->ai_family == AF_INET6) { struct sockaddr_in6 *t = (struct sockaddr_in6 *)resolved->ai_addr; m_addr_family = AF_INET6; - m_address.ipv6 = *t; + m_address.ipv6 = t->sin6_addr; } else { - freeaddrinfo(resolved); - throw ResolveError(""); + m_addr_family = 0; } freeaddrinfo(resolved); } @@ -163,47 +152,37 @@ std::string Address::serializeString() const // windows XP doesnt have inet_ntop, maybe use better func #ifdef _WIN32 if (m_addr_family == AF_INET) { - u8 a, b, c, d; - u32 addr; - addr = ntohl(m_address.ipv4.sin_addr.s_addr); - a = (addr & 0xFF000000) >> 24; - b = (addr & 0x00FF0000) >> 16; - c = (addr & 0x0000FF00) >> 8; - d = (addr & 0x000000FF); - return itos(a) + "." + itos(b) + "." + itos(c) + "." + itos(d); + return inet_ntoa(m_address.ipv4); } else if (m_addr_family == AF_INET6) { std::ostringstream os; + os << std::hex; for (int i = 0; i < 16; i += 2) { - u16 section = (m_address.ipv6.sin6_addr.s6_addr[i] << 8) | - (m_address.ipv6.sin6_addr.s6_addr[i + 1]); - os << std::hex << section; + u16 section = (m_address.ipv6.s6_addr[i] << 8) | + (m_address.ipv6.s6_addr[i + 1]); + os << section; if (i < 14) os << ":"; } return os.str(); - } else - return std::string(""); + } else { + return ""; + } #else char str[INET6_ADDRSTRLEN]; - if (inet_ntop(m_addr_family, - (m_addr_family == AF_INET) - ? (void *)&(m_address.ipv4.sin_addr) - : (void *)&(m_address.ipv6.sin6_addr), - str, INET6_ADDRSTRLEN) == NULL) { - return std::string(""); - } - return std::string(str); + if (inet_ntop(m_addr_family, (void*) &m_address, str, sizeof(str)) == nullptr) + return ""; + return str; #endif } -struct sockaddr_in Address::getAddress() const +struct in_addr Address::getAddress() const { - return m_address.ipv4; // NOTE: NO PORT INCLUDED, use getPort() + return m_address.ipv4; } -struct sockaddr_in6 Address::getAddress6() const +struct in6_addr Address::getAddress6() const { - return m_address.ipv6; // NOTE: NO PORT INCLUDED, use getPort() + return m_address.ipv6; } u16 Address::getPort() const @@ -211,52 +190,39 @@ u16 Address::getPort() const return m_port; } -int Address::getFamily() const -{ - return m_addr_family; -} - -bool Address::isIPv6() const -{ - return m_addr_family == AF_INET6; -} - bool Address::isZero() const { if (m_addr_family == AF_INET) { - return m_address.ipv4.sin_addr.s_addr == 0; + return m_address.ipv4.s_addr == 0; } if (m_addr_family == AF_INET6) { static const char zero[16] = {0}; - return memcmp(m_address.ipv6.sin6_addr.s6_addr, zero, 16) == 0; + return memcmp(m_address.ipv6.s6_addr, zero, 16) == 0; } + return false; } void Address::setAddress(u32 address) { m_addr_family = AF_INET; - m_address.ipv4.sin_family = AF_INET; - m_address.ipv4.sin_addr.s_addr = htonl(address); + m_address.ipv4.s_addr = htonl(address); } void Address::setAddress(u8 a, u8 b, u8 c, u8 d) { - m_addr_family = AF_INET; - m_address.ipv4.sin_family = AF_INET; - u32 addr = htonl((a << 24) | (b << 16) | (c << 8) | d); - m_address.ipv4.sin_addr.s_addr = addr; + u32 addr = (a << 24) | (b << 16) | (c << 8) | d; + setAddress(addr); } void Address::setAddress(const IPv6AddressBytes *ipv6_bytes) { m_addr_family = AF_INET6; - m_address.ipv6.sin6_family = AF_INET6; if (ipv6_bytes) - memcpy(m_address.ipv6.sin6_addr.s6_addr, ipv6_bytes->bytes, 16); + memcpy(m_address.ipv6.s6_addr, ipv6_bytes->bytes, 16); else - memset(m_address.ipv6.sin6_addr.s6_addr, 0, 16); + memset(m_address.ipv6.s6_addr, 0, 16); } void Address::setPort(u16 port) @@ -264,27 +230,30 @@ void Address::setPort(u16 port) m_port = port; } -void Address::print(std::ostream *s) const +void Address::print(std::ostream& s) const { if (m_addr_family == AF_INET6) - *s << "[" << serializeString() << "]:" << m_port; + s << "[" << serializeString() << "]:" << m_port; + else if (m_addr_family == AF_INET) + s << serializeString() << ":" << m_port; else - *s << serializeString() << ":" << m_port; + s << "(undefined)"; } bool Address::isLocalhost() const { if (isIPv6()) { - static const unsigned char localhost_bytes[] = { + static const u8 localhost_bytes[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; - static const unsigned char mapped_ipv4_localhost[] = { + static const u8 mapped_ipv4_localhost[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0x7f, 0, 0, 0}; - auto addr = m_address.ipv6.sin6_addr.s6_addr; + auto addr = m_address.ipv6.s6_addr; return memcmp(addr, localhost_bytes, 16) == 0 || memcmp(addr, mapped_ipv4_localhost, 13) == 0; } - return (m_address.ipv4.sin_addr.s_addr & 0xFF) == 0x7f; + auto addr = ntohl(m_address.ipv4.s_addr); + return (addr >> 24) == 0x7f; } diff --git a/src/network/address.h b/src/network/address.h index 4329c84a8..692bf82c5 100644 --- a/src/network/address.h +++ b/src/network/address.h @@ -36,9 +36,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes.h" #include "networkexceptions.h" -class IPv6AddressBytes +struct IPv6AddressBytes { -public: u8 bytes[16]; IPv6AddressBytes() { memset(bytes, 0, 16); } }; @@ -50,30 +49,34 @@ public: Address(u32 address, u16 port); Address(u8 a, u8 b, u8 c, u8 d, u16 port); Address(const IPv6AddressBytes *ipv6_bytes, u16 port); + bool operator==(const Address &address); - bool operator!=(const Address &address); + bool operator!=(const Address &address) { return !(*this == address); } + + struct in_addr getAddress() const; + struct in6_addr getAddress6() const; + u16 getPort() const; + int getFamily() const { return m_addr_family; } + bool isIPv6() const { return m_addr_family == AF_INET6; } + bool isZero() const; + void print(std::ostream &s) const; + std::string serializeString() const; + bool isLocalhost() const; + // Resolve() may throw ResolveError (address is unchanged in this case) void Resolve(const char *name); - struct sockaddr_in getAddress() const; - unsigned short getPort() const; + void setAddress(u32 address); void setAddress(u8 a, u8 b, u8 c, u8 d); void setAddress(const IPv6AddressBytes *ipv6_bytes); - struct sockaddr_in6 getAddress6() const; - int getFamily() const; - bool isIPv6() const; - bool isZero() const; - void setPort(unsigned short port); - void print(std::ostream *s) const; - std::string serializeString() const; - bool isLocalhost() const; + void setPort(u16 port); private: - unsigned int m_addr_family = 0; + unsigned short m_addr_family = 0; union { - struct sockaddr_in ipv4; - struct sockaddr_in6 ipv6; + struct in_addr ipv4; + struct in6_addr ipv6; } m_address; u16 m_port = 0; // Port is separate from sockaddr structures }; diff --git a/src/network/clientopcodes.cpp b/src/network/clientopcodes.cpp index a98a5e7d1..6a78b4652 100644 --- a/src/network/clientopcodes.cpp +++ b/src/network/clientopcodes.cpp @@ -123,6 +123,7 @@ const ToClientCommandHandler toClientCommandTable[TOCLIENT_NUM_MSG_TYPES] = { "TOCLIENT_SRP_BYTES_S_B", TOCLIENT_STATE_NOT_CONNECTED, &Client::handleCommand_SrpBytesSandB }, // 0x60 { "TOCLIENT_FORMSPEC_PREPEND", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_FormspecPrepend }, // 0x61, { "TOCLIENT_MINIMAP_MODES", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_MinimapModes }, // 0x62, + { "TOCLIENT_SET_LIGHTING", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_SetLighting }, // 0x63, }; const static ServerCommandFactory null_command_factory = { "TOSERVER_NULL", 0, false }; diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 6497bb26a..29e3364db 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -45,6 +45,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "tileanimation.h" #include "gettext.h" #include "skyparams.h" +#include <memory> void Client::handleCommand_Deprecated(NetworkPacket* pkt) { @@ -184,7 +185,7 @@ void Client::handleCommand_AccessDenied(NetworkPacket* pkt) m_access_denied_reason = "Unknown"; if (pkt->getCommand() != TOCLIENT_ACCESS_DENIED) { - // 13/03/15 Legacy code from 0.4.12 and lesser but is still used + // Legacy code from 0.4.12 and older but is still used // in some places of the server code if (pkt->getSize() >= 2) { std::wstring wide_reason; @@ -197,14 +198,14 @@ void Client::handleCommand_AccessDenied(NetworkPacket* pkt) if (pkt->getSize() < 1) return; - u8 denyCode = SERVER_ACCESSDENIED_UNEXPECTED_DATA; + u8 denyCode; *pkt >> denyCode; + if (denyCode == SERVER_ACCESSDENIED_SHUTDOWN || denyCode == SERVER_ACCESSDENIED_CRASH) { *pkt >> m_access_denied_reason; - if (m_access_denied_reason.empty()) { + if (m_access_denied_reason.empty()) m_access_denied_reason = accessDeniedStrings[denyCode]; - } u8 reconnect; *pkt >> reconnect; m_access_denied_reconnect = reconnect & 1; @@ -221,9 +222,8 @@ void Client::handleCommand_AccessDenied(NetworkPacket* pkt) // Until then (which may be never), this is outside // of the defined protocol. *pkt >> m_access_denied_reason; - if (m_access_denied_reason.empty()) { + if (m_access_denied_reason.empty()) m_access_denied_reason = "Unknown"; - } } } @@ -918,11 +918,6 @@ 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; } @@ -1269,19 +1264,17 @@ void Client::handleCommand_HudSetSky(NetworkPacket* pkt) } catch (...) {} // Use default skybox settings: - SkyboxDefaults sky_defaults; - SunParams sun = sky_defaults.getSunDefaults(); - MoonParams moon = sky_defaults.getMoonDefaults(); - StarParams stars = sky_defaults.getStarDefaults(); + SunParams sun = SkyboxDefaults::getSunDefaults(); + MoonParams moon = SkyboxDefaults::getMoonDefaults(); + StarParams stars = SkyboxDefaults::getStarDefaults(); // Fix for "regular" skies, as color isn't kept: if (skybox.type == "regular") { - skybox.sky_color = sky_defaults.getSkyColorDefaults(); + skybox.sky_color = SkyboxDefaults::getSkyColorDefaults(); skybox.fog_tint_type = "default"; skybox.fog_moon_tint = video::SColor(255, 255, 255, 255); skybox.fog_sun_tint = video::SColor(255, 255, 255, 255); - } - else { + } else { sun.visible = false; sun.sunrise_visible = false; moon.visible = false; @@ -1431,6 +1424,8 @@ void Client::handleCommand_LocalPlayerAnimations(NetworkPacket* pkt) *pkt >> player->local_animations[2]; *pkt >> player->local_animations[3]; *pkt >> player->local_animation_speed; + + player->last_animation = -1; } void Client::handleCommand_EyeOffset(NetworkPacket* pkt) @@ -1589,7 +1584,7 @@ void Client::handleCommand_MediaPush(NetworkPacket *pkt) m_media_pushed_files.insert(filename); // create a downloader for this file - auto downloader = new SingleMediaDownloader(cached); + auto downloader(std::make_shared<SingleMediaDownloader>(cached)); m_pending_media_downloads.emplace_back(token, downloader); downloader->addFile(filename, raw_hash); for (const auto &baseurl : m_remote_media_servers) @@ -1716,3 +1711,11 @@ void Client::handleCommand_MinimapModes(NetworkPacket *pkt) if (m_minimap) m_minimap->setModeIndex(mode); } + +void Client::handleCommand_SetLighting(NetworkPacket *pkt) +{ + Lighting& lighting = m_env.getLocalPlayer()->getLighting(); + + if (pkt->getRemainingBytes() >= 4) + *pkt >> lighting.shadow_intensity; +} diff --git a/src/network/connection.cpp b/src/network/connection.cpp index a4970954f..6fb676f25 100644 --- a/src/network/connection.cpp +++ b/src/network/connection.cpp @@ -41,39 +41,37 @@ namespace con /* defines used for debugging and profiling */ /******************************************************************************/ #ifdef NDEBUG - #define LOG(a) a #define PROFILE(a) #else - #if 0 - /* this mutex is used to achieve log message consistency */ - std::mutex log_message_mutex; - #define LOG(a) \ - { \ - MutexAutoLock loglock(log_message_mutex); \ - a; \ - } - #else - // Prevent deadlocks until a solution is found after 5.2.0 (TODO) - #define LOG(a) a - #endif - #define PROFILE(a) a #endif +// TODO: Clean this up. +#define LOG(a) a + #define PING_TIMEOUT 5.0 -BufferedPacket makePacket(Address &address, const SharedBuffer<u8> &data, +u16 BufferedPacket::getSeqnum() const +{ + if (size() < BASE_HEADER_SIZE + 3) + return 0; // should never happen + + return readU16(&data[BASE_HEADER_SIZE + 1]); +} + +BufferedPacketPtr makePacket(Address &address, const SharedBuffer<u8> &data, u32 protocol_id, session_t sender_peer_id, u8 channel) { u32 packet_size = data.getSize() + BASE_HEADER_SIZE; - BufferedPacket p(packet_size); - p.address = address; - writeU32(&p.data[0], protocol_id); - writeU16(&p.data[4], sender_peer_id); - writeU8(&p.data[6], channel); + BufferedPacketPtr p(new BufferedPacket(packet_size)); + p->address = address; + + writeU32(&p->data[0], protocol_id); + writeU16(&p->data[4], sender_peer_id); + writeU8(&p->data[6], channel); - memcpy(&p.data[BASE_HEADER_SIZE], *data, data.getSize()); + memcpy(&p->data[BASE_HEADER_SIZE], *data, data.getSize()); return p; } @@ -169,9 +167,8 @@ void ReliablePacketBuffer::print() MutexAutoLock listlock(m_list_mutex); LOG(dout_con<<"Dump of ReliablePacketBuffer:" << std::endl); unsigned int index = 0; - for (BufferedPacket &bufferedPacket : m_list) { - u16 s = readU16(&(bufferedPacket.data[BASE_HEADER_SIZE+1])); - LOG(dout_con<<index<< ":" << s << std::endl); + for (BufferedPacketPtr &packet : m_list) { + LOG(dout_con<<index<< ":" << packet->getSeqnum() << std::endl); index++; } } @@ -188,16 +185,13 @@ u32 ReliablePacketBuffer::size() return m_list.size(); } -RPBSearchResult ReliablePacketBuffer::findPacket(u16 seqnum) +RPBSearchResult ReliablePacketBuffer::findPacketNoLock(u16 seqnum) { - std::list<BufferedPacket>::iterator i = m_list.begin(); - for(; i != m_list.end(); ++i) - { - u16 s = readU16(&(i->data[BASE_HEADER_SIZE+1])); - if (s == seqnum) - break; + for (auto it = m_list.begin(); it != m_list.end(); ++it) { + if ((*it)->getSeqnum() == seqnum) + return it; } - return i; + return m_list.end(); } bool ReliablePacketBuffer::getFirstSeqnum(u16& result) @@ -205,54 +199,54 @@ bool ReliablePacketBuffer::getFirstSeqnum(u16& result) MutexAutoLock listlock(m_list_mutex); if (m_list.empty()) return false; - const BufferedPacket &p = m_list.front(); - result = readU16(&p.data[BASE_HEADER_SIZE + 1]); + result = m_list.front()->getSeqnum(); return true; } -BufferedPacket ReliablePacketBuffer::popFirst() +BufferedPacketPtr ReliablePacketBuffer::popFirst() { MutexAutoLock listlock(m_list_mutex); if (m_list.empty()) throw NotFoundException("Buffer is empty"); - BufferedPacket p = std::move(m_list.front()); + + BufferedPacketPtr p(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.front().data[BASE_HEADER_SIZE + 1]); + m_oldest_non_answered_ack = m_list.front()->getSeqnum(); } return p; } -BufferedPacket ReliablePacketBuffer::popSeqnum(u16 seqnum) +BufferedPacketPtr ReliablePacketBuffer::popSeqnum(u16 seqnum) { MutexAutoLock listlock(m_list_mutex); - RPBSearchResult r = findPacket(seqnum); - if (r == notFound()) { + RPBSearchResult r = findPacketNoLock(seqnum); + if (r == m_list.end()) { LOG(dout_con<<"Sequence number: " << seqnum << " not found in reliable buffer"<<std::endl); throw NotFoundException("seqnum not found in buffer"); } - BufferedPacket p = std::move(*r); + BufferedPacketPtr p(*r); m_list.erase(r); if (m_list.empty()) { m_oldest_non_answered_ack = 0; } else { - m_oldest_non_answered_ack = - readU16(&m_list.front().data[BASE_HEADER_SIZE + 1]); + m_oldest_non_answered_ack = m_list.front()->getSeqnum(); } return p; } -void ReliablePacketBuffer::insert(const BufferedPacket &p, u16 next_expected) +void ReliablePacketBuffer::insert(BufferedPacketPtr &p_ptr, u16 next_expected) { MutexAutoLock listlock(m_list_mutex); - if (p.data.getSize() < BASE_HEADER_SIZE + 3) { + const BufferedPacket &p = *p_ptr; + + if (p.size() < BASE_HEADER_SIZE + 3) { errorstream << "ReliablePacketBuffer::insert(): Invalid data size for " "reliable packet" << std::endl; return; @@ -263,7 +257,7 @@ void ReliablePacketBuffer::insert(const BufferedPacket &p, u16 next_expected) << std::endl; return; } - u16 seqnum = readU16(&p.data[BASE_HEADER_SIZE + 1]); + const u16 seqnum = p.getSeqnum(); if (!seqnum_in_window(seqnum, next_expected, MAX_RELIABLE_WINDOW_SIZE)) { errorstream << "ReliablePacketBuffer::insert(): seqnum is outside of " @@ -280,44 +274,44 @@ void ReliablePacketBuffer::insert(const BufferedPacket &p, u16 next_expected) // Find the right place for the packet and insert it there // If list is empty, just add it - if (m_list.empty()) - { - m_list.push_back(p); + if (m_list.empty()) { + m_list.push_back(p_ptr); m_oldest_non_answered_ack = seqnum; // Done. return; } // Otherwise find the right place - std::list<BufferedPacket>::iterator i = m_list.begin(); + auto it = m_list.begin(); // Find the first packet in the list which has a higher seqnum - u16 s = readU16(&(i->data[BASE_HEADER_SIZE+1])); + u16 s = (*it)->getSeqnum(); /* case seqnum is smaller then next_expected seqnum */ /* this is true e.g. on wrap around */ if (seqnum < next_expected) { - while(((s < seqnum) || (s >= next_expected)) && (i != m_list.end())) { - ++i; - if (i != m_list.end()) - s = readU16(&(i->data[BASE_HEADER_SIZE+1])); + while(((s < seqnum) || (s >= next_expected)) && (it != m_list.end())) { + ++it; + if (it != m_list.end()) + s = (*it)->getSeqnum(); } } /* non wrap around case (at least for incoming and next_expected */ else { - while(((s < seqnum) && (s >= next_expected)) && (i != m_list.end())) { - ++i; - if (i != m_list.end()) - s = readU16(&(i->data[BASE_HEADER_SIZE+1])); + while(((s < seqnum) && (s >= next_expected)) && (it != m_list.end())) { + ++it; + if (it != m_list.end()) + s = (*it)->getSeqnum(); } } if (s == seqnum) { /* nothing to do this seems to be a resent packet */ /* for paranoia reason data should be compared */ + auto &i = *it; if ( - (readU16(&(i->data[BASE_HEADER_SIZE+1])) != seqnum) || - (i->data.getSize() != p.data.getSize()) || + (i->getSeqnum() != seqnum) || + (i->size() != p.size()) || (i->address != p.address) ) { @@ -325,51 +319,52 @@ void ReliablePacketBuffer::insert(const BufferedPacket &p, u16 next_expected) fprintf(stderr, "Duplicated seqnum %d non matching packet detected:\n", seqnum); - fprintf(stderr, "Old: seqnum: %05d size: %04d, address: %s\n", - readU16(&(i->data[BASE_HEADER_SIZE+1])),i->data.getSize(), + fprintf(stderr, "Old: seqnum: %05d size: %04zu, address: %s\n", + i->getSeqnum(), i->size(), i->address.serializeString().c_str()); - fprintf(stderr, "New: seqnum: %05d size: %04u, address: %s\n", - readU16(&(p.data[BASE_HEADER_SIZE+1])),p.data.getSize(), + fprintf(stderr, "New: seqnum: %05d size: %04zu, address: %s\n", + p.getSeqnum(), p.size(), p.address.serializeString().c_str()); throw IncomingDataCorruption("duplicated packet isn't same as original one"); } } /* insert or push back */ - else if (i != m_list.end()) { - m_list.insert(i, p); + else if (it != m_list.end()) { + m_list.insert(it, p_ptr); } else { - m_list.push_back(p); + m_list.push_back(p_ptr); } /* update last packet number */ - m_oldest_non_answered_ack = readU16(&m_list.front().data[BASE_HEADER_SIZE+1]); + m_oldest_non_answered_ack = m_list.front()->getSeqnum(); } void ReliablePacketBuffer::incrementTimeouts(float dtime) { MutexAutoLock listlock(m_list_mutex); - for (BufferedPacket &bufferedPacket : m_list) { - bufferedPacket.time += dtime; - bufferedPacket.totaltime += dtime; + for (auto &packet : m_list) { + packet->time += dtime; + packet->totaltime += dtime; } } -std::list<BufferedPacket> +std::list<ConstSharedPtr<BufferedPacket>> ReliablePacketBuffer::getTimedOuts(float timeout, u32 max_packets) { MutexAutoLock listlock(m_list_mutex); - std::list<BufferedPacket> 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++; + std::list<ConstSharedPtr<BufferedPacket>> timed_outs; + for (auto &packet : m_list) { + if (packet->time < timeout) + continue; - timed_outs.push_back(bufferedPacket); + // caller will resend packet so reset time and increase counter + packet->time = 0.0f; + packet->resend_count++; - if (timed_outs.size() >= max_packets) - break; - } + timed_outs.emplace_back(packet); + + if (timed_outs.size() >= max_packets) + break; } return timed_outs; } @@ -428,11 +423,13 @@ IncomingSplitBuffer::~IncomingSplitBuffer() } } -SharedBuffer<u8> IncomingSplitBuffer::insert(const BufferedPacket &p, bool reliable) +SharedBuffer<u8> IncomingSplitBuffer::insert(BufferedPacketPtr &p_ptr, bool reliable) { MutexAutoLock listlock(m_map_mutex); + const BufferedPacket &p = *p_ptr; + u32 headersize = BASE_HEADER_SIZE + 7; - if (p.data.getSize() < headersize) { + if (p.size() < headersize) { errorstream << "Invalid data size for split packet" << std::endl; return SharedBuffer<u8>(); } @@ -473,7 +470,7 @@ SharedBuffer<u8> IncomingSplitBuffer::insert(const BufferedPacket &p, bool relia <<std::endl); // Cut chunk data out of packet - u32 chunkdatasize = p.data.getSize() - headersize; + u32 chunkdatasize = p.size() - headersize; SharedBuffer<u8> chunkdata(chunkdatasize); memcpy(*chunkdata, &(p.data[headersize]), chunkdatasize); @@ -520,14 +517,67 @@ void IncomingSplitBuffer::removeUnreliableTimedOuts(float dtime, float timeout) ConnectionCommand */ -void ConnectionCommand::send(session_t peer_id_, u8 channelnum_, NetworkPacket *pkt, - bool reliable_) +ConnectionCommandPtr ConnectionCommand::create(ConnectionCommandType type) +{ + return ConnectionCommandPtr(new ConnectionCommand(type)); +} + +ConnectionCommandPtr ConnectionCommand::serve(Address address) { - type = CONNCMD_SEND; - peer_id = peer_id_; - channelnum = channelnum_; - data = pkt->oldForgePacket(); - reliable = reliable_; + auto c = create(CONNCMD_SERVE); + c->address = address; + return c; +} + +ConnectionCommandPtr ConnectionCommand::connect(Address address) +{ + auto c = create(CONNCMD_CONNECT); + c->address = address; + return c; +} + +ConnectionCommandPtr ConnectionCommand::disconnect() +{ + return create(CONNCMD_DISCONNECT); +} + +ConnectionCommandPtr ConnectionCommand::disconnect_peer(session_t peer_id) +{ + auto c = create(CONNCMD_DISCONNECT_PEER); + c->peer_id = peer_id; + return c; +} + +ConnectionCommandPtr ConnectionCommand::send(session_t peer_id, u8 channelnum, + NetworkPacket *pkt, bool reliable) +{ + auto c = create(CONNCMD_SEND); + c->peer_id = peer_id; + c->channelnum = channelnum; + c->reliable = reliable; + c->data = pkt->oldForgePacket(); + return c; +} + +ConnectionCommandPtr ConnectionCommand::ack(session_t peer_id, u8 channelnum, const Buffer<u8> &data) +{ + auto c = create(CONCMD_ACK); + c->peer_id = peer_id; + c->channelnum = channelnum; + c->reliable = false; + data.copyTo(c->data); + return c; +} + +ConnectionCommandPtr ConnectionCommand::createPeer(session_t peer_id, const Buffer<u8> &data) +{ + auto c = create(CONCMD_CREATE_PEER); + c->peer_id = peer_id; + c->channelnum = 0; + c->reliable = true; + c->raw = true; + data.copyTo(c->data); + return c; } /* @@ -562,39 +612,38 @@ void Channel::setNextSplitSeqNum(u16 seqnum) u16 Channel::getOutgoingSequenceNumber(bool& successful) { MutexAutoLock internal(m_internal_mutex); + u16 retval = next_outgoing_seqnum; - u16 lowest_unacked_seqnumber; + successful = false; /* shortcut if there ain't any packet in outgoing list */ - if (outgoing_reliables_sent.empty()) - { + if (outgoing_reliables_sent.empty()) { + successful = true; next_outgoing_seqnum++; return retval; } - if (outgoing_reliables_sent.getFirstSeqnum(lowest_unacked_seqnumber)) - { + u16 lowest_unacked_seqnumber; + if (outgoing_reliables_sent.getFirstSeqnum(lowest_unacked_seqnumber)) { if (lowest_unacked_seqnumber < next_outgoing_seqnum) { // ugly cast but this one is required in order to tell compiler we // know about difference of two unsigned may be negative in general // but we already made sure it won't happen in this case - if (((u16)(next_outgoing_seqnum - lowest_unacked_seqnumber)) > window_size) { - successful = false; + if (((u16)(next_outgoing_seqnum - lowest_unacked_seqnumber)) > m_window_size) { return 0; } - } - else { + } else { // ugly cast but this one is required in order to tell compiler we // know about difference of two unsigned may be negative in general // but we already made sure it won't happen in this case if ((next_outgoing_seqnum + (u16)(SEQNUM_MAX - lowest_unacked_seqnumber)) > - window_size) { - successful = false; + m_window_size) { return 0; } } } + successful = true; next_outgoing_seqnum++; return retval; } @@ -666,7 +715,7 @@ void Channel::UpdateTimers(float dtime) //packet_too_late = current_packet_too_late; packets_successful = current_packet_successful; - if (current_bytes_transfered > (unsigned int) (window_size*512/2)) { + if (current_bytes_transfered > (unsigned int) (m_window_size*512/2)) { reasonable_amount_of_data_transmitted = true; } current_packet_loss = 0; @@ -681,37 +730,25 @@ void Channel::UpdateTimers(float dtime) if (packets_successful > 0) { successful_to_lost_ratio = packet_loss/packets_successful; } else if (packet_loss > 0) { - window_size = std::max( - (window_size - 10), - MIN_RELIABLE_WINDOW_SIZE); + setWindowSize(m_window_size - 10); done = true; } if (!done) { - if ((successful_to_lost_ratio < 0.01f) && - (window_size < MAX_RELIABLE_WINDOW_SIZE)) { + if (successful_to_lost_ratio < 0.01f) { /* don't even think about increasing if we didn't even * use major parts of our window */ if (reasonable_amount_of_data_transmitted) - window_size = std::min( - (window_size + 100), - MAX_RELIABLE_WINDOW_SIZE); - } else if ((successful_to_lost_ratio < 0.05f) && - (window_size < MAX_RELIABLE_WINDOW_SIZE)) { + setWindowSize(m_window_size + 100); + } else if (successful_to_lost_ratio < 0.05f) { /* don't even think about increasing if we didn't even * use major parts of our window */ if (reasonable_amount_of_data_transmitted) - window_size = std::min( - (window_size + 50), - MAX_RELIABLE_WINDOW_SIZE); + setWindowSize(m_window_size + 50); } else if (successful_to_lost_ratio > 0.15f) { - window_size = std::max( - (window_size - 100), - MIN_RELIABLE_WINDOW_SIZE); + setWindowSize(m_window_size - 100); } else if (successful_to_lost_ratio > 0.1f) { - window_size = std::max( - (window_size - 50), - MIN_RELIABLE_WINDOW_SIZE); + setWindowSize(m_window_size - 50); } } } @@ -958,45 +995,45 @@ bool UDPPeer::Ping(float dtime,SharedBuffer<u8>& data) return false; } -void UDPPeer::PutReliableSendCommand(ConnectionCommand &c, +void UDPPeer::PutReliableSendCommand(ConnectionCommandPtr &c, unsigned int max_packet_size) { if (m_pending_disconnect) return; - Channel &chan = channels[c.channelnum]; + Channel &chan = channels[c->channelnum]; if (chan.queued_commands.empty() && /* don't queue more packets then window size */ - (chan.queued_reliables.size() < chan.getWindowSize() / 2)) { + (chan.queued_reliables.size() + 1 < chan.getWindowSize() / 2)) { LOG(dout_con<<m_connection->getDesc() - <<" processing reliable command for peer id: " << c.peer_id - <<" data size: " << c.data.getSize() << std::endl); - if (!processReliableSendCommand(c,max_packet_size)) { - chan.queued_commands.push_back(c); - } - } - else { + <<" processing reliable command for peer id: " << c->peer_id + <<" data size: " << c->data.getSize() << std::endl); + if (processReliableSendCommand(c, max_packet_size)) + return; + } else { LOG(dout_con<<m_connection->getDesc() - <<" Queueing reliable command for peer id: " << c.peer_id - <<" data size: " << c.data.getSize() <<std::endl); - chan.queued_commands.push_back(c); - if (chan.queued_commands.size() >= chan.getWindowSize() / 2) { + <<" Queueing reliable command for peer id: " << c->peer_id + <<" data size: " << c->data.getSize() <<std::endl); + + if (chan.queued_commands.size() + 1 >= chan.getWindowSize() / 2) { LOG(derr_con << m_connection->getDesc() - << "Possible packet stall to peer id: " << c.peer_id + << "Possible packet stall to peer id: " << c->peer_id << " queued_commands=" << chan.queued_commands.size() << std::endl); } } + chan.queued_commands.push_back(c); } bool UDPPeer::processReliableSendCommand( - ConnectionCommand &c, + ConnectionCommandPtr &c_ptr, unsigned int max_packet_size) { if (m_pending_disconnect) return true; + const auto &c = *c_ptr; Channel &chan = channels[c.channelnum]; u32 chunksize_max = max_packet_size @@ -1015,9 +1052,9 @@ bool UDPPeer::processReliableSendCommand( chan.setNextSplitSeqNum(split_sequence_number); } - bool have_sequence_number = true; + bool have_sequence_number = false; bool have_initial_sequence_number = false; - std::queue<BufferedPacket> toadd; + std::queue<BufferedPacketPtr> toadd; volatile u16 initial_sequence_number = 0; for (SharedBuffer<u8> &original : originals) { @@ -1036,25 +1073,23 @@ bool UDPPeer::processReliableSendCommand( SharedBuffer<u8> reliable = makeReliablePacket(original, seqnum); // Add base headers and make a packet - BufferedPacket p = con::makePacket(address, reliable, + BufferedPacketPtr p = con::makePacket(address, reliable, m_connection->GetProtocolID(), m_connection->GetPeerID(), c.channelnum); - toadd.push(std::move(p)); + toadd.push(p); } if (have_sequence_number) { - volatile u16 pcount = 0; while (!toadd.empty()) { - BufferedPacket p = std::move(toadd.front()); + BufferedPacketPtr p = toadd.front(); toadd.pop(); // LOG(dout_con<<connection->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(std::move(p)); - pcount++; + chan.queued_reliables.push(p); } sanity_check(chan.queued_reliables.size() < 0xFFFF); return true; @@ -1063,6 +1098,7 @@ bool UDPPeer::processReliableSendCommand( volatile u16 packets_available = toadd.size(); /* we didn't get a single sequence number no need to fill queue */ if (!have_initial_sequence_number) { + LOG(derr_con << m_connection->getDesc() << "Ran out of sequence numbers!" << std::endl); return false; } @@ -1108,18 +1144,18 @@ void UDPPeer::RunCommandQueues( (channel.queued_reliables.size() < maxtransfer) && (commands_processed < maxcommands)) { try { - ConnectionCommand c = channel.queued_commands.front(); + ConnectionCommandPtr c = channel.queued_commands.front(); LOG(dout_con << m_connection->getDesc() << " processing queued reliable command " << std::endl); // Packet is processed, remove it from queue - if (processReliableSendCommand(c,max_packet_size)) { + if (processReliableSendCommand(c, max_packet_size)) { channel.queued_commands.pop_front(); } else { LOG(dout_con << m_connection->getDesc() - << " Failed to queue packets for peer_id: " << c.peer_id - << ", delaying sending of " << c.data.getSize() + << " Failed to queue packets for peer_id: " << c->peer_id + << ", delaying sending of " << c->data.getSize() << " bytes" << std::endl); } } @@ -1142,7 +1178,7 @@ void UDPPeer::setNextSplitSequenceNumber(u8 channel, u16 seqnum) channels[channel].setNextSplitSeqNum(seqnum); } -SharedBuffer<u8> UDPPeer::addSplitPacket(u8 channel, const BufferedPacket &toadd, +SharedBuffer<u8> UDPPeer::addSplitPacket(u8 channel, BufferedPacketPtr &toadd, bool reliable) { assert(channel < CHANNEL_COUNT); // Pre-condition @@ -1150,6 +1186,63 @@ SharedBuffer<u8> UDPPeer::addSplitPacket(u8 channel, const BufferedPacket &toadd } /* + ConnectionEvent +*/ + +const char *ConnectionEvent::describe() const +{ + switch(type) { + case CONNEVENT_NONE: + return "CONNEVENT_NONE"; + case CONNEVENT_DATA_RECEIVED: + return "CONNEVENT_DATA_RECEIVED"; + case CONNEVENT_PEER_ADDED: + return "CONNEVENT_PEER_ADDED"; + case CONNEVENT_PEER_REMOVED: + return "CONNEVENT_PEER_REMOVED"; + case CONNEVENT_BIND_FAILED: + return "CONNEVENT_BIND_FAILED"; + } + return "Invalid ConnectionEvent"; +} + + +ConnectionEventPtr ConnectionEvent::create(ConnectionEventType type) +{ + return std::shared_ptr<ConnectionEvent>(new ConnectionEvent(type)); +} + +ConnectionEventPtr ConnectionEvent::dataReceived(session_t peer_id, const Buffer<u8> &data) +{ + auto e = create(CONNEVENT_DATA_RECEIVED); + e->peer_id = peer_id; + data.copyTo(e->data); + return e; +} + +ConnectionEventPtr ConnectionEvent::peerAdded(session_t peer_id, Address address) +{ + auto e = create(CONNEVENT_PEER_ADDED); + e->peer_id = peer_id; + e->address = address; + return e; +} + +ConnectionEventPtr ConnectionEvent::peerRemoved(session_t peer_id, bool is_timeout, Address address) +{ + auto e = create(CONNEVENT_PEER_REMOVED); + e->peer_id = peer_id; + e->timeout = is_timeout; + e->address = address; + return e; +} + +ConnectionEventPtr ConnectionEvent::bindFailed() +{ + return create(CONNEVENT_BIND_FAILED); +} + +/* Connection */ @@ -1198,18 +1291,12 @@ Connection::~Connection() /* Internal stuff */ -void Connection::putEvent(const ConnectionEvent &e) +void Connection::putEvent(ConnectionEventPtr e) { - assert(e.type != CONNEVENT_NONE); // Pre-condition + 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(); @@ -1272,11 +1359,9 @@ bool Connection::deletePeer(session_t peer_id, bool timeout) Address peer_address; //any peer has a primary address this never fails! peer->getAddress(MTP_PRIMARY, peer_address); - // Create event - ConnectionEvent e; - e.peerRemoved(peer_id, timeout, peer_address); - putEvent(e); + // Create event + putEvent(ConnectionEvent::peerRemoved(peer_id, timeout, peer_address)); peer->Drop(); return true; @@ -1284,18 +1369,16 @@ bool Connection::deletePeer(session_t peer_id, bool timeout) /* Interface */ -ConnectionEvent Connection::waitEvent(u32 timeout_ms) +ConnectionEventPtr Connection::waitEvent(u32 timeout_ms) { try { return m_event_queue.pop_front(timeout_ms); } catch(ItemNotFoundException &ex) { - ConnectionEvent e; - e.type = CONNEVENT_NONE; - return e; + return ConnectionEvent::create(CONNEVENT_NONE); } } -void Connection::putCommand(const ConnectionCommand &c) +void Connection::putCommand(ConnectionCommandPtr c) { if (!m_shutting_down) { m_command_queue.push_back(c); @@ -1303,26 +1386,14 @@ void Connection::putCommand(const 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; - c.serve(bind_addr); - putCommand(c); + putCommand(ConnectionCommand::serve(bind_addr)); } void Connection::Connect(Address address) { - ConnectionCommand c; - c.connect(address); - putCommand(c); + putCommand(ConnectionCommand::connect(address)); } bool Connection::Connected() @@ -1344,9 +1415,7 @@ bool Connection::Connected() void Connection::Disconnect() { - ConnectionCommand c; - c.disconnect(); - putCommand(c); + putCommand(ConnectionCommand::disconnect()); } bool Connection::Receive(NetworkPacket *pkt, u32 timeout) @@ -1357,11 +1426,15 @@ bool Connection::Receive(NetworkPacket *pkt, u32 timeout) This is not considered to be a problem (is it?) */ for(;;) { - ConnectionEvent e = waitEvent(timeout); - if (e.type != CONNEVENT_NONE) + ConnectionEventPtr e_ptr = waitEvent(timeout); + const ConnectionEvent &e = *e_ptr; + + if (e.type != CONNEVENT_NONE) { LOG(dout_con << getDesc() << ": Receive: got event: " << e.describe() << std::endl); - switch(e.type) { + } + + switch (e.type) { case CONNEVENT_NONE: return false; case CONNEVENT_DATA_RECEIVED: @@ -1409,10 +1482,7 @@ void Connection::Send(session_t peer_id, u8 channelnum, { assert(channelnum < CHANNEL_COUNT); // Pre-condition - ConnectionCommand c; - - c.send(peer_id, channelnum, pkt, reliable); - putCommand(std::move(c)); + putCommand(ConnectionCommand::send(peer_id, channelnum, pkt, reliable)); } Address Connection::GetPeerAddress(session_t peer_id) @@ -1511,41 +1581,31 @@ u16 Connection::createPeer(Address& sender, MTProtocols protocol, int fd) LOG(dout_con << getDesc() << "createPeer(): giving peer_id=" << peer_id_new << std::endl); - ConnectionCommand cmd; - Buffer<u8> 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(std::move(cmd)); + { + Buffer<u8> reply(4); + writeU8(&reply[0], PACKET_TYPE_CONTROL); + writeU8(&reply[1], CONTROLTYPE_SET_PEER_ID); + writeU16(&reply[2], peer_id_new); + putCommand(ConnectionCommand::createPeer(peer_id_new, reply)); + } // Create peer addition event - ConnectionEvent e; - e.peerAdded(peer_id_new, sender); - putEvent(e); + putEvent(ConnectionEvent::peerAdded(peer_id_new, sender)); // We're now talking to a valid peer_id return peer_id_new; } -void Connection::PrintInfo(std::ostream &out) -{ - m_info_mutex.lock(); - out<<getDesc()<<": "; - m_info_mutex.unlock(); -} - const std::string Connection::getDesc() { + MutexAutoLock _(m_info_mutex); return std::string("con(")+ itos(m_udpSocket.GetHandle())+"/"+itos(m_peer_id)+")"; } void Connection::DisconnectPeer(session_t peer_id) { - ConnectionCommand discon; - discon.disconnect_peer(peer_id); - putCommand(discon); + putCommand(ConnectionCommand::disconnect_peer(peer_id)); } void Connection::sendAck(session_t peer_id, u8 channelnum, u16 seqnum) @@ -1557,14 +1617,12 @@ void Connection::sendAck(session_t peer_id, u8 channelnum, u16 seqnum) " channel: " << (channelnum & 0xFF) << " seqnum: " << seqnum << std::endl); - ConnectionCommand c; SharedBuffer<u8> ack(4); writeU8(&ack[0], PACKET_TYPE_CONTROL); writeU8(&ack[1], CONTROLTYPE_ACK); writeU16(&ack[2], seqnum); - c.ack(peer_id, channelnum, ack); - putCommand(std::move(c)); + putCommand(ConnectionCommand::ack(peer_id, channelnum, ack)); m_sendThread->Trigger(); } diff --git a/src/network/connection.h b/src/network/connection.h index 49bb65c3e..b5ae24882 100644 --- a/src/network/connection.h +++ b/src/network/connection.h @@ -32,6 +32,95 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <vector> #include <map> +#define MAX_UDP_PEERS 65535 + +/* +=== NOTES === + +A packet is sent through a channel to a peer with a basic header: + Header (7 bytes): + [0] u32 protocol_id + [4] session_t sender_peer_id + [6] u8 channel +sender_peer_id: + Unique to each peer. + value 0 (PEER_ID_INEXISTENT) is reserved for making new connections + value 1 (PEER_ID_SERVER) is reserved for server + these constants are defined in constants.h +channel: + Channel numbers have no intrinsic meaning. Currently only 0, 1, 2 exist. +*/ +#define BASE_HEADER_SIZE 7 +#define CHANNEL_COUNT 3 + +/* +Packet types: + +CONTROL: This is a packet used by the protocol. +- When this is processed, nothing is handed to the user. + Header (2 byte): + [0] u8 type + [1] u8 controltype +controltype and data description: + CONTROLTYPE_ACK + [2] u16 seqnum + CONTROLTYPE_SET_PEER_ID + [2] session_t peer_id_new + CONTROLTYPE_PING + - There is no actual reply, but this can be sent in a reliable + packet to get a reply + CONTROLTYPE_DISCO +*/ +enum ControlType : u8 { + CONTROLTYPE_ACK = 0, + CONTROLTYPE_SET_PEER_ID = 1, + CONTROLTYPE_PING = 2, + CONTROLTYPE_DISCO = 3, +}; + +/* +ORIGINAL: This is a plain packet with no control and no error +checking at all. +- When this is processed, it is directly handed to the user. + Header (1 byte): + [0] u8 type +*/ +//#define TYPE_ORIGINAL 1 +#define ORIGINAL_HEADER_SIZE 1 + +/* +SPLIT: These are sequences of packets forming one bigger piece of +data. +- When processed and all the packet_nums 0...packet_count-1 are + present (this should be buffered), the resulting data shall be + directly handed to the user. +- If the data fails to come up in a reasonable time, the buffer shall + be silently discarded. +- These can be sent as-is or atop of a RELIABLE packet stream. + Header (7 bytes): + [0] u8 type + [1] u16 seqnum + [3] u16 chunk_count + [5] u16 chunk_num +*/ +//#define TYPE_SPLIT 2 + +/* +RELIABLE: Delivery of all RELIABLE packets shall be forced by ACKs, +and they shall be delivered in the same order as sent. This is done +with a buffer in the receiving and transmitting end. +- When this is processed, the contents of each packet is recursively + processed as packets. + Header (3 bytes): + [0] u8 type + [1] u16 seqnum + +*/ +//#define TYPE_RELIABLE 3 +#define RELIABLE_HEADER_SIZE 3 +#define SEQNUM_INITIAL 65500 +#define SEQNUM_MAX 65535 + class NetworkPacket; namespace con @@ -46,9 +135,13 @@ typedef enum MTProtocols { MTP_MINETEST_RELIABLE_UDP } MTProtocols; -#define MAX_UDP_PEERS 65535 - -#define SEQNUM_MAX 65535 +enum PacketType : u8 { + PACKET_TYPE_CONTROL = 0, + PACKET_TYPE_ORIGINAL = 1, + PACKET_TYPE_SPLIT = 2, + PACKET_TYPE_RELIABLE = 3, + PACKET_TYPE_MAX +}; inline bool seqnum_higher(u16 totest, u16 base) { @@ -85,24 +178,40 @@ static inline float CALC_DTIME(u64 lasttime, u64 curtime) return MYMAX(MYMIN(value,0.1),0.0); } -struct BufferedPacket -{ - BufferedPacket(u8 *a_data, u32 a_size): - data(a_data, a_size) - {} - BufferedPacket(u32 a_size): - data(a_size) - {} - Buffer<u8> data; // Data of the packet, including headers +/* + Struct for all kinds of packets. Includes following data: + BASE_HEADER + u8[] packet data (usually copied from SharedBuffer<u8>) +*/ +struct BufferedPacket { + BufferedPacket(u32 a_size) + { + m_data.resize(a_size); + data = &m_data[0]; + } + + DISABLE_CLASS_COPY(BufferedPacket) + + u16 getSeqnum() const; + + inline size_t size() const { return m_data.size(); } + + u8 *data; // Direct memory access float time = 0.0f; // Seconds from buffering the packet or re-sending float totaltime = 0.0f; // Seconds from buffering the packet u64 absolute_send_time = -1; Address address; // Sender or destination unsigned int resend_count = 0; + +private: + std::vector<u8> m_data; // Data of the packet, including headers }; +typedef std::shared_ptr<BufferedPacket> BufferedPacketPtr; + + // This adds the base headers to the data and makes a packet out of it -BufferedPacket makePacket(Address &address, const SharedBuffer<u8> &data, +BufferedPacketPtr makePacket(Address &address, const SharedBuffer<u8> &data, u32 protocol_id, session_t sender_peer_id, u8 channel); // Depending on size, make a TYPE_ORIGINAL or TYPE_SPLIT packet @@ -137,100 +246,11 @@ private: }; /* -=== NOTES === - -A packet is sent through a channel to a peer with a basic header: - Header (7 bytes): - [0] u32 protocol_id - [4] session_t sender_peer_id - [6] u8 channel -sender_peer_id: - Unique to each peer. - value 0 (PEER_ID_INEXISTENT) is reserved for making new connections - value 1 (PEER_ID_SERVER) is reserved for server - these constants are defined in constants.h -channel: - Channel numbers have no intrinsic meaning. Currently only 0, 1, 2 exist. -*/ -#define BASE_HEADER_SIZE 7 -#define CHANNEL_COUNT 3 -/* -Packet types: - -CONTROL: This is a packet used by the protocol. -- When this is processed, nothing is handed to the user. - Header (2 byte): - [0] u8 type - [1] u8 controltype -controltype and data description: - CONTROLTYPE_ACK - [2] u16 seqnum - CONTROLTYPE_SET_PEER_ID - [2] session_t peer_id_new - CONTROLTYPE_PING - - There is no actual reply, but this can be sent in a reliable - packet to get a reply - CONTROLTYPE_DISCO -*/ -//#define TYPE_CONTROL 0 -#define CONTROLTYPE_ACK 0 -#define CONTROLTYPE_SET_PEER_ID 1 -#define CONTROLTYPE_PING 2 -#define CONTROLTYPE_DISCO 3 - -/* -ORIGINAL: This is a plain packet with no control and no error -checking at all. -- When this is processed, it is directly handed to the user. - Header (1 byte): - [0] u8 type -*/ -//#define TYPE_ORIGINAL 1 -#define ORIGINAL_HEADER_SIZE 1 -/* -SPLIT: These are sequences of packets forming one bigger piece of -data. -- When processed and all the packet_nums 0...packet_count-1 are - present (this should be buffered), the resulting data shall be - directly handed to the user. -- If the data fails to come up in a reasonable time, the buffer shall - be silently discarded. -- These can be sent as-is or atop of a RELIABLE packet stream. - Header (7 bytes): - [0] u8 type - [1] u16 seqnum - [3] u16 chunk_count - [5] u16 chunk_num -*/ -//#define TYPE_SPLIT 2 -/* -RELIABLE: Delivery of all RELIABLE packets shall be forced by ACKs, -and they shall be delivered in the same order as sent. This is done -with a buffer in the receiving and transmitting end. -- When this is processed, the contents of each packet is recursively - processed as packets. - Header (3 bytes): - [0] u8 type - [1] u16 seqnum - -*/ -//#define TYPE_RELIABLE 3 -#define RELIABLE_HEADER_SIZE 3 -#define SEQNUM_INITIAL 65500 - -enum PacketType: u8 { - PACKET_TYPE_CONTROL = 0, - PACKET_TYPE_ORIGINAL = 1, - PACKET_TYPE_SPLIT = 2, - PACKET_TYPE_RELIABLE = 3, - PACKET_TYPE_MAX -}; -/* A buffer which stores reliable packets and sorts them internally for fast access to the smallest one. */ -typedef std::list<BufferedPacket>::iterator RPBSearchResult; +typedef std::list<BufferedPacketPtr>::iterator RPBSearchResult; class ReliablePacketBuffer { @@ -239,12 +259,12 @@ public: bool getFirstSeqnum(u16& result); - BufferedPacket popFirst(); - BufferedPacket popSeqnum(u16 seqnum); - void insert(const BufferedPacket &p, u16 next_expected); + BufferedPacketPtr popFirst(); + BufferedPacketPtr popSeqnum(u16 seqnum); + void insert(BufferedPacketPtr &p_ptr, u16 next_expected); void incrementTimeouts(float dtime); - std::list<BufferedPacket> getTimedOuts(float timeout, u32 max_packets); + std::list<ConstSharedPtr<BufferedPacket>> getTimedOuts(float timeout, u32 max_packets); void print(); bool empty(); @@ -252,10 +272,9 @@ public: private: - RPBSearchResult findPacket(u16 seqnum); // does not perform locking - inline RPBSearchResult notFound() { return m_list.end(); } + RPBSearchResult findPacketNoLock(u16 seqnum); - std::list<BufferedPacket> m_list; + std::list<BufferedPacketPtr> m_list; u16 m_oldest_non_answered_ack; @@ -274,7 +293,7 @@ public: Returns a reference counted buffer of length != 0 when a full split packet is constructed. If not, returns one of length 0. */ - SharedBuffer<u8> insert(const BufferedPacket &p, bool reliable); + SharedBuffer<u8> insert(BufferedPacketPtr &p_ptr, bool reliable); void removeUnreliableTimedOuts(float dtime, float timeout); @@ -285,25 +304,6 @@ private: std::mutex m_map_mutex; }; -struct OutgoingPacket -{ - session_t peer_id; - u8 channelnum; - SharedBuffer<u8> data; - bool reliable; - bool ack; - - OutgoingPacket(session_t peer_id_, u8 channelnum_, const SharedBuffer<u8> &data_, - bool reliable_,bool ack_=false): - peer_id(peer_id_), - channelnum(channelnum_), - data(data_), - reliable(reliable_), - ack(ack_) - { - } -}; - enum ConnectionCommandType{ CONNCMD_NONE, CONNCMD_SERVE, @@ -316,9 +316,13 @@ enum ConnectionCommandType{ CONCMD_CREATE_PEER }; +struct ConnectionCommand; +typedef std::shared_ptr<ConnectionCommand> ConnectionCommandPtr; + +// This is very similar to ConnectionEvent struct ConnectionCommand { - enum ConnectionCommandType type = CONNCMD_NONE; + const ConnectionCommandType type; Address address; session_t peer_id = PEER_ID_INEXISTENT; u8 channelnum = 0; @@ -326,48 +330,21 @@ struct ConnectionCommand bool reliable = false; bool raw = false; - ConnectionCommand() = default; + DISABLE_CLASS_COPY(ConnectionCommand); - void serve(Address address_) - { - type = CONNCMD_SERVE; - address = address_; - } - void connect(Address address_) - { - type = CONNCMD_CONNECT; - address = address_; - } - void disconnect() - { - type = CONNCMD_DISCONNECT; - } - void disconnect_peer(session_t peer_id_) - { - type = CONNCMD_DISCONNECT_PEER; - peer_id = peer_id_; - } + static ConnectionCommandPtr serve(Address address); + static ConnectionCommandPtr connect(Address address); + static ConnectionCommandPtr disconnect(); + static ConnectionCommandPtr disconnect_peer(session_t peer_id); + static ConnectionCommandPtr send(session_t peer_id, u8 channelnum, NetworkPacket *pkt, bool reliable); + static ConnectionCommandPtr ack(session_t peer_id, u8 channelnum, const Buffer<u8> &data); + static ConnectionCommandPtr createPeer(session_t peer_id, const Buffer<u8> &data); - void send(session_t peer_id_, u8 channelnum_, NetworkPacket *pkt, bool reliable_); - - void ack(session_t peer_id_, u8 channelnum_, const Buffer<u8> &data_) - { - type = CONCMD_ACK; - peer_id = peer_id_; - channelnum = channelnum_; - data = data_; - reliable = false; - } +private: + ConnectionCommand(ConnectionCommandType type_) : + type(type_) {} - void createPeer(session_t peer_id_, const Buffer<u8> &data_) - { - type = CONCMD_CREATE_PEER; - peer_id = peer_id_; - data = data_; - channelnum = 0; - reliable = true; - raw = true; - } + static ConnectionCommandPtr create(ConnectionCommandType type); }; /* maximum window size to use, 0xFFFF is theoretical maximum. don't think about @@ -402,10 +379,10 @@ public: ReliablePacketBuffer outgoing_reliables_sent; //queued reliable packets - std::queue<BufferedPacket> queued_reliables; + std::queue<BufferedPacketPtr> queued_reliables; //queue commands prior splitting to packets - std::deque<ConnectionCommand> queued_commands; + std::deque<ConnectionCommandPtr> queued_commands; IncomingSplitBuffer incoming_splits; @@ -420,34 +397,38 @@ public: void UpdateTimers(float dtime); - const float getCurrentDownloadRateKB() + float getCurrentDownloadRateKB() { MutexAutoLock lock(m_internal_mutex); return cur_kbps; }; - const float getMaxDownloadRateKB() + float getMaxDownloadRateKB() { MutexAutoLock lock(m_internal_mutex); return max_kbps; }; - const float getCurrentLossRateKB() + float getCurrentLossRateKB() { MutexAutoLock lock(m_internal_mutex); return cur_kbps_lost; }; - const float getMaxLossRateKB() + float getMaxLossRateKB() { MutexAutoLock lock(m_internal_mutex); return max_kbps_lost; }; - const float getCurrentIncomingRateKB() + float getCurrentIncomingRateKB() { MutexAutoLock lock(m_internal_mutex); return cur_incoming_kbps; }; - const float getMaxIncomingRateKB() + float getMaxIncomingRateKB() { MutexAutoLock lock(m_internal_mutex); return max_incoming_kbps; }; - const float getAvgDownloadRateKB() + float getAvgDownloadRateKB() { MutexAutoLock lock(m_internal_mutex); return avg_kbps; }; - const float getAvgLossRateKB() + float getAvgLossRateKB() { MutexAutoLock lock(m_internal_mutex); return avg_kbps_lost; }; - const float getAvgIncomingRateKB() + float getAvgIncomingRateKB() { MutexAutoLock lock(m_internal_mutex); return avg_incoming_kbps; }; - const unsigned int getWindowSize() const { return window_size; }; + u16 getWindowSize() const { return m_window_size; }; + + void setWindowSize(long size) + { + m_window_size = (u16)rangelim(size, MIN_RELIABLE_WINDOW_SIZE, MAX_RELIABLE_WINDOW_SIZE); + } - void setWindowSize(unsigned int size) { window_size = size; }; private: std::mutex m_internal_mutex; - int window_size = MIN_RELIABLE_WINDOW_SIZE; + u16 m_window_size = MIN_RELIABLE_WINDOW_SIZE; u16 next_incoming_seqnum = SEQNUM_INITIAL; @@ -510,7 +491,7 @@ class Peer { public: friend class PeerHelper; - Peer(Address address_,u16 id_,Connection* connection) : + Peer(Address address_,session_t id_,Connection* connection) : id(id_), m_connection(connection), address(address_), @@ -524,11 +505,11 @@ class Peer { }; // Unique id of the peer - u16 id; + const session_t id; void Drop(); - virtual void PutReliableSendCommand(ConnectionCommand &c, + virtual void PutReliableSendCommand(ConnectionCommandPtr &c, unsigned int max_packet_size) {}; virtual bool getAddress(MTProtocols type, Address& toset) = 0; @@ -545,7 +526,7 @@ class Peer { virtual u16 getNextSplitSequenceNumber(u8 channel) { return 0; }; virtual void setNextSplitSequenceNumber(u8 channel, u16 seqnum) {}; - virtual SharedBuffer<u8> addSplitPacket(u8 channel, const BufferedPacket &toadd, + virtual SharedBuffer<u8> addSplitPacket(u8 channel, BufferedPacketPtr &toadd, bool reliable) { errorstream << "Peer::addSplitPacket called," @@ -582,7 +563,7 @@ class Peer { bool IncUseCount(); void DecUseCount(); - std::mutex m_exclusive_access_mutex; + mutable std::mutex m_exclusive_access_mutex; bool m_pending_deletion = false; @@ -630,7 +611,7 @@ public: UDPPeer(u16 a_id, Address a_address, Connection* connection); virtual ~UDPPeer() = default; - void PutReliableSendCommand(ConnectionCommand &c, + void PutReliableSendCommand(ConnectionCommandPtr &c, unsigned int max_packet_size); bool getAddress(MTProtocols type, Address& toset); @@ -638,7 +619,7 @@ public: u16 getNextSplitSequenceNumber(u8 channel); void setNextSplitSequenceNumber(u8 channel, u16 seqnum); - SharedBuffer<u8> addSplitPacket(u8 channel, const BufferedPacket &toadd, + SharedBuffer<u8> addSplitPacket(u8 channel, BufferedPacketPtr &toadd, bool reliable); protected: @@ -667,7 +648,7 @@ private: float resend_timeout = 0.5; bool processReliableSendCommand( - ConnectionCommand &c, + ConnectionCommandPtr &c_ptr, unsigned int max_packet_size); }; @@ -675,7 +656,7 @@ private: Connection */ -enum ConnectionEventType{ +enum ConnectionEventType { CONNEVENT_NONE, CONNEVENT_DATA_RECEIVED, CONNEVENT_PEER_ADDED, @@ -683,56 +664,32 @@ enum ConnectionEventType{ CONNEVENT_BIND_FAILED, }; +struct ConnectionEvent; +typedef std::shared_ptr<ConnectionEvent> ConnectionEventPtr; + +// This is very similar to ConnectionCommand struct ConnectionEvent { - enum ConnectionEventType type = CONNEVENT_NONE; + const ConnectionEventType type; session_t peer_id = 0; Buffer<u8> data; bool timeout = false; Address address; - ConnectionEvent() = default; + // We don't want to copy "data" + DISABLE_CLASS_COPY(ConnectionEvent); - const char *describe() const - { - switch(type) { - case CONNEVENT_NONE: - return "CONNEVENT_NONE"; - case CONNEVENT_DATA_RECEIVED: - return "CONNEVENT_DATA_RECEIVED"; - case CONNEVENT_PEER_ADDED: - return "CONNEVENT_PEER_ADDED"; - case CONNEVENT_PEER_REMOVED: - return "CONNEVENT_PEER_REMOVED"; - case CONNEVENT_BIND_FAILED: - return "CONNEVENT_BIND_FAILED"; - } - return "Invalid ConnectionEvent"; - } + static ConnectionEventPtr create(ConnectionEventType type); + static ConnectionEventPtr dataReceived(session_t peer_id, const Buffer<u8> &data); + static ConnectionEventPtr peerAdded(session_t peer_id, Address address); + static ConnectionEventPtr peerRemoved(session_t peer_id, bool is_timeout, Address address); + static ConnectionEventPtr bindFailed(); - void dataReceived(session_t peer_id_, const Buffer<u8> &data_) - { - type = CONNEVENT_DATA_RECEIVED; - peer_id = peer_id_; - data = data_; - } - void peerAdded(session_t peer_id_, Address address_) - { - type = CONNEVENT_PEER_ADDED; - peer_id = peer_id_; - address = address_; - } - void peerRemoved(session_t peer_id_, bool timeout_, Address address_) - { - type = CONNEVENT_PEER_REMOVED; - peer_id = peer_id_; - timeout = timeout_; - address = address_; - } - void bindFailed() - { - type = CONNEVENT_BIND_FAILED; - } + const char *describe() const; + +private: + ConnectionEvent(ConnectionEventType type_) : + type(type_) {} }; class PeerHandler; @@ -748,10 +705,9 @@ public: ~Connection(); /* Interface */ - ConnectionEvent waitEvent(u32 timeout_ms); - // Warning: creates an unnecessary copy, prefer putCommand(T&&) if possible - void putCommand(const ConnectionCommand &c); - void putCommand(ConnectionCommand &&c); + ConnectionEventPtr waitEvent(u32 timeout_ms); + + void putCommand(ConnectionCommandPtr c); void SetTimeoutMs(u32 timeout) { m_bc_receive_timeout = timeout; } void Serve(Address bind_addr); @@ -765,7 +721,7 @@ public: Address GetPeerAddress(session_t peer_id); float getPeerStat(session_t peer_id, rtt_stat_type type); float getLocalStat(rate_stat_type type); - const u32 GetProtocolID() const { return m_protocol_id; }; + u32 GetProtocolID() const { return m_protocol_id; }; const std::string getDesc(); void DisconnectPeer(session_t peer_id); @@ -781,8 +737,6 @@ protected: void sendAck(session_t peer_id, u8 channelnum, u16 seqnum); - void PrintInfo(std::ostream &out); - std::vector<session_t> getPeerIDs() { MutexAutoLock peerlock(m_peers_mutex); @@ -791,23 +745,21 @@ protected: UDPSocket m_udpSocket; // Command queue: user -> SendThread - MutexedQueue<ConnectionCommand> m_command_queue; + MutexedQueue<ConnectionCommandPtr> m_command_queue; bool Receive(NetworkPacket *pkt, u32 timeout); - // Warning: creates an unnecessary copy, prefer putEvent(T&&) if possible - void putEvent(const ConnectionEvent &e); - void putEvent(ConnectionEvent &&e); + void putEvent(ConnectionEventPtr e); void TriggerSend(); - - bool ConnectedToServer() + + bool ConnectedToServer() { return getPeerNoEx(PEER_ID_SERVER) != nullptr; } private: // Event queue: ReceiveThread -> user - MutexedQueue<ConnectionEvent> m_event_queue; + MutexedQueue<ConnectionEventPtr> m_event_queue; session_t m_peer_id = 0; u32 m_protocol_id; @@ -819,7 +771,7 @@ private: std::unique_ptr<ConnectionSendThread> m_sendThread; std::unique_ptr<ConnectionReceiveThread> m_receiveThread; - std::mutex m_info_mutex; + mutable std::mutex m_info_mutex; // Backwards compatibility PeerHandler *m_bc_peerhandler; diff --git a/src/network/connectionthreads.cpp b/src/network/connectionthreads.cpp index 47678dac5..90936b43d 100644 --- a/src/network/connectionthreads.cpp +++ b/src/network/connectionthreads.cpp @@ -32,32 +32,25 @@ namespace con /* defines used for debugging and profiling */ /******************************************************************************/ #ifdef NDEBUG -#define LOG(a) a #define PROFILE(a) #undef DEBUG_CONNECTION_KBPS #else /* this mutex is used to achieve log message consistency */ -std::mutex log_conthread_mutex; -#define LOG(a) \ - { \ - MutexAutoLock loglock(log_conthread_mutex); \ - a; \ - } #define PROFILE(a) a //#define DEBUG_CONNECTION_KBPS #undef DEBUG_CONNECTION_KBPS #endif -/* maximum number of retries for reliable packets */ -#define MAX_RELIABLE_RETRY 5 +// TODO: Clean this up. +#define LOG(a) a #define WINDOW_SIZE 5 -static session_t readPeerId(u8 *packetdata) +static session_t readPeerId(const u8 *packetdata) { return readU16(&packetdata[4]); } -static u8 readChannel(u8 *packetdata) +static u8 readChannel(const u8 *packetdata) { return readU8(&packetdata[6]); } @@ -117,9 +110,9 @@ void *ConnectionSendThread::run() } /* translate commands to packets */ - ConnectionCommand c = m_connection->m_command_queue.pop_frontNoEx(0); - while (c.type != CONNCMD_NONE) { - if (c.reliable) + auto c = m_connection->m_command_queue.pop_frontNoEx(0); + while (c && c->type != CONNCMD_NONE) { + if (c->reliable) processReliableCommand(c); else processNonReliableCommand(c); @@ -212,7 +205,6 @@ void ConnectionSendThread::runTimeouts(float dtime) } float resend_timeout = udpPeer->getResendTimeout(); - bool retry_count_exceeded = false; for (Channel &channel : udpPeer->channels) { // Remove timed out incomplete unreliable split packets @@ -231,45 +223,29 @@ void ConnectionSendThread::runTimeouts(float dtime) m_iteration_packets_avaialble -= timed_outs.size(); 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])); + u8 channelnum = readChannel(k->data); + u16 seqnum = k->getSeqnum(); - channel.UpdateBytesLost(k.data.getSize()); - - 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*/ - break; - } + channel.UpdateBytesLost(k->size()); 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 + << "count=" << k->resend_count << ", channel=" << ((int) channelnum & 0xff) << ", seqnum=" << seqnum << std::endl); - rawSend(k); + rawSend(k.get()); // do not handle rtt here as we can't decide if this packet was // lost or really takes more time to transmit } - if (retry_count_exceeded) { - break; /* no need to check other channels if we already did timeout */ - } - channel.UpdateTimers(dtime); } - /* skip to next peer if we did timeout */ - if (retry_count_exceeded) - continue; - /* send ping if necessary */ if (udpPeer->Ping(dtime, data)) { LOG(dout_con << m_connection->getDesc() @@ -294,25 +270,24 @@ void ConnectionSendThread::runTimeouts(float dtime) } } -void ConnectionSendThread::rawSend(const BufferedPacket &packet) +void ConnectionSendThread::rawSend(const BufferedPacket *p) { try { - m_connection->m_udpSocket.Send(packet.address, *packet.data, - packet.data.getSize()); + m_connection->m_udpSocket.Send(p->address, p->data, p->size()); LOG(dout_con << m_connection->getDesc() - << " rawSend: " << packet.data.getSize() + << " rawSend: " << p->size() << " bytes sent" << std::endl); } catch (SendFailedException &e) { LOG(derr_con << m_connection->getDesc() << "Connection::rawSend(): SendFailedException: " - << packet.address.serializeString() << std::endl); + << p->address.serializeString() << std::endl); } } -void ConnectionSendThread::sendAsPacketReliable(BufferedPacket &p, Channel *channel) +void ConnectionSendThread::sendAsPacketReliable(BufferedPacketPtr &p, Channel *channel) { try { - p.absolute_send_time = porting::getTimeMs(); + p->absolute_send_time = porting::getTimeMs(); // Buffer the packet channel->outgoing_reliables_sent.insert(p, (channel->readOutgoingSequenceNumber() - MAX_RELIABLE_WINDOW_SIZE) @@ -325,7 +300,7 @@ void ConnectionSendThread::sendAsPacketReliable(BufferedPacket &p, Channel *chan } // Send the packet - rawSend(p); + rawSend(p.get()); } bool ConnectionSendThread::rawSendAsPacket(session_t peer_id, u8 channelnum, @@ -341,11 +316,10 @@ bool ConnectionSendThread::rawSendAsPacket(session_t peer_id, u8 channelnum, Channel *channel = &(dynamic_cast<UDPPeer *>(&peer)->channels[channelnum]); if (reliable) { - bool have_sequence_number_for_raw_packet = true; - u16 seqnum = - channel->getOutgoingSequenceNumber(have_sequence_number_for_raw_packet); + bool have_seqnum = false; + const u16 seqnum = channel->getOutgoingSequenceNumber(have_seqnum); - if (!have_sequence_number_for_raw_packet) + if (!have_seqnum) return false; SharedBuffer<u8> reliable = makeReliablePacket(data, seqnum); @@ -353,13 +327,12 @@ bool ConnectionSendThread::rawSendAsPacket(session_t peer_id, u8 channelnum, peer->getAddress(MTP_MINETEST_RELIABLE_UDP, peer_address); // Add base headers and make a packet - BufferedPacket p = con::makePacket(peer_address, reliable, + BufferedPacketPtr p = con::makePacket(peer_address, reliable, m_connection->GetProtocolID(), m_connection->GetPeerID(), channelnum); // first check if our send window is already maxed out - if (channel->outgoing_reliables_sent.size() - < channel->getWindowSize()) { + if (channel->outgoing_reliables_sent.size() < channel->getWindowSize()) { LOG(dout_con << m_connection->getDesc() << " INFO: sending a reliable packet to peer_id " << peer_id << " channel: " << (u32)channelnum @@ -372,19 +345,19 @@ 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(std::move(p)); + channel->queued_reliables.push(p); return false; } Address peer_address; if (peer->getAddress(MTP_UDP, peer_address)) { // Add base headers and make a packet - BufferedPacket p = con::makePacket(peer_address, data, + BufferedPacketPtr p = con::makePacket(peer_address, data, m_connection->GetProtocolID(), m_connection->GetPeerID(), channelnum); // Send the packet - rawSend(p); + rawSend(p.get()); return true; } @@ -394,11 +367,11 @@ bool ConnectionSendThread::rawSendAsPacket(session_t peer_id, u8 channelnum, return false; } -void ConnectionSendThread::processReliableCommand(ConnectionCommand &c) +void ConnectionSendThread::processReliableCommand(ConnectionCommandPtr &c) { - assert(c.reliable); // Pre-condition + assert(c->reliable); // Pre-condition - switch (c.type) { + switch (c->type) { case CONNCMD_NONE: LOG(dout_con << m_connection->getDesc() << "UDP processing reliable CONNCMD_NONE" << std::endl); @@ -419,7 +392,7 @@ void ConnectionSendThread::processReliableCommand(ConnectionCommand &c) case CONCMD_CREATE_PEER: LOG(dout_con << m_connection->getDesc() << "UDP processing reliable CONCMD_CREATE_PEER" << std::endl); - if (!rawSendAsPacket(c.peer_id, c.channelnum, c.data, c.reliable)) { + if (!rawSendAsPacket(c->peer_id, c->channelnum, c->data, c->reliable)) { /* put to queue if we couldn't send it immediately */ sendReliable(c); } @@ -432,13 +405,14 @@ void ConnectionSendThread::processReliableCommand(ConnectionCommand &c) FATAL_ERROR("Got command that shouldn't be reliable as reliable command"); default: LOG(dout_con << m_connection->getDesc() - << " Invalid reliable command type: " << c.type << std::endl); + << " Invalid reliable command type: " << c->type << std::endl); } } -void ConnectionSendThread::processNonReliableCommand(ConnectionCommand &c) +void ConnectionSendThread::processNonReliableCommand(ConnectionCommandPtr &c_ptr) { + const ConnectionCommand &c = *c_ptr; assert(!c.reliable); // Pre-condition switch (c.type) { @@ -500,9 +474,7 @@ void ConnectionSendThread::serve(Address bind_address) } catch (SocketException &e) { // Create event - ConnectionEvent ce; - ce.bindFailed(); - m_connection->putEvent(ce); + m_connection->putEvent(ConnectionEvent::bindFailed()); } } @@ -515,9 +487,7 @@ void ConnectionSendThread::connect(Address address) UDPPeer *peer = m_connection->createServerPeer(address); // Create event - ConnectionEvent e; - e.peerAdded(peer->id, peer->address); - m_connection->putEvent(e); + m_connection->putEvent(ConnectionEvent::peerAdded(peer->id, peer->address)); Address bind_addr; @@ -606,9 +576,9 @@ void ConnectionSendThread::send(session_t peer_id, u8 channelnum, } } -void ConnectionSendThread::sendReliable(ConnectionCommand &c) +void ConnectionSendThread::sendReliable(ConnectionCommandPtr &c) { - PeerHelper peer = m_connection->getPeerNoEx(c.peer_id); + PeerHelper peer = m_connection->getPeerNoEx(c->peer_id); if (!peer) return; @@ -624,7 +594,7 @@ void ConnectionSendThread::sendToAll(u8 channelnum, const SharedBuffer<u8> &data } } -void ConnectionSendThread::sendToAllReliable(ConnectionCommand &c) +void ConnectionSendThread::sendToAllReliable(ConnectionCommandPtr &c) { std::vector<session_t> peerids = m_connection->getPeerIDs(); @@ -683,8 +653,12 @@ void ConnectionSendThread::sendPackets(float dtime) // first send queued reliable packets for all peers (if possible) for (unsigned int i = 0; i < CHANNEL_COUNT; i++) { Channel &channel = udpPeer->channels[i]; - u16 next_to_ack = 0; + // Reduces logging verbosity + if (channel.queued_reliables.empty()) + continue; + + u16 next_to_ack = 0; channel.outgoing_reliables_sent.getFirstSeqnum(next_to_ack); u16 next_to_receive = 0; channel.incoming_reliables.getFirstSeqnum(next_to_receive); @@ -714,13 +688,13 @@ void ConnectionSendThread::sendPackets(float dtime) channel.outgoing_reliables_sent.size() < channel.getWindowSize() && peer->m_increment_packets_remaining > 0) { - BufferedPacket p = std::move(channel.queued_reliables.front()); + BufferedPacketPtr p = 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]) + << ", seqnum: " << p->getSeqnum() << std::endl); sendAsPacketReliable(p, &channel); @@ -901,17 +875,14 @@ void ConnectionReceiveThread::receive(SharedBuffer<u8> &packetdata, try { // First, see if there any buffered packets we can process now if (packet_queued) { - bool data_left = true; session_t peer_id; SharedBuffer<u8> resultdata; - while (data_left) { + while (true) { try { - data_left = getFromBuffers(peer_id, resultdata); - if (data_left) { - ConnectionEvent e; - e.dataReceived(peer_id, resultdata); - m_connection->putEvent(std::move(e)); - } + if (!getFromBuffers(peer_id, resultdata)) + break; + + m_connection->putEvent(ConnectionEvent::dataReceived(peer_id, resultdata)); } catch (ProcessedSilentlyException &e) { /* try reading again */ @@ -928,7 +899,7 @@ void ConnectionReceiveThread::receive(SharedBuffer<u8> &packetdata, return; if ((received_size < BASE_HEADER_SIZE) || - (readU32(&packetdata[0]) != m_connection->GetProtocolID())) { + (readU32(&packetdata[0]) != m_connection->GetProtocolID())) { LOG(derr_con << m_connection->getDesc() << "Receive(): Invalid incoming packet, " << "size: " << received_size @@ -1019,9 +990,7 @@ void ConnectionReceiveThread::receive(SharedBuffer<u8> &packetdata, << ", channel: " << (u32)channelnum << ", returned " << resultdata.getSize() << " bytes" << std::endl); - ConnectionEvent e; - e.dataReceived(peer_id, resultdata); - m_connection->putEvent(std::move(e)); + m_connection->putEvent(ConnectionEvent::dataReceived(peer_id, resultdata)); } catch (ProcessedSilentlyException &e) { } @@ -1046,10 +1015,11 @@ bool ConnectionReceiveThread::getFromBuffers(session_t &peer_id, SharedBuffer<u8 if (!peer) continue; - if (dynamic_cast<UDPPeer *>(&peer) == 0) + UDPPeer *p = dynamic_cast<UDPPeer *>(&peer); + if (!p) continue; - for (Channel &channel : (dynamic_cast<UDPPeer *>(&peer))->channels) { + for (Channel &channel : p->channels) { if (checkIncomingBuffers(&channel, peer_id, dst)) { return true; } @@ -1062,32 +1032,34 @@ bool ConnectionReceiveThread::checkIncomingBuffers(Channel *channel, session_t &peer_id, SharedBuffer<u8> &dst) { u16 firstseqnum = 0; - if (channel->incoming_reliables.getFirstSeqnum(firstseqnum)) { - if (firstseqnum == channel->readNextIncomingSeqNum()) { - BufferedPacket p = channel->incoming_reliables.popFirst(); - peer_id = readPeerId(*p.data); - u8 channelnum = readChannel(*p.data); - u16 seqnum = readU16(&p.data[BASE_HEADER_SIZE + 1]); + if (!channel->incoming_reliables.getFirstSeqnum(firstseqnum)) + return false; - LOG(dout_con << m_connection->getDesc() - << "UNBUFFERING TYPE_RELIABLE" - << " seqnum=" << seqnum - << " peer_id=" << peer_id - << " channel=" << ((int) channelnum & 0xff) - << std::endl); + if (firstseqnum != channel->readNextIncomingSeqNum()) + return false; - channel->incNextIncomingSeqNum(); + BufferedPacketPtr p = channel->incoming_reliables.popFirst(); - u32 headers_size = BASE_HEADER_SIZE + RELIABLE_HEADER_SIZE; - // Get out the inside packet and re-process it - SharedBuffer<u8> payload(p.data.getSize() - headers_size); - memcpy(*payload, &p.data[headers_size], payload.getSize()); + peer_id = readPeerId(p->data); // Carried over to caller function + u8 channelnum = readChannel(p->data); + u16 seqnum = p->getSeqnum(); - dst = processPacket(channel, payload, peer_id, channelnum, true); - return true; - } - } - return false; + LOG(dout_con << m_connection->getDesc() + << "UNBUFFERING TYPE_RELIABLE" + << " seqnum=" << seqnum + << " peer_id=" << peer_id + << " channel=" << ((int) channelnum & 0xff) + << std::endl); + + channel->incNextIncomingSeqNum(); + + u32 headers_size = BASE_HEADER_SIZE + RELIABLE_HEADER_SIZE; + // Get out the inside packet and re-process it + SharedBuffer<u8> payload(p->size() - headers_size); + memcpy(*payload, &p->data[headers_size], payload.getSize()); + + dst = processPacket(channel, payload, peer_id, channelnum, true); + return true; } SharedBuffer<u8> ConnectionReceiveThread::processPacket(Channel *channel, @@ -1135,7 +1107,7 @@ SharedBuffer<u8> ConnectionReceiveThread::handlePacketType_Control(Channel *chan if (packetdata.getSize() < 2) throw InvalidIncomingDataException("packetdata.getSize() < 2"); - u8 controltype = readU8(&(packetdata[1])); + ControlType controltype = (ControlType)readU8(&(packetdata[1])); if (controltype == CONTROLTYPE_ACK) { assert(channel != NULL); @@ -1151,31 +1123,32 @@ SharedBuffer<u8> ConnectionReceiveThread::handlePacketType_Control(Channel *chan << seqnum << " ]" << std::endl); try { - BufferedPacket p = channel->outgoing_reliables_sent.popSeqnum(seqnum); + BufferedPacketPtr p = channel->outgoing_reliables_sent.popSeqnum(seqnum); - // only calculate rtt from straight sent packets - if (p.resend_count == 0) { + // the rtt calculation will be a bit off for re-sent packets but that's okay + { // Get round trip time u64 current_time = porting::getTimeMs(); // a overflow is quite unlikely but as it'd result in major // rtt miscalculation we handle it here - if (current_time > p.absolute_send_time) { - float rtt = (current_time - p.absolute_send_time) / 1000.0; + if (current_time > p->absolute_send_time) { + float rtt = (current_time - p->absolute_send_time) / 1000.0; // Let peer calculate stuff according to it // (avg_rtt and resend_timeout) dynamic_cast<UDPPeer *>(peer)->reportRTT(rtt); - } else if (p.totaltime > 0) { - float rtt = p.totaltime; + } else if (p->totaltime > 0) { + float rtt = p->totaltime; // Let peer calculate stuff according to it // (avg_rtt and resend_timeout) dynamic_cast<UDPPeer *>(peer)->reportRTT(rtt); } } + // put bytes for max bandwidth calculation - channel->UpdateBytesSent(p.data.getSize(), 1); + channel->UpdateBytesSent(p->size(), 1); if (channel->outgoing_reliables_sent.size() == 0) m_connection->TriggerSend(); } catch (NotFoundException &e) { @@ -1223,7 +1196,7 @@ SharedBuffer<u8> ConnectionReceiveThread::handlePacketType_Control(Channel *chan throw ProcessedSilentlyException("Got a DISCO"); } else { LOG(derr_con << m_connection->getDesc() - << "INVALID TYPE_CONTROL: invalid controltype=" + << "INVALID controltype=" << ((int) controltype & 0xff) << std::endl); throw InvalidIncomingDataException("Invalid control type"); } @@ -1251,7 +1224,7 @@ SharedBuffer<u8> ConnectionReceiveThread::handlePacketType_Split(Channel *channe if (peer->getAddress(MTP_UDP, peer_address)) { // We have to create a packet again for buffering // This isn't actually too bad an idea. - BufferedPacket packet = makePacket(peer_address, + BufferedPacketPtr packet = con::makePacket(peer_address, packetdata, m_connection->GetProtocolID(), peer->id, @@ -1286,7 +1259,7 @@ SharedBuffer<u8> ConnectionReceiveThread::handlePacketType_Reliable(Channel *cha if (packetdata.getSize() < RELIABLE_HEADER_SIZE) throw InvalidIncomingDataException("packetdata.getSize() < RELIABLE_HEADER_SIZE"); - u16 seqnum = readU16(&packetdata[1]); + const u16 seqnum = readU16(&packetdata[1]); bool is_future_packet = false; bool is_old_packet = false; @@ -1330,7 +1303,7 @@ SharedBuffer<u8> ConnectionReceiveThread::handlePacketType_Reliable(Channel *cha // This one comes later, buffer it. // Actually we have to make a packet to buffer one. // Well, we have all the ingredients, so just do it. - BufferedPacket packet = con::makePacket( + BufferedPacketPtr packet = con::makePacket( peer_address, packetdata, m_connection->GetProtocolID(), @@ -1347,9 +1320,7 @@ SharedBuffer<u8> ConnectionReceiveThread::handlePacketType_Reliable(Channel *cha throw ProcessedQueued("Buffered future reliable packet"); } catch (AlreadyExistsException &e) { } catch (IncomingDataCorruption &e) { - ConnectionCommand discon; - discon.disconnect_peer(peer->id); - m_connection->putCommand(discon); + m_connection->putCommand(ConnectionCommand::disconnect_peer(peer->id)); LOG(derr_con << m_connection->getDesc() << "INVALID, TYPE_RELIABLE peer_id: " << peer->id @@ -1370,7 +1341,7 @@ SharedBuffer<u8> ConnectionReceiveThread::handlePacketType_Reliable(Channel *cha u16 queued_seqnum = 0; if (channel->incoming_reliables.getFirstSeqnum(queued_seqnum)) { if (queued_seqnum == seqnum) { - BufferedPacket queued_packet = channel->incoming_reliables.popFirst(); + BufferedPacketPtr queued_packet = channel->incoming_reliables.popFirst(); /** TODO find a way to verify the new against the old packet */ } } diff --git a/src/network/connectionthreads.h b/src/network/connectionthreads.h index 612407c3b..c2e2dae12 100644 --- a/src/network/connectionthreads.h +++ b/src/network/connectionthreads.h @@ -29,6 +29,25 @@ namespace con class Connection; +struct OutgoingPacket +{ + session_t peer_id; + u8 channelnum; + SharedBuffer<u8> data; + bool reliable; + bool ack; + + OutgoingPacket(session_t peer_id_, u8 channelnum_, const SharedBuffer<u8> &data_, + bool reliable_,bool ack_=false): + peer_id(peer_id_), + channelnum(channelnum_), + data(data_), + reliable(reliable_), + ack(ack_) + { + } +}; + class ConnectionSendThread : public Thread { @@ -51,27 +70,27 @@ public: private: void runTimeouts(float dtime); - void rawSend(const BufferedPacket &packet); + void rawSend(const BufferedPacket *p); bool rawSendAsPacket(session_t peer_id, u8 channelnum, const SharedBuffer<u8> &data, bool reliable); - void processReliableCommand(ConnectionCommand &c); - void processNonReliableCommand(ConnectionCommand &c); + void processReliableCommand(ConnectionCommandPtr &c); + void processNonReliableCommand(ConnectionCommandPtr &c); void serve(Address bind_address); void connect(Address address); void disconnect(); void disconnect_peer(session_t peer_id); void send(session_t peer_id, u8 channelnum, const SharedBuffer<u8> &data); - void sendReliable(ConnectionCommand &c); + void sendReliable(ConnectionCommandPtr &c); void sendToAll(u8 channelnum, const SharedBuffer<u8> &data); - void sendToAllReliable(ConnectionCommand &c); + void sendToAllReliable(ConnectionCommandPtr &c); void sendPackets(float dtime); void sendAsPacket(session_t peer_id, u8 channelnum, const SharedBuffer<u8> &data, bool ack = false); - void sendAsPacketReliable(BufferedPacket &p, Channel *channel); + void sendAsPacketReliable(BufferedPacketPtr &p, Channel *channel); bool packetsQueued(); diff --git a/src/network/networkpacket.h b/src/network/networkpacket.h index b1c44f055..b9c39f332 100644 --- a/src/network/networkpacket.h +++ b/src/network/networkpacket.h @@ -41,7 +41,7 @@ public: u32 getSize() const { return m_datasize; } session_t getPeerId() const { return m_peer_id; } u16 getCommand() { return m_command; } - const u32 getRemainingBytes() const { return m_datasize - m_read_offset; } + u32 getRemainingBytes() const { return m_datasize - m_read_offset; } const char *getRemainingString() { return getString(m_read_offset); } // Returns a c-string without copying. diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index 8214cc5b1..3923cb858 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -206,7 +206,6 @@ with this program; if not, write to the Free Software Foundation, Inc., Adds new sun, moon and stars packets Minimap modes PROTOCOL VERSION 40: - Added 'basic_debug' privilege TOCLIENT_MEDIA_PUSH changed, TOSERVER_HAVE_MEDIA added */ @@ -230,7 +229,7 @@ with this program; if not, write to the Free Software Foundation, Inc., // base64-encoded SHA-1 (27+\0). // See also: Formspec Version History in doc/lua_api.txt -#define FORMSPEC_API_VERSION 4 +#define FORMSPEC_API_VERSION 5 #define TEXTURENAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.-" @@ -763,7 +762,12 @@ enum ToClientCommand std::string extra */ - TOCLIENT_NUM_MSG_TYPES = 0x63, + TOCLIENT_SET_LIGHTING = 0x63, + /* + f32 shadow_intensity + */ + + TOCLIENT_NUM_MSG_TYPES = 0x64, }; enum ToServerCommand @@ -1002,7 +1006,7 @@ enum AuthMechanism AUTH_MECHANISM_FIRST_SRP = 1 << 2, }; -enum AccessDeniedCode { +enum AccessDeniedCode : u8 { SERVER_ACCESSDENIED_WRONG_PASSWORD, SERVER_ACCESSDENIED_UNEXPECTED_DATA, SERVER_ACCESSDENIED_SINGLEPLAYER, @@ -1025,18 +1029,18 @@ enum NetProtoCompressionMode { const static std::string accessDeniedStrings[SERVER_ACCESSDENIED_MAX] = { "Invalid password", - "Your client sent something the server didn't expect. Try reconnecting or updating your client", + "Your client sent something the server didn't expect. Try reconnecting or updating your client.", "The server is running in simple singleplayer mode. You cannot connect.", - "Your client's version is not supported.\nPlease contact server administrator.", - "Player name contains disallowed characters.", - "Player name not allowed.", - "Too many users.", + "Your client's version is not supported.\nPlease contact the server administrator.", + "Player name contains disallowed characters", + "Player name not allowed", + "Too many users", "Empty passwords are disallowed. Set a password and try again.", "Another client is connected with this name. If your client closed unexpectedly, try again in a minute.", - "Server authentication failed. This is likely a server error.", + "Internal server error", "", - "Server shutting down.", - "This server has experienced an internal error. You will now be disconnected." + "Server shutting down", + "The server has experienced an internal error. You will now be disconnected." }; enum PlayerListModifer : u8 diff --git a/src/network/serveropcodes.cpp b/src/network/serveropcodes.cpp index 44b65e8da..12665e7f1 100644 --- a/src/network/serveropcodes.cpp +++ b/src/network/serveropcodes.cpp @@ -176,7 +176,7 @@ const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] = { "TOCLIENT_ACTIVE_OBJECT_MESSAGES", 0, true }, // 0x32 (may be sent as unrel over channel 1 too) { "TOCLIENT_HP", 0, true }, // 0x33 { "TOCLIENT_MOVE_PLAYER", 0, true }, // 0x34 - { "TOCLIENT_ACCESS_DENIED_LEGACY", 0, true }, // 0x35 + null_command_factory, // 0x35 { "TOCLIENT_FOV", 0, true }, // 0x36 { "TOCLIENT_DEATHSCREEN", 0, true }, // 0x37 { "TOCLIENT_MEDIA", 2, true }, // 0x38 diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index 4c609644f..4b9de488c 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -86,7 +86,7 @@ void Server::handleCommand_Init(NetworkPacket* pkt) // Do not allow multiple players in simple singleplayer mode. // This isn't a perfect way to do it, but will suffice for now - if (m_simple_singleplayer_mode && m_clients.getClientIDs().size() > 1) { + if (m_simple_singleplayer_mode && !m_clients.getClientIDs().empty()) { infostream << "Server: Not allowing another client (" << addr_s << ") to connect in simple singleplayer mode" << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_SINGLEPLAYER); @@ -227,7 +227,7 @@ void Server::handleCommand_Init(NetworkPacket* pkt) Compose auth methods for answer */ std::string encpwd; // encrypted Password field for the user - bool has_auth = m_script->getAuth(playername, &encpwd, NULL); + bool has_auth = m_script->getAuth(playername, &encpwd, nullptr); u32 auth_mechs = 0; client->chosen_mech = AUTH_MECHANISM_NONE; @@ -380,55 +380,47 @@ void Server::handleCommand_ClientReady(NetworkPacket* pkt) { session_t peer_id = pkt->getPeerId(); - PlayerSAO* playersao = StageTwoClientInit(peer_id); - - if (playersao == NULL) { - errorstream << "TOSERVER_CLIENT_READY stage 2 client init failed " - "peer_id=" << peer_id << std::endl; - DisconnectPeer(peer_id); - return; - } - - - if (pkt->getSize() < 8) { - errorstream << "TOSERVER_CLIENT_READY client sent inconsistent data, " - "disconnecting peer_id: " << peer_id << std::endl; - DisconnectPeer(peer_id); - return; - } - + // decode all information first u8 major_ver, minor_ver, patch_ver, reserved; + u16 formspec_ver = 1; // v1 for clients older than 5.1.0-dev std::string full_ver; + *pkt >> major_ver >> minor_ver >> patch_ver >> reserved >> full_ver; + if (pkt->getRemainingBytes() >= 2) + *pkt >> formspec_ver; m_clients.setClientVersion(peer_id, major_ver, minor_ver, patch_ver, full_ver); - if (pkt->getRemainingBytes() >= 2) - *pkt >> playersao->getPlayer()->formspec_version; - - const std::vector<std::string> &players = m_clients.getPlayerNames(); - NetworkPacket list_pkt(TOCLIENT_UPDATE_PLAYER_LIST, 0, peer_id); - list_pkt << (u8) PLAYER_LIST_INIT << (u16) players.size(); - for (const std::string &player: players) { - list_pkt << player; + // Emerge player + PlayerSAO* playersao = StageTwoClientInit(peer_id); + if (!playersao) { + errorstream << "Server: stage 2 client init failed " + "peer_id=" << peer_id << std::endl; + DisconnectPeer(peer_id); + return; } - m_clients.send(peer_id, 0, &list_pkt, true); - NetworkPacket notice_pkt(TOCLIENT_UPDATE_PLAYER_LIST, 0, PEER_ID_INEXISTENT); - // (u16) 1 + std::string represents a pseudo vector serialization representation - notice_pkt << (u8) PLAYER_LIST_ADD << (u16) 1 << std::string(playersao->getPlayer()->getName()); - m_clients.sendToAll(¬ice_pkt); + playersao->getPlayer()->formspec_version = formspec_ver; m_clients.event(peer_id, CSE_SetClientReady); + // Send player list to this client + { + const std::vector<std::string> &players = m_clients.getPlayerNames(); + NetworkPacket list_pkt(TOCLIENT_UPDATE_PLAYER_LIST, 0, peer_id); + list_pkt << (u8) PLAYER_LIST_INIT << (u16) players.size(); + for (const auto &player : players) + list_pkt << player; + Send(peer_id, &list_pkt); + } + s64 last_login; m_script->getAuth(playersao->getPlayer()->getName(), nullptr, nullptr, &last_login); m_script->on_joinplayer(playersao, last_login); // Send shutdown timer if shutdown has been scheduled - if (m_shutdown_state.isTimerRunning()) { + if (m_shutdown_state.isTimerRunning()) SendChatMessage(peer_id, m_shutdown_state.getShutdownTimerMessage()); - } } void Server::handleCommand_GotBlocks(NetworkPacket* pkt) @@ -452,7 +444,7 @@ void Server::handleCommand_GotBlocks(NetworkPacket* pkt) ("GOTBLOCKS length is too short"); } - m_clients.lock(); + ClientInterface::AutoLock lock(m_clients); RemoteClient *client = m_clients.lockedGetClientNoEx(pkt->getPeerId()); for (u16 i = 0; i < count; i++) { @@ -460,7 +452,6 @@ void Server::handleCommand_GotBlocks(NetworkPacket* pkt) *pkt >> p; client->GotBlock(p); } - m_clients.unlock(); } void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao, @@ -482,7 +473,6 @@ void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao, f32 yaw = (f32)f32yaw / 100.0f; u32 keyPressed = 0; - // default behavior (in case an old client doesn't send these) f32 fov = 0; u8 wanted_range = 0; @@ -508,13 +498,7 @@ void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao, playersao->setFov(fov); playersao->setWantedRange(wanted_range); - player->keyPressed = keyPressed; - player->control.jump = (keyPressed & (0x1 << 4)); - player->control.aux1 = (keyPressed & (0x1 << 5)); - player->control.sneak = (keyPressed & (0x1 << 6)); - player->control.dig = (keyPressed & (0x1 << 7)); - player->control.place = (keyPressed & (0x1 << 8)); - player->control.zoom = (keyPressed & (0x1 << 9)); + player->control.unpackKeysPressed(keyPressed); if (playersao->checkMovementCheat()) { // Call callbacks @@ -826,7 +810,7 @@ void Server::handleCommand_Damage(NetworkPacket* pkt) << std::endl; PlayerHPChangeReason reason(PlayerHPChangeReason::FALL); - playersao->setHP((s32)playersao->getHP() - (s32)damage, reason); + playersao->setHP((s32)playersao->getHP() - (s32)damage, reason, true); } } @@ -920,6 +904,13 @@ bool Server::checkInteractDistance(RemotePlayer *player, const f32 d, const std: return true; } +// Tiny helper to retrieve the selected item into an Optional +static inline void getWieldedItem(const PlayerSAO *playersao, Optional<ItemStack> &ret) +{ + ret = ItemStack(); + playersao->getWieldedItem(&(*ret)); +} + void Server::handleCommand_Interact(NetworkPacket *pkt) { /* @@ -1111,8 +1102,8 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) float time_from_last_punch = playersao->resetTimeFromLastPunch(); - u16 wear = pointed_object->punch(dir, &toolcap, playersao, - time_from_last_punch); + u32 wear = pointed_object->punch(dir, &toolcap, playersao, + time_from_last_punch, tool_item.wear); // Callback may have changed item, so get it again playersao->getWieldedItem(&selected_item); @@ -1165,7 +1156,8 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) // Get diggability and expected digging time DigParams params = getDigParams(m_nodedef->get(n).groups, - &selected_item.getToolCapabilities(m_itemdef)); + &selected_item.getToolCapabilities(m_itemdef), + selected_item.wear); // If can't dig, try hand if (!params.diggable) { params = getDigParams(m_nodedef->get(n).groups, @@ -1227,14 +1219,17 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) // Place block or right-click object case INTERACT_PLACE: { - ItemStack selected_item; - playersao->getWieldedItem(&selected_item, nullptr); + Optional<ItemStack> selected_item; + getWieldedItem(playersao, selected_item); // Reset build time counter if (pointed.type == POINTEDTHING_NODE && - selected_item.getDefinition(m_itemdef).type == ITEM_NODE) + selected_item->getDefinition(m_itemdef).type == ITEM_NODE) getClient(peer_id)->m_time_from_building = 0.0; + const bool had_prediction = !selected_item->getDefinition(m_itemdef). + node_placement_prediction.empty(); + if (pointed.type == POINTEDTHING_OBJECT) { // Right click object @@ -1247,11 +1242,9 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) << pointed_object->getDescription() << std::endl; // Do stuff - if (m_script->item_OnSecondaryUse( - selected_item, playersao, pointed)) { - if (playersao->setWieldedItem(selected_item)) { + if (m_script->item_OnSecondaryUse(selected_item, playersao, pointed)) { + if (selected_item.has_value() && playersao->setWieldedItem(*selected_item)) SendInventory(playersao, true); - } } pointed_object->rightClick(playersao); @@ -1259,7 +1252,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) // Placement was handled in lua // Apply returned ItemStack - if (playersao->setWieldedItem(selected_item)) + if (selected_item.has_value() && playersao->setWieldedItem(*selected_item)) SendInventory(playersao, true); } @@ -1271,8 +1264,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) RemoteClient *client = getClient(peer_id); v3s16 blockpos = getNodeBlockPos(pointed.node_abovesurface); v3s16 blockpos2 = getNodeBlockPos(pointed.node_undersurface); - if (!selected_item.getDefinition(m_itemdef - ).node_placement_prediction.empty()) { + if (had_prediction) { client->SetBlockNotSent(blockpos); if (blockpos2 != blockpos) client->SetBlockNotSent(blockpos2); @@ -1286,15 +1278,15 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) } // action == INTERACT_PLACE case INTERACT_USE: { - ItemStack selected_item; - playersao->getWieldedItem(&selected_item, nullptr); + Optional<ItemStack> selected_item; + getWieldedItem(playersao, selected_item); - actionstream << player->getName() << " uses " << selected_item.name + actionstream << player->getName() << " uses " << selected_item->name << ", pointing at " << pointed.dump() << std::endl; if (m_script->item_OnUse(selected_item, playersao, pointed)) { // Apply returned ItemStack - if (playersao->setWieldedItem(selected_item)) + if (selected_item.has_value() && playersao->setWieldedItem(*selected_item)) SendInventory(playersao, true); } @@ -1303,16 +1295,17 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) // Rightclick air case INTERACT_ACTIVATE: { - ItemStack selected_item; - playersao->getWieldedItem(&selected_item, nullptr); + Optional<ItemStack> selected_item; + getWieldedItem(playersao, selected_item); actionstream << player->getName() << " activates " - << selected_item.name << std::endl; + << selected_item->name << std::endl; pointed.type = POINTEDTHING_NOTHING; // can only ever be NOTHING if (m_script->item_OnSecondaryUse(selected_item, playersao, pointed)) { - if (playersao->setWieldedItem(selected_item)) + // Apply returned ItemStack + if (selected_item.has_value() && playersao->setWieldedItem(*selected_item)) SendInventory(playersao, true); } @@ -1468,11 +1461,9 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt) session_t peer_id = pkt->getPeerId(); RemoteClient *client = getClient(peer_id, CS_Invalid); ClientState cstate = client->getState(); + const std::string playername = client->getName(); - std::string playername = client->getName(); - - std::string salt; - std::string verification_key; + std::string salt, verification_key; std::string addr_s = getPeerAddress(peer_id).serializeString(); u8 is_empty; @@ -1482,6 +1473,9 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt) verbosestream << "Server: Got TOSERVER_FIRST_SRP from " << addr_s << ", with is_empty=" << (is_empty == 1) << std::endl; + const bool empty_disallowed = !isSingleplayer() && is_empty == 1 && + g_settings->getBool("disallow_empty_password"); + // Either this packet is sent because the user is new or to change the password if (cstate == CS_HelloSent) { if (!client->isMechAllowed(AUTH_MECHANISM_FIRST_SRP)) { @@ -1492,9 +1486,7 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt) return; } - if (!isSingleplayer() && - g_settings->getBool("disallow_empty_password") && - is_empty == 1) { + if (empty_disallowed) { actionstream << "Server: " << playername << " supplied empty password from " << addr_s << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_EMPTY_PASSWORD); @@ -1502,8 +1494,19 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt) } std::string initial_ver_key; - initial_ver_key = encode_srp_verifier(verification_key, salt); + + // It is possible for multiple connections to get this far with the same + // player name. In the end only one player with a given name will be emerged + // (see Server::StateTwoClientInit) but we still have to be careful here. + if (m_script->getAuth(playername, nullptr, nullptr)) { + // Another client beat us to it + actionstream << "Server: Client from " << addr_s + << " tried to register " << playername << " a second time." + << std::endl; + DenyAccess(peer_id, SERVER_ACCESSDENIED_ALREADY_CONNECTED); + return; + } m_script->createAuth(playername, initial_ver_key); m_script->on_authplayer(playername, addr_s, true); @@ -1516,6 +1519,15 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt) return; } m_clients.event(peer_id, CSE_SudoLeave); + + if (empty_disallowed) { + actionstream << "Server: " << playername + << " supplied empty password" << std::endl; + SendChatMessage(peer_id, ChatMessage(CHATMESSAGE_TYPE_SYSTEM, + L"Changing to an empty password is not allowed.")); + return; + } + std::string pw_db_field = encode_srp_verifier(verification_key, salt); bool success = m_script->setPassword(playername, pw_db_field); if (success) { @@ -1537,8 +1549,6 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt) RemoteClient *client = getClient(peer_id, CS_Invalid); ClientState cstate = client->getState(); - bool wantSudo = (cstate == CS_Active); - if (!((cstate == CS_HelloSent) || (cstate == CS_Active))) { actionstream << "Server: got SRP _A packet in wrong state " << cstate << " from " << getPeerAddress(peer_id).serializeString() << @@ -1546,6 +1556,8 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt) return; } + const bool wantSudo = (cstate == CS_Active); + if (client->chosen_mech != AUTH_MECHANISM_NONE) { actionstream << "Server: got SRP _A packet, while auth is already " "going on with mech " << client->chosen_mech << " from " << @@ -1592,8 +1604,7 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt) client->chosen_mech = chosen; - std::string salt; - std::string verifier; + std::string salt, verifier; if (based_on == 0) { @@ -1625,6 +1636,7 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt) << std::endl; if (wantSudo) { DenySudoAccess(peer_id); + client->resetChosenMech(); return; } @@ -1642,10 +1654,10 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt) session_t peer_id = pkt->getPeerId(); RemoteClient *client = getClient(peer_id, CS_Invalid); ClientState cstate = client->getState(); - std::string addr_s = getPeerAddress(pkt->getPeerId()).serializeString(); - std::string playername = client->getName(); + const std::string addr_s = client->getAddress().serializeString(); + const std::string playername = client->getName(); - bool wantSudo = (cstate == CS_Active); + const bool wantSudo = (cstate == CS_Active); verbosestream << "Server: Received TOSERVER_SRP_BYTES_M." << std::endl; @@ -1691,6 +1703,7 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt) << " tried to change their password, but supplied wrong" << " (SRP) password for authentication." << std::endl; DenySudoAccess(peer_id); + client->resetChosenMech(); return; } @@ -1704,8 +1717,7 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt) if (client->create_player_on_auth_success) { m_script->createAuth(playername, client->enc_pwd); - std::string checkpwd; // not used, but needed for passing something - if (!m_script->getAuth(playername, &checkpwd, NULL)) { + if (!m_script->getAuth(playername, nullptr, nullptr)) { errorstream << "Server: " << playername << " cannot be authenticated (auth handler does not work?)" << std::endl; diff --git a/src/network/socket.cpp b/src/network/socket.cpp index 94a9f4180..df15c89ba 100644 --- a/src/network/socket.cpp +++ b/src/network/socket.cpp @@ -23,14 +23,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <iostream> #include <cstdlib> #include <cstring> -#include <cerrno> -#include <sstream> #include <iomanip> #include "util/string.h" #include "util/numeric.h" #include "constants.h" #include "debug.h" -#include "settings.h" #include "log.h" #ifdef _WIN32 @@ -42,9 +39,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <winsock2.h> #include <ws2tcpip.h> #define LAST_SOCKET_ERR() WSAGetLastError() -typedef SOCKET socket_t; +#define SOCKET_ERR_STR(e) itos(e) typedef int socklen_t; #else +#include <cerrno> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> @@ -53,7 +51,7 @@ typedef int socklen_t; #include <unistd.h> #include <arpa/inet.h> #define LAST_SOCKET_ERR() (errno) -typedef int socket_t; +#define SOCKET_ERR_STR(e) strerror(e) #endif // Set to true to enable verbose debug output @@ -113,7 +111,7 @@ bool UDPSocket::init(bool ipv6, bool noExceptions) } throw SocketException(std::string("Failed to create socket: error ") + - itos(LAST_SOCKET_ERR())); + SOCKET_ERR_STR(LAST_SOCKET_ERR())); } setTimeoutMs(0); @@ -153,40 +151,40 @@ void UDPSocket::Bind(Address addr) } if (addr.getFamily() != m_addr_family) { - static const char *errmsg = + const char *errmsg = "Socket and bind address families do not match"; errorstream << "Bind failed: " << errmsg << std::endl; throw SocketException(errmsg); } + int ret = 0; + if (m_addr_family == AF_INET6) { struct sockaddr_in6 address; memset(&address, 0, sizeof(address)); - address = addr.getAddress6(); address.sin6_family = AF_INET6; + address.sin6_addr = addr.getAddress6(); address.sin6_port = htons(addr.getPort()); - if (bind(m_handle, (const struct sockaddr *)&address, - sizeof(struct sockaddr_in6)) < 0) { - dstream << (int)m_handle << ": Bind failed: " << strerror(errno) - << std::endl; - throw SocketException("Failed to bind socket"); - } + ret = bind(m_handle, (const struct sockaddr *) &address, + sizeof(struct sockaddr_in6)); } else { struct sockaddr_in address; memset(&address, 0, sizeof(address)); - address = addr.getAddress(); address.sin_family = AF_INET; + address.sin_addr = addr.getAddress(); address.sin_port = htons(addr.getPort()); - if (bind(m_handle, (const struct sockaddr *)&address, - sizeof(struct sockaddr_in)) < 0) { - dstream << (int)m_handle << ": Bind failed: " << strerror(errno) - << std::endl; - throw SocketException("Failed to bind socket"); - } + ret = bind(m_handle, (const struct sockaddr *) &address, + sizeof(struct sockaddr_in)); + } + + if (ret < 0) { + dstream << (int)m_handle << ": Bind failed: " + << SOCKET_ERR_STR(LAST_SOCKET_ERR()) << std::endl; + throw SocketException("Failed to bind socket"); } } @@ -200,7 +198,7 @@ void UDPSocket::Send(const Address &destination, const void *data, int size) if (socket_enable_debug_output) { // Print packet destination and size dstream << (int)m_handle << " -> "; - destination.print(&dstream); + destination.print(dstream); dstream << ", size=" << size; // Print packet contents @@ -233,13 +231,19 @@ void UDPSocket::Send(const Address &destination, const void *data, int size) int sent; if (m_addr_family == AF_INET6) { - struct sockaddr_in6 address = destination.getAddress6(); + struct sockaddr_in6 address = {}; + address.sin6_family = AF_INET6; + address.sin6_addr = destination.getAddress6(); address.sin6_port = htons(destination.getPort()); + sent = sendto(m_handle, (const char *)data, size, 0, (struct sockaddr *)&address, sizeof(struct sockaddr_in6)); } else { - struct sockaddr_in address = destination.getAddress(); + struct sockaddr_in address = {}; + address.sin_family = AF_INET; + address.sin_addr = destination.getAddress(); address.sin_port = htons(destination.getPort()); + sent = sendto(m_handle, (const char *)data, size, 0, (struct sockaddr *)&address, sizeof(struct sockaddr_in)); } @@ -267,9 +271,9 @@ int UDPSocket::Receive(Address &sender, void *data, int size) return -1; u16 address_port = ntohs(address.sin6_port); - IPv6AddressBytes bytes; - memcpy(bytes.bytes, address.sin6_addr.s6_addr, 16); - sender = Address(&bytes, address_port); + const auto *bytes = reinterpret_cast<IPv6AddressBytes*> + (address.sin6_addr.s6_addr); + sender = Address(bytes, address_port); } else { struct sockaddr_in address; memset(&address, 0, sizeof(address)); @@ -291,7 +295,7 @@ int UDPSocket::Receive(Address &sender, void *data, int size) if (socket_enable_debug_output) { // Print packet sender and size dstream << (int)m_handle << " <- "; - sender.print(&dstream); + sender.print(dstream); dstream << ", size=" << received; // Print packet contents @@ -341,7 +345,12 @@ bool UDPSocket::WaitData(int timeout_ms) if (result == 0) return false; - if (result < 0 && (errno == EINTR || errno == EBADF)) { + int e = LAST_SOCKET_ERR(); +#ifdef _WIN32 + if (result < 0 && (e == WSAEINTR || e == WSAEBADF)) { +#else + if (result < 0 && (e == EINTR || e == EBADF)) { +#endif // N.B. select() fails when sockets are destroyed on Connection's dtor // with EBADF. Instead of doing tricky synchronization, allow this // thread to exit but don't throw an exception. @@ -349,18 +358,9 @@ bool UDPSocket::WaitData(int timeout_ms) } if (result < 0) { - dstream << m_handle << ": Select failed: " << strerror(errno) + dstream << (int)m_handle << ": Select failed: " << SOCKET_ERR_STR(e) << std::endl; -#ifdef _WIN32 - int e = WSAGetLastError(); - dstream << (int)m_handle << ": WSAGetLastError()=" << e << std::endl; - if (e == 10004 /* WSAEINTR */ || e == 10009 /* WSAEBADF */) { - infostream << "Ignoring WSAEINTR/WSAEBADF." << std::endl; - return false; - } -#endif - throw SocketException("Select failed"); } else if (!FD_ISSET(m_handle, &readset)) { // No data diff --git a/src/network/socket.h b/src/network/socket.h index e0e76f4c2..d34186b44 100644 --- a/src/network/socket.h +++ b/src/network/socket.h @@ -19,18 +19,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once -#ifdef _WIN32 -#ifndef _WIN32_WINNT -#define _WIN32_WINNT 0x0501 -#endif -#include <windows.h> -#include <winsock2.h> -#include <ws2tcpip.h> -#else -#include <sys/socket.h> -#include <netinet/in.h> -#endif - #include <ostream> #include <cstring> #include "address.h" @@ -53,8 +41,6 @@ public: bool init(bool ipv6, bool noExceptions = false); - // void Close(); - // bool IsOpen(); void Send(const Address &destination, const void *data, int size); // Returns -1 if there is no data int Receive(Address &sender, void *data, int size); diff --git a/src/nodedef.cpp b/src/nodedef.cpp index f27a8154b..2b8ebd773 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -33,6 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "nameidmapping.h" #include "util/numeric.h" #include "util/serialize.h" +#include "util/string.h" #include "exceptions.h" #include "debug.h" #include "gamedef.h" @@ -207,7 +208,28 @@ void TileDef::serialize(std::ostream &os, u16 protocol_version) const u8 version = 6; writeU8(os, version); - os << serializeString16(name); + if (protocol_version > 39) { + os << serializeString16(name); + } else { + // Before f018737, TextureSource::getTextureAverageColor did not handle + // missing textures. "[png" can be used as base texture, but is not known + // on older clients. Hence use "blank.png" to avoid this problem. + // To be forward-compatible with future base textures/modifiers, + // we apply the same prefix to any texture beginning with [, + // except for the ones that are supported on older clients. + bool pass_through = true; + + if (!name.empty() && name[0] == '[') { + pass_through = str_starts_with(name, "[combine:") || + str_starts_with(name, "[inventorycube{") || + str_starts_with(name, "[lowpart:"); + } + + if (pass_through) + os << serializeString16(name); + else + os << serializeString16("blank.png^" + name); + } animation.serialize(os, version); bool has_scale = scale > 0; u16 flags = 0; @@ -403,6 +425,8 @@ void ContentFeatures::reset() palette_name = ""; palette = NULL; node_dig_prediction = "air"; + move_resistance = 0; + liquid_move_physics = false; } void ContentFeatures::setAlphaFromLegacy(u8 legacy_alpha) @@ -440,7 +464,12 @@ void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const writeU16(os, groups.size()); for (const auto &group : groups) { os << serializeString16(group.first); - writeS16(os, group.second); + if (protocol_version < 41 && group.first.compare("bouncy") == 0) { + // Old clients may choke on negative bouncy value + writeS16(os, abs(group.second)); + } else { + writeS16(os, group.second); + } } writeU8(os, param_type); writeU8(os, param_type_2); @@ -489,7 +518,16 @@ void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const writeU32(os, damage_per_second); // liquid - writeU8(os, liquid_type); + LiquidType liquid_type_bc = liquid_type; + if (protocol_version <= 39) { + // Since commit 7f25823, liquid drawtypes can be used even with LIQUID_NONE + // solution: force liquid type accordingly to accepted values + if (drawtype == NDT_LIQUID) + liquid_type_bc = LIQUID_SOURCE; + else if (drawtype == NDT_FLOWINGLIQUID) + liquid_type_bc = LIQUID_FLOWING; + } + writeU8(os, liquid_type_bc); os << serializeString16(liquid_alternative_flowing); os << serializeString16(liquid_alternative_source); writeU8(os, liquid_viscosity); @@ -512,9 +550,12 @@ void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const writeU8(os, legacy_facedir_simple); writeU8(os, legacy_wallmounted); + // new attributes os << serializeString16(node_dig_prediction); writeU8(os, leveled_max); writeU8(os, alpha); + writeU8(os, move_resistance); + writeU8(os, liquid_move_physics); } void ContentFeatures::deSerialize(std::istream &is) @@ -584,9 +625,11 @@ void ContentFeatures::deSerialize(std::istream &is) // liquid liquid_type = (enum LiquidType) readU8(is); + liquid_move_physics = liquid_type != LIQUID_NONE; liquid_alternative_flowing = deSerializeString16(is); liquid_alternative_source = deSerializeString16(is); liquid_viscosity = readU8(is); + move_resistance = liquid_viscosity; // set default move_resistance liquid_renewable = readU8(is); liquid_range = readU8(is); drowning = readU8(is); @@ -618,6 +661,16 @@ void ContentFeatures::deSerialize(std::istream &is) if (is.eof()) throw SerializationError(""); alpha = static_cast<enum AlphaMode>(tmp); + + tmp = readU8(is); + if (is.eof()) + throw SerializationError(""); + move_resistance = tmp; + + tmp = readU8(is); + if (is.eof()) + throw SerializationError(""); + liquid_move_physics = tmp; } catch(SerializationError &e) {}; } @@ -634,7 +687,7 @@ static void fillTileAttribs(ITextureSource *tsrc, TileLayer *layer, bool has_scale = tiledef.scale > 0; bool use_autoscale = tsettings.autoscale_mode == AUTOSCALE_FORCE || (tsettings.autoscale_mode == AUTOSCALE_ENABLE && !has_scale); - if (use_autoscale) { + if (use_autoscale && layer->texture) { auto texture_size = layer->texture->getOriginalSize(); float base_size = tsettings.node_texture_size; float size = std::fmin(texture_size.Width, texture_size.Height); @@ -670,6 +723,7 @@ static void fillTileAttribs(ITextureSource *tsrc, TileLayer *layer, // Animation parameters int frame_count = 1; if (layer->material_flags & MATERIAL_FLAG_ANIMATION) { + assert(layer->texture); int frame_length_ms; tiledef.animation.determineParams(layer->texture->getOriginalSize(), &frame_count, &frame_length_ms, NULL); @@ -680,14 +734,13 @@ static void fillTileAttribs(ITextureSource *tsrc, TileLayer *layer, if (frame_count == 1) { layer->material_flags &= ~MATERIAL_FLAG_ANIMATION; } else { - std::ostringstream os(std::ios::binary); - if (!layer->frames) { + assert(layer->texture); + if (!layer->frames) layer->frames = new std::vector<FrameSpec>(); - } layer->frames->resize(frame_count); + std::ostringstream os(std::ios::binary); for (int i = 0; i < frame_count; i++) { - FrameSpec frame; os.str(""); @@ -779,8 +832,10 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc TileDef tdef[6]; for (u32 j = 0; j < 6; j++) { tdef[j] = tiledef[j]; - if (tdef[j].name.empty()) - tdef[j].name = "unknown_node.png"; + if (tdef[j].name.empty()) { + tdef[j].name = "no_texture.png"; + tdef[j].backface_culling = false; + } } // also the overlay tiles TileDef tdef_overlay[6]; @@ -788,8 +843,9 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc tdef_overlay[j] = tiledef_overlay[j]; // also the special tiles TileDef tdef_spec[6]; - for (u32 j = 0; j < CF_SPECIAL_COUNT; j++) + for (u32 j = 0; j < CF_SPECIAL_COUNT; j++) { tdef_spec[j] = tiledef_special[j]; + } bool is_liquid = false; @@ -1035,6 +1091,10 @@ void NodeDefManager::clear() { ContentFeatures f; f.name = "unknown"; + TileDef unknownTile; + unknownTile.name = "unknown_node.png"; + for (int t = 0; t < 6; t++) + f.tiledef[t] = unknownTile; // Insert directly into containers content_t c = CONTENT_UNKNOWN; m_content_features[c] = f; diff --git a/src/nodedef.h b/src/nodedef.h index 8a6d88071..f90caff8a 100644 --- a/src/nodedef.h +++ b/src/nodedef.h @@ -376,11 +376,15 @@ struct ContentFeatures u32 damage_per_second; // client dig prediction std::string node_dig_prediction; + // how slow players move through + u8 move_resistance = 0; // --- LIQUID PROPERTIES --- // Whether the node is non-liquid, source liquid or flowing liquid enum LiquidType liquid_type; + // If true, movement (e.g. of players) inside this node is liquid-like. + bool liquid_move_physics; // If the content is liquid, this is the flowing version of the liquid. std::string liquid_alternative_flowing; content_t liquid_alternative_flowing_id; @@ -474,6 +478,12 @@ struct ContentFeatures return (liquid_alternative_flowing_id == f.liquid_alternative_flowing_id); } + bool lightingEquivalent(const ContentFeatures &other) const { + return light_propagates == other.light_propagates + && sunlight_propagates == other.sunlight_propagates + && light_source == other.light_source; + } + int getGroup(const std::string &group) const { return itemgroup_get(groups, group); diff --git a/src/noise.cpp b/src/noise.cpp index a10efa3c4..2f4de6855 100644 --- a/src/noise.cpp +++ b/src/noise.cpp @@ -312,51 +312,6 @@ float noise2d_perlin(float x, float y, s32 seed, } -float noise2d_perlin_abs(float x, float y, s32 seed, - int octaves, float persistence, bool eased) -{ - float a = 0; - float f = 1.0; - float g = 1.0; - for (int i = 0; i < octaves; i++) { - a += g * std::fabs(noise2d_gradient(x * f, y * f, seed + i, eased)); - f *= 2.0; - g *= persistence; - } - return a; -} - - -float noise3d_perlin(float x, float y, float z, s32 seed, - int octaves, float persistence, bool eased) -{ - float a = 0; - float f = 1.0; - float g = 1.0; - for (int i = 0; i < octaves; i++) { - a += g * noise3d_gradient(x * f, y * f, z * f, seed + i, eased); - f *= 2.0; - g *= persistence; - } - return a; -} - - -float noise3d_perlin_abs(float x, float y, float z, s32 seed, - int octaves, float persistence, bool eased) -{ - float a = 0; - float f = 1.0; - float g = 1.0; - for (int i = 0; i < octaves; i++) { - a += g * std::fabs(noise3d_gradient(x * f, y * f, z * f, seed + i, eased)); - f *= 2.0; - g *= persistence; - } - return a; -} - - float contour(float v) { v = std::fabs(v); diff --git a/src/noise.h b/src/noise.h index 854781731..e4a9ed6c7 100644 --- a/src/noise.h +++ b/src/noise.h @@ -224,15 +224,6 @@ float noise3d_gradient(float x, float y, float z, s32 seed, bool eased=false); float noise2d_perlin(float x, float y, s32 seed, int octaves, float persistence, bool eased=true); -float noise2d_perlin_abs(float x, float y, s32 seed, - int octaves, float persistence, bool eased=true); - -float noise3d_perlin(float x, float y, float z, s32 seed, - int octaves, float persistence, bool eased=false); - -float noise3d_perlin_abs(float x, float y, float z, s32 seed, - int octaves, float persistence, bool eased=false); - inline float easeCurve(float t) { return t * t * t * (t * (6.f * t - 15.f) + 10.f); diff --git a/src/object_properties.cpp b/src/object_properties.cpp index db06f8930..c7f6becf0 100644 --- a/src/object_properties.cpp +++ b/src/object_properties.cpp @@ -28,7 +28,7 @@ static const video::SColor NULL_BGCOLOR{0, 1, 1, 1}; ObjectProperties::ObjectProperties() { - textures.emplace_back("unknown_object.png"); + textures.emplace_back("no_texture.png"); colors.emplace_back(255,255,255,255); } diff --git a/src/player.cpp b/src/player.cpp index 13b79da04..789d852ea 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "player.h" +#include <cmath> #include "threading/mutex_auto_lock.h" #include "util/numeric.h" #include "hud.h" @@ -70,7 +71,7 @@ Player::Player(const char *name, IItemDefManager *idef): HUD_FLAG_HOTBAR_VISIBLE | HUD_FLAG_HEALTHBAR_VISIBLE | HUD_FLAG_CROSSHAIR_VISIBLE | HUD_FLAG_WIELDITEM_VISIBLE | HUD_FLAG_BREATHBAR_VISIBLE | HUD_FLAG_MINIMAP_VISIBLE | - HUD_FLAG_MINIMAP_RADAR_VISIBLE; + HUD_FLAG_MINIMAP_RADAR_VISIBLE | HUD_FLAG_BASIC_DEBUG; hud_hotbar_itemcount = HUD_HOTBAR_ITEMCOUNT_DEFAULT; @@ -159,6 +160,64 @@ void Player::clearHud() } } +#ifndef SERVER + +u32 PlayerControl::getKeysPressed() const +{ + u32 keypress_bits = + ( (u32)(jump & 1) << 4) | + ( (u32)(aux1 & 1) << 5) | + ( (u32)(sneak & 1) << 6) | + ( (u32)(dig & 1) << 7) | + ( (u32)(place & 1) << 8) | + ( (u32)(zoom & 1) << 9) + ; + + // If any direction keys are pressed pass those through + if (direction_keys != 0) + { + keypress_bits |= direction_keys; + } + // Otherwise set direction keys based on joystick movement (for mod compatibility) + else if (isMoving()) + { + float abs_d; + + // (absolute value indicates forward / backward) + abs_d = abs(movement_direction); + if (abs_d < 3.0f / 8.0f * M_PI) + keypress_bits |= (u32)1; // Forward + if (abs_d > 5.0f / 8.0f * M_PI) + keypress_bits |= (u32)1 << 1; // Backward + + // rotate entire coordinate system by 90 degree + abs_d = movement_direction + M_PI_2; + if (abs_d >= M_PI) + abs_d -= 2 * M_PI; + abs_d = abs(abs_d); + // (value now indicates left / right) + if (abs_d < 3.0f / 8.0f * M_PI) + keypress_bits |= (u32)1 << 2; // Left + if (abs_d > 5.0f / 8.0f * M_PI) + keypress_bits |= (u32)1 << 3; // Right + } + + return keypress_bits; +} + +#endif + +void PlayerControl::unpackKeysPressed(u32 keypress_bits) +{ + direction_keys = keypress_bits & 0xf; + jump = keypress_bits & (1 << 4); + aux1 = keypress_bits & (1 << 5); + sneak = keypress_bits & (1 << 6); + dig = keypress_bits & (1 << 7); + place = keypress_bits & (1 << 8); + zoom = keypress_bits & (1 << 9); +} + void PlayerSettings::readGlobalSettings() { freecam = g_settings->getBool("freecam"); diff --git a/src/player.h b/src/player.h index 8f7b0a3fe..0216cfe9c 100644 --- a/src/player.h +++ b/src/player.h @@ -49,18 +49,18 @@ struct PlayerControl PlayerControl() = default; PlayerControl( - bool a_jump, - bool a_aux1, - bool a_sneak, + bool a_up, bool a_down, bool a_left, bool a_right, + bool a_jump, bool a_aux1, bool a_sneak, bool a_zoom, - bool a_dig, - bool a_place, - float a_pitch, - float a_yaw, - float a_movement_speed, - float a_movement_direction + bool a_dig, bool a_place, + float a_pitch, float a_yaw, + float a_movement_speed, float a_movement_direction ) { + // Encode direction keys into a single value so nobody uses it accidentally + // as movement_{speed,direction} is supposed to be the source of truth. + direction_keys = (a_up&1) | ((a_down&1) << 1) | + ((a_left&1) << 2) | ((a_right&1) << 3); jump = a_jump; aux1 = a_aux1; sneak = a_sneak; @@ -72,15 +72,26 @@ struct PlayerControl movement_speed = a_movement_speed; movement_direction = a_movement_direction; } + +#ifndef SERVER + // For client use + u32 getKeysPressed() const; + inline bool isMoving() const { return movement_speed > 0.001f; } +#endif + + // For server use + void unpackKeysPressed(u32 keypress_bits); + + u8 direction_keys = 0; bool jump = false; bool aux1 = false; bool sneak = false; bool zoom = false; bool dig = false; bool place = false; + // Note: These four are NOT available on the server float pitch = 0.0f; float yaw = 0.0f; - // Note: These two are NOT available on the server float movement_speed = 0.0f; float movement_direction = 0.0f; }; @@ -190,8 +201,6 @@ public: return m_fov_override_spec; } - u32 keyPressed = 0; - HudElement* getHud(u32 id); u32 addHud(HudElement* hud); HudElement* removeHud(u32 id); diff --git a/src/porting.cpp b/src/porting.cpp index 4c87bddee..09627431c 100644 --- a/src/porting.cpp +++ b/src/porting.cpp @@ -35,6 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <algorithm> #include <shlwapi.h> #include <shellapi.h> + #include <mmsystem.h> #endif #if !defined(_WIN32) #include <unistd.h> @@ -69,6 +70,15 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <cstdarg> #include <cstdio> +#if !defined(SERVER) && defined(_WIN32) +// On Windows export some driver-specific variables to encourage Minetest to be +// executed on the discrete GPU in case of systems with two. Portability is fun. +extern "C" { + __declspec(dllexport) DWORD NvOptimusEnablement = 1; + __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 1; +} +#endif + namespace porting { @@ -598,7 +608,7 @@ void initializePaths() // First try $XDG_CACHE_HOME/PROJECT_NAME const char *cache_dir = getenv("XDG_CACHE_HOME"); const char *home_dir = getenv("HOME"); - if (cache_dir) { + if (cache_dir && cache_dir[0] != '\0') { path_cache = std::string(cache_dir) + DIR_DELIM + PROJECT_NAME; } else if (home_dir) { // Then try $HOME/.cache/PROJECT_NAME @@ -766,6 +776,9 @@ bool open_directory(const std::string &path) inline double get_perf_freq() { + // Also use this opportunity to enable high-res timers + timeBeginPeriod(1); + LARGE_INTEGER freq; QueryPerformanceFrequency(&freq); return freq.QuadPart; diff --git a/src/porting_android.cpp b/src/porting_android.cpp index 29e95b8ca..c71fe5ad8 100644 --- a/src/porting_android.cpp +++ b/src/porting_android.cpp @@ -152,48 +152,35 @@ static std::string javaStringToUTF8(jstring js) return str; } -// Calls static method if obj is NULL -static std::string getAndroidPath( - jclass cls, jobject obj, jmethodID mt_getAbsPath, const char *getter) -{ - // Get getter method - jmethodID mt_getter; - if (obj) - mt_getter = jnienv->GetMethodID(cls, getter, "()Ljava/io/File;"); - else - mt_getter = jnienv->GetStaticMethodID(cls, getter, "()Ljava/io/File;"); - - // Call getter - jobject ob_file; - if (obj) - ob_file = jnienv->CallObjectMethod(obj, mt_getter); - else - ob_file = jnienv->CallStaticObjectMethod(cls, mt_getter); - - // Call getAbsolutePath - auto js_path = (jstring) jnienv->CallObjectMethod(ob_file, mt_getAbsPath); - - return javaStringToUTF8(js_path); -} - void initializePathsAndroid() { - // Get Environment class - jclass cls_Env = jnienv->FindClass("android/os/Environment"); - // Get File class - jclass cls_File = jnienv->FindClass("java/io/File"); - // Get getAbsolutePath method - jmethodID mt_getAbsPath = jnienv->GetMethodID(cls_File, - "getAbsolutePath", "()Ljava/lang/String;"); - std::string path_storage = getAndroidPath(cls_Env, nullptr, - mt_getAbsPath, "getExternalStorageDirectory"); - - 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(); + // Set user and share paths + { + jmethodID getUserDataPath = jnienv->GetMethodID(nativeActivity, + "getUserDataPath", "()Ljava/lang/String;"); + FATAL_ERROR_IF(getUserDataPath==nullptr, + "porting::initializePathsAndroid unable to find Java getUserDataPath method"); + jobject result = jnienv->CallObjectMethod(app_global->activity->clazz, getUserDataPath); + const char *javachars = jnienv->GetStringUTFChars((jstring) result, nullptr); + path_user = javachars; + path_share = javachars; + path_locale = path_share + DIR_DELIM + "locale"; + jnienv->ReleaseStringUTFChars((jstring) result, javachars); + } + + // Set cache path + { + jmethodID getCachePath = jnienv->GetMethodID(nativeActivity, + "getCachePath", "()Ljava/lang/String;"); + FATAL_ERROR_IF(getCachePath==nullptr, + "porting::initializePathsAndroid unable to find Java getCachePath method"); + jobject result = jnienv->CallObjectMethod(app_global->activity->clazz, getCachePath); + const char *javachars = jnienv->GetStringUTFChars((jstring) result, nullptr); + path_cache = javachars; + jnienv->ReleaseStringUTFChars((jstring) result, javachars); + + migrateCachePath(); + } } void showInputDialog(const std::string &acceptButton, const std::string &hint, diff --git a/src/remoteplayer.cpp b/src/remoteplayer.cpp index 925ad001b..20be7a8c8 100644 --- a/src/remoteplayer.cpp +++ b/src/remoteplayer.cpp @@ -31,6 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc., /* RemotePlayer */ + // static config cache for remoteplayer bool RemotePlayer::m_setting_cache_loaded = false; float RemotePlayer::m_setting_chat_message_limit_per_10sec = 0.0f; @@ -46,6 +47,7 @@ RemotePlayer::RemotePlayer(const char *name, IItemDefManager *idef): g_settings->getU16("chat_message_limit_trigger_kick"); RemotePlayer::m_setting_cache_loaded = true; } + movement_acceleration_default = g_settings->getFloat("movement_acceleration_default") * BS; movement_acceleration_air = g_settings->getFloat("movement_acceleration_air") * BS; movement_acceleration_fast = g_settings->getFloat("movement_acceleration_fast") * BS; @@ -59,32 +61,16 @@ RemotePlayer::RemotePlayer(const char *name, IItemDefManager *idef): movement_liquid_sink = g_settings->getFloat("movement_liquid_sink") * BS; movement_gravity = g_settings->getFloat("movement_gravity") * BS; - // copy defaults - m_cloud_params.density = 0.4f; - m_cloud_params.color_bright = video::SColor(229, 240, 240, 255); - m_cloud_params.color_ambient = video::SColor(255, 0, 0, 0); - m_cloud_params.height = 120.0f; - m_cloud_params.thickness = 16.0f; - m_cloud_params.speed = v2f(0.0f, -2.0f); - // Skybox defaults: - - SkyboxDefaults sky_defaults; - - m_skybox_params.sky_color = sky_defaults.getSkyColorDefaults(); - m_skybox_params.type = "regular"; - m_skybox_params.clouds = true; - m_skybox_params.fog_sun_tint = video::SColor(255, 244, 125, 29); - m_skybox_params.fog_moon_tint = video::SColorf(0.5, 0.6, 0.8, 1).toSColor(); - m_skybox_params.fog_tint_type = "default"; - - m_sun_params = sky_defaults.getSunDefaults(); - m_moon_params = sky_defaults.getMoonDefaults(); - m_star_params = sky_defaults.getStarDefaults(); + m_cloud_params = SkyboxDefaults::getCloudDefaults(); + m_skybox_params = SkyboxDefaults::getSkyDefaults(); + m_sun_params = SkyboxDefaults::getSunDefaults(); + m_moon_params = SkyboxDefaults::getMoonDefaults(); + m_star_params = SkyboxDefaults::getStarDefaults(); } -const RemotePlayerChatResult RemotePlayer::canSendChatMessage() +RemotePlayerChatResult RemotePlayer::canSendChatMessage() { // Rate limit messages u32 now = time(NULL); diff --git a/src/remoteplayer.h b/src/remoteplayer.h index 8d086fc5a..0ab33adfe 100644 --- a/src/remoteplayer.h +++ b/src/remoteplayer.h @@ -21,8 +21,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include "player.h" -#include "cloudparams.h" #include "skyparams.h" +#include "lighting.h" class PlayerSAO; @@ -47,7 +47,7 @@ public: PlayerSAO *getPlayerSAO() { return m_sao; } void setPlayerSAO(PlayerSAO *sao) { m_sao = sao; } - const RemotePlayerChatResult canSendChatMessage(); + RemotePlayerChatResult canSendChatMessage(); void setHotbarItemcount(s32 hotbar_itemcount) { @@ -126,12 +126,14 @@ public: *frame_speed = local_animation_speed; } + void setLighting(const Lighting &lighting) { m_lighting = lighting; } + + const Lighting& getLighting() const { return m_lighting; } + void setDirty(bool dirty) { m_dirty = true; } u16 protocol_version = 0; - - // v1 for clients older than 5.1.0-dev - u16 formspec_version = 1; + u16 formspec_version = 0; session_t getPeerId() const { return m_peer_id; } @@ -163,5 +165,7 @@ private: MoonParams m_moon_params; StarParams m_star_params; + Lighting m_lighting; + session_t m_peer_id = PEER_ID_INEXISTENT; }; diff --git a/src/rollback.cpp b/src/rollback.cpp index 3cd9c7ce7..b454e40c9 100644 --- a/src/rollback.cpp +++ b/src/rollback.cpp @@ -483,6 +483,7 @@ const std::list<ActionRow> RollbackManager::actionRowsFromSelect(sqlite3_stmt* s row.actor = sqlite3_column_int (stmt, 0); row.timestamp = sqlite3_column_int64(stmt, 1); row.type = sqlite3_column_int (stmt, 2); + row.nodeMeta = 0; if (row.type == RollbackAction::TYPE_MODIFY_INVENTORY_STACK) { text = sqlite3_column_text (stmt, 3); @@ -941,12 +942,6 @@ void RollbackManager::addAction(const RollbackAction & action) } } -std::list<RollbackAction> RollbackManager::getEntriesSince(time_t first_time) -{ - flush(); - return getActionsSince(first_time); -} - std::list<RollbackAction> RollbackManager::getNodeActors(v3s16 pos, int range, time_t seconds, int limit) { diff --git a/src/rollback.h b/src/rollback.h index 1d9949d15..ff96e513f 100644 --- a/src/rollback.h +++ b/src/rollback.h @@ -46,7 +46,6 @@ public: void flush(); void addAction(const RollbackAction & action); - std::list<RollbackAction> getEntriesSince(time_t first_time); std::list<RollbackAction> getNodeActors(v3s16 pos, int range, time_t seconds, int limit); std::list<RollbackAction> getRevertActions( diff --git a/src/script/common/CMakeLists.txt b/src/script/common/CMakeLists.txt index d07f6ab1b..3e84b46c7 100644 --- a/src/script/common/CMakeLists.txt +++ b/src/script/common/CMakeLists.txt @@ -3,6 +3,7 @@ set(common_SCRIPT_COMMON_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/c_converter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/c_types.cpp ${CMAKE_CURRENT_SOURCE_DIR}/c_internal.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/c_packer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/helper.cpp PARENT_SCOPE) diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 8ca3a722f..0bdeaab9e 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -751,6 +751,9 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index) // the slowest possible f.liquid_viscosity = getintfield_default(L, index, "liquid_viscosity", f.liquid_viscosity); + // If move_resistance is not set explicitly, + // move_resistance is equal to liquid_viscosity + f.move_resistance = f.liquid_viscosity; f.liquid_range = getintfield_default(L, index, "liquid_range", f.liquid_range); f.leveled = getintfield_default(L, index, "leveled", f.leveled); @@ -854,6 +857,21 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index) getstringfield(L, index, "node_dig_prediction", f.node_dig_prediction); + // How much the node slows down players, ranging from 1 to 7, + // the higher, the slower. + f.move_resistance = getintfield_default(L, index, + "move_resistance", f.move_resistance); + + // Whether e.g. players in this node will have liquid movement physics + lua_getfield(L, index, "liquid_move_physics"); + if(lua_isboolean(L, -1)) { + f.liquid_move_physics = lua_toboolean(L, -1); + } else if(lua_isnil(L, -1)) { + f.liquid_move_physics = f.liquid_type != LIQUID_NONE; + } else { + errorstream << "Field \"liquid_move_physics\": Invalid type!" << std::endl; + } + lua_pop(L, 1); } void push_content_features(lua_State *L, const ContentFeatures &c) @@ -1004,6 +1022,10 @@ void push_content_features(lua_State *L, const ContentFeatures &c) lua_setfield(L, -2, "legacy_wallmounted"); lua_pushstring(L, c.node_dig_prediction.c_str()); lua_setfield(L, -2, "node_dig_prediction"); + lua_pushnumber(L, c.move_resistance); + lua_setfield(L, -2, "move_resistance"); + lua_pushboolean(L, c.liquid_move_physics); + lua_setfield(L, -2, "liquid_move_physics"); } /******************************************************************************/ @@ -1384,24 +1406,27 @@ void push_tool_capabilities(lua_State *L, } /******************************************************************************/ -void push_inventory(lua_State *L, Inventory *inventory) +void push_inventory_list(lua_State *L, const InventoryList &invlist) { - if (! inventory) - throw SerializationError("Attempt to push nonexistant inventory"); - std::vector<const InventoryList*> lists = inventory->getLists(); - std::vector<const InventoryList*>::iterator iter = lists.begin(); + push_items(L, invlist.getItems()); +} + +/******************************************************************************/ +void push_inventory_lists(lua_State *L, const Inventory &inv) +{ + const auto &lists = inv.getLists(); lua_createtable(L, 0, lists.size()); - for (; iter != lists.end(); iter++) { - const char* name = (*iter)->getName().c_str(); - lua_pushstring(L, name); - push_inventory_list(L, inventory, name); + for(const InventoryList *list : lists) { + const std::string &name = list->getName(); + lua_pushlstring(L, name.c_str(), name.size()); + push_inventory_list(L, *list); lua_rawset(L, -3); } } /******************************************************************************/ void read_inventory_list(lua_State *L, int tableindex, - Inventory *inv, const char *name, Server* srv, int forcesize) + Inventory *inv, const char *name, IGameDef *gdef, int forcesize) { if(tableindex < 0) tableindex = lua_gettop(L) + 1 + tableindex; @@ -1413,7 +1438,7 @@ void read_inventory_list(lua_State *L, int tableindex, } // Get Lua-specified items to insert into the list - std::vector<ItemStack> items = read_items(L, tableindex,srv); + std::vector<ItemStack> items = read_items(L, tableindex, gdef); size_t listsize = (forcesize >= 0) ? forcesize : items.size(); // Create or resize/clear list @@ -1430,19 +1455,6 @@ void read_inventory_list(lua_State *L, int tableindex, } } -void push_inventory_list(lua_State *L, Inventory *inv, const char *name) -{ - InventoryList *invlist = inv->getList(name); - if(invlist == NULL){ - lua_pushnil(L); - return; - } - std::vector<ItemStack> items; - for(u32 i=0; i<invlist->getSize(); i++) - items.push_back(invlist->getItem(i)); - push_items(L, items); -} - /******************************************************************************/ struct TileAnimationParams read_animation_definition(lua_State *L, int index) { @@ -1701,7 +1713,7 @@ void push_items(lua_State *L, const std::vector<ItemStack> &items) } /******************************************************************************/ -std::vector<ItemStack> read_items(lua_State *L, int index, Server *srv) +std::vector<ItemStack> read_items(lua_State *L, int index, IGameDef *gdef) { if(index < 0) index = lua_gettop(L) + 1 + index; @@ -1717,7 +1729,7 @@ std::vector<ItemStack> read_items(lua_State *L, int index, Server *srv) if (items.size() < (u32) key) { items.resize(key); } - items[key - 1] = read_item(L, -1, srv->idef()); + items[key - 1] = read_item(L, -1, gdef->idef()); lua_pop(L, 1); } return items; @@ -1768,24 +1780,19 @@ bool read_noiseparams(lua_State *L, int index, NoiseParams *np) void push_noiseparams(lua_State *L, NoiseParams *np) { lua_newtable(L); - push_float_string(L, np->offset); - lua_setfield(L, -2, "offset"); - push_float_string(L, np->scale); - lua_setfield(L, -2, "scale"); - push_float_string(L, np->persist); - lua_setfield(L, -2, "persistence"); - push_float_string(L, np->lacunarity); - lua_setfield(L, -2, "lacunarity"); - lua_pushnumber(L, np->seed); - lua_setfield(L, -2, "seed"); - lua_pushnumber(L, np->octaves); - lua_setfield(L, -2, "octaves"); + setfloatfield(L, -1, "offset", np->offset); + setfloatfield(L, -1, "scale", np->scale); + setfloatfield(L, -1, "persist", np->persist); + setfloatfield(L, -1, "persistence", np->persist); + setfloatfield(L, -1, "lacunarity", np->lacunarity); + setintfield( L, -1, "seed", np->seed); + setintfield( L, -1, "octaves", np->octaves); push_flags_string(L, flagdesc_noiseparams, np->flags, np->flags); lua_setfield(L, -2, "flags"); - push_v3_float_string(L, np->spread); + push_v3f(L, np->spread); lua_setfield(L, -2, "spread"); } @@ -2042,6 +2049,12 @@ void push_hud_element(lua_State *L, HudElement *elem) lua_pushnumber(L, elem->number); lua_setfield(L, -2, "number"); + if (elem->type == HUD_ELEM_WAYPOINT) { + // waypoints reuse the item field to store precision, precision = item - 1 + lua_pushnumber(L, elem->item - 1); + lua_setfield(L, -2, "precision"); + } + // push the item field for waypoints as well for backwards compatibility lua_pushnumber(L, elem->item); lua_setfield(L, -2, "item"); diff --git a/src/script/common/c_content.h b/src/script/common/c_content.h index 1aed7901e..a6b96c012 100644 --- a/src/script/common/c_content.h +++ b/src/script/common/c_content.h @@ -55,10 +55,11 @@ struct ObjectProperties; struct SimpleSoundSpec; struct ServerSoundParams; class Inventory; +class InventoryList; struct NodeBox; struct ContentFeatures; struct TileDef; -class Server; +class IGameDef; struct DigParams; struct HitParams; struct EnumString; @@ -124,11 +125,12 @@ void push_inventory (lua_State *L, Inventory *inventory); void push_inventory_list (lua_State *L, - Inventory *inv, - const char *name); + const InventoryList &invlist); +void push_inventory_lists (lua_State *L, + const Inventory &inv); void read_inventory_list (lua_State *L, int tableindex, Inventory *inv, const char *name, - Server *srv, int forcesize=-1); + IGameDef *gdef, int forcesize=-1); MapNode readnode (lua_State *L, int index, const NodeDefManager *ndef); @@ -168,7 +170,7 @@ void push_items (lua_State *L, std::vector<ItemStack> read_items (lua_State *L, int index, - Server* srv); + IGameDef* gdef); void push_soundspec (lua_State *L, const SimpleSoundSpec &spec); diff --git a/src/script/common/c_converter.cpp b/src/script/common/c_converter.cpp index 19734b913..b5ff52f73 100644 --- a/src/script/common/c_converter.cpp +++ b/src/script/common/c_converter.cpp @@ -52,32 +52,12 @@ if (value < F1000_MIN || value > F1000_MAX) { \ /** - * A helper which sets (if available) the vector metatable from builtin as metatable - * for the table on top of the stack + * A helper which sets the vector 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) -{ - auto str = ftos(value); - lua_pushstring(L, str.c_str()); + lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_VECTOR_METATABLE); + lua_setmetatable(L, -2); } void push_v3f(lua_State *L, v3f p) @@ -101,26 +81,6 @@ void push_v2f(lua_State *L, v2f p) lua_setfield(L, -2, "y"); } -void push_v3_float_string(lua_State *L, v3f p) -{ - lua_createtable(L, 0, 3); - push_float_string(L, p.X); - lua_setfield(L, -2, "x"); - push_float_string(L, p.Y); - lua_setfield(L, -2, "y"); - push_float_string(L, p.Z); - lua_setfield(L, -2, "z"); -} - -void push_v2_float_string(lua_State *L, v2f p) -{ - lua_createtable(L, 0, 2); - push_float_string(L, p.X); - lua_setfield(L, -2, "x"); - push_float_string(L, p.Y); - lua_setfield(L, -2, "y"); -} - v2s16 read_v2s16(lua_State *L, int index) { v2s16 p; @@ -479,17 +439,9 @@ size_t read_stringlist(lua_State *L, int index, std::vector<std::string> *result Table field getters */ -#if defined(__MINGW32__) && !defined(__MINGW64__) -/* MinGW 32-bit somehow crashes in the std::set destructor when this - * variable is thread-local, so just don't do that. */ -static std::set<u64> warned_msgs; -#endif - bool check_field_or_nil(lua_State *L, int index, int type, const char *fieldname) { -#if !defined(__MINGW32__) || defined(__MINGW64__) thread_local std::set<u64> warned_msgs; -#endif int t = lua_type(L, index); if (t == LUA_TNIL) diff --git a/src/script/common/c_converter.h b/src/script/common/c_converter.h index 6ad6f3212..a14eb9186 100644 --- a/src/script/common/c_converter.h +++ b/src/script/common/c_converter.h @@ -118,9 +118,6 @@ std::vector<aabb3f> read_aabb3f_vector (lua_State *L, int index, f32 scale); size_t read_stringlist (lua_State *L, int index, std::vector<std::string> *result); -void push_float_string (lua_State *L, float value); -void push_v3_float_string(lua_State *L, v3f p); -void push_v2_float_string(lua_State *L, v2f p); void push_v2s16 (lua_State *L, v2s16 p); void push_v2s32 (lua_State *L, v2s32 p); void push_v3s16 (lua_State *L, v3s16 p); diff --git a/src/script/common/c_internal.cpp b/src/script/common/c_internal.cpp index df82dba14..ddd2d184c 100644 --- a/src/script/common/c_internal.cpp +++ b/src/script/common/c_internal.cpp @@ -166,3 +166,17 @@ void log_deprecated(lua_State *L, std::string message, int stack_depth) infostream << script_get_backtrace(L) << std::endl; } +void call_string_dump(lua_State *L, int idx) +{ + // Retrieve string.dump from insecure env to avoid it being tampered with + lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_GLOBALS_BACKUP); + if (!lua_isnil(L, -1)) + lua_getfield(L, -1, "string"); + else + lua_getglobal(L, "string"); + lua_getfield(L, -1, "dump"); + lua_remove(L, -2); // remove _G + lua_remove(L, -2); // remove 'string' table + lua_pushvalue(L, idx); + lua_call(L, 1, 1); +} diff --git a/src/script/common/c_internal.h b/src/script/common/c_internal.h index ab2d7b975..272a39941 100644 --- a/src/script/common/c_internal.h +++ b/src/script/common/c_internal.h @@ -54,6 +54,10 @@ extern "C" { #define CUSTOM_RIDX_GLOBALS_BACKUP (CUSTOM_RIDX_BASE + 1) #define CUSTOM_RIDX_CURRENT_MOD_NAME (CUSTOM_RIDX_BASE + 2) #define CUSTOM_RIDX_BACKTRACE (CUSTOM_RIDX_BASE + 3) +#define CUSTOM_RIDX_HTTP_API_LUA (CUSTOM_RIDX_BASE + 4) +#define CUSTOM_RIDX_VECTOR_METATABLE (CUSTOM_RIDX_BASE + 5) +#define CUSTOM_RIDX_METATABLE_MAP (CUSTOM_RIDX_BASE + 6) + // Determine if CUSTOM_RIDX_SCRIPTAPI will hold a light or full userdata #if defined(__aarch64__) && USE_LUAJIT @@ -136,3 +140,7 @@ DeprecatedHandlingMode get_deprecated_handling_mode(); * @param stack_depth How far on the stack to the first user function (ie: not builtin or core) */ void log_deprecated(lua_State *L, std::string message, int stack_depth = 1); + +// Safely call string.dump on a function value +// (does not pop, leaves one value on stack) +void call_string_dump(lua_State *L, int idx); diff --git a/src/script/common/c_packer.cpp b/src/script/common/c_packer.cpp new file mode 100644 index 000000000..ede00c758 --- /dev/null +++ b/src/script/common/c_packer.cpp @@ -0,0 +1,596 @@ +/* +Minetest +Copyright (C) 2022 sfan5 <sfan5@live.de> + +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 <cstdio> +#include <cstring> +#include <cmath> +#include <cassert> +#include <unordered_set> +#include <unordered_map> +#include "c_packer.h" +#include "c_internal.h" +#include "log.h" +#include "debug.h" +#include "threading/mutex_auto_lock.h" + +extern "C" { +#include <lauxlib.h> +} + +// +// Helpers +// + +// convert negative index to absolute position on Lua stack +static inline int absidx(lua_State *L, int idx) +{ + assert(idx < 0); + return lua_gettop(L) + idx + 1; +} + +// does the type put anything into PackedInstr::sdata? +static inline bool uses_sdata(int type) +{ + switch (type) { + case LUA_TSTRING: + case LUA_TFUNCTION: + case LUA_TUSERDATA: + return true; + default: + return false; + } +} + +// does the type put anything into PackedInstr::<union>? +static inline bool uses_union(int type) +{ + switch (type) { + case LUA_TNIL: + case LUA_TSTRING: + case LUA_TFUNCTION: + return false; + default: + return true; + } +} + +static inline bool can_set_into(int ktype, int vtype) +{ + switch (ktype) { + case LUA_TNUMBER: + return !uses_union(vtype); + case LUA_TSTRING: + return !uses_sdata(vtype); + default: + return false; + } +} + +// is the key suitable for use with set_into? +static inline bool suitable_key(lua_State *L, int idx) +{ + if (lua_type(L, idx) == LUA_TSTRING) { + // strings may not have a NULL byte (-> lua_setfield) + size_t len; + const char *str = lua_tolstring(L, idx, &len); + return strlen(str) == len; + } else { + assert(lua_type(L, idx) == LUA_TNUMBER); + // numbers must fit into an s32 and be integers (-> lua_rawseti) + lua_Number n = lua_tonumber(L, idx); + return std::floor(n) == n && n >= S32_MIN && n <= S32_MAX; + } +} + +namespace { + // checks if you left any values on the stack, for debugging + class StackChecker { + lua_State *L; + int top; + public: + StackChecker(lua_State *L) : L(L), top(lua_gettop(L)) {} + ~StackChecker() { + assert(lua_gettop(L) >= top); + if (lua_gettop(L) > top) { + rawstream << "Lua stack not cleaned up: " + << lua_gettop(L) << " != " << top + << " (false-positive if exception thrown)" << std::endl; + } + } + }; + + // Since an std::vector may reallocate, this is the only safe way to keep + // a reference to a particular element. + template <typename T> + class VectorRef { + std::vector<T> *vec; + size_t idx; + VectorRef(std::vector<T> *vec, size_t idx) : vec(vec), idx(idx) {} + public: + constexpr VectorRef() : vec(nullptr), idx(0) {} + static VectorRef<T> front(std::vector<T> &vec) { + return VectorRef(&vec, 0); + } + static VectorRef<T> back(std::vector<T> &vec) { + return VectorRef(&vec, vec.size() - 1); + } + T &operator*() { return (*vec)[idx]; } + T *operator->() { return &(*vec)[idx]; } + operator bool() const { return vec != nullptr; } + }; + + struct Packer { + PackInFunc fin; + PackOutFunc fout; + }; + + typedef std::pair<std::string, Packer> PackerTuple; +} + +static inline auto emplace(PackedValue &pv, s16 type) +{ + pv.i.emplace_back(); + auto ref = VectorRef<PackedInstr>::back(pv.i); + ref->type = type; + // Initialize fields that may be left untouched + if (type == LUA_TTABLE) { + ref->uidata1 = 0; + ref->uidata2 = 0; + } else if (type == LUA_TUSERDATA) { + ref->ptrdata = nullptr; + } else if (type == INSTR_POP) { + ref->sidata2 = 0; + } + return ref; +} + +// +// Management of registered packers +// + +static std::unordered_map<std::string, Packer> g_packers; +static std::mutex g_packers_lock; + +void script_register_packer(lua_State *L, const char *regname, + PackInFunc fin, PackOutFunc fout) +{ + // Store away callbacks + { + MutexAutoLock autolock(g_packers_lock); + auto it = g_packers.find(regname); + if (it == g_packers.end()) { + auto &ref = g_packers[regname]; + ref.fin = fin; + ref.fout = fout; + } else { + FATAL_ERROR_IF(it->second.fin != fin || it->second.fout != fout, + "Packer registered twice with mismatching callbacks"); + } + } + + // Save metatable so we can identify instances later + lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_METATABLE_MAP); + if (lua_isnil(L, -1)) { + lua_newtable(L); + lua_pushvalue(L, -1); + lua_rawseti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_METATABLE_MAP); + } + + luaL_getmetatable(L, regname); + FATAL_ERROR_IF(lua_isnil(L, -1), "No metatable registered with that name"); + + // CUSTOM_RIDX_METATABLE_MAP contains { [metatable] = "regname", ... } + // check first + lua_pushstring(L, regname); + lua_rawget(L, -3); + if (!lua_isnil(L, -1)) { + FATAL_ERROR_IF(lua_topointer(L, -1) != lua_topointer(L, -2), + "Packer registered twice with inconsistent metatable"); + } + lua_pop(L, 1); + // then set + lua_pushstring(L, regname); + lua_rawset(L, -3); + + lua_pop(L, 1); +} + +static bool find_packer(const char *regname, PackerTuple &out) +{ + MutexAutoLock autolock(g_packers_lock); + auto it = g_packers.find(regname); + if (it == g_packers.end()) + return false; + // copy data for thread safety + out.first = it->first; + out.second = it->second; + return true; +} + +static bool find_packer(lua_State *L, int idx, PackerTuple &out) +{ +#ifndef NDEBUG + StackChecker checker(L); +#endif + + // retrieve metatable of the object + if (lua_getmetatable(L, idx) != 1) + return false; + + // use our global table to map it to the registry name + lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_METATABLE_MAP); + assert(lua_istable(L, -1)); + lua_pushvalue(L, -2); + lua_rawget(L, -2); + if (lua_isnil(L, -1)) { + lua_pop(L, 3); + return false; + } + + // load the associated data + bool found = find_packer(lua_tostring(L, -1), out); + FATAL_ERROR_IF(!found, "Inconsistent internal state"); + lua_pop(L, 3); + return true; +} + +// +// Packing implementation +// + +static VectorRef<PackedInstr> record_object(lua_State *L, int idx, PackedValue &pv, + std::unordered_map<const void *, s32> &seen) +{ + const void *ptr = lua_topointer(L, idx); + assert(ptr); + auto found = seen.find(ptr); + if (found == seen.end()) { + seen[ptr] = pv.i.size(); + return VectorRef<PackedInstr>(); + } + s32 ref = found->second; + assert(ref < (s32)pv.i.size()); + // reuse the value from first time + auto r = emplace(pv, INSTR_PUSHREF); + r->ref = ref; + pv.i[ref].keep_ref = true; + return r; +} + +static VectorRef<PackedInstr> pack_inner(lua_State *L, int idx, int vidx, PackedValue &pv, + std::unordered_map<const void *, s32> &seen) +{ +#ifndef NDEBUG + StackChecker checker(L); + assert(idx > 0); + assert(vidx > 0); +#endif + + switch (lua_type(L, idx)) { + case LUA_TNONE: + case LUA_TNIL: + return emplace(pv, LUA_TNIL); + case LUA_TBOOLEAN: { + auto r = emplace(pv, LUA_TBOOLEAN); + r->bdata = lua_toboolean(L, idx); + return r; + } + case LUA_TNUMBER: { + auto r = emplace(pv, LUA_TNUMBER); + r->ndata = lua_tonumber(L, idx); + return r; + } + case LUA_TSTRING: { + auto r = emplace(pv, LUA_TSTRING); + size_t len; + const char *str = lua_tolstring(L, idx, &len); + assert(str); + r->sdata.assign(str, len); + return r; + } + case LUA_TTABLE: { + auto r = record_object(L, idx, pv, seen); + if (r) + return r; + break; // execution continues + } + case LUA_TFUNCTION: { + auto r = record_object(L, idx, pv, seen); + if (r) + return r; + r = emplace(pv, LUA_TFUNCTION); + call_string_dump(L, idx); + size_t len; + const char *str = lua_tolstring(L, -1, &len); + assert(str); + r->sdata.assign(str, len); + lua_pop(L, 1); + return r; + } + case LUA_TUSERDATA: { + auto r = record_object(L, idx, pv, seen); + if (r) + return r; + PackerTuple ser; + if (!find_packer(L, idx, ser)) + throw LuaError("Cannot serialize unsupported userdata"); + pv.contains_userdata = true; + r = emplace(pv, LUA_TUSERDATA); + r->sdata = ser.first; + r->ptrdata = ser.second.fin(L, idx); + return r; + } + default: { + std::string err = "Cannot serialize type "; + err += lua_typename(L, lua_type(L, idx)); + throw LuaError(err); + } + } + + // LUA_TTABLE + lua_checkstack(L, 5); + + auto rtable = emplace(pv, LUA_TTABLE); + const int vi_table = vidx++; + + lua_pushnil(L); + while (lua_next(L, idx) != 0) { + // key at -2, value at -1 + const int ktype = lua_type(L, -2), vtype = lua_type(L, -1); + if (ktype == LUA_TNUMBER) + rtable->uidata1++; // narr + else + rtable->uidata2++; // nrec + + // check if we can use a shortcut + if (can_set_into(ktype, vtype) && suitable_key(L, -2)) { + // push only the value + auto rval = pack_inner(L, absidx(L, -1), vidx, pv, seen); + rval->pop = rval->type != LUA_TTABLE; + // and where to put it: + rval->set_into = vi_table; + if (ktype == LUA_TSTRING) + rval->sdata = lua_tostring(L, -2); + else + rval->sidata1 = lua_tointeger(L, -2); + // pop tables after the fact + if (!rval->pop) { + auto ri1 = emplace(pv, INSTR_POP); + ri1->sidata1 = vidx; + } + } else { + // push the key and value + pack_inner(L, absidx(L, -2), vidx, pv, seen); + vidx++; + pack_inner(L, absidx(L, -1), vidx, pv, seen); + vidx++; + // push an instruction to set them + auto ri1 = emplace(pv, INSTR_SETTABLE); + ri1->set_into = vi_table; + ri1->sidata1 = vidx - 2; + ri1->sidata2 = vidx - 1; + ri1->pop = true; + vidx -= 2; + } + + lua_pop(L, 1); + } + + assert(vidx == vi_table + 1); + return rtable; +} + +PackedValue *script_pack(lua_State *L, int idx) +{ + if (idx < 0) + idx = absidx(L, idx); + + PackedValue pv; + std::unordered_map<const void *, s32> seen; + pack_inner(L, idx, 1, pv, seen); + + return new PackedValue(std::move(pv)); +} + +// +// Unpacking implementation +// + +void script_unpack(lua_State *L, PackedValue *pv) +{ + lua_newtable(L); // table at index top to track ref indices -> objects + const int top = lua_gettop(L); + int ctr = 0; + + for (size_t packed_idx = 0; packed_idx < pv->i.size(); packed_idx++) { + auto &i = pv->i[packed_idx]; + + // If leaving values on stack make sure there's space (every 5th iteration) + if (!i.pop && (ctr++) >= 5) { + lua_checkstack(L, 5); + ctr = 0; + } + + switch (i.type) { + /* Instructions */ + case INSTR_SETTABLE: + lua_pushvalue(L, top + i.sidata1); // key + lua_pushvalue(L, top + i.sidata2); // value + lua_rawset(L, top + i.set_into); + if (i.pop) { + if (i.sidata1 != i.sidata2) { + // removing moves indices so pop higher index first + lua_remove(L, top + std::max(i.sidata1, i.sidata2)); + lua_remove(L, top + std::min(i.sidata1, i.sidata2)); + } else { + lua_remove(L, top + i.sidata1); + } + } + continue; + case INSTR_POP: + lua_remove(L, top + i.sidata1); + if (i.sidata2 > 0) + lua_remove(L, top + i.sidata2); + continue; + case INSTR_PUSHREF: + lua_pushinteger(L, i.ref); + lua_rawget(L, top); + break; + + /* Lua types */ + case LUA_TNIL: + lua_pushnil(L); + break; + case LUA_TBOOLEAN: + lua_pushboolean(L, i.bdata); + break; + case LUA_TNUMBER: + lua_pushnumber(L, i.ndata); + break; + case LUA_TSTRING: + lua_pushlstring(L, i.sdata.data(), i.sdata.size()); + break; + case LUA_TTABLE: + lua_createtable(L, i.uidata1, i.uidata2); + break; + case LUA_TFUNCTION: + luaL_loadbuffer(L, i.sdata.data(), i.sdata.size(), nullptr); + break; + case LUA_TUSERDATA: { + PackerTuple ser; + sanity_check(find_packer(i.sdata.c_str(), ser)); + ser.second.fout(L, i.ptrdata); + i.ptrdata = nullptr; // ownership taken by callback + break; + } + + default: + assert(0); + break; + } + + if (i.keep_ref) { + lua_pushinteger(L, packed_idx); + lua_pushvalue(L, -2); + lua_rawset(L, top); + } + + if (i.set_into) { + if (!i.pop) + lua_pushvalue(L, -1); + if (uses_sdata(i.type)) + lua_rawseti(L, top + i.set_into, i.sidata1); + else + lua_setfield(L, top + i.set_into, i.sdata.c_str()); + } else { + if (i.pop) + lua_pop(L, 1); + } + } + + // as part of the unpacking process we take ownership of all userdata + pv->contains_userdata = false; + // leave exactly one value on the stack + lua_settop(L, top+1); + lua_remove(L, top); +} + +// +// PackedValue +// + +PackedValue::~PackedValue() +{ + if (!contains_userdata) + return; + for (auto &i : this->i) { + if (i.type == LUA_TUSERDATA && i.ptrdata) { + PackerTuple ser; + if (find_packer(i.sdata.c_str(), ser)) { + // tell it to deallocate object + ser.second.fout(nullptr, i.ptrdata); + } else { + assert(false); + } + } + } +} + +// +// script_dump_packed +// + +#ifndef NDEBUG +void script_dump_packed(const PackedValue *val) +{ + printf("instruction stream: [\n"); + for (const auto &i : val->i) { + printf("\t("); + switch (i.type) { + case INSTR_SETTABLE: + printf("SETTABLE(%d, %d)", i.sidata1, i.sidata2); + break; + case INSTR_POP: + printf(i.sidata2 ? "POP(%d, %d)" : "POP(%d)", i.sidata1, i.sidata2); + break; + case INSTR_PUSHREF: + printf("PUSHREF(%d)", i.ref); + break; + case LUA_TNIL: + printf("nil"); + break; + case LUA_TBOOLEAN: + printf(i.bdata ? "true" : "false"); + break; + case LUA_TNUMBER: + printf("%f", i.ndata); + break; + case LUA_TSTRING: + printf("\"%s\"", i.sdata.c_str()); + break; + case LUA_TTABLE: + printf("table(%d, %d)", i.uidata1, i.uidata2); + break; + case LUA_TFUNCTION: + printf("function(%d byte)", i.sdata.size()); + break; + case LUA_TUSERDATA: + printf("userdata %s %p", i.sdata.c_str(), i.ptrdata); + break; + default: + printf("!!UNKNOWN!!"); + break; + } + if (i.set_into) { + if (i.type >= 0 && uses_sdata(i.type)) + printf(", k=%d, into=%d", i.sidata1, i.set_into); + else if (i.type >= 0) + printf(", k=\"%s\", into=%d", i.sdata.c_str(), i.set_into); + else + printf(", into=%d", i.set_into); + } + if (i.keep_ref) + printf(", keep_ref"); + if (i.pop) + printf(", pop"); + printf(")\n"); + } + printf("]\n"); +} +#endif diff --git a/src/script/common/c_packer.h b/src/script/common/c_packer.h new file mode 100644 index 000000000..fe072c10a --- /dev/null +++ b/src/script/common/c_packer.h @@ -0,0 +1,126 @@ +/* +Minetest +Copyright (C) 2022 sfan5 <sfan5@live.de> + +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 <string> +#include <vector> +#include "irrlichttypes.h" +#include "util/basic_macros.h" + +extern "C" { +#include <lua.h> +} + +/* + This file defines an in-memory representation of Lua objects including + support for functions and userdata. It it used to move data between Lua + states and cannot be used for persistence or network transfer. +*/ + +#define INSTR_SETTABLE (-10) +#define INSTR_POP (-11) +#define INSTR_PUSHREF (-12) + +/** + * Represents a single instruction that pushes a new value or works with existing ones. + */ +struct PackedInstr +{ + s16 type; // LUA_T* or INSTR_* + u16 set_into; // set into table on stack + bool keep_ref; // is referenced later by INSTR_PUSHREF? + bool pop; // remove from stack? + union { + bool bdata; // boolean: value + lua_Number ndata; // number: value + struct { + u16 uidata1, uidata2; // table: narr, nrec + }; + struct { + /* + SETTABLE: key index, value index + POP: indices to remove + otherwise w/ set_into: numeric key, - + */ + s32 sidata1, sidata2; + }; + void *ptrdata; // userdata: implementation defined + s32 ref; // PUSHREF: index of referenced instr + }; + /* + - string: value + - function: buffer + - w/ set_into: string key (no null bytes!) + - userdata: name in registry + */ + std::string sdata; + + PackedInstr() : type(0), set_into(0), keep_ref(false), pop(false) {} +}; + +/** + * A packed value can be a primitive like a string or number but also a table + * including all of its contents. It is made up of a linear stream of + * 'instructions' that build the final value when executed. + */ +struct PackedValue +{ + std::vector<PackedInstr> i; + // Indicates whether there are any userdata pointers that need to be deallocated + bool contains_userdata = false; + + PackedValue() = default; + ~PackedValue(); + + DISABLE_CLASS_COPY(PackedValue) + + ALLOW_CLASS_MOVE(PackedValue) +}; + +/* + * Packing callback: Turns a Lua value at given index into a void* + */ +typedef void *(*PackInFunc)(lua_State *L, int idx); +/* + * Unpacking callback: Turns a void* back into the Lua value (left on top of stack) + * + * Note that this function must take ownership of the pointer, so make sure + * to free or keep the memory. + * `L` can be nullptr to indicate that data should just be discarded. + */ +typedef void (*PackOutFunc)(lua_State *L, void *ptr); +/* + * Register a packable type with the name of its metatable. + * + * Even though the callbacks are global this must be called for every Lua state + * that supports objects of this type. + * This function is thread-safe. + */ +void script_register_packer(lua_State *L, const char *regname, + PackInFunc fin, PackOutFunc fout); + +// Pack a Lua value +PackedValue *script_pack(lua_State *L, int idx); +// Unpack a Lua value (left on top of stack) +// Note that this may modify the PackedValue, reusability is not guaranteed! +void script_unpack(lua_State *L, PackedValue *val); + +// Dump contents of PackedValue to stdout for debugging +void script_dump_packed(const PackedValue *val); diff --git a/src/script/cpp_api/s_async.cpp b/src/script/cpp_api/s_async.cpp index dacdcd75a..42a794ceb 100644 --- a/src/script/cpp_api/s_async.cpp +++ b/src/script/cpp_api/s_async.cpp @@ -21,9 +21,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <cstdlib> extern "C" { -#include "lua.h" -#include "lauxlib.h" -#include "lualib.h" +#include <lua.h> +#include <lauxlib.h> +#include <lualib.h> } #include "server.h" @@ -32,6 +32,7 @@ extern "C" { #include "filesys.h" #include "porting.h" #include "common/c_internal.h" +#include "common/c_packer.h" #include "lua_api/l_base.h" /******************************************************************************/ @@ -76,19 +77,34 @@ void AsyncEngine::initialize(unsigned int numEngines) { initDone = true; - for (unsigned int i = 0; i < numEngines; i++) { - AsyncWorkerThread *toAdd = new AsyncWorkerThread(this, - std::string("AsyncWorker-") + itos(i)); - workerThreads.push_back(toAdd); - toAdd->start(); + if (numEngines == 0) { + // Leave one core for the main thread and one for whatever else + autoscaleMaxWorkers = Thread::getNumberOfProcessors(); + if (autoscaleMaxWorkers >= 2) + autoscaleMaxWorkers -= 2; + infostream << "AsyncEngine: using at most " << autoscaleMaxWorkers + << " threads with automatic scaling" << std::endl; + + addWorkerThread(); + } else { + for (unsigned int i = 0; i < numEngines; i++) + addWorkerThread(); } } +void AsyncEngine::addWorkerThread() +{ + AsyncWorkerThread *toAdd = new AsyncWorkerThread(this, + std::string("AsyncWorker-") + itos(workerThreads.size())); + workerThreads.push_back(toAdd); + toAdd->start(); +} + /******************************************************************************/ u32 AsyncEngine::queueAsyncJob(std::string &&func, std::string &¶ms, const std::string &mod_origin) { - jobQueueMutex.lock(); + MutexAutoLock autolock(jobQueueMutex); u32 jobId = jobIdCounter++; jobQueue.emplace_back(); @@ -99,7 +115,23 @@ u32 AsyncEngine::queueAsyncJob(std::string &&func, std::string &¶ms, to_add.mod_origin = mod_origin; jobQueueCounter.post(); - jobQueueMutex.unlock(); + return jobId; +} + +u32 AsyncEngine::queueAsyncJob(std::string &&func, PackedValue *params, + const std::string &mod_origin) +{ + MutexAutoLock autolock(jobQueueMutex); + u32 jobId = jobIdCounter++; + + jobQueue.emplace_back(); + auto &to_add = jobQueue.back(); + to_add.id = jobId; + to_add.function = std::move(func); + to_add.params_ext.reset(params); + to_add.mod_origin = mod_origin; + + jobQueueCounter.post(); return jobId; } @@ -132,6 +164,12 @@ void AsyncEngine::putJobResult(LuaJobInfo &&result) /******************************************************************************/ void AsyncEngine::step(lua_State *L) { + stepJobResults(L); + stepAutoscale(); +} + +void AsyncEngine::stepJobResults(lua_State *L) +{ int error_handler = PUSH_ERROR_HANDLER(L); lua_getglobal(L, "core"); @@ -148,7 +186,10 @@ void AsyncEngine::step(lua_State *L) luaL_checktype(L, -1, LUA_TFUNCTION); lua_pushinteger(L, j.id); - lua_pushlstring(L, j.result.data(), j.result.size()); + if (j.result_ext) + script_unpack(L, j.result_ext.get()); + else + lua_pushlstring(L, j.result.data(), j.result.size()); // Call handler const char *origin = j.mod_origin.empty() ? nullptr : j.mod_origin.c_str(); @@ -161,12 +202,71 @@ void AsyncEngine::step(lua_State *L) lua_pop(L, 2); // Pop core and error handler } +void AsyncEngine::stepAutoscale() +{ + if (workerThreads.size() >= autoscaleMaxWorkers) + return; + + MutexAutoLock autolock(jobQueueMutex); + + // 2) If the timer elapsed, check again + if (autoscaleTimer && porting::getTimeMs() >= autoscaleTimer) { + autoscaleTimer = 0; + // Determine overlap with previous snapshot + unsigned int n = 0; + for (const auto &it : jobQueue) + n += autoscaleSeenJobs.count(it.id); + autoscaleSeenJobs.clear(); + infostream << "AsyncEngine: " << n << " jobs were still waiting after 1s" << std::endl; + // Start this many new threads + while (workerThreads.size() < autoscaleMaxWorkers && n > 0) { + addWorkerThread(); + n--; + } + return; + } + + // 1) Check if there's anything in the queue + if (!autoscaleTimer && !jobQueue.empty()) { + // Take a snapshot of all jobs we have seen + for (const auto &it : jobQueue) + autoscaleSeenJobs.emplace(it.id); + // and set a timer for 1 second + autoscaleTimer = porting::getTimeMs() + 1000; + } +} + /******************************************************************************/ -void AsyncEngine::prepareEnvironment(lua_State* L, int top) +bool AsyncEngine::prepareEnvironment(lua_State* L, int top) { for (StateInitializer &stateInitializer : stateInitializers) { stateInitializer(L, top); } + + auto *script = ModApiBase::getScriptApiBase(L); + try { + script->loadMod(Server::getBuiltinLuaPath() + DIR_DELIM + "init.lua", + BUILTIN_MOD_NAME); + } catch (const ModError &e) { + errorstream << "Execution of async base environment failed: " + << e.what() << std::endl; + FATAL_ERROR("Execution of async base environment failed"); + } + + // Load per mod stuff + if (server) { + const auto &list = server->m_async_init_files; + try { + for (auto &it : list) + script->loadMod(it.second, it.first); + } catch (const ModError &e) { + errorstream << "Failed to load mod script inside async environment." << std::endl; + server->setAsyncFatalError(e.what()); + return false; + } + } + + return true; } /******************************************************************************/ @@ -178,15 +278,25 @@ AsyncWorkerThread::AsyncWorkerThread(AsyncEngine* jobDispatcher, { lua_State *L = getStack(); + if (jobDispatcher->server) { + setGameDef(jobDispatcher->server); + + if (g_settings->getBool("secure.enable_security")) + initializeSecurity(); + } + // Prepare job lua environment lua_getglobal(L, "core"); int top = lua_gettop(L); // Push builtin initialization type - lua_pushstring(L, "async"); + lua_pushstring(L, jobDispatcher->server ? "async_game" : "async"); lua_setglobal(L, "INIT"); - jobDispatcher->prepareEnvironment(L, top); + if (!jobDispatcher->prepareEnvironment(L, top)) { + // can't throw from here so we're stuck with this + isErrored = true; + } } /******************************************************************************/ @@ -198,19 +308,20 @@ AsyncWorkerThread::~AsyncWorkerThread() /******************************************************************************/ void* AsyncWorkerThread::run() { - lua_State *L = getStack(); + if (isErrored) + return nullptr; - try { - loadMod(getServer()->getBuiltinLuaPath() + DIR_DELIM + "init.lua", - BUILTIN_MOD_NAME); - } catch (const ModError &e) { - errorstream << "Execution of async base environment failed: " - << e.what() << std::endl; - FATAL_ERROR("Execution of async base environment failed"); - } + lua_State *L = getStack(); int error_handler = PUSH_ERROR_HANDLER(L); + auto report_error = [this] (const ModError &e) { + if (jobDispatcher->server) + jobDispatcher->server->setAsyncFatalError(e.what()); + else + errorstream << e.what() << std::endl; + }; + lua_getglobal(L, "core"); if (lua_isnil(L, -1)) { FATAL_ERROR("Unable to find core within async environment!"); @@ -223,6 +334,8 @@ void* AsyncWorkerThread::run() if (!jobDispatcher->getJob(&j) || stopRequested()) continue; + const bool use_ext = !!j.params_ext; + lua_getfield(L, -1, "job_processor"); if (lua_isnil(L, -1)) FATAL_ERROR("Unable to get async job processor!"); @@ -232,7 +345,10 @@ void* AsyncWorkerThread::run() errorstream << "ASYNC WORKER: Unable to deserialize function" << std::endl; lua_pushnil(L); } - lua_pushlstring(L, j.params.data(), j.params.size()); + if (use_ext) + script_unpack(L, j.params_ext.get()); + else + lua_pushlstring(L, j.params.data(), j.params.size()); // Call it setOriginDirect(j.mod_origin.empty() ? nullptr : j.mod_origin.c_str()); @@ -241,19 +357,28 @@ void* AsyncWorkerThread::run() try { scriptError(result, "<async>"); } catch (const ModError &e) { - errorstream << e.what() << std::endl; + report_error(e); } } else { // Fetch result - size_t length; - const char *retval = lua_tolstring(L, -1, &length); - j.result.assign(retval, length); + if (use_ext) { + try { + j.result_ext.reset(script_pack(L, -1)); + } catch (const ModError &e) { + report_error(e); + result = LUA_ERRERR; + } + } else { + size_t length; + const char *retval = lua_tolstring(L, -1, &length); + j.result.assign(retval, length); + } } lua_pop(L, 1); // Pop retval // Put job result - if (!j.result.empty()) + if (result == 0) jobDispatcher->putJobResult(std::move(j)); } diff --git a/src/script/cpp_api/s_async.h b/src/script/cpp_api/s_async.h index 697cb0221..1e34e40ea 100644 --- a/src/script/cpp_api/s_async.h +++ b/src/script/cpp_api/s_async.h @@ -21,11 +21,15 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <vector> #include <deque> +#include <unordered_set> +#include <memory> +#include <lua.h> #include "threading/semaphore.h" #include "threading/thread.h" -#include "lua.h" +#include "common/c_packer.h" #include "cpp_api/s_base.h" +#include "cpp_api/s_security.h" // Forward declarations class AsyncEngine; @@ -42,8 +46,12 @@ struct LuaJobInfo std::string function; // Parameter to be passed to function (serialized) std::string params; + // Alternative parameters + std::unique_ptr<PackedValue> params_ext; // Result of function call (serialized) std::string result; + // Alternative result + std::unique_ptr<PackedValue> result_ext; // Name of the mod who invoked this call std::string mod_origin; // JobID used to identify a job and match it to callback @@ -51,7 +59,8 @@ struct LuaJobInfo }; // Asynchronous working environment -class AsyncWorkerThread : public Thread, virtual public ScriptApiBase { +class AsyncWorkerThread : public Thread, + virtual public ScriptApiBase, public ScriptApiSecurity { friend class AsyncEngine; public: virtual ~AsyncWorkerThread(); @@ -63,6 +72,7 @@ protected: private: AsyncEngine *jobDispatcher = nullptr; + bool isErrored = false; }; // Asynchornous thread and job management @@ -71,6 +81,7 @@ class AsyncEngine { typedef void (*StateInitializer)(lua_State *L, int top); public: AsyncEngine() = default; + AsyncEngine(Server *server) : server(server) {}; ~AsyncEngine(); /** @@ -81,7 +92,7 @@ public: /** * Create async engine tasks and lock function registration - * @param numEngines Number of async threads to be started + * @param numEngines Number of worker threads, 0 for automatic scaling */ void initialize(unsigned int numEngines); @@ -95,8 +106,16 @@ public: const std::string &mod_origin = ""); /** + * Queue an async job + * @param func Serialized lua function + * @param params Serialized parameters (takes ownership!) + * @return ID of queued job + */ + u32 queueAsyncJob(std::string &&func, PackedValue *params, + const std::string &mod_origin = ""); + + /** * Engine step to process finished jobs - * the engine step is one way to pass events back, PushFinishedJobs another * @param L The Lua stack */ void step(lua_State *L); @@ -117,18 +136,43 @@ protected: void putJobResult(LuaJobInfo &&result); /** + * Start an additional worker thread + */ + void addWorkerThread(); + + /** + * Process finished jobs callbacks + */ + void stepJobResults(lua_State *L); + + /** + * Handle automatic scaling of worker threads + */ + void stepAutoscale(); + + /** * Initialize environment with current registred functions * this function adds all functions registred by registerFunction to the * passed lua stack * @param L Lua stack to initialize * @param top Stack position + * @return false if a mod error ocurred */ - void prepareEnvironment(lua_State* L, int top); + bool prepareEnvironment(lua_State* L, int top); private: // Variable locking the engine against further modification bool initDone = false; + // Maximum number of worker threads for automatic scaling + // 0 if disabled + unsigned int autoscaleMaxWorkers = 0; + u64 autoscaleTimer = 0; + std::unordered_set<u32> autoscaleSeenJobs; + + // Only set for the server async environment (duh) + Server *server = nullptr; + // Internal store for registred state initializers std::vector<StateInitializer> stateInitializers; diff --git a/src/script/cpp_api/s_base.cpp b/src/script/cpp_api/s_base.cpp index 27d730b1d..ae4a16771 100644 --- a/src/script/cpp_api/s_base.cpp +++ b/src/script/cpp_api/s_base.cpp @@ -38,6 +38,8 @@ extern "C" { #include "lualib.h" #if USE_LUAJIT #include "luajit.h" +#else + #include "bit.h" #endif } @@ -89,6 +91,11 @@ ScriptApiBase::ScriptApiBase(ScriptingType type): else*/ luaL_openlibs(m_luastack); + // Load bit library + lua_pushcfunction(m_luastack, luaopen_bit); + lua_pushstring(m_luastack, LUA_BITLIBNAME); + lua_call(m_luastack, 1, 0); + // Make the ScriptApiBase* accessible to ModApiBase #if INDIRECT_SCRIPTAPI_RIDX *(void **)(lua_newuserdata(m_luastack, sizeof(void *))) = this; @@ -115,6 +122,14 @@ ScriptApiBase::ScriptApiBase(ScriptingType type): lua_newtable(m_luastack); lua_setglobal(m_luastack, "core"); + // vector.metatable is stored in the registry for quick access from C++. + lua_newtable(m_luastack); + lua_rawseti(m_luastack, LUA_REGISTRYINDEX, CUSTOM_RIDX_VECTOR_METATABLE); + lua_newtable(m_luastack); + lua_rawgeti(m_luastack, LUA_REGISTRYINDEX, CUSTOM_RIDX_VECTOR_METATABLE); + lua_setfield(m_luastack, -2, "metatable"); + lua_setglobal(m_luastack, "vector"); + if (m_type == ScriptingType::Client) lua_pushstring(m_luastack, "/"); else diff --git a/src/script/cpp_api/s_base.h b/src/script/cpp_api/s_base.h index e49745f4e..19ae8783b 100644 --- a/src/script/cpp_api/s_base.h +++ b/src/script/cpp_api/s_base.h @@ -128,6 +128,15 @@ protected: friend class ModApiEnvMod; friend class LuaVoxelManip; + /* + Subtle edge case with coroutines: If for whatever reason you have a + method in a subclass that's called from existing lua_CFunction + (any of the l_*.cpp files) then make it static and take the lua_State* + as an argument. This is REQUIRED because getStack() will not return the + correct state if called inside coroutines. + + Also note that src/script/common/ is the better place for such helpers. + */ lua_State* getStack() { return m_luastack; } diff --git a/src/script/cpp_api/s_client.cpp b/src/script/cpp_api/s_client.cpp index 5d20f547d..e54a1361d 100644 --- a/src/script/cpp_api/s_client.cpp +++ b/src/script/cpp_api/s_client.cpp @@ -399,7 +399,7 @@ bool ScriptApiClient::on_inventory_open(Inventory *inventory) lua_getglobal(L, "core"); lua_getfield(L, -1, "registered_on_inventory_open"); - push_inventory(L, inventory); + push_inventory_lists(L, *inventory); try { runCallbacks(1, RUN_CALLBACKS_MODE_OR); diff --git a/src/script/cpp_api/s_entity.cpp b/src/script/cpp_api/s_entity.cpp index 746f7013e..06337b9e8 100644 --- a/src/script/cpp_api/s_entity.cpp +++ b/src/script/cpp_api/s_entity.cpp @@ -240,7 +240,7 @@ void ScriptApiEntity::luaentity_Step(u16 id, float dtime, // tool_capabilities, direction, damage) bool ScriptApiEntity::luaentity_Punch(u16 id, ServerActiveObject *puncher, float time_from_last_punch, - const ToolCapabilities *toolcap, v3f dir, s16 damage) + const ToolCapabilities *toolcap, v3f dir, s32 damage) { SCRIPTAPI_PRECHECKHEADER diff --git a/src/script/cpp_api/s_entity.h b/src/script/cpp_api/s_entity.h index b52f6e447..7658ae922 100644 --- a/src/script/cpp_api/s_entity.h +++ b/src/script/cpp_api/s_entity.h @@ -42,7 +42,7 @@ public: const collisionMoveResult *moveresult); bool luaentity_Punch(u16 id, ServerActiveObject *puncher, float time_from_last_punch, - const ToolCapabilities *toolcap, v3f dir, s16 damage); + const ToolCapabilities *toolcap, v3f dir, s32 damage); bool luaentity_on_death(u16 id, ServerActiveObject *killer); void luaentity_Rightclick(u16 id, ServerActiveObject *clicker); void luaentity_on_attach_child(u16 id, ServerActiveObject *child); diff --git a/src/script/cpp_api/s_env.cpp b/src/script/cpp_api/s_env.cpp index 874c37b6e..af68f689f 100644 --- a/src/script/cpp_api/s_env.cpp +++ b/src/script/cpp_api/s_env.cpp @@ -140,10 +140,10 @@ void ScriptApiEnv::initializeEnvironment(ServerEnvironment *env) bool simple_catch_up = true; getboolfield(L, current_abm, "catch_up", simple_catch_up); - + s16 min_y = INT16_MIN; getintfield(L, current_abm, "min_y", min_y); - + s16 max_y = INT16_MAX; getintfield(L, current_abm, "max_y", max_y); diff --git a/src/script/cpp_api/s_item.cpp b/src/script/cpp_api/s_item.cpp index 48dce14f3..b1916070e 100644 --- a/src/script/cpp_api/s_item.cpp +++ b/src/script/cpp_api/s_item.cpp @@ -59,13 +59,14 @@ bool ScriptApiItem::item_OnDrop(ItemStack &item, return true; } -bool ScriptApiItem::item_OnPlace(ItemStack &item, +bool ScriptApiItem::item_OnPlace(Optional<ItemStack> &ret_item, ServerActiveObject *placer, const PointedThing &pointed) { SCRIPTAPI_PRECHECKHEADER int error_handler = PUSH_ERROR_HANDLER(L); + const ItemStack &item = *ret_item; // Push callback function on stack if (!getItemCallback(item.name.c_str(), "on_place")) return false; @@ -82,22 +83,25 @@ bool ScriptApiItem::item_OnPlace(ItemStack &item, PCALL_RES(lua_pcall(L, 3, 1, error_handler)); if (!lua_isnil(L, -1)) { try { - item = read_item(L, -1, getServer()->idef()); + ret_item = read_item(L, -1, getServer()->idef()); } catch (LuaError &e) { throw WRAP_LUAERROR(e, "item=" + item.name); } + } else { + ret_item = nullopt; } lua_pop(L, 2); // Pop item and error handler return true; } -bool ScriptApiItem::item_OnUse(ItemStack &item, +bool ScriptApiItem::item_OnUse(Optional<ItemStack> &ret_item, ServerActiveObject *user, const PointedThing &pointed) { SCRIPTAPI_PRECHECKHEADER int error_handler = PUSH_ERROR_HANDLER(L); + const ItemStack &item = *ret_item; // Push callback function on stack if (!getItemCallback(item.name.c_str(), "on_use")) return false; @@ -109,22 +113,25 @@ bool ScriptApiItem::item_OnUse(ItemStack &item, PCALL_RES(lua_pcall(L, 3, 1, error_handler)); if(!lua_isnil(L, -1)) { try { - item = read_item(L, -1, getServer()->idef()); + ret_item = read_item(L, -1, getServer()->idef()); } catch (LuaError &e) { throw WRAP_LUAERROR(e, "item=" + item.name); } + } else { + ret_item = nullopt; } lua_pop(L, 2); // Pop item and error handler return true; } -bool ScriptApiItem::item_OnSecondaryUse(ItemStack &item, +bool ScriptApiItem::item_OnSecondaryUse(Optional<ItemStack> &ret_item, ServerActiveObject *user, const PointedThing &pointed) { SCRIPTAPI_PRECHECKHEADER int error_handler = PUSH_ERROR_HANDLER(L); + const ItemStack &item = *ret_item; if (!getItemCallback(item.name.c_str(), "on_secondary_use")) return false; @@ -134,10 +141,12 @@ bool ScriptApiItem::item_OnSecondaryUse(ItemStack &item, PCALL_RES(lua_pcall(L, 3, 1, error_handler)); if (!lua_isnil(L, -1)) { try { - item = read_item(L, -1, getServer()->idef()); + ret_item = read_item(L, -1, getServer()->idef()); } catch (LuaError &e) { throw WRAP_LUAERROR(e, "item=" + item.name); } + } else { + ret_item = nullopt; } lua_pop(L, 2); // Pop item and error handler return true; diff --git a/src/script/cpp_api/s_item.h b/src/script/cpp_api/s_item.h index 25a3501f9..5015d8bd4 100644 --- a/src/script/cpp_api/s_item.h +++ b/src/script/cpp_api/s_item.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "cpp_api/s_base.h" #include "irr_v3d.h" +#include "util/Optional.h" struct PointedThing; struct ItemStack; @@ -35,13 +36,20 @@ class ScriptApiItem : virtual public ScriptApiBase { public: + /* + * Functions with Optional<ItemStack> are for callbacks where Lua may + * want to prevent the engine from modifying the inventory after it's done. + * This has a longer backstory where on_use may need to empty the player's + * inventory without the engine interfering (see issue #6546). + */ + bool item_OnDrop(ItemStack &item, ServerActiveObject *dropper, v3f pos); - bool item_OnPlace(ItemStack &item, + bool item_OnPlace(Optional<ItemStack> &item, ServerActiveObject *placer, const PointedThing &pointed); - bool item_OnUse(ItemStack &item, + bool item_OnUse(Optional<ItemStack> &item, ServerActiveObject *user, const PointedThing &pointed); - bool item_OnSecondaryUse(ItemStack &item, + bool item_OnSecondaryUse(Optional<ItemStack> &item, ServerActiveObject *user, const PointedThing &pointed); bool item_OnCraft(ItemStack &item, ServerActiveObject *user, const InventoryList *old_craft_grid, const InventoryLocation &craft_inv); diff --git a/src/script/cpp_api/s_node.h b/src/script/cpp_api/s_node.h index 3f771c838..3c6a8445b 100644 --- a/src/script/cpp_api/s_node.h +++ b/src/script/cpp_api/s_node.h @@ -53,6 +53,7 @@ public: static struct EnumString es_ContentParamType[]; static struct EnumString es_ContentParamType2[]; static struct EnumString es_LiquidType[]; + static struct EnumString es_LiquidMoveType[]; static struct EnumString es_NodeBoxType[]; static struct EnumString es_TextureAlphaMode[]; }; diff --git a/src/script/cpp_api/s_player.cpp b/src/script/cpp_api/s_player.cpp index d3e6138dc..22b24f363 100644 --- a/src/script/cpp_api/s_player.cpp +++ b/src/script/cpp_api/s_player.cpp @@ -60,7 +60,7 @@ bool ScriptApiPlayer::on_punchplayer(ServerActiveObject *player, float time_from_last_punch, const ToolCapabilities *toolcap, v3f dir, - s16 damage) + s32 damage) { SCRIPTAPI_PRECHECKHEADER // Get core.registered_on_punchplayers diff --git a/src/script/cpp_api/s_player.h b/src/script/cpp_api/s_player.h index c0f141862..e866aee46 100644 --- a/src/script/cpp_api/s_player.h +++ b/src/script/cpp_api/s_player.h @@ -46,7 +46,7 @@ public: void on_cheat(ServerActiveObject *player, const std::string &cheat_type); bool on_punchplayer(ServerActiveObject *player, ServerActiveObject *hitter, float time_from_last_punch, const ToolCapabilities *toolcap, - v3f dir, s16 damage); + v3f dir, s32 damage); void on_rightclickplayer(ServerActiveObject *player, ServerActiveObject *clicker); s32 on_player_hpchange(ServerActiveObject *player, s32 hp_change, const PlayerHPChangeReason &reason); diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index bd9c80e15..76509038f 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -27,6 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <cerrno> #include <string> +#include <algorithm> #include <iostream> @@ -97,6 +98,7 @@ void ScriptApiSecurity::initializeSecurity() "type", "unpack", "_VERSION", + "vector", "xpcall", }; static const char *whitelist_tables[] = { @@ -106,6 +108,7 @@ void ScriptApiSecurity::initializeSecurity() "string", "table", "math", + "bit" }; static const char *io_whitelist[] = { "open", @@ -120,21 +123,17 @@ void ScriptApiSecurity::initializeSecurity() "date", "difftime", "getenv", - "setlocale", "time", - "tmpname", }; static const char *debug_whitelist[] = { "gethook", "traceback", "getinfo", "getmetatable", - "setupvalue", "setmetatable", "upvalueid", "sethook", "debug", - "setlocal", }; static const char *package_whitelist[] = { "config", @@ -220,6 +219,7 @@ void ScriptApiSecurity::initializeSecurity() // And replace unsafe ones SECURE_API(os, remove); SECURE_API(os, rename); + SECURE_API(os, setlocale); lua_setglobal(L, "os"); lua_pop(L, 1); // Pop old OS @@ -251,6 +251,15 @@ void ScriptApiSecurity::initializeSecurity() lua_pop(L, 1); // Pop old jit #endif + // Get rid of 'core' in the old globals, we don't want anyone thinking it's + // safe or even usable. + lua_pushnil(L); + lua_setfield(L, old_globals, "core"); + + // 'vector' as well. + lua_pushnil(L); + lua_setfield(L, old_globals, "vector"); + lua_pop(L, 1); // Pop globals_backup @@ -286,19 +295,21 @@ void ScriptApiSecurity::initializeSecurityClient() "rawset", "select", "setfenv", - // getmetatable can be used to escape the sandbox + // getmetatable can be used to escape the sandbox <- ??? "setmetatable", "tonumber", "tostring", "type", "unpack", "_VERSION", + "vector", "xpcall", // Completely safe libraries "coroutine", "string", "table", "math", + "bit", }; static const char *os_whitelist[] = { "clock", @@ -307,7 +318,7 @@ void ScriptApiSecurity::initializeSecurityClient() "time" }; static const char *debug_whitelist[] = { - "getinfo", + "getinfo", // used by builtin and unset before mods load "traceback" }; #if USE_LUAJIT @@ -607,6 +618,38 @@ bool ScriptApiSecurity::checkPath(lua_State *L, const char *path, return false; } +bool ScriptApiSecurity::checkWhitelisted(lua_State *L, const std::string &setting) +{ + assert(str_starts_with(setting, "secure.")); + + // We have to make sure that this function is being called directly by + // a mod, otherwise a malicious mod could override this function and + // steal its return value. + lua_Debug info; + + // Make sure there's only one item below this function on the stack... + if (lua_getstack(L, 2, &info)) + return false; + FATAL_ERROR_IF(!lua_getstack(L, 1, &info), "lua_getstack() failed"); + FATAL_ERROR_IF(!lua_getinfo(L, "S", &info), "lua_getinfo() failed"); + + // ...and that that item is the main file scope. + if (strcmp(info.what, "main") != 0) + return false; + + // Mod must be listed in secure.http_mods or secure.trusted_mods + lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); + if (!lua_isstring(L, -1)) + return false; + std::string mod_name = readParam<std::string>(L, -1); + + std::string value = g_settings->get(setting); + value.erase(std::remove(value.begin(), value.end(), ' '), value.end()); + auto mod_list = str_split(value, ','); + + return CONTAINS(mod_list, mod_name); +} + int ScriptApiSecurity::sl_g_dofile(lua_State *L) { @@ -838,3 +881,20 @@ int ScriptApiSecurity::sl_os_remove(lua_State *L) return 2; } + +int ScriptApiSecurity::sl_os_setlocale(lua_State *L) +{ + const bool cat = lua_gettop(L) > 1; + // Don't allow changes + if (!lua_isnoneornil(L, 1)) { + lua_pushnil(L); + return 1; + } + + push_original(L, "os", "setlocale"); + lua_pushnil(L); + if (cat) + lua_pushvalue(L, 2); + lua_call(L, cat ? 2 : 1, 1); + return 1; +} diff --git a/src/script/cpp_api/s_security.h b/src/script/cpp_api/s_security.h index 73e763548..880ce1638 100644 --- a/src/script/cpp_api/s_security.h +++ b/src/script/cpp_api/s_security.h @@ -40,11 +40,6 @@ with this program; if not, write to the Free Software Foundation, Inc., class ScriptApiSecurity : virtual public ScriptApiBase { public: - int getThread(lua_State *L); - // creates an empty Lua environment - void createEmptyEnv(lua_State *L); - // sets the enviroment to the table thats on top of the stack - void setLuaEnv(lua_State *L, int thread); // Sets up security on the ScriptApi's Lua state void initializeSecurity(); void initializeSecurityClient(); @@ -57,8 +52,17 @@ public: // Checks if mods are allowed to read (and optionally write) to the path static bool checkPath(lua_State *L, const char *path, bool write_required, bool *write_allowed=NULL); + // Check if mod is whitelisted in the given setting + // This additionally checks that the mod's main file scope is executing. + static bool checkWhitelisted(lua_State *L, const std::string &setting); private: + int getThread(lua_State *L); + // sets the enviroment to the table thats on top of the stack + void setLuaEnv(lua_State *L, int thread); + // creates an empty Lua environment + void createEmptyEnv(lua_State *L); + // Syntax: "sl_" <Library name or 'g' (global)> '_' <Function name> // (sl stands for Secure Lua) @@ -75,4 +79,5 @@ private: static int sl_os_rename(lua_State *L); static int sl_os_remove(lua_State *L); + static int sl_os_setlocale(lua_State *L); }; diff --git a/src/script/cpp_api/s_server.cpp b/src/script/cpp_api/s_server.cpp index 6ddb2630d..c255b0c71 100644 --- a/src/script/cpp_api/s_server.cpp +++ b/src/script/cpp_api/s_server.cpp @@ -198,10 +198,8 @@ std::string ScriptApiServer::formatChatMessage(const std::string &name, return ret; } -u32 ScriptApiServer::allocateDynamicMediaCallback(int f_idx) +u32 ScriptApiServer::allocateDynamicMediaCallback(lua_State *L, int f_idx) { - lua_State *L = getStack(); - if (f_idx < 0) f_idx = lua_gettop(L) + f_idx + 1; @@ -235,7 +233,7 @@ u32 ScriptApiServer::allocateDynamicMediaCallback(int f_idx) void ScriptApiServer::freeDynamicMediaCallback(u32 token) { - lua_State *L = getStack(); + SCRIPTAPI_PRECHECKHEADER verbosestream << "freeDynamicMediaCallback(" << token << ")" << std::endl; diff --git a/src/script/cpp_api/s_server.h b/src/script/cpp_api/s_server.h index c5c3d5596..58c8c0e48 100644 --- a/src/script/cpp_api/s_server.h +++ b/src/script/cpp_api/s_server.h @@ -51,7 +51,7 @@ public: const std::string &password); /* dynamic media handling */ - u32 allocateDynamicMediaCallback(int f_idx); + static u32 allocateDynamicMediaCallback(lua_State *L, int f_idx); void freeDynamicMediaCallback(u32 token); void on_dynamic_media_added(u32 token, const char *playername); diff --git a/src/script/lua_api/l_areastore.cpp b/src/script/lua_api/l_areastore.cpp index 45724e604..ec2656c4a 100644 --- a/src/script/lua_api/l_areastore.cpp +++ b/src/script/lua_api/l_areastore.cpp @@ -27,26 +27,26 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "filesys.h" #include <fstream> -static inline void get_data_and_border_flags(lua_State *L, u8 start_i, - bool *borders, bool *data) +static inline void get_data_and_corner_flags(lua_State *L, u8 start_i, + bool *corners, bool *data) { if (!lua_isboolean(L, start_i)) return; - *borders = lua_toboolean(L, start_i); + *corners = lua_toboolean(L, start_i); if (!lua_isboolean(L, start_i + 1)) return; *data = lua_toboolean(L, start_i + 1); } static void push_area(lua_State *L, const Area *a, - bool include_borders, bool include_data) + bool include_corners, bool include_data) { - if (!include_borders && !include_data) { + if (!include_corners && !include_data) { lua_pushboolean(L, true); return; } lua_newtable(L); - if (include_borders) { + if (include_corners) { push_v3s16(L, a->minedge); lua_setfield(L, -2, "min"); push_v3s16(L, a->maxedge); @@ -59,13 +59,13 @@ static void push_area(lua_State *L, const Area *a, } static inline void push_areas(lua_State *L, const std::vector<Area *> &areas, - bool borders, bool data) + bool corners, bool data) { lua_newtable(L); size_t cnt = areas.size(); for (size_t i = 0; i < cnt; i++) { lua_pushnumber(L, areas[i]->id); - push_area(L, areas[i], borders, data); + push_area(L, areas[i], corners, data); lua_settable(L, -3); } } @@ -94,7 +94,7 @@ int LuaAreaStore::gc_object(lua_State *L) return 0; } -// get_area(id, include_borders, include_data) +// get_area(id, include_corners, include_data) int LuaAreaStore::l_get_area(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -104,9 +104,9 @@ int LuaAreaStore::l_get_area(lua_State *L) u32 id = luaL_checknumber(L, 2); - bool include_borders = true; + bool include_corners = true; bool include_data = false; - get_data_and_border_flags(L, 3, &include_borders, &include_data); + get_data_and_corner_flags(L, 3, &include_corners, &include_data); const Area *res; @@ -114,12 +114,12 @@ int LuaAreaStore::l_get_area(lua_State *L) if (!res) return 0; - push_area(L, res, include_borders, include_data); + push_area(L, res, include_corners, include_data); return 1; } -// get_areas_for_pos(pos, include_borders, include_data) +// get_areas_for_pos(pos, include_corners, include_data) int LuaAreaStore::l_get_areas_for_pos(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -129,19 +129,19 @@ int LuaAreaStore::l_get_areas_for_pos(lua_State *L) v3s16 pos = check_v3s16(L, 2); - bool include_borders = true; + bool include_corners = true; bool include_data = false; - get_data_and_border_flags(L, 3, &include_borders, &include_data); + get_data_and_corner_flags(L, 3, &include_corners, &include_data); std::vector<Area *> res; ast->getAreasForPos(&res, pos); - push_areas(L, res, include_borders, include_data); + push_areas(L, res, include_corners, include_data); return 1; } -// get_areas_in_area(edge1, edge2, accept_overlap, include_borders, include_data) +// get_areas_in_area(corner1, corner2, accept_overlap, include_corners, include_data) int LuaAreaStore::l_get_areas_in_area(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -149,25 +149,26 @@ int LuaAreaStore::l_get_areas_in_area(lua_State *L) LuaAreaStore *o = checkobject(L, 1); AreaStore *ast = o->as; - v3s16 minedge = check_v3s16(L, 2); - v3s16 maxedge = check_v3s16(L, 3); + v3s16 minp = check_v3s16(L, 2); + v3s16 maxp = check_v3s16(L, 3); + sortBoxVerticies(minp, maxp); - bool include_borders = true; + bool include_corners = true; bool include_data = false; bool accept_overlap = false; if (lua_isboolean(L, 4)) { accept_overlap = readParam<bool>(L, 4); - get_data_and_border_flags(L, 5, &include_borders, &include_data); + get_data_and_corner_flags(L, 5, &include_corners, &include_data); } std::vector<Area *> res; - ast->getAreasInArea(&res, minedge, maxedge, accept_overlap); - push_areas(L, res, include_borders, include_data); + ast->getAreasInArea(&res, minp, maxp, accept_overlap); + push_areas(L, res, include_corners, include_data); return 1; } -// insert_area(edge1, edge2, data, id) +// insert_area(corner1, corner2, data, id) int LuaAreaStore::l_insert_area(lua_State *L) { NO_MAP_LOCK_REQUIRED; diff --git a/src/script/lua_api/l_client.cpp b/src/script/lua_api/l_client.cpp index 1d769c73e..265c7d3fc 100644 --- a/src/script/lua_api/l_client.cpp +++ b/src/script/lua_api/l_client.cpp @@ -474,7 +474,7 @@ int ModApiClient::l_get_inventory(lua_State *L) inventory = client->getInventory(inventory_location); if (! inventory) throw SerializationError(std::string("Attempt to access nonexistant inventory (") + location + ")"); - push_inventory(L, inventory); + push_inventory_lists(L, *inventory); } catch (SerializationError &) { lua_pushnil(L); } diff --git a/src/script/lua_api/l_craft.cpp b/src/script/lua_api/l_craft.cpp index 18622ee00..137b210be 100644 --- a/src/script/lua_api/l_craft.cpp +++ b/src/script/lua_api/l_craft.cpp @@ -371,8 +371,9 @@ int ModApiCraft::l_clear_craft(lua_State *L) int ModApiCraft::l_get_craft_result(lua_State *L) { NO_MAP_LOCK_REQUIRED; + IGameDef *gdef = getGameDef(L); - int input_i = 1; + const int input_i = 1; std::string method_s = getstringfield_default(L, input_i, "method", "normal"); enum CraftMethod method = (CraftMethod)getenumfield(L, input_i, "method", es_CraftMethod, CRAFT_METHOD_NORMAL); @@ -382,10 +383,9 @@ int ModApiCraft::l_get_craft_result(lua_State *L) width = luaL_checkinteger(L, -1); lua_pop(L, 1); lua_getfield(L, input_i, "items"); - std::vector<ItemStack> items = read_items(L, -1,getServer(L)); + std::vector<ItemStack> items = read_items(L, -1, gdef); lua_pop(L, 1); // items - IGameDef *gdef = getServer(L); ICraftDefManager *cdef = gdef->cdef(); CraftInput input(method, width, items); CraftOutput output; @@ -465,13 +465,13 @@ static void push_craft_recipes(lua_State *L, IGameDef *gdef, const std::vector<CraftDefinition*> &recipes, const CraftOutput &output) { - lua_createtable(L, recipes.size(), 0); - if (recipes.empty()) { lua_pushnil(L); return; } + lua_createtable(L, recipes.size(), 0); + std::vector<CraftDefinition*>::const_iterator it = recipes.begin(); for (unsigned i = 0; it != recipes.end(); ++it) { lua_newtable(L); @@ -487,10 +487,9 @@ int ModApiCraft::l_get_craft_recipe(lua_State *L) NO_MAP_LOCK_REQUIRED; std::string item = luaL_checkstring(L, 1); - Server *server = getServer(L); + IGameDef *gdef = getGameDef(L); CraftOutput output(item, 0); - std::vector<CraftDefinition*> recipes = server->cdef() - ->getCraftRecipes(output, server, 1); + auto recipes = gdef->cdef()->getCraftRecipes(output, gdef, 1); lua_createtable(L, 1, 0); @@ -500,7 +499,7 @@ int ModApiCraft::l_get_craft_recipe(lua_State *L) setintfield(L, -1, "width", 0); return 1; } - push_craft_recipe(L, server, recipes[0], output); + push_craft_recipe(L, gdef, recipes[0], output); return 1; } @@ -510,12 +509,11 @@ int ModApiCraft::l_get_all_craft_recipes(lua_State *L) NO_MAP_LOCK_REQUIRED; std::string item = luaL_checkstring(L, 1); - Server *server = getServer(L); + IGameDef *gdef = getGameDef(L); CraftOutput output(item, 0); - std::vector<CraftDefinition*> recipes = server->cdef() - ->getCraftRecipes(output, server); + auto recipes = gdef->cdef()->getCraftRecipes(output, gdef); - push_craft_recipes(L, server, recipes, output); + push_craft_recipes(L, gdef, recipes, output); return 1; } @@ -527,3 +525,11 @@ void ModApiCraft::Initialize(lua_State *L, int top) API_FCT(register_craft); API_FCT(clear_craft); } + +void ModApiCraft::InitializeAsync(lua_State *L, int top) +{ + // all read-only functions + API_FCT(get_all_craft_recipes); + API_FCT(get_craft_recipe); + API_FCT(get_craft_result); +} diff --git a/src/script/lua_api/l_craft.h b/src/script/lua_api/l_craft.h index 9002b23ef..5234af56f 100644 --- a/src/script/lua_api/l_craft.h +++ b/src/script/lua_api/l_craft.h @@ -45,4 +45,5 @@ private: public: static void Initialize(lua_State *L, int top); + static void InitializeAsync(lua_State *L, int top); }; diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index 876f84d53..a489d245c 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -477,7 +477,7 @@ int ModApiEnvMod::l_place_node(lua_State *L) return 1; } // Create item to place - ItemStack item(ndef->get(n).name, 1, 0, idef); + Optional<ItemStack> item = ItemStack(ndef->get(n).name, 1, 0, idef); // Make pointed position PointedThing pointed; pointed.type = POINTEDTHING_NODE; @@ -1038,6 +1038,21 @@ int ModApiEnvMod::l_find_nodes_near_under_air_except(lua_State *L) return 2; } +static void checkArea(v3s16 &minp, v3s16 &maxp) +{ + auto volume = VoxelArea(minp, maxp).getVolume(); + // Volume limit equal to 8 default mapchunks, (80 * 2) ^ 3 = 4,096,000 + if (volume > 4096000) { + throw LuaError("Area volume exceeds allowed value of 4096000"); + } + + // Clamp to map range to avoid problems +#define CLAMP(arg) core::clamp(arg, (s16)-MAX_MAP_GENERATION_LIMIT, (s16)MAX_MAP_GENERATION_LIMIT) + minp = v3s16(CLAMP(minp.X), CLAMP(minp.Y), CLAMP(minp.Z)); + maxp = v3s16(CLAMP(maxp.X), CLAMP(maxp.Y), CLAMP(maxp.Z)); +#undef CLAMP +} + // find_nodes_in_area(minp, maxp, nodenames, [grouped]) int ModApiEnvMod::l_find_nodes_in_area(lua_State *L) { @@ -1057,13 +1072,7 @@ int ModApiEnvMod::l_find_nodes_in_area(lua_State *L) } #endif - v3s16 cube = maxp - minp + 1; - // Volume limit equal to 8 default mapchunks, (80 * 2) ^ 3 = 4,096,000 - if ((u64)cube.X * (u64)cube.Y * (u64)cube.Z > 4096000) { - luaL_error(L, "find_nodes_in_area(): area volume" - " exceeds allowed value of 4096000"); - return 0; - } + checkArea(minp, maxp); std::vector<content_t> filter; collectNodeIds(L, 3, ndef, filter); @@ -1168,13 +1177,7 @@ int ModApiEnvMod::l_find_nodes_in_area_under_air(lua_State *L) } #endif - v3s16 cube = maxp - minp + 1; - // Volume limit equal to 8 default mapchunks, (80 * 2) ^ 3 = 4,096,000 - if ((u64)cube.X * (u64)cube.Y * (u64)cube.Z > 4096000) { - luaL_error(L, "find_nodes_in_area_under_air(): area volume" - " exceeds allowed value of 4096000"); - return 0; - } + checkArea(minp, maxp); std::vector<content_t> filter; collectNodeIds(L, 3, ndef, filter); @@ -1541,7 +1544,7 @@ int ModApiEnvMod::l_transforming_liquid_add(lua_State *L) GET_ENV_PTR; v3s16 p0 = read_v3s16(L, 1); - env->getMap().transforming_liquid_add(p0); + env->getServerMap().transforming_liquid_add(p0); return 1; } diff --git a/src/script/lua_api/l_env.h b/src/script/lua_api/l_env.h index a5ac21e21..70a8d2398 100644 --- a/src/script/lua_api/l_env.h +++ b/src/script/lua_api/l_env.h @@ -114,7 +114,7 @@ private: // get_objects_inside_radius(pos, radius) static int l_get_objects_inside_radius(lua_State *L); - + // get_objects_in_area(pos, minp, maxp) static int l_get_objects_in_area(lua_State *L); diff --git a/src/script/lua_api/l_http.cpp b/src/script/lua_api/l_http.cpp index 751ec9837..5566a8523 100644 --- a/src/script/lua_api/l_http.cpp +++ b/src/script/lua_api/l_http.cpp @@ -21,14 +21,13 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "common/c_converter.h" #include "common/c_content.h" #include "lua_api/l_http.h" +#include "cpp_api/s_security.h" #include "httpfetch.h" #include "settings.h" #include "debug.h" #include "log.h" -#include <algorithm> #include <iomanip> -#include <cctype> #define HTTP_API(name) \ lua_pushstring(L, #name); \ @@ -167,54 +166,22 @@ int ModApiHttp::l_request_http_api(lua_State *L) { NO_MAP_LOCK_REQUIRED; - // We have to make sure that this function is being called directly by - // a mod, otherwise a malicious mod could override this function and - // steal its return value. - lua_Debug info; - - // Make sure there's only one item below this function on the stack... - if (lua_getstack(L, 2, &info)) { - return 0; - } - FATAL_ERROR_IF(!lua_getstack(L, 1, &info), "lua_getstack() failed"); - FATAL_ERROR_IF(!lua_getinfo(L, "S", &info), "lua_getinfo() failed"); - - // ...and that that item is the main file scope. - if (strcmp(info.what, "main") != 0) { - return 0; - } - - // Mod must be listed in secure.http_mods or secure.trusted_mods - lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); - if (!lua_isstring(L, -1)) { - return 0; - } - - std::string mod_name = readParam<std::string>(L, -1); - std::string http_mods = g_settings->get("secure.http_mods"); - http_mods.erase(std::remove(http_mods.begin(), http_mods.end(), ' '), http_mods.end()); - std::vector<std::string> mod_list_http = str_split(http_mods, ','); - - std::string trusted_mods = g_settings->get("secure.trusted_mods"); - trusted_mods.erase(std::remove(trusted_mods.begin(), trusted_mods.end(), ' '), trusted_mods.end()); - std::vector<std::string> mod_list_trusted = str_split(trusted_mods, ','); - - mod_list_http.insert(mod_list_http.end(), mod_list_trusted.begin(), mod_list_trusted.end()); - if (std::find(mod_list_http.begin(), mod_list_http.end(), mod_name) == mod_list_http.end()) { + if (!ScriptApiSecurity::checkWhitelisted(L, "secure.http_mods") && + !ScriptApiSecurity::checkWhitelisted(L, "secure.trusted_mods")) { lua_pushnil(L); return 1; } - lua_getglobal(L, "core"); - lua_getfield(L, -1, "http_add_fetch"); + lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_HTTP_API_LUA); + assert(lua_isfunction(L, -1)); lua_newtable(L); HTTP_API(fetch_async); HTTP_API(fetch_async_get); // Stack now looks like this: - // <core.http_add_fetch> <table with fetch_async, fetch_async_get> - // Now call core.http_add_fetch to append .fetch(request, callback) to table + // <function> <table with fetch_async, fetch_async_get> + // Now call it to append .fetch(request, callback) to table lua_call(L, 1, 1); return 1; @@ -234,6 +201,22 @@ int ModApiHttp::l_get_http_api(lua_State *L) #endif +int ModApiHttp::l_set_http_api_lua(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + +#if USE_CURL + // This is called by builtin to give us a function that will later + // populate the http_api table with additional method(s). + // We need this because access to the HTTP api is security-relevant and + // any mod could just mess with a global variable. + luaL_checktype(L, 1, LUA_TFUNCTION); + lua_rawseti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_HTTP_API_LUA); +#endif + + return 0; +} + void ModApiHttp::Initialize(lua_State *L, int top) { #if USE_CURL @@ -247,8 +230,14 @@ void ModApiHttp::Initialize(lua_State *L, int top) API_FCT(get_http_api); } else { API_FCT(request_http_api); + API_FCT(set_http_api_lua); } +#else + + // Define this function anyway so builtin can call it without checking + API_FCT(set_http_api_lua); + #endif } diff --git a/src/script/lua_api/l_http.h b/src/script/lua_api/l_http.h index c3a2a5276..8d084ecd9 100644 --- a/src/script/lua_api/l_http.h +++ b/src/script/lua_api/l_http.h @@ -48,6 +48,10 @@ private: static int l_get_http_api(lua_State *L); #endif + // set_http_api_lua() [internal] + static int l_set_http_api_lua(lua_State *L); + + public: static void Initialize(lua_State *L, int top); static void InitializeAsync(lua_State *L, int top); diff --git a/src/script/lua_api/l_internal.h b/src/script/lua_api/l_internal.h index 672e535ca..de73ff42a 100644 --- a/src/script/lua_api/l_internal.h +++ b/src/script/lua_api/l_internal.h @@ -69,7 +69,7 @@ with this program; if not, write to the Free Software Foundation, Inc., // Retrieve Environment pointer as `env` (no map lock) #define GET_PLAIN_ENV_PTR_NO_MAP_LOCK \ - Environment *env = (Environment *)getEnv(L); \ + Environment *env = getEnv(L); \ if (env == NULL) \ return 0 diff --git a/src/script/lua_api/l_inventory.cpp b/src/script/lua_api/l_inventory.cpp index 0dd418462..175047e58 100644 --- a/src/script/lua_api/l_inventory.cpp +++ b/src/script/lua_api/l_inventory.cpp @@ -214,11 +214,16 @@ int InvRef::l_get_list(lua_State *L) InvRef *ref = checkobject(L, 1); const char *listname = luaL_checkstring(L, 2); Inventory *inv = getinv(L, ref); - if(inv){ - push_inventory_list(L, inv, listname); - } else { + if (!inv) { lua_pushnil(L); + return 1; } + InventoryList *invlist = inv->getList(listname); + if (!invlist) { + lua_pushnil(L); + return 1; + } + push_inventory_list(L, *invlist); return 1; } @@ -242,7 +247,7 @@ int InvRef::l_set_list(lua_State *L) return 0; } -// get_lists(self) -> list of InventoryLists +// get_lists(self) -> table that maps listnames to InventoryLists int InvRef::l_get_lists(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -251,15 +256,7 @@ int InvRef::l_get_lists(lua_State *L) if (!inv) { return 0; } - std::vector<const InventoryList*> lists = inv->getLists(); - std::vector<const InventoryList*>::iterator iter = lists.begin(); - lua_createtable(L, 0, lists.size()); - for (; iter != lists.end(); iter++) { - const char* name = (*iter)->getName().c_str(); - lua_pushstring(L, name); - push_inventory_list(L, inv, name); - lua_rawset(L, -3); - } + push_inventory_lists(L, *inv); return 1; } @@ -421,19 +418,6 @@ void InvRef::create(lua_State *L, const InventoryLocation &loc) luaL_getmetatable(L, className); lua_setmetatable(L, -2); } -void InvRef::createPlayer(lua_State *L, RemotePlayer *player) -{ - NO_MAP_LOCK_REQUIRED; - InventoryLocation loc; - loc.setPlayer(player->getName()); - create(L, loc); -} -void InvRef::createNodeMeta(lua_State *L, v3s16 p) -{ - InventoryLocation loc; - loc.setNodeMeta(p); - create(L, loc); -} void InvRef::Register(lua_State *L) { diff --git a/src/script/lua_api/l_inventory.h b/src/script/lua_api/l_inventory.h index 94f670c9d..6a75bac0f 100644 --- a/src/script/lua_api/l_inventory.h +++ b/src/script/lua_api/l_inventory.h @@ -111,8 +111,6 @@ public: // Creates an InvRef and leaves it on top of stack // Not callable from Lua; all references are created on the C side. static void create(lua_State *L, const InventoryLocation &loc); - static void createPlayer(lua_State *L, RemotePlayer *player); - static void createNodeMeta(lua_State *L, v3s16 p); static void Register(lua_State *L); }; diff --git a/src/script/lua_api/l_item.cpp b/src/script/lua_api/l_item.cpp index 2ea2bd4f6..9220259ff 100644 --- a/src/script/lua_api/l_item.cpp +++ b/src/script/lua_api/l_item.cpp @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_internal.h" #include "common/c_converter.h" #include "common/c_content.h" +#include "common/c_packer.h" #include "itemdef.h" #include "nodedef.h" #include "server.h" @@ -446,6 +447,7 @@ int LuaItemStack::create_object(lua_State *L) lua_setmetatable(L, -2); return 1; } + // Not callable from Lua int LuaItemStack::create(lua_State *L, const ItemStack &item) { @@ -462,6 +464,20 @@ LuaItemStack *LuaItemStack::checkobject(lua_State *L, int narg) return *(LuaItemStack **)luaL_checkudata(L, narg, className); } +void *LuaItemStack::packIn(lua_State *L, int idx) +{ + LuaItemStack *o = checkobject(L, idx); + return new ItemStack(o->getItem()); +} + +void LuaItemStack::packOut(lua_State *L, void *ptr) +{ + ItemStack *stack = reinterpret_cast<ItemStack*>(ptr); + if (L) + create(L, *stack); + delete stack; +} + void LuaItemStack::Register(lua_State *L) { lua_newtable(L); @@ -493,6 +509,8 @@ void LuaItemStack::Register(lua_State *L) // Can be created from Lua (ItemStack(itemstack or itemstring or table or nil)) lua_register(L, className, create_object); + + script_register_packer(L, className, packIn, packOut); } const char LuaItemStack::className[] = "ItemStack"; @@ -638,8 +656,8 @@ int ModApiItemMod::l_get_content_id(lua_State *L) NO_MAP_LOCK_REQUIRED; std::string name = luaL_checkstring(L, 1); - const IItemDefManager *idef = getGameDef(L)->getItemDefManager(); - const NodeDefManager *ndef = getGameDef(L)->getNodeDefManager(); + const IItemDefManager *idef = getGameDef(L)->idef(); + const NodeDefManager *ndef = getGameDef(L)->ndef(); // If this is called at mod load time, NodeDefManager isn't aware of // aliases yet, so we need to handle them manually @@ -664,7 +682,7 @@ int ModApiItemMod::l_get_name_from_content_id(lua_State *L) NO_MAP_LOCK_REQUIRED; content_t c = luaL_checkint(L, 1); - const NodeDefManager *ndef = getGameDef(L)->getNodeDefManager(); + const NodeDefManager *ndef = getGameDef(L)->ndef(); const char *name = ndef->get(c).name.c_str(); lua_pushstring(L, name); @@ -679,3 +697,10 @@ void ModApiItemMod::Initialize(lua_State *L, int top) API_FCT(get_content_id); API_FCT(get_name_from_content_id); } + +void ModApiItemMod::InitializeAsync(lua_State *L, int top) +{ + // all read-only functions + API_FCT(get_content_id); + API_FCT(get_name_from_content_id); +} diff --git a/src/script/lua_api/l_item.h b/src/script/lua_api/l_item.h index 16878c101..180975061 100644 --- a/src/script/lua_api/l_item.h +++ b/src/script/lua_api/l_item.h @@ -141,8 +141,11 @@ public: // Not callable from Lua static int create(lua_State *L, const ItemStack &item); static LuaItemStack* checkobject(lua_State *L, int narg); - static void Register(lua_State *L); + static void *packIn(lua_State *L, int idx); + static void packOut(lua_State *L, void *ptr); + + static void Register(lua_State *L); }; class ModApiItemMod : public ModApiBase { @@ -152,6 +155,8 @@ private: static int l_register_alias_raw(lua_State *L); static int l_get_content_id(lua_State *L); static int l_get_name_from_content_id(lua_State *L); + public: static void Initialize(lua_State *L, int top); + static void InitializeAsync(lua_State *L, int top); }; diff --git a/src/script/lua_api/l_localplayer.cpp b/src/script/lua_api/l_localplayer.cpp index 769b3ef2b..1da0679d6 100644 --- a/src/script/lua_api/l_localplayer.cpp +++ b/src/script/lua_api/l_localplayer.cpp @@ -196,11 +196,11 @@ int LuaLocalPlayer::l_is_in_liquid_stable(lua_State *L) return 1; } -int LuaLocalPlayer::l_get_liquid_viscosity(lua_State *L) +int LuaLocalPlayer::l_get_move_resistance(lua_State *L) { LocalPlayer *player = getobject(L, 1); - lua_pushinteger(L, player->liquid_viscosity); + lua_pushinteger(L, player->move_resistance); return 1; } @@ -302,13 +302,15 @@ int LuaLocalPlayer::l_get_control(lua_State *L) set("dig", c.dig); set("place", c.place); // Player movement in polar coordinates and non-binary speed - set("movement_speed", c.movement_speed); - set("movement_direction", c.movement_direction); + lua_pushnumber(L, c.movement_speed); + lua_setfield(L, -2, "movement_speed"); + lua_pushnumber(L, c.movement_direction); + lua_setfield(L, -2, "movement_direction"); // Provide direction keys to ensure compatibility - set("up", player->keyPressed & (1 << 0)); // Up, down, left, and right were removed in favor of - set("down", player->keyPressed & (1 << 1)); // analog direction indicators and are therefore not - set("left", player->keyPressed & (1 << 2)); // available as booleans anymore. The corresponding values - set("right", player->keyPressed & (1 << 3)); // can still be read from the keyPressed bits though. + set("up", c.direction_keys & (1 << 0)); + set("down", c.direction_keys & (1 << 1)); + set("left", c.direction_keys & (1 << 2)); + set("right", c.direction_keys & (1 << 3)); return 1; } @@ -576,7 +578,6 @@ const luaL_Reg LuaLocalPlayer::methods[] = { luamethod(LuaLocalPlayer, is_touching_ground), luamethod(LuaLocalPlayer, is_in_liquid), luamethod(LuaLocalPlayer, is_in_liquid_stable), - luamethod(LuaLocalPlayer, get_liquid_viscosity), luamethod(LuaLocalPlayer, is_climbing), luamethod(LuaLocalPlayer, swimming_vertical), luamethod(LuaLocalPlayer, get_physics_override), @@ -602,5 +603,7 @@ const luaL_Reg LuaLocalPlayer::methods[] = { luamethod(LuaLocalPlayer, get_object), luamethod(LuaLocalPlayer, get_hotbar_size), + luamethod(LuaLocalPlayer, get_move_resistance), + {0, 0} }; diff --git a/src/script/lua_api/l_localplayer.h b/src/script/lua_api/l_localplayer.h index bb5a294ca..458c824e6 100644 --- a/src/script/lua_api/l_localplayer.h +++ b/src/script/lua_api/l_localplayer.h @@ -72,7 +72,6 @@ private: static int l_is_touching_ground(lua_State *L); static int l_is_in_liquid(lua_State *L); static int l_is_in_liquid_stable(lua_State *L); - static int l_get_liquid_viscosity(lua_State *L); static int l_is_climbing(lua_State *L); static int l_swimming_vertical(lua_State *L); @@ -121,6 +120,8 @@ private: // hud_get(self, id) static int l_hud_get(lua_State *L); + static int l_get_move_resistance(lua_State *L); + // get_object(self) static int l_get_object(lua_State *L); diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index 8eb0e252a..2b46a4d51 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -323,9 +323,9 @@ int ModApiMainMenu::l_get_games(lua_State *L) lua_newtable(L); int table2 = lua_gettop(L); int internal_index = 1; - for (const std::string &addon_mods_path : game.addon_mods_paths) { + for (const auto &addon_mods_path : game.addon_mods_paths) { lua_pushnumber(L, internal_index); - lua_pushstring(L, addon_mods_path.c_str()); + lua_pushstring(L, addon_mods_path.second.c_str()); lua_settable(L, table2); internal_index++; } @@ -414,25 +414,53 @@ int ModApiMainMenu::l_create_world(lua_State *L) const char *name = luaL_checkstring(L, 1); int gameidx = luaL_checkinteger(L,2) -1; + StringMap use_settings; + luaL_checktype(L, 3, LUA_TTABLE); + lua_pushnil(L); + while (lua_next(L, 3) != 0) { + // key at index -2 and value at index -1 + use_settings[luaL_checkstring(L, -2)] = luaL_checkstring(L, -1); + lua_pop(L, 1); + } + lua_pop(L, 1); + std::string path = porting::path_user + DIR_DELIM "worlds" + DIR_DELIM + sanitizeDirName(name, "world_"); std::vector<SubgameSpec> games = getAvailableGames(); + if (gameidx < 0 || gameidx >= (int) games.size()) { + lua_pushstring(L, "Invalid game index"); + return 1; + } - if ((gameidx >= 0) && - (gameidx < (int) games.size())) { + // Set the settings for world creation + // this is a bad hack but the best we have right now.. + StringMap backup; + for (auto it : use_settings) { + if (g_settings->existsLocal(it.first)) + backup[it.first] = g_settings->get(it.first); + g_settings->set(it.first, it.second); + } - // Create world if it doesn't exist - try { - loadGameConfAndInitWorld(path, name, games[gameidx], true); - lua_pushnil(L); - } catch (const BaseException &e) { - lua_pushstring(L, (std::string("Failed to initialize world: ") + e.what()).c_str()); - } - } else { - lua_pushstring(L, "Invalid game index"); + // Create world if it doesn't exist + try { + loadGameConfAndInitWorld(path, name, games[gameidx], true); + lua_pushnil(L); + } catch (const BaseException &e) { + auto err = std::string("Failed to initialize world: ") + e.what(); + lua_pushstring(L, err.c_str()); + } + + // Restore previous settings + for (auto it : use_settings) { + auto it2 = backup.find(it.first); + if (it2 == backup.end()) + g_settings->remove(it.first); // wasn't set before + else + g_settings->set(it.first, it2->second); // was set before } + return 1; } @@ -503,6 +531,21 @@ int ModApiMainMenu::l_get_modpath(lua_State *L) } /******************************************************************************/ +int ModApiMainMenu::l_get_modpaths(lua_State *L) +{ + lua_newtable(L); + + ModApiMainMenu::l_get_modpath(L); + lua_setfield(L, -2, "mods"); + + for (const std::string &component : getEnvModPaths()) { + lua_pushstring(L, component.c_str()); + lua_setfield(L, -2, fs::AbsolutePath(component).c_str()); + } + return 1; +} + +/******************************************************************************/ int ModApiMainMenu::l_get_clientmodpath(lua_State *L) { std::string modpath = fs::RemoveRelativePathComponents( @@ -548,7 +591,10 @@ int ModApiMainMenu::l_get_cache_path(lua_State *L) /******************************************************************************/ int ModApiMainMenu::l_get_temp_path(lua_State *L) { - lua_pushstring(L, fs::TempPath().c_str()); + if (lua_isnoneornil(L, 1) || !lua_toboolean(L, 1)) + lua_pushstring(L, fs::TempPath().c_str()); + else + lua_pushstring(L, fs::CreateTempFile().c_str()); return 1; } @@ -588,26 +634,24 @@ int ModApiMainMenu::l_copy_dir(lua_State *L) const char *destination = luaL_checkstring(L, 2); bool keep_source = true; + if (!lua_isnoneornil(L, 3)) + keep_source = readParam<bool>(L, 3); - if ((!lua_isnone(L,3)) && - (!lua_isnil(L,3))) { - keep_source = readParam<bool>(L,3); - } - - std::string absolute_destination = fs::RemoveRelativePathComponents(destination); - std::string absolute_source = fs::RemoveRelativePathComponents(source); + std::string abs_destination = fs::RemoveRelativePathComponents(destination); + std::string abs_source = fs::RemoveRelativePathComponents(source); - if ((ModApiMainMenu::mayModifyPath(absolute_destination))) { - bool retval = fs::CopyDir(absolute_source,absolute_destination); - - if (retval && (!keep_source)) { - - retval &= fs::RecursiveDelete(absolute_source); - } - lua_pushboolean(L,retval); + if (!ModApiMainMenu::mayModifyPath(abs_destination) || + (!keep_source && !ModApiMainMenu::mayModifyPath(abs_source))) { + lua_pushboolean(L, false); return 1; } - lua_pushboolean(L,false); + + bool retval; + if (keep_source) + retval = fs::CopyDir(abs_source, abs_destination); + else + retval = fs::MoveDir(abs_source, abs_destination); + lua_pushboolean(L, retval); return 1; } @@ -629,9 +673,9 @@ int ModApiMainMenu::l_extract_zip(lua_State *L) std::string absolute_destination = fs::RemoveRelativePathComponents(destination); if (ModApiMainMenu::mayModifyPath(absolute_destination)) { - auto rendering_engine = getGuiEngine(L)->m_rendering_engine; - fs::CreateAllDirs(absolute_destination); - lua_pushboolean(L, fs::extractZipFile(rendering_engine->get_filesystem(), zipfile, destination)); + auto fs = RenderingEngine::get_raw_device()->getFileSystem(); + bool ok = fs::extractZipFile(fs, zipfile, destination); + lua_pushboolean(L, ok); return 1; } @@ -759,8 +803,9 @@ int ModApiMainMenu::l_get_video_drivers(lua_State *L) /******************************************************************************/ int ModApiMainMenu::l_gettext(lua_State *L) { - std::string text = strgettext(std::string(luaL_checkstring(L, 1))); - lua_pushstring(L, text.c_str()); + const char *srctext = luaL_checkstring(L, 1); + const char *text = *srctext ? gettext(srctext) : ""; + lua_pushstring(L, text); return 1; } @@ -860,6 +905,7 @@ void ModApiMainMenu::Initialize(lua_State *L, int top) API_FCT(get_mapgen_names); API_FCT(get_user_path); API_FCT(get_modpath); + API_FCT(get_modpaths); API_FCT(get_clientmodpath); API_FCT(get_gamepath); API_FCT(get_texturepath); @@ -893,6 +939,7 @@ void ModApiMainMenu::InitializeAsync(lua_State *L, int top) API_FCT(get_mapgen_names); API_FCT(get_user_path); API_FCT(get_modpath); + API_FCT(get_modpaths); API_FCT(get_clientmodpath); API_FCT(get_gamepath); API_FCT(get_texturepath); @@ -903,10 +950,10 @@ void ModApiMainMenu::InitializeAsync(lua_State *L, int top) API_FCT(delete_dir); API_FCT(copy_dir); API_FCT(is_dir); - //API_FCT(extract_zip); //TODO remove dependency to GuiEngine + API_FCT(extract_zip); API_FCT(may_modify_path); API_FCT(download_file); API_FCT(get_min_supp_proto); API_FCT(get_max_supp_proto); - //API_FCT(gettext); (gettext lib isn't threadsafe) + API_FCT(gettext); } diff --git a/src/script/lua_api/l_mainmenu.h b/src/script/lua_api/l_mainmenu.h index ec2d20da2..781185425 100644 --- a/src/script/lua_api/l_mainmenu.h +++ b/src/script/lua_api/l_mainmenu.h @@ -112,6 +112,8 @@ private: static int l_get_modpath(lua_State *L); + static int l_get_modpaths(lua_State *L); + static int l_get_clientmodpath(lua_State *L); static int l_get_gamepath(lua_State *L); diff --git a/src/script/lua_api/l_metadata.cpp b/src/script/lua_api/l_metadata.cpp index 21002e6a7..d00cb4daa 100644 --- a/src/script/lua_api/l_metadata.cpp +++ b/src/script/lua_api/l_metadata.cpp @@ -82,9 +82,10 @@ int MetaDataRef::l_get(lua_State *L) std::string str; if (meta->getStringToRef(name, str)) { lua_pushlstring(L, str.c_str(), str.size()); - return 1; + } else { + lua_pushnil(L); } - return 0; + return 1; } // get_string(self, name) diff --git a/src/script/lua_api/l_nodemeta.cpp b/src/script/lua_api/l_nodemeta.cpp index 60d14f8f2..1d052685e 100644 --- a/src/script/lua_api/l_nodemeta.cpp +++ b/src/script/lua_api/l_nodemeta.cpp @@ -89,7 +89,10 @@ int NodeMetaRef::l_get_inventory(lua_State *L) NodeMetaRef *ref = checkobject(L, 1); ref->getmeta(true); // try to ensure the metadata exists - InvRef::createNodeMeta(L, ref->m_p); + + InventoryLocation loc; + loc.setNodeMeta(ref->m_p); + InvRef::create(L, loc); return 1; } @@ -124,18 +127,14 @@ void NodeMetaRef::handleToTable(lua_State *L, Metadata *_meta) // fields MetaDataRef::handleToTable(L, _meta); - NodeMetadata *meta = (NodeMetadata*) _meta; + NodeMetadata *meta = (NodeMetadata *) _meta; // inventory - lua_newtable(L); Inventory *inv = meta->getInventory(); if (inv) { - std::vector<const InventoryList *> lists = inv->getLists(); - for(std::vector<const InventoryList *>::const_iterator - i = lists.begin(); i != lists.end(); ++i) { - push_inventory_list(L, inv, (*i)->getName().c_str()); - lua_setfield(L, -2, (*i)->getName().c_str()); - } + push_inventory_lists(L, *inv); + } else { + lua_newtable(L); } lua_setfield(L, -2, "inventory"); } diff --git a/src/script/lua_api/l_noise.cpp b/src/script/lua_api/l_noise.cpp index f43ba837a..5561eaebf 100644 --- a/src/script/lua_api/l_noise.cpp +++ b/src/script/lua_api/l_noise.cpp @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_internal.h" #include "common/c_converter.h" #include "common/c_content.h" +#include "common/c_packer.h" #include "log.h" #include "porting.h" #include "util/numeric.h" @@ -30,7 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc., LuaPerlinNoise */ -LuaPerlinNoise::LuaPerlinNoise(NoiseParams *params) : +LuaPerlinNoise::LuaPerlinNoise(const NoiseParams *params) : np(*params) { } @@ -101,6 +102,25 @@ LuaPerlinNoise *LuaPerlinNoise::checkobject(lua_State *L, int narg) } +void *LuaPerlinNoise::packIn(lua_State *L, int idx) +{ + LuaPerlinNoise *o = checkobject(L, idx); + return new NoiseParams(o->np); +} + +void LuaPerlinNoise::packOut(lua_State *L, void *ptr) +{ + NoiseParams *np = reinterpret_cast<NoiseParams*>(ptr); + if (L) { + LuaPerlinNoise *o = new LuaPerlinNoise(np); + *(void **)(lua_newuserdata(L, sizeof(void *))) = o; + luaL_getmetatable(L, className); + lua_setmetatable(L, -2); + } + delete np; +} + + void LuaPerlinNoise::Register(lua_State *L) { lua_newtable(L); @@ -126,6 +146,8 @@ void LuaPerlinNoise::Register(lua_State *L) lua_pop(L, 1); lua_register(L, className, create_object); + + script_register_packer(L, className, packIn, packOut); } @@ -141,12 +163,10 @@ luaL_Reg LuaPerlinNoise::methods[] = { LuaPerlinNoiseMap */ -LuaPerlinNoiseMap::LuaPerlinNoiseMap(NoiseParams *params, s32 seed, v3s16 size) +LuaPerlinNoiseMap::LuaPerlinNoiseMap(const NoiseParams *np, s32 seed, v3s16 size) { - m_is3d = size.Z > 1; - np = *params; try { - noise = new Noise(&np, seed, size.X, size.Y, size.Z); + noise = new Noise(np, seed, size.X, size.Y, size.Z); } catch (InvalidNoiseParamsException &e) { throw LuaError(e.what()); } @@ -217,7 +237,7 @@ int LuaPerlinNoiseMap::l_get_3d_map(lua_State *L) LuaPerlinNoiseMap *o = checkobject(L, 1); v3f p = check_v3f(L, 2); - if (!o->m_is3d) + if (!o->is3D()) return 0; Noise *n = o->noise; @@ -248,7 +268,7 @@ int LuaPerlinNoiseMap::l_get_3d_map_flat(lua_State *L) v3f p = check_v3f(L, 2); bool use_buffer = lua_istable(L, 3); - if (!o->m_is3d) + if (!o->is3D()) return 0; Noise *n = o->noise; @@ -289,7 +309,7 @@ int LuaPerlinNoiseMap::l_calc_3d_map(lua_State *L) LuaPerlinNoiseMap *o = checkobject(L, 1); v3f p = check_v3f(L, 2); - if (!o->m_is3d) + if (!o->is3D()) return 0; Noise *n = o->noise; @@ -359,6 +379,35 @@ LuaPerlinNoiseMap *LuaPerlinNoiseMap::checkobject(lua_State *L, int narg) } +struct NoiseMapParams { + NoiseParams np; + s32 seed; + v3s16 size; +}; + +void *LuaPerlinNoiseMap::packIn(lua_State *L, int idx) +{ + LuaPerlinNoiseMap *o = checkobject(L, idx); + NoiseMapParams *ret = new NoiseMapParams(); + ret->np = o->noise->np; + ret->seed = o->noise->seed; + ret->size = v3s16(o->noise->sx, o->noise->sy, o->noise->sz); + return ret; +} + +void LuaPerlinNoiseMap::packOut(lua_State *L, void *ptr) +{ + NoiseMapParams *p = reinterpret_cast<NoiseMapParams*>(ptr); + if (L) { + LuaPerlinNoiseMap *o = new LuaPerlinNoiseMap(&p->np, p->seed, p->size); + *(void **)(lua_newuserdata(L, sizeof(void *))) = o; + luaL_getmetatable(L, className); + lua_setmetatable(L, -2); + } + delete p; +} + + void LuaPerlinNoiseMap::Register(lua_State *L) { lua_newtable(L); @@ -384,6 +433,8 @@ void LuaPerlinNoiseMap::Register(lua_State *L) lua_pop(L, 1); lua_register(L, className, create_object); + + script_register_packer(L, className, packIn, packOut); } diff --git a/src/script/lua_api/l_noise.h b/src/script/lua_api/l_noise.h index 9f50dfd3f..5d34a479b 100644 --- a/src/script/lua_api/l_noise.h +++ b/src/script/lua_api/l_noise.h @@ -30,6 +30,7 @@ class LuaPerlinNoise : public ModApiBase { private: NoiseParams np; + static const char className[]; static luaL_Reg methods[]; @@ -42,7 +43,7 @@ private: static int l_get_3d(lua_State *L); public: - LuaPerlinNoise(NoiseParams *params); + LuaPerlinNoise(const NoiseParams *params); ~LuaPerlinNoise() = default; // LuaPerlinNoise(seed, octaves, persistence, scale) @@ -51,6 +52,9 @@ public: static LuaPerlinNoise *checkobject(lua_State *L, int narg); + static void *packIn(lua_State *L, int idx); + static void packOut(lua_State *L, void *ptr); + static void Register(lua_State *L); }; @@ -59,9 +63,8 @@ public: */ class LuaPerlinNoiseMap : public ModApiBase { - NoiseParams np; Noise *noise; - bool m_is3d; + static const char className[]; static luaL_Reg methods[]; @@ -80,16 +83,20 @@ class LuaPerlinNoiseMap : public ModApiBase static int l_get_map_slice(lua_State *L); public: - LuaPerlinNoiseMap(NoiseParams *np, s32 seed, v3s16 size); - + LuaPerlinNoiseMap(const NoiseParams *np, s32 seed, v3s16 size); ~LuaPerlinNoiseMap(); + inline bool is3D() const { return noise->sz > 1; } + // LuaPerlinNoiseMap(np, size) // Creates an LuaPerlinNoiseMap and leaves it on top of stack static int create_object(lua_State *L); static LuaPerlinNoiseMap *checkobject(lua_State *L, int narg); + static void *packIn(lua_State *L, int idx); + static void packOut(lua_State *L, void *ptr); + static void Register(lua_State *L); }; diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index b7185f7ec..39b19364e 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -174,7 +174,7 @@ int ObjectRef::l_punch(lua_State *L) v3f dir = readParam<v3f>(L, 5, sao->getBasePosition() - puncher->getBasePosition()); dir.normalize(); - u16 wear = sao->punch(dir, &toolcap, puncher, time_from_last_punch); + u32 wear = sao->punch(dir, &toolcap, puncher, time_from_last_punch); lua_pushnumber(L, wear); return 1; @@ -420,8 +420,7 @@ int ObjectRef::l_set_local_animation(lua_State *L) float frame_speed = readParam<float>(L, 6, 30.0f); getServer(L)->setLocalPlayerAnimations(player, frames, frame_speed); - lua_pushboolean(L, true); - return 1; + return 0; } // get_local_animation(self) @@ -464,8 +463,7 @@ int ObjectRef::l_set_eye_offset(lua_State *L) offset_third.Y = rangelim(offset_third.Y,-10,15); //1.5*BS getServer(L)->setPlayerEyeOffset(player, offset_first, offset_third); - lua_pushboolean(L, true); - return 1; + return 0; } // get_eye_offset(self) @@ -737,8 +735,7 @@ int ObjectRef::l_set_nametag_attributes(lua_State *L) prop->validate(); sao->notifyObjectPropertiesModified(); - lua_pushboolean(L, true); - return 1; + return 0; } // get_nametag_attributes(self) @@ -1116,7 +1113,7 @@ int ObjectRef::l_set_look_vertical(lua_State *L) float pitch = readParam<float>(L, 2) * core::RADTODEG; playersao->setLookPitchAndSend(pitch); - return 1; + return 0; } // set_look_horizontal(self, radians) @@ -1131,7 +1128,7 @@ int ObjectRef::l_set_look_horizontal(lua_State *L) float yaw = readParam<float>(L, 2) * core::RADTODEG; playersao->setPlayerYawAndSend(yaw); - return 1; + return 0; } // DEPRECATED @@ -1151,7 +1148,7 @@ int ObjectRef::l_set_look_pitch(lua_State *L) float pitch = readParam<float>(L, 2) * core::RADTODEG; playersao->setLookPitchAndSend(pitch); - return 1; + return 0; } // DEPRECATED @@ -1171,7 +1168,7 @@ int ObjectRef::l_set_look_yaw(lua_State *L) float yaw = readParam<float>(L, 2) * core::RADTODEG; playersao->setPlayerYawAndSend(yaw); - return 1; + return 0; } // set_fov(self, degrees, is_multiplier, transition_time) @@ -1310,8 +1307,7 @@ int ObjectRef::l_set_inventory_formspec(lua_State *L) player->inventory_formspec = formspec; getServer(L)->reportInventoryFormspecModified(player->getName()); - lua_pushboolean(L, true); - return 1; + return 0; } // get_inventory_formspec(self) -> formspec @@ -1342,8 +1338,7 @@ int ObjectRef::l_set_formspec_prepend(lua_State *L) player->formspec_prepend = formspec; getServer(L)->reportFormspecPrependModified(player->getName()); - lua_pushboolean(L, true); - return 1; + return 0; } // get_formspec_prepend(self) @@ -1367,20 +1362,19 @@ int ObjectRef::l_get_player_control(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == nullptr) { - lua_pushlstring(L, "", 0); - return 1; - } - const PlayerControl &control = player->getPlayerControl(); lua_newtable(L); - lua_pushboolean(L, player->keyPressed & (1 << 0)); + if (player == nullptr) + return 1; + + const PlayerControl &control = player->getPlayerControl(); + lua_pushboolean(L, control.direction_keys & (1 << 0)); lua_setfield(L, -2, "up"); - lua_pushboolean(L, player->keyPressed & (1 << 1)); + lua_pushboolean(L, control.direction_keys & (1 << 1)); lua_setfield(L, -2, "down"); - lua_pushboolean(L, player->keyPressed & (1 << 2)); + lua_pushboolean(L, control.direction_keys & (1 << 2)); lua_setfield(L, -2, "left"); - lua_pushboolean(L, player->keyPressed & (1 << 3)); + lua_pushboolean(L, control.direction_keys & (1 << 3)); lua_setfield(L, -2, "right"); lua_pushboolean(L, control.jump); lua_setfield(L, -2, "jump"); @@ -1409,11 +1403,25 @@ int ObjectRef::l_get_player_control_bits(lua_State *L) ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); if (player == nullptr) { - lua_pushlstring(L, "", 0); + lua_pushinteger(L, 0); return 1; } - lua_pushnumber(L, player->keyPressed); + const auto &c = player->getPlayerControl(); + + // This is very close to PlayerControl::getKeysPressed() but duplicated + // here so the encoding in the API is not inadvertedly changed. + u32 keypress_bits = + c.direction_keys | + ( (u32)(c.jump & 1) << 4) | + ( (u32)(c.aux1 & 1) << 5) | + ( (u32)(c.sneak & 1) << 6) | + ( (u32)(c.dig & 1) << 7) | + ( (u32)(c.place & 1) << 8) | + ( (u32)(c.zoom & 1) << 9) + ; + + lua_pushinteger(L, keypress_bits); return 1; } @@ -1590,8 +1598,7 @@ int ObjectRef::l_hud_set_flags(lua_State *L) if (!getServer(L)->hudSetFlags(player, flags, mask)) return 0; - lua_pushboolean(L, true); - return 1; + return 0; } // hud_get_flags(self) @@ -1604,20 +1611,11 @@ int ObjectRef::l_hud_get_flags(lua_State *L) return 0; lua_newtable(L); - lua_pushboolean(L, player->hud_flags & HUD_FLAG_HOTBAR_VISIBLE); - lua_setfield(L, -2, "hotbar"); - lua_pushboolean(L, player->hud_flags & HUD_FLAG_HEALTHBAR_VISIBLE); - lua_setfield(L, -2, "healthbar"); - lua_pushboolean(L, player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE); - lua_setfield(L, -2, "crosshair"); - lua_pushboolean(L, player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE); - lua_setfield(L, -2, "wielditem"); - lua_pushboolean(L, player->hud_flags & HUD_FLAG_BREATHBAR_VISIBLE); - lua_setfield(L, -2, "breathbar"); - lua_pushboolean(L, player->hud_flags & HUD_FLAG_MINIMAP_VISIBLE); - lua_setfield(L, -2, "minimap"); - lua_pushboolean(L, player->hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE); - lua_setfield(L, -2, "minimap_radar"); + const EnumString *esp = es_HudBuiltinElement; + for (int i = 0; esp[i].str; i++) { + lua_pushboolean(L, (player->hud_flags & esp[i].num) != 0); + lua_setfield(L, -2, esp[i].str); + } return 1; } @@ -1722,9 +1720,11 @@ int ObjectRef::l_set_sky(lua_State *L) return 0; SkyboxParams sky_params = player->getSkyParams(); - bool is_colorspec = is_color_table(L, 2); - if (lua_istable(L, 2) && !is_colorspec) { + // reset if empty + if (lua_isnoneornil(L, 2) && lua_isnone(L, 3)) { + sky_params = SkyboxDefaults::getSkyDefaults(); + } else if (lua_istable(L, 2) && !is_color_table(L, 2)) { lua_getfield(L, 2, "base_color"); if (!lua_isnil(L, -1)) read_color(L, -1, &sky_params.bgcolor); @@ -1748,17 +1748,11 @@ int ObjectRef::l_set_sky(lua_State *L) } lua_pop(L, 1); - /* - We want to avoid crashes, so we're checking even if we're not using them. - However, we want to ensure that the skybox can be set to nil when - using "regular" or "plain" skybox modes as textures aren't needed. - */ - - if (sky_params.textures.size() != 6 && sky_params.textures.size() > 0) + // Validate that we either have six or zero textures + if (sky_params.textures.size() != 6 && !sky_params.textures.empty()) throw LuaError("Skybox expects 6 textures!"); - sky_params.clouds = getboolfield_default(L, 2, - "clouds", sky_params.clouds); + sky_params.clouds = getboolfield_default(L, 2, "clouds", sky_params.clouds); lua_getfield(L, 2, "sky_color"); if (lua_istable(L, -1)) { @@ -1806,7 +1800,7 @@ int ObjectRef::l_set_sky(lua_State *L) sky_params.fog_tint_type = luaL_checkstring(L, -1); lua_pop(L, 1); - // Because we need to leave the "sky_color" table. + // pop "sky_color" table lua_pop(L, 1); } } else { @@ -1842,11 +1836,8 @@ int ObjectRef::l_set_sky(lua_State *L) if (lua_istable(L, 4)) { lua_pushnil(L); while (lua_next(L, 4) != 0) { - // Key at index -2, and value at index -1 - if (lua_isstring(L, -1)) - sky_params.textures.emplace_back(readParam<std::string>(L, -1)); - else - sky_params.textures.emplace_back(""); + // Key at index -2, and value at index -1 + sky_params.textures.emplace_back(readParam<std::string>(L, -1)); // Remove the value, keep the key for the next iteration lua_pop(L, 1); } @@ -1862,12 +1853,39 @@ int ObjectRef::l_set_sky(lua_State *L) getServer(L)->setMoon(player, moon_params); getServer(L)->setStars(player, star_params); } + getServer(L)->setSky(player, sky_params); - lua_pushboolean(L, true); - return 1; + return 0; +} + +static void push_sky_color(lua_State *L, const SkyboxParams ¶ms) +{ + lua_newtable(L); + if (params.type == "regular") { + push_ARGB8(L, params.sky_color.day_sky); + lua_setfield(L, -2, "day_sky"); + push_ARGB8(L, params.sky_color.day_horizon); + lua_setfield(L, -2, "day_horizon"); + push_ARGB8(L, params.sky_color.dawn_sky); + lua_setfield(L, -2, "dawn_sky"); + push_ARGB8(L, params.sky_color.dawn_horizon); + lua_setfield(L, -2, "dawn_horizon"); + push_ARGB8(L, params.sky_color.night_sky); + lua_setfield(L, -2, "night_sky"); + push_ARGB8(L, params.sky_color.night_horizon); + lua_setfield(L, -2, "night_horizon"); + push_ARGB8(L, params.sky_color.indoors); + lua_setfield(L, -2, "indoors"); + } + push_ARGB8(L, params.fog_sun_tint); + lua_setfield(L, -2, "fog_sun_tint"); + push_ARGB8(L, params.fog_moon_tint); + lua_setfield(L, -2, "fog_moon_tint"); + lua_pushstring(L, params.fog_tint_type.c_str()); + lua_setfield(L, -2, "fog_tint_type"); } -// get_sky(self) +// get_sky(self, as_table) int ObjectRef::l_get_sky(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -1876,10 +1894,30 @@ int ObjectRef::l_get_sky(lua_State *L) if (player == nullptr) return 0; - SkyboxParams skybox_params = player->getSkyParams(); + const SkyboxParams &skybox_params = player->getSkyParams(); + + // handle the deprecated version + if (!readParam<bool>(L, 2, false)) { + log_deprecated(L, "Deprecated call to get_sky, please check lua_api.txt"); + push_ARGB8(L, skybox_params.bgcolor); + lua_pushlstring(L, skybox_params.type.c_str(), skybox_params.type.size()); + + lua_newtable(L); + s16 i = 1; + for (const std::string &texture : skybox_params.textures) { + lua_pushlstring(L, texture.c_str(), texture.size()); + lua_rawseti(L, -2, i++); + } + lua_pushboolean(L, skybox_params.clouds); + return 4; + } + + lua_newtable(L); push_ARGB8(L, skybox_params.bgcolor); + lua_setfield(L, -2, "base_color"); lua_pushlstring(L, skybox_params.type.c_str(), skybox_params.type.size()); + lua_setfield(L, -2, "type"); lua_newtable(L); s16 i = 1; @@ -1887,44 +1925,30 @@ int ObjectRef::l_get_sky(lua_State *L) lua_pushlstring(L, texture.c_str(), texture.size()); lua_rawseti(L, -2, i++); } + lua_setfield(L, -2, "textures"); lua_pushboolean(L, skybox_params.clouds); - return 4; + lua_setfield(L, -2, "clouds"); + + push_sky_color(L, skybox_params); + lua_setfield(L, -2, "sky_color"); + return 1; } +// DEPRECATED // get_sky_color(self) int ObjectRef::l_get_sky_color(lua_State *L) { NO_MAP_LOCK_REQUIRED; + + log_deprecated(L, "Deprecated call to get_sky_color, use get_sky instead"); + ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); if (player == nullptr) return 0; const SkyboxParams &skybox_params = player->getSkyParams(); - - lua_newtable(L); - if (skybox_params.type == "regular") { - push_ARGB8(L, skybox_params.sky_color.day_sky); - lua_setfield(L, -2, "day_sky"); - push_ARGB8(L, skybox_params.sky_color.day_horizon); - lua_setfield(L, -2, "day_horizon"); - push_ARGB8(L, skybox_params.sky_color.dawn_sky); - lua_setfield(L, -2, "dawn_sky"); - push_ARGB8(L, skybox_params.sky_color.dawn_horizon); - lua_setfield(L, -2, "dawn_horizon"); - push_ARGB8(L, skybox_params.sky_color.night_sky); - lua_setfield(L, -2, "night_sky"); - push_ARGB8(L, skybox_params.sky_color.night_horizon); - lua_setfield(L, -2, "night_horizon"); - push_ARGB8(L, skybox_params.sky_color.indoors); - lua_setfield(L, -2, "indoors"); - } - push_ARGB8(L, skybox_params.fog_sun_tint); - lua_setfield(L, -2, "fog_sun_tint"); - push_ARGB8(L, skybox_params.fog_moon_tint); - lua_setfield(L, -2, "fog_moon_tint"); - lua_pushstring(L, skybox_params.fog_tint_type.c_str()); - lua_setfield(L, -2, "fog_tint_type"); + push_sky_color(L, skybox_params); return 1; } @@ -1937,25 +1961,23 @@ int ObjectRef::l_set_sun(lua_State *L) if (player == nullptr) return 0; - luaL_checktype(L, 2, LUA_TTABLE); SunParams sun_params = player->getSunParams(); - sun_params.visible = getboolfield_default(L, 2, - "visible", sun_params.visible); - sun_params.texture = getstringfield_default(L, 2, - "texture", sun_params.texture); - sun_params.tonemap = getstringfield_default(L, 2, - "tonemap", sun_params.tonemap); - sun_params.sunrise = getstringfield_default(L, 2, - "sunrise", sun_params.sunrise); - sun_params.sunrise_visible = getboolfield_default(L, 2, - "sunrise_visible", sun_params.sunrise_visible); - sun_params.scale = getfloatfield_default(L, 2, - "scale", sun_params.scale); + // reset if empty + if (lua_isnoneornil(L, 2)) { + sun_params = SkyboxDefaults::getSunDefaults(); + } else { + luaL_checktype(L, 2, LUA_TTABLE); + sun_params.visible = getboolfield_default(L, 2, "visible", sun_params.visible); + sun_params.texture = getstringfield_default(L, 2, "texture", sun_params.texture); + sun_params.tonemap = getstringfield_default(L, 2, "tonemap", sun_params.tonemap); + sun_params.sunrise = getstringfield_default(L, 2, "sunrise", sun_params.sunrise); + sun_params.sunrise_visible = getboolfield_default(L, 2, "sunrise_visible", sun_params.sunrise_visible); + sun_params.scale = getfloatfield_default(L, 2, "scale", sun_params.scale); + } getServer(L)->setSun(player, sun_params); - lua_pushboolean(L, true); - return 1; + return 0; } //get_sun(self) @@ -1994,21 +2016,21 @@ int ObjectRef::l_set_moon(lua_State *L) if (player == nullptr) return 0; - luaL_checktype(L, 2, LUA_TTABLE); MoonParams moon_params = player->getMoonParams(); - moon_params.visible = getboolfield_default(L, 2, - "visible", moon_params.visible); - moon_params.texture = getstringfield_default(L, 2, - "texture", moon_params.texture); - moon_params.tonemap = getstringfield_default(L, 2, - "tonemap", moon_params.tonemap); - moon_params.scale = getfloatfield_default(L, 2, - "scale", moon_params.scale); + // reset if empty + if (lua_isnoneornil(L, 2)) { + moon_params = SkyboxDefaults::getMoonDefaults(); + } else { + luaL_checktype(L, 2, LUA_TTABLE); + moon_params.visible = getboolfield_default(L, 2, "visible", moon_params.visible); + moon_params.texture = getstringfield_default(L, 2, "texture", moon_params.texture); + moon_params.tonemap = getstringfield_default(L, 2, "tonemap", moon_params.tonemap); + moon_params.scale = getfloatfield_default(L, 2, "scale", moon_params.scale); + } getServer(L)->setMoon(player, moon_params); - lua_pushboolean(L, true); - return 1; + return 0; } // get_moon(self) @@ -2043,25 +2065,27 @@ int ObjectRef::l_set_stars(lua_State *L) if (player == nullptr) return 0; - luaL_checktype(L, 2, LUA_TTABLE); StarParams star_params = player->getStarParams(); - star_params.visible = getboolfield_default(L, 2, - "visible", star_params.visible); - star_params.count = getintfield_default(L, 2, - "count", star_params.count); + // reset if empty + if (lua_isnoneornil(L, 2)) { + star_params = SkyboxDefaults::getStarDefaults(); + } else { + luaL_checktype(L, 2, LUA_TTABLE); + star_params.visible = getboolfield_default(L, 2, "visible", star_params.visible); + star_params.count = getintfield_default(L, 2, "count", star_params.count); - lua_getfield(L, 2, "star_color"); - if (!lua_isnil(L, -1)) - read_color(L, -1, &star_params.starcolor); - lua_pop(L, 1); + lua_getfield(L, 2, "star_color"); + if (!lua_isnil(L, -1)) + read_color(L, -1, &star_params.starcolor); + lua_pop(L, 1); - star_params.scale = getfloatfield_default(L, 2, - "scale", star_params.scale); + star_params.scale = getfloatfield_default(L, 2, + "scale", star_params.scale); + } getServer(L)->setStars(player, star_params); - lua_pushboolean(L, true); - return 1; + return 0; } // get_stars(self) @@ -2096,35 +2120,39 @@ int ObjectRef::l_set_clouds(lua_State *L) if (player == nullptr) return 0; - luaL_checktype(L, 2, LUA_TTABLE); CloudParams cloud_params = player->getCloudParams(); - cloud_params.density = getfloatfield_default(L, 2, "density", cloud_params.density); + // reset if empty + if (lua_isnoneornil(L, 2)) { + cloud_params = SkyboxDefaults::getCloudDefaults(); + } else { + luaL_checktype(L, 2, LUA_TTABLE); + cloud_params.density = getfloatfield_default(L, 2, "density", cloud_params.density); - lua_getfield(L, 2, "color"); - if (!lua_isnil(L, -1)) - read_color(L, -1, &cloud_params.color_bright); - lua_pop(L, 1); - lua_getfield(L, 2, "ambient"); - if (!lua_isnil(L, -1)) - read_color(L, -1, &cloud_params.color_ambient); - lua_pop(L, 1); + lua_getfield(L, 2, "color"); + if (!lua_isnil(L, -1)) + read_color(L, -1, &cloud_params.color_bright); + lua_pop(L, 1); + lua_getfield(L, 2, "ambient"); + if (!lua_isnil(L, -1)) + read_color(L, -1, &cloud_params.color_ambient); + lua_pop(L, 1); - cloud_params.height = getfloatfield_default(L, 2, "height", cloud_params.height ); - cloud_params.thickness = getfloatfield_default(L, 2, "thickness", cloud_params.thickness); + cloud_params.height = getfloatfield_default(L, 2, "height", cloud_params.height); + cloud_params.thickness = getfloatfield_default(L, 2, "thickness", cloud_params.thickness); - lua_getfield(L, 2, "speed"); - if (lua_istable(L, -1)) { - v2f new_speed; - new_speed.X = getfloatfield_default(L, -1, "x", 0); - new_speed.Y = getfloatfield_default(L, -1, "z", 0); - cloud_params.speed = new_speed; + lua_getfield(L, 2, "speed"); + if (lua_istable(L, -1)) { + v2f new_speed; + new_speed.X = getfloatfield_default(L, -1, "x", 0); + new_speed.Y = getfloatfield_default(L, -1, "z", 0); + cloud_params.speed = new_speed; + } + lua_pop(L, 1); } - lua_pop(L, 1); getServer(L)->setClouds(player, cloud_params); - lua_pushboolean(L, true); - return 1; + return 0; } int ObjectRef::l_get_clouds(lua_State *L) @@ -2178,8 +2206,7 @@ int ObjectRef::l_override_day_night_ratio(lua_State *L) } getServer(L)->overrideDayNightRatio(player, do_override, ratio); - lua_pushboolean(L, true); - return 1; + return 0; } // get_day_night_ratio(self) @@ -2256,6 +2283,46 @@ int ObjectRef::l_set_minimap_modes(lua_State *L) return 0; } +// set_lighting(self, lighting) +int ObjectRef::l_set_lighting(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + RemotePlayer *player = getplayer(ref); + if (player == nullptr) + return 0; + + luaL_checktype(L, 2, LUA_TTABLE); + Lighting lighting = player->getLighting(); + lua_getfield(L, 2, "shadows"); + if (lua_istable(L, -1)) { + lighting.shadow_intensity = getfloatfield_default(L, -1, "intensity", lighting.shadow_intensity); + } + lua_pop(L, -1); + + getServer(L)->setLighting(player, lighting); + return 0; +} + +// get_lighting(self) +int ObjectRef::l_get_lighting(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + RemotePlayer *player = getplayer(ref); + if (player == nullptr) + return 0; + + const Lighting &lighting = player->getLighting(); + + lua_newtable(L); // result + lua_newtable(L); // "shadows" + lua_pushnumber(L, lighting.shadow_intensity); + lua_setfield(L, -2, "intensity"); + lua_setfield(L, -2, "shadows"); + return 1; +} + ObjectRef::ObjectRef(ServerActiveObject *object): m_object(object) {} @@ -2409,5 +2476,7 @@ luaL_Reg ObjectRef::methods[] = { luamethod(ObjectRef, get_eye_offset), luamethod(ObjectRef, send_mapblock), luamethod(ObjectRef, set_minimap_modes), + luamethod(ObjectRef, set_lighting), + luamethod(ObjectRef, get_lighting), {0,0} }; diff --git a/src/script/lua_api/l_object.h b/src/script/lua_api/l_object.h index db3a3a7cf..3e4e6681a 100644 --- a/src/script/lua_api/l_object.h +++ b/src/script/lua_api/l_object.h @@ -316,9 +316,10 @@ private: // set_sky(self, sky_parameters) static int l_set_sky(lua_State *L); - // get_sky(self) + // get_sky(self, as_table) static int l_get_sky(lua_State *L); + // DEPRECATED // get_sky_color(self) static int l_get_sky_color(lua_State* L); @@ -375,4 +376,10 @@ private: // set_minimap_modes(self, modes, wanted_mode) static int l_set_minimap_modes(lua_State *L); + + // set_lighting(self, lighting) + static int l_set_lighting(lua_State *L); + + // get_lighting(self) + static int l_get_lighting(lua_State *L); }; diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index b4672fe2a..e8105dd75 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_internal.h" #include "common/c_converter.h" #include "common/c_content.h" +#include "common/c_packer.h" #include "cpp_api/s_base.h" #include "cpp_api/s_security.h" #include "scripting_server.h" @@ -57,6 +58,14 @@ int ModApiServer::l_get_server_uptime(lua_State *L) return 1; } +// get_server_max_lag() +int ModApiServer::l_get_server_max_lag(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + GET_ENV_PTR; + lua_pushnumber(L, env->getMaxLagEstimate()); + return 1; +} // print(text) int ModApiServer::l_print(lua_State *L) @@ -282,8 +291,10 @@ int ModApiServer::l_ban_player(lua_State *L) { NO_MAP_LOCK_REQUIRED; - Server *server = getServer(L); + if (!getEnv(L)) + throw LuaError("Can't ban player before server has started up"); + Server *server = getServer(L); const char *name = luaL_checkstring(L, 1); RemotePlayer *player = server->getEnv().getPlayer(name); if (!player) { @@ -297,23 +308,30 @@ int ModApiServer::l_ban_player(lua_State *L) return 1; } -// kick_player(name, [reason]) -> success -int ModApiServer::l_kick_player(lua_State *L) +// disconnect_player(name, [reason]) -> success +int ModApiServer::l_disconnect_player(lua_State *L) { NO_MAP_LOCK_REQUIRED; + + if (!getEnv(L)) + throw LuaError("Can't kick player before server has started up"); + const char *name = luaL_checkstring(L, 1); - std::string message("Kicked"); + std::string message; if (lua_isstring(L, 2)) - message.append(": ").append(readParam<std::string>(L, 2)); + message.append(readParam<std::string>(L, 2)); else - message.append("."); + message.append("Disconnected."); + + Server *server = getServer(L); - RemotePlayer *player = dynamic_cast<ServerEnvironment *>(getEnv(L))->getPlayer(name); - if (player == NULL) { + RemotePlayer *player = server->getEnv().getPlayer(name); + if (!player) { lua_pushboolean(L, false); // No such player return 1; } - getServer(L)->DenyAccess_Legacy(player->getPeerId(), utf8_to_wide(message)); + + server->DenyAccess(player->getPeerId(), SERVER_ACCESSDENIED_CUSTOM_STRING, message); lua_pushboolean(L, true); return 1; } @@ -324,7 +342,8 @@ int ModApiServer::l_remove_player(lua_State *L) NO_MAP_LOCK_REQUIRED; std::string name = luaL_checkstring(L, 1); ServerEnvironment *s_env = dynamic_cast<ServerEnvironment *>(getEnv(L)); - assert(s_env); + if (!s_env) + throw LuaError("Can't remove player before server has started up"); RemotePlayer *player = s_env->getPlayer(name.c_str()); if (!player) @@ -375,12 +394,11 @@ int ModApiServer::l_get_modpath(lua_State *L) { NO_MAP_LOCK_REQUIRED; std::string modname = luaL_checkstring(L, 1); - const ModSpec *mod = getServer(L)->getModSpec(modname); - if (!mod) { + const ModSpec *mod = getGameDef(L)->getModSpec(modname); + if (!mod) lua_pushnil(L); - return 1; - } - lua_pushstring(L, mod->path.c_str()); + else + lua_pushstring(L, mod->path.c_str()); return 1; } @@ -392,13 +410,14 @@ int ModApiServer::l_get_modnames(lua_State *L) // Get a list of mods std::vector<std::string> modlist; - getServer(L)->getModNames(modlist); + for (auto &it : getGameDef(L)->getMods()) + modlist.emplace_back(it.name); std::sort(modlist.begin(), modlist.end()); // Package them up for Lua lua_createtable(L, modlist.size(), 0); - std::vector<std::string>::iterator iter = modlist.begin(); + auto iter = modlist.begin(); for (u16 i = 0; iter != modlist.end(); ++iter) { lua_pushstring(L, iter->c_str()); lua_rawseti(L, -2, ++i); @@ -410,8 +429,8 @@ int ModApiServer::l_get_modnames(lua_State *L) int ModApiServer::l_get_worldpath(lua_State *L) { NO_MAP_LOCK_REQUIRED; - std::string worldpath = getServer(L)->getWorldPath(); - lua_pushstring(L, worldpath.c_str()); + const Server *srv = getServer(L); + lua_pushstring(L, srv->getWorldPath().c_str()); return 1; } @@ -479,7 +498,7 @@ int ModApiServer::l_dynamic_add_media(lua_State *L) CHECK_SECURE_PATH(L, filepath.c_str(), false); - u32 token = server->getScriptIface()->allocateDynamicMediaCallback(2); + u32 token = server->getScriptIface()->allocateDynamicMediaCallback(L, 2); bool ok = server->dynamicAddMedia(filepath, token, to_player, ephemeral); if (!ok) @@ -493,7 +512,8 @@ int ModApiServer::l_dynamic_add_media(lua_State *L) int ModApiServer::l_is_singleplayer(lua_State *L) { NO_MAP_LOCK_REQUIRED; - lua_pushboolean(L, getServer(L)->isSingleplayer()); + const Server *srv = getServer(L); + lua_pushboolean(L, srv->isSingleplayer()); return 1; } @@ -508,11 +528,82 @@ int ModApiServer::l_notify_authentication_modified(lua_State *L) return 0; } +// do_async_callback(func, params, mod_origin) +int ModApiServer::l_do_async_callback(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ServerScripting *script = getScriptApi<ServerScripting>(L); + + luaL_checktype(L, 1, LUA_TFUNCTION); + luaL_checktype(L, 2, LUA_TTABLE); + luaL_checktype(L, 3, LUA_TSTRING); + + call_string_dump(L, 1); + size_t func_length; + const char *serialized_func_raw = lua_tolstring(L, -1, &func_length); + + PackedValue *param = script_pack(L, 2); + + std::string mod_origin = readParam<std::string>(L, 3); + + u32 jobId = script->queueAsync( + std::string(serialized_func_raw, func_length), + param, mod_origin); + + lua_settop(L, 0); + lua_pushinteger(L, jobId); + return 1; +} + +// register_async_dofile(path) +int ModApiServer::l_register_async_dofile(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + std::string path = readParam<std::string>(L, 1); + CHECK_SECURE_PATH(L, path.c_str(), false); + + // Find currently running mod name (only at init time) + lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); + if (!lua_isstring(L, -1)) + return 0; + std::string modname = readParam<std::string>(L, -1); + + getServer(L)->m_async_init_files.emplace_back(modname, path); + lua_pushboolean(L, true); + return 1; +} + +// serialize_roundtrip(value) +// Meant for unit testing the packer from Lua +int ModApiServer::l_serialize_roundtrip(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + int top = lua_gettop(L); + auto *pv = script_pack(L, 1); + if (top != lua_gettop(L)) + throw LuaError("stack values leaked"); + +#ifndef NDEBUG + script_dump_packed(pv); +#endif + + top = lua_gettop(L); + script_unpack(L, pv); + delete pv; + if (top + 1 != lua_gettop(L)) + throw LuaError("stack values leaked"); + + return 1; +} + void ModApiServer::Initialize(lua_State *L, int top) { API_FCT(request_shutdown); API_FCT(get_server_status); API_FCT(get_server_uptime); + API_FCT(get_server_max_lag); API_FCT(get_worldpath); API_FCT(is_singleplayer); @@ -536,7 +627,22 @@ void ModApiServer::Initialize(lua_State *L, int top) API_FCT(get_ban_list); API_FCT(get_ban_description); API_FCT(ban_player); - API_FCT(kick_player); + API_FCT(disconnect_player); + API_FCT(remove_player); API_FCT(unban_player_or_ip); API_FCT(notify_authentication_modified); + + API_FCT(do_async_callback); + API_FCT(register_async_dofile); + API_FCT(serialize_roundtrip); +} + +void ModApiServer::InitializeAsync(lua_State *L, int top) +{ + API_FCT(get_worldpath); + API_FCT(is_singleplayer); + + API_FCT(get_current_modname); + API_FCT(get_modpath); + API_FCT(get_modnames); } diff --git a/src/script/lua_api/l_server.h b/src/script/lua_api/l_server.h index c688e494b..a4f38c34e 100644 --- a/src/script/lua_api/l_server.h +++ b/src/script/lua_api/l_server.h @@ -33,6 +33,9 @@ private: // get_server_uptime() static int l_get_server_uptime(lua_State *L); + // get_server_max_lag() + static int l_get_server_max_lag(lua_State *L); + // get_worldpath() static int l_get_worldpath(lua_State *L); @@ -94,8 +97,8 @@ private: // unban_player_or_ip() static int l_unban_player_or_ip(lua_State *L); - // kick_player(name, [message]) -> success - static int l_kick_player(lua_State *L); + // disconnect_player(name, [reason]) -> success + static int l_disconnect_player(lua_State *L); // remove_player(name) static int l_remove_player(lua_State *L); @@ -103,6 +106,16 @@ private: // notify_authentication_modified(name) static int l_notify_authentication_modified(lua_State *L); + // do_async_callback(func, params, mod_origin) + static int l_do_async_callback(lua_State *L); + + // register_async_dofile(path) + static int l_register_async_dofile(lua_State *L); + + // serialize_roundtrip(obj) + static int l_serialize_roundtrip(lua_State *L); + public: static void Initialize(lua_State *L, int top); + static void InitializeAsync(lua_State *L, int top); }; diff --git a/src/script/lua_api/l_storage.cpp b/src/script/lua_api/l_storage.cpp index 978b315d5..b8f4347a8 100644 --- a/src/script/lua_api/l_storage.cpp +++ b/src/script/lua_api/l_storage.cpp @@ -32,19 +32,23 @@ int ModApiStorage::l_get_mod_storage(lua_State *L) std::string mod_name = readParam<std::string>(L, -1); - ModMetadata *store = new ModMetadata(mod_name); + ModMetadata *store = nullptr; + if (IGameDef *gamedef = getGameDef(L)) { - store->load(gamedef->getModStoragePath()); - gamedef->registerModStorage(store); + store = new ModMetadata(mod_name, gamedef->getModStorageDatabase()); + if (gamedef->registerModStorage(store)) { + StorageRef::create(L, store); + int object = lua_gettop(L); + lua_pushvalue(L, object); + return 1; + } } else { - delete store; assert(false); // this should not happen } - StorageRef::create(L, store); - int object = lua_gettop(L); + delete store; - lua_pushvalue(L, object); + lua_pushnil(L); return 1; } diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index d575eb603..fa749c2e5 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -41,7 +41,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/hex.h" #include "util/sha1.h" #include "util/png.h" -#include <algorithm> #include <cstdio> // log([level,] text) @@ -160,28 +159,33 @@ int ModApiUtil::l_write_json(lua_State *L) return 1; } -// get_dig_params(groups, tool_capabilities) +// get_dig_params(groups, tool_capabilities[, wear]) int ModApiUtil::l_get_dig_params(lua_State *L) { NO_MAP_LOCK_REQUIRED; ItemGroupList groups; read_groups(L, 1, groups); ToolCapabilities tp = read_tool_capabilities(L, 2); - push_dig_params(L, getDigParams(groups, &tp)); + if (lua_isnoneornil(L, 3)) { + push_dig_params(L, getDigParams(groups, &tp)); + } else { + u16 wear = readParam<int>(L, 3); + push_dig_params(L, getDigParams(groups, &tp, wear)); + } return 1; } -// get_hit_params(groups, tool_capabilities[, time_from_last_punch]) +// get_hit_params(groups, tool_capabilities[, time_from_last_punch, [, wear]]) int ModApiUtil::l_get_hit_params(lua_State *L) { NO_MAP_LOCK_REQUIRED; std::unordered_map<std::string, int> groups; read_groups(L, 1, groups); ToolCapabilities tp = read_tool_capabilities(L, 2); - if(lua_isnoneornil(L, 3)) - push_hit_params(L, getHitParams(groups, &tp)); - else - push_hit_params(L, getHitParams(groups, &tp, readParam<float>(L, 3))); + float time_from_last_punch = readParam<float>(L, 3, 1000000); + int wear = readParam<int>(L, 4, 0); + push_hit_params(L, getHitParams(groups, &tp, + time_from_last_punch, wear)); return 1; } @@ -344,6 +348,49 @@ int ModApiUtil::l_mkdir(lua_State *L) return 1; } +// rmdir(path, recursive) +int ModApiUtil::l_rmdir(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + const char *path = luaL_checkstring(L, 1); + CHECK_SECURE_PATH(L, path, true); + + bool recursive = readParam<bool>(L, 2, false); + + if (recursive) + lua_pushboolean(L, fs::RecursiveDelete(path)); + else + lua_pushboolean(L, fs::DeleteSingleFileOrEmptyDirectory(path)); + + return 1; +} + +// cpdir(source, destination) +int ModApiUtil::l_cpdir(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + const char *source = luaL_checkstring(L, 1); + const char *destination = luaL_checkstring(L, 2); + CHECK_SECURE_PATH(L, source, false); + CHECK_SECURE_PATH(L, destination, true); + + lua_pushboolean(L, fs::CopyDir(source, destination)); + return 1; +} + +// mpdir(source, destination) +int ModApiUtil::l_mvdir(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + const char *source = luaL_checkstring(L, 1); + const char *destination = luaL_checkstring(L, 2); + CHECK_SECURE_PATH(L, source, true); + CHECK_SECURE_PATH(L, destination, true); + + lua_pushboolean(L, fs::MoveDir(source, destination)); + return 1; +} + // get_dir_list(path, is_dir) int ModApiUtil::l_get_dir_list(lua_State *L) { @@ -396,36 +443,7 @@ int ModApiUtil::l_request_insecure_environment(lua_State *L) return 1; } - // We have to make sure that this function is being called directly by - // a mod, otherwise a malicious mod could override this function and - // steal its return value. - lua_Debug info; - // Make sure there's only one item below this function on the stack... - if (lua_getstack(L, 2, &info)) { - return 0; - } - FATAL_ERROR_IF(!lua_getstack(L, 1, &info), "lua_getstack() failed"); - FATAL_ERROR_IF(!lua_getinfo(L, "S", &info), "lua_getinfo() failed"); - // ...and that that item is the main file scope. - if (strcmp(info.what, "main") != 0) { - return 0; - } - - // Get mod name - lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); - if (!lua_isstring(L, -1)) { - return 0; - } - - // Check secure.trusted_mods - std::string mod_name = readParam<std::string>(L, -1); - std::string trusted_mods = g_settings->get("secure.trusted_mods"); - trusted_mods.erase(std::remove_if(trusted_mods.begin(), - trusted_mods.end(), static_cast<int(*)(int)>(&std::isspace)), - trusted_mods.end()); - std::vector<std::string> mod_list = str_split(trusted_mods, ','); - if (std::find(mod_list.begin(), mod_list.end(), mod_name) == - mod_list.end()) { + if (!ScriptApiSecurity::checkWhitelisted(L, "secure.trusted_mods")) { return 0; } @@ -583,6 +601,9 @@ void ModApiUtil::Initialize(lua_State *L, int top) API_FCT(decompress); API_FCT(mkdir); + API_FCT(rmdir); + API_FCT(cpdir); + API_FCT(mvdir); API_FCT(get_dir_list); API_FCT(safe_file_write); @@ -651,7 +672,13 @@ void ModApiUtil::InitializeAsync(lua_State *L, int top) API_FCT(decompress); API_FCT(mkdir); + API_FCT(rmdir); + API_FCT(cpdir); + API_FCT(mvdir); API_FCT(get_dir_list); + API_FCT(safe_file_write); + + API_FCT(request_insecure_environment); API_FCT(encode_base64); API_FCT(decode_base64); @@ -661,6 +688,8 @@ void ModApiUtil::InitializeAsync(lua_State *L, int top) API_FCT(colorspec_to_colorstring); API_FCT(colorspec_to_bytes); + API_FCT(encode_png); + API_FCT(get_last_run_mod); API_FCT(set_last_run_mod); diff --git a/src/script/lua_api/l_util.h b/src/script/lua_api/l_util.h index cc91e8d39..cc5563577 100644 --- a/src/script/lua_api/l_util.h +++ b/src/script/lua_api/l_util.h @@ -50,10 +50,10 @@ private: // write_json(data[, styled]) static int l_write_json(lua_State *L); - // get_dig_params(groups, tool_capabilities[, time_from_last_punch]) + // get_dig_params(groups, tool_capabilities[, wear]) static int l_get_dig_params(lua_State *L); - // get_hit_params(groups, tool_capabilities[, time_from_last_punch]) + // get_hit_params(groups, tool_capabilities[, time_from_last_punch[, wear]]) static int l_get_hit_params(lua_State *L); // check_password_entry(name, entry, password) @@ -80,6 +80,15 @@ private: // mkdir(path) static int l_mkdir(lua_State *L); + // rmdir(path, recursive) + static int l_rmdir(lua_State *L); + + // cpdir(source, destination, remove_source) + static int l_cpdir(lua_State *L); + + // mvdir(source, destination) + static int l_mvdir(lua_State *L); + // get_dir_list(path, is_dir) static int l_get_dir_list(lua_State *L); @@ -120,6 +129,4 @@ public: static void Initialize(lua_State *L, int top); static void InitializeAsync(lua_State *L, int top); static void InitializeClient(lua_State *L, int top); - - static void InitializeAsync(AsyncEngine &engine); }; diff --git a/src/script/lua_api/l_vmanip.cpp b/src/script/lua_api/l_vmanip.cpp index e040e545b..6187a47db 100644 --- a/src/script/lua_api/l_vmanip.cpp +++ b/src/script/lua_api/l_vmanip.cpp @@ -17,11 +17,12 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ - +#include <map> #include "lua_api/l_vmanip.h" #include "lua_api/l_internal.h" #include "common/c_content.h" #include "common/c_converter.h" +#include "common/c_packer.h" #include "emerge.h" #include "environment.h" #include "map.h" @@ -45,6 +46,8 @@ int LuaVoxelManip::l_read_from_map(lua_State *L) LuaVoxelManip *o = checkobject(L, 1); MMVManip *vm = o->vm; + if (vm->isOrphan()) + return 0; v3s16 bp1 = getNodeBlockPos(check_v3s16(L, 2)); v3s16 bp2 = getNodeBlockPos(check_v3s16(L, 3)); @@ -112,23 +115,23 @@ int LuaVoxelManip::l_write_to_map(lua_State *L) LuaVoxelManip *o = checkobject(L, 1); bool update_light = !lua_isboolean(L, 2) || readParam<bool>(L, 2); + GET_ENV_PTR; ServerMap *map = &(env->getServerMap()); + + std::map<v3s16, MapBlock*> modified_blocks; if (o->is_mapgen_vm || !update_light) { - o->vm->blitBackAll(&(o->modified_blocks)); + o->vm->blitBackAll(&modified_blocks); } else { - voxalgo::blit_back_with_light(map, o->vm, - &(o->modified_blocks)); + voxalgo::blit_back_with_light(map, o->vm, &modified_blocks); } MapEditEvent event; event.type = MEET_OTHER; - for (const auto &modified_block : o->modified_blocks) - event.modified_blocks.insert(modified_block.first); - + for (const auto &it : modified_blocks) + event.modified_blocks.insert(it.first); map->dispatchEvent(event); - o->modified_blocks.clear(); return 0; } @@ -166,7 +169,7 @@ int LuaVoxelManip::l_update_liquids(lua_State *L) LuaVoxelManip *o = checkobject(L, 1); - Map *map = &(env->getMap()); + ServerMap *map = &(env->getServerMap()); const NodeDefManager *ndef = getServer(L)->getNodeDefManager(); MMVManip *vm = o->vm; @@ -429,6 +432,34 @@ LuaVoxelManip *LuaVoxelManip::checkobject(lua_State *L, int narg) return *(LuaVoxelManip **)ud; // unbox pointer } +void *LuaVoxelManip::packIn(lua_State *L, int idx) +{ + LuaVoxelManip *o = checkobject(L, idx); + + if (o->is_mapgen_vm) + throw LuaError("nope"); + return o->vm->clone(); +} + +void LuaVoxelManip::packOut(lua_State *L, void *ptr) +{ + MMVManip *vm = reinterpret_cast<MMVManip*>(ptr); + if (!L) { + delete vm; + return; + } + + // Associate vmanip with map if the Lua env has one + Environment *env = getEnv(L); + if (env) + vm->reparent(&(env->getMap())); + + LuaVoxelManip *o = new LuaVoxelManip(vm, false); + *(void **)(lua_newuserdata(L, sizeof(void *))) = o; + luaL_getmetatable(L, className); + lua_setmetatable(L, -2); +} + void LuaVoxelManip::Register(lua_State *L) { lua_newtable(L); @@ -455,6 +486,8 @@ void LuaVoxelManip::Register(lua_State *L) // Can be created from Lua (VoxelManip()) lua_register(L, className, create_object); + + script_register_packer(L, className, packIn, packOut); } const char LuaVoxelManip::className[] = "VoxelManip"; diff --git a/src/script/lua_api/l_vmanip.h b/src/script/lua_api/l_vmanip.h index 15ab9eef8..005133335 100644 --- a/src/script/lua_api/l_vmanip.h +++ b/src/script/lua_api/l_vmanip.h @@ -19,7 +19,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once -#include <map> #include "irr_v3d.h" #include "lua_api/l_base.h" @@ -33,7 +32,6 @@ class MMVManip; class LuaVoxelManip : public ModApiBase { private: - std::map<v3s16, MapBlock *> modified_blocks; bool is_mapgen_vm = false; static const char className[]; @@ -77,5 +75,8 @@ public: static LuaVoxelManip *checkobject(lua_State *L, int narg); + static void *packIn(lua_State *L, int idx); + static void packOut(lua_State *L, void *ptr); + static void Register(lua_State *L); }; diff --git a/src/script/scripting_server.cpp b/src/script/scripting_server.cpp index 85411ded4..b462141b0 100644 --- a/src/script/scripting_server.cpp +++ b/src/script/scripting_server.cpp @@ -47,11 +47,12 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_storage.h" extern "C" { -#include "lualib.h" +#include <lualib.h> } ServerScripting::ServerScripting(Server* server): - ScriptApiBase(ScriptingType::Server) + ScriptApiBase(ScriptingType::Server), + asyncEngine(server) { setGameDef(server); @@ -88,6 +89,48 @@ ServerScripting::ServerScripting(Server* server): infostream << "SCRIPTAPI: Initialized game modules" << std::endl; } +void ServerScripting::initAsync() +{ + // Save globals to transfer + { + lua_State *L = getStack(); + lua_getglobal(L, "core"); + luaL_checktype(L, -1, LUA_TTABLE); + lua_getfield(L, -1, "get_globals_to_transfer"); + lua_call(L, 0, 1); + auto *data = script_pack(L, -1); + assert(!data->contains_userdata); + getServer()->m_async_globals_data.reset(data); + lua_pushnil(L); + lua_setfield(L, -3, "get_globals_to_transfer"); // unset function too + lua_pop(L, 2); // pop 'core', return value + } + + infostream << "SCRIPTAPI: Initializing async engine" << std::endl; + asyncEngine.registerStateInitializer(InitializeAsync); + asyncEngine.registerStateInitializer(ModApiUtil::InitializeAsync); + asyncEngine.registerStateInitializer(ModApiCraft::InitializeAsync); + asyncEngine.registerStateInitializer(ModApiItemMod::InitializeAsync); + asyncEngine.registerStateInitializer(ModApiServer::InitializeAsync); + // not added: ModApiMapgen is a minefield for thread safety + // not added: ModApiHttp async api can't really work together with our jobs + // not added: ModApiStorage is probably not thread safe(?) + + asyncEngine.initialize(0); +} + +void ServerScripting::stepAsync() +{ + asyncEngine.step(getStack()); +} + +u32 ServerScripting::queueAsync(std::string &&serialized_func, + PackedValue *param, const std::string &mod_origin) +{ + return asyncEngine.queueAsyncJob(std::move(serialized_func), + param, mod_origin); +} + void ServerScripting::InitializeModApi(lua_State *L, int top) { // Register reference classes (userdata) @@ -125,3 +168,24 @@ void ServerScripting::InitializeModApi(lua_State *L, int top) ModApiStorage::Initialize(L, top); ModApiChannels::Initialize(L, top); } + +void ServerScripting::InitializeAsync(lua_State *L, int top) +{ + // classes + LuaItemStack::Register(L); + LuaPerlinNoise::Register(L); + LuaPerlinNoiseMap::Register(L); + LuaPseudoRandom::Register(L); + LuaPcgRandom::Register(L); + LuaSecureRandom::Register(L); + LuaVoxelManip::Register(L); + LuaSettings::Register(L); + + // globals data + lua_getglobal(L, "core"); + luaL_checktype(L, -1, LUA_TTABLE); + auto *data = ModApiBase::getServer(L)->m_async_globals_data.get(); + script_unpack(L, data); + lua_setfield(L, -2, "transferred_globals"); + lua_pop(L, 1); // pop 'core' +} diff --git a/src/script/scripting_server.h b/src/script/scripting_server.h index bf06ab197..9803397c5 100644 --- a/src/script/scripting_server.h +++ b/src/script/scripting_server.h @@ -27,6 +27,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "cpp_api/s_player.h" #include "cpp_api/s_server.h" #include "cpp_api/s_security.h" +#include "cpp_api/s_async.h" + +struct PackedValue; /*****************************************************************************/ /* Scripting <-> Server Game Interface */ @@ -48,6 +51,20 @@ public: // use ScriptApiBase::loadMod() to load mods + // Initialize async engine, call this AFTER loading all mods + void initAsync(); + + // Global step handler to collect async results + void stepAsync(); + + // Pass job to async threads + u32 queueAsync(std::string &&serialized_func, + PackedValue *param, const std::string &mod_origin); + private: void InitializeModApi(lua_State *L, int top); + + static void InitializeAsync(lua_State *L, int top); + + AsyncEngine asyncEngine; }; diff --git a/src/serialization.cpp b/src/serialization.cpp index b6ce3b37f..11164a0ed 100644 --- a/src/serialization.cpp +++ b/src/serialization.cpp @@ -108,7 +108,6 @@ void decompressZlib(std::istream &is, std::ostream &os, size_t limit) char output_buffer[bufsize]; int status = 0; int ret; - int bytes_read = 0; int bytes_written = 0; int input_buffer_len = 0; @@ -122,8 +121,6 @@ void decompressZlib(std::istream &is, std::ostream &os, size_t limit) z.avail_in = 0; - //dstream<<"initial fail="<<is.fail()<<" bad="<<is.bad()<<std::endl; - for(;;) { int output_size = bufsize; @@ -147,19 +144,13 @@ void decompressZlib(std::istream &is, std::ostream &os, size_t limit) is.read(input_buffer, bufsize); input_buffer_len = is.gcount(); z.avail_in = input_buffer_len; - //dstream<<"read fail="<<is.fail()<<" bad="<<is.bad()<<std::endl; } if(z.avail_in == 0) { - //dstream<<"z.avail_in == 0"<<std::endl; break; } - //dstream<<"1 z.avail_in="<<z.avail_in<<std::endl; status = inflate(&z, Z_NO_FLUSH); - //dstream<<"2 z.avail_in="<<z.avail_in<<std::endl; - bytes_read += is.gcount() - z.avail_in; - //dstream<<"bytes_read="<<bytes_read<<std::endl; if(status == Z_NEED_DICT || status == Z_DATA_ERROR || status == Z_MEM_ERROR) @@ -168,16 +159,11 @@ void decompressZlib(std::istream &is, std::ostream &os, size_t limit) throw SerializationError("decompressZlib: inflate failed"); } int count = output_size - z.avail_out; - //dstream<<"count="<<count<<std::endl; if(count) os.write(output_buffer, count); bytes_written += count; if(status == Z_STREAM_END) { - //dstream<<"Z_STREAM_END"<<std::endl; - - //dstream<<"z.avail_in="<<z.avail_in<<std::endl; - //dstream<<"fail="<<is.fail()<<" bad="<<is.bad()<<std::endl; // Unget all the data that inflate didn't take is.clear(); // Just in case EOF is set for(u32 i=0; i < z.avail_in; i++) @@ -211,9 +197,10 @@ struct ZSTD_Deleter { 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 + // it will be destroyed when the thread ends thread_local std::unique_ptr<ZSTD_CStream, ZSTD_Deleter> stream(ZSTD_createCStream()); + ZSTD_initCStream(stream.get(), level); const size_t bufsize = 16384; @@ -257,7 +244,7 @@ void compressZstd(const std::string &data, std::ostream &os, int level) void decompressZstd(std::istream &is, std::ostream &os) { // reusing the context is recommended for performance - // it will destroyed when the thread ends + // it will be destroyed when the thread ends thread_local std::unique_ptr<ZSTD_DStream, ZSTD_Deleter> stream(ZSTD_createDStream()); ZSTD_initDStream(stream.get()); diff --git a/src/server.cpp b/src/server.cpp index 7fb9a78e9..d93f300d2 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -66,6 +66,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "server/player_sao.h" #include "server/serverinventorymgr.h" #include "translation.h" +#include "database/database-sqlite3.h" +#include "database/database-files.h" +#include "database/database-dummy.h" +#include "gameparams.h" class ClientNotFoundException : public BaseException { @@ -250,7 +254,7 @@ Server::Server( #if USE_PROMETHEUS m_metrics_backend = std::unique_ptr<MetricsBackend>(createPrometheusMetricsBackend()); #else - m_metrics_backend = std::unique_ptr<MetricsBackend>(new MetricsBackend()); + m_metrics_backend = std::make_unique<MetricsBackend>(); #endif m_uptime_counter = m_metrics_backend->addCounter("minetest_core_server_uptime", "Server uptime (in seconds)"); @@ -264,9 +268,15 @@ Server::Server( "minetest_core_latency", "Latency value (in seconds)"); - m_aom_buffer_counter = m_metrics_backend->addCounter( - "minetest_core_aom_generated_count", - "Number of active object messages generated"); + + const std::string aom_types[] = {"reliable", "unreliable"}; + for (u32 i = 0; i < ARRLEN(aom_types); i++) { + std::string help_str("Number of active object messages generated ("); + help_str.append(aom_types[i]).append(")"); + m_aom_buffer_counter[i] = m_metrics_backend->addCounter( + "minetest_core_aom_generated_count", help_str, + {{"type", aom_types[i]}}); + } m_packet_recv_counter = m_metrics_backend->addCounter( "minetest_core_server_packet_recv", @@ -276,6 +286,10 @@ Server::Server( "minetest_core_server_packet_recv_processed", "Valid received packets processed"); + m_map_edit_event_counter = m_metrics_backend->addCounter( + "minetest_core_map_edit_events", + "Number of map edit events"); + m_lag_gauge->set(g_settings->getFloat("dedicated_server_step")); } @@ -344,10 +358,15 @@ Server::~Server() delete m_thread; } + // Write any changes before deletion. + if (m_mod_storage_database) + m_mod_storage_database->endSave(); + // Delete things in the reverse order of creation delete m_emerge; delete m_env; delete m_rollback; + delete m_mod_storage_database; delete m_banmanager; delete m_itemdef; delete m_nodedef; @@ -387,13 +406,17 @@ void Server::init() } // Create emerge manager - m_emerge = new EmergeManager(this); + m_emerge = new EmergeManager(this, m_metrics_backend.get()); // Create ban manager std::string ban_path = m_path_world + DIR_DELIM "ipban.txt"; m_banmanager = new BanManager(ban_path); - m_modmgr = std::unique_ptr<ServerModManager>(new ServerModManager(m_path_world)); + // Create mod storage database and begin a save for later + m_mod_storage_database = openModStorageDatabase(m_path_world); + m_mod_storage_database->beginSave(); + + m_modmgr = std::make_unique<ServerModManager>(m_path_world); std::vector<ModSpec> unsatisfied_mods = m_modmgr->getUnsatisfiedMods(); // complain about mods with unsatisfied dependencies if (!m_modmgr->isConsistent()) { @@ -413,7 +436,7 @@ void Server::init() m_script = new ServerScripting(this); // Must be created before mod loading because we have some inventory creation - m_inventory_mgr = std::unique_ptr<ServerInventoryManager>(new ServerInventoryManager()); + m_inventory_mgr = std::make_unique<ServerInventoryManager>(); m_script->loadMod(getBuiltinLuaPath() + DIR_DELIM "init.lua", BUILTIN_MOD_NAME); @@ -448,7 +471,8 @@ void Server::init() // Initialize Environment m_startup_server_map = nullptr; // Ownership moved to ServerEnvironment - m_env = new ServerEnvironment(servermap, m_script, this, m_path_world); + m_env = new ServerEnvironment(servermap, m_script, this, + m_path_world, m_metrics_backend.get()); m_inventory_mgr->setEnv(m_env); m_clients.setEnv(m_env); @@ -467,6 +491,9 @@ void Server::init() // Give environment reference to scripting api m_script->initializeEnvironment(m_env); + // Do this after regular script init is done + m_script->initAsync(); + // Register us to receive map edit events servermap->addEventReceiver(this); @@ -499,16 +526,17 @@ void Server::start() // ASCII art for the win! std::cerr - << " .__ __ __ " << std::endl - << " _____ |__| ____ _____/ |_ ____ _______/ |_ " << std::endl - << " / \\| |/ \\_/ __ \\ __\\/ __ \\ / ___/\\ __\\" << std::endl - << "| Y Y \\ | | \\ ___/| | \\ ___/ \\___ \\ | | " << std::endl - << "|__|_| /__|___| /\\___ >__| \\___ >____ > |__| " << std::endl - << " \\/ \\/ \\/ \\/ \\/ " << std::endl; + << " __. __. __. " << std::endl + << " _____ |__| ____ _____ / |_ _____ _____ / |_ " << std::endl + << " / \\| |/ \\ / __ \\ _\\/ __ \\/ __> _\\" << std::endl + << "| Y Y \\ | | \\ ___/| | | ___/\\___ \\| | " << std::endl + << "|__|_| / |___| /\\______> | \\______>_____/| | " << std::endl + << " \\/ \\/ \\/ \\/ \\/ " << std::endl; actionstream << "World at [" << m_path_world << "]" << std::endl; actionstream << "Server for gameid=\"" << m_gamespec.id - << "\" listening on " << m_bind_addr.serializeString() << ":" - << m_bind_addr.getPort() << "." << std::endl; + << "\" listening on "; + m_bind_addr.print(actionstream); + actionstream << "." << std::endl; } void Server::stop() @@ -517,9 +545,7 @@ void Server::stop() // Stop threads (set run=false first so both start stopping) m_thread->stop(); - //m_emergethread.setRun(false); m_thread->wait(); - //m_emergethread.stop(); infostream<<"Server: Threads stopped"<<std::endl; } @@ -607,6 +633,7 @@ void Server::AsyncRunStep(bool initial_step) max_lag = dtime; } m_env->reportMaxLagEstimate(max_lag); + // Step environment m_env->step(dtime); } @@ -653,7 +680,7 @@ void Server::AsyncRunStep(bool initial_step) ScopeProfiler sp(g_profiler, "Server: liquid transform"); std::map<v3s16, MapBlock*> modified_blocks; - m_env->getMap().transformLiquids(modified_blocks, m_env); + m_env->getServerMap().transformLiquids(modified_blocks, m_env); /* Set the modified blocks unsent for all the clients @@ -711,43 +738,36 @@ void Server::AsyncRunStep(bool initial_step) //infostream<<"Server: Checking added and deleted active objects"<<std::endl; MutexAutoLock envlock(m_env_mutex); - m_clients.lock(); - const RemoteClientMap &clients = m_clients.getClientList(); - ScopeProfiler sp(g_profiler, "Server: update objects within range"); + { + ClientInterface::AutoLock clientlock(m_clients); + const RemoteClientMap &clients = m_clients.getClientList(); + ScopeProfiler sp(g_profiler, "Server: update objects within range"); - m_player_gauge->set(clients.size()); - for (const auto &client_it : clients) { - RemoteClient *client = client_it.second; + m_player_gauge->set(clients.size()); + for (const auto &client_it : clients) { + RemoteClient *client = client_it.second; - if (client->getState() < CS_DefinitionsSent) - continue; + if (client->getState() < CS_DefinitionsSent) + continue; - // This can happen if the client times out somehow - if (!m_env->getPlayer(client->peer_id)) - continue; + // This can happen if the client times out somehow + if (!m_env->getPlayer(client->peer_id)) + continue; - PlayerSAO *playersao = getPlayerSAO(client->peer_id); - if (!playersao) - continue; + PlayerSAO *playersao = getPlayerSAO(client->peer_id); + if (!playersao) + continue; - SendActiveObjectRemoveAdd(client, playersao); + SendActiveObjectRemoveAdd(client, playersao); + } } - m_clients.unlock(); - // Save mod storages if modified + // Write changes to the mod storage m_mod_storage_save_timer -= dtime; if (m_mod_storage_save_timer <= 0.0f) { m_mod_storage_save_timer = g_settings->getFloat("server_map_save_interval"); - int n = 0; - for (std::unordered_map<std::string, ModMetadata *>::const_iterator - it = m_mod_storages.begin(); it != m_mod_storages.end(); ++it) { - if (it->second->isModified()) { - it->second->save(getModStoragePath()); - n++; - } - } - if (n > 0) - infostream << "Saved " << n << " modified mod storages." << std::endl; + m_mod_storage_database->endSave(); + m_mod_storage_database->beginSave(); } } @@ -764,10 +784,14 @@ void Server::AsyncRunStep(bool initial_step) // Get active object messages from environment ActiveObjectMessage aom(0); - u32 aom_count = 0; + u32 count_reliable = 0, count_unreliable = 0; for(;;) { if (!m_env->getActiveObjectMessage(&aom)) break; + if (aom.reliable) + count_reliable++; + else + count_unreliable++; std::vector<ActiveObjectMessage>* message_list = nullptr; auto n = buffered_messages.find(aom.id); @@ -778,68 +802,69 @@ void Server::AsyncRunStep(bool initial_step) message_list = n->second; } message_list->push_back(std::move(aom)); - aom_count++; - } - - m_aom_buffer_counter->increment(aom_count); - - m_clients.lock(); - const RemoteClientMap &clients = m_clients.getClientList(); - // Route data to every client - std::string reliable_data, unreliable_data; - for (const auto &client_it : clients) { - reliable_data.clear(); - unreliable_data.clear(); - RemoteClient *client = client_it.second; - PlayerSAO *player = getPlayerSAO(client->peer_id); - // Go through all objects in message buffer - for (const auto &buffered_message : buffered_messages) { - // If object does not exist or is not known by client, skip it - u16 id = buffered_message.first; - ServerActiveObject *sao = m_env->getActiveObject(id); - if (!sao || client->m_known_objects.find(id) == client->m_known_objects.end()) - continue; + } - // Get message list of object - std::vector<ActiveObjectMessage>* list = buffered_message.second; - // Go through every message - for (const ActiveObjectMessage &aom : *list) { - // Send position updates to players who do not see the attachment - if (aom.datastring[0] == AO_CMD_UPDATE_POSITION) { - if (sao->getId() == player->getId()) - continue; - - // Do not send position updates for attached players - // as long the parent is known to the client - ServerActiveObject *parent = sao->getParent(); - if (parent && client->m_known_objects.find(parent->getId()) != - client->m_known_objects.end()) - continue; - } + m_aom_buffer_counter[0]->increment(count_reliable); + m_aom_buffer_counter[1]->increment(count_unreliable); - // Add full new data to appropriate buffer - std::string &buffer = aom.reliable ? reliable_data : unreliable_data; - char idbuf[2]; - writeU16((u8*) idbuf, aom.id); - // u16 id - // std::string data - buffer.append(idbuf, sizeof(idbuf)); - buffer.append(serializeString16(aom.datastring)); + { + ClientInterface::AutoLock clientlock(m_clients); + const RemoteClientMap &clients = m_clients.getClientList(); + // Route data to every client + std::string reliable_data, unreliable_data; + for (const auto &client_it : clients) { + reliable_data.clear(); + unreliable_data.clear(); + RemoteClient *client = client_it.second; + PlayerSAO *player = getPlayerSAO(client->peer_id); + // Go through all objects in message buffer + for (const auto &buffered_message : buffered_messages) { + // If object does not exist or is not known by client, skip it + u16 id = buffered_message.first; + ServerActiveObject *sao = m_env->getActiveObject(id); + if (!sao || client->m_known_objects.find(id) == client->m_known_objects.end()) + continue; + + // Get message list of object + std::vector<ActiveObjectMessage>* list = buffered_message.second; + // Go through every message + for (const ActiveObjectMessage &aom : *list) { + // Send position updates to players who do not see the attachment + if (aom.datastring[0] == AO_CMD_UPDATE_POSITION) { + if (sao->getId() == player->getId()) + continue; + + // Do not send position updates for attached players + // as long the parent is known to the client + ServerActiveObject *parent = sao->getParent(); + if (parent && client->m_known_objects.find(parent->getId()) != + client->m_known_objects.end()) + continue; + } + + // Add full new data to appropriate buffer + std::string &buffer = aom.reliable ? reliable_data : unreliable_data; + char idbuf[2]; + writeU16((u8*) idbuf, aom.id); + // u16 id + // std::string data + buffer.append(idbuf, sizeof(idbuf)); + buffer.append(serializeString16(aom.datastring)); + } + } + /* + reliable_data and unreliable_data are now ready. + Send them. + */ + if (!reliable_data.empty()) { + SendActiveObjectMessages(client->peer_id, reliable_data); } - } - /* - reliable_data and unreliable_data are now ready. - Send them. - */ - if (!reliable_data.empty()) { - SendActiveObjectMessages(client->peer_id, reliable_data); - } - if (!unreliable_data.empty()) { - SendActiveObjectMessages(client->peer_id, unreliable_data, false); + if (!unreliable_data.empty()) { + SendActiveObjectMessages(client->peer_id, unreliable_data, false); + } } } - m_clients.unlock(); // Clear buffered_messages for (auto &buffered_message : buffered_messages) { @@ -854,15 +879,13 @@ void Server::AsyncRunStep(bool initial_step) // We will be accessing the environment MutexAutoLock lock(m_env_mutex); - // Don't send too many at a time - //u32 count = 0; - - // Single change sending is disabled if queue size is not small + // Single change sending is disabled if queue size is big bool disable_single_change_sending = false; if(m_unsent_map_edit_queue.size() >= 4) disable_single_change_sending = true; - int event_count = m_unsent_map_edit_queue.size(); + const auto event_count = m_unsent_map_edit_queue.size(); + m_map_edit_event_counter->increment(event_count); // We'll log the amount of each Profiler prof; @@ -954,14 +977,14 @@ void Server::AsyncRunStep(bool initial_step) } /* - Trigger emergethread (it somehow gets to a non-triggered but - bysy state sometimes) + Trigger emerge thread + Doing this every 2s is left over from old code, unclear if this is still needed. */ { float &counter = m_emergethread_trigger_timer; - counter += dtime; - if (counter >= 2.0) { - counter = 0.0; + counter -= dtime; + if (counter <= 0.0f) { + counter = 2.0f; m_emerge->startThreads(); } @@ -1032,8 +1055,7 @@ void Server::Receive() } catch (const ClientStateError &e) { errorstream << "ProcessData: peer=" << peer_id << " what()=" << e.what() << std::endl; - DenyAccess_Legacy(peer_id, L"Your client sent something server didn't expect." - L"Try reconnecting or updating your client"); + DenyAccess(peer_id, SERVER_ACCESSDENIED_UNEXPECTED_DATA); } catch (const con::PeerNotFoundException &e) { // Do nothing } catch (const con::NoIncomingDataException &e) { @@ -1046,18 +1068,14 @@ PlayerSAO* Server::StageTwoClientInit(session_t peer_id) { std::string playername; PlayerSAO *playersao = NULL; - m_clients.lock(); - try { + { + ClientInterface::AutoLock clientlock(m_clients); RemoteClient* client = m_clients.lockedGetClientNoEx(peer_id, CS_InitDone); if (client) { playername = client->getName(); playersao = emergePlayer(playername.c_str(), peer_id, client->net_proto_version); } - } catch (std::exception &e) { - m_clients.unlock(); - throw; } - m_clients.unlock(); RemotePlayer *player = m_env->getPlayer(playername.c_str()); @@ -1066,15 +1084,13 @@ PlayerSAO* Server::StageTwoClientInit(session_t peer_id) if (player && player->getPeerId() != PEER_ID_INEXISTENT) { actionstream << "Server: Failed to emerge player \"" << playername << "\" (player allocated to an another client)" << std::endl; - DenyAccess_Legacy(peer_id, L"Another client is connected with this " - L"name. If your client closed unexpectedly, try again in " - L"a minute."); + DenyAccess(peer_id, SERVER_ACCESSDENIED_ALREADY_CONNECTED); } else { errorstream << "Server: " << playername << ": Failed to emerge player" << std::endl; - DenyAccess_Legacy(peer_id, L"Could not allocate player."); + DenyAccess(peer_id, SERVER_ACCESSDENIED_SERVER_FAIL); } - return NULL; + return nullptr; } /* @@ -1091,30 +1107,32 @@ PlayerSAO* Server::StageTwoClientInit(session_t peer_id) // Send inventory SendInventory(playersao, false); - // Send HP or death screen + // Send HP + SendPlayerHP(playersao); + + // Send death screen if (playersao->isDead()) SendDeathscreen(peer_id, false, v3f(0,0,0)); - else - SendPlayerHP(peer_id); // Send Breath SendPlayerBreath(playersao); /* - Print out action + Update player list and print action */ { - Address addr = getPeerAddress(player->getPeerId()); - std::string ip_str = addr.serializeString(); - const std::vector<std::string> &names = m_clients.getPlayerNames(); + NetworkPacket notice_pkt(TOCLIENT_UPDATE_PLAYER_LIST, 0, PEER_ID_INEXISTENT); + notice_pkt << (u8) PLAYER_LIST_ADD << (u16) 1 << std::string(player->getName()); + m_clients.sendToAll(¬ice_pkt); + } + { + std::string ip_str = getPeerAddress(player->getPeerId()).serializeString(); + const auto &names = m_clients.getPlayerNames(); actionstream << player->getName() << " [" << ip_str << "] joins game. List of players: "; - - for (const std::string &name : names) { + for (const std::string &name : names) actionstream << name << " "; - } - - actionstream << player->getName() <<std::endl; + actionstream << player->getName() << std::endl; } return playersao; } @@ -1137,18 +1155,16 @@ void Server::ProcessData(NetworkPacket *pkt) Address address = getPeerAddress(peer_id); std::string addr_s = address.serializeString(); - if(m_banmanager->isIpBanned(addr_s)) { + // FIXME: Isn't it a bit excessive to check this for every packet? + if (m_banmanager->isIpBanned(addr_s)) { std::string ban_name = m_banmanager->getBanName(addr_s); infostream << "Server: A banned client tried to connect from " - << addr_s << "; banned name was " - << ban_name << std::endl; - // This actually doesn't seem to transfer to the client - DenyAccess_Legacy(peer_id, L"Your ip is banned. Banned name was " - + utf8_to_wide(ban_name)); + << addr_s << "; banned name was " << ban_name << std::endl; + DenyAccess(peer_id, SERVER_ACCESSDENIED_CUSTOM_STRING, + "Your IP is banned. Banned name was " + ban_name); return; } - } - catch(con::PeerNotFoundException &e) { + } catch (con::PeerNotFoundException &e) { /* * no peer for this packet found * most common reason is peer timeout, e.g. peer didn't @@ -1228,13 +1244,12 @@ void Server::onMapEditEvent(const MapEditEvent &event) void Server::SetBlocksNotSent(std::map<v3s16, MapBlock *>& block) { std::vector<session_t> clients = m_clients.getClientIDs(); - m_clients.lock(); + ClientInterface::AutoLock clientlock(m_clients); // Set the modified blocks unsent for all the clients for (const session_t client_id : clients) { if (RemoteClient *client = m_clients.lockedGetClientNoEx(client_id)) client->SetBlocksNotSent(block); } - m_clients.unlock(); } void Server::peerAdded(con::Peer *peer) @@ -1262,13 +1277,11 @@ bool Server::getClientConInfo(session_t peer_id, con::rtt_stat_type type, float* bool Server::getClientInfo(session_t peer_id, ClientInfo &ret) { - m_clients.lock(); + ClientInterface::AutoLock clientlock(m_clients); RemoteClient* client = m_clients.lockedGetClientNoEx(peer_id, CS_Invalid); - if (!client) { - m_clients.unlock(); + if (!client) return false; - } ret.state = client->getState(); ret.addr = client->getAddress(); @@ -1283,8 +1296,6 @@ bool Server::getClientInfo(session_t peer_id, ClientInfo &ret) ret.lang_code = client->getLangCode(); - m_clients.unlock(); - return true; } @@ -1361,18 +1372,21 @@ void Server::SendMovement(session_t peer_id) Send(&pkt); } -void Server::SendPlayerHPOrDie(PlayerSAO *playersao, const PlayerHPChangeReason &reason) +void Server::HandlePlayerHPChange(PlayerSAO *playersao, const PlayerHPChangeReason &reason) { - if (playersao->isImmortal()) - return; + m_script->player_event(playersao, "health_changed"); + SendPlayerHP(playersao); - session_t peer_id = playersao->getPeerID(); - bool is_alive = !playersao->isDead(); + // Send to other clients + playersao->sendPunchCommand(); - if (is_alive) - SendPlayerHP(peer_id); - else - DiePlayer(peer_id, reason); + if (playersao->isDead()) + HandlePlayerDeath(playersao, reason); +} + +void Server::SendPlayerHP(PlayerSAO *playersao) +{ + SendHP(playersao->getPeerID(), playersao->getHP()); } void Server::SendHP(session_t peer_id, u16 hp) @@ -1404,13 +1418,6 @@ void Server::SendAccessDenied(session_t peer_id, AccessDeniedCode reason, Send(&pkt); } -void Server::SendAccessDenied_Legacy(session_t peer_id,const std::wstring &reason) -{ - NetworkPacket pkt(TOCLIENT_ACCESS_DENIED_LEGACY, 0, peer_id); - pkt << reason; - Send(&pkt); -} - void Server::SendDeathscreen(session_t peer_id, bool set_camera_point_target, v3f camera_point_target) { @@ -1793,6 +1800,16 @@ void Server::SendOverrideDayNightRatio(session_t peer_id, bool do_override, Send(&pkt); } +void Server::SendSetLighting(session_t peer_id, const Lighting &lighting) +{ + NetworkPacket pkt(TOCLIENT_SET_LIGHTING, + 4, peer_id); + + pkt << lighting.shadow_intensity; + + Send(&pkt); +} + void Server::SendTimeOfDay(session_t peer_id, u16 time, f32 time_speed) { NetworkPacket pkt(TOCLIENT_TIME_OF_DAY, 0, peer_id); @@ -1806,18 +1823,6 @@ void Server::SendTimeOfDay(session_t peer_id, u16 time, f32 time_speed) } } -void Server::SendPlayerHP(session_t peer_id) -{ - PlayerSAO *playersao = getPlayerSAO(peer_id); - assert(playersao); - - SendHP(peer_id, playersao->getHP()); - m_script->player_event(playersao,"health_changed"); - - // Send to other clients - playersao->sendPunchCommand(); -} - void Server::SendPlayerBreath(PlayerSAO *sao) { assert(sao); @@ -2222,7 +2227,7 @@ void Server::sendRemoveNode(v3s16 p, std::unordered_set<u16> *far_players, pkt << p; std::vector<session_t> clients = m_clients.getClientIDs(); - m_clients.lock(); + ClientInterface::AutoLock clientlock(m_clients); for (session_t client_id : clients) { RemoteClient *client = m_clients.lockedGetClientNoEx(client_id); @@ -2245,8 +2250,6 @@ void Server::sendRemoveNode(v3s16 p, std::unordered_set<u16> *far_players, // Send as reliable m_clients.send(client_id, 0, &pkt, true); } - - m_clients.unlock(); } void Server::sendAddNode(v3s16 p, MapNode n, std::unordered_set<u16> *far_players, @@ -2261,7 +2264,7 @@ void Server::sendAddNode(v3s16 p, MapNode n, std::unordered_set<u16> *far_player << (u8) (remove_metadata ? 0 : 1); std::vector<session_t> clients = m_clients.getClientIDs(); - m_clients.lock(); + ClientInterface::AutoLock clientlock(m_clients); for (session_t client_id : clients) { RemoteClient *client = m_clients.lockedGetClientNoEx(client_id); @@ -2284,8 +2287,6 @@ void Server::sendAddNode(v3s16 p, MapNode n, std::unordered_set<u16> *far_player // Send as reliable m_clients.send(client_id, 0, &pkt, true); } - - m_clients.unlock(); } void Server::sendMetadataChanged(const std::list<v3s16> &meta_updates, float far_d_nodes) @@ -2294,7 +2295,7 @@ void Server::sendMetadataChanged(const std::list<v3s16> &meta_updates, float far NodeMetadataList meta_updates_list(false); std::vector<session_t> clients = m_clients.getClientIDs(); - m_clients.lock(); + ClientInterface::AutoLock clientlock(m_clients); for (session_t i : clients) { RemoteClient *client = m_clients.lockedGetClientNoEx(i); @@ -2335,27 +2336,37 @@ void Server::sendMetadataChanged(const std::list<v3s16> &meta_updates, float far meta_updates_list.clear(); } - - m_clients.unlock(); } void Server::SendBlockNoLock(session_t peer_id, MapBlock *block, u8 ver, - u16 net_proto_version) + u16 net_proto_version, SerializedBlockCache *cache) { - /* - Create a packet with the block in the right format - */ thread_local const int net_compression_level = rangelim(g_settings->getS16("map_compression_level_net"), -1, 9); - std::ostringstream os(std::ios_base::binary); - block->serialize(os, ver, false, net_compression_level); - block->serializeNetworkSpecific(os); - std::string s = os.str(); + std::string s, *sptr = nullptr; - NetworkPacket pkt(TOCLIENT_BLOCKDATA, 2 + 2 + 2 + s.size(), peer_id); + if (cache) { + auto it = cache->find({block->getPos(), ver}); + if (it != cache->end()) + sptr = &it->second; + } + + // Serialize the block in the right format + if (!sptr) { + std::ostringstream os(std::ios_base::binary); + block->serialize(os, ver, false, net_compression_level); + block->serializeNetworkSpecific(os); + s = os.str(); + sptr = &s; + } + NetworkPacket pkt(TOCLIENT_BLOCKDATA, 2 + 2 + 2 + sptr->size(), peer_id); pkt << block->getPos(); - pkt.putRawString(s.c_str(), s.size()); + pkt.putRawString(*sptr); Send(&pkt); + + // Store away in cache + if (cache && sptr == &s) + (*cache)[{block->getPos(), ver}] = std::move(s); } void Server::SendBlocks(float dtime) @@ -2365,14 +2376,14 @@ void Server::SendBlocks(float dtime) std::vector<PrioritySortedBlockTransfer> queue; - u32 total_sending = 0; + u32 total_sending = 0, unique_clients = 0; { ScopeProfiler sp2(g_profiler, "Server::SendBlocks(): Collect list"); std::vector<session_t> clients = m_clients.getClientIDs(); - m_clients.lock(); + ClientInterface::AutoLock clientlock(m_clients); for (const session_t client_id : clients) { RemoteClient *client = m_clients.lockedGetClientNoEx(client_id, CS_Active); @@ -2380,9 +2391,10 @@ void Server::SendBlocks(float dtime) continue; total_sending += client->getSendingCount(); + const auto old_count = queue.size(); client->GetNextBlocks(m_env,m_emerge, dtime, queue); + unique_clients += queue.size() > old_count ? 1 : 0; } - m_clients.unlock(); } // Sort. @@ -2390,7 +2402,7 @@ void Server::SendBlocks(float dtime) // Lowest is most important. std::sort(queue.begin(), queue.end()); - m_clients.lock(); + ClientInterface::AutoLock clientlock(m_clients); // Maximal total count calculation // The per-client block sends is halved with the maximal online users @@ -2400,6 +2412,12 @@ void Server::SendBlocks(float dtime) ScopeProfiler sp(g_profiler, "Server::SendBlocks(): Send to clients"); Map &map = m_env->getMap(); + SerializedBlockCache cache, *cache_ptr = nullptr; + if (unique_clients > 1) { + // caching is pointless with a single client + cache_ptr = &cache; + } + for (const PrioritySortedBlockTransfer &block_to_send : queue) { if (total_sending >= max_blocks_to_send) break; @@ -2414,12 +2432,11 @@ void Server::SendBlocks(float dtime) continue; SendBlockNoLock(block_to_send.peer_id, block, client->serialization_version, - client->net_proto_version); + client->net_proto_version, cache_ptr); client->SentBlock(block_to_send.pos); total_sending++; } - m_clients.unlock(); } bool Server::SendBlock(session_t peer_id, const v3s16 &blockpos) @@ -2428,15 +2445,12 @@ bool Server::SendBlock(session_t peer_id, const v3s16 &blockpos) if (!block) return false; - m_clients.lock(); + ClientInterface::AutoLock clientlock(m_clients); RemoteClient *client = m_clients.lockedGetClientNoEx(peer_id, CS_Active); - if (!client || client->isBlockSent(blockpos)) { - m_clients.unlock(); + if (!client || client->isBlockSent(blockpos)) return false; - } SendBlockNoLock(peer_id, block, client->serialization_version, client->net_proto_version); - m_clients.unlock(); return true; } @@ -2746,23 +2760,18 @@ void Server::sendDetachedInventories(session_t peer_id, bool incremental) Something random */ -void Server::DiePlayer(session_t peer_id, const PlayerHPChangeReason &reason) +void Server::HandlePlayerDeath(PlayerSAO *playersao, const PlayerHPChangeReason &reason) { - PlayerSAO *playersao = getPlayerSAO(peer_id); - assert(playersao); - infostream << "Server::DiePlayer(): Player " << playersao->getPlayer()->getName() << " dies" << std::endl; - playersao->setHP(0, reason); playersao->clearParentAttachment(); // Trigger scripted stuff m_script->on_dieplayer(playersao, reason); - SendPlayerHP(peer_id); - SendDeathscreen(peer_id, false, v3f(0,0,0)); + SendDeathscreen(playersao->getPeerID(), false, v3f(0,0,0)); } void Server::RespawnPlayer(session_t peer_id) @@ -2783,8 +2792,6 @@ void Server::RespawnPlayer(session_t peer_id) // setPos will send the new position to client playersao->setPos(findSpawnPos()); } - - SendPlayerHP(peer_id); } @@ -2795,29 +2802,10 @@ void Server::DenySudoAccess(session_t peer_id) } -void Server::DenyAccessVerCompliant(session_t peer_id, u16 proto_ver, AccessDeniedCode reason, - const std::string &str_reason, bool reconnect) -{ - SendAccessDenied(peer_id, reason, str_reason, reconnect); - - m_clients.event(peer_id, CSE_SetDenied); - DisconnectPeer(peer_id); -} - - void Server::DenyAccess(session_t peer_id, AccessDeniedCode reason, - const std::string &custom_reason) -{ - SendAccessDenied(peer_id, reason, custom_reason); - m_clients.event(peer_id, CSE_SetDenied); - DisconnectPeer(peer_id); -} - -// 13/03/15: remove this function when protocol version 25 will become -// the minimum version for MT users, maybe in 1 year -void Server::DenyAccess_Legacy(session_t peer_id, const std::wstring &reason) + const std::string &custom_reason, bool reconnect) { - SendAccessDenied_Legacy(peer_id, reason); + SendAccessDenied(peer_id, reason, custom_reason, reconnect); m_clients.event(peer_id, CSE_SetDenied); DisconnectPeer(peer_id); } @@ -3003,8 +2991,8 @@ std::wstring Server::handleChat(const std::string &name, return ws.str(); } case RPLAYER_CHATRESULT_KICK: - DenyAccess_Legacy(player->getPeerId(), - L"You have been kicked due to message flooding."); + DenyAccess(player->getPeerId(), SERVER_ACCESSDENIED_CUSTOM_STRING, + "You have been kicked due to message flooding."); return L""; case RPLAYER_CHATRESULT_OK: break; @@ -3119,15 +3107,18 @@ std::string Server::getStatusString() std::ostringstream os(std::ios_base::binary); os << "# Server: "; // Version - os << "version=" << g_version_string; + os << "version: " << g_version_string; + // Game + os << " | game: " << (m_gamespec.name.empty() ? m_gamespec.id : m_gamespec.name); // Uptime - os << ", uptime=" << m_uptime_counter->get(); + os << " | uptime: " << duration_to_string((int) m_uptime_counter->get()); // Max lag estimate - os << ", max_lag=" << (m_env ? m_env->getMaxLagEstimate() : 0); + os << " | max lag: " << std::setprecision(3); + os << (m_env ? m_env->getMaxLagEstimate() : 0) << "s"; // Information about clients bool first = true; - os << ", clients={"; + os << " | clients: "; if (m_env) { std::vector<session_t> clients = m_clients.getClientIDs(); for (session_t client_id : clients) { @@ -3144,7 +3135,6 @@ std::string Server::getStatusString() os << name; } } - os << "}"; if (m_env && !((ServerMap*)(&m_env->getMap()))->isSavingEnabled()) os << std::endl << "# Server: " << " WARNING: Map saving is disabled."; @@ -3298,9 +3288,12 @@ bool Server::hudSetFlags(RemotePlayer *player, u32 flags, u32 mask) if (!player) return false; + u32 new_hud_flags = (player->hud_flags & ~mask) | flags; + if (new_hud_flags == player->hud_flags) // no change + return true; + SendHUDSetFlags(player->getPeerId(), flags, mask); - player->hud_flags &= ~mask; - player->hud_flags |= flags; + player->hud_flags = new_hud_flags; PlayerSAO* playersao = player->getPlayerSAO(); @@ -3409,6 +3402,13 @@ void Server::overrideDayNightRatio(RemotePlayer *player, bool do_override, SendOverrideDayNightRatio(player->getPeerId(), do_override, ratio); } +void Server::setLighting(RemotePlayer *player, const Lighting &lighting) +{ + sanity_check(player); + player->setLighting(lighting); + SendSetLighting(player->getPeerId(), lighting); +} + void Server::notifyPlayers(const std::wstring &msg) { SendChatMessage(PEER_ID_INEXISTENT, ChatMessage(msg)); @@ -3543,34 +3543,49 @@ bool Server::dynamicAddMedia(std::string filepath, legacy_pkt.putLongString(filedata); std::unordered_set<session_t> delivered, waiting; - m_clients.lock(); - for (auto &pair : m_clients.getClientList()) { - if (pair.second->getState() < CS_DefinitionsSent) - continue; - const auto proto_ver = pair.second->net_proto_version; - if (proto_ver < 39) - continue; + { + ClientInterface::AutoLock clientlock(m_clients); + for (auto &pair : m_clients.getClientList()) { + if (pair.second->getState() == CS_DefinitionsSent && !ephemeral) { + /* + If a client is in the DefinitionsSent state it is too late to + transfer the file via sendMediaAnnouncement() but at the same + time the client cannot accept a media push yet. + Short of artificially delaying the joining process there is no + way for the server to resolve this so we (currently) opt not to. + */ + warningstream << "The media \"" << filename << "\" (dynamic) could " + "not be delivered to " << pair.second->getName() + << " due to a race condition." << std::endl; + continue; + } + if (pair.second->getState() < CS_Active) + continue; - const session_t peer_id = pair.second->peer_id; - if (!to_player.empty() && getPlayerName(peer_id) != to_player) - continue; + const auto proto_ver = pair.second->net_proto_version; + if (proto_ver < 39) + 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); + 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) { @@ -3680,21 +3695,11 @@ const ModSpec *Server::getModSpec(const std::string &modname) const return m_modmgr->getModSpec(modname); } -void Server::getModNames(std::vector<std::string> &modlist) -{ - m_modmgr->getModNames(modlist); -} - std::string Server::getBuiltinLuaPath() { return porting::path_share + DIR_DELIM + "builtin"; } -std::string Server::getModStoragePath() const -{ - return m_path_world + DIR_DELIM + "mod_storage"; -} - v3f Server::findSpawnPos() { ServerMap &map = m_env->getServerMap(); @@ -3858,11 +3863,8 @@ bool Server::registerModStorage(ModMetadata *storage) void Server::unregisterModStorage(const std::string &name) { std::unordered_map<std::string, ModMetadata *>::const_iterator it = m_mod_storages.find(name); - if (it != m_mod_storages.end()) { - // Save unconditionaly on unregistration - it->second->save(getModStoragePath()); + if (it != m_mod_storages.end()) m_mod_storages.erase(name); - } } void dedicated_server_loop(Server &server, bool &kill) @@ -4000,3 +4002,106 @@ Translations *Server::getTranslationLanguage(const std::string &lang_code) return translations; } + +ModMetadataDatabase *Server::openModStorageDatabase(const std::string &world_path) +{ + std::string world_mt_path = world_path + DIR_DELIM + "world.mt"; + Settings world_mt; + if (!world_mt.readConfigFile(world_mt_path.c_str())) + throw BaseException("Cannot read world.mt!"); + + std::string backend = world_mt.exists("mod_storage_backend") ? + world_mt.get("mod_storage_backend") : "files"; + if (backend == "files") + warningstream << "/!\\ You are using the old mod storage files backend. " + << "This backend is deprecated and may be removed in a future release /!\\" + << std::endl << "Switching to SQLite3 is advised, " + << "please read http://wiki.minetest.net/Database_backends." << std::endl; + + return openModStorageDatabase(backend, world_path, world_mt); +} + +ModMetadataDatabase *Server::openModStorageDatabase(const std::string &backend, + const std::string &world_path, const Settings &world_mt) +{ + if (backend == "sqlite3") + return new ModMetadataDatabaseSQLite3(world_path); + + if (backend == "files") + return new ModMetadataDatabaseFiles(world_path); + + if (backend == "dummy") + return new Database_Dummy(); + + throw BaseException("Mod storage database backend " + backend + " not supported"); +} + +bool Server::migrateModStorageDatabase(const GameParams &game_params, const Settings &cmd_args) +{ + std::string migrate_to = cmd_args.get("migrate-mod-storage"); + Settings world_mt; + std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt"; + if (!world_mt.readConfigFile(world_mt_path.c_str())) { + errorstream << "Cannot read world.mt!" << std::endl; + return false; + } + + std::string backend = world_mt.exists("mod_storage_backend") ? + world_mt.get("mod_storage_backend") : "files"; + if (backend == migrate_to) { + errorstream << "Cannot migrate: new backend is same" + << " as the old one" << std::endl; + return false; + } + + ModMetadataDatabase *srcdb = nullptr; + ModMetadataDatabase *dstdb = nullptr; + + bool succeeded = false; + + try { + srcdb = Server::openModStorageDatabase(backend, game_params.world_path, world_mt); + dstdb = Server::openModStorageDatabase(migrate_to, game_params.world_path, world_mt); + + dstdb->beginSave(); + + std::vector<std::string> mod_list; + srcdb->listMods(&mod_list); + for (const std::string &modname : mod_list) { + StringMap meta; + srcdb->getModEntries(modname, &meta); + for (const auto &pair : meta) { + dstdb->setModEntry(modname, pair.first, pair.second); + } + } + + dstdb->endSave(); + + succeeded = true; + + actionstream << "Successfully migrated the metadata of " + << mod_list.size() << " mods" << std::endl; + world_mt.set("mod_storage_backend", migrate_to); + if (!world_mt.updateConfigFile(world_mt_path.c_str())) + errorstream << "Failed to update world.mt!" << std::endl; + else + actionstream << "world.mt updated" << std::endl; + + } catch (BaseException &e) { + errorstream << "An error occurred during migration: " << e.what() << std::endl; + } + + delete srcdb; + delete dstdb; + + if (succeeded && backend == "files") { + // Back up files + const std::string storage_path = game_params.world_path + DIR_DELIM + "mod_storage"; + const std::string backup_path = game_params.world_path + DIR_DELIM + "mod_storage.bak"; + if (!fs::Rename(storage_path, backup_path)) + warningstream << "After migration, " << storage_path + << " could not be renamed to " << backup_path << std::endl; + } + + return succeeded; +} diff --git a/src/server.h b/src/server.h index c5db0fdfb..2c21f5dfc 100644 --- a/src/server.h +++ b/src/server.h @@ -69,9 +69,11 @@ struct SkyboxParams; struct SunParams; struct MoonParams; struct StarParams; +struct Lighting; class ServerThread; class ServerModManager; class ServerInventoryManager; +struct PackedValue; enum ClientDeletionReason { CDR_LEAVE, @@ -283,6 +285,7 @@ public: virtual u16 allocateUnknownNodeId(const std::string &name); IRollbackManager *getRollbackManager() { return m_rollback; } virtual EmergeManager *getEmergeManager() { return m_emerge; } + virtual ModMetadataDatabase *getModStorageDatabase() { return m_mod_storage_database; } IWritableItemDefManager* getWritableItemDefManager(); NodeDefManager* getWritableNodeDefManager(); @@ -290,12 +293,10 @@ public: virtual const std::vector<ModSpec> &getMods() const; virtual const ModSpec* getModSpec(const std::string &modname) const; - void getModNames(std::vector<std::string> &modlist); - std::string getBuiltinLuaPath(); + static std::string getBuiltinLuaPath(); virtual std::string getWorldPath() const { return m_path_world; } - virtual std::string getModStoragePath() const; - inline bool isSingleplayer() + inline bool isSingleplayer() const { return m_simple_singleplayer_mode; } inline void setAsyncFatalError(const std::string &error) @@ -333,24 +334,24 @@ public: void overrideDayNightRatio(RemotePlayer *player, bool do_override, float brightness); + void setLighting(RemotePlayer *player, const Lighting &lighting); + /* con::PeerHandler implementation. */ void peerAdded(con::Peer *peer); void deletingPeer(con::Peer *peer, bool timeout); void DenySudoAccess(session_t peer_id); - void DenyAccessVerCompliant(session_t peer_id, u16 proto_ver, AccessDeniedCode reason, - const std::string &str_reason = "", bool reconnect = false); void DenyAccess(session_t peer_id, AccessDeniedCode reason, - const std::string &custom_reason = ""); + const std::string &custom_reason = "", bool reconnect = false); void acceptAuth(session_t peer_id, bool forSudoMode); - void DenyAccess_Legacy(session_t peer_id, const std::wstring &reason); void DisconnectPeer(session_t peer_id); bool getClientConInfo(session_t peer_id, con::rtt_stat_type type, float *retval); bool getClientInfo(session_t peer_id, ClientInfo &ret); void printToConsoleOnly(const std::string &text); - void SendPlayerHPOrDie(PlayerSAO *player, const PlayerHPChangeReason &reason); + void HandlePlayerHPChange(PlayerSAO *sao, const PlayerHPChangeReason &reason); + void SendPlayerHP(PlayerSAO *sao); void SendPlayerBreath(PlayerSAO *sao); void SendInventory(PlayerSAO *playerSAO, bool incremental); void SendMovePlayer(session_t peer_id); @@ -377,6 +378,20 @@ public: // Get or load translations for a language Translations *getTranslationLanguage(const std::string &lang_code); + static ModMetadataDatabase *openModStorageDatabase(const std::string &world_path); + + static ModMetadataDatabase *openModStorageDatabase(const std::string &backend, + const std::string &world_path, const Settings &world_mt); + + static bool migrateModStorageDatabase(const GameParams &game_params, + const Settings &cmd_args); + + // Lua files registered for init of async env, pair of modname + path + std::vector<std::pair<std::string, std::string>> m_async_init_files; + + // Data transferred into async envs at init time + std::unique_ptr<PackedValue> m_async_globals_data; + // Bind address Address m_bind_addr; @@ -410,6 +425,16 @@ private: std::unordered_set<session_t> waiting_players; }; + // the standard library does not implement std::hash for pairs so we have this: + struct SBCHash { + size_t operator() (const std::pair<v3s16, u16> &p) const { + return (((size_t) p.first.X) << 48) | (((size_t) p.first.Y) << 32) | + (((size_t) p.first.Z) << 16) | ((size_t) p.second); + } + }; + + typedef std::unordered_map<std::pair<v3s16, u16>, std::string, SBCHash> SerializedBlockCache; + void init(); void SendMovement(session_t peer_id); @@ -430,7 +455,6 @@ private: virtual void SendChatMessage(session_t peer_id, const ChatMessage &message); void SendTimeOfDay(session_t peer_id, u16 time, f32 time_speed); - void SendPlayerHP(session_t peer_id); void SendLocalPlayerAnimations(session_t peer_id, v2s32 animation_frames[4], f32 animation_speed); @@ -451,6 +475,7 @@ private: void SendSetStars(session_t peer_id, const StarParams ¶ms); void SendCloudParams(session_t peer_id, const CloudParams ¶ms); void SendOverrideDayNightRatio(session_t peer_id, bool do_override, float ratio); + void SendSetLighting(session_t peer_id, const Lighting &lighting); void broadcastModChannelMessage(const std::string &channel, const std::string &message, session_t from_peer); @@ -470,7 +495,9 @@ private: float far_d_nodes = 100); // Environment and Connection must be locked when called - void SendBlockNoLock(session_t peer_id, MapBlock *block, u8 ver, u16 net_proto_version); + // `cache` may only be very short lived! (invalidation not handeled) + void SendBlockNoLock(session_t peer_id, MapBlock *block, u8 ver, + u16 net_proto_version, SerializedBlockCache *cache = nullptr); // Sends blocks to clients (locks env and con on its own) void SendBlocks(float dtime); @@ -502,7 +529,7 @@ private: Something random */ - void DiePlayer(session_t peer_id, const PlayerHPChangeReason &reason); + void HandlePlayerDeath(PlayerSAO* sao, const PlayerHPChangeReason &reason); void RespawnPlayer(session_t peer_id); void DeleteClient(session_t peer_id, ClientDeletionReason reason); void UpdateCrafting(RemotePlayer *player); @@ -678,6 +705,7 @@ private: s32 nextSoundId(); std::unordered_map<std::string, ModMetadata *> m_mod_storages; + ModMetadataDatabase *m_mod_storage_database = nullptr; float m_mod_storage_save_timer = 10.0f; // CSM restrictions byteflag @@ -697,11 +725,11 @@ private: MetricCounterPtr m_uptime_counter; MetricGaugePtr m_player_gauge; MetricGaugePtr m_timeofday_gauge; - // current server step lag MetricGaugePtr m_lag_gauge; - MetricCounterPtr m_aom_buffer_counter; + MetricCounterPtr m_aom_buffer_counter[2]; // [0] = rel, [1] = unrel MetricCounterPtr m_packet_recv_counter; MetricCounterPtr m_packet_recv_processed_counter; + MetricCounterPtr m_map_edit_event_counter; }; /* diff --git a/src/server/luaentity_sao.cpp b/src/server/luaentity_sao.cpp index 3bcbe107b..82f6da231 100644 --- a/src/server/luaentity_sao.cpp +++ b/src/server/luaentity_sao.cpp @@ -108,7 +108,12 @@ void LuaEntitySAO::addedToEnvironment(u32 dtime_s) m_env->getScriptIface()-> luaentity_Activate(m_id, m_init_state, dtime_s); } else { + // It's an unknown object + // Use entitystring as infotext for debugging m_prop.infotext = m_init_name; + // Set unknown object texture + m_prop.textures.clear(); + m_prop.textures.emplace_back("unknown_object.png"); } } @@ -300,10 +305,11 @@ void LuaEntitySAO::getStaticData(std::string *result) const *result = os.str(); } -u16 LuaEntitySAO::punch(v3f dir, +u32 LuaEntitySAO::punch(v3f dir, const ToolCapabilities *toolcap, ServerActiveObject *puncher, - float time_from_last_punch) + float time_from_last_punch, + u16 initial_wear) { if (!m_registered) { // Delete unknown LuaEntities when punched @@ -321,7 +327,8 @@ u16 LuaEntitySAO::punch(v3f dir, m_armor_groups, toolcap, &tool_item, - time_from_last_punch); + time_from_last_punch, + initial_wear); bool damage_handled = m_env->getScriptIface()->luaentity_Punch(m_id, puncher, time_from_last_punch, toolcap, dir, result.did_punch ? result.damage : 0); diff --git a/src/server/luaentity_sao.h b/src/server/luaentity_sao.h index 6883ae1b9..87b664a8b 100644 --- a/src/server/luaentity_sao.h +++ b/src/server/luaentity_sao.h @@ -44,9 +44,10 @@ public: bool isStaticAllowed() const { return m_prop.static_save; } bool shouldUnload() const { return true; } void getStaticData(std::string *result) const; - u16 punch(v3f dir, const ToolCapabilities *toolcap = nullptr, + u32 punch(v3f dir, const ToolCapabilities *toolcap = nullptr, ServerActiveObject *puncher = nullptr, - float time_from_last_punch = 1000000.0f); + float time_from_last_punch = 1000000.0f, + u16 initial_wear = 0); void rightClick(ServerActiveObject *clicker); void setPos(const v3f &pos); void moveTo(v3f pos, bool continuous); diff --git a/src/server/mods.cpp b/src/server/mods.cpp index 609d8c346..ba76d4746 100644 --- a/src/server/mods.cpp +++ b/src/server/mods.cpp @@ -41,8 +41,10 @@ ServerModManager::ServerModManager(const std::string &worldpath) : SubgameSpec gamespec = findWorldSubgame(worldpath); // Add all game mods and all world mods - addModsInPath(gamespec.gamemods_path); - addModsInPath(worldpath + DIR_DELIM + "worldmods"); + std::string game_virtual_path; + game_virtual_path.append("games/").append(gamespec.id).append("/mods"); + addModsInPath(gamespec.gamemods_path, game_virtual_path); + addModsInPath(worldpath + DIR_DELIM + "worldmods", "worldmods"); // Load normal mods std::string worldmt = worldpath + DIR_DELIM + "world.mt"; diff --git a/src/server/player_sao.cpp b/src/server/player_sao.cpp index d4d036726..d076d5783 100644 --- a/src/server/player_sao.cpp +++ b/src/server/player_sao.cpp @@ -409,10 +409,11 @@ void PlayerSAO::setLookPitchAndSend(const float pitch) m_env->getGameDef()->SendMovePlayer(m_peer_id); } -u16 PlayerSAO::punch(v3f dir, +u32 PlayerSAO::punch(v3f dir, const ToolCapabilities *toolcap, ServerActiveObject *puncher, - float time_from_last_punch) + float time_from_last_punch, + u16 initial_wear) { if (!toolcap) return 0; @@ -430,7 +431,7 @@ u16 PlayerSAO::punch(v3f dir, s32 old_hp = getHP(); HitParams hitparams = getHitParams(m_armor_groups, toolcap, - time_from_last_punch); + time_from_last_punch, initial_wear); PlayerSAO *playersao = m_player->getPlayerSAO(); @@ -462,35 +463,33 @@ void PlayerSAO::rightClick(ServerActiveObject *clicker) m_env->getScriptIface()->on_rightclickplayer(this, clicker); } -void PlayerSAO::setHP(s32 hp, const PlayerHPChangeReason &reason) +void PlayerSAO::setHP(s32 target_hp, const PlayerHPChangeReason &reason, bool from_client) { - if (hp == (s32)m_hp) - return; // Nothing to do - - if (m_hp <= 0 && hp < (s32)m_hp) - return; // Cannot take more damage + target_hp = rangelim(target_hp, 0, U16_MAX); - { - s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - m_hp, reason); - if (hp_change == 0) - return; + if (target_hp == m_hp) + return; // Nothing to do - hp = m_hp + hp_change; - } + s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, target_hp - (s32)m_hp, reason); - s32 oldhp = m_hp; - hp = rangelim(hp, 0, m_prop.hp_max); + s32 hp = (s32)m_hp + std::min(hp_change, U16_MAX); // Protection against s32 overflow + hp = rangelim(hp, 0, U16_MAX); - if (hp < oldhp && isImmortal()) - return; // Do not allow immortal players to be damaged + if (hp > m_prop.hp_max) + hp = m_prop.hp_max; - m_hp = hp; + if (hp < m_hp && isImmortal()) + hp = m_hp; // Do not allow immortal players to be damaged // Update properties on death - if ((hp == 0) != (oldhp == 0)) + if ((hp == 0) != (m_hp == 0)) m_properties_sent = false; - m_env->getGameDef()->SendPlayerHPOrDie(this, reason); + if (hp != m_hp) { + m_hp = hp; + m_env->getGameDef()->HandlePlayerHPChange(this, reason); + } else if (from_client) + m_env->getGameDef()->SendPlayerHP(this); } void PlayerSAO::setBreath(const u16 breath, bool send) diff --git a/src/server/player_sao.h b/src/server/player_sao.h index 8e2d8803f..1067801e7 100644 --- a/src/server/player_sao.h +++ b/src/server/player_sao.h @@ -72,24 +72,24 @@ public: PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t peer_id_, bool is_singleplayer); - ActiveObjectType getType() const { return ACTIVEOBJECT_TYPE_PLAYER; } - ActiveObjectType getSendType() const { return ACTIVEOBJECT_TYPE_GENERIC; } - std::string getDescription(); + ActiveObjectType getType() const override { return ACTIVEOBJECT_TYPE_PLAYER; } + ActiveObjectType getSendType() const override { return ACTIVEOBJECT_TYPE_GENERIC; } + std::string getDescription() override; /* Active object <-> environment interface */ - void addedToEnvironment(u32 dtime_s); - void removingFromEnvironment(); - bool isStaticAllowed() const { return false; } - bool shouldUnload() const { return false; } - std::string getClientInitializationData(u16 protocol_version); - void getStaticData(std::string *result) const; - void step(float dtime, bool send_recommended); + void addedToEnvironment(u32 dtime_s) override; + void removingFromEnvironment() override; + bool isStaticAllowed() const override { return false; } + bool shouldUnload() const override { return false; } + std::string getClientInitializationData(u16 protocol_version) override; + void getStaticData(std::string *result) const override; + void step(float dtime, bool send_recommended) override; void setBasePosition(const v3f &position); - void setPos(const v3f &pos); - void moveTo(v3f pos, bool continuous); + void setPos(const v3f &pos) override; + void moveTo(v3f pos, bool continuous) override; void setPlayerYaw(const float yaw); // Data should not be sent at player initialization void setPlayerYawAndSend(const float yaw); @@ -109,10 +109,14 @@ public: Interaction interface */ - u16 punch(v3f dir, const ToolCapabilities *toolcap, ServerActiveObject *puncher, - float time_from_last_punch); - void rightClick(ServerActiveObject *clicker); - void setHP(s32 hp, const PlayerHPChangeReason &reason); + u32 punch(v3f dir, const ToolCapabilities *toolcap, ServerActiveObject *puncher, + float time_from_last_punch, u16 initial_wear = 0) override; + void rightClick(ServerActiveObject *clicker) override; + void setHP(s32 hp, const PlayerHPChangeReason &reason) override + { + return setHP(hp, reason, false); + } + void setHP(s32 hp, const PlayerHPChangeReason &reason, bool from_client); void setHPRaw(u16 hp) { m_hp = hp; } u16 getBreath() const { return m_breath; } void setBreath(const u16 breath, bool send = true); @@ -120,13 +124,13 @@ public: /* Inventory interface */ - Inventory *getInventory() const; - InventoryLocation getInventoryLocation() const; - void setInventoryModified() {} - std::string getWieldList() const { return "main"; } - u16 getWieldIndex() const; - ItemStack getWieldedItem(ItemStack *selected, ItemStack *hand = nullptr) const; - bool setWieldedItem(const ItemStack &item); + Inventory *getInventory() const override; + InventoryLocation getInventoryLocation() const override; + void setInventoryModified() override {} + std::string getWieldList() const override { return "main"; } + u16 getWieldIndex() const override; + ItemStack getWieldedItem(ItemStack *selected, ItemStack *hand = nullptr) const override; + bool setWieldedItem(const ItemStack &item) override; /* PlayerSAO-specific @@ -167,9 +171,9 @@ public: m_is_singleplayer = is_singleplayer; } - bool getCollisionBox(aabb3f *toset) const; - bool getSelectionBox(aabb3f *toset) const; - bool collideWithObjects() const { return true; } + bool getCollisionBox(aabb3f *toset) const override; + bool getSelectionBox(aabb3f *toset) const override; + bool collideWithObjects() const override { return true; } void finalize(RemotePlayer *player, const std::set<std::string> &privs); diff --git a/src/server/serveractiveobject.h b/src/server/serveractiveobject.h index 51f445914..5b0ee2d9b 100644 --- a/src/server/serveractiveobject.h +++ b/src/server/serveractiveobject.h @@ -145,11 +145,12 @@ public: virtual bool shouldUnload() const { return true; } - // Returns tool wear - virtual u16 punch(v3f dir, + // Returns added tool wear + virtual u32 punch(v3f dir, const ToolCapabilities *toolcap = nullptr, ServerActiveObject *puncher = nullptr, - float time_from_last_punch = 1000000.0f) + float time_from_last_punch = 1000000.0f, + u16 initial_wear = 0) { return 0; } virtual void rightClick(ServerActiveObject *clicker) {} diff --git a/src/server/serverinventorymgr.cpp b/src/server/serverinventorymgr.cpp index 3aee003b4..63d1645cb 100644 --- a/src/server/serverinventorymgr.cpp +++ b/src/server/serverinventorymgr.cpp @@ -39,24 +39,29 @@ ServerInventoryManager::~ServerInventoryManager() Inventory *ServerInventoryManager::getInventory(const InventoryLocation &loc) { + // No m_env check here: allow creation and modification of detached inventories + switch (loc.type) { case InventoryLocation::UNDEFINED: case InventoryLocation::CURRENT_PLAYER: break; case InventoryLocation::PLAYER: { + if (!m_env) + return nullptr; + RemotePlayer *player = m_env->getPlayer(loc.name.c_str()); if (!player) return NULL; + PlayerSAO *playersao = player->getPlayerSAO(); - if (!playersao) - return NULL; - return playersao->getInventory(); + return playersao ? playersao->getInventory() : nullptr; } break; case InventoryLocation::NODEMETA: { + if (!m_env) + return nullptr; + NodeMetadata *meta = m_env->getMap().getNodeMetadata(loc.p); - if (!meta) - return NULL; - return meta->getInventory(); + return meta ? meta->getInventory() : nullptr; } break; case InventoryLocation::DETACHED: { auto it = m_detached_inventories.find(loc.name); @@ -151,12 +156,13 @@ bool ServerInventoryManager::removeDetachedInventory(const std::string &name) const std::string &owner = inv_it->second.owner; if (!owner.empty()) { - RemotePlayer *player = m_env->getPlayer(owner.c_str()); - - if (player && player->getPeerId() != PEER_ID_INEXISTENT) - m_env->getGameDef()->sendDetachedInventory( - nullptr, name, player->getPeerId()); + if (m_env) { + RemotePlayer *player = m_env->getPlayer(owner.c_str()); + if (player && player->getPeerId() != PEER_ID_INEXISTENT) + m_env->getGameDef()->sendDetachedInventory( + nullptr, name, player->getPeerId()); + } } else if (m_env) { // Notify all players about the change as soon ServerEnv exists m_env->getGameDef()->sendDetachedInventory( diff --git a/src/server/unit_sao.cpp b/src/server/unit_sao.cpp index acbdd478a..9a49b0f43 100644 --- a/src/server/unit_sao.cpp +++ b/src/server/unit_sao.cpp @@ -84,8 +84,11 @@ void UnitSAO::setBonePosition(const std::string &bone, v3f position, v3f rotatio void UnitSAO::getBonePosition(const std::string &bone, v3f *position, v3f *rotation) { - *position = m_bone_position[bone].X; - *rotation = m_bone_position[bone].Y; + auto it = m_bone_position.find(bone); + if (it != m_bone_position.end()) { + *position = it->second.X; + *rotation = it->second.Y; + } } // clang-format off diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index b8ba25235..6a9001052 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -393,7 +393,7 @@ static std::random_device seed; ServerEnvironment::ServerEnvironment(ServerMap *map, ServerScripting *scriptIface, Server *server, - const std::string &path_world): + const std::string &path_world, MetricsBackend *mb): Environment(server), m_map(map), m_script(scriptIface), @@ -460,6 +460,15 @@ ServerEnvironment::ServerEnvironment(ServerMap *map, m_player_database = openPlayerDatabase(player_backend_name, path_world, conf); m_auth_database = openAuthDatabase(auth_backend_name, path_world, conf); + + m_step_time_counter = mb->addCounter( + "minetest_env_step_time", "Time spent in environment step (in microseconds)"); + + m_active_block_gauge = mb->addGauge( + "minetest_env_active_blocks", "Number of active blocks"); + + m_active_object_gauge = mb->addGauge( + "minetest_env_active_objects", "Number of active objects"); } ServerEnvironment::~ServerEnvironment() @@ -552,10 +561,8 @@ bool ServerEnvironment::removePlayerFromDatabase(const std::string &name) void ServerEnvironment::kickAllPlayers(AccessDeniedCode reason, const std::string &str_reason, bool reconnect) { - for (RemotePlayer *player : m_players) { - m_server->DenyAccessVerCompliant(player->getPeerId(), - player->protocol_version, reason, str_reason, reconnect); - } + for (RemotePlayer *player : m_players) + m_server->DenyAccess(player->getPeerId(), reason, str_reason, reconnect); } void ServerEnvironment::saveLoadedPlayers(bool force) @@ -892,7 +899,7 @@ public: for (ActiveABM &aabm : *m_aabms[c]) { if ((p.Y < aabm.min_y) || (p.Y > aabm.max_y)) continue; - + if (myrand() % aabm.chance != 0) continue; @@ -1285,6 +1292,8 @@ void ServerEnvironment::clearObjects(ClearObjectsMode mode) void ServerEnvironment::step(float dtime) { ScopeProfiler sp2(g_profiler, "ServerEnv::step()", SPT_AVG); + const auto start_time = porting::getTimeUs(); + /* Step time of day */ stepTimeOfDay(dtime); @@ -1354,6 +1363,8 @@ void ServerEnvironment::step(float dtime) m_active_blocks.update(players, active_block_range, active_object_range, blocks_removed, blocks_added); + m_active_block_gauge->set(m_active_blocks.size()); + /* Handle removed blocks */ @@ -1483,6 +1494,8 @@ void ServerEnvironment::step(float dtime) */ m_script->environment_Step(dtime); + m_script->stepAsync(); + /* Step active objects */ @@ -1497,9 +1510,12 @@ void ServerEnvironment::step(float dtime) send_recommended = true; } - auto cb_state = [this, dtime, send_recommended] (ServerActiveObject *obj) { + u32 object_count = 0; + + auto cb_state = [&] (ServerActiveObject *obj) { if (obj->isGone()) return; + object_count++; // Step object obj->step(dtime, send_recommended); @@ -1507,6 +1523,8 @@ void ServerEnvironment::step(float dtime) obj->dumpAOMessagesToQueue(m_active_object_messages); }; m_ao_manager.step(dtime, cb_state); + + m_active_object_gauge->set(object_count); } /* @@ -1548,6 +1566,9 @@ void ServerEnvironment::step(float dtime) // Send outdated detached inventories m_server->sendDetachedInventories(PEER_ID_INEXISTENT, true); + + const auto end_time = porting::getTimeUs(); + m_step_time_counter->increment(end_time - start_time); } ServerEnvironment::BlockStatus ServerEnvironment::getBlockStatus(v3s16 blockpos) diff --git a/src/serverenvironment.h b/src/serverenvironment.h index 8733c2dd2..5dc329a60 100644 --- a/src/serverenvironment.h +++ b/src/serverenvironment.h @@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" #include "server/activeobjectmgr.h" #include "util/numeric.h" +#include "util/metricsbackend.h" #include <set> #include <random> @@ -167,19 +168,21 @@ public: std::set<v3s16> &blocks_removed, std::set<v3s16> &blocks_added); - bool contains(v3s16 p){ + bool contains(v3s16 p) const { return (m_list.find(p) != m_list.end()); } - void clear(){ + auto size() const { + return m_list.size(); + } + + void clear() { m_list.clear(); } std::set<v3s16> m_list; std::set<v3s16> m_abm_list; std::set<v3s16> m_forceloaded_list; - -private: }; /* @@ -198,7 +201,7 @@ class ServerEnvironment : public Environment { public: ServerEnvironment(ServerMap *map, ServerScripting *scriptIface, - Server *server, const std::string &path_world); + Server *server, const std::string &path_world, MetricsBackend *mb); ~ServerEnvironment(); Map & getMap(); @@ -487,5 +490,10 @@ private: std::unordered_map<u32, float> m_particle_spawners; std::unordered_map<u32, u16> m_particle_spawner_attachments; + // Environment metrics + MetricCounterPtr m_step_time_counter; + MetricGaugePtr m_active_block_gauge; + MetricGaugePtr m_active_object_gauge; + ServerActiveObject* createSAO(ActiveObjectType type, v3f pos, const std::string &data); }; diff --git a/src/serverlist.cpp b/src/serverlist.cpp index 3bcab3d58..29e3ac9a6 100644 --- a/src/serverlist.cpp +++ b/src/serverlist.cpp @@ -97,6 +97,7 @@ void sendAnnounce(AnnounceAction action, } HTTPFetchRequest fetch_request; + fetch_request.caller = HTTPFETCH_PRINT_ERR; fetch_request.url = g_settings->get("serverlist_url") + std::string("/announce"); fetch_request.method = HTTP_POST; fetch_request.fields["json"] = fastWriteJson(server); diff --git a/src/settings.cpp b/src/settings.cpp index f4de5bec9..0e44ee0bc 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -88,7 +88,7 @@ void SettingsHierarchy::onLayerCreated(int layer, Settings *obj) void SettingsHierarchy::onLayerRemoved(int layer) { - assert(layer >= 0 && layer < layers.size()); + assert(layer >= 0 && layer < (int)layers.size()); layers[layer] = nullptr; if (this == &g_hierarchy && layer == (int)SL_GLOBAL) g_settings = nullptr; @@ -104,8 +104,7 @@ Settings *Settings::createLayer(SettingsLayer sl, const std::string &end_tag) Settings *Settings::getLayer(SettingsLayer sl) { - sanity_check((int)sl >= 0 && sl < SL_TOTAL_COUNT); - return g_hierarchy.layers[(int)sl]; + return g_hierarchy.getLayer(sl); } @@ -660,9 +659,7 @@ bool Settings::getNoiseParamsFromGroup(const std::string &name, bool Settings::exists(const std::string &name) const { - MutexAutoLock lock(m_mutex); - - if (m_settings.find(name) != m_settings.end()) + if (existsLocal(name)) return true; if (auto parent = getParent()) return parent->exists(name); @@ -670,6 +667,14 @@ bool Settings::exists(const std::string &name) const } +bool Settings::existsLocal(const std::string &name) const +{ + MutexAutoLock lock(m_mutex); + + return m_settings.find(name) != m_settings.end(); +} + + std::vector<std::string> Settings::getNames() const { MutexAutoLock lock(m_mutex); diff --git a/src/settings.h b/src/settings.h index 4e32a3488..767d057f9 100644 --- a/src/settings.h +++ b/src/settings.h @@ -172,9 +172,12 @@ public: bool getNoiseParamsFromValue(const std::string &name, NoiseParams &np) const; bool getNoiseParamsFromGroup(const std::string &name, NoiseParams &np) const; - // return all keys used + // return all keys used in this object std::vector<std::string> getNames() const; + // check if setting exists anywhere in the hierarchy bool exists(const std::string &name) const; + // check if setting exists in this object ("locally") + bool existsLocal(const std::string &name) const; /*************************************** diff --git a/src/settings_translation_file.cpp b/src/settings_translation_file.cpp index 49591d1ee..d7811bafa 100644 --- a/src/settings_translation_file.cpp +++ b/src/settings_translation_file.cpp @@ -1,5 +1,5 @@ // This file is automatically generated -// It conatins a bunch of fake gettext calls, to tell xgettext about the strings in config files +// It contains a bunch of fake gettext calls, to tell xgettext about the strings in config files // To update it, refer to the bottom of builtin/mainmenu/dlg_settings_advanced.lua fake_function() { @@ -54,10 +54,10 @@ fake_function() { gettext("The type of joystick"); gettext("Joystick button repetition interval"); gettext("The time in seconds it takes between repeated events\nwhen holding down a joystick button combination."); - gettext("Joystick deadzone"); - gettext("The deadzone of the joystick"); + gettext("Joystick dead zone"); + gettext("The dead zone of the joystick"); gettext("Joystick frustum sensitivity"); - gettext("The sensitivity of the joystick axes for moving the\ningame view frustum around."); + gettext("The sensitivity of the joystick axes for moving the\nin-game view frustum around."); gettext("Forward key"); gettext("Key for moving the player forward.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); gettext("Backward key"); @@ -203,8 +203,8 @@ fake_function() { gettext("Graphics"); gettext("In-Game"); gettext("Basic"); - gettext("Show nametag backgrounds by default"); - gettext("Whether nametag backgrounds should be shown by default.\nMods may still set a background."); + gettext("Show name tag backgrounds by default"); + gettext("Whether name tag backgrounds should be shown by default.\nMods may still set a background."); gettext("VBO"); gettext("Enable vertex buffer objects.\nThis should greatly improve graphics performance."); gettext("Fog"); @@ -215,6 +215,8 @@ fake_function() { gettext("Connects glass if supported by node."); gettext("Smooth lighting"); gettext("Enable smooth lighting with simple ambient occlusion.\nDisable for speed or for different looks."); + gettext("Tradeoffs for performance"); + gettext("Enables tradeoffs that reduce CPU load or increase rendering performance\nat the expense of minor visual glitches that do not impact game playability."); gettext("Clouds"); gettext("Clouds are a client side effect."); gettext("3D clouds"); @@ -225,7 +227,7 @@ fake_function() { gettext("Adds particles when digging a node."); gettext("Filtering"); gettext("Mipmapping"); - gettext("Use mip mapping to scale textures. May slightly increase performance,\nespecially when using a high resolution texture pack.\nGamma correct downscaling is not supported."); + gettext("Use mipmapping to scale textures. May slightly increase performance,\nespecially when using a high resolution texture pack.\nGamma correct downscaling is not supported."); gettext("Anisotropic filtering"); gettext("Use anisotropic filtering when viewing at textures from an angle."); gettext("Bilinear filtering"); @@ -235,7 +237,7 @@ fake_function() { gettext("Clean transparent textures"); 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. 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("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 applied 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"); @@ -264,26 +266,26 @@ fake_function() { 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 strength gamma"); + gettext("Set the shadow strength gamma.\nAdjusts the intensity of in-game dynamic shadows.\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("Texture size to render the shadow map on.\nThis must be a power of two.\nBigger numbers create better shadows but 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("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("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("Enable colored shadows.\nOn true translucent nodes cast colored shadows. This is expensive."); + gettext("Map shadows update frames"); + gettext("Spread a complete update of shadow map over given amount of frames.\nHigher values might make shadows laggy, lower values\nwill consume more resources.\nMinimum value: 1; maximum value: 16"); 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("Set the soft shadow radius size.\nLower values mean sharper shadows, bigger values mean softer shadows.\nMinimum value: 1.0; maximum 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("Set the tilt of Sun/Moon orbit in degrees.\nValue of 0 means no tilt / vertical orbit.\nMinimum value: 0.0; maximum value: 60.0"); gettext("Advanced"); gettext("Arm inertia"); gettext("Arm inertia, gives a more realistic movement of\nthe arm when the camera moves."); @@ -356,7 +358,7 @@ fake_function() { gettext("Crosshair color"); gettext("Crosshair color (R,G,B).\nAlso controls the object crosshair color"); gettext("Crosshair alpha"); - gettext("Crosshair alpha (opaqueness, between 0 and 255).\nAlso controls the object crosshair color"); + gettext("Crosshair alpha (opaqueness, between 0 and 255).\nThis also applies to the object crosshair."); gettext("Recent Chat Messages"); gettext("Maximum number of recent chat messages to show"); gettext("Desynchronize block animation"); @@ -364,7 +366,7 @@ fake_function() { gettext("Maximum hotbar width"); gettext("Maximum proportion of current window to be used for hotbar.\nUseful if there's something to be displayed right or left of hotbar."); gettext("HUD scale factor"); - gettext("Modifies the size of the hudbar elements."); + gettext("Modifies the size of the HUD elements."); gettext("Mesh cache"); gettext("Enables caching of facedir rotated meshes."); gettext("Mapblock mesh generation delay"); @@ -393,6 +395,8 @@ fake_function() { gettext("World-aligned textures may be scaled to span several nodes. However,\nthe server may not send the scale you want, especially if you use\na specially-designed texture pack; with this option, the client tries\nto determine the scale automatically basing on the texture size.\nSee also texture_min_size.\nWarning: This option is EXPERIMENTAL!"); gettext("Show entity selection boxes"); gettext("Show entity selection boxes\nA restart is required after changing this."); + gettext("Transparency Sorting Distance"); + gettext("Distance in nodes at which transparency depth sorting is enabled\nUse this to limit the performance impact of transparency depth sorting"); gettext("Menus"); gettext("Clouds in menu"); gettext("Use a cloud animation for the main menu background."); @@ -406,8 +410,6 @@ fake_function() { gettext("Delay showing tooltips, stated in milliseconds."); gettext("Append item name"); gettext("Append item name to tooltip."); - gettext("FreeType fonts"); - gettext("Whether FreeType fonts are used, requires FreeType support to be compiled in.\nIf disabled, bitmap and XML vectors fonts are used instead."); gettext("Font bold by default"); gettext("Font italic by default"); gettext("Font shadow"); @@ -415,21 +417,25 @@ fake_function() { gettext("Font shadow alpha"); gettext("Opaqueness (alpha) of the shadow behind the default font, between 0 and 255."); gettext("Font size"); - gettext("Font size of the default font in point (pt)."); + gettext("Font size of the default font where 1 unit = 1 pixel at 96 DPI"); + gettext("Font size divisible by"); + gettext("For pixel-style fonts that do not scale well, this ensures that font sizes used\nwith this font will always be divisible by this value, in pixels. For instance,\na pixel font 16 pixels tall should have this set to 16, so it will only ever be\nsized 16, 32, 48, etc., so a mod requesting a size of 25 will get 32."); gettext("Regular font path"); - gettext("Path to the default font.\nIf “freetype” setting is enabled: Must be a TrueType font.\nIf “freetype” setting is disabled: Must be a bitmap or XML vectors font.\nThe fallback font will be used if the font cannot be loaded."); + gettext("Path to the default font. Must be a TrueType font.\nThe fallback font will be used if the font cannot be loaded."); gettext("Bold font path"); gettext("Italic font path"); gettext("Bold and italic font path"); gettext("Monospace font size"); - gettext("Font size of the monospace font in point (pt)."); + gettext("Font size of the monospace font where 1 unit = 1 pixel at 96 DPI"); + gettext("Monospace font size divisible by"); + gettext("For pixel-style fonts that do not scale well, this ensures that font sizes used\nwith this font will always be divisible by this value, in pixels. For instance,\na pixel font 16 pixels tall should have this set to 16, so it will only ever be\nsized 16, 32, 48, etc., so a mod requesting a size of 25 will get 32."); gettext("Monospace font path"); - gettext("Path to the monospace 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 is used for e.g. the console and profiler screen."); + gettext("Path to the monospace font. Must be a TrueType font.\nThis font is used for e.g. the console and profiler screen."); gettext("Bold monospace font path"); gettext("Italic monospace font path"); gettext("Bold and italic monospace font path"); 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("Path of the fallback font. Must be a TrueType font.\nThis font will be used for certain languages or if the default font is unavailable."); gettext("Chat font size"); gettext("Font size of the recent chat text and chat prompt in point (pt).\nValue 0 will use the default font size."); gettext("Screenshot folder"); @@ -441,6 +447,8 @@ fake_function() { gettext("Advanced"); gettext("DPI"); gettext("Adjust dpi configuration to your screen (non X11/Android only) e.g. for 4k screens."); + gettext("Display Density Scaling Factor"); + gettext("Adjust the detected display density, used for scaling UI elements."); gettext("Enable console window"); gettext("Windows systems only: Start Minetest with the command line window in the background.\nContains the same information as the file debug.txt (default name)."); gettext("Sound"); @@ -451,13 +459,17 @@ fake_function() { gettext("Mute sound"); gettext("Whether to mute sounds. You can unmute sounds at any time, unless the\nsound system is disabled (enable_sound=false).\nIn-game, you can toggle the mute state with the mute key or by using the\npause menu."); gettext("Client"); + gettext("Chat weblinks"); + gettext("Clickable weblinks (middle-click or Ctrl+left-click) enabled in chat console output."); + gettext("Weblink color"); + gettext("Optional override for chat weblink color."); gettext("Network"); gettext("Server address"); gettext("Address to connect to.\nLeave this blank to start a local server.\nNote that the address field in the main menu overrides this setting."); gettext("Remote port"); gettext("Port to connect to (UDP).\nNote that the port field in the main menu overrides this setting."); gettext("Prometheus listener address"); - gettext("Prometheus listener address.\nIf minetest is compiled with ENABLE_PROMETHEUS option enabled,\nenable metrics listener for Prometheus on that address.\nMetrics can be fetch on http://127.0.0.1:30000/metrics"); + gettext("Prometheus listener address.\nIf Minetest is compiled with ENABLE_PROMETHEUS option enabled,\nenable metrics listener for Prometheus on that address.\nMetrics can be fetched on http://127.0.0.1:30000/metrics"); gettext("Saving map received from server"); gettext("Save the map received by the client on disk."); gettext("Connect to external media server"); @@ -513,7 +525,7 @@ fake_function() { gettext("Max. packets per iteration"); gettext("Maximum number of packets sent per send step, if you have a slow connection\ntry reducing it, but don't reduce it to a number below double of targeted\nclient number."); gettext("Map Compression Level for Network Transfer"); - gettext("ZLib compression level to use when sending mapblocks to the client.\n-1 - Zlib's default compression level\n0 - no compresson, fastest\n9 - best compression, slowest\n(levels 1-3 use Zlib's \"fast\" method, 4-9 use the normal method)"); + gettext("Compression level to use when sending mapblocks to the client.\n-1 - use default compression level\n0 - least compression, fastest\n9 - best compression, slowest"); gettext("Game"); gettext("Default game"); gettext("Default game when creating a new world.\nThis will be overridden when creating a world from the main menu."); @@ -616,7 +628,7 @@ fake_function() { gettext("Deprecated Lua API handling"); gettext("Handling for deprecated Lua API calls:\n- none: Do not log deprecated calls\n- log: mimic and log backtrace of deprecated call (default).\n- error: abort on usage of deprecated call (suggested for mod developers)."); gettext("Max. clearobjects extra blocks"); - gettext("Number of extra blocks that can be loaded by /clearobjects at once.\nThis is a trade-off between sqlite transaction overhead and\nmemory consumption (4096=100MB, as a rule of thumb)."); + gettext("Number of extra blocks that can be loaded by /clearobjects at once.\nThis is a trade-off between SQLite transaction overhead and\nmemory consumption (4096=100MB, as a rule of thumb)."); gettext("Unload unused server data"); gettext("How much the server will wait before unloading unused mapblocks.\nHigher value is smoother, but will use more RAM."); gettext("Maximum objects per block"); @@ -624,7 +636,7 @@ fake_function() { gettext("Synchronous SQLite"); gettext("See https://www.sqlite.org/pragma.html#pragma_synchronous"); gettext("Map Compression Level for Disk Storage"); - gettext("ZLib compression level to use when saving mapblocks to disk.\n-1 - Zlib's default compression level\n0 - no compresson, fastest\n9 - best compression, slowest\n(levels 1-3 use Zlib's \"fast\" method, 4-9 use the normal method)"); + gettext("Compression level to use when saving mapblocks to disk.\n-1 - use default compression level\n0 - least compression, fastest\n9 - best compression, slowest"); gettext("Dedicated server step"); gettext("Length of a server tick and the interval at which objects are generally updated over\nnetwork."); gettext("Active block management interval"); @@ -673,8 +685,8 @@ fake_function() { gettext("Instrument the action function of Active Block Modifiers on registration."); gettext("Loading Block Modifiers"); gettext("Instrument the action function of Loading Block Modifiers on registration."); - gettext("Chatcommands"); - gettext("Instrument chatcommands on registration."); + gettext("Chat commands"); + gettext("Instrument chat commands on registration."); gettext("Global callbacks"); gettext("Instrument global callback functions on registration.\n(anything you pass to a minetest.register_*() function)"); gettext("Advanced"); @@ -716,7 +728,7 @@ fake_function() { gettext("Map generation limit"); gettext("Limit of map generation, in nodes, in all 6 directions from (0, 0, 0).\nOnly mapchunks completely within the mapgen limit are generated.\nValue is stored per-world."); gettext("Mapgen flags"); - gettext("Global map generation attributes.\nIn Mapgen v6 the 'decorations' flag controls all decorations except trees\nand junglegrass, in all other mapgens this flag controls all decorations."); + gettext("Global map generation attributes.\nIn Mapgen v6 the 'decorations' flag controls all decorations except trees\nand jungle grass, in all other mapgens this flag controls all decorations."); gettext("Biome API temperature and humidity noise parameters"); gettext("Heat noise"); gettext("Temperature variation for biomes."); diff --git a/src/skyparams.h b/src/skyparams.h index 1de494d69..f7f694427 100644 --- a/src/skyparams.h +++ b/src/skyparams.h @@ -68,11 +68,36 @@ struct StarParams f32 scale; }; +struct CloudParams +{ + float density; + video::SColor color_bright; + video::SColor color_ambient; + float thickness; + float height; + v2f speed; +}; + // Utility class for setting default sky, sun, moon, stars values: class SkyboxDefaults { public: - const SkyColor getSkyColorDefaults() + SkyboxDefaults() = delete; + + static const SkyboxParams getSkyDefaults() + { + SkyboxParams sky; + sky.bgcolor = video::SColor(255, 255, 255, 255); + sky.type = "regular"; + sky.clouds = true; + sky.sky_color = getSkyColorDefaults(); + sky.fog_sun_tint = video::SColor(255, 244, 125, 29); + sky.fog_moon_tint = video::SColorf(0.5, 0.6, 0.8, 1).toSColor(); + sky.fog_tint_type = "default"; + return sky; + } + + static const SkyColor getSkyColorDefaults() { SkyColor sky; // Horizon colors @@ -87,7 +112,7 @@ public: return sky; } - const SunParams getSunDefaults() + static const SunParams getSunDefaults() { SunParams sun; sun.visible = true; @@ -99,7 +124,7 @@ public: return sun; } - const MoonParams getMoonDefaults() + static const MoonParams getMoonDefaults() { MoonParams moon; moon.visible = true; @@ -109,7 +134,7 @@ public: return moon; } - const StarParams getStarDefaults() + static const StarParams getStarDefaults() { StarParams stars; stars.visible = true; @@ -118,4 +143,16 @@ public: stars.scale = 1; return stars; } + + static const CloudParams getCloudDefaults() + { + CloudParams clouds; + clouds.density = 0.4f; + clouds.color_bright = video::SColor(229, 240, 240, 255); + clouds.color_ambient = video::SColor(255, 0, 0, 0); + clouds.thickness = 16.0f; + clouds.height = 120; + clouds.speed = v2f(0.0f, -2.0f); + return clouds; + } }; diff --git a/src/terminal_chat_console.cpp b/src/terminal_chat_console.cpp index 9e3d33736..b12261c3b 100644 --- a/src/terminal_chat_console.cpp +++ b/src/terminal_chat_console.cpp @@ -17,6 +17,7 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include <inttypes.h> #include "config.h" #if USE_CURSES #include "version.h" @@ -398,7 +399,7 @@ void TerminalChatConsole::step(int ch) minutes = (float)minutes / 1000 * 60; if (m_game_time) - printw(" | Game %d Time of day %02d:%02d ", + printw(" | Game %" PRIu64 " Time of day %02d:%02d ", m_game_time, hours, minutes); // draw text diff --git a/src/threading/thread.cpp b/src/threading/thread.cpp index 5cfc60995..ef025ac1d 100644 --- a/src/threading/thread.cpp +++ b/src/threading/thread.cpp @@ -121,12 +121,12 @@ bool Thread::start() return false; } - // Allow spawned thread to continue - m_start_finished_mutex.unlock(); - while (!m_running) sleep_ms(1); + // Allow spawned thread to continue + m_start_finished_mutex.unlock(); + m_joinable = true; return true; diff --git a/src/tool.cpp b/src/tool.cpp index 3f639a69e..075c6b3c5 100644 --- a/src/tool.cpp +++ b/src/tool.cpp @@ -183,9 +183,74 @@ void ToolCapabilities::deserializeJson(std::istream &is) } } +static u32 calculateResultWear(const u32 uses, const u16 initial_wear) +{ + if (uses == 0) { + // Trivial case: Infinite uses + return 0; + } + /* Finite uses. This is not trivial, + as the maximum wear is not neatly evenly divisible by + most possible uses numbers. For example, for 128 + uses, the calculation of wear is trivial, as + 65536 / 128 uses = 512 wear, + so the tool will get 512 wear 128 times in its lifetime. + But for a number like 130, this does not work: + 65536 / 130 uses = 504.123... wear. + Since wear must be an integer, we will get + 504*130 = 65520, which would lead to the wrong number + of uses. + + Instead, we partition the "wear range" into blocks: + A block represents a single use and can be + of two possible sizes: normal and oversized. + A normal block is equal to floor(65536 / uses). + An oversized block is a normal block plus 1. + Then we determine how many oversized and normal + blocks we need and finally, whether we add + the normal wear or the oversized wear. + + Example for 130 uses: + * Normal wear = 504 + * Number of normal blocks = 114 + * Oversized wear = 505 + * Number of oversized blocks = 16 + + If we add everything together, we get: + 114*504 + 16*505 = 65536 + */ + u32 result_wear; + u32 wear_normal = ((U16_MAX+1) / uses); + // Will be non-zero if its not evenly divisible + u16 blocks_oversize = (U16_MAX+1) % uses; + // Whether to add one extra wear point in case + // of oversized wear. + u16 wear_extra = 0; + if (blocks_oversize > 0) { + u16 blocks_normal = uses - blocks_oversize; + /* When the wear has reached this value, we + know that wear_normal has been applied + for blocks_normal times, therefore, + only oversized blocks remain. + This also implies the raw tool wear number + increases a bit faster after this point, + but this should be barely noticable by the + player. + */ + u16 wear_extra_at = blocks_normal * wear_normal; + if (initial_wear >= wear_extra_at) { + wear_extra = 1; + } + } + result_wear = wear_normal + wear_extra; + return result_wear; +} + DigParams getDigParams(const ItemGroupList &groups, - const ToolCapabilities *tp) + const ToolCapabilities *tp, + const u16 initial_wear) { + // Group dig_immediate defaults to fixed time and no wear if (tp->groupcaps.find("dig_immediate") == tp->groupcaps.cend()) { switch (itemgroup_get(groups, "dig_immediate")) { @@ -201,7 +266,7 @@ DigParams getDigParams(const ItemGroupList &groups, // Values to be returned (with a bit of conversion) bool result_diggable = false; float result_time = 0.0; - float result_wear = 0.0; + u32 result_wear = 0; std::string result_main_group; int level = itemgroup_get(groups, "level"); @@ -224,22 +289,24 @@ DigParams getDigParams(const ItemGroupList &groups, if (!result_diggable || time < result_time) { result_time = time; result_diggable = true; - if (cap.uses != 0) - result_wear = 1.0 / cap.uses / pow(3.0, leveldiff); - else - result_wear = 0; + // The actual number of uses increases + // exponentially with leveldiff. + // If the levels are equal, real_uses equals cap.uses. + u32 real_uses = cap.uses * pow(3.0, leveldiff); + real_uses = MYMIN(real_uses, U16_MAX); + result_wear = calculateResultWear(real_uses, initial_wear); result_main_group = groupname; } } - u16 wear_i = U16_MAX * result_wear; - return DigParams(result_diggable, result_time, wear_i, result_main_group); + return DigParams(result_diggable, result_time, result_wear, result_main_group); } HitParams getHitParams(const ItemGroupList &armor_groups, - const ToolCapabilities *tp, float time_from_last_punch) + const ToolCapabilities *tp, float time_from_last_punch, + u16 initial_wear) { - s16 damage = 0; + s32 damage = 0; float result_wear = 0.0f; float punch_interval_multiplier = rangelim(time_from_last_punch / tp->full_punch_interval, 0.0f, 1.0f); @@ -249,10 +316,14 @@ HitParams getHitParams(const ItemGroupList &armor_groups, damage += damageGroup.second * punch_interval_multiplier * armor / 100.0; } - if (tp->punch_attack_uses > 0) - result_wear = 1.0f / tp->punch_attack_uses * punch_interval_multiplier; + if (tp->punch_attack_uses > 0) { + result_wear = calculateResultWear(tp->punch_attack_uses, initial_wear); + result_wear *= punch_interval_multiplier; + } + // Keep damage in sane bounds for simplicity + damage = rangelim(damage, -U16_MAX, U16_MAX); - u16 wear_i = U16_MAX * result_wear; + u32 wear_i = (u32) result_wear; return {damage, wear_i}; } @@ -266,7 +337,8 @@ PunchDamageResult getPunchDamage( const ItemGroupList &armor_groups, const ToolCapabilities *toolcap, const ItemStack *punchitem, - float time_from_last_punch + float time_from_last_punch, + u16 initial_wear ){ bool do_hit = true; { @@ -286,7 +358,8 @@ PunchDamageResult getPunchDamage( if(do_hit) { HitParams hitparams = getHitParams(armor_groups, toolcap, - time_from_last_punch); + time_from_last_punch, + punchitem->wear); result.did_punch = true; result.wear = hitparams.wear; result.damage = hitparams.hp; diff --git a/src/tool.h b/src/tool.h index 59dd501f5..8409f59af 100644 --- a/src/tool.h +++ b/src/tool.h @@ -88,10 +88,10 @@ struct DigParams // Digging time in seconds float time; // Caused wear - u16 wear; + u32 wear; // u32 because wear could be 65536 (single-use tool) std::string main_group; - DigParams(bool a_diggable = false, float a_time = 0.0f, u16 a_wear = 0, + DigParams(bool a_diggable = false, float a_time = 0.0f, u32 a_wear = 0, const std::string &a_main_group = ""): diggable(a_diggable), time(a_time), @@ -101,21 +101,24 @@ struct DigParams }; DigParams getDigParams(const ItemGroupList &groups, - const ToolCapabilities *tp); + const ToolCapabilities *tp, + const u16 initial_wear = 0); struct HitParams { - s16 hp; - u16 wear; + s32 hp; + // Caused wear + u32 wear; // u32 because wear could be 65536 (single-use weapon) - HitParams(s16 hp_ = 0, u16 wear_ = 0): + HitParams(s32 hp_ = 0, u32 wear_ = 0): hp(hp_), wear(wear_) {} }; HitParams getHitParams(const ItemGroupList &armor_groups, - const ToolCapabilities *tp, float time_from_last_punch); + const ToolCapabilities *tp, float time_from_last_punch, + u16 initial_wear = 0); HitParams getHitParams(const ItemGroupList &armor_groups, const ToolCapabilities *tp); @@ -135,7 +138,8 @@ PunchDamageResult getPunchDamage( const ItemGroupList &armor_groups, const ToolCapabilities *toolcap, const ItemStack *punchitem, - float time_from_last_punch + float time_from_last_punch, + u16 initial_wear = 0 ); f32 getToolRange(const ItemDefinition &def_selected, const ItemDefinition &def_hand); diff --git a/src/unittest/CMakeLists.txt b/src/unittest/CMakeLists.txt index 5703b8906..84f769e87 100644 --- a/src/unittest/CMakeLists.txt +++ b/src/unittest/CMakeLists.txt @@ -11,14 +11,16 @@ set (UNITTEST_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/test_filepath.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_inventory.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_irrptr.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_lua.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_map.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_map_settings_manager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_mapnode.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_modchannels.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_modmetadatadatabase.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_nodedef.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_noderesolver.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_noise.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_objdef.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/test_player.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_profiler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_random.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_schematic.cpp @@ -33,6 +35,7 @@ set (UNITTEST_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/test_voxelarea.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_voxelalgorithms.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_voxelmanipulator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_gettext.cpp PARENT_SCOPE) set (UNITTEST_CLIENT_SRCS @@ -44,6 +47,7 @@ set (UNITTEST_CLIENT_SRCS set (TEST_WORLDDIR ${CMAKE_CURRENT_SOURCE_DIR}/test_world) set (TEST_SUBGAME_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../games/devtest) +set (TEST_MOD_PATH ${CMAKE_CURRENT_SOURCE_DIR}/test_mod) configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/test_config.h.in" diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index d4841d559..4fd4b930b 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gamedef.h" #include "modchannels.h" #include "content/mods.h" +#include "database/database-dummy.h" #include "util/numeric.h" #include "porting.h" @@ -57,6 +58,7 @@ public: scene::ISceneManager *getSceneManager() { return m_scenemgr; } IRollbackManager *getRollbackManager() { return m_rollbackmgr; } EmergeManager *getEmergeManager() { return m_emergemgr; } + ModMetadataDatabase *getModStorageDatabase() { return m_mod_storage_database; } scene::IAnimatedMesh *getMesh(const std::string &filename) { return NULL; } bool checkLocalPrivilege(const std::string &priv) { return false; } @@ -70,7 +72,6 @@ public: return testmodspec; } virtual const ModSpec* getModSpec(const std::string &modname) const { return NULL; } - virtual std::string getModStoragePath() const { return "."; } virtual bool registerModStorage(ModMetadata *meta) { return true; } virtual void unregisterModStorage(const std::string &name) {} bool joinModChannel(const std::string &channel); @@ -91,11 +92,13 @@ private: scene::ISceneManager *m_scenemgr = nullptr; IRollbackManager *m_rollbackmgr = nullptr; EmergeManager *m_emergemgr = nullptr; + ModMetadataDatabase *m_mod_storage_database = nullptr; std::unique_ptr<ModChannelMgr> m_modchannel_mgr; }; TestGameDef::TestGameDef() : + m_mod_storage_database(new Database_Dummy()), m_modchannel_mgr(new ModChannelMgr()) { m_itemdef = createItemDefManager(); @@ -109,6 +112,7 @@ TestGameDef::~TestGameDef() { delete m_itemdef; delete m_nodedef; + delete m_mod_storage_database; } diff --git a/src/unittest/test_address.cpp b/src/unittest/test_address.cpp index 35d4effb6..f46135577 100644 --- a/src/unittest/test_address.cpp +++ b/src/unittest/test_address.cpp @@ -56,7 +56,7 @@ void TestAddress::testIsLocalhost() UASSERT(!Address(172, 45, 37, 68, 0).isLocalhost()); // v6 - std::unique_ptr<IPv6AddressBytes> ipv6Bytes(new IPv6AddressBytes()); + auto ipv6Bytes = std::make_unique<IPv6AddressBytes>(); std::vector<u8> ipv6RawAddr = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; memcpy(ipv6Bytes->bytes, &ipv6RawAddr[0], 16); UASSERT(Address(ipv6Bytes.get(), 0).isLocalhost()) diff --git a/src/unittest/test_config.h.in b/src/unittest/test_config.h.in index 36850b00d..50d2398e4 100644 --- a/src/unittest/test_config.h.in +++ b/src/unittest/test_config.h.in @@ -4,3 +4,4 @@ #define TEST_WORLDDIR "@TEST_WORLDDIR@" #define TEST_SUBGAME_PATH "@TEST_SUBGAME_PATH@" +#define TEST_MOD_PATH "@TEST_MOD_PATH@" diff --git a/src/unittest/test_connection.cpp b/src/unittest/test_connection.cpp index 23b7e9105..04fea90d6 100644 --- a/src/unittest/test_connection.cpp +++ b/src/unittest/test_connection.cpp @@ -124,7 +124,7 @@ void TestConnection::testHelpers() Address a(127,0,0,1, 10); const u16 seqnum = 34352; - con::BufferedPacket p1 = con::makePacket(a, data1, + con::BufferedPacketPtr p1 = con::makePacket(a, data1, proto_id, peer_id, channel); /* We should now have a packet with this data: @@ -135,10 +135,10 @@ void TestConnection::testHelpers() Data: [7] u8 data1[0] */ - UASSERT(readU32(&p1.data[0]) == proto_id); - UASSERT(readU16(&p1.data[4]) == peer_id); - UASSERT(readU8(&p1.data[6]) == channel); - UASSERT(readU8(&p1.data[7]) == data1[0]); + UASSERT(readU32(&p1->data[0]) == proto_id); + UASSERT(readU16(&p1->data[4]) == peer_id); + UASSERT(readU8(&p1->data[6]) == channel); + UASSERT(readU8(&p1->data[7]) == data1[0]); //infostream<<"initial data1[0]="<<((u32)data1[0]&0xff)<<std::endl; diff --git a/src/unittest/test_eventmanager.cpp b/src/unittest/test_eventmanager.cpp index bb0e59336..fec57f9fe 100644 --- a/src/unittest/test_eventmanager.cpp +++ b/src/unittest/test_eventmanager.cpp @@ -82,7 +82,7 @@ void TestEventManager::testDeregister() void TestEventManager::testRealEvent() { EventManager ev; - std::unique_ptr<EventManagerTest> emt(new EventManagerTest()); + auto emt = std::make_unique<EventManagerTest>(); ev.reg(MtEvent::PLAYER_REGAIN_GROUND, EventManagerTest::eventTest, emt.get()); // Put event & verify event value @@ -93,7 +93,7 @@ void TestEventManager::testRealEvent() void TestEventManager::testRealEventAfterDereg() { EventManager ev; - std::unique_ptr<EventManagerTest> emt(new EventManagerTest()); + auto emt = std::make_unique<EventManagerTest>(); ev.reg(MtEvent::PLAYER_REGAIN_GROUND, EventManagerTest::eventTest, emt.get()); // Put event & verify event value diff --git a/src/unittest/test_gettext.cpp b/src/unittest/test_gettext.cpp new file mode 100644 index 000000000..338a416d7 --- /dev/null +++ b/src/unittest/test_gettext.cpp @@ -0,0 +1,43 @@ +#include "test.h" +#include "porting.h" +#include "gettext.h" + +class TestGettext : public TestBase +{ +public: + TestGettext() { + TestManager::registerTestModule(this); + } + + const char *getName() { return "TestGettext"; } + + void runTests(IGameDef *gamedef); + + void testFmtgettext(); +}; + +static TestGettext g_test_instance; + +void TestGettext::runTests(IGameDef *gamedef) +{ + TEST(testFmtgettext); +} + +// Make sure updatepo.sh does not pick up the strings +#define dummyname fmtgettext + +void TestGettext::testFmtgettext() +{ + std::string buf = dummyname("sample text %d", 12); + UASSERTEQ(std::string, buf, "sample text 12"); + + std::string src, expect; + src = "You are about to join this server with the name \"%s\".\n"; + expect = "You are about to join this server with the name \"foo\".\n"; + for (int i = 0; i < 20; i++) { + src.append("loooong text"); + expect.append("loooong text"); + } + buf = dummyname(src.c_str(), "foo"); + UASSERTEQ(const std::string &, buf, expect); +} diff --git a/src/unittest/test_irrptr.cpp b/src/unittest/test_irrptr.cpp index 3484f1514..2fb7cfcd6 100644 --- a/src/unittest/test_irrptr.cpp +++ b/src/unittest/test_irrptr.cpp @@ -93,7 +93,9 @@ void TestIrrPtr::testRefCounting() #if defined(__clang__) #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wself-assign-overloaded" + #if __clang_major__ >= 7 + #pragma GCC diagnostic ignored "-Wself-assign-overloaded" + #endif #pragma GCC diagnostic ignored "-Wself-move" #endif diff --git a/src/unittest/test_lua.cpp b/src/unittest/test_lua.cpp new file mode 100644 index 000000000..fc8f895af --- /dev/null +++ b/src/unittest/test_lua.cpp @@ -0,0 +1,79 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> +Copyright (C) 2021 TurkeyMcMac, Jude Melton-Houghton <jwmhjwmh@gmail.com> + +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 "test.h" + +extern "C" { +#include <lua.h> +#include <lauxlib.h> +} + +class TestLua : public TestBase +{ +public: + TestLua() { TestManager::registerTestModule(this); } + const char *getName() { return "TestLua"; } + + void runTests(IGameDef *gamedef); + + void testLuaDestructors(); +}; + +static TestLua g_test_instance; + +void TestLua::runTests(IGameDef *gamedef) +{ + TEST(testLuaDestructors); +} + +//////////////////////////////////////////////////////////////////////////////// + +namespace +{ + + class DestructorDetector { + bool *did_destruct; + public: + DestructorDetector(bool *did_destruct) : did_destruct(did_destruct) + { + *did_destruct = false; + } + ~DestructorDetector() + { + *did_destruct = true; + } + }; + +} + +void TestLua::testLuaDestructors() +{ + bool did_destruct = false; + + lua_State *L = luaL_newstate(); + lua_cpcall(L, [](lua_State *L) -> int { + DestructorDetector d(reinterpret_cast<bool*>(lua_touserdata(L, 1))); + luaL_error(L, "error"); + return 0; + }, &did_destruct); + lua_close(L); + + UASSERT(did_destruct); +} diff --git a/src/unittest/test_map.cpp b/src/unittest/test_map.cpp new file mode 100644 index 000000000..82e55e1aa --- /dev/null +++ b/src/unittest/test_map.cpp @@ -0,0 +1,68 @@ +/* +Minetest + +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 "test.h" + +#include <cstdio> +#include "mapblock.h" + +class TestMap : public TestBase +{ +public: + TestMap() { TestManager::registerTestModule(this); } + const char *getName() { return "TestMap"; } + + void runTests(IGameDef *gamedef); + + void testMaxMapgenLimit(); +}; + +static TestMap g_test_instance; + +void TestMap::runTests(IGameDef *gamedef) +{ + TEST(testMaxMapgenLimit); +} + +//////////////////////////////////////////////////////////////////////////////// + +void TestMap::testMaxMapgenLimit() +{ + // limit must end on a mapblock boundary + UASSERTEQ(int, MAX_MAP_GENERATION_LIMIT % MAP_BLOCKSIZE, MAP_BLOCKSIZE - 1); + + // objectpos_over_limit should do exactly this except the last node + // actually spans from LIMIT-0.5 to LIMIT+0.5 + float limit_times_bs = MAX_MAP_GENERATION_LIMIT * BS; + UASSERT(objectpos_over_limit(v3f(limit_times_bs-BS/2)) == false); + UASSERT(objectpos_over_limit(v3f(limit_times_bs)) == false); + UASSERT(objectpos_over_limit(v3f(limit_times_bs+BS/2)) == false); + UASSERT(objectpos_over_limit(v3f(limit_times_bs+BS)) == true); + + UASSERT(objectpos_over_limit(v3f(-limit_times_bs+BS/2)) == false); + UASSERT(objectpos_over_limit(v3f(-limit_times_bs)) == false); + UASSERT(objectpos_over_limit(v3f(-limit_times_bs-BS/2)) == false); + UASSERT(objectpos_over_limit(v3f(-limit_times_bs-BS)) == true); + + // blockpos_over_max_limit + s16 limit_block = MAX_MAP_GENERATION_LIMIT / MAP_BLOCKSIZE; + UASSERT(blockpos_over_max_limit(v3s16(limit_block)) == false); + UASSERT(blockpos_over_max_limit(v3s16(limit_block+1)) == true); + UASSERT(blockpos_over_max_limit(v3s16(-limit_block)) == false); + UASSERT(blockpos_over_max_limit(v3s16(-limit_block-1)) == true); +} diff --git a/src/unittest/test_mod/test_mod/init.lua b/src/unittest/test_mod/test_mod/init.lua new file mode 100644 index 000000000..724a863f5 --- /dev/null +++ b/src/unittest/test_mod/test_mod/init.lua @@ -0,0 +1 @@ +-- deliberately empty diff --git a/src/unittest/test_mod/test_mod/mod.conf b/src/unittest/test_mod/test_mod/mod.conf new file mode 100644 index 000000000..56c64b2d8 --- /dev/null +++ b/src/unittest/test_mod/test_mod/mod.conf @@ -0,0 +1,2 @@ +name = test_mod +description = A mod doing nothing, to test if MINETEST_MOD_PATH is recognised diff --git a/src/unittest/test_modmetadatadatabase.cpp b/src/unittest/test_modmetadatadatabase.cpp new file mode 100644 index 000000000..be97fae5e --- /dev/null +++ b/src/unittest/test_modmetadatadatabase.cpp @@ -0,0 +1,253 @@ +/* +Minetest +Copyright (C) 2018 bendeutsch, Ben Deutsch <ben@bendeutsch.de> +Copyright (C) 2021 TurkeyMcMac, Jude Melton-Houghton <jwmhjwmh@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +// This file is an edited copy of test_authdatabase.cpp + +#include "test.h" + +#include <algorithm> +#include "database/database-files.h" +#include "database/database-sqlite3.h" +#include "filesys.h" + +namespace +{ +// Anonymous namespace to create classes that are only +// visible to this file +// +// These are helpers that return a *ModMetadataDatabase and +// allow us to run the same tests on different databases and +// database acquisition strategies. + +class ModMetadataDatabaseProvider +{ +public: + virtual ~ModMetadataDatabaseProvider() = default; + virtual ModMetadataDatabase *getModMetadataDatabase() = 0; +}; + +class FixedProvider : public ModMetadataDatabaseProvider +{ +public: + FixedProvider(ModMetadataDatabase *mod_meta_db) : mod_meta_db(mod_meta_db){}; + virtual ~FixedProvider(){}; + virtual ModMetadataDatabase *getModMetadataDatabase() { return mod_meta_db; }; + +private: + ModMetadataDatabase *mod_meta_db; +}; + +class FilesProvider : public ModMetadataDatabaseProvider +{ +public: + FilesProvider(const std::string &dir) : dir(dir){}; + virtual ~FilesProvider() + { + if (mod_meta_db) + mod_meta_db->endSave(); + delete mod_meta_db; + } + virtual ModMetadataDatabase *getModMetadataDatabase() + { + if (mod_meta_db) + mod_meta_db->endSave(); + delete mod_meta_db; + mod_meta_db = new ModMetadataDatabaseFiles(dir); + mod_meta_db->beginSave(); + return mod_meta_db; + }; + +private: + std::string dir; + ModMetadataDatabase *mod_meta_db = nullptr; +}; + +class SQLite3Provider : public ModMetadataDatabaseProvider +{ +public: + SQLite3Provider(const std::string &dir) : dir(dir){}; + virtual ~SQLite3Provider() + { + if (mod_meta_db) + mod_meta_db->endSave(); + delete mod_meta_db; + } + virtual ModMetadataDatabase *getModMetadataDatabase() + { + if (mod_meta_db) + mod_meta_db->endSave(); + delete mod_meta_db; + mod_meta_db = new ModMetadataDatabaseSQLite3(dir); + mod_meta_db->beginSave(); + return mod_meta_db; + }; + +private: + std::string dir; + ModMetadataDatabase *mod_meta_db = nullptr; +}; +} + +class TestModMetadataDatabase : public TestBase +{ +public: + TestModMetadataDatabase() { TestManager::registerTestModule(this); } + const char *getName() { return "TestModMetadataDatabase"; } + + void runTests(IGameDef *gamedef); + void runTestsForCurrentDB(); + + void testRecallFail(); + void testCreate(); + void testRecall(); + void testChange(); + void testRecallChanged(); + void testListMods(); + void testRemove(); + +private: + ModMetadataDatabaseProvider *mod_meta_provider; +}; + +static TestModMetadataDatabase g_test_instance; + +void TestModMetadataDatabase::runTests(IGameDef *gamedef) +{ + // fixed directory, for persistence + thread_local const std::string test_dir = getTestTempDirectory(); + + // Each set of tests is run twice for each database type: + // one where we reuse the same ModMetadataDatabase object (to test local caching), + // and one where we create a new ModMetadataDatabase object for each call + // (to test actual persistence). + + rawstream << "-------- Files database (same object)" << std::endl; + + ModMetadataDatabase *mod_meta_db = new ModMetadataDatabaseFiles(test_dir); + mod_meta_provider = new FixedProvider(mod_meta_db); + + runTestsForCurrentDB(); + + delete mod_meta_db; + delete mod_meta_provider; + + // reset database + fs::RecursiveDelete(test_dir + DIR_DELIM + "mod_storage"); + + rawstream << "-------- Files database (new objects)" << std::endl; + + mod_meta_provider = new FilesProvider(test_dir); + + runTestsForCurrentDB(); + + delete mod_meta_provider; + + rawstream << "-------- SQLite3 database (same object)" << std::endl; + + mod_meta_db = new ModMetadataDatabaseSQLite3(test_dir); + mod_meta_provider = new FixedProvider(mod_meta_db); + + runTestsForCurrentDB(); + + delete mod_meta_db; + delete mod_meta_provider; + + // reset database + fs::DeleteSingleFileOrEmptyDirectory(test_dir + DIR_DELIM + "mod_storage.sqlite"); + + rawstream << "-------- SQLite3 database (new objects)" << std::endl; + + mod_meta_provider = new SQLite3Provider(test_dir); + + runTestsForCurrentDB(); + + delete mod_meta_provider; +} + +//////////////////////////////////////////////////////////////////////////////// + +void TestModMetadataDatabase::runTestsForCurrentDB() +{ + TEST(testRecallFail); + TEST(testCreate); + TEST(testRecall); + TEST(testChange); + TEST(testRecallChanged); + TEST(testListMods); + TEST(testRemove); + TEST(testRecallFail); +} + +void TestModMetadataDatabase::testRecallFail() +{ + ModMetadataDatabase *mod_meta_db = mod_meta_provider->getModMetadataDatabase(); + StringMap recalled; + mod_meta_db->getModEntries("mod1", &recalled); + UASSERT(recalled.empty()); +} + +void TestModMetadataDatabase::testCreate() +{ + ModMetadataDatabase *mod_meta_db = mod_meta_provider->getModMetadataDatabase(); + StringMap recalled; + UASSERT(mod_meta_db->setModEntry("mod1", "key1", "value1")); +} + +void TestModMetadataDatabase::testRecall() +{ + ModMetadataDatabase *mod_meta_db = mod_meta_provider->getModMetadataDatabase(); + StringMap recalled; + mod_meta_db->getModEntries("mod1", &recalled); + UASSERT(recalled.size() == 1); + UASSERT(recalled["key1"] == "value1"); +} + +void TestModMetadataDatabase::testChange() +{ + ModMetadataDatabase *mod_meta_db = mod_meta_provider->getModMetadataDatabase(); + StringMap recalled; + UASSERT(mod_meta_db->setModEntry("mod1", "key1", "value2")); +} + +void TestModMetadataDatabase::testRecallChanged() +{ + ModMetadataDatabase *mod_meta_db = mod_meta_provider->getModMetadataDatabase(); + StringMap recalled; + mod_meta_db->getModEntries("mod1", &recalled); + UASSERT(recalled.size() == 1); + UASSERT(recalled["key1"] == "value2"); +} + +void TestModMetadataDatabase::testListMods() +{ + ModMetadataDatabase *mod_meta_db = mod_meta_provider->getModMetadataDatabase(); + UASSERT(mod_meta_db->setModEntry("mod2", "key1", "value1")); + std::vector<std::string> mod_list; + mod_meta_db->listMods(&mod_list); + UASSERT(mod_list.size() == 2); + UASSERT(std::find(mod_list.cbegin(), mod_list.cend(), "mod1") != mod_list.cend()); + UASSERT(std::find(mod_list.cbegin(), mod_list.cend(), "mod2") != mod_list.cend()); +} + +void TestModMetadataDatabase::testRemove() +{ + ModMetadataDatabase *mod_meta_db = mod_meta_provider->getModMetadataDatabase(); + UASSERT(mod_meta_db->removeModEntry("mod1", "key1")); +} diff --git a/src/unittest/test_server_shutdown_state.cpp b/src/unittest/test_server_shutdown_state.cpp index fbb76ff6a..50305e725 100644 --- a/src/unittest/test_server_shutdown_state.cpp +++ b/src/unittest/test_server_shutdown_state.cpp @@ -26,12 +26,10 @@ with this program; if not, write to the Free Software Foundation, Inc., class FakeServer : public Server { public: - // clang-format off FakeServer() : Server("fakeworld", SubgameSpec("fakespec", "fakespec"), true, Address(), true, nullptr) { } - // clang-format on private: void SendChatMessage(session_t peer_id, const ChatMessage &message) @@ -95,7 +93,7 @@ void TestServerShutdownState::testTrigger() void TestServerShutdownState::testTick() { - std::unique_ptr<FakeServer> fakeServer(new FakeServer()); + auto fakeServer = std::make_unique<FakeServer>(); Server::ShutdownState ss; ss.trigger(28.0f, "testtrigger", true); ss.tick(0.0f, fakeServer.get()); diff --git a/src/unittest/test_servermodmanager.cpp b/src/unittest/test_servermodmanager.cpp index e3edb0c32..4c473d8b5 100644 --- a/src/unittest/test_servermodmanager.cpp +++ b/src/unittest/test_servermodmanager.cpp @@ -48,14 +48,20 @@ static TestServerModManager g_test_instance; void TestServerModManager::runTests(IGameDef *gamedef) { const char *saved_env_mt_subgame_path = getenv("MINETEST_SUBGAME_PATH"); + const char *saved_env_mt_mod_path = getenv("MINETEST_MOD_PATH"); #ifdef WIN32 { std::string subgame_path("MINETEST_SUBGAME_PATH="); subgame_path.append(TEST_SUBGAME_PATH); _putenv(subgame_path.c_str()); + + std::string mod_path("MINETEST_MOD_PATH="); + mod_path.append(TEST_MOD_PATH); + _putenv(mod_path.c_str()); } #else setenv("MINETEST_SUBGAME_PATH", TEST_SUBGAME_PATH, 1); + setenv("MINETEST_MOD_PATH", TEST_MOD_PATH, 1); #endif TEST(testCreation); @@ -75,12 +81,21 @@ void TestServerModManager::runTests(IGameDef *gamedef) if (saved_env_mt_subgame_path) subgame_path.append(saved_env_mt_subgame_path); _putenv(subgame_path.c_str()); + + std::string mod_path("MINETEST_MOD_PATH="); + if (saved_env_mt_mod_path) + mod_path.append(saved_env_mt_mod_path); + _putenv(mod_path.c_str()); } #else if (saved_env_mt_subgame_path) setenv("MINETEST_SUBGAME_PATH", saved_env_mt_subgame_path, 1); else unsetenv("MINETEST_SUBGAME_PATH"); + if (saved_env_mt_mod_path) + setenv("MINETEST_MOD_PATH", saved_env_mt_mod_path, 1); + else + unsetenv("MINETEST_MOD_PATH"); #endif } @@ -89,6 +104,7 @@ void TestServerModManager::testCreation() std::string path = std::string(TEST_WORLDDIR) + DIR_DELIM + "world.mt"; Settings world_config; world_config.set("gameid", "devtest"); + world_config.set("load_mod_test_mod", "true"); UASSERTEQ(bool, world_config.updateConfigFile(path.c_str()), true); ServerModManager sm(TEST_WORLDDIR); } @@ -119,16 +135,21 @@ void TestServerModManager::testGetMods() UASSERTEQ(bool, mods.empty(), false); // Ensure we found basenodes mod (part of devtest) + // and test_mod (for testing MINETEST_MOD_PATH). bool default_found = false; + bool test_mod_found = false; for (const auto &m : mods) { if (m.name == "basenodes") default_found = true; + if (m.name == "test_mod") + test_mod_found = true; // Verify if paths are not empty UASSERTEQ(bool, m.path.empty(), false); } UASSERTEQ(bool, default_found, true); + UASSERTEQ(bool, test_mod_found, true); } void TestServerModManager::testGetModspec() diff --git a/src/unittest/test_socket.cpp b/src/unittest/test_socket.cpp index 6d5cf334d..620021b59 100644 --- a/src/unittest/test_socket.cpp +++ b/src/unittest/test_socket.cpp @@ -97,11 +97,11 @@ void TestSocket::testIPv4Socket() UASSERT(strncmp(sendbuffer, rcvbuffer, sizeof(sendbuffer)) == 0); if (address != Address(0, 0, 0, 0, port)) { - UASSERT(sender.getAddress().sin_addr.s_addr == - address.getAddress().sin_addr.s_addr); + UASSERT(sender.getAddress().s_addr == + address.getAddress().s_addr); } else { - UASSERT(sender.getAddress().sin_addr.s_addr == - Address(127, 0, 0, 1, 0).getAddress().sin_addr.s_addr); + UASSERT(sender.getAddress().s_addr == + Address(127, 0, 0, 1, 0).getAddress().s_addr); } } @@ -128,7 +128,7 @@ void TestSocket::testIPv6Socket() socket6.Bind(address6); - try { + { socket6.Send(Address(&bytes, port), sendbuffer, sizeof(sendbuffer)); sleep_ms(50); @@ -142,10 +142,8 @@ void TestSocket::testIPv6Socket() } //FIXME: This fails on some systems UASSERT(strncmp(sendbuffer, rcvbuffer, sizeof(sendbuffer)) == 0); - UASSERT(memcmp(sender.getAddress6().sin6_addr.s6_addr, - Address(&bytes, 0).getAddress6().sin6_addr.s6_addr, 16) == 0); - } catch (SendFailedException &e) { - errorstream << "IPv6 support enabled but not available!" - << std::endl; + + UASSERT(memcmp(sender.getAddress6().s6_addr, + Address(&bytes, 0).getAddress6().s6_addr, 16) == 0); } } diff --git a/src/unittest/test_utilities.cpp b/src/unittest/test_utilities.cpp index 039110d54..98a143d1f 100644 --- a/src/unittest/test_utilities.cpp +++ b/src/unittest/test_utilities.cpp @@ -43,7 +43,6 @@ public: void testPadString(); void testStartsWith(); void testStrEqual(); - void testStringTrim(); void testStrToIntConversion(); void testStringReplace(); void testStringAllowed(); @@ -58,6 +57,7 @@ public: void testStringJoin(); void testEulerConversion(); void testBase64(); + void testSanitizeDirName(); }; static TestUtilities g_test_instance; @@ -75,7 +75,6 @@ void TestUtilities::runTests(IGameDef *gamedef) TEST(testPadString); TEST(testStartsWith); TEST(testStrEqual); - TEST(testStringTrim); TEST(testStrToIntConversion); TEST(testStringReplace); TEST(testStringAllowed); @@ -90,6 +89,7 @@ void TestUtilities::runTests(IGameDef *gamedef) TEST(testStringJoin); TEST(testEulerConversion); TEST(testBase64); + TEST(testSanitizeDirName); } //////////////////////////////////////////////////////////////////////////////// @@ -190,6 +190,8 @@ void TestUtilities::testTrim() UASSERT(trim("dirt_with_grass") == "dirt_with_grass"); UASSERT(trim("\n \t\r Foo bAR \r\n\t\t ") == "Foo bAR"); UASSERT(trim("\n \t\r \r\n\t\t ") == ""); + UASSERT(trim(" a") == "a"); + UASSERT(trim("a ") == "a"); } @@ -255,15 +257,6 @@ void TestUtilities::testStrEqual() } -void TestUtilities::testStringTrim() -{ - UASSERT(trim(" a") == "a"); - UASSERT(trim(" a ") == "a"); - UASSERT(trim("a ") == "a"); - UASSERT(trim("") == ""); -} - - void TestUtilities::testStrToIntConversion() { UASSERT(mystoi("123", 0, 1000) == 123); @@ -392,9 +385,9 @@ void TestUtilities::testIsPowerOfTwo() UASSERT(is_power_of_two(2) == true); UASSERT(is_power_of_two(3) == false); for (int exponent = 2; exponent <= 31; ++exponent) { - UASSERT(is_power_of_two((1 << exponent) - 1) == false); - UASSERT(is_power_of_two((1 << exponent)) == true); - UASSERT(is_power_of_two((1 << exponent) + 1) == false); + UASSERT(is_power_of_two((1U << exponent) - 1) == false); + UASSERT(is_power_of_two((1U << exponent)) == true); + UASSERT(is_power_of_two((1U << exponent) + 1) == false); } UASSERT(is_power_of_two(U32_MAX) == false); } @@ -629,4 +622,17 @@ void TestUtilities::testBase64() 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 +} + + +void TestUtilities::testSanitizeDirName() +{ + UASSERT(sanitizeDirName("a", "~") == "a"); + UASSERT(sanitizeDirName(" ", "~") == "__"); + UASSERT(sanitizeDirName(" a ", "~") == "_a_"); + UASSERT(sanitizeDirName("COM1", "~") == "~COM1"); + UASSERT(sanitizeDirName("COM1", ":") == "_COM1"); + UASSERT(sanitizeDirName("cOm\u00B2", "~") == "~cOm\u00B2"); + UASSERT(sanitizeDirName("cOnIn$", "~") == "~cOnIn$"); + UASSERT(sanitizeDirName(" cOnIn$ ", "~") == "_cOnIn$_"); +} diff --git a/src/unittest/test_voxelarea.cpp b/src/unittest/test_voxelarea.cpp index 6ec0412d5..a79c9778e 100644 --- a/src/unittest/test_voxelarea.cpp +++ b/src/unittest/test_voxelarea.cpp @@ -30,6 +30,7 @@ public: void test_addarea(); void test_pad(); + void test_extent(); void test_volume(); void test_contains_voxelarea(); void test_contains_point(); @@ -65,6 +66,7 @@ void TestVoxelArea::runTests(IGameDef *gamedef) { TEST(test_addarea); TEST(test_pad); + TEST(test_extent); TEST(test_volume); TEST(test_contains_voxelarea); TEST(test_contains_point); @@ -113,10 +115,22 @@ void TestVoxelArea::test_pad() UASSERT(v1.MaxEdge == v3s16(-47, -9347, 969)); } +void TestVoxelArea::test_extent() +{ + VoxelArea v1(v3s16(-1337, -547, -789), v3s16(-147, 447, 669)); + UASSERT(v1.getExtent() == v3s16(1191, 995, 1459)); + + VoxelArea v2(v3s16(32493, -32507, 32752), v3s16(32508, -32492, 32767)); + UASSERT(v2.getExtent() == v3s16(16, 16, 16)); +} + void TestVoxelArea::test_volume() { - VoxelArea v1(v3s16(-1337, 447, -789), v3s16(-147, -9547, 669)); - UASSERTEQ(s32, v1.getVolume(), -184657133); + VoxelArea v1(v3s16(-1337, -547, -789), v3s16(-147, 447, 669)); + UASSERTEQ(s32, v1.getVolume(), 1728980655); + + VoxelArea v2(v3s16(32493, -32507, 32752), v3s16(32508, -32492, 32767)); + UASSERTEQ(s32, v2.getVolume(), 4096); } void TestVoxelArea::test_contains_voxelarea() diff --git a/src/util/Optional.h b/src/util/Optional.h index 9c2842b43..eda7fff89 100644 --- a/src/util/Optional.h +++ b/src/util/Optional.h @@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once +#include <utility> #include "debug.h" struct nullopt_t @@ -43,18 +44,38 @@ class Optional public: Optional() noexcept {} Optional(nullopt_t) noexcept {} + Optional(const T &value) noexcept : m_has_value(true), m_value(value) {} + Optional(T &&value) noexcept : m_has_value(true), m_value(std::move(value)) {} + Optional(const Optional<T> &other) noexcept : m_has_value(other.m_has_value), m_value(other.m_value) + {} + Optional(Optional<T> &&other) noexcept : + m_has_value(other.m_has_value), m_value(std::move(other.m_value)) { + other.m_has_value = false; } - void operator=(nullopt_t) noexcept { m_has_value = false; } + Optional<T> &operator=(nullopt_t) noexcept { m_has_value = false; return *this; } - void operator=(const Optional<T> &other) noexcept + Optional<T> &operator=(const Optional<T> &other) noexcept { + if (&other == this) + return *this; m_has_value = other.m_has_value; m_value = other.m_value; + return *this; + } + + Optional<T> &operator=(Optional<T> &&other) noexcept + { + if (&other == this) + return *this; + m_has_value = other.m_has_value; + m_value = std::move(other.m_value); + other.m_has_value = false; + return *this; } T &value() @@ -71,6 +92,13 @@ public: const T &value_or(const T &def) const { return m_has_value ? m_value : def; } + // Unchecked access consistent with std::optional + T* operator->() { return &m_value; } + const T* operator->() const { return &m_value; } + + T& operator*() { return m_value; } + const T& operator*() const { return m_value; } + bool has_value() const noexcept { return m_has_value; } explicit operator bool() const { return m_has_value; } diff --git a/src/util/areastore.cpp b/src/util/areastore.cpp index 67bfef0c0..bf751476f 100644 --- a/src/util/areastore.cpp +++ b/src/util/areastore.cpp @@ -308,6 +308,7 @@ void SpatialAreaStore::getAreasInArea(std::vector<Area *> *result, SpatialAreaStore::~SpatialAreaStore() { delete m_tree; + delete m_storagemanager; } SpatialAreaStore::SpatialAreaStore() diff --git a/src/util/basic_macros.h b/src/util/basic_macros.h index 334e342e0..3910c6185 100644 --- a/src/util/basic_macros.h +++ b/src/util/basic_macros.h @@ -29,13 +29,19 @@ with this program; if not, write to the Free Software Foundation, Inc., #define CONTAINS(c, v) (std::find((c).begin(), (c).end(), (v)) != (c).end()) // To disable copy constructors and assignment operations for some class -// 'Foobar', add the macro DISABLE_CLASS_COPY(Foobar) as a private member. +// 'Foobar', add the macro DISABLE_CLASS_COPY(Foobar) in the class definition. // Note this also disables copying for any classes derived from 'Foobar' as well // as classes having a 'Foobar' member. #define DISABLE_CLASS_COPY(C) \ C(const C &) = delete; \ C &operator=(const C &) = delete; +// If you have used DISABLE_CLASS_COPY with a class but still want to permit moving +// use this macro to add the default move constructors back. +#define ALLOW_CLASS_MOVE(C) \ + C(C &&other) = default; \ + C &operator=(C &&) = default; + #ifndef _MSC_VER #define UNUSED_ATTRIBUTE __attribute__ ((unused)) #else diff --git a/src/util/ieee_float.cpp b/src/util/ieee_float.cpp index 887258921..b73763c55 100644 --- a/src/util/ieee_float.cpp +++ b/src/util/ieee_float.cpp @@ -39,7 +39,7 @@ f32 u32Tof32Slow(u32 i) if (exp == 0xFF) { // Inf/NaN if (imant == 0) { - if (std::numeric_limits<f32>::has_infinity) + if (std::numeric_limits<f32>::has_infinity) return sign ? -std::numeric_limits<f32>::infinity() : std::numeric_limits<f32>::infinity(); return sign ? std::numeric_limits<f32>::max() : diff --git a/src/util/metricsbackend.cpp b/src/util/metricsbackend.cpp index 4454557a3..63b49ac0a 100644 --- a/src/util/metricsbackend.cpp +++ b/src/util/metricsbackend.cpp @@ -18,6 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "metricsbackend.h" +#include "util/thread.h" #if USE_PROMETHEUS #include <prometheus/exposer.h> #include <prometheus/registry.h> @@ -27,18 +28,78 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" #endif +/* Plain implementation */ + +class SimpleMetricCounter : public MetricCounter +{ +public: + SimpleMetricCounter() : MetricCounter(), m_counter(0.0) {} + + virtual ~SimpleMetricCounter() {} + + void increment(double number) override + { + MutexAutoLock lock(m_mutex); + m_counter += number; + } + double get() const override + { + MutexAutoLock lock(m_mutex); + return m_counter; + } + +private: + mutable std::mutex m_mutex; + double m_counter; +}; + +class SimpleMetricGauge : public MetricGauge +{ +public: + SimpleMetricGauge() : MetricGauge(), m_gauge(0.0) {} + + virtual ~SimpleMetricGauge() {} + + void increment(double number) override + { + MutexAutoLock lock(m_mutex); + m_gauge += number; + } + void decrement(double number) override + { + MutexAutoLock lock(m_mutex); + m_gauge -= number; + } + void set(double number) override + { + MutexAutoLock lock(m_mutex); + m_gauge = number; + } + double get() const override + { + MutexAutoLock lock(m_mutex); + return m_gauge; + } + +private: + mutable std::mutex m_mutex; + double m_gauge; +}; + MetricCounterPtr MetricsBackend::addCounter( - const std::string &name, const std::string &help_str) + const std::string &name, const std::string &help_str, Labels labels) { - return std::make_shared<SimpleMetricCounter>(name, help_str); + return std::make_shared<SimpleMetricCounter>(); } MetricGaugePtr MetricsBackend::addGauge( - const std::string &name, const std::string &help_str) + const std::string &name, const std::string &help_str, Labels labels) { - return std::make_shared<SimpleMetricGauge>(name, help_str); + return std::make_shared<SimpleMetricGauge>(); } +/* Prometheus backend */ + #if USE_PROMETHEUS class PrometheusMetricCounter : public MetricCounter @@ -47,13 +108,14 @@ public: PrometheusMetricCounter() = delete; PrometheusMetricCounter(const std::string &name, const std::string &help_str, + MetricsBackend::Labels labels, std::shared_ptr<prometheus::Registry> registry) : MetricCounter(), m_family(prometheus::BuildCounter() .Name(name) .Help(help_str) .Register(*registry)), - m_counter(m_family.Add({})) + m_counter(m_family.Add(labels)) { } @@ -73,13 +135,14 @@ public: PrometheusMetricGauge() = delete; PrometheusMetricGauge(const std::string &name, const std::string &help_str, + MetricsBackend::Labels labels, std::shared_ptr<prometheus::Registry> registry) : MetricGauge(), m_family(prometheus::BuildGauge() .Name(name) .Help(help_str) .Register(*registry)), - m_gauge(m_family.Add({})) + m_gauge(m_family.Add(labels)) { } @@ -99,8 +162,7 @@ class PrometheusMetricsBackend : public MetricsBackend { public: PrometheusMetricsBackend(const std::string &addr) : - MetricsBackend(), m_exposer(std::unique_ptr<prometheus::Exposer>( - new prometheus::Exposer(addr))), + MetricsBackend(), m_exposer(std::make_unique<prometheus::Exposer>(addr)), m_registry(std::make_shared<prometheus::Registry>()) { m_exposer->RegisterCollectable(m_registry); @@ -108,10 +170,12 @@ public: virtual ~PrometheusMetricsBackend() {} - virtual MetricCounterPtr addCounter( - const std::string &name, const std::string &help_str); - virtual MetricGaugePtr addGauge( - const std::string &name, const std::string &help_str); + MetricCounterPtr addCounter( + const std::string &name, const std::string &help_str, + Labels labels = {}) override; + MetricGaugePtr addGauge( + const std::string &name, const std::string &help_str, + Labels labels = {}) override; private: std::unique_ptr<prometheus::Exposer> m_exposer; @@ -119,15 +183,15 @@ private: }; MetricCounterPtr PrometheusMetricsBackend::addCounter( - const std::string &name, const std::string &help_str) + const std::string &name, const std::string &help_str, Labels labels) { - return std::make_shared<PrometheusMetricCounter>(name, help_str, m_registry); + return std::make_shared<PrometheusMetricCounter>(name, help_str, labels, m_registry); } MetricGaugePtr PrometheusMetricsBackend::addGauge( - const std::string &name, const std::string &help_str) + const std::string &name, const std::string &help_str, Labels labels) { - return std::make_shared<PrometheusMetricGauge>(name, help_str, m_registry); + return std::make_shared<PrometheusMetricGauge>(name, help_str, labels, m_registry); } MetricsBackend *createPrometheusMetricsBackend() diff --git a/src/util/metricsbackend.h b/src/util/metricsbackend.h index c37306392..644c73325 100644 --- a/src/util/metricsbackend.h +++ b/src/util/metricsbackend.h @@ -19,8 +19,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include <memory> +#include <string> +#include <utility> #include "config.h" -#include "util/thread.h" class MetricCounter { @@ -35,38 +36,6 @@ public: typedef std::shared_ptr<MetricCounter> MetricCounterPtr; -class SimpleMetricCounter : public MetricCounter -{ -public: - SimpleMetricCounter() = delete; - - virtual ~SimpleMetricCounter() {} - - SimpleMetricCounter(const std::string &name, const std::string &help_str) : - MetricCounter(), m_name(name), m_help_str(help_str), - m_counter(0.0) - { - } - - virtual void increment(double number) - { - MutexAutoLock lock(m_mutex); - m_counter += number; - } - virtual double get() const - { - MutexAutoLock lock(m_mutex); - return m_counter; - } - -private: - std::string m_name; - std::string m_help_str; - - mutable std::mutex m_mutex; - double m_counter; -}; - class MetricGauge { public: @@ -81,47 +50,6 @@ public: typedef std::shared_ptr<MetricGauge> MetricGaugePtr; -class SimpleMetricGauge : public MetricGauge -{ -public: - SimpleMetricGauge() = delete; - - SimpleMetricGauge(const std::string &name, const std::string &help_str) : - MetricGauge(), m_name(name), m_help_str(help_str), m_gauge(0.0) - { - } - - virtual ~SimpleMetricGauge() {} - - virtual void increment(double number) - { - MutexAutoLock lock(m_mutex); - m_gauge += number; - } - virtual void decrement(double number) - { - MutexAutoLock lock(m_mutex); - m_gauge -= number; - } - virtual void set(double number) - { - MutexAutoLock lock(m_mutex); - m_gauge = number; - } - virtual double get() const - { - MutexAutoLock lock(m_mutex); - return m_gauge; - } - -private: - std::string m_name; - std::string m_help_str; - - mutable std::mutex m_mutex; - double m_gauge; -}; - class MetricsBackend { public: @@ -129,10 +57,14 @@ public: virtual ~MetricsBackend() {} + typedef std::initializer_list<std::pair<const std::string, std::string>> Labels; + virtual MetricCounterPtr addCounter( - const std::string &name, const std::string &help_str); + const std::string &name, const std::string &help_str, + Labels labels = {}); virtual MetricGaugePtr addGauge( - const std::string &name, const std::string &help_str); + const std::string &name, const std::string &help_str, + Labels labels = {}); }; #if USE_PROMETHEUS diff --git a/src/util/png.cpp b/src/util/png.cpp index 7ac2e94a1..698cbc9a5 100755 --- a/src/util/png.cpp +++ b/src/util/png.cpp @@ -37,11 +37,11 @@ static void writeChunk(std::ostringstream &target, const std::string &chunk_str) std::string encodePNG(const u8 *data, u32 width, u32 height, s32 compression) { - auto file = std::ostringstream(std::ios::binary); + std::ostringstream file(std::ios::binary); file << "\x89PNG\r\n\x1a\n"; { - auto IHDR = std::ostringstream(std::ios::binary); + std::ostringstream IHDR(std::ios::binary); IHDR << "IHDR"; writeU32(IHDR, width); writeU32(IHDR, height); @@ -51,9 +51,9 @@ std::string encodePNG(const u8 *data, u32 width, u32 height, s32 compression) } { - auto IDAT = std::ostringstream(std::ios::binary); + std::ostringstream IDAT(std::ios::binary); IDAT << "IDAT"; - auto scanlines = std::ostringstream(std::ios::binary); + std::ostringstream scanlines(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); diff --git a/src/util/pointer.h b/src/util/pointer.h index 7fc5de551..245ac85bf 100644 --- a/src/util/pointer.h +++ b/src/util/pointer.h @@ -22,6 +22,21 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes.h" #include "debug.h" // For assert() #include <cstring> +#include <memory> // std::shared_ptr + + +template<typename T> class ConstSharedPtr { +public: + ConstSharedPtr(T *ptr) : ptr(ptr) {} + ConstSharedPtr(const std::shared_ptr<T> &ptr) : ptr(ptr) {} + + const T* get() const noexcept { return ptr.get(); } + const T& operator*() const noexcept { return *ptr.get(); } + const T* operator->() const noexcept { return ptr.get(); } + +private: + std::shared_ptr<T> ptr; +}; template <typename T> class Buffer @@ -40,17 +55,11 @@ public: else data = NULL; } - Buffer(const Buffer &buffer) - { - m_size = buffer.m_size; - if(m_size != 0) - { - data = new T[buffer.m_size]; - memcpy(data, buffer.data, buffer.m_size); - } - else - data = NULL; - } + + // Disable class copy + Buffer(const Buffer &) = delete; + Buffer &operator=(const Buffer &) = delete; + Buffer(Buffer &&buffer) { m_size = buffer.m_size; @@ -81,21 +90,6 @@ public: drop(); } - Buffer& operator=(const Buffer &buffer) - { - if(this == &buffer) - return *this; - drop(); - m_size = buffer.m_size; - if(m_size != 0) - { - data = new T[buffer.m_size]; - memcpy(data, buffer.data, buffer.m_size); - } - else - data = NULL; - return *this; - } Buffer& operator=(Buffer &&buffer) { if(this == &buffer) @@ -113,6 +107,18 @@ public: return *this; } + void copyTo(Buffer &buffer) const + { + buffer.drop(); + buffer.m_size = m_size; + if (m_size != 0) { + buffer.data = new T[m_size]; + memcpy(buffer.data, data, m_size); + } else { + buffer.data = nullptr; + } + } + T & operator[](unsigned int i) const { return data[i]; diff --git a/src/util/srp.cpp b/src/util/srp.cpp index ceb2fef9e..daa7f332b 100644 --- a/src/util/srp.cpp +++ b/src/util/srp.cpp @@ -354,7 +354,7 @@ static size_t hash_length(SRP_HashAlgorithm alg) case SRP_SHA384: return SHA384_DIGEST_LENGTH; case SRP_SHA512: return SHA512_DIGEST_LENGTH; */ - default: return -1; + default: return 0; }; } // clang-format on @@ -422,7 +422,7 @@ static SRP_Result H_nn( } static SRP_Result H_ns(mpz_t result, SRP_HashAlgorithm alg, const unsigned char *n, - size_t len_n, const unsigned char *bytes, uint32_t len_bytes) + size_t len_n, const unsigned char *bytes, size_t len_bytes) { unsigned char buff[SHA512_DIGEST_LENGTH]; size_t nbytes = len_n + len_bytes; diff --git a/src/util/stream.h b/src/util/stream.h new file mode 100644 index 000000000..2e61b46d2 --- /dev/null +++ b/src/util/stream.h @@ -0,0 +1,70 @@ +/* +Minetest +Copyright (C) 2022 Minetest Authors + +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 <iostream> +#include <string> +#include <functional> + +template<int BufferLength, typename Emitter = std::function<void(const std::string &)> > +class StringStreamBuffer : public std::streambuf { +public: + StringStreamBuffer(Emitter emitter) : m_emitter(emitter) { + buffer_index = 0; + } + + int overflow(int c) { + push_back(c); + return c; + } + + void push_back(char c) { + if (c == '\n' || c == '\r') { + if (buffer_index) + m_emitter(std::string(buffer, buffer_index)); + buffer_index = 0; + } else { + buffer[buffer_index++] = c; + if (buffer_index >= BufferLength) { + m_emitter(std::string(buffer, buffer_index)); + buffer_index = 0; + } + } + } + + std::streamsize xsputn(const char *s, std::streamsize n) { + for (int i = 0; i < n; ++i) + push_back(s[i]); + return n; + } +private: + Emitter m_emitter; + char buffer[BufferLength]; + int buffer_index; +}; + +class DummyStreamBuffer : public std::streambuf { + int overflow(int c) { + return c; + } + std::streamsize xsputn(const char *s, std::streamsize n) { + return n; + } +}; diff --git a/src/util/string.cpp b/src/util/string.cpp index eec5ab4cd..b805b2f78 100644 --- a/src/util/string.cpp +++ b/src/util/string.cpp @@ -39,8 +39,13 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <windows.h> #endif -#if defined(_ICONV_H_) && (defined(__FreeBSD__) || defined(__NetBSD__) || \ - defined(__OpenBSD__) || defined(__DragonFly__)) +#ifdef __NetBSD__ + #include <sys/param.h> + #if __NetBSD_Version__ <= 999001500 + #define BSD_ICONV_USED + #endif +#elif defined(_ICONV_H_) && (defined(__FreeBSD__) || defined(__OpenBSD__) || \ + defined(__DragonFly__)) #define BSD_ICONV_USED #endif @@ -79,6 +84,14 @@ static bool convert(const char *to, const char *from, char *outbuf, #ifdef __ANDROID__ // On Android iconv disagrees how big a wchar_t is for whatever reason const char *DEFAULT_ENCODING = "UTF-32LE"; +#elif defined(__NetBSD__) + // NetBSD does not allow "WCHAR_T" as a charset input to iconv. + #include <sys/endian.h> + #if BYTE_ORDER == BIG_ENDIAN + const char *DEFAULT_ENCODING = "UTF-32BE"; + #else + const char *DEFAULT_ENCODING = "UTF-32LE"; + #endif #else const char *DEFAULT_ENCODING = "WCHAR_T"; #endif @@ -94,7 +107,7 @@ std::wstring utf8_to_wide(const std::string &input) std::wstring out; out.resize(outbuf_size / sizeof(wchar_t)); -#ifdef __ANDROID__ +#if defined(__ANDROID__) || defined(__NetBSD__) SANITY_CHECK(sizeof(wchar_t) == 4); #endif @@ -481,6 +494,7 @@ const static std::unordered_map<std::string, u32> s_named_colors = { {"plum", 0xdda0dd}, {"powderblue", 0xb0e0e6}, {"purple", 0x800080}, + {"rebeccapurple", 0x663399}, {"red", 0xff0000}, {"rosybrown", 0xbc8f8f}, {"royalblue", 0x4169e1}, @@ -808,9 +822,11 @@ std::wstring translate_string(const std::wstring &s) #endif } -static const std::array<std::wstring, 22> disallowed_dir_names = { +static const std::array<std::wstring, 30> disallowed_dir_names = { // Problematic filenames from here: // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#file-and-directory-names + // Plus undocumented values from here: + // https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html L"CON", L"PRN", L"AUX", @@ -824,6 +840,9 @@ static const std::array<std::wstring, 22> disallowed_dir_names = { L"COM7", L"COM8", L"COM9", + L"COM\u00B2", + L"COM\u00B3", + L"COM\u00B9", L"LPT1", L"LPT2", L"LPT3", @@ -833,6 +852,11 @@ static const std::array<std::wstring, 22> disallowed_dir_names = { L"LPT7", L"LPT8", L"LPT9", + L"LPT\u00B2", + L"LPT\u00B3", + L"LPT\u00B9", + L"CONIN$", + L"CONOUT$", }; /** @@ -840,12 +864,7 @@ static const std::array<std::wstring, 22> disallowed_dir_names = { */ static const std::wstring disallowed_path_chars = L"<>:\"/\\|?*."; -/** - * Sanitize the name of a new directory. This consists of two stages: - * 1. Check for 'reserved filenames' that can't be used on some filesystems - * and add a prefix to them - * 2. Remove 'unsafe' characters from the name by replacing them with '_' - */ + std::string sanitizeDirName(const std::string &str, const std::string &optional_prefix) { std::wstring safe_name = utf8_to_wide(str); @@ -857,7 +876,18 @@ std::string sanitizeDirName(const std::string &str, const std::string &optional_ } } - for (unsigned long i = 0; i < safe_name.length(); i++) { + // Replace leading and trailing spaces with underscores. + size_t start = safe_name.find_first_not_of(L' '); + size_t end = safe_name.find_last_not_of(L' '); + if (start == std::wstring::npos || end == std::wstring::npos) + start = end = safe_name.size(); + for (size_t i = 0; i < start; i++) + safe_name[i] = L'_'; + for (size_t i = end + 1; i < safe_name.size(); i++) + safe_name[i] = L'_'; + + // Replace other disallowed characters with underscores + for (size_t i = 0; i < safe_name.length(); i++) { bool is_valid = true; // Unlikely, but control characters should always be blacklisted @@ -869,8 +899,24 @@ std::string sanitizeDirName(const std::string &str, const std::string &optional_ } if (!is_valid) - safe_name[i] = '_'; + safe_name[i] = L'_'; } return wide_to_utf8(safe_name); } + + +void safe_print_string(std::ostream &os, const std::string &str) +{ + std::ostream::fmtflags flags = os.flags(); + os << std::hex; + for (const char c : str) { + if (IS_ASCII_PRINTABLE_CHAR(c) || IS_UTF8_MULTB_START(c) || + IS_UTF8_MULTB_INNER(c) || c == '\n' || c == '\t') { + os << c; + } else { + os << '<' << std::setw(2) << (int)c << '>'; + } + } + os.setf(flags); +} diff --git a/src/util/string.h b/src/util/string.h index 21f1d6877..aa4329f2f 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -295,11 +295,11 @@ inline std::string lowercase(const std::string &str) inline std::string trim(const std::string &str) { size_t front = 0; + size_t back = str.size(); - while (std::isspace(str[front])) + while (front < back && std::isspace(str[front])) ++front; - size_t back = str.size(); while (back > front && std::isspace(str[back - 1])) --back; @@ -410,7 +410,7 @@ DEFINE_STD_TOSTRING_FLOATINGPOINT(long double) template <typename T> inline wstring to_wstring(T val) { - return utf8_to_wide(to_string(val)); + return utf8_to_wide(to_string(val)); } } #endif @@ -661,28 +661,49 @@ inline const char *bool_to_cstr(bool val) return val ? "true" : "false"; } +/** + * Converts a duration in seconds to a pretty-printed duration in + * days, hours, minutes and seconds. + * + * @param sec duration in seconds + * @return pretty-printed duration + */ inline const std::string duration_to_string(int sec) { + std::ostringstream ss; + const char *neg = ""; + if (sec < 0) { + sec = -sec; + neg = "-"; + } + int total_sec = sec; int min = sec / 60; sec %= 60; int hour = min / 60; min %= 60; + int day = hour / 24; + hour %= 24; + + if (day > 0) { + ss << neg << day << "d"; + if (hour > 0 || min > 0 || sec > 0) + ss << " "; + } - std::stringstream ss; if (hour > 0) { - ss << hour << "h"; + ss << neg << hour << "h"; if (min > 0 || sec > 0) ss << " "; } if (min > 0) { - ss << min << "min"; + ss << neg << min << "min"; if (sec > 0) ss << " "; } - if (sec > 0) { - ss << sec << "s"; + if (sec > 0 || total_sec == 0) { + ss << neg << sec << "s"; } return ss.str(); @@ -728,7 +749,15 @@ inline irr::core::stringw utf8_to_stringw(const std::string &input) /** * Sanitize the name of a new directory. This consists of two stages: * 1. Check for 'reserved filenames' that can't be used on some filesystems - * and prefix them + * and add a prefix to them * 2. Remove 'unsafe' characters from the name by replacing them with '_' */ std::string sanitizeDirName(const std::string &str, const std::string &optional_prefix); + +/** + * Prints a sanitized version of a string without control characters. + * '\t' and '\n' are allowed, as are UTF-8 control characters (e.g. RTL). + * ASCII control characters are replaced with their hex encoding in angle + * brackets (e.g. "a\x1eb" -> "a<1e>b"). + */ +void safe_print_string(std::ostream &os, const std::string &str); diff --git a/src/version.cpp b/src/version.cpp index c555f30af..f2aac37df 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -37,7 +37,6 @@ const char *g_build_info = #ifndef SERVER "USE_GETTEXT=" STR(USE_GETTEXT) "\n" "USE_SOUND=" STR(USE_SOUND) "\n" - "USE_FREETYPE=" STR(USE_FREETYPE) "\n" #endif "STATIC_SHAREDIR=" STR(STATIC_SHAREDIR) #if USE_GETTEXT && defined(STATIC_LOCALEDIR) |