diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 24cee5aae..32b7f5673 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -15,11 +15,10 @@ body: label: Minetest version description: | Paste the Minetest version below. - If you are on a devel version, please add a git commit hash. - You can use `minetest --version` to find it. - You can also refer to the "About" tab of the menu. + If you are on a dev version, please also indicate the git commit hash. + Refer to the "About" tab of the menu or run `minetest --version` on the command line. placeholder: | - Example: + Example: Minetest 5.7.0-dev-ca13c51 (Linux) Using Irrlicht 1.9.0mt9 Using LuaJIT 2.1.0-beta3 @@ -33,59 +32,53 @@ body: render: "true" validations: required: true - - type: input - attributes: - label: Active renderer - description: For graphical and input-related issues. You can find these in the About tab in the mainmenu. - placeholder: "Example: OpenGL 4.6.0" - validations: - required: false - type: input attributes: label: Irrlicht device - description: + description: placeholder: "Example: X11" validations: required: false - type: input attributes: label: Operating system and version - description: It is recommended to upgrade your operating system to see if the problem still exists. + description: It is recommended to upgrade your operating system to see if the problem persists. placeholder: "Example: Ubuntu 22.04" validations: required: true - type: input attributes: label: CPU model - description: Usually found in system settings. - placeholder: "Example: Intel i5-2410M (4) @ 2.900GHz" + description: Usually found in OS/system settings. + placeholder: "Example: Intel Core i5-2410M" validations: required: false - type: markdown attributes: - value: The GPU model and OpenGL version can be omitted if the bug is not a graphical issue. + value: The GPU model and renderer can be omitted if the bug is not a graphical issue. - type: input attributes: label: GPU model - description: Usually found in system settings. - placeholder: "Example: NVIDA GeForce RTX 4090" + description: Usually found in OS/system settings. + placeholder: "Example: NVIDIA GeForce GTX 1660" validations: required: false - type: input attributes: - label: OpenGL version - placeholder: "Example: 4.6" + label: Active renderer + description: You can find this in the "About" tab in the main menu. + placeholder: "Example: OpenGL 4.6.0" validations: required: false - type: textarea - attributes: + attributes: label: Summary description: Describe your problem here. validations: required: true - type: textarea - attributes: + attributes: label: Steps to reproduce - description: Explain how the problem has happened, providing a minimal test (i.e. a code snippet reduced to the bone) where possible. + description: Explain how the problem has happened, providing a minimal test (e.g. a minimized code snippet) where possible. validations: required: true diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index ede978268..ffcae5cf5 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -27,7 +27,7 @@ jobs: build: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install deps run: | sudo apt-get update @@ -35,22 +35,22 @@ jobs: - name: Build with Gradle run: cd android; ./gradlew assemblerelease - name: Save armeabi artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Minetest-armeabi-v7a.apk path: android/app/build/outputs/apk/release/app-armeabi-v7a-release-unsigned.apk - name: Save arm64 artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Minetest-arm64-v8a.apk path: android/app/build/outputs/apk/release/app-arm64-v8a-release-unsigned.apk - name: Save x86 artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Minetest-x86.apk path: android/app/build/outputs/apk/release/app-x86-release-unsigned.apk - name: Save x86_64 artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Minetest-x86_64.apk path: android/app/build/outputs/apk/release/app-x86_64-release-unsigned.apk diff --git a/.github/workflows/cpp_lint.yml b/.github/workflows/cpp_lint.yml index f15ba705f..51c6c8273 100644 --- a/.github/workflows/cpp_lint.yml +++ b/.github/workflows/cpp_lint.yml @@ -30,7 +30,7 @@ jobs: clang_tidy: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install deps run: | source ./util/ci/common.sh diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 392bbfc6f..3d831d92f 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -37,7 +37,7 @@ jobs: gcc_7: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install deps run: | source ./util/ci/common.sh @@ -58,7 +58,7 @@ jobs: gcc_12: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install deps run: | source ./util/ci/common.sh @@ -82,11 +82,11 @@ jobs: clang_7: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install deps run: | source ./util/ci/common.sh - install_linux_deps clang-7 valgrind + install_linux_deps clang-7 llvm - name: Build run: | @@ -94,20 +94,17 @@ jobs: env: CC: clang-7 CXX: clang++-7 + CMAKE_FLAGS: '-DCMAKE_C_FLAGS="-fsanitize=address" -DCMAKE_CXX_FLAGS="-fsanitize=address"' - name: Unittest run: | ./bin/minetest --run-unittests - - name: Valgrind - run: | - valgrind --leak-check=full --leak-check-heuristics=all --undef-value-errors=no --error-exitcode=9 ./bin/minetest --run-unittests - # Current clang version clang_14: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install deps run: | source ./util/ci/common.sh @@ -133,7 +130,7 @@ jobs: name: "clang_9 (PROMETHEUS=1)" runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install deps run: | source ./util/ci/common.sh @@ -159,7 +156,7 @@ jobs: name: "Docker image" runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build docker image run: | docker build . -t minetest:latest diff --git a/.github/workflows/lua.yml b/.github/workflows/lua.yml index 21cbbdcee..ff556908f 100644 --- a/.github/workflows/lua.yml +++ b/.github/workflows/lua.yml @@ -19,7 +19,7 @@ jobs: name: "Compile and run multiplayer tests" runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install deps run: | source ./util/ci/common.sh @@ -43,11 +43,11 @@ jobs: steps: - - uses: actions/checkout@v3 - - uses: leafo/gh-actions-lua@v9 + - uses: actions/checkout@v4 + - uses: leafo/gh-actions-lua@v10 with: luaVersion: "5.1.5" - - uses: leafo/gh-actions-luarocks@v4 + - uses: leafo/gh-actions-luarocks@v4.3.0 - name: Install LuaJIT run: | diff --git a/.github/workflows/lua_api_deploy.yml b/.github/workflows/lua_api_deploy.yml index 574e3ba3e..bcefb0972 100644 --- a/.github/workflows/lua_api_deploy.yml +++ b/.github/workflows/lua_api_deploy.yml @@ -19,10 +19,10 @@ jobs: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.11 @@ -36,13 +36,13 @@ jobs: ./build.sh - name: Setup Pages - uses: actions/configure-pages@v3 + uses: actions/configure-pages@v4 - name: Upload artifact - uses: actions/upload-pages-artifact@v2 + uses: actions/upload-pages-artifact@v3 with: path: 'public/' - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v2 + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index d93057364..56b3f9fdc 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -25,7 +25,7 @@ jobs: build: runs-on: macos-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install deps run: | source ./util/ci/common.sh @@ -56,7 +56,7 @@ jobs: cd build cpack -G ZIP -B macos - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: minetest-macos path: ./build/macos/*.zip diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 4544f3171..eb9a13fac 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -32,17 +32,17 @@ jobs: name: "MinGW cross-compiler (32-bit)" runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install compiler run: | sudo apt-get update && sudo apt-get install -y gettext - sudo ./util/buildbot/download_toolchain.sh i686 /usr + sudo ./util/buildbot/download_toolchain.sh /usr - name: Build run: | EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin32.sh B - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: mingw32 path: B/build/*.zip @@ -52,17 +52,17 @@ jobs: name: "MinGW cross-compiler (64-bit)" runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install compiler run: | sudo apt-get update && sudo apt-get install -y gettext - sudo ./util/buildbot/download_toolchain.sh x86_64 /usr + sudo ./util/buildbot/download_toolchain.sh /usr - name: Build run: | EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin64.sh B - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: mingw64 path: B/build/*.zip @@ -74,7 +74,7 @@ jobs: env: VCPKG_VERSION: 8eb57355a4ffb410a2e94c07b4dca2dffbee8e50 # 2023.10.19 - vcpkg_packages: zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp opengl-registry sdl2 + vcpkg_packages: zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp sdl2 strategy: fail-fast: false matrix: @@ -95,7 +95,7 @@ jobs: # Enable it, when working on the installer. steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Checkout IrrlichtMt run: | @@ -145,7 +145,7 @@ jobs: env: TYPE: ${{matrix.type}} - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: msvc-${{ matrix.config.arch }}-${{ matrix.type }} path: .\Package\ diff --git a/.gitignore b/.gitignore index fe3686d57..c6d92af13 100644 --- a/.gitignore +++ b/.gitignore @@ -92,11 +92,8 @@ cmake_install.cmake CMakeCache.txt CPackConfig.cmake CPackSourceConfig.cmake -src/test_config.h src/cmake_config.h src/cmake_config_githash.h -src/unittest/test_world/world.mt -games/devtest/mods/testnodes/textures/testnodes_generated_*.png /locale/ .directory *.cbp diff --git a/.luacheckrc b/.luacheckrc index 54ece656a..fcc04cab3 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -17,6 +17,7 @@ read_globals = { "VoxelArea", "profiler", "Settings", + "PerlinNoise", "PerlinNoiseMap", string = {fields = {"split", "trim"}}, table = {fields = {"copy", "getn", "indexof", "insert_all"}}, @@ -70,7 +71,6 @@ files["builtin/mainmenu"] = { read_globals = { "PLATFORM", - "TOUCHSCREEN_GUI", }, } @@ -81,9 +81,3 @@ files["builtin/common/tests"] = { "assert", }, } - -files["builtin/fstk"] = { - read_globals = { - "TOUCHSCREEN_GUI", - }, -} diff --git a/CMakeLists.txt b/CMakeLists.txt index 98c326ec8..85423b4f3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,4 @@ -cmake_minimum_required(VERSION 3.5) - -# Set policies up to 3.9 since we want to enable the IPO option -if(${CMAKE_VERSION} VERSION_LESS 3.9) - cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) -else() - cmake_policy(VERSION 3.9) -endif() +cmake_minimum_required(VERSION 3.12) # This can be read from ${PROJECT_NAME} after project() is called project(minetest) @@ -44,6 +37,25 @@ set(BUILD_UNITTESTS TRUE CACHE BOOL "Build unittests") set(BUILD_BENCHMARKS FALSE CACHE BOOL "Build benchmarks") set(BUILD_DOCUMENTATION TRUE CACHE BOOL "Build documentation") +set(DEFAULT_ENABLE_LTO TRUE) +# by default don't enable on Debug builds to get faster builds +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set(DEFAULT_ENABLE_LTO FALSE) +endif() +#### LTO testing list #### +# - Linux: seems to work always +# - win32/msvc: works +# - win32/gcc: fails to link +# - win32/clang: works +# - macOS on x86: seems to be fine +# - macOS on ARM: crashes, see +# Note: since CMake has no easy architecture detection disabling for Mac entirely +#### #### +if((WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR APPLE) + set(DEFAULT_ENABLE_LTO FALSE) +endif() +set(ENABLE_LTO ${DEFAULT_ENABLE_LTO} CACHE BOOL "Use Link Time Optimization") + set(DEFAULT_RUN_IN_PLACE FALSE) if(WIN32) set(DEFAULT_RUN_IN_PLACE TRUE) @@ -140,7 +152,7 @@ elseif(BUILD_CLIENT AND TARGET IrrlichtMt::IrrlichtMt) endif() message(STATUS "Found IrrlichtMt ${IrrlichtMt_VERSION}") - set(TARGET_VER_S 1.9.0mt14) + set(TARGET_VER_S 1.9.0mt15) string(REPLACE "mt" "." TARGET_VER ${TARGET_VER_S}) if(IrrlichtMt_VERSION VERSION_LESS ${TARGET_VER}) message(FATAL_ERROR "At least IrrlichtMt ${TARGET_VER_S} is required to build") @@ -149,6 +161,20 @@ elseif(BUILD_CLIENT AND TARGET IrrlichtMt::IrrlichtMt) endif() endif() +if (ENABLE_LTO OR CMAKE_INTERPROCEDURAL_OPTIMIZATION) + include(CheckIPOSupported) + check_ipo_supported(RESULT lto_supported OUTPUT lto_output) + if(lto_supported) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) + message(STATUS "LTO/IPO is enabled") + else() + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION FALSE) + message(STATUS "LTO/IPO was requested but is not supported by the compiler: ${lto_output}") + endif() +else() + message(STATUS "LTO/IPO is not enabled") +endif() + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "${GCC_MINIMUM_VERSION}") message(FATAL_ERROR "Insufficient gcc version, found ${CMAKE_CXX_COMPILER_VERSION}. " diff --git a/Dockerfile b/Dockerfile index 4564d780f..95476663f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ -ARG DOCKER_IMAGE=alpine:3.16 +ARG DOCKER_IMAGE=alpine:3.19 FROM $DOCKER_IMAGE AS dev ENV IRRLICHT_VERSION master -ENV SPATIALINDEX_VERSION 1.9.3 +ENV SPATIALINDEX_VERSION master ENV LUAJIT_VERSION v2.1 RUN apk add --no-cache git build-base cmake curl-dev zlib-dev zstd-dev \ @@ -10,7 +10,7 @@ RUN apk add --no-cache git build-base cmake curl-dev zlib-dev zstd-dev \ gmp-dev jsoncpp-dev ninja ca-certificates WORKDIR /usr/src/ -RUN git clone --recursive https://github.com/jupp0r/prometheus-cpp/ && \ +RUN git clone --recursive https://github.com/jupp0r/prometheus-cpp && \ cd prometheus-cpp && \ cmake -B build \ -DCMAKE_INSTALL_PREFIX=/usr/local \ @@ -29,9 +29,9 @@ RUN git clone --recursive https://github.com/jupp0r/prometheus-cpp/ && \ cd /usr/src/ && \ git clone --recursive https://luajit.org/git/luajit.git -b ${LUAJIT_VERSION} && \ cd luajit && \ - make && make install && \ + make amalg && make install && \ cd /usr/src/ && \ - git clone --depth=1 https://github.com/minetest/irrlicht/ -b ${IRRLICHT_VERSION} && \ + git clone --depth=1 https://github.com/minetest/irrlicht -b ${IRRLICHT_VERSION} && \ cp -r irrlicht/include /usr/include/irrlichtmt FROM dev as builder @@ -56,13 +56,12 @@ RUN cmake -B build \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_SERVER=TRUE \ -DENABLE_PROMETHEUS=TRUE \ - -DBUILD_UNITTESTS=FALSE \ + -DBUILD_UNITTESTS=FALSE -DBUILD_BENCHMARKS=FALSE \ -DBUILD_CLIENT=FALSE \ -GNinja && \ cmake --build build && \ cmake --install build -ARG DOCKER_IMAGE=alpine:3.16 FROM $DOCKER_IMAGE AS runtime RUN apk add --no-cache curl gmp libstdc++ libgcc libpq jsoncpp zstd-libs \ diff --git a/builtin/client/init.lua b/builtin/client/init.lua index 68fb169f0..301a8050c 100644 --- a/builtin/client/init.lua +++ b/builtin/client/init.lua @@ -7,6 +7,7 @@ dofile(clientpath .. "register.lua") dofile(commonpath .. "after.lua") dofile(commonpath .. "mod_storage.lua") dofile(commonpath .. "chatcommands.lua") +dofile(commonpath .. "information_formspecs.lua") dofile(clientpath .. "chatcommands.lua") dofile(clientpath .. "death_formspec.lua") dofile(clientpath .. "misc.lua") diff --git a/builtin/common/chatcommands.lua b/builtin/common/chatcommands.lua index 7c3da0601..7a8a49558 100644 --- a/builtin/common/chatcommands.lua +++ b/builtin/common/chatcommands.lua @@ -89,7 +89,7 @@ local function do_help_cmd(name, param) if #args > 1 then return false, S("Too many arguments, try using just /help ") end - local use_gui = INIT ~= "client" and core.get_player_by_name(name) + local use_gui = INIT == "client" or core.get_player_by_name(name) use_gui = use_gui and not opts:find("t") if #args == 0 and not use_gui then @@ -163,8 +163,8 @@ end if INIT == "client" then core.register_chatcommand("help", { - params = core.gettext("[all | ]"), - description = core.gettext("Get help for commands"), + params = core.gettext("[all | ] [-t]"), + description = core.gettext("Get help for commands (-t: output in chat)"), func = function(param) return do_help_cmd(nil, param) end, diff --git a/builtin/common/information_formspecs.lua b/builtin/common/information_formspecs.lua index 3405263bf..3fa397d25 100644 --- a/builtin/common/information_formspecs.lua +++ b/builtin/common/information_formspecs.lua @@ -61,15 +61,20 @@ local function build_chatcommands_formspec(name, sel, copy) for i, data in ipairs(mod_cmds) do rows[#rows + 1] = COLOR_BLUE .. ",0," .. F(data[1]) .. "," for j, cmds in ipairs(data[2]) do - local has_priv = check_player_privs(name, cmds[2].privs) + local has_priv = INIT == "client" or check_player_privs(name, cmds[2].privs) rows[#rows + 1] = ("%s,1,%s,%s"):format( has_priv and COLOR_GREEN or COLOR_GRAY, cmds[1], F(cmds[2].params)) if sel == #rows then description = cmds[2].description if copy then - core.chat_send_player(name, S("Command: @1 @2", - core.colorize("#0FF", "/" .. cmds[1]), cmds[2].params)) + local msg = S("Command: @1 @2", + core.colorize("#0FF", "/" .. cmds[1]), cmds[2].params) + if INIT == "client" then + core.display_chat_message(msg) + else + core.chat_send_player(name, msg) + end end end end @@ -111,26 +116,46 @@ end -- DETAILED CHAT COMMAND INFORMATION +if INIT == "client" then + core.register_on_formspec_input(function(formname, fields) + if formname ~= "__builtin:help_cmds" or fields.quit then + return + end -core.register_on_player_receive_fields(function(player, formname, fields) - if formname ~= "__builtin:help_cmds" or fields.quit then - return - end + local event = core.explode_table_event(fields.list) + if event.type ~= "INV" then + core.show_formspec("__builtin:help_cmds", + build_chatcommands_formspec(nil, event.row, event.type == "DCL")) + end + end) +else + core.register_on_player_receive_fields(function(player, formname, fields) + if formname ~= "__builtin:help_cmds" or fields.quit then + return + end - local event = core.explode_table_event(fields.list) - if event.type ~= "INV" then - local name = player:get_player_name() - core.show_formspec(name, "__builtin:help_cmds", - build_chatcommands_formspec(name, event.row, event.type == "DCL")) - end -end) + local event = core.explode_table_event(fields.list) + if event.type ~= "INV" then + local name = player:get_player_name() + core.show_formspec(name, "__builtin:help_cmds", + build_chatcommands_formspec(name, event.row, event.type == "DCL")) + end + end) +end function core.show_general_help_formspec(name) - core.show_formspec(name, "__builtin:help_cmds", - build_chatcommands_formspec(name)) + if INIT == "client" then + core.show_formspec("__builtin:help_cmds", + build_chatcommands_formspec(name)) + else + core.show_formspec(name, "__builtin:help_cmds", + build_chatcommands_formspec(name)) + end end -function core.show_privs_help_formspec(name) - core.show_formspec(name, "__builtin:help_privs", - build_privs_formspec(name)) +if INIT ~= "client" then + function core.show_privs_help_formspec(name) + core.show_formspec(name, "__builtin:help_privs", + build_privs_formspec(name)) + end end diff --git a/builtin/emerge/env.lua b/builtin/emerge/env.lua new file mode 100644 index 000000000..43848082a --- /dev/null +++ b/builtin/emerge/env.lua @@ -0,0 +1,61 @@ +-- Reimplementations of some environment function on vmanips, since this is +-- what the emerge environment operates on + +-- core.vmanip = -- set by C++ + +function core.set_node(pos, node) + return core.vmanip:set_node_at(pos, node) +end + +function core.bulk_set_node(pos_list, node) + local vm = core.vmanip + local set_node_at = vm.set_node_at + for _, pos in ipairs(pos_list) do + if not set_node_at(vm, pos, node) then + return false + end + end + return true +end + +core.add_node = core.set_node + +-- we don't deal with metadata currently +core.swap_node = core.set_node + +function core.remove_node(pos) + return core.vmanip:set_node_at(pos, {name="air"}) +end + +function core.get_node(pos) + return core.vmanip:get_node_at(pos) +end + +function core.get_node_or_nil(pos) + local node = core.vmanip:get_node_at(pos) + return node.name ~= "ignore" and node +end + +function core.get_perlin(seed, octaves, persist, spread) + local params + if type(seed) == "table" then + params = table.copy(seed) + else + assert(type(seed) == "number") + params = { + seed = seed, + octaves = octaves, + persist = persist, + spread = {x=spread, y=spread, z=spread}, + } + end + params.seed = core.get_seed(params.seed) -- add mapgen seed + return PerlinNoise(params) +end + + +function core.get_perlin_map(params, size) + local params2 = table.copy(params) + params2.seed = core.get_seed(params.seed) -- add mapgen seed + return PerlinNoiseMap(params2, size) +end diff --git a/builtin/emerge/init.lua b/builtin/emerge/init.lua new file mode 100644 index 000000000..cb7d9266a --- /dev/null +++ b/builtin/emerge/init.lua @@ -0,0 +1,21 @@ +local gamepath = core.get_builtin_path() .. "game" .. DIR_DELIM +local commonpath = core.get_builtin_path() .. "common" .. DIR_DELIM +local epath = core.get_builtin_path() .. "emerge" .. DIR_DELIM + +local builtin_shared = {} + +-- Import parts shared with "game" environment +dofile(gamepath .. "constants.lua") +assert(loadfile(commonpath .. "item_s.lua"))(builtin_shared) +dofile(gamepath .. "misc_s.lua") +dofile(gamepath .. "features.lua") +dofile(gamepath .. "voxelarea.lua") + +-- Now for our own stuff +assert(loadfile(commonpath .. "register.lua"))(builtin_shared) +assert(loadfile(epath .. "register.lua"))(builtin_shared) +dofile(epath .. "env.lua") + +builtin_shared.cache_content_ids() + +core.log("info", "Initialized emerge Lua environment") diff --git a/builtin/emerge/register.lua b/builtin/emerge/register.lua new file mode 100644 index 000000000..308fe4d7e --- /dev/null +++ b/builtin/emerge/register.lua @@ -0,0 +1,54 @@ +local builtin_shared = ... + +-- Copy all the registration tables over +do + local all = assert(core.transferred_globals) + core.transferred_globals = nil + + all.registered_nodes = {} + all.registered_craftitems = {} + all.registered_tools = {} + for k, v in pairs(all.registered_items) do + -- Disable further modification + setmetatable(v, {__newindex = {}}) + -- Reassemble the other tables + if v.type == "node" then + getmetatable(v).__index = all.nodedef_default + all.registered_nodes[k] = v + elseif v.type == "craft" then + getmetatable(v).__index = all.craftitemdef_default + all.registered_craftitems[k] = v + elseif v.type == "tool" then + getmetatable(v).__index = all.tooldef_default + all.registered_tools[k] = v + else + getmetatable(v).__index = all.noneitemdef_default + end + end + + for k, v in pairs(all) do + core[k] = v + end +end + +-- For tables that are indexed by item name: +-- If table[X] does not exist, default to table[core.registered_aliases[X]] +local alias_metatable = { + __index = function(t, name) + return rawget(t, core.registered_aliases[name]) + end +} +setmetatable(core.registered_items, alias_metatable) +setmetatable(core.registered_nodes, alias_metatable) +setmetatable(core.registered_craftitems, alias_metatable) +setmetatable(core.registered_tools, alias_metatable) + +-- +-- Callbacks +-- + +local make_registration = builtin_shared.make_registration + +core.registered_on_mods_loaded, core.register_on_mods_loaded = make_registration() +core.registered_on_generateds, core.register_on_generated = make_registration() +core.registered_on_shutdown, core.register_on_shutdown = make_registration() diff --git a/builtin/fstk/buttonbar.lua b/builtin/fstk/buttonbar.lua index effd0eba7..c7da41280 100644 --- a/builtin/fstk/buttonbar.lua +++ b/builtin/fstk/buttonbar.lua @@ -18,7 +18,9 @@ local BASE_SPACING = 0.1 -local SCROLL_BTN_WIDTH = TOUCHSCREEN_GUI and 0.8 or 0.5 +local function get_scroll_btn_width() + return core.settings:get_bool("enable_touch") and 0.8 or 0.5 +end local function buttonbar_formspec(self) if self.hidden then @@ -39,7 +41,7 @@ local function buttonbar_formspec(self) -- The number of buttons per page is always calculated as if the scroll -- buttons were visible. - local avail_space = self.size.x - 2*BASE_SPACING - 2*SCROLL_BTN_WIDTH + local avail_space = self.size.x - 2*BASE_SPACING - 2*get_scroll_btn_width() local btns_per_page = math.floor((avail_space - BASE_SPACING) / (btn_size + BASE_SPACING)) self.num_pages = math.ceil(#self.buttons / btns_per_page) @@ -55,7 +57,7 @@ local function buttonbar_formspec(self) local btn_start_x = self.pos.x + btn_spacing if show_scroll_btns then - btn_start_x = btn_start_x + BASE_SPACING + SCROLL_BTN_WIDTH + btn_start_x = btn_start_x + BASE_SPACING + get_scroll_btn_width() end for i = first_btn, first_btn + btns_per_page - 1 do @@ -80,7 +82,7 @@ local function buttonbar_formspec(self) y = self.pos.y + BASE_SPACING, } local btn_next_pos = { - x = self.pos.x + self.size.x - BASE_SPACING - SCROLL_BTN_WIDTH, + x = self.pos.x + self.size.x - BASE_SPACING - get_scroll_btn_width(), y = self.pos.y + BASE_SPACING, } @@ -88,11 +90,11 @@ local function buttonbar_formspec(self) self.btn_prev_name, self.btn_next_name)) table.insert(formspec, string.format("button[%f,%f;%f,%f;%s;<]", - btn_prev_pos.x, btn_prev_pos.y, SCROLL_BTN_WIDTH, btn_size, + btn_prev_pos.x, btn_prev_pos.y, get_scroll_btn_width(), btn_size, self.btn_prev_name)) table.insert(formspec, string.format("button[%f,%f;%f,%f;%s;>]", - btn_next_pos.x, btn_next_pos.y, SCROLL_BTN_WIDTH, btn_size, + btn_next_pos.x, btn_next_pos.y, get_scroll_btn_width(), btn_size, self.btn_next_name)) end diff --git a/builtin/game/falling.lua b/builtin/game/falling.lua index ec9c56c29..33473091d 100644 --- a/builtin/game/falling.lua +++ b/builtin/game/falling.lua @@ -79,6 +79,9 @@ core.register_entity(":__builtin:falling_node", { -- Cache whether we're supposed to float on water self.floats = core.get_item_group(node.name, "float") ~= 0 + -- Save liquidtype for falling water + self.liquidtype = def.liquidtype + -- Set entity visuals if def.drawtype == "torchlike" or def.drawtype == "signlike" then local textures @@ -294,9 +297,17 @@ core.register_entity(":__builtin:falling_node", { end -- Decide if we're replacing the node or placing on top + -- This condition is very similar to the check in core.check_single_for_falling(p) local np = vector.copy(bcp) - if bcd and bcd.buildable_to and - (not self.floats or bcd.liquidtype == "none") then + if bcd and bcd.buildable_to + and -- Take "float" group into consideration: + ( + -- Fall through non-liquids + not self.floats or bcd.liquidtype == "none" or + -- Only let sources fall through flowing liquids + (self.floats and self.liquidtype ~= "none" and bcd.liquidtype ~= "source") + ) then + core.remove_node(bcp) else np.y = np.y + 1 @@ -307,7 +318,7 @@ core.register_entity(":__builtin:falling_node", { local nd = core.registered_nodes[n2.name] -- If it's not air or liquid, remove node and replace it with -- it's drops - if n2.name ~= "air" and (not nd or nd.liquidtype == "none") then + if n2.name ~= "air" and (not nd or nd.liquidtype ~= "source") then if nd and nd.buildable_to == false then nd.on_dig(np, n2, nil) -- If it's still there, it might be protected @@ -555,11 +566,19 @@ function core.check_single_for_falling(p) local success, _ = convert_to_falling_node(p, n) return success end + local d_falling = core.registered_nodes[n.name] + local do_float = core.get_item_group(n.name, "float") > 0 -- Otherwise only if the bottom node is considered "fall through" if not same and - (not d_bottom.walkable or d_bottom.buildable_to) and - (core.get_item_group(n.name, "float") == 0 or - d_bottom.liquidtype == "none") then + (not d_bottom.walkable or d_bottom.buildable_to) + and -- Take "float" group into consideration: + ( + -- Fall through non-liquids + not do_float or d_bottom.liquidtype == "none" or + -- Only let sources fall through flowing liquids + (do_float and d_falling.liquidtype == "source" and d_bottom.liquidtype ~= "source") + ) then + local success, _ = convert_to_falling_node(p, n) return success end diff --git a/builtin/game/features.lua b/builtin/game/features.lua index 510d0461f..68005811f 100644 --- a/builtin/game/features.lua +++ b/builtin/game/features.lua @@ -35,6 +35,8 @@ core.features = { wallmounted_rotate = true, item_specific_pointabilities = true, blocking_pointability_type = true, + dynamic_add_media_startup = true, + dynamic_add_media_filepath = true, } function core.has_feature(arg) diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua index a30c42fa0..dc394a00c 100644 --- a/builtin/game/misc.lua +++ b/builtin/game/misc.lua @@ -237,8 +237,8 @@ end core.dynamic_media_callbacks = {} --- Transfer of certain globals into async environment --- see builtin/async/game.lua for the other side +-- Transfer of certain globals into seconday Lua environments +-- see builtin/async/game.lua or builtin/emerge/register.lua for the unpacking local function copy_filtering(t, seen) if type(t) == "userdata" or type(t) == "function" then @@ -261,6 +261,9 @@ function core.get_globals_to_transfer() local all = { registered_items = copy_filtering(core.registered_items), registered_aliases = core.registered_aliases, + registered_biomes = core.registered_biomes, + registered_ores = core.registered_ores, + registered_decorations = core.registered_decorations, nodedef_default = copy_filtering(core.nodedef_default), craftitemdef_default = copy_filtering(core.craftitemdef_default), diff --git a/builtin/game/statbars.lua b/builtin/game/statbars.lua index 72b711bdc..5412db6be 100644 --- a/builtin/game/statbars.lua +++ b/builtin/game/statbars.lua @@ -24,6 +24,13 @@ local bar_definitions = { size = {x = 24, y = 24}, offset = {x = 25, y= -(48 + 24 + 16)}, }, + minimap = { + type = "minimap", + position = {x = 1, y = 0}, + alignment = {x = -1, y = 1}, + offset = {x = -10, y = 10}, + size = {x = 256 , y = 256}, + }, } local hud_ids = {} @@ -92,6 +99,16 @@ local function update_builtin_statbars(player) end, name, hud.id_breathbar) hud.id_breathbar = nil end + + -- Don't add a minimap for clients which already have it hardcoded in C++. + local show_minimap = flags.minimap and + minetest.get_player_information(name).protocol_version >= 44 + if show_minimap and not hud.id_minimap then + hud.id_minimap = player:hud_add(bar_definitions.minimap) + elseif not show_minimap and hud.id_minimap then + player:hud_remove(hud.id_minimap) + hud.id_minimap = nil + end end local function cleanup_builtin_statbars(player) @@ -138,8 +155,7 @@ local function player_event_handler(player,eventname) end function core.hud_replace_builtin(hud_name, definition) - if type(definition) ~= "table" or - (definition.type or definition.hud_elem_type) ~= "statbar" then + if type(definition) ~= "table" then return false end @@ -175,6 +191,20 @@ function core.hud_replace_builtin(hud_name, definition) return true end + if hud_name == "minimap" then + bar_definitions.minimap = definition + + for name, ids in pairs(hud_ids) do + local player = core.get_player_by_name(name) + if player and ids.id_minimap then + player:hud_remove(ids.id_minimap) + ids.id_minimap = nil + update_builtin_statbars(player) + end + end + return true + end + return false end diff --git a/builtin/init.lua b/builtin/init.lua index e03c2c6de..b62dbf07a 100644 --- a/builtin/init.lua +++ b/builtin/init.lua @@ -31,8 +31,6 @@ minetest = core -- Load other files local scriptdir = core.get_builtin_path() -local gamepath = scriptdir .. "game" .. DIR_DELIM -local clientpath = scriptdir .. "client" .. DIR_DELIM local commonpath = scriptdir .. "common" .. DIR_DELIM local asyncpath = scriptdir .. "async" .. DIR_DELIM @@ -42,7 +40,7 @@ dofile(commonpath .. "serialize.lua") dofile(commonpath .. "misc_helpers.lua") if INIT == "game" then - dofile(gamepath .. "init.lua") + dofile(scriptdir .. "game" .. DIR_DELIM .. "init.lua") assert(not core.get_http_api) elseif INIT == "mainmenu" then local mm_script = core.settings:get("main_menu_script") @@ -67,7 +65,9 @@ elseif INIT == "async" then elseif INIT == "async_game" then dofile(asyncpath .. "game.lua") elseif INIT == "client" then - dofile(clientpath .. "init.lua") + dofile(scriptdir .. "client" .. DIR_DELIM .. "init.lua") +elseif INIT == "emerge" then + dofile(scriptdir .. "emerge" .. DIR_DELIM .. "init.lua") else error(("Unrecognized builtin initialization type %s!"):format(tostring(INIT))) end diff --git a/builtin/mainmenu/content/dlg_contentstore.lua b/builtin/mainmenu/content/dlg_contentstore.lua index e567fcef7..1d6b30cf6 100644 --- a/builtin/mainmenu/content/dlg_contentstore.lua +++ b/builtin/mainmenu/content/dlg_contentstore.lua @@ -154,7 +154,9 @@ local function start_install(package, reason) if conf_path then local conf = Settings(conf_path) - conf:set("title", package.title) + if not conf:get("title") then + conf:set("title", package.title) + end if not name_is_title then conf:set("name", package.name) end @@ -642,8 +644,21 @@ local function fetch_pkgs() end end + local languages + local current_language = core.get_language() + if current_language ~= "" then + languages = { current_language, "en;q=0.8" } + else + languages = { "en" } + end + local http = core.get_http_api() - local response = http.fetch_sync({ url = url }) + local response = http.fetch_sync({ + url = url, + extra_headers = { + "Accept-Language: " .. table.concat(languages, ", ") + }, + }) if not response.succeeded then return end @@ -898,7 +913,7 @@ local function get_info_formspec(text) return table.concat({ "formspec_version[6]", "size[15.75,9.5]", - TOUCHSCREEN_GUI and "padding[0.01,0.01]" or "position[0.5,0.55]", + core.settings:get_bool("enable_touch") and "padding[0.01,0.01]" or "position[0.5,0.55]", "label[4,4.35;", text, "]", "container[0,", H - 0.8 - 0.375, "]", @@ -928,7 +943,7 @@ function store.get_formspec(dlgdata) local formspec = { "formspec_version[6]", "size[15.75,9.5]", - TOUCHSCREEN_GUI and "padding[0.01,0.01]" or "position[0.5,0.55]", + core.settings:get_bool("enable_touch") and "padding[0.01,0.01]" or "position[0.5,0.55]", "style[status,downloading,queued;border=false]", @@ -1175,8 +1190,8 @@ end function store.handle_events(event) if event == "DialogShow" then - -- On mobile, don't show the "MINETEST" header behind the dialog. - mm_game_theme.set_engine(TOUCHSCREEN_GUI) + -- On touchscreen, don't show the "MINETEST" header behind the dialog. + mm_game_theme.set_engine(core.settings:get_bool("enable_touch")) -- If the store is already loaded, auto-install packages here. do_auto_install() diff --git a/builtin/mainmenu/content/pkgmgr.lua b/builtin/mainmenu/content/pkgmgr.lua index 9408eb994..b167e1423 100644 --- a/builtin/mainmenu/content/pkgmgr.lua +++ b/builtin/mainmenu/content/pkgmgr.lua @@ -150,6 +150,8 @@ function pkgmgr.get_mods(path, virtual_path, listing, modpack) toadd.virtual_path = mod_virtual_path toadd.type = "mod" + pkgmgr.update_translations({ toadd }) + -- Check modpack.txt -- Note: modpack.conf is already checked above local modpackfile = io.open(mod_path .. DIR_DELIM .. "modpack.txt") @@ -189,6 +191,8 @@ function pkgmgr.get_texture_packs() load_texture_packs(txtpath_system, retval) end + pkgmgr.update_translations(retval) + table.sort(retval, function(a, b) return a.title:lower() < b.title:lower() end) @@ -775,6 +779,29 @@ function pkgmgr.update_gamelist() table.sort(pkgmgr.games, function(a, b) return a.title:lower() < b.title:lower() end) + pkgmgr.update_translations(pkgmgr.games) +end + +-------------------------------------------------------------------------------- +function pkgmgr.update_translations(list) + for _, item in ipairs(list) do + local info = core.get_content_info(item.path) + assert(info.path) + assert(info.textdomain) + + assert(not item.is_translated) + item.is_translated = true + + if info.title and info.title ~= "" then + item.title = core.get_content_translation(info.path, info.textdomain, + core.translate(info.textdomain, info.title)) + end + + if info.description and info.description ~= "" then + item.description = core.get_content_translation(info.path, info.textdomain, + core.translate(info.textdomain, info.description)) + end + end end -------------------------------------------------------------------------------- diff --git a/builtin/mainmenu/dlg_create_world.lua b/builtin/mainmenu/dlg_create_world.lua index b844923ed..2fd7dc421 100644 --- a/builtin/mainmenu/dlg_create_world.lua +++ b/builtin/mainmenu/dlg_create_world.lua @@ -70,6 +70,8 @@ local flag_checkboxes = { { "trees", fgettext("Trees and jungle grass") }, { "flat", fgettext("Flat terrain") }, { "mudflow", fgettext("Mud flow"), fgettext("Terrain surface erosion") }, + { "temples", fgettext("Desert temples"), + fgettext("Different dungeon variant generated in desert biomes (only if dungeons enabled)") }, -- Biome settings are in mgv6_biomes below }, } @@ -279,7 +281,7 @@ local function create_world_formspec(dialogdata) end local retval = - "size[12.25,7,true]" .. + "size[12.25,7.4,true]" .. -- Left side "container[0,0]".. @@ -321,8 +323,10 @@ local function create_world_formspec(dialogdata) "container_end[]".. -- Menu buttons - "button[3.25,6.5;3,0.5;world_create_confirm;" .. fgettext("Create") .. "]" .. - "button[6.25,6.5;3,0.5;world_create_cancel;" .. fgettext("Cancel") .. "]" + "container[0,6.9]".. + "button[3.25,0;3,0.5;world_create_confirm;" .. fgettext("Create") .. "]" .. + "button[6.25,0;3,0.5;world_create_cancel;" .. fgettext("Cancel") .. "]" .. + "container_end[]" return retval diff --git a/builtin/mainmenu/settings/dlg_settings.lua b/builtin/mainmenu/settings/dlg_settings.lua index 9e5d2a46c..d6bbf2570 100644 --- a/builtin/mainmenu/settings/dlg_settings.lua +++ b/builtin/mainmenu/settings/dlg_settings.lua @@ -316,8 +316,8 @@ local function check_requirements(name, requires) local special = { android = PLATFORM == "Android", desktop = PLATFORM ~= "Android", - touchscreen_gui = TOUCHSCREEN_GUI, - keyboard_mouse = not TOUCHSCREEN_GUI, + touchscreen_gui = core.settings:get_bool("enable_touch"), + keyboard_mouse = not core.settings:get_bool("enable_touch"), shaders_support = shaders_support, shaders = core.settings:get_bool("enable_shaders") and shaders_support, opengl = video_driver == "opengl", @@ -449,13 +449,14 @@ local function get_formspec(dialogdata) local extra_h = 1 -- not included in tabsize.height local tabsize = { - width = TOUCHSCREEN_GUI and 16.5 or 15.5, - height = TOUCHSCREEN_GUI and (10 - extra_h) or 12, + width = core.settings:get_bool("enable_touch") and 16.5 or 15.5, + height = core.settings:get_bool("enable_touch") and (10 - extra_h) or 12, } - local scrollbar_w = TOUCHSCREEN_GUI and 0.6 or 0.4 + local scrollbar_w = core.settings:get_bool("enable_touch") and 0.6 or 0.4 - local left_pane_width = TOUCHSCREEN_GUI and 4.5 or 4.25 + local left_pane_width = core.settings:get_bool("enable_touch") and 4.5 or 4.25 + local left_pane_padding = 0.25 local search_width = left_pane_width + scrollbar_w - (0.75 * 2) local back_w = 3 @@ -468,7 +469,7 @@ local function get_formspec(dialogdata) local fs = { "formspec_version[6]", "size[", tostring(tabsize.width), ",", tostring(tabsize.height + extra_h), "]", - TOUCHSCREEN_GUI and "padding[0.01,0.01]" or "", + core.settings:get_bool("enable_touch") and "padding[0.01,0.01]" or "", "bgcolor[#0000]", -- HACK: this is needed to allow resubmitting the same formspec @@ -516,9 +517,9 @@ local function get_formspec(dialogdata) y = y + 0.82 end fs[#fs + 1] = ("box[0,%f;%f,0.8;%s]"):format( - y, left_pane_width, other_page.id == page_id and "#467832FF" or "#3339") + y, left_pane_width-left_pane_padding, other_page.id == page_id and "#467832FF" or "#3339") fs[#fs + 1] = ("button[0,%f;%f,0.8;page_%s;%s]") - :format(y, left_pane_width, other_page.id, fgettext(other_page.title)) + :format(y, left_pane_width-left_pane_padding, other_page.id, fgettext(other_page.title)) y = y + 0.82 end @@ -641,11 +642,22 @@ local function buttonhandler(this, fields) local value = core.is_yes(fields.show_advanced) core.settings:set_bool("show_advanced", value) write_settings_early() + end + -- enable_touch is a checkbox in a setting component. We handle this + -- setting differently so we can hide/show pages using the next if-statement + if fields.enable_touch ~= nil then + local value = core.is_yes(fields.enable_touch) + core.settings:set_bool("enable_touch", value) + write_settings_early() + end + + if fields.show_advanced ~= nil or fields.enable_touch ~= nil then local suggested_page_id = update_filtered_pages(dialogdata.query) + dialogdata.components = nil + if not filtered_page_by_id[dialogdata.page_id] then - dialogdata.components = nil dialogdata.leftscroll = 0 dialogdata.rightscroll = 0 diff --git a/builtin/mainmenu/tab_content.lua b/builtin/mainmenu/tab_content.lua index abe127a69..26f2e2a3c 100644 --- a/builtin/mainmenu/tab_content.lua +++ b/builtin/mainmenu/tab_content.lua @@ -114,12 +114,13 @@ local function get_formspec(tabview, name, tabdata) modscreenshot = defaulttexturedir .. "no_screenshot.png" end - local info = core.get_content_info(selected_pkg.path) local desc = fgettext("No package description available") - if info.description and info.description:trim() ~= "" then - desc = core.formspec_escape(info.description) + if selected_pkg.description and selected_pkg.description:trim() ~= "" then + desc = core.formspec_escape(selected_pkg.description) end + local info = core.get_content_info(selected_pkg.path) + local title_and_name if selected_pkg.type == "game" then title_and_name = selected_pkg.name diff --git a/builtin/mainmenu/tab_local.lua b/builtin/mainmenu/tab_local.lua index c35a3f6fb..ff1a22398 100644 --- a/builtin/mainmenu/tab_local.lua +++ b/builtin/mainmenu/tab_local.lua @@ -94,7 +94,7 @@ function singleplayer_refresh_gamebar() local btnbar = buttonbar_create( "game_button_bar", - TOUCHSCREEN_GUI and {x = 0, y = 7.25} or {x = 0, y = 7.475}, + core.settings:get_bool("enable_touch") and {x = 0, y = 7.25} or {x = 0, y = 7.475}, {x = 15.5, y = 1.25}, "#000000", game_buttonbar_button_handler) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 454187929..8a60fc85c 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -168,6 +168,11 @@ invert_hotbar_mouse_wheel (Hotbar: Invert mouse wheel direction) bool false [*Touchscreen] +# Enables touchscreen mode, allowing you to play the game with a touchscreen. +# +# Requires: !android +enable_touch (Enable touchscreen) bool true + # The length in pixels it takes for touchscreen interaction to start. # # Requires: touchscreen_gui @@ -422,7 +427,8 @@ anisotropic_filter (Anisotropic filtering) bool false # # * None - No antialiasing (default) # -# * FSAA - Hardware-provided full-screen antialiasing (incompatible with shaders) +# * FSAA - Hardware-provided full-screen antialiasing +# (incompatible with Post Processing and Undersampling) # A.K.A multi-sample antialiasing (MSAA) # Smoothens out block edges but does not affect the insides of textures. # A restart is required to change this option. @@ -577,12 +583,17 @@ shadow_sky_body_orbit_tilt (Sky Body Orbit Tilt) float 0.0 -60.0 60.0 [**Post Processing] +# Enables the post processing pipeline. +# +# Requires: shaders +enable_post_processing (Enable Post Processing) bool true + # Enables Hable's 'Uncharted 2' filmic tone mapping. # Simulates the tone curve of photographic film and how this approximates the # appearance of high dynamic range images. Mid-range contrast is slightly # enhanced, highlights and shadows are gradually compressed. # -# Requires: shaders +# Requires: shaders, enable_post_processing tone_mapping (Filmic tone mapping) bool false # Enable automatic exposure correction @@ -590,14 +601,14 @@ tone_mapping (Filmic tone mapping) bool false # automatically adjust to the brightness of the scene, # simulating the behavior of human eye. # -# Requires: shaders +# Requires: shaders, enable_post_processing enable_auto_exposure (Enable Automatic Exposure) bool false # Set the exposure compensation in EV units. # Value of 0.0 (default) means no exposure compensation. # Range: from -1 to 1.0 # -# Requires: shaders, enable_auto_exposure +# Requires: shaders, enable_post_processing, enable_auto_exposure exposure_compensation (Exposure compensation) float 0.0 -1.0 1.0 # Apply dithering to reduce color banding artifacts. @@ -608,7 +619,7 @@ exposure_compensation (Exposure compensation) float 0.0 -1.0 1.0 # With OpenGL ES, dithering only works if the shader supports high # floating-point precision and it may have a higher performance impact. # -# Requires: shaders +# Requires: shaders, enable_post_processing debanding (Enable Debanding) bool true [**Bloom] @@ -616,7 +627,7 @@ debanding (Enable Debanding) bool true # Set to true to enable bloom effect. # Bright colors will bleed over the neighboring objects. # -# Requires: shaders +# Requires: shaders, enable_post_processing enable_bloom (Enable Bloom) bool false # Set to true to render debugging breakdown of the bloom effect. @@ -624,32 +635,32 @@ enable_bloom (Enable Bloom) bool false # top-left - processed base image, top-right - final image # bottom-left - raw base image, bottom-right - bloom texture. # -# Requires: shaders, enable_bloom +# Requires: shaders, enable_post_processing, enable_bloom enable_bloom_debug (Enable Bloom Debug) bool false # Defines how much bloom is applied to the rendered image # Smaller values make bloom more subtle # Range: from 0.01 to 1.0, default: 0.05 # -# Requires: shaders, enable_bloom +# Requires: shaders, enable_post_processing, enable_bloom bloom_intensity (Bloom Intensity) float 0.05 0.01 1.0 # Defines the magnitude of bloom overexposure. # Range: from 0.1 to 10.0, default: 1.0 # -# Requires: shaders, enable_bloom +# Requires: shaders, enable_post_processing, enable_bloom bloom_strength_factor (Bloom Strength Factor) float 1.0 0.1 10.0 # Logical value that controls how far the bloom effect spreads # from the bright objects. # Range: from 0.1 to 8, default: 1 # -# Requires: shaders, enable_bloom +# Requires: shaders, enable_post_processing, enable_bloom bloom_radius (Bloom Radius) float 1 0.1 8 # Set to true to enable volumetric lighting effect (a.k.a. "Godrays"). # -# Requires: shaders, enable_bloom +# Requires: shaders, enable_post_processing, enable_bloom enable_volumetric_lighting (Volumetric lighting) bool false [*Audio] @@ -1089,7 +1100,8 @@ mgv5_np_dungeons (Dungeon noise) noise_params_3d 0.9, 0.5, (500, 500, 500), 0, 2 # The 'snowbiomes' flag enables the new 5 biome system. # When the 'snowbiomes' flag is enabled jungles are automatically enabled and # the 'jungles' flag is ignored. -mgv6_spflags (Mapgen V6 specific flags) flags jungles,biomeblend,mudflow,snowbiomes,noflat,trees jungles,biomeblend,mudflow,snowbiomes,flat,trees,nojungles,nobiomeblend,nomudflow,nosnowbiomes,noflat,notrees +# The 'temples' flag disables generation of desert temples. Normal dungeons will appear instead. +mgv6_spflags (Mapgen V6 specific flags) flags jungles,biomeblend,mudflow,snowbiomes,noflat,trees,temples jungles,biomeblend,mudflow,snowbiomes,flat,trees,temples,nojungles,nobiomeblend,nomudflow,nosnowbiomes,noflat,notrees,notemples # Deserts occur when np_biome exceeds this value. # When the 'snowbiomes' flag is enabled, this is ignored. @@ -2028,9 +2040,10 @@ ask_reconnect_on_crash (Ask to reconnect after crash) bool false [**Server/Env Performance] -# Length of a server tick and the interval at which objects are generally updated over -# network, stated in seconds. -dedicated_server_step (Dedicated server step) float 0.09 0.0 +# Length of a server tick (the interval at which everything is generally updated), +# stated in seconds. +# Does not apply to sessions hosted from the client menu. +dedicated_server_step (Dedicated server step) float 0.09 0.0 1.0 # Whether players are shown to clients without any range limit. # Deprecated, use the setting player_transfer_distance instead. @@ -2100,8 +2113,7 @@ liquid_update (Liquid update tick) float 1.0 0.001 # At this distance the server will aggressively optimize which blocks are sent to # clients. # Small values potentially improve performance a lot, at the expense of visible -# rendering glitches (some blocks will not be rendered under water and in caves, -# as well as sometimes on land). +# rendering glitches (some blocks might not be rendered correctly in caves). # Setting this to a value greater than max_block_send_distance disables this # optimization. # Stated in MapBlocks (16 nodes). diff --git a/doc/compiling/README.md b/doc/compiling/README.md index f4812e77d..4ecaa88bb 100644 --- a/doc/compiling/README.md +++ b/doc/compiling/README.md @@ -28,6 +28,7 @@ General options and their default values: ENABLE_REDIS=ON - Build with libhiredis; Enables use of Redis map backend ENABLE_SPATIAL=ON - Build with LibSpatial; Speeds up AreaStores ENABLE_SOUND=ON - Build with OpenAL, libogg & libvorbis; in-game sounds + ENABLE_LTO= - Build with IPO/LTO optimizations (smaller and more efficient than regular build) ENABLE_LUAJIT=ON - Build with LuaJIT (much faster than non-JIT Lua) ENABLE_PROMETHEUS=OFF - Build with Prometheus metrics exporter (listens on tcp/30000 by default) ENABLE_SYSTEM_GMP=ON - Use GMP from system (much faster than bundled mini-gmp) @@ -37,7 +38,7 @@ General options and their default values: INSTALL_DEVTEST=FALSE - Whether the Development Test game should be installed alongside Minetest USE_GPROF=FALSE - Enable profiling using GProf VERSION_EXTRA= - Text to append to version (e.g. VERSION_EXTRA=foobar -> Minetest 0.4.9-foobar) - ENABLE_TOUCH=FALSE - Enable Touchscreen support (requires support by IrrlichtMt) + ENABLE_TOUCH=FALSE - Enable touchscreen support by default (requires support by IrrlichtMt) Library specific options: diff --git a/doc/compiling/macos.md b/doc/compiling/macos.md index 34d6aa675..bea40f6ef 100644 --- a/doc/compiling/macos.md +++ b/doc/compiling/macos.md @@ -8,7 +8,7 @@ Install dependencies with homebrew: ``` -brew install cmake freetype gettext gmp hiredis jpeg jsoncpp leveldb libogg libpng libvorbis luajit zstd gettext +brew install cmake freetype gettext gmp hiredis jpeg-turbo jsoncpp leveldb libogg libpng libvorbis luajit zstd gettext ``` ## Download diff --git a/doc/compiling/windows.md b/doc/compiling/windows.md index e24243a23..b42bf8206 100644 --- a/doc/compiling/windows.md +++ b/doc/compiling/windows.md @@ -14,7 +14,7 @@ It is highly recommended to use vcpkg as package manager. After you successfully built vcpkg you can easily install the required libraries: ```powershell -vcpkg install zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp opengl-registry gettext sdl2 --triplet x64-windows +vcpkg install zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp gettext sdl2 --triplet x64-windows ``` - **Don't forget about IrrlichtMt.** The easiest way is to clone it to `lib/irrlichtmt`: diff --git a/doc/lua_api.md b/doc/lua_api.md index d5a25ed9f..2eefacc08 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -61,7 +61,8 @@ The game directory can contain the following files: * `game.conf`, with the following keys: * `title`: Required, a human-readable title to address the game, e.g. `title = Minetest Game`. * `name`: (Deprecated) same as title. - * `description`: Short description to be shown in the content tab + * `description`: Short description to be shown in the content tab. + See [Translating content meta](#translating-content-meta). * `allowed_mapgens = ` e.g. `allowed_mapgens = v5,v6,flat` Mapgens not in this list are removed from the list of mapgens for the @@ -87,10 +88,11 @@ The game directory can contain the following files: `enable_damage`, `creative_mode`, `enable_server`. * `map_persistent`: Specifies whether newly created worlds should use a persistent map backend. Defaults to `true` (= "sqlite3") - * `author`: The author of the game. It only appears when downloaded from - ContentDB. + * `author`: The author's ContentDB username. * `release`: Ignore this: Should only ever be set by ContentDB, as it is an internal ID used to track versions. + * `textdomain`: Textdomain used to translate description. Defaults to game id. + See [Translating content meta](#translating-content-meta). * `minetest.conf`: Used to set default settings when running this game. * `settingtypes.txt`: @@ -156,13 +158,14 @@ The file is a key-value store of modpack details. * `name`: The modpack name. Allows Minetest to determine the modpack name even if the folder is wrongly named. +* `title`: A human-readable title to address the modpack. See [Translating content meta](#translating-content-meta). * `description`: Description of mod to be shown in the Mods tab of the main - menu. -* `author`: The author of the modpack. It only appears when downloaded from - ContentDB. + menu. See [Translating content meta](#translating-content-meta). +* `author`: The author's ContentDB username. * `release`: Ignore this: Should only ever be set by ContentDB, as it is an internal ID used to track versions. -* `title`: A human-readable title to address the modpack. +* `textdomain`: Textdomain used to translate title and description. Defaults to modpack name. + See [Translating content meta](#translating-content-meta). Note: to support 0.4.x, please also create an empty modpack.txt file. @@ -201,17 +204,18 @@ A `Settings` file that provides meta information about the mod. * `name`: The mod name. Allows Minetest to determine the mod name even if the folder is wrongly named. +* `title`: A human-readable title to address the mod. See [Translating content meta](#translating-content-meta). * `description`: Description of mod to be shown in the Mods tab of the main - menu. + menu. See [Translating content meta](#translating-content-meta). * `depends`: A comma separated list of dependencies. These are mods that must be loaded before this mod. * `optional_depends`: A comma separated list of optional dependencies. Like a dependency, but no error if the mod doesn't exist. -* `author`: The author of the mod. It only appears when downloaded from - ContentDB. +* `author`: The author's ContentDB username. * `release`: Ignore this: Should only ever be set by ContentDB, as it is an internal ID used to track versions. -* `title`: A human-readable title to address the mod. +* `textdomain`: Textdomain used to translate title and description. Defaults to modname. + See [Translating content meta](#translating-content-meta). ### `screenshot.png` @@ -506,8 +510,8 @@ Example: * ``: width * ``: height -* ``: x position -* ``: y position +* ``: x position, negative numbers allowed +* ``: y position, negative numbers allowed * ``: texture to combine Creates a texture of size `` times `` and blits the listed files to their @@ -613,13 +617,13 @@ Creates an inventorycube with `grass.png`, `dirt.png^grass_side.png` and * ``: y position * ``: a `ColorString`. -Creates a texture of the given size and color, optionally with an , +Creates a texture of the given size and color, optionally with an `,` position. An alpha value may be specified in the `Colorstring`. -The optional , position is only used if the [fill is being overlaid +The optional `,` position is only used if the `[fill` is being overlaid onto another texture with '^'. -When [fill is overlaid onto another texture it will not upscale or change +When `[fill` is overlaid onto another texture it will not upscale or change the resolution of the texture, the base texture will determine the output resolution. @@ -2167,6 +2171,8 @@ to games. Negative damage values are discarded as no damage. * `falling_node`: if there is no walkable block under the node it will fall * `float`: the node will not fall through liquids (`liquidtype ~= "none"`) + * A liquid source with `groups = {falling_node = 1, float = 1}` + will fall through flowing liquids. * `level`: Can be used to give an additional sense of progression in the game. * A larger level will cause e.g. a weapon of a lower level make much less damage, and get worn out much faster, or not be able to get drops @@ -4133,6 +4139,46 @@ the table returned by `minetest.get_player_information(name)`. IMPORTANT: This functionality should only be used for sorting, filtering or similar purposes. You do not need to use this to get translated strings to show up on the client. +Translating content meta +------------------------ + +You can translate content meta, such as `title` and `description`, by placing +translations in a `locale/DOMAIN.LANG.tr` file. The textdomain defaults to the +content name, but can be customised using `textdomain` in the content's .conf. + +### Mods and Texture Packs + +Say you have a mod called `mymod` with a short description in mod.conf: + +``` +description = This is the short description +``` + +Minetest will look for translations in the `mymod` textdomain as there's no +textdomain specified in mod.conf. For example, `mymod/locale/mymod.fr.tr`: + +``` +# textdomain:mymod +This is the short description=Voici la description succincte +``` + +### Games and Modpacks + +For games and modpacks, Minetest will look for the textdomain in all mods. + +Say you have a game called `mygame` with the following game.conf: + +``` +description = This is the game's short description +textdomain = mygame +``` + +Minetest will then look for the textdomain `mygame` in all mods, for example, +`mygame/mods/anymod/locale/mygame.fr.tr`. Note that it is still recommended that your +textdomain match the mod name, but this isn't required. + + + Perlin noise ============ @@ -4677,6 +4723,7 @@ differences: into it; it's not necessary to call `VoxelManip:read_from_map()`. Note that the region of map it has loaded is NOT THE SAME as the `minp`, `maxp` parameters of `on_generated()`. Refer to `minetest.get_mapgen_object` docs. + Once you're done you still need to call `VoxelManip:write_to_map()` * The `on_generated()` callbacks of some mods may place individual nodes in the generated area using non-VoxelManip map modification methods. Because the @@ -4873,10 +4920,10 @@ Mapgen objects ============== A mapgen object is a construct used in map generation. Mapgen objects can be -used by an `on_generate` callback to speed up operations by avoiding +used by an `on_generated` callback to speed up operations by avoiding unnecessary recalculations, these can be retrieved using the `minetest.get_mapgen_object()` function. If the requested Mapgen object is -unavailable, or `get_mapgen_object()` was called outside of an `on_generate()` +unavailable, or `get_mapgen_object()` was called outside of an `on_generated` callback, `nil` is returned. The following Mapgen objects are currently available: @@ -4908,12 +4955,14 @@ generated chunk by the current mapgen. ### `gennotify` -Returns a table mapping requested generation notification types to arrays of -positions at which the corresponding generated structures are located within -the current chunk. To enable the capture of positions of interest to be recorded -call `minetest.set_gen_notify()` first. +Returns a table. You need to announce your interest in a specific +field by calling `minetest.set_gen_notify()` *before* map generation happens. -Possible fields of the returned table are: +* key = string: generation notification type +* value = list of positions (usually) + * Exceptions are denoted in the listing below. + +Available generation notification types: * `dungeon`: bottom center position of dungeon rooms * `temple`: as above but for desert temples (mgv6 only) @@ -4921,7 +4970,12 @@ Possible fields of the returned table are: * `cave_end` * `large_cave_begin` * `large_cave_end` -* `decoration#id` (see below) +* `custom`: data originating from [Mapgen environment] (Lua API) + * This is a table. + * key = user-defined ID (string) + * value = arbitrary Lua value +* `decoration#id`: decorations + * (see below) Decorations have a key in the format of `"decoration#id"`, where `id` is the numeric unique decoration ID as returned by `minetest.get_decoration_id()`. @@ -5302,6 +5356,10 @@ Utilities item_specific_pointabilities = true, -- Nodes `pointable` property can be `"blocking"` (5.9.0) blocking_pointability_type = true, + -- dynamic_add_media can be called at startup when leaving callback as `nil` (5.9.0) + dynamic_add_media_startup = true, + -- dynamic_add_media supports `filename` and `filedata` parameters (5.9.0) + dynamic_add_media_filepath = true, } ``` @@ -5426,6 +5484,9 @@ Utilities * `minetest.sha1(data, [raw])`: returns the sha1 hash of data * `data`: string of data to hash * `raw`: return raw bytes instead of hex digits, default: false +* `minetest.sha256(data, [raw])`: returns the sha256 hash of data + * `data`: string of data to hash + * `raw`: return raw bytes instead of hex digits, default: false * `minetest.colorspec_to_colorstring(colorspec)`: Converts a ColorSpec to a ColorString. If the ColorSpec is invalid, returns `nil`. * `colorspec`: The ColorSpec to convert @@ -5445,7 +5506,7 @@ Utilities You can use `colorspec_to_bytes` to generate raw RGBA values. Palettes are not supported at the moment. You may use this to procedurally generate textures during server init. -* `minetest.urlencode(str)`: Encodes non-unreserved URI characters by a +* `minetest.urlencode(str)`: Encodes reserved URI characters by a percent sign followed by two hex digits. See [RFC 3986, section 2.3](https://datatracker.ietf.org/doc/html/rfc3986#section-2.3). @@ -5581,8 +5642,10 @@ Call these functions only at load time! * `minetest.register_on_punchnode(function(pos, node, puncher, pointed_thing))` * Called when a node is punched * `minetest.register_on_generated(function(minp, maxp, blockseed))` - * Called after generating a piece of world. Modifying nodes inside the area - is a bit faster than usual. + * Called after generating a piece of world between `minp` and `maxp`. + * **Avoid using this** whenever possible. As with other callbacks this blocks + the main thread and introduces noticable latency. + Consider [Mapgen environment] for an alternative. * `minetest.register_on_newplayer(function(ObjectRef))` * Called when a new player enters the world for the first time * `minetest.register_on_punchplayer(function(player, hitter, time_from_last_punch, tool_capabilities, dir, damage))` @@ -5948,8 +6011,11 @@ Environment access * `minetest.add_entity(pos, name, [staticdata])`: Spawn Lua-defined entity at position. * Returns `ObjectRef`, or `nil` if failed + * Entities with `static_save = true` can be added also + to unloaded and non-generated blocks. * `minetest.add_item(pos, item)`: Spawn item * Returns `ObjectRef`, or `nil` if failed + * Items can be added also to unloaded and non-generated blocks. * `minetest.get_player_by_name(name)`: Get an `ObjectRef` to a player * `minetest.get_objects_inside_radius(pos, radius)`: returns a list of ObjectRefs. @@ -5995,20 +6061,18 @@ Environment access * `minetest.get_voxel_manip([pos1, pos2])` * Return voxel manipulator object. * Loads the manipulator from the map if positions are passed. -* `minetest.set_gen_notify(flags, {deco_ids})` +* `minetest.set_gen_notify(flags, [deco_ids], [custom_ids])` * Set the types of on-generate notifications that should be collected. - * `flags` is a flag field with the available flags: - * dungeon - * temple - * cave_begin - * cave_end - * large_cave_begin - * large_cave_end - * decoration - * The second parameter is a list of IDs of decorations which notification + * `flags`: flag field, see [`gennotify`] for available generation notification types. + * The following parameters are optional: + * `deco_ids` is a list of IDs of decorations which notification is requested for. + * `custom_ids` is a list of user-defined IDs (strings) which are + requested. By convention these should be the mod name with an optional + colon and specifier added, e.g. `"default"` or `"default:dungeon_loot"` * `minetest.get_gen_notify()` - * Returns a flagstring and a table with the `deco_id`s. + * Returns a flagstring, a table with the `deco_id`s and a table with + user-defined IDs. * `minetest.get_decoration_id(decoration_name)` * Returns the decoration ID number for the provided decoration name string, or `nil` on failure. @@ -6180,6 +6244,17 @@ Environment access * increase level of leveled node by level, default `level` equals `1` * if `totallevel > maxlevel`, returns rest (`total-max`) * `level` must be between -127 and 127 +* `minetest.get_node_boxes(box_type, pos, [node])` + * `box_type` must be `"node_box"`, `"collision_box"` or `"selection_box"`. + * `pos` must be a node position. + * `node` can be a table in the form `{name=string, param1=number, param2=number}`. + If `node` is `nil`, the actual node at `pos` is used instead. + * Resolves any facedir-rotated boxes, connected boxes and the like into + actual boxes. + * Returns a list of boxes in the form + `{{x1, y1, z1, x2, y2, z2}, {x1, y1, z1, x2, y2, z2}, ...}`. Coordinates + are relative to `pos`. + * See also: [Node boxes](#node-boxes) * `minetest.fix_light(pos1, pos2)`: returns `true`/`false` * resets the light in a cuboid-shaped part of the map and removes lighting bugs. @@ -6544,7 +6619,6 @@ Class instances that can be transferred between environments: Functions: * Standalone helpers such as logging, filesystem, encoding, hashing or compression APIs -* `minetest.request_insecure_environment` (same restrictions apply) Variables: * `minetest.settings` @@ -6553,6 +6627,85 @@ Variables: * with all functions and userdata values replaced by `true`, calling any callbacks here is obviously not possible +Mapgen environment +------------------ + +The engine runs the map generator on separate threads, each of these also has +a Lua environment. Its primary purpose is to allow mods to operate on newly +generated parts of the map to e.g. generate custom structures. +Internally it is referred to as "emerge environment". + +Refer to [Async environment] for the usual disclaimer on what environment isolation entails. + +The map generator threads, which also contain the above mentioned Lua environment, +are initialized after all mods have been loaded by the server. After that the +registered scripts (not all mods!) - see below - are run during initialization of +the mapgen environment. After that only callbacks happen. The mapgen env +does not have a global step or timer. + +* `minetest.register_mapgen_script(path)`: + * Register a path to a Lua file to be imported when a mapgen environment + is initialized. Run in order of registration. + +### List of APIs exclusive to the mapgen env + +* `minetest.register_on_generated(function(vmanip, minp, maxp, blockseed))` + * Called after the engine mapgen finishes a chunk but before it is written to + the map. + * Chunk data resides in `vmanip`. Other parts of the map are not accessible. + The area of the chunk if comprised of `minp` and `maxp`, note that is smaller + than the emerged area of the VoxelManip. + Note: calling `read_from_map()` or `write_to_map()` on the VoxelManipulator object + is not necessary and is disallowed. + * `blockseed`: 64-bit seed number used for this chunk +* `minetest.save_gen_notify(id, data)` + * Saves data for retrieval using the gennotify mechanism (see [Mapgen objects]). + * Data is bound to the chunk that is currently being processed, so this function + only makes sense inside the `on_generated` callback. + * `id`: user-defined ID (a string) + By convention these should be the mod name with an optional + colon and specifier added, e.g. `"default"` or `"default:dungeon_loot"` + * `data`: any Lua object (will be serialized, no userdata allowed) + * returns `true` if the data was remembered. That is if `minetest.set_gen_notify` + was called with the same user-defined ID before. + +### List of APIs available in the mapgen env + +Classes: +* `AreaStore` +* `ItemStack` +* `PerlinNoise` +* `PerlinNoiseMap` +* `PseudoRandom` +* `PcgRandom` +* `SecureRandom` +* `VoxelArea` +* `VoxelManip` + * only given by callbacks; cannot access rest of map +* `Settings` + +Functions: +* Standalone helpers such as logging, filesystem, encoding, + hashing or compression APIs +* `minetest.get_biome_id`, `get_biome_name`, `get_heat`, `get_humidity`, + `get_biome_data`, `get_mapgen_object`, `get_mapgen_params`, `get_mapgen_edges`, + `get_mapgen_setting`, `get_noiseparams`, `get_decoration_id` and more +* `minetest.get_node`, `set_node`, `find_node_near`, `find_nodes_in_area`, + `spawn_tree` and similar + * these only operate on the current chunk (if inside a callback) + +Variables: +* `minetest.settings` +* `minetest.registered_items`, `registered_nodes`, `registered_tools`, + `registered_craftitems` and `registered_aliases` + * with all functions and userdata values replaced by `true`, calling any + callbacks here is obviously not possible +* `minetest.registered_biomes`, `registered_ores`, `registered_decorations` + +Note that node metadata does not exist in the mapgen env, we suggest deferring +setting any metadata you need to the `on_generated` callback in the regular env. +You can use the gennotify mechanism to transfer this information. + Server ------ @@ -6583,11 +6736,15 @@ Server * Returns boolean indicating success (false if player nonexistent) * `minetest.dynamic_add_media(options, callback)` * `options`: table containing the following parameters - * `filepath`: path to a media file on the filesystem + * `filename`: name the media file will be usable as + (optional if `filepath` present) + * `filepath`: path to the file on the filesystem [*] + * `filedata`: the data of the file to be sent [*] * `to_player`: name of the player the media should be sent to instead of all players (optional) * `ephemeral`: boolean that marks the media as ephemeral, it will not be cached on the client (optional, default false) + * Exactly one of the paramters marked [*] must be specified. * `callback`: function with arguments `name`, which is a player name * Pushes the specified media file to client(s). (details below) The file must be a supported image, sound or model format. @@ -6605,6 +6762,9 @@ Server name twice is not possible/guaranteed to work. An exception to this is the use of `to_player` to send the same, already existent file to multiple chosen players. + * You can also call this at startup time. In that case `callback` MUST + be `nil` and you cannot use `ephemeral` or `to_player`, as these logically + do not make sense. * Clients will attempt to fetch files added this way via remote media, this can make transfer of bigger files painless (if set up). Nevertheless it is advised not to use dynamic media for big media files. @@ -6786,7 +6946,7 @@ Misc. (regardless of online status) * `minetest.hud_replace_builtin(name, hud_definition)` * Replaces definition of a builtin hud element - * `name`: `"breath"` or `"health"` + * `name`: `"breath"`, `"health"` or `"minimap"` * `hud_definition`: definition to replace builtin definition * `minetest.parse_relative_number(arg, relative_to)`: returns number or nil * Helper function for chat commands. @@ -7054,10 +7214,6 @@ Global tables * Map of registered decoration definitions, indexed by the `name` field. * If `name` is nil, the key is the object handle returned by `minetest.register_decoration`. -* `minetest.registered_schematics` - * Map of registered schematic definitions, indexed by the `name` field. - * If `name` is nil, the key is the object handle returned by - `minetest.register_schematic`. * `minetest.registered_chatcommands` * Map of registered chat command definitions, indexed by name * `minetest.registered_privileges` @@ -7273,6 +7429,8 @@ an itemstring, a table or `nil`. the item breaks after `max_uses` times * Valid `max_uses` range is [0,65536] * Does nothing if item is not a tool or if `max_uses` is 0 +* `get_wear_bar_params()`: returns the wear bar parameters of the item, + or nil if none are defined for this item type or in the stack's meta * `add_item(item)`: returns leftover `ItemStack` * Put some item or stack onto this stack * `item_fits(item)`: returns `true` if item or stack can be fully added to @@ -7314,6 +7472,10 @@ Can be obtained via `item:get_meta()`. * Overrides the item's tool capabilities * A nil value will clear the override data and restore the original behavior. +* `set_wear_bar_params([wear_bar_params])` + * Overrides the item's wear bar parameters (see "Wear Bar Color" section) + * A nil value will clear the override data and restore the original + behavior. `MetaDataRef` ------------- @@ -8397,11 +8559,14 @@ Player properties need to be saved manually. -- If `rotate = false`, the selection box will not rotate with the object itself, remaining fixed to the axes. -- If `rotate = true`, it will match the object's rotation and any attachment rotations. -- Raycasts use the selection box and object's rotation, but do *not* obey attachment rotations. + -- For server-side raycasts to work correctly, + -- the selection box should extend at most 5 units in each direction. pointable = true, -- Can be `true` if it is pointable, `false` if it can be pointed through, -- or `"blocking"` if it is pointable but not selectable. + -- Clients older than 5.9.0 interpret `pointable = "blocking"` as `pointable = true`. -- Can be overridden by the `pointabilities` of the held item. visual = "cube" / "sprite" / "upright_sprite" / "mesh" / "wielditem" / "item", @@ -8814,6 +8979,19 @@ Used by `minetest.register_node`, `minetest.register_craftitem`, and -- fallback behavior. }, + -- Set wear bar color of the tool by setting color stops and blend mode + -- See "Wear Bar Color" section for further explanation including an example + wear_color = { + -- interpolation mode: 'constant' or 'linear' + -- (nil defaults to 'constant') + blend = "linear", + color_stops = { + [0.0] = "#ff0000", + [0.5] = "#ffff00", + [1.0] = "#00ff00", + } + }, + node_placement_prediction = nil, -- If nil and item is node, prediction is made automatically. -- If nil and item is not a node, no prediction is made. @@ -9015,6 +9193,7 @@ Used by `minetest.register_node`. pointable = true, -- Can be `true` if it is pointable, `false` if it can be pointed through, -- or `"blocking"` if it is pointable but not selectable. + -- Clients older than 5.9.0 interpret `pointable = "blocking"` as `pointable = true`. -- Can be overridden by the `pointabilities` of the held item. -- A client may be able to point non-pointable nodes, since it isn't checked server-side. @@ -9380,6 +9559,46 @@ Used by `minetest.register_node`. } ``` +Wear Bar Color +-------------- + +'Wear Bar' is a property of items that defines the coloring +of the bar that appears under damaged tools. +If it is absent, the default behavior of green-yellow-red is +used. + +### Wear bar colors definition + +#### Syntax + +```lua +{ + -- 'constant' or 'linear' + -- (nil defaults to 'constant') + blend = "linear", + color_stops = { + [0.0] = "#ff0000", + [0.5] = "slateblue", + [1.0] = {r=0, g=255, b=0, a=150}, + } +} +``` + +#### Blend mode `blend` + +* `linear`: blends smoothly between each defined color point. +* `constant`: each color starts at its defined point, and continues up to the next point + +#### Color stops `color_stops` + +Specified as `ColorSpec` color values assigned to `float` durability keys. + +"Durability" is defined as `1 - (wear / 65535)`. + +#### Shortcut usage + +Wear bar color can also be specified as a single `ColorSpec` instead of a table. + Crafting recipes ---------------- diff --git a/doc/menu_lua_api.md b/doc/menu_lua_api.md index 67f7edc69..3e8bb3583 100644 --- a/doc/menu_lua_api.md +++ b/doc/menu_lua_api.md @@ -323,6 +323,7 @@ Package - content which is downloadable from the content db, may or may not be i description = "description", author = "author", path = "path/to/content", + textdomain = "textdomain", -- textdomain to translate title / description with depends = {"mod", "names"}, -- mods only optional_depends = {"mod", "names"}, -- mods only } @@ -340,6 +341,13 @@ Package - content which is downloadable from the content db, may or may not be i error_message = "", -- message or nil } ``` +* `core.get_content_translation(path, domain, string)` + * Translates `string` using `domain` in content directory at `path`. + * Textdomains will be found by looking through all locale folders. + * String should contain translation markup from `core.translate(textdomain, ...)`. + * Ex: `core.get_content_translation("mods/mymod", "mymod", core.translate("mymod", "Hello World"))` + will translate "Hello World" into the current user's language + using `mods/mymod/locale/mymod.fr.tr`. Logging ------- diff --git a/doc/minetest.6 b/doc/minetest.6 index 06062721e..dcd38501f 100644 --- a/doc/minetest.6 +++ b/doc/minetest.6 @@ -119,7 +119,7 @@ Display an interactive terminal over ncurses during execution. .SH ENVIRONMENT .TP -.B MINETEST_SUBGAME_PATH +.B MINETEST_GAME_PATH Colon delimited list of directories to search for games. .TP .B MINETEST_MOD_PATH diff --git a/doc/texture_packs.md b/doc/texture_packs.md index bea1af93a..b6f9306d9 100644 --- a/doc/texture_packs.md +++ b/doc/texture_packs.md @@ -25,8 +25,14 @@ texture pack. The name must not be “base”. ### `texture_pack.conf` A key-value config file with the following keys: -* `title` - human readable title +* `name`: The texture pack name. Allows Minetest to determine the texture pack name even if + the folder is wrongly named. +* `title` - human-readable title * `description` - short description, shown in the content tab +* `author`: The author's ContentDB username. +* `textdomain`: Textdomain used to translate title and description. + Defaults to the texture pack name. + See [Translating content meta](lua_api.md#translating-content-meta). ### `description.txt` **Deprecated**, you should use texture_pack.conf instead. @@ -205,7 +211,8 @@ Here are targets you can choose from: Nodes support all targets, but other items only support 'inventory' and 'wield'. -¹ : `N` is an integer [0,255]. Sets align_style = "world" and scale = N on the tile, refer to lua_api.md for details. +¹ : `N` is an integer [0,255]. Sets align_style = "world" and scale = N on the tile, + refer to lua_api.md for details. ### Using the special targets diff --git a/games/devtest/mods/basetools/init.lua b/games/devtest/mods/basetools/init.lua index c0bd1d00b..aa91d5e92 100644 --- a/games/devtest/mods/basetools/init.lua +++ b/games/devtest/mods/basetools/init.lua @@ -420,36 +420,141 @@ minetest.register_tool("basetools:dagger_steel", { } }) --- Test tool uses and punch_attack_uses -local uses = { 1, 2, 3, 5, 10, 50, 100, 1000, 10000, 65535 } -for i=1, #uses do - local u = uses[i] - local ustring - if i == 1 then - ustring = u.."-Use" - else - ustring = u.."-Uses" - end - local color = string.format("#FF00%02X", math.floor(((i-1)/#uses) * 255)) - minetest.register_tool("basetools:pick_uses_"..string.format("%05d", u), { +-- Test tool uses, punch_attack_uses, and wear bar coloring +local tool_params = { + {uses = 1}, + {uses = 2}, + {uses = 3}, + { + uses = 5, + wear_color = "#5865f2", + wear_description = "Solid color: #5865f2", + }, + { + uses = 10, + wear_color = "slateblue", + wear_description = "Solid color: slateblue", + }, + { + uses = 50, + wear_color = { + color_stops = { + [0] = "red", + [0.5] = "yellow", + [1.0] = "blue" + }, + blend = "linear" + }, + wear_description = "Ranges from blue to yellow to red", + }, + { + uses = 100, + wear_color = { + color_stops = { + [0] = "#ffff00", + [0.2] = "#ff00ff", + [0.3] = "#ffff00", + [0.45] = "#c0ffee", + [0.6] = {r=255, g=255, b=0, a=100}, -- continues until the end + }, + blend = "constant" + }, + wear_description = "Misc. colors, constant interpolation", + }, + {uses = 1e3}, + {uses = 1e4}, + {uses = 65535}, +} + +for i, params in ipairs(tool_params) do + local uses = params.uses + local ustring = uses.."-Use"..(uses == 1 and "" or "s") + local color = string.format("#FF00%02X", math.floor(((i-1)/#tool_params) * 255)) + minetest.register_tool("basetools:pick_uses_"..string.format("%05d", uses), { description = ustring.." Pickaxe".."\n".. - "Digs cracky=3", + "Digs cracky=3".. + (params.wear_description and "\n".."Wear bar: " .. params.wear_description or ""), inventory_image = "basetools_usespick.png^[colorize:"..color..":127", tool_capabilities = { max_drop_level=0, groupcaps={ - cracky={times={[3]=0.1, [2]=0.2, [1]=0.3}, uses=u, maxlevel=0} + cracky={times={[3]=0.1, [2]=0.2, [1]=0.3}, uses=uses, maxlevel=0} }, }, + wear_color = params.wear_color }) - minetest.register_tool("basetools:sword_uses_"..string.format("%05d", u), { + minetest.register_tool("basetools:sword_uses_"..string.format("%05d", uses), { description = ustring.." Sword".."\n".. "Damage: fleshy=1", inventory_image = "basetools_usessword.png^[colorize:"..color..":127", tool_capabilities = { damage_groups = {fleshy=1}, - punch_attack_uses = u, + punch_attack_uses = uses, }, }) end + +minetest.register_chatcommand("wear_color", { + params = "[idx]", + description = "Set wear bar color override", + func = function(player_name, param) + local player = minetest.get_player_by_name(player_name) + if not player then return end + + local wear_color = nil + local wear_desc = "Reset override" + + if param ~= "" then + local params = tool_params[tonumber(param)] + if not params then + return false, "idx out of bounds" + end + wear_color = params.wear_color + wear_desc = "Set override: "..(params.wear_description or "Default behavior") + end + local tool = player:get_wielded_item() + if tool:get_count() == 0 then + return false, "Tool not found" + end + tool:get_meta():set_wear_bar_params(wear_color) + player:set_wielded_item(tool) + return true, wear_desc + end +}) + +-- Punch handler to set random color & wear +local wear_on_use = function(itemstack, user, pointed_thing) + local meta = itemstack:get_meta() + local color = math.random(0, 0xFFFFFF) + local colorstr = string.format("#%06x", color) + meta:set_wear_bar_params(colorstr) + minetest.log("action", "[basetool] Wear bar color of "..itemstack:get_name().." changed to "..colorstr) + itemstack:set_wear(math.random(0, 65535)) + return itemstack +end + +-- Place handler to clear item metadata color +local wear_on_place = function(itemstack, user, pointed_thing) + local meta = itemstack:get_meta() + meta:set_wear_bar_params(nil) + return itemstack +end + +minetest.register_tool("basetools:random_wear_bar", { + description = "Wear Bar Color Test\n" .. + "Punch: Set random color & wear\n" .. + "Place: Clear color", + -- Base texture: A grayscale square (can be colorized) + inventory_image = "basetools_usespick.png^[colorize:#FFFFFF:127", + tool_capabilities = { + max_drop_level=0, + groupcaps={ + cracky={times={[3]=0.1, [2]=0.2, [1]=0.3}, uses=1000, maxlevel=0} + }, + }, + + on_use = wear_on_use, + on_place = wear_on_place, + on_secondary_use = wear_on_place, +}) diff --git a/games/devtest/mods/testnodes/liquids.lua b/games/devtest/mods/testnodes/liquids.lua index 13b8dd9d7..334b16dff 100644 --- a/games/devtest/mods/testnodes/liquids.lua +++ b/games/devtest/mods/testnodes/liquids.lua @@ -9,7 +9,7 @@ for d=0, 8 do end minetest.register_node("testnodes:rliquid_"..d, { description = "Test Liquid Source, Range "..d.. - tt_normal, + tt_normal .. "\n" .. "(falling & floating node)", drawtype = "liquid", tiles = {"testnodes_liquidsource_r"..d..".png"}, special_tiles = { @@ -25,6 +25,8 @@ for d=0, 8 do liquid_alternative_flowing = "testnodes:rliquid_flowing_"..d, liquid_alternative_source = "testnodes:rliquid_"..d, liquid_range = d, + -- Also use these nodes to test falling, floating liquid source nodes + groups = {float = 1, falling_node = 1}, }) minetest.register_node("testnodes:rliquid_flowing_"..d, { diff --git a/games/devtest/mods/testnodes/textures.lua b/games/devtest/mods/testnodes/textures.lua index 9ecf0cc12..8c7f198c3 100644 --- a/games/devtest/mods/testnodes/textures.lua +++ b/games/devtest/mods/testnodes/textures.lua @@ -149,15 +149,28 @@ fractal = nil frac_emb = nil checker = nil -local textures_path = minetest.get_modpath( minetest.get_current_modname() ) .. "/textures/" -minetest.safe_file_write( - textures_path .. "testnodes_generated_mb.png", +do + -- we used to write the textures to our mod folder. in order to avoid + -- duplication errors delete them if they still exist. + local path = core.get_modpath(core.get_current_modname()) .. "/textures/" + os.remove(path .. "testnodes_generated_mb.png") + os.remove(path .. "testnodes_generated_ck.png") +end + +local textures_path = core.get_worldpath() .. "/" +core.safe_file_write( + textures_path .. "testnodes1.png", encode_and_check(512, 512, "rgb", data_mb) ) -minetest.safe_file_write( - textures_path .. "testnodes_generated_ck.png", - encode_and_check(512, 512, "gray", data_ck) -) +local png_ck = encode_and_check(512, 512, "gray", data_ck) +core.dynamic_add_media({ + filename = "testnodes_generated_mb.png", + filepath = textures_path .. "testnodes1.png" +}) +core.dynamic_add_media({ + filename = "testnodes_generated_ck.png", + filedata = png_ck, +}) minetest.register_node("testnodes:generated_png_mb", { description = S("Generated Mandelbrot PNG Test Node"), @@ -200,6 +213,8 @@ minetest.register_node("testnodes:generated_png_dst_emb", { groups = { dig_immediate = 2 }, }) +png_ck = nil +png_emb = nil data_emb = nil data_mb = nil data_ck = nil diff --git a/games/devtest/mods/testtools/init.lua b/games/devtest/mods/testtools/init.lua index 09280f61f..2378cf982 100644 --- a/games/devtest/mods/testtools/init.lua +++ b/games/devtest/mods/testtools/init.lua @@ -6,6 +6,7 @@ testtools = {} dofile(minetest.get_modpath("testtools") .. "/light.lua") dofile(minetest.get_modpath("testtools") .. "/privatizer.lua") dofile(minetest.get_modpath("testtools") .. "/particles.lua") +dofile(minetest.get_modpath("testtools") .. "/node_box_visualizer.lua") local pointabilities_nodes = { nodes = { diff --git a/games/devtest/mods/testtools/node_box_visualizer.lua b/games/devtest/mods/testtools/node_box_visualizer.lua new file mode 100644 index 000000000..60ec8a518 --- /dev/null +++ b/games/devtest/mods/testtools/node_box_visualizer.lua @@ -0,0 +1,79 @@ +local S = minetest.get_translator("testtools") + +minetest.register_entity("testtools:visual_box", { + initial_properties = { + visual = "cube", + textures = { + "blank.png", "blank.png", "blank.png", + "blank.png", "blank.png", "blank.png", + }, + use_texture_alpha = true, + physical = false, + pointable = false, + static_save = false, + }, + + on_activate = function(self) + self.timestamp = minetest.get_us_time() + 5000000 + end, + + on_step = function(self) + if minetest.get_us_time() >= self.timestamp then + self.object:remove() + end + end, +}) + +local BOX_TYPES = {"node_box", "collision_box", "selection_box"} +local DEFAULT_BOX_TYPE = "selection_box" + +local function visualizer_on_use(itemstack, user, pointed_thing) + if pointed_thing.type ~= "node" then + return + end + + local meta = itemstack:get_meta() + local box_type = meta:get("box_type") or DEFAULT_BOX_TYPE + + local result = minetest.get_node_boxes(box_type, pointed_thing.under) + local t = "testtools_visual_" .. box_type .. ".png" + + for _, box in ipairs(result) do + local box_min = pointed_thing.under + vector.new(box[1], box[2], box[3]) + local box_max = pointed_thing.under + vector.new(box[4], box[5], box[6]) + local box_center = (box_min + box_max) / 2 + local obj = minetest.add_entity(box_center, "testtools:visual_box") + if not obj then + break + end + obj:set_properties({ + textures = {t, t, t, t, t, t}, + -- Add a small offset to avoid Z-fighting. + visual_size = vector.add(box_max - box_min, 0.01), + }) + end +end + +local function visualizer_on_place(itemstack, placer, pointed_thing) + local meta = itemstack:get_meta() + local prev_value = meta:get("box_type") or DEFAULT_BOX_TYPE + local prev_index = table.indexof(BOX_TYPES, prev_value) + assert(prev_index ~= -1) + + local new_value = BOX_TYPES[(prev_index % #BOX_TYPES) + 1] + meta:set_string("box_type", new_value) + minetest.chat_send_player(placer:get_player_name(), S("[Node Box Visualizer] box_type = @1", new_value)) + + return itemstack +end + +minetest.register_tool("testtools:node_box_visualizer", { + description = S("Node Box Visualizer") .. "\n" .. + S("Punch: Show node/collision/selection boxes of the pointed node") .. "\n" .. + S("Place: Change selected box type (default: selection box)"), + inventory_image = "testtools_node_box_visualizer.png", + groups = { testtool = 1, disable_repair = 1 }, + on_use = visualizer_on_use, + on_place = visualizer_on_place, + on_secondary_use = visualizer_on_place, +}) diff --git a/games/devtest/mods/testtools/textures/testtools_node_box_visualizer.png b/games/devtest/mods/testtools/textures/testtools_node_box_visualizer.png new file mode 100644 index 000000000..1b16aa794 Binary files /dev/null and b/games/devtest/mods/testtools/textures/testtools_node_box_visualizer.png differ diff --git a/games/devtest/mods/testtools/textures/testtools_visual_collision_box.png b/games/devtest/mods/testtools/textures/testtools_visual_collision_box.png new file mode 100644 index 000000000..c8ab97cdf Binary files /dev/null and b/games/devtest/mods/testtools/textures/testtools_visual_collision_box.png differ diff --git a/games/devtest/mods/testtools/textures/testtools_visual_node_box.png b/games/devtest/mods/testtools/textures/testtools_visual_node_box.png new file mode 100644 index 000000000..dbbb8d9f6 Binary files /dev/null and b/games/devtest/mods/testtools/textures/testtools_visual_node_box.png differ diff --git a/games/devtest/mods/testtools/textures/testtools_visual_selection_box.png b/games/devtest/mods/testtools/textures/testtools_visual_selection_box.png new file mode 100644 index 000000000..1f4bbf3c8 Binary files /dev/null and b/games/devtest/mods/testtools/textures/testtools_visual_selection_box.png differ diff --git a/games/devtest/mods/unittests/inside_mapgen_env.lua b/games/devtest/mods/unittests/inside_mapgen_env.lua new file mode 100644 index 000000000..a8df004de --- /dev/null +++ b/games/devtest/mods/unittests/inside_mapgen_env.lua @@ -0,0 +1,32 @@ +core.log("info", "Hello World") + +local function do_tests() + assert(core == minetest) + -- stuff that should not be here + assert(not core.get_player_by_name) + assert(not core.object_refs) + -- stuff that should be here + assert(core.register_on_generated) + assert(core.get_node) + assert(core.spawn_tree) + assert(ItemStack) + local meta = ItemStack():get_meta() + assert(type(meta) == "userdata") + assert(type(meta.set_tool_capabilities) == "function") + assert(core.registered_items[""]) + assert(core.save_gen_notify) + -- alias handling + assert(core.registered_items["unittests:steel_ingot_alias"].name == + "unittests:steel_ingot") + -- fallback to item defaults + assert(core.registered_items["unittests:description_test"].on_place == true) +end + +-- there's no (usable) communcation path between mapgen and the regular env +-- so we just run the test unconditionally +do_tests() + +core.register_on_generated(function(vm, pos1, pos2, blockseed) + local n = tonumber(core.get_mapgen_setting("chunksize")) * 16 - 1 + assert(pos2:subtract(pos1) == vector.new(n, n, n)) +end) diff --git a/games/devtest/mods/unittests/metadata.lua b/games/devtest/mods/unittests/metadata.lua index 6b0dcf04a..defd81dd3 100644 --- a/games/devtest/mods/unittests/metadata.lua +++ b/games/devtest/mods/unittests/metadata.lua @@ -77,6 +77,29 @@ local function test_metadata(meta) assert(not meta:equals(compare_meta)) end +local function test_metadata_compat(meta) + -- key/value removal using set_string (undocumented, deprecated way) + meta:set_string("key", "value") + assert(meta:get_string("key") == "value") + meta:set_string("key", nil) -- ignore warning + assert(meta:to_table().fields["key"] == nil) + + -- undocumented but supported consequence of Lua's + -- automatic string <--> number cast + meta:set_string("key", 2) + assert(meta:get_string("key") == "2") + + -- from_table with non-string keys (supported) + local values = meta:to_table() + values.fields["new"] = 420 + meta:from_table(values) + assert(meta:get_int("new") == 420) + values.fields["new"] = nil + meta:from_table(values) + assert(meta:get("new") == nil) +end + + local storage_a = core.get_mod_storage() local storage_b = core.get_mod_storage() local function test_mod_storage() @@ -86,7 +109,9 @@ end unittests.register("test_mod_storage", test_mod_storage) local function test_item_metadata() - test_metadata(ItemStack("unittest:coal_lump"):get_meta()) + local meta = ItemStack("unittest:coal_lump"):get_meta() + test_metadata(meta) + test_metadata_compat(meta) end unittests.register("test_item_metadata", test_item_metadata) diff --git a/games/devtest/mods/unittests/misc.lua b/games/devtest/mods/unittests/misc.lua index 6181d7617..034b99b52 100644 --- a/games/devtest/mods/unittests/misc.lua +++ b/games/devtest/mods/unittests/misc.lua @@ -1,3 +1,6 @@ +core.register_mapgen_script(core.get_modpath(core.get_current_modname()) .. + DIR_DELIM .. "inside_mapgen_env.lua") + local function test_pseudo_random() -- We have comprehensive unit tests in C++, this is just to make sure the API code isn't messing up local gen1 = PseudoRandom(13) @@ -108,6 +111,13 @@ unittests.register("test_punch_node", function(_, pos) -- currently failing: assert(on_punch_called) end, {map=true}) +local function test_hashing() + local input = "hello\000world" + assert(core.sha1(input) == "f85b420f1e43ebf88649dfcab302b898d889606c") + assert(core.sha256(input) == "b206899bc103669c8e7b36de29d73f95b46795b508aa87d612b2ce84bfb29df2") +end +unittests.register("test_hashing", test_hashing) + local function test_compress() -- This text should be compressible, to make sure the results are... normal local text = "The\000 icey canoe couldn't move very well on the\128 lake. The\000 ice was too stiff and the icey canoe's paddles simply wouldn't punch through." @@ -130,6 +140,12 @@ local function test_compress() end unittests.register("test_compress", test_compress) +local function test_urlencode() + -- checks that API code handles null bytes + assert(core.urlencode("foo\000bar!") == "foo%00bar%21") +end +unittests.register("test_urlencode", test_urlencode) + local function test_game_info() local info = minetest.get_game_info() local game_conf = Settings(info.path .. "/game.conf") @@ -204,3 +220,30 @@ local function test_on_mapblocks_changed(cb, player, pos) end end unittests.register("test_on_mapblocks_changed", test_on_mapblocks_changed, {map=true, async=true}) + +local function test_gennotify_api() + local DECO_ID = 123 + local UD_ID = "unittests:dummy" + + -- the engine doesn't check if the id is actually valid, maybe it should + core.set_gen_notify({decoration=true}, {DECO_ID}) + + core.set_gen_notify({custom=true}, nil, {UD_ID}) + + local flags, deco, custom = core.get_gen_notify() + local function ff(flag) + return (" " .. flags .. " "):match("[ ,]" .. flag .. "[ ,]") ~= nil + end + assert(ff("decoration"), "'decoration' flag missing") + assert(ff("custom"), "'custom' flag missing") + assert(table.indexof(deco, DECO_ID) > 0) + assert(table.indexof(custom, UD_ID) > 0) + + core.set_gen_notify({decoration=false, custom=false}) + + flags, deco, custom = core.get_gen_notify() + assert(not ff("decoration") and not ff("custom")) + assert(#deco == 0, "deco ids not empty") + assert(#custom == 0, "custom ids not empty") +end +unittests.register("test_gennotify_api", test_gennotify_api) diff --git a/games/devtest/mods/util_commands/init.lua b/games/devtest/mods/util_commands/init.lua index 48cd47f10..6285d4754 100644 --- a/games/devtest/mods/util_commands/init.lua +++ b/games/devtest/mods/util_commands/init.lua @@ -210,6 +210,29 @@ minetest.register_chatcommand("dump_item", { end, }) +minetest.register_chatcommand("dump_itemdef", { + params = "", + description = "Prints a dump of the wielded item's definition in table form", + func = function(name, param) + local player = minetest.get_player_by_name(name) + local str = dump(player:get_wielded_item():get_definition()) + print(str) + return true, str + end, +}) + +minetest.register_chatcommand("dump_wear_bar", { + params = "", + description = "Prints a dump of the wielded item's wear bar parameters in table form", + func = function(name, param) + local player = minetest.get_player_by_name(name) + local item = player:get_wielded_item() + local str = dump(item:get_wear_bar_params()) + print(str) + return true, str + end, +}) + core.register_chatcommand("set_saturation", { params = "", description = "Set the saturation for current player.", diff --git a/lib/irrlichtmt b/lib/irrlichtmt index 85081d6fe..154064522 160000 --- a/lib/irrlichtmt +++ b/lib/irrlichtmt @@ -1 +1 @@ -Subproject commit 85081d6fe0c422cf47f74714ee25562715528aa2 +Subproject commit 154064522ef548c019ce66131ea654642e663a42 diff --git a/misc/irrlichtmt_tag.txt b/misc/irrlichtmt_tag.txt index 287b95d7b..98b3fdbf1 100644 --- a/misc/irrlichtmt_tag.txt +++ b/misc/irrlichtmt_tag.txt @@ -1 +1 @@ -1.9.0mt14 +1.9.0mt15 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6879f2e86..5eb6677a9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -109,9 +109,10 @@ if(BUILD_CLIENT AND ENABLE_SOUND) endif() endif() -option(ENABLE_TOUCH "Enable Touchscreen support" FALSE) +option(ENABLE_TOUCH "Enable touchscreen by default" FALSE) if(ENABLE_TOUCH) - add_definitions(-DHAVE_TOUCHSCREENGUI) + message(STATUS "Touchscreen support enabled by default.") + add_definitions(-DENABLE_TOUCH) endif() if(BUILD_CLIENT) diff --git a/src/activeobject.h b/src/activeobject.h index 02c16b557..52f997fdf 100644 --- a/src/activeobject.h +++ b/src/activeobject.h @@ -46,7 +46,7 @@ enum ActiveObjectType { struct ActiveObjectMessage { - ActiveObjectMessage(u16 id_, bool reliable_=true, const std::string &data_ = "") : + ActiveObjectMessage(u16 id_, bool reliable_=true, std::string_view data_ = "") : id(id_), reliable(reliable_), datastring(data_) diff --git a/src/chat.cpp b/src/chat.cpp index 4ba074538..5331a87bd 100644 --- a/src/chat.cpp +++ b/src/chat.cpp @@ -577,6 +577,7 @@ void ChatPrompt::historyNext() void ChatPrompt::nickCompletion(const std::set &names, bool backwards) { + const std::wstring_view line(getLineRef()); // Two cases: // (a) m_nick_completion_start == m_nick_completion_end == 0 // Then no previous nick completion is active. @@ -586,7 +587,6 @@ void ChatPrompt::nickCompletion(const std::set &names, bool backwar // m_nick_completion_start..m_nick_completion_end are the // interval where the originally used prefix was. Cycle // through the list of completions of that prefix. - const std::wstring &line = getLineRef(); u32 prefix_start = m_nick_completion_start; u32 prefix_end = m_nick_completion_end; bool initial = (prefix_end == 0); @@ -601,7 +601,7 @@ void ChatPrompt::nickCompletion(const std::set &names, bool backwar if (prefix_start == prefix_end) return; } - std::wstring prefix = line.substr(prefix_start, prefix_end - prefix_start); + auto prefix = line.substr(prefix_start, prefix_end - prefix_start); // find all names that start with the selected prefix std::vector completions; @@ -624,7 +624,7 @@ void ChatPrompt::nickCompletion(const std::set &names, bool backwar { while (word_end < line.size() && !iswspace(line[word_end])) ++word_end; - std::wstring word = line.substr(prefix_start, word_end - prefix_start); + auto word = line.substr(prefix_start, word_end - prefix_start); // cycle through completions for (u32 i = 0; i < completions.size(); ++i) @@ -640,7 +640,7 @@ void ChatPrompt::nickCompletion(const std::set &names, bool backwar } } } - std::wstring replacement = completions[replacement_index]; + const auto &replacement = completions[replacement_index]; if (word_end < line.size() && iswspace(line[word_end])) ++word_end; diff --git a/src/client/camera.cpp b/src/client/camera.cpp index af70f4ebd..fda9948e9 100644 --- a/src/client/camera.cpp +++ b/src/client/camera.cpp @@ -138,8 +138,8 @@ void Camera::notifyFovChange() // Returns the fractional part of x inline f32 my_modf(f32 x) { - double dummy; - return modf(x, &dummy); + float dummy; + return std::modf(x, &dummy); } void Camera::step(f32 dtime) @@ -407,10 +407,10 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio) f32 bobdir = (m_view_bobbing_anim < 0.5) ? 1.0 : -1.0; f32 bobknob = 1.2; - f32 bobtmp = sin(pow(bobfrac, bobknob) * M_PI); + f32 bobtmp = std::sin(std::pow(bobfrac, bobknob) * M_PI); v3f bobvec = v3f( - 0.3 * bobdir * sin(bobfrac * M_PI), + 0.3 * bobdir * std::sin(bobfrac * M_PI), -0.28 * bobtmp * bobtmp, 0.); @@ -531,11 +531,9 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio) addArmInertia(yaw); // Position the wielded item - //v3f wield_position = v3f(45, -35, 65); v3f wield_position = v3f(m_wieldmesh_offset.X, m_wieldmesh_offset.Y, 65); - //v3f wield_rotation = v3f(-100, 120, -100); v3f wield_rotation = v3f(-100, 120, -100); - wield_position.Y += fabs(m_wield_change_timer)*320 - 40; + wield_position.Y += std::abs(m_wield_change_timer)*320 - 40; if(m_digging_anim < 0.05 || m_digging_anim > 0.5) { f32 frac = 1.0; @@ -543,33 +541,29 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio) frac = 2.0 * (m_digging_anim - 0.5); // This value starts from 1 and settles to 0 f32 ratiothing = std::pow((1.0f - tool_reload_ratio), 0.5f); - //f32 ratiothing2 = pow(ratiothing, 0.5f); f32 ratiothing2 = (easeCurve(ratiothing*0.5))*2.0; - wield_position.Y -= frac * 25.0 * pow(ratiothing2, 1.7f); - //wield_position.Z += frac * 5.0 * ratiothing2; - wield_position.X -= frac * 35.0 * pow(ratiothing2, 1.1f); - wield_rotation.Y += frac * 70.0 * pow(ratiothing2, 1.4f); - //wield_rotation.X -= frac * 15.0 * pow(ratiothing2, 1.4f); - //wield_rotation.Z += frac * 15.0 * pow(ratiothing2, 1.0f); + wield_position.Y -= frac * 25.0f * std::pow(ratiothing2, 1.7f); + wield_position.X -= frac * 35.0f * std::pow(ratiothing2, 1.1f); + wield_rotation.Y += frac * 70.0f * std::pow(ratiothing2, 1.4f); } if (m_digging_button != -1) { f32 digfrac = m_digging_anim; - wield_position.X -= 50 * sin(pow(digfrac, 0.8f) * M_PI); - wield_position.Y += 24 * sin(digfrac * 1.8 * M_PI); + wield_position.X -= 50 * std::sin(std::pow(digfrac, 0.8f) * M_PI); + wield_position.Y += 24 * std::sin(digfrac * 1.8 * M_PI); wield_position.Z += 25 * 0.5; // Euler angles are PURE EVIL, so why not use quaternions? core::quaternion quat_begin(wield_rotation * core::DEGTORAD); core::quaternion quat_end(v3f(80, 30, 100) * core::DEGTORAD); core::quaternion quat_slerp; - quat_slerp.slerp(quat_begin, quat_end, sin(digfrac * M_PI)); + quat_slerp.slerp(quat_begin, quat_end, std::sin(digfrac * M_PI)); quat_slerp.toEuler(wield_rotation); wield_rotation *= core::RADTODEG; } else { f32 bobfrac = my_modf(m_view_bobbing_anim); - wield_position.X -= sin(bobfrac*M_PI*2.0) * 3.0; - wield_position.Y += sin(my_modf(bobfrac*2.0)*M_PI) * 3.0; + wield_position.X -= std::sin(bobfrac*M_PI*2.0) * 3.0; + wield_position.Y += std::sin(my_modf(bobfrac*2.0)*M_PI) * 3.0; } m_wieldnode->setPosition(wield_position); m_wieldnode->setRotation(wield_rotation); @@ -584,8 +578,8 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio) // view bobbing is enabled and free_move is off, // start (or continue) the view bobbing animation. const v3f &speed = player->getSpeed(); - const bool movement_XZ = hypot(speed.X, speed.Z) > BS; - const bool movement_Y = fabs(speed.Y) > BS; + const bool movement_XZ = std::hypot(speed.X, speed.Z) > BS; + const bool movement_Y = std::abs(speed.Y) > BS; const bool walking = movement_XZ && player->touching_ground; const bool swimming = (movement_XZ || player->swimming_vertical) && player->in_liquid; diff --git a/src/client/client.cpp b/src/client/client.cpp index 5d9c07ce5..ba4c8ee98 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -1806,7 +1806,7 @@ struct TextureUpdateArgs { void Client::showUpdateProgressTexture(void *args, u32 progress, u32 max_progress) { TextureUpdateArgs* targs = (TextureUpdateArgs*) args; - u16 cur_percent = ceil(progress / (double) max_progress * 100.); + u16 cur_percent = std::ceil(progress / max_progress * 100.f); // update the loading menu -- if necessary bool do_draw = false; @@ -1971,21 +1971,11 @@ void Client::makeScreenshot() raw_image->drop(); } -bool Client::shouldShowMinimap() const -{ - return !m_minimap_disabled_by_server; -} - void Client::pushToEventQueue(ClientEvent *event) { m_client_event_queue.push(event); } -void Client::showMinimap(const bool show) -{ - m_game_ui->showMinimap(show); -} - // IGameDef interface // Under envlock IItemDefManager* Client::getItemDefManager() @@ -2133,3 +2123,11 @@ const std::string &Client::getFormspecPrepend() const { return m_env.getLocalPlayer()->formspec_prepend; } + +void Client::removeActiveObjectSounds(u16 id) +{ + for (auto it : m_sounds_to_objects) { + if (it.second == id) + m_sound->stopSound(it.first); + } +} diff --git a/src/client/client.h b/src/client/client.h index 3a630c27c..ae003c011 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -369,8 +369,6 @@ public: Camera* getCamera () { return m_camera; } scene::ISceneManager *getSceneManager(); - bool shouldShowMinimap() const; - // IGameDef interface IItemDefManager* getItemDefManager() override; const NodeDefManager* getNodeDefManager() override; @@ -412,8 +410,6 @@ public: void pushToEventQueue(ClientEvent *event); - void showMinimap(bool show = true); - // IP and port we're connected to const Address getServerAddress(); @@ -475,6 +471,9 @@ private: bool canSendChatMessage() const; + // remove sounds attached to object + void removeActiveObjectSounds(u16 id); + float m_packetcounter_timer = 0.0f; float m_connection_reinit_timer = 0.1f; float m_avg_rtt_timer = 0.0f; @@ -498,7 +497,6 @@ private: ELoginRegister m_allow_login_or_register = ELoginRegister::Any; Camera *m_camera = nullptr; Minimap *m_minimap = nullptr; - bool m_minimap_disabled_by_server = false; // Server serialization version u8 m_server_ser_ver; diff --git a/src/client/clientenvironment.cpp b/src/client/clientenvironment.cpp index b9bb82bdf..af815c015 100644 --- a/src/client/clientenvironment.cpp +++ b/src/client/clientenvironment.cpp @@ -182,7 +182,7 @@ void ClientEnvironment::step(float dtime) Stuff that has a maximum time increment */ - u32 steps = ceil(dtime / dtime_max_increment); + u32 steps = std::ceil(dtime / dtime_max_increment); f32 dtime_part = dtime / steps; for (; steps > 0; --steps) { /* diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp index aaaf5c483..c23a1e3e9 100644 --- a/src/client/clientlauncher.cpp +++ b/src/client/clientlauncher.cpp @@ -114,12 +114,6 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args) m_rendering_engine->setupTopLevelWindow(); - /* - This changes the minimum allowed number of vertices in a VBO. - Default is 500. - */ - //driver->setMinHardwareBufferVertexCount(50); - // Create game callback for menus g_gamecallback = new MainGameCallback(); @@ -204,11 +198,18 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args) while (m_rendering_engine->run() && !*kill && !g_gamecallback->shutdown_requested) { // Set the window caption +#if IRRLICHT_VERSION_MT_REVISION >= 15 + auto driver_name = m_rendering_engine->getVideoDriver()->getName(); +#else + auto driver_name = wide_to_utf8(m_rendering_engine->getVideoDriver()->getName()); +#endif + std::string caption = std::string(PROJECT_NAME_C) + + " " + g_version_hash + + " [" + gettext("Main Menu") + "]" + + " [" + driver_name + "]"; + m_rendering_engine->get_raw_device()-> - setWindowCaption((utf8_to_wide(PROJECT_NAME_C) + - L" " + utf8_to_wide(g_version_hash) + - L" [" + wstrgettext("Main Menu") + L"]" + - L" [" + m_rendering_engine->getVideoDriver()->getName() + L"]" ).c_str()); + setWindowCaption(utf8_to_wide(caption).c_str()); try { // This is used for catching disconnects @@ -248,10 +249,10 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args) m_rendering_engine->get_video_driver()->setTextureCreationFlag( video::ETCF_CREATE_MIP_MAPS, g_settings->getBool("mip_map")); -#ifdef HAVE_TOUCHSCREENGUI - receiver->m_touchscreengui = new TouchScreenGUI(m_rendering_engine->get_raw_device(), receiver); - g_touchscreengui = receiver->m_touchscreengui; -#endif + if (g_settings->getBool("enable_touch")) { + receiver->m_touchscreengui = new TouchScreenGUI(m_rendering_engine->get_raw_device(), receiver); + g_touchscreengui = receiver->m_touchscreengui; + } the_game( kill, @@ -282,11 +283,11 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args) m_rendering_engine->get_scene_manager()->clear(); -#ifdef HAVE_TOUCHSCREENGUI - delete g_touchscreengui; - g_touchscreengui = NULL; - receiver->m_touchscreengui = NULL; -#endif + if (g_touchscreengui) { + delete g_touchscreengui; + g_touchscreengui = NULL; + receiver->m_touchscreengui = NULL; + } /* Save the settings when leaving the game. * This makes sure that setting changes made in-game are persisted even diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp index cc2ed6516..45995c0ea 100644 --- a/src/client/clientmap.cpp +++ b/src/client/clientmap.cpp @@ -1305,7 +1305,7 @@ 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); + f32 sorting_distance_sq = std::pow(m_cache_transparency_sorting_distance * BS, 2.0f); // Update the order of transparent mesh buffers in each mesh diff --git a/src/client/clientmedia.cpp b/src/client/clientmedia.cpp index f78fe9e35..764fac422 100644 --- a/src/client/clientmedia.cpp +++ b/src/client/clientmedia.cpp @@ -29,6 +29,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/serialize.h" #include "util/sha1.h" #include "util/string.h" +#include static std::string getMediaCacheDir() { @@ -41,7 +42,16 @@ bool clientMediaUpdateCache(const std::string &raw_hash, const std::string &file std::string sha1_hex = hex_encode(raw_hash); if (!media_cache.exists(sha1_hex)) return media_cache.update(sha1_hex, filedata); - return true; + return false; +} + +bool clientMediaUpdateCacheCopy(const std::string &raw_hash, const std::string &path) +{ + FileCache media_cache(getMediaCacheDir()); + std::string sha1_hex = hex_encode(raw_hash); + if (!media_cache.exists(sha1_hex)) + return media_cache.updateCopyFile(sha1_hex, path); + return false; } /* @@ -189,10 +199,6 @@ void ClientMediaDownloader::initialStep(Client *client) assert(m_uncached_received_count == 0); - // Create the media cache dir if we are likely to write to it - if (m_uncached_count != 0) - createCacheDirs(); - // If we found all files in the cache, report this fact to the server. // If the server reported no remote servers, immediately start // conventional transfers. Note: if cURL support is not compiled in, @@ -511,18 +517,6 @@ IClientMediaDownloader::IClientMediaDownloader(): { } -void IClientMediaDownloader::createCacheDirs() -{ - if (!m_write_to_cache) - return; - - std::string path = getMediaCacheDir(); - if (!fs::CreateAllDirs(path)) { - errorstream << "Client: Could not create media cache directory: " - << path << std::endl; - } -} - bool IClientMediaDownloader::tryLoadFromCache(const std::string &name, const std::string &sha1, Client *client) { @@ -547,11 +541,9 @@ bool IClientMediaDownloader::checkAndLoad( // Compute actual checksum of data std::string data_sha1; { - SHA1 data_sha1_calculator; - data_sha1_calculator.addBytes(data.c_str(), data.size()); - unsigned char *data_tmpdigest = data_sha1_calculator.getDigest(); - data_sha1.assign((char*) data_tmpdigest, 20); - free(data_tmpdigest); + SHA1 ctx; + ctx.addBytes(data); + data_sha1 = ctx.getDigest(); } // Check that received file matches announced checksum @@ -726,8 +718,6 @@ void SingleMediaDownloader::initialStep(Client *client) if (isDone()) return; - createCacheDirs(); - // If the server reported no remote servers, immediately fall back to // conventional transfer. if (!USE_CURL || m_remotes.empty()) { diff --git a/src/client/clientmedia.h b/src/client/clientmedia.h index c297d737f..27af86e4b 100644 --- a/src/client/clientmedia.h +++ b/src/client/clientmedia.h @@ -22,7 +22,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes.h" #include "filecache.h" #include "util/basic_macros.h" -#include #include #include #include @@ -35,10 +34,15 @@ struct HTTPFetchResult; #define MTHASHSET_FILE_NAME "index.mth" // Store file into media cache (unless it exists already) -// Validating the hash is responsibility of the caller +// Caller should check the hash. +// return true if something was updated bool clientMediaUpdateCache(const std::string &raw_hash, const std::string &filedata); +// Copy file on disk(!) into media cache (unless it exists already) +bool clientMediaUpdateCacheCopy(const std::string &raw_hash, + const std::string &path); + // more of a base class than an interface but this name was most convenient... class IClientMediaDownloader { @@ -81,8 +85,6 @@ protected: virtual bool loadMedia(Client *client, const std::string &data, const std::string &name) = 0; - void createCacheDirs(); - bool tryLoadFromCache(const std::string &name, const std::string &sha1, Client *client); diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index d80869d9c..7e8dfaeb2 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -1216,7 +1216,7 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) } } - if (node && fabs(m_prop.automatic_rotate) > 0.001f) { + if (node && std::abs(m_prop.automatic_rotate) > 0.001f) { // This is the child node's rotation. It is only used for automatic_rotate. v3f local_rot = node->getRotation(); local_rot.Y = modulo360f(local_rot.Y - dtime * core::RADTODEG * diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp index 297a07bcd..f7cb7b473 100644 --- a/src/client/content_mapblock.cpp +++ b/src/client/content_mapblock.cpp @@ -77,7 +77,7 @@ MapblockMeshGenerator::MapblockMeshGenerator(MeshMakeData *input, MeshCollector scene::IMeshManipulator *mm): data(input), collector(output), - nodedef(data->m_client->ndef()), + nodedef(data->nodedef), meshmanip(mm), blockpos_nodes(data->m_blockpos * MAP_BLOCKSIZE), enable_mesh_cache(g_settings->getBool("enable_mesh_cache") && @@ -617,14 +617,14 @@ void MapblockMeshGenerator::calculateCornerLevels() cur_liquid.corner_levels[k][i] = getCornerLevel(i, k); } -f32 MapblockMeshGenerator::getCornerLevel(int i, int k) +f32 MapblockMeshGenerator::getCornerLevel(int i, int k) const { float sum = 0; int count = 0; int air_count = 0; for (int dk = 0; dk < 2; dk++) for (int di = 0; di < 2; di++) { - LiquidData::NeighborData &neighbor_data = cur_liquid.neighbors[k + dk][i + di]; + const LiquidData::NeighborData &neighbor_data = cur_liquid.neighbors[k + dk][i + di]; content_t content = neighbor_data.content; // If top is liquid, draw starting from top of node diff --git a/src/client/content_mapblock.h b/src/client/content_mapblock.h index 99abb7f99..730330a03 100644 --- a/src/client/content_mapblock.h +++ b/src/client/content_mapblock.h @@ -138,7 +138,7 @@ private: void prepareLiquidNodeDrawing(); void getLiquidNeighborhood(); void calculateCornerLevels(); - f32 getCornerLevel(int i, int k); + f32 getCornerLevel(int i, int k) const; void drawLiquidSides(); void drawLiquidTop(); void drawLiquidBottom(); diff --git a/src/client/event_manager.h b/src/client/event_manager.h index 16f7bcf07..99895049c 100644 --- a/src/client/event_manager.h +++ b/src/client/event_manager.h @@ -70,14 +70,13 @@ public: } void dereg(MtEvent::Type type, event_receive_func f, void *data) override { - std::map::iterator i = m_dest.find(type); + auto i = m_dest.find(type); if (i != m_dest.end()) { std::list &funcs = i->second.funcs; - auto j = funcs.begin(); - while (j != funcs.end()) { + for (auto j = funcs.begin(); j != funcs.end(); ) { bool remove = (j->f == f && (!data || j->d == data)); if (remove) - funcs.erase(j++); + j = funcs.erase(j); else ++j; } diff --git a/src/client/filecache.cpp b/src/client/filecache.cpp index 46bbe4059..cb9f5440b 100644 --- a/src/client/filecache.cpp +++ b/src/client/filecache.cpp @@ -28,6 +28,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include +void FileCache::createDir() +{ + if (!fs::CreateAllDirs(m_dir)) { + errorstream << "Could not create cache directory: " + << m_dir << std::endl; + } +} + bool FileCache::loadByPath(const std::string &path, std::ostream &os) { std::ifstream fis(path.c_str(), std::ios_base::binary); @@ -40,8 +48,8 @@ bool FileCache::loadByPath(const std::string &path, std::ostream &os) bool bad = false; for(;;){ - char buf[1024]; - fis.read(buf, 1024); + char buf[4096]; + fis.read(buf, sizeof(buf)); std::streamsize len = fis.gcount(); os.write(buf, len); if(fis.eof()) @@ -59,8 +67,9 @@ bool FileCache::loadByPath(const std::string &path, std::ostream &os) return !bad; } -bool FileCache::updateByPath(const std::string &path, const std::string &data) +bool FileCache::updateByPath(const std::string &path, std::string_view data) { + createDir(); std::ofstream file(path.c_str(), std::ios_base::binary | std::ios_base::trunc); @@ -71,13 +80,13 @@ bool FileCache::updateByPath(const std::string &path, const std::string &data) return false; } - file.write(data.c_str(), data.length()); + file << data; file.close(); return !file.fail(); } -bool FileCache::update(const std::string &name, const std::string &data) +bool FileCache::update(const std::string &name, std::string_view data) { std::string path = m_dir + DIR_DELIM + name; return updateByPath(path, data); @@ -95,3 +104,11 @@ bool FileCache::exists(const std::string &name) std::ifstream fis(path.c_str(), std::ios_base::binary); return fis.good(); } + +bool FileCache::updateCopyFile(const std::string &name, const std::string &src_path) +{ + std::string path = m_dir + DIR_DELIM + name; + + createDir(); + return fs::CopyFileContents(src_path, path); +} diff --git a/src/client/filecache.h b/src/client/filecache.h index ea6afc4b2..c58e71522 100644 --- a/src/client/filecache.h +++ b/src/client/filecache.h @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include +#include class FileCache { @@ -31,13 +32,17 @@ public: */ FileCache(const std::string &dir) : m_dir(dir) {} - bool update(const std::string &name, const std::string &data); + bool update(const std::string &name, std::string_view data); bool load(const std::string &name, std::ostream &os); bool exists(const std::string &name); + // Copy another file on disk into the cache + bool updateCopyFile(const std::string &name, const std::string &src_path); + private: std::string m_dir; + void createDir(); bool loadByPath(const std::string &path, std::ostream &os); - bool updateByPath(const std::string &path, const std::string &data); + bool updateByPath(const std::string &path, std::string_view data); }; diff --git a/src/client/game.cpp b/src/client/game.cpp index dd956c52b..48e9d9ea0 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -33,6 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/mapblock_mesh.h" #include "client/sound.h" #include "clientmap.h" +#include "clientmedia.h" // For clientMediaUpdateCacheCopy #include "clouds.h" #include "config.h" #include "content_cao.h" @@ -663,11 +664,7 @@ public: } }; -#ifdef HAVE_TOUCHSCREENGUI -#define SIZE_TAG "size[11,5.5]" -#else -#define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop -#endif +#define SIZE_TAG "size[11,5.5,true]" // Fixed size (ignored in touchscreen mode) /**************************************************************************** ****************************************************************************/ @@ -769,6 +766,7 @@ protected: bool initSound(); bool createSingleplayerServer(const std::string &map_dir, const SubgameSpec &gamespec, u16 port); + void copyServerClientCache(); // Client creation bool createClient(const GameStartData &start_data); @@ -1019,13 +1017,11 @@ private: // this happens in pause menu in singleplayer bool m_is_paused = false; -#ifdef HAVE_TOUCHSCREENGUI - bool m_cache_hold_aux1; + bool m_touch_simulate_aux1 = false; bool m_touch_use_crosshair; - inline bool isNoCrosshairAllowed() { + inline bool isTouchCrosshairDisabled() { return !m_touch_use_crosshair && camera->getCameraMode() == CAMERA_MODE_FIRST; } -#endif #ifdef __ANDROID__ bool m_android_chat_open; #endif @@ -1073,11 +1069,6 @@ Game::Game() : &settingChangedCallback, this); readSettings(); - -#ifdef HAVE_TOUCHSCREENGUI - m_cache_hold_aux1 = false; // This is initialised properly later -#endif - } @@ -1180,9 +1171,7 @@ bool Game::startup(bool *kill, m_first_loop_after_window_activation = true; -#ifdef HAVE_TOUCHSCREENGUI m_touch_use_crosshair = g_settings->getBool("touch_use_crosshair"); -#endif g_client_translations->clear(); @@ -1217,10 +1206,8 @@ void Game::run() set_light_table(g_settings->getFloat("display_gamma")); -#ifdef HAVE_TOUCHSCREENGUI - m_cache_hold_aux1 = g_settings->getBool("fast_move") + m_touch_simulate_aux1 = g_settings->getBool("fast_move") && client->checkPrivilege("fast"); -#endif const irr::core::dimension2du initial_screen_size( g_settings->getU16("screen_w"), @@ -1288,9 +1275,6 @@ void Game::run() updateFrame(&graph, &stats, dtime, cam_view); updateProfilerGraphs(&graph); - // Update if minimap has been disabled by the server - m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap(); - if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) { showPauseMenu(); } @@ -1309,9 +1293,8 @@ void Game::shutdown() // Clear text when exiting. m_game_ui->clearText(); -#ifdef HAVE_TOUCHSCREENGUI - g_touchscreengui->hide(); -#endif + if (g_touchscreengui) + g_touchscreengui->hide(); showOverlayMessage(N_("Shutting down..."), 0, 0, false); @@ -1453,9 +1436,31 @@ bool Game::createSingleplayerServer(const std::string &map_dir, false, nullptr, error_message); server->start(); + copyServerClientCache(); + return true; } +void Game::copyServerClientCache() +{ + // It would be possible to let the client directly read the media files + // from where the server knows they are. But aside from being more complicated + // it would also *not* fill the media cache and cause slower joining of + // remote servers. + // (Imagine that you launch a game once locally and then connect to a server.) + + assert(server); + auto map = server->getMediaList(); + u32 n = 0; + for (auto &it : map) { + assert(it.first.size() == 20); // SHA1 + if (clientMediaUpdateCacheCopy(it.first, it.second)) + n++; + } + infostream << "Copied " << n << " files directly from server to client cache" + << std::endl; +} + bool Game::createClient(const GameStartData &start_data) { showOverlayMessage(N_("Creating client..."), 0, 10); @@ -1499,11 +1504,10 @@ bool Game::createClient(const GameStartData &start_data) if (client->modsLoaded()) client->getScript()->on_camera_ready(camera); client->setCamera(camera); -#ifdef HAVE_TOUCHSCREENGUI + if (g_touchscreengui) { - g_touchscreengui->setUseCrosshair(!isNoCrosshairAllowed()); + g_touchscreengui->setUseCrosshair(!isTouchCrosshairDisabled()); } -#endif /* Clouds */ @@ -1530,18 +1534,20 @@ bool Game::createClient(const GameStartData &start_data) /* Set window caption */ - std::wstring str = utf8_to_wide(PROJECT_NAME_C); - str += L" "; - str += utf8_to_wide(g_version_hash); - str += L" ["; - str += simple_singleplayer_mode ? wstrgettext("Singleplayer") - : wstrgettext("Multiplayer"); - str += L"]"; - str += L" ["; - str += driver->getName(); - str += L"]"; +#if IRRLICHT_VERSION_MT_REVISION >= 15 + auto driver_name = driver->getName(); +#else + auto driver_name = wide_to_utf8(driver->getName()); +#endif + std::string str = std::string(PROJECT_NAME_C) + + " " + g_version_hash + " ["; + str += simple_singleplayer_mode ? gettext("Singleplayer") + : gettext("Multiplayer"); + str += "] ["; + str += driver_name; + str += "]"; - device->setWindowCaption(str.c_str()); + device->setWindowCaption(utf8_to_wide(str).c_str()); LocalPlayer *player = client->getEnv().getLocalPlayer(); player->hurt_tilt_timer = 0; @@ -1571,10 +1577,8 @@ bool Game::initGui() gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(), -1, chat_backend, client, &g_menumgr); -#ifdef HAVE_TOUCHSCREENGUI if (g_touchscreengui) g_touchscreengui->init(texture_src); -#endif return true; } @@ -2008,18 +2012,17 @@ void Game::processUserInput(f32 dtime) } else { input->clear(); } -#ifdef HAVE_TOUCHSCREENGUI - g_touchscreengui->hide(); -#endif + + if (g_touchscreengui) + g_touchscreengui->hide(); + } else { -#ifdef HAVE_TOUCHSCREENGUI 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 m_game_focused = true; } @@ -2211,13 +2214,11 @@ void Game::processItemSelection(u16 *new_playeritem) } } -#ifdef HAVE_TOUCHSCREENGUI if (g_touchscreengui) { std::optional selection = g_touchscreengui->getHotbarSelection(); if (selection) *new_playeritem = *selection; } -#endif // Clamp selection again in case it wasn't changed but max_item was *new_playeritem = MYMIN(*new_playeritem, max_item); @@ -2368,9 +2369,7 @@ void Game::toggleFast() m_game_ui->showTranslatedStatusText("Fast mode disabled"); } -#ifdef HAVE_TOUCHSCREENGUI - m_cache_hold_aux1 = fast_move && has_fast_privs; -#endif + m_touch_simulate_aux1 = fast_move && has_fast_privs; } @@ -2455,26 +2454,14 @@ void Game::toggleMinimap(bool shift_pressed) // --> u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags; - if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) { - m_game_ui->m_flags.show_minimap = false; - } else { - - // If radar is disabled, try to find a non radar mode or fall back to 0 - if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE)) - while (mapper->getModeIndex() && - mapper->getModeDef().type == MINIMAP_TYPE_RADAR) - mapper->nextMode(); - - m_game_ui->m_flags.show_minimap = mapper->getModeDef().type != MINIMAP_TYPE_OFF; - // <-- - // End of 'not so satifying code' - if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) || - (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP))) - m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label)); - else - m_game_ui->showTranslatedStatusText( - "Minimap currently disabled by game or mod"); - } + if (hud_flags & HUD_FLAG_MINIMAP_VISIBLE) { + // If radar is disabled, try to find a non radar mode or fall back to 0 + if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE)) + while (mapper->getModeIndex() && + mapper->getModeDef().type == MINIMAP_TYPE_RADAR) + mapper->nextMode(); + } + m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label)); } void Game::toggleFog() @@ -2627,10 +2614,8 @@ void Game::updateCameraDirection(CameraOrientation *cam, float dtime) Since Minetest has its own code to synthesize mouse events from touch events, this results in duplicated input. To avoid that, we don't enable relative mouse mode if we're in touchscreen mode. */ -#ifndef HAVE_TOUCHSCREENGUI if (cur_control) - cur_control->setRelativeMode(!isMenuActive()); -#endif + cur_control->setRelativeMode(!g_touchscreengui && !isMenuActive()); if ((device->isWindowActive() && device->isWindowFocused() && !isMenuActive()) || input->isRandom()) { @@ -2668,17 +2653,15 @@ f32 Game::getSensitivityScaleFactor() const // Multiply by a constant such that it becomes 1.0 at 72 degree FOV and // 16:9 aspect ratio to minimize disruption of existing sensitivity // settings. - return tan(fov_y / 2.0f) * 1.3763818698f; + return std::tan(fov_y / 2.0f) * 1.3763819f; } void Game::updateCameraOrientation(CameraOrientation *cam, float dtime) { -#ifdef HAVE_TOUCHSCREENGUI if (g_touchscreengui) { cam->camera_yaw += g_touchscreengui->getYawChange(); cam->camera_pitch += g_touchscreengui->getPitchChange(); } else { -#endif v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2); v2s32 dist = input->getMousePos() - center; @@ -2692,9 +2675,7 @@ void Game::updateCameraOrientation(CameraOrientation *cam, float dtime) if (dist.X != 0 || dist.Y != 0) input->setMousePos(center.X, center.Y); -#ifdef HAVE_TOUCHSCREENGUI } -#endif if (m_cache_enable_joysticks) { f32 sens_scale = getSensitivityScaleFactor(); @@ -2735,20 +2716,18 @@ void Game::updatePlayerControl(const CameraOrientation &cam) client->activeObjectsReceived() && !player->isDead()) { control.movement_speed = 1.0f; // sideways movement only - float dx = sin(control.movement_direction); - control.movement_direction = atan2(dx, 1.0f); + float dx = std::sin(control.movement_direction); + control.movement_direction = std::atan2(dx, 1.0f); } -#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 * touch then its meaning is inverted (i.e. holding aux1 means walk and * not fast) */ - if (m_cache_hold_aux1) { + if (g_touchscreengui && m_touch_simulate_aux1) { control.aux1 = control.aux1 ^ true; } -#endif client->setPlayerControl(control); @@ -2777,10 +2756,16 @@ inline void Game::step(f32 dtime) g_settings->getFloat("fps_max_unfocused") : g_settings->getFloat("fps_max"); fps_max = std::max(fps_max, 1.0f); - float steplen = 1.0f / fps_max; + /* + * Unless you have a barebones game, running the server at more than 60Hz + * is hardly realistic and you're at the point of diminishing returns. + * fps_max is also not necessarily anywhere near the FPS actually achieved + * (also due to vsync). + */ + fps_max = std::min(fps_max, 60.0f); server->setStepSettings(Server::StepSettings{ - steplen, + 1.0f / fps_max, m_is_paused }); @@ -3229,10 +3214,8 @@ void Game::updateCamera(f32 dtime) camera->toggleCameraMode(); -#ifdef HAVE_TOUCHSCREENGUI if (g_touchscreengui) - g_touchscreengui->setUseCrosshair(!isNoCrosshairAllowed()); -#endif + g_touchscreengui->setUseCrosshair(!isTouchCrosshairDisabled()); // Make the player visible depending on camera mode. playercao->updateMeshCulling(); @@ -3333,8 +3316,7 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud) } shootline.end = shootline.start + camera_direction * BS * d; -#ifdef HAVE_TOUCHSCREENGUI - if (g_touchscreengui && isNoCrosshairAllowed()) { + if (g_touchscreengui && isTouchCrosshairDisabled()) { shootline = g_touchscreengui->getShootline(); // Scale shootline to the acual distance the player can reach shootline.end = shootline.start + @@ -3342,7 +3324,6 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud) shootline.start += intToFloat(camera_offset, BS); shootline.end += intToFloat(camera_offset, BS); } -#endif PointedThing pointed = updatePointedThing(shootline, selected_def.liquids_pointable, @@ -3353,10 +3334,8 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud) if (pointed != runData.pointed_old) infostream << "Pointing at " << pointed.dump() << std::endl; -#ifdef HAVE_TOUCHSCREENGUI if (g_touchscreengui) g_touchscreengui->applyContextControls(selected_def.touch_interaction.getMode(pointed)); -#endif // 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 @@ -3734,11 +3713,11 @@ bool Game::nodePlacement(const ItemDefinition &selected_def, break; }; case NDT_SIGNLIKE: { - rotate90 = abs(pdir.X) < abs(pdir.Z); + rotate90 = std::abs(pdir.X) < std::abs(pdir.Z); break; } default: { - rotate90 = abs(pdir.X) > abs(pdir.Z); + rotate90 = std::abs(pdir.X) > std::abs(pdir.Z); break; } } @@ -4276,14 +4255,14 @@ void Game::updateShadows() if (!shadow) return; - float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f); + float in_timeofday = std::fmod(runData.time_of_day_smooth, 1.0f); 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; + timeoftheday = std::fmod(timeoftheday + 0.75f, 0.5f) + 0.25f; const float offset_constant = 10000.0f; v3f light = is_day ? sky->getSunDirection() : sky->getMoonDirection(); @@ -4339,12 +4318,12 @@ void Game::drawScene(ProfilerGraph *graph, RunStats *stats) bool draw_crosshair = ( (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) && (this->camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT)); -#ifdef HAVE_TOUCHSCREENGUI - if (this->isNoCrosshairAllowed()) + + if (g_touchscreengui && isTouchCrosshairDisabled()) draw_crosshair = false; -#endif + this->m_rendering_engine->draw_scene(sky_color, this->m_game_ui->m_flags.show_hud, - this->m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair); + draw_wield_tool, draw_crosshair); /* Profiler graph @@ -4490,21 +4469,23 @@ void Game::showDeathFormspec() #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name()) void Game::showPauseMenu() { -#ifdef HAVE_TOUCHSCREENGUI - static const std::string control_text = strgettext("Controls:\n" - "No menu open:\n" - "- slide finger: look around\n" - "- tap: place/punch/use (default)\n" - "- long tap: dig/use (default)\n" - "Menu/inventory open:\n" - "- double tap (outside):\n" - " --> close\n" - "- touch stack, touch slot:\n" - " --> move stack\n" - "- touch&drag, tap 2nd finger\n" - " --> place single item to slot\n" - ); -#endif + std::string control_text; + + if (g_touchscreengui) { + control_text = strgettext("Controls:\n" + "No menu open:\n" + "- slide finger: look around\n" + "- tap: place/punch/use (default)\n" + "- long tap: dig/use (default)\n" + "Menu/inventory open:\n" + "- double tap (outside):\n" + " --> close\n" + "- touch stack, touch slot:\n" + " --> move stack\n" + "- touch&drag, tap 2nd finger\n" + " --> place single item to slot\n" + ); + } float ypos = simple_singleplayer_mode ? 0.7f : 0.1f; std::ostringstream os; @@ -4534,9 +4515,9 @@ void Game::showPauseMenu() << strgettext("Exit to Menu") << "]"; os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;" << strgettext("Exit to OS") << "]"; -#ifdef HAVE_TOUCHSCREENGUI + if (!control_text.empty()) { os << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"; -#endif + } os << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n" << "\n" << strgettext("Game info:") << "\n"; diff --git a/src/client/gameui.cpp b/src/client/gameui.cpp index 3ea1e2624..0577943cb 100644 --- a/src/client/gameui.cpp +++ b/src/client/gameui.cpp @@ -219,11 +219,6 @@ void GameUI::initFlags() m_flags.show_minimal_debug = g_settings->getBool("show_debug"); } -void GameUI::showMinimap(bool show) -{ - m_flags.show_minimap = show; -} - void GameUI::showTranslatedStatusText(const char *str) { showStatusText(wstrgettext(str)); diff --git a/src/client/gameui.h b/src/client/gameui.h index f97ee7e50..5b87d43e6 100644 --- a/src/client/gameui.h +++ b/src/client/gameui.h @@ -57,7 +57,6 @@ public: { bool show_chat = true; bool show_hud = true; - bool show_minimap = false; bool show_minimal_debug = false; bool show_basic_debug = false; bool show_profiler_graph = false; @@ -71,8 +70,6 @@ public: void initFlags(); const Flags &getFlags() const { return m_flags; } - void showMinimap(bool show); - inline void setInfoText(const std::wstring &str) { m_infotext = str; } inline void clearInfoText() { m_infotext.clear(); } diff --git a/src/client/hud.cpp b/src/client/hud.cpp index 1618b757f..181dcb0e4 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -39,10 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "wieldmesh.h" #include "client/renderingengine.h" #include "client/minimap.h" - -#ifdef HAVE_TOUCHSCREENGUI #include "gui/touchscreengui.h" -#endif #define OBJECT_CROSSHAIR_LINE_SIZE 8 #define CROSSHAIR_LINE_SIZE 10 @@ -292,10 +289,8 @@ void Hud::drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount, drawItem(mainlist->getItem(i), item_rect, (i + 1) == selectitem); -#ifdef HAVE_TOUCHSCREENGUI if (is_hotbar && g_touchscreengui) g_touchscreengui->registerHotbarRect(i, item_rect); -#endif } } @@ -340,6 +335,14 @@ void Hud::drawLuaElements(const v3s16 &camera_offset) std::vector elems; elems.reserve(player->maxHudId()); + // Add builtin minimap if the server doesn't send it. + HudElement minimap; + if (client->getProtoVersion() < 44 && (player->hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) { + minimap = {HUD_ELEM_MINIMAP, v2f(1, 0), "", v2f(), "", 0 , 0, 0, v2f(-1, 1), + v2f(-10, 10), v3f(), v2s32(256, 256), 0, "", 0}; + elems.push_back(&minimap); + } + for (size_t i = 0; i != player->maxHudId(); i++) { HudElement *e = player->getHud(i); if (!e) @@ -741,10 +744,8 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, void Hud::drawHotbar(u16 playeritem) { -#ifdef HAVE_TOUCHSCREENGUI if (g_touchscreengui) g_touchscreengui->resetHotbarRects(); -#endif InventoryList *mainlist = inventory->getList("main"); if (mainlist == NULL) { @@ -1179,17 +1180,26 @@ void drawItemStack( (1 - wear) * progressrect.LowerRightCorner.X; // Compute progressbar color + // default scheme: // wear = 0.0: green // wear = 0.5: yellow // wear = 1.0: red - video::SColor color(255, 255, 255, 255); - int wear_i = MYMIN(std::floor(wear * 600), 511); - wear_i = MYMIN(wear_i + 10, 511); - if (wear_i <= 255) - color.set(255, wear_i, 255, 0); - else - color.set(255, 255, 511 - wear_i, 0); + video::SColor color; + auto barParams = item.getWearBarParams(client->idef()); + if (barParams.has_value()) { + f32 durabilityPercent = 1.0 - wear; + color = barParams->getWearBarColor(durabilityPercent); + } else { + color = video::SColor(255, 255, 255, 255); + int wear_i = MYMIN(std::floor(wear * 600), 511); + wear_i = MYMIN(wear_i + 10, 511); + + if (wear_i <= 255) + color.set(255, wear_i, 255, 0); + else + color.set(255, 255, 511 - wear_i, 0); + } core::rect progressrect2 = progressrect; progressrect2.LowerRightCorner.X = progressmid; diff --git a/src/client/imagefilters.cpp b/src/client/imagefilters.cpp index ed8483fcf..55dedd09d 100644 --- a/src/client/imagefilters.cpp +++ b/src/client/imagefilters.cpp @@ -65,20 +65,27 @@ public: } }; -/* Fill in RGB values for transparent pixels, to correct for odd colors - * appearing at borders when blending. This is because many PNG optimizers - * like to discard RGB values of transparent pixels, but when blending then - * with non-transparent neighbors, their RGB values will show up nonetheless. - * - * This function modifies the original image in-place. - * - * Parameter "threshold" is the alpha level below which pixels are considered - * transparent. Should be 127 when the texture is used with ALPHA_CHANNEL_REF, - * 0 when alpha blending is used. - */ -void imageCleanTransparent(video::IImage *src, u32 threshold) +template +static void imageCleanTransparentWithInlining(video::IImage *src, u32 threshold) { - core::dimension2d dim = src->getDimension(); + void *const src_data = src->getData(); + const core::dimension2d dim = src->getDimension(); + + auto get_pixel = [=](u32 x, u32 y) -> video::SColor { + if constexpr (IS_A8R8G8B8) { + return reinterpret_cast(src_data)[y*dim.Width + x]; + } else { + return src->getPixel(x, y); + } + }; + auto set_pixel = [=](u32 x, u32 y, video::SColor color) { + if constexpr (IS_A8R8G8B8) { + u32 *dest = &reinterpret_cast(src_data)[y*dim.Width + x]; + *dest = color.color; + } else { + src->setPixel(x, y, color); + } + }; Bitmap bitmap(dim.Width, dim.Height); @@ -86,7 +93,7 @@ void imageCleanTransparent(video::IImage *src, u32 threshold) // Note: loop y around x for better cache locality. for (u32 ctry = 0; ctry < dim.Height; ctry++) for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) { - if (src->getPixel(ctrx, ctry).getAlpha() > threshold) + if (get_pixel(ctrx, ctry).getAlpha() > threshold) bitmap.set(ctrx, ctry); } @@ -125,7 +132,7 @@ void imageCleanTransparent(video::IImage *src, u32 threshold) // 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); + video::SColor d = get_pixel(sx, sy); u32 a = d.getAlpha() <= threshold ? 255 : d.getAlpha(); ss += a; sr += a * d.getRed(); @@ -135,11 +142,11 @@ void imageCleanTransparent(video::IImage *src, u32 threshold) // Set pixel to average weighted by alpha if (ss > 0) { - video::SColor c = src->getPixel(ctrx, ctry); + video::SColor c = get_pixel(ctrx, ctry); c.setRed(sr / ss); c.setGreen(sg / ss); c.setBlue(sb / ss); - src->setPixel(ctrx, ctry, c); + set_pixel(ctrx, ctry, c); newmap.set(ctrx, ctry); } } @@ -154,6 +161,25 @@ void imageCleanTransparent(video::IImage *src, u32 threshold) } } +/* Fill in RGB values for transparent pixels, to correct for odd colors + * appearing at borders when blending. This is because many PNG optimizers + * like to discard RGB values of transparent pixels, but when blending then + * with non-transparent neighbors, their RGB values will show up nonetheless. + * + * This function modifies the original image in-place. + * + * Parameter "threshold" is the alpha level below which pixels are considered + * transparent. Should be 127 when the texture is used with ALPHA_CHANNEL_REF, + * 0 when alpha blending is used. + */ +void imageCleanTransparent(video::IImage *src, u32 threshold) +{ + if (src->getColorFormat() == video::ECF_A8R8G8B8) + imageCleanTransparentWithInlining(src, threshold); + else + imageCleanTransparentWithInlining(src, threshold); +} + /* Scale a region of an image into another image, using nearest-neighbor with * anti-aliasing; treat pixels as crisp rectangles, but blend them at boundaries * to prevent non-integer scaling ratio artifacts. Note that this may cause diff --git a/src/client/inputhandler.cpp b/src/client/inputhandler.cpp index 7b54ac93d..82303c455 100644 --- a/src/client/inputhandler.cpp +++ b/src/client/inputhandler.cpp @@ -102,11 +102,9 @@ bool MyEventReceiver::OnEvent(const SEvent &event) React to nothing here if a menu is active */ if (isMenuActive()) { -#ifdef HAVE_TOUCHSCREENGUI if (m_touchscreengui) { m_touchscreengui->setVisible(false); } -#endif return g_menumgr.preprocessEvent(event); } @@ -130,12 +128,10 @@ bool MyEventReceiver::OnEvent(const SEvent &event) return true; } -#ifdef HAVE_TOUCHSCREENGUI } else if (m_touchscreengui && event.EventType == irr::EET_TOUCH_INPUT_EVENT) { // In case of touchscreengui, we have to handle different events m_touchscreengui->translateEvent(event); return true; -#endif } else if (event.EventType == irr::EET_JOYSTICK_INPUT_EVENT) { // joystick may be nullptr if game is launched with '--random-input' parameter diff --git a/src/client/inputhandler.h b/src/client/inputhandler.h index 81bed61a8..68bd7bb03 100644 --- a/src/client/inputhandler.h +++ b/src/client/inputhandler.h @@ -24,10 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include "keycode.h" #include "renderingengine.h" - -#ifdef HAVE_TOUCHSCREENGUI #include "gui/touchscreengui.h" -#endif class InputHandler; @@ -203,16 +200,12 @@ public: MyEventReceiver() { -#ifdef HAVE_TOUCHSCREENGUI m_touchscreengui = NULL; -#endif } JoystickController *joystick = nullptr; -#ifdef HAVE_TOUCHSCREENGUI TouchScreenGUI *m_touchscreengui; -#endif private: s32 mouse_wheel = 0; @@ -332,11 +325,9 @@ public: return 0.0f; return 1.0f; // If there is a keyboard event, assume maximum speed } -#ifdef HAVE_TOUCHSCREENGUI - return m_receiver->m_touchscreengui->getMovementSpeed(); -#else + if (m_receiver->m_touchscreengui && m_receiver->m_touchscreengui->getMovementSpeed()) + return m_receiver->m_touchscreengui->getMovementSpeed(); return joystick.getMovementSpeed(); -#endif } virtual float getMovementDirection() @@ -355,12 +346,9 @@ public: if (x != 0 || z != 0) /* If there is a keyboard event, it takes priority */ return atan2(x, z); - else -#ifdef HAVE_TOUCHSCREENGUI + else if (m_receiver->m_touchscreengui && m_receiver->m_touchscreengui->getMovementDirection()) return m_receiver->m_touchscreengui->getMovementDirection(); -#else - return joystick.getMovementDirection(); -#endif + return joystick.getMovementDirection(); } virtual bool cancelPressed() diff --git a/src/client/joystick_controller.cpp b/src/client/joystick_controller.cpp index 19626d24e..5acb0b751 100644 --- a/src/client/joystick_controller.cpp +++ b/src/client/joystick_controller.cpp @@ -318,12 +318,14 @@ float JoystickController::getAxisWithoutDead(JoystickAxis axis) float JoystickController::getMovementDirection() { - return atan2(getAxisWithoutDead(JA_SIDEWARD_MOVE), -getAxisWithoutDead(JA_FORWARD_MOVE)); + return std::atan2(getAxisWithoutDead(JA_SIDEWARD_MOVE), + -getAxisWithoutDead(JA_FORWARD_MOVE)); } float JoystickController::getMovementSpeed() { - float speed = sqrt(pow(getAxisWithoutDead(JA_FORWARD_MOVE), 2) + pow(getAxisWithoutDead(JA_SIDEWARD_MOVE), 2)); + float speed = std::sqrt(std::pow(getAxisWithoutDead(JA_FORWARD_MOVE), 2) + + std::pow(getAxisWithoutDead(JA_SIDEWARD_MOVE), 2)); if (speed > 1.0f) speed = 1.0f; return speed; diff --git a/src/client/keycode.cpp b/src/client/keycode.cpp index 1fcd15e01..14360b1a1 100644 --- a/src/client/keycode.cpp +++ b/src/client/keycode.cpp @@ -18,7 +18,6 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "keycode.h" -#include "exceptions.h" #include "settings.h" #include "log.h" #include "debug.h" @@ -26,13 +25,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/string.h" #include "util/basic_macros.h" -class UnknownKeycode : public BaseException -{ -public: - UnknownKeycode(const char *s) : - BaseException(s) {}; -}; - struct table_key { const char *Name; irr::EKEY_CODE Key; diff --git a/src/client/keycode.h b/src/client/keycode.h index 7036705d1..6bc44bb1d 100644 --- a/src/client/keycode.h +++ b/src/client/keycode.h @@ -19,11 +19,19 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once +#include "exceptions.h" #include "irrlichttypes.h" #include "Keycodes.h" #include #include +class UnknownKeycode : public BaseException +{ +public: + UnknownKeycode(const char *s) : + BaseException(s) {}; +}; + /* A key press, consisting of either an Irrlicht keycode or an actual char */ diff --git a/src/client/localplayer.cpp b/src/client/localplayer.cpp index 6e5458c8a..1470d819a 100644 --- a/src/client/localplayer.cpp +++ b/src/client/localplayer.cpp @@ -600,7 +600,8 @@ void LocalPlayer::applyControl(float dtime, Environment *env) } } - speedH = v3f(sin(control.movement_direction), 0.0f, cos(control.movement_direction)); + speedH = v3f(std::sin(control.movement_direction), 0.0f, + std::cos(control.movement_direction)); if (m_autojump) { // release autojump after a given time diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index 09ca5262b..6e1817260 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -38,10 +38,9 @@ with this program; if not, write to the Free Software Foundation, Inc., MeshMakeData */ -MeshMakeData::MeshMakeData(Client *client, bool use_shaders): - m_mesh_grid(client->getMeshGrid()), - side_length(MAP_BLOCKSIZE * m_mesh_grid.cell_size), - m_client(client), +MeshMakeData::MeshMakeData(const NodeDefManager *ndef, u16 side_length, bool use_shaders): + side_length(side_length), + nodedef(ndef), m_use_shaders(use_shaders) {} @@ -147,7 +146,7 @@ u16 getFaceLight(MapNode n, MapNode n2, const NodeDefManager *ndef) static u16 getSmoothLightCombined(const v3s16 &p, const std::array &dirs, MeshMakeData *data) { - const NodeDefManager *ndef = data->m_client->ndef(); + const NodeDefManager *ndef = data->nodedef; u16 ambient_occlusion = 0; u16 light_count = 0; @@ -360,7 +359,7 @@ static const v3s16 vertex_dirs_table[] = { */ void getNodeTileN(MapNode mn, const v3s16 &p, u8 tileindex, MeshMakeData *data, TileSpec &tile) { - const NodeDefManager *ndef = data->m_client->ndef(); + const NodeDefManager *ndef = data->nodedef; const ContentFeatures &f = ndef->get(mn); tile = f.tiles[tileindex]; bool has_crack = p == data->m_crack_pos_relative; @@ -380,7 +379,7 @@ void getNodeTileN(MapNode mn, const v3s16 &p, u8 tileindex, MeshMakeData *data, */ void getNodeTile(MapNode mn, const v3s16 &p, const v3s16 &dir, MeshMakeData *data, TileSpec &tile) { - const NodeDefManager *ndef = data->m_client->ndef(); + const NodeDefManager *ndef = data->nodedef; // Direction must be (1,0,0), (-1,0,0), (0,1,0), (0,-1,0), // (0,0,1), (0,0,-1) or (0,0,0) @@ -635,9 +634,9 @@ void PartialMeshBuffer::afterDraw() const MapBlockMesh */ -MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): - m_tsrc(data->m_client->getTextureSource()), - m_shdrsrc(data->m_client->getShaderSource()), +MapBlockMesh::MapBlockMesh(Client *client, MeshMakeData *data, v3s16 camera_offset): + m_tsrc(client->getTextureSource()), + m_shdrsrc(client->getShaderSource()), m_bounding_sphere_center((data->side_length * 0.5f - 0.5f) * BS), m_animation_force_timer(0), // force initial animation m_last_crack(-1), @@ -648,26 +647,27 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): m_enable_shaders = data->m_use_shaders; m_enable_vbo = g_settings->getBool("enable_vbo"); + auto mesh_grid = client->getMeshGrid(); v3s16 bp = data->m_blockpos; // Only generate minimap mapblocks at even coordinates. - if (data->m_mesh_grid.isMeshPos(bp) && data->m_client->getMinimap()) { - m_minimap_mapblocks.resize(data->m_mesh_grid.getCellVolume(), nullptr); + if (mesh_grid.isMeshPos(bp) && client->getMinimap()) { + m_minimap_mapblocks.resize(mesh_grid.getCellVolume(), nullptr); v3s16 ofs; // See also client.cpp for the code that reads the array of minimap blocks. - for (ofs.Z = 0; ofs.Z < data->m_mesh_grid.cell_size; ofs.Z++) - for (ofs.Y = 0; ofs.Y < data->m_mesh_grid.cell_size; ofs.Y++) - for (ofs.X = 0; ofs.X < data->m_mesh_grid.cell_size; ofs.X++) { + for (ofs.Z = 0; ofs.Z < mesh_grid.cell_size; ofs.Z++) + for (ofs.Y = 0; ofs.Y < mesh_grid.cell_size; ofs.Y++) + for (ofs.X = 0; ofs.X < mesh_grid.cell_size; ofs.X++) { v3s16 p = (bp + ofs) * MAP_BLOCKSIZE; if (data->m_vmanip.getNodeNoEx(p).getContent() != CONTENT_IGNORE) { MinimapMapblock *block = new MinimapMapblock; - m_minimap_mapblocks[data->m_mesh_grid.getOffsetIndex(ofs)] = block; + m_minimap_mapblocks[mesh_grid.getOffsetIndex(ofs)] = block; block->getMinimapNodes(&data->m_vmanip, p); } } } - v3f offset = intToFloat((data->m_blockpos - data->m_mesh_grid.getMeshPos(data->m_blockpos)) * MAP_BLOCKSIZE, BS); + v3f offset = intToFloat((data->m_blockpos - mesh_grid.getMeshPos(data->m_blockpos)) * MAP_BLOCKSIZE, BS); MeshCollector collector(m_bounding_sphere_center, offset); /* Add special graphics: @@ -679,7 +679,7 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): { MapblockMeshGenerator(data, &collector, - data->m_client->getSceneManager()->getMeshManipulator()).generate(); + client->getSceneManager()->getMeshManipulator()).generate(); } /* @@ -1011,7 +1011,7 @@ u8 get_solid_sides(MeshMakeData *data) std::unordered_map results; v3s16 ofs; v3s16 blockpos_nodes = data->m_blockpos * MAP_BLOCKSIZE; - const NodeDefManager *ndef = data->m_client->ndef(); + const NodeDefManager *ndef = data->nodedef; u8 result = 0x3F; // all sides solid; diff --git a/src/client/mapblock_mesh.h b/src/client/mapblock_mesh.h index 5deaad131..7eb141c70 100644 --- a/src/client/mapblock_mesh.h +++ b/src/client/mapblock_mesh.h @@ -27,6 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include class Client; +class NodeDefManager; class IShaderSource; /* @@ -43,13 +44,12 @@ struct MeshMakeData v3s16 m_blockpos = v3s16(-1337,-1337,-1337); v3s16 m_crack_pos_relative = v3s16(-1337,-1337,-1337); bool m_smooth_lighting = false; - MeshGrid m_mesh_grid; u16 side_length; - Client *m_client; + const NodeDefManager *nodedef; bool m_use_shaders; - MeshMakeData(Client *client, bool use_shaders); + MeshMakeData(const NodeDefManager *ndef, u16 side_length, bool use_shaders); /* Copy block data manually (to allow optimizations by the caller) @@ -179,7 +179,7 @@ class MapBlockMesh { public: // Builds the mesh given - MapBlockMesh(MeshMakeData *data, v3s16 camera_offset); + MapBlockMesh(Client *client, MeshMakeData *data, v3s16 camera_offset); ~MapBlockMesh(); // Main animation function, parameters: diff --git a/src/client/mesh_generator_thread.cpp b/src/client/mesh_generator_thread.cpp index 167638269..32a01205d 100644 --- a/src/client/mesh_generator_thread.cpp +++ b/src/client/mesh_generator_thread.cpp @@ -189,16 +189,17 @@ void MeshUpdateQueue::done(v3s16 pos) void MeshUpdateQueue::fillDataFromMapBlocks(QueuedMeshUpdate *q) { - MeshMakeData *data = new MeshMakeData(m_client, m_cache_enable_shaders); + auto mesh_grid = m_client->getMeshGrid(); + MeshMakeData *data = new MeshMakeData(m_client->ndef(), MAP_BLOCKSIZE * mesh_grid.cell_size, m_cache_enable_shaders); q->data = data; data->fillBlockDataBegin(q->p); v3s16 pos; int i = 0; - for (pos.X = q->p.X - 1; pos.X <= q->p.X + data->m_mesh_grid.cell_size; pos.X++) - for (pos.Z = q->p.Z - 1; pos.Z <= q->p.Z + data->m_mesh_grid.cell_size; pos.Z++) - for (pos.Y = q->p.Y - 1; pos.Y <= q->p.Y + data->m_mesh_grid.cell_size; pos.Y++) { + for (pos.X = q->p.X - 1; pos.X <= q->p.X + mesh_grid.cell_size; pos.X++) + for (pos.Z = q->p.Z - 1; pos.Z <= q->p.Z + mesh_grid.cell_size; pos.Z++) + for (pos.Y = q->p.Y - 1; pos.Y <= q->p.Y + mesh_grid.cell_size; pos.Y++) { MapBlock *block = q->map_blocks[i++]; data->fillBlockData(pos, block ? block->getData() : block_placeholder.data); } @@ -211,8 +212,8 @@ void MeshUpdateQueue::fillDataFromMapBlocks(QueuedMeshUpdate *q) MeshUpdateWorkerThread */ -MeshUpdateWorkerThread::MeshUpdateWorkerThread(MeshUpdateQueue *queue_in, MeshUpdateManager *manager, v3s16 *camera_offset) : - UpdateThread("Mesh"), m_queue_in(queue_in), m_manager(manager), m_camera_offset(camera_offset) +MeshUpdateWorkerThread::MeshUpdateWorkerThread(Client *client, MeshUpdateQueue *queue_in, MeshUpdateManager *manager, v3s16 *camera_offset) : + UpdateThread("Mesh"), m_client(client), m_queue_in(queue_in), m_manager(manager), m_camera_offset(camera_offset) { m_generation_interval = g_settings->getU16("mesh_generation_interval"); m_generation_interval = rangelim(m_generation_interval, 0, 50); @@ -226,7 +227,7 @@ void MeshUpdateWorkerThread::doUpdate() sleep_ms(m_generation_interval); ScopeProfiler sp(g_profiler, "Client: Mesh making (sum)"); - MapBlockMesh *mesh_new = new MapBlockMesh(q->data, *m_camera_offset); + MapBlockMesh *mesh_new = new MapBlockMesh(m_client, q->data, *m_camera_offset); @@ -262,7 +263,7 @@ MeshUpdateManager::MeshUpdateManager(Client *client): infostream << "MeshUpdateManager: using " << number_of_threads << " threads" << std::endl; for (int i = 0; i < number_of_threads; i++) - m_workers.push_back(std::make_unique(&m_queue_in, this, &m_camera_offset)); + m_workers.push_back(std::make_unique(client, &m_queue_in, this, &m_camera_offset)); } void MeshUpdateManager::updateBlock(Map *map, v3s16 p, bool ack_block_to_server, diff --git a/src/client/mesh_generator_thread.h b/src/client/mesh_generator_thread.h index 61eb5c7ad..607bc66df 100644 --- a/src/client/mesh_generator_thread.h +++ b/src/client/mesh_generator_thread.h @@ -109,12 +109,13 @@ class MeshUpdateManager; class MeshUpdateWorkerThread : public UpdateThread { public: - MeshUpdateWorkerThread(MeshUpdateQueue *queue_in, MeshUpdateManager *manager, v3s16 *camera_offset); + MeshUpdateWorkerThread(Client *client, MeshUpdateQueue *queue_in, MeshUpdateManager *manager, v3s16 *camera_offset); protected: virtual void doUpdate(); private: + Client *m_client; MeshUpdateQueue *m_queue_in; MeshUpdateManager *m_manager; v3s16 *m_camera_offset; diff --git a/src/client/minimap.cpp b/src/client/minimap.cpp index 8ebe2fb03..1362dae68 100644 --- a/src/client/minimap.cpp +++ b/src/client/minimap.cpp @@ -206,25 +206,6 @@ Minimap::Minimap(Client *client) data->minimap_shape_round = g_settings->getBool("minimap_shape_round"); - // Get round minimap textures - data->minimap_mask_round = driver->createImage( - m_tsrc->getTexture("minimap_mask_round.png"), - core::position2d(0, 0), - core::dimension2d(MINIMAP_MAX_SX, MINIMAP_MAX_SY)); - data->minimap_overlay_round = m_tsrc->getTexture("minimap_overlay_round.png"); - - // Get square minimap textures - data->minimap_mask_square = driver->createImage( - m_tsrc->getTexture("minimap_mask_square.png"), - core::position2d(0, 0), - core::dimension2d(MINIMAP_MAX_SX, MINIMAP_MAX_SY)); - data->minimap_overlay_square = m_tsrc->getTexture("minimap_overlay_square.png"); - - // Create player marker texture - data->player_marker = m_tsrc->getTexture("player_marker.png"); - // Create object marker texture - data->object_marker_red = m_tsrc->getTexture("object_marker_red.png"); - setModeIndex(0); // Create mesh buffer for minimap @@ -243,8 +224,10 @@ Minimap::~Minimap() m_meshbuffer->drop(); - data->minimap_mask_round->drop(); - data->minimap_mask_square->drop(); + if (data->minimap_mask_round) + data->minimap_mask_round->drop(); + if (data->minimap_mask_square) + data->minimap_mask_square->drop(); driver->removeTexture(data->texture); driver->removeTexture(data->heightmap_texture); @@ -461,6 +444,29 @@ void Minimap::blitMinimapPixelsToImageSurface( } } +video::IImage *Minimap::getMinimapMask() +{ + if (data->minimap_shape_round) { + if (!data->minimap_mask_round) { + // Get round minimap textures + data->minimap_mask_round = driver->createImage( + m_tsrc->getTexture("minimap_mask_round.png"), + core::position2d(0, 0), + core::dimension2d(MINIMAP_MAX_SX, MINIMAP_MAX_SY)); + } + return data->minimap_mask_round; + } + + if (!data->minimap_mask_square) { + // Get square minimap textures + data->minimap_mask_square = driver->createImage( + m_tsrc->getTexture("minimap_mask_square.png"), + core::position2d(0, 0), + core::dimension2d(MINIMAP_MAX_SX, MINIMAP_MAX_SY)); + } + return data->minimap_mask_square; +} + video::ITexture *Minimap::getMinimapTexture() { // update minimap textures when new scan is ready @@ -509,16 +515,13 @@ video::ITexture *Minimap::getMinimapTexture() map_image->copyToScaling(minimap_image); map_image->drop(); - video::IImage *minimap_mask = data->minimap_shape_round ? - data->minimap_mask_round : data->minimap_mask_square; + video::IImage *minimap_mask = getMinimapMask(); - if (minimap_mask) { - for (s16 y = 0; y < MINIMAP_MAX_SY; y++) - for (s16 x = 0; x < MINIMAP_MAX_SX; x++) { - const video::SColor &mask_col = minimap_mask->getPixel(x, y); - if (!mask_col.getAlpha()) - minimap_image->setPixel(x, y, video::SColor(0,0,0,0)); - } + for (s16 y = 0; y < MINIMAP_MAX_SY; y++) + for (s16 x = 0; x < MINIMAP_MAX_SX; x++) { + const video::SColor &mask_col = minimap_mask->getPixel(x, y); + if (!mask_col.getAlpha()) + minimap_image->setPixel(x, y, video::SColor(0,0,0,0)); } if (data->texture) @@ -571,25 +574,22 @@ scene::SMeshBuffer *Minimap::getMinimapMeshBuffer() return buf; } -void Minimap::drawMinimap() +void Minimap::drawMinimap(core::rect rect) { - // Non hud managed minimap drawing (legacy minimap) - v2u32 screensize = RenderingEngine::getWindowSize(); - const u32 size = 0.25 * screensize.Y; - - drawMinimap(core::rect( - screensize.X - size - 10, 10, - screensize.X - 10, size + 10)); -} - -void Minimap::drawMinimap(core::rect rect) { + if (data->mode.type == MINIMAP_TYPE_OFF) + return; + // Get textures video::ITexture *minimap_texture = getMinimapTexture(); if (!minimap_texture) return; - - if (data->mode.type == MINIMAP_TYPE_OFF) - return; + if (!data->textures_initialised) { + data->minimap_overlay_round = m_tsrc->getTexture("minimap_overlay_round.png"); + data->minimap_overlay_square = m_tsrc->getTexture("minimap_overlay_square.png"); + data->player_marker = m_tsrc->getTexture("player_marker.png"); + data->object_marker_red = m_tsrc->getTexture("object_marker_red.png"); + data->textures_initialised = true; + } updateActiveMarkers(); @@ -700,8 +700,7 @@ void Minimap::removeMarker(MinimapMarker **m) void Minimap::updateActiveMarkers() { - video::IImage *minimap_mask = data->minimap_shape_round ? - data->minimap_mask_round : data->minimap_mask_square; + video::IImage *minimap_mask = getMinimapMask(); m_active_markers.clear(); v3f cam_offset = intToFloat(client->getCamera()->getOffset(), BS); diff --git a/src/client/minimap.h b/src/client/minimap.h index f06a2e173..f819deaf4 100644 --- a/src/client/minimap.h +++ b/src/client/minimap.h @@ -79,6 +79,7 @@ struct MinimapData { video::IImage *minimap_mask_square = nullptr; video::ITexture *texture = nullptr; video::ITexture *heightmap_texture = nullptr; + bool textures_initialised = false; // True if the following textures are not nullptrs. video::ITexture *minimap_overlay_round = nullptr; video::ITexture *minimap_overlay_square = nullptr; video::ITexture *player_marker = nullptr; @@ -140,6 +141,7 @@ public: MinimapModeDef getModeDef() const { return data->mode; } + video::IImage *getMinimapMask(); video::ITexture *getMinimapTexture(); void blitMinimapPixelsToImageRadar(video::IImage *map_image); @@ -152,7 +154,6 @@ public: void removeMarker(MinimapMarker **marker); void updateActiveMarkers(); - void drawMinimap(); void drawMinimap(core::rect rect); video::IVideoDriver *driver; diff --git a/src/client/particles.cpp b/src/client/particles.cpp index 0697763ca..14384f3b8 100644 --- a/src/client/particles.cpp +++ b/src/client/particles.cpp @@ -213,10 +213,10 @@ void Particle::step(float dtime) if (m_p.animation.type != TAT_NONE) { m_animation_time += dtime; - int frame_length_i, frame_count; + int frame_length_i = 0; m_p.animation.determineParams( m_material.getTexture(0)->getSize(), - &frame_count, &frame_length_i, NULL); + NULL, &frame_length_i, NULL); float frame_length = frame_length_i / 1000.0; while (m_animation_time > frame_length) { m_animation_frame++; diff --git a/src/client/render/core.cpp b/src/client/render/core.cpp index 37a5106df..dfd9dc02e 100644 --- a/src/client/render/core.cpp +++ b/src/client/render/core.cpp @@ -36,7 +36,7 @@ RenderingCore::~RenderingCore() delete shadow_renderer; } -void RenderingCore::draw(video::SColor _skycolor, bool _show_hud, bool _show_minimap, +void RenderingCore::draw(video::SColor _skycolor, bool _show_hud, bool _draw_wield_tool, bool _draw_crosshair) { v2u32 screensize = device->getVideoDriver()->getScreenSize(); @@ -46,7 +46,6 @@ void RenderingCore::draw(video::SColor _skycolor, bool _show_hud, bool _show_min context.draw_crosshair = _draw_crosshair; context.draw_wield_tool = _draw_wield_tool; context.show_hud = _show_hud; - context.show_minimap = _show_minimap; pipeline->reset(context); pipeline->run(context); @@ -55,4 +54,4 @@ void RenderingCore::draw(video::SColor _skycolor, bool _show_hud, bool _show_min v2u32 RenderingCore::getVirtualSize() const { return virtual_size; -} \ No newline at end of file +} diff --git a/src/client/render/core.h b/src/client/render/core.h index 2d9e41047..c5617bcb2 100644 --- a/src/client/render/core.h +++ b/src/client/render/core.h @@ -53,7 +53,7 @@ public: RenderingCore &operator=(const RenderingCore &) = delete; RenderingCore &operator=(RenderingCore &&) = delete; - void draw(video::SColor _skycolor, bool _show_hud, bool _show_minimap, + void draw(video::SColor _skycolor, bool _show_hud, bool _draw_wield_tool, bool _draw_crosshair); v2u32 getVirtualSize() const; diff --git a/src/client/render/pipeline.h b/src/client/render/pipeline.h index f446773ba..abb108652 100644 --- a/src/client/render/pipeline.h +++ b/src/client/render/pipeline.h @@ -46,7 +46,6 @@ struct PipelineContext v2u32 target_size; bool show_hud {true}; - bool show_minimap {true}; bool draw_wield_tool {true}; bool draw_crosshair {true}; }; diff --git a/src/client/render/plain.cpp b/src/client/render/plain.cpp index dccdaedb3..60a732415 100644 --- a/src/client/render/plain.cpp +++ b/src/client/render/plain.cpp @@ -64,9 +64,6 @@ void DrawHUD::run(PipelineContext &context) context.hud->drawHotbar(context.client->getEnv().getLocalPlayer()->getWieldIndex()); context.hud->drawLuaElements(context.client->getCamera()->getOffset()); context.client->getCamera()->drawNametags(); - auto mapper = context.client->getMinimap(); - if (mapper && context.show_minimap) - mapper->drawMinimap(); } context.device->getGUIEnvironment()->drawAll(); } @@ -106,7 +103,7 @@ void UpscaleStep::run(PipelineContext &context) std::unique_ptr create3DStage(Client *client, v2f scale) { RenderStep *step = new Draw3D(); - if (g_settings->getBool("enable_shaders")) { + if (g_settings->getBool("enable_shaders") && g_settings->getBool("enable_post_processing")) { RenderPipeline *pipeline = new RenderPipeline(); pipeline->addStep(pipeline->own(std::unique_ptr(step))); @@ -131,7 +128,7 @@ RenderStep* addUpscaling(RenderPipeline *pipeline, RenderStep *previousStep, v2f return previousStep; // When shaders are enabled, post-processing pipeline takes care of rescaling - if (g_settings->getBool("enable_shaders")) + if (g_settings->getBool("enable_shaders") && g_settings->getBool("enable_post_processing")) return previousStep; diff --git a/src/client/renderingengine.cpp b/src/client/renderingengine.cpp index 5e82185e7..c1a51d289 100644 --- a/src/client/renderingengine.cpp +++ b/src/client/renderingengine.cpp @@ -152,6 +152,9 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver) driver = m_device->getVideoDriver(); infostream << "Using the " << RenderingEngine::getVideoDriverInfo(driver->getDriverType()).friendly_name << " video driver" << std::endl; + // This changes the minimum allowed number of vertices in a VBO. Default is 500. + driver->setMinHardwareBufferVertexCount(4); + s_singleton = this; auto skin = createSkin(m_device->getGUIEnvironment(), @@ -319,9 +322,9 @@ void RenderingEngine::finalize() } void RenderingEngine::draw_scene(video::SColor skycolor, bool show_hud, - bool show_minimap, bool draw_wield_tool, bool draw_crosshair) + bool draw_wield_tool, bool draw_crosshair) { - core->draw(skycolor, show_hud, show_minimap, draw_wield_tool, draw_crosshair); + core->draw(skycolor, show_hud, draw_wield_tool, draw_crosshair); } const VideoDriverInfo &RenderingEngine::getVideoDriverInfo(irr::video::E_DRIVER_TYPE type) diff --git a/src/client/renderingengine.h b/src/client/renderingengine.h index 7d2291172..b8293f49a 100644 --- a/src/client/renderingengine.h +++ b/src/client/renderingengine.h @@ -118,7 +118,7 @@ public: float dtime = 0, int percent = 0, bool sky = true); void draw_scene(video::SColor skycolor, bool show_hud, - bool show_minimap, bool draw_wield_tool, bool draw_crosshair); + bool draw_wield_tool, bool draw_crosshair); void initialize(Client *client, Hud *hud); void finalize(); diff --git a/src/client/shader.cpp b/src/client/shader.cpp index 65f135a82..59852763e 100644 --- a/src/client/shader.cpp +++ b/src/client/shader.cpp @@ -804,7 +804,7 @@ ShaderInfo ShaderSource::generateShader(const std::string &name, } void dumpShaderProgram(std::ostream &output_stream, - const std::string &program_type, const std::string &program) + const std::string &program_type, std::string_view program) { output_stream << program_type << " shader program:" << std::endl << "----------------------------------" << std::endl; diff --git a/src/client/shader.h b/src/client/shader.h index 100ab567f..fabb19922 100644 --- a/src/client/shader.h +++ b/src/client/shader.h @@ -192,4 +192,4 @@ public: IWritableShaderSource *createShaderSource(); void dumpShaderProgram(std::ostream &output_stream, - const std::string &program_type, const std::string &program); + const std::string &program_type, std::string_view program); diff --git a/src/client/shadows/dynamicshadowsrender.cpp b/src/client/shadows/dynamicshadowsrender.cpp index ce3fdfc6d..fc8592a21 100644 --- a/src/client/shadows/dynamicshadowsrender.cpp +++ b/src/client/shadows/dynamicshadowsrender.cpp @@ -171,8 +171,8 @@ f32 ShadowRenderer::getMaxShadowFar() const void ShadowRenderer::setShadowIntensity(float shadow_intensity) { - m_shadow_strength = pow(shadow_intensity, 1.0f / m_shadow_strength_gamma); - if (m_shadow_strength > 1E-2) + m_shadow_strength = std::pow(shadow_intensity, 1.0f / m_shadow_strength_gamma); + if (m_shadow_strength > 1e-2f) enable(); else disable(); @@ -183,8 +183,12 @@ void ShadowRenderer::addNodeToShadowList( { m_shadow_node_array.emplace_back(node, shadowMode); // node should never be ClientMap +#if IRRLICHT_VERSION_MT_REVISION >= 15 + assert(!node->getName().has_value() || *node->getName() != "ClientMap"); +#else + // TODO: Remove this as soon as we require 1.9.0mt15 assert(strcmp(node->getName(), "ClientMap") != 0); - +#endif node->forEachMaterial([this] (auto &mat) { mat.setTexture(TEXTURE_LAYER_SHADOW, shadowMapTextureFinal); }); diff --git a/src/client/sky.cpp b/src/client/sky.cpp index e2f828605..12eb7f0e8 100644 --- a/src/client/sky.cpp +++ b/src/client/sky.cpp @@ -680,7 +680,7 @@ void Sky::draw_stars(video::IVideoDriver * driver, float wicked_time_of_day) float tod = wicked_time_of_day < 0.5f ? wicked_time_of_day : (1.0f - wicked_time_of_day); float day_opacity = clamp(m_star_params.day_opacity, 0.0f, 1.0f); - float starbrightness = (0.25f - fabs(tod)) * 20.0f; + float starbrightness = (0.25f - std::abs(tod)) * 20.0f; float alpha = clamp(starbrightness, day_opacity, 1.0f); m_star_color = m_star_params.starcolor; diff --git a/src/client/tile.cpp b/src/client/tile.cpp index cc6b3273c..624e927d8 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -35,6 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiscalingfilter.h" #include "renderingengine.h" #include "util/base64.h" +#include "irrlicht_changes/printing.h" /* A cache from texture name to texture path @@ -403,7 +404,7 @@ private: // Generate image based on a string like "stone.png" or "[crack:1:0". // if baseimg is NULL, it is created. Otherwise stuff is made on it. // source_image_names is important to determine when to flush the image from a cache (dynamic media) - bool generateImagePart(std::string part_of_name, video::IImage *& baseimg, std::set &source_image_names); + bool generateImagePart(std::string_view part_of_name, video::IImage *& baseimg, std::set &source_image_names); /*! Generates an image from a full string like * "stone.png^mineral_coal.png^[crack:1:0". @@ -411,7 +412,7 @@ private: * The returned Image should be dropped. * source_image_names is important to determine when to flush the image from a cache (dynamic media) */ - video::IImage* generateImage(const std::string &name, std::set &source_image_names); + video::IImage* generateImage(std::string_view name, std::set &source_image_names); // Thread-safe cache of what source images are known (true = known) MutexedMap m_source_image_existence; @@ -538,14 +539,11 @@ u32 TextureSource::getTextureId(const std::string &name) // Draw an image on top of another one, using the alpha channel of the // source image +// overlay: only modify destination pixels that are fully opaque. +template static void blit_with_alpha(video::IImage *src, video::IImage *dst, v2s32 src_pos, v2s32 dst_pos, v2u32 size); -// Like blit_with_alpha, but only modifies destination pixels that -// are fully opaque -static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst, - v2s32 src_pos, v2s32 dst_pos, v2u32 size); - // Apply a color to an image. Uses an int (0-255) to calculate the ratio. // If the ratio is 255 or -1 and keep_alpha is true, then it multiples the // color alpha with the destination alpha. @@ -595,7 +593,7 @@ static void draw_crack(video::IImage *crack, video::IImage *dst, // Brighten image void brighten(video::IImage *image); // Parse a transform name -u32 parseImageTransform(const std::string& s); +u32 parseImageTransform(std::string_view s); // Apply transform to image dimension core::dimension2d imageTransformDimension(u32 transform, core::dimension2d dim); // Apply transform to image data @@ -965,7 +963,8 @@ static video::IImage *createInventoryCubeImage( return result; } -video::IImage* TextureSource::generateImage(const std::string &name, std::set &source_image_names) +video::IImage* TextureSource::generateImage(std::string_view name, + std::set &source_image_names) { // Get the base image @@ -1026,15 +1025,15 @@ video::IImage* TextureSource::generateImage(const std::string &name, std::set &source_image_names) { const char escape = '\\'; // same as in generateImage() @@ -1218,8 +1217,9 @@ bool TextureSource::generateImagePart(std::string part_of_name, // Stuff starting with [ are special commands if (part_of_name.empty() || part_of_name[0] != '[') { - source_image_names.insert(part_of_name); - video::IImage *image = m_sourcecache.getOrLoad(part_of_name); + std::string part_s(part_of_name); + source_image_names.insert(part_s); + video::IImage *image = m_sourcecache.getOrLoad(part_s); if (!image) { // Do not create the dummy texture if (part_of_name.empty()) @@ -1323,37 +1323,45 @@ bool TextureSource::generateImagePart(std::string part_of_name, sf.next(":"); u32 w0 = stoi(sf.next("x")); u32 h0 = stoi(sf.next(":")); - CHECK_DIM(w0, h0); - core::dimension2d dim(w0,h0); - if (baseimg == NULL) { - baseimg = driver->createImage(video::ECF_A8R8G8B8, dim); + if (!baseimg) { + CHECK_DIM(w0, h0); + baseimg = driver->createImage(video::ECF_A8R8G8B8, {w0, h0}); baseimg->fill(video::SColor(0,0,0,0)); } + while (!sf.at_end()) { - u32 x = stoi(sf.next(",")); - u32 y = stoi(sf.next("=")); + v2s32 pos_base; + pos_base.X = stoi(sf.next(",")); + pos_base.Y = stoi(sf.next("=")); std::string filename = unescape_string(sf.next_esc(":", escape), escape); - if (x >= w0 || y >= h0) - COMPLAIN_INVALID("X or Y offset"); - infostream<<"Adding \""<getDimension(); + if (pos_base.X > (s32)basedim.Width || pos_base.Y > (s32)basedim.Height) { + warningstream << "generateImagePart(): Skipping \"" + << filename << "\" as it's out-of-bounds " << pos_base + << " for [combine" << std::endl; + continue; + } + infostream << "Adding \"" << filename<< "\" to combined " + << pos_base << std::endl; video::IImage *img = generateImage(filename, source_image_names); - if (img) { - core::dimension2d dim = img->getDimension(); - core::position2d pos_base(x, y); - video::IImage *img2 = - driver->createImage(video::ECF_A8R8G8B8, dim); - img->copyTo(img2); - img->drop(); - blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim); - img2->drop(); - } else { + if (!img) { errorstream << "generateImagePart(): Failed to load image \"" << filename << "\" for [combine" << std::endl; + continue; } + const auto dim = img->getDimension(); + if (pos_base.X + dim.Width <= 0 || pos_base.Y + dim.Height <= 0) { + warningstream << "generateImagePart(): Skipping \"" + << filename << "\" as it's out-of-bounds " << pos_base + << " for [combine" << std::endl; + img->drop(); + continue; + } + + blit_with_alpha(img, baseimg, v2s32(0,0), pos_base, dim); + img->drop(); } } /* @@ -1510,8 +1518,10 @@ bool TextureSource::generateImagePart(std::string part_of_name, return false; } - str_replace(part_of_name, '&', '^'); - Strfnd sf(part_of_name); + std::string part_s(part_of_name); + str_replace(part_s, '&', '^'); + + Strfnd sf(part_s); sf.next("{"); std::string imagename_top = sf.next("{"); std::string imagename_left = sf.next("{"); @@ -1867,7 +1877,7 @@ bool TextureSource::generateImagePart(std::string part_of_name, else if (str_starts_with(part_of_name, "[png:")) { std::string png; { - std::string blob = part_of_name.substr(5); + auto blob = part_of_name.substr(5); if (!base64_is_valid(blob)) { errorstream << "generateImagePart(): " << "malformed base64 in [png" << std::endl; @@ -2033,32 +2043,21 @@ static inline video::SColor blitPixel(const video::SColor src_c, const video::SC This exists because IImage::copyToWithAlpha() doesn't seem to always work. */ +template static void blit_with_alpha(video::IImage *src, video::IImage *dst, v2s32 src_pos, v2s32 dst_pos, v2u32 size) { - for (u32 y0=0; y0getPixel(src_x, src_y); - video::SColor dst_c = dst->getPixel(dst_x, dst_y); - dst_c = blitPixel(src_c, dst_c, src_c.getAlpha()); - dst->setPixel(dst_x, dst_y, dst_c); - } -} + auto src_dim = src->getDimension(); + auto dst_dim = dst->getDimension(); -/* - Draw an image on top of another one, using the alpha channel of the - source image; only modify fully opaque pixels in destinaion -*/ -static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst, - v2s32 src_pos, v2s32 dst_pos, v2u32 size) -{ - for (u32 y0=0; y0({size.Y, src_dim.Height, dst_dim.Height - (s64) dst_pos.Y}); + ++y0) + for (u32 x0 = std::max(0, -dst_pos.X); + x0 < std::min({size.X, src_dim.Width, dst_dim.Width - (s64) dst_pos.X}); + ++x0) { s32 src_x = src_pos.X + x0; s32 src_y = src_pos.Y + y0; @@ -2066,45 +2065,13 @@ static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst, s32 dst_y = dst_pos.Y + y0; video::SColor src_c = src->getPixel(src_x, src_y); video::SColor dst_c = dst->getPixel(dst_x, dst_y); - if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0) - { + if (!overlay || (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)) { dst_c = blitPixel(src_c, dst_c, src_c.getAlpha()); dst->setPixel(dst_x, dst_y, dst_c); } } } -// This function has been disabled because it is currently unused. -// Feel free to re-enable if you find it handy. -#if 0 -/* - Draw an image on top of another one, using the specified ratio - modify all partially-opaque pixels in the destination. -*/ -static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst, - v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio) -{ - for (u32 y0 = 0; y0 < size.Y; y0++) - for (u32 x0 = 0; x0 < size.X; x0++) - { - s32 src_x = src_pos.X + x0; - s32 src_y = src_pos.Y + y0; - s32 dst_x = dst_pos.X + x0; - s32 dst_y = dst_pos.Y + y0; - video::SColor src_c = src->getPixel(src_x, src_y); - video::SColor dst_c = dst->getPixel(dst_x, dst_y); - if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0) - { - if (ratio == -1) - dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f); - else - dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f); - dst->setPixel(dst_x, dst_y, dst_c); - } - } -} -#endif - /* Apply color to destination, using a weighted interpolation blend */ @@ -2443,7 +2410,7 @@ static void draw_crack(video::IImage *crack, video::IImage *dst, if (!crack_scaled) return; - auto blit = use_overlay ? blit_with_alpha_overlay : blit_with_alpha; + auto blit = use_overlay ? blit_with_alpha : blit_with_alpha; for (s32 i = 0; i < frame_count; ++i) { v2s32 dst_pos(0, frame_size.Height * i); blit(crack_scaled, dst, v2s32(0,0), dst_pos, frame_size); @@ -2470,7 +2437,7 @@ void brighten(video::IImage *image) } } -u32 parseImageTransform(const std::string& s) +u32 parseImageTransform(std::string_view s) { int total_transform = 0; diff --git a/src/client/wieldmesh.cpp b/src/client/wieldmesh.cpp index e83a79f92..c89f3bd4a 100644 --- a/src/client/wieldmesh.cpp +++ b/src/client/wieldmesh.cpp @@ -315,7 +315,7 @@ void WieldMeshSceneNode::setExtruded(const std::string &imagename, static scene::SMesh *createSpecialNodeMesh(Client *client, MapNode n, std::vector *colors, const ContentFeatures &f) { - MeshMakeData mesh_make_data(client, false); + MeshMakeData mesh_make_data(client->ndef(), 1, false); MeshCollector collector(v3f(0.0f * BS), v3f()); mesh_make_data.setSmoothLighting(false); MapblockMeshGenerator gen(&mesh_make_data, &collector, diff --git a/src/clientdynamicinfo.h b/src/clientdynamicinfo.h index 547329b06..bbea5d149 100644 --- a/src/clientdynamicinfo.h +++ b/src/clientdynamicinfo.h @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #ifndef SERVER #include "settings.h" #include "client/renderingengine.h" +#include "gui/touchscreengui.h" #endif @@ -37,8 +38,8 @@ public: bool equal(const ClientDynamicInfo &other) const { return render_target_size == other.render_target_size && - abs(real_gui_scaling - other.real_gui_scaling) < 0.001f && - abs(real_hud_scaling - other.real_hud_scaling) < 0.001f && + std::abs(real_gui_scaling - other.real_gui_scaling) < 0.001f && + std::abs(real_hud_scaling - other.real_hud_scaling) < 0.001f && touch_controls == other.touch_controls; } @@ -50,11 +51,7 @@ public: f32 hud_scaling = g_settings->getFloat("hud_scaling", 0.5f, 20.0f); f32 real_gui_scaling = gui_scaling * density; f32 real_hud_scaling = hud_scaling * density; -#ifdef HAVE_TOUCHSCREENGUI - bool touch_controls = true; -#else - bool touch_controls = false; -#endif + bool touch_controls = g_touchscreengui; return { screen_size, real_gui_scaling, real_hud_scaling, @@ -67,12 +64,7 @@ public: private: #ifndef SERVER static v2f32 calculateMaxFSSize(v2u32 render_target_size, f32 gui_scaling) { - f32 factor = -#ifdef HAVE_TOUCHSCREENGUI - 10 / gui_scaling; -#else - 15 / gui_scaling; -#endif + f32 factor = (g_settings->getBool("enable_touch") ? 10 : 15) / gui_scaling; f32 ratio = (f32)render_target_size.X / (f32)render_target_size.Y; if (ratio < 1) return { factor, factor / ratio }; diff --git a/src/content/content.cpp b/src/content/content.cpp index e576943ff..ad5cc9e64 100644 --- a/src/content/content.cpp +++ b/src/content/content.cpp @@ -24,68 +24,59 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "filesys.h" #include "settings.h" -enum ContentType +ContentType getContentType(const std::string &path) { - ECT_UNKNOWN, - ECT_MOD, - ECT_MODPACK, - ECT_GAME, - ECT_TXP -}; - -ContentType getContentType(const ContentSpec &spec) -{ - std::ifstream modpack_is((spec.path + DIR_DELIM + "modpack.txt").c_str()); + std::ifstream modpack_is((path + DIR_DELIM + "modpack.txt").c_str()); if (modpack_is.good()) { modpack_is.close(); - return ECT_MODPACK; + return ContentType::MODPACK; } - std::ifstream modpack2_is((spec.path + DIR_DELIM + "modpack.conf").c_str()); + std::ifstream modpack2_is((path + DIR_DELIM + "modpack.conf").c_str()); if (modpack2_is.good()) { modpack2_is.close(); - return ECT_MODPACK; + return ContentType::MODPACK; } - std::ifstream init_is((spec.path + DIR_DELIM + "init.lua").c_str()); + std::ifstream init_is((path + DIR_DELIM + "init.lua").c_str()); if (init_is.good()) { init_is.close(); - return ECT_MOD; + return ContentType::MOD; } - std::ifstream game_is((spec.path + DIR_DELIM + "game.conf").c_str()); + std::ifstream game_is((path + DIR_DELIM + "game.conf").c_str()); if (game_is.good()) { game_is.close(); - return ECT_GAME; + return ContentType::GAME; } - std::ifstream txp_is((spec.path + DIR_DELIM + "texture_pack.conf").c_str()); + std::ifstream txp_is((path + DIR_DELIM + "texture_pack.conf").c_str()); if (txp_is.good()) { txp_is.close(); - return ECT_TXP; + return ContentType::TXP; } - return ECT_UNKNOWN; + return ContentType::UNKNOWN; } void parseContentInfo(ContentSpec &spec) { std::string conf_path; - switch (getContentType(spec)) { - case ECT_MOD: + switch (getContentType(spec.path)) { + case ContentType::MOD: spec.type = "mod"; conf_path = spec.path + DIR_DELIM + "mod.conf"; break; - case ECT_MODPACK: + case ContentType::MODPACK: spec.type = "modpack"; conf_path = spec.path + DIR_DELIM + "modpack.conf"; break; - case ECT_GAME: + case ContentType::GAME: spec.type = "game"; conf_path = spec.path + DIR_DELIM + "game.conf"; break; - case ECT_TXP: + case ContentType::TXP: spec.type = "txp"; conf_path = spec.path + DIR_DELIM + "texture_pack.conf"; break; @@ -104,6 +95,15 @@ void parseContentInfo(ContentSpec &spec) if (spec.type != "game" && conf.exists("name")) spec.name = conf.get("name"); + if (conf.exists("title")) + spec.title = conf.get("title"); + + if (spec.type == "game") { + if (spec.title.empty()) + spec.title = spec.name; + spec.name = ""; + } + if (conf.exists("description")) spec.desc = conf.get("description"); @@ -112,8 +112,17 @@ void parseContentInfo(ContentSpec &spec) if (conf.exists("release")) spec.release = conf.getS32("release"); + + if (conf.exists("textdomain")) + spec.textdomain = conf.get("textdomain"); } + if (spec.name.empty()) + spec.name = fs::GetFilenameFromPath(spec.path.c_str()); + + if (spec.textdomain.empty()) + spec.textdomain = spec.name; + if (spec.desc.empty()) { std::ifstream is((spec.path + DIR_DELIM + "description.txt").c_str()); spec.desc = std::string((std::istreambuf_iterator(is)), diff --git a/src/content/content.h b/src/content/content.h index ce09a2eb9..c7fc220dc 100644 --- a/src/content/content.h +++ b/src/content/content.h @@ -22,6 +22,16 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "convert_json.h" #include "irrlichttypes.h" +enum class ContentType +{ + UNKNOWN, + MOD, + MODPACK, + GAME, + TXP +}; + + struct ContentSpec { std::string type; @@ -37,6 +47,9 @@ struct ContentSpec /// Short description std::string desc; std::string path; + std::string textdomain; }; + +ContentType getContentType(const std::string &path); void parseContentInfo(ContentSpec &spec); diff --git a/src/content/mod_configuration.cpp b/src/content/mod_configuration.cpp index ffb075732..9e0461f56 100644 --- a/src/content/mod_configuration.cpp +++ b/src/content/mod_configuration.cpp @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" #include "filesys.h" #include "gettext.h" +#include "exceptions.h" std::string ModConfiguration::getUnsatisfiedModsError() const diff --git a/src/content/mods.cpp b/src/content/mods.cpp index 13d104320..ea13a84a1 100644 --- a/src/content/mods.cpp +++ b/src/content/mods.cpp @@ -228,7 +228,7 @@ bool ModStorage::contains(const std::string &name) const return m_database->hasModEntry(m_mod_name, name); } -bool ModStorage::setString(const std::string &name, const std::string &var) +bool ModStorage::setString(const std::string &name, std::string_view var) { if (var.empty()) return m_database->removeModEntry(m_mod_name, name); diff --git a/src/content/mods.h b/src/content/mods.h index ce844397a..228684c6e 100644 --- a/src/content/mods.h +++ b/src/content/mods.h @@ -123,7 +123,7 @@ public: bool contains(const std::string &name) const override; - bool setString(const std::string &name, const std::string &var) override; + bool setString(const std::string &name, std::string_view var) override; const StringMap &getStrings(StringMap *place) const override; diff --git a/src/content/subgames.cpp b/src/content/subgames.cpp index c50c6f429..0ec927189 100644 --- a/src/content/subgames.cpp +++ b/src/content/subgames.cpp @@ -77,8 +77,21 @@ struct GameFindPath std::string getSubgamePathEnv() { + static bool has_warned = false; char *subgame_path = getenv("MINETEST_SUBGAME_PATH"); - return subgame_path ? std::string(subgame_path) : ""; + if (subgame_path && !has_warned) { + warningstream << "MINETEST_SUBGAME_PATH is deprecated, use MINETEST_GAME_PATH instead." + << std::endl; + has_warned = true; + } + + char *game_path = getenv("MINETEST_GAME_PATH"); + + if (game_path) + return std::string(game_path); + else if (subgame_path) + return std::string(subgame_path); + return ""; } SubgameSpec findSubgame(const std::string &id) @@ -226,9 +239,9 @@ std::set getAvailableGameIds() // Add it to result const char *ends[] = {"_game", NULL}; - std::string shorter = removeStringEnd(dln.name, ends); + auto shorter = removeStringEnd(dln.name, ends); if (!shorter.empty()) - gameids.insert(shorter); + gameids.emplace(shorter); else gameids.insert(dln.name); } diff --git a/src/craftdef.cpp b/src/craftdef.cpp index ac04716b4..72b8e8f9d 100644 --- a/src/craftdef.cpp +++ b/src/craftdef.cpp @@ -34,7 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc., inline bool isGroupRecipeStr(const std::string &rec_name) { - return str_starts_with(rec_name, std::string("group:")); + return str_starts_with(rec_name, "group:"); } static bool hasGroupItem(const std::vector &recipe) diff --git a/src/database/database-dummy.cpp b/src/database/database-dummy.cpp index 19cb6e4fa..300de1792 100644 --- a/src/database/database-dummy.cpp +++ b/src/database/database-dummy.cpp @@ -25,7 +25,7 @@ Dummy database class #include "remoteplayer.h" -bool Database_Dummy::saveBlock(const v3s16 &pos, const std::string &data) +bool Database_Dummy::saveBlock(const v3s16 &pos, std::string_view data) { m_database[getBlockAsInteger(pos)] = data; return true; @@ -128,11 +128,12 @@ bool Database_Dummy::hasModEntry(const std::string &modname, const std::string & } bool Database_Dummy::setModEntry(const std::string &modname, - const std::string &key, const std::string &value) + const std::string &key, std::string_view value) { auto mod_pair = m_mod_storage_database.find(modname); if (mod_pair == m_mod_storage_database.end()) { - m_mod_storage_database[modname] = StringMap({{key, value}}); + auto &map = m_mod_storage_database[modname]; + map[key] = value; } else { mod_pair->second[key] = value; } diff --git a/src/database/database-dummy.h b/src/database/database-dummy.h index ecfa82c34..05f28317b 100644 --- a/src/database/database-dummy.h +++ b/src/database/database-dummy.h @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include +#include #include #include "database.h" #include "irrlichttypes.h" @@ -27,7 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., class Database_Dummy : public MapDatabase, public PlayerDatabase, public ModStorageDatabase { public: - bool saveBlock(const v3s16 &pos, const std::string &data); + bool saveBlock(const v3s16 &pos, std::string_view data); void loadBlock(const v3s16 &pos, std::string *block); bool deleteBlock(const v3s16 &pos); void listAllLoadableBlocks(std::vector &dst); @@ -43,7 +44,7 @@ public: const std::string &key, std::string *value); bool hasModEntry(const std::string &modname, const std::string &key); bool setModEntry(const std::string &modname, - const std::string &key, const std::string &value); + const std::string &key,std::string_view value); bool removeModEntry(const std::string &modname, const std::string &key); bool removeModEntries(const std::string &modname); void listMods(std::vector *res); diff --git a/src/database/database-files.cpp b/src/database/database-files.cpp index 4357b5ea4..d670b3ef0 100644 --- a/src/database/database-files.cpp +++ b/src/database/database-files.cpp @@ -428,13 +428,14 @@ bool ModStorageDatabaseFiles::hasModEntry(const std::string &modname, const std: } bool ModStorageDatabaseFiles::setModEntry(const std::string &modname, - const std::string &key, const std::string &value) + const std::string &key, std::string_view value) { Json::Value *meta = getOrCreateJson(modname); if (!meta) return false; - (*meta)[key] = Json::Value(value); + Json::Value value_v(value.data(), value.data() + value.size()); + (*meta)[key] = std::move(value_v); m_modified.insert(modname); return true; diff --git a/src/database/database-files.h b/src/database/database-files.h index 61a8fd9a2..04a74aa83 100644 --- a/src/database/database-files.h +++ b/src/database/database-files.h @@ -84,7 +84,7 @@ public: const std::string &key, std::string *value); virtual bool hasModEntry(const std::string &modname, const std::string &key); virtual bool setModEntry(const std::string &modname, - const std::string &key, const std::string &value); + const std::string &key, std::string_view value); virtual bool removeModEntry(const std::string &modname, const std::string &key); virtual bool removeModEntries(const std::string &modname); virtual void listMods(std::vector *res); diff --git a/src/database/database-leveldb.cpp b/src/database/database-leveldb.cpp index 6a1325937..71c56f90a 100644 --- a/src/database/database-leveldb.cpp +++ b/src/database/database-leveldb.cpp @@ -53,10 +53,11 @@ Database_LevelDB::Database_LevelDB(const std::string &savedir) m_database.reset(db); } -bool Database_LevelDB::saveBlock(const v3s16 &pos, const std::string &data) +bool Database_LevelDB::saveBlock(const v3s16 &pos, std::string_view data) { + leveldb::Slice data_s(data.data(), data.size()); leveldb::Status status = m_database->Put(leveldb::WriteOptions(), - i64tos(getBlockAsInteger(pos)), data); + i64tos(getBlockAsInteger(pos)), data_s); if (!status.ok()) { warningstream << "saveBlock: LevelDB error saving block " << pos << ": " << status.ToString() << std::endl; diff --git a/src/database/database-leveldb.h b/src/database/database-leveldb.h index 812752a67..d6a57b66b 100644 --- a/src/database/database-leveldb.h +++ b/src/database/database-leveldb.h @@ -34,7 +34,7 @@ public: Database_LevelDB(const std::string &savedir); ~Database_LevelDB() = default; - bool saveBlock(const v3s16 &pos, const std::string &data); + bool saveBlock(const v3s16 &pos, std::string_view data); void loadBlock(const v3s16 &pos, std::string *block); bool deleteBlock(const v3s16 &pos); void listAllLoadableBlocks(std::vector &dst); diff --git a/src/database/database-postgresql.cpp b/src/database/database-postgresql.cpp index 7b6ecc934..667d7afbb 100644 --- a/src/database/database-postgresql.cpp +++ b/src/database/database-postgresql.cpp @@ -223,7 +223,7 @@ void MapDatabasePostgreSQL::initStatements() "SELECT posX, posY, posZ FROM blocks"); } -bool MapDatabasePostgreSQL::saveBlock(const v3s16 &pos, const std::string &data) +bool MapDatabasePostgreSQL::saveBlock(const v3s16 &pos, std::string_view data) { // Verify if we don't overflow the platform integer with the mapblock size if (data.size() > INT_MAX) { @@ -240,7 +240,7 @@ bool MapDatabasePostgreSQL::saveBlock(const v3s16 &pos, const std::string &data) y = htonl(pos.Y); z = htonl(pos.Z); - const void *args[] = { &x, &y, &z, data.c_str() }; + const void *args[] = { &x, &y, &z, data.data() }; const int argLen[] = { sizeof(x), sizeof(y), sizeof(z), (int)data.size() }; @@ -940,11 +940,11 @@ bool ModStorageDatabasePostgreSQL::hasModEntry(const std::string &modname, } bool ModStorageDatabasePostgreSQL::setModEntry(const std::string &modname, - const std::string &key, const std::string &value) + const std::string &key, std::string_view value) { verifyDatabase(); - const void *args[] = { modname.c_str(), key.c_str(), value.c_str() }; + const void *args[] = { modname.c_str(), key.c_str(), value.data() }; const int argLen[] = { -1, (int)MYMIN(key.size(), INT_MAX), diff --git a/src/database/database-postgresql.h b/src/database/database-postgresql.h index 56b3d6b53..28ff1c35f 100644 --- a/src/database/database-postgresql.h +++ b/src/database/database-postgresql.h @@ -120,7 +120,7 @@ public: MapDatabasePostgreSQL(const std::string &connect_string); virtual ~MapDatabasePostgreSQL() = default; - bool saveBlock(const v3s16 &pos, const std::string &data); + bool saveBlock(const v3s16 &pos, std::string_view data); void loadBlock(const v3s16 &pos, std::string *block); bool deleteBlock(const v3s16 &pos); void listAllLoadableBlocks(std::vector &dst); @@ -186,7 +186,7 @@ public: bool getModEntry(const std::string &modname, const std::string &key, std::string *value); bool hasModEntry(const std::string &modname, const std::string &key); bool setModEntry(const std::string &modname, - const std::string &key, const std::string &value); + const std::string &key, std::string_view value); bool removeModEntry(const std::string &modname, const std::string &key); bool removeModEntries(const std::string &modname); void listMods(std::vector *res); diff --git a/src/database/database-redis.cpp b/src/database/database-redis.cpp index 4509bb3c6..a9e549c33 100644 --- a/src/database/database-redis.cpp +++ b/src/database/database-redis.cpp @@ -91,12 +91,12 @@ void Database_Redis::endSave() { freeReplyObject(reply); } -bool Database_Redis::saveBlock(const v3s16 &pos, const std::string &data) +bool Database_Redis::saveBlock(const v3s16 &pos, std::string_view data) { std::string tmp = i64tos(getBlockAsInteger(pos)); redisReply *reply = static_cast(redisCommand(ctx, "HSET %s %s %b", - hash.c_str(), tmp.c_str(), data.c_str(), data.size())); + hash.c_str(), tmp.c_str(), data.data(), data.size())); if (!reply) { warningstream << "saveBlock: redis command 'HSET' failed on " "block " << pos << ": " << ctx->errstr << std::endl; diff --git a/src/database/database-redis.h b/src/database/database-redis.h index 6bea563bc..324fcd177 100644 --- a/src/database/database-redis.h +++ b/src/database/database-redis.h @@ -38,7 +38,7 @@ public: void beginSave(); void endSave(); - bool saveBlock(const v3s16 &pos, const std::string &data); + bool saveBlock(const v3s16 &pos, std::string_view data); void loadBlock(const v3s16 &pos, std::string *block); bool deleteBlock(const v3s16 &pos); void listAllLoadableBlocks(std::vector &dst); diff --git a/src/database/database-sqlite3.cpp b/src/database/database-sqlite3.cpp index 6f6ad341e..32b3be65b 100644 --- a/src/database/database-sqlite3.cpp +++ b/src/database/database-sqlite3.cpp @@ -258,7 +258,7 @@ bool MapDatabaseSQLite3::deleteBlock(const v3s16 &pos) return good; } -bool MapDatabaseSQLite3::saveBlock(const v3s16 &pos, const std::string &data) +bool MapDatabaseSQLite3::saveBlock(const v3s16 &pos, std::string_view data) { verifyDatabase(); @@ -283,13 +283,8 @@ void MapDatabaseSQLite3::loadBlock(const v3s16 &pos, std::string *block) return; } - const char *data = (const char *) sqlite3_column_blob(m_stmt_read, 0); - size_t len = sqlite3_column_bytes(m_stmt_read, 0); - - if (data) - block->assign(data, len); - else - block->clear(); + auto data = sqlite_to_blob(m_stmt_read, 0); + block->assign(data); sqlite3_step(m_stmt_read); // We should never get more than 1 row, so ok to reset @@ -553,7 +548,7 @@ bool PlayerDatabaseSQLite3::loadPlayer(RemotePlayer *player, PlayerSAO *sao) int_to_sqlite(m_stmt_player_load_inventory_items, 2, invId); while (sqlite3_step(m_stmt_player_load_inventory_items) == SQLITE_ROW) { const std::string itemStr = sqlite_to_string(m_stmt_player_load_inventory_items, 1); - if (itemStr.length() > 0) { + if (!itemStr.empty()) { ItemStack stack; stack.deSerialize(itemStr); invList->changeItem(sqlite_to_uint(m_stmt_player_load_inventory_items, 0), stack); @@ -567,7 +562,7 @@ bool PlayerDatabaseSQLite3::loadPlayer(RemotePlayer *player, PlayerSAO *sao) str_to_sqlite(m_stmt_player_metadata_load, 1, sao->getPlayer()->getName()); while (sqlite3_step(m_stmt_player_metadata_load) == SQLITE_ROW) { std::string attr = sqlite_to_string(m_stmt_player_metadata_load, 0); - std::string value = sqlite_to_string(m_stmt_player_metadata_load, 1); + auto value = sqlite_to_string_view(m_stmt_player_metadata_load, 1); sao->getMeta().setString(attr, value); } @@ -592,7 +587,7 @@ void PlayerDatabaseSQLite3::listPlayers(std::vector &res) verifyDatabase(); while (sqlite3_step(m_stmt_player_list) == SQLITE_ROW) - res.push_back(sqlite_to_string(m_stmt_player_list, 0)); + res.emplace_back(sqlite_to_string_view(m_stmt_player_list, 0)); sqlite3_reset(m_stmt_player_list); } @@ -669,14 +664,14 @@ bool AuthDatabaseSQLite3::getAuth(const std::string &name, AuthEntry &res) return false; } res.id = sqlite_to_uint(m_stmt_read, 0); - res.name = sqlite_to_string(m_stmt_read, 1); - res.password = sqlite_to_string(m_stmt_read, 2); + res.name = sqlite_to_string_view(m_stmt_read, 1); + res.password = sqlite_to_string_view(m_stmt_read, 2); res.last_login = sqlite_to_int64(m_stmt_read, 3); sqlite3_reset(m_stmt_read); int64_to_sqlite(m_stmt_read_privs, 1, res.id); while (sqlite3_step(m_stmt_read_privs) == SQLITE_ROW) { - res.privileges.emplace_back(sqlite_to_string(m_stmt_read_privs, 0)); + res.privileges.emplace_back(sqlite_to_string_view(m_stmt_read_privs, 0)); } sqlite3_reset(m_stmt_read_privs); @@ -741,7 +736,7 @@ void AuthDatabaseSQLite3::listNames(std::vector &res) verifyDatabase(); while (sqlite3_step(m_stmt_list_names) == SQLITE_ROW) { - res.push_back(sqlite_to_string(m_stmt_list_names, 0)); + res.emplace_back(sqlite_to_string_view(m_stmt_list_names, 0)); } sqlite3_reset(m_stmt_list_names); } @@ -815,11 +810,9 @@ void ModStorageDatabaseSQLite3::getModEntries(const std::string &modname, String str_to_sqlite(m_stmt_get_all, 1, modname); while (sqlite3_step(m_stmt_get_all) == SQLITE_ROW) { - const char *key_data = (const char *) sqlite3_column_blob(m_stmt_get_all, 0); - size_t key_len = sqlite3_column_bytes(m_stmt_get_all, 0); - const char *value_data = (const char *) sqlite3_column_blob(m_stmt_get_all, 1); - size_t value_len = sqlite3_column_bytes(m_stmt_get_all, 1); - (*storage)[std::string(key_data, key_len)] = std::string(value_data, value_len); + auto key = sqlite_to_blob(m_stmt_get_all, 0); + auto value = sqlite_to_blob(m_stmt_get_all, 1); + (*storage)[std::string(key)].assign(value); } sqlite3_vrfy(sqlite3_errcode(m_database), SQLITE_DONE); @@ -833,9 +826,8 @@ void ModStorageDatabaseSQLite3::getModKeys(const std::string &modname, str_to_sqlite(m_stmt_get_keys, 1, modname); while (sqlite3_step(m_stmt_get_keys) == SQLITE_ROW) { - const char *key_data = (const char *) sqlite3_column_blob(m_stmt_get_keys, 0); - size_t key_len = sqlite3_column_bytes(m_stmt_get_keys, 0); - storage->emplace_back(key_data, key_len); + auto key = sqlite_to_blob(m_stmt_get_keys, 0); + storage->emplace_back(key); } sqlite3_vrfy(sqlite3_errcode(m_database), SQLITE_DONE); @@ -852,9 +844,8 @@ bool ModStorageDatabaseSQLite3::getModEntry(const std::string &modname, "Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__)); bool found = sqlite3_step(m_stmt_get) == SQLITE_ROW; if (found) { - const char *value_data = (const char *) sqlite3_column_blob(m_stmt_get, 0); - size_t value_len = sqlite3_column_bytes(m_stmt_get, 0); - value->assign(value_data, value_len); + auto sv = sqlite_to_blob(m_stmt_get, 0); + value->assign(sv); sqlite3_step(m_stmt_get); } @@ -881,7 +872,7 @@ bool ModStorageDatabaseSQLite3::hasModEntry(const std::string &modname, } bool ModStorageDatabaseSQLite3::setModEntry(const std::string &modname, - const std::string &key, const std::string &value) + const std::string &key, std::string_view value) { verifyDatabase(); diff --git a/src/database/database-sqlite3.h b/src/database/database-sqlite3.h index a400537ff..d008f43e2 100644 --- a/src/database/database-sqlite3.h +++ b/src/database/database-sqlite3.h @@ -44,14 +44,9 @@ protected: void verifyDatabase(); // Convertors - inline void str_to_sqlite(sqlite3_stmt *s, int iCol, const std::string &str) const + inline void str_to_sqlite(sqlite3_stmt *s, int iCol, std::string_view str) const { - sqlite3_vrfy(sqlite3_bind_text(s, iCol, str.c_str(), str.size(), NULL)); - } - - inline void str_to_sqlite(sqlite3_stmt *s, int iCol, const char *str) const - { - sqlite3_vrfy(sqlite3_bind_text(s, iCol, str, strlen(str), NULL)); + sqlite3_vrfy(sqlite3_bind_text(s, iCol, str.data(), str.size(), NULL)); } inline void int_to_sqlite(sqlite3_stmt *s, int iCol, int val) const @@ -69,10 +64,28 @@ protected: sqlite3_vrfy(sqlite3_bind_double(s, iCol, val)); } - inline std::string sqlite_to_string(sqlite3_stmt *s, int iCol) + // Note that the return value is only valid until the statement is stepped or reset. + inline std::string_view sqlite_to_string_view(sqlite3_stmt *s, int iCol) { const char* text = reinterpret_cast(sqlite3_column_text(s, iCol)); - return std::string(text ? text : ""); + return text ? std::string_view(text) : std::string_view(); + } + + // Avoid using this in favor of `sqlite_to_string_view`. + inline std::string sqlite_to_string(sqlite3_stmt *s, int iCol) + { + return std::string(sqlite_to_string_view(s, iCol)); + } + + // Converts a BLOB-type column into a string_view (null byte safe). + // Note that the return value is only valid until the statement is stepped or reset. + inline std::string_view sqlite_to_blob(sqlite3_stmt *s, int iCol) + { + const char *data = reinterpret_cast(sqlite3_column_blob(s, iCol)); + if (!data) + return std::string_view(); + size_t len = sqlite3_column_bytes(s, iCol); + return std::string_view(data, len); } inline s32 sqlite_to_int(sqlite3_stmt *s, int iCol) @@ -107,13 +120,16 @@ protected: } // Query verifiers helpers - inline void sqlite3_vrfy(int s, const std::string &m = "", int r = SQLITE_OK) const + inline void sqlite3_vrfy(int s, std::string_view m = "", int r = SQLITE_OK) const { - if (s != r) - throw DatabaseException(m + ": " + sqlite3_errmsg(m_database)); + if (s != r) { + std::string msg(m); + msg.append(": ").append(sqlite3_errmsg(m_database)); + throw DatabaseException(msg); + } } - inline void sqlite3_vrfy(const int s, const int r, const std::string &m = "") const + inline void sqlite3_vrfy(const int s, const int r, std::string_view m = "") const { sqlite3_vrfy(s, m, r); } @@ -146,7 +162,7 @@ public: MapDatabaseSQLite3(const std::string &savedir); virtual ~MapDatabaseSQLite3(); - bool saveBlock(const v3s16 &pos, const std::string &data); + bool saveBlock(const v3s16 &pos, std::string_view data); void loadBlock(const v3s16 &pos, std::string *block); bool deleteBlock(const v3s16 &pos); void listAllLoadableBlocks(std::vector &dst); @@ -245,7 +261,7 @@ public: const std::string &key, std::string *value); virtual bool hasModEntry(const std::string &modname, const std::string &key); virtual bool setModEntry(const std::string &modname, - const std::string &key, const std::string &value); + const std::string &key,std::string_view value); virtual bool removeModEntry(const std::string &modname, const std::string &key); virtual bool removeModEntries(const std::string &modname); virtual void listMods(std::vector *res); diff --git a/src/database/database.h b/src/database/database.h index 84912e795..d4a3f005c 100644 --- a/src/database/database.h +++ b/src/database/database.h @@ -19,12 +19,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once -#include #include +#include #include #include "irr_v3d.h" #include "irrlichttypes.h" -#include "util/basic_macros.h" #include "util/string.h" class Database @@ -40,7 +39,7 @@ class MapDatabase : public Database public: virtual ~MapDatabase() = default; - virtual bool saveBlock(const v3s16 &pos, const std::string &data) = 0; + virtual bool saveBlock(const v3s16 &pos, std::string_view data) = 0; virtual void loadBlock(const v3s16 &pos, std::string *block) = 0; virtual bool deleteBlock(const v3s16 &pos) = 0; @@ -97,7 +96,7 @@ public: virtual bool getModEntry(const std::string &modname, const std::string &key, std::string *value) = 0; virtual bool setModEntry(const std::string &modname, - const std::string &key, const std::string &value) = 0; + const std::string &key, std::string_view value) = 0; virtual bool removeModEntry(const std::string &modname, const std::string &key) = 0; virtual bool removeModEntries(const std::string &modname) = 0; virtual void listMods(std::vector *res) = 0; diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 39a403b38..cd14e09fd 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -39,6 +39,11 @@ void set_default_settings() // Client settings->setDefault("address", ""); settings->setDefault("enable_sound", "true"); +#if ENABLE_TOUCH + settings->setDefault("enable_touch", "true"); +#else + settings->setDefault("enable_touch", "false"); +#endif settings->setDefault("sound_volume", "0.8"); settings->setDefault("sound_volume_unfocused", "0.3"); settings->setDefault("mute_sound", "false"); @@ -90,7 +95,7 @@ void set_default_settings() settings->setDefault("keymap_cmd_local", "."); settings->setDefault("keymap_minimap", "KEY_KEY_V"); settings->setDefault("keymap_console", "KEY_F10"); -#if HAVE_TOUCHSCREENGUI +#if ENABLE_TOUCH // See https://github.com/minetest/minetest/issues/12792 settings->setDefault("keymap_rangeselect", "KEY_KEY_R"); #else @@ -192,7 +197,11 @@ void set_default_settings() settings->setDefault("screen_h", "600"); settings->setDefault("window_maximized", "false"); settings->setDefault("autosave_screensize", "true"); +#ifdef ENABLE_TOUCH + settings->setDefault("fullscreen", "true"); +#else settings->setDefault("fullscreen", "false"); +#endif settings->setDefault("vsync", "false"); settings->setDefault("fov", "72"); settings->setDefault("leaves_style", "fancy"); @@ -253,6 +262,7 @@ void set_default_settings() settings->setDefault("minimap_double_scan_height", "true"); // Effects + settings->setDefault("enable_post_processing", "true"); settings->setDefault("directional_colored_fog", "true"); settings->setDefault("inventory_items_animations", "false"); settings->setDefault("mip_map", "false"); @@ -301,7 +311,7 @@ void set_default_settings() settings->setDefault("aux1_descends", "false"); settings->setDefault("doubletap_jump", "false"); settings->setDefault("always_fly_fast", "true"); -#ifdef HAVE_TOUCHSCREENGUI +#ifdef ENABLE_TOUCH settings->setDefault("autojump", "true"); #else settings->setDefault("autojump", "false"); @@ -480,12 +490,12 @@ void set_default_settings() settings->setDefault("keymap_sneak", "KEY_SHIFT"); #endif -#ifdef HAVE_TOUCHSCREENGUI settings->setDefault("touchscreen_threshold", "20"); settings->setDefault("touchscreen_sensitivity", "0.2"); settings->setDefault("touch_use_crosshair", "false"); settings->setDefault("fixed_virtual_joystick", "false"); settings->setDefault("virtual_joystick_triggers_aux1", "false"); +#ifdef ENABLE_TOUCH settings->setDefault("clickable_chat_weblinks", "false"); #else settings->setDefault("clickable_chat_weblinks", "true"); @@ -494,7 +504,6 @@ void set_default_settings() #ifdef __ANDROID__ settings->setDefault("screen_w", "0"); settings->setDefault("screen_h", "0"); - settings->setDefault("fullscreen", "true"); settings->setDefault("performance_tradeoffs", "true"); settings->setDefault("max_simultaneous_block_sends_per_client", "10"); settings->setDefault("emergequeue_limit_diskonly", "16"); @@ -506,6 +515,7 @@ void set_default_settings() settings->setDefault("active_block_range", "2"); settings->setDefault("viewing_range", "50"); settings->setDefault("leaves_style", "simple"); + settings->setDefault("enable_post_processing", "false"); settings->setDefault("debanding", "false"); settings->setDefault("curl_verify_cert", "false"); diff --git a/src/emerge.cpp b/src/emerge.cpp index 5d096c5b0..bef1e34ac 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -19,19 +19,16 @@ with this program; if not, write to the Free Software Foundation, Inc., */ -#include "emerge.h" +#include "emerge_internal.h" #include -#include #include "util/container.h" -#include "util/thread.h" -#include "threading/event.h" - #include "config.h" #include "constants.h" #include "environment.h" #include "irrlicht_changes/printing.h" +#include "filesys.h" #include "log.h" #include "map.h" #include "mapblock.h" @@ -42,76 +39,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "nodedef.h" #include "profiler.h" #include "scripting_server.h" +#include "scripting_emerge.h" #include "server.h" #include "settings.h" #include "voxel.h" -class EmergeThread : public Thread { -public: - bool enable_mapgen_debug_info; - int id; - - EmergeThread(Server *server, int ethreadid); - ~EmergeThread() = default; - - void *run(); - void signal(); - - // Requires queue mutex held - bool pushBlock(const v3s16 &pos); - - void cancelPendingItems(); - -protected: - - void runCompletionCallbacks( - const v3s16 &pos, EmergeAction action, - const EmergeCallbackList &callbacks); - -private: - Server *m_server; - ServerMap *m_map; - EmergeManager *m_emerge; - Mapgen *m_mapgen; - - Event m_queue_event; - std::queue m_block_queue; - - bool popBlockEmerge(v3s16 *pos, BlockEmergeData *bedata); - - EmergeAction getBlockOrStartGen( - const v3s16 &pos, bool allow_gen, MapBlock **block, BlockMakeData *data); - MapBlock *finishGen(v3s16 pos, BlockMakeData *bmdata, - std::map *modified_blocks); - - friend class EmergeManager; -}; - -class MapEditEventAreaIgnorer -{ -public: - MapEditEventAreaIgnorer(VoxelArea *ignorevariable, const VoxelArea &a): - m_ignorevariable(ignorevariable) - { - if(m_ignorevariable->getVolume() == 0) - *m_ignorevariable = a; - else - m_ignorevariable = NULL; - } - - ~MapEditEventAreaIgnorer() - { - if(m_ignorevariable) - { - assert(m_ignorevariable->getVolume() != 0); - *m_ignorevariable = VoxelArea(); - } - } - -private: - VoxelArea *m_ignorevariable; -}; - EmergeParams::~EmergeParams() { infostream << "EmergeParams: destroying " << this << std::endl; @@ -131,6 +63,7 @@ EmergeParams::EmergeParams(EmergeManager *parent, const BiomeGen *biomegen, enable_mapgen_debug_info(parent->enable_mapgen_debug_info), gen_notify_on(parent->gen_notify_on), gen_notify_on_deco_ids(&parent->gen_notify_on_deco_ids), + gen_notify_on_custom(&parent->gen_notify_on_custom), biomemgr(biomemgr->clone()), oremgr(oremgr->clone()), decomgr(decomgr->clone()), schemmgr(schemmgr->clone()) { @@ -518,9 +451,10 @@ EmergeThread::EmergeThread(Server *server, int ethreadid) : enable_mapgen_debug_info(false), id(ethreadid), m_server(server), - m_map(NULL), - m_emerge(NULL), - m_mapgen(NULL) + m_map(nullptr), + m_emerge(nullptr), + m_mapgen(nullptr), + m_trans_liquid(nullptr) { m_name = "Emerge-" + itos(ethreadid); } @@ -641,13 +575,13 @@ MapBlock *EmergeThread::finishGen(v3s16 pos, BlockMakeData *bmdata, v3s16(1,1,1) * (MAP_BLOCKSIZE - 1); // Ignore map edit events, they will not need to be sent - // to anybody because the block hasn't been sent to anybody + // to anyone because the block hasn't been sent yet. MapEditEventAreaIgnorer ign( &m_server->m_ignore_map_edit_events_area, VoxelArea(minp, maxp)); /* - Run Lua on_generated callbacks + Run Lua on_generated callbacks in the server environment */ try { m_server->getScriptIface()->environment_OnGenerated( @@ -674,6 +608,36 @@ MapBlock *EmergeThread::finishGen(v3s16 pos, BlockMakeData *bmdata, } +bool EmergeThread::initScripting() +{ + m_script = std::make_unique(this); + + try { + m_script->loadMod(Server::getBuiltinLuaPath() + DIR_DELIM + "init.lua", + BUILTIN_MOD_NAME); + m_script->checkSetByBuiltin(); + } catch (const ModError &e) { + errorstream << "Execution of mapgen base environment failed." << std::endl; + m_server->setAsyncFatalError(e.what()); + return false; + } + + const auto &list = m_server->m_mapgen_init_files; + try { + for (auto &it : list) + m_script->loadMod(it.second, it.first); + + m_script->on_mods_loaded(); + } catch (const ModError &e) { + errorstream << "Failed to load mod script inside mapgen environment." << std::endl; + m_server->setAsyncFatalError(e.what()); + return false; + } + + return true; +} + + void *EmergeThread::run() { BEGIN_DEBUG_EXCEPTION_HANDLER @@ -686,6 +650,11 @@ void *EmergeThread::run() m_mapgen = m_emerge->m_mapgens[id]; enable_mapgen_debug_info = m_emerge->enable_mapgen_debug_info; + if (!initScripting()) { + m_script.reset(); + stop(); // do not enter main loop + } + try { while (!stopRequested()) { BlockEmergeData bedata; @@ -706,6 +675,9 @@ void *EmergeThread::run() action = getBlockOrStartGen(pos, allow_gen, &block, &bmdata); if (action == EMERGE_GENERATED) { + bool error = false; + m_trans_liquid = &bmdata.transforming_liquid; + { ScopeProfiler sp(g_profiler, "EmergeThread: Mapgen::makeChunk", SPT_AVG); @@ -713,9 +685,24 @@ void *EmergeThread::run() m_mapgen->makeChunk(&bmdata); } - block = finishGen(pos, &bmdata, &modified_blocks); - if (!block) + { + ScopeProfiler sp(g_profiler, + "EmergeThread: Lua on_generated", SPT_AVG); + + try { + m_script->on_generated(&bmdata); + } catch (const LuaError &e) { + m_server->setAsyncFatalError(e); + error = true; + } + } + + if (!error) + block = finishGen(pos, &bmdata, &modified_blocks); + if (!block || error) action = EMERGE_ERRORED; + + m_trans_liquid = nullptr; } runCompletionCallbacks(pos, action, bedata.callbacks); @@ -752,6 +739,13 @@ void *EmergeThread::run() m_server->setAsyncFatalError(err.str()); } + try { + if (m_script) + m_script->on_shutdown(); + } catch (const ModError &e) { + m_server->setAsyncFatalError(e.what()); + } + cancelPendingItems(); END_DEBUG_EXCEPTION_HANDLER diff --git a/src/emerge.h b/src/emerge.h index 1bac4b708..ace9f6e46 100644 --- a/src/emerge.h +++ b/src/emerge.h @@ -107,6 +107,7 @@ public: u32 gen_notify_on; const std::set *gen_notify_on_deco_ids; // shared + const std::set *gen_notify_on_custom; // shared BiomeGen *biomegen; BiomeManager *biomemgr; @@ -114,6 +115,11 @@ public: DecorationManager *decomgr; SchematicManager *schemmgr; + inline GenerateNotifier createNotifier() const { + return GenerateNotifier(gen_notify_on, gen_notify_on_deco_ids, + gen_notify_on_custom); + } + private: EmergeParams(EmergeManager *parent, const BiomeGen *biomegen, const BiomeManager *biomemgr, @@ -134,6 +140,7 @@ public: // Generation Notify u32 gen_notify_on = 0; std::set gen_notify_on_deco_ids; + std::set gen_notify_on_custom; // Parameters passed to mapgens owned by ServerMap // TODO(hmmmm): Remove this after mapgen helper methods using them diff --git a/src/emerge_internal.h b/src/emerge_internal.h new file mode 100644 index 000000000..439c8227b --- /dev/null +++ b/src/emerge_internal.h @@ -0,0 +1,115 @@ +/* +Minetest +Copyright (C) 2010-2013 kwolekr, Ryan Kwolek + +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 + +/******************************************************************/ +/* may only be included by emerge.cpp or emerge scripting related */ +/******************************************************************/ + +#include "emerge.h" + +#include + +#include "util/thread.h" +#include "threading/event.h" + +class Server; +class ServerMap; +class Mapgen; + +class EmergeManager; +class EmergeScripting; + +class EmergeThread : public Thread { +public: + bool enable_mapgen_debug_info; + int id; + + EmergeThread(Server *server, int ethreadid); + ~EmergeThread() = default; + + void *run(); + void signal(); + + // Requires queue mutex held + bool pushBlock(const v3s16 &pos); + + void cancelPendingItems(); + + EmergeManager *getEmergeManager() { return m_emerge; } + Mapgen *getMapgen() { return m_mapgen; } + +protected: + + void runCompletionCallbacks( + const v3s16 &pos, EmergeAction action, + const EmergeCallbackList &callbacks); + +private: + Server *m_server; + ServerMap *m_map; + EmergeManager *m_emerge; + Mapgen *m_mapgen; + + std::unique_ptr m_script; + // read from scripting: + UniqueQueue *m_trans_liquid; //< non-null only when generating a mapblock + + Event m_queue_event; + std::queue m_block_queue; + + bool initScripting(); + + bool popBlockEmerge(v3s16 *pos, BlockEmergeData *bedata); + + EmergeAction getBlockOrStartGen( + const v3s16 &pos, bool allow_gen, MapBlock **block, BlockMakeData *data); + MapBlock *finishGen(v3s16 pos, BlockMakeData *bmdata, + std::map *modified_blocks); + + friend class EmergeManager; + friend class EmergeScripting; + friend class ModApiMapgen; +}; + +// Scoped helper to set Server::m_ignore_map_edit_events_area +class MapEditEventAreaIgnorer +{ +public: + MapEditEventAreaIgnorer(VoxelArea *ignorevariable, const VoxelArea &a): + m_ignorevariable(ignorevariable) + { + if (m_ignorevariable->getVolume() == 0) + *m_ignorevariable = a; + else + m_ignorevariable = nullptr; + } + + ~MapEditEventAreaIgnorer() + { + if (m_ignorevariable) { + assert(m_ignorevariable->getVolume() != 0); + *m_ignorevariable = VoxelArea(); + } + } + +private: + VoxelArea *m_ignorevariable; +}; diff --git a/src/filesys.cpp b/src/filesys.cpp index 4fc51a5da..88d617801 100644 --- a/src/filesys.cpp +++ b/src/filesys.cpp @@ -33,6 +33,13 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #endif +#ifdef __linux__ +#include +#include +#ifndef FICLONE +#define FICLONE _IOW(0x94, 9, int) +#endif +#endif namespace fs { @@ -216,6 +223,31 @@ std::string CreateTempFile() return path; } +bool CopyFileContents(const std::string &source, const std::string &target) +{ + BOOL ok = CopyFileEx(source.c_str(), target.c_str(), nullptr, nullptr, + nullptr, COPY_FILE_ALLOW_DECRYPTED_DESTINATION); + if (!ok) { + errorstream << "copying " << source << " to " << target + << " failed: " << GetLastError() << std::endl; + return false; + } + + // docs: "File attributes for the existing file are copied to the new file." + // This is not our intention so get rid of unwanted attributes: + DWORD attr = GetFileAttributes(target.c_str()); + if (attr == INVALID_FILE_ATTRIBUTES) { + errorstream << target << ": file disappeared after copy" << std::endl; + return false; + } + attr &= ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN); + SetFileAttributes(target.c_str(), attr); + + tracestream << "copied " << source << " to " << target + << " using CopyFileEx" << std::endl; + return true; +} + #else /********* @@ -414,6 +446,101 @@ std::string CreateTempFile() return path; } +namespace { + struct FileDeleter { + void operator()(FILE *stream) { + fclose(stream); + } + }; + + typedef std::unique_ptr FileUniquePtr; +} + +bool CopyFileContents(const std::string &source, const std::string &target) +{ + FileUniquePtr sourcefile, targetfile; + +#ifdef __linux__ + // Try to clone using Copy-on-Write (CoW). This is instant but supported + // only by some filesystems. + + int srcfd, tgtfd; + srcfd = open(source.c_str(), O_RDONLY); + if (srcfd == -1) { + errorstream << source << ": can't open for reading: " + << strerror(errno) << std::endl; + return false; + } + tgtfd = open(target.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (tgtfd == -1) { + errorstream << target << ": can't open for writing: " + << strerror(errno) << std::endl; + close(srcfd); + return false; + } + + if (ioctl(tgtfd, FICLONE, srcfd) == 0) { + tracestream << "copied " << source << " to " << target + << " using FICLONE" << std::endl; + close(srcfd); + close(tgtfd); + return true; + } + + // fallback to normal copy, but no need to reopen the files + sourcefile.reset(fdopen(srcfd, "rb")); + targetfile.reset(fdopen(tgtfd, "wb")); + goto fallback; + +#endif + + sourcefile.reset(fopen(source.c_str(), "rb")); + targetfile.reset(fopen(target.c_str(), "wb")); + +fallback: + + if (!sourcefile) { + errorstream << source << ": can't open for reading: " + << strerror(errno) << std::endl; + return false; + } + if (!targetfile) { + errorstream << target << ": can't open for writing: " + << strerror(errno) << std::endl; + return false; + } + + size_t total = 0; + bool done = false; + char readbuffer[BUFSIZ]; + while (!done) { + size_t readbytes = fread(readbuffer, 1, + sizeof(readbuffer), sourcefile.get()); + total += readbytes; + if (ferror(sourcefile.get())) { + errorstream << source << ": IO error: " + << strerror(errno) << std::endl; + return false; + } + if (readbytes > 0) + fwrite(readbuffer, 1, readbytes, targetfile.get()); + if (feof(sourcefile.get())) { + // flush destination file to catch write errors (e.g. disk full) + fflush(targetfile.get()); + done = true; + } + if (ferror(targetfile.get())) { + errorstream << target << ": IO error: " + << strerror(errno) << std::endl; + return false; + } + } + tracestream << "copied " << total << " bytes from " + << source << " to " << target << std::endl; + + return true; +} + #endif /**************************** @@ -488,60 +615,6 @@ bool CreateAllDirs(const std::string &path) return true; } -bool CopyFileContents(const std::string &source, const std::string &target) -{ - FILE *sourcefile = fopen(source.c_str(), "rb"); - if(sourcefile == NULL){ - errorstream< 0){ - fwrite(readbuffer, 1, readbytes, targetfile); - } - if(feof(sourcefile) || ferror(sourcefile)){ - // flush destination file to catch write errors - // (e.g. disk full) - fflush(targetfile); - done = true; - } - if(ferror(targetfile)){ - errorstream< #include +#include #include -#include "exceptions.h" #ifdef _WIN32 #define DIR_DELIM "\\" @@ -142,7 +142,7 @@ std::string AbsolutePath(const std::string &path); // delimiter is found. const char *GetFilenameFromPath(const char *path); -bool safeWriteToFile(const std::string &path, const std::string &content); +bool safeWriteToFile(const std::string &path, std::string_view content); #ifndef SERVER bool extractZipFile(irr::io::IFileSystem *fs, const char *filename, const std::string &destination); diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 4434b14a0..87575f320 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -1,8 +1,3 @@ -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}/guiAnimatedImage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiBackgroundImage.cpp @@ -29,6 +24,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} + ${CMAKE_CURRENT_SOURCE_DIR}/touchscreengui.cpp PARENT_SCOPE ) diff --git a/src/gui/guiEngine.cpp b/src/gui/guiEngine.cpp index 1ae463e37..3f21e69e6 100644 --- a/src/gui/guiEngine.cpp +++ b/src/gui/guiEngine.cpp @@ -36,6 +36,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/guiscalingfilter.h" #include "irrlicht_changes/static_text.h" #include "client/tile.h" +#include "content/content.h" +#include "content/mods.h" #if USE_SOUND #include "client/sound/sound_openal.h" @@ -204,6 +206,57 @@ GUIEngine::GUIEngine(JoystickController *joystick, m_menu.reset(); } + +/******************************************************************************/ +std::string findLocaleFileInMods(const std::string &path, const std::string &filename) +{ + std::vector mods = flattenMods(getModsInPath(path, "root", true)); + + for (const auto &mod : mods) { + std::string ret = mod.path + DIR_DELIM "locale" DIR_DELIM + filename; + if (fs::PathExists(ret)) { + return ret; + } + } + + return ""; +} + +/******************************************************************************/ +Translations *GUIEngine::getContentTranslations(const std::string &path, + const std::string &domain, const std::string &lang_code) +{ + if (domain.empty() || lang_code.empty()) + return nullptr; + + std::string filename = domain + "." + lang_code + ".tr"; + std::string key = path + DIR_DELIM "locale" DIR_DELIM + filename; + + if (key == m_last_translations_key) + return &m_last_translations; + + std::string trans_path = key; + ContentType type = getContentType(path); + if (type == ContentType::GAME) + trans_path = findLocaleFileInMods(path + DIR_DELIM "mods" DIR_DELIM, filename); + else if (type == ContentType::MODPACK) + trans_path = findLocaleFileInMods(path, filename); + // We don't need to search for locale files in a mod, as there's only one `locale` folder. + + if (trans_path.empty()) + return nullptr; + + m_last_translations_key = key; + m_last_translations = {}; + + std::string data; + if (fs::ReadFile(trans_path, data)) { + m_last_translations.loadTranslation(data); + } + + return &m_last_translations; +} + /******************************************************************************/ bool GUIEngine::loadMainMenuScript() { diff --git a/src/gui/guiEngine.h b/src/gui/guiEngine.h index cb8a97942..7d33ce7c5 100644 --- a/src/gui/guiEngine.h +++ b/src/gui/guiEngine.h @@ -28,6 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/sound.h" #include "client/tile.h" #include "util/enriched_string.h" +#include "translation.h" /******************************************************************************/ /* Structs and macros */ @@ -165,7 +166,22 @@ public: return m_scriptdir; } + /** + * Get translations for content + * + * Only loads a single textdomain from the path, as specified by `domain`, + * for performance reasons. + * + * WARNING: Do not store the returned pointer for long as the contents may + * change with the next call to `getContentTranslations`. + * */ + Translations *getContentTranslations(const std::string &path, + const std::string &domain, const std::string &lang_code); + private: + std::string m_last_translations_key; + /** Only the most recently used translation set is kept loaded */ + Translations m_last_translations; /** find and run the main menu script */ bool loadMainMenuScript(); diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index a93f8c505..e4e123c16 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -316,13 +316,11 @@ void GUIFormSpecMenu::parseSize(parserData* data, const std::string &element) data->invsize.Y = MYMAX(0, stof(parts[1])); lockSize(false); -#ifndef HAVE_TOUCHSCREENGUI - if (parts.size() == 3) { + if (!g_settings->getBool("enable_touch") && parts.size() == 3) { if (parts[2] == "true") { lockSize(true,v2u32(800,600)); } } -#endif data->explicit_size = true; return; } @@ -755,7 +753,7 @@ void GUIFormSpecMenu::parseScrollBarOptions(parserData* data, const std::string data->scrollbar_options.thumb_size = value <= 0 ? 1 : value; continue; } else if (options[0] == "arrows") { - std::string value = trim(options[1]); + auto value = trim(options[1]); if (value == "hide") data->scrollbar_options.arrow_visiblity = GUIScrollBar::HIDE; else if (value == "show") @@ -2449,8 +2447,8 @@ bool GUIFormSpecMenu::parseSizeDirect(parserData* data, const std::string &eleme if (parts.size() < 2) return false; - std::string type = trim(parts[0]); - std::string description = trim(parts[1]); + auto type = trim(parts[0]); + std::string description(trim(parts[1])); if (type != "size" && type != "invsize") return false; @@ -2473,8 +2471,8 @@ bool GUIFormSpecMenu::parsePositionDirect(parserData *data, const std::string &e if (parts.size() != 2) return false; - std::string type = trim(parts[0]); - std::string description = trim(parts[1]); + auto type = trim(parts[0]); + std::string description(trim(parts[1])); if (type != "position") return false; @@ -2512,8 +2510,8 @@ bool GUIFormSpecMenu::parseAnchorDirect(parserData *data, const std::string &ele if (parts.size() != 2) return false; - std::string type = trim(parts[0]); - std::string description = trim(parts[1]); + auto type = trim(parts[0]); + std::string description(trim(parts[1])); if (type != "anchor") return false; @@ -2552,8 +2550,8 @@ bool GUIFormSpecMenu::parsePaddingDirect(parserData *data, const std::string &el if (parts.size() != 2) return false; - std::string type = trim(parts[0]); - std::string description = trim(parts[1]); + auto type = trim(parts[0]); + std::string description(trim(parts[1])); if (type != "padding") return false; @@ -2624,7 +2622,7 @@ bool GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element, b std::vector selectors = split(parts[0], ','); for (size_t sel = 0; sel < selectors.size(); sel++) { - std::string selector = trim(selectors[sel]); + std::string selector(trim(selectors[sel])); // Copy the style properties to a new StyleSpec // This allows a separate state mask per-selector @@ -3216,7 +3214,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) mydata.real_coordinates = m_formspec_version >= 2; for (; i < elements.size(); i++) { std::vector parts = split(elements[i], '['); - std::string name = trim(parts[0]); + auto name = trim(parts[0]); if (name != "real_coordinates" || parts.size() != 2) break; // Invalid format @@ -3284,14 +3282,15 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) 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 = min_screen_dim / 10 * gui_scaling; -#else - // Desktop computers have more space, so try to fit 15 coordinates. - double prefer_imgsize = min_screen_dim / 15 * gui_scaling; -#endif + double prefer_imgsize; + if (g_settings->getBool("enable_touch")) { + // The preferred imgsize should be larger to accommodate the + // smaller screensize. + prefer_imgsize = min_screen_dim / 10 * gui_scaling; + } else { + // Desktop computers have more space, so try to fit 15 coordinates. + prefer_imgsize = min_screen_dim / 15 * gui_scaling; + } // Try to use the preferred imgsize, but if that's bigger than the maximum // size, use the maximum size. use_imgsize = std::min(prefer_imgsize, diff --git a/src/gui/guiPasswordChange.cpp b/src/gui/guiPasswordChange.cpp index b1264ddfe..6b0c0063d 100644 --- a/src/gui/guiPasswordChange.cpp +++ b/src/gui/guiPasswordChange.cpp @@ -25,10 +25,6 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include -#ifdef HAVE_TOUCHSCREENGUI - #include "client/renderingengine.h" -#endif - #include "porting.h" #include "gettext.h" @@ -66,11 +62,8 @@ void GUIPasswordChange::regenerateGui(v2u32 screensize) /* Calculate new sizes and positions */ -#ifdef HAVE_TOUCHSCREENGUI - const float s = m_gui_scale * RenderingEngine::getDisplayDensity() / 2; -#else const float s = m_gui_scale; -#endif + DesiredRect = core::rect( screensize.X / 2 - 580 * s / 2, screensize.Y / 2 - 300 * s / 2, diff --git a/src/gui/guiTable.cpp b/src/gui/guiTable.cpp index d84107450..7d2cf66f3 100644 --- a/src/gui/guiTable.cpp +++ b/src/gui/guiTable.cpp @@ -210,7 +210,7 @@ void GUITable::setTable(const TableOptions &options, s32 colcount = columns.size(); assert(colcount >= 1); // rowcount = ceil(cellcount / colcount) but use integer arithmetic - s32 rowcount = (content.size() + colcount - 1) / colcount; + s32 rowcount = std::min(((u32)content.size() + colcount - 1) / colcount, (u32)S32_MAX); assert(rowcount >= 0); // Append empty strings to content if there is an incomplete row s32 cellcount = rowcount * colcount; diff --git a/src/gui/modalMenu.cpp b/src/gui/modalMenu.cpp index 11cb55e1c..ae2a89e96 100644 --- a/src/gui/modalMenu.cpp +++ b/src/gui/modalMenu.cpp @@ -27,10 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gettext.h" #include "porting.h" #include "settings.h" - -#ifdef HAVE_TOUCHSCREENGUI #include "touchscreengui.h" -#endif GUIModalMenu::GUIModalMenu(gui::IGUIEnvironment* env, gui::IGUIElement* parent, s32 id, IMenuManager *menumgr, bool remap_dbl_click) : @@ -44,11 +41,12 @@ GUIModalMenu::GUIModalMenu(gui::IGUIEnvironment* env, gui::IGUIElement* parent, { m_gui_scale = std::max(g_settings->getFloat("gui_scaling"), 0.5f); const float screen_dpi_scale = RenderingEngine::getDisplayDensity(); -#ifdef HAVE_TOUCHSCREENGUI - m_gui_scale *= 1.1f - 0.3f * screen_dpi_scale + 0.2f * screen_dpi_scale * screen_dpi_scale; -#else - m_gui_scale *= screen_dpi_scale; -#endif + + if (g_settings->getBool("enable_touch")) { + m_gui_scale *= 1.1f - 0.3f * screen_dpi_scale + 0.2f * screen_dpi_scale * screen_dpi_scale; + } else { + m_gui_scale *= screen_dpi_scale; + } setVisible(true); m_menumgr->createdMenu(this); @@ -102,10 +100,8 @@ void GUIModalMenu::quitMenu() Environment->removeFocus(this); m_menumgr->deletingMenu(this); this->remove(); -#ifdef HAVE_TOUCHSCREENGUI if (g_touchscreengui) g_touchscreengui->show(); -#endif } static bool isChild(gui::IGUIElement *tocheck, gui::IGUIElement *parent) diff --git a/src/gui/touchscreengui.cpp b/src/gui/touchscreengui.cpp index d8ac39c31..d6f22acd2 100644 --- a/src/gui/touchscreengui.cpp +++ b/src/gui/touchscreengui.cpp @@ -55,6 +55,7 @@ const std::string joystick_image_names[] = { static EKEY_CODE id_to_keycode(touch_gui_button_id id) { + EKEY_CODE code; // ESC isn't part of the keymap. if (id == exit_id) return KEY_ESCAPE; @@ -110,7 +111,15 @@ static EKEY_CODE id_to_keycode(touch_gui_button_id id) break; } assert(!key.empty()); - return keyname_to_keycode(g_settings->get("keymap_" + key).c_str()); + std::string resolved = g_settings->get("keymap_" + key); + try { + code = keyname_to_keycode(resolved.c_str()); + } catch (UnknownKeycode &e) { + code = KEY_UNKNOWN; + warningstream << "TouchScreenGUI: Unknown key '" << resolved + << "' for '" << key << "', hiding button." << std::endl; + } + return code; } static void load_button_texture(const button_info *btn, const std::string &path, @@ -523,13 +532,23 @@ void TouchScreenGUI::init(ISimpleTextureSource *tsrc) + 0.5f * button_size), AHBB_Dir_Right_Left, 3.0f); - m_settings_bar.addButton(fly_id, L"fly", "fly_btn.png"); - m_settings_bar.addButton(noclip_id, L"noclip", "noclip_btn.png"); - m_settings_bar.addButton(fast_id, L"fast", "fast_btn.png"); - m_settings_bar.addButton(debug_id, L"debug", "debug_btn.png"); - m_settings_bar.addButton(camera_id, L"camera", "camera_btn.png"); - m_settings_bar.addButton(range_id, L"rangeview", "rangeview_btn.png"); - m_settings_bar.addButton(minimap_id, L"minimap", "minimap_btn.png"); + const static std::map settings_bar_buttons { + {fly_id, "fly"}, + {noclip_id, "noclip"}, + {fast_id, "fast"}, + {debug_id, "debug"}, + {camera_id, "camera"}, + {range_id, "rangeview"}, + {minimap_id, "minimap"}, + }; + for (const auto &pair : settings_bar_buttons) { + if (id_to_keycode(pair.first) == KEY_UNKNOWN) + continue; + + std::wstring wide = utf8_to_wide(pair.second); + m_settings_bar.addButton(pair.first, wide.c_str(), + pair.second + "_btn.png"); + } // Chat is shown by default, so chat_hide_btn.png is shown first. m_settings_bar.addToggleButton(toggle_chat_id, L"togglechat", @@ -545,10 +564,20 @@ void TouchScreenGUI::init(ISimpleTextureSource *tsrc) + 0.5f * button_size), AHBB_Dir_Left_Right, 2.0f); - m_rare_controls_bar.addButton(chat_id, L"chat", "chat_btn.png"); - m_rare_controls_bar.addButton(inventory_id, L"inv", "inventory_btn.png"); - m_rare_controls_bar.addButton(drop_id, L"drop", "drop_btn.png"); - m_rare_controls_bar.addButton(exit_id, L"exit", "exit_btn.png"); + const static std::map rare_controls_bar_buttons { + {chat_id, "chat"}, + {inventory_id, "inventory"}, + {drop_id, "drop"}, + {exit_id, "exit"}, + }; + for (const auto &pair : rare_controls_bar_buttons) { + if (id_to_keycode(pair.first) == KEY_UNKNOWN) + continue; + + std::wstring wide = utf8_to_wide(pair.second); + m_rare_controls_bar.addButton(pair.first, wide.c_str(), + pair.second + "_btn.png"); + } m_initialized = true; } @@ -687,12 +716,9 @@ void TouchScreenGUI::handleReleaseEvent(size_t evt_id) << evt_id << std::endl; } - for (auto iter = m_known_ids.begin(); iter != m_known_ids.end(); ++iter) { - if (iter->id == evt_id) { - m_known_ids.erase(iter); - break; - } - } + // By the way: Android reuses pointer IDs, so m_pointer_pos[evt_id] + // will be overwritten soon anyway. + m_pointer_pos.erase(evt_id); } void TouchScreenGUI::translateEvent(const SEvent &event) @@ -719,17 +745,6 @@ void TouchScreenGUI::translateEvent(const SEvent &event) const v2s32 dir_fixed = touch_pos - fixed_joystick_center; if (event.TouchInput.Event == ETIE_PRESSED_DOWN) { - /* - * Add to own copy of event list... - * android would provide this information but Irrlicht guys don't - * wanna design an efficient interface - */ - id_status to_be_added{}; - to_be_added.id = event.TouchInput.ID; - to_be_added.X = event.TouchInput.X; - to_be_added.Y = event.TouchInput.Y; - m_known_ids.push_back(to_be_added); - size_t eventID = event.TouchInput.ID; touch_gui_button_id button = getButtonID(X, Y); @@ -786,6 +801,7 @@ void TouchScreenGUI::translateEvent(const SEvent &event) m_move_id = event.TouchInput.ID; m_move_has_really_moved = false; m_move_downtime = porting::getTimeMs(); + m_move_pos = touch_pos; // DON'T reset m_tap_state here, otherwise many short taps // will be ignored if you tap very fast. } @@ -804,8 +820,7 @@ void TouchScreenGUI::translateEvent(const SEvent &event) m_pointer_pos[event.TouchInput.ID] == touch_pos) return; - const v2s32 free_joystick_center = v2s32(m_pointer_pos[event.TouchInput.ID].X, - m_pointer_pos[event.TouchInput.ID].Y); + const v2s32 free_joystick_center = m_pointer_pos[event.TouchInput.ID]; const v2s32 dir_free = touch_pos - free_joystick_center; const double touch_threshold_sq = m_touchscreen_threshold * m_touchscreen_threshold; @@ -814,6 +829,7 @@ void TouchScreenGUI::translateEvent(const SEvent &event) if (dir_free.getLengthSQ() > touch_threshold_sq || m_move_has_really_moved) { m_move_has_really_moved = true; + m_move_pos = touch_pos; m_pointer_pos[event.TouchInput.ID] = touch_pos; if (m_tap_state == TapState::None || m_draw_crosshair) { @@ -994,7 +1010,9 @@ void TouchScreenGUI::step(float dtime) // thus the camera position can change, it doesn't suffice to update the // shootline when a touch event occurs. // Note that the shootline isn't used if touch_use_crosshair is enabled. - if (!m_draw_crosshair) { + // Only updating when m_has_move_id means that the shootline will stay at + // it's last in-world position when the player doesn't need it. + if (!m_draw_crosshair && m_has_move_id) { v2s32 pointer_pos = getPointerPos(); m_shootline = m_device ->getSceneManager() @@ -1032,8 +1050,8 @@ void TouchScreenGUI::setVisible(bool visible) // clear all active buttons if (!visible) { - while (!m_known_ids.empty()) - handleReleaseEvent(m_known_ids.begin()->id); + while (!m_pointer_pos.empty()) + handleReleaseEvent(m_pointer_pos.begin()->first); m_settings_bar.hide(); m_rare_controls_bar.hide(); @@ -1063,7 +1081,9 @@ v2s32 TouchScreenGUI::getPointerPos() { if (m_draw_crosshair) return v2s32(m_screensize.X / 2, m_screensize.Y / 2); - return m_pointer_pos[m_move_id]; + // We can't just use m_pointer_pos[m_move_id] because applyContextControls + // may emit release events after m_pointer_pos[m_move_id] is erased. + return m_move_pos; } void TouchScreenGUI::emitMouseEvent(EMOUSE_INPUT_EVENT type) diff --git a/src/gui/touchscreengui.h b/src/gui/touchscreengui.h index 661f70f10..104c75eba 100644 --- a/src/gui/touchscreengui.h +++ b/src/gui/touchscreengui.h @@ -246,6 +246,8 @@ private: size_t m_move_id; bool m_move_has_really_moved = false; u64 m_move_downtime = 0; + // m_move_pos stays valid even after m_move_id has been released. + v2s32 m_move_pos; bool m_has_joystick_id = false; size_t m_joystick_id; @@ -281,16 +283,6 @@ private: const rect &button_rect, int texture_id, bool visible = true); - struct id_status - { - size_t id; - int X; - int Y; - }; - - // vector to store known ids and their initial touch positions - std::vector m_known_ids; - // handle a button event void handleButtonEvent(touch_gui_button_id bID, size_t eventID, bool action); @@ -303,7 +295,7 @@ private: // apply joystick status void applyJoystickStatus(); - // array for saving last known position of a pointer + // map to store the IDs and positions of currently pressed pointers std::unordered_map m_pointer_pos; // settings bar diff --git a/src/inventory.h b/src/inventory.h index 64ca857fc..e92893a8e 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -131,6 +131,15 @@ struct ItemStack return metadata.getToolCapabilities(*item_cap); // Check for override } + const std::optional &getWearBarParams( + const IItemDefManager *itemdef) const + { + auto &meta_override = metadata.getWearBarParamOverride(); + if (meta_override.has_value()) + return meta_override; + return itemdef->get(name).wear_bar_params; + } + // Wear out (only tools) // Returns true if the item is (was) a tool bool addWear(s32 amount, const IItemDefManager *itemdef) diff --git a/src/irrlicht_changes/CGUITTFont.cpp b/src/irrlicht_changes/CGUITTFont.cpp index 7e2ea8186..9274ee69f 100644 --- a/src/irrlicht_changes/CGUITTFont.cpp +++ b/src/irrlicht_changes/CGUITTFont.cpp @@ -240,37 +240,6 @@ CGUITTFont* CGUITTFont::createTTFont(IGUIEnvironment *env, const io::path& filen return font; } -CGUITTFont* CGUITTFont::createTTFont(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias, const bool transparency) -{ - if (!c_libraryLoaded) - { - if (FT_Init_FreeType(&c_library)) - return 0; - c_libraryLoaded = true; - } - - CGUITTFont* font = new CGUITTFont(device->getGUIEnvironment()); - font->Device = device; - bool ret = font->load(filename, size, antialias, transparency); - if (!ret) - { - font->drop(); - return 0; - } - - return font; -} - -CGUITTFont* CGUITTFont::create(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias, const bool transparency) -{ - return CGUITTFont::createTTFont(env, filename, size, antialias, transparency); -} - -CGUITTFont* CGUITTFont::create(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias, const bool transparency) -{ - return CGUITTFont::createTTFont(device, filename, size, antialias, transparency); -} - ////////////////////// //! Constructor. @@ -304,6 +273,7 @@ bool CGUITTFont::load(const io::path& filename, const u32 size, const bool antia io::IFileSystem* filesystem = Environment->getFileSystem(); irr::ILogger* logger = (Device != 0 ? Device->getLogger() : 0); + // FIXME: this is always null ^ this->size = size; this->filename = filename; @@ -314,7 +284,7 @@ bool CGUITTFont::load(const io::path& filename, const u32 size, const bool antia // Log. if (logger) - logger->log(L"CGUITTFont", core::stringw(core::stringw(L"Creating new font: ") + core::stringw(filename) + L" " + core::stringc(size) + L"pt " + (antialias ? L"+antialias " : L"-antialias ") + (transparency ? L"+transparency" : L"-transparency")).c_str(), irr::ELL_INFORMATION); + logger->log("CGUITTFont", (core::stringc(L"Creating new font: ") + filename + " " + core::stringc(size) + "pt " + (antialias ? "+antialias " : "-antialias ") + (transparency ? "+transparency" : "-transparency")).c_str(), irr::ELL_INFORMATION); // Grab the face. SGUITTFace* face = 0; @@ -330,7 +300,7 @@ bool CGUITTFont::load(const io::path& filename, const u32 size, const bool antia io::IReadFile* file = filesystem->createAndOpenFile(filename); if (file == 0) { - if (logger) logger->log(L"CGUITTFont", L"Failed to open the file.", irr::ELL_INFORMATION); + if (logger) logger->log("CGUITTFont", "Failed to open the file.", irr::ELL_INFORMATION); c_faces.erase(filename); delete face; @@ -345,7 +315,7 @@ bool CGUITTFont::load(const io::path& filename, const u32 size, const bool antia // Create the face. if (FT_New_Memory_Face(c_library, face->face_buffer, face->face_buffer_size, 0, &face->face)) { - if (logger) logger->log(L"CGUITTFont", L"FT_New_Memory_Face failed.", irr::ELL_INFORMATION); + if (logger) logger->log("CGUITTFont", "FT_New_Memory_Face failed.", irr::ELL_INFORMATION); c_faces.erase(filename); delete face; @@ -357,7 +327,7 @@ bool CGUITTFont::load(const io::path& filename, const u32 size, const bool antia { if (FT_New_Face(c_library, reinterpret_cast(filename.c_str()), 0, &face->face)) { - if (logger) logger->log(L"CGUITTFont", L"FT_New_Face failed.", irr::ELL_INFORMATION); + if (logger) logger->log("CGUITTFont", "FT_New_Face failed.", irr::ELL_INFORMATION); c_faces.erase(filename); delete face; diff --git a/src/irrlicht_changes/CGUITTFont.h b/src/irrlicht_changes/CGUITTFont.h index 6fa45678a..b67d518ae 100644 --- a/src/irrlicht_changes/CGUITTFont.h +++ b/src/irrlicht_changes/CGUITTFont.h @@ -229,9 +229,6 @@ namespace gui //! \param transparency set the use_transparency flag //! \return Returns a pointer to a CGUITTFont. Will return 0 if the font failed to load. static CGUITTFont* createTTFont(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias = true, const bool transparency = true, const u32 shadow = 0, const u32 shadow_alpha = 255); - static CGUITTFont* createTTFont(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias = true, const bool transparency = true); - static CGUITTFont* create(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias = true, const bool transparency = true); - static CGUITTFont* create(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias = true, const bool transparency = true); //! Destructor virtual ~CGUITTFont(); diff --git a/src/irrlichttypes.h b/src/irrlichttypes.h index 343eba1dc..90f522017 100644 --- a/src/irrlichttypes.h +++ b/src/irrlichttypes.h @@ -29,33 +29,17 @@ with this program; if not, write to the Free Software Foundation, Inc., using namespace irr; -namespace irr { +#define S8_MIN INT8_MIN +#define S16_MIN INT16_MIN +#define S32_MIN INT32_MIN +#define S64_MIN INT64_MIN -// Define missing constant for vector math with 16-bit numbers -namespace core { - template - inline T roundingError(); +#define S8_MAX INT8_MAX +#define S16_MAX INT16_MAX +#define S32_MAX INT32_MAX +#define S64_MAX INT64_MAX - template <> - inline s16 roundingError() - { - return 0; - } -} - -} - -#define S8_MIN (-0x7F - 1) -#define S16_MIN (-0x7FFF - 1) -#define S32_MIN (-0x7FFFFFFF - 1) -#define S64_MIN (-0x7FFFFFFFFFFFFFFF - 1) - -#define S8_MAX 0x7F -#define S16_MAX 0x7FFF -#define S32_MAX 0x7FFFFFFF -#define S64_MAX 0x7FFFFFFFFFFFFFFF - -#define U8_MAX 0xFF -#define U16_MAX 0xFFFF -#define U32_MAX 0xFFFFFFFF -#define U64_MAX 0xFFFFFFFFFFFFFFFF +#define U8_MAX UINT8_MAX +#define U16_MAX UINT16_MAX +#define U32_MAX UINT32_MAX +#define U64_MAX UINT64_MAX diff --git a/src/itemdef.cpp b/src/itemdef.cpp index ee3e3829d..5c63270c0 100644 --- a/src/itemdef.cpp +++ b/src/itemdef.cpp @@ -125,6 +125,7 @@ ItemDefinition& ItemDefinition::operator=(const ItemDefinition &def) pointabilities = def.pointabilities; if (def.tool_capabilities) tool_capabilities = new ToolCapabilities(*def.tool_capabilities); + wear_bar_params = def.wear_bar_params; groups = def.groups; node_placement_prediction = def.node_placement_prediction; place_param2 = def.place_param2; @@ -149,6 +150,7 @@ void ItemDefinition::resetInitial() { // Initialize pointers to NULL so reset() does not delete undefined pointers tool_capabilities = NULL; + wear_bar_params = std::nullopt; reset(); } @@ -171,6 +173,7 @@ void ItemDefinition::reset() pointabilities = std::nullopt; delete tool_capabilities; tool_capabilities = NULL; + wear_bar_params.reset(); groups.clear(); sound_place = SoundSpec(); sound_place_failed = SoundSpec(); @@ -251,6 +254,13 @@ void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const pointabilities_s = tmp_os.str(); } os << serializeString16(pointabilities_s); + + if (wear_bar_params.has_value()) { + writeU8(os, 1); + wear_bar_params->serialize(os); + } else { + writeU8(os, 0); + } } void ItemDefinition::deSerialize(std::istream &is, u16 protocol_version) @@ -333,6 +343,10 @@ void ItemDefinition::deSerialize(std::istream &is, u16 protocol_version) pointabilities = std::make_optional(); pointabilities->deSerialize(tmp_is); } + + if (readU8(is)) { + wear_bar_params = WearBarParams::deserialize(is); + } } catch(SerializationError &e) {}; } diff --git a/src/itemdef.h b/src/itemdef.h index 192e90095..f8cb1b613 100644 --- a/src/itemdef.h +++ b/src/itemdef.h @@ -28,6 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "itemgroup.h" #include "sound.h" #include "texture_override.h" // TextureOverride +#include "tool.h" #include "util/pointabilities.h" class IGameDef; class Client; @@ -103,6 +104,8 @@ struct ItemDefinition // They may be NULL. If non-NULL, deleted by destructor ToolCapabilities *tool_capabilities; + std::optional wear_bar_params; + ItemGroupList groups; SoundSpec sound_place; SoundSpec sound_place_failed; diff --git a/src/itemstackmetadata.cpp b/src/itemstackmetadata.cpp index c8ce2449f..be1715e1a 100644 --- a/src/itemstackmetadata.cpp +++ b/src/itemstackmetadata.cpp @@ -21,7 +21,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "itemstackmetadata.h" #include "util/serialize.h" #include "util/strfnd.h" + #include +#include #define DESERIALIZE_START '\x01' #define DESERIALIZE_KV_DELIM '\x02' @@ -31,11 +33,13 @@ with this program; if not, write to the Free Software Foundation, Inc., #define DESERIALIZE_PAIR_DELIM_STR "\x03" #define TOOLCAP_KEY "tool_capabilities" +#define WEAR_BAR_KEY "wear_color" void ItemStackMetadata::clear() { SimpleMetadata::clear(); updateToolCapabilities(); + updateWearBarParams(); } static void sanitize_string(std::string &str) @@ -45,16 +49,18 @@ static void sanitize_string(std::string &str) str.erase(std::remove(str.begin(), str.end(), DESERIALIZE_PAIR_DELIM), str.end()); } -bool ItemStackMetadata::setString(const std::string &name, const std::string &var) +bool ItemStackMetadata::setString(const std::string &name, std::string_view var) { std::string clean_name = name; - std::string clean_var = var; + std::string clean_var(var); sanitize_string(clean_name); sanitize_string(clean_var); bool result = SimpleMetadata::setString(clean_name, clean_var); if (clean_name == TOOLCAP_KEY) updateToolCapabilities(); + else if (clean_name == WEAR_BAR_KEY) + updateWearBarParams(); return result; } @@ -91,6 +97,7 @@ void ItemStackMetadata::deSerialize(std::istream &is) } } updateToolCapabilities(); + updateWearBarParams(); } void ItemStackMetadata::updateToolCapabilities() @@ -116,3 +123,25 @@ void ItemStackMetadata::clearToolCapabilities() { setString(TOOLCAP_KEY, ""); } + +void ItemStackMetadata::updateWearBarParams() +{ + if (contains(WEAR_BAR_KEY)) { + std::istringstream is(getString(WEAR_BAR_KEY)); + wear_bar_override = WearBarParams::deserializeJson(is); + } else { + wear_bar_override.reset(); + } +} + +void ItemStackMetadata::setWearBarParams(const WearBarParams ¶ms) +{ + std::ostringstream os; + params.serializeJson(os); + setString(WEAR_BAR_KEY, os.str()); +} + +void ItemStackMetadata::clearWearBarParams() +{ + setString(WEAR_BAR_KEY, ""); +} diff --git a/src/itemstackmetadata.h b/src/itemstackmetadata.h index 48a029c51..db450dc4a 100644 --- a/src/itemstackmetadata.h +++ b/src/itemstackmetadata.h @@ -22,17 +22,21 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "metadata.h" #include "tool.h" +#include + class Inventory; class IItemDefManager; class ItemStackMetadata : public SimpleMetadata { public: - ItemStackMetadata() : toolcaps_overridden(false) {} + ItemStackMetadata(): + toolcaps_overridden(false) + {} // Overrides void clear() override; - bool setString(const std::string &name, const std::string &var) override; + bool setString(const std::string &name, std::string_view var) override; void serialize(std::ostream &os) const; void deSerialize(std::istream &is); @@ -46,9 +50,20 @@ public: void setToolCapabilities(const ToolCapabilities &caps); void clearToolCapabilities(); + const std::optional &getWearBarParamOverride() const + { + return wear_bar_override; + } + + + void setWearBarParams(const WearBarParams ¶ms); + void clearWearBarParams(); + private: void updateToolCapabilities(); + void updateWearBarParams(); bool toolcaps_overridden; ToolCapabilities toolcaps_override; + std::optional wear_bar_override; }; diff --git a/src/main.cpp b/src/main.cpp index d5d7269f1..e7ec6dd53 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -50,9 +50,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gui/guiEngine.h" #include "gui/mainmenumanager.h" #endif -#ifdef HAVE_TOUCHSCREENGUI - #include "gui/touchscreengui.h" -#endif // for version information only extern "C" { @@ -67,12 +64,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 ================================== +#if defined(__MINGW32__) && !defined(__clang__) +// see https://github.com/minetest/minetest/issues/14140 or +// https://github.com/minetest/minetest/issues/10137 for one of the various issues we had +#error ================================== +#error MinGW gcc has a broken TLS implementation and is not supported for building \ + Minetest. Look at testTLS() in test_threading.cpp and see for yourself. \ + Please use a clang-based compiler or alternatively MSVC. +#error ================================== #endif #define DEBUGFILE "debug.txt" @@ -435,6 +434,13 @@ static void print_version(std::ostream &os) os << "Using " << LUAJIT_VERSION << std::endl; #else os << "Using " << LUA_RELEASE << std::endl; +#endif +#if defined(__clang__) + os << "Built by Clang " << __clang_major__ << "." << __clang_minor__ << std::endl; +#elif defined(__GNUC__) + os << "Built by GCC " << __GNUC__ << "." << __GNUC_MINOR__ << std::endl; +#elif defined(_MSC_VER) + os << "Built by MSVC " << (_MSC_VER / 100) << "." << (_MSC_VER % 100) << std::endl; #endif os << "Running on " << porting::get_sysinfo() << std::endl; os << g_build_info << std::endl; diff --git a/src/map.cpp b/src/map.cpp index d0b3e046d..9d3cc8376 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -228,12 +228,12 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n, std::vector > oldnodes; oldnodes.emplace_back(p, oldnode); voxalgo::update_lighting_nodes(this, oldnodes, modified_blocks); - - for (auto &modified_block : modified_blocks) { - modified_block.second->expireDayNightDiff(); - } } + if (n.getContent() != oldnode.getContent() && + (oldnode.getContent() == CONTENT_AIR || n.getContent() == CONTENT_AIR)) + block->expireIsAirCache(); + // Report for rollback if(m_gamedef->rollback()) { @@ -1462,14 +1462,14 @@ void ServerMap::finishBlockMake(BlockMakeData *data, if (!block) continue; /* - Update day/night difference cache of the MapBlocks + Update is air cache of the MapBlocks */ - block->expireDayNightDiff(); + block->expireIsAirCache(); /* Set block as modified */ block->raiseModified(MOD_STATE_WRITE_NEEDED, - MOD_REASON_EXPIRE_DAYNIGHTDIFF); + MOD_REASON_EXPIRE_IS_AIR); } /* @@ -1506,12 +1506,11 @@ MapSector *ServerMap::createSector(v2s16 p2d) Do not create over max mapgen limit */ if (blockpos_over_max_limit(v3s16(p2d.X, 0, p2d.Y))) - throw InvalidPositionException("createSector(): pos. over max mapgen limit"); + throw InvalidPositionException("createSector(): pos over max mapgen limit"); /* Generate blank sector */ - sector = new MapSector(this, p2d, m_gamedef); /* @@ -1524,20 +1523,11 @@ MapSector *ServerMap::createSector(v2s16 p2d) MapBlock * ServerMap::createBlock(v3s16 p) { - /* - Do not create over max mapgen limit - */ - if (blockpos_over_max_limit(p)) - throw InvalidPositionException("createBlock(): pos. over max mapgen limit"); - v2s16 p2d(p.X, p.Z); s16 block_y = p.Y; + /* This will create or load a sector if not found in memory. - If block exists on disk, it will be loaded. - - NOTE: On old save formats, this will be slow, as it generates - lighting on blocks for them. */ MapSector *sector; try { @@ -1552,11 +1542,16 @@ MapBlock * ServerMap::createBlock(v3s16 p) */ MapBlock *block = sector->getBlockNoCreateNoEx(block_y); - if (block) { + if (block) return block; - } + // Create blank - block = sector->createBlankBlock(block_y); + try { + block = sector->createBlankBlock(block_y); + } catch (InvalidPositionException &e) { + infostream << "createBlock: createBlankBlock() failed" << std::endl; + throw e; + } return block; } @@ -1576,10 +1571,10 @@ MapBlock * ServerMap::emergeBlock(v3s16 p, bool create_blank) } if (create_blank) { - MapSector *sector = createSector(v2s16(p.X, p.Z)); - MapBlock *block = sector->createBlankBlock(p.Y); - - return block; + try { + MapSector *sector = createSector(v2s16(p.X, p.Z)); + return sector->createBlankBlock(p.Y); + } catch (InvalidPositionException &e) {} } return NULL; @@ -1803,6 +1798,7 @@ bool ServerMap::saveBlock(MapBlock *block, MapDatabase *db, int compression_leve o.write((char*) &version, 1); block->serialize(o, version, true, compression_level); + // FIXME: zero copy possible in c++20 or with custom rdbuf bool ret = db->saveBlock(p3d, o.str()); if (ret) { // We just wrote it to the disk so clear modified flag @@ -2000,9 +1996,7 @@ void MMVManip::initialEmerge(v3s16 blockpos_min, v3s16 blockpos_max, u8 flags = 0; MapBlock *block; v3s16 p(x,y,z); - std::map::iterator n; - n = m_loaded_blocks.find(p); - if(n != m_loaded_blocks.end()) + if (m_loaded_blocks.count(p) > 0) continue; bool block_data_inexistent = false; @@ -2020,10 +2014,7 @@ void MMVManip::initialEmerge(v3s16 blockpos_min, v3s16 blockpos_max, { if (load_if_inexistent && !blockpos_over_max_limit(p)) { - ServerMap *svrmap = (ServerMap *)m_map; - block = svrmap->emergeBlock(p, false); - if (block == NULL) - block = svrmap->createBlock(p); + block = m_map->emergeBlock(p, true); block->copyTo(*this); } else { flags |= VMANIP_BLOCK_DATA_INEXIST; @@ -2073,6 +2064,7 @@ void MMVManip::blitBackAll(std::map *modified_blocks, block->copyFrom(*this); block->raiseModified(MOD_STATE_WRITE_NEEDED, MOD_REASON_VMANIP); + block->expireIsAirCache(); if(modified_blocks) (*modified_blocks)[p] = block; diff --git a/src/mapblock.cpp b/src/mapblock.cpp index c48d3defe..36210cad1 100644 --- a/src/mapblock.cpp +++ b/src/mapblock.cpp @@ -186,57 +186,27 @@ void MapBlock::copyFrom(VoxelManipulator &dst) getPosRelative(), data_size); } -void MapBlock::actuallyUpdateDayNightDiff() +void MapBlock::actuallyUpdateIsAir() { - const NodeDefManager *nodemgr = m_gamedef->ndef(); + // Running this function un-expires m_is_air + m_is_air_expired = false; - // Running this function un-expires m_day_night_differs - m_day_night_differs_expired = false; - - bool differs = false; - - /* - Check if any lighting value differs - */ - - MapNode previous_n(CONTENT_IGNORE); + bool only_air = true; for (u32 i = 0; i < nodecount; i++) { - MapNode n = data[i]; - - // If node is identical to previous node, don't verify if it differs - if (n == previous_n) - continue; - - differs = !n.isLightDayNightEq(nodemgr->getLightingFlags(n)); - if (differs) + MapNode &n = data[i]; + if (n.getContent() != CONTENT_AIR) { + only_air = false; break; - previous_n = n; - } - - /* - If some lighting values differ, check if the whole thing is - just air. If it is just air, differs = false - */ - if (differs) { - bool only_air = true; - for (u32 i = 0; i < nodecount; i++) { - MapNode &n = data[i]; - if (n.getContent() != CONTENT_AIR) { - only_air = false; - break; - } } - if (only_air) - differs = false; } // Set member variable - m_day_night_differs = differs; + m_is_air = only_air; } -void MapBlock::expireDayNightDiff() +void MapBlock::expireIsAirCache() { - m_day_night_differs_expired = true; + m_is_air_expired = true; } /* @@ -369,7 +339,12 @@ void MapBlock::serialize(std::ostream &os_compressed, u8 version, bool disk, int u8 flags = 0; if(is_underground) flags |= 0x01; - if(getDayNightDiff()) + // This flag used to be day-night-differs, and it is no longer used. + // We write it anyway so that old servers can still use this. + // Above ground isAir implies !day-night-differs, !isAir is good enough for old servers + // to check whether above ground blocks should be sent. + // See RemoteClient::getNextBlocks(...) + if(!isAir()) flags |= 0x02; if (!m_generated) flags |= 0x08; @@ -382,7 +357,7 @@ void MapBlock::serialize(std::ostream &os_compressed, u8 version, bool disk, int Bulk node data */ NameIdMapping nimap; - SharedBuffer buf; + Buffer buf; const u8 content_width = 2; const u8 params_width = 2; if(disk) @@ -473,7 +448,7 @@ void MapBlock::deSerialize(std::istream &in_compressed, u8 version, bool disk) TRACESTREAM(<<"MapBlock::deSerialize "<=25)"< databuf_nodelist(nodecount * ser_length); + Buffer databuf_nodelist(nodecount * ser_length); // These have no compression if (version <= 3 || version == 5 || version == 6) { @@ -713,7 +694,6 @@ void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk) u8 flags; is.read((char*)&flags, 1); is_underground = (flags & 0x01) != 0; - m_day_night_differs = (flags & 0x02) != 0; if (version >= 18) m_generated = (flags & 0x08) == 0; @@ -798,9 +778,13 @@ void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk) // If supported, read node definition id mapping if (version >= 21) { nimap.deSerialize(is); + u16 dummy; + m_is_air = nimap.size() == 1 && nimap.getId("air", dummy); // Else set the legacy mapping } else { content_mapnode_get_name_id_mapping(&nimap); + m_is_air = false; + m_is_air_expired = true; } correctBlockNodeIds(&nimap, data, m_gamedef); } diff --git a/src/mapblock.h b/src/mapblock.h index c88818498..a32f9b312 100644 --- a/src/mapblock.h +++ b/src/mapblock.h @@ -60,7 +60,7 @@ enum ModReason : u32 { MOD_REASON_STATIC_DATA_ADDED = 1 << 13, MOD_REASON_STATIC_DATA_REMOVED = 1 << 14, MOD_REASON_STATIC_DATA_CHANGED = 1 << 15, - MOD_REASON_EXPIRE_DAYNIGHTDIFF = 1 << 16, + MOD_REASON_EXPIRE_IS_AIR = 1 << 16, MOD_REASON_VMANIP = 1 << 17, MOD_REASON_UNKNOWN = 1 << 18, }; @@ -310,20 +310,19 @@ public: // Copies data from VoxelManipulator getPosRelative() void copyFrom(VoxelManipulator &dst); - // Update day-night lighting difference flag. - // Sets m_day_night_differs to appropriate value. - // These methods don't care about neighboring blocks. - void actuallyUpdateDayNightDiff(); + // Update is air flag. + // Sets m_is_air to appropriate value. + void actuallyUpdateIsAir(); // Call this to schedule what the previous function does to be done // when the value is actually needed. - void expireDayNightDiff(); + void expireIsAirCache(); - inline bool getDayNightDiff() + inline bool isAir() { - if (m_day_night_differs_expired) - actuallyUpdateDayNightDiff(); - return m_day_night_differs; + if (m_is_air_expired) + actuallyUpdateIsAir(); + return m_is_air; } bool onObjectsActivation(); @@ -517,8 +516,8 @@ public: private: // Whether day and night lighting differs - bool m_day_night_differs = false; - bool m_day_night_differs_expired = true; + bool m_is_air = false; + bool m_is_air_expired = true; /* - On the server, this is used for telling whether the diff --git a/src/mapgen/mapgen.cpp b/src/mapgen/mapgen.cpp index a4557037e..80ffebc9e 100644 --- a/src/mapgen/mapgen.cpp +++ b/src/mapgen/mapgen.cpp @@ -70,6 +70,7 @@ FlagDesc flagdesc_gennotify[] = { {"large_cave_begin", 1 << GENNOTIFY_LARGECAVE_BEGIN}, {"large_cave_end", 1 << GENNOTIFY_LARGECAVE_END}, {"decoration", 1 << GENNOTIFY_DECORATION}, + {"custom", 1 << GENNOTIFY_CUSTOM}, {NULL, 0} }; @@ -108,7 +109,7 @@ static_assert( //// Mapgen::Mapgen(int mapgenid, MapgenParams *params, EmergeParams *emerge) : - gennotify(emerge->gen_notify_on, emerge->gen_notify_on_deco_ids) + gennotify(emerge->createNotifier()) { id = mapgenid; water_level = params->water_level; @@ -980,39 +981,67 @@ void MapgenBasic::generateDungeons(s16 max_stone_y) //// GenerateNotifier::GenerateNotifier(u32 notify_on, - const std::set *notify_on_deco_ids) + const std::set *notify_on_deco_ids, + const std::set *notify_on_custom) { m_notify_on = notify_on; m_notify_on_deco_ids = notify_on_deco_ids; + m_notify_on_custom = notify_on_custom; } -bool GenerateNotifier::addEvent(GenNotifyType type, v3s16 pos, u32 id) +bool GenerateNotifier::addEvent(GenNotifyType type, v3s16 pos) { - if (!(m_notify_on & (1 << type))) - return false; - - if (type == GENNOTIFY_DECORATION && - m_notify_on_deco_ids->find(id) == m_notify_on_deco_ids->cend()) + assert(type != GENNOTIFY_DECORATION && type != GENNOTIFY_CUSTOM); + if (!shouldNotifyOn(type)) return false; GenNotifyEvent gne; gne.type = type; gne.pos = pos; - gne.id = id; - m_notify_events.push_back(gne); + m_notify_events.emplace_back(std::move(gne)); + return true; +} + +bool GenerateNotifier::addDecorationEvent(v3s16 pos, u32 id) +{ + if (!shouldNotifyOn(GENNOTIFY_DECORATION)) + return false; + // check if data relating to this decoration was requested + assert(m_notify_on_deco_ids); + if (m_notify_on_deco_ids->find(id) == m_notify_on_deco_ids->cend()) + return false; + + GenNotifyEvent gne; + gne.type = GENNOTIFY_DECORATION; + gne.pos = pos; + gne.id = id; + m_notify_events.emplace_back(std::move(gne)); + return true; +} + + +bool GenerateNotifier::setCustom(const std::string &key, const std::string &value) +{ + if (!shouldNotifyOn(GENNOTIFY_CUSTOM)) + return false; + // check if this key was requested to be saved + assert(m_notify_on_custom); + if (m_notify_on_custom->count(key) == 0) + return false; + + m_notify_custom[key] = value; return true; } void GenerateNotifier::getEvents( - std::map > &event_map) + std::map> &event_map) const { - std::list::iterator it; + for (auto &gn : m_notify_events) { + assert(gn.type != GENNOTIFY_CUSTOM); // never stored in this list - for (it = m_notify_events.begin(); it != m_notify_events.end(); ++it) { - GenNotifyEvent &gn = *it; std::string name = (gn.type == GENNOTIFY_DECORATION) ? "decoration#"+ itos(gn.id) : flagdesc_gennotify[gn.type].name; @@ -1025,6 +1054,7 @@ void GenerateNotifier::getEvents( void GenerateNotifier::clearEvents() { m_notify_events.clear(); + m_notify_custom.clear(); } diff --git a/src/mapgen/mapgen.h b/src/mapgen/mapgen.h index b1d1a1bf0..d2d21a9fc 100644 --- a/src/mapgen/mapgen.h +++ b/src/mapgen/mapgen.h @@ -76,29 +76,41 @@ enum GenNotifyType { GENNOTIFY_LARGECAVE_BEGIN, GENNOTIFY_LARGECAVE_END, GENNOTIFY_DECORATION, + GENNOTIFY_CUSTOM, // user-defined data NUM_GENNOTIFY_TYPES }; -struct GenNotifyEvent { - GenNotifyType type; - v3s16 pos; - u32 id; -}; - class GenerateNotifier { public: + struct GenNotifyEvent { + GenNotifyType type; + v3s16 pos; + u32 id; // for GENNOTIFY_DECORATION + }; + // Use only for temporary Mapgen objects with no map generation! GenerateNotifier() = default; - GenerateNotifier(u32 notify_on, const std::set *notify_on_deco_ids); + // normal constructor + GenerateNotifier(u32 notify_on, const std::set *notify_on_deco_ids, + const std::set *notify_on_custom); - bool addEvent(GenNotifyType type, v3s16 pos, u32 id=0); - void getEvents(std::map > &event_map); + bool addEvent(GenNotifyType type, v3s16 pos); + bool addDecorationEvent(v3s16 pos, u32 deco_id); + bool setCustom(const std::string &key, const std::string &value); + void getEvents(std::map> &map) const; + const StringMap &getCustomData() const { return m_notify_custom; } void clearEvents(); private: u32 m_notify_on = 0; const std::set *m_notify_on_deco_ids = nullptr; + const std::set *m_notify_on_custom = nullptr; std::list m_notify_events; + StringMap m_notify_custom; + + inline bool shouldNotifyOn(GenNotifyType type) const { + return m_notify_on & (1 << type); + } }; // Order must match the order of 'static MapgenDesc g_reg_mapgens[]' in mapgen.cpp diff --git a/src/mapgen/mapgen_v6.cpp b/src/mapgen/mapgen_v6.cpp index 4db8b25ef..80a3d3be3 100644 --- a/src/mapgen/mapgen_v6.cpp +++ b/src/mapgen/mapgen_v6.cpp @@ -47,6 +47,7 @@ FlagDesc flagdesc_mapgen_v6[] = { {"snowbiomes", MGV6_SNOWBIOMES}, {"flat", MGV6_FLAT}, {"trees", MGV6_TREES}, + {"temples", MGV6_TEMPLES}, {NULL, 0} }; @@ -225,7 +226,8 @@ void MapgenV6Params::writeParams(Settings *settings) const void MapgenV6Params::setDefaultSettings(Settings *settings) { settings->setDefault("mgv6_spflags", flagdesc_mapgen_v6, MGV6_JUNGLES | - MGV6_SNOWBIOMES | MGV6_TREES | MGV6_BIOMEBLEND | MGV6_MUDFLOW); + MGV6_SNOWBIOMES | MGV6_TREES | MGV6_BIOMEBLEND | MGV6_MUDFLOW | + MGV6_TEMPLES); } @@ -578,7 +580,8 @@ void MapgenV6::makeChunk(BlockMakeData *data) dp.np_alt_wall = NoiseParams(-0.4, 1.0, v3f(40.0, 40.0, 40.0), 32474, 6, 1.1, 2.0); - if (getBiome(0, v2s16(node_min.X, node_min.Z)) == BT_DESERT) { + if ((spflags & MGV6_TEMPLES) && + getBiome(0, v2s16(node_min.X, node_min.Z)) == BT_DESERT) { dp.c_wall = c_desert_stone; dp.c_alt_wall = CONTENT_IGNORE; dp.c_stair = c_stair_desert_stone; diff --git a/src/mapgen/mapgen_v6.h b/src/mapgen/mapgen_v6.h index 5122bf365..4b439810c 100644 --- a/src/mapgen/mapgen_v6.h +++ b/src/mapgen/mapgen_v6.h @@ -39,6 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #define MGV6_SNOWBIOMES 0x08 #define MGV6_FLAT 0x10 #define MGV6_TREES 0x20 +#define MGV6_TEMPLES 0x40 extern FlagDesc flagdesc_mapgen_v6[]; diff --git a/src/mapgen/mg_biome.cpp b/src/mapgen/mg_biome.cpp index 5a4501693..beb457c0f 100644 --- a/src/mapgen/mg_biome.cpp +++ b/src/mapgen/mg_biome.cpp @@ -297,7 +297,11 @@ Biome *BiomeGenOriginal::calcBiomeFromNoise(float heat, float humidity, v3s16 po // Carefully tune pseudorandom seed variation to avoid single node dither // and create larger scale blending patterns similar to horizontal biome // blend. - const u64 seed = pos.Y + (heat + humidity) * 0.9f; + // The calculation can be a negative floating point number, which is an + // undefined behavior if assigned to unsigned integer. Cast the result + // into signed integer before it is casted into unsigned integer to + // eliminate the undefined behavior. + const u64 seed = static_cast(pos.Y + (heat + humidity) * 0.9f); PcgRandom rng(seed); if (biome_closest_blend && dist_min_blend <= dist_min && diff --git a/src/mapgen/mg_decoration.cpp b/src/mapgen/mg_decoration.cpp index 75764a9f1..e9e249220 100644 --- a/src/mapgen/mg_decoration.cpp +++ b/src/mapgen/mg_decoration.cpp @@ -236,8 +236,7 @@ size_t Decoration::placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax) v3s16 pos(x, y, z); if (generate(mg->vm, &ps, pos, false)) - mg->gennotify.addEvent( - GENNOTIFY_DECORATION, pos, index); + mg->gennotify.addDecorationEvent(pos, index); } } @@ -249,8 +248,7 @@ size_t Decoration::placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax) v3s16 pos(x, y, z); if (generate(mg->vm, &ps, pos, true)) - mg->gennotify.addEvent( - GENNOTIFY_DECORATION, pos, index); + mg->gennotify.addDecorationEvent(pos, index); } } } else { // Heightmap decorations @@ -273,7 +271,7 @@ size_t Decoration::placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax) v3s16 pos(x, y, z); if (generate(mg->vm, &ps, pos, false)) - mg->gennotify.addEvent(GENNOTIFY_DECORATION, pos, index); + mg->gennotify.addDecorationEvent(pos, index); } } } diff --git a/src/mapgen/mg_schematic.cpp b/src/mapgen/mg_schematic.cpp index 28a1abf83..b6b78f9e9 100644 --- a/src/mapgen/mg_schematic.cpp +++ b/src/mapgen/mg_schematic.cpp @@ -384,7 +384,7 @@ bool Schematic::serializeToMts(std::ostream *os) const } // compressed bulk node data - SharedBuffer buf = MapNode::serializeBulk(MTSCHEM_MAPNODE_SER_FMT_VER, + auto buf = MapNode::serializeBulk(MTSCHEM_MAPNODE_SER_FMT_VER, schemdata, size.X * size.Y * size.Z, 2, 2); compress(buf, ss, MTSCHEM_MAPNODE_SER_FMT_VER); diff --git a/src/mapnode.cpp b/src/mapnode.cpp index 33354b2d9..d16f6d647 100644 --- a/src/mapnode.cpp +++ b/src/mapnode.cpp @@ -690,7 +690,7 @@ void MapNode::deSerialize(u8 *source, u8 version) } } -SharedBuffer MapNode::serializeBulk(int version, +Buffer MapNode::serializeBulk(int version, const MapNode *nodes, u32 nodecount, u8 content_width, u8 params_width) { @@ -706,7 +706,7 @@ SharedBuffer MapNode::serializeBulk(int version, throw SerializationError("MapNode::serializeBulk: serialization to " "version < 24 not possible"); - SharedBuffer databuf(nodecount * (content_width + params_width)); + Buffer databuf(nodecount * (content_width + params_width)); u32 start1 = content_width * nodecount; u32 start2 = (content_width + 1) * nodecount; diff --git a/src/mapnode.h b/src/mapnode.h index c3137bbcb..93dfd01b9 100644 --- a/src/mapnode.h +++ b/src/mapnode.h @@ -316,7 +316,7 @@ struct alignas(u32) MapNode // content_width = the number of bytes of content per node // params_width = the number of bytes of params per node // compressed = true to zlib-compress output - static SharedBuffer serializeBulk(int version, + static Buffer serializeBulk(int version, const MapNode *nodes, u32 nodecount, u8 content_width, u8 params_width); static void deSerializeBulk(std::istream &is, int version, diff --git a/src/mapsector.cpp b/src/mapsector.cpp index 0eaf81ed4..e3fc3cfee 100644 --- a/src/mapsector.cpp +++ b/src/mapsector.cpp @@ -71,6 +71,9 @@ std::unique_ptr MapSector::createBlankBlockNoInsert(s16 y) { assert(getBlockBuffered(y) == nullptr); // Pre-condition + if (blockpos_over_max_limit(v3s16(0, y, 0))) + throw InvalidPositionException("createBlankBlockNoInsert(): pos over max mapgen limit"); + v3s16 blockpos_map(m_pos.X, y, m_pos.Y); return std::make_unique(blockpos_map, m_gamedef); diff --git a/src/metadata.cpp b/src/metadata.cpp index f02495fa4..f8fc02da5 100644 --- a/src/metadata.cpp +++ b/src/metadata.cpp @@ -70,7 +70,7 @@ bool IMetadata::getStringToRef(const std::string &name, const std::string &IMetadata::resolveString(const std::string &str, std::string *place, u16 recursion, bool deprecated) const { - if (recursion <= 1 && str.substr(0, 2) == "${" && str[str.length() - 1] == '}') { + if (recursion <= 1 && str_starts_with(str, "${") && str.back() == '}') { if (deprecated) { warningstream << "Deprecated use of recursive resolution syntax in metadata: "; safe_print_string(warningstream, str); @@ -128,7 +128,7 @@ const std::string *SimpleMetadata::getStringRaw(const std::string &name, std::st return found != m_stringvars.cend() ? &found->second : nullptr; } -bool SimpleMetadata::setString(const std::string &name, const std::string &var) +bool SimpleMetadata::setString(const std::string &name, std::string_view var) { if (var.empty()) { if (m_stringvars.erase(name) == 0) @@ -137,7 +137,7 @@ bool SimpleMetadata::setString(const std::string &name, const std::string &var) StringMap::iterator it = m_stringvars.find(name); if (it != m_stringvars.end() && it->second == var) return false; - m_stringvars[name] = var; + m_stringvars[name].assign(var); } m_modified = true; return true; diff --git a/src/metadata.h b/src/metadata.h index 1a0350bbf..be053662e 100644 --- a/src/metadata.h +++ b/src/metadata.h @@ -53,7 +53,7 @@ public: bool getStringToRef(const std::string &name, std::string &str, u16 recursion = 0) const; // Returns whether the metadata was (potentially) changed. - virtual bool setString(const std::string &name, const std::string &var) = 0; + virtual bool setString(const std::string &name, std::string_view var) = 0; inline bool removeString(const std::string &name) { return setString(name, ""); } @@ -89,7 +89,7 @@ public: size_t size() const; bool contains(const std::string &name) const override; - virtual bool setString(const std::string &name, const std::string &var) override; + virtual bool setString(const std::string &name, std::string_view var) override; const StringMap &getStrings(StringMap *) const override final; const std::vector &getKeys(std::vector *place) const override final; diff --git a/src/network/clientopcodes.h b/src/network/clientopcodes.h index 683d3534d..20bfc6697 100644 --- a/src/network/clientopcodes.h +++ b/src/network/clientopcodes.h @@ -20,10 +20,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once +#include "client/client.h" #include "networkprotocol.h" class NetworkPacket; -class Client; +// Note: don't forward-declare Client here (#14324) enum ToClientConnectionState { TOCLIENT_STATE_NOT_CONNECTED, diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 9c860c0c6..1e52ff692 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -471,6 +471,7 @@ void Client::handleCommand_ActiveObjectRemoveAdd(NetworkPacket* pkt) for (u16 i = 0; i < removed_count; i++) { *pkt >> id; m_env.removeActiveObject(id); + removeActiveObjectSounds(id); } // Read added objects @@ -1281,7 +1282,6 @@ void Client::handleCommand_HudSetFlags(NetworkPacket* pkt) LocalPlayer *player = m_env.getLocalPlayer(); assert(player != NULL); - bool was_minimap_visible = player->hud_flags & HUD_FLAG_MINIMAP_VISIBLE; bool was_minimap_radar_visible = player->hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE; player->hud_flags &= ~mask; @@ -1292,29 +1292,21 @@ void Client::handleCommand_HudSetFlags(NetworkPacket* pkt) player->hud_flags = player->hud_flags | HUD_FLAG_MINIMAP_VISIBLE | HUD_FLAG_MINIMAP_RADAR_VISIBLE; } else { - m_minimap_disabled_by_server = !(player->hud_flags & HUD_FLAG_MINIMAP_VISIBLE); - bool m_minimap_radar_disabled_by_server = - !(player->hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE); + bool m_minimap_radar_disabled_by_server = + !(player->hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE); - // Not so satisying code to keep compatibility with old fixed mode system - // --> - - // Hide minimap if it has been disabled by the server - if (m_minimap && m_minimap_disabled_by_server && was_minimap_visible) - // defers a minimap update, therefore only call it if really - // needed, by checking that minimap was visible before - m_minimap->setModeIndex(0); - - // If radar has been disabled, try to find a non radar mode or fall back to 0 - if (m_minimap && m_minimap_radar_disabled_by_server && - was_minimap_radar_visible) { - while (m_minimap->getModeIndex() > 0 && - m_minimap->getModeDef().type == MINIMAP_TYPE_RADAR) - m_minimap->nextMode(); - } - } - // <-- - // End of 'not so satifying code' + // Not so satisying code to keep compatibility with old fixed mode system + // --> + // If radar has been disabled, try to find a non radar mode or fall back to 0 + if (m_minimap && m_minimap_radar_disabled_by_server && + was_minimap_radar_visible) { + while (m_minimap->getModeIndex() > 0 && + m_minimap->getModeDef().type == MINIMAP_TYPE_RADAR) + m_minimap->nextMode(); + } + // <-- + // End of 'not so satifying code' + } } void Client::handleCommand_HudSetParam(NetworkPacket* pkt) @@ -1673,10 +1665,8 @@ void Client::handleCommand_MediaPush(NetworkPacket *pkt) std::string computed_hash; { SHA1 ctx; - ctx.addBytes(filedata.c_str(), filedata.size()); - unsigned char *buf = ctx.getDigest(); - computed_hash.assign((char*) buf, 20); - free(buf); + ctx.addBytes(filedata); + computed_hash = ctx.getDigest(); } if (raw_hash != computed_hash) { verbosestream << "Hash of file data mismatches, ignoring." << std::endl; diff --git a/src/network/connection.h b/src/network/connection.h index 5dc97441a..4c7c7a609 100644 --- a/src/network/connection.h +++ b/src/network/connection.h @@ -615,7 +615,7 @@ class Peer { u64 m_last_timeout_check; }; -class UDPPeer : public Peer +class UDPPeer final : public Peer { public: @@ -628,15 +628,15 @@ public: virtual ~UDPPeer() = default; void PutReliableSendCommand(ConnectionCommandPtr &c, - unsigned int max_packet_size); + unsigned int max_packet_size) override; - bool getAddress(MTProtocols type, Address& toset); + bool getAddress(MTProtocols type, Address& toset) override; - u16 getNextSplitSequenceNumber(u8 channel); - void setNextSplitSequenceNumber(u8 channel, u16 seqnum); + u16 getNextSplitSequenceNumber(u8 channel) override; + void setNextSplitSequenceNumber(u8 channel, u16 seqnum) override; SharedBuffer addSplitPacket(u8 channel, BufferedPacketPtr &toadd, - bool reliable); + bool reliable) override; bool isTimedOut(float timeout, std::string &reason) override; @@ -645,7 +645,7 @@ protected: Calculates avg_rtt and resend_timeout. rtt=-1 only recalculates resend_timeout */ - void reportRTT(float rtt); + void reportRTT(float rtt) override; void RunCommandQueues( unsigned int max_packet_size, @@ -657,7 +657,7 @@ protected: void setResendTimeout(float timeout) { MutexAutoLock lock(m_exclusive_access_mutex); resend_timeout = timeout; } - bool Ping(float dtime,SharedBuffer& data); + bool Ping(float dtime, SharedBuffer& data) override; Channel channels[CHANNEL_COUNT]; bool m_pending_disconnect = false; diff --git a/src/network/networkpacket.cpp b/src/network/networkpacket.cpp index 5b7b51391..48e8660b6 100644 --- a/src/network/networkpacket.cpp +++ b/src/network/networkpacket.cpp @@ -99,7 +99,7 @@ NetworkPacket& NetworkPacket::operator>>(std::string& dst) return *this; } -NetworkPacket& NetworkPacket::operator<<(const std::string &src) +NetworkPacket& NetworkPacket::operator<<(std::string_view src) { if (src.size() > STRING_MAX_LEN) { throw PacketError("String too long"); @@ -109,12 +109,12 @@ NetworkPacket& NetworkPacket::operator<<(const std::string &src) *this << msgsize; - putRawString(src.c_str(), (u32)msgsize); + putRawString(src.data(), (u32)msgsize); return *this; } -void NetworkPacket::putLongString(const std::string &src) +void NetworkPacket::putLongString(std::string_view src) { if (src.size() > LONG_STRING_MAX_LEN) { throw PacketError("String too long"); @@ -124,7 +124,7 @@ void NetworkPacket::putLongString(const std::string &src) *this << msgsize; - putRawString(src.c_str(), msgsize); + putRawString(src.data(), msgsize); } static constexpr bool NEED_SURROGATE_CODING = sizeof(wchar_t) > 2; @@ -160,7 +160,7 @@ NetworkPacket& NetworkPacket::operator>>(std::wstring& dst) return *this; } -NetworkPacket& NetworkPacket::operator<<(const std::wstring &src) +NetworkPacket& NetworkPacket::operator<<(std::wstring_view src) { if (src.size() > WIDE_STRING_MAX_LEN) { throw PacketError("String too long"); diff --git a/src/network/networkpacket.h b/src/network/networkpacket.h index 5f768f51a..ee85b2951 100644 --- a/src/network/networkpacket.h +++ b/src/network/networkpacket.h @@ -55,18 +55,18 @@ public: const char *getString(u32 from_offset) const; // major difference to putCString(): doesn't write len into the buffer void putRawString(const char *src, u32 len); - void putRawString(const std::string &src) + void putRawString(std::string_view src) { - putRawString(src.c_str(), src.size()); + putRawString(src.data(), src.size()); } NetworkPacket &operator>>(std::string &dst); - NetworkPacket &operator<<(const std::string &src); + NetworkPacket &operator<<(std::string_view src); - void putLongString(const std::string &src); + void putLongString(std::string_view src); NetworkPacket &operator>>(std::wstring &dst); - NetworkPacket &operator<<(const std::wstring &src); + NetworkPacket &operator<<(std::wstring_view src); std::string readLongString(); diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index 76d1a1170..add80b3b2 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -222,6 +222,7 @@ with this program; if not, write to the Free Software Foundation, Inc., PROTOCOL VERSION 44: AO_CMD_SET_BONE_POSITION extended Add TOCLIENT_MOVE_PLAYER_REL + Move default minimap from client-side C++ to server-side builtin Lua [scheduled bump for 5.9.0] */ @@ -233,8 +234,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #define SERVER_PROTOCOL_VERSION_MAX LATEST_PROTOCOL_VERSION // Client's supported network protocol range -// The minimal version depends on whether -// send_pre_v25_init is enabled or not #define CLIENT_PROTOCOL_VERSION_MIN 37 #define CLIENT_PROTOCOL_VERSION_MAX LATEST_PROTOCOL_VERSION diff --git a/src/network/serveropcodes.h b/src/network/serveropcodes.h index c461da44a..275270ab9 100644 --- a/src/network/serveropcodes.h +++ b/src/network/serveropcodes.h @@ -20,10 +20,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once +#include "server.h" #include "networkprotocol.h" class NetworkPacket; -class Server; +// Note: don't forward-declare Server here (#14324) enum ToServerConnectionState { TOSERVER_STATE_NOT_CONNECTED, diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index 3c6b63500..c3888c110 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -512,7 +512,7 @@ void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao, if (playersao->checkMovementCheat()) { // Call callbacks m_script->on_cheat(playersao, "moved_too_fast"); - SendMovePlayer(pkt->getPeerId()); + SendMovePlayer(playersao); } } @@ -993,7 +993,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) return; } - playersao->getPlayer()->setWieldIndex(item_i); + player->setWieldIndex(item_i); // Get pointed to object (NULL if not POINTEDTYPE_OBJECT) ServerActiveObject *pointed_object = NULL; @@ -1161,7 +1161,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) // Get player's wielded item // See also: Game::handleDigging ItemStack selected_item, hand_item; - playersao->getPlayer()->getWieldedItem(&selected_item, &hand_item); + player->getWieldedItem(&selected_item, &hand_item); // Get diggability and expected digging time DigParams params = getDigParams(m_nodedef->get(n).groups, @@ -1253,7 +1253,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) // Do stuff if (m_script->item_OnSecondaryUse(selected_item, playersao, pointed)) { if (selected_item.has_value() && playersao->setWieldedItem(*selected_item)) - SendInventory(playersao, true); + SendInventory(player, true); } pointed_object->rightClick(playersao); @@ -1262,7 +1262,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) // Apply returned ItemStack if (selected_item.has_value() && playersao->setWieldedItem(*selected_item)) - SendInventory(playersao, true); + SendInventory(player, true); } if (pointed.type != POINTEDTHING_NODE) @@ -1296,7 +1296,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) if (m_script->item_OnUse(selected_item, playersao, pointed)) { // Apply returned ItemStack if (selected_item.has_value() && playersao->setWieldedItem(*selected_item)) - SendInventory(playersao, true); + SendInventory(player, true); } return; @@ -1315,7 +1315,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) if (m_script->item_OnSecondaryUse(selected_item, playersao, pointed)) { // Apply returned ItemStack if (selected_item.has_value() && playersao->setWieldedItem(*selected_item)) - SendInventory(playersao, true); + SendInventory(player, true); } return; @@ -1336,15 +1336,14 @@ void Server::handleCommand_RemovedSounds(NetworkPacket* pkt) *pkt >> id; - std::unordered_map::iterator i = - m_playing_sounds.find(id); + auto i = m_playing_sounds.find(id); if (i == m_playing_sounds.end()) continue; ServerPlayingSound &psound = i->second; psound.clients.erase(pkt->getPeerId()); if (psound.clients.empty()) - m_playing_sounds.erase(i++); + m_playing_sounds.erase(i); } } diff --git a/src/nodedef.cpp b/src/nodedef.cpp index 830ee1ff4..6fc220145 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -356,6 +356,8 @@ void ContentFeatures::reset() has_on_construct = false; has_on_destruct = false; has_after_destruct = false; + floats = false; + /* Actual data @@ -734,7 +736,7 @@ static void fillTileAttribs(ITextureSource *tsrc, TileLayer *layer, int frame_count = 1; if (layer->material_flags & MATERIAL_FLAG_ANIMATION) { assert(layer->texture); - int frame_length_ms; + int frame_length_ms = 0; tiledef.animation.determineParams(layer->texture->getOriginalSize(), &frame_count, &frame_length_ms, NULL); layer->animation_frame_count = frame_count; @@ -1140,17 +1142,16 @@ bool NodeDefManager::getIds(const std::string &name, std::vector &result) const { //TimeTaker t("getIds", NULL, PRECISION_MICRO); - if (name.substr(0,6) != "group:") { + if (!str_starts_with(name, "group:")) { content_t id = CONTENT_IGNORE; bool exists = getId(name, id); if (exists) result.push_back(id); return exists; } - std::string group = name.substr(6); - std::unordered_map>::const_iterator - i = m_group_to_items.find(group); + std::string group = name.substr(6); + auto i = m_group_to_items.find(group); if (i == m_group_to_items.end()) return true; @@ -1845,7 +1846,7 @@ bool NodeResolver::getIdsFromNrBacklog(std::vector *result_out, content_t c; std::string &name = m_nodenames[m_nodenames_idx++]; - if (name.substr(0,6) != "group:") { + if (!str_starts_with(name, "group:")) { if (m_ndef->getId(name, c)) { result_out->push_back(c); } else if (all_required) { diff --git a/src/nodemetadata.h b/src/nodemetadata.h index a791cc5df..da277aabd 100644 --- a/src/nodemetadata.h +++ b/src/nodemetadata.h @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include +#include #include "metadata.h" /* diff --git a/src/particles.cpp b/src/particles.cpp index e48c52030..c67d72711 100644 --- a/src/particles.cpp +++ b/src/particles.cpp @@ -46,9 +46,9 @@ T RangedParameter::pickWithin() const auto p = numericAbsolute(bias) + 1; for (size_t i = 0; i < sizeof(values) / sizeof(values[0]); ++i) { if (bias < 0) - values[i] = 1.0f - pow(myrand_float(), p); + values[i] = 1.0f - std::pow(myrand_float(), p); else - values[i] = pow(myrand_float(), p); + values[i] = std::pow(myrand_float(), p); } return T::pick(values, min, max); } diff --git a/src/player.cpp b/src/player.cpp index f0461c9e1..14750b950 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -191,7 +191,7 @@ u32 PlayerControl::getKeysPressed() const float abs_d; // (absolute value indicates forward / backward) - abs_d = abs(movement_direction); + abs_d = std::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) @@ -201,7 +201,7 @@ u32 PlayerControl::getKeysPressed() const abs_d = movement_direction + M_PI_2; if (abs_d >= M_PI) abs_d -= 2 * M_PI; - abs_d = abs(abs_d); + abs_d = std::abs(abs_d); // (value now indicates left / right) if (abs_d < 3.0f / 8.0f * M_PI) keypress_bits |= (u32)1 << 2; // Left diff --git a/src/porting.cpp b/src/porting.cpp index 76c3ae985..e2cc3dffd 100644 --- a/src/porting.cpp +++ b/src/porting.cpp @@ -832,7 +832,7 @@ static bool open_uri(const std::string &uri) bool open_url(const std::string &url) { - if (url.substr(0, 7) != "http://" && url.substr(0, 8) != "https://") { + if (!str_starts_with(url, "http://") && !str_starts_with(url, "https://")) { errorstream << "Unable to open browser as URL is missing schema: " << url << std::endl; return false; } diff --git a/src/raycast.h b/src/raycast.h index d33f13c98..ea366f80b 100644 --- a/src/raycast.h +++ b/src/raycast.h @@ -55,7 +55,7 @@ public: bool m_objects_pointable; bool m_liquids_pointable; - const std::optional &m_pointabilities; + const std::optional m_pointabilities; //! The code needs to search these nodes around the center node. core::aabbox3d m_search_range { 0, 0, 0, 0, 0, 0 }; diff --git a/src/remoteplayer.cpp b/src/remoteplayer.cpp index 20be7a8c8..9658dca06 100644 --- a/src/remoteplayer.cpp +++ b/src/remoteplayer.cpp @@ -69,6 +69,11 @@ RemotePlayer::RemotePlayer(const char *name, IItemDefManager *idef): m_star_params = SkyboxDefaults::getStarDefaults(); } +RemotePlayer::~RemotePlayer() +{ + if (m_sao) + m_sao->setPlayer(nullptr); +} RemotePlayerChatResult RemotePlayer::canSendChatMessage() { diff --git a/src/remoteplayer.h b/src/remoteplayer.h index 0ab33adfe..a38f31731 100644 --- a/src/remoteplayer.h +++ b/src/remoteplayer.h @@ -42,7 +42,7 @@ class RemotePlayer : public Player public: RemotePlayer(const char *name, IItemDefManager *idef); - virtual ~RemotePlayer() = default; + virtual ~RemotePlayer(); PlayerSAO *getPlayerSAO() { return m_sao; } void setPlayerSAO(PlayerSAO *sao) { m_sao = sao; } @@ -135,6 +135,7 @@ public: u16 protocol_version = 0; u16 formspec_version = 0; + /// returns PEER_ID_INEXISTENT when PlayerSAO is not ready session_t getPeerId() const { return m_peer_id; } void setPeerId(session_t peer_id) { m_peer_id = peer_id; } diff --git a/src/script/CMakeLists.txt b/src/script/CMakeLists.txt index bebe2f037..ccb5785bd 100644 --- a/src/script/CMakeLists.txt +++ b/src/script/CMakeLists.txt @@ -5,6 +5,7 @@ add_subdirectory(lua_api) # Used by server and client set(common_SCRIPT_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/scripting_server.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/scripting_emerge.cpp ${common_SCRIPT_COMMON_SRCS} ${common_SCRIPT_CPP_API_SRCS} ${common_SCRIPT_LUA_API_SRCS} diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 5259d3f38..3bda40ebb 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -35,6 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "server/player_sao.h" #include "util/pointedthing.h" #include "debug.h" // For FATAL_ERROR +#include #include struct EnumString es_TileAnimationType[] = @@ -94,6 +95,15 @@ void read_item_definition(lua_State* L, int index, def.tool_capabilities = new ToolCapabilities( read_tool_capabilities(L, -1)); } + lua_getfield(L, index, "wear_color"); + if (lua_istable(L, -1)) { + def.wear_bar_params = read_wear_bar_params(L, -1); + } else if (lua_isstring(L, -1)) { + video::SColor color; + read_color(L, -1, &color); + def.wear_bar_params = WearBarParams({{0.0, color}}, + WearBarParams::BLEND_MODE_CONSTANT); + } // If name is "" (hand), ensure there are ToolCapabilities // because it will be looked up there whenever any other item has @@ -213,6 +223,10 @@ void push_item_definition_full(lua_State *L, const ItemDefinition &i) push_tool_capabilities(L, *i.tool_capabilities); lua_setfield(L, -2, "tool_capabilities"); } + if (i.wear_bar_params.has_value()) { + push_wear_bar_params(L, *i.wear_bar_params); + lua_setfield(L, -2, "wear_color"); + } push_groups(L, i.groups); lua_setfield(L, -2, "groups"); push_simplesoundspec(L, i.sound_place); @@ -1096,7 +1110,7 @@ void push_nodebox(lua_State *L, const NodeBox &box) case NODEBOX_FIXED: lua_pushstring(L, "fixed"); lua_setfield(L, -2, "type"); - push_box(L, box.fixed); + push_aabb3f_vector(L, box.fixed); lua_setfield(L, -2, "fixed"); break; case NODEBOX_WALLMOUNTED: @@ -1113,17 +1127,17 @@ void push_nodebox(lua_State *L, const NodeBox &box) lua_pushstring(L, "connected"); lua_setfield(L, -2, "type"); const auto &c = box.getConnected(); - push_box(L, c.connect_top); + push_aabb3f_vector(L, c.connect_top); lua_setfield(L, -2, "connect_top"); - push_box(L, c.connect_bottom); + push_aabb3f_vector(L, c.connect_bottom); lua_setfield(L, -2, "connect_bottom"); - push_box(L, c.connect_front); + push_aabb3f_vector(L, c.connect_front); lua_setfield(L, -2, "connect_front"); - push_box(L, c.connect_back); + push_aabb3f_vector(L, c.connect_back); lua_setfield(L, -2, "connect_back"); - push_box(L, c.connect_left); + push_aabb3f_vector(L, c.connect_left); lua_setfield(L, -2, "connect_left"); - push_box(L, c.connect_right); + push_aabb3f_vector(L, c.connect_right); lua_setfield(L, -2, "connect_right"); // half the boxes are missing here? break; @@ -1134,16 +1148,6 @@ void push_nodebox(lua_State *L, const NodeBox &box) } } -void push_box(lua_State *L, const std::vector &box) -{ - lua_createtable(L, box.size(), 0); - u8 i = 1; - for (const aabb3f &it : box) { - push_aabb3f(L, it); - lua_rawseti(L, -2, i++); - } -} - /******************************************************************************/ void push_palette(lua_State *L, const std::vector *palette) { @@ -1454,6 +1458,22 @@ void push_tool_capabilities(lua_State *L, lua_setfield(L, -2, "damage_groups"); } +/******************************************************************************/ +void push_wear_bar_params(lua_State *L, + const WearBarParams ¶ms) +{ + lua_newtable(L); + setstringfield(L, -1, "blend", WearBarParams::es_BlendMode[params.blend].str); + + lua_newtable(L); + for (const std::pair item: params.colorStops) { + lua_pushnumber(L, item.first); // key + push_ARGB8(L, item.second); + lua_rawset(L, -3); + } + lua_setfield(L, -2, "color_stops"); +} + /******************************************************************************/ void push_inventory_list(lua_State *L, const InventoryList &invlist) { @@ -1644,7 +1664,7 @@ Pointabilities read_pointabilities(lua_State *L, int index) std::string name = luaL_checkstring(L, -2); // handle groups - if(std::string_view(name).substr(0,6)=="group:") { + if (str_starts_with(name, "group:")) { pointabilities.node_groups[name.substr(6)] = read_pointability_type(L, -1); } else { pointabilities.nodes[name] = read_pointability_type(L, -1); @@ -1665,7 +1685,7 @@ Pointabilities read_pointabilities(lua_State *L, int index) std::string name = luaL_checkstring(L, -2); // handle groups - if(std::string_view(name).substr(0,6)=="group:") { + if (str_starts_with(name, "group:")) { pointabilities.object_groups[name.substr(6)] = read_pointability_type(L, -1); } else { pointabilities.objects[name] = read_pointability_type(L, -1); @@ -1732,6 +1752,54 @@ void push_pointabilities(lua_State *L, const Pointabilities &pointabilities) } } +/******************************************************************************/ +WearBarParams read_wear_bar_params( + lua_State *L, int stack_idx) +{ + if (lua_isstring(L, stack_idx)) { + video::SColor color; + read_color(L, stack_idx, &color); + return WearBarParams(color); + } + + if (!lua_istable(L, stack_idx)) + throw LuaError("Expected wear bar color table or colorstring"); + + lua_getfield(L, stack_idx, "color_stops"); + if (!check_field_or_nil(L, -1, LUA_TTABLE, "color_stops")) + throw LuaError("color_stops must be a table"); + + std::map colorStops; + // color stops table is on the stack + int table_values = lua_gettop(L); + lua_pushnil(L); + while (lua_next(L, table_values) != 0) { + // key at index -2 and value at index -1 within table_values + f32 point = luaL_checknumber(L, -2); + if (point < 0 || point > 1) + throw LuaError("Wear bar color stop key out of range"); + video::SColor color; + read_color(L, -1, &color); + colorStops.emplace(point, color); + + // removes value, keeps key for next iteration + lua_pop(L, 1); + } + lua_pop(L, 1); // pop color stops table + + auto blend = WearBarParams::BLEND_MODE_CONSTANT; + lua_getfield(L, stack_idx, "blend"); + if (check_field_or_nil(L, -1, LUA_TSTRING, "blend")) { + int blendInt; + if (!string_to_enum(WearBarParams::es_BlendMode, blendInt, std::string(lua_tostring(L, -1)))) + throw LuaError("Invalid wear bar color blend mode"); + blend = static_cast(blendInt); + } + lua_pop(L, 1); + + return WearBarParams(colorStops, blend); +} + /******************************************************************************/ void push_dig_params(lua_State *L,const DigParams ¶ms) { diff --git a/src/script/common/c_content.h b/src/script/common/c_content.h index 6f54b7b23..07f77f8c7 100644 --- a/src/script/common/c_content.h +++ b/src/script/common/c_content.h @@ -83,9 +83,6 @@ void push_content_features (lua_State *L, void push_nodebox (lua_State *L, const NodeBox &box); -void push_box (lua_State *L, - const std::vector &box); - void push_palette (lua_State *L, const std::vector *palette); @@ -116,6 +113,9 @@ void push_pointabilities (lua_State *L, const Pointabilities ToolCapabilities read_tool_capabilities (lua_State *L, int table); void push_tool_capabilities (lua_State *L, const ToolCapabilities &prop); +WearBarParams read_wear_bar_params (lua_State *L, int table); +void push_wear_bar_params (lua_State *L, + const WearBarParams &prop); void read_item_definition (lua_State *L, int index, const ItemDefinition &default_def, ItemDefinition &def); diff --git a/src/script/common/c_converter.cpp b/src/script/common/c_converter.cpp index 7924b9fc2..a7b18365a 100644 --- a/src/script/common/c_converter.cpp +++ b/src/script/common/c_converter.cpp @@ -364,20 +364,20 @@ aabb3f read_aabb3f(lua_State *L, int index, f32 scale) return box; } -void push_aabb3f(lua_State *L, aabb3f box) +void push_aabb3f(lua_State *L, aabb3f box, f32 divisor) { lua_createtable(L, 6, 0); - lua_pushnumber(L, box.MinEdge.X); + lua_pushnumber(L, box.MinEdge.X / divisor); lua_rawseti(L, -2, 1); - lua_pushnumber(L, box.MinEdge.Y); + lua_pushnumber(L, box.MinEdge.Y / divisor); lua_rawseti(L, -2, 2); - lua_pushnumber(L, box.MinEdge.Z); + lua_pushnumber(L, box.MinEdge.Z / divisor); lua_rawseti(L, -2, 3); - lua_pushnumber(L, box.MaxEdge.X); + lua_pushnumber(L, box.MaxEdge.X / divisor); lua_rawseti(L, -2, 4); - lua_pushnumber(L, box.MaxEdge.Y); + lua_pushnumber(L, box.MaxEdge.Y / divisor); lua_rawseti(L, -2, 5); - lua_pushnumber(L, box.MaxEdge.Z); + lua_pushnumber(L, box.MaxEdge.Z / divisor); lua_rawseti(L, -2, 6); } @@ -409,6 +409,16 @@ std::vector read_aabb3f_vector(lua_State *L, int index, f32 scale) return boxes; } +void push_aabb3f_vector(lua_State *L, const std::vector &boxes, f32 divisor) +{ + lua_createtable(L, boxes.size(), 0); + int i = 1; + for (const aabb3f &box : boxes) { + push_aabb3f(L, box, divisor); + lua_rawseti(L, -2, i++); + } +} + size_t read_stringlist(lua_State *L, int index, std::vector *result) { if (index < 0) @@ -473,6 +483,17 @@ bool check_field_or_nil(lua_State *L, int index, int type, const char *fieldname bool getstringfield(lua_State *L, int table, const char *fieldname, std::string &result) +{ + std::string_view sv; + if (getstringfield(L, table, fieldname, sv)) { + result = sv; + return true; + } + return false; +} + +bool getstringfield(lua_State *L, int table, + const char *fieldname, std::string_view &result) { lua_getfield(L, table, fieldname); bool got = false; @@ -481,7 +502,7 @@ bool getstringfield(lua_State *L, int table, size_t len = 0; const char *ptr = lua_tolstring(L, -1, &len); if (ptr) { - result.assign(ptr, len); + result = std::string_view(ptr, len); got = true; } } diff --git a/src/script/common/c_converter.h b/src/script/common/c_converter.h index 90358147a..05d43953e 100644 --- a/src/script/common/c_converter.h +++ b/src/script/common/c_converter.h @@ -27,7 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include -#include +#include #include "irrlichttypes_bloated.h" #include "common/c_types.h" @@ -67,11 +67,11 @@ v3s16 getv3s16field_default(lua_State *L, int table, bool getstringfield(lua_State *L, int table, const char *fieldname, std::string &result); +bool getstringfield(lua_State *L, int table, + const char *fieldname, std::string_view &result); size_t getstringlistfield(lua_State *L, int table, const char *fieldname, std::vector *result); -void read_groups(lua_State *L, int index, - std::unordered_map &result); bool getboolfield(lua_State *L, int table, const char *fieldname, bool &result); bool getfloatfield(lua_State *L, int table, @@ -110,11 +110,13 @@ void push_v2s16 (lua_State *L, v2s16 p); void push_v2s32 (lua_State *L, v2s32 p); void push_v2u32 (lua_State *L, v2u32 p); void push_v3s16 (lua_State *L, v3s16 p); -void push_aabb3f (lua_State *L, aabb3f box); +void push_aabb3f (lua_State *L, aabb3f box, f32 divisor = 1.0f); void push_ARGB8 (lua_State *L, video::SColor color); void pushFloatPos (lua_State *L, v3f p); void push_v3f (lua_State *L, v3f p); void push_v2f (lua_State *L, v2f p); +void push_aabb3f_vector (lua_State *L, const std::vector &boxes, + f32 divisor = 1.0f); void warn_if_field_exists(lua_State *L, int table, const char *fieldname, diff --git a/src/script/common/c_internal.cpp b/src/script/common/c_internal.cpp index b80ee6e40..ae83b8df0 100644 --- a/src/script/common/c_internal.cpp +++ b/src/script/common/c_internal.cpp @@ -189,7 +189,7 @@ void log_deprecated(lua_State *L, std::string message, int stack_depth, bool onc } if (mode == DeprecatedHandlingMode::Error) - script_error(L, LUA_ERRRUN, nullptr, nullptr); + throw LuaError(message); else if (log) infostream << script_get_backtrace(L) << std::endl; } diff --git a/src/script/common/c_packer.cpp b/src/script/common/c_packer.cpp index 597f5e447..c133e0ee2 100644 --- a/src/script/common/c_packer.cpp +++ b/src/script/common/c_packer.cpp @@ -70,6 +70,7 @@ static inline bool uses_union(int type) } } +// can set_into be used with these key / value types in principle? static inline bool can_set_into(int ktype, int vtype) { switch (ktype) { @@ -82,7 +83,7 @@ static inline bool can_set_into(int ktype, int vtype) } } -// is the key suitable for use with set_into? +// is the actual key suitable for use with set_into? static inline bool suitable_key(lua_State *L, int idx) { if (lua_type(L, idx) == LUA_TSTRING) { @@ -143,6 +144,13 @@ namespace { typedef std::pair PackerTuple; } +/** + * Append instruction to end. + * + * @param pv target + * @param type instruction type + * @return reference to instruction +*/ static inline auto emplace(PackedValue &pv, s16 type) { pv.i.emplace_back(); @@ -211,6 +219,13 @@ void script_register_packer(lua_State *L, const char *regname, lua_pop(L, 1); } +/** + * Find a packer for a metatable. + * + * @param regname metatable name + * @param out packer will be placed here + * @return success +*/ static bool find_packer(const char *regname, PackerTuple &out) { MutexAutoLock autolock(g_packers_lock); @@ -223,6 +238,14 @@ static bool find_packer(const char *regname, PackerTuple &out) return true; } +/** + * Find a packer matching the metatable of the Lua value. + * + * @param L Lua state + * @param idx Index on stack + * @param out packer will be placed here + * @return success +*/ static bool find_packer(lua_State *L, int idx, PackerTuple &out) { #ifndef NDEBUG @@ -254,6 +277,21 @@ static bool find_packer(lua_State *L, int idx, PackerTuple &out) // Packing implementation // +/** + * Keeps track of seen objects, which is needed to make circular references work. + * The first time an object is seen it remembers the instruction index. + * The caller is expected to add instructions that produce the value immediately after. + * For second, third, ... calls it pushes an instruction that references the already + * created value. + * + * @param L Lua state + * @param idx Index of value on Lua stack + * @param pv target + * @param seen Map of seen objects + * @return empty reference (first time) or reference to instruction that + * reproduces the value (otherwise) + * +*/ static VectorRef record_object(lua_State *L, int idx, PackedValue &pv, std::unordered_map &seen) { @@ -261,18 +299,31 @@ static VectorRef record_object(lua_State *L, int idx, PackedValue & assert(ptr); auto found = seen.find(ptr); if (found == seen.end()) { + // first time, record index + assert(pv.i.size() <= S32_MAX); seen[ptr] = pv.i.size(); return VectorRef(); } + 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; + r->sidata1 = ref; pv.i[ref].keep_ref = true; return r; } +/** + * Pack a single Lua value and add it to the instruction stream. + * + * @param L Lua state + * @param idx Index of value on Lua stack. Must be positive, use absidx if not! + * @param vidx Next free index on the stack as it would look during unpacking. (v = virtual) + * @param pv target + * @param seen Map of seen objects (see record_object) + * @return reference to the instruction that creates the value +*/ static VectorRef pack_inner(lua_State *L, int idx, int vidx, PackedValue &pv, std::unordered_map &seen) { @@ -330,6 +381,7 @@ static VectorRef pack_inner(lua_State *L, int idx, int vidx, Packed PackerTuple ser; if (!find_packer(L, idx, ser)) throw LuaError("Cannot serialize unsupported userdata"); + // use packer callback to turn into a void* pv.contains_userdata = true; r = emplace(pv, LUA_TUSERDATA); r->sdata = ser.first; @@ -358,22 +410,28 @@ static VectorRef pack_inner(lua_State *L, int idx, int vidx, Packed else rtable->uidata2++; // nrec - // check if we can use a shortcut + // set_into is a shortcut that allows a pushed value + // to be directly set into a table without separately pushing + // the key and using SETTABLE. + // only works in certain circumstances, hence the check: 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); + vidx++; rval->pop = rval->type != LUA_TTABLE; - // and where to put it: + // 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 + // since tables take multiple instructions to populate we have to + // pop them separately afterwards. if (!rval->pop) { auto ri1 = emplace(pv, INSTR_POP); - ri1->sidata1 = vidx; + ri1->sidata1 = vidx - 1; } + vidx--; } else { // push the key and value pack_inner(L, absidx(L, -2), vidx, pv, seen); @@ -392,6 +450,7 @@ static VectorRef pack_inner(lua_State *L, int idx, int vidx, Packed lua_pop(L, 1); } + // exactly the table should be left on stack assert(vidx == vi_table + 1); return rtable; } @@ -405,6 +464,7 @@ PackedValue *script_pack(lua_State *L, int idx) std::unordered_map seen; pack_inner(L, idx, 1, pv, seen); + // allocate last for exception safety return new PackedValue(std::move(pv)); } @@ -414,14 +474,15 @@ PackedValue *script_pack(lua_State *L, int idx) void script_unpack(lua_State *L, PackedValue *pv) { - lua_newtable(L); // table at index top to track ref indices -> objects + // table that tracks objects for keep_ref / PUSHREF (key = instr index) + lua_newtable(L); 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) + // Make sure there's space on the stack (if applicable) if (!i.pop && (ctr++) >= 5) { lua_checkstack(L, 5); ctr = 0; @@ -449,7 +510,8 @@ void script_unpack(lua_State *L, PackedValue *pv) lua_remove(L, top + i.sidata2); continue; case INSTR_PUSHREF: - lua_pushinteger(L, i.ref); + // retrieve from top table + lua_pushinteger(L, i.sidata1); lua_rawget(L, top); break; @@ -476,7 +538,7 @@ void script_unpack(lua_State *L, PackedValue *pv) PackerTuple ser; sanity_check(find_packer(i.sdata.c_str(), ser)); ser.second.fout(L, i.ptrdata); - i.ptrdata = nullptr; // ownership taken by callback + i.ptrdata = nullptr; // ownership taken by packer callback break; } @@ -486,13 +548,14 @@ void script_unpack(lua_State *L, PackedValue *pv) } if (i.keep_ref) { + // remember in top table lua_pushinteger(L, packed_idx); lua_pushvalue(L, -2); lua_rawset(L, top); } if (i.set_into) { - if (!i.pop) + if (!i.pop) // set will consume lua_pushvalue(L, -1); if (uses_sdata(i.type)) lua_rawseti(L, top + i.set_into, i.sidata1); @@ -504,7 +567,7 @@ void script_unpack(lua_State *L, PackedValue *pv) } } - // as part of the unpacking process we take ownership of all userdata + // as part of the unpacking process all userdata is "used up" pv->contains_userdata = false; // leave exactly one value on the stack lua_settop(L, top+1); @@ -523,7 +586,7 @@ PackedValue::~PackedValue() if (i.type == LUA_TUSERDATA && i.ptrdata) { PackerTuple ser; if (find_packer(i.sdata.c_str(), ser)) { - // tell it to deallocate object + // tell packer to deallocate object ser.second.fout(nullptr, i.ptrdata); } else { assert(false); @@ -536,7 +599,6 @@ PackedValue::~PackedValue() // script_dump_packed // -#ifndef NDEBUG void script_dump_packed(const PackedValue *val) { printf("instruction stream: [\n"); @@ -550,7 +612,7 @@ void script_dump_packed(const PackedValue *val) printf(i.sidata2 ? "POP(%d, %d)" : "POP(%d)", i.sidata1, i.sidata2); break; case INSTR_PUSHREF: - printf("PUSHREF(%d)", i.ref); + printf("PUSHREF(%d)", i.sidata1); break; case LUA_TNIL: printf("nil"); @@ -568,7 +630,7 @@ void script_dump_packed(const PackedValue *val) printf("table(%d, %d)", i.uidata1, i.uidata2); break; case LUA_TFUNCTION: - printf("function(%lu byte)", i.sdata.size()); + printf("function(%d bytes)", (int)i.sdata.size()); break; case LUA_TUSERDATA: printf("userdata %s %p", i.sdata.c_str(), i.ptrdata); @@ -593,4 +655,3 @@ void script_dump_packed(const PackedValue *val) } printf("]\n"); } -#endif diff --git a/src/script/common/c_packer.h b/src/script/common/c_packer.h index fe072c10a..17f25fd17 100644 --- a/src/script/common/c_packer.h +++ b/src/script/common/c_packer.h @@ -39,30 +39,31 @@ extern "C" { #define INSTR_PUSHREF (-12) /** - * Represents a single instruction that pushes a new value or works with existing ones. + * Represents a single instruction that pushes a new value or operates 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 keep_ref; // referenced later by INSTR_PUSHREF? bool pop; // remove from stack? + // Note: the remaining members are named by type, not usage union { bool bdata; // boolean: value lua_Number ndata; // number: value struct { - u16 uidata1, uidata2; // table: narr, nrec + u16 uidata1, uidata2; // table: narr | nrec }; struct { /* - SETTABLE: key index, value index + SETTABLE: key index | value index POP: indices to remove - otherwise w/ set_into: numeric key, - + PUSHREF: index of referenced instr | unused + otherwise w/ set_into: numeric key | unused */ s32 sidata1, sidata2; }; void *ptrdata; // userdata: implementation defined - s32 ref; // PUSHREF: index of referenced instr }; /* - string: value @@ -78,7 +79,7 @@ struct PackedInstr /** * 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. + * instructions that build the final value when executed. */ struct PackedValue { diff --git a/src/script/common/helper.cpp b/src/script/common/helper.cpp index 72de2b14a..a9a05b2cb 100644 --- a/src/script/common/helper.cpp +++ b/src/script/common/helper.cpp @@ -25,6 +25,7 @@ extern "C" { #include #include #include +#include #include "c_converter.h" #include "c_types.h" @@ -78,11 +79,16 @@ v3f LuaHelper::readParam(lua_State *L, int index) } template <> -std::string LuaHelper::readParam(lua_State *L, int index) +std::string_view LuaHelper::readParam(lua_State *L, int index) { size_t length; - std::string result; const char *str = luaL_checklstring(L, index, &length); - result.assign(str, length); - return result; + return std::string_view(str, length); +} + +template <> +std::string LuaHelper::readParam(lua_State *L, int index) +{ + auto sv = readParam(L, index); + return std::string(sv); // a copy } diff --git a/src/script/common/helper.h b/src/script/common/helper.h index fc462b6ef..106966376 100644 --- a/src/script/common/helper.h +++ b/src/script/common/helper.h @@ -19,6 +19,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once +#include + extern "C" { #include } @@ -27,23 +29,22 @@ class LuaHelper { protected: /** - * Read a value using a template type T from Lua State L and index - * + * Read a value using a template type T from Lua state L at index * * @tparam T type to read from Lua * @param L Lua state - * @param index Lua Index to read + * @param index Lua index to read * @return read value from Lua */ template static T readParam(lua_State *L, int index); /** - * Read a value using a template type T from Lua State L and index + * Read a value using a template type T from Lua state L at index * * @tparam T type to read from Lua * @param L Lua state - * @param index Lua Index to read + * @param index Lua index to read * @param default_value default value to apply if nil * @return read value from Lua or default value if nil */ @@ -53,3 +54,18 @@ protected: return lua_isnoneornil(L, index) ? default_value : readParam(L, index); } }; + +// (only declared for documentation purposes:) + +/** + * Read a string from Lua state L at index without copying it. + * + * Note that the returned string view is only valid as long as the value is on + * the stack and has not been modified. Be careful. + * + * @param L Lua state + * @param index Lua index to read + * @return string view + */ +template <> +std::string_view LuaHelper::readParam(lua_State *L, int index); diff --git a/src/script/cpp_api/CMakeLists.txt b/src/script/cpp_api/CMakeLists.txt index 3cfd7709a..d9d157d5f 100644 --- a/src/script/cpp_api/CMakeLists.txt +++ b/src/script/cpp_api/CMakeLists.txt @@ -5,6 +5,7 @@ set(common_SCRIPT_CPP_API_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/s_env.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_inventory.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_item.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/s_mapgen.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_modchannels.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_node.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_nodemeta.cpp diff --git a/src/script/cpp_api/s_base.cpp b/src/script/cpp_api/s_base.cpp index 73f8c49a1..7731de7a7 100644 --- a/src/script/cpp_api/s_base.cpp +++ b/src/script/cpp_api/s_base.cpp @@ -153,13 +153,6 @@ ScriptApiBase::ScriptApiBase(ScriptingType type): lua_pushstring(m_luastack, porting::getPlatformName()); lua_setglobal(m_luastack, "PLATFORM"); -#ifdef HAVE_TOUCHSCREENGUI - lua_pushboolean(m_luastack, true); -#else - lua_pushboolean(m_luastack, false); -#endif - lua_setglobal(m_luastack, "TOUCHSCREEN_GUI"); - // Make sure Lua uses the right locale setlocale(LC_NUMERIC, "C"); } @@ -179,6 +172,7 @@ int ScriptApiBase::luaPanic(lua_State *L) return 0; } +#ifndef SERVER void ScriptApiBase::clientOpenLibs(lua_State *L) { static const std::vector> m_libs = { @@ -199,6 +193,7 @@ void ScriptApiBase::clientOpenLibs(lua_State *L) lua_call(L, 1, 0); } } +#endif void ScriptApiBase::checkSetByBuiltin() { @@ -223,6 +218,45 @@ void ScriptApiBase::checkSetByBuiltin() } } +std::string ScriptApiBase::getCurrentModNameInsecure(lua_State *L) +{ + lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); + auto ret = lua_isstring(L, -1) ? readParam(L, -1) : ""; + lua_pop(L, 1); + return ret; +} + +std::string ScriptApiBase::getCurrentModName(lua_State *L) +{ + auto script = ModApiBase::getScriptApiBase(L); + if (script->getType() == ScriptingType::Async || + script->getType() == ScriptingType::Emerge) + { + // As a precaution never return a "secure" mod name in the async and + // emerge environment, because these currently do not track mod origins + // in a spoof-safe way (see l_register_async_dofile and l_register_mapgen_script). + return ""; + } + + // We have to make sure that this function is being called directly by + // a mod, otherwise a malicious mod could override a function and + // steal its return value. (e.g. request_insecure_environment) + lua_Debug info; + + // Make sure there's only one item below this function on the stack... + if (lua_getstack(L, 2, &info)) + return ""; + 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 ""; + + // at this point we can trust this value: + return getCurrentModNameInsecure(L); +} + void ScriptApiBase::loadMod(const std::string &script_path, const std::string &mod_name) { diff --git a/src/script/cpp_api/s_base.h b/src/script/cpp_api/s_base.h index fb152b371..cacc79a83 100644 --- a/src/script/cpp_api/s_base.h +++ b/src/script/cpp_api/s_base.h @@ -61,13 +61,15 @@ enum class ScriptingType: u8 { Async, // either mainmenu (client) or ingame (server) Client, MainMenu, - Server + Server, + Emerge }; class Server; #ifndef SERVER class Client; #endif +class EmergeThread; class IGameDef; class Environment; class GUIEngine; @@ -108,13 +110,29 @@ public: Client* getClient(); #endif - // IMPORTANT: these cannot be used for any security-related uses, they exist - // only to enrich error messages + // IMPORTANT: These cannot be used for any security-related uses, they exist + // only to enrich error messages. const std::string &getOrigin() { return m_last_run_mod; } void setOriginDirect(const char *origin); void setOriginFromTableRaw(int index, const char *fxn); + // Returns the currently running mod, only during init time. + // The reason this is "insecure" is that mods can mess with each others code, + // so the boundary of who is responsible is fuzzy. + // Note: checking this against BUILTIN_MOD_NAME is always safe (not spoofable). + // returns "" on error + static std::string getCurrentModNameInsecure(lua_State *L); + // Returns the currently running mod, only during init time. + // This checks the Lua stack to only permit direct calls in the file + // scope. That way it is assured that it's really the mod it claims to be. + // returns "" on error + static std::string getCurrentModName(lua_State *L); + +#ifdef SERVER + inline void clientOpenLibs(lua_State *L) { assert(false); } +#else void clientOpenLibs(lua_State *L); +#endif // Check things that should be set by the builtin mod. void checkSetByBuiltin(); @@ -158,6 +176,9 @@ protected: void setGuiEngine(GUIEngine* guiengine) { m_guiengine = guiengine; } #endif + EmergeThread* getEmergeThread() { return m_emerge; } + void setEmergeThread(EmergeThread *emerge) { m_emerge = emerge; } + void objectrefGetOrCreate(lua_State *L, ServerActiveObject *cobj); void pushPlayerHPChangeReason(lua_State *L, const PlayerHPChangeReason& reason); @@ -180,5 +201,7 @@ private: #ifndef SERVER GUIEngine *m_guiengine = nullptr; #endif + EmergeThread *m_emerge = nullptr; + ScriptingType m_type; }; diff --git a/src/script/cpp_api/s_mapgen.cpp b/src/script/cpp_api/s_mapgen.cpp new file mode 100644 index 000000000..c363ce2c2 --- /dev/null +++ b/src/script/cpp_api/s_mapgen.cpp @@ -0,0 +1,76 @@ +/* +Minetest +Copyright (C) 2022 sfan5 + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "cpp_api/s_mapgen.h" +#include "cpp_api/s_internal.h" +#include "common/c_converter.h" +#include "lua_api/l_vmanip.h" +#include "emerge.h" + +void ScriptApiMapgen::on_mods_loaded() +{ + SCRIPTAPI_PRECHECKHEADER + + // Get registered shutdown hooks + lua_getglobal(L, "core"); + lua_getfield(L, -1, "registered_on_mods_loaded"); + // Call callbacks + runCallbacks(0, RUN_CALLBACKS_MODE_FIRST); +} + +void ScriptApiMapgen::on_shutdown() +{ + SCRIPTAPI_PRECHECKHEADER + + // Get registered shutdown hooks + lua_getglobal(L, "core"); + lua_getfield(L, -1, "registered_on_shutdown"); + // Call callbacks + runCallbacks(0, RUN_CALLBACKS_MODE_FIRST); +} + +void ScriptApiMapgen::on_generated(BlockMakeData *bmdata) +{ + SCRIPTAPI_PRECHECKHEADER + + v3s16 minp = bmdata->blockpos_min * MAP_BLOCKSIZE; + v3s16 maxp = bmdata->blockpos_max * MAP_BLOCKSIZE + + v3s16(1,1,1) * (MAP_BLOCKSIZE - 1); + + LuaVoxelManip::create(L, bmdata->vmanip, true); + const int vmanip = lua_gettop(L); + + // Store vmanip globally (used by helpers) + lua_getglobal(L, "core"); + lua_pushvalue(L, vmanip); + lua_setfield(L, -2, "vmanip"); + + // Call callbacks + lua_getfield(L, -1, "registered_on_generateds"); + lua_pushvalue(L, vmanip); + push_v3s16(L, minp); + push_v3s16(L, maxp); + lua_pushnumber(L, bmdata->seed); + runCallbacks(4, RUN_CALLBACKS_MODE_FIRST); + lua_pop(L, 1); // return val + + // Unset core.vmanip again + lua_pushnil(L); + lua_setfield(L, -2, "vmanip"); +} diff --git a/src/script/cpp_api/s_mapgen.h b/src/script/cpp_api/s_mapgen.h new file mode 100644 index 000000000..e881ca04a --- /dev/null +++ b/src/script/cpp_api/s_mapgen.h @@ -0,0 +1,40 @@ +/* +Minetest +Copyright (C) 2022 sfan5 + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include "cpp_api/s_base.h" + +struct BlockMakeData; + +/* + * Note that this is the class defining the functions called inside the emerge + * Lua state, not the server one. + */ + +class ScriptApiMapgen : virtual public ScriptApiBase +{ +public: + + void on_mods_loaded(); + void on_shutdown(); + + // Called after generating a piece of map before writing it to the map + void on_generated(BlockMakeData *bmdata); +}; diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index 43b7d9f32..8f5c261b2 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -555,10 +555,9 @@ bool ScriptApiSecurity::checkPath(lua_State *L, const char *path, return false; // Get mod name - lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); - if (lua_isstring(L, -1)) { - std::string mod_name = readParam(L, -1); - + // FIXME: insecure = problem here? + std::string mod_name = ScriptApiBase::getCurrentModNameInsecure(L); + if (!mod_name.empty()) { // Builtin can access anything if (mod_name == BUILTIN_MOD_NAME) { if (write_allowed) *write_allowed = true; @@ -578,7 +577,6 @@ bool ScriptApiSecurity::checkPath(lua_State *L, const char *path, } } } - lua_pop(L, 1); // Pop mod name // Allow read-only access to game directory if (!write_required) { @@ -629,26 +627,9 @@ bool ScriptApiSecurity::checkWhitelisted(lua_State *L, const std::string &settin { 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)) + std::string mod_name = ScriptApiBase::getCurrentModName(L); + if (mod_name.empty()) return false; - FATAL_ERROR_IF(!lua_getstack(L, 1, &info), "lua_getstack() failed"); - FATAL_ERROR_IF(!lua_getinfo(L, "S", &info), "lua_getinfo() failed"); - - // ...and that that item is the main file scope. - if (strcmp(info.what, "main") != 0) - return false; - - // Mod must be listed in secure.http_mods or secure.trusted_mods - lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); - if (!lua_isstring(L, -1)) - return false; - std::string mod_name = readParam(L, -1); std::string value = g_settings->get(setting); value.erase(std::remove(value.begin(), value.end(), ' '), value.end()); diff --git a/src/script/lua_api/l_base.cpp b/src/script/lua_api/l_base.cpp index 03d8ee62d..962b262a1 100644 --- a/src/script/lua_api/l_base.cpp +++ b/src/script/lua_api/l_base.cpp @@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "server.h" #include #include +#include ScriptApiBase *ModApiBase::getScriptApiBase(lua_State *L) { @@ -74,10 +75,14 @@ GUIEngine *ModApiBase::getGuiEngine(lua_State *L) } #endif +EmergeThread *ModApiBase::getEmergeThread(lua_State *L) +{ + return getScriptApiBase(L)->getEmergeThread(); +} + std::string ModApiBase::getCurrentModPath(lua_State *L) { - lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); - std::string current_mod_name = readParam(L, -1, ""); + std::string current_mod_name = ScriptApiBase::getCurrentModNameInsecure(L); if (current_mod_name.empty()) return "."; @@ -147,11 +152,14 @@ int ModApiBase::l_deprecated_function(lua_State *L, const char *good, const char == deprecated_logged.end()) { deprecated_logged.emplace_back(hash); - warningstream << "Call to deprecated function '" << bad << "', please use '" - << good << "' at " << backtrace << std::endl; + + std::stringstream msg; + msg << "Call to deprecated function '" << bad << "', use '" << good << "' instead"; + + warningstream << msg.str() << " at " << backtrace << std::endl; if (dep_mode == DeprecatedHandlingMode::Error) - script_error(L, LUA_ERRRUN, NULL, NULL); + throw LuaError(msg.str()); } u64 end_time = porting::getTimeUs(); diff --git a/src/script/lua_api/l_base.h b/src/script/lua_api/l_base.h index 329e2cc26..37132426a 100644 --- a/src/script/lua_api/l_base.h +++ b/src/script/lua_api/l_base.h @@ -34,7 +34,7 @@ extern "C" { class Client; class GUIEngine; #endif - +class EmergeThread; class ScriptApiBase; class Server; class Environment; @@ -49,6 +49,7 @@ public: static Client* getClient(lua_State *L); static GUIEngine* getGuiEngine(lua_State *L); #endif // !SERVER + static EmergeThread* getEmergeThread(lua_State *L); static IGameDef* getGameDef(lua_State *L); diff --git a/src/script/lua_api/l_client.cpp b/src/script/lua_api/l_client.cpp index 8d7eb0984..da19ed0ea 100644 --- a/src/script/lua_api/l_client.cpp +++ b/src/script/lua_api/l_client.cpp @@ -62,7 +62,11 @@ const static CSMFlagDesc flagdesc_csm_restriction[] = { // get_current_modname() int ModApiClient::l_get_current_modname(lua_State *L) { - lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); + std::string s = ScriptApiBase::getCurrentModNameInsecure(L); + if (!s.empty()) + lua_pushstring(L, s.c_str()); + else + lua_pushnil(L); return 1; } @@ -76,30 +80,6 @@ int ModApiClient::l_get_modpath(lua_State *L) return 1; } -// get_last_run_mod() -int ModApiClient::l_get_last_run_mod(lua_State *L) -{ - lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); - std::string current_mod = readParam(L, -1, ""); - if (current_mod.empty()) { - lua_pop(L, 1); - lua_pushstring(L, getScriptApiBase(L)->getOrigin().c_str()); - } - return 1; -} - -// set_last_run_mod(modname) -int ModApiClient::l_set_last_run_mod(lua_State *L) -{ - if (!lua_isstring(L, 1)) - return 0; - - const char *mod = lua_tostring(L, 1); - getScriptApiBase(L)->setOriginDirect(mod); - lua_pushboolean(L, true); - return 1; -} - // print(text) int ModApiClient::l_print(lua_State *L) { @@ -367,8 +347,6 @@ void ModApiClient::Initialize(lua_State *L, int top) API_FCT(send_chat_message); API_FCT(clear_out_chat_queue); API_FCT(get_player_names); - API_FCT(set_last_run_mod); - API_FCT(get_last_run_mod); API_FCT(show_formspec); API_FCT(send_respawn); API_FCT(gettext); diff --git a/src/script/lua_api/l_client.h b/src/script/lua_api/l_client.h index 7eb43e913..e960dc4cf 100644 --- a/src/script/lua_api/l_client.h +++ b/src/script/lua_api/l_client.h @@ -60,12 +60,6 @@ private: // gettext(text) static int l_gettext(lua_State *L); - // get_last_run_mod(n) - static int l_get_last_run_mod(lua_State *L); - - // set_last_run_mod(modname) - static int l_set_last_run_mod(lua_State *L); - // get_node(pos) static int l_get_node_or_nil(lua_State *L); diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index b8daa86a0..39f36a31a 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -34,7 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "daynightratio.h" #include "util/pointedthing.h" #include "mapgen/treegen.h" -#include "emerge.h" +#include "emerge_internal.h" #include "pathfinder.h" #include "face_position_cache.h" #include "remoteplayer.h" @@ -241,7 +241,7 @@ void LuaEmergeAreaCallback(v3s16 blockpos, EmergeAction action, void *param) delete state; } -// Exported functions +/* Exported functions */ // set_node(pos, node) // pos = {x=num, y=num, z=num} @@ -562,6 +562,40 @@ int ModApiEnv::l_add_node_level(lua_State *L) return 1; } +// get_node_boxes(box_type, pos, [node]) -> table +// box_type = string +// pos = {x=num, y=num, z=num} +// node = {name=string, param1=num, param2=num} or nil +int ModApiEnv::l_get_node_boxes(lua_State *L) +{ + GET_ENV_PTR; + + std::string box_type = luaL_checkstring(L, 1); + v3s16 pos = read_v3s16(L, 2); + MapNode n; + if (lua_istable(L, 3)) + n = readnode(L, 3); + else + n = env->getMap().getNode(pos); + + u8 neighbors = n.getNeighbors(pos, &env->getMap()); + const NodeDefManager *ndef = env->getGameDef()->ndef(); + + std::vector boxes; + if (box_type == "node_box") + n.getNodeBoxes(ndef, &boxes, neighbors); + else if (box_type == "collision_box") + n.getCollisionBoxes(ndef, &boxes, neighbors); + else if (box_type == "selection_box") + n.getSelectionBoxes(ndef, &boxes, neighbors); + else + luaL_error(L, "get_node_boxes: box_type is invalid. Allowed values: \"node_box\", \"collision_box\", \"selection_box\""); + + push_aabb3f_vector(L, boxes, BS); + + return 1; +} + // find_nodes_with_meta(pos1, pos2) int ModApiEnv::l_find_nodes_with_meta(lua_State *L) { @@ -1456,6 +1490,7 @@ void ModApiEnv::Initialize(lua_State *L, int top) API_FCT(get_node_level); API_FCT(set_node_level); API_FCT(add_node_level); + API_FCT(get_node_boxes); API_FCT(add_entity); API_FCT(find_nodes_with_meta); API_FCT(get_meta); @@ -1503,3 +1538,189 @@ void ModApiEnv::InitializeClient(lua_State *L, int top) API_FCT(line_of_sight); API_FCT(raycast); } + +#define GET_VM_PTR \ + MMVManip *vm = getVManip(L); \ + if (!vm) \ + return 0 + +// get_node_max_level(pos) +int ModApiEnvVM::l_get_node_max_level(lua_State *L) +{ + GET_VM_PTR; + + v3s16 pos = read_v3s16(L, 1); + MapNode n = vm->getNodeNoExNoEmerge(pos); + lua_pushnumber(L, n.getMaxLevel(getGameDef(L)->ndef())); + return 1; +} + +// get_node_level(pos) +int ModApiEnvVM::l_get_node_level(lua_State *L) +{ + GET_VM_PTR; + + v3s16 pos = read_v3s16(L, 1); + MapNode n = vm->getNodeNoExNoEmerge(pos); + lua_pushnumber(L, n.getLevel(getGameDef(L)->ndef())); + return 1; +} + +// set_node_level(pos, level) +int ModApiEnvVM::l_set_node_level(lua_State *L) +{ + GET_VM_PTR; + + v3s16 pos = read_v3s16(L, 1); + u8 level = 1; + if (lua_isnumber(L, 2)) + level = lua_tonumber(L, 2); + MapNode n = vm->getNodeNoExNoEmerge(pos); + lua_pushnumber(L, n.setLevel(getGameDef(L)->ndef(), level)); + vm->setNodeNoEmerge(pos, n); + return 1; +} + +// add_node_level(pos, level) +int ModApiEnvVM::l_add_node_level(lua_State *L) +{ + GET_VM_PTR; + + v3s16 pos = read_v3s16(L, 1); + u8 level = 1; + if (lua_isnumber(L, 2)) + level = lua_tonumber(L, 2); + MapNode n = vm->getNodeNoExNoEmerge(pos); + lua_pushnumber(L, n.addLevel(getGameDef(L)->ndef(), level)); + vm->setNodeNoEmerge(pos, n); + return 1; +} + +// find_node_near(pos, radius, nodenames, [search_center]) +int ModApiEnvVM::l_find_node_near(lua_State *L) +{ + GET_VM_PTR; + + const NodeDefManager *ndef = getGameDef(L)->ndef(); + + v3s16 pos = read_v3s16(L, 1); + int radius = luaL_checkinteger(L, 2); + std::vector filter; + collectNodeIds(L, 3, ndef, filter); + int start_radius = (lua_isboolean(L, 4) && readParam(L, 4)) ? 0 : 1; + + auto getNode = [&vm] (v3s16 p) -> MapNode { + return vm->getNodeNoExNoEmerge(p); + }; + return findNodeNear(L, pos, radius, filter, start_radius, getNode); +} + +// find_nodes_in_area(minp, maxp, nodenames, [grouped]) +int ModApiEnvVM::l_find_nodes_in_area(lua_State *L) +{ + GET_VM_PTR; + + const NodeDefManager *ndef = getGameDef(L)->ndef(); + + v3s16 minp = read_v3s16(L, 1); + v3s16 maxp = read_v3s16(L, 2); + sortBoxVerticies(minp, maxp); + + checkArea(minp, maxp); + // avoid the loop going out-of-bounds + { + VoxelArea cropped = VoxelArea(minp, maxp).intersect(vm->m_area); + minp = cropped.MinEdge; + maxp = cropped.MaxEdge; + } + + std::vector filter; + collectNodeIds(L, 3, ndef, filter); + + bool grouped = lua_isboolean(L, 4) && readParam(L, 4); + + auto iterate = [&] (auto callback) { + for (s16 z = minp.Z; z <= maxp.Z; z++) + for (s16 y = minp.Y; y <= maxp.Y; y++) { + u32 vi = vm->m_area.index(minp.X, y, z); + for (s16 x = minp.X; x <= maxp.X; x++) { + v3s16 pos(x, y, z); + MapNode n = vm->m_data[vi]; + if (!callback(pos, n)) + return; + ++vi; + } + } + }; + return findNodesInArea(L, ndef, filter, grouped, iterate); +} + +// find_nodes_in_area_under_air(minp, maxp, nodenames) +int ModApiEnvVM::l_find_nodes_in_area_under_air(lua_State *L) +{ + GET_VM_PTR; + + const NodeDefManager *ndef = getGameDef(L)->ndef(); + + v3s16 minp = read_v3s16(L, 1); + v3s16 maxp = read_v3s16(L, 2); + sortBoxVerticies(minp, maxp); + checkArea(minp, maxp); + + std::vector filter; + collectNodeIds(L, 3, ndef, filter); + + auto getNode = [&vm] (v3s16 p) -> MapNode { + return vm->getNodeNoExNoEmerge(p); + }; + return findNodesInAreaUnderAir(L, minp, maxp, filter, getNode); +} + +// spawn_tree(pos, treedef) +int ModApiEnvVM::l_spawn_tree(lua_State *L) +{ + GET_VM_PTR; + + const NodeDefManager *ndef = getGameDef(L)->ndef(); + + v3s16 p0 = read_v3s16(L, 1); + + treegen::TreeDef tree_def; + if (!read_tree_def(L, 2, ndef, tree_def)) + return 0; + + treegen::error e; + if ((e = treegen::make_ltree(*vm, p0, ndef, tree_def)) != treegen::SUCCESS) { + if (e == treegen::UNBALANCED_BRACKETS) { + throw LuaError("spawn_tree(): closing ']' has no matching opening bracket"); + } else { + throw LuaError("spawn_tree(): unknown error"); + } + } + + lua_pushboolean(L, true); + return 1; +} + +MMVManip *ModApiEnvVM::getVManip(lua_State *L) +{ + auto emerge = getEmergeThread(L); + if (emerge) + return emerge->getMapgen()->vm; + return nullptr; +} + +void ModApiEnvVM::InitializeEmerge(lua_State *L, int top) +{ + // other, more trivial functions are in builtin/emerge/env.lua + API_FCT(get_node_max_level); + API_FCT(get_node_level); + API_FCT(set_node_level); + API_FCT(add_node_level); + API_FCT(find_node_near); + API_FCT(find_nodes_in_area); + API_FCT(find_nodes_in_area_under_air); + API_FCT(spawn_tree); +} + +#undef GET_VM_PTR diff --git a/src/script/lua_api/l_env.h b/src/script/lua_api/l_env.h index f1fa680e9..327cf9f75 100644 --- a/src/script/lua_api/l_env.h +++ b/src/script/lua_api/l_env.h @@ -120,6 +120,12 @@ private: // pos = {x=num, y=num, z=num} static int l_add_node_level(lua_State *L); + // get_node_boxes(box_type, pos, [node]) -> table + // box_type = string + // pos = {x=num, y=num, z=num} + // node = {name=string, param1=num, param2=num} or nil + static int l_get_node_boxes(lua_State *L); + // find_nodes_with_meta(pos1, pos2) static int l_find_nodes_with_meta(lua_State *L); @@ -237,6 +243,44 @@ public: static void InitializeClient(lua_State *L, int top); }; +/* + * Duplicates of certain env APIs that operate not on the global + * map but on a VoxelManipulator. This is for emerge scripting. + */ +class ModApiEnvVM : public ModApiEnvBase { +private: + + // get_node_max_level(pos) + static int l_get_node_max_level(lua_State *L); + + // get_node_level(pos) + static int l_get_node_level(lua_State *L); + + // set_node_level(pos) + static int l_set_node_level(lua_State *L); + + // add_node_level(pos) + static int l_add_node_level(lua_State *L); + + // find_node_near(pos, radius, nodenames, [search_center]) + static int l_find_node_near(lua_State *L); + + // find_nodes_in_area(minp, maxp, nodenames, [grouped]) + static int l_find_nodes_in_area(lua_State *L); + + // find_surface_nodes_in_area(minp, maxp, nodenames) + static int l_find_nodes_in_area_under_air(lua_State *L); + + // spawn_tree(pos, treedef) + static int l_spawn_tree(lua_State *L); + + // Helper: get the vmanip we're operating on + static MMVManip *getVManip(lua_State *L); + +public: + static void InitializeEmerge(lua_State *L, int top); +}; + class LuaABM : public ActiveBlockModifier { private: int m_id; diff --git a/src/script/lua_api/l_item.cpp b/src/script/lua_api/l_item.cpp index c68046b5d..4ac998a8f 100644 --- a/src/script/lua_api/l_item.cpp +++ b/src/script/lua_api/l_item.cpp @@ -163,6 +163,9 @@ int LuaItemStack::l_get_metadata(lua_State *L) NO_MAP_LOCK_REQUIRED; LuaItemStack *o = checkObject(L, 1); ItemStack &item = o->m_stack; + + log_deprecated(L, "ItemStack:get_metadata is deprecated", 1, true); + const std::string &value = item.metadata.getString(""); lua_pushlstring(L, value.c_str(), value.size()); return 1; @@ -176,6 +179,8 @@ int LuaItemStack::l_set_metadata(lua_State *L) LuaItemStack *o = checkObject(L, 1); ItemStack &item = o->m_stack; + log_deprecated(L, "ItemStack:set_metadata is deprecated", 1, true); + size_t len = 0; const char *ptr = luaL_checklstring(L, 2, &len); item.metadata.setString("", std::string(ptr, len)); @@ -376,6 +381,22 @@ int LuaItemStack::l_add_wear_by_uses(lua_State *L) return 1; } +// get_wear_bar_params(self) -> table +// Returns the effective wear bar parameters. +// Returns nil if this item has none associated. +int LuaItemStack::l_get_wear_bar_params(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + LuaItemStack *o = checkObject(L, 1); + ItemStack &item = o->m_stack; + auto params = item.getWearBarParams(getGameDef(L)->idef()); + if (params.has_value()) { + push_wear_bar_params(L, *params); + return 1; + } + return 0; +} + // add_item(self, itemstack or itemstring or table or nil) -> itemstack // Returns leftover item stack int LuaItemStack::l_add_item(lua_State *L) @@ -551,6 +572,7 @@ const luaL_Reg LuaItemStack::methods[] = { luamethod(LuaItemStack, get_tool_capabilities), luamethod(LuaItemStack, add_wear), luamethod(LuaItemStack, add_wear_by_uses), + luamethod(LuaItemStack, get_wear_bar_params), luamethod(LuaItemStack, add_item), luamethod(LuaItemStack, item_fits), luamethod(LuaItemStack, take_item), diff --git a/src/script/lua_api/l_item.h b/src/script/lua_api/l_item.h index 55333ec73..243d17d72 100644 --- a/src/script/lua_api/l_item.h +++ b/src/script/lua_api/l_item.h @@ -125,6 +125,11 @@ private: // Returns true if the item is (or was) a tool. static int l_add_wear_by_uses(lua_State *L); + // get_wear_bar_params(self) -> table + // Returns the effective wear bar parameters. + // Returns nil if this item has none associated. + static int l_get_wear_bar_params(lua_State *L); + // add_item(self, itemstack or itemstring or table or nil) -> itemstack // Returns leftover item stack static int l_add_item(lua_State *L); diff --git a/src/script/lua_api/l_itemstackmeta.cpp b/src/script/lua_api/l_itemstackmeta.cpp index 8ce9673db..ebabf7bae 100644 --- a/src/script/lua_api/l_itemstackmeta.cpp +++ b/src/script/lua_api/l_itemstackmeta.cpp @@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_itemstackmeta.h" #include "lua_api/l_internal.h" #include "common/c_content.h" +#include "common/c_converter.h" +#include "tool.h" /* ItemStackMetaRef @@ -58,6 +60,20 @@ int ItemStackMetaRef::l_set_tool_capabilities(lua_State *L) return 0; } +int ItemStackMetaRef::l_set_wear_bar_params(lua_State *L) +{ + ItemStackMetaRef *metaref = checkObject(L, 1); + if (lua_isnoneornil(L, 2)) { + metaref->clearWearBarParams(); + } else if (lua_istable(L, 2) || lua_isstring(L, 2)) { + metaref->setWearBarParams(read_wear_bar_params(L, 2)); + } else { + luaL_typerror(L, 2, "table, ColorString, or nil"); + } + + return 0; +} + ItemStackMetaRef::ItemStackMetaRef(LuaItemStack *istack): istack(istack) { istack->grab(); @@ -102,5 +118,6 @@ const luaL_Reg ItemStackMetaRef::methods[] = { luamethod(MetaDataRef, from_table), luamethod(MetaDataRef, equals), luamethod(ItemStackMetaRef, set_tool_capabilities), + luamethod(ItemStackMetaRef, set_wear_bar_params), {0,0} }; diff --git a/src/script/lua_api/l_itemstackmeta.h b/src/script/lua_api/l_itemstackmeta.h index ac27bcc2c..27adc57e0 100644 --- a/src/script/lua_api/l_itemstackmeta.h +++ b/src/script/lua_api/l_itemstackmeta.h @@ -49,8 +49,19 @@ private: istack->getItem().metadata.clearToolCapabilities(); } + void setWearBarParams(const WearBarParams ¶ms) + { + istack->getItem().metadata.setWearBarParams(params); + } + + void clearWearBarParams() + { + istack->getItem().metadata.clearWearBarParams(); + } + // Exported functions static int l_set_tool_capabilities(lua_State *L); + static int l_set_wear_bar_params(lua_State *L); public: // takes a reference ItemStackMetaRef(LuaItemStack *istack); diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index 5bcf0f228..44b4b57e7 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -363,6 +363,9 @@ int ModApiMainMenu::l_get_content_info(lua_State *L) lua_pushstring(L, spec.name.c_str()); lua_setfield(L, -2, "name"); + lua_pushstring(L, spec.title.c_str()); + lua_setfield(L, -2, "title"); + lua_pushstring(L, spec.type.c_str()); lua_setfield(L, -2, "type"); @@ -383,6 +386,9 @@ int ModApiMainMenu::l_get_content_info(lua_State *L) lua_pushstring(L, spec.path.c_str()); lua_setfield(L, -2, "path"); + lua_pushstring(L, spec.textdomain.c_str()); + lua_setfield(L, -2, "textdomain"); + if (spec.type == "mod") { ModSpec spec; spec.path = path; @@ -432,8 +438,7 @@ int ModApiMainMenu::l_check_mod_configuration(lua_State *L) // Ignore non-string keys if (lua_type(L, -2) != LUA_TSTRING) { throw LuaError( - "Unexpected non-string key in table passed to " - "core.check_mod_configuration"); + "Unexpected non-string key in table passed to core.check_mod_configuration"); } std::string modpath = luaL_checkstring(L, -1); @@ -472,7 +477,6 @@ int ModApiMainMenu::l_check_mod_configuration(lua_State *L) return 1; } - lua_newtable(L); lua_pushboolean(L, modmgr.isConsistent()); @@ -500,7 +504,25 @@ int ModApiMainMenu::l_check_mod_configuration(lua_State *L) index++; } lua_setfield(L, -2, "satisfied_mods"); + return 1; +} +/******************************************************************************/ +int ModApiMainMenu::l_get_content_translation(lua_State *L) +{ + GUIEngine* engine = getGuiEngine(L); + sanity_check(engine != NULL); + + std::string path = luaL_checkstring(L, 1); + std::string domain = luaL_checkstring(L, 2); + std::string string = luaL_checkstring(L, 3); + std::string lang = gettext("LANG_CODE"); + if (lang == "LANG_CODE") + lang = ""; + + auto *translations = engine->getContentTranslations(path, domain, lang); + string = wide_to_utf8(translate_string(utf8_to_wide(string), translations)); + lua_pushstring(L, string.c_str()); return 1; } @@ -911,6 +933,17 @@ int ModApiMainMenu::l_get_video_drivers(lua_State *L) return 1; } +/******************************************************************************/ +int ModApiMainMenu::l_get_language(lua_State *L) +{ + std::string lang = gettext("LANG_CODE"); + if (lang == "LANG_CODE") + lang = ""; + + lua_pushstring(L, lang.c_str()); + return 1; +} + /******************************************************************************/ int ModApiMainMenu::l_gettext(lua_State *L) { @@ -963,7 +996,12 @@ int ModApiMainMenu::l_get_active_driver(lua_State *L) int ModApiMainMenu::l_get_active_renderer(lua_State *L) { - lua_pushstring(L, wide_to_utf8(RenderingEngine::get_video_driver()->getName()).c_str()); +#if IRRLICHT_VERSION_MT_REVISION >= 15 + lua_pushstring(L, RenderingEngine::get_video_driver()->getName()); +#else + auto tmp = wide_to_utf8(RenderingEngine::get_video_driver()->getName()); + lua_pushstring(L, tmp.c_str()); +#endif return 1; } @@ -1097,6 +1135,7 @@ void ModApiMainMenu::Initialize(lua_State *L, int top) API_FCT(get_games); API_FCT(get_content_info); API_FCT(check_mod_configuration); + API_FCT(get_content_translation); API_FCT(start); API_FCT(close); API_FCT(show_keys_menu); @@ -1123,6 +1162,7 @@ void ModApiMainMenu::Initialize(lua_State *L, int top) API_FCT(get_mainmenu_path); API_FCT(show_path_select_dialog); API_FCT(download_file); + API_FCT(get_language); API_FCT(gettext); API_FCT(get_video_drivers); API_FCT(get_window_info); @@ -1163,5 +1203,6 @@ void ModApiMainMenu::InitializeAsync(lua_State *L, int top) API_FCT(download_file); API_FCT(get_min_supp_proto); API_FCT(get_max_supp_proto); + API_FCT(get_language); API_FCT(gettext); } diff --git a/src/script/lua_api/l_mainmenu.h b/src/script/lua_api/l_mainmenu.h index a5fff3872..780e3b5b8 100644 --- a/src/script/lua_api/l_mainmenu.h +++ b/src/script/lua_api/l_mainmenu.h @@ -74,6 +74,8 @@ private: static int l_get_mapgen_names(lua_State *L); + static int l_get_language(lua_State *L); + static int l_gettext(lua_State *L); //packages @@ -84,6 +86,8 @@ private: static int l_check_mod_configuration(lua_State *L); + static int l_get_content_translation(lua_State *L); + //gui static int l_show_keys_menu(lua_State *L); diff --git a/src/script/lua_api/l_mapgen.cpp b/src/script/lua_api/l_mapgen.cpp index 86e0c1c32..b8c6e7105 100644 --- a/src/script/lua_api/l_mapgen.cpp +++ b/src/script/lua_api/l_mapgen.cpp @@ -26,7 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/serialize.h" #include "server.h" #include "environment.h" -#include "emerge.h" +#include "emerge_internal.h" #include "mapgen/mg_biome.h" #include "mapgen/mg_ore.h" #include "mapgen/mg_decoration.h" @@ -482,7 +482,7 @@ int ModApiMapgen::l_get_biome_id(lua_State *L) const char *biome_str = luaL_checkstring(L, 1); - const BiomeManager *bmgr = getServer(L)->getEmergeManager()->getBiomeManager(); + const BiomeManager *bmgr = getEmergeManager(L)->getBiomeManager(); if (!bmgr) return 0; @@ -504,7 +504,7 @@ int ModApiMapgen::l_get_biome_name(lua_State *L) int biome_id = luaL_checkinteger(L, 1); - const BiomeManager *bmgr = getServer(L)->getEmergeManager()->getBiomeManager(); + const BiomeManager *bmgr = getEmergeManager(L)->getBiomeManager(); if (!bmgr) return 0; @@ -523,8 +523,7 @@ int ModApiMapgen::l_get_heat(lua_State *L) v3s16 pos = read_v3s16(L, 1); - const BiomeGen *biomegen = getServer(L)->getEmergeManager()->getBiomeGen(); - + const BiomeGen *biomegen = getBiomeGen(L); if (!biomegen || biomegen->getType() != BIOMEGEN_ORIGINAL) return 0; @@ -544,8 +543,7 @@ int ModApiMapgen::l_get_humidity(lua_State *L) v3s16 pos = read_v3s16(L, 1); - const BiomeGen *biomegen = getServer(L)->getEmergeManager()->getBiomeGen(); - + const BiomeGen *biomegen = getBiomeGen(L); if (!biomegen || biomegen->getType() != BIOMEGEN_ORIGINAL) return 0; @@ -565,7 +563,7 @@ int ModApiMapgen::l_get_biome_data(lua_State *L) v3s16 pos = read_v3s16(L, 1); - const BiomeGen *biomegen = getServer(L)->getEmergeManager()->getBiomeGen(); + const BiomeGen *biomegen = getBiomeGen(L); if (!biomegen) return 0; @@ -607,8 +605,7 @@ int ModApiMapgen::l_get_mapgen_object(lua_State *L) enum MapgenObject mgobj = (MapgenObject)mgobjint; - EmergeManager *emerge = getServer(L)->getEmergeManager(); - Mapgen *mg = emerge->getCurrentMapgen(); + Mapgen *mg = getMapgen(L); if (!mg) throw LuaError("Must only be called in a mapgen thread!"); @@ -683,8 +680,7 @@ int ModApiMapgen::l_get_mapgen_object(lua_State *L) return 1; } case MGOBJ_GENNOTIFY: { - std::map >event_map; - + std::map> event_map; mg->gennotify.getEvents(event_map); lua_createtable(L, 0, event_map.size()); @@ -699,6 +695,24 @@ int ModApiMapgen::l_get_mapgen_object(lua_State *L) lua_setfield(L, -2, it->first.c_str()); } + // push user-defined data + auto &custom_map = mg->gennotify.getCustomData(); + + lua_createtable(L, 0, custom_map.size()); + lua_getglobal(L, "core"); + lua_getfield(L, -1, "deserialize"); + lua_remove(L, -2); // remove 'core' + for (const auto &it : custom_map) { + lua_pushvalue(L, -1); // deserialize func + lua_pushlstring(L, it.second.c_str(), it.second.size()); + lua_pushboolean(L, true); + lua_call(L, 2, 1); + + lua_setfield(L, -3, it.first.c_str()); // put into table + } + lua_pop(L, 1); // remove func + lua_setfield(L, -2, "custom"); // put into top-level table + return 1; } } @@ -728,6 +742,31 @@ int ModApiMapgen::l_get_spawn_level(lua_State *L) } +// get_seed([add]) +int ModApiMapgen::l_get_seed(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + // This exists to + // 1. not duplicate the truncation logic from Mapgen::Mapgen() once more + // 2. because I don't trust myself to do it correctly in Lua + + auto *emerge = getEmergeManager(L); + if (!emerge || !emerge->mgparams) + return 0; + + int add = 0; + if (lua_isnumber(L, 1)) + add = luaL_checkint(L, 1); + + s32 seed = (s32)emerge->mgparams->seed; + seed += add; + + lua_pushinteger(L, seed); + return 1; +} + + int ModApiMapgen::l_get_mapgen_params(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -737,8 +776,8 @@ int ModApiMapgen::l_get_mapgen_params(lua_State *L) std::string value; - MapSettingsManager *settingsmgr = - getServer(L)->getEmergeManager()->map_settings_mgr; + const MapSettingsManager *settingsmgr = + getEmergeManager(L)->map_settings_mgr; lua_newtable(L); @@ -810,7 +849,8 @@ int ModApiMapgen::l_get_mapgen_edges(lua_State *L) { NO_MAP_LOCK_REQUIRED; - MapSettingsManager *settingsmgr = getServer(L)->getEmergeManager()->map_settings_mgr; + const MapSettingsManager *settingsmgr = + getEmergeManager(L)->map_settings_mgr; // MapSettingsManager::makeMapgenParams cannot be used here because it would // make mapgen settings immutable from then on. Mapgen settings should stay @@ -846,8 +886,8 @@ int ModApiMapgen::l_get_mapgen_setting(lua_State *L) NO_MAP_LOCK_REQUIRED; std::string value; - MapSettingsManager *settingsmgr = - getServer(L)->getEmergeManager()->map_settings_mgr; + const MapSettingsManager *settingsmgr = + getEmergeManager(L)->map_settings_mgr; const char *name = luaL_checkstring(L, 1); if (!settingsmgr->getMapSetting(name, &value)) @@ -863,8 +903,8 @@ int ModApiMapgen::l_get_mapgen_setting_noiseparams(lua_State *L) NO_MAP_LOCK_REQUIRED; NoiseParams np; - MapSettingsManager *settingsmgr = - getServer(L)->getEmergeManager()->map_settings_mgr; + const MapSettingsManager *settingsmgr = + getEmergeManager(L)->map_settings_mgr; const char *name = luaL_checkstring(L, 1); if (!settingsmgr->getMapSettingNoiseParams(name, &np)) @@ -964,7 +1004,7 @@ int ModApiMapgen::l_get_noiseparams(lua_State *L) } -// set_gen_notify(flags, {deco_id_table}) +// set_gen_notify(flags, {deco_ids}, {custom_ids}) int ModApiMapgen::l_set_gen_notify(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -986,11 +1026,26 @@ int ModApiMapgen::l_set_gen_notify(lua_State *L) } } + if (lua_istable(L, 3)) { + lua_pushnil(L); + while (lua_next(L, 3)) { + emerge->gen_notify_on_custom.insert(readParam(L, -1)); + lua_pop(L, 1); + } + } + + // Clear sets if relevant flag disabled + if ((emerge->gen_notify_on & (1 << GENNOTIFY_DECORATION)) == 0) + emerge->gen_notify_on_deco_ids.clear(); + if ((emerge->gen_notify_on & (1 << GENNOTIFY_CUSTOM)) == 0) + emerge->gen_notify_on_custom.clear(); + return 0; } // get_gen_notify() +// returns flagstring, {deco_ids}, {custom_ids}) int ModApiMapgen::l_get_gen_notify(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -999,13 +1054,43 @@ int ModApiMapgen::l_get_gen_notify(lua_State *L) push_flags_string(L, flagdesc_gennotify, emerge->gen_notify_on, emerge->gen_notify_on); - lua_newtable(L); + lua_createtable(L, emerge->gen_notify_on_deco_ids.size(), 0); int i = 1; - for (u32 gen_notify_on_deco_id : emerge->gen_notify_on_deco_ids) { - lua_pushnumber(L, gen_notify_on_deco_id); + for (u32 id : emerge->gen_notify_on_deco_ids) { + lua_pushnumber(L, id); lua_rawseti(L, -2, i++); } - return 2; + + lua_createtable(L, emerge->gen_notify_on_custom.size(), 0); + int j = 1; + for (const auto &id : emerge->gen_notify_on_custom) { + lua_pushstring(L, id.c_str()); + lua_rawseti(L, -2, j++); + } + + return 3; +} + + +// save_gen_notify(custom_id, data) [in emerge thread] +int ModApiMapgen::l_save_gen_notify(lua_State *L) +{ + auto *emerge = getEmergeThread(L); + + std::string key = readParam(L, 1); + + lua_getglobal(L, "core"); + lua_getfield(L, -1, "serialize"); + lua_remove(L, -2); // remove 'core' + lua_pushvalue(L, 2); + lua_call(L, 1, 1); + std::string val = readParam(L, -1); + lua_pop(L, 1); + + bool set = emerge->getMapgen()->gennotify.setCustom(key, val); + + lua_pushboolean(L, set); + return 1; } @@ -1020,8 +1105,7 @@ int ModApiMapgen::l_get_decoration_id(lua_State *L) return 0; const DecorationManager *dmgr = - getServer(L)->getEmergeManager()->getDecorationManager(); - + getEmergeManager(L)->getDecorationManager(); if (!dmgr) return 0; @@ -1452,20 +1536,26 @@ int ModApiMapgen::l_clear_registered_schematics(lua_State *L) } -// generate_ores(vm, p1, p2, [ore_id]) +// generate_ores(vm, p1, p2) int ModApiMapgen::l_generate_ores(lua_State *L) { NO_MAP_LOCK_REQUIRED; - EmergeManager *emerge = getServer(L)->getEmergeManager(); + auto *emerge = getEmergeManager(L); if (!emerge || !emerge->mgparams) return 0; + OreManager *oremgr; + if (auto mg = getMapgen(L)) + oremgr = mg->m_emerge->oremgr; + else + oremgr = emerge->oremgr; + Mapgen mg; // Intentionally truncates to s32, see Mapgen::Mapgen() mg.seed = (s32)emerge->mgparams->seed; mg.vm = checkObject(L, 1)->vm; - mg.ndef = getServer(L)->getNodeDefManager(); + mg.ndef = emerge->ndef; v3s16 pmin = lua_istable(L, 2) ? check_v3s16(L, 2) : mg.vm->m_area.MinEdge + v3s16(1,1,1) * MAP_BLOCKSIZE; @@ -1475,26 +1565,32 @@ int ModApiMapgen::l_generate_ores(lua_State *L) u32 blockseed = Mapgen::getBlockSeed(pmin, mg.seed); - emerge->oremgr->placeAllOres(&mg, blockseed, pmin, pmax); + oremgr->placeAllOres(&mg, blockseed, pmin, pmax); return 0; } -// generate_decorations(vm, p1, p2, [deco_id]) +// generate_decorations(vm, p1, p2) int ModApiMapgen::l_generate_decorations(lua_State *L) { NO_MAP_LOCK_REQUIRED; - EmergeManager *emerge = getServer(L)->getEmergeManager(); + auto *emerge = getEmergeManager(L); if (!emerge || !emerge->mgparams) return 0; + DecorationManager *decomgr; + if (auto mg = getMapgen(L)) + decomgr = mg->m_emerge->decomgr; + else + decomgr = emerge->decomgr; + Mapgen mg; // Intentionally truncates to s32, see Mapgen::Mapgen() mg.seed = (s32)emerge->mgparams->seed; mg.vm = checkObject(L, 1)->vm; - mg.ndef = getServer(L)->getNodeDefManager(); + mg.ndef = emerge->ndef; v3s16 pmin = lua_istable(L, 2) ? check_v3s16(L, 2) : mg.vm->m_area.MinEdge + v3s16(1,1,1) * MAP_BLOCKSIZE; @@ -1504,7 +1600,7 @@ int ModApiMapgen::l_generate_decorations(lua_State *L) u32 blockseed = Mapgen::getBlockSeed(pmin, mg.seed); - emerge->decomgr->placeAllDecos(&mg, blockseed, pmin, pmax); + decomgr->placeAllDecos(&mg, blockseed, pmin, pmax); return 0; } @@ -1629,7 +1725,11 @@ int ModApiMapgen::l_place_schematic_on_vmanip(lua_State *L) { NO_MAP_LOCK_REQUIRED; - SchematicManager *schemmgr = getServer(L)->getEmergeManager()->schemmgr; + SchematicManager *schemmgr; + if (auto mg = getMapgen(L)) + schemmgr = mg->m_emerge->schemmgr; + else + schemmgr = getServer(L)->getEmergeManager()->schemmgr; //// Read VoxelManip object MMVManip *vm = checkObject(L, 1)->vm; @@ -1677,7 +1777,7 @@ int ModApiMapgen::l_serialize_schematic(lua_State *L) { NO_MAP_LOCK_REQUIRED; - const SchematicManager *schemmgr = getServer(L)->getEmergeManager()->getSchematicManager(); + const SchematicManager *schemmgr = getEmergeManager(L)->getSchematicManager(); //// Read options bool use_comments = getboolfield_default(L, 3, "lua_use_comments", false); @@ -1727,8 +1827,7 @@ int ModApiMapgen::l_read_schematic(lua_State *L) { NO_MAP_LOCK_REQUIRED; - const SchematicManager *schemmgr = - getServer(L)->getEmergeManager()->getSchematicManager(); + const SchematicManager *schemmgr = getEmergeManager(L)->getSchematicManager(); const NodeDefManager *ndef = getGameDef(L)->ndef(); //// Read options @@ -1806,17 +1905,22 @@ int ModApiMapgen::l_read_schematic(lua_State *L) int ModApiMapgen::update_liquids(lua_State *L, MMVManip *vm) { - GET_ENV_PTR; + UniqueQueue *trans_liquid; + if (auto emerge = getEmergeThread(L)) { + trans_liquid = emerge->m_trans_liquid; + } else { + GET_ENV_PTR; + trans_liquid = &env->getServerMap().m_transforming_liquid; + } + assert(trans_liquid); - ServerMap *map = &(env->getServerMap()); - const NodeDefManager *ndef = getServer(L)->getNodeDefManager(); + const NodeDefManager *ndef = getGameDef(L)->ndef(); Mapgen mg; mg.vm = vm; mg.ndef = ndef; - mg.updateLiquid(&map->m_transforming_liquid, - vm->m_area.MinEdge, vm->m_area.MaxEdge); + mg.updateLiquid(trans_liquid, vm->m_area.MinEdge, vm->m_area.MaxEdge); return 0; } @@ -1824,7 +1928,7 @@ int ModApiMapgen::calc_lighting(lua_State *L, MMVManip *vm, v3s16 pmin, v3s16 pmax, bool propagate_shadow) { const NodeDefManager *ndef = getGameDef(L)->ndef(); - EmergeManager *emerge = getServer(L)->getEmergeManager(); + auto emerge = getEmergeManager(L); assert(vm->m_area.contains(VoxelArea(pmin, pmax))); @@ -1850,6 +1954,35 @@ int ModApiMapgen::set_lighting(lua_State *L, MMVManip *vm, return 0; } +const EmergeManager *ModApiMapgen::getEmergeManager(lua_State *L) +{ + auto emerge = getEmergeThread(L); + if (emerge) + return emerge->getEmergeManager(); + return getServer(L)->getEmergeManager(); +} + +const BiomeGen *ModApiMapgen::getBiomeGen(lua_State *L) +{ + // path 1: we're in the emerge environment + auto emerge = getEmergeThread(L); + if (emerge) + return emerge->getMapgen()->m_emerge->biomegen; + // path 2: we're in the server environment + auto manager = getServer(L)->getEmergeManager(); + return manager->getBiomeGen(); +} + +Mapgen *ModApiMapgen::getMapgen(lua_State *L) +{ + // path 1 + auto emerge = getEmergeThread(L); + if (emerge) + return emerge->getMapgen(); + // path 2 + return getServer(L)->getEmergeManager()->getCurrentMapgen(); +} + void ModApiMapgen::Initialize(lua_State *L, int top) { API_FCT(get_biome_id); @@ -1891,3 +2024,28 @@ void ModApiMapgen::Initialize(lua_State *L, int top) API_FCT(serialize_schematic); API_FCT(read_schematic); } + +void ModApiMapgen::InitializeEmerge(lua_State *L, int top) +{ + API_FCT(get_biome_id); + API_FCT(get_biome_name); + API_FCT(get_heat); + API_FCT(get_humidity); + API_FCT(get_biome_data); + API_FCT(get_mapgen_object); + + API_FCT(get_seed); + API_FCT(get_mapgen_params); + API_FCT(get_mapgen_edges); + API_FCT(get_mapgen_setting); + API_FCT(get_mapgen_setting_noiseparams); + API_FCT(get_noiseparams); + API_FCT(get_decoration_id); + API_FCT(save_gen_notify); + + API_FCT(generate_ores); + API_FCT(generate_decorations); + API_FCT(place_schematic_on_vmanip); + API_FCT(serialize_schematic); + API_FCT(read_schematic); +} diff --git a/src/script/lua_api/l_mapgen.h b/src/script/lua_api/l_mapgen.h index e7984b2dd..c540057b7 100644 --- a/src/script/lua_api/l_mapgen.h +++ b/src/script/lua_api/l_mapgen.h @@ -25,6 +25,9 @@ with this program; if not, write to the Free Software Foundation, Inc., typedef u16 biome_t; // copy from mg_biome.h to avoid an unnecessary include class MMVManip; +class BiomeManager; +class BiomeGen; +class Mapgen; class ModApiMapgen : public ModApiBase { @@ -68,6 +71,9 @@ private: // get_mapgen_edges([mapgen_limit[, chunksize]]) static int l_get_mapgen_edges(lua_State *L); + // get_seed([add]) + static int l_get_seed(lua_State *L); + // get_mapgen_setting(name) static int l_get_mapgen_setting(lua_State *L); @@ -86,12 +92,15 @@ private: // get_noiseparam_defaults(name) static int l_get_noiseparams(lua_State *L); - // set_gen_notify(flags, {deco_id_table}) + // set_gen_notify(flags, {deco_ids}, {ud_ids}) static int l_set_gen_notify(lua_State *L); // get_gen_notify() static int l_get_gen_notify(lua_State *L); + // save_gen_notify(ud_id, data) + static int l_save_gen_notify(lua_State *L); + // get_decoration_id(decoration_name) // returns the decoration ID as used in gennotify static int l_get_decoration_id(lua_State *L); @@ -158,8 +167,18 @@ private: static int set_lighting(lua_State *L, MMVManip *vm, v3s16 pmin, v3s16 pmax, u8 light); + // Helpers + + // get a read-only(!) EmergeManager + static const EmergeManager *getEmergeManager(lua_State *L); + // get the thread-local or global BiomeGen (still read-only) + static const BiomeGen *getBiomeGen(lua_State *L); + // get the thread-local mapgen + static Mapgen *getMapgen(lua_State *L); + public: static void Initialize(lua_State *L, int top); + static void InitializeEmerge(lua_State *L, int top); static struct EnumString es_BiomeTerrainType[]; static struct EnumString es_DecorationType[]; diff --git a/src/script/lua_api/l_metadata.cpp b/src/script/lua_api/l_metadata.cpp index 52d7617d1..e5144259f 100644 --- a/src/script/lua_api/l_metadata.cpp +++ b/src/script/lua_api/l_metadata.cpp @@ -115,9 +115,13 @@ int MetaDataRef::l_set_string(lua_State *L) MetaDataRef *ref = checkAnyMetadata(L, 1); std::string name = luaL_checkstring(L, 2); - size_t len = 0; - const char *s = lua_tolstring(L, 3, &len); - std::string str(s, len); + std::string_view str; + if (!lua_isnoneornil(L, 3)) { + str = readParam(L, 3); + } else { + log_deprecated(L, "Value passed to set_string is nil. This behaviour is" + " undocumented and will result in an error in the future.", 1, true); + } IMetadata *meta = ref->getmeta(!str.empty()); if (meta != NULL && meta->setString(name, str)) @@ -300,9 +304,8 @@ bool MetaDataRef::handleFromTable(lua_State *L, int table, IMetadata *meta) while (lua_next(L, fieldstable) != 0) { // key at index -2 and value at index -1 std::string name = readParam(L, -2); - size_t cl; - const char *cs = lua_tolstring(L, -1, &cl); - meta->setString(name, std::string(cs, cl)); + auto value = readParam(L, -1); + meta->setString(name, value); lua_pop(L, 1); // Remove value, keep key for next iteration } lua_pop(L, 1); diff --git a/src/script/lua_api/l_minimap.cpp b/src/script/lua_api/l_minimap.cpp index d70063c51..fcaf89bbb 100644 --- a/src/script/lua_api/l_minimap.cpp +++ b/src/script/lua_api/l_minimap.cpp @@ -132,9 +132,6 @@ int LuaMinimap::l_show(lua_State *L) if (!g_settings->getBool("enable_minimap")) return 1; - Client *client = getClient(L); - assert(client); - LuaMinimap *ref = checkObject(L, 1); Minimap *m = getobject(ref); @@ -144,15 +141,11 @@ int LuaMinimap::l_show(lua_State *L) if (m->getModeIndex() == 0 && m->getMaxModeIndex() > 0) m->setModeIndex(1); - client->showMinimap(true); return 1; } int LuaMinimap::l_hide(lua_State *L) { - Client *client = getClient(L); - assert(client); - LuaMinimap *ref = checkObject(L, 1); Minimap *m = getobject(ref); @@ -162,7 +155,6 @@ int LuaMinimap::l_hide(lua_State *L) if (m->getModeIndex() != 0) m->setModeIndex(0); - client->showMinimap(false); return 1; } diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index 764cf87fe..c291e605a 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -326,7 +326,7 @@ int ObjectRef::l_set_wielded_item(lua_State *L) bool success = sao->setWieldedItem(item); if (success && sao->getType() == ACTIVEOBJECT_TYPE_PLAYER) { - getServer(L)->SendInventory((PlayerSAO *)sao, true); + getServer(L)->SendInventory(((PlayerSAO *)sao)->getPlayer(), true); } lua_pushboolean(L, success); return 1; diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index 264e88801..7fd086910 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -167,26 +167,6 @@ int ModApiServer::l_get_player_information(lua_State *L) return 1; } - /* - Be careful not to introduce a depdendency on the connection to - the peer here. This function is >>REQUIRED<< to still be able to return - values even when the peer unexpectedly disappears. - Hence all the ConInfo values here are optional. - */ - - auto getConInfo = [&] (con::rtt_stat_type type, float *value) -> bool { - return server->getClientConInfo(player->getPeerId(), type, value); - }; - - float min_rtt, max_rtt, avg_rtt, min_jitter, max_jitter, avg_jitter; - bool have_con_info = - getConInfo(con::MIN_RTT, &min_rtt) && - getConInfo(con::MAX_RTT, &max_rtt) && - getConInfo(con::AVG_RTT, &avg_rtt) && - getConInfo(con::MIN_JITTER, &min_jitter) && - getConInfo(con::MAX_JITTER, &max_jitter) && - getConInfo(con::AVG_JITTER, &avg_jitter); - ClientInfo info; if (!server->getClientInfo(player->getPeerId(), info)) { warningstream << FUNCTION_NAME << ": no client info?!" << std::endl; @@ -211,6 +191,26 @@ int ModApiServer::l_get_player_information(lua_State *L) } lua_settable(L, table); + /* + Be careful not to introduce a depdendency on the connection to + the peer here. This function is >>REQUIRED<< to still be able to return + values even when the peer unexpectedly disappears. + Hence all the ConInfo values here are optional. + */ + + auto getConInfo = [&] (con::rtt_stat_type type, float *value) -> bool { + return server->getClientConInfo(player->getPeerId(), type, value); + }; + + float min_rtt, max_rtt, avg_rtt, min_jitter, max_jitter, avg_jitter; + bool have_con_info = + getConInfo(con::MIN_RTT, &min_rtt) && + getConInfo(con::MAX_RTT, &max_rtt) && + getConInfo(con::AVG_RTT, &avg_rtt) && + getConInfo(con::MIN_JITTER, &min_jitter) && + getConInfo(con::MAX_JITTER, &max_jitter) && + getConInfo(con::AVG_JITTER, &avg_jitter); + if (have_con_info) { // may be missing lua_pushstring(L, "min_rtt"); lua_pushnumber(L, min_rtt); @@ -440,7 +440,11 @@ int ModApiServer::l_show_formspec(lua_State *L) int ModApiServer::l_get_current_modname(lua_State *L) { NO_MAP_LOCK_REQUIRED; - lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); + std::string s = ScriptApiBase::getCurrentModNameInsecure(L); + if (!s.empty()) + lua_pushstring(L, s.c_str()); + else + lua_pushnil(L); return 1; } @@ -545,32 +549,57 @@ int ModApiServer::l_dynamic_add_media(lua_State *L) { NO_MAP_LOCK_REQUIRED; - if (!getEnv(L)) - throw LuaError("Dynamic media cannot be added before server has started up"); Server *server = getServer(L); + const bool at_startup = !getEnv(L); - std::string filepath; - std::string to_player; - bool ephemeral = false; + std::string tmp; + Server::DynamicMediaArgs args; if (lua_istable(L, 1)) { - getstringfield(L, 1, "filepath", filepath); - getstringfield(L, 1, "to_player", to_player); - getboolfield(L, 1, "ephemeral", ephemeral); + getstringfield(L, 1, "filename", args.filename); + if (getstringfield(L, 1, "filepath", tmp)) + args.filepath = tmp; + args.data.emplace(); + if (!getstringfield(L, 1, "filedata", *args.data)) + args.data.reset(); + getstringfield(L, 1, "to_player", args.to_player); + getboolfield(L, 1, "ephemeral", args.ephemeral); } else { - filepath = readParam(L, 1); + tmp = readParam(L, 1); + args.filepath = tmp; + } + if (at_startup) { + if (!lua_isnoneornil(L, 2)) + throw LuaError("must be called without callback at load-time"); + // In order to keep edge cases to a minimum actually use an empty function. + int err = luaL_loadstring(L, ""); + SANITY_CHECK(err == 0); + lua_replace(L, 2); + } else { + luaL_checktype(L, 2, LUA_TFUNCTION); } - if (filepath.empty()) - luaL_typerror(L, 1, "non-empty string"); - luaL_checktype(L, 2, LUA_TFUNCTION); - CHECK_SECURE_PATH(L, filepath.c_str(), false); + // validate + if (args.filepath) { + if (args.filepath->empty()) + throw LuaError("filepath must be non-empty"); + if (args.data) + throw LuaError("cannot provide both filepath and filedata"); + } else if (args.data) { + if (args.filename.empty()) + throw LuaError("filename required"); + } else { + throw LuaError("either filepath or filedata must be provided"); + } - u32 token = server->getScriptIface()->allocateDynamicMediaCallback(L, 2); + if (args.filepath) + CHECK_SECURE_PATH(L, args.filepath->c_str(), false); - bool ok = server->dynamicAddMedia(filepath, token, to_player, ephemeral); + args.token = server->getScriptIface()->allocateDynamicMediaCallback(L, 2); + + bool ok = server->dynamicAddMedia(args); if (!ok) - server->getScriptIface()->freeDynamicMediaCallback(token); + server->getScriptIface()->freeDynamicMediaCallback(args.token); lua_pushboolean(L, ok); return 1; @@ -631,17 +660,32 @@ int ModApiServer::l_register_async_dofile(lua_State *L) std::string path = readParam(L, 1); CHECK_SECURE_PATH(L, path.c_str(), false); - // Find currently running mod name (only at init time) - lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); - if (!lua_isstring(L, -1)) - return 0; - std::string modname = readParam(L, -1); + std::string modname = ScriptApiBase::getCurrentModNameInsecure(L); + if (modname.empty()) + throw ModError("cannot determine mod name"); getServer(L)->m_async_init_files.emplace_back(modname, path); lua_pushboolean(L, true); return 1; } +// register_mapgen_script(path) +int ModApiServer::l_register_mapgen_script(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + std::string path = readParam(L, 1); + CHECK_SECURE_PATH(L, path.c_str(), false); + + std::string modname = ScriptApiBase::getCurrentModNameInsecure(L); + if (modname.empty()) + throw ModError("cannot determine mod name"); + + getServer(L)->m_mapgen_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) @@ -705,6 +749,8 @@ void ModApiServer::Initialize(lua_State *L, int top) API_FCT(do_async_callback); API_FCT(register_async_dofile); API_FCT(serialize_roundtrip); + + API_FCT(register_mapgen_script); } void ModApiServer::InitializeAsync(lua_State *L, int top) diff --git a/src/script/lua_api/l_server.h b/src/script/lua_api/l_server.h index 29fea7677..33dd814b4 100644 --- a/src/script/lua_api/l_server.h +++ b/src/script/lua_api/l_server.h @@ -118,6 +118,9 @@ private: // register_async_dofile(path) static int l_register_async_dofile(lua_State *L); + // register_mapgen_script(path) + static int l_register_mapgen_script(lua_State *L); + // serialize_roundtrip(obj) static int l_serialize_roundtrip(lua_State *L); diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index 351d12205..f98eb0288 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -42,6 +42,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "version.h" #include "util/hex.h" #include "util/sha1.h" +#include "util/sha256.h" #include "util/png.h" #include @@ -318,8 +319,7 @@ int ModApiUtil::l_compress(lua_State *L) { NO_MAP_LOCK_REQUIRED; - size_t size; - const char *data = luaL_checklstring(L, 1, &size); + auto data = readParam(L, 1); LuaCompressMethod method = get_compress_method(L, 2); @@ -330,13 +330,13 @@ int ModApiUtil::l_compress(lua_State *L) if (!lua_isnoneornil(L, 3)) level = readParam(L, 3); - compressZlib(reinterpret_cast(data), size, os, level); + compressZlib(data, os, level); } else if (method == LUA_COMPRESS_METHOD_ZSTD) { int level = ZSTD_CLEVEL_DEFAULT; if (!lua_isnoneornil(L, 3)) level = readParam(L, 3); - compressZstd(reinterpret_cast(data), size, os, level); + compressZstd(data, os, level); } std::string out = os.str(); @@ -350,12 +350,12 @@ int ModApiUtil::l_decompress(lua_State *L) { NO_MAP_LOCK_REQUIRED; - size_t size; - const char *data = luaL_checklstring(L, 1, &size); + auto data = readParam(L, 1); LuaCompressMethod method = get_compress_method(L, 2); - std::istringstream is(std::string(data, size), std::ios_base::binary); + // FIXME: zero copy possible in c++26 or with custom rdbuf + std::istringstream is(std::string(data), std::ios_base::binary); std::ostringstream os(std::ios_base::binary); if (method == LUA_COMPRESS_METHOD_DEFLATE) { @@ -375,10 +375,9 @@ int ModApiUtil::l_encode_base64(lua_State *L) { NO_MAP_LOCK_REQUIRED; - size_t size; - const char *data = luaL_checklstring(L, 1, &size); + auto data = readParam(L, 1); - std::string out = base64_encode((const unsigned char *)(data), size); + std::string out = base64_encode(data); lua_pushlstring(L, out.data(), out.size()); return 1; @@ -389,9 +388,7 @@ int ModApiUtil::l_decode_base64(lua_State *L) { NO_MAP_LOCK_REQUIRED; - size_t size; - const char *d = luaL_checklstring(L, 1, &size); - const std::string data = std::string(d, size); + auto data = readParam(L, 1); if (!base64_is_valid(data)) { lua_pushnil(L); @@ -487,12 +484,11 @@ int ModApiUtil::l_safe_file_write(lua_State *L) { NO_MAP_LOCK_REQUIRED; const char *path = luaL_checkstring(L, 1); - size_t size; - const char *content = luaL_checklstring(L, 2, &size); + auto content = readParam(L, 2); CHECK_SECURE_PATH(L, path, true); - bool ret = fs::safeWriteToFile(path, std::string(content, size)); + bool ret = fs::safeWriteToFile(path, content); lua_pushboolean(L, ret); return 1; @@ -549,18 +545,16 @@ int ModApiUtil::l_get_version(lua_State *L) int ModApiUtil::l_sha1(lua_State *L) { NO_MAP_LOCK_REQUIRED; - size_t size; - const char *data = luaL_checklstring(L, 1, &size); + + auto data = readParam(L, 1); bool hex = !lua_isboolean(L, 2) || !readParam(L, 2); // Compute actual checksum of data std::string data_sha1; { SHA1 ctx; - ctx.addBytes(data, size); - unsigned char *data_tmpdigest = ctx.getDigest(); - data_sha1.assign((char*) data_tmpdigest, 20); - free(data_tmpdigest); + ctx.addBytes(data); + data_sha1 = ctx.getDigest(); } if (hex) { @@ -573,6 +567,27 @@ int ModApiUtil::l_sha1(lua_State *L) return 1; } +int ModApiUtil::l_sha256(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + auto data = readParam(L, 1); + bool hex = !lua_isboolean(L, 2) || !readParam(L, 2); + + std::string data_sha256; + data_sha256.resize(SHA256_DIGEST_LENGTH); + SHA256(reinterpret_cast(data.data()), data.size(), + reinterpret_cast(data_sha256.data())); + + if (hex) { + lua_pushstring(L, hex_encode(data_sha256).c_str()); + } else { + lua_pushlstring(L, data_sha256.data(), data_sha256.size()); + } + + return 1; +} + // colorspec_to_colorstring(colorspec) int ModApiUtil::l_colorspec_to_colorstring(lua_State *L) { @@ -632,12 +647,10 @@ int ModApiUtil::l_get_last_run_mod(lua_State *L) { NO_MAP_LOCK_REQUIRED; - lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); - std::string current_mod = readParam(L, -1, ""); - if (current_mod.empty()) { - lua_pop(L, 1); - lua_pushstring(L, getScriptApiBase(L)->getOrigin().c_str()); - } + std::string current_mod = ScriptApiBase::getCurrentModNameInsecure(L); + if (current_mod.empty()) + current_mod = getScriptApiBase(L)->getOrigin(); + lua_pushstring(L, current_mod.c_str()); return 1; } @@ -656,8 +669,8 @@ int ModApiUtil::l_urlencode(lua_State *L) { NO_MAP_LOCK_REQUIRED; - const char *value = luaL_checkstring(L, 1); - lua_pushstring(L, urlencode(value).c_str()); + auto s = readParam(L, 1); + lua_pushstring(L, urlencode(s).c_str()); return 1; } @@ -699,6 +712,7 @@ void ModApiUtil::Initialize(lua_State *L, int top) API_FCT(get_version); API_FCT(sha1); + API_FCT(sha256); API_FCT(colorspec_to_colorstring); API_FCT(colorspec_to_bytes); @@ -732,9 +746,13 @@ void ModApiUtil::InitializeClient(lua_State *L, int top) API_FCT(get_version); API_FCT(sha1); + API_FCT(sha256); API_FCT(colorspec_to_colorstring); API_FCT(colorspec_to_bytes); + API_FCT(get_last_run_mod); + API_FCT(set_last_run_mod); + API_FCT(urlencode); LuaSettings::create(L, g_settings, g_settings_path); @@ -765,13 +783,14 @@ void ModApiUtil::InitializeAsync(lua_State *L, int top) API_FCT(get_dir_list); API_FCT(safe_file_write); - API_FCT(request_insecure_environment); + // no request_insecure_environment here! mod origins are not tracked securely here. API_FCT(encode_base64); API_FCT(decode_base64); API_FCT(get_version); API_FCT(sha1); + API_FCT(sha256); API_FCT(colorspec_to_colorstring); API_FCT(colorspec_to_bytes); diff --git a/src/script/lua_api/l_util.h b/src/script/lua_api/l_util.h index 07c4b86eb..056e09090 100644 --- a/src/script/lua_api/l_util.h +++ b/src/script/lua_api/l_util.h @@ -113,6 +113,9 @@ private: // sha1(string, raw) static int l_sha1(lua_State *L); + // sha256(string, raw) + static int l_sha256(lua_State *L); + // colorspec_to_colorstring(colorspec) static int l_colorspec_to_colorstring(lua_State *L); diff --git a/src/script/lua_api/l_vmanip.cpp b/src/script/lua_api/l_vmanip.cpp index da89f4e57..76b5aff0f 100644 --- a/src/script/lua_api/l_vmanip.cpp +++ b/src/script/lua_api/l_vmanip.cpp @@ -48,6 +48,9 @@ int LuaVoxelManip::l_read_from_map(lua_State *L) if (vm->isOrphan()) return 0; + if (getEmergeThread(L)) + throw LuaError("VoxelManip:read_from_map called in mapgen environment"); + v3s16 bp1 = getNodeBlockPos(check_v3s16(L, 2)); v3s16 bp2 = getNodeBlockPos(check_v3s16(L, 3)); sortBoxVerticies(bp1, bp2); @@ -110,14 +113,18 @@ int LuaVoxelManip::l_set_data(lua_State *L) int LuaVoxelManip::l_write_to_map(lua_State *L) { - GET_ENV_PTR; - LuaVoxelManip *o = checkObject(L, 1); bool update_light = !lua_isboolean(L, 2) || readParam(L, 2); if (o->vm->isOrphan()) return 0; + // This wouldn't work anyway as we have no env ptr, but it's still unsafe. + if (getEmergeThread(L)) + throw LuaError("VoxelManip:write_to_map called in mapgen environment"); + + GET_ENV_PTR; + ServerMap *map = &(env->getServerMap()); std::map modified_blocks; @@ -154,9 +161,8 @@ int LuaVoxelManip::l_set_node_at(lua_State *L) v3s16 pos = check_v3s16(L, 2); MapNode n = readnode(L, 3); - o->vm->setNodeNoEmerge(pos, n); - - return 0; + lua_pushboolean(L, o->vm->setNodeNoEmerge(pos, n)); + return 1; } int LuaVoxelManip::l_update_liquids(lua_State *L) @@ -193,8 +199,8 @@ int LuaVoxelManip::l_set_lighting(lua_State *L) { LuaVoxelManip *o = checkObject(L, 1); if (!o->is_mapgen_vm) { - warningstream << "VoxelManip:set_lighting called for a non-mapgen " - "VoxelManip object" << std::endl; + log_deprecated(L, "set_lighting called for a non-mapgen " + "VoxelManip object"); return 0; } diff --git a/src/script/scripting_emerge.cpp b/src/script/scripting_emerge.cpp new file mode 100644 index 000000000..3467b1495 --- /dev/null +++ b/src/script/scripting_emerge.cpp @@ -0,0 +1,93 @@ +/* +Minetest +Copyright (C) 2022 sfan5 + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "scripting_emerge.h" +#include "emerge_internal.h" +#include "server.h" +#include "settings.h" +#include "cpp_api/s_internal.h" +#include "common/c_packer.h" +#include "lua_api/l_areastore.h" +#include "lua_api/l_base.h" +#include "lua_api/l_craft.h" +#include "lua_api/l_env.h" +#include "lua_api/l_item.h" +#include "lua_api/l_itemstackmeta.h" +#include "lua_api/l_mapgen.h" +#include "lua_api/l_noise.h" +#include "lua_api/l_server.h" +#include "lua_api/l_util.h" +#include "lua_api/l_vmanip.h" +#include "lua_api/l_settings.h" + +extern "C" { +#include +} + +EmergeScripting::EmergeScripting(EmergeThread *parent): + ScriptApiBase(ScriptingType::Emerge) +{ + setGameDef(parent->m_server); + setEmergeThread(parent); + + SCRIPTAPI_PRECHECKHEADER + + if (g_settings->getBool("secure.enable_security")) + initializeSecurity(); + + lua_getglobal(L, "core"); + int top = lua_gettop(L); + + InitializeModApi(L, top); + + auto *data = ModApiBase::getServer(L)->m_lua_globals_data.get(); + assert(data); + script_unpack(L, data); + lua_setfield(L, top, "transferred_globals"); + + lua_pop(L, 1); + + // Push builtin initialization type + lua_pushstring(L, "emerge"); + lua_setglobal(L, "INIT"); +} + +void EmergeScripting::InitializeModApi(lua_State *L, int top) +{ + // Register reference classes (userdata) + ItemStackMetaRef::Register(L); + LuaAreaStore::Register(L); + LuaItemStack::Register(L); + LuaPerlinNoise::Register(L); + LuaPerlinNoiseMap::Register(L); + LuaPseudoRandom::Register(L); + LuaPcgRandom::Register(L); + LuaSecureRandom::Register(L); + LuaVoxelManip::Register(L); + LuaSettings::Register(L); + + // Initialize mod api modules + ModApiCraft::InitializeAsync(L, top); + ModApiEnvVM::InitializeEmerge(L, top); + ModApiItem::InitializeAsync(L, top); + ModApiMapgen::InitializeEmerge(L, top); + ModApiServer::InitializeAsync(L, top); + ModApiUtil::InitializeAsync(L, top); + // TODO ^ these should also be renamed to InitializeRO or such +} diff --git a/src/script/scripting_emerge.h b/src/script/scripting_emerge.h new file mode 100644 index 000000000..713dda7c2 --- /dev/null +++ b/src/script/scripting_emerge.h @@ -0,0 +1,37 @@ +/* +Minetest +Copyright (C) 2022 sfan5 + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once +#include "cpp_api/s_base.h" +#include "cpp_api/s_mapgen.h" +#include "cpp_api/s_security.h" + +class EmergeThread; + +class EmergeScripting: + virtual public ScriptApiBase, + public ScriptApiMapgen, + public ScriptApiSecurity +{ +public: + EmergeScripting(EmergeThread *parent); + +private: + void InitializeModApi(lua_State *L, int top); +}; diff --git a/src/serialization.cpp b/src/serialization.cpp index c0a104224..689e4d986 100644 --- a/src/serialization.cpp +++ b/src/serialization.cpp @@ -106,11 +106,6 @@ void compressZlib(const u8 *data, size_t data_size, std::ostream &os, int level) } } -void compressZlib(const std::string &data, std::ostream &os, int level) -{ - compressZlib((u8*)data.c_str(), data.size(), os, level); -} - void decompressZlib(std::istream &is, std::ostream &os, size_t limit) { z_stream z; @@ -211,7 +206,6 @@ void compressZstd(const u8 *data, size_t data_size, std::ostream &os, int level) // it will be destroyed when the thread ends thread_local std::unique_ptr stream(ZSTD_createCStream()); - ZSTD_initCStream(stream.get(), level); const size_t bufsize = 16384; @@ -247,11 +241,6 @@ void compressZstd(const u8 *data, size_t data_size, std::ostream &os, int level) } -void compressZstd(const std::string &data, std::ostream &os, int level) -{ - compressZstd((u8*)data.c_str(), data.size(), os, level); -} - void decompressZstd(std::istream &is, std::ostream &os) { // reusing the context is recommended for performance @@ -295,7 +284,7 @@ void decompressZstd(std::istream &is, std::ostream &os) } } -void compress(u8 *data, u32 size, std::ostream &os, u8 version, int level) +void compress(const u8 *data, u32 size, std::ostream &os, u8 version, int level) { if(version >= 29) { @@ -345,16 +334,6 @@ void compress(u8 *data, u32 size, std::ostream &os, u8 version, int level) os.write((char*)¤t_byte, 1); } -void compress(const SharedBuffer &data, std::ostream &os, u8 version, int level) -{ - compress(*data, data.getSize(), os, version, level); -} - -void compress(const std::string &data, std::ostream &os, u8 version, int level) -{ - compress((u8*)data.c_str(), data.size(), os, version, level); -} - void decompress(std::istream &is, std::ostream &os, u8 version) { if(version >= 29) diff --git a/src/serialization.h b/src/serialization.h index e83a8c179..d2ffb054f 100644 --- a/src/serialization.h +++ b/src/serialization.h @@ -22,7 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes.h" #include "exceptions.h" #include -#include "util/pointer.h" +#include /* Map format serialization version @@ -83,19 +83,27 @@ inline bool ser_ver_supported(s32 v) { } /* - Misc. serialization functions + Compression functions */ void compressZlib(const u8 *data, size_t data_size, std::ostream &os, int level = -1); -void compressZlib(const std::string &data, std::ostream &os, int level = -1); +inline void compressZlib(std::string_view data, std::ostream &os, int level = -1) +{ + compressZlib(reinterpret_cast(data.data()), data.size(), os, level); +} void decompressZlib(std::istream &is, std::ostream &os, size_t limit = 0); void compressZstd(const u8 *data, size_t data_size, std::ostream &os, int level = 0); -void compressZstd(const std::string &data, std::ostream &os, int level = 0); +inline void compressZstd(std::string_view data, std::ostream &os, int level = 0) +{ + compressZstd(reinterpret_cast(data.data()), data.size(), os, level); +} void decompressZstd(std::istream &is, std::ostream &os); -// These choose between zlib and a self-made one according to version -void compress(const SharedBuffer &data, std::ostream &os, u8 version, int level = -1); -void compress(const std::string &data, std::ostream &os, u8 version, int level = -1); -void compress(u8 *data, u32 size, std::ostream &os, u8 version, int level = -1); +// These choose between zstd, zlib and a self-made one according to version +void compress(const u8 *data, u32 size, std::ostream &os, u8 version, int level = -1); +inline void compress(std::string_view data, std::ostream &os, u8 version, int level = -1) +{ + compress(reinterpret_cast(data.data()), data.size(), os, version, level); +} void decompress(std::istream &is, std::ostream &os, u8 version); diff --git a/src/server.cpp b/src/server.cpp index baacce3f6..9441f5050 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -343,7 +343,7 @@ Server::~Server() kick_msg = g_settings->get("kick_msg_shutdown"); } m_env->saveLoadedPlayers(true); - m_env->kickAllPlayers(SERVER_ACCESSDENIED_SHUTDOWN, + kickAllPlayers(SERVER_ACCESSDENIED_SHUTDOWN, kick_msg, reconnect); } @@ -381,6 +381,13 @@ Server::~Server() if (m_mod_storage_database) m_mod_storage_database->endSave(); + // Clean up files + for (auto &it : m_media) { + if (it.second.delete_at_shutdown) { + fs::DeleteSingleFileOrEmptyDirectory(it.second.path); + } + } + // Delete things in the reverse order of creation delete m_emerge; delete m_env; @@ -590,7 +597,7 @@ void Server::step() std::string async_err = m_async_fatal_error.get(); if (!async_err.empty()) { if (!m_simple_singleplayer_mode) { - m_env->kickAllPlayers(SERVER_ACCESSDENIED_CRASH, + kickAllPlayers(SERVER_ACCESSDENIED_CRASH, g_settings->get("kick_msg_crash"), g_settings->getBool("ask_reconnect_on_crash")); } @@ -639,15 +646,29 @@ void Server::AsyncRunStep(float dtime, bool initial_step) { MutexAutoLock lock(m_env_mutex); - // Figure out and report maximum lag to environment float max_lag = m_env->getMaxLagEstimate(); - max_lag *= 0.9998; // Decrease slowly (about half per 5 minutes) - if(dtime > max_lag){ - if(dtime > 0.1 && dtime > max_lag * 2.0) - infostream<<"Server: Maximum lag peaked to "<10% off target + // also report if we crossed into the warning boundary + if (dtime >= max_lag * 1.2f || + (max_lag < lag_warn_threshold && dtime >= lag_warn_threshold)) { + const float steplen = getStepSettings().steplen; + if (dtime > steplen * 1.1f) { + auto &to = dtime >= lag_warn_threshold ? warningstream : infostream; + to << "Server: Maximum lag peaked at " << dtime + << " (steplen=" << steplen << ")" << std::endl; + } + } + max_lag = std::max(max_lag, dtime), + m_env->reportMaxLagEstimate(max_lag); // Step environment @@ -1105,7 +1126,7 @@ PlayerSAO* Server::StageTwoClientInit(session_t peer_id) /* Send complete position information */ - SendMovePlayer(peer_id); + SendMovePlayer(playersao); // Send privileges SendPlayerPrivileges(peer_id); @@ -1114,7 +1135,7 @@ PlayerSAO* Server::StageTwoClientInit(session_t peer_id) SendPlayerInventoryFormspec(peer_id); // Send inventory - SendInventory(playersao, false); + SendInventory(player, false); // Send HP SendPlayerHP(playersao, false); @@ -1458,10 +1479,8 @@ void Server::SendNodeDef(session_t peer_id, Non-static send methods */ -void Server::SendInventory(PlayerSAO *sao, bool incremental) +void Server::SendInventory(RemotePlayer *player, bool incremental) { - RemotePlayer *player = sao->getPlayer(); - // Do not send new format to old clients incremental &= player->protocol_version >= 38; @@ -1471,11 +1490,11 @@ void Server::SendInventory(PlayerSAO *sao, bool incremental) Serialize it */ - NetworkPacket pkt(TOCLIENT_INVENTORY, 0, sao->getPeerID()); + NetworkPacket pkt(TOCLIENT_INVENTORY, 0, player->getPeerId()); std::ostringstream os(std::ios::binary); - sao->getInventory()->serialize(os, incremental); - sao->getInventory()->setModified(false); + player->inventory.serialize(os, incremental); + player->inventory.setModified(false); player->setModified(true); const std::string &s = os.str(); @@ -1900,17 +1919,12 @@ void Server::SendPlayerBreath(PlayerSAO *sao) SendBreath(sao->getPeerID(), sao->getBreath()); } -void Server::SendMovePlayer(session_t peer_id) +void Server::SendMovePlayer(PlayerSAO *sao) { - RemotePlayer *player = m_env->getPlayer(peer_id); - assert(player); - PlayerSAO *sao = player->getPlayerSAO(); - assert(sao); - // Send attachment updates instantly to the client prior updating position sao->sendOutdatedData(); - NetworkPacket pkt(TOCLIENT_MOVE_PLAYER, sizeof(v3f) + sizeof(f32) * 2, peer_id); + NetworkPacket pkt(TOCLIENT_MOVE_PLAYER, sizeof(v3f) + sizeof(f32) * 2, sao->getPeerID()); pkt << sao->getBasePosition() << sao->getLookPitch() << sao->getRotation().Y; { @@ -2264,6 +2278,22 @@ void Server::fadeSound(s32 handle, float step, float gain) m_playing_sounds.erase(it); } +void Server::stopAttachedSounds(u16 id) +{ + assert(id); + + for (auto it = m_playing_sounds.begin(); it != m_playing_sounds.end(); ) { + const ServerPlayingSound &sound = it->second; + + if (sound.object == id) { + // Remove sound reference + it = m_playing_sounds.erase(it); + } + else + it++; + } +} + void Server::sendRemoveNode(v3s16 p, std::unordered_set *far_players, float far_d_nodes) { @@ -2534,14 +2564,13 @@ bool Server::addMediaFile(const std::string &filename, } SHA1 sha1; - sha1.addBytes(filedata.c_str(), filedata.length()); + sha1.addBytes(filedata); - unsigned char *digest = sha1.getDigest(); - std::string sha1_base64 = base64_encode(digest, 20); - std::string sha1_hex = hex_encode((char*) digest, 20); + std::string digest = sha1.getDigest(); + std::string sha1_base64 = base64_encode(digest); + std::string sha1_hex = hex_encode(digest); if (digest_to) - *digest_to = std::string((char*) digest, 20); - free(digest); + *digest_to = digest; // Put in list m_media[filename] = MediaInfo(filepath, sha1_base64); @@ -2877,6 +2906,15 @@ void Server::DenyAccess(session_t peer_id, AccessDeniedCode reason, DisconnectPeer(peer_id); } +void Server::kickAllPlayers(AccessDeniedCode reason, + const std::string &str_reason, bool reconnect) +{ + std::vector clients = m_clients.getClientIDs(); + for (const session_t client_id : clients) { + DenyAccess(client_id, reason, str_reason, reconnect); + } +} + void Server::DisconnectPeer(session_t peer_id) { m_modchannel_mgr->leaveAllChannels(peer_id); @@ -2915,12 +2953,11 @@ void Server::DeleteClient(session_t peer_id, ClientDeletionReason reason) /* Clear references to playing sounds */ - for (std::unordered_map::iterator - i = m_playing_sounds.begin(); i != m_playing_sounds.end();) { + for (auto i = m_playing_sounds.begin(); i != m_playing_sounds.end();) { ServerPlayingSound &psound = i->second; psound.clients.erase(peer_id); if (psound.clients.empty()) - m_playing_sounds.erase(i++); + i = m_playing_sounds.erase(i); else ++i; } @@ -3566,72 +3603,126 @@ void Server::deleteParticleSpawner(const std::string &playername, u32 id) SendDeleteParticleSpawner(peer_id, id); } -bool Server::dynamicAddMedia(std::string filepath, - const u32 token, const std::string &to_player, bool ephemeral) +namespace { + std::string writeToTempFile(std::string_view content) + { + auto filepath = fs::CreateTempFile(); + if (filepath.empty()) + return ""; + std::ofstream os(filepath, std::ios::binary); + if (!os.good()) + return ""; + os << content; + os.close(); + if (os.fail()) { + fs::DeleteSingleFileOrEmptyDirectory(filepath); + return ""; + } + return filepath; + } +} + +bool Server::dynamicAddMedia(const DynamicMediaArgs &a) { - std::string filename = fs::GetFilenameFromPath(filepath.c_str()); + std::string filename = a.filename; + std::string filepath; + + // Deal with file -or- data, as provided + // (Note: caller must ensure validity, so sanity_check is okay) + if (a.filepath) { + sanity_check(!a.data); + filepath = *a.filepath; + if (filename.empty()) + filename = fs::GetFilenameFromPath(filepath.c_str()); + } else { + sanity_check(a.data); + sanity_check(!filename.empty()); + + // Write the file to disk. addMediaFile() will read it right back but this + // is the best way without turning the media loading code into spaghetti. + filepath = writeToTempFile(*a.data); + if (filepath.empty()) { + errorstream << "Server: failed writing media file \"" + << filename << "\" to disk" << std::endl; + return false; + } + verbosestream << "Server: \"" << filename << "\" temporarily written to " + << filepath << std::endl; + } + + // Do some checks auto it = m_media.find(filename); if (it != m_media.end()) { // Allow the same path to be "added" again in certain conditions - if (ephemeral || it->second.path != filepath) { + if (a.ephemeral || it->second.path != filepath) { errorstream << "Server::dynamicAddMedia(): file \"" << filename << "\" already exists in media cache" << std::endl; return false; } } + if (!m_env && (!a.to_player.empty() || a.ephemeral)) { + errorstream << "Server::dynamicAddMedia(): " + "adding ephemeral or player-specific media at startup is nonsense" + << std::endl; + return false; + } + // Load the file and add it to our media cache std::string filedata, raw_hash; bool ok = addMediaFile(filename, filepath, &filedata, &raw_hash); if (!ok) return false; + assert(!filedata.empty()); - if (ephemeral) { - // Create a copy of the file and swap out the path, this removes the - // requirement that mods keep the file accessible at the original path. - filepath = fs::CreateTempFile(); - bool ok = ([&] () -> bool { - if (filepath.empty()) + const auto &media_it = m_media.find(filename); + assert(media_it != m_media.end()); + + if (a.ephemeral) { + if (!a.data) { + // Create a copy of the file and swap out the path, this removes the + // requirement that mods keep the file accessible at the original path. + filepath = writeToTempFile(filedata); + if (filepath.empty()) { + errorstream << "Server: failed creating a copy of media file \"" + << filename << "\"" << std::endl; + m_media.erase(filename); return false; - std::ofstream os(filepath.c_str(), std::ios::binary); - if (!os.good()) - return false; - os << filedata; - os.close(); - return !os.fail(); - })(); - if (!ok) { - errorstream << "Server: failed to create a copy of media file " - << "\"" << filename << "\"" << std::endl; - m_media.erase(filename); - return false; + } + verbosestream << "Server: \"" << filename << "\" temporarily copied to " + << filepath << std::endl; + media_it->second.path = filepath; } - verbosestream << "Server: \"" << filename << "\" temporarily copied to " - << filepath << std::endl; - m_media[filename].path = filepath; - m_media[filename].no_announce = true; - // stepPendingDynMediaCallbacks will clean this up later. - } else if (!to_player.empty()) { - m_media[filename].no_announce = true; + media_it->second.no_announce = true; + // stepPendingDynMediaCallbacks will clean the file up later + } else if (a.data) { + // data is in a temporary file but not ephemeral, so the cleanup point + // is different. + media_it->second.delete_at_shutdown = true; + } + if (!a.to_player.empty()) { + // only sent to one player (who must be online), so shouldn't announce. + media_it->second.no_announce = true; } - // Push file to existing clients - NetworkPacket pkt(TOCLIENT_MEDIA_PUSH, 0); - pkt << raw_hash << filename << (bool)ephemeral; - - NetworkPacket legacy_pkt = pkt; - - // Newer clients get asked to fetch the file (asynchronous) - pkt << token; - // Older clients have an awful hack that just throws the data at them - legacy_pkt.putLongString(filedata); - std::unordered_set delivered, waiting; - { + + // Push file to existing clients + if (m_env) { + NetworkPacket pkt(TOCLIENT_MEDIA_PUSH, 0); + pkt << raw_hash << filename << static_cast(a.ephemeral); + + NetworkPacket legacy_pkt = pkt; + + // Newer clients get asked to fetch the file (asynchronous) + pkt << a.token; + // Older clients have an awful hack that just throws the data at them + legacy_pkt.putLongString(filedata); + ClientInterface::AutoLock clientlock(m_clients); for (auto &pair : m_clients.getClientList()) { - if (pair.second->getState() == CS_DefinitionsSent && !ephemeral) { + if (pair.second->getState() == CS_DefinitionsSent && !a.ephemeral) { /* If a client is in the DefinitionsSent state it is too late to transfer the file via sendMediaAnnouncement() but at the same @@ -3652,7 +3743,7 @@ bool Server::dynamicAddMedia(std::string filepath, continue; const session_t peer_id = pair.second->peer_id; - if (!to_player.empty() && getPlayerName(peer_id) != to_player) + if (!a.to_player.empty() && getPlayerName(peer_id) != a.to_player) continue; if (proto_ver < 40) { @@ -3675,15 +3766,15 @@ bool Server::dynamicAddMedia(std::string filepath, // Run callback for players that already had the file delivered (legacy-only) for (session_t peer_id : delivered) { if (auto player = m_env->getPlayer(peer_id)) - getScriptIface()->on_dynamic_media_added(token, player->getName()); + getScriptIface()->on_dynamic_media_added(a.token, player->getName()); } // Save all others in our pending state - auto &state = m_pending_dyn_media[token]; + auto &state = m_pending_dyn_media[a.token]; state.waiting_players = std::move(waiting); // regardless of success throw away the callback after a while state.expiry_timer = 60.0f; - if (ephemeral) + if (a.ephemeral) state.filename = filename; return true; @@ -3929,6 +4020,23 @@ PlayerSAO* Server::emergePlayer(const char *name, session_t peer_id, u16 proto_v return NULL; } + /* + Object construction sequence/hierarchy + -------------------------------------- + 1. RemoteClient (tightly connection-bound) + 2. RemotePlayer (controls, in-game appearance) + 3. PlayerSAO (movable object in-game) + PlayerSAO controls the peer_id assignment of RemotePlayer, + indicating whether the player is ready + + Destruction sequence + -------------------- + 1. PlayerSAO pending removal flag + 2. PlayerSAO save data before free + 3. RemotePlayer, then PlayerSAO freed + 4. RemoteClient freed + */ + if (!player) { player = new RemotePlayer(name, idef()); } @@ -4089,6 +4197,19 @@ Translations *Server::getTranslationLanguage(const std::string &lang_code) return translations; } +std::unordered_map Server::getMediaList() +{ + MutexAutoLock env_lock(m_env_mutex); + + std::unordered_map ret; + for (auto &it : m_media) { + if (it.second.no_announce) + continue; + ret.emplace(base64_decode(it.second.sha1_digest), it.second.path); + } + return ret; +} + ModStorageDatabase *Server::openModStorageDatabase(const std::string &world_path) { std::string world_mt_path = world_path + DIR_DELIM + "world.mt"; diff --git a/src/server.h b/src/server.h index c57dde0d7..d262ca3c2 100644 --- a/src/server.h +++ b/src/server.h @@ -44,6 +44,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include +#include +#include class ChatEvent; struct ChatEventChat; @@ -52,7 +54,6 @@ class IWritableItemDefManager; class NodeDefManager; class IWritableCraftDefManager; class BanManager; -class EventManager; class Inventory; class ModChannelMgr; class RemotePlayer; @@ -87,13 +88,17 @@ struct MediaInfo { std::string path; std::string sha1_digest; // base64-encoded - bool no_announce; // true: not announced in TOCLIENT_ANNOUNCE_MEDIA (at player join) + // true = not announced in TOCLIENT_ANNOUNCE_MEDIA (at player join) + bool no_announce; + // does what it says. used by some cases of dynamic media. + bool delete_at_shutdown; - MediaInfo(const std::string &path_="", - const std::string &sha1_digest_=""): + MediaInfo(std::string_view path_ = "", + std::string_view sha1_digest_ = ""): path(path_), sha1_digest(sha1_digest_), - no_announce(false) + no_announce(false), + delete_at_shutdown(false) { } }; @@ -234,6 +239,7 @@ public: s32 playSound(ServerPlayingSound ¶ms, bool ephemeral=false); void stopSound(s32 handle); void fadeSound(s32 handle, float step, float gain); + void stopAttachedSounds(u16 id); // Envlock std::set getPlayerEffectivePrivs(const std::string &name); @@ -258,8 +264,15 @@ public: void deleteParticleSpawner(const std::string &playername, u32 id); - bool dynamicAddMedia(std::string filepath, u32 token, - const std::string &to_player, bool ephemeral); + struct DynamicMediaArgs { + std::string filename; + std::optional filepath; + std::optional data; + u32 token; + std::string to_player; + bool ephemeral = false; + }; + bool dynamicAddMedia(const DynamicMediaArgs &args); ServerInventoryManager *getInventoryMgr() const { return m_inventory_mgr.get(); } void sendDetachedInventory(Inventory *inventory, const std::string &name, session_t peer_id); @@ -352,6 +365,8 @@ public: void DenySudoAccess(session_t peer_id); void DenyAccess(session_t peer_id, AccessDeniedCode reason, const std::string &custom_reason = "", bool reconnect = false); + void kickAllPlayers(AccessDeniedCode reason, + const std::string &str_reason, bool reconnect); void acceptAuth(session_t peer_id, bool forSudoMode); void DisconnectPeer(session_t peer_id); bool getClientConInfo(session_t peer_id, con::rtt_stat_type type, float *retval); @@ -363,8 +378,8 @@ public: void HandlePlayerHPChange(PlayerSAO *sao, const PlayerHPChangeReason &reason); void SendPlayerHP(PlayerSAO *sao, bool effect); void SendPlayerBreath(PlayerSAO *sao); - void SendInventory(PlayerSAO *playerSAO, bool incremental); - void SendMovePlayer(session_t peer_id); + void SendInventory(RemotePlayer *player, bool incremental); + void SendMovePlayer(PlayerSAO *sao); void SendMovePlayerRel(session_t peer_id, const v3f &added_pos); void SendPlayerSpeed(session_t peer_id, const v3f &added_vel); void SendPlayerFov(session_t peer_id); @@ -386,6 +401,10 @@ public: // Get or load translations for a language Translations *getTranslationLanguage(const std::string &lang_code); + // Returns all media files the server knows about + // map key = binary sha1, map value = file path + std::unordered_map getMediaList(); + static ModStorageDatabase *openModStorageDatabase(const std::string &world_path); static ModStorageDatabase *openModStorageDatabase(const std::string &backend, @@ -399,6 +418,8 @@ public: // Lua files registered for init of async env, pair of modname + path std::vector> m_async_init_files; + // Identical but for mapgen env + std::vector> m_mapgen_init_files; // Data transferred into other Lua envs at init time std::unique_ptr m_lua_globals_data; @@ -588,12 +609,14 @@ private: MutexedVariable m_async_fatal_error; // Some timers + float m_time_of_day_send_timer = 0.0f; float m_liquid_transform_timer = 0.0f; float m_liquid_transform_every = 1.0f; float m_masterserver_timer = 0.0f; float m_emergethread_trigger_timer = 0.0f; float m_savemap_timer = 0.0f; IntervalLimiter m_map_timer_and_unload_interval; + IntervalLimiter m_max_lag_decrease; // Environment ServerEnvironment *m_env = nullptr; @@ -641,12 +664,6 @@ private: // The server mainly operates in this thread ServerThread *m_thread = nullptr; - /* - Time related stuff - */ - // Timer for sending time of day over network - float m_time_of_day_send_timer = 0.0f; - /* Client interface */ diff --git a/src/server/ban.cpp b/src/server/ban.cpp index 3decc9666..47a6ccd5e 100644 --- a/src/server/ban.cpp +++ b/src/server/ban.cpp @@ -123,9 +123,9 @@ void BanManager::add(const std::string &ip, const std::string &name) void BanManager::remove(const std::string &ip_or_name) { MutexAutoLock lock(m_mutex); - for (StringMap::iterator it = m_ips.begin(); it != m_ips.end();) { + for (auto it = m_ips.begin(); it != m_ips.end();) { if ((it->first == ip_or_name) || (it->second == ip_or_name)) { - m_ips.erase(it++); + it = m_ips.erase(it); m_modified = true; } else { ++it; diff --git a/src/server/clientiface.cpp b/src/server/clientiface.cpp index 22e3e42d0..451e74407 100644 --- a/src/server/clientiface.cpp +++ b/src/server/clientiface.cpp @@ -354,18 +354,12 @@ void RemoteClient::GetNextBlocks ( continue; /* - If block is not close, don't send it unless it is near - ground level. - - Block is near ground level if night-time mesh - differs from day-time mesh. + If block is not close, don't send it if it + consists of air only. */ - if (d >= d_opt) { - if (!block->getIsUnderground() && !block->getDayNightDiff()) + if (d >= d_opt && block->isAir()) continue; - } } - /* Check occlusion cache first. */ diff --git a/src/server/luaentity_sao.cpp b/src/server/luaentity_sao.cpp index 3995f656d..ae5c97bfd 100644 --- a/src/server/luaentity_sao.cpp +++ b/src/server/luaentity_sao.cpp @@ -203,7 +203,7 @@ void LuaEntitySAO::step(float dtime, bool send_recommended) } } - if (fabs(m_prop.automatic_rotate) > 0.001f) { + if (std::abs(m_prop.automatic_rotate) > 0.001f) { m_rotation_add_yaw = modulo360f(m_rotation_add_yaw + dtime * core::RADTODEG * m_prop.automatic_rotate); } diff --git a/src/server/player_sao.cpp b/src/server/player_sao.cpp index 688dcde4e..0806ffd73 100644 --- a/src/server/player_sao.cpp +++ b/src/server/player_sao.cpp @@ -29,10 +29,10 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t p bool is_singleplayer): UnitSAO(env_, v3f(0,0,0)), m_player(player_), - m_peer_id(peer_id_), + m_peer_id_initial(peer_id_), m_is_singleplayer(is_singleplayer) { - SANITY_CHECK(m_peer_id != PEER_ID_INEXISTENT); + SANITY_CHECK(m_peer_id_initial != PEER_ID_INEXISTENT); m_prop.hp_max = PLAYER_MAX_HP_DEFAULT; m_prop.breath_max = PLAYER_MAX_BREATH_DEFAULT; @@ -88,7 +88,8 @@ void PlayerSAO::addedToEnvironment(u32 dtime_s) ServerActiveObject::addedToEnvironment(dtime_s); ServerActiveObject::setBasePosition(m_base_position); m_player->setPlayerSAO(this); - m_player->setPeerId(m_peer_id); + m_player->setPeerId(m_peer_id_initial); + m_peer_id_initial = PEER_ID_INEXISTENT; // don't try to use it again. m_last_good_position = m_base_position; } @@ -96,11 +97,13 @@ void PlayerSAO::addedToEnvironment(u32 dtime_s) void PlayerSAO::removingFromEnvironment() { ServerActiveObject::removingFromEnvironment(); - if (m_player->getPlayerSAO() == this) { - unlinkPlayerSessionAndSave(); - for (u32 attached_particle_spawner : m_attached_particle_spawners) { - m_env->deleteParticleSpawner(attached_particle_spawner, false); - } + + // If this fails, fix the ActiveObjectMgr code in ServerEnvironment + SANITY_CHECK(m_player->getPlayerSAO() == this); + + unlinkPlayerSessionAndSave(); + for (u32 attached_particle_spawner : m_attached_particle_spawners) { + m_env->deleteParticleSpawner(attached_particle_spawner, false); } } @@ -236,7 +239,7 @@ void PlayerSAO::step(float dtime, bool send_recommended) " is attached to nonexistent parent. This is a bug." << std::endl; clearParentAttachment(); setBasePosition(m_last_good_position); - m_env->getGameDef()->SendMovePlayer(m_peer_id); + m_env->getGameDef()->SendMovePlayer(this); } //dstream<<"PlayerSAO::step: dtime: "<getGameDef()->SendBlock(m_peer_id, blockpos); + m_env->getGameDef()->SendBlock(getPeerID(), blockpos); setBasePosition(pos); // Movement caused by this command is always valid m_last_good_position = getBasePosition(); m_move_pool.empty(); m_time_from_last_teleport = 0.0; - m_env->getGameDef()->SendMovePlayer(m_peer_id); + m_env->getGameDef()->SendMovePlayer(this); } void PlayerSAO::addPos(const v3f &added_pos) @@ -381,14 +384,14 @@ void PlayerSAO::addPos(const v3f &added_pos) // Send mapblock of target location v3f pos = getBasePosition() + added_pos; v3s16 blockpos = v3s16(pos.X / MAP_BLOCKSIZE, pos.Y / MAP_BLOCKSIZE, pos.Z / MAP_BLOCKSIZE); - m_env->getGameDef()->SendBlock(m_peer_id, blockpos); + m_env->getGameDef()->SendBlock(getPeerID(), blockpos); setBasePosition(pos); // Movement caused by this command is always valid m_last_good_position = getBasePosition(); m_move_pool.empty(); m_time_from_last_teleport = 0.0; - m_env->getGameDef()->SendMovePlayerRel(m_peer_id, added_pos); + m_env->getGameDef()->SendMovePlayerRel(getPeerID(), added_pos); } void PlayerSAO::moveTo(v3f pos, bool continuous) @@ -401,7 +404,7 @@ void PlayerSAO::moveTo(v3f pos, bool continuous) m_last_good_position = getBasePosition(); m_move_pool.empty(); m_time_from_last_teleport = 0.0; - m_env->getGameDef()->SendMovePlayer(m_peer_id); + m_env->getGameDef()->SendMovePlayer(this); } void PlayerSAO::setPlayerYaw(const float yaw) @@ -433,7 +436,7 @@ void PlayerSAO::setWantedRange(const s16 range) void PlayerSAO::setPlayerYawAndSend(const float yaw) { setPlayerYaw(yaw); - m_env->getGameDef()->SendMovePlayer(m_peer_id); + m_env->getGameDef()->SendMovePlayer(this); } void PlayerSAO::setLookPitch(const float pitch) @@ -447,7 +450,7 @@ void PlayerSAO::setLookPitch(const float pitch) void PlayerSAO::setLookPitchAndSend(const float pitch) { setLookPitch(pitch); - m_env->getGameDef()->SendMovePlayer(m_peer_id); + m_env->getGameDef()->SendMovePlayer(this); } u32 PlayerSAO::punch(v3f dir, @@ -512,8 +515,9 @@ void PlayerSAO::setHP(s32 target_hp, const PlayerHPChangeReason &reason, bool fr return; // Nothing to do s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, target_hp - (s32)m_hp, reason); + hp_change = std::min(hp_change, U16_MAX); // Protect against overflow - s32 hp = (s32)m_hp + std::min(hp_change, U16_MAX); // Protection against s32 overflow + s32 hp = (s32)m_hp + hp_change; hp = rangelim(hp, 0, U16_MAX); if (hp > m_prop.hp_max) @@ -578,16 +582,20 @@ bool PlayerSAO::setWieldedItem(const ItemStack &item) void PlayerSAO::disconnected() { - m_peer_id = PEER_ID_INEXISTENT; markForRemoval(); + m_player->setPeerId(PEER_ID_INEXISTENT); +} + +session_t PlayerSAO::getPeerID() const +{ + // Before adding `this` to the server env, m_player is still nullptr. + return m_player ? m_player->getPeerId() : PEER_ID_INEXISTENT; } void PlayerSAO::unlinkPlayerSessionAndSave() { assert(m_player->getPlayerSAO() == this); - m_player->setPeerId(PEER_ID_INEXISTENT); m_env->savePlayer(m_player); - m_player->setPlayerSAO(NULL); m_env->removePlayer(m_player); } diff --git a/src/server/player_sao.h b/src/server/player_sao.h index 854464508..b26304589 100644 --- a/src/server/player_sao.h +++ b/src/server/player_sao.h @@ -142,8 +142,9 @@ public: void disconnected(); + void setPlayer(RemotePlayer *player) { m_player = player; } RemotePlayer *getPlayer() { return m_player; } - session_t getPeerID() const { return m_peer_id; } + session_t getPeerID() const; // Cheat prevention @@ -193,7 +194,7 @@ private: std::string generateUpdatePhysicsOverrideCommand() const; RemotePlayer *m_player = nullptr; - session_t m_peer_id = 0; + session_t m_peer_id_initial = 0; ///< only used to initialize RemotePlayer // Cheat prevention LagPool m_dig_pool; diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index e0cf99378..f3fe2c731 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -625,13 +625,6 @@ bool ServerEnvironment::removePlayerFromDatabase(const std::string &name) return m_player_database->removePlayer(name); } -void ServerEnvironment::kickAllPlayers(AccessDeniedCode reason, - const std::string &str_reason, bool reconnect) -{ - for (RemotePlayer *player : m_players) - m_server->DenyAccess(player->getPeerId(), reason, str_reason, reconnect); -} - void ServerEnvironment::saveLoadedPlayers(bool force) { for (RemotePlayer *player : m_players) { @@ -1051,7 +1044,8 @@ void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime) <m_static_objects.clearStored(); // do not set changed flag to avoid unnecessary mapblock writes } @@ -1259,10 +1253,7 @@ void ServerEnvironment::clearObjects(ClearObjectsMode mode) return false; } - // Tell the object about removal - obj->removingFromEnvironment(); - // Deregister in scripting api - m_script->removeObjectReference(obj); + processActiveObjectRemove(obj, id); // Delete active object return true; @@ -1622,9 +1613,8 @@ void ServerEnvironment::step(float dtime) Manage particle spawner expiration */ if (m_particle_management_interval.step(dtime, 1.0)) { - for (std::unordered_map::iterator i = m_particle_spawners.begin(); - i != m_particle_spawners.end(); ) { - //non expiring spawners + for (auto i = m_particle_spawners.begin(); i != m_particle_spawners.end(); ) { + // non expiring spawners if (i->second == PARTICLE_SPAWNER_NO_EXPIRY) { ++i; continue; @@ -1632,7 +1622,7 @@ void ServerEnvironment::step(float dtime) i->second -= 1.0f; if (i->second <= 0.f) - m_particle_spawners.erase(i++); + i = m_particle_spawners.erase(i); else ++i; } @@ -1643,9 +1633,8 @@ void ServerEnvironment::step(float dtime) if (player->getPeerId() == PEER_ID_INEXISTENT) continue; - PlayerSAO *sao = player->getPlayerSAO(); - if (sao && player->inventory.checkModified()) - m_server->SendInventory(sao, true); + if (player->inventory.checkModified()) + m_server->SendInventory(player, true); } // Send outdated detached inventories @@ -1834,8 +1823,8 @@ void ServerEnvironment::getSelectedActiveObjects( const std::optional &pointabilities) { std::vector objs; - getObjectsInsideRadius(objs, shootline_on_map.start, - shootline_on_map.getLength() + 10.0f, nullptr); + getObjectsInsideRadius(objs, shootline_on_map.getMiddle(), + 0.5 * shootline_on_map.getLength() + 5 * BS, nullptr); const v3f line_vector = shootline_on_map.getVector(); for (auto obj : objs) { @@ -1900,11 +1889,6 @@ u16 ServerEnvironment::addActiveObjectRaw(std::unique_ptr ob return 0; } - // Register reference in scripting api (must be done before post-init) - m_script->addObjectReference(object); - // Post-initialize object - object->addedToEnvironment(dtime_s); - // Add static data to block if (object->isStaticAllowed()) { // Add static object to active static list of the block @@ -1923,12 +1907,20 @@ u16 ServerEnvironment::addActiveObjectRaw(std::unique_ptr ob MOD_REASON_ADD_ACTIVE_OBJECT_RAW); } else { v3s16 p = floatToInt(objectpos, BS); - errorstream<<"ServerEnvironment::addActiveObjectRaw(): " - <<"could not emerge block for storing id="<getId() - <<" statically (pos="<getId()); + return 0; } } + // Register reference in scripting api (must be done before post-init) + m_script->addObjectReference(object); + // Post-initialize object + object->addedToEnvironment(dtime_s); + return object->getId(); } @@ -1981,10 +1973,7 @@ void ServerEnvironment::removeRemovedObjects() } } - // Tell the object about removal - obj->removingFromEnvironment(); - // Deregister in scripting api - m_script->removeObjectReference(obj); + processActiveObjectRemove(obj, id); // Delete return true; @@ -2221,10 +2210,7 @@ void ServerEnvironment::deactivateFarObjects(bool _force_delete) return false; } - // Tell the object about removal - obj->removingFromEnvironment(); - // Deregister in scripting api - m_script->removeObjectReference(obj); + processActiveObjectRemove(obj, id); // Delete active object return true; @@ -2287,6 +2273,16 @@ bool ServerEnvironment::saveStaticToBlock( return true; } +void ServerEnvironment::processActiveObjectRemove(ServerActiveObject *obj, u16 id) +{ + // Tell the object about removal + obj->removingFromEnvironment(); + // Deregister in scripting api + m_script->removeObjectReference(obj); + // stop attached sounds + m_server->stopAttachedSounds(id); +} + PlayerDatabase *ServerEnvironment::openPlayerDatabase(const std::string &name, const std::string &savedir, const Settings &conf) { diff --git a/src/serverenvironment.h b/src/serverenvironment.h index ad6a3acc5..01f232c8c 100644 --- a/src/serverenvironment.h +++ b/src/serverenvironment.h @@ -239,8 +239,6 @@ public: float getSendRecommendedInterval() { return m_recommended_send_interval; } - void kickAllPlayers(AccessDeniedCode reason, - const std::string &str_reason, bool reconnect); // Save players void saveLoadedPlayers(bool force = false); void savePlayer(RemotePlayer *player); @@ -369,7 +367,7 @@ public: u32 getGameTime() const { return m_game_time; } void reportMaxLagEstimate(float f) { m_max_lag_estimate = f; } - float getMaxLagEstimate() { return m_max_lag_estimate; } + float getMaxLagEstimate() const { return m_max_lag_estimate; } std::set* getForceloadedBlocks() { return &m_active_blocks.m_forceloaded_list; } @@ -456,6 +454,8 @@ private: bool saveStaticToBlock(v3s16 blockpos, u16 store_id, ServerActiveObject *obj, const StaticObject &s_obj, u32 mod_reason); + void processActiveObjectRemove(ServerActiveObject *obj, u16 id); + /* Member variables */ diff --git a/src/settings.cpp b/src/settings.cpp index 8b89bf489..74d00762d 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -97,7 +97,7 @@ void SettingsHierarchy::onLayerRemoved(int layer) /* Settings implementation */ -Settings *Settings::createLayer(SettingsLayer sl, const std::string &end_tag) +Settings *Settings::createLayer(SettingsLayer sl, std::string_view end_tag) { return new Settings(end_tag, &g_hierarchy, (int)sl); } @@ -109,7 +109,7 @@ Settings *Settings::getLayer(SettingsLayer sl) } -Settings::Settings(const std::string &end_tag, SettingsHierarchy *h, +Settings::Settings(std::string_view end_tag, SettingsHierarchy *h, int settings_layer) : m_end_tag(end_tag), m_hierarchy(h), @@ -131,7 +131,7 @@ Settings::~Settings() } -Settings & Settings::operator = (const Settings &other) +Settings & Settings::operator=(const Settings &other) { if (&other == this) return *this; @@ -151,7 +151,7 @@ Settings & Settings::operator = (const Settings &other) } -bool Settings::checkNameValid(const std::string &name) +bool Settings::checkNameValid(std::string_view name) { bool valid = name.find_first_of("=\"{}#") == std::string::npos; if (valid) @@ -166,7 +166,7 @@ bool Settings::checkNameValid(const std::string &name) } -bool Settings::checkValueValid(const std::string &value) +bool Settings::checkValueValid(std::string_view value) { if (value.substr(0, 3) == "\"\"\"" || value.find("\n\"\"\"") != std::string::npos) { @@ -407,13 +407,13 @@ bool Settings::parseCommandLine(int argc, char *argv[], { int nonopt_index = 0; for (int i = 1; i < argc; i++) { - std::string arg_name = argv[i]; + std::string_view arg_name(argv[i]); if (arg_name.substr(0, 2) != "--") { // If option doesn't start with -, read it in as nonoptX - if (arg_name[0] != '-'){ + if (arg_name[0] != '-') { std::string name = "nonopt"; name += itos(nonopt_index); - set(name, arg_name); + set(name, std::string(arg_name)); nonopt_index++; continue; } @@ -422,7 +422,7 @@ bool Settings::parseCommandLine(int argc, char *argv[], return false; } - std::string name = arg_name.substr(2); + std::string name(arg_name.substr(2)); auto n = allowed_options.find(name); if (n == allowed_options.end()) { @@ -997,7 +997,7 @@ bool Settings::remove(const std::string &name) SettingsParseEvent Settings::parseConfigObject(const std::string &line, std::string &name, std::string &value) { - std::string trimmed_line = trim(line); + auto trimmed_line = trim(line); if (trimmed_line.empty()) return SPE_NONE; diff --git a/src/settings.h b/src/settings.h index 1aeec0255..692001c59 100644 --- a/src/settings.h +++ b/src/settings.h @@ -23,8 +23,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/string.h" #include "util/basic_macros.h" #include -#include #include +#include #include class Settings; @@ -124,18 +124,17 @@ typedef std::unordered_map SettingEntries; class Settings { public: /* These functions operate on the global hierarchy! */ - static Settings *createLayer(SettingsLayer sl, const std::string &end_tag = ""); + static Settings *createLayer(SettingsLayer sl, std::string_view end_tag = ""); static Settings *getLayer(SettingsLayer sl); /**/ - Settings(const std::string &end_tag = "") : + Settings(std::string_view end_tag = "") : m_end_tag(end_tag) {} - Settings(const std::string &end_tag, SettingsHierarchy *h, int settings_layer); + Settings(std::string_view end_tag, SettingsHierarchy *h, int settings_layer); ~Settings(); - Settings & operator += (const Settings &other); - Settings & operator = (const Settings &other); + Settings & operator=(const Settings &other); /*********************** * Reading and writing * @@ -258,8 +257,8 @@ private: bool updateConfigObject(std::istream &is, std::ostream &os, u32 tab_depth=0); - static bool checkNameValid(const std::string &name); - static bool checkValueValid(const std::string &value); + static bool checkNameValid(std::string_view name); + static bool checkValueValid(std::string_view value); static std::string getMultiline(std::istream &is, size_t *num_lines=NULL); static void printEntry(std::ostream &os, const std::string &name, const SettingsEntry &entry, u32 tab_depth=0); @@ -276,9 +275,7 @@ private: // For sane mutex locking when iterating friend class LuaSettings; - void updateNoLock(const Settings &other); void clearNoLock(); - void clearDefaultsNoLock(); void doCallbacks(const std::string &name) const; diff --git a/src/sound.h b/src/sound.h index 5a593e6d0..afb9ac9ef 100644 --- a/src/sound.h +++ b/src/sound.h @@ -33,7 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc., */ struct SoundSpec { - SoundSpec(const std::string &name = "", float gain = 1.0f, + SoundSpec(std::string_view name = "", float gain = 1.0f, bool loop = false, float fade = 0.0f, float pitch = 1.0f, float start_time = 0.0f) : name(name), gain(gain), fade(fade), pitch(pitch), start_time(start_time), diff --git a/src/tileanimation.cpp b/src/tileanimation.cpp index bbccfbaa1..53ae264af 100644 --- a/src/tileanimation.cpp +++ b/src/tileanimation.cpp @@ -31,11 +31,11 @@ void TileAnimationParams::serialize(std::ostream &os, u16 protocol_ver) const if (type == TAT_VERTICAL_FRAMES) { writeU16(os, vertical_frames.aspect_w); writeU16(os, vertical_frames.aspect_h); - writeF32(os, need_abs ? fabs(vertical_frames.length) : vertical_frames.length); + writeF32(os, need_abs ? std::abs(vertical_frames.length) : vertical_frames.length); } else if (type == TAT_SHEET_2D) { writeU8(os, sheet_2d.frames_w); writeU8(os, sheet_2d.frames_h); - writeF32(os, need_abs ? fabs(sheet_2d.frame_length) : sheet_2d.frame_length); + writeF32(os, need_abs ? std::abs(sheet_2d.frame_length) : sheet_2d.frame_length); } } diff --git a/src/tool.cpp b/src/tool.cpp index 220df24ac..3f3c2f7bd 100644 --- a/src/tool.cpp +++ b/src/tool.cpp @@ -26,7 +26,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "convert_json.h" #include "util/serialize.h" #include "util/numeric.h" +#include "util/hex.h" +#include "common/c_content.h" #include + void ToolGroupCap::toJson(Json::Value &object) const { @@ -183,6 +186,127 @@ void ToolCapabilities::deserializeJson(std::istream &is) } } +void WearBarParams::serialize(std::ostream &os) const +{ + writeU8(os, 1); // Version for future-proofing + writeU8(os, blend); + writeU16(os, colorStops.size()); + for (const std::pair item : colorStops) { + writeF32(os, item.first); + writeARGB8(os, item.second); + } +} + +WearBarParams WearBarParams::deserialize(std::istream &is) +{ + u8 version = readU8(is); + if (version > 1) + throw SerializationError("unsupported WearBarParams version"); + + auto blend = static_cast(readU8(is)); + if (blend >= BlendMode_END) + throw SerializationError("invalid blend mode"); + u16 count = readU16(is); + if (count == 0) + throw SerializationError("no stops"); + std::map colorStops; + for (u16 i = 0; i < count; i++) { + f32 key = readF32(is); + if (key < 0 || key > 1) + throw SerializationError("key out of range"); + video::SColor color = readARGB8(is); + colorStops.emplace(key, color); + } + return WearBarParams(colorStops, blend); +} + +void WearBarParams::serializeJson(std::ostream &os) const +{ + Json::Value root; + Json::Value color_stops; + for (const std::pair item : colorStops) { + color_stops[ftos(item.first)] = encodeHexColorString(item.second); + } + root["color_stops"] = color_stops; + root["blend"] = WearBarParams::es_BlendMode[blend].str; + + fastWriteJson(root, os); +} + +std::optional WearBarParams::deserializeJson(std::istream &is) +{ + Json::Value root; + is >> root; + if (!root.isObject() || !root["color_stops"].isObject() || !root["blend"].isString()) + return std::nullopt; + + int blendInt; + WearBarParams::BlendMode blend; + if (string_to_enum(WearBarParams::es_BlendMode, blendInt, root["blend"].asString())) + blend = static_cast(blendInt); + else + return std::nullopt; + + const Json::Value &color_stops_object = root["color_stops"]; + std::map colorStops; + for (const std::string &key : color_stops_object.getMemberNames()) { + f32 stop = stof(key); + if (stop < 0 || stop > 1) + return std::nullopt; + const Json::Value &value = color_stops_object[key]; + if (value.isString()) { + video::SColor color; + parseColorString(value.asString(), color, false); + colorStops.emplace(stop, color); + } + } + if (colorStops.empty()) + return std::nullopt; + return WearBarParams(colorStops, blend); +} + +video::SColor WearBarParams::getWearBarColor(f32 durabilityPercent) { + if (colorStops.empty()) + return video::SColor(); + + /* + * Strategy: + * Find upper bound of durabilityPercent + * + * if it == stops.end() -> return last color in the map + * if it == stops.begin() -> return first color in the map + * + * else: + * lower_bound = it - 1 + * interpolate/do constant + */ + auto upper = colorStops.upper_bound(durabilityPercent); + + if (upper == colorStops.end()) // durability is >= the highest defined color stop + return std::prev(colorStops.end())->second; // return last element of the map + + if (upper == colorStops.begin()) // durability is <= the lowest defined color stop + return upper->second; + + auto lower = std::prev(upper); + f32 lower_bound = lower->first; + video::SColor lower_color = lower->second; + f32 upper_bound = upper->first; + video::SColor upper_color = upper->second; + + f32 progress = (durabilityPercent - lower_bound) / (upper_bound - lower_bound); + + switch (blend) { + case BLEND_MODE_CONSTANT: + return lower_color; + case BLEND_MODE_LINEAR: + return upper_color.getInterpolated(lower_color, progress); + case BlendMode_END: + throw std::logic_error("dummy value"); + } + throw std::logic_error("invalid blend value"); +} + u32 calculateResultWear(const u32 uses, const u16 initial_wear) { if (uses == 0) { @@ -292,8 +416,7 @@ DigParams getDigParams(const ItemGroupList &groups, // 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); + const u32 real_uses = std::min(cap.uses * pow(3.0, leveldiff), U16_MAX); result_wear = calculateResultWear(real_uses, initial_wear); result_main_group = groupname; } diff --git a/src/tool.h b/src/tool.h index 8a22fca7b..3121e907d 100644 --- a/src/tool.h +++ b/src/tool.h @@ -20,10 +20,15 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include "irrlichttypes.h" -#include -#include #include "itemgroup.h" #include "json-forwards.h" +#include "common/c_types.h" +#include +#include + +#include +#include +#include struct ItemDefinition; @@ -82,6 +87,37 @@ struct ToolCapabilities void deserializeJson(std::istream &is); }; +struct WearBarParams +{ + std::map colorStops; + enum BlendMode: u8 { + BLEND_MODE_CONSTANT, + BLEND_MODE_LINEAR, + BlendMode_END // Dummy for validity check + }; + constexpr const static EnumString es_BlendMode[3] = { + {WearBarParams::BLEND_MODE_CONSTANT, "constant"}, + {WearBarParams::BLEND_MODE_LINEAR, "linear"}, + {0, NULL} + }; + BlendMode blend; + + WearBarParams(const std::map &colorStops, BlendMode blend): + colorStops(colorStops), + blend(blend) + {} + + WearBarParams(const video::SColor color): + WearBarParams({{0.0, color}}, WearBarParams::BLEND_MODE_CONSTANT) + {}; + + void serialize(std::ostream &os) const; + static WearBarParams deserialize(std::istream &is); + void serializeJson(std::ostream &os) const; + static std::optional deserializeJson(std::istream &is); + video::SColor getWearBarColor(f32 durabilityPercent); +}; + struct DigParams { bool diggable; diff --git a/src/translation.cpp b/src/translation.cpp index eabd0ec0a..336e3dd6b 100644 --- a/src/translation.cpp +++ b/src/translation.cpp @@ -54,6 +54,7 @@ const std::wstring &Translations::getTranslation( void Translations::loadTranslation(const std::string &data) { std::istringstream is(data); + std::string textdomain_narrow; std::wstring textdomain; std::string line; @@ -70,7 +71,8 @@ void Translations::loadTranslation(const std::string &data) << "\"" << std::endl; continue; } - textdomain = utf8_to_wide(trim(parts[1])); + textdomain_narrow = trim(parts[1]); + textdomain = utf8_to_wide(textdomain_narrow); } if (line.empty() || line[0] == '#') continue; @@ -116,7 +118,7 @@ void Translations::loadTranslation(const std::string &data) if (i == wline.length()) { errorstream << "Malformed translation line \"" << line << "\"" - << std::endl; + << " in text domain " << textdomain_narrow << std::endl; continue; } i++; diff --git a/src/unittest/CMakeLists.txt b/src/unittest/CMakeLists.txt index 8466902df..3b8a48aaf 100644 --- a/src/unittest/CMakeLists.txt +++ b/src/unittest/CMakeLists.txt @@ -42,9 +42,12 @@ set (UNITTEST_SRCS PARENT_SCOPE) set (UNITTEST_CLIENT_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/mesh_compare.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_clientactiveobjectmgr.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_content_mapblock.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_eventmanager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_gameui.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_mesh_compare.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_keycode.cpp PARENT_SCOPE) diff --git a/src/unittest/mesh_compare.cpp b/src/unittest/mesh_compare.cpp new file mode 100644 index 000000000..6e21d377b --- /dev/null +++ b/src/unittest/mesh_compare.cpp @@ -0,0 +1,103 @@ +/* +Minetest +Copyright (C) 2023 Vitaliy Lobachevskiy + +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 "mesh_compare.h" +#include +#include +#include + +static std::vector expandMesh(const std::vector &vertices, const std::vector &indices) +{ + const int n_indices = indices.size(); + const int n_triangles = n_indices / 3; + if (n_indices % 3) + throw std::invalid_argument("got fractional number of triangles"); + + std::vector ret(n_triangles); + for (int i_triangle = 0; i_triangle < n_triangles; i_triangle++) { + ret.at(i_triangle) = { + vertices.at(indices.at(3 * i_triangle)), + vertices.at(indices.at(3 * i_triangle + 1)), + vertices.at(indices.at(3 * i_triangle + 2)), + }; + } + + return ret; +} + +/// Sorts triangle vertices, keeping winding order. +static Triangle sortTriangle(Triangle t) +{ + if (t[0] < t[1] && t[0] < t[2]) return {t[0], t[1], t[2]}; + if (t[1] < t[2] && t[1] < t[0]) return {t[1], t[2], t[0]}; + if (t[2] < t[0] && t[2] < t[1]) return {t[2], t[0], t[1]}; + throw std::invalid_argument("got bad triangle"); +} + +static std::vector canonicalizeMesh(const std::vector &vertices, const std::vector &indices) +{ + std::vector mesh = expandMesh(vertices, indices); + for (auto &triangle: mesh) + triangle = sortTriangle(triangle); + std::sort(std::begin(mesh), std::end(mesh)); + return mesh; +} + +bool checkMeshEqual(const std::vector &vertices, const std::vector &indices, const std::vector &expected) +{ + auto actual = canonicalizeMesh(vertices, indices); + return actual == expected; +} + +bool checkMeshEqual(const std::vector &vertices, const std::vector &indices, const std::vector &expected) +{ + using QuadRefCount = std::array; + struct QuadRef { + unsigned quad_id; + int quad_part; + }; + + std::vector refs(expected.size()); + std::map tris; + for (unsigned k = 0; k < expected.size(); k++) { + auto &&quad = expected[k]; + // There are 2 ways to split a quad into two triangles. So for each quad, + // the mesh must contain either triangles 0 and 1, or triangles 2 and 3, + // from the following list. No more, no less. + tris.insert({sortTriangle({quad[0], quad[1], quad[2]}), {k, 0}}); + tris.insert({sortTriangle({quad[0], quad[2], quad[3]}), {k, 1}}); + tris.insert({sortTriangle({quad[0], quad[1], quad[3]}), {k, 2}}); + tris.insert({sortTriangle({quad[1], quad[2], quad[3]}), {k, 3}}); + } + + auto actual = canonicalizeMesh(vertices, indices); + for (auto &&tri: actual) { + auto itri = tris.find(tri); + if (itri == tris.end()) + return false; + refs[itri->second.quad_id][itri->second.quad_part] += 1; + } + + for (unsigned k = 0; k < expected.size(); k++) { + if (refs[k] != QuadRefCount{1, 1, 0, 0} && refs[k] != QuadRefCount{0, 0, 1, 1}) + return false; + } + + return true; +} diff --git a/src/unittest/mesh_compare.h b/src/unittest/mesh_compare.h new file mode 100644 index 000000000..3f0d53d67 --- /dev/null +++ b/src/unittest/mesh_compare.h @@ -0,0 +1,47 @@ +/* +Minetest +Copyright (C) 2023 Vitaliy Lobachevskiy + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once +#include +#include +#include +#include + +/// Represents a triangle as three vertices. +/// “Smallest” (according to <) vertex is expected to be first, others should follow in the counter-clockwise order. +using Triangle = std::array; + +/// Represents a quad as four vertices. +/// Vertices should be in the counter-clockwise order. +using Quad = std::array; + +/// Compare two meshes for equality. +/// @param vertices Vertices of the first mesh. Order doesn’t matter. +/// @param indices Indices of the first mesh. Triangle order doesn’t matter. Vertex order in a triangle only matters for winding. +/// @param expected The second mesh, in an expanded form. Must be sorted. +/// @returns Whether the two meshes are equal. +[[nodiscard]] bool checkMeshEqual(const std::vector &vertices, const std::vector &indices, const std::vector &expected); + +/// Compare two meshes for equality. +/// @param vertices Vertices of the first mesh. Order doesn’t matter. +/// @param indices Indices of the first mesh. Triangle order doesn’t matter. Vertex order in a triangle only matters for winding. +/// @param expected The second mesh, in a quad form. +/// @returns Whether the two meshes are equal. +/// @note There are two ways to split a quad into 2 triangles; either is allowed. +[[nodiscard]] bool checkMeshEqual(const std::vector &vertices, const std::vector &indices, const std::vector &expected); diff --git a/src/unittest/test_compression.cpp b/src/unittest/test_compression.cpp index a96282f58..3ada3fa00 100644 --- a/src/unittest/test_compression.cpp +++ b/src/unittest/test_compression.cpp @@ -57,7 +57,7 @@ void TestCompression::runTests(IGameDef *gamedef) void TestCompression::testRLECompression() { - SharedBuffer fromdata(4); + Buffer fromdata(4); fromdata[0]=1; fromdata[1]=5; fromdata[2]=5; @@ -106,7 +106,7 @@ void TestCompression::testRLECompression() void TestCompression::testZlibCompression() { - SharedBuffer fromdata(4); + Buffer fromdata(4); fromdata[0]=1; fromdata[1]=5; fromdata[2]=5; diff --git a/src/unittest/test_content_mapblock.cpp b/src/unittest/test_content_mapblock.cpp new file mode 100644 index 000000000..798206a5f --- /dev/null +++ b/src/unittest/test_content_mapblock.cpp @@ -0,0 +1,268 @@ +/* +Minetest +Copyright (C) 2023 Vitaliy Lobachevskiy + +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 +#include + +#include "gamedef.h" +#include "dummygamedef.h" +#include "client/content_mapblock.h" +#include "client/mapblock_mesh.h" +#include "client/meshgen/collector.h" +#include "mesh_compare.h" +#include "util/directiontables.h" + +namespace { + +class MockGameDef : public DummyGameDef { +public: + IWritableItemDefManager *item_mgr() noexcept { + return static_cast(m_itemdef); + } + + NodeDefManager *node_mgr() noexcept { + return const_cast(m_nodedef); + } + + content_t registerNode(ItemDefinition itemdef, ContentFeatures nodedef) { + item_mgr()->registerItem(itemdef); + return node_mgr()->set(nodedef.name, nodedef); + } + + void finalize() { + node_mgr()->resolveCrossrefs(); + } + + MeshMakeData makeSingleNodeMMD(bool smooth_lighting = true, bool for_shaders = true) + { + MeshMakeData data{ndef(), 1, for_shaders}; + data.setSmoothLighting(smooth_lighting); + data.m_blockpos = {0, 0, 0}; + for (s16 x = -1; x <= 1; x++) + for (s16 y = -1; y <= 1; y++) + for (s16 z = -1; z <= 1; z++) + data.m_vmanip.setNode({x, y, z}, {CONTENT_AIR, 0, 0}); + return data; + } + + content_t addSimpleNode(std::string name, u32 texture) + { + ItemDefinition itemdef; + itemdef.type = ITEM_NODE; + itemdef.name = "test:" + name; + itemdef.description = name; + + ContentFeatures f; + f.name = itemdef.name; + f.drawtype = NDT_NORMAL; + f.solidness = 2; + f.alpha = ALPHAMODE_OPAQUE; + for (TileDef &tiledef : f.tiledef) + tiledef.name = name + ".png"; + for (TileSpec &tile : f.tiles) + tile.layers[0].texture_id = texture; + + return registerNode(itemdef, f); + } + + content_t addLiquidSource(std::string name, u32 texture) + { + ItemDefinition itemdef; + itemdef.type = ITEM_NODE; + itemdef.name = "test:" + name + "_source"; + itemdef.description = name; + + ContentFeatures f; + f.name = itemdef.name; + f.drawtype = NDT_LIQUID; + f.solidness = 1; + f.alpha = ALPHAMODE_BLEND; + f.light_propagates = true; + f.param_type = CPT_LIGHT; + f.liquid_type = LIQUID_SOURCE; + f.liquid_viscosity = 4; + f.groups["liquids"] = 3; + f.liquid_alternative_source = "test:" + name + "_source"; + f.liquid_alternative_flowing = "test:" + name + "_flowing"; + for (TileDef &tiledef : f.tiledef) + tiledef.name = name + ".png"; + for (TileSpec &tile : f.tiles) + tile.layers[0].texture_id = texture; + + return registerNode(itemdef, f); + } + + content_t addLiquidFlowing(std::string name, u32 texture_top, u32 texture_side) + { + ItemDefinition itemdef; + itemdef.type = ITEM_NODE; + itemdef.name = "test:" + name + "_flowing"; + itemdef.description = name; + + ContentFeatures f; + f.name = itemdef.name; + f.drawtype = NDT_FLOWINGLIQUID; + f.solidness = 0; + f.alpha = ALPHAMODE_BLEND; + f.light_propagates = true; + f.param_type = CPT_LIGHT; + f.liquid_type = LIQUID_FLOWING; + f.liquid_viscosity = 4; + f.groups["liquids"] = 3; + f.liquid_alternative_source = "test:" + name + "_source"; + f.liquid_alternative_flowing = "test:" + name + "_flowing"; + f.tiledef_special[0].name = name + "_top.png"; + f.tiledef_special[1].name = name + "_side.png"; + f.special_tiles[0].layers[0].texture_id = texture_top; + f.special_tiles[1].layers[0].texture_id = texture_side; + + return registerNode(itemdef, f); + } +}; + +void set_light_decode_table() +{ + u8 table[LIGHT_SUN + 1] = { + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, + }; + memcpy(const_cast(light_decode_table), table, sizeof(table)); +} + +class TestMapblockMeshGenerator : public TestBase { +public: + TestMapblockMeshGenerator() { TestManager::registerTestModule(this); } + const char *getName() override { return "TestMapblockMeshGenerator"; } + + void runTests(IGameDef *gamedef) override; + void testSimpleNode(); + void testSurroundedNode(); + void testInterliquidSame(); + void testInterliquidDifferent(); +}; + +static TestMapblockMeshGenerator g_test_instance; + +void TestMapblockMeshGenerator::runTests(IGameDef *gamedef) +{ + set_light_decode_table(); + TEST(testSimpleNode); + TEST(testSurroundedNode); + TEST(testInterliquidSame); + TEST(testInterliquidDifferent); +} + +namespace quad { + constexpr float h = BS / 2.0f; + const Quad zp{{{{-h, -h, h}, {0, 0, 1}, 0, {1, 1}}, {{h, -h, h}, {0, 0, 1}, 0, {0, 1}}, {{h, h, h}, {0, 0, 1}, 0, {0, 0}}, {{-h, h, h}, {0, 0, 1}, 0, {1, 0}}}}; + const Quad yp{{{{-h, h, -h}, {0, 1, 0}, 0, {0, 1}}, {{-h, h, h}, {0, 1, 0}, 0, {0, 0}}, {{h, h, h}, {0, 1, 0}, 0, {1, 0}}, {{h, h, -h}, {0, 1, 0}, 0, {1, 1}}}}; + const Quad xp{{{{h, -h, -h}, {1, 0, 0}, 0, {0, 1}}, {{h, h, -h}, {1, 0, 0}, 0, {0, 0}}, {{h, h, h}, {1, 0, 0}, 0, {1, 0}}, {{h, -h, h}, {1, 0, 0}, 0, {1, 1}}}}; + const Quad zn{{{{-h, -h, -h}, {0, 0, -1}, 0, {0, 1}}, {{-h, h, -h}, {0, 0, -1}, 0, {0, 0}}, {{h, h, -h}, {0, 0, -1}, 0, {1, 0}}, {{h, -h, -h}, {0, 0, -1}, 0, {1, 1}}}}; + const Quad yn{{{{-h, -h, -h}, {0, -1, 0}, 0, {0, 0}}, {{h, -h, -h}, {0, -1, 0}, 0, {1, 0}}, {{h, -h, h}, {0, -1, 0}, 0, {1, 1}}, {{-h, -h, h}, {0, -1, 0}, 0, {0, 1}}}}; + const Quad xn{{{{-h, -h, -h}, {-1, 0, 0}, 0, {1, 1}}, {{-h, -h, h}, {-1, 0, 0}, 0, {0, 1}}, {{-h, h, h}, {-1, 0, 0}, 0, {0, 0}}, {{-h, h, -h}, {-1, 0, 0}, 0, {1, 0}}}}; +} + +void TestMapblockMeshGenerator::testSimpleNode() +{ + MockGameDef gamedef; + content_t stone = gamedef.addSimpleNode("stone", 42); + gamedef.finalize(); + + MeshMakeData data = gamedef.makeSingleNodeMMD(); + data.m_vmanip.setNode({0, 0, 0}, {stone, 0, 0}); + + MeshCollector col{{}}; + MapblockMeshGenerator mg{&data, &col, nullptr}; + mg.generate(); + UASSERTEQ(std::size_t, col.prebuffers[0].size(), 1); + UASSERTEQ(std::size_t, col.prebuffers[1].size(), 0); + + auto &&buf = col.prebuffers[0][0]; + UASSERTEQ(u32, buf.layer.texture_id, 42); + UASSERT(checkMeshEqual(buf.vertices, buf.indices, {quad::xn, quad::xp, quad::yn, quad::yp, quad::zn, quad::zp})); +} + +void TestMapblockMeshGenerator::testSurroundedNode() +{ + MockGameDef gamedef; + content_t stone = gamedef.addSimpleNode("stone", 42); + content_t wood = gamedef.addSimpleNode("wood", 13); + gamedef.finalize(); + + MeshMakeData data = gamedef.makeSingleNodeMMD(); + data.m_vmanip.setNode({0, 0, 0}, {stone, 0, 0}); + data.m_vmanip.setNode({1, 0, 0}, {wood, 0, 0}); + + MeshCollector col{{}}; + MapblockMeshGenerator mg{&data, &col, nullptr}; + mg.generate(); + UASSERTEQ(std::size_t, col.prebuffers[0].size(), 1); + UASSERTEQ(std::size_t, col.prebuffers[1].size(), 0); + + auto &&buf = col.prebuffers[0][0]; + UASSERTEQ(u32, buf.layer.texture_id, 42); + UASSERT(checkMeshEqual(buf.vertices, buf.indices, {quad::xn, quad::yn, quad::yp, quad::zn, quad::zp})); +} + +void TestMapblockMeshGenerator::testInterliquidSame() +{ + MockGameDef gamedef; + auto water = gamedef.addLiquidSource("water", 42); + gamedef.finalize(); + + MeshMakeData data = gamedef.makeSingleNodeMMD(); + data.m_vmanip.setNode({0, 0, 0}, {water, 0, 0}); + data.m_vmanip.setNode({1, 0, 0}, {water, 0, 0}); + + MeshCollector col{{}}; + MapblockMeshGenerator mg{&data, &col, nullptr}; + mg.generate(); + UASSERTEQ(std::size_t, col.prebuffers[0].size(), 1); + UASSERTEQ(std::size_t, col.prebuffers[1].size(), 0); + + auto &&buf = col.prebuffers[0][0]; + UASSERTEQ(u32, buf.layer.texture_id, 42); + UASSERT(checkMeshEqual(buf.vertices, buf.indices, {quad::xn, quad::yn, quad::yp, quad::zn, quad::zp})); +} + +void TestMapblockMeshGenerator::testInterliquidDifferent() +{ + MockGameDef gamedef; + auto water = gamedef.addLiquidSource("water", 42); + auto lava = gamedef.addLiquidSource("lava", 13); + gamedef.finalize(); + + MeshMakeData data = gamedef.makeSingleNodeMMD(); + data.m_vmanip.setNode({0, 0, 0}, {water, 0, 0}); + data.m_vmanip.setNode({0, 0, 1}, {lava, 0, 0}); + + MeshCollector col{{}}; + MapblockMeshGenerator mg{&data, &col, nullptr}; + mg.generate(); + UASSERTEQ(std::size_t, col.prebuffers[0].size(), 1); + UASSERTEQ(std::size_t, col.prebuffers[1].size(), 0); + + auto &&buf = col.prebuffers[0][0]; + UASSERTEQ(u32, buf.layer.texture_id, 42); + UASSERT(checkMeshEqual(buf.vertices, buf.indices, {quad::xn, quad::xp, quad::yn, quad::yp, quad::zn, quad::zp})); +} + +} diff --git a/src/unittest/test_datastructures.cpp b/src/unittest/test_datastructures.cpp index a7e24b989..0bf892353 100644 --- a/src/unittest/test_datastructures.cpp +++ b/src/unittest/test_datastructures.cpp @@ -104,10 +104,12 @@ void TestDataStructures::testMap1() UASSERT(t0.deleted); UASSERT(!t1.copied); UASSERT(!t1.deleted); - if (once |= 1) + if ((once |= 1)) break; } UASSERT(once); + + map.clear(); // ASan complains about stack-use-after-scope otherwise } void TestDataStructures::testMap2() @@ -121,6 +123,8 @@ void TestDataStructures::testMap2() UASSERT(t0.deleted); UASSERT(!t1.copied); UASSERT(!t1.deleted); + + map.clear(); } void TestDataStructures::testMap3() diff --git a/src/unittest/test_filesys.cpp b/src/unittest/test_filesys.cpp index dde541a7c..302371361 100644 --- a/src/unittest/test_filesys.cpp +++ b/src/unittest/test_filesys.cpp @@ -40,6 +40,7 @@ public: void testRemoveLastPathComponentWithTrailingDelimiter(); void testRemoveRelativePathComponent(); void testSafeWriteToFile(); + void testCopyFileContents(); }; static TestFileSys g_test_instance; @@ -52,6 +53,7 @@ void TestFileSys::runTests(IGameDef *gamedef) TEST(testRemoveLastPathComponentWithTrailingDelimiter); TEST(testRemoveRelativePathComponent); TEST(testSafeWriteToFile); + TEST(testCopyFileContents); } //////////////////////////////////////////////////////////////////////////////// @@ -59,7 +61,7 @@ void TestFileSys::runTests(IGameDef *gamedef) // adjusts a POSIX path to system-specific conventions // -> changes '/' to DIR_DELIM // -> absolute paths start with "C:\\" on windows -std::string p(std::string path) +static std::string p(std::string path) { for (size_t i = 0; i < path.size(); ++i) { if (path[i] == '/') { @@ -275,5 +277,37 @@ void TestFileSys::testSafeWriteToFile() UASSERT(fs::PathExists(dest_path)); std::string contents_actual; UASSERT(fs::ReadFile(dest_path, contents_actual)); - UASSERT(contents_actual == test_data); + UASSERTEQ(auto, contents_actual, test_data); +} + +void TestFileSys::testCopyFileContents() +{ + const auto dir_path = getTestTempDirectory(); + const auto file1 = dir_path + DIR_DELIM "src", file2 = dir_path + DIR_DELIM "dst"; + const std::string test_data("hello\0world", 11); + + // error case + UASSERT(!fs::CopyFileContents(file1, "somewhere")); + + { + std::ofstream ofs(file1); + ofs << test_data; + } + + // normal case + UASSERT(fs::CopyFileContents(file1, file2)); + std::string contents_actual; + UASSERT(fs::ReadFile(file2, contents_actual)); + UASSERTEQ(auto, contents_actual, test_data); + + // should overwrite and truncate + { + std::ofstream ofs(file2); + for (int i = 0; i < 10; i++) + ofs << "OH MY GAH"; + } + UASSERT(fs::CopyFileContents(file1, file2)); + contents_actual.clear(); + UASSERT(fs::ReadFile(file2, contents_actual)); + UASSERTEQ(auto, contents_actual, test_data); } diff --git a/src/unittest/test_gameui.cpp b/src/unittest/test_gameui.cpp index a6d853f19..7170031ba 100644 --- a/src/unittest/test_gameui.cpp +++ b/src/unittest/test_gameui.cpp @@ -30,7 +30,6 @@ public: void runTests(IGameDef *gamedef); void testInit(); - void testFlagSetters(); void testInfoText(); void testStatusText(); }; @@ -40,7 +39,6 @@ static TestGameUI g_test_instance; void TestGameUI::runTests(IGameDef *gamedef) { TEST(testInit); - TEST(testFlagSetters); TEST(testInfoText); TEST(testStatusText); } @@ -51,30 +49,18 @@ void TestGameUI::testInit() // Ensure flags on GameUI init UASSERT(gui.getFlags().show_chat) UASSERT(gui.getFlags().show_hud) - UASSERT(!gui.getFlags().show_minimap) UASSERT(!gui.getFlags().show_profiler_graph) // And after the initFlags init stage gui.initFlags(); UASSERT(gui.getFlags().show_chat) UASSERT(gui.getFlags().show_hud) - UASSERT(!gui.getFlags().show_minimap) UASSERT(!gui.getFlags().show_profiler_graph) // @TODO verify if we can create non UI nulldevice to test this function // gui.init(); } -void TestGameUI::testFlagSetters() -{ - GameUI gui{}; - gui.showMinimap(true); - UASSERT(gui.getFlags().show_minimap); - - gui.showMinimap(false); - UASSERT(!gui.getFlags().show_minimap); -} - void TestGameUI::testStatusText() { GameUI gui{}; diff --git a/src/unittest/test_map_settings_manager.cpp b/src/unittest/test_map_settings_manager.cpp index 84548a596..acada024b 100644 --- a/src/unittest/test_map_settings_manager.cpp +++ b/src/unittest/test_map_settings_manager.cpp @@ -189,10 +189,9 @@ void TestMapSettingsManager::testMapSettingsManager() SHA1 ctx; std::string metafile_contents; UASSERT(fs::ReadFile(test_mapmeta_path, metafile_contents)); - ctx.addBytes(&metafile_contents[0], metafile_contents.size()); - unsigned char *sha1_result = ctx.getDigest(); - int resultdiff = memcmp(sha1_result, expected_contents_hash, 20); - free(sha1_result); + ctx.addBytes(metafile_contents); + std::string sha1_result = ctx.getDigest(); + int resultdiff = memcmp(sha1_result.data(), expected_contents_hash, 20); UASSERT(!resultdiff); #endif diff --git a/src/unittest/test_mesh_compare.cpp b/src/unittest/test_mesh_compare.cpp new file mode 100644 index 000000000..63ce04231 --- /dev/null +++ b/src/unittest/test_mesh_compare.cpp @@ -0,0 +1,152 @@ +/* +Minetest +Copyright (C) 2023 Vitaliy Lobachevskiy + +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 "mesh_compare.h" + +// This is a self-test to ensure proper functionality of the vertex +// building functions (`Triangle`, `Quad`) and its validation function +// `checkMeshEqual` in preparation for the tests in test_content_mapblock.cpp +class TestMeshCompare : public TestBase { +public: + TestMeshCompare() { TestManager::registerTestModule(this); } + const char *getName() override { return "TestMeshCompare"; } + + void runTests(IGameDef *gamedef) override { + TEST(testTriangle); + TEST(testQuad); + } + + void testTriangle() { + UASSERT(checkMeshEqual({ + {{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}}, + {{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}}, + {{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}}, + }, {0, 1, 2}, { + Triangle{{ + {{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}}, + {{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}}, + {{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}}, + }}, + })); + UASSERT(checkMeshEqual({ + {{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}}, + {{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}}, + {{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}}, + }, {2, 0, 1}, { + Triangle{{ + {{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}}, + {{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}}, + {{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}}, + }}, + })); + UASSERT(!checkMeshEqual({ + {{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}}, + {{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}}, + {{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}}, + }, {0, 2, 1}, { + Triangle{{ + {{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}}, + {{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}}, + {{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}}, + }}, + })); + + UASSERT(checkMeshEqual({ + {{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}}, + {{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}}, + {{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}}, + }, {0, 1, 2}, { + Triangle{{ + {{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}}, + {{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}}, + {{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}}, + }}, + })); + UASSERT(!checkMeshEqual({ + {{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}}, + {{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}}, + {{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}}, + }, {0, 1, 2}, { + Triangle{{ + {{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}}, + {{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}}, + {{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}}, + }}, + })); + } + + void testQuad() { + UASSERT(checkMeshEqual({ + {{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}}, + {{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}}, + {{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}}, + {{1., -1., 1.}, {4., 0., 0.}, 4, {0., 0.}}, + }, {0, 1, 2, 0, 2, 3}, { + Quad{{ + {{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}}, + {{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}}, + {{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}}, + {{1., -1., 1.}, {4., 0., 0.}, 4, {0., 0.}}, + }}, + })); + UASSERT(checkMeshEqual({ + {{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}}, + {{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}}, + {{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}}, + {{1., -1., 1.}, {4., 0., 0.}, 4, {0., 0.}}, + }, {2, 3, 0, 1, 2, 0}, { + Quad{{ + {{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}}, + {{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}}, + {{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}}, + {{1., -1., 1.}, {4., 0., 0.}, 4, {0., 0.}}, + }}, + })); + UASSERT(checkMeshEqual({ + {{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}}, + {{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}}, + {{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}}, + {{1., -1., 1.}, {4., 0., 0.}, 4, {0., 0.}}, + }, {2, 3, 1, 0, 1, 3}, { + Quad{{ + {{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}}, + {{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}}, + {{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}}, + {{1., -1., 1.}, {4., 0., 0.}, 4, {0., 0.}}, + }}, + })); + UASSERT(checkMeshEqual({ + {{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}}, + {{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}}, + {{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}}, + {{1., -1., 1.}, {4., 0., 0.}, 4, {0., 0.}}, + }, {3, 0, 1, 1, 2, 3}, { + Quad{{ + {{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}}, + {{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}}, + {{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}}, + {{1., -1., 1.}, {4., 0., 0.}, 4, {0., 0.}}, + }}, + })); + } +}; + +static TestMeshCompare mesh_compare_test; diff --git a/src/unittest/test_servermodmanager.cpp b/src/unittest/test_servermodmanager.cpp index 30b02f184..f023e3a3b 100644 --- a/src/unittest/test_servermodmanager.cpp +++ b/src/unittest/test_servermodmanager.cpp @@ -82,7 +82,7 @@ void TestServerModManager::runTests(IGameDef *gamedef) TEST(testGetModNames); TEST(testGetModMediaPathsWrongDir); TEST(testGetModMediaPaths); - // TODO: test MINETEST_SUBGAME_PATH + // TODO: test MINETEST_GAME_PATH unsetenv("MINETEST_MOD_PATH"); } diff --git a/src/unittest/test_utilities.cpp b/src/unittest/test_utilities.cpp index 0a19f1466..9ebf3b873 100644 --- a/src/unittest/test_utilities.cpp +++ b/src/unittest/test_utilities.cpp @@ -240,20 +240,26 @@ void TestUtilities::testPadString() void TestUtilities::testStartsWith() { - UASSERT(str_starts_with(std::string(), std::string()) == true); + std::string the("the"); + UASSERT(str_starts_with(std::string(), "") == true); UASSERT(str_starts_with(std::string("the sharp pickaxe"), std::string()) == true); UASSERT(str_starts_with(std::string("the sharp pickaxe"), - std::string("the")) == true); + std::string_view(the)) == true); UASSERT(str_starts_with(std::string("the sharp pickaxe"), std::string("The")) == false); UASSERT(str_starts_with(std::string("the sharp pickaxe"), std::string("The"), true) == true); - UASSERT(str_starts_with(std::string("T"), std::string("The")) == false); + UASSERT(str_starts_with(std::string("T"), "The") == false); } void TestUtilities::testStrEqual() { + std::string foo("foo"); + UASSERT(str_equal(foo, std::string_view(foo))); + UASSERT(!str_equal(foo, std::string("bar"))); + UASSERT(str_equal(std::string_view(foo), std::string_view(foo))); + UASSERT(str_equal(std::wstring(L"FOO"), std::wstring(L"foo"), true)); UASSERT(str_equal(utf8_to_wide("abc"), utf8_to_wide("abc"))); UASSERT(str_equal(utf8_to_wide("ABC"), utf8_to_wide("abc"), true)); } @@ -629,14 +635,14 @@ void TestUtilities::testBase64() 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$_"); + UASSERTEQ(auto, sanitizeDirName("a", "~"), "a"); + UASSERTEQ(auto, sanitizeDirName(" ", "~"), "__"); + UASSERTEQ(auto, sanitizeDirName(" a ", "~"), "_a_"); + UASSERTEQ(auto, sanitizeDirName("COM1", "~"), "~COM1"); + UASSERTEQ(auto, sanitizeDirName("COM1", ":"), "_COM1"); + UASSERTEQ(auto, sanitizeDirName("cOm\u00B2", "~"), "~cOm\u00B2"); + UASSERTEQ(auto, sanitizeDirName("cOnIn$", "~"), "~cOnIn$"); + UASSERTEQ(auto, sanitizeDirName(" cOnIn$ ", "~"), "_cOnIn$_"); } template diff --git a/src/util/auth.cpp b/src/util/auth.cpp index 3dd5a9afa..d7d1da280 100644 --- a/src/util/auth.cpp +++ b/src/util/auth.cpp @@ -39,10 +39,9 @@ std::string translate_password(const std::string &name, std::string slt = name + password; SHA1 sha1; - sha1.addBytes(slt.c_str(), slt.length()); - unsigned char *digest = sha1.getDigest(); - std::string pwd = base64_encode(digest, 20); - free(digest); + sha1.addBytes(slt); + std::string digest = sha1.getDigest(); + std::string pwd = base64_encode(digest); return pwd; } @@ -112,8 +111,8 @@ std::string encode_srp_verifier(const std::string &verifier, { std::ostringstream ret_str; ret_str << "#1#" - << base64_encode((unsigned char *)salt.c_str(), salt.size()) << "#" - << base64_encode((unsigned char *)verifier.c_str(), verifier.size()); + << base64_encode(salt) << "#" + << base64_encode(verifier); return ret_str.str(); } diff --git a/src/util/base64.cpp b/src/util/base64.cpp index ac4421f51..2acf78167 100644 --- a/src/util/base64.cpp +++ b/src/util/base64.cpp @@ -45,15 +45,15 @@ static inline bool is_base64(unsigned char c) || c == '+' || c == '/'; } -bool base64_is_valid(std::string const& s) +bool base64_is_valid(std::string_view s) { size_t i = 0; for (; i < s.size(); ++i) if (!is_base64(s[i])) break; unsigned char padding = 3 - ((i + 3) % 4); - if ((padding == 1 && base64_chars_padding_1.find(s[i - 1]) == std::string::npos) - || (padding == 2 && base64_chars_padding_2.find(s[i - 1]) == std::string::npos) + if ((padding == 1 && base64_chars_padding_1.find(s[i - 1]) == s.npos) + || (padding == 2 && base64_chars_padding_2.find(s[i - 1]) == s.npos) || padding == 3) return false; int actual_padding = s.size() - i; @@ -69,8 +69,14 @@ bool base64_is_valid(std::string const& s) return padding == actual_padding; } -std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) { +std::string base64_encode(std::string_view s) +{ + const unsigned char *bytes_to_encode = reinterpret_cast(s.data()); + size_t in_len = s.size(); + std::string ret; + ret.reserve(in_len + in_len / 3); + int i = 0; int j = 0; unsigned char char_array_3[3]; @@ -110,16 +116,17 @@ std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_ } return ret; - } -std::string base64_decode(std::string const& encoded_string) { +std::string base64_decode(std::string_view encoded_string) +{ int in_len = encoded_string.size(); int i = 0; int j = 0; int in_ = 0; unsigned char char_array_4[4], char_array_3[3]; std::string ret; + ret.reserve(in_len / 4 * 3); while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) { char_array_4[i++] = encoded_string[in_]; in_++; diff --git a/src/util/base64.h b/src/util/base64.h index 68fa5b743..3846d3a25 100644 --- a/src/util/base64.h +++ b/src/util/base64.h @@ -29,7 +29,8 @@ René Nyffenegger rene.nyffenegger@adp-gmbh.ch #pragma once #include +#include -bool base64_is_valid(std::string const& s); -std::string base64_encode(unsigned char const* , unsigned int len); -std::string base64_decode(std::string const& s); +bool base64_is_valid(std::string_view s); +std::string base64_encode(std::string_view s); +std::string base64_decode(std::string_view s); diff --git a/src/util/hex.h b/src/util/hex.h index 708f33024..30e02994f 100644 --- a/src/util/hex.h +++ b/src/util/hex.h @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include +#include static const char hex_chars[] = "0123456789abcdef"; @@ -41,9 +42,9 @@ static inline std::string hex_encode(const char *data, unsigned int data_size) return ret; } -static inline std::string hex_encode(const std::string &data) +static inline std::string hex_encode(std::string_view data) { - return hex_encode(data.c_str(), data.size()); + return hex_encode(data.data(), data.size()); } static inline bool hex_digit_decode(char hexdigit, unsigned char &value) diff --git a/src/util/numeric.h b/src/util/numeric.h index d67dd0f5f..ec3145734 100644 --- a/src/util/numeric.h +++ b/src/util/numeric.h @@ -350,12 +350,10 @@ class IntervalLimiter public: IntervalLimiter() = default; - /* - dtime: time from last call to this method - wanted_interval: interval wanted - return value: - true: action should be skipped - false: action should be done + /** + @param dtime time from last call to this method + @param wanted_interval interval wanted + @return true if action should be done */ bool step(float dtime, float wanted_interval) { diff --git a/src/util/pointer.h b/src/util/pointer.h index f4b70f822..528897a1c 100644 --- a/src/util/pointer.h +++ b/src/util/pointer.h @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "debug.h" // For assert() #include #include // std::shared_ptr +#include template class ConstSharedPtr { @@ -133,6 +134,13 @@ public: return m_size; } + operator std::string_view() const + { + if (!data) + return std::string_view(); + return std::string_view(reinterpret_cast(data), m_size); + } + private: void drop() { diff --git a/src/util/serialize.cpp b/src/util/serialize.cpp index ee46fd941..e1552f602 100644 --- a/src/util/serialize.cpp +++ b/src/util/serialize.cpp @@ -34,7 +34,7 @@ FloatType g_serialize_f32_type = FLOATTYPE_UNKNOWN; //// String //// -std::string serializeString16(const std::string &plain) +std::string serializeString16(std::string_view plain) { std::string s; char buf[2]; @@ -76,7 +76,7 @@ std::string deSerializeString16(std::istream &is) //// Long String //// -std::string serializeString32(const std::string &plain) +std::string serializeString32(std::string_view plain) { std::string s; char buf[4]; @@ -122,7 +122,7 @@ std::string deSerializeString32(std::istream &is) //// JSON-like strings //// -std::string serializeJsonString(const std::string &plain) +std::string serializeJsonString(std::string_view plain) { std::string tmp; @@ -263,13 +263,13 @@ std::string deSerializeJsonString(std::istream &is) return tmp; } -std::string serializeJsonStringIfNeeded(const std::string &s) +std::string serializeJsonStringIfNeeded(std::string_view s) { for (size_t i = 0; i < s.size(); ++i) { if (s[i] <= 0x1f || s[i] >= 0x7f || s[i] == ' ' || s[i] == '\"') return serializeJsonString(s); } - return s; + return std::string(s); } std::string deSerializeJsonStringIfNeeded(std::istream &is) diff --git a/src/util/serialize.h b/src/util/serialize.h index 4dc1a54c6..00250ece5 100644 --- a/src/util/serialize.h +++ b/src/util/serialize.h @@ -41,7 +41,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include // for memcpy #include #include -#include +#include #define FIXEDPOINT_FACTOR 1000.0f @@ -450,26 +450,26 @@ inline v3f clampToF1000(v3f v) } // Creates a string with the length as the first two bytes -std::string serializeString16(const std::string &plain); +std::string serializeString16(std::string_view plain); // Reads a string with the length as the first two bytes std::string deSerializeString16(std::istream &is); // Creates a string with the length as the first four bytes -std::string serializeString32(const std::string &plain); +std::string serializeString32(std::string_view plain); // Reads a string with the length as the first four bytes std::string deSerializeString32(std::istream &is); // Creates a string encoded in JSON format (almost equivalent to a C string literal) -std::string serializeJsonString(const std::string &plain); +std::string serializeJsonString(std::string_view plain); // Reads a string encoded in JSON format std::string deSerializeJsonString(std::istream &is); // If the string contains spaces, quotes or control characters, encodes as JSON. // Else returns the string unmodified. -std::string serializeJsonStringIfNeeded(const std::string &s); +std::string serializeJsonStringIfNeeded(std::string_view s); // Parses a string serialized by serializeJsonStringIfNeeded. std::string deSerializeJsonStringIfNeeded(std::istream &is); diff --git a/src/util/sha1.cpp b/src/util/sha1.cpp index ba95909ff..5d8b92598 100644 --- a/src/util/sha1.cpp +++ b/src/util/sha1.cpp @@ -65,7 +65,7 @@ void SHA1::storeBigEndianUint32( unsigned char* byte, Uint32 num ) SHA1::SHA1() { // make sure that the data type is the right size - assert( sizeof( Uint32 ) * 5 == 20 ); + static_assert( sizeof( Uint32 ) * 5 == 20 ); } // Destructor ******************************************************** @@ -134,20 +134,19 @@ void SHA1::process() } // addBytes ********************************************************** -void SHA1::addBytes( const char* data, int num ) +void SHA1::addBytes( const char* data, Uint32 num ) { assert( data ); - assert( num >= 0 ); // add these bytes to the running total size += num; // repeat until all data is processed while( num > 0 ) { // number of bytes required to complete block - int needed = 64 - unprocessedBytes; - assert( needed > 0 ); + Uint32 needed = 64 - unprocessedBytes; + assert( needed <= 64 ); // number of bytes to copy (use smaller of two) - int toCopy = (num < needed) ? num : needed; + Uint32 toCopy = (num < needed) ? num : needed; // Copy the bytes memcpy( bytes + unprocessedBytes, data, toCopy ); // Bytes have been copied @@ -161,7 +160,7 @@ void SHA1::addBytes( const char* data, int num ) } // digest ************************************************************ -unsigned char* SHA1::getDigest() +void SHA1::getDigest(unsigned char *digest) { // save the message size Uint32 totalBitsL = size << 3; @@ -179,20 +178,16 @@ unsigned char* SHA1::getDigest() addBytes( (char*)footer, 64 - unprocessedBytes); assert( unprocessedBytes <= 56 ); // how many zeros do we need - int neededZeros = 56 - unprocessedBytes; + Uint32 neededZeros = 56 - unprocessedBytes; // store file size (in bits) in big-endian format storeBigEndianUint32( footer + neededZeros , totalBitsH ); storeBigEndianUint32( footer + neededZeros + 4, totalBitsL ); // finish the final block addBytes( (char*)footer, neededZeros + 8 ); - // allocate memory for the digest bytes - unsigned char* digest = (unsigned char*)malloc( 20 ); // copy the digest bytes storeBigEndianUint32( digest, H0 ); storeBigEndianUint32( digest + 4, H1 ); storeBigEndianUint32( digest + 8, H2 ); storeBigEndianUint32( digest + 12, H3 ); storeBigEndianUint32( digest + 16, H4 ); - // return the digest - return digest; } diff --git a/src/util/sha1.h b/src/util/sha1.h index 20f89eae6..81eeeca97 100644 --- a/src/util/sha1.h +++ b/src/util/sha1.h @@ -26,7 +26,11 @@ SOFTWARE. #pragma once -typedef unsigned int Uint32; +#include +#include +#include + +typedef uint32_t Uint32; class SHA1 { @@ -38,15 +42,24 @@ private: Uint32 H3 = 0x10325476; Uint32 H4 = 0xc3d2e1f0; unsigned char bytes[64]; - int unprocessedBytes = 0; + Uint32 unprocessedBytes = 0; Uint32 size = 0; void process(); public: SHA1(); ~SHA1(); - void addBytes(const char *data, int num); - unsigned char *getDigest(); + void addBytes(const char *data, Uint32 num); + inline void addBytes(std::string_view data) { + addBytes(data.data(), data.size()); + } + void getDigest(unsigned char *to); + inline std::string getDigest() { + std::string ret(20, '\000'); + getDigest(reinterpret_cast(ret.data())); + return ret; + } + // utility methods static Uint32 lrot(Uint32 x, int bits); static void storeBigEndianUint32(unsigned char *byte, Uint32 num); diff --git a/src/util/strfnd.h b/src/util/strfnd.h index 4ba599437..296e37a82 100644 --- a/src/util/strfnd.h +++ b/src/util/strfnd.h @@ -20,14 +20,19 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include +#include +// FIXME: convert this class to string_view template class BasicStrfnd { typedef std::basic_string String; String str; size_t pos; public: - BasicStrfnd(const String &s) : str(s), pos(0) {} + BasicStrfnd(const String &s) { start(s); } + BasicStrfnd(const T *ptr) { str = ptr; pos = 0; } + BasicStrfnd(std::basic_string_view sv) { str = sv; pos = 0; } + void start(const String &s) { str = s; pos = 0; } size_t where() { return pos; } void to(size_t i) { pos = i; } diff --git a/src/util/string.cpp b/src/util/string.cpp index b4af7a404..534459e2a 100644 --- a/src/util/string.cpp +++ b/src/util/string.cpp @@ -82,14 +82,14 @@ const char *DEFAULT_ENCODING = "UTF-32LE"; const char *DEFAULT_ENCODING = "WCHAR_T"; #endif -std::wstring utf8_to_wide(const std::string &input) +std::wstring utf8_to_wide(std::string_view input) { const size_t inbuf_size = input.length(); // maximum possible size, every character is sizeof(wchar_t) bytes size_t outbuf_size = input.length() * sizeof(wchar_t); char *inbuf = new char[inbuf_size]; // intentionally NOT null-terminated - memcpy(inbuf, input.c_str(), inbuf_size); + memcpy(inbuf, input.data(), inbuf_size); std::wstring out; out.resize(outbuf_size / sizeof(wchar_t)); @@ -110,14 +110,14 @@ std::wstring utf8_to_wide(const std::string &input) return out; } -std::string wide_to_utf8(const std::wstring &input) +std::string wide_to_utf8(std::wstring_view input) { const size_t inbuf_size = input.length() * sizeof(wchar_t); // maximum possible size: utf-8 encodes codepoints using 1 up to 4 bytes size_t outbuf_size = input.length() * 4; char *inbuf = new char[inbuf_size]; // intentionally NOT null-terminated - memcpy(inbuf, input.c_str(), inbuf_size); + memcpy(inbuf, input.data(), inbuf_size); std::string out; out.resize(outbuf_size); @@ -135,24 +135,24 @@ std::string wide_to_utf8(const std::wstring &input) #else // _WIN32 -std::wstring utf8_to_wide(const std::string &input) +std::wstring utf8_to_wide(std::string_view input) { size_t outbuf_size = input.size() + 1; wchar_t *outbuf = new wchar_t[outbuf_size]; memset(outbuf, 0, outbuf_size * sizeof(wchar_t)); - MultiByteToWideChar(CP_UTF8, 0, input.c_str(), input.size(), + MultiByteToWideChar(CP_UTF8, 0, input.data(), input.size(), outbuf, outbuf_size); std::wstring out(outbuf); delete[] outbuf; return out; } -std::string wide_to_utf8(const std::wstring &input) +std::string wide_to_utf8(std::wstring_view input) { size_t outbuf_size = (input.size() + 1) * 6; char *outbuf = new char[outbuf_size]; memset(outbuf, 0, outbuf_size); - WideCharToMultiByte(CP_UTF8, 0, input.c_str(), input.size(), + WideCharToMultiByte(CP_UTF8, 0, input.data(), input.size(), outbuf, outbuf_size, NULL, NULL); std::string out(outbuf); delete[] outbuf; @@ -162,9 +162,9 @@ std::string wide_to_utf8(const std::wstring &input) #endif // _WIN32 -std::string urlencode(const std::string &str) +std::string urlencode(std::string_view str) { - // Encodes non-unreserved URI characters by a percent sign + // Encodes reserved URI characters by a percent sign // followed by two hex digits. See RFC 3986, section 2.3. static const char url_hex_chars[] = "0123456789ABCDEF"; std::ostringstream oss(std::ios::binary); @@ -180,7 +180,7 @@ std::string urlencode(const std::string &str) return oss.str(); } -std::string urldecode(const std::string &str) +std::string urldecode(std::string_view str) { // Inverse of urlencode std::ostringstream oss(std::ios::binary); @@ -574,6 +574,20 @@ bool parseColorString(const std::string &value, video::SColor &color, bool quiet return success; } +std::string encodeHexColorString(const video::SColor &color) +{ + std::string color_string = "#"; + const char red = color.getRed(); + const char green = color.getGreen(); + const char blue = color.getBlue(); + const char alpha = color.getAlpha(); + color_string += hex_encode(&red, 1); + color_string += hex_encode(&green, 1); + color_string += hex_encode(&blue, 1); + color_string += hex_encode(&alpha, 1); + return color_string; +} + void str_replace(std::string &str, char from, char to) { std::replace(str.begin(), str.end(), from, to); @@ -601,10 +615,10 @@ void str_replace(std::string &str, char from, char to) * before filling it again. */ -void translate_all(const std::wstring &s, size_t &i, +static void translate_all(const std::wstring &s, size_t &i, Translations *translations, std::wstring &res); -void translate_string(const std::wstring &s, Translations *translations, +static void translate_string(const std::wstring &s, Translations *translations, const std::wstring &textdomain, size_t &i, std::wstring &res) { std::wostringstream output; @@ -718,14 +732,15 @@ void translate_string(const std::wstring &s, Translations *translations, res = result.str(); } -void translate_all(const std::wstring &s, size_t &i, +static void translate_all(const std::wstring &s, size_t &i, Translations *translations, std::wstring &res) { - std::wostringstream output; + res.clear(); + res.reserve(s.length()); while (i < s.length()) { // Not an escape sequence: just add the character. if (s[i] != '\x1b') { - output.put(s[i]); + res.append(1, s[i]); ++i; continue; } @@ -733,7 +748,7 @@ void translate_all(const std::wstring &s, size_t &i, // We have an escape sequence: locate it and its data // It is either a single character, or it begins with '(' // and extends up to the following ')', with '\' as an escape character. - size_t escape_start = i; + const size_t escape_start = i; ++i; size_t start_index = i; size_t length; @@ -770,14 +785,12 @@ void translate_all(const std::wstring &s, size_t &i, textdomain = parts[1]; std::wstring translated; translate_string(s, translations, textdomain, i, translated); - output << translated; + res.append(translated); } else { // Another escape sequence, such as colors. Preserve it. - output << std::wstring(s, escape_start, i - escape_start); + res.append(&s[escape_start], i - escape_start); } } - - res = output.str(); } // Translate string server side @@ -799,7 +812,7 @@ std::wstring translate_string(const std::wstring &s) #endif } -static const std::array disallowed_dir_names = { +static const std::array 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: @@ -839,10 +852,10 @@ static const std::array disallowed_dir_names = { /** * List of characters that are blacklisted from created directories */ -static const std::wstring disallowed_path_chars = L"<>:\"/\\|?*."; +static const std::wstring_view disallowed_path_chars = L"<>:\"/\\|?*."; -std::string sanitizeDirName(const std::string &str, const std::string &optional_prefix) +std::string sanitizeDirName(std::string_view str, std::string_view optional_prefix) { std::wstring safe_name = utf8_to_wide(str); @@ -883,7 +896,7 @@ std::string sanitizeDirName(const std::string &str, const std::string &optional_ } -void safe_print_string(std::ostream &os, const std::string &str) +void safe_print_string(std::ostream &os, std::string_view str) { std::ostream::fmtflags flags = os.flags(); os << std::hex; @@ -899,7 +912,7 @@ void safe_print_string(std::ostream &os, const std::string &str) } -v3f str_to_v3f(const std::string &str) +v3f str_to_v3f(std::string_view str) { v3f value; Strfnd f(str); diff --git a/src/util/string.h b/src/util/string.h index 5d49cc77d..36c374bdf 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -23,10 +23,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrString.h" #include #include +#include #include #include #include -#include #include #include #include @@ -43,13 +43,19 @@ class Translations; ( (unsigned int)(x) <= 0x7e)) // Checks whether a value is in a Unicode private use area -#define IS_PRIVATE_USE_CHAR(x) \ - (((wchar_t)(x) >= 0xE000 && \ - (wchar_t)(x) <= 0xF8FF) || \ - ((wchar_t)(x) >= 0xF0000 && \ +#define IS_PRIVATE_USE_CHAR16(x) \ + ((wchar_t)(x) >= 0xE000 && \ + (wchar_t)(x) <= 0xF8FF) +#define IS_PRIVATE_USE_CHAR32(x) \ + (((wchar_t)(x) >= 0xF0000 && \ (wchar_t)(x) <= 0xFFFFD) || \ ((wchar_t)(x) >= 0x100000 && \ - (wchar_t)(x) <= 0x10FFFD)) \ + (wchar_t)(x) <= 0x10FFFD)) +#if WCHAR_MAX > 0xFFFF +#define IS_PRIVATE_USE_CHAR(x) (IS_PRIVATE_USE_CHAR16(x) || IS_PRIVATE_USE_CHAR32(x)) +#else +#define IS_PRIVATE_USE_CHAR(x) IS_PRIVATE_USE_CHAR16(x) +#endif // Checks whether a byte is an inner byte for an utf-8 multibyte sequence #define IS_UTF8_MULTB_INNER(x) \ @@ -76,18 +82,22 @@ struct FlagDesc { // Try to avoid converting between wide and UTF-8 unless you need to // input/output stuff via Irrlicht -std::wstring utf8_to_wide(const std::string &input); -std::string wide_to_utf8(const std::wstring &input); +std::wstring utf8_to_wide(std::string_view input); +std::string wide_to_utf8(std::wstring_view input); + +std::string urlencode(std::string_view str); +std::string urldecode(std::string_view str); -std::string urlencode(const std::string &str); -std::string urldecode(const std::string &str); u32 readFlagString(std::string str, const FlagDesc *flagdesc, u32 *flagmask); std::string writeFlagString(u32 flags, const FlagDesc *flagdesc, u32 flagmask); + size_t mystrlcpy(char *dst, const char *src, size_t size) noexcept; char *mystrtok_r(char *s, const char *sep, char **lasts) noexcept; + u64 read_seed(const char *str); bool parseColorString(const std::string &value, video::SColor &color, bool quiet, unsigned char default_alpha = 0xff); +std::string encodeHexColorString(const video::SColor &color); /** @@ -114,23 +124,32 @@ inline std::string padStringRight(std::string str, size_t len) * * @return If no end could be removed then "" is returned. */ -inline std::string removeStringEnd(const std::string &str, +inline std::string_view removeStringEnd(std::string_view str, const char *ends[]) { const char **p = ends; for (; *p && (*p)[0] != '\0'; p++) { - std::string end = *p; + std::string_view end(*p); if (str.size() < end.size()) continue; if (str.compare(str.size() - end.size(), end.size(), end) == 0) return str.substr(0, str.size() - end.size()); } - return ""; + return std::string_view(); } +#define MAKE_VARIANT(_name, _t0, _t1) \ + template \ + inline auto _name(_t0 arg1, _t1 arg2, Args&&... args) \ + { \ + return (_name)(std::basic_string_view(arg1), std::basic_string_view(arg2), \ + std::forward(args)...); \ + } + + /** * Check two strings for equivalence. If \p case_insensitive is true * then the case of the strings is ignored (default is false). @@ -141,8 +160,8 @@ inline std::string removeStringEnd(const std::string &str, * @return true if the strings match */ template -inline bool str_equal(const std::basic_string &s1, - const std::basic_string &s2, +inline bool str_equal(std::basic_string_view s1, + std::basic_string_view s2, bool case_insensitive = false) { if (!case_insensitive) @@ -158,6 +177,16 @@ inline bool str_equal(const std::basic_string &s1, return true; } +// For some reason an std::string will not implicitly get converted +// to an std::basic_string_view in the template case above, so we need +// these three wrappers. It works if you take out the template parameters. +// see also +MAKE_VARIANT(str_equal, const std::basic_string &, const std::basic_string &) + +MAKE_VARIANT(str_equal, std::basic_string_view, const std::basic_string &) + +MAKE_VARIANT(str_equal, const std::basic_string &, std::basic_string_view) + /** * Check whether \p str begins with the string prefix. If \p case_insensitive @@ -170,8 +199,8 @@ inline bool str_equal(const std::basic_string &s1, * @return true if the str begins with prefix */ template -inline bool str_starts_with(const std::basic_string &str, - const std::basic_string &prefix, +inline bool str_starts_with(std::basic_string_view str, + std::basic_string_view prefix, bool case_insensitive = false) { if (str.size() < prefix.size()) @@ -186,24 +215,17 @@ inline bool str_starts_with(const std::basic_string &str, return true; } -/** - * Check whether \p str begins with the string prefix. If \p case_insensitive - * is true then the check is case insensitve (default is false; i.e. case is - * significant). - * - * @param str - * @param prefix - * @param case_insensitive - * @return true if the str begins with prefix - */ -template -inline bool str_starts_with(const std::basic_string &str, - const T *prefix, - bool case_insensitive = false) -{ - return str_starts_with(str, std::basic_string(prefix), - case_insensitive); -} +// (same conversion issue here) +MAKE_VARIANT(str_starts_with, const std::basic_string &, const std::basic_string &) + +MAKE_VARIANT(str_starts_with, std::basic_string_view, const std::basic_string &) + +MAKE_VARIANT(str_starts_with, const std::basic_string &, std::basic_string_view) + +// (the same but with char pointers, only for the prefix argument) +MAKE_VARIANT(str_starts_with, const std::basic_string &, const T*) + +MAKE_VARIANT(str_starts_with, std::basic_string_view, const T*) /** @@ -217,8 +239,8 @@ inline bool str_starts_with(const std::basic_string &str, * @return true if the str begins with suffix */ template -inline bool str_ends_with(const std::basic_string &str, - const std::basic_string &suffix, +inline bool str_ends_with(std::basic_string_view str, + std::basic_string_view suffix, bool case_insensitive = false) { if (str.size() < suffix.size()) @@ -234,25 +256,20 @@ inline bool str_ends_with(const std::basic_string &str, return true; } +// (same conversion issue here) +MAKE_VARIANT(str_ends_with, const std::basic_string &, const std::basic_string &) -/** - * Check whether \p str ends with the string suffix. If \p case_insensitive - * is true then the check is case insensitve (default is false; i.e. case is - * significant). - * - * @param str - * @param suffix - * @param case_insensitive - * @return true if the str begins with suffix - */ -template -inline bool str_ends_with(const std::basic_string &str, - const T *suffix, - bool case_insensitive = false) -{ - return str_ends_with(str, std::basic_string(suffix), - case_insensitive); -} +MAKE_VARIANT(str_ends_with, std::basic_string_view, const std::basic_string &) + +MAKE_VARIANT(str_ends_with, const std::basic_string &, std::basic_string_view) + +// (the same but with char pointers, only for the suffix argument) +MAKE_VARIANT(str_ends_with, const std::basic_string &, const T*) + +MAKE_VARIANT(str_ends_with, std::basic_string_view, const T*) + + +#undef MAKE_VARIANT /** @@ -281,24 +298,21 @@ inline std::vector > str_split( * @param str * @return A copy of \p str converted to all lowercase characters. */ -inline std::string lowercase(const std::string &str) +inline std::string lowercase(std::string_view str) { std::string s2; - - s2.reserve(str.size()); - - for (char i : str) - s2 += tolower(i); - + s2.resize(str.size()); + for (size_t i = 0; i < str.size(); i++) + s2[i] = tolower(str[i]); return s2; } /** * @param str - * @return A copy of \p str with leading and trailing whitespace removed. + * @return A view of \p str with leading and trailing whitespace removed. */ -inline std::string trim(const std::string &str) +inline std::string_view trim(std::string_view str) { size_t front = 0; size_t back = str.size(); @@ -312,6 +326,26 @@ inline std::string trim(const std::string &str) return str.substr(front, back - front); } +// If input was a temporary string keep it one to make sure patterns like +// trim(func_that_returns_str()) are predictable regarding memory allocation +// and don't lead to UAF. ↓ ↓ ↓ + +/** + * @param str + * @return A copy of \p str with leading and trailing whitespace removed. + */ +inline std::string trim(std::string &&str) +{ + std::string ret(trim(std::string_view(str))); + return ret; +} + +// The above declaration causes ambiguity with char pointers so we have to fix that: +inline std::string_view trim(const char *str) +{ + return trim(std::string_view(str)); +} + /** * Returns whether \p str should be regarded as (bool) true. Case and leading @@ -319,7 +353,7 @@ inline std::string trim(const std::string &str) * true are "y", "yes", "true" and any number that is not 0. * @param str */ -inline bool is_yes(const std::string &str) +inline bool is_yes(std::string_view str) { std::string s2 = lowercase(trim(str)); @@ -376,7 +410,7 @@ inline float mystof(const std::string &str) template inline T from_string(const std::string &str) { - std::stringstream tmp(str); + std::istringstream tmp(str); T t; tmp >> t; return t; @@ -385,42 +419,6 @@ inline T from_string(const std::string &str) /// Returns a 64-bit signed value represented by the string \p str (decimal). inline s64 stoi64(const std::string &str) { return from_string(str); } -#if __cplusplus < 201103L -namespace std { - -/// Returns a string representing the value \p val. -template -inline string to_string(T val) -{ - ostringstream oss; - oss << val; - return oss.str(); -} -#define DEFINE_STD_TOSTRING_FLOATINGPOINT(T) \ - template <> \ - inline string to_string(T val) \ - { \ - ostringstream oss; \ - oss << std::fixed \ - << std::setprecision(6) \ - << val; \ - return oss.str(); \ - } -DEFINE_STD_TOSTRING_FLOATINGPOINT(float) -DEFINE_STD_TOSTRING_FLOATINGPOINT(double) -DEFINE_STD_TOSTRING_FLOATINGPOINT(long double) - -#undef DEFINE_STD_TOSTRING_FLOATINGPOINT - -/// Returns a wide string representing the value \p val -template -inline wstring to_wstring(T val) -{ - return utf8_to_wide(to_string(val)); -} -} -#endif - /// Returns a string representing the decimal value of the 32-bit value \p i. inline std::string itos(s32 i) { return std::to_string(i); } /// Returns a string representing the decimal value of the 64-bit value \p i. @@ -442,8 +440,8 @@ inline std::string ftos(float f) * @param pattern The pattern to replace. * @param replacement What to replace the pattern with. */ -inline void str_replace(std::string &str, const std::string &pattern, - const std::string &replacement) +inline void str_replace(std::string &str, std::string_view pattern, + std::string_view replacement) { std::string::size_type start = str.find(pattern, 0); while (start != str.npos) { @@ -453,7 +451,7 @@ inline void str_replace(std::string &str, const std::string &pattern, } /** - * Escapes characters [ ] \ , ; that cannot be used in formspecs + * Escapes characters that cannot be used in formspecs */ inline void str_formspec_escape(std::string &str) { @@ -485,7 +483,7 @@ void str_replace(std::string &str, char from, char to); * * @see string_allowed_blacklist() */ -inline bool string_allowed(const std::string &str, const std::string &allowed_chars) +inline bool string_allowed(std::string_view str, std::string_view allowed_chars) { return str.find_first_not_of(allowed_chars) == str.npos; } @@ -501,8 +499,8 @@ inline bool string_allowed(const std::string &str, const std::string &allowed_ch * @see string_allowed() */ -inline bool string_allowed_blacklist(const std::string &str, - const std::string &blacklisted_chars) +inline bool string_allowed_blacklist(std::string_view str, + std::string_view blacklisted_chars) { return str.find_first_of(blacklisted_chars) == str.npos; } @@ -523,12 +521,12 @@ inline bool string_allowed_blacklist(const std::string &str, * @param row_len The row length (in characters). * @return A new string with the wrapping applied. */ -inline std::string wrap_rows(const std::string &from, - unsigned row_len) +inline std::string wrap_rows(std::string_view from, unsigned row_len) { std::string to; + to.reserve(from.size()); - size_t character_idx = 0; + unsigned character_idx = 0; for (size_t i = 0; i < from.size(); i++) { if (!IS_UTF8_MULTB_INNER(from[i])) { // Wrap string after last inner byte of char @@ -550,6 +548,7 @@ template inline std::basic_string unescape_string(const std::basic_string &s) { std::basic_string res; + res.reserve(s.size()); for (size_t i = 0; i < s.length(); i++) { if (s[i] == '\\') { @@ -573,6 +572,7 @@ template std::basic_string unescape_enriched(const std::basic_string &s) { std::basic_string output; + output.reserve(s.size()); size_t i = 0; while (i < s.length()) { if (s[i] == '\x1b') { @@ -645,7 +645,7 @@ inline std::wstring unescape_translate(const std::wstring &s) { * @return true if to_check is not empty and all characters in to_check are * decimal digits, otherwise false */ -inline bool is_number(const std::string &to_check) +inline bool is_number(std::string_view to_check) { for (char i : to_check) if (!std::isdigit(i)) @@ -719,7 +719,7 @@ inline const std::string duration_to_string(int sec) * @return A std::string */ inline std::string str_join(const std::vector &list, - const std::string &delimiter) + std::string_view delimiter) { std::ostringstream oss; bool first = true; @@ -737,17 +737,17 @@ inline std::string str_join(const std::vector &list, */ inline std::string stringw_to_utf8(const irr::core::stringw &input) { - std::wstring str(input.c_str()); - return wide_to_utf8(str); + std::wstring_view sv(input.c_str(), input.size()); + return wide_to_utf8(sv); } /** * Create an irr::core:stringw from a UTF8 std::string. */ -inline irr::core::stringw utf8_to_stringw(const std::string &input) +inline irr::core::stringw utf8_to_stringw(std::string_view input) { std::wstring str = utf8_to_wide(input); - return irr::core::stringw(str.c_str()); + return irr::core::stringw(str.c_str(), str.size()); } /** @@ -756,7 +756,7 @@ inline irr::core::stringw utf8_to_stringw(const std::string &input) * 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::string sanitizeDirName(std::string_view str, std::string_view optional_prefix); /** * Prints a sanitized version of a string without control characters. @@ -764,12 +764,12 @@ std::string sanitizeDirName(const std::string &str, const std::string &optional_ * 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); +void safe_print_string(std::ostream &os, std::string_view str); /** * Parses a string of form `(1, 2, 3)` to a v3f * - * @param str String - * @return + * @param str string + * @return float vector */ -v3f str_to_v3f(const std::string &str); +v3f str_to_v3f(std::string_view str); diff --git a/util/buildbot/buildwin32.sh b/util/buildbot/buildwin32.sh index 1c5b901b5..3504505e5 100755 --- a/util/buildbot/buildwin32.sh +++ b/util/buildbot/buildwin32.sh @@ -13,42 +13,36 @@ libdir=$builddir/libs source $topdir/common.sh -# Test which win32 compiler is present -command -v i686-w64-mingw32-gcc >/dev/null && - compiler=i686-w64-mingw32-gcc -command -v i686-w64-mingw32-gcc-posix >/dev/null && - compiler=i686-w64-mingw32-gcc-posix +compiler=i686-w64-mingw32-clang -if [ -z "$compiler" ]; then - echo "Unable to determine which MinGW compiler to use" +if ! command -v "$compiler" >/dev/null; then + echo "Unable to find $compiler" exit 1 fi -toolchain_file=$topdir/toolchain_${compiler/-gcc/}.cmake +toolchain_file=$topdir/toolchain_${compiler%-*}.cmake echo "Using $toolchain_file" -find_runtime_dlls i686-w64-mingw32 +find_runtime_dlls ${compiler%-*} # Get stuff irrlicht_version=$(cat $topdir/../../misc/irrlichtmt_tag.txt) mkdir -p $libdir -# 'ucrt' just points to rebuilt versions after a toolchain change - cd $libdir libhost="http://minetest.kitsunemimi.pw" -download "https://github.com/minetest/irrlicht/releases/download/$irrlicht_version/win32.zip" irrlicht-$irrlicht_version-win32.zip -download "$libhost/zlib-$zlib_version-win32.zip" -download "$libhost/ucrt/zstd-$zstd_version-win32.zip" -download "$libhost/ucrt/libogg-$ogg_version-win32.zip" -download "$libhost/ucrt/libvorbis-$vorbis_version-win32.zip" -download "$libhost/curl-$curl_version-win32.zip" +download "https://github.com/minetest/irrlicht/releases/download/$irrlicht_version/win32-llvm.zip" irrlicht-$irrlicht_version-win32.zip +download "$libhost/llvm/zlib-$zlib_version-win32.zip" +download "$libhost/llvm/zstd-$zstd_version-win32.zip" +download "$libhost/llvm/libogg-$ogg_version-win32.zip" +download "$libhost/llvm/libvorbis-$vorbis_version-win32.zip" +download "$libhost/llvm/curl-$curl_version-win32.zip" download "$libhost/ucrt/gettext-$gettext_version-win32.zip" -download "$libhost/freetype-$freetype_version-win32.zip" -download "$libhost/sqlite3-$sqlite3_version-win32.zip" -download "$libhost/luajit-$luajit_version-win32.zip" -download "$libhost/ucrt/libleveldb-$leveldb_version-win32.zip" leveldb-$leveldb_version-win32.zip -download "$libhost/openal-soft-$openal_version-win32.zip" +download "$libhost/llvm/freetype-$freetype_version-win32.zip" +download "$libhost/llvm/sqlite3-$sqlite3_version-win32.zip" +download "$libhost/llvm/luajit-$luajit_version-win32.zip" +download "$libhost/llvm/libleveldb-$leveldb_version-win32.zip" +download "$libhost/llvm/openal-soft-$openal_version-win32.zip" # Set source dir, downloading Minetest as needed get_sources diff --git a/util/buildbot/buildwin64.sh b/util/buildbot/buildwin64.sh index c7ae7dcc6..774a1dfb8 100755 --- a/util/buildbot/buildwin64.sh +++ b/util/buildbot/buildwin64.sh @@ -13,42 +13,36 @@ libdir=$builddir/libs source $topdir/common.sh -# Test which win64 compiler is present -command -v x86_64-w64-mingw32-gcc >/dev/null && - compiler=x86_64-w64-mingw32-gcc -command -v x86_64-w64-mingw32-gcc-posix >/dev/null && - compiler=x86_64-w64-mingw32-gcc-posix +compiler=x86_64-w64-mingw32-clang -if [ -z "$compiler" ]; then - echo "Unable to determine which MinGW compiler to use" +if ! command -v "$compiler" >/dev/null; then + echo "Unable to find $compiler" exit 1 fi -toolchain_file=$topdir/toolchain_${compiler/-gcc/}.cmake +toolchain_file=$topdir/toolchain_${compiler%-*}.cmake echo "Using $toolchain_file" -find_runtime_dlls x86_64-w64-mingw32 +find_runtime_dlls ${compiler%-*} # Get stuff irrlicht_version=$(cat $topdir/../../misc/irrlichtmt_tag.txt) mkdir -p $libdir -# 'ucrt' just points to rebuilt versions after a toolchain change - cd $libdir libhost="http://minetest.kitsunemimi.pw" -download "https://github.com/minetest/irrlicht/releases/download/$irrlicht_version/win64.zip" irrlicht-$irrlicht_version-win64.zip -download "$libhost/zlib-$zlib_version-win64.zip" -download "$libhost/ucrt/zstd-$zstd_version-win64.zip" -download "$libhost/ucrt/libogg-$ogg_version-win64.zip" -download "$libhost/ucrt/libvorbis-$vorbis_version-win64.zip" -download "$libhost/curl-$curl_version-win64.zip" +download "https://github.com/minetest/irrlicht/releases/download/$irrlicht_version/win64-llvm.zip" irrlicht-$irrlicht_version-win64.zip +download "$libhost/llvm/zlib-$zlib_version-win64.zip" +download "$libhost/llvm/zstd-$zstd_version-win64.zip" +download "$libhost/llvm/libogg-$ogg_version-win64.zip" +download "$libhost/llvm/libvorbis-$vorbis_version-win64.zip" +download "$libhost/llvm/curl-$curl_version-win64.zip" download "$libhost/ucrt/gettext-$gettext_version-win64.zip" -download "$libhost/freetype-$freetype_version-win64.zip" -download "$libhost/sqlite3-$sqlite3_version-win64.zip" -download "$libhost/luajit-$luajit_version-win64.zip" -download "$libhost/ucrt/libleveldb-$leveldb_version-win64.zip" leveldb-$leveldb_version-win64.zip -download "$libhost/openal-soft-$openal_version-win64.zip" +download "$libhost/llvm/freetype-$freetype_version-win64.zip" +download "$libhost/llvm/sqlite3-$sqlite3_version-win64.zip" +download "$libhost/llvm/luajit-$luajit_version-win64.zip" +download "$libhost/llvm/libleveldb-$leveldb_version-win64.zip" +download "$libhost/llvm/openal-soft-$openal_version-win64.zip" # Set source dir, downloading Minetest as needed get_sources diff --git a/util/buildbot/common.sh b/util/buildbot/common.sh index 9fe04bf87..5db13820e 100644 --- a/util/buildbot/common.sh +++ b/util/buildbot/common.sh @@ -9,9 +9,9 @@ curl_version=8.5.0 gettext_version=0.20.2 freetype_version=2.13.2 sqlite3_version=3.44.2 -luajit_version=20231211 +luajit_version=20240125 leveldb_version=1.23 -zlib_version=1.3 +zlib_version=1.3.1 zstd_version=1.5.5 download () { @@ -48,11 +48,11 @@ get_sources () { # sets $runtime_dlls find_runtime_dlls () { local triple=$1 - # Try to find runtime DLLs in various paths (varies by distribution, sigh) + # Try to find runtime DLLs in various paths local tmp=$(dirname "$(command -v $compiler)")/.. runtime_dlls= - for name in lib{gcc_,stdc++-,winpthread-}'*'.dll; do - for dir in $tmp/$triple/{bin,lib} $tmp/lib/gcc/$triple/*; do + for name in lib{clang_rt,c++,unwind,winpthread-}'*'.dll; do + for dir in $tmp/$triple/{bin,lib}; do [ -d "$dir" ] || continue local file=$(echo $dir/$name) [ -f "$file" ] && { runtime_dlls+="$file;"; break; } @@ -65,14 +65,20 @@ find_runtime_dlls () { fi } -add_cmake_libs () { - local irr_dlls=$(echo $libdir/irrlicht/lib/*.dll | tr ' ' ';') - local vorbis_dlls=$(echo $libdir/libvorbis/bin/libvorbis{,file}-*.dll | tr ' ' ';') - local gettext_dlls=$(echo $libdir/gettext/bin/lib{intl,iconv}-*.dll | tr ' ' ';') +_dlls () { + for f in "$@"; do + if [ ! -e "$f" ]; then + echo "Could not find $f" >&2 + elif [[ -f "$f" && "$f" == *.dll ]]; then + printf '%s;' "$f" + fi + done +} +add_cmake_libs () { cmake_args+=( -DCMAKE_PREFIX_PATH=$libdir/irrlicht - -DIRRLICHT_DLL="$irr_dlls" + -DIRRLICHT_DLL="$(_dlls $libdir/irrlicht/lib/*)" -DZLIB_INCLUDE_DIR=$libdir/zlib/include -DZLIB_LIBRARY=$libdir/zlib/lib/libz.dll.a @@ -87,37 +93,37 @@ add_cmake_libs () { -DOGG_INCLUDE_DIR=$libdir/libogg/include -DOGG_LIBRARY=$libdir/libogg/lib/libogg.dll.a - -DOGG_DLL=$libdir/libogg/bin/libogg-0.dll + -DOGG_DLL="$(_dlls $libdir/libogg/bin/*)" -DVORBIS_INCLUDE_DIR=$libdir/libvorbis/include -DVORBIS_LIBRARY=$libdir/libvorbis/lib/libvorbis.dll.a - -DVORBIS_DLL="$vorbis_dlls" + -DVORBIS_DLL="$(_dlls $libdir/libvorbis/bin/libvorbis{,file}[-.]*)" -DVORBISFILE_LIBRARY=$libdir/libvorbis/lib/libvorbisfile.dll.a -DOPENAL_INCLUDE_DIR=$libdir/openal/include/AL -DOPENAL_LIBRARY=$libdir/openal/lib/libOpenAL32.dll.a -DOPENAL_DLL=$libdir/openal/bin/OpenAL32.dll - -DCURL_DLL=$libdir/curl/bin/libcurl-4.dll + -DCURL_DLL="$(_dlls $libdir/curl/bin/libcurl*)" -DCURL_INCLUDE_DIR=$libdir/curl/include -DCURL_LIBRARY=$libdir/curl/lib/libcurl.dll.a -DGETTEXT_MSGFMT=`command -v msgfmt` - -DGETTEXT_DLL="$gettext_dlls" + -DGETTEXT_DLL="$(_dlls $libdir/gettext/bin/lib{intl,iconv}*)" -DGETTEXT_INCLUDE_DIR=$libdir/gettext/include -DGETTEXT_LIBRARY=$libdir/gettext/lib/libintl.dll.a -DFREETYPE_INCLUDE_DIR_freetype2=$libdir/freetype/include/freetype2 -DFREETYPE_INCLUDE_DIR_ft2build=$libdir/freetype/include/freetype2 -DFREETYPE_LIBRARY=$libdir/freetype/lib/libfreetype.dll.a - -DFREETYPE_DLL=$libdir/freetype/bin/libfreetype-6.dll + -DFREETYPE_DLL="$(_dlls $libdir/freetype/bin/libfreetype*)" -DSQLITE3_INCLUDE_DIR=$libdir/sqlite3/include -DSQLITE3_LIBRARY=$libdir/sqlite3/lib/libsqlite3.dll.a - -DSQLITE3_DLL=$libdir/sqlite3/bin/libsqlite3-0.dll + -DSQLITE3_DLL="$(_dlls $libdir/sqlite3/bin/libsqlite*)" - -DLEVELDB_INCLUDE_DIR=$libdir/leveldb/include - -DLEVELDB_LIBRARY=$libdir/leveldb/lib/libleveldb.dll.a - -DLEVELDB_DLL=$libdir/leveldb/bin/libleveldb.dll + -DLEVELDB_INCLUDE_DIR=$libdir/libleveldb/include + -DLEVELDB_LIBRARY=$libdir/libleveldb/lib/libleveldb.dll.a + -DLEVELDB_DLL=$libdir/libleveldb/bin/libleveldb.dll ) } diff --git a/util/buildbot/download_toolchain.sh b/util/buildbot/download_toolchain.sh index e28f8dc27..3097f803b 100755 --- a/util/buildbot/download_toolchain.sh +++ b/util/buildbot/download_toolchain.sh @@ -1,18 +1,17 @@ #!/bin/bash set -e topdir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -if [[ -z "$1" || -z "$2" ]]; then - echo "Usage: $0 " +if [ -z "$1" ]; then + echo "Usage: $0 " exit 1 fi -# our current toolchain: -# binutils 2.41 + GCC 13.2.0 + Mingw-w64 11.0.1 with UCRT enabled and winpthreads support -# built from source on Ubuntu 22.04, so should work on any similarily up-to-date distro -ver=13.2.0 -os=ubuntu22.04 -name="mingw-w64-${1}_${ver}_${os}.tar.xz" -wget "http://minetest.kitsunemimi.pw/$name" -O "$name" +# key points: +# * Clang + LLD + libc++ instead of GCC + binutils + stdc++ +# * Mingw-w64 with UCRT enabled and winpthreads support +# why are we avoiding GCC? -> Thread Local Storage (TLS) is totally broken +name=llvm-mingw-20231128-ucrt-ubuntu-20.04-x86_64.tar.xz +wget "https://github.com/mstorsjo/llvm-mingw/releases/download/20231128/$name" -O "$name" sha256sum -w -c <(grep -F "$name" "$topdir/sha256sums.txt") -tar -xaf "$name" -C "$2" +tar -xaf "$name" -C "$1" --strip-components=1 rm -f "$name" diff --git a/util/buildbot/sha256sums.txt b/util/buildbot/sha256sums.txt index 9ca9a7580..42fddbcb8 100644 --- a/util/buildbot/sha256sums.txt +++ b/util/buildbot/sha256sums.txt @@ -1,26 +1,25 @@ -c6759580175dee6c3673bb0544f0aca855f76b415b441db2b949fe9e2af4e6ee curl-8.5.0-win32.zip -a99ebdccad524f3738fa3a6a9d1dcabc39cb668f97790638d77b4bb96ea3edca curl-8.5.0-win64.zip -d70c9886526513a2c8a7962815fb425f296ab934239470a03ea350944169a7ac freetype-2.13.2-win32.zip -06aa20c71724e832874baa296d047aa866db2c336e26aa49e4faa72e559414a6 freetype-2.13.2-win64.zip +753dc38c591e078eae6a0a6b25f69826211256f444f3691a170670d8a12988f9 curl-8.5.0-win32.zip +aa86abc3eb054d74d5fe15996f281cf84230a61b4ab7b3a702ab7dbb71e1203f curl-8.5.0-win64.zip +3e9d7bbca953b96dfd65acc28baaa87e8881aab29809ba03b9c9aefe3d071189 freetype-2.13.2-win32.zip +acf901e93aedbcfa92eb3aab1def252676af845b1747ca5c3e7c5866576168cc freetype-2.13.2-win64.zip 41b10766de2773f0f0851fde16b363024685e0397f4bb2e5cd2a7be196960a01 gettext-0.20.2-win32.zip 1ceed167ff16fea944f76ab6ea2969160c71a67419259b17c9c523e7a01eb883 gettext-0.20.2-win64.zip -faa09cd5c3790fdad3fcb43ba1d5c5102492a7b88b9301de59cecb92af37c162 irrlicht-1.9.0mt14-win32.zip -a5724e994f417b04e43bc9dd54adf6f32050e39b2cc1a79acdf361a9d3972ece irrlicht-1.9.0mt14-win64.zip -6d49348215916ff355187fec808d0847450f70e45fe2719f45af9eb61c047358 leveldb-1.23-win32.zip -30c680277320bdda130b238d0adc30c3c59e7522dc008d677893ebfaea22f28b leveldb-1.23-win64.zip -d58b67954f3f552fba5e315ed476c38b230d0cf53445fe07dc733e72f8ba7dc2 libogg-1.3.5-win32.zip -2083cceb79b648cd500afe8b71c56170481f309cb6abd950195cdd13570e03dd libogg-1.3.5-win64.zip -1ce1c71e1dfdd99f47c93614a521ec0797d8fb55fb3fc07b67937ea7c6f76cca libvorbis-1.3.7-win32.zip -1c6fe4aa1c38079f2917e17e6b5acd7505331236c426e3b86054efccec6cee1c libvorbis-1.3.7-win64.zip -1c9b9580d869ee57b8c30a083d0e9a737310c1bb5e376b05fba483bac99eb2e1 luajit-20231211-win32.zip -3b42a31887ad7901f83a9f5b5faa4745ce95c7e95a7d8fd569d603fc95573ea5 luajit-20231211-win64.zip -9f0cfab8ca089d48be7a59f85d5fd5648f18f54c91d7ac6c31b281ba5e90852a mingw-w64-i686_13.2.0_ubuntu22.04.tar.xz -93bc9f04d43a023358d1ae2b76dec42d3d79baecd452402ee9fb3ee21945fdfe mingw-w64-x86_64_13.2.0_ubuntu22.04.tar.xz -34c4e6826a8e0dc4f7a49f7e4e4d54676f89a20fe781bad876795b857f7c5395 openal-soft-1.23.1-win32.zip -4b9c9a7f42aa8f7e6d26347ad61c55a32a2b11e4f02b8562542bcd132b0c7115 openal-soft-1.23.1-win64.zip -082dfee313c7e29e48ff798503acb286a4542c315618d5d3b33fc2bbed4170a5 sqlite3-3.44.2-win32.zip -e8fda50178f1371c52f85ac19a0998d797ad6b2439f1da87c49a1f44ba33649c sqlite3-3.44.2-win64.zip -3c5abd40e9492c834651d995db6bbf0f57a7579d091d2d03110293b95e9b039a zlib-1.3-win32.zip -f63d9a38c2ee56fa1e95a486224c274412cb5b3275734c1da53b0a68a7e8c654 zlib-1.3-win64.zip -7508d714dbed4e1b1340cfb13ea77ef631746dad99ac97434171f2f4dd64d94b zstd-1.5.5-win32.zip -30353afddb459974c4e90c4eb3fbf975951247cf310fa5f40208806e275776fa zstd-1.5.5-win64.zip +14bb60cbf9dd93e906d9c9117a99d19a500cda6bcfd6fc125e04f3c75778acc2 irrlicht-1.9.0mt15-win32.zip +31edd6a0af033b9ab455c5e019748cfa7e0cf167c9cbc5957227e72e971c2565 irrlicht-1.9.0mt15-win64.zip +f54e9a577e2db47ed28f4a01e74181d2c607627c551d30f48263e01b59e84f67 libleveldb-1.23-win32.zip +2f039848a4e6c05a2347fe5a7fa63c430dd08d1bc88235645a863c859e14f5f8 libleveldb-1.23-win64.zip +0df94afb8efa361cceb132ecf9491720afbc45ba844a7b1c94607295829b53ca libogg-1.3.5-win32.zip +5c4acb4c99429a04b5e69650719b2eb17616bf52837d2372a0f859952eebce48 libogg-1.3.5-win64.zip +456ece10a2be4247b27fbe88f88ddd54aae604736a6b76ba9a922b602fe40f40 libvorbis-1.3.7-win32.zip +57f4db02da00556895bb63fafa6e46b5f7dac87c25fde27af4315f56a1aa7a86 libvorbis-1.3.7-win64.zip +0f21ff3be90311092fe32e0e30878ef3ae9d9437b8d9ac25ef279e0d84e9bb8e llvm-mingw-20231128-ucrt-ubuntu-20.04-x86_64.tar.xz +da6ad10632cf172992158e9ea0977a87914b5d5de93a972c3430b6a412237556 luajit-20240125-win32.zip +2b1dabe83d478b398cf9226d96de7fa62c973365c4aea70d27ba5782fb49d2d0 luajit-20240125-win64.zip +e2443451fe5c2066eb564c64b8a1762738a88b7fd749c8b5907fed45c785497b openal-soft-1.23.1-win32.zip +cb041445a118469caefbad2647470cb8571c8337bce2adc07634011ab5625417 openal-soft-1.23.1-win64.zip +326701086a0ed66e09a9f3ec4d971654c13b6bd79cfdd079c947ecdcd6409525 sqlite3-3.44.2-win32.zip +b2d474e3625f8f426b6cc5c0ecac831a1de46f7d1027bf4a9f6267b0b0411d42 sqlite3-3.44.2-win64.zip +8af10515d57dbfee5d2106cd66cafa2adeb4270d4c6047ccbf7e8b5d2d50681c zlib-1.3.1-win32.zip +ad43f5d23052590c65633530743e5d622cc76b33c109072e6fd7b487aff56bca zlib-1.3.1-win64.zip +3564dabbe17ec4ecae1fb9a78fe48d9f7c71e2b1166456f6ee27e52fd9c84357 zstd-1.5.5-win32.zip +e61b1f327ce2d836d1f8ca00c40ac77d3ab5309135851c98229bbdf82b060ae5 zstd-1.5.5-win64.zip diff --git a/util/buildbot/toolchain_i686-w64-mingw32-posix.cmake b/util/buildbot/toolchain_i686-w64-mingw32-posix.cmake deleted file mode 100644 index 5d4d310f8..000000000 --- a/util/buildbot/toolchain_i686-w64-mingw32-posix.cmake +++ /dev/null @@ -1,19 +0,0 @@ -# name of the target operating system -SET(CMAKE_SYSTEM_NAME Windows) - -# which compilers to use for C and C++ -# *-posix is Ubuntu's naming for the MinGW variant that comes with support -# for pthreads / std::thread (required by MT) -SET(CMAKE_C_COMPILER i686-w64-mingw32-gcc-posix) -SET(CMAKE_CXX_COMPILER i686-w64-mingw32-g++-posix) -SET(CMAKE_RC_COMPILER i686-w64-mingw32-windres) - -# here is the target environment located -SET(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32) - -# adjust the default behavior of the FIND_XXX() commands: -# search headers and libraries in the target environment, search -# programs in the host environment -set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) -set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) -set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) diff --git a/util/buildbot/toolchain_i686-w64-mingw32.cmake b/util/buildbot/toolchain_i686-w64-mingw32.cmake index b0bc2b6b0..015682394 100644 --- a/util/buildbot/toolchain_i686-w64-mingw32.cmake +++ b/util/buildbot/toolchain_i686-w64-mingw32.cmake @@ -2,8 +2,8 @@ SET(CMAKE_SYSTEM_NAME Windows) # which compilers to use for C and C++ -SET(CMAKE_C_COMPILER i686-w64-mingw32-gcc) -SET(CMAKE_CXX_COMPILER i686-w64-mingw32-g++) +SET(CMAKE_C_COMPILER i686-w64-mingw32-clang) +SET(CMAKE_CXX_COMPILER i686-w64-mingw32-clang++) SET(CMAKE_RC_COMPILER i686-w64-mingw32-windres) # here is the target environment located diff --git a/util/buildbot/toolchain_x86_64-w64-mingw32-posix.cmake b/util/buildbot/toolchain_x86_64-w64-mingw32-posix.cmake deleted file mode 100644 index fa3b2f4d3..000000000 --- a/util/buildbot/toolchain_x86_64-w64-mingw32-posix.cmake +++ /dev/null @@ -1,19 +0,0 @@ -# name of the target operating system -SET(CMAKE_SYSTEM_NAME Windows) - -# which compilers to use for C and C++ -# *-posix is Ubuntu's naming for the MinGW variant that comes with support -# for pthreads / std::thread (required by MT) -SET(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc-posix) -SET(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++-posix) -SET(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres) - -# here is the target environment located -SET(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32) - -# adjust the default behavior of the FIND_XXX() commands: -# search headers and libraries in the target environment, search -# programs in the host environment -set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) -set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) -set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) diff --git a/util/buildbot/toolchain_x86_64-w64-mingw32.cmake b/util/buildbot/toolchain_x86_64-w64-mingw32.cmake index 1f769ed85..113cc0e78 100644 --- a/util/buildbot/toolchain_x86_64-w64-mingw32.cmake +++ b/util/buildbot/toolchain_x86_64-w64-mingw32.cmake @@ -2,8 +2,8 @@ SET(CMAKE_SYSTEM_NAME Windows) # which compilers to use for C and C++ -SET(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc) -SET(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++) +SET(CMAKE_C_COMPILER x86_64-w64-mingw32-clang) +SET(CMAKE_CXX_COMPILER x86_64-w64-mingw32-clang++) SET(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres) # here is the target environment located diff --git a/util/ci/build.sh b/util/ci/build.sh index 88349b852..944cc2124 100755 --- a/util/ci/build.sh +++ b/util/ci/build.sh @@ -1,7 +1,8 @@ -#! /bin/bash -e +#!/bin/bash -e cmake -B build \ -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE:-Debug} \ + -DENABLE_LTO=FALSE \ -DRUN_IN_PLACE=TRUE \ -DENABLE_GETTEXT=${CMAKE_ENABLE_GETTEXT:-TRUE} \ -DBUILD_SERVER=${CMAKE_BUILD_SERVER:-TRUE} \ diff --git a/util/mod_translation_updater.py b/util/mod_translation_updater.py index b2feaaf15..c941331a8 100755 --- a/util/mod_translation_updater.py +++ b/util/mod_translation_updater.py @@ -166,16 +166,9 @@ def get_modname(folder): if match: return match.group(1) except FileNotFoundError: - if not os.path.isfile(os.path.join(folder, "modpack.txt")): - folder_name = os.path.basename(folder) - # Special case when run in Minetest's builtin directory - if folder_name == "builtin": - return "__builtin" - else: - return folder_name - else: - return None - return None + folder_name = os.path.basename(folder) + # Special case when run in Minetest's builtin directory + return "__builtin" if folder_name == "builtin" else folder_name # If there are already .tr files in /locale, returns a list of their names def get_existing_tr_files(folder): @@ -432,6 +425,7 @@ def generate_template(folder, mod_name): sources = sorted(list(sources), key=str.lower) newSources = [] for i in sources: + i = "/".join(os.path.split(i)).lstrip("/") newSources.append(f"{symbol_source_prefix} {i} {symbol_source_suffix}") dOut[d] = newSources @@ -463,27 +457,32 @@ def update_tr_file(dNew, mod_name, tr_file): # Updates translation files for the mod in the given folder def update_mod(folder): - modname = get_modname(folder) - if modname is not None: - print(f"Updating translations for {modname}") - data = generate_template(folder, modname) - if data == None: - print(f"No translatable strings found in {modname}") - else: - for tr_file in get_existing_tr_files(folder): - update_tr_file(data, modname, os.path.join(folder, "locale/", tr_file)) - else: - print(f"Unable to determine the mod name in folder {folder}. Missing 'name' field in mod.conf.", file=_stderr) + if not os.path.exists(os.path.join(folder, "init.lua")): + print(f"Mod folder {folder} is missing init.lua, aborting.") exit(1) + assert not is_modpack(folder) + modname = get_modname(folder) + print(f"Updating translations for {modname}") + data = generate_template(folder, modname) + if data == None: + print(f"No translatable strings found in {modname}") + else: + for tr_file in get_existing_tr_files(folder): + update_tr_file(data, modname, os.path.join(folder, "locale/", tr_file)) -# Determines if the folder being pointed to is a mod or a mod pack +def is_modpack(folder): + return os.path.exists(os.path.join(folder, "modpack.txt")) or os.path.exists(os.path.join(folder, "modpack.conf")) + +def is_game(folder): + return os.path.exists(os.path.join(folder, "game.conf")) and os.path.exists(os.path.join(folder, "mods")) + +# Determines if the folder being pointed to is a game, mod or a mod pack # and then runs update_mod accordingly def update_folder(folder): - is_modpack = os.path.exists(os.path.join(folder, "modpack.txt")) or os.path.exists(os.path.join(folder, "modpack.conf")) - if is_modpack: - subfolders = [f.path for f in os.scandir(folder) if f.is_dir() and not f.name.startswith('.')] - for subfolder in subfolders: - update_mod(subfolder) + if is_game(folder): + run_all_subfolders(os.path.join(folder, "mods")) + elif is_modpack(folder): + run_all_subfolders(folder) else: update_mod(folder) print("Done.") diff --git a/util/wireshark/minetest.lua b/util/wireshark/minetest.lua index 93e4f0387..837ea2963 100644 --- a/util/wireshark/minetest.lua +++ b/util/wireshark/minetest.lua @@ -1280,13 +1280,14 @@ do function p_minetest.dissector(buffer, pinfo, tree) - -- Add Minetest tree item and verify the ID + -- Defer if payload doesn't have Minetest's magic number + if buffer(0,4):uint() ~= minetest_id then + return false + end + + -- Add Minetest tree item local t = tree:add(p_minetest, buffer(0,8)) t:add(f_id, buffer(0,4)) - if buffer(0,4):uint() ~= minetest_id then - t:add_expert_info(PI_UNDECODED, PI_WARN, "Invalid ID, this is not a Minetest packet") - return - end -- ID is valid, so replace packet's shown protocol pinfo.cols.protocol = "Minetest" @@ -1339,12 +1340,11 @@ do end pinfo.cols.info:append(" (" .. reliability_info .. ")") + return true end - -- FIXME Is there a way to let the dissector table check if the first payload bytes are 0x4f457403? - DissectorTable.get("udp.port"):add(30000, p_minetest) - DissectorTable.get("udp.port"):add(30001, p_minetest) + p_minetest:register_heuristic("udp", p_minetest.dissector) end