diff --git a/.clang-format b/.clang-format index 0db8ab167..63f12b6c4 100644 --- a/.clang-format +++ b/.clang-format @@ -1,6 +1,7 @@ BasedOnStyle: LLVM -IndentWidth: 8 +IndentWidth: 4 UseTab: Always +TabWidth: 4 BreakBeforeBraces: Custom Standard: Cpp11 BraceWrapping: @@ -16,7 +17,7 @@ BraceWrapping: FixNamespaceComments: false AllowShortIfStatementsOnASingleLine: false IndentCaseLabels: false -AccessModifierOffset: -8 +AccessModifierOffset: -4 ColumnLimit: 90 AllowShortFunctionsOnASingleLine: InlineOnly SortIncludes: false @@ -26,7 +27,7 @@ IncludeCategories: - Regex: '^<.*' Priority: 1 AlignAfterOpenBracket: DontAlign -ContinuationIndentWidth: 16 -ConstructorInitializerIndentWidth: 16 +ContinuationIndentWidth: 8 +ConstructorInitializerIndentWidth: 8 BreakConstructorInitializers: AfterColon AlwaysBreakTemplateDeclarations: Yes diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 660b5c8df..20411a332 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -21,24 +21,22 @@ on: jobs: build: - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 - with: - java-version: 1.8 + - uses: actions/checkout@v3 - name: Install deps - run: sudo apt-get update; sudo apt-get install -y --no-install-recommends gettext + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends gettext openjdk-11-jdk-headless - name: Build with Gradle run: cd android; ./gradlew assemblerelease - name: Save armeabi artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 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@v2 + uses: actions/upload-artifact@v3 with: name: Minetest-arm64-v8a.apk path: android/app/build/outputs/apk/release/app-arm64-v8a-release-unsigned.apk diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 417b4f650..2cc83923b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,53 +30,53 @@ on: - '.dockerignore' jobs: - # This is our minor gcc compiler - gcc_6: + # Older gcc version (should be close to our minimum supported version) + gcc_5: runs-on: ubuntu-18.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install deps run: | source ./util/ci/common.sh - install_linux_deps g++-6 + install_linux_deps g++-5 - name: Build run: | ./util/ci/build.sh env: - CC: gcc-6 - CXX: g++-6 + CC: gcc-5 + CXX: g++-5 - name: Test run: | ./bin/minetest --run-unittests - # This is the current gcc compiler (available in bionic) - gcc_8: - runs-on: ubuntu-18.04 + # Current gcc version + gcc_10: + runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install deps run: | source ./util/ci/common.sh - install_linux_deps g++-8 + install_linux_deps g++-10 - name: Build run: | ./util/ci/build.sh env: - CC: gcc-8 - CXX: g++-8 + CC: gcc-10 + CXX: g++-10 - name: Test run: | ./bin/minetest --run-unittests - # This is our minor clang compiler + # Older clang version (should be close to our minimum supported version) clang_3_9: runs-on: ubuntu-18.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install deps run: | source ./util/ci/common.sh @@ -93,26 +93,26 @@ jobs: run: | ./bin/minetest --run-unittests - - name: Integration test + - name: Integration test + devtest run: | ./util/test_multiplayer.sh - # This is the current clang version - clang_9: - runs-on: ubuntu-18.04 + # Current clang version + clang_10: + runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install deps run: | source ./util/ci/common.sh - install_linux_deps clang-9 valgrind libluajit-5.1-dev + install_linux_deps clang-10 valgrind libluajit-5.1-dev - name: Build run: | ./util/ci/build.sh env: - CC: clang-9 - CXX: clang++-9 + CC: clang-10 + CXX: clang++-10 CMAKE_FLAGS: "-DREQUIRE_LUAJIT=1" - name: Test @@ -126,9 +126,9 @@ jobs: # Build with prometheus-cpp (server-only) clang_9_prometheus: name: "clang_9 (PROMETHEUS=1)" - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install deps run: | source ./util/ci/common.sh @@ -150,34 +150,11 @@ jobs: run: | ./bin/minetestserver --run-unittests - # Build without freetype (client-only) - clang_9_no_freetype: - name: "clang_9 (FREETYPE=0)" - runs-on: ubuntu-18.04 - steps: - - uses: actions/checkout@v2 - - name: Install deps - run: | - source ./util/ci/common.sh - install_linux_deps clang-9 - - - name: Build - run: | - ./util/ci/build.sh - env: - CC: clang-9 - CXX: clang++-9 - CMAKE_FLAGS: "-DENABLE_FREETYPE=0 -DBUILD_SERVER=0" - - - name: Test - run: | - ./bin/minetest --run-unittests - docker: name: "Docker image" - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Build docker image run: | docker build . -t minetest:latest @@ -185,13 +162,13 @@ jobs: win32: name: "MinGW cross-compiler (32-bit)" - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install compiler run: | - sudo apt-get update -q && sudo apt-get install gettext -qyy - wget http://minetest.kitsunemimi.pw/mingw-w64-i686_9.2.0_ubuntu18.04.tar.xz -O mingw.tar.xz + sudo apt-get update && sudo apt-get install -y gettext + wget http://minetest.kitsunemimi.pw/mingw-w64-i686_11.2.0_ubuntu20.04.tar.xz -O mingw.tar.xz sudo tar -xaf mingw.tar.xz -C /usr - name: Build @@ -203,13 +180,13 @@ jobs: win64: name: "MinGW cross-compiler (64-bit)" - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install compiler run: | - sudo apt-get update -q && sudo apt-get install gettext -qyy - wget http://minetest.kitsunemimi.pw/mingw-w64-x86_64_9.2.0_ubuntu18.04.tar.xz -O mingw.tar.xz + sudo apt-get update && sudo apt-get install -y gettext + wget http://minetest.kitsunemimi.pw/mingw-w64-x86_64_11.2.0_ubuntu20.04.tar.xz -O mingw.tar.xz sudo tar -xaf mingw.tar.xz -C /usr - name: Build @@ -222,13 +199,10 @@ jobs: msvc: name: VS 2019 ${{ matrix.config.arch }}-${{ matrix.type }} runs-on: windows-2019 - #### Disabled due to Irrlicht switch - if: false - #### Disabled due to Irrlicht switch env: - VCPKG_VERSION: 0bf3923f9fab4001c00f0f429682a0853b5749e0 -# 2020.11 - vcpkg_packages: irrlicht zlib zstd curl[winssl] openal-soft libvorbis libogg sqlite3 freetype luajit + VCPKG_VERSION: 5cf60186a241e84e8232641ee973395d4fde90e1 + # 2022.02 + vcpkg_packages: zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp opengl-registry strategy: fail-fast: false matrix: @@ -249,11 +223,17 @@ jobs: # Enable it, when working on the installer. steps: - - name: Checkout - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + + - name: Checkout IrrlichtMt + uses: actions/checkout@v3 + with: + repository: minetest/irrlicht + path: lib/irrlichtmt/ + ref: "1.9.0mt5" - name: Restore from cache and run vcpkg - uses: lukka/run-vcpkg@v5 + uses: lukka/run-vcpkg@v7 with: vcpkgArguments: ${{env.vcpkg_packages}} vcpkgDirectory: '${{ github.workspace }}\vcpkg' @@ -261,7 +241,7 @@ jobs: vcpkgGitCommitId: ${{ env.VCPKG_VERSION }} vcpkgTriplet: ${{ matrix.config.vcpkg_triplet }} - - name: CMake + - name: Minetest CMake run: | cmake ${{matrix.config.generator}} ` -DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}\vcpkg\scripts\buildsystems\vcpkg.cmake" ` @@ -269,7 +249,7 @@ jobs: -DENABLE_POSTGRESQL=OFF ` -DRUN_IN_PLACE=${{ contains(matrix.type, 'portable') }} . - - name: Build + - name: Build Minetest run: cmake --build . --config Release - name: CPack @@ -288,7 +268,7 @@ jobs: - name: Package Clean run: rm -r $env:GITHUB_WORKSPACE\Package\_CPack_Packages - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v3 with: name: msvc-${{ matrix.config.arch }}-${{ matrix.type }} path: .\Package\ diff --git a/.github/workflows/cpp_lint.yml b/.github/workflows/cpp_lint.yml index 2bd884c7a..581ee06d6 100644 --- a/.github/workflows/cpp_lint.yml +++ b/.github/workflows/cpp_lint.yml @@ -26,12 +26,13 @@ on: jobs: # clang_format: -# runs-on: ubuntu-18.04 +# runs-on: ubuntu-20.04 # steps: -# - uses: actions/checkout@v2 +# - uses: actions/checkout@v3 # - name: Install clang-format # run: | -# sudo apt-get install clang-format-9 -qyy +# sudo apt-get update +# sudo apt-get install -y clang-format-9 # # - name: Run clang-format # run: | @@ -41,14 +42,13 @@ jobs: # CLANG_FORMAT: clang-format-9 clang_tidy: - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install deps run: | - sudo apt-get install clang-tidy-9 -qyy source ./util/ci/common.sh - install_linux_deps + install_linux_deps clang-tidy-9 - name: Run clang-tidy run: | diff --git a/.github/workflows/lua.yml b/.github/workflows/lua.yml new file mode 100644 index 000000000..3af4a6ee7 --- /dev/null +++ b/.github/workflows/lua.yml @@ -0,0 +1,61 @@ +name: lua_lint + +# Lint on lua changes on builtin or if workflow changed +on: + push: + paths: + - 'builtin/**.lua' + - 'games/devtest/**.lua' + - '.github/workflows/**.yml' + pull_request: + paths: + - 'builtin/**.lua' + - 'games/devtest/**.lua' + - '.github/workflows/**.yml' + +jobs: + # Note that the integration tests are also run build.yml, but only when C++ code is changed. + integration_tests: + name: "Compile and run multiplayer tests" + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - name: Install deps + run: | + source ./util/ci/common.sh + install_linux_deps clang-10 gdb libluajit-5.1-dev + + - name: Build + run: | + ./util/ci/build.sh + env: + CC: clang-10 + CXX: clang++-10 + CMAKE_FLAGS: "-DENABLE_GETTEXT=0 -DBUILD_SERVER=0" + + - name: Integration test + devtest + run: | + ./util/test_multiplayer.sh + + luacheck: + name: "Builtin Luacheck and Unit Tests" + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - name: Install luarocks + run: | + sudo apt-get update && sudo apt-get install -y luarocks + + - name: Install luarocks tools + run: | + luarocks install --local luacheck + luarocks install --local busted + + - name: Run checks (builtin) + run: | + $HOME/.luarocks/bin/luacheck builtin + $HOME/.luarocks/bin/busted builtin + + - name: Run checks (devtest) + run: | + $HOME/.luarocks/bin/luacheck --config=games/devtest/.luacheckrc games/devtest diff --git a/.github/workflows/lua_lint.yml b/.github/workflows/lua_lint.yml deleted file mode 100644 index 738e5afff..000000000 --- a/.github/workflows/lua_lint.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: lua_lint - -# Lint on lua changes on builtin or if workflow changed -on: - push: - paths: - - 'builtin/**.lua' - - '.github/workflows/**.yml' - pull_request: - paths: - - 'builtin/**.lua' - - '.github/workflows/**.yml' - -jobs: - luacheck: - name: "Builtin Luacheck and Unit Tests" - runs-on: ubuntu-18.04 - steps: - - uses: actions/checkout@v2 - - name: Install luarocks - run: | - sudo apt-get install luarocks -qyy - - - name: Install luarocks tools - run: | - luarocks install --local luacheck - luarocks install --local busted - - - name: Run checks - run: | - $HOME/.luarocks/bin/luacheck builtin - $HOME/.luarocks/bin/busted builtin diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index d97cff1aa..c0278a38c 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -22,7 +22,7 @@ on: - '.github/workflows/macos.yml' env: - IRRLICHT_TAG: 1.9.0mt3 + IRRLICHT_TAG: 1.9.0mt5 MINETEST_GAME_REPO: https://github.com/minetest/minetest_game.git MINETEST_GAME_BRANCH: master MINETEST_GAME_NAME: minetest_game @@ -31,7 +31,7 @@ jobs: build: runs-on: macos-10.15 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install deps run: | pkgs=(cmake freetype gettext gmp hiredis jpeg jsoncpp leveldb libogg libpng libvorbis luajit zstd) @@ -45,8 +45,8 @@ jobs: git clone -b $MINETEST_GAME_BRANCH $MINETEST_GAME_REPO games/$MINETEST_GAME_NAME rm -rvf games/$MINETEST_GAME_NAME/.git git clone https://github.com/minetest/irrlicht -b $IRRLICHT_TAG lib/irrlichtmt - mkdir cmakebuild - cd cmakebuild + mkdir build + cd build cmake .. \ -DCMAKE_OSX_DEPLOYMENT_TARGET=10.14 \ -DCMAKE_FIND_FRAMEWORK=LAST \ @@ -60,7 +60,7 @@ jobs: run: | ./build/macos/minetest.app/Contents/MacOS/minetest --run-unittests - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: minetest-macos path: ./build/macos/ diff --git a/.gitignore b/.gitignore index 2e410b48a..8a963d3c9 100644 --- a/.gitignore +++ b/.gitignore @@ -107,6 +107,13 @@ CMakeDoxy* compile_commands.json *.apk *.zip +# Visual Studio +*.vcxproj* +*.sln +.vs/ # Optional user provided library folder lib/irrlichtmt + +# Generated mod storage database +client/mod_storage.sqlite diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5b085c36c..c225bfcd4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -9,7 +9,7 @@ stages: - deploy variables: - IRRLICHT_TAG: "1.9.0mt3" + IRRLICHT_TAG: "1.9.0mt5" MINETEST_GAME_REPO: "https://github.com/minetest/minetest_game.git" CONTAINER_IMAGE: registry.gitlab.com/$CI_PROJECT_PATH @@ -20,11 +20,9 @@ variables: - DEBIAN_FRONTEND=noninteractive apt-get -y install build-essential git cmake libpng-dev libjpeg-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libleveldb-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev libzstd-dev script: - git clone https://github.com/minetest/irrlicht -b $IRRLICHT_TAG lib/irrlichtmt - - mkdir cmakebuild - - cd cmakebuild - - cmake -DCMAKE_INSTALL_PREFIX=../artifact/minetest/usr/ -DCMAKE_BUILD_TYPE=Release -DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE -DBUILD_SERVER=TRUE .. - - make -j2 - - make install + - cmake -B build -DCMAKE_INSTALL_PREFIX=../artifact/minetest/usr/ -DCMAKE_BUILD_TYPE=Release -DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE -DBUILD_SERVER=TRUE .. + - cmake --build build --parallel $(($(nproc) + 1)) + - cmake --install build artifacts: when: on_success expire_in: 1h @@ -198,25 +196,12 @@ build:fedora-28: before_script: - apt-get update - DEBIAN_FRONTEND=noninteractive apt-get install -y wget xz-utils unzip git cmake gettext - - wget -nv http://minetest.kitsunemimi.pw/mingw-w64-${WIN_ARCH}_9.2.0_ubuntu18.04.tar.xz -O mingw.tar.xz + - wget -nv http://minetest.kitsunemimi.pw/mingw-w64-${WIN_ARCH}_11.2.0_ubuntu20.04.tar.xz -O mingw.tar.xz - tar -xaf mingw.tar.xz -C /usr .build_win_template: extends: .generic_win_template stage: build - artifacts: - expire_in: 1h - paths: - - build/build/*.zip - -.package_win_template: - extends: .generic_win_template - stage: package - script: - - unzip build/build/*.zip - - cp -p /usr/${WIN_ARCH}-w64-mingw32/bin/libgcc*.dll minetest-*-win*/bin/ - - cp -p /usr/${WIN_ARCH}-w64-mingw32/bin/libstdc++*.dll minetest-*-win*/bin/ - - cp -p /usr/${WIN_ARCH}-w64-mingw32/bin/libwinpthread*.dll minetest-*-win*/bin/ artifacts: expire_in: 90 day paths: @@ -226,28 +211,15 @@ build:win32: extends: .build_win_template script: - EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin32.sh build + - unzip -q build/build/*.zip variables: WIN_ARCH: "i686" -package:win32: - extends: .package_win_template - needs: - - build:win32 - variables: - WIN_ARCH: "i686" - - build:win64: extends: .build_win_template script: - EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin64.sh build - variables: - WIN_ARCH: "x86_64" - -package:win64: - extends: .package_win_template - needs: - - build:win64 + - unzip -q build/build/*.zip variables: WIN_ARCH: "x86_64" diff --git a/CMakeLists.txt b/CMakeLists.txt index deb327c5b..018e233da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,13 +11,14 @@ endif() project(minetest) set(PROJECT_NAME_CAPITALIZED "Dragonfire") -set(CMAKE_CXX_STANDARD 11) -set(GCC_MINIMUM_VERSION "4.8") -set(CLANG_MINIMUM_VERSION "3.4") +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED TRUE) +set(GCC_MINIMUM_VERSION "5.1") +set(CLANG_MINIMUM_VERSION "3.5") # Also remember to set PROTOCOL_VERSION in network/networkprotocol.h when releasing set(VERSION_MAJOR 5) -set(VERSION_MINOR 5) +set(VERSION_MINOR 6) set(VERSION_PATCH 0) set(VERSION_EXTRA "dragonfire" CACHE STRING "Stuff to append to version string") @@ -26,7 +27,7 @@ set(DEVELOPMENT_BUILD FALSE) set(VERSION_STRING "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}") if(VERSION_EXTRA) - set(VERSION_STRING ${VERSION_STRING}-${VERSION_EXTRA}) + set(VERSION_STRING "${VERSION_STRING}-${VERSION_EXTRA}") elseif(DEVELOPMENT_BUILD) set(VERSION_STRING "${VERSION_STRING}-dev") endif() @@ -51,7 +52,7 @@ set(RUN_IN_PLACE ${DEFAULT_RUN_IN_PLACE} CACHE BOOL set(BUILD_CLIENT TRUE CACHE BOOL "Build client") set(BUILD_SERVER FALSE CACHE BOOL "Build server") set(BUILD_UNITTESTS TRUE CACHE BOOL "Build unittests") - +set(BUILD_BENCHMARKS FALSE CACHE BOOL "Build benchmarks") set(WARN_ALL TRUE CACHE BOOL "Enable -Wall for Release build") @@ -64,8 +65,21 @@ endif() set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/") +set(IRRLICHTMT_BUILD_DIR "" CACHE PATH "Path to IrrlichtMt build directory.") +if(NOT "${IRRLICHTMT_BUILD_DIR}" STREQUAL "") + find_package(IrrlichtMt QUIET + PATHS "${IRRLICHTMT_BUILD_DIR}" + NO_DEFAULT_PATH + ) + + if(NOT TARGET IrrlichtMt::IrrlichtMt) + # find_package() searches certain subdirectories. ${PATH}/cmake is not + # the only one, but it is the one where IrrlichtMt is supposed to export + # IrrlichtMtConfig.cmake + message(FATAL_ERROR "Could not find IrrlichtMtConfig.cmake in ${IRRLICHTMT_BUILD_DIR}/cmake.") + endif() # This is done here so that relative search paths are more reasonable -if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/lib/irrlichtmt") +elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/lib/irrlichtmt") message(STATUS "Using user-provided IrrlichtMt at subdirectory 'lib/irrlichtmt'") if(BUILD_CLIENT) # tell IrrlichtMt to create a static library @@ -101,11 +115,13 @@ else() # Note that we can't use target_include_directories() since that doesn't work for IMPORTED targets before CMake 3.11 set_target_properties(IrrlichtMt::IrrlichtMt PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${IRRLICHT_INCLUDE_DIR}") - else() - message(STATUS "Found IrrlichtMt ${IrrlichtMt_VERSION}") endif() endif() +if(TARGET IrrlichtMt::IrrlichtMt) + message(STATUS "Found IrrlichtMt ${IrrlichtMt_VERSION}") +endif() + # Installation @@ -135,15 +151,16 @@ elseif(UNIX) # Linux, BSD etc set(ICONDIR "unix/icons") set(LOCALEDIR "locale") else() - set(SHAREDIR "${CMAKE_INSTALL_PREFIX}/share/${PROJECT_NAME}") - set(BINDIR "${CMAKE_INSTALL_PREFIX}/bin") - set(DOCDIR "${CMAKE_INSTALL_PREFIX}/share/doc/${PROJECT_NAME}") - set(MANDIR "${CMAKE_INSTALL_PREFIX}/share/man") + include(GNUInstallDirs) + set(SHAREDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}") + set(BINDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}") + set(DOCDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DOCDIR}") + set(MANDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_MANDIR}") set(EXAMPLE_CONF_DIR ${DOCDIR}) - set(XDG_APPS_DIR "${CMAKE_INSTALL_PREFIX}/share/applications") - set(APPDATADIR "${CMAKE_INSTALL_PREFIX}/share/metainfo") - set(ICONDIR "${CMAKE_INSTALL_PREFIX}/share/icons") - set(LOCALEDIR "${CMAKE_INSTALL_PREFIX}/share/locale") + set(XDG_APPS_DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/applications") + set(APPDATADIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/metainfo") + set(ICONDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/icons") + set(LOCALEDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LOCALEDIR}") endif() endif() @@ -246,10 +263,10 @@ endif() find_package(GMP REQUIRED) find_package(Json REQUIRED) find_package(Lua REQUIRED) - -# JsonCpp doesn't compile well on GCC 4.8 -if(NOT USE_SYSTEM_JSONCPP) - set(GCC_MINIMUM_VERSION "4.9") +if(NOT USE_LUAJIT) + set(LUA_BIT_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib/bitop) + set(LUA_BIT_LIBRARY bitop) + add_subdirectory(lib/bitop) endif() if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") @@ -264,9 +281,12 @@ elseif(CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?Clang") endif() endif() +if(BUILD_BENCHMARKS) + add_subdirectory(lib/catch2) +endif() + # Subdirectories # Be sure to add all relevant definitions above this - add_subdirectory(src) diff --git a/Dockerfile b/Dockerfile index 8d1008fa2..3dd82e772 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,23 +27,20 @@ RUN apk add --no-cache git build-base cmake sqlite-dev curl-dev zlib-dev zstd-de WORKDIR /usr/src/ RUN git clone --recursive https://github.com/jupp0r/prometheus-cpp/ && \ - mkdir prometheus-cpp/build && \ - cd prometheus-cpp/build && \ - cmake .. \ + cd prometheus-cpp && \ + cmake -B build \ -DCMAKE_INSTALL_PREFIX=/usr/local \ -DCMAKE_BUILD_TYPE=Release \ -DENABLE_TESTING=0 \ -GNinja && \ - ninja && \ - ninja install + cmake --build build && \ + cmake --install build RUN git clone --depth=1 https://github.com/minetest/irrlicht/ -b ${IRRLICHT_VERSION} && \ cp -r irrlicht/include /usr/include/irrlichtmt WORKDIR /usr/src/minetest -RUN mkdir build && \ - cd build && \ - cmake .. \ +RUN cmake -B build \ -DCMAKE_INSTALL_PREFIX=/usr/local \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_SERVER=TRUE \ @@ -51,8 +48,8 @@ RUN mkdir build && \ -DBUILD_UNITTESTS=FALSE \ -DBUILD_CLIENT=FALSE \ -GNinja && \ - ninja && \ - ninja install + cmake --build build && \ + cmake --install build ARG DOCKER_IMAGE=alpine:3.14 FROM $DOCKER_IMAGE AS runtime diff --git a/README.md b/README.md index 5f4dded59..b28a0ff06 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Minetest Minetest is a free open-source voxel game engine with easy modding and game creation. -Copyright (C) 2010-2020 Perttu Ahola +Copyright (C) 2010-2022 Perttu Ahola and contributors (see source file comments and the version control log) In case you downloaded the source code @@ -132,10 +132,11 @@ Compiling | Dependency | Version | Commentary | |------------|---------|------------| -| GCC | 4.9+ | Can be replaced with Clang 3.4+ | +| GCC | 5.1+ | or Clang 3.5+ | | CMake | 3.5+ | | | IrrlichtMt | - | Custom version of Irrlicht, see https://github.com/minetest/irrlicht | -| SQLite3 | 3.0+ | | +| Freetype | 2.0+ | | +| SQLite3 | 3+ | | | Zstd | 1.0+ | | | LuaJIT | 2.0+ | Bundled Lua 5.1 is used if not present | | GMP | 5.0.0+ | Bundled mini-GMP is used if not present | @@ -143,12 +144,12 @@ Compiling For Debian/Ubuntu users: - sudo apt install g++ make libc6-dev cmake libpng-dev libjpeg-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev libzstd-dev + sudo apt install g++ make libc6-dev cmake libpng-dev libjpeg-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev libzstd-dev libluajit-5.1-dev For Fedora users: sudo dnf install make automake gcc gcc-c++ kernel-devel cmake libcurl-devel openal-soft-devel libvorbis-devel libXxf86vm-devel libogg-devel freetype-devel mesa-libGL-devel zlib-devel jsoncpp-devel gmp-devel sqlite-devel luajit-devel leveldb-devel ncurses-devel spatialindex-devel libzstd-devel - + For Arch users: sudo pacman -S base-devel libcurl-gnutls cmake libxxf86vm libpng sqlite libogg libvorbis openal freetype2 jsoncpp gmp luajit leveldb ncurses zstd @@ -224,10 +225,13 @@ Run it: - Debug build is slower, but gives much more useful output in a debugger. - If you build a bare server you don't need to have the Irrlicht or IrrlichtMt library installed. - In that case use `-DIRRLICHT_INCLUDE_DIR=/some/where/irrlicht/include`. -- IrrlichtMt can also be installed somewhere that is not a standard install path. - - In that case use `-DCMAKE_PREFIX_PATH=/path/to/install_prefix` - - The path must be set so that `$(CMAKE_PREFIX_PATH)/lib/cmake/IrrlichtMt` exists - or that `$(CMAKE_PREFIX_PATH)` is the path of an IrrlichtMt build folder. + +- Minetest will use the IrrlichtMt package that is found first, given by the following order: + 1. Specified `IRRLICHTMT_BUILD_DIR` CMake variable + 2. `${PROJECT_SOURCE_DIR}/lib/irrlichtmt` (if existent) + 3. Installation of IrrlichtMt in the system-specific library paths + 4. For server builds with disabled `BUILD_CLIENT` variable, the headers from `IRRLICHT_INCLUDE_DIR` will be used. + - NOTE: Changing the IrrlichtMt build directory (includes system installs) requires regenerating the CMake cache (`rm CMakeCache.txt`) ### CMake options @@ -236,6 +240,7 @@ General options and their default values: BUILD_CLIENT=TRUE - Build Minetest client BUILD_SERVER=FALSE - Build Minetest server BUILD_UNITTESTS=TRUE - Build unittest sources + BUILD_BENCHMARKS=FALSE - Build benchmark sources CMAKE_BUILD_TYPE=Release - Type of build (Release vs. Debug) Release - Release build Debug - Debug build @@ -244,9 +249,8 @@ General options and their default values: MinSizeRel - Release build with -Os passed to compiler to make executable as small as possible ENABLE_CURL=ON - Build with cURL; Enables use of online mod repo, public serverlist and remote media fetching via http ENABLE_CURSES=ON - Build with (n)curses; Enables a server side terminal (command line option: --terminal) - ENABLE_FREETYPE=ON - Build with FreeType2; Allows using TTF fonts ENABLE_GETTEXT=ON - Build with Gettext; Allows using translations - ENABLE_GLES=OFF - Build for OpenGL ES instead of OpenGL (requires support by IrrlichtMt) + ENABLE_GLES=OFF - Enable extra support code for OpenGL ES (requires support by IrrlichtMt) ENABLE_LEVELDB=ON - Build with LevelDB; Enables use of LevelDB map backend ENABLE_POSTGRESQL=ON - Build with libpq; Enables use of PostgreSQL map backend (PostgreSQL 9.5 or greater recommended) ENABLE_REDIS=ON - Build with libhiredis; Enables use of Redis map backend @@ -256,10 +260,10 @@ General options and their default values: 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) ENABLE_SYSTEM_JSONCPP=ON - Use JsonCPP from system - OPENGL_GL_PREFERENCE=LEGACY - Linux client build only; See CMake Policy CMP0072 for reference RUN_IN_PLACE=FALSE - Create a portable install (worlds, settings etc. in current directory) 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) Library specific options: @@ -268,10 +272,11 @@ Library specific options: CURL_LIBRARY - Only if building with cURL; path to libcurl.a/libcurl.so/libcurl.lib EGL_INCLUDE_DIR - Only if building with GLES; directory that contains egl.h EGL_LIBRARY - Only if building with GLES; path to libEGL.a/libEGL.so - FREETYPE_INCLUDE_DIR_freetype2 - Only if building with FreeType 2; directory that contains an freetype directory with files such as ftimage.h in it - FREETYPE_INCLUDE_DIR_ft2build - Only if building with FreeType 2; directory that contains ft2build.h - FREETYPE_LIBRARY - Only if building with FreeType 2; path to libfreetype.a/libfreetype.so/freetype.lib - FREETYPE_DLL - Only if building with FreeType 2 on Windows; path to libfreetype.dll + EXTRA_DLL - Only on Windows; optional paths to additional DLLs that should be packaged + FREETYPE_INCLUDE_DIR_freetype2 - Directory that contains files such as ftimage.h + FREETYPE_INCLUDE_DIR_ft2build - Directory that contains ft2build.h + FREETYPE_LIBRARY - Path to libfreetype.a/libfreetype.so/freetype.lib + FREETYPE_DLL - Only on Windows; path to libfreetype-6.dll GETTEXT_DLL - Only when building with gettext on Windows; paths to libintl + libiconv DLLs GETTEXT_INCLUDE_DIR - Only when building with gettext; directory that contains iconv.h GETTEXT_LIBRARY - Only when building with gettext on Windows; path to libintl.dll.a @@ -295,15 +300,12 @@ Library specific options: OPENAL_DLL - Only if building with sound on Windows; path to OpenAL32.dll OPENAL_INCLUDE_DIR - Only if building with sound; directory where al.h is located OPENAL_LIBRARY - Only if building with sound; path to libopenal.a/libopenal.so/OpenAL32.lib - OPENGLES2_INCLUDE_DIR - Only if building with GLES; directory that contains gl2.h - OPENGLES2_LIBRARY - Only if building with GLES; path to libGLESv2.a/libGLESv2.so SQLITE3_INCLUDE_DIR - Directory that contains sqlite3.h SQLITE3_LIBRARY - Path to libsqlite3.a/libsqlite3.so/sqlite3.lib VORBISFILE_LIBRARY - Only if building with sound; path to libvorbisfile.a/libvorbisfile.so/libvorbisfile.dll.a VORBIS_DLL - Only if building with sound on Windows; paths to vorbis DLLs VORBIS_INCLUDE_DIR - Only if building with sound; directory that contains a directory vorbis with vorbisenc.h inside VORBIS_LIBRARY - Only if building with sound; path to libvorbis.a/libvorbis.so/libvorbis.dll.a - XXF86VM_LIBRARY - Only on Linux; path to libXXf86vm.a/libXXf86vm.so ZLIB_DLL - Only on Windows; path to zlib1.dll ZLIB_INCLUDE_DIR - Directory that contains zlib.h ZLIB_LIBRARY - Path to libz.a/libz.so/zlib.lib @@ -326,13 +328,12 @@ 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 sqlite3 freetype luajit gmp jsoncpp --triplet x64-windows +vcpkg install zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp opengl-registry --triplet x64-windows ``` - **Don't forget about IrrlichtMt.** The easiest way is to clone it to `lib/irrlichtmt` as described in the Linux section. - `curl` is optional, but required to read the serverlist, `curl[winssl]` is required to use the content store. - `openal-soft`, `libvorbis` and `libogg` are optional, but required to use sound. -- `freetype` is optional, it allows true-type font rendering. - `luajit` is optional, it replaces the integrated Lua interpreter with a faster just-in-time interpreter. - `gmp` and `jsoncpp` are optional, otherwise the bundled versions will be compiled @@ -381,6 +382,60 @@ Build the binaries as described above, but make sure you unselect `RUN_IN_PLACE` Open the generated project file with Visual Studio. Right-click **Package** and choose **Generate**. It may take some minutes to generate the installer. +### Compiling on MacOS + +#### Requirements +- [Homebrew](https://brew.sh/) +- [Git](https://git-scm.com/downloads) + +Install dependencies with homebrew: + +``` +brew install cmake freetype gettext gmp hiredis jpeg jsoncpp leveldb libogg libpng libvorbis luajit zstd +``` + +#### Download + +Download source (this is the URL to the latest of source repository, which might not work at all times) using Git: + +```bash +git clone --depth 1 https://github.com/minetest/minetest.git +cd minetest +``` + +Download minetest_game (otherwise only the "Development Test" game is available) using Git: + +``` +git clone --depth 1 https://github.com/minetest/minetest_game.git games/minetest_game +``` + +Download Minetest's fork of Irrlicht: + +``` +git clone --depth 1 https://github.com/minetest/irrlicht.git lib/irrlichtmt +``` + +#### Build + +```bash +mkdir build +cd build + +cmake .. \ + -DCMAKE_OSX_DEPLOYMENT_TARGET=10.14 \ + -DCMAKE_FIND_FRAMEWORK=LAST \ + -DCMAKE_INSTALL_PREFIX=../build/macos/ \ + -DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE + +make -j$(sysctl -n hw.logicalcpu) +make install +``` + +#### Run + +``` +open ./build/macos/minetest.app +``` Docker ------ diff --git a/android/app/build.gradle b/android/app/build.gradle index 53fe85910..e8ba95722 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,12 +1,12 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 29 + compileSdkVersion 30 buildToolsVersion '30.0.3' - ndkVersion '22.0.7026061' + ndkVersion "$ndk_version" defaultConfig { applicationId 'net.minetest.minetest' minSdkVersion 16 - targetSdkVersion 29 + targetSdkVersion 30 versionName "${versionMajor}.${versionMinor}.${versionPatch}" versionCode project.versionCode } @@ -68,7 +68,7 @@ task prepareAssets() { from "${projRoot}/client/shaders" into "${assetsFolder}/client/shaders" } copy { - from "../native/deps/Android/Irrlicht/shaders" into "${assetsFolder}/client/shaders/Irrlicht" + from "../native/deps/armeabi-v7a/Irrlicht/Shaders" into "${assetsFolder}/client/shaders/Irrlicht" } copy { from "${projRoot}/fonts" include "*.ttf" into "${assetsFolder}/fonts" @@ -112,5 +112,5 @@ android.applicationVariants.all { variant -> dependencies { implementation project(':native') - implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.appcompat:appcompat:1.3.1' } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index fa93e7069..6ea677cb9 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -30,7 +30,8 @@ android:configChanges="orientation|keyboardHidden|navigation|screenSize" android:maxAspectRatio="3.0" android:screenOrientation="sensorLandscape" - android:theme="@style/AppTheme"> + android:theme="@style/AppTheme" + android:exported="true"> @@ -44,7 +45,8 @@ android:launchMode="singleTask" android:maxAspectRatio="3.0" android:screenOrientation="sensorLandscape" - android:theme="@style/AppTheme"> + android:theme="@style/AppTheme" + android:exported="true"> diff --git a/android/app/src/main/java/net/minetest/minetest/CopyZipTask.java b/android/app/src/main/java/net/minetest/minetest/CopyZipTask.java deleted file mode 100644 index 6d4b6ab0f..000000000 --- a/android/app/src/main/java/net/minetest/minetest/CopyZipTask.java +++ /dev/null @@ -1,82 +0,0 @@ -/* -Minetest -Copyright (C) 2014-2020 MoNTE48, Maksim Gamarnik -Copyright (C) 2014-2020 ubulem, Bektur Mambetov - -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. -*/ - -package net.minetest.minetest; - -import android.content.Intent; -import android.os.AsyncTask; -import android.widget.Toast; - -import androidx.appcompat.app.AppCompatActivity; - -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.lang.ref.WeakReference; - -public class CopyZipTask extends AsyncTask { - - private final WeakReference activityRef; - - CopyZipTask(AppCompatActivity activity) { - activityRef = new WeakReference<>(activity); - } - - protected String doInBackground(String... params) { - copyAsset(params[0]); - return params[0]; - } - - @Override - protected void onPostExecute(String result) { - startUnzipService(result); - } - - private void copyAsset(String zipName) { - String filename = zipName.substring(zipName.lastIndexOf("/") + 1); - try (InputStream in = activityRef.get().getAssets().open(filename); - OutputStream out = new FileOutputStream(zipName)) { - copyFile(in, out); - } catch (IOException e) { - AppCompatActivity activity = activityRef.get(); - if (activity != null) { - activity.runOnUiThread(() -> Toast.makeText(activityRef.get(), e.getLocalizedMessage(), Toast.LENGTH_LONG).show()); - } - cancel(true); - } - } - - private void copyFile(InputStream in, OutputStream out) throws IOException { - byte[] buffer = new byte[1024]; - int read; - while ((read = in.read(buffer)) != -1) - out.write(buffer, 0, read); - } - - private void startUnzipService(String file) { - Intent intent = new Intent(activityRef.get(), UnzipService.class); - intent.putExtra(UnzipService.EXTRA_KEY_IN_FILE, file); - AppCompatActivity activity = activityRef.get(); - if (activity != null) { - activity.startService(intent); - } - } -} diff --git a/android/app/src/main/java/net/minetest/minetest/GameActivity.java b/android/app/src/main/java/net/minetest/minetest/GameActivity.java index bdf764138..46fc9b1de 100644 --- a/android/app/src/main/java/net/minetest/minetest/GameActivity.java +++ b/android/app/src/main/java/net/minetest/minetest/GameActivity.java @@ -171,4 +171,12 @@ public class GameActivity extends NativeActivity { Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri)); startActivity(browserIntent); } + + public String getUserDataPath() { + return Utils.getUserDataDirectory(this).getAbsolutePath(); + } + + public String getCachePath() { + return Utils.getCacheDirectory(this).getAbsolutePath(); + } } diff --git a/android/app/src/main/java/net/minetest/minetest/MainActivity.java b/android/app/src/main/java/net/minetest/minetest/MainActivity.java index 2aa50d9ad..b6567b4b7 100644 --- a/android/app/src/main/java/net/minetest/minetest/MainActivity.java +++ b/android/app/src/main/java/net/minetest/minetest/MainActivity.java @@ -29,12 +29,14 @@ import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; +import android.os.Environment; import android.view.View; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; +import androidx.annotation.StringRes; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; @@ -43,11 +45,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import static net.minetest.minetest.UnzipService.ACTION_FAILURE; -import static net.minetest.minetest.UnzipService.ACTION_PROGRESS; -import static net.minetest.minetest.UnzipService.ACTION_UPDATE; -import static net.minetest.minetest.UnzipService.FAILURE; -import static net.minetest.minetest.UnzipService.SUCCESS; +import static net.minetest.minetest.UnzipService.*; public class MainActivity extends AppCompatActivity { private final static int versionCode = BuildConfig.VERSION_CODE; @@ -56,26 +54,40 @@ public class MainActivity extends AppCompatActivity { new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; private static final String SETTINGS = "MinetestSettings"; private static final String TAG_VERSION_CODE = "versionCode"; + private ProgressBar mProgressBar; private TextView mTextView; private SharedPreferences sharedPreferences; + private final BroadcastReceiver myReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { int progress = 0; - if (intent != null) + @StringRes int message = 0; + if (intent != null) { progress = intent.getIntExtra(ACTION_PROGRESS, 0); - if (progress >= 0) { - if (mProgressBar != null) { - mProgressBar.setVisibility(View.VISIBLE); - mProgressBar.setProgress(progress); - } - mTextView.setVisibility(View.VISIBLE); - } else if (progress == FAILURE) { + message = intent.getIntExtra(ACTION_PROGRESS_MESSAGE, 0); + } + + if (progress == FAILURE) { Toast.makeText(MainActivity.this, intent.getStringExtra(ACTION_FAILURE), Toast.LENGTH_LONG).show(); finish(); - } else if (progress == SUCCESS) + } else if (progress == SUCCESS) { startNative(); + } else { + if (mProgressBar != null) { + mProgressBar.setVisibility(View.VISIBLE); + if (progress == INDETERMINATE) { + mProgressBar.setIndeterminate(true); + } else { + mProgressBar.setIndeterminate(false); + mProgressBar.setProgress(progress); + } + } + mTextView.setVisibility(View.VISIBLE); + if (message != 0) + mTextView.setText(message); + } } }; @@ -88,7 +100,9 @@ public class MainActivity extends AppCompatActivity { mProgressBar = findViewById(R.id.progressBar); mTextView = findViewById(R.id.textView); sharedPreferences = getSharedPreferences(SETTINGS, Context.MODE_PRIVATE); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && + Build.VERSION.SDK_INT < Build.VERSION_CODES.R) checkPermission(); else checkAppVersion(); @@ -120,6 +134,7 @@ public class MainActivity extends AppCompatActivity { if (grantResult != PackageManager.PERMISSION_GRANTED) { Toast.makeText(this, R.string.not_granted, Toast.LENGTH_LONG).show(); finish(); + return; } } checkAppVersion(); @@ -127,10 +142,27 @@ public class MainActivity extends AppCompatActivity { } private void checkAppVersion() { - if (sharedPreferences.getInt(TAG_VERSION_CODE, 0) == versionCode) + if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + Toast.makeText(this, R.string.no_external_storage, Toast.LENGTH_LONG).show(); + finish(); + return; + } + + if (UnzipService.getIsRunning()) { + mProgressBar.setVisibility(View.VISIBLE); + mProgressBar.setIndeterminate(true); + mTextView.setVisibility(View.VISIBLE); + } else if (sharedPreferences.getInt(TAG_VERSION_CODE, 0) == versionCode && + Utils.isInstallValid(this)) { startNative(); - else - new CopyZipTask(this).execute(getCacheDir() + "/Minetest.zip"); + } else { + mProgressBar.setVisibility(View.VISIBLE); + mProgressBar.setIndeterminate(true); + mTextView.setVisibility(View.VISIBLE); + + Intent intent = new Intent(this, UnzipService.class); + startService(intent); + } } private void startNative() { diff --git a/android/app/src/main/java/net/minetest/minetest/UnzipService.java b/android/app/src/main/java/net/minetest/minetest/UnzipService.java index b69f7f36e..a61a49139 100644 --- a/android/app/src/main/java/net/minetest/minetest/UnzipService.java +++ b/android/app/src/main/java/net/minetest/minetest/UnzipService.java @@ -24,16 +24,22 @@ import android.app.IntentService; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; +import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.os.Build; import android.os.Environment; -import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import androidx.annotation.StringRes; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -42,32 +48,61 @@ import java.util.zip.ZipInputStream; public class UnzipService extends IntentService { public static final String ACTION_UPDATE = "net.minetest.minetest.UPDATE"; public static final String ACTION_PROGRESS = "net.minetest.minetest.PROGRESS"; + public static final String ACTION_PROGRESS_MESSAGE = "net.minetest.minetest.PROGRESS_MESSAGE"; public static final String ACTION_FAILURE = "net.minetest.minetest.FAILURE"; - public static final String EXTRA_KEY_IN_FILE = "file"; public static final int SUCCESS = -1; public static final int FAILURE = -2; + public static final int INDETERMINATE = -3; private final int id = 1; private NotificationManager mNotifyManager; private boolean isSuccess = true; private String failureMessage; + private static boolean isRunning = false; + public static synchronized boolean getIsRunning() { + return isRunning; + } + private static synchronized void setIsRunning(boolean v) { + isRunning = v; + } + public UnzipService() { super("net.minetest.minetest.UnzipService"); } - private void isDir(String dir, String location) { - File f = new File(location, dir); - if (!f.isDirectory()) - f.mkdirs(); - } - @Override protected void onHandleIntent(Intent intent) { - createNotification(); - unzip(intent); + Notification.Builder notificationBuilder = createNotification(); + final File zipFile = new File(getCacheDir(), "Minetest.zip"); + try { + setIsRunning(true); + File userDataDirectory = Utils.getUserDataDirectory(this); + if (userDataDirectory == null) { + throw new IOException("Unable to find user data directory"); + } + + try (InputStream in = this.getAssets().open(zipFile.getName())) { + try (OutputStream out = new FileOutputStream(zipFile)) { + int readLen; + byte[] readBuffer = new byte[16384]; + while ((readLen = in.read(readBuffer)) != -1) { + out.write(readBuffer, 0, readLen); + } + } + } + + migrate(notificationBuilder, userDataDirectory); + unzip(notificationBuilder, zipFile, userDataDirectory); + } catch (IOException e) { + isSuccess = false; + failureMessage = e.getLocalizedMessage(); + } finally { + setIsRunning(false); + zipFile.delete(); + } } - private void createNotification() { + private Notification.Builder createNotification() { String name = "net.minetest.minetest"; String channelId = "Minetest channel"; String description = "notifications from Minetest"; @@ -92,66 +127,133 @@ public class UnzipService extends IntentService { } else { builder = new Notification.Builder(this); } + + Intent notificationIntent = new Intent(this, MainActivity.class); + notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP + | Intent.FLAG_ACTIVITY_SINGLE_TOP); + PendingIntent intent = PendingIntent.getActivity(this, 0, + notificationIntent, 0); + builder.setContentTitle(getString(R.string.notification_title)) .setSmallIcon(R.mipmap.ic_launcher) - .setContentText(getString(R.string.notification_description)); + .setContentText(getString(R.string.notification_description)) + .setContentIntent(intent) + .setOngoing(true) + .setProgress(0, 0, true); + mNotifyManager.notify(id, builder.build()); + return builder; } - private void unzip(Intent intent) { - String zip = intent.getStringExtra(EXTRA_KEY_IN_FILE); - isDir("Minetest", Environment.getExternalStorageDirectory().toString()); - String location = Environment.getExternalStorageDirectory() + File.separator + "Minetest" + File.separator; + private void unzip(Notification.Builder notificationBuilder, File zipFile, File userDataDirectory) throws IOException { int per = 0; - int size = getSummarySize(zip); - File zipFile = new File(zip); + + int size; + try (ZipFile zipSize = new ZipFile(zipFile)) { + size = zipSize.size(); + } + int readLen; - byte[] readBuffer = new byte[8192]; + byte[] readBuffer = new byte[16384]; try (FileInputStream fileInputStream = new FileInputStream(zipFile); ZipInputStream zipInputStream = new ZipInputStream(fileInputStream)) { ZipEntry ze; while ((ze = zipInputStream.getNextEntry()) != null) { if (ze.isDirectory()) { ++per; - isDir(ze.getName(), location); - } else { - publishProgress(100 * ++per / size); - try (OutputStream outputStream = new FileOutputStream(location + ze.getName())) { - while ((readLen = zipInputStream.read(readBuffer)) != -1) { - outputStream.write(readBuffer, 0, readLen); - } + Utils.createDirs(userDataDirectory, ze.getName()); + continue; + } + publishProgress(notificationBuilder, R.string.loading, 100 * ++per / size); + try (OutputStream outputStream = new FileOutputStream( + new File(userDataDirectory, ze.getName()))) { + while ((readLen = zipInputStream.read(readBuffer)) != -1) { + outputStream.write(readBuffer, 0, readLen); } } - zipFile.delete(); } - } catch (IOException e) { - isSuccess = false; - failureMessage = e.getLocalizedMessage(); } } - private void publishProgress(int progress) { + void moveFileOrDir(@NonNull File src, @NonNull File dst) throws IOException { + try { + Process p = new ProcessBuilder("/system/bin/mv", + src.getAbsolutePath(), dst.getAbsolutePath()).start(); + int exitcode = p.waitFor(); + if (exitcode != 0) + throw new IOException("Move failed with exit code " + exitcode); + } catch (InterruptedException e) { + throw new IOException("Move operation interrupted"); + } + } + + boolean recursivelyDeleteDirectory(@NonNull File loc) { + try { + Process p = new ProcessBuilder("/system/bin/rm", "-rf", + loc.getAbsolutePath()).start(); + return p.waitFor() == 0; + } catch (IOException | InterruptedException e) { + return false; + } + } + + /** + * Migrates user data from deprecated external storage to app scoped storage + */ + private void migrate(Notification.Builder notificationBuilder, File newLocation) throws IOException { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + return; + } + + File oldLocation = new File(Environment.getExternalStorageDirectory(), "Minetest"); + if (!oldLocation.isDirectory()) + return; + + publishProgress(notificationBuilder, R.string.migrating, 0); + newLocation.mkdir(); + + String[] dirs = new String[] { "worlds", "games", "mods", "textures", "client" }; + for (int i = 0; i < dirs.length; i++) { + publishProgress(notificationBuilder, R.string.migrating, 100 * i / dirs.length); + File dir = new File(oldLocation, dirs[i]), dir2 = new File(newLocation, dirs[i]); + if (dir.isDirectory() && !dir2.isDirectory()) { + moveFileOrDir(dir, dir2); + } + } + + for (String filename : new String[] { "minetest.conf" }) { + File file = new File(oldLocation, filename), file2 = new File(newLocation, filename); + if (file.isFile() && !file2.isFile()) { + moveFileOrDir(file, file2); + } + } + + recursivelyDeleteDirectory(oldLocation); + } + + private void publishProgress(@Nullable Notification.Builder notificationBuilder, @StringRes int message, int progress) { Intent intentUpdate = new Intent(ACTION_UPDATE); intentUpdate.putExtra(ACTION_PROGRESS, progress); - if (!isSuccess) intentUpdate.putExtra(ACTION_FAILURE, failureMessage); + intentUpdate.putExtra(ACTION_PROGRESS_MESSAGE, message); + if (!isSuccess) + intentUpdate.putExtra(ACTION_FAILURE, failureMessage); sendBroadcast(intentUpdate); - } - private int getSummarySize(String zip) { - int size = 0; - try { - ZipFile zipSize = new ZipFile(zip); - size += zipSize.size(); - } catch (IOException e) { - Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show(); + if (notificationBuilder != null) { + notificationBuilder.setContentText(getString(message)); + if (progress == INDETERMINATE) { + notificationBuilder.setProgress(100, 50, true); + } else { + notificationBuilder.setProgress(100, progress, false); + } + mNotifyManager.notify(id, notificationBuilder.build()); } - return size; } @Override public void onDestroy() { super.onDestroy(); mNotifyManager.cancel(id); - publishProgress(isSuccess ? SUCCESS : FAILURE); + publishProgress(null, R.string.loading, isSuccess ? SUCCESS : FAILURE); } } diff --git a/android/app/src/main/java/net/minetest/minetest/Utils.java b/android/app/src/main/java/net/minetest/minetest/Utils.java new file mode 100644 index 000000000..b2553c844 --- /dev/null +++ b/android/app/src/main/java/net/minetest/minetest/Utils.java @@ -0,0 +1,39 @@ +package net.minetest.minetest; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import java.io.File; + +public class Utils { + public static @NonNull File createDirs(File root, String dir) { + File f = new File(root, dir); + if (!f.isDirectory()) + f.mkdirs(); + + return f; + } + + public static @Nullable File getUserDataDirectory(Context context) { + File extDir = context.getExternalFilesDir(null); + if (extDir == null) { + return null; + } + + return createDirs(extDir, "Minetest"); + } + + public static @Nullable File getCacheDirectory(Context context) { + return context.getCacheDir(); + } + + public static boolean isInstallValid(Context context) { + File userDataDirectory = getUserDataDirectory(context); + return userDataDirectory != null && userDataDirectory.isDirectory() && + new File(userDataDirectory, "games").isDirectory() && + new File(userDataDirectory, "builtin").isDirectory() && + new File(userDataDirectory, "client").isDirectory() && + new File(userDataDirectory, "textures").isDirectory(); + } +} diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml index e6f461f14..93508c3cb 100644 --- a/android/app/src/main/res/layout/activity_main.xml +++ b/android/app/src/main/res/layout/activity_main.xml @@ -1,4 +1,5 @@ + android:visibility="gone" + tools:visibility="visible" /> + android:visibility="gone" + tools:visibility="visible" /> diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 85238117f..99f948c99 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -3,9 +3,11 @@ Minetest Loading… + Migrating save data from old install… (this may take a while) Required permission wasn\'t granted, Minetest can\'t run without it Loading Minetest Less than 1 minute… Done + External storage isn\'t available. If you use an SDCard, please reinsert it. Otherwise, try restarting your phone or contacting the Minetest developers diff --git a/android/build.gradle b/android/build.gradle index 3ba51a4bb..f861ba702 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,21 +1,22 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. project.ext.set("versionMajor", 5) // Version Major -project.ext.set("versionMinor", 5) // Version Minor +project.ext.set("versionMinor", 6) // Version Minor project.ext.set("versionPatch", 0) // Version Patch project.ext.set("versionExtra", "-dev") // Version Extra -project.ext.set("versionCode", 32) // Android Version Code +project.ext.set("versionCode", 38) // Android Version Code // NOTE: +2 after each release! // +1 for ARM and +1 for ARM64 APK's, because // each APK must have a larger `versionCode` than the previous buildscript { + ext.ndk_version = '23.0.7599858' repositories { google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.1' + classpath 'com.android.tools.build:gradle:7.0.3' classpath 'de.undercouch:gradle-download-task:4.1.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 7fd9307d7..8ad73a75c 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Fri Jan 08 17:52:00 UTC 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip diff --git a/android/gradlew b/android/gradlew index 83f2acfdc..25e0c1148 100755 --- a/android/gradlew +++ b/android/gradlew @@ -98,7 +98,7 @@ location of your Java installation." fi else JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + command -v java >/dev/null || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." diff --git a/android/native/build.gradle b/android/native/build.gradle index 8ea6347b3..2ddc77135 100644 --- a/android/native/build.gradle +++ b/android/native/build.gradle @@ -2,12 +2,12 @@ apply plugin: 'com.android.library' apply plugin: 'de.undercouch.download' android { - compileSdkVersion 29 + compileSdkVersion 30 buildToolsVersion '30.0.3' - ndkVersion '22.0.7026061' + ndkVersion "$ndk_version" defaultConfig { minSdkVersion 16 - targetSdkVersion 29 + targetSdkVersion 30 externalNativeBuild { ndkBuild { arguments '-j' + Runtime.getRuntime().availableProcessors(), @@ -41,58 +41,28 @@ android { arguments 'NDEBUG=1' } } + + ndk { + debugSymbolLevel 'SYMBOL_TABLE' + } } } } // get precompiled deps -def folder = 'minetest_android_deps_binaries' - task downloadDeps(type: Download) { - src 'https://github.com/minetest/' + folder + '/archive/master.zip' + src 'https://github.com/minetest/minetest_android_deps/releases/download/latest/deps.zip' dest new File(buildDir, 'deps.zip') overwrite false } task getDeps(dependsOn: downloadDeps, type: Copy) { - def deps = file('deps') - def f = file("$buildDir/" + folder + "-master") - - if (!deps.exists() && !f.exists()) { + def deps = new File(buildDir.parent, 'deps') + if (!deps.exists()) { + deps.mkdir() from zipTree(downloadDeps.dest) - into buildDir - } - - doLast { - if (!deps.exists()) { - file(f).renameTo(file(deps)) - } - } -} - -// get sqlite -def sqlite_ver = '3340000' -task downloadSqlite(dependsOn: getDeps, type: Download) { - src 'https://www.sqlite.org/2020/sqlite-amalgamation-' + sqlite_ver + '.zip' - dest new File(buildDir, 'sqlite.zip') - overwrite false -} - -task getSqlite(dependsOn: downloadSqlite, type: Copy) { - def sqlite = file('deps/Android/sqlite') - def f = file("$buildDir/sqlite-amalgamation-" + sqlite_ver) - - if (!sqlite.exists() && !f.exists()) { - from zipTree(downloadSqlite.dest) - into buildDir - } - - doLast { - if (!sqlite.exists()) { - file(f).renameTo(file(sqlite)) - } + into deps } } preBuild.dependsOn getDeps -preBuild.dependsOn getSqlite diff --git a/android/native/jni/Android.mk b/android/native/jni/Android.mk index 26e9b058b..f8ca74d3c 100644 --- a/android/native/jni/Android.mk +++ b/android/native/jni/Android.mk @@ -4,62 +4,82 @@ LOCAL_PATH := $(call my-dir)/.. include $(CLEAR_VARS) LOCAL_MODULE := Curl -LOCAL_SRC_FILES := deps/Android/Curl/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libcurl.a +LOCAL_SRC_FILES := deps/$(APP_ABI)/Curl/libcurl.a +include $(PREBUILT_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := libmbedcrypto +LOCAL_SRC_FILES := deps/$(APP_ABI)/Curl/libmbedcrypto.a +include $(PREBUILT_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := libmbedtls +LOCAL_SRC_FILES := deps/$(APP_ABI)/Curl/libmbedtls.a +include $(PREBUILT_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := libmbedx509 +LOCAL_SRC_FILES := deps/$(APP_ABI)/Curl/libmbedx509.a include $(PREBUILT_STATIC_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := Freetype -LOCAL_SRC_FILES := deps/Android/Freetype/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libfreetype.a +LOCAL_SRC_FILES := deps/$(APP_ABI)/Freetype/libfreetype.a +include $(PREBUILT_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := Iconv +LOCAL_SRC_FILES := deps/$(APP_ABI)/Iconv/libiconv.a +include $(PREBUILT_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := libcharset +LOCAL_SRC_FILES := deps/$(APP_ABI)/Iconv/libcharset.a include $(PREBUILT_STATIC_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := Irrlicht -LOCAL_SRC_FILES := deps/Android/Irrlicht/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libIrrlichtMt.a +LOCAL_SRC_FILES := deps/$(APP_ABI)/Irrlicht/libIrrlichtMt.a include $(PREBUILT_STATIC_LIBRARY) -#include $(CLEAR_VARS) -#LOCAL_MODULE := LevelDB -#LOCAL_SRC_FILES := deps/Android/LevelDB/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libleveldb.a -#include $(PREBUILT_STATIC_LIBRARY) - include $(CLEAR_VARS) LOCAL_MODULE := LuaJIT -LOCAL_SRC_FILES := deps/Android/LuaJIT/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libluajit.a -include $(PREBUILT_STATIC_LIBRARY) - -include $(CLEAR_VARS) -LOCAL_MODULE := mbedTLS -LOCAL_SRC_FILES := deps/Android/mbedTLS/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libmbedtls.a -include $(PREBUILT_STATIC_LIBRARY) - -include $(CLEAR_VARS) -LOCAL_MODULE := mbedx509 -LOCAL_SRC_FILES := deps/Android/mbedTLS/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libmbedx509.a -include $(PREBUILT_STATIC_LIBRARY) - -include $(CLEAR_VARS) -LOCAL_MODULE := mbedcrypto -LOCAL_SRC_FILES := deps/Android/mbedTLS/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libmbedcrypto.a +LOCAL_SRC_FILES := deps/$(APP_ABI)/LuaJIT/libluajit.a include $(PREBUILT_STATIC_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := OpenAL -LOCAL_SRC_FILES := deps/Android/OpenAL-Soft/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libopenal.a +LOCAL_SRC_FILES := deps/$(APP_ABI)/OpenAL-Soft/libopenal.a include $(PREBUILT_STATIC_LIBRARY) include $(CLEAR_VARS) -LOCAL_MODULE := GetText -LOCAL_SRC_FILES := deps/Android/GetText/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libintl.a +LOCAL_MODULE := Gettext +LOCAL_SRC_FILES := deps/$(APP_ABI)/Gettext/libintl.a +include $(PREBUILT_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := SQLite3 +LOCAL_SRC_FILES := deps/$(APP_ABI)/SQLite/libsqlite3.a include $(PREBUILT_STATIC_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := Vorbis -LOCAL_SRC_FILES := deps/Android/Vorbis/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libvorbis.a +LOCAL_SRC_FILES := deps/$(APP_ABI)/Vorbis/libvorbis.a +include $(PREBUILT_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := libvorbisfile +LOCAL_SRC_FILES := deps/$(APP_ABI)/Vorbis/libvorbisfile.a +include $(PREBUILT_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := libogg +LOCAL_SRC_FILES := deps/$(APP_ABI)/Vorbis/libogg.a include $(PREBUILT_STATIC_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := Zstd -LOCAL_SRC_FILES := deps/Android/Zstd/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libzstd.a +LOCAL_SRC_FILES := deps/$(APP_ABI)/Zstd/libzstd.a include $(PREBUILT_STATIC_LIBRARY) include $(CLEAR_VARS) @@ -71,7 +91,6 @@ LOCAL_CFLAGS += \ -DENABLE_GLES=1 \ -DUSE_CURL=1 \ -DUSE_SOUND=1 \ - -DUSE_FREETYPE=1 \ -DUSE_LEVELDB=0 \ -DUSE_LUAJIT=1 \ -DUSE_GETTEXT=1 \ @@ -96,18 +115,16 @@ LOCAL_C_INCLUDES := \ ../../src/script \ ../../lib/gmp \ ../../lib/jsoncpp \ - deps/Android/Curl/include \ - deps/Android/Freetype/include \ - deps/Android/Irrlicht/include \ - deps/Android/LevelDB/include \ - deps/Android/GetText/include \ - deps/Android/libiconv/include \ - deps/Android/libiconv/libcharset/include \ - deps/Android/LuaJIT/src \ - deps/Android/OpenAL-Soft/include \ - deps/Android/sqlite \ - deps/Android/Vorbis/include \ - deps/Android/Zstd/include + deps/$(APP_ABI)/Curl/include \ + deps/$(APP_ABI)/Freetype/include/freetype2 \ + deps/$(APP_ABI)/Irrlicht/include \ + deps/$(APP_ABI)/Gettext/include \ + deps/$(APP_ABI)/Iconv/include \ + deps/$(APP_ABI)/LuaJIT/include \ + deps/$(APP_ABI)/OpenAL-Soft/include \ + deps/$(APP_ABI)/SQLite/include \ + deps/$(APP_ABI)/Vorbis/include \ + deps/$(APP_ABI)/Zstd/include LOCAL_SRC_FILES := \ $(wildcard ../../src/client/*.cpp) \ @@ -190,24 +207,24 @@ LOCAL_SRC_FILES := \ ../../src/voxel.cpp \ ../../src/voxelalgorithms.cpp -# LevelDB backend is disabled -# ../../src/database/database-leveldb.cpp - # GMP LOCAL_SRC_FILES += ../../lib/gmp/mini-gmp.c # JSONCPP LOCAL_SRC_FILES += ../../lib/jsoncpp/jsoncpp.cpp -# iconv -LOCAL_SRC_FILES += \ - deps/Android/libiconv/lib/iconv.c \ - deps/Android/libiconv/libcharset/lib/localcharset.c - -# SQLite3 -LOCAL_SRC_FILES += deps/Android/sqlite/sqlite3.c - -LOCAL_STATIC_LIBRARIES += Curl Freetype Irrlicht OpenAL mbedTLS mbedx509 mbedcrypto Vorbis LuaJIT GetText Zstd android_native_app_glue $(PROFILER_LIBS) #LevelDB +LOCAL_STATIC_LIBRARIES += \ + Curl libmbedcrypto libmbedtls libmbedx509 \ + Freetype \ + Iconv libcharset \ + Irrlicht \ + LuaJIT \ + OpenAL \ + Gettext \ + SQLite3 \ + Vorbis libvorbisfile libogg \ + Zstd +LOCAL_STATIC_LIBRARIES += android_native_app_glue $(PROFILER_LIBS) LOCAL_LDLIBS := -lEGL -lGLESv1_CM -lGLESv2 -landroid -lOpenSLES diff --git a/android/native/jni/Application.mk b/android/native/jni/Application.mk index 82f0148f0..9d9596137 100644 --- a/android/native/jni/Application.mk +++ b/android/native/jni/Application.mk @@ -5,22 +5,22 @@ NDK_TOOLCHAIN_VERSION := clang APP_SHORT_COMMANDS := true APP_MODULES := Minetest -APP_CPPFLAGS := -Ofast -fvisibility=hidden -fexceptions -Wno-deprecated-declarations -Wno-extra-tokens +APP_CPPFLAGS := -O2 -fvisibility=hidden ifeq ($(APP_ABI),armeabi-v7a) -APP_CPPFLAGS += -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 -mthumb +APP_CPPFLAGS += -mfloat-abi=softfp -mfpu=vfpv3-d16 -mthumb endif -#ifeq ($(APP_ABI),x86) -#APP_CPPFLAGS += -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32 -funroll-loops -#endif +ifeq ($(APP_ABI),x86) +APP_CPPFLAGS += -mssse3 -mfpmath=sse -funroll-loops +endif ifndef NDEBUG -APP_CPPFLAGS := -g -D_DEBUG -O0 -fno-omit-frame-pointer -fexceptions +APP_CPPFLAGS := -g -Og -fno-omit-frame-pointer endif -APP_CFLAGS := $(APP_CPPFLAGS) -Wno-parentheses-equality #-Werror=shorten-64-to-32 -APP_CXXFLAGS := $(APP_CPPFLAGS) -frtti -std=gnu++17 +APP_CFLAGS := $(APP_CPPFLAGS) -Wno-inconsistent-missing-override -Wno-parentheses-equality +APP_CXXFLAGS := $(APP_CPPFLAGS) -fexceptions -frtti -std=gnu++14 APP_LDFLAGS := -Wl,--no-warn-mismatch,--gc-sections,--icf=safe ifeq ($(APP_ABI),arm64-v8a) diff --git a/builtin/async/game.lua b/builtin/async/game.lua new file mode 100644 index 000000000..e3119d8cb --- /dev/null +++ b/builtin/async/game.lua @@ -0,0 +1,46 @@ +core.log("info", "Initializing asynchronous environment (game)") + +local function pack2(...) + return {n=select('#', ...), ...} +end + +-- Entrypoint to run async jobs, called by C++ +function core.job_processor(func, params) + local retval = pack2(func(unpack(params, 1, params.n))) + + return retval +end + +-- Import a bunch of individual files from builtin/game/ +local gamepath = core.get_builtin_path() .. "game" .. DIR_DELIM +local commonpath = core.get_builtin_path() .. "common" .. DIR_DELIM + +dofile(gamepath .. "constants.lua") +dofile(gamepath .. "item_s.lua") +dofile(gamepath .. "misc_s.lua") +dofile(gamepath .. "features.lua") +dofile(commonpath .. "voxelarea.lua") + +-- Transfer of globals +do + local all = assert(core.transferred_globals) + core.transferred_globals = nil + + -- reassemble other tables + all.registered_nodes = {} + all.registered_craftitems = {} + all.registered_tools = {} + for k, v in pairs(all.registered_items) do + if v.type == "node" then + all.registered_nodes[k] = v + elseif v.type == "craftitem" then + all.registered_craftitems[k] = v + elseif v.type == "tool" then + all.registered_tools[k] = v + end + end + + for k, v in pairs(all) do + core[k] = v + end +end diff --git a/builtin/async/init.lua b/builtin/async/mainmenu.lua similarity index 76% rename from builtin/async/init.lua rename to builtin/async/mainmenu.lua index 3803994d6..0e9c222d1 100644 --- a/builtin/async/init.lua +++ b/builtin/async/mainmenu.lua @@ -1,5 +1,4 @@ - -core.log("info", "Initializing Asynchronous environment") +core.log("info", "Initializing asynchronous environment") function core.job_processor(func, serialized_param) local param = core.deserialize(serialized_param) @@ -8,4 +7,3 @@ function core.job_processor(func, serialized_param) return retval or core.serialize(nil) end - diff --git a/builtin/client/register.lua b/builtin/client/register.lua index f17188b84..637a22556 100644 --- a/builtin/client/register.lua +++ b/builtin/client/register.lua @@ -1,4 +1,3 @@ - core.callback_origins = {} local getinfo = debug.getinfo diff --git a/builtin/common/after.lua b/builtin/common/after.lua index e20f292f0..bce262537 100644 --- a/builtin/common/after.lua +++ b/builtin/common/after.lua @@ -37,7 +37,14 @@ function core.after(after, func, ...) arg = {...}, mod_origin = core.get_last_run_mod(), } + jobs[#jobs + 1] = new_job time_next = math.min(time_next, expire) - return { cancel = function() new_job.func = function() end end } + + return { + cancel = function() + new_job.func = function() end + new_job.args = {} + end + } end diff --git a/builtin/common/chatcommands.lua b/builtin/common/chatcommands.lua index 62f9eacd0..817f1f526 100644 --- a/builtin/common/chatcommands.lua +++ b/builtin/common/chatcommands.lua @@ -6,6 +6,42 @@ local S = core.get_translator("__builtin") core.registered_chatcommands = {} +-- Interpret the parameters of a command, separating options and arguments. +-- Input: command, param +-- command: name of command +-- param: parameters of command +-- Returns: opts, args +-- opts is a string of option letters, or false on error +-- args is an array with the non-option arguments in order, or an error message +-- Example: for this command line: +-- /command a b -cd e f -g +-- the function would receive: +-- a b -cd e f -g +-- and it would return: +-- "cdg", {"a", "b", "e", "f"} +-- Negative numbers are taken as arguments. Long options (--option) are +-- currently rejected as reserved. +local function getopts(command, param) + local opts = "" + local args = {} + for match in param:gmatch("%S+") do + if match:byte(1) == 45 then -- 45 = '-' + local second = match:byte(2) + if second == 45 then + return false, S("Invalid parameters (see /help @1).", command) + elseif second and (second < 48 or second > 57) then -- 48 = '0', 57 = '9' + opts = opts .. match:sub(2) + else + -- numeric, add it to args + args[#args + 1] = match + end + else + args[#args + 1] = match + end + end + return opts, args +end + function core.register_chatcommand(cmd, def) def = def or {} def.params = def.params or "" @@ -78,22 +114,30 @@ if INIT == "client" then end end -local function do_help_cmd(name, param) - local function format_help_line(cmd, def) - local cmd_marker = "/" - if INIT == "client" then - cmd_marker = "." - end - local msg = core.colorize("#00ffff", cmd_marker .. cmd) - if def.params and def.params ~= "" then - msg = msg .. " " .. def.params - end - if def.description and def.description ~= "" then - msg = msg .. ": " .. def.description - end - return msg +local function format_help_line(cmd, def) + local cmd_marker = INIT == "client" and "." or "/" + local msg = core.colorize("#00ffff", cmd_marker .. cmd) + if def.params and def.params ~= "" then + msg = msg .. " " .. def.params end - if param == "" then + if def.description and def.description ~= "" then + msg = msg .. ": " .. def.description + end + return msg +end + +local function do_help_cmd(name, param) + local opts, args = getopts("help", param) + if not opts then + return false, args + end + 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) + use_gui = use_gui and not opts:find("t") + + if #args == 0 and not use_gui then local cmds = {} for cmd, def in pairs(core.registered_chatcommands) do if INIT == "client" or core.check_player_privs(name, def.privs) then @@ -116,7 +160,10 @@ local function do_help_cmd(name, param) .. "everything.") end return true, msg - elseif param == "all" then + elseif #args == 0 or (args[1] == "all" and use_gui) then + core.show_general_help_formspec(name) + return true + elseif args[1] == "all" then local cmds = {} for cmd, def in pairs(core.registered_chatcommands) do if INIT == "client" or core.check_player_privs(name, def.privs) then @@ -131,7 +178,11 @@ local function do_help_cmd(name, param) msg = core.gettext("Available commands:") end return true, msg.."\n"..table.concat(cmds, "\n") - elseif INIT == "game" and param == "privs" then + elseif INIT == "game" and args[1] == "privs" then + if use_gui then + core.show_privs_help_formspec(name) + return true + end local privs = {} for priv, def in pairs(core.registered_privileges) do privs[#privs + 1] = priv .. ": " .. def.description @@ -139,7 +190,7 @@ local function do_help_cmd(name, param) table.sort(privs) return true, S("Available privileges:").."\n"..table.concat(privs, "\n") else - local cmd = param + local cmd = args[1] local def = core.registered_chatcommands[cmd] if not def then local msg @@ -165,8 +216,8 @@ if INIT == "client" then }) else core.register_chatcommand("help", { - params = S("[all | privs | ]"), - description = S("Get help for commands or list privileges"), + params = S("[all | privs | ] [-t]"), + description = S("Get help for commands or list privileges (-t: output in chat)"), func = do_help_cmd, }) end diff --git a/builtin/common/information_formspecs.lua b/builtin/common/information_formspecs.lua index e814b4c43..3405263bf 100644 --- a/builtin/common/information_formspecs.lua +++ b/builtin/common/information_formspecs.lua @@ -125,30 +125,12 @@ core.register_on_player_receive_fields(function(player, formname, fields) end end) - -local help_command = core.registered_chatcommands["help"] -local old_help_func = help_command.func - -help_command.func = function(name, param) - local admin = core.settings:get("name") - - -- If the admin ran help, put the output in the chat buffer as well to - -- work with the server terminal - if param == "privs" then - core.show_formspec(name, "__builtin:help_privs", - build_privs_formspec(name)) - if name ~= admin then - return true - end - end - if param == "" or param == "all" then - core.show_formspec(name, "__builtin:help_cmds", - build_chatcommands_formspec(name)) - if name ~= admin then - return true - end - end - - return old_help_func(name, param) +function core.show_general_help_formspec(name) + core.show_formspec(name, "__builtin:help_cmds", + build_chatcommands_formspec(name)) end +function core.show_privs_help_formspec(name) + core.show_formspec(name, "__builtin:help_privs", + build_privs_formspec(name)) +end diff --git a/builtin/common/misc_helpers.lua b/builtin/common/misc_helpers.lua index e4bc056d9..542b2040d 100644 --- a/builtin/common/misc_helpers.lua +++ b/builtin/common/misc_helpers.lua @@ -543,7 +543,7 @@ if INIT == "mainmenu" then end end -if INIT == "client" or INIT == "mainmenu" then +if core.gettext then -- for client and mainmenu function fgettext_ne(text, ...) text = core.gettext(text) local arg = {n=select('#', ...), ...} diff --git a/builtin/common/strict.lua b/builtin/common/strict.lua index ccde9676b..f46234dc6 100644 --- a/builtin/common/strict.lua +++ b/builtin/common/strict.lua @@ -1,8 +1,3 @@ - --- Always warn when creating a global variable, even outside of a function. --- This ignores mod namespaces (variables with the same name as the current mod). -local WARN_INIT = false - local getinfo = debug.getinfo function core.global_exists(name) @@ -33,11 +28,6 @@ function meta:__newindex(name, value) end declared[name] = true end - -- Ignore mod namespaces - if WARN_INIT and name ~= core.get_current_modname() then - core.log("warning", ("Global variable %q created at %s.") - :format(name, desc)) - end rawset(self, name, value) end @@ -54,4 +44,3 @@ function meta:__index(name) end setmetatable(_G, meta) - diff --git a/builtin/common/tests/misc_helpers_spec.lua b/builtin/common/tests/misc_helpers_spec.lua index b16987f0b..b11236860 100644 --- a/builtin/common/tests/misc_helpers_spec.lua +++ b/builtin/common/tests/misc_helpers_spec.lua @@ -1,4 +1,5 @@ _G.core = {} +_G.vector = {metatable = {}} dofile("builtin/common/vector.lua") dofile("builtin/common/misc_helpers.lua") diff --git a/builtin/common/tests/serialize_spec.lua b/builtin/common/tests/serialize_spec.lua index e46b7dcc5..69b2b567c 100644 --- a/builtin/common/tests/serialize_spec.lua +++ b/builtin/common/tests/serialize_spec.lua @@ -1,4 +1,5 @@ _G.core = {} +_G.vector = {metatable = {}} _G.setfenv = require 'busted.compatibility'.setfenv diff --git a/builtin/common/tests/vector_spec.lua b/builtin/common/tests/vector_spec.lua index 2a50e2889..6a0b81a89 100644 --- a/builtin/common/tests/vector_spec.lua +++ b/builtin/common/tests/vector_spec.lua @@ -1,4 +1,4 @@ -_G.vector = {} +_G.vector = {metatable = {}} dofile("builtin/common/vector.lua") describe("vector", function() @@ -128,6 +128,14 @@ describe("vector", function() assert.equal(vector.new(4.1, 5.9, 5.5), a:apply(f)) end) + it("combine()", function() + local a = vector.new(1, 2, 3) + local b = vector.new(3, 2, 1) + assert.equal(vector.add(a, b), vector.combine(a, b, function(x, y) return x + y end)) + assert.equal(vector.new(3, 2, 3), vector.combine(a, b, math.max)) + assert.equal(vector.new(1, 2, 1), vector.combine(a, b, math.min)) + end) + it("equals()", function() local function assertE(a, b) assert.is_true(vector.equals(a, b)) @@ -300,6 +308,7 @@ describe("vector", function() it("from_string()", function() local v = vector.new(1, 2, 3.14) + assert.is_true(vector.check(vector.from_string("(1, 2, 3.14)"))) assert.same({v, 13}, {vector.from_string("(1, 2, 3.14)")}) assert.same({v, 12}, {vector.from_string("(1,2 ,3.14)")}) assert.same({v, 12}, {vector.from_string("(1,2,3.14,)")}) diff --git a/builtin/common/vector.lua b/builtin/common/vector.lua index 02fc1bdee..a08472e32 100644 --- a/builtin/common/vector.lua +++ b/builtin/common/vector.lua @@ -6,10 +6,8 @@ Note: The vector.*-functions must be able to accept old vectors that had no meta -- localize functions local setmetatable = setmetatable -vector = {} - -local metatable = {} -vector.metatable = metatable +-- vector.metatable is set by C++. +local metatable = vector.metatable local xyz = {"x", "y", "z"} @@ -61,7 +59,7 @@ function vector.from_string(s, init) if not (x and y and z) then return nil end - return {x = x, y = y, z = z}, np + return fast_new(x, y, z), np end function vector.to_string(v) @@ -112,6 +110,14 @@ function vector.apply(v, func) ) end +function vector.combine(a, b, func) + return fast_new( + func(a.x, b.x), + func(a.y, b.y), + func(a.z, b.z) + ) +end + function vector.distance(a, b) local x = a.x - b.x local y = a.y - b.y diff --git a/builtin/fstk/ui.lua b/builtin/fstk/ui.lua index 976659ed3..13f9cbec2 100644 --- a/builtin/fstk/ui.lua +++ b/builtin/fstk/ui.lua @@ -63,7 +63,7 @@ function ui.update() -- handle errors if gamedata ~= nil and gamedata.reconnect_requested then local error_message = core.formspec_escape( - gamedata.errormessage or "") + gamedata.errormessage or fgettext("")) formspec = { "size[14,8]", "real_coordinates[true]", diff --git a/builtin/game/async.lua b/builtin/game/async.lua new file mode 100644 index 000000000..469f179d7 --- /dev/null +++ b/builtin/game/async.lua @@ -0,0 +1,22 @@ + +core.async_jobs = {} + +function core.async_event_handler(jobid, retval) + local callback = core.async_jobs[jobid] + assert(type(callback) == "function") + callback(unpack(retval, 1, retval.n)) + core.async_jobs[jobid] = nil +end + +function core.handle_async(func, callback, ...) + assert(type(func) == "function" and type(callback) == "function", + "Invalid minetest.handle_async invocation") + local args = {n = select("#", ...), ...} + local mod_origin = core.get_last_run_mod() + + local jobid = core.do_async_callback(func, args, mod_origin) + core.async_jobs[jobid] = callback + + return true +end + diff --git a/builtin/game/chat.lua b/builtin/game/chat.lua index 99296f782..c4fb6314e 100644 --- a/builtin/game/chat.lua +++ b/builtin/game/chat.lua @@ -310,12 +310,7 @@ local function handle_revoke_command(caller, revokename, revokeprivstr) and revokename == core.settings:get("name") and revokename ~= "" if revokeprivstr == "all" then - revokeprivs = privs - privs = {} - else - for priv, _ in pairs(revokeprivs) do - privs[priv] = nil - end + revokeprivs = table.copy(privs) end local privs_unknown = "" @@ -332,7 +327,10 @@ local function handle_revoke_command(caller, revokename, revokeprivstr) end local def = core.registered_privileges[priv] if not def then - privs_unknown = privs_unknown .. S("Unknown privilege: @1", priv) .. "\n" + -- Old/removed privileges might still be granted to certain players + if not privs[priv] then + privs_unknown = privs_unknown .. S("Unknown privilege: @1", priv) .. "\n" + end elseif is_singleplayer and def.give_to_singleplayer then irrevokable[priv] = true elseif is_admin and def.give_to_admin then @@ -359,19 +357,22 @@ local function handle_revoke_command(caller, revokename, revokeprivstr) end local revokecount = 0 + for priv, _ in pairs(revokeprivs) do + privs[priv] = nil + revokecount = revokecount + 1 + end + + if revokecount == 0 then + return false, S("No privileges were revoked.") + end core.set_player_privs(revokename, privs) for priv, _ in pairs(revokeprivs) do -- call the on_revoke callbacks core.run_priv_callbacks(revokename, priv, caller, "revoke") - revokecount = revokecount + 1 end local new_privs = core.get_player_privs(revokename) - if revokecount == 0 then - return false, S("No privileges were revoked.") - end - core.log("action", caller..' revoked (' ..core.privs_to_string(revokeprivs, ', ') ..') privileges from '..revokename) @@ -524,7 +525,7 @@ end -- Teleports player to

if possible local function teleport_to_pos(name, p) - local lm = 31000 + local lm = 31007 -- equals MAX_MAP_GENERATION_LIMIT in C++ if p.x < -lm or p.x > lm or p.y < -lm or p.y > lm or p.z < -lm or p.z > lm then return false, S("Cannot teleport out of map bounds!") @@ -621,6 +622,10 @@ core.register_chatcommand("set", { setname, setvalue = string.match(param, "([^ ]+) (.+)") if setname and setvalue then + if setname:sub(1, 7) == "secure." then + return false, S("Failed. Cannot modify secure settings. " + .. "Edit the settings file manually.") + end if not core.settings:get(setname) then return false, S("Failed. Use '/set -n ' " .. "to create a new setting.") @@ -1034,12 +1039,11 @@ core.register_chatcommand("time", { end local hour, minute = param:match("^(%d+):(%d+)$") if not hour then - local new_time = tonumber(param) - if not new_time then - return false, S("Invalid time.") + local new_time = tonumber(param) or -1 + if new_time ~= new_time or new_time < 0 or new_time > 24000 then + return false, S("Invalid time (must be between 0 and 24000).") end - -- Backward compatibility. - core.set_timeofday((new_time % 24000) / 24000) + core.set_timeofday(new_time / 24000) core.log("action", name .. " sets time to " .. new_time) return true, S("Time of day changed.") end @@ -1283,7 +1287,7 @@ local function handle_kill_command(killer, victim) return false, S("@1 is already dead.", victim) end end - if not killer == victim then + if killer ~= victim then core.log("action", string.format("%s killed %s", killer, victim)) end -- Kill victim diff --git a/builtin/game/features.lua b/builtin/game/features.lua index 583ef5092..0d55bb01f 100644 --- a/builtin/game/features.lua +++ b/builtin/game/features.lua @@ -22,6 +22,7 @@ core.features = { degrotate_240_steps = true, abm_min_max_y = true, dynamic_add_media_table = true, + get_sky_as_table = true, } function core.has_feature(arg) diff --git a/builtin/game/init.lua b/builtin/game/init.lua index 9a9966d7e..b9ab97b58 100644 --- a/builtin/game/init.lua +++ b/builtin/game/init.lua @@ -8,6 +8,7 @@ local gamepath = scriptpath .. "game".. DIR_DELIM local builtin_shared = {} dofile(gamepath .. "constants.lua") +dofile(gamepath .. "item_s.lua") assert(loadfile(gamepath .. "item.lua"))(builtin_shared) dofile(gamepath .. "register.lua") @@ -19,6 +20,7 @@ dofile(commonpath .. "after.lua") dofile(commonpath .. "voxelarea.lua") dofile(gamepath .. "item_entity.lua") dofile(gamepath .. "deprecated.lua") +dofile(gamepath .. "misc_s.lua") dofile(gamepath .. "misc.lua") dofile(gamepath .. "privileges.lua") dofile(gamepath .. "auth.lua") @@ -32,5 +34,6 @@ dofile(gamepath .. "features.lua") dofile(gamepath .. "forceloading.lua") dofile(gamepath .. "statbars.lua") dofile(gamepath .. "knockback.lua") +dofile(gamepath .. "async.lua") profiler = nil diff --git a/builtin/game/item.lua b/builtin/game/item.lua index 92818b177..5543e9a3f 100644 --- a/builtin/game/item.lua +++ b/builtin/game/item.lua @@ -15,144 +15,19 @@ end -- Item definition helpers -- -function core.dir_to_facedir(dir, is6d) - --account for y if requested - if is6d and math.abs(dir.y) > math.abs(dir.x) and math.abs(dir.y) > math.abs(dir.z) then - - --from above - if dir.y < 0 then - if math.abs(dir.x) > math.abs(dir.z) then - if dir.x < 0 then - return 19 - else - return 13 - end - else - if dir.z < 0 then - return 10 - else - return 4 - end - end - - --from below - else - if math.abs(dir.x) > math.abs(dir.z) then - if dir.x < 0 then - return 15 - else - return 17 - end - else - if dir.z < 0 then - return 6 - else - return 8 - end - end - end - - --otherwise, place horizontally - elseif math.abs(dir.x) > math.abs(dir.z) then - if dir.x < 0 then - return 3 - else - return 1 - end - else - if dir.z < 0 then - return 2 - else - return 0 +function core.get_pointed_thing_position(pointed_thing, above) + if pointed_thing.type == "node" then + if above then + -- The position where a node would be placed + return pointed_thing.above end + -- The position where a node would be dug + return pointed_thing.under + elseif pointed_thing.type == "object" then + return pointed_thing.ref and pointed_thing.ref:get_pos() end end --- Table of possible dirs -local facedir_to_dir = { - vector.new( 0, 0, 1), - vector.new( 1, 0, 0), - vector.new( 0, 0, -1), - vector.new(-1, 0, 0), - vector.new( 0, -1, 0), - vector.new( 0, 1, 0), -} --- Mapping from facedir value to index in facedir_to_dir. -local facedir_to_dir_map = { - [0]=1, 2, 3, 4, - 5, 2, 6, 4, - 6, 2, 5, 4, - 1, 5, 3, 6, - 1, 6, 3, 5, - 1, 4, 3, 2, -} -function core.facedir_to_dir(facedir) - return facedir_to_dir[facedir_to_dir_map[facedir % 32]] -end - -function core.dir_to_wallmounted(dir) - if math.abs(dir.y) > math.max(math.abs(dir.x), math.abs(dir.z)) then - if dir.y < 0 then - return 1 - else - return 0 - end - elseif math.abs(dir.x) > math.abs(dir.z) then - if dir.x < 0 then - return 3 - else - return 2 - end - else - if dir.z < 0 then - return 5 - else - return 4 - end - end -end - --- table of dirs in wallmounted order -local wallmounted_to_dir = { - [0] = vector.new( 0, 1, 0), - vector.new( 0, -1, 0), - vector.new( 1, 0, 0), - vector.new(-1, 0, 0), - vector.new( 0, 0, 1), - vector.new( 0, 0, -1), -} -function core.wallmounted_to_dir(wallmounted) - return wallmounted_to_dir[wallmounted % 8] -end - -function core.dir_to_yaw(dir) - return -math.atan2(dir.x, dir.z) -end - -function core.yaw_to_dir(yaw) - return vector.new(-math.sin(yaw), 0, math.cos(yaw)) -end - -function core.is_colored_paramtype(ptype) - return (ptype == "color") or (ptype == "colorfacedir") or - (ptype == "colorwallmounted") or (ptype == "colordegrotate") -end - -function core.strip_param2_color(param2, paramtype2) - if not core.is_colored_paramtype(paramtype2) then - return nil - end - if paramtype2 == "colorfacedir" then - param2 = math.floor(param2 / 32) * 32 - elseif paramtype2 == "colorwallmounted" then - param2 = math.floor(param2 / 8) * 8 - elseif paramtype2 == "colordegrotate" then - param2 = math.floor(param2 / 32) * 32 - end - -- paramtype2 == "color" requires no modification. - return param2 -end - local function has_all_groups(tbl, required_groups) if type(required_groups) == "string" then return (tbl[required_groups] or 0) ~= 0 @@ -477,34 +352,41 @@ function core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed return result end end + -- read definition before potentially emptying the stack local def = itemstack:get_definition() - if itemstack:take_item() ~= nil then - user:set_hp(user:get_hp() + hp_change) + if itemstack:take_item():is_empty() then + return itemstack + end - if def and def.sound and def.sound.eat then - core.sound_play(def.sound.eat, { - pos = user:get_pos(), - max_hear_distance = 16 - }, true) - end + if def and def.sound and def.sound.eat then + core.sound_play(def.sound.eat, { + pos = user:get_pos(), + max_hear_distance = 16 + }, true) + end - if replace_with_item then - if itemstack:is_empty() then - itemstack:add_item(replace_with_item) + -- Changing hp might kill the player causing mods to do who-knows-what to the + -- inventory, so do this before set_hp(). + if replace_with_item then + if itemstack:is_empty() then + itemstack:add_item(replace_with_item) + else + local inv = user:get_inventory() + -- Check if inv is null, since non-players don't have one + if inv and inv:room_for_item("main", {name=replace_with_item}) then + inv:add_item("main", replace_with_item) else - local inv = user:get_inventory() - -- Check if inv is null, since non-players don't have one - if inv and inv:room_for_item("main", {name=replace_with_item}) then - inv:add_item("main", replace_with_item) - else - local pos = user:get_pos() - pos.y = math.floor(pos.y + 0.5) - core.add_item(pos, replace_with_item) - end + local pos = user:get_pos() + pos.y = math.floor(pos.y + 0.5) + core.add_item(pos, replace_with_item) end end end - return itemstack + user:set_wielded_item(itemstack) + + user:set_hp(user:get_hp() + hp_change) + + return nil -- don't overwrite wield item a second time end function core.item_eat(hp_change, replace_with_item) @@ -585,7 +467,7 @@ function core.node_dig(pos, node, digger) if wielded then local wdef = wielded:get_definition() local tp = wielded:get_tool_capabilities() - local dp = core.get_dig_params(def and def.groups, tp) + local dp = core.get_dig_params(def and def.groups, tp, wielded:get_wear()) if wdef and wdef.after_use then wielded = wdef.after_use(wielded, digger, node, dp) or wielded else @@ -647,9 +529,7 @@ function core.node_dig(pos, node, digger) -- Run script hook for _, callback in ipairs(core.registered_on_dignodes) do local origin = core.callback_origins[callback] - if origin then - core.set_last_run_mod(origin.mod) - end + core.set_last_run_mod(origin.mod) -- Copy pos and node because callback can modify them local pos_copy = vector.new(pos) diff --git a/builtin/game/item_entity.lua b/builtin/game/item_entity.lua index 9b1b23bfd..53f98a7c7 100644 --- a/builtin/game/item_entity.lua +++ b/builtin/game/item_entity.lua @@ -58,17 +58,21 @@ core.register_entity(":__builtin:item", { local glow = def and def.light_source and math.floor(def.light_source / 2 + 0.5) + local size_bias = 1e-3 * math.random() -- small random bias to counter Z-fighting + local c = {-size, -size, -size, size, size, size} self.object:set_properties({ is_visible = true, visual = "wielditem", textures = {itemname}, - visual_size = {x = size, y = size}, - collisionbox = {-size, -size, -size, size, size, size}, + visual_size = {x = size + size_bias, y = size + size_bias}, + collisionbox = c, automatic_rotate = math.pi * 0.5 * 0.2 / size, wield_item = self.itemstring, glow = glow, }) + -- cache for usage in on_step + self._collisionbox = c end, get_staticdata = function(self) @@ -93,6 +97,7 @@ core.register_entity(":__builtin:item", { self.object:set_armor_groups({immortal = 1}) self.object:set_velocity({x = 0, y = 2, z = 0}) self.object:set_acceleration({x = 0, y = -gravity, z = 0}) + self._collisionbox = self.initial_properties.collisionbox self:set_item() end, @@ -163,7 +168,7 @@ core.register_entity(":__builtin:item", { local pos = self.object:get_pos() local node = core.get_node_or_nil({ x = pos.x, - y = pos.y + self.object:get_properties().collisionbox[2] - 0.05, + y = pos.y + self._collisionbox[2] - 0.05, z = pos.z }) -- Delete in 'ignore' nodes @@ -176,7 +181,7 @@ core.register_entity(":__builtin:item", { if self.force_out then -- This code runs after the entity got a push from the is_stuck code. -- It makes sure the entity is entirely outside the solid node - local c = self.object:get_properties().collisionbox + local c = self._collisionbox local s = self.force_out_start local f = self.force_out local ok = (f.x > 0 and pos.x + c[1] > s.x + 0.5) or diff --git a/builtin/game/item_s.lua b/builtin/game/item_s.lua new file mode 100644 index 000000000..a51cd0a1c --- /dev/null +++ b/builtin/game/item_s.lua @@ -0,0 +1,156 @@ +-- Minetest: builtin/item_s.lua +-- The distinction of what goes here is a bit tricky, basically it's everything +-- that does not (directly or indirectly) need access to ServerEnvironment, +-- Server or writable access to IGameDef on the engine side. +-- (The '_s' stands for standalone.) + +-- +-- Item definition helpers +-- + +function core.inventorycube(img1, img2, img3) + img2 = img2 or img1 + img3 = img3 or img1 + return "[inventorycube" + .. "{" .. img1:gsub("%^", "&") + .. "{" .. img2:gsub("%^", "&") + .. "{" .. img3:gsub("%^", "&") +end + +function core.dir_to_facedir(dir, is6d) + --account for y if requested + if is6d and math.abs(dir.y) > math.abs(dir.x) and math.abs(dir.y) > math.abs(dir.z) then + + --from above + if dir.y < 0 then + if math.abs(dir.x) > math.abs(dir.z) then + if dir.x < 0 then + return 19 + else + return 13 + end + else + if dir.z < 0 then + return 10 + else + return 4 + end + end + + --from below + else + if math.abs(dir.x) > math.abs(dir.z) then + if dir.x < 0 then + return 15 + else + return 17 + end + else + if dir.z < 0 then + return 6 + else + return 8 + end + end + end + + --otherwise, place horizontally + elseif math.abs(dir.x) > math.abs(dir.z) then + if dir.x < 0 then + return 3 + else + return 1 + end + else + if dir.z < 0 then + return 2 + else + return 0 + end + end +end + +-- Table of possible dirs +local facedir_to_dir = { + vector.new( 0, 0, 1), + vector.new( 1, 0, 0), + vector.new( 0, 0, -1), + vector.new(-1, 0, 0), + vector.new( 0, -1, 0), + vector.new( 0, 1, 0), +} +-- Mapping from facedir value to index in facedir_to_dir. +local facedir_to_dir_map = { + [0]=1, 2, 3, 4, + 5, 2, 6, 4, + 6, 2, 5, 4, + 1, 5, 3, 6, + 1, 6, 3, 5, + 1, 4, 3, 2, +} +function core.facedir_to_dir(facedir) + return facedir_to_dir[facedir_to_dir_map[facedir % 32]] +end + +function core.dir_to_wallmounted(dir) + if math.abs(dir.y) > math.max(math.abs(dir.x), math.abs(dir.z)) then + if dir.y < 0 then + return 1 + else + return 0 + end + elseif math.abs(dir.x) > math.abs(dir.z) then + if dir.x < 0 then + return 3 + else + return 2 + end + else + if dir.z < 0 then + return 5 + else + return 4 + end + end +end + +-- table of dirs in wallmounted order +local wallmounted_to_dir = { + [0] = vector.new( 0, 1, 0), + vector.new( 0, -1, 0), + vector.new( 1, 0, 0), + vector.new(-1, 0, 0), + vector.new( 0, 0, 1), + vector.new( 0, 0, -1), +} +function core.wallmounted_to_dir(wallmounted) + return wallmounted_to_dir[wallmounted % 8] +end + +function core.dir_to_yaw(dir) + return -math.atan2(dir.x, dir.z) +end + +function core.yaw_to_dir(yaw) + return vector.new(-math.sin(yaw), 0, math.cos(yaw)) +end + +function core.is_colored_paramtype(ptype) + return (ptype == "color") or (ptype == "colorfacedir") or + (ptype == "colorwallmounted") or (ptype == "colordegrotate") +end + +function core.strip_param2_color(param2, paramtype2) + if not core.is_colored_paramtype(paramtype2) then + return nil + end + if paramtype2 == "colorfacedir" then + param2 = math.floor(param2 / 32) * 32 + elseif paramtype2 == "colorwallmounted" then + param2 = math.floor(param2 / 8) * 8 + elseif paramtype2 == "colordegrotate" then + param2 = math.floor(param2 / 32) * 32 + end + -- paramtype2 == "color" requires no modification. + return param2 +end diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua index 63d64817c..997b1894a 100644 --- a/builtin/game/misc.lua +++ b/builtin/game/misc.lua @@ -6,6 +6,16 @@ local S = core.get_translator("__builtin") -- Misc. API functions -- +-- @spec core.kick_player(String, String) :: Boolean +function core.kick_player(player_name, reason) + if type(reason) == "string" then + reason = "Kicked: " .. reason + else + reason = "Kicked." + end + return core.disconnect_player(player_name, reason) +end + function core.check_player_privs(name, ...) if core.is_player(name) then name = name:get_player_name() @@ -111,53 +121,6 @@ function core.get_player_radius_area(player_name, radius) end -function core.hash_node_position(pos) - return (pos.z + 32768) * 65536 * 65536 - + (pos.y + 32768) * 65536 - + pos.x + 32768 -end - - -function core.get_position_from_hash(hash) - local x = (hash % 65536) - 32768 - hash = math.floor(hash / 65536) - local y = (hash % 65536) - 32768 - hash = math.floor(hash / 65536) - local z = (hash % 65536) - 32768 - return vector.new(x, y, z) -end - - -function core.get_item_group(name, group) - if not core.registered_items[name] or not - core.registered_items[name].groups[group] then - return 0 - end - return core.registered_items[name].groups[group] -end - - -function core.get_node_group(name, group) - core.log("deprecated", "Deprecated usage of get_node_group, use get_item_group instead") - return core.get_item_group(name, group) -end - - -function core.setting_get_pos(name) - local value = core.settings:get(name) - if not value then - return nil - end - return core.string_to_pos(value) -end - - --- See l_env.cpp for the other functions -function core.get_artificial_light(param1) - return math.floor(param1 / 16) -end - - -- To be overriden by protection mods function core.is_protected(pos, name) @@ -240,7 +203,7 @@ end -- HTTP callback interface -function core.http_add_fetch(httpenv) +core.set_http_api_lua(function(httpenv) httpenv.fetch = function(req, callback) local handle = httpenv.fetch_async(req) @@ -256,7 +219,8 @@ function core.http_add_fetch(httpenv) end return httpenv -end +end) +core.set_http_api_lua = nil function core.close_formspec(player_name, formname) @@ -273,40 +237,30 @@ end core.dynamic_media_callbacks = {} --- PNG encoder safety wrapper +-- Transfer of certain globals into async environment +-- see builtin/async/game.lua for the other side -local o_encode_png = core.encode_png -function core.encode_png(width, height, data, compression) - if type(width) ~= "number" then - error("Incorrect type for 'width', expected number, got " .. type(width)) +local function copy_filtering(t, seen) + if type(t) == "userdata" or type(t) == "function" then + return true -- don't use nil so presence can still be detected + elseif type(t) ~= "table" then + return t end - if type(height) ~= "number" then - error("Incorrect type for 'height', expected number, got " .. type(height)) + local n = {} + seen = seen or {} + seen[t] = n + for k, v in pairs(t) do + local k_ = seen[k] or copy_filtering(k, seen) + local v_ = seen[v] or copy_filtering(v, seen) + n[k_] = v_ end - - local expected_byte_count = width * height * 4 - - if type(data) ~= "table" and type(data) ~= "string" then - error("Incorrect type for 'height', expected table or string, got " .. type(height)) - end - - local data_length = type(data) == "table" and #data * 4 or string.len(data) - - if data_length ~= expected_byte_count then - error(string.format( - "Incorrect length of 'data', width and height imply %d bytes but %d were provided", - expected_byte_count, - data_length - )) - end - - if type(data) == "table" then - local dataBuf = {} - for i = 1, #data do - dataBuf[i] = core.colorspec_to_bytes(data[i]) - end - data = table.concat(dataBuf) - end - - return o_encode_png(width, height, data, compression or 6) + return n +end + +function core.get_globals_to_transfer() + local all = { + registered_items = copy_filtering(core.registered_items), + registered_aliases = core.registered_aliases, + } + return all end diff --git a/builtin/game/misc_s.lua b/builtin/game/misc_s.lua new file mode 100644 index 000000000..67a0ec684 --- /dev/null +++ b/builtin/game/misc_s.lua @@ -0,0 +1,93 @@ +-- Minetest: builtin/misc_s.lua +-- The distinction of what goes here is a bit tricky, basically it's everything +-- that does not (directly or indirectly) need access to ServerEnvironment, +-- Server or writable access to IGameDef on the engine side. +-- (The '_s' stands for standalone.) + +-- +-- Misc. API functions +-- + +function core.hash_node_position(pos) + return (pos.z + 32768) * 65536 * 65536 + + (pos.y + 32768) * 65536 + + pos.x + 32768 +end + + +function core.get_position_from_hash(hash) + local x = (hash % 65536) - 32768 + hash = math.floor(hash / 65536) + local y = (hash % 65536) - 32768 + hash = math.floor(hash / 65536) + local z = (hash % 65536) - 32768 + return vector.new(x, y, z) +end + + +function core.get_item_group(name, group) + if not core.registered_items[name] or not + core.registered_items[name].groups[group] then + return 0 + end + return core.registered_items[name].groups[group] +end + + +function core.get_node_group(name, group) + core.log("deprecated", "Deprecated usage of get_node_group, use get_item_group instead") + return core.get_item_group(name, group) +end + + +function core.setting_get_pos(name) + local value = core.settings:get(name) + if not value then + return nil + end + return core.string_to_pos(value) +end + + +-- See l_env.cpp for the other functions +function core.get_artificial_light(param1) + return math.floor(param1 / 16) +end + +-- PNG encoder safety wrapper + +local o_encode_png = core.encode_png +function core.encode_png(width, height, data, compression) + if type(width) ~= "number" then + error("Incorrect type for 'width', expected number, got " .. type(width)) + end + if type(height) ~= "number" then + error("Incorrect type for 'height', expected number, got " .. type(height)) + end + + local expected_byte_count = width * height * 4 + + if type(data) ~= "table" and type(data) ~= "string" then + error("Incorrect type for 'data', expected table or string, got " .. type(data)) + end + + local data_length = type(data) == "table" and #data * 4 or string.len(data) + + if data_length ~= expected_byte_count then + error(string.format( + "Incorrect length of 'data', width and height imply %d bytes but %d were provided", + expected_byte_count, + data_length + )) + end + + if type(data) == "table" then + local dataBuf = {} + for i = 1, #data do + dataBuf[i] = core.colorspec_to_bytes(data[i]) + end + data = table.concat(dataBuf) + end + + return o_encode_png(width, height, data, compression or 6) +end diff --git a/builtin/game/privileges.lua b/builtin/game/privileges.lua index 97681655e..2ff4c093c 100644 --- a/builtin/game/privileges.lua +++ b/builtin/game/privileges.lua @@ -97,10 +97,6 @@ core.register_privilege("rollback", { description = S("Can use the rollback functionality"), give_to_singleplayer = false, }) -core.register_privilege("basic_debug", { - description = S("Can view more debug info that might give a gameplay advantage"), - give_to_singleplayer = false, -}) core.register_privilege("debug", { description = S("Can enable wireframe"), give_to_singleplayer = false, diff --git a/builtin/game/register.lua b/builtin/game/register.lua index 56e40c75c..0be107c36 100644 --- a/builtin/game/register.lua +++ b/builtin/game/register.lua @@ -403,8 +403,14 @@ function core.override_item(name, redefinition) register_item_raw(item) end - -core.callback_origins = {} +do + local default = {mod = "??", name = "??"} + core.callback_origins = setmetatable({}, { + __index = function() + return default + end + }) +end function core.run_callbacks(callbacks, mode, ...) assert(type(callbacks) == "table") @@ -419,9 +425,7 @@ function core.run_callbacks(callbacks, mode, ...) local ret = nil for i = 1, cb_len do local origin = core.callback_origins[callbacks[i]] - if origin then - core.set_last_run_mod(origin.mod) - end + core.set_last_run_mod(origin.mod) local cb_ret = callbacks[i](...) if mode == 0 and i == 1 then diff --git a/builtin/game/statbars.lua b/builtin/game/statbars.lua index db5087a16..cb7ff7b76 100644 --- a/builtin/game/statbars.lua +++ b/builtin/game/statbars.lua @@ -1,39 +1,39 @@ -- cache setting local enable_damage = core.settings:get_bool("enable_damage") -local health_bar_definition = { - hud_elem_type = "statbar", - position = {x = 0.5, y = 1}, - text = "heart.png", - text2 = "heart_gone.png", - number = core.PLAYER_MAX_HP_DEFAULT, - item = core.PLAYER_MAX_HP_DEFAULT, - direction = 0, - size = {x = 24, y = 24}, - offset = {x = (-10 * 24) - 25, y = -(48 + 24 + 16)}, -} - -local breath_bar_definition = { - hud_elem_type = "statbar", - position = {x = 0.5, y = 1}, - text = "bubble.png", - text2 = "bubble_gone.png", - number = core.PLAYER_MAX_BREATH_DEFAULT, - item = core.PLAYER_MAX_BREATH_DEFAULT * 2, - direction = 0, - size = {x = 24, y = 24}, - offset = {x = 25, y= -(48 + 24 + 16)}, +local bar_definitions = { + hp = { + hud_elem_type = "statbar", + position = {x = 0.5, y = 1}, + text = "heart.png", + text2 = "heart_gone.png", + number = core.PLAYER_MAX_HP_DEFAULT, + item = core.PLAYER_MAX_HP_DEFAULT, + direction = 0, + size = {x = 24, y = 24}, + offset = {x = (-10 * 24) - 25, y = -(48 + 24 + 16)}, + }, + breath = { + hud_elem_type = "statbar", + position = {x = 0.5, y = 1}, + text = "bubble.png", + text2 = "bubble_gone.png", + number = core.PLAYER_MAX_BREATH_DEFAULT * 2, + item = core.PLAYER_MAX_BREATH_DEFAULT * 2, + direction = 0, + size = {x = 24, y = 24}, + offset = {x = 25, y= -(48 + 24 + 16)}, + }, } local hud_ids = {} -local function scaleToDefault(player, field) - -- Scale "hp" or "breath" to the default dimensions +local function scaleToHudMax(player, field) + -- Scale "hp" or "breath" to the hud maximum dimensions local current = player["get_" .. field](player) - local nominal = core["PLAYER_MAX_" .. field:upper() .. "_DEFAULT"] - local max_display = math.max(nominal, - math.max(player:get_properties()[field .. "_max"], current)) - return current / max_display * nominal + local nominal = bar_definitions[field].item + local max_display = math.max(player:get_properties()[field .. "_max"], current) + return math.ceil(current / max_display * nominal) end local function update_builtin_statbars(player) @@ -55,9 +55,9 @@ local function update_builtin_statbars(player) local immortal = player:get_armor_groups().immortal == 1 if flags.healthbar and enable_damage and not immortal then - local number = scaleToDefault(player, "hp") + local number = scaleToHudMax(player, "hp") if hud.id_healthbar == nil then - local hud_def = table.copy(health_bar_definition) + local hud_def = table.copy(bar_definitions.hp) hud_def.number = number hud.id_healthbar = player:hud_add(hud_def) else @@ -73,9 +73,9 @@ local function update_builtin_statbars(player) local breath = player:get_breath() local breath_max = player:get_properties().breath_max if show_breathbar and breath <= breath_max then - local number = 2 * scaleToDefault(player, "breath") + local number = scaleToHudMax(player, "breath") if not hud.id_breathbar and breath < breath_max then - local hud_def = table.copy(breath_bar_definition) + local hud_def = table.copy(bar_definitions.breath) hud_def.number = number hud.id_breathbar = player:hud_add(hud_def) elseif hud.id_breathbar then @@ -145,7 +145,7 @@ function core.hud_replace_builtin(hud_name, definition) end if hud_name == "health" then - health_bar_definition = definition + bar_definitions.hp = definition for name, ids in pairs(hud_ids) do local player = core.get_player_by_name(name) @@ -159,7 +159,7 @@ function core.hud_replace_builtin(hud_name, definition) end if hud_name == "breath" then - breath_bar_definition = definition + bar_definitions.breath = definition for name, ids in pairs(hud_ids) do local player = core.get_player_by_name(name) diff --git a/builtin/init.lua b/builtin/init.lua index 8bb69a33d..fd176e942 100644 --- a/builtin/init.lua +++ b/builtin/init.lua @@ -55,8 +55,10 @@ elseif INIT == "mainmenu" then if not custom_loaded then dofile(core.get_mainmenu_path() .. DIR_DELIM .. "init.lua") end -elseif INIT == "async" then - dofile(asyncpath .. "init.lua") +elseif INIT == "async" then + dofile(asyncpath .. "mainmenu.lua") +elseif INIT == "async_game" then + dofile(asyncpath .. "game.lua") elseif INIT == "client" then dofile(clientpath .. "init.lua") else diff --git a/builtin/locale/__builtin.de.tr b/builtin/locale/__builtin.de.tr index aa40ffc8d..1b29f81e7 100644 --- a/builtin/locale/__builtin.de.tr +++ b/builtin/locale/__builtin.de.tr @@ -139,7 +139,7 @@ This command was disabled by a mod or game.=Dieser Befehl wurde von einer Mod od Show or set time of day=Tageszeit anzeigen oder setzen Current time is @1:@2.=Es ist jetzt @1:@2 Uhr. You don't have permission to run this command (missing privilege: @1).=Sie haben nicht die Erlaubnis, diesen Befehl auszuführen (fehlendes Privileg: @1). -Invalid time.=Ungültige Zeit. +Invalid time (must be between 0 and 24000).=Ungültige Zeit (muss zwischen 0 und 24000 liegen). Time of day changed.=Tageszeit geändert. Invalid hour (must be between 0 and 23 inclusive).=Ungültige Stunde (muss zwischen 0 und 23 inklusive liegen). Invalid minute (must be between 0 and 59 inclusive).=Ungültige Minute (muss zwischen 0 und 59 inklusive liegen). @@ -187,12 +187,14 @@ You are already dead.=Sie sind schon tot. @1 is already dead.=@1 ist bereits tot. @1 has been killed.=@1 wurde getötet. Kill player or yourself=Einen Spieler oder Sie selbst töten +Invalid parameters (see /help @1).=Ungültige Parameter (siehe „/help @1“). +Too many arguments, try using just /help =Zu viele Argumente. Probieren Sie es mit „/help “ Available commands: @1=Verfügbare Befehle: @1 Use '/help ' to get more information, or '/help all' to list everything.=„/help “ benutzen, um mehr Informationen zu erhalten, oder „/help all“, um alles aufzulisten. Available commands:=Verfügbare Befehle: Command not available: @1=Befehl nicht verfügbar: @1 -[all | privs | ]=[all | privs | ] -Get help for commands or list privileges=Hilfe für Befehle erhalten oder Privilegien auflisten +[all | privs | ] [-t]=[all | privs | ] [-t] +Get help for commands or list privileges (-t: output in chat)=Hilfe für Befehle erhalten oder Privilegien auflisten (-t: Ausgabe im Chat) Available privileges:=Verfügbare Privilegien: Command=Befehl Parameters=Parameter @@ -230,7 +232,8 @@ Can use fly mode=Kann den Flugmodus benutzen Can use fast mode=Kann den Schnellmodus benutzen Can fly through solid nodes using noclip mode=Kann durch feste Blöcke mit dem Geistmodus fliegen Can use the rollback functionality=Kann die Rollback-Funktionalität benutzen -Allows enabling various debug options that may affect gameplay=Erlaubt die Aktivierung diverser Debugoptionen, die das Spielgeschehen beeinflussen könnten +Can view more debug info that might give a gameplay advantage=Kann zusätzliche Debuginformationen betrachten, welche einen spielerischen Vorteil geben könnten +Can enable wireframe=Kann Drahtmodell aktivieren Unknown Item=Unbekannter Gegenstand Air=Luft Ignore=Ignorieren diff --git a/builtin/locale/__builtin.it.tr b/builtin/locale/__builtin.it.tr index 449c2b85e..77f85c766 100644 --- a/builtin/locale/__builtin.it.tr +++ b/builtin/locale/__builtin.it.tr @@ -139,7 +139,7 @@ This command was disabled by a mod or game.=Questo comando è stato disabilitato Show or set time of day=Mostra o imposta l'orario della giornata Current time is @1:@2.=Orario corrente: @1:@2. You don't have permission to run this command (missing privilege: @1).=Non hai il permesso di eseguire questo comando (privilegio mancante: @1) -Invalid time.=Orario non valido. +Invalid time (must be between 0 and 24000).= Time of day changed.=Orario della giornata cambiato. Invalid hour (must be between 0 and 23 inclusive).=Ora non valida (deve essere tra 0 e 23 inclusi) Invalid minute (must be between 0 and 59 inclusive).=Minuto non valido (deve essere tra 0 e 59 inclusi) @@ -187,12 +187,14 @@ You are already dead.=Sei già mortǝ. @1 is already dead.=@1 è già mortǝ. @1 has been killed.=@1 è stato uccisǝ. Kill player or yourself=Uccide un giocatore o te stessǝ +Invalid parameters (see /help @1).= +Too many arguments, try using just /help = Available commands: @1=Comandi disponibili: @1 Use '/help ' to get more information, or '/help all' to list everything.=Usa '/help ' per ottenere più informazioni, o '/help all' per elencare tutti i comandi. Available commands:=Comandi disponibili: Command not available: @1=Comando non disponibile: @1 -[all | privs | ]=[all | privs | ] -Get help for commands or list privileges=Richiama la finestra d'aiuto dei comandi o dei privilegi +[all | privs | ] [-t]= +Get help for commands or list privileges (-t: output in chat)= Available privileges:=Privilegi disponibili: Command=Comando Parameters=Parametri @@ -230,7 +232,8 @@ Can use fly mode=Si può usare la modalità volo Can use fast mode=Si può usare la modalità rapida Can fly through solid nodes using noclip mode=Si può volare attraverso i nodi solidi con la modalità incorporea Can use the rollback functionality=Si può usare la funzione di rollback -Allows enabling various debug options that may affect gameplay=Permette di abilitare varie opzioni di debug che potrebbero influenzare l'esperienza di gioco +Can view more debug info that might give a gameplay advantage= +Can enable wireframe= Unknown Item=Oggetto sconosciuto Air=Aria Ignore=Ignora @@ -244,6 +247,10 @@ Profile saved to @1= ##### not used anymore ##### +Invalid time.=Orario non valido. +[all | privs | ]=[all | privs | ] +Get help for commands or list privileges=Richiama la finestra d'aiuto dei comandi o dei privilegi +Allows enabling various debug options that may affect gameplay=Permette di abilitare varie opzioni di debug che potrebbero influenzare l'esperienza di gioco [ | -1] [reconnect] []=[ | -1] [reconnect] [] Shutdown server (-1 cancels a delayed shutdown)=Arresta il server (-1 annulla un arresto programmato) ( | all)= ( | all) diff --git a/builtin/locale/template.txt b/builtin/locale/template.txt index 7049dde36..308d17f37 100644 --- a/builtin/locale/template.txt +++ b/builtin/locale/template.txt @@ -139,7 +139,7 @@ This command was disabled by a mod or game.= Show or set time of day= Current time is @1:@2.= You don't have permission to run this command (missing privilege: @1).= -Invalid time.= +Invalid time (must be between 0 and 24000).= Time of day changed.= Invalid hour (must be between 0 and 23 inclusive).= Invalid minute (must be between 0 and 59 inclusive).= @@ -187,12 +187,14 @@ You are already dead.= @1 is already dead.= @1 has been killed.= Kill player or yourself= +Invalid parameters (see /help @1).= +Too many arguments, try using just /help = Available commands: @1= Use '/help ' to get more information, or '/help all' to list everything.= Available commands:= Command not available: @1= -[all | privs | ]= -Get help for commands or list privileges= +[all | privs | ] [-t]= +Get help for commands or list privileges (-t: output in chat)= Available privileges:= Command= Parameters= @@ -230,7 +232,8 @@ Can use fly mode= Can use fast mode= Can fly through solid nodes using noclip mode= Can use the rollback functionality= -Allows enabling various debug options that may affect gameplay= +Can view more debug info that might give a gameplay advantage= +Can enable wireframe= Unknown Item= Air= Ignore= diff --git a/builtin/mainmenu/common.lua b/builtin/mainmenu/common.lua index 6db351048..8db8bb8d1 100644 --- a/builtin/mainmenu/common.lua +++ b/builtin/mainmenu/common.lua @@ -119,31 +119,27 @@ function render_serverlist_row(spec) return table.concat(details, ",") end - --------------------------------------------------------------------------------- -os.tempfolder = function() - local temp = core.get_temp_path() - return temp .. DIR_DELIM .. "MT_" .. math.random(0, 10000) -end - +--------------------------------------------------------------------------------- os.tmpname = function() - local path = os.tempfolder() - io.open(path, "w"):close() - return path + error('do not use') -- instead use core.get_temp_path() end -------------------------------------------------------------------------------- -function menu_render_worldlist() - local retval = "" +function menu_render_worldlist(show_gameid) + local retval = {} local current_worldlist = menudata.worldlist:get_list() + local row for i, v in ipairs(current_worldlist) do - if retval ~= "" then retval = retval .. "," end - retval = retval .. core.formspec_escape(v.name) .. - " \\[" .. core.formspec_escape(v.gameid) .. "\\]" + row = v.name + if show_gameid == nil or show_gameid == true then + row = row .. " [" .. v.gameid .. "]" + end + retval[#retval+1] = core.formspec_escape(row) + end - return retval + return table.concat(retval, ",") end function menu_handle_key_up_down(fields, textlist, settingname) diff --git a/builtin/mainmenu/dlg_config_world.lua b/builtin/mainmenu/dlg_config_world.lua index 9bdf92a74..f73256612 100644 --- a/builtin/mainmenu/dlg_config_world.lua +++ b/builtin/mainmenu/dlg_config_world.lua @@ -163,10 +163,13 @@ local function get_formspec(data) "button[8.95,0.125;2.5,0.5;btn_enable_all_mods;" .. fgettext("Enable all") .. "]" end + + local use_technical_names = core.settings:get_bool("show_technical_names") + return retval .. "tablecolumns[color;tree;text]" .. "table[5.5,0.75;5.75,6;world_config_modlist;" .. - pkgmgr.render_packagelist(data.list) .. ";" .. data.selected_mod .."]" + pkgmgr.render_packagelist(data.list, use_technical_names) .. ";" .. data.selected_mod .."]" end local function handle_buttons(this, fields) @@ -205,14 +208,19 @@ local function handle_buttons(this, fields) local mods = worldfile:to_table() local rawlist = this.data.list:get_raw_list() + local was_set = {} for i = 1, #rawlist do local mod = rawlist[i] if not mod.is_modpack and not mod.is_game_content then if modname_valid(mod.name) then - worldfile:set("load_mod_" .. mod.name, - mod.enabled and "true" or "false") + if mod.enabled then + worldfile:set("load_mod_" .. mod.name, mod.virtual_path) + was_set[mod.name] = true + elseif not was_set[mod.name] then + worldfile:set("load_mod_" .. mod.name, "false") + end elseif mod.enabled then gamedata.errormessage = fgettext_ne("Failed to enable mo" .. "d \"$1\" as it contains disallowed characters. " .. @@ -256,12 +264,26 @@ local function handle_buttons(this, fields) if fields.btn_enable_all_mods then local list = this.data.list:get_raw_list() + -- When multiple copies of a mod are installed, we need to avoid enabling multiple of them + -- at a time. So lets first collect all the enabled mods, and then use this to exclude + -- multiple enables. + + local was_enabled = {} for i = 1, #list do if not list[i].is_game_content - and not list[i].is_modpack then - list[i].enabled = true + and not list[i].is_modpack and list[i].enabled then + was_enabled[list[i].name] = true end end + + for i = 1, #list do + if not list[i].is_game_content and not list[i].is_modpack and + not was_enabled[list[i].name] then + list[i].enabled = true + was_enabled[list[i].name] = true + end + end + enabled_all = true return true end diff --git a/builtin/mainmenu/dlg_contentstore.lua b/builtin/mainmenu/dlg_contentstore.lua index 790da03ba..ff32c8c9f 100644 --- a/builtin/mainmenu/dlg_contentstore.lua +++ b/builtin/mainmenu/dlg_contentstore.lua @@ -25,7 +25,7 @@ end -- Unordered preserves the original order of the ContentDB API, -- before the package list is ordered based on installed state. -local store = { packages = {}, packages_full = {}, packages_full_unordered = {} } +local store = { packages = {}, packages_full = {}, packages_full_unordered = {}, aliases = {} } local http = core.get_http_api() @@ -62,9 +62,19 @@ local REASON_UPDATE = "update" local REASON_DEPENDENCY = "dependency" +-- encodes for use as URL parameter or path component +local function urlencode(str) + return str:gsub("[^%a%d()._~-]", function(char) + return string.format("%%%02X", string.byte(char)) + end) +end +assert(urlencode("sample text?") == "sample%20text%3F") + + local function get_download_url(package, reason) local base_url = core.settings:get("contentdb_url") - local ret = base_url .. ("/packages/%s/%s/releases/%d/download/"):format(package.author, package.name, package.release) + local ret = base_url .. ("/packages/%s/releases/%d/download/"):format( + package.url_part, package.release) if reason then ret = ret .. "?reason=" .. reason end @@ -72,34 +82,52 @@ local function get_download_url(package, reason) end -local function download_package(param) - if core.download_file(param.url, param.filename) then +local function download_and_extract(param) + local package = param.package + + local filename = core.get_temp_path(true) + if filename == "" or not core.download_file(param.url, filename) then + core.log("error", "Downloading " .. dump(param.url) .. " failed") return { - filename = param.filename, - successful = true, - } - else - core.log("error", "downloading " .. dump(param.url) .. " failed") - return { - successful = false, + msg = fgettext("Failed to download $1", package.name) } end + + local tempfolder = core.get_temp_path() + if tempfolder ~= "" then + tempfolder = tempfolder .. DIR_DELIM .. "MT_" .. math.random(1, 1024000) + if not core.extract_zip(filename, tempfolder) then + tempfolder = nil + end + else + tempfolder = nil + end + os.remove(filename) + if not tempfolder then + return { + msg = fgettext("Install: Unsupported file type or broken archive"), + } + end + + return { + path = tempfolder + } end local function start_install(package, reason) local params = { package = package, url = get_download_url(package, reason), - filename = os.tempfolder() .. "_MODNAME_" .. package.name .. ".zip", } number_downloading = number_downloading + 1 local function callback(result) - if result.successful then - local path, msg = pkgmgr.install(package.type, - result.filename, package.name, - package.path) + if result.msg then + gamedata.errormessage = result.msg + else + local path, msg = pkgmgr.install_dir(package.type, result.path, package.name, package.path) + core.delete_dir(result.path) if not path then gamedata.errormessage = msg else @@ -137,9 +165,6 @@ local function start_install(package, reason) conf:write() end end - os.remove(result.filename) - else - gamedata.errormessage = fgettext("Failed to download $1", package.name) end package.downloading = false @@ -159,7 +184,7 @@ local function start_install(package, reason) package.queued = false package.downloading = true - if not core.handle_async(download_package, params, callback) then + if not core.handle_async(download_and_extract, params, callback) then core.log("error", "ERROR: async event failed") gamedata.errormessage = fgettext("Failed to download $1", package.name) return @@ -184,7 +209,7 @@ local function get_raw_dependencies(package) local url_fmt = "/api/packages/%s/dependencies/?only_hard=1&protocol_version=%s&engine_version=%s" local version = core.get_version() local base_url = core.settings:get("contentdb_url") - local url = base_url .. url_fmt:format(package.id, core.get_max_supp_proto(), version.string) + local url = base_url .. url_fmt:format(package.url_part, core.get_max_supp_proto(), urlencode(version.string)) local response = http.fetch_sync({ url = url }) if not response.succeeded then @@ -559,17 +584,16 @@ function store.load() local base_url = core.settings:get("contentdb_url") local url = base_url .. "/api/packages/?type=mod&type=game&type=txp&protocol_version=" .. - core.get_max_supp_proto() .. "&engine_version=" .. version.string + core.get_max_supp_proto() .. "&engine_version=" .. urlencode(version.string) for _, item in pairs(core.settings:get("contentdb_flag_blacklist"):split(",")) do item = item:trim() if item ~= "" then - url = url .. "&hide=" .. item + url = url .. "&hide=" .. urlencode(item) end end - local timeout = tonumber(core.settings:get("curl_file_download_timeout")) - local response = http.fetch_sync({ url = url, timeout = timeout }) + local response = http.fetch_sync({ url = url }) if not response.succeeded then return end @@ -579,12 +603,16 @@ function store.load() for _, package in pairs(store.packages_full) do local name_len = #package.name + -- This must match what store.update_paths() does! + package.id = package.author:lower() .. "/" if package.type == "game" and name_len > 5 and package.name:sub(name_len - 4) == "_game" then - package.id = package.author:lower() .. "/" .. package.name:sub(1, name_len - 5) + package.id = package.id .. package.name:sub(1, name_len - 5) else - package.id = package.author:lower() .. "/" .. package.name + package.id = package.id .. package.name end + package.url_part = urlencode(package.author) .. "/" .. urlencode(package.name) + if package.aliases then for _, alias in ipairs(package.aliases) do -- We currently don't support name changing @@ -834,8 +862,7 @@ function store.get_formspec(dlgdata) formspec[#formspec + 1] = "cdb_downloading.png;3;400;]" elseif package.queued then formspec[#formspec + 1] = left_base - formspec[#formspec + 1] = core.formspec_escape(defaulttexturedir) - formspec[#formspec + 1] = "cdb_queued.png;queued]" + formspec[#formspec + 1] = "cdb_queued.png;queued;]" elseif not package.path then local elem_name = "install_" .. i .. ";" formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#71aa34]" @@ -998,9 +1025,9 @@ function store.handle_submit(this, fields) end if fields["view_" .. i] then - local url = ("%s/packages/%s/%s?protocol_version=%d"):format( - core.settings:get("contentdb_url"), - package.author, package.name, core.get_max_supp_proto()) + local url = ("%s/packages/%s?protocol_version=%d"):format( + core.settings:get("contentdb_url"), package.url_part, + core.get_max_supp_proto()) core.open_url(url) return true end diff --git a/builtin/mainmenu/dlg_create_world.lua b/builtin/mainmenu/dlg_create_world.lua index 1938747fe..76ceb0f16 100644 --- a/builtin/mainmenu/dlg_create_world.lua +++ b/builtin/mainmenu/dlg_create_world.lua @@ -15,7 +15,8 @@ --with this program; if not, write to the Free Software Foundation, Inc., --51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -local worldname = "" +-- cf. tab_local, the gamebar already provides game selection so we hide the list from here +local hide_gamelist = PLATFORM ~= "Android" local function table_to_flags(ftable) -- Convert e.g. { jungles = true, caves = false } to "jungles,nocaves" @@ -31,9 +32,8 @@ local function strflag(flags, flag) return (flags[flag] == true) and "true" or "false" end -local cb_caverns = { "caverns", fgettext("Caverns"), "caverns", +local cb_caverns = { "caverns", fgettext("Caverns"), fgettext("Very large caverns deep in the underground") } -local tt_sea_rivers = fgettext("Sea level rivers") local flag_checkboxes = { v5 = { @@ -41,39 +41,38 @@ local flag_checkboxes = { }, v7 = { cb_caverns, - { "ridges", fgettext("Rivers"), "ridges", tt_sea_rivers }, - { "mountains", fgettext("Mountains"), "mountains" }, - { "floatlands", fgettext("Floatlands (experimental)"), "floatlands", + { "ridges", fgettext("Rivers"), fgettext("Sea level rivers") }, + { "mountains", fgettext("Mountains") }, + { "floatlands", fgettext("Floatlands (experimental)"), fgettext("Floating landmasses in the sky") }, }, carpathian = { cb_caverns, - { "rivers", fgettext("Rivers"), "rivers", tt_sea_rivers }, + { "rivers", fgettext("Rivers"), fgettext("Sea level rivers") }, }, valleys = { - { "altitude-chill", fgettext("Altitude chill"), "altitude_chill", + { "altitude_chill", fgettext("Altitude chill"), fgettext("Reduces heat with altitude") }, - { "altitude-dry", fgettext("Altitude dry"), "altitude_dry", + { "altitude_dry", fgettext("Altitude dry"), fgettext("Reduces humidity with altitude") }, - { "humid-rivers", fgettext("Humid rivers"), "humid_rivers", + { "humid_rivers", fgettext("Humid rivers"), fgettext("Increases humidity around rivers") }, - { "vary-river-depth", fgettext("Vary river depth"), "vary_river_depth", + { "vary_river_depth", fgettext("Vary river depth"), fgettext("Low humidity and high heat causes shallow or dry rivers") }, }, flat = { cb_caverns, - { "hills", fgettext("Hills"), "hills" }, - { "lakes", fgettext("Lakes"), "lakes" }, + { "hills", fgettext("Hills") }, + { "lakes", fgettext("Lakes") }, }, fractal = { - { "terrain", fgettext("Additional terrain"), "terrain", + { "terrain", fgettext("Additional terrain"), fgettext("Generate non-fractal terrain: Oceans and underground") }, }, v6 = { - { "trees", fgettext("Trees and jungle grass"), "trees" }, - { "flat", fgettext("Flat terrain"), "flat" }, - { "mudflow", fgettext("Mud flow"), "mudflow", - fgettext("Terrain surface erosion") }, + { "trees", fgettext("Trees and jungle grass") }, + { "flat", fgettext("Flat terrain") }, + { "mudflow", fgettext("Mud flow"), fgettext("Terrain surface erosion") }, -- Biome settings are in mgv6_biomes below }, } @@ -105,38 +104,26 @@ local function create_world_formspec(dialogdata) "button[4.75,2.5;3,0.5;world_create_cancel;" .. fgettext("Cancel") .. "]" end + local current_mg = dialogdata.mg local mapgens = core.get_mapgen_names() - local current_seed = core.settings:get("fixed_map_seed") or "" - local current_mg = core.settings:get("mg_name") local gameid = core.settings:get("menu_last_game") - local flags = { - main = core.settings:get_flags("mg_flags"), - v5 = core.settings:get_flags("mgv5_spflags"), - v6 = core.settings:get_flags("mgv6_spflags"), - v7 = core.settings:get_flags("mgv7_spflags"), - fractal = core.settings:get_flags("mgfractal_spflags"), - carpathian = core.settings:get_flags("mgcarpathian_spflags"), - valleys = core.settings:get_flags("mgvalleys_spflags"), - flat = core.settings:get_flags("mgflat_spflags"), - } + local flags = dialogdata.flags - local gameidx = 0 - if gameid ~= nil then - local _ - _, gameidx = pkgmgr.find_by_gameid(gameid) - - if gameidx == nil then - gameidx = 0 - end + local game, gameidx = pkgmgr.find_by_gameid(gameid) + if game == nil and hide_gamelist then + -- should never happen but just pick the first game + game = pkgmgr.get_game(1) + gameidx = 1 + core.settings:set("menu_last_game", game.id) + elseif game == nil then + gameidx = 0 end - local game_by_gameidx = core.get_game(gameidx) local disallowed_mapgen_settings = {} - if game_by_gameidx ~= nil then - local gamepath = game_by_gameidx.path - local gameconfig = Settings(gamepath.."/game.conf") + if game ~= nil then + local gameconfig = Settings(game.path.."/game.conf") local allowed_mapgens = (gameconfig:get("allowed_mapgens") or ""):split() for key, value in pairs(allowed_mapgens) do @@ -156,7 +143,7 @@ local function create_world_formspec(dialogdata) end end - if disallowed_mapgens then + if #disallowed_mapgens > 0 then for i = #mapgens, 1, -1 do if table.indexof(disallowed_mapgens, mapgens[i]) > 0 then table.remove(mapgens, i) @@ -172,23 +159,29 @@ local function create_world_formspec(dialogdata) local mglist = "" local selindex - local i = 1 - local first_mg - for k,v in pairs(mapgens) do - if not first_mg then - first_mg = v + do -- build the list of mapgens + local i = 1 + local first_mg + for k, v in pairs(mapgens) do + if not first_mg then + first_mg = v + end + if current_mg == v then + selindex = i + end + i = i + 1 + mglist = mglist .. core.formspec_escape(v) .. "," end - if current_mg == v then - selindex = i + if not selindex then + selindex = 1 + current_mg = first_mg end - i = i + 1 - mglist = mglist .. v .. "," + mglist = mglist:sub(1, -2) end - if not selindex then - selindex = 1 - current_mg = first_mg - end - mglist = mglist:sub(1, -2) + + -- The logic of the flag element IDs is as follows: + -- "flag_main_foo-bar-baz" controls dialogdata.flags["main"]["foo_bar_baz"] + -- see the buttonhandler for the implementation of this local mg_main_flags = function(mapgen, y) if mapgen == "singlenode" then @@ -198,11 +191,11 @@ local function create_world_formspec(dialogdata) return "", y end - local form = "checkbox[0," .. y .. ";flag_mg_caves;" .. + local form = "checkbox[0," .. y .. ";flag_main_caves;" .. fgettext("Caves") .. ";"..strflag(flags.main, "caves").."]" y = y + 0.5 - form = form .. "checkbox[0,"..y..";flag_mg_dungeons;" .. + form = form .. "checkbox[0,"..y..";flag_main_dungeons;" .. fgettext("Dungeons") .. ";"..strflag(flags.main, "dungeons").."]" y = y + 0.5 @@ -213,7 +206,7 @@ local function create_world_formspec(dialogdata) else d_tt = fgettext("Structures appearing on the terrain, typically trees and plants") end - form = form .. "checkbox[0,"..y..";flag_mg_decorations;" .. + form = form .. "checkbox[0,"..y..";flag_main_decorations;" .. d_name .. ";" .. strflag(flags.main, "decorations").."]" .. "tooltip[flag_mg_decorations;" .. @@ -221,7 +214,7 @@ local function create_world_formspec(dialogdata) "]" y = y + 0.5 - form = form .. "tooltip[flag_mg_caves;" .. + form = form .. "tooltip[flag_main_caves;" .. fgettext("Network of tunnels and caves") .. "]" return form, y @@ -235,13 +228,13 @@ local function create_world_formspec(dialogdata) return "", y end local form = "" - for _,tab in pairs(flag_checkboxes[mapgen]) do - local id = "flag_mg"..mapgen.."_"..tab[1] + for _, tab in pairs(flag_checkboxes[mapgen]) do + local id = "flag_"..mapgen.."_"..tab[1]:gsub("_", "-") form = form .. ("checkbox[0,%f;%s;%s;%s]"): - format(y, id, tab[2], strflag(flags[mapgen], tab[3])) + format(y, id, tab[2], strflag(flags[mapgen], tab[1])) - if tab[4] then - form = form .. "tooltip["..id..";"..tab[4].."]" + if tab[3] then + form = form .. "tooltip["..id..";"..tab[3].."]" end y = y + 0.5 end @@ -277,16 +270,14 @@ local function create_world_formspec(dialogdata) -- biomeblend y = y + 0.55 - form = form .. "checkbox[0,"..y..";flag_mgv6_biomeblend;" .. + form = form .. "checkbox[0,"..y..";flag_v6_biomeblend;" .. fgettext("Biome blending") .. ";"..strflag(flags.v6, "biomeblend").."]" .. - "tooltip[flag_mgv6_biomeblend;" .. + "tooltip[flag_v6_biomeblend;" .. fgettext("Smooth transition between biomes") .. "]" return form, y end - current_seed = core.formspec_escape(current_seed) - local y_start = 0.0 local y = y_start local str_flags, str_spflags @@ -323,21 +314,32 @@ local function create_world_formspec(dialogdata) "container[0,0]".. "field[0.3,0.6;6,0.5;te_world_name;" .. fgettext("World name") .. - ";" .. core.formspec_escape(worldname) .. "]" .. + ";" .. core.formspec_escape(dialogdata.worldname) .. "]" .. + "set_focus[te_world_name;false]" - "field[0.3,1.7;6,0.5;te_seed;" .. - fgettext("Seed") .. - ";".. current_seed .. "]" .. + if not disallowed_mapgen_settings["seed"] then + retval = retval .. "field[0.3,1.7;6,0.5;te_seed;" .. + fgettext("Seed") .. + ";".. core.formspec_escape(dialogdata.seed) .. "]" + + end + + retval = retval .. "label[0,2;" .. fgettext("Mapgen") .. "]".. - "dropdown[0,2.5;6.3;dd_mapgen;" .. mglist .. ";" .. selindex .. "]" .. + "dropdown[0,2.5;6.3;dd_mapgen;" .. mglist .. ";" .. selindex .. "]" - "label[0,3.35;" .. fgettext("Game") .. "]".. - "textlist[0,3.85;5.8,"..gamelist_height..";games;" .. - pkgmgr.gamelist() .. ";" .. gameidx .. ";false]" .. - "container[0,4.5]" .. - devtest_only .. - "container_end[]" .. + if not hide_gamelist or devtest_only ~= "" then + retval = retval .. + "label[0,3.35;" .. fgettext("Game") .. "]".. + "textlist[0,3.85;5.8,"..gamelist_height..";games;" .. + pkgmgr.gamelist() .. ";" .. gameidx .. ";false]" .. + "container[0,4.5]" .. + devtest_only .. + "container_end[]" + end + + retval = retval .. "container_end[]" .. -- Right side @@ -360,9 +362,20 @@ local function create_world_buttonhandler(this, fields) fields["key_enter"] then local worldname = fields["te_world_name"] - local gameindex = core.get_textlist_index("games") + local game, gameindex + if hide_gamelist then + game, gameindex = pkgmgr.find_by_gameid(core.settings:get("menu_last_game")) + else + gameindex = core.get_textlist_index("games") + game = pkgmgr.get_game(gameindex) + end - if gameindex ~= nil then + local message + if game == nil then + message = fgettext("No game selected") + end + + if message == nil then -- For unnamed worlds use the generated name 'world', -- where the number increments: it is set to 1 larger than the largest -- generated name number found. @@ -377,36 +390,48 @@ local function create_world_buttonhandler(this, fields) worldname = "world" .. worldnum_max + 1 end - core.settings:set("fixed_map_seed", fields["te_seed"]) - - local message - if not menudata.worldlist:uid_exists_raw(worldname) then - core.settings:set("mg_name",fields["dd_mapgen"]) - message = core.create_world(worldname,gameindex) - else + if menudata.worldlist:uid_exists_raw(worldname) then message = fgettext("A world named \"$1\" already exists", worldname) end - - if message ~= nil then - gamedata.errormessage = message - else - core.settings:set("menu_last_game",pkgmgr.games[gameindex].id) - if this.data.update_worldlist_filter then - menudata.worldlist:set_filtercriteria(pkgmgr.games[gameindex].id) - mm_texture.update("singleplayer", pkgmgr.games[gameindex].id) - end - menudata.worldlist:refresh() - core.settings:set("mainmenu_last_selected_world", - menudata.worldlist:raw_index_by_uid(worldname)) - end - else - gamedata.errormessage = fgettext("No game selected") end + + if message == nil then + this.data.seed = fields["te_seed"] or "" + this.data.mg = fields["dd_mapgen"] + + -- actual names as used by engine + local settings = { + fixed_map_seed = this.data.seed, + mg_name = this.data.mg, + mg_flags = table_to_flags(this.data.flags.main), + mgv5_spflags = table_to_flags(this.data.flags.v5), + mgv6_spflags = table_to_flags(this.data.flags.v6), + mgv7_spflags = table_to_flags(this.data.flags.v7), + mgfractal_spflags = table_to_flags(this.data.flags.fractal), + mgcarpathian_spflags = table_to_flags(this.data.flags.carpathian), + mgvalleys_spflags = table_to_flags(this.data.flags.valleys), + mgflat_spflags = table_to_flags(this.data.flags.flat), + } + message = core.create_world(worldname, gameindex, settings) + end + + if message == nil then + core.settings:set("menu_last_game", game.id) + if this.data.update_worldlist_filter then + menudata.worldlist:set_filtercriteria(game.id) + end + menudata.worldlist:refresh() + core.settings:set("mainmenu_last_selected_world", + menudata.worldlist:raw_index_by_uid(worldname)) + end + + gamedata.errormessage = message this:delete() return true end - worldname = fields.te_world_name + this.data.worldname = fields["te_world_name"] + this.data.seed = fields["te_seed"] or "" if fields["games"] then local gameindex = core.get_textlist_index("games") @@ -417,22 +442,11 @@ local function create_world_buttonhandler(this, fields) for k,v in pairs(fields) do local split = string.split(k, "_", nil, 3) if split and split[1] == "flag" then - local setting - if split[2] == "mg" then - setting = "mg_flags" - else - setting = split[2].."_spflags" - end -- We replaced the underscore of flag names with a dash. local flag = string.gsub(split[3], "-", "_") - local ftable = core.settings:get_flags(setting) - if v == "true" then - ftable[flag] = true - else - ftable[flag] = false - end - local flags = table_to_flags(ftable) - core.settings:set(setting, flags) + local ftable = this.data.flags[split[2]] + assert(ftable) + ftable[flag] = v == "true" return true end end @@ -446,18 +460,16 @@ local function create_world_buttonhandler(this, fields) local entry = core.formspec_escape(fields["mgv6_biomes"]) for b=1, #mgv6_biomes do if entry == mgv6_biomes[b][1] then - local ftable = core.settings:get_flags("mgv6_spflags") + local ftable = this.data.flags.v6 ftable.jungles = mgv6_biomes[b][2].jungles ftable.snowbiomes = mgv6_biomes[b][2].snowbiomes - local flags = table_to_flags(ftable) - core.settings:set("mgv6_spflags", flags) return true end end end if fields["dd_mapgen"] then - core.settings:set("mg_name", fields["dd_mapgen"]) + this.data.mg = fields["dd_mapgen"] return true end @@ -466,12 +478,27 @@ end function create_create_world_dlg(update_worldlistfilter) - worldname = "" local retval = dialog_create("sp_create_world", create_world_formspec, create_world_buttonhandler, nil) - retval.update_worldlist_filter = update_worldlistfilter + retval.data = { + update_worldlist_filter = update_worldlistfilter, + worldname = "", + -- settings the world is created with: + seed = core.settings:get("fixed_map_seed") or "", + mg = core.settings:get("mg_name"), + flags = { + main = core.settings:get_flags("mg_flags"), + v5 = core.settings:get_flags("mgv5_spflags"), + v6 = core.settings:get_flags("mgv6_spflags"), + v7 = core.settings:get_flags("mgv7_spflags"), + fractal = core.settings:get_flags("mgfractal_spflags"), + carpathian = core.settings:get_flags("mgcarpathian_spflags"), + valleys = core.settings:get_flags("mgvalleys_spflags"), + flat = core.settings:get_flags("mgflat_spflags"), + } + } return retval end diff --git a/builtin/mainmenu/dlg_settings_advanced.lua b/builtin/mainmenu/dlg_settings_advanced.lua index 38a658969..d6f485cf7 100644 --- a/builtin/mainmenu/dlg_settings_advanced.lua +++ b/builtin/mainmenu/dlg_settings_advanced.lua @@ -378,7 +378,7 @@ local function parse_config_file(read_all, parse_mods) -- Parse mods local mods_category_initialized = false local mods = {} - get_mods(core.get_modpath(), mods) + get_mods(core.get_modpath(), "mods", mods) for _, mod in ipairs(mods) do local path = mod.path .. DIR_DELIM .. FILENAME local file = io.open(path, "r") @@ -395,6 +395,7 @@ local function parse_config_file(read_all, parse_mods) table.insert(settings, { name = mod.name, + readable_name = mod.title, level = 1, type = "category", }) @@ -408,7 +409,7 @@ local function parse_config_file(read_all, parse_mods) -- Parse clientmods local clientmods_category_initialized = false local clientmods = {} - get_mods(core.get_clientmodpath(), clientmods) + get_mods(core.get_clientmodpath(), "clientmods", clientmods) for _, clientmod in ipairs(clientmods) do local path = clientmod.path .. DIR_DELIM .. FILENAME local file = io.open(path, "r") @@ -527,44 +528,40 @@ end local function get_current_np_group(setting) local value = core.settings:get_np_group(setting.name) - local t = {} if value == nil then - t = setting.values - else - table.insert(t, value.offset) - table.insert(t, value.scale) - table.insert(t, value.spread.x) - table.insert(t, value.spread.y) - table.insert(t, value.spread.z) - table.insert(t, value.seed) - table.insert(t, value.octaves) - table.insert(t, value.persistence) - table.insert(t, value.lacunarity) - table.insert(t, value.flags) + return setting.values end - return t + local p = "%g" + return { + p:format(value.offset), + p:format(value.scale), + p:format(value.spread.x), + p:format(value.spread.y), + p:format(value.spread.z), + p:format(value.seed), + p:format(value.octaves), + p:format(value.persistence), + p:format(value.lacunarity), + value.flags + } end local function get_current_np_group_as_string(setting) local value = core.settings:get_np_group(setting.name) - local t if value == nil then - t = setting.default - else - t = value.offset .. ", " .. - value.scale .. ", (" .. - value.spread.x .. ", " .. - value.spread.y .. ", " .. - value.spread.z .. "), " .. - value.seed .. ", " .. - value.octaves .. ", " .. - value.persistence .. ", " .. - value.lacunarity - if value.flags ~= "" then - t = t .. ", " .. value.flags - end + return setting.default end - return t + return ("%g, %g, (%g, %g, %g), %g, %g, %g, %g"):format( + value.offset, + value.scale, + value.spread.x, + value.spread.y, + value.spread.z, + value.seed, + value.octaves, + value.persistence, + value.lacunarity + ) .. (value.flags ~= "" and (", " .. value.flags) or "") end local checkboxes = {} -- handle checkboxes events @@ -697,7 +694,7 @@ local function create_change_setting_formspec(dialogdata) elseif setting.type == "v3f" then local val = get_current_value(setting) local v3f = {} - for line in val:gmatch("[+-]?[%d.-e]+") do -- All numeric characters + for line in val:gmatch("[+-]?[%d.+-eE]+") do -- All numeric characters table.insert(v3f, line) end @@ -990,7 +987,7 @@ local function create_settings_formspec(tabview, _, tabdata) local current_level = 0 for _, entry in ipairs(settings) do local name - if not core.settings:get_bool("main_menu_technical_settings") and entry.readable_name then + if not core.settings:get_bool("show_technical_names") and entry.readable_name then name = fgettext_ne(entry.readable_name) else name = entry.name @@ -1031,7 +1028,7 @@ local function create_settings_formspec(tabview, _, tabdata) "button[10,4.9;2,1;btn_edit;" .. fgettext("Edit") .. "]" .. "button[7,4.9;3,1;btn_restore;" .. fgettext("Restore Default") .. "]" .. "checkbox[0,4.3;cb_tech_settings;" .. fgettext("Show technical names") .. ";" - .. dump(core.settings:get_bool("main_menu_technical_settings")) .. "]" + .. dump(core.settings:get_bool("show_technical_names")) .. "]" return formspec end @@ -1114,7 +1111,7 @@ local function handle_settings_buttons(this, fields, tabname, tabdata) end if fields["cb_tech_settings"] then - core.settings:set("main_menu_technical_settings", fields["cb_tech_settings"]) + core.settings:set("show_technical_names", fields["cb_tech_settings"]) core.settings:write() core.update_formspec(this:get_formspec()) return true diff --git a/builtin/mainmenu/textures.lua b/builtin/mainmenu/game_theme.lua similarity index 57% rename from builtin/mainmenu/textures.lua rename to builtin/mainmenu/game_theme.lua index a3acbbdec..89e1b66c8 100644 --- a/builtin/mainmenu/textures.lua +++ b/builtin/mainmenu/game_theme.lua @@ -16,23 +16,25 @@ --51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -mm_texture = {} +mm_game_theme = {} -------------------------------------------------------------------------------- -function mm_texture.init() - mm_texture.defaulttexturedir = core.get_texturepath() .. DIR_DELIM .. "base" .. +function mm_game_theme.init() + mm_game_theme.defaulttexturedir = core.get_texturepath_share() .. DIR_DELIM .. "base" .. DIR_DELIM .. "pack" .. DIR_DELIM - mm_texture.basetexturedir = mm_texture.defaulttexturedir + mm_game_theme.basetexturedir = mm_game_theme.defaulttexturedir - mm_texture.texturepack = core.settings:get("texture_path") + mm_game_theme.texturepack = core.settings:get("texture_path") - mm_texture.gameid = nil + mm_game_theme.gameid = nil + + mm_game_theme.music_handle = nil end -------------------------------------------------------------------------------- -function mm_texture.update(tab,gamedetails) +function mm_game_theme.update(tab,gamedetails) if tab ~= "singleplayer" then - mm_texture.reset() + mm_game_theme.reset() return end @@ -40,50 +42,54 @@ function mm_texture.update(tab,gamedetails) return end - mm_texture.update_game(gamedetails) + mm_game_theme.update_game(gamedetails) end -------------------------------------------------------------------------------- -function mm_texture.reset() - mm_texture.gameid = nil +function mm_game_theme.reset() + mm_game_theme.gameid = nil local have_bg = false - local have_overlay = mm_texture.set_generic("overlay") + local have_overlay = mm_game_theme.set_generic("overlay") if not have_overlay then - have_bg = mm_texture.set_generic("background") + have_bg = mm_game_theme.set_generic("background") end - mm_texture.clear("header") - mm_texture.clear("footer") + mm_game_theme.clear("header") + mm_game_theme.clear("footer") core.set_clouds(false) - mm_texture.set_generic("footer") - mm_texture.set_generic("header") + mm_game_theme.set_generic("footer") + mm_game_theme.set_generic("header") if not have_bg then if core.settings:get_bool("menu_clouds") then core.set_clouds(true) else - mm_texture.set_dirt_bg() + mm_game_theme.set_dirt_bg() end end + + if mm_game_theme.music_handle ~= nil then + core.sound_stop(mm_game_theme.music_handle) + end end -------------------------------------------------------------------------------- -function mm_texture.update_game(gamedetails) - if mm_texture.gameid == gamedetails.id then +function mm_game_theme.update_game(gamedetails) + if mm_game_theme.gameid == gamedetails.id then return end local have_bg = false - local have_overlay = mm_texture.set_game("overlay",gamedetails) + local have_overlay = mm_game_theme.set_game("overlay",gamedetails) if not have_overlay then - have_bg = mm_texture.set_game("background",gamedetails) + have_bg = mm_game_theme.set_game("background",gamedetails) end - mm_texture.clear("header") - mm_texture.clear("footer") + mm_game_theme.clear("header") + mm_game_theme.clear("footer") core.set_clouds(false) if not have_bg then @@ -91,34 +97,34 @@ function mm_texture.update_game(gamedetails) if core.settings:get_bool("menu_clouds") then core.set_clouds(true) else - mm_texture.set_dirt_bg() + mm_game_theme.set_dirt_bg() end end - mm_texture.set_game("footer",gamedetails) - mm_texture.set_game("header",gamedetails) + mm_game_theme.set_game("footer",gamedetails) + mm_game_theme.set_game("header",gamedetails) - mm_texture.gameid = gamedetails.id + mm_game_theme.gameid = gamedetails.id end -------------------------------------------------------------------------------- -function mm_texture.clear(identifier) +function mm_game_theme.clear(identifier) core.set_background(identifier,"") end -------------------------------------------------------------------------------- -function mm_texture.set_generic(identifier) +function mm_game_theme.set_generic(identifier) --try texture pack first - if mm_texture.texturepack ~= nil then - local path = mm_texture.texturepack .. DIR_DELIM .."menu_" .. + if mm_game_theme.texturepack ~= nil then + local path = mm_game_theme.texturepack .. DIR_DELIM .."menu_" .. identifier .. ".png" if core.set_background(identifier,path) then return true end end - if mm_texture.defaulttexturedir ~= nil then - local path = mm_texture.defaulttexturedir .. DIR_DELIM .."menu_" .. + if mm_game_theme.defaulttexturedir ~= nil then + local path = mm_game_theme.defaulttexturedir .. DIR_DELIM .."menu_" .. identifier .. ".png" if core.set_background(identifier,path) then return true @@ -129,14 +135,16 @@ function mm_texture.set_generic(identifier) end -------------------------------------------------------------------------------- -function mm_texture.set_game(identifier, gamedetails) +function mm_game_theme.set_game(identifier, gamedetails) if gamedetails == nil then return false end - if mm_texture.texturepack ~= nil then - local path = mm_texture.texturepack .. DIR_DELIM .. + mm_game_theme.set_music(gamedetails) + + if mm_game_theme.texturepack ~= nil then + local path = mm_game_theme.texturepack .. DIR_DELIM .. gamedetails.id .. "_menu_" .. identifier .. ".png" if core.set_background(identifier, path) then return true @@ -171,9 +179,10 @@ function mm_texture.set_game(identifier, gamedetails) return false end -function mm_texture.set_dirt_bg() - if mm_texture.texturepack ~= nil then - local path = mm_texture.texturepack .. DIR_DELIM .."default_dirt.png" +-------------------------------------------------------------------------------- +function mm_game_theme.set_dirt_bg() + if mm_game_theme.texturepack ~= nil then + local path = mm_game_theme.texturepack .. DIR_DELIM .."default_dirt.png" if core.set_background("background", path, true, 128) then return true end @@ -183,3 +192,12 @@ function mm_texture.set_dirt_bg() local minimalpath = defaulttexturedir .. "menu_bg.png" core.set_background("background", minimalpath, true, 128) end + +-------------------------------------------------------------------------------- +function mm_game_theme.set_music(gamedetails) + if mm_game_theme.music_handle ~= nil then + core.sound_stop(mm_game_theme.music_handle) + end + local music_path = gamedetails.path .. DIR_DELIM .. "menu" .. DIR_DELIM .. "theme" + mm_game_theme.music_handle = core.sound_play(music_path, true) +end diff --git a/builtin/mainmenu/generate_from_settingtypes.lua b/builtin/mainmenu/generate_from_settingtypes.lua index 43fc57bb9..0f551fbb1 100644 --- a/builtin/mainmenu/generate_from_settingtypes.lua +++ b/builtin/mainmenu/generate_from_settingtypes.lua @@ -31,7 +31,7 @@ local group_format_template = [[ # octaves = %s, # persistence = %s, # lacunarity = %s, -# flags = %s +# flags =%s # } ]] @@ -55,7 +55,11 @@ local function create_minetest_conf_example() end if entry.comment ~= "" then for _, comment_line in ipairs(entry.comment:split("\n", true)) do - insert(result, "# " .. comment_line .. "\n") + if comment_line == "" then + insert(result, "#\n") + else + insert(result, "# " .. comment_line .. "\n") + end end end insert(result, "# type: " .. entry.type) @@ -73,10 +77,14 @@ local function create_minetest_conf_example() end insert(result, "\n") if group_format == true then + local flags = entry.values[10] + if flags ~= "" then + flags = " "..flags + end insert(result, sprintf(group_format_template, entry.name, entry.values[1], entry.values[2], entry.values[3], entry.values[4], entry.values[5], entry.values[6], entry.values[7], entry.values[8], entry.values[9], - entry.values[10])) + flags)) else local append if entry.default ~= "" then @@ -91,7 +99,7 @@ end local translation_file_header = [[ // This file is automatically generated -// It conatins a bunch of fake gettext calls, to tell xgettext about the strings in config files +// It contains a bunch of fake gettext calls, to tell xgettext about the strings in config files // To update it, refer to the bottom of builtin/mainmenu/dlg_settings_advanced.lua fake_function() {]] @@ -126,4 +134,3 @@ file = assert(io.open("src/settings_translation_file.cpp", "w")) --file = assert(io.open("settings_translation_file.cpp", "w")) file:write(create_translation_file()) file:close() - diff --git a/builtin/mainmenu/init.lua b/builtin/mainmenu/init.lua index 2481078e2..943454677 100644 --- a/builtin/mainmenu/init.lua +++ b/builtin/mainmenu/init.lua @@ -35,7 +35,7 @@ dofile(menupath .. DIR_DELIM .. "async_event.lua") dofile(menupath .. DIR_DELIM .. "common.lua") dofile(menupath .. DIR_DELIM .. "pkgmgr.lua") dofile(menupath .. DIR_DELIM .. "serverlistmgr.lua") -dofile(menupath .. DIR_DELIM .. "textures.lua") +dofile(menupath .. DIR_DELIM .. "game_theme.lua") dofile(menupath .. DIR_DELIM .. "dlg_config_world.lua") dofile(menupath .. DIR_DELIM .. "dlg_settings_advanced.lua") @@ -87,7 +87,7 @@ local function init_globals() core.settings:set("menu_last_game", default_game) end - mm_texture.init() + mm_game_theme.init() -- Create main tabview local tv_main = tabview_create("maintab", {x = 12, y = 5.4}, {x = 0, y = 0}) @@ -113,7 +113,7 @@ local function init_globals() if tv_main.current_tab == "local" then local game = pkgmgr.find_by_gameid(core.settings:get("menu_last_game")) if game == nil then - mm_texture.reset() + mm_game_theme.reset() end end @@ -121,8 +121,6 @@ local function init_globals() tv_main:show() ui.update() - - core.sound_play("main_menu", true) end init_globals() diff --git a/builtin/mainmenu/pkgmgr.lua b/builtin/mainmenu/pkgmgr.lua index 58a4ed8c1..072c41f0c 100644 --- a/builtin/mainmenu/pkgmgr.lua +++ b/builtin/mainmenu/pkgmgr.lua @@ -78,34 +78,35 @@ local function load_texture_packs(txtpath, retval) for _, item in ipairs(list) do if item ~= "base" then - local name = item - local path = txtpath .. DIR_DELIM .. item .. DIR_DELIM - if path == current_texture_path then - name = fgettext("$1 (Enabled)", name) - end - local conf = Settings(path .. "texture_pack.conf") + local enabled = path == current_texture_path + local title = conf:get("title") or item + + -- list_* is only used if non-nil, else the regular versions are used. retval[#retval + 1] = { name = item, + title = title, + list_name = enabled and fgettext("$1 (Enabled)", item) or nil, + list_title = enabled and fgettext("$1 (Enabled)", title) or nil, author = conf:get("author"), release = tonumber(conf:get("release")) or 0, - list_name = name, type = "txp", path = path, - enabled = path == current_texture_path, + enabled = enabled, } end end end -function get_mods(path,retval,modpack) +function get_mods(path, virtual_path, retval, modpack) local mods = core.get_dir_list(path, true) for _, name in ipairs(mods) do if name:sub(1, 1) ~= "." then - local prefix = path .. DIR_DELIM .. name + local mod_path = path .. DIR_DELIM .. name + local mod_virtual_path = virtual_path .. "/" .. name local toadd = { dir_name = name, parent_dir = path, @@ -114,18 +115,18 @@ function get_mods(path,retval,modpack) -- Get config file local mod_conf - local modpack_conf = io.open(prefix .. DIR_DELIM .. "modpack.conf") + local modpack_conf = io.open(mod_path .. DIR_DELIM .. "modpack.conf") if modpack_conf then toadd.is_modpack = true modpack_conf:close() - mod_conf = Settings(prefix .. DIR_DELIM .. "modpack.conf"):to_table() + mod_conf = Settings(mod_path .. DIR_DELIM .. "modpack.conf"):to_table() if mod_conf.name then name = mod_conf.name toadd.is_name_explicit = true end else - mod_conf = Settings(prefix .. DIR_DELIM .. "mod.conf"):to_table() + mod_conf = Settings(mod_path .. DIR_DELIM .. "mod.conf"):to_table() if mod_conf.name then name = mod_conf.name toadd.is_name_explicit = true @@ -134,14 +135,16 @@ function get_mods(path,retval,modpack) -- Read from config toadd.name = name + toadd.title = mod_conf.title toadd.author = mod_conf.author toadd.release = tonumber(mod_conf.release) or 0 - toadd.path = prefix + toadd.path = mod_path + toadd.virtual_path = mod_virtual_path toadd.type = "mod" -- Check modpack.txt -- Note: modpack.conf is already checked above - local modpackfile = io.open(prefix .. DIR_DELIM .. "modpack.txt") + local modpackfile = io.open(mod_path .. DIR_DELIM .. "modpack.txt") if modpackfile then modpackfile:close() toadd.is_modpack = true @@ -153,7 +156,7 @@ function get_mods(path,retval,modpack) elseif toadd.is_modpack then toadd.type = "modpack" toadd.is_modpack = true - get_mods(prefix, retval, name) + get_mods(mod_path, mod_virtual_path, retval, name) end end end @@ -181,21 +184,6 @@ function pkgmgr.get_texture_packs() end -------------------------------------------------------------------------------- -function pkgmgr.extract(modfile) - if modfile.type == "zip" then - local tempfolder = os.tempfolder() - - if tempfolder ~= nil and - tempfolder ~= "" then - core.create_dir(tempfolder) - if core.extract_zip(modfile.name,tempfolder) then - return tempfolder - end - end - end - return nil -end - function pkgmgr.get_folder_type(path) local testfile = io.open(path .. DIR_DELIM .. "init.lua","r") if testfile ~= nil then @@ -349,7 +337,7 @@ function pkgmgr.identify_modname(modpath,filename) return nil end -------------------------------------------------------------------------------- -function pkgmgr.render_packagelist(render_list) +function pkgmgr.render_packagelist(render_list, use_technical_names) if not render_list then if not pkgmgr.global_mods then pkgmgr.refresh_globals() @@ -385,7 +373,12 @@ function pkgmgr.render_packagelist(render_list) else retval[#retval + 1] = "0" end - retval[#retval + 1] = core.formspec_escape(v.list_name or v.name) + + if use_technical_names then + retval[#retval + 1] = core.formspec_escape(v.list_name or v.name) + else + retval[#retval + 1] = core.formspec_escape(v.list_title or v.list_name or v.title or v.name) + end end return table.concat(retval, ",") @@ -412,6 +405,14 @@ function pkgmgr.is_modpack_entirely_enabled(data, name) return true end +local function disable_all_by_name(list, name, except) + for i=1, #list do + if list[i].name == name and list[i] ~= except then + list[i].enabled = false + end + end +end + ---------- toggles or en/disables a mod or modpack and its dependencies -------- local function toggle_mod_or_modpack(list, toggled_mods, enabled_mods, toset, mod) if not mod.is_modpack then @@ -420,13 +421,16 @@ local function toggle_mod_or_modpack(list, toggled_mods, enabled_mods, toset, mo toset = not mod.enabled end if mod.enabled ~= toset then - mod.enabled = toset toggled_mods[#toggled_mods+1] = mod.name end if toset then -- Mark this mod for recursive dependency traversal enabled_mods[mod.name] = true + + -- Disable other mods with the same name + disable_all_by_name(list, mod.name, mod) end + mod.enabled = toset else -- Toggle or en/disable every mod in the modpack, -- interleaved unsupported @@ -451,7 +455,7 @@ function pkgmgr.enable_mod(this, toset) local enabled_mods = {} toggle_mod_or_modpack(list, toggled_mods, enabled_mods, toset, mod) - if not toset then + if next(enabled_mods) == nil then -- Mod(s) were disabled, so no dependencies need to be enabled table.sort(toggled_mods) core.log("info", "Following mods were disabled: " .. @@ -461,11 +465,16 @@ function pkgmgr.enable_mod(this, toset) -- Enable mods' depends after activation - -- Make a list of mod ids indexed by their names + -- Make a list of mod ids indexed by their names. Among mods with the + -- same name, enabled mods take precedence, after which game mods take + -- precedence, being last in the mod list. local mod_ids = {} for id, mod2 in pairs(list) do if mod2.type == "mod" and not mod2.is_modpack then - mod_ids[mod2.name] = id + local prev_id = mod_ids[mod2.name] + if not prev_id or not list[prev_id].enabled then + mod_ids[mod2.name] = id + end end end @@ -482,6 +491,7 @@ function pkgmgr.enable_mod(this, toset) end end end + -- If sp is 0, every dependency is already activated while sp > 0 do local name = to_enable[sp] @@ -494,14 +504,14 @@ function pkgmgr.enable_mod(this, toset) core.log("warning", "Mod dependency \"" .. name .. "\" not found!") else - if mod_to_enable.enabled == false then + if not mod_to_enable.enabled then mod_to_enable.enabled = true toggled_mods[#toggled_mods+1] = mod_to_enable.name end -- Push the dependencies of the dependency onto the stack local depends = pkgmgr.get_dependencies(mod_to_enable.path) for i = 1, #depends do - if not enabled_mods[name] then + if not enabled_mods[depends[i]] then sp = sp+1 to_enable[sp] = depends[i] end @@ -561,11 +571,10 @@ function pkgmgr.install_dir(type, path, basename, targetpath) local from = basefolder and basefolder.path or path if targetpath then core.delete_dir(targetpath) - core.create_dir(targetpath) else targetpath = core.get_texturepath() .. DIR_DELIM .. basename end - if not core.copy_dir(from, targetpath) then + if not core.copy_dir(from, targetpath, false) then return nil, fgettext("Failed to install $1 to $2", basename, targetpath) end @@ -586,7 +595,6 @@ function pkgmgr.install_dir(type, path, basename, targetpath) -- Get destination name for modpack if targetpath then core.delete_dir(targetpath) - core.create_dir(targetpath) else local clean_path = nil if basename ~= nil then @@ -610,7 +618,6 @@ function pkgmgr.install_dir(type, path, basename, targetpath) if targetpath then core.delete_dir(targetpath) - core.create_dir(targetpath) else local targetfolder = basename if targetfolder == nil then @@ -636,14 +643,13 @@ function pkgmgr.install_dir(type, path, basename, targetpath) if targetpath then core.delete_dir(targetpath) - core.create_dir(targetpath) else targetpath = core.get_gamepath() .. DIR_DELIM .. basename end end -- Copy it - if not core.copy_dir(basefolder.path, targetpath) then + if not core.copy_dir(basefolder.path, targetpath, false) then return nil, fgettext("Failed to install $1 to $2", basename, targetpath) end @@ -657,23 +663,6 @@ function pkgmgr.install_dir(type, path, basename, targetpath) return targetpath, nil end --------------------------------------------------------------------------------- -function pkgmgr.install(type, modfilename, basename, dest) - local archive_info = pkgmgr.identify_filetype(modfilename) - local path = pkgmgr.extract(archive_info) - - if path == nil then - return nil, - fgettext("Install: file: \"$1\"", archive_info.name) .. "\n" .. - fgettext("Install: Unsupported file type \"$1\" or broken archive", - archive_info.type) - end - - local targetpath, msg = pkgmgr.install_dir(type, path, basename, dest) - core.delete_dir(path) - return targetpath, msg -end - -------------------------------------------------------------------------------- function pkgmgr.prepareclientmodlist(data) local retval = {} @@ -683,9 +672,8 @@ function pkgmgr.prepareclientmodlist(data) --read clientmods local modpath = core.get_clientmodpath() - if modpath ~= nil and - modpath ~= "" then - get_mods(modpath,clientmods) + if modpath ~= nil and modpath ~= "" then + get_mods(modpath, "clientmods", clientmods) end for i=1,#clientmods,1 do @@ -730,16 +718,15 @@ function pkgmgr.preparemodlist(data) local game_mods = {} --read global mods - local modpath = core.get_modpath() - - if modpath ~= nil and - modpath ~= "" then - get_mods(modpath,global_mods) + local modpaths = core.get_modpaths() + for key, modpath in pairs(modpaths) do + get_mods(modpath, key, global_mods) end for i=1,#global_mods,1 do global_mods[i].type = "mod" global_mods[i].loc = "global" + global_mods[i].enabled = false retval[#retval + 1] = global_mods[i] end @@ -773,22 +760,37 @@ function pkgmgr.preparemodlist(data) DIR_DELIM .. "world.mt" local worldfile = Settings(filename) - - for key,value in pairs(worldfile:to_table()) do + for key, value in pairs(worldfile:to_table()) do if key:sub(1, 9) == "load_mod_" then key = key:sub(10) - local element = nil - for i=1,#retval,1 do + local mod_found = false + + local fallback_found = false + local fallback_mod = nil + + for i=1, #retval do if retval[i].name == key and - not retval[i].is_modpack then - element = retval[i] - break + not retval[i].is_modpack then + if core.is_yes(value) or retval[i].virtual_path == value then + retval[i].enabled = true + mod_found = true + break + elseif fallback_found then + -- Only allow fallback if only one mod matches + fallback_mod = nil + else + fallback_found = true + fallback_mod = retval[i] + end end end - if element ~= nil then - element.enabled = value ~= "false" and value ~= "nil" and value - else - core.log("info", "Mod: " .. key .. " " .. dump(value) .. " but not found") + + if not mod_found then + if fallback_mod and value:find("/") then + fallback_mod.enabled = true + else + core.log("info", "Mod: " .. key .. " " .. dump(value) .. " but not found") + end end end end @@ -871,45 +873,6 @@ function pkgmgr.refresh_globals() pkgmgr.clientmods:set_sortmode("alphabetic") end --------------------------------------------------------------------------------- -function pkgmgr.identify_filetype(name) - - if name:sub(-3):lower() == "zip" then - return { - name = name, - type = "zip" - } - end - - if name:sub(-6):lower() == "tar.gz" or - name:sub(-3):lower() == "tgz"then - return { - name = name, - type = "tgz" - } - end - - if name:sub(-6):lower() == "tar.bz2" then - return { - name = name, - type = "tbz" - } - end - - if name:sub(-2):lower() == "7z" then - return { - name = name, - type = "7z" - } - end - - return { - name = name, - type = "ukn" - } -end - - -------------------------------------------------------------------------------- function pkgmgr.find_by_gameid(gameid) for i=1,#pkgmgr.games,1 do @@ -925,7 +888,7 @@ function pkgmgr.get_game_mods(gamespec, retval) if gamespec ~= nil and gamespec.gamemods_path ~= nil and gamespec.gamemods_path ~= "" then - get_mods(gamespec.gamemods_path, retval) + get_mods(gamespec.gamemods_path, ("games/%s/mods"):format(gamespec.id), retval) end end diff --git a/builtin/mainmenu/tab_about.lua b/builtin/mainmenu/tab_about.lua index cb566f773..3336786ba 100644 --- a/builtin/mainmenu/tab_about.lua +++ b/builtin/mainmenu/tab_about.lua @@ -38,32 +38,35 @@ local core_developers = { "Lars Hofhansl ", "Pierre-Yves Rollo ", "v-rob ", + "hecks", + "Hugues Ross ", + "Dmitry Kostenko (x2048) ", } -- For updating active/previous contributors, see the script in ./util/gather_git_credits.py local active_contributors = { - "Wuzzy [devtest game, visual corrections]", - "Zughy [Visual improvements, various fixes]", - "Maksim (MoNTE48) [Android]", + "Wuzzy [I18n for builtin, liquid features, fixes]", + "Zughy [Various features and fixes]", "numzero [Graphics and rendering]", - "appgurueu [Various internal fixes]", - "Desour [Formspec and vector API changes]", - "HybridDog [Rendering fixes and documentation]", - "Hugues Ross [Graphics-related improvements]", - "ANAND (ClobberXD) [Mouse buttons rebinding]", - "luk3yx [Fixes]", - "hecks [Audiovisuals, Lua API]", - "LoneWolfHT [Object crosshair, documentation fixes]", - "Lejo [Server-related improvements]", - "EvidenceB [Compass HUD element]", - "Paul Ouellette (pauloue) [Lua API, documentation]", - "TheTermos [Collision detection, physics]", + "Desour [Internal fixes, Clipboard on X11]", + "Lars Müller [Various internal fixes]", + "JosiahWI [CMake, cleanups and fixes]", + "HybridDog [builtin, documentation]", + "Jude Melton-Houghton [Database implementation]", + "savilli [Fixes]", + "Liso [Shadow Mapping]", + "MoNTE48 [Build fix]", + "Jean-Patrick Guerrero (kilbith) [Fixes]", + "ROllerozxa [Code cleanups]", + "Lejo [bitop library integration]", + "LoneWolfHT [Build fixes]", + "NeroBurner [Joystick]", + "Elias Fleckenstein [Internal fixes]", "David CARLIER [Unix & Haiku build fixes]", - "dcbrwn [Object shading]", - "Elias Fleckenstein [API features/fixes]", - "Jean-Patrick Guerrero (kilbith) [model element, visual fixes]", - "k.h.lai [Memory leak fixes, documentation]", + "pecksin [Clickable web links]", + "srfqi [Android & rendering fixes]", + "EvidenceB [Formspec]", } local previous_core_developers = { @@ -80,6 +83,7 @@ local previous_core_developers = { "Zeno", "ShadowNinja ", "Auke Kok (sofar) ", + "Aaron Suen ", } local previous_contributors = { @@ -90,10 +94,10 @@ local previous_contributors = { "MirceaKitsune ", "Constantin Wenger (SpeedProg)", "Ciaran Gultnieks (CiaranG)", - "stujones11 [Android UX improvements]", - "Rogier [Fixes]", - "Gregory Currie (gregorycu) [optimisation]", - "srifqi [Fixes]", + "Paul Ouellette (pauloue)", + "stujones11", + "Rogier ", + "Gregory Currie (gregorycu)", "JacobF", "Jeija [HTTP, particles]", } diff --git a/builtin/mainmenu/tab_content.lua b/builtin/mainmenu/tab_content.lua index 1bffeeb22..a366d4ab4 100644 --- a/builtin/mainmenu/tab_content.lua +++ b/builtin/mainmenu/tab_content.lua @@ -88,12 +88,14 @@ local function get_formspec(tabview, name, tabdata) tabdata.selected_pkg = 1 end + local use_technical_names = core.settings:get_bool("show_technical_names") + local retval = "label[0.05,-0.25;".. fgettext("Installed Packages:") .. "]" .. "tablecolumns[color;tree;text]" .. "table[0,0.25;5.1,4.3;pkglist;" .. - pkgmgr.render_packagelist(packages) .. + pkgmgr.render_packagelist(packages, use_technical_names) .. ";" .. tabdata.selected_pkg .. "]" .. "button[0,4.85;5.25,0.5;btn_contentdb;".. fgettext("Browse online content") .. "]" @@ -124,9 +126,17 @@ local function get_formspec(tabview, name, tabdata) desc = info.description end + local title_and_name + if selected_pkg.type == "game" then + title_and_name = selected_pkg.name + else + title_and_name = (selected_pkg.title or selected_pkg.name) .. "\n" .. + core.colorize("#BFBFBF", selected_pkg.name) + end + retval = retval .. "image[5.5,0;3,2;" .. core.formspec_escape(modscreenshot) .. "]" .. - "label[8.25,0.6;" .. core.formspec_escape(selected_pkg.name) .. "]" .. + "label[8.25,0.6;" .. core.formspec_escape(title_and_name) .. "]" .. "box[5.5,2.2;6.15,2.35;#000]" if selected_pkg.type == "mod" then @@ -214,6 +224,9 @@ local function handle_doubleclick(pkg, pkg_name) core.settings:set("texture_path", pkg.path) end packages = nil + + mm_game_theme.init() + mm_game_theme.reset() elseif pkg.is_clientside then pkgmgr.enable_mod({data = {list = packages, selected_mod = pkg_name}}) packages = nil @@ -276,17 +289,17 @@ local function handle_buttons(tabview, fields, tabname, tabdata) return true end - if fields.btn_mod_mgr_use_txp then - local txp = packages:get_list()[tabdata.selected_pkg] - core.settings:set("texture_path", txp.path) - packages = nil - return true - end + if fields.btn_mod_mgr_use_txp or fields.btn_mod_mgr_disable_txp then + local txp_path = "" + if fields.btn_mod_mgr_use_txp then + txp_path = packages:get_list()[tabdata.selected_pkg].path + end - - if fields.btn_mod_mgr_disable_txp then - core.settings:set("texture_path", "") + core.settings:set("texture_path", txp_path) packages = nil + + mm_game_theme.init() + mm_game_theme.reset() return true end diff --git a/builtin/mainmenu/tab_local.lua b/builtin/mainmenu/tab_local.lua index be5f905ac..e77c6f04d 100644 --- a/builtin/mainmenu/tab_local.lua +++ b/builtin/mainmenu/tab_local.lua @@ -33,10 +33,30 @@ if enable_gamebar then return game end + -- Apply menu changes from given game + function apply_game(game) + core.set_topleft_text(game.name) + core.settings:set("menu_last_game", game.id) + menudata.worldlist:set_filtercriteria(game.id) + + mm_game_theme.update("singleplayer", game) -- this refreshes the formspec + + local index = filterlist.get_current_index(menudata.worldlist, + tonumber(core.settings:get("mainmenu_last_selected_world"))) + if not index or index < 1 then + local selected = core.get_textlist_index("sp_worlds") + if selected ~= nil and selected < #menudata.worldlist:get_list() then + index = selected + else + index = #menudata.worldlist:get_list() + end + end + menu_worldmt_legacy(index) + end + function singleplayer_refresh_gamebar() local old_bar = ui.find_by_name("game_button_bar") - if old_bar ~= nil then old_bar:delete() end @@ -51,26 +71,10 @@ if enable_gamebar then return true end - for key,value in pairs(fields) do - for j=1,#pkgmgr.games,1 do - if ("game_btnbar_" .. pkgmgr.games[j].id == key) then - mm_texture.update("singleplayer", pkgmgr.games[j]) - core.set_topleft_text(pkgmgr.games[j].name) - core.settings:set("menu_last_game",pkgmgr.games[j].id) - menudata.worldlist:set_filtercriteria(pkgmgr.games[j].id) - local index = filterlist.get_current_index(menudata.worldlist, - tonumber(core.settings:get("mainmenu_last_selected_world"))) - if not index or index < 1 then - local selected = core.get_textlist_index("sp_worlds") - if selected ~= nil and selected < #menudata.worldlist:get_list() then - index = selected - else - index = #menudata.worldlist:get_list() - end - end - menu_worldmt_legacy(index) - return true - end + for _, game in ipairs(pkgmgr.games) do + if fields["game_btnbar_" .. game.id] then + apply_game(game) + return true end end end @@ -79,25 +83,22 @@ if enable_gamebar then game_buttonbar_button_handler, {x=-0.3,y=5.9}, "horizontal", {x=12.4,y=1.15}) - for i=1,#pkgmgr.games,1 do - local btn_name = "game_btnbar_" .. pkgmgr.games[i].id + for _, game in ipairs(pkgmgr.games) do + local btn_name = "game_btnbar_" .. game.id local image = nil local text = nil - local tooltip = core.formspec_escape(pkgmgr.games[i].name) + local tooltip = core.formspec_escape(game.name) - if pkgmgr.games[i].menuicon_path ~= nil and - pkgmgr.games[i].menuicon_path ~= "" then - image = core.formspec_escape(pkgmgr.games[i].menuicon_path) + if (game.menuicon_path or "") ~= "" then + image = core.formspec_escape(game.menuicon_path) else - - local part1 = pkgmgr.games[i].id:sub(1,5) - local part2 = pkgmgr.games[i].id:sub(6,10) - local part3 = pkgmgr.games[i].id:sub(11) + local part1 = game.id:sub(1,5) + local part2 = game.id:sub(6,10) + local part3 = game.id:sub(11) text = part1 .. "\n" .. part2 - if part3 ~= nil and - part3 ~= "" then + if part3 ~= "" then text = text .. "\n" .. part3 end end @@ -147,8 +148,12 @@ local function get_formspec(tabview, name, tabdata) tonumber(core.settings:get("mainmenu_last_selected_world"))) local list = menudata.worldlist:get_list() local world = list and index and list[index] - local gameid = world and world.gameid - local game = gameid and pkgmgr.find_by_gameid(gameid) + local game + if world then + game = pkgmgr.find_by_gameid(world.gameid) + else + game = current_game() + end local disabled_settings = get_disabled_settings(game) local creative, damage, host = "", "", "" @@ -182,7 +187,7 @@ local function get_formspec(tabview, name, tabdata) damage .. host .. "textlist[3.9,0.4;7.9,3.45;sp_worlds;" .. - menu_render_worldlist() .. + menu_render_worldlist(not enable_gamebar) .. ";" .. index .. "]" if core.settings:get_bool("enable_server") and disabled_settings["enable_server"] == nil then @@ -319,11 +324,11 @@ local function main_button_handler(this, fields, name, tabdata) end if fields["world_create"] ~= nil then - local create_world_dlg = create_create_world_dlg(true) + local create_world_dlg = create_create_world_dlg(enable_gamebar) create_world_dlg:set_parent(this) this:hide() create_world_dlg:show() - mm_texture.update("singleplayer", current_game()) + mm_game_theme.update("singleplayer", current_game()) return true end @@ -340,7 +345,7 @@ local function main_button_handler(this, fields, name, tabdata) delete_world_dlg:set_parent(this) this:hide() delete_world_dlg:show() - mm_texture.update("singleplayer",current_game()) + mm_game_theme.update("singleplayer",current_game()) end end @@ -358,7 +363,7 @@ local function main_button_handler(this, fields, name, tabdata) configdialog:set_parent(this) this:hide() configdialog:show() - mm_texture.update("singleplayer",current_game()) + mm_game_theme.update("singleplayer",current_game()) end end @@ -371,11 +376,8 @@ if enable_gamebar then function on_change(type, old_tab, new_tab) if (type == "ENTER") then local game = current_game() - if game then - menudata.worldlist:set_filtercriteria(game.id) - core.set_topleft_text(game.name) - mm_texture.update("singleplayer",game) + apply_game(game) end singleplayer_refresh_gamebar() @@ -387,7 +389,7 @@ if enable_gamebar then gamebar:hide() end core.set_topleft_text("") - mm_texture.update(new_tab,nil) + mm_game_theme.update(new_tab,nil) end end end diff --git a/builtin/mainmenu/tab_settings.lua b/builtin/mainmenu/tab_settings.lua index f06e35872..0e761d324 100644 --- a/builtin/mainmenu/tab_settings.lua +++ b/builtin/mainmenu/tab_settings.lua @@ -247,7 +247,7 @@ local function handle_settings_buttons(this, fields, tabname, tabdata) adv_settings_dlg:set_parent(this) this:hide() adv_settings_dlg:show() - --mm_texture.update("singleplayer", current_game()) + --mm_game_theme.update("singleplayer", current_game()) return true end if fields["cb_smooth_lighting"] then @@ -275,13 +275,7 @@ local function handle_settings_buttons(this, fields, tabname, tabdata) return true end if fields["cb_shaders"] then - if (core.settings:get("video_driver") == "direct3d8" or - core.settings:get("video_driver") == "direct3d9") then - core.settings:set("enable_shaders", "false") - gamedata.errormessage = fgettext("To enable shaders the OpenGL driver needs to be used.") - else - core.settings:set("enable_shaders", fields["cb_shaders"]) - end + core.settings:set("enable_shaders", fields["cb_shaders"]) return true end if fields["cb_tonemapping"] then @@ -370,11 +364,11 @@ local function handle_settings_buttons(this, fields, tabname, tabdata) core.settings:set("enable_dynamic_shadows", "false") else local shadow_presets = { - [2] = { 80, 512, "true", 0, "false" }, - [3] = { 120, 1024, "true", 1, "false" }, - [4] = { 350, 2048, "true", 1, "false" }, - [5] = { 350, 2048, "true", 2, "true" }, - [6] = { 450, 4096, "true", 2, "true" }, + [2] = { 55, 512, "true", 0, "false" }, + [3] = { 82, 1024, "true", 1, "false" }, + [4] = { 240, 2048, "true", 1, "false" }, + [5] = { 240, 2048, "true", 2, "true" }, + [6] = { 300, 4096, "true", 2, "true" }, } local s = shadow_presets[table.indexof(labels.shadow_levels, fields["dd_shadows"])] if s then diff --git a/builtin/mainmenu/tests/serverlistmgr_spec.lua b/builtin/mainmenu/tests/serverlistmgr_spec.lua index a091959fb..ab7a6c60c 100644 --- a/builtin/mainmenu/tests/serverlistmgr_spec.lua +++ b/builtin/mainmenu/tests/serverlistmgr_spec.lua @@ -1,4 +1,5 @@ _G.core = {} +_G.vector = {metatable = {}} _G.unpack = table.unpack _G.serverlistmgr = {} diff --git a/builtin/profiler/instrumentation.lua b/builtin/profiler/instrumentation.lua index 6b951a2c2..f80314b32 100644 --- a/builtin/profiler/instrumentation.lua +++ b/builtin/profiler/instrumentation.lua @@ -102,8 +102,9 @@ local function instrument(def) -- also called https://en.wikipedia.org/wiki/Continuation_passing_style -- Compared to table creation and unpacking it won't lose `nil` returns -- and is expected to be faster - -- `measure` will be executed after time() and func(...) - return measure(modname, instrument_name, time(), func(...)) + -- `measure` will be executed after func(...) + local start = time() + return measure(modname, instrument_name, start, func(...)) end end diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index e023aeab7..2e0bb560a 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -146,17 +146,17 @@ enable_joysticks (Enable joysticks) bool false joystick_id (Joystick ID) int 0 # The type of joystick -joystick_type (Joystick type) enum auto auto,generic,xbox +joystick_type (Joystick type) enum auto auto,generic,xbox,dragonrise_gamecube # The time in seconds it takes between repeated events # when holding down a joystick button combination. repeat_joystick_button_time (Joystick button repetition interval) float 0.17 0.001 -# The deadzone of the joystick -joystick_deadzone (Joystick deadzone) int 2048 +# The dead zone of the joystick +joystick_deadzone (Joystick dead zone) int 2048 # The sensitivity of the joystick axes for moving the -# ingame view frustum around. +# in-game view frustum around. joystick_frustum_sensitivity (Joystick frustum sensitivity) float 170 # Key for moving the player forward. @@ -463,9 +463,9 @@ keymap_decrease_viewing_range_min (View range decrease key) key - [**Basic] -# Whether nametag backgrounds should be shown by default. +# Whether name tag backgrounds should be shown by default. # Mods may still set a background. -show_nametag_backgrounds (Show nametag backgrounds by default) bool true +show_nametag_backgrounds (Show name tag backgrounds by default) bool true # Enable vertex buffer objects. # This should greatly improve graphics performance. @@ -487,6 +487,10 @@ connected_glass (Connect glass) bool false # Disable for speed or for different looks. smooth_lighting (Smooth lighting) bool true +# Enables tradeoffs that reduce CPU load or increase rendering performance +# at the expense of minor visual glitches that do not impact game playability. +performance_tradeoffs (Tradeoffs for performance) bool false + # Clouds are a client side effect. enable_clouds (Clouds) bool true @@ -501,7 +505,7 @@ enable_particles (Digging particles) bool true [**Filtering] -# Use mip mapping to scale textures. May slightly increase performance, +# Use mipmapping to scale textures. May slightly increase performance, # especially when using a high resolution texture pack. # Gamma correct downscaling is not supported. mip_map (Mipmapping) bool false @@ -600,9 +604,10 @@ enable_waving_plants (Waving plants) bool false # Requires shaders to be enabled. enable_dynamic_shadows (Dynamic shadows) bool false -# Set the shadow strength. +# Set the shadow strength gamma. +# Adjusts the intensity of in-game dynamic shadows. # Lower value means lighter shadows, higher value means darker shadows. -shadow_strength (Shadow strength) float 0.2 0.05 1.0 +shadow_strength_gamma (Shadow strength gamma) float 1.0 0.1 10.0 # Maximum distance to render shadows. shadow_map_max_distance (Shadow map max distance in nodes to render shadows) float 120.0 10.0 1000.0 @@ -621,12 +626,12 @@ shadow_map_texture_32bit (Shadow map texture in 32 bits) bool true # On true uses Poisson disk to make "soft shadows". Otherwise uses PCF filtering. shadow_poisson_filter (Poisson filtering) bool true -# Define shadow filtering quality +# Define shadow filtering quality. # This simulates the soft shadows effect by applying a PCF or Poisson disk # but also uses more resources. shadow_filters (Shadow filter quality) enum 1 0,1,2 -# Enable colored shadows. +# Enable colored shadows. # On true translucent nodes cast colored shadows. This is expensive. shadow_map_color (Colored shadows) bool false @@ -638,10 +643,10 @@ shadow_update_frames (Map shadows update frames) int 8 1 16 # Set the soft shadow radius size. # Lower values mean sharper shadows, bigger values mean softer shadows. -# Minimum value: 1.0; maxiumum value: 10.0 +# Minimum value: 1.0; maximum value: 10.0 shadow_soft_radius (Soft shadow radius) float 1.0 1.0 10.0 -# Set the tilt of Sun/Moon orbit in degrees +# Set the tilt of Sun/Moon orbit in degrees. # Value of 0 means no tilt / vertical orbit. # Minimum value: 0.0; maximum value: 60.0 shadow_sky_body_orbit_tilt (Sky Body Orbit Tilt) float 0.0 0.0 60.0 @@ -801,7 +806,7 @@ desynchronize_mapblock_texture_animation (Desynchronize block animation) bool tr # Useful if there's something to be displayed right or left of hotbar. hud_hotbar_max_width (Maximum hotbar width) float 1.0 -# Modifies the size of the hudbar elements. +# Modifies the size of the HUD elements. hud_scaling (HUD scale factor) float 1.0 # Enables caching of facedir rotated meshes. @@ -865,6 +870,10 @@ autoscale_mode (Autoscaling mode) enum disable disable,enable,force # A restart is required after changing this. show_entity_selectionbox (Show entity selection boxes) bool false +# Distance in nodes at which transparency depth sorting is enabled +# Use this to limit the performance impact of transparency depth sorting +transparency_sorting_distance (Transparency Sorting Distance) int 16 0 128 + [*Menus] # Use a cloud animation for the main menu background. @@ -894,10 +903,6 @@ tooltip_show_delay (Tooltip delay) int 400 # Append item name to tooltip. tooltip_append_itemname (Append item name) bool false -# Whether FreeType fonts are used, requires FreeType support to be compiled in. -# If disabled, bitmap and XML vectors fonts are used instead. -freetype (FreeType fonts) bool true - font_bold (Font bold by default) bool false font_italic (Font italic by default) bool false @@ -908,12 +913,16 @@ font_shadow (Font shadow) int 1 # Opaqueness (alpha) of the shadow behind the default font, between 0 and 255. font_shadow_alpha (Font shadow alpha) int 127 0 255 -# Font size of the default font in point (pt). +# Font size of the default font where 1 unit = 1 pixel at 96 DPI font_size (Font size) int 16 1 -# Path to the default font. -# If “freetype” setting is enabled: Must be a TrueType font. -# If “freetype” setting is disabled: Must be a bitmap or XML vectors font. +# For pixel-style fonts that do not scale well, this ensures that font sizes used +# with this font will always be divisible by this value, in pixels. For instance, +# a pixel font 16 pixels tall should have this set to 16, so it will only ever be +# sized 16, 32, 48, etc., so a mod requesting a size of 25 will get 32. +font_size_divisible_by (Font size divisible by) int 1 1 + +# Path to the default font. Must be a TrueType font. # The fallback font will be used if the font cannot be loaded. font_path (Regular font path) filepath fonts/Arimo-Regular.ttf @@ -921,12 +930,16 @@ font_path_bold (Bold font path) filepath fonts/Arimo-Bold.ttf font_path_italic (Italic font path) filepath fonts/Arimo-Italic.ttf font_path_bold_italic (Bold and italic font path) filepath fonts/Arimo-BoldItalic.ttf -# Font size of the monospace font in point (pt). -mono_font_size (Monospace font size) int 15 1 +# Font size of the monospace font where 1 unit = 1 pixel at 96 DPI +mono_font_size (Monospace font size) int 16 1 -# Path to the monospace font. -# If “freetype” setting is enabled: Must be a TrueType font. -# If “freetype” setting is disabled: Must be a bitmap or XML vectors font. +# For pixel-style fonts that do not scale well, this ensures that font sizes used +# with this font will always be divisible by this value, in pixels. For instance, +# a pixel font 16 pixels tall should have this set to 16, so it will only ever be +# sized 16, 32, 48, etc., so a mod requesting a size of 25 will get 32. +mono_font_size_divisible_by (Monospace font size divisible by) int 1 1 + +# Path to the monospace font. Must be a TrueType font. # This font is used for e.g. the console and profiler screen. mono_font_path (Monospace font path) filepath fonts/Cousine-Regular.ttf @@ -934,9 +947,7 @@ mono_font_path_bold (Bold monospace font path) filepath fonts/Cousine-Bold.ttf mono_font_path_italic (Italic monospace font path) filepath fonts/Cousine-Italic.ttf mono_font_path_bold_italic (Bold and italic monospace font path) filepath fonts/Cousine-BoldItalic.ttf -# Path of the fallback font. -# If “freetype” setting is enabled: Must be a TrueType font. -# If “freetype” setting is disabled: Must be a bitmap or XML vectors font. +# Path of the fallback font. Must be a TrueType font. # This font will be used for certain languages or if the default font is unavailable. fallback_font_path (Fallback font path) filepath fonts/DroidSansFallbackFull.ttf @@ -949,7 +960,7 @@ chat_font_size (Chat font size) int 0 screenshot_path (Screenshot folder) path screenshots # Format of screenshots. -screenshot_format (Screenshot format) enum png png,jpg,bmp,pcx,ppm,tga +screenshot_format (Screenshot format) enum png png,jpg # Screenshot quality. Only used for JPEG format. # 1 means worst quality; 100 means best quality. @@ -961,6 +972,9 @@ screenshot_quality (Screenshot quality) int 0 0 100 # Adjust dpi configuration to your screen (non X11/Android only) e.g. for 4k screens. screen_dpi (DPI) int 72 1 +# Adjust the detected display density, used for scaling UI elements. +display_density_factor (Display Density Scaling Factor) float 1 + # Windows systems only: Start Minetest with the command line window in the background. # Contains the same information as the file debug.txt (default name). enable_console (Enable console window) bool false @@ -985,8 +999,8 @@ mute_sound (Mute sound) bool false [Client] -# Clickable weblinks (middle-click or ctrl-left-click) enabled in chat console output. -clickable_chat_weblinks (Chat weblinks) bool false +# Clickable weblinks (middle-click or Ctrl+left-click) enabled in chat console output. +clickable_chat_weblinks (Chat weblinks) bool true # Optional override for chat weblink color. chat_weblink_color (Weblink color) string @@ -1044,6 +1058,12 @@ client_unload_unused_data_timeout (Mapblock unload timeout) int 600 # Set to -1 for unlimited amount. client_mapblock_limit (Mapblock limit) int 7500 +# Whether to show technical names. +# Affects mods and texture packs in the Content and Select Mods menus, as well as +# setting names in All Settings. +# Controlled by the checkbox in the "All settings" menu. +show_technical_names (Show technical names) bool false + # Whether to show the client debug info (has the same effect as hitting F5). show_debug (Show debug info) bool false @@ -1114,7 +1134,7 @@ max_packets_per_iteration (Max. packets per iteration) int 1024 # Compression level to use when sending mapblocks to the client. # -1 - use default compression level -# 0 - least compresson, fastest +# 0 - least compression, fastest # 9 - best compression, slowest map_compression_level_net (Map Compression Level for Network Transfer) int -1 -1 9 @@ -1178,7 +1198,7 @@ enable_mod_channels (Mod channels) bool false # If this is set, players will always (re)spawn at the given position. static_spawnpoint (Static spawnpoint) string -# If enabled, new players cannot join with an empty password. +# If enabled, players cannot join without a password or change theirs to an empty password. disallow_empty_password (Disallow empty passwords) bool false # If enabled, disable cheat prevention in multiplayer. @@ -1300,7 +1320,7 @@ movement_gravity (Gravity) float 9.81 deprecated_lua_api_handling (Deprecated Lua API handling) enum log none,log,error # Number of extra blocks that can be loaded by /clearobjects at once. -# This is a trade-off between sqlite transaction overhead and +# This is a trade-off between SQLite transaction overhead and # memory consumption (4096=100MB, as a rule of thumb). max_clearobjects_extra_loaded_blocks (Max. clearobjects extra blocks) int 4096 @@ -1309,14 +1329,14 @@ max_clearobjects_extra_loaded_blocks (Max. clearobjects extra blocks) int 4096 server_unload_unused_data_timeout (Unload unused server data) int 29 # Maximum number of statically stored objects in a block. -max_objects_per_block (Maximum objects per block) int 64 +max_objects_per_block (Maximum objects per block) int 256 # See https://www.sqlite.org/pragma.html#pragma_synchronous sqlite_synchronous (Synchronous SQLite) enum 2 0,1,2 # Compression level to use when saving mapblocks to disk. # -1 - use default compression level -# 0 - least compresson, fastest +# 0 - least compression, fastest # 9 - best compression, slowest map_compression_level_disk (Map Compression Level for Disk Storage) int -1 -1 9 @@ -1423,8 +1443,8 @@ instrument.abm (Active Block Modifiers) bool true # Instrument the action function of Loading Block Modifiers on registration. instrument.lbm (Loading Block Modifiers) bool true -# Instrument chatcommands on registration. -instrument.chatcommand (Chatcommands) bool true +# Instrument chat commands on registration. +instrument.chatcommand (Chat commands) bool true # Instrument global callback functions on registration. # (anything you pass to a minetest.register_*() function) @@ -1460,7 +1480,8 @@ language (Language) enum ,be,bg,ca,cs,da,de,el,en,eo,es,et,eu,fi,fr,gd,gl,hu,i # - action # - info # - verbose -debug_log_level (Debug log level) enum action ,none,error,warning,action,info,verbose +# - trace +debug_log_level (Debug log level) enum action ,none,error,warning,action,info,verbose,trace # If the file size of debug.txt exceeds the number of megabytes specified in # this setting when it is opened, the file is moved to debug.txt.1, @@ -1469,7 +1490,7 @@ debug_log_level (Debug log level) enum action ,none,error,warning,action,info,ve debug_log_size_max (Debug log file size threshold) int 50 # Minimal level of logging to be written to chat. -chat_log_level (Chat log level) enum error ,none,error,warning,action,info,verbose +chat_log_level (Chat log level) enum error ,none,error,warning,action,info,verbose,trace # Enable IPv6 support (for both client and server). # Required for IPv6 connections to work at all. @@ -1514,11 +1535,11 @@ max_block_generate_distance (Max block generate distance) int 10 # Limit of map generation, in nodes, in all 6 directions from (0, 0, 0). # Only mapchunks completely within the mapgen limit are generated. # Value is stored per-world. -mapgen_limit (Map generation limit) int 31000 0 31000 +mapgen_limit (Map generation limit) int 31007 0 31007 # Global map generation attributes. # In Mapgen v6 the 'decorations' flag controls all decorations except trees -# and junglegrass, in all other mapgens this flag controls all decorations. +# and jungle grass, in all other mapgens this flag controls all decorations. mg_flags (Mapgen flags) flags caves,dungeons,light,decorations,biomes,ores caves,dungeons,light,decorations,biomes,ores,nocaves,nodungeons,nolight,nodecorations,nobiomes,noores [*Biome API temperature and humidity noise parameters] @@ -2270,7 +2291,7 @@ contentdb_max_concurrent_downloads (ContentDB Max Concurrent Downloads) int 3 [Cheat Menu] # Font to use for cheat menu -cheat_menu_font (MenuFont) enum FM_Mono FM_Standard,FM_Mono,FM_Fallback,FM_Simple,FM_SimpleMono,FM_MaxMode,FM_Unspecified +cheat_menu_font (MenuFont) enum FM_Mono FM_Standard,FM_Mono,FM_Fallback,FM_MaxMode,FM_Unspecified # (RGB value) cheat_menu_bg_color (Cell background color) v3f 255, 145, 88 diff --git a/client/shaders/nodes_shader/opengl_fragment.glsl b/client/shaders/nodes_shader/opengl_fragment.glsl index 87ef9af7d..8110f6fd3 100644 --- a/client/shaders/nodes_shader/opengl_fragment.glsl +++ b/client/shaders/nodes_shader/opengl_fragment.glsl @@ -1,5 +1,6 @@ uniform sampler2D baseTexture; +uniform vec3 dayLight; uniform vec4 skyBgColor; uniform float fogDistance; uniform vec3 eyePosition; @@ -15,10 +16,15 @@ uniform float animationTimer; uniform float f_textureresolution; uniform mat4 m_ShadowViewProj; uniform float f_shadowfar; - varying float normalOffsetScale; + uniform float f_shadow_strength; + uniform vec4 CameraPos; + uniform float xyPerspectiveBias0; + uniform float xyPerspectiveBias1; + varying float adj_shadow_strength; varying float cosLight; varying float f_normal_length; + varying vec3 shadow_position; #endif @@ -42,23 +48,7 @@ varying float nightRatio; const float fogStart = FOG_START; const float fogShadingParameter = 1.0 / ( 1.0 - fogStart); - - #ifdef ENABLE_DYNAMIC_SHADOWS -const float bias0 = 0.9; -const float zPersFactor = 0.5; -const float bias1 = 1.0 - bias0 + 1e-6; - -vec4 getPerspectiveFactor(in vec4 shadowPosition) -{ - - float pDistance = length(shadowPosition.xy); - float pFactor = pDistance * bias0 + bias1; - - shadowPosition.xyz *= vec3(vec2(1.0 / pFactor), zPersFactor); - - return shadowPosition; -} // assuming near is always 1.0 float getLinearDepth() @@ -68,16 +58,7 @@ float getLinearDepth() vec3 getLightSpacePosition() { - vec4 pLightSpace; - // some drawtypes have zero normals, so we need to handle it :( - #if DRAW_TYPE == NDT_PLANTLIKE - pLightSpace = m_ShadowViewProj * vec4(worldPosition, 1.0); - #else - float offsetScale = (0.0057 * getLinearDepth() + normalOffsetScale); - pLightSpace = m_ShadowViewProj * vec4(worldPosition + offsetScale * normalize(vNormal), 1.0); - #endif - pLightSpace = getPerspectiveFactor(pLightSpace); - return pLightSpace.xyz * 0.5 + 0.5; + return shadow_position * 0.5 + 0.5; } // custom smoothstep implementation because it's not defined in glsl1.2 // https://docs.gl/sl4/smoothstep @@ -170,13 +151,13 @@ float getHardShadowDepth(sampler2D shadowsampler, vec2 smTexCoord, float realDis float getBaseLength(vec2 smTexCoord) { - float l = length(2.0 * smTexCoord.xy - 1.0); // length in texture coords - return bias1 / (1.0 / l - bias0); // return to undistorted coords + float l = length(2.0 * smTexCoord.xy - 1.0 - CameraPos.xy); // length in texture coords + return xyPerspectiveBias1 / (1.0 / l - xyPerspectiveBias0); // return to undistorted coords } float getDeltaPerspectiveFactor(float l) { - return 0.1 / (bias0 * l + bias1); // original distortion factor, divided by 10 + return 0.04 * pow(512.0 / f_textureresolution, 0.4) / (xyPerspectiveBias0 * l + xyPerspectiveBias1); // original distortion factor, divided by 10 } float getPenumbraRadius(sampler2D shadowsampler, vec2 smTexCoord, float realDistance, float multiplier) @@ -185,6 +166,9 @@ float getPenumbraRadius(sampler2D shadowsampler, vec2 smTexCoord, float realDist float perspectiveFactor; // Return fast if sharp shadows are requested + if (PCFBOUND == 0.0) + return 0.0; + if (SOFTSHADOWRADIUS <= 1.0) { perspectiveFactor = getDeltaPerspectiveFactor(baseLength); return max(2 * length(smTexCoord.xy) * 2048 / f_textureresolution / pow(perspectiveFactor, 3), SOFTSHADOWRADIUS); @@ -479,36 +463,59 @@ void main(void) vec4 col = vec4(color.rgb * varColor.rgb, 1.0); #ifdef ENABLE_DYNAMIC_SHADOWS - float shadow_int = 0.0; - vec3 shadow_color = vec3(0.0, 0.0, 0.0); - vec3 posLightSpace = getLightSpacePosition(); + if (f_shadow_strength > 0.0) { + float shadow_int = 0.0; + vec3 shadow_color = vec3(0.0, 0.0, 0.0); + vec3 posLightSpace = getLightSpacePosition(); - float distance_rate = (1 - pow(clamp(2.0 * length(posLightSpace.xy - 0.5),0.0,1.0), 20.0)); - float f_adj_shadow_strength = max(adj_shadow_strength-mtsmoothstep(0.9,1.1, posLightSpace.z ),0.0); + float distance_rate = (1.0 - pow(clamp(2.0 * length(posLightSpace.xy - 0.5),0.0,1.0), 10.0)); + if (max(abs(posLightSpace.x - 0.5), abs(posLightSpace.y - 0.5)) > 0.5) + distance_rate = 0.0; + float f_adj_shadow_strength = max(adj_shadow_strength-mtsmoothstep(0.9,1.1, posLightSpace.z),0.0); + + if (distance_rate > 1e-7) { - if (distance_rate > 1e-7) { - #ifdef COLORED_SHADOWS - vec4 visibility = getShadowColor(ShadowMapSampler, posLightSpace.xy, posLightSpace.z); - shadow_int = visibility.r; - shadow_color = visibility.gba; + vec4 visibility; + if (cosLight > 0.0 || f_normal_length < 1e-3) + visibility = getShadowColor(ShadowMapSampler, posLightSpace.xy, posLightSpace.z); + else + visibility = vec4(1.0, 0.0, 0.0, 0.0); + shadow_int = visibility.r; + shadow_color = visibility.gba; #else - shadow_int = getShadow(ShadowMapSampler, posLightSpace.xy, posLightSpace.z); + if (cosLight > 0.0 || f_normal_length < 1e-3) + shadow_int = getShadow(ShadowMapSampler, posLightSpace.xy, posLightSpace.z); + else + shadow_int = 1.0; #endif - shadow_int *= distance_rate; - shadow_int *= 1.0 - nightRatio; + shadow_int *= distance_rate; + shadow_int = clamp(shadow_int, 0.0, 1.0); + } + // turns out that nightRatio falls off much faster than + // actual brightness of artificial light in relation to natual light. + // Power ratio was measured on torches in MTG (brightness = 14). + float adjusted_night_ratio = pow(max(0.0, nightRatio), 0.6); + + // Apply self-shadowing when light falls at a narrow angle to the surface + // Cosine of the cut-off angle. + const float self_shadow_cutoff_cosine = 0.035; + if (f_normal_length != 0 && cosLight < self_shadow_cutoff_cosine) { + shadow_int = max(shadow_int, 1 - clamp(cosLight, 0.0, self_shadow_cutoff_cosine)/self_shadow_cutoff_cosine); + shadow_color = mix(vec3(0.0), shadow_color, min(cosLight, self_shadow_cutoff_cosine)/self_shadow_cutoff_cosine); + } + + shadow_int *= f_adj_shadow_strength; + + // calculate fragment color from components: + col.rgb = + adjusted_night_ratio * col.rgb + // artificial light + (1.0 - adjusted_night_ratio) * ( // natural light + col.rgb * (1.0 - shadow_int * (1.0 - shadow_color)) + // filtered texture color + dayLight * shadow_color * shadow_int); // reflected filtered sunlight/moonlight } - - if (f_normal_length != 0 && cosLight < 0.035) { - shadow_int = max(shadow_int, min(clamp(1.0-nightRatio, 0.0, 1.0), 1 - clamp(cosLight, 0.0, 0.035)/0.035)); - } - - shadow_int = 1.0 - (shadow_int * f_adj_shadow_strength); - - col.rgb = mix(shadow_color,col.rgb,shadow_int)*shadow_int; - // col.r = 0.5 * clamp(getPenumbraRadius(ShadowMapSampler, posLightSpace.xy, posLightSpace.z, 1.0) / SOFTSHADOWRADIUS, 0.0, 1.0) + 0.5 * col.r; #endif #if ENABLE_TONE_MAPPING @@ -528,6 +535,6 @@ void main(void) - fogShadingParameter * length(eyeVec) / fogDistance, 0.0, 1.0); col = mix(skyBgColor, col, clarity); col = vec4(col.rgb, base.a); - + gl_FragColor = col; } diff --git a/client/shaders/nodes_shader/opengl_vertex.glsl b/client/shaders/nodes_shader/opengl_vertex.glsl index d316930b2..3ea0faa36 100644 --- a/client/shaders/nodes_shader/opengl_vertex.glsl +++ b/client/shaders/nodes_shader/opengl_vertex.glsl @@ -32,10 +32,13 @@ centroid varying vec2 varTexCoord; uniform float f_shadowfar; uniform float f_shadow_strength; uniform float f_timeofday; + uniform vec4 CameraPos; + varying float cosLight; varying float normalOffsetScale; varying float adj_shadow_strength; varying float f_normal_length; + varying vec3 shadow_position; #endif @@ -45,8 +48,38 @@ varying float nightRatio; const vec3 artificialLight = vec3(1.04, 1.04, 1.04); const float e = 2.718281828459; const float BS = 10.0; +uniform float xyPerspectiveBias0; +uniform float xyPerspectiveBias1; +uniform float zPerspectiveBias; #ifdef ENABLE_DYNAMIC_SHADOWS + +vec4 getRelativePosition(in vec4 position) +{ + vec2 l = position.xy - CameraPos.xy; + vec2 s = l / abs(l); + s = (1.0 - s * CameraPos.xy); + l /= s; + return vec4(l, s); +} + +float getPerspectiveFactor(in vec4 relativePosition) +{ + float pDistance = length(relativePosition.xy); + float pFactor = pDistance * xyPerspectiveBias0 + xyPerspectiveBias1; + return pFactor; +} + +vec4 applyPerspectiveDistortion(in vec4 position) +{ + vec4 l = getRelativePosition(position); + float pFactor = getPerspectiveFactor(l); + l.xy /= pFactor; + position.xy = l.xy * l.zw + CameraPos.xy; + position.z *= zPerspectiveBias; + return position; +} + // custom smoothstep implementation because it's not defined in glsl1.2 // https://docs.gl/sl4/smoothstep float mtsmoothstep(in float edge0, in float edge1, in float x) @@ -193,24 +226,45 @@ void main(void) varColor = clamp(color, 0.0, 1.0); #ifdef ENABLE_DYNAMIC_SHADOWS - vec3 nNormal = normalize(vNormal); - cosLight = dot(nNormal, -v_LightDirection); - float texelSize = 767.0 / f_textureresolution; - float slopeScale = clamp(1.0 - abs(cosLight), 0.0, 1.0); - normalOffsetScale = texelSize * slopeScale; - - if (f_timeofday < 0.2) { - adj_shadow_strength = f_shadow_strength * 0.5 * - (1.0 - mtsmoothstep(0.18, 0.2, f_timeofday)); - } else if (f_timeofday >= 0.8) { - adj_shadow_strength = f_shadow_strength * 0.5 * - mtsmoothstep(0.8, 0.83, f_timeofday); - } else { - adj_shadow_strength = f_shadow_strength * - mtsmoothstep(0.20, 0.25, f_timeofday) * - (1.0 - mtsmoothstep(0.7, 0.8, f_timeofday)); - } - f_normal_length = length(vNormal); -#endif + if (f_shadow_strength > 0.0) { + vec3 nNormal; + f_normal_length = length(vNormal); + /* normalOffsetScale is in world coordinates (1/10th of a meter) + z_bias is in light space coordinates */ + float normalOffsetScale, z_bias; + float pFactor = getPerspectiveFactor(getRelativePosition(m_ShadowViewProj * mWorld * inVertexPosition)); + if (f_normal_length > 0.0) { + nNormal = normalize(vNormal); + cosLight = dot(nNormal, -v_LightDirection); + float sinLight = pow(1 - pow(cosLight, 2.0), 0.5); + normalOffsetScale = 2.0 * pFactor * pFactor * sinLight * min(f_shadowfar, 500.0) / + xyPerspectiveBias1 / f_textureresolution; + z_bias = 1.0 * sinLight / cosLight; + } + else { + nNormal = vec3(0.0); + cosLight = clamp(dot(v_LightDirection, normalize(vec3(v_LightDirection.x, 0.0, v_LightDirection.z))), 1e-2, 1.0); + float sinLight = pow(1 - pow(cosLight, 2.0), 0.5); + normalOffsetScale = 0.0; + z_bias = 3.6e3 * sinLight / cosLight; + } + z_bias *= pFactor * pFactor / f_textureresolution / f_shadowfar; + + shadow_position = applyPerspectiveDistortion(m_ShadowViewProj * mWorld * (inVertexPosition + vec4(normalOffsetScale * nNormal, 0.0))).xyz; + shadow_position.z -= z_bias; + + if (f_timeofday < 0.2) { + adj_shadow_strength = f_shadow_strength * 0.5 * + (1.0 - mtsmoothstep(0.18, 0.2, f_timeofday)); + } else if (f_timeofday >= 0.8) { + adj_shadow_strength = f_shadow_strength * 0.5 * + mtsmoothstep(0.8, 0.83, f_timeofday); + } else { + adj_shadow_strength = f_shadow_strength * + mtsmoothstep(0.20, 0.25, f_timeofday) * + (1.0 - mtsmoothstep(0.7, 0.8, f_timeofday)); + } + } +#endif } diff --git a/client/shaders/object_shader/opengl_fragment.glsl b/client/shaders/object_shader/opengl_fragment.glsl index 9a0b90f15..7baf5826f 100644 --- a/client/shaders/object_shader/opengl_fragment.glsl +++ b/client/shaders/object_shader/opengl_fragment.glsl @@ -1,28 +1,14 @@ uniform sampler2D baseTexture; uniform vec4 emissiveColor; +uniform vec3 dayLight; uniform vec4 skyBgColor; uniform float fogDistance; uniform vec3 eyePosition; -varying vec3 vNormal; -varying vec3 vPosition; -varying vec3 worldPosition; -varying lowp vec4 varColor; -#ifdef GL_ES -varying mediump vec2 varTexCoord; -#else -centroid varying vec2 varTexCoord; -#endif - -varying vec3 eyeVec; -varying float vIDiff; - -const float e = 2.718281828459; -const float BS = 10.0; -const float fogStart = FOG_START; -const float fogShadingParameter = 1.0 / (1.0 - fogStart); - +// The cameraOffset is the current center of the visible world. +uniform vec3 cameraOffset; +uniform float animationTimer; #ifdef ENABLE_DYNAMIC_SHADOWS // shadow texture uniform sampler2D ShadowMapSampler; @@ -31,57 +17,41 @@ const float fogShadingParameter = 1.0 / (1.0 - fogStart); uniform float f_textureresolution; uniform mat4 m_ShadowViewProj; uniform float f_shadowfar; - uniform float f_timeofday; - varying float normalOffsetScale; + uniform float f_shadow_strength; + uniform vec4 CameraPos; + uniform float xyPerspectiveBias0; + uniform float xyPerspectiveBias1; + varying float adj_shadow_strength; varying float cosLight; varying float f_normal_length; + varying vec3 shadow_position; #endif -#if ENABLE_TONE_MAPPING -/* Hable's UC2 Tone mapping parameters - A = 0.22; - B = 0.30; - C = 0.10; - D = 0.20; - E = 0.01; - F = 0.30; - W = 11.2; - equation used: ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F -*/ -vec3 uncharted2Tonemap(vec3 x) -{ - return ((x * (0.22 * x + 0.03) + 0.002) / (x * (0.22 * x + 0.3) + 0.06)) - 0.03333; -} - -vec4 applyToneMapping(vec4 color) -{ - color = vec4(pow(color.rgb, vec3(2.2)), color.a); - const float gamma = 1.6; - const float exposureBias = 5.5; - color.rgb = uncharted2Tonemap(exposureBias * color.rgb); - // Precalculated white_scale from - //vec3 whiteScale = 1.0 / uncharted2Tonemap(vec3(W)); - vec3 whiteScale = vec3(1.036015346); - color.rgb *= whiteScale; - return vec4(pow(color.rgb, vec3(1.0 / gamma)), color.a); -} +varying vec3 vNormal; +varying vec3 vPosition; +// World position in the visible world (i.e. relative to the cameraOffset.) +// This can be used for many shader effects without loss of precision. +// If the absolute position is required it can be calculated with +// cameraOffset + worldPosition (for large coordinates the limits of float +// precision must be considered). +varying vec3 worldPosition; +varying lowp vec4 varColor; +#ifdef GL_ES +varying mediump vec2 varTexCoord; +#else +centroid varying vec2 varTexCoord; #endif +varying vec3 eyeVec; +varying float nightRatio; + +varying float vIDiff; + +const float fogStart = FOG_START; +const float fogShadingParameter = 1.0 / (1.0 - fogStart); #ifdef ENABLE_DYNAMIC_SHADOWS -const float bias0 = 0.9; -const float zPersFactor = 0.5; -const float bias1 = 1.0 - bias0; - -vec4 getPerspectiveFactor(in vec4 shadowPosition) -{ - float pDistance = length(shadowPosition.xy); - float pFactor = pDistance * bias0 + bias1; - shadowPosition.xyz *= vec3(vec2(1.0 / pFactor), zPersFactor); - - return shadowPosition; -} // assuming near is always 1.0 float getLinearDepth() @@ -91,11 +61,14 @@ float getLinearDepth() vec3 getLightSpacePosition() { - vec4 pLightSpace; - float normalBias = 0.0005 * getLinearDepth() * cosLight + normalOffsetScale; - pLightSpace = m_ShadowViewProj * vec4(worldPosition + normalBias * normalize(vNormal), 1.0); - pLightSpace = getPerspectiveFactor(pLightSpace); - return pLightSpace.xyz * 0.5 + 0.5; + return shadow_position * 0.5 + 0.5; +} +// custom smoothstep implementation because it's not defined in glsl1.2 +// https://docs.gl/sl4/smoothstep +float mtsmoothstep(in float edge0, in float edge1, in float x) +{ + float t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0); + return t * t * (3.0 - 2.0 * t); } #ifdef COLORED_SHADOWS @@ -124,10 +97,10 @@ vec4 getHardShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDist { vec4 texDepth = texture2D(shadowsampler, smTexCoord.xy).rgba; - float visibility = step(0.0, (realDistance-2e-5) - texDepth.r); + float visibility = step(0.0, realDistance - texDepth.r); vec4 result = vec4(visibility, vec3(0.0,0.0,0.0));//unpackColor(texDepth.g)); if (visibility < 0.1) { - visibility = step(0.0, (realDistance-2e-5) - texDepth.r); + visibility = step(0.0, realDistance - texDepth.b); result = vec4(visibility, unpackColor(texDepth.a)); } return result; @@ -138,13 +111,13 @@ vec4 getHardShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDist float getHardShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) { float texDepth = texture2D(shadowsampler, smTexCoord.xy).r; - float visibility = step(0.0, (realDistance-2e-5) - texDepth); - + float visibility = step(0.0, realDistance - texDepth); return visibility; } #endif + #if SHADOW_FILTER == 2 #define PCFBOUND 3.5 #define PCFSAMPLES 64.0 @@ -163,6 +136,76 @@ float getHardShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance #define PCFSAMPLES 1.0 #endif #endif +#ifdef COLORED_SHADOWS +float getHardShadowDepth(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) +{ + vec4 texDepth = texture2D(shadowsampler, smTexCoord.xy); + float depth = max(realDistance - texDepth.r, realDistance - texDepth.b); + return depth; +} +#else +float getHardShadowDepth(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) +{ + float texDepth = texture2D(shadowsampler, smTexCoord.xy).r; + float depth = realDistance - texDepth; + return depth; +} +#endif + +float getBaseLength(vec2 smTexCoord) +{ + float l = length(2.0 * smTexCoord.xy - 1.0 - CameraPos.xy); // length in texture coords + return xyPerspectiveBias1 / (1.0 / l - xyPerspectiveBias0); // return to undistorted coords +} + +float getDeltaPerspectiveFactor(float l) +{ + return 0.04 * pow(512.0 / f_textureresolution, 0.4) / (xyPerspectiveBias0 * l + xyPerspectiveBias1); // original distortion factor, divided by 10 +} + +float getPenumbraRadius(sampler2D shadowsampler, vec2 smTexCoord, float realDistance, float multiplier) +{ + float baseLength = getBaseLength(smTexCoord); + float perspectiveFactor; + + // Return fast if sharp shadows are requested + if (PCFBOUND == 0.0) + return 0.0; + + if (SOFTSHADOWRADIUS <= 1.0) { + perspectiveFactor = getDeltaPerspectiveFactor(baseLength); + return max(2 * length(smTexCoord.xy) * 2048 / f_textureresolution / pow(perspectiveFactor, 3), SOFTSHADOWRADIUS); + } + + vec2 clampedpos; + float texture_size = 1.0 / (2048 /*f_textureresolution*/ * 0.5); + float y, x; + float depth = 0.0; + float pointDepth; + float maxRadius = SOFTSHADOWRADIUS * 5.0 * multiplier; + + float bound = clamp(PCFBOUND * (1 - baseLength), 0.0, PCFBOUND); + int n = 0; + + for (y = -bound; y <= bound; y += 1.0) + for (x = -bound; x <= bound; x += 1.0) { + clampedpos = vec2(x,y); + perspectiveFactor = getDeltaPerspectiveFactor(baseLength + length(clampedpos) * texture_size * maxRadius); + clampedpos = clampedpos * texture_size * perspectiveFactor * maxRadius * perspectiveFactor + smTexCoord.xy; + + pointDepth = getHardShadowDepth(shadowsampler, clampedpos.xy, realDistance); + if (pointDepth > -0.01) { + depth += pointDepth; + n += 1; + } + } + + depth = depth / n; + depth = pow(clamp(depth, 0.0, 1000.0), 1.6) / 0.001; + + perspectiveFactor = getDeltaPerspectiveFactor(baseLength); + return max(length(smTexCoord.xy) * 2 * 2048 / f_textureresolution / pow(perspectiveFactor, 3), depth * maxRadius); +} #ifdef POISSON_FILTER const vec2[64] poissonDisk = vec2[64]( @@ -238,17 +281,28 @@ vec4 getShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDistance { vec2 clampedpos; vec4 visibility = vec4(0.0); + float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance, 1.5); // scale to align with PCF + if (radius < 0.1) { + // we are in the middle of even brightness, no need for filtering + return getHardShadowColor(shadowsampler, smTexCoord.xy, realDistance); + } + + float baseLength = getBaseLength(smTexCoord); + float perspectiveFactor; float texture_size = 1.0 / (f_textureresolution * 0.5); - int init_offset = int(floor(mod(((smTexCoord.x * 34.0) + 1.0) * smTexCoord.y, 64.0-PCFSAMPLES))); - int end_offset = int(PCFSAMPLES) + init_offset; + int samples = int(clamp(PCFSAMPLES * (1 - baseLength) * (1 - baseLength), PCFSAMPLES / 4, PCFSAMPLES)); + int init_offset = int(floor(mod(((smTexCoord.x * 34.0) + 1.0) * smTexCoord.y, 64.0-samples))); + int end_offset = int(samples) + init_offset; for (int x = init_offset; x < end_offset; x++) { - clampedpos = poissonDisk[x] * texture_size * SOFTSHADOWRADIUS + smTexCoord.xy; + clampedpos = poissonDisk[x]; + perspectiveFactor = getDeltaPerspectiveFactor(baseLength + length(clampedpos) * texture_size * radius); + clampedpos = clampedpos * texture_size * perspectiveFactor * radius * perspectiveFactor + smTexCoord.xy; visibility += getHardShadowColor(shadowsampler, clampedpos.xy, realDistance); } - return visibility / PCFSAMPLES; + return visibility / samples; } #else @@ -257,17 +311,28 @@ float getShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) { vec2 clampedpos; float visibility = 0.0; + float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance, 1.5); // scale to align with PCF + if (radius < 0.1) { + // we are in the middle of even brightness, no need for filtering + return getHardShadow(shadowsampler, smTexCoord.xy, realDistance); + } + + float baseLength = getBaseLength(smTexCoord); + float perspectiveFactor; float texture_size = 1.0 / (f_textureresolution * 0.5); - int init_offset = int(floor(mod(((smTexCoord.x * 34.0) + 1.0) * smTexCoord.y, 64.0-PCFSAMPLES))); - int end_offset = int(PCFSAMPLES) + init_offset; + int samples = int(clamp(PCFSAMPLES * (1 - baseLength) * (1 - baseLength), PCFSAMPLES / 4, PCFSAMPLES)); + int init_offset = int(floor(mod(((smTexCoord.x * 34.0) + 1.0) * smTexCoord.y, 64.0-samples))); + int end_offset = int(samples) + init_offset; for (int x = init_offset; x < end_offset; x++) { - clampedpos = poissonDisk[x] * texture_size * SOFTSHADOWRADIUS + smTexCoord.xy; + clampedpos = poissonDisk[x]; + perspectiveFactor = getDeltaPerspectiveFactor(baseLength + length(clampedpos) * texture_size * radius); + clampedpos = clampedpos * texture_size * perspectiveFactor * radius * perspectiveFactor + smTexCoord.xy; visibility += getHardShadow(shadowsampler, clampedpos.xy, realDistance); } - return visibility / PCFSAMPLES; + return visibility / samples; } #endif @@ -281,19 +346,31 @@ vec4 getShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDistance { vec2 clampedpos; vec4 visibility = vec4(0.0); - float sradius=0.0; - if( PCFBOUND>0) - sradius = SOFTSHADOWRADIUS / PCFBOUND; - float texture_size = 1.0 / (f_textureresolution * 0.5); - float y, x; - // basic PCF filter - for (y = -PCFBOUND; y <= PCFBOUND; y += 1.0) - for (x = -PCFBOUND; x <= PCFBOUND; x += 1.0) { - clampedpos = vec2(x,y) * texture_size* sradius + smTexCoord.xy; - visibility += getHardShadowColor(shadowsampler, clampedpos.xy, realDistance); + float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance, 1.0); + if (radius < 0.1) { + // we are in the middle of even brightness, no need for filtering + return getHardShadowColor(shadowsampler, smTexCoord.xy, realDistance); } - return visibility / PCFSAMPLES; + float baseLength = getBaseLength(smTexCoord); + float perspectiveFactor; + + float texture_size = 1.0 / (f_textureresolution * 0.5); + float y, x; + float bound = clamp(PCFBOUND * (1 - baseLength), PCFBOUND / 2, PCFBOUND); + int n = 0; + + // basic PCF filter + for (y = -bound; y <= bound; y += 1.0) + for (x = -bound; x <= bound; x += 1.0) { + clampedpos = vec2(x,y); // screen offset + perspectiveFactor = getDeltaPerspectiveFactor(baseLength + length(clampedpos) * texture_size * radius / bound); + clampedpos = clampedpos * texture_size * perspectiveFactor * radius * perspectiveFactor / bound + smTexCoord.xy; // both dx,dy and radius are adjusted + visibility += getHardShadowColor(shadowsampler, clampedpos.xy, realDistance); + n += 1; + } + + return visibility / n; } #else @@ -301,20 +378,31 @@ float getShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) { vec2 clampedpos; float visibility = 0.0; - float sradius=0.0; - if( PCFBOUND>0) - sradius = SOFTSHADOWRADIUS / PCFBOUND; - - float texture_size = 1.0 / (f_textureresolution * 0.5); - float y, x; - // basic PCF filter - for (y = -PCFBOUND; y <= PCFBOUND; y += 1.0) - for (x = -PCFBOUND; x <= PCFBOUND; x += 1.0) { - clampedpos = vec2(x,y) * texture_size * sradius + smTexCoord.xy; - visibility += getHardShadow(shadowsampler, clampedpos.xy, realDistance); + float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance, 1.0); + if (radius < 0.1) { + // we are in the middle of even brightness, no need for filtering + return getHardShadow(shadowsampler, smTexCoord.xy, realDistance); } - return visibility / PCFSAMPLES; + float baseLength = getBaseLength(smTexCoord); + float perspectiveFactor; + + float texture_size = 1.0 / (f_textureresolution * 0.5); + float y, x; + float bound = clamp(PCFBOUND * (1 - baseLength), PCFBOUND / 2, PCFBOUND); + int n = 0; + + // basic PCF filter + for (y = -bound; y <= bound; y += 1.0) + for (x = -bound; x <= bound; x += 1.0) { + clampedpos = vec2(x,y); // screen offset + perspectiveFactor = getDeltaPerspectiveFactor(baseLength + length(clampedpos) * texture_size * radius / bound); + clampedpos = clampedpos * texture_size * perspectiveFactor * radius * perspectiveFactor / bound + smTexCoord.xy; // both dx,dy and radius are adjusted + visibility += getHardShadow(shadowsampler, clampedpos.xy, realDistance); + n += 1; + } + + return visibility / n; } #endif @@ -322,12 +410,46 @@ float getShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) #endif #endif +#if ENABLE_TONE_MAPPING + +/* Hable's UC2 Tone mapping parameters + A = 0.22; + B = 0.30; + C = 0.10; + D = 0.20; + E = 0.01; + F = 0.30; + W = 11.2; + equation used: ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F +*/ + +vec3 uncharted2Tonemap(vec3 x) +{ + return ((x * (0.22 * x + 0.03) + 0.002) / (x * (0.22 * x + 0.3) + 0.06)) - 0.03333; +} + +vec4 applyToneMapping(vec4 color) +{ + color = vec4(pow(color.rgb, vec3(2.2)), color.a); + const float gamma = 1.6; + const float exposureBias = 5.5; + color.rgb = uncharted2Tonemap(exposureBias * color.rgb); + // Precalculated white_scale from + //vec3 whiteScale = 1.0 / uncharted2Tonemap(vec3(W)); + vec3 whiteScale = vec3(1.036015346); + color.rgb *= whiteScale; + return vec4(pow(color.rgb, vec3(1.0 / gamma)), color.a); +} +#endif + + + void main(void) { vec3 color; vec2 uv = varTexCoord.st; - vec4 base = texture2D(baseTexture, uv).rgba; + vec4 base = texture2D(baseTexture, uv).rgba; // If alpha is zero, we can just discard the pixel. This fixes transparency // on GPUs like GC7000L, where GL_ALPHA_TEST is not implemented in mesa, // and also on GLES 2, where GL_ALPHA_TEST is missing entirely. @@ -341,34 +463,66 @@ void main(void) #endif color = base.rgb; - vec4 col = vec4(color.rgb, base.a); - col.rgb *= varColor.rgb; - col.rgb *= emissiveColor.rgb * vIDiff; + vec4 col = vec4(color.rgb * varColor.rgb, 1.0); + col.rgb *= vIDiff; #ifdef ENABLE_DYNAMIC_SHADOWS - float shadow_int = 0.0; - vec3 shadow_color = vec3(0.0, 0.0, 0.0); - vec3 posLightSpace = getLightSpacePosition(); + if (f_shadow_strength > 0.0) { + float shadow_int = 0.0; + vec3 shadow_color = vec3(0.0, 0.0, 0.0); + vec3 posLightSpace = getLightSpacePosition(); + + float distance_rate = (1.0 - pow(clamp(2.0 * length(posLightSpace.xy - 0.5),0.0,1.0), 10.0)); + if (max(abs(posLightSpace.x - 0.5), abs(posLightSpace.y - 0.5)) > 0.5) + distance_rate = 0.0; + float f_adj_shadow_strength = max(adj_shadow_strength-mtsmoothstep(0.9,1.1, posLightSpace.z),0.0); + + if (distance_rate > 1e-7) { #ifdef COLORED_SHADOWS - vec4 visibility = getShadowColor(ShadowMapSampler, posLightSpace.xy, posLightSpace.z); - shadow_int = visibility.r; - shadow_color = visibility.gba; + vec4 visibility; + if (cosLight > 0.0 || f_normal_length < 1e-3) + visibility = getShadowColor(ShadowMapSampler, posLightSpace.xy, posLightSpace.z); + else + visibility = vec4(1.0, 0.0, 0.0, 0.0); + shadow_int = visibility.r; + shadow_color = visibility.gba; #else - shadow_int = getShadow(ShadowMapSampler, posLightSpace.xy, posLightSpace.z); + if (cosLight > 0.0 || f_normal_length < 1e-3) + if (cosLight > 0.0) + shadow_int = getShadow(ShadowMapSampler, posLightSpace.xy, posLightSpace.z); + else + shadow_int = 1.0; #endif + shadow_int *= distance_rate; + shadow_int = clamp(shadow_int, 0.0, 1.0); - if (f_normal_length != 0 && cosLight <= 0.001) { - shadow_int = clamp(shadow_int + 0.5 * abs(cosLight), 0.0, 1.0); + } + + // turns out that nightRatio falls off much faster than + // actual brightness of artificial light in relation to natual light. + // Power ratio was measured on torches in MTG (brightness = 14). + float adjusted_night_ratio = pow(max(0.0, nightRatio), 0.6); + + // Apply self-shadowing when light falls at a narrow angle to the surface + // Cosine of the cut-off angle. + const float self_shadow_cutoff_cosine = 0.14; + if (f_normal_length != 0 && cosLight < self_shadow_cutoff_cosine) { + shadow_int = max(shadow_int, 1 - clamp(cosLight, 0.0, self_shadow_cutoff_cosine)/self_shadow_cutoff_cosine); + shadow_color = mix(vec3(0.0), shadow_color, min(cosLight, self_shadow_cutoff_cosine)/self_shadow_cutoff_cosine); + } + + shadow_int *= f_adj_shadow_strength; + + // calculate fragment color from components: + col.rgb = + adjusted_night_ratio * col.rgb + // artificial light + (1.0 - adjusted_night_ratio) * ( // natural light + col.rgb * (1.0 - shadow_int * (1.0 - shadow_color)) + // filtered texture color + dayLight * shadow_color * shadow_int); // reflected filtered sunlight/moonlight } - - shadow_int = 1.0 - (shadow_int * adj_shadow_strength); - - col.rgb = mix(shadow_color, col.rgb, shadow_int) * shadow_int; #endif - - #if ENABLE_TONE_MAPPING col = applyToneMapping(col); #endif @@ -385,6 +539,7 @@ void main(void) float clarity = clamp(fogShadingParameter - fogShadingParameter * length(eyeVec) / fogDistance, 0.0, 1.0); col = mix(skyBgColor, col, clarity); - - gl_FragColor = vec4(col.rgb, base.a); + col = vec4(col.rgb, base.a); + + gl_FragColor = col; } diff --git a/client/shaders/object_shader/opengl_vertex.glsl b/client/shaders/object_shader/opengl_vertex.glsl index f135ab9dc..6dc25f854 100644 --- a/client/shaders/object_shader/opengl_vertex.glsl +++ b/client/shaders/object_shader/opengl_vertex.glsl @@ -1,7 +1,10 @@ uniform mat4 mWorld; - +uniform vec3 dayLight; uniform vec3 eyePosition; uniform float animationTimer; +uniform vec4 emissiveColor; +uniform vec3 cameraOffset; + varying vec3 vNormal; varying vec3 vPosition; @@ -21,19 +24,53 @@ centroid varying vec2 varTexCoord; uniform float f_shadowfar; uniform float f_shadow_strength; uniform float f_timeofday; + uniform vec4 CameraPos; + varying float cosLight; - varying float normalOffsetScale; varying float adj_shadow_strength; varying float f_normal_length; + varying vec3 shadow_position; #endif varying vec3 eyeVec; +varying float nightRatio; +// Color of the light emitted by the light sources. +const vec3 artificialLight = vec3(1.04, 1.04, 1.04); varying float vIDiff; - const float e = 2.718281828459; const float BS = 10.0; +uniform float xyPerspectiveBias0; +uniform float xyPerspectiveBias1; +uniform float zPerspectiveBias; #ifdef ENABLE_DYNAMIC_SHADOWS + +vec4 getRelativePosition(in vec4 position) +{ + vec2 l = position.xy - CameraPos.xy; + vec2 s = l / abs(l); + s = (1.0 - s * CameraPos.xy); + l /= s; + return vec4(l, s); +} + +float getPerspectiveFactor(in vec4 relativePosition) +{ + float pDistance = length(relativePosition.xy); + float pFactor = pDistance * xyPerspectiveBias0 + xyPerspectiveBias1; + return pFactor; +} + +vec4 applyPerspectiveDistortion(in vec4 position) +{ + vec4 l = getRelativePosition(position); + float pFactor = getPerspectiveFactor(l); + l.xy /= pFactor; + position.xy = l.xy * l.zw + CameraPos.xy; + position.z *= zPerspectiveBias; + return position; +} + // custom smoothstep implementation because it's not defined in glsl1.2 // https://docs.gl/sl4/smoothstep float mtsmoothstep(in float edge0, in float edge1, in float x) @@ -60,7 +97,7 @@ void main(void) gl_Position = mWorldViewProj * inVertexPosition; vPosition = gl_Position.xyz; - vNormal = inVertexNormal; + vNormal = (mWorld * vec4(inVertexNormal, 0.0)).xyz; worldPosition = (mWorld * inVertexPosition).xyz; eyeVec = -(mWorldView * inVertexPosition).xyz; @@ -75,29 +112,68 @@ void main(void) #endif #ifdef GL_ES - varColor = inVertexColor.bgra; + vec4 color = inVertexColor.bgra; #else - varColor = inVertexColor; + vec4 color = inVertexColor; #endif + color *= emissiveColor; + + // The alpha gives the ratio of sunlight in the incoming light. + nightRatio = 1.0 - color.a; + color.rgb = color.rgb * (color.a * dayLight.rgb + + nightRatio * artificialLight.rgb) * 2.0; + color.a = 1.0; + + // Emphase blue a bit in darker places + // See C++ implementation in mapblock_mesh.cpp final_color_blend() + float brightness = (color.r + color.g + color.b) / 3.0; + color.b += max(0.0, 0.021 - abs(0.2 * brightness - 0.021) + + 0.07 * brightness); + + varColor = clamp(color, 0.0, 1.0); + + #ifdef ENABLE_DYNAMIC_SHADOWS + if (f_shadow_strength > 0.0) { + vec3 nNormal = normalize(vNormal); + f_normal_length = length(vNormal); - cosLight = max(0.0, dot(vNormal, -v_LightDirection)); - float texelSize = 0.51; - float slopeScale = clamp(1.0 - cosLight, 0.0, 1.0); - normalOffsetScale = texelSize * slopeScale; - if (f_timeofday < 0.2) { - adj_shadow_strength = f_shadow_strength * 0.5 * - (1.0 - mtsmoothstep(0.18, 0.2, f_timeofday)); - } else if (f_timeofday >= 0.8) { - adj_shadow_strength = f_shadow_strength * 0.5 * - mtsmoothstep(0.8, 0.83, f_timeofday); - } else { - adj_shadow_strength = f_shadow_strength * - mtsmoothstep(0.20, 0.25, f_timeofday) * - (1.0 - mtsmoothstep(0.7, 0.8, f_timeofday)); + /* normalOffsetScale is in world coordinates (1/10th of a meter) + z_bias is in light space coordinates */ + float normalOffsetScale, z_bias; + float pFactor = getPerspectiveFactor(getRelativePosition(m_ShadowViewProj * mWorld * inVertexPosition)); + if (f_normal_length > 0.0) { + nNormal = normalize(vNormal); + cosLight = dot(nNormal, -v_LightDirection); + float sinLight = pow(1 - pow(cosLight, 2.0), 0.5); + normalOffsetScale = 0.1 * pFactor * pFactor * sinLight * min(f_shadowfar, 500.0) / + xyPerspectiveBias1 / f_textureresolution; + z_bias = 1e3 * sinLight / cosLight * (0.5 + f_textureresolution / 1024.0); + } + else { + nNormal = vec3(0.0); + cosLight = clamp(dot(v_LightDirection, normalize(vec3(v_LightDirection.x, 0.0, v_LightDirection.z))), 1e-2, 1.0); + float sinLight = pow(1 - pow(cosLight, 2.0), 0.5); + normalOffsetScale = 0.0; + z_bias = 3.6e3 * sinLight / cosLight; + } + z_bias *= pFactor * pFactor / f_textureresolution / f_shadowfar; + + shadow_position = applyPerspectiveDistortion(m_ShadowViewProj * mWorld * (inVertexPosition + vec4(normalOffsetScale * nNormal, 0.0))).xyz; + shadow_position.z -= z_bias; + + if (f_timeofday < 0.2) { + adj_shadow_strength = f_shadow_strength * 0.5 * + (1.0 - mtsmoothstep(0.18, 0.2, f_timeofday)); + } else if (f_timeofday >= 0.8) { + adj_shadow_strength = f_shadow_strength * 0.5 * + mtsmoothstep(0.8, 0.83, f_timeofday); + } else { + adj_shadow_strength = f_shadow_strength * + mtsmoothstep(0.20, 0.25, f_timeofday) * + (1.0 - mtsmoothstep(0.7, 0.8, f_timeofday)); + } } - f_normal_length = length(vNormal); - #endif } diff --git a/client/shaders/shadow_shaders/pass1_trans_fragment.glsl b/client/shaders/shadow_shaders/pass1_trans_fragment.glsl index 9f9e5be8c..b267c2214 100644 --- a/client/shaders/shadow_shaders/pass1_trans_fragment.glsl +++ b/client/shaders/shadow_shaders/pass1_trans_fragment.glsl @@ -2,6 +2,8 @@ uniform sampler2D ColorMapSampler; varying vec4 tPos; #ifdef COLORED_SHADOWS +varying vec3 varColor; + // c_precision of 128 fits within 7 base-10 digits const float c_precision = 128.0; const float c_precisionp1 = c_precision + 1.0; @@ -30,7 +32,9 @@ void main() //col.rgb = col.a == 1.0 ? vec3(1.0) : col.rgb; #ifdef COLORED_SHADOWS - float packedColor = packColor(mix(col.rgb, black, col.a)); + col.rgb *= varColor.rgb; + // premultiply color alpha (see-through side) + float packedColor = packColor(col.rgb * (1.0 - col.a)); gl_FragColor = vec4(depth, packedColor, 0.0,1.0); #else gl_FragColor = vec4(depth, 0.0, 0.0, 1.0); diff --git a/client/shaders/shadow_shaders/pass1_trans_vertex.glsl b/client/shaders/shadow_shaders/pass1_trans_vertex.glsl index ca59f2796..244d2562a 100644 --- a/client/shaders/shadow_shaders/pass1_trans_vertex.glsl +++ b/client/shaders/shadow_shaders/pass1_trans_vertex.glsl @@ -1,26 +1,50 @@ uniform mat4 LightMVP; // world matrix +uniform vec4 CameraPos; varying vec4 tPos; +#ifdef COLORED_SHADOWS +varying vec3 varColor; +#endif -const float bias0 = 0.9; -const float zPersFactor = 0.5; -const float bias1 = 1.0 - bias0 + 1e-6; +uniform float xyPerspectiveBias0; +uniform float xyPerspectiveBias1; +uniform float zPerspectiveBias; -vec4 getPerspectiveFactor(in vec4 shadowPosition) +vec4 getRelativePosition(in vec4 position) { - float pDistance = length(shadowPosition.xy); - float pFactor = pDistance * bias0 + bias1; - shadowPosition.xyz *= vec3(vec2(1.0 / pFactor), zPersFactor); - - return shadowPosition; + vec2 l = position.xy - CameraPos.xy; + vec2 s = l / abs(l); + s = (1.0 - s * CameraPos.xy); + l /= s; + return vec4(l, s); } +float getPerspectiveFactor(in vec4 relativePosition) +{ + float pDistance = length(relativePosition.xy); + float pFactor = pDistance * xyPerspectiveBias0 + xyPerspectiveBias1; + return pFactor; +} + +vec4 applyPerspectiveDistortion(in vec4 position) +{ + vec4 l = getRelativePosition(position); + float pFactor = getPerspectiveFactor(l); + l.xy /= pFactor; + position.xy = l.xy * l.zw + CameraPos.xy; + position.z *= zPerspectiveBias; + return position; +} void main() { vec4 pos = LightMVP * gl_Vertex; - tPos = getPerspectiveFactor(LightMVP * gl_Vertex); + tPos = applyPerspectiveDistortion(LightMVP * gl_Vertex); gl_Position = vec4(tPos.xyz, 1.0); gl_TexCoord[0].st = gl_MultiTexCoord0.st; + +#ifdef COLORED_SHADOWS + varColor = gl_Color.rgb; +#endif } diff --git a/client/shaders/shadow_shaders/pass1_vertex.glsl b/client/shaders/shadow_shaders/pass1_vertex.glsl index a6d8b3db8..1dceb93c6 100644 --- a/client/shaders/shadow_shaders/pass1_vertex.glsl +++ b/client/shaders/shadow_shaders/pass1_vertex.glsl @@ -1,26 +1,43 @@ uniform mat4 LightMVP; // world matrix +uniform vec4 CameraPos; // camera position varying vec4 tPos; -const float bias0 = 0.9; -const float zPersFactor = 0.5; -const float bias1 = 1.0 - bias0 + 1e-6; +uniform float xyPerspectiveBias0; +uniform float xyPerspectiveBias1; +uniform float zPerspectiveBias; -vec4 getPerspectiveFactor(in vec4 shadowPosition) +vec4 getRelativePosition(in vec4 position) { - float pDistance = length(shadowPosition.xy); - float pFactor = pDistance * bias0 + bias1; - shadowPosition.xyz *= vec3(vec2(1.0 / pFactor), zPersFactor); - - return shadowPosition; + vec2 l = position.xy - CameraPos.xy; + vec2 s = l / abs(l); + s = (1.0 - s * CameraPos.xy); + l /= s; + return vec4(l, s); } +float getPerspectiveFactor(in vec4 relativePosition) +{ + float pDistance = length(relativePosition.xy); + float pFactor = pDistance * xyPerspectiveBias0 + xyPerspectiveBias1; + return pFactor; +} + +vec4 applyPerspectiveDistortion(in vec4 position) +{ + vec4 l = getRelativePosition(position); + float pFactor = getPerspectiveFactor(l); + l.xy /= pFactor; + position.xy = l.xy * l.zw + CameraPos.xy; + position.z *= zPerspectiveBias; + return position; +} void main() { vec4 pos = LightMVP * gl_Vertex; - tPos = getPerspectiveFactor(pos); + tPos = applyPerspectiveDistortion(pos); gl_Position = vec4(tPos.xyz, 1.0); - gl_TexCoord[0].st = gl_MultiTexCoord0.st; + gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0; } diff --git a/clientmods/preview/mod.conf b/clientmods/preview/mod.conf index 4e56ec293..23a5c3e90 100644 --- a/clientmods/preview/mod.conf +++ b/clientmods/preview/mod.conf @@ -1 +1 @@ -name = preview +name = preview diff --git a/cmake/Modules/FindOpenGLES2.cmake b/cmake/Modules/FindOpenGLES2.cmake deleted file mode 100644 index ce04191dd..000000000 --- a/cmake/Modules/FindOpenGLES2.cmake +++ /dev/null @@ -1,73 +0,0 @@ -#------------------------------------------------------------------- -# The contents of this file are placed in the public domain. Feel -# free to make use of it in any way you like. -#------------------------------------------------------------------- - -# - Try to find OpenGLES and EGL -# Once done this will define -# -# OPENGLES2_FOUND - system has OpenGLES -# OPENGLES2_INCLUDE_DIR - the GL include directory -# OPENGLES2_LIBRARIES - Link these to use OpenGLES -# -# EGL_FOUND - system has EGL -# EGL_INCLUDE_DIR - the EGL include directory -# EGL_LIBRARIES - Link these to use EGL - -# Win32 and Apple are not tested! -# Linux tested and works - -if(WIN32) - find_path(OPENGLES2_INCLUDE_DIR GLES2/gl2.h) - find_library(OPENGLES2_LIBRARY libGLESv2) -elseif(APPLE) - create_search_paths(/Developer/Platforms) - findpkg_framework(OpenGLES2) - set(OPENGLES2_LIBRARY "-framework OpenGLES") -else() - # Unix - find_path(OPENGLES2_INCLUDE_DIR GLES2/gl2.h - PATHS /usr/openwin/share/include - /opt/graphics/OpenGL/include - /usr/X11R6/include - /usr/include - ) - - find_library(OPENGLES2_LIBRARY - NAMES GLESv2 - PATHS /opt/graphics/OpenGL/lib - /usr/openwin/lib - /usr/X11R6/lib - /usr/lib - ) - - include(FindPackageHandleStandardArgs) - find_package_handle_standard_args(OpenGLES2 DEFAULT_MSG OPENGLES2_LIBRARY OPENGLES2_INCLUDE_DIR) - - find_path(EGL_INCLUDE_DIR EGL/egl.h - PATHS /usr/openwin/share/include - /opt/graphics/OpenGL/include - /usr/X11R6/include - /usr/include - ) - - find_library(EGL_LIBRARY - NAMES EGL - PATHS /opt/graphics/OpenGL/lib - /usr/openwin/lib - /usr/X11R6/lib - /usr/lib - ) - - find_package_handle_standard_args(EGL DEFAULT_MSG EGL_LIBRARY EGL_INCLUDE_DIR) -endif() - -set(OPENGLES2_LIBRARIES ${OPENGLES2_LIBRARY}) -set(EGL_LIBRARIES ${EGL_LIBRARY}) - -mark_as_advanced( - OPENGLES2_INCLUDE_DIR - OPENGLES2_LIBRARY - EGL_INCLUDE_DIR - EGL_LIBRARY -) diff --git a/cmake/Modules/FindSQLite3.cmake b/cmake/Modules/FindSQLite3.cmake index b23553a80..8a66cb241 100644 --- a/cmake/Modules/FindSQLite3.cmake +++ b/cmake/Modules/FindSQLite3.cmake @@ -1,4 +1,4 @@ -mark_as_advanced(SQLITE3_LIBRARY SQLITE3_INCLUDE_DIR) +mark_as_advanced(SQLITE3_LIBRARY SQLITE3_INCLUDE_DIR) find_path(SQLITE3_INCLUDE_DIR sqlite3.h) diff --git a/cmake/Modules/FindVorbis.cmake b/cmake/Modules/FindVorbis.cmake index e5fe7f25e..222ddd9d5 100644 --- a/cmake/Modules/FindVorbis.cmake +++ b/cmake/Modules/FindVorbis.cmake @@ -29,18 +29,17 @@ else(NOT GP2XWIZ) find_package_handle_standard_args(Vorbis DEFAULT_MSG VORBIS_INCLUDE_DIR VORBIS_LIBRARY) endif(NOT GP2XWIZ) - + if(VORBIS_FOUND) - if(NOT GP2XWIZ) - set(VORBIS_LIBRARIES ${VORBISFILE_LIBRARY} ${VORBIS_LIBRARY} - ${OGG_LIBRARY}) - else(NOT GP2XWIZ) - set(VORBIS_LIBRARIES ${VORBIS_LIBRARY}) - endif(NOT GP2XWIZ) + if(NOT GP2XWIZ) + set(VORBIS_LIBRARIES ${VORBISFILE_LIBRARY} ${VORBIS_LIBRARY} + ${OGG_LIBRARY}) + else(NOT GP2XWIZ) + set(VORBIS_LIBRARIES ${VORBIS_LIBRARY}) + endif(NOT GP2XWIZ) else(VORBIS_FOUND) - set(VORBIS_LIBRARIES) + set(VORBIS_LIBRARIES) endif(VORBIS_FOUND) mark_as_advanced(OGG_INCLUDE_DIR VORBIS_INCLUDE_DIR) mark_as_advanced(OGG_LIBRARY VORBIS_LIBRARY VORBISFILE_LIBRARY) - diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in index d7816f0e4..ae36fd6bf 100644 --- a/doc/Doxyfile.in +++ b/doc/Doxyfile.in @@ -16,7 +16,6 @@ PREDEFINED = "USE_SPATIAL=1" \ "USE_REDIS=1" \ "USE_SOUND=1" \ "USE_CURL=1" \ - "USE_FREETYPE=1" \ "USE_GETTEXT=1" # Input diff --git a/doc/breakages.md b/doc/breakages.md new file mode 100644 index 000000000..f7078f1e9 --- /dev/null +++ b/doc/breakages.md @@ -0,0 +1,8 @@ +# Minetest Major Breakages List + +This document contains a list of breaking changes to be made in the next major version. + +* Remove attachment space multiplier (*10) +* `get_sky()` returns a table (without arg) +* `game.conf` name/id mess +* remove `depends.txt` / `description.txt` (would simplify ContentDB and Minetest code a little) diff --git a/doc/client_lua_api.txt b/doc/client_lua_api.txt index 40e0327e4..75e40945f 100644 --- a/doc/client_lua_api.txt +++ b/doc/client_lua_api.txt @@ -1,4 +1,4 @@ -Minetest Lua Client Modding API Reference 5.5.0 +Minetest Lua Client Modding API Reference 5.6.0 ================================================ * More information at * Developer Wiki: @@ -587,6 +587,7 @@ Spatial Vectors * `vector.floor(v)`: returns a vector, each dimension rounded down * `vector.round(v)`: returns a vector, each dimension rounded to nearest int * `vector.apply(v, func)`: returns a vector +* `vector.combine(v, w, func)`: returns a vector * `vector.equals(v1, v2)`: returns a boolean For the following functions `x` can be either a vector or a number: @@ -1276,8 +1277,8 @@ Methods: * returns true if player is in a liquid (This oscillates so that the player jumps a bit above the surface) * `is_in_liquid_stable()` * returns true if player is in a stable liquid (This is more stable and defines the maximum speed of the player) -* `get_liquid_viscosity()` - * returns liquid viscosity (Gets the viscosity of liquid to calculate friction) +* `get_move_resistance()` + * returns move resistance of current node, the higher the slower the player moves * `is_climbing()` * returns true if player is climbing * `swimming_vertical()` @@ -1581,7 +1582,7 @@ It can be created via `Raycast(pos1, pos2, objects, liquids)` or liquid_type = , -- A string containing "none", "flowing", or "source" *May not exist* liquid_alternative_flowing = , -- Alternative node for liquid *May not exist* liquid_alternative_source = , -- Alternative node for liquid *May not exist* - liquid_viscosity = , -- How fast the liquid flows *May not exist* + liquid_viscosity = , -- How slow the liquid flows *May not exist* liquid_renewable = , -- Whether the liquid makes an infinite source *May not exist* liquid_range = , -- How far the liquid flows *May not exist* drowning = bool, -- Whether the player will drown in the node @@ -1596,6 +1597,7 @@ It can be created via `Raycast(pos1, pos2, objects, liquids)` or }, legacy_facedir_simple = bool, -- Whether to use old facedir legacy_wallmounted = bool -- Whether to use old wallmounted + move_resistance = , -- How slow players can move through the node *May not exist* } ``` diff --git a/doc/lgpl-2.1.txt b/doc/lgpl-2.1.txt index 4362b4915..e5ab03e12 100644 --- a/doc/lgpl-2.1.txt +++ b/doc/lgpl-2.1.txt @@ -55,7 +55,7 @@ modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. - + Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a @@ -111,7 +111,7 @@ modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. - + GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION @@ -158,7 +158,7 @@ Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. - + 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 @@ -216,7 +216,7 @@ instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. - + Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. @@ -267,7 +267,7 @@ Library will still fall under Section 6.) distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. - + 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work @@ -329,7 +329,7 @@ restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. - + 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined @@ -370,7 +370,7 @@ subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. - + 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or @@ -422,7 +422,7 @@ conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. - + 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is @@ -456,7 +456,7 @@ SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS - + How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest diff --git a/doc/lua_api.txt b/doc/lua_api.txt index a9bcc1707..8aea490cf 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -78,7 +78,7 @@ The game directory can contain the following files: * `disallowed_mapgen_settings= ` e.g. `disallowed_mapgen_settings = mgv5_spflags` These mapgen settings are hidden for this game in the world creation - dialog and game start menu. + dialog and game start menu. Add `seed` to hide the seed input field. * `disabled_settings = ` e.g. `disabled_settings = enable_damage, creative_mode` These settings are hidden for this game in the "Start game" tab @@ -113,8 +113,16 @@ If you want to specify multiple images for one identifier, add additional images named like `$identifier.$n.png`, with an ascending number $n starting with 1, and a random image will be chosen from the provided ones. +Menu music +----------- +Games can provide custom main menu music. They are put inside a `menu` +directory inside the game directory. +The music files are named `theme.ogg`. +If you want to specify multiple music files for one game, add additional +images named like `theme.$n.ogg`, with an ascending number $n starting +with 1 (max 10), and a random music file will be chosen from the provided ones. Mods ==== @@ -628,6 +636,23 @@ Result is more like what you'd expect if you put a color on top of another color, meaning white surfaces get a lot of your new color while black parts don't change very much. +#### `[png:` + +Embed a base64 encoded PNG image in the texture string. +You can produce a valid string for this by calling +`minetest.encode_base64(minetest.encode_png(tex))`, +refer to the documentation of these functions for details. +You can use this to send disposable images such as captchas +to individual clients, or render things that would be too +expensive to compose with `[combine:`. + +IMPORTANT: Avoid sending large images this way. +This is not a replacement for asset files, do not use it to do anything +that you could instead achieve by just using a file. +In particular consider `minetest.dynamic_add_media` and test whether +using other texture modifiers could result in a shorter string than +embedding a whole image, this may vary by use case. + Hardware coloring ----------------- @@ -1001,7 +1026,7 @@ The function of `param1` is determined by `paramtype` in node definition. `param1` is reserved for the engine when `paramtype != "none"`. * `paramtype = "light"` - * The value stores light with and without sun in its upper and lower 4 bits + * The value stores light with and without sun in its lower and upper 4 bits respectively. * Required by a light source node to enable spreading its light. * Required by the following drawtypes as they determine their visual @@ -1519,15 +1544,12 @@ Displays a minimap on the HUD. Representations of simple things ================================ -Position/vector ---------------- +Vector (ie. a position) +----------------------- - {x=num, y=num, z=num} + vector.new(x, y, z) - Note: it is highly recommended to construct a vector using the helper function: - vector.new(num, num, num) - -For helper functions see [Spatial Vectors]. +See [Spatial Vectors] for details. `pointed_thing` --------------- @@ -1548,8 +1570,7 @@ Exact pointing location (currently only `Raycast` supports these fields): from 1). * `pointed_thing.intersection_normal`: Unit vector, points outwards of the selected selection box. This specifies which face is pointed at. - Is a null vector `{x = 0, y = 0, z = 0}` when the pointer is inside the - selection box. + Is a null vector `vector.zero()` when the pointer is inside the selection box. @@ -1658,10 +1679,10 @@ wear value. Syntax: Examples: -* `'default:apple'`: 1 apple -* `'default:dirt 5'`: 5 dirt -* `'default:pick_stone'`: a new stone pickaxe -* `'default:pick_wood 1 21323'`: a wooden pickaxe, ca. 1/3 worn out +* `"default:apple"`: 1 apple +* `"default:dirt 5"`: 5 dirt +* `"default:pick_stone"`: a new stone pickaxe +* `"default:pick_wood 1 21323"`: a wooden pickaxe, ca. 1/3 worn out ### Table format @@ -1751,21 +1772,21 @@ Groups in crafting recipes An example: Make meat soup from any meat, any water and any bowl: { - output = 'food:meat_soup_raw', + output = "food:meat_soup_raw", recipe = { - {'group:meat'}, - {'group:water'}, - {'group:bowl'}, + {"group:meat"}, + {"group:water"}, + {"group:bowl"}, }, - -- preserve = {'group:bowl'}, -- Not implemented yet (TODO) + -- preserve = {"group:bowl"}, -- Not implemented yet (TODO) } Another example: Make red wool from white wool and red dye: { - type = 'shapeless', - output = 'wool:red', - recipe = {'wool:white', 'group:dye,basecolor_red'}, + type = "shapeless", + output = "wool:red", + recipe = {"wool:white", "group:dye,basecolor_red"}, } Special groups @@ -1804,7 +1825,7 @@ to games. - (14) -- constant tolerance 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 +* `float`: the node will not fall through liquids (`liquidtype ~= "none"`) * `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 @@ -1936,8 +1957,9 @@ to implement this. ### Uses (tools only) Determines how many uses the tool has when it is used for digging a node, -of this group, of the maximum level. For lower leveled nodes, the use count -is multiplied by `3^leveldiff`. +of this group, of the maximum level. The maximum supported number of +uses is 65535. The special number 0 is used for infinite uses. +For lower leveled nodes, the use count is multiplied by `3^leveldiff`. `leveldiff` is the difference of the tool's `maxlevel` `groupcaps` and the node's `level` group. The node cannot be dug if `leveldiff` is less than zero. @@ -2148,6 +2170,13 @@ Some of the values in the key-value store are handled specially: * `color`: A `ColorString`, which sets the stack's color. * `palette_index`: If the item has a palette, this is used to get the current color from the palette. +* `count_meta`: Replace the displayed count with any string. +* `count_alignment`: Set the alignment of the displayed count value. This is an + int value. The lowest 2 bits specify the alignment in x-direction, the 3rd and + 4th bit specify the alignment in y-direction: + 0 = default, 1 = left / up, 2 = middle, 3 = right / down + The default currently is the same as right/down. + Example: 6 = 2 + 1*4 = middle,up Example: @@ -2155,6 +2184,21 @@ Example: meta:set_string("key", "value") print(dump(meta:to_table())) +Example manipulations of "description" and expected output behaviors: + + print(ItemStack("default:pick_steel"):get_description()) --> Steel Pickaxe + print(ItemStack("foobar"):get_description()) --> Unknown Item + + local stack = ItemStack("default:stone") + stack:get_meta():set_string("description", "Custom description\nAnother line") + print(stack:get_description()) --> Custom description\nAnother line + print(stack:get_short_description()) --> Custom description + + stack:get_meta():set_string("short_description", "Short") + print(stack:get_description()) --> Custom description\nAnother line + print(stack:get_short_description()) --> Short + + print(ItemStack("mod:item_with_no_desc"):get_description()) --> mod:item_with_no_desc @@ -2230,18 +2274,20 @@ Examples Version History --------------- -* FORMSPEC VERSION 1: +* Formspec version 1 (pre-5.1.0): * (too much) -* FORMSPEC VERSION 2: +* Formspec version 2 (5.1.0): * Forced real coordinates * background9[]: 9-slice scaling parameters -* FORMSPEC VERSION 3: +* Formspec version 3 (5.2.0): * Formspec elements are drawn in the order of definition * bgcolor[]: use 3 parameters (bgcolor, formspec (now an enum), fbgcolor) * box[] and image[] elements enable clipping by default * new element: scroll_container[] -* FORMSPEC VERSION 4: +* Formspec version 4 (5.4.0): * Allow dropdown indexing events +* Formspec version 5 (5.5.0): + * Added padding[] element Elements -------- @@ -2285,9 +2331,20 @@ Elements * `position` and `anchor` elements need suitable values to avoid a formspec extending off the game window due to particular game window sizes. -### `no_prepend[]` +### `padding[,]` * Must be used after the `size`, `position`, and `anchor` elements (if present). +* Defines how much space is padded around the formspec if the formspec tries to + increase past the size of the screen and coordinates have to be shrunk. +* For X and Y, 0.0 represents no padding (the formspec can touch the edge of the + screen), and 0.5 represents half the screen (which forces the coordinate size + to 0). If negative, the formspec can extend off the edge of the screen. +* Defaults to [0.05, 0.05]. + +### `no_prepend[]` + +* Must be used after the `size`, `position`, `anchor`, and `padding` elements + (if present). * Disables player:set_formspec_prepend() from applying to this formspec. ### `real_coordinates[]` @@ -2334,21 +2391,23 @@ Elements * End of a scroll_container, following elements are no longer bound to this container. -### `list[;;,;,;]` +### `list[;;,;,;]` -* Show an inventory list if it has been sent to the client. Nothing will - be shown if the inventory list is of size 0. +* Show an inventory list if it has been sent to the client. +* If the inventory list changes (eg. it didn't exist before, it's resized, or its items + are moved) while the formspec is open, the formspec element may (but is not guaranteed + to) adapt to the new inventory list. +* Item slots are drawn in a grid from left to right, then up to down, ordered + according to the slot index. +* `W` and `H` are in inventory slots, not in coordinates. +* `starting item index` (Optional): The index of the first (upper-left) item to draw. + Indices start at `0`. Default is `0`. +* The number of shown slots is the minimum of `W*H` and the inventory list's size minus + `starting item index`. * **Note**: With the new coordinate system, the spacing between inventory slots is one-fourth the size of an inventory slot by default. Also see [Styling Formspecs] for changing the size of slots and spacing. -### `list[;;,;,;]` - -* Show an inventory list if it has been sent to the client. Nothing will - be shown if the inventory list is of size 0. -* **Note**: With the new coordinate system, the spacing between inventory - slots is one-fourth the size of an inventory slot. - ### `listring[;]` * Allows to create a ring of inventory lists @@ -3189,7 +3248,7 @@ Colors `#RRGGBBAA` defines a color in hexadecimal format and alpha channel. Named colors are also supported and are equivalent to -[CSS Color Module Level 4](http://dev.w3.org/csswg/css-color/#named-colors). +[CSS Color Module Level 4](https://www.w3.org/TR/css-color-4/#named-color). To specify the value of the alpha channel, append `#A` or `#AA` to the end of the color name (e.g. `colorname#08`). @@ -3242,33 +3301,76 @@ The following functions provide escape sequences: Spatial Vectors =============== -A spatial vector is similar to a position, but instead using -absolute world coordinates, it uses *relative* coordinates, relative to -no particular point. -Internally, it is implemented as a table with the 3 fields -`x`, `y` and `z`. Example: `{x = 0, y = 1, z = 0}`. -However, one should *never* create a vector manually as above, such misbehavior -is deprecated. The vector helpers set a metatable for the created vectors which -allows indexing with numbers, calling functions directly on vectors and using -operators (like `+`). Furthermore, the internal implementation might change in +Minetest stores 3-dimensional spatial vectors in Lua as tables of 3 coordinates, +and has a class to represent them (`vector.*`), which this chapter is about. +For details on what a spatial vectors is, please refer to Wikipedia: +https://en.wikipedia.org/wiki/Euclidean_vector. + +Spatial vectors are used for various things, including, but not limited to: + +* any 3D spatial vector (x/y/z-directions) +* Euler angles (pitch/yaw/roll in radians) (Spatial vectors have no real semantic + meaning here. Therefore, most vector operations make no sense in this use case.) + +Note that they are *not* used for: + +* n-dimensional vectors where n is not 3 (ie. n=2) +* arrays of the form `{num, num, num}` + +The API documentation may refer to spatial vectors, as produced by `vector.new`, +by any of the following notations: + +* `(x, y, z)` (Used rarely, and only if it's clear that it's a vector.) +* `vector.new(x, y, z)` +* `{x=num, y=num, z=num}` (Even here you are still supposed to use `vector.new`.) + +Compatibility notes +------------------- + +Vectors used to be defined as tables of the form `{x = num, y = num, z = num}`. +Since Minetest 5.5.0, vectors additionally have a metatable to enable easier use. +Note: Those old-style vectors can still be found in old mod code. Hence, mod and +engine APIs still need to be able to cope with them in many places. + +Manually constructed tables are deprecated and highly discouraged. This interface +should be used to ensure seamless compatibility between mods and the Minetest API. +This is especially important to callback function parameters and functions overwritten +by mods. +Also, though not likely, the internal implementation of a vector might change in the future. -Old code might still use vectors without metatables, be aware of this! +In your own code, or if you define your own API, you can, of course, still use +other representations of vectors. + +Vectors provided by API functions will provide an instance of this class if not +stated otherwise. Mods should adapt this for convenience reasons. + +Special properties of the class +------------------------------- + +Vectors can be indexed with numbers and allow method and operator syntax. All these forms of addressing a vector `v` are valid: `v[1]`, `v[3]`, `v.x`, `v[1] = 42`, `v.y = 13` +Note: Prefer letter over number indexing for performance and compatibility reasons. Where `v` is a vector and `foo` stands for any function name, `v:foo(...)` does the same as `vector.foo(v, ...)`, apart from deprecated functionality. +`tostring` is defined for vectors, see `vector.to_string`. + The metatable that is used for vectors can be accessed via `vector.metatable`. Do not modify it! All `vector.*` functions allow vectors `{x = X, y = Y, z = Z}` without metatables. Returned vectors always have a metatable set. -For the following functions, `v`, `v1`, `v2` are vectors, -`p1`, `p2` are positions, +Common functions and methods +---------------------------- + +For the following functions (and subchapters), +`v`, `v1`, `v2` are vectors, +`p1`, `p2` are position vectors, `s` is a scalar (a number), vectors are written like this: `(x, y, z)`: @@ -3290,6 +3392,7 @@ vectors are written like this: `(x, y, z)`: * `init`: If given starts looking for the vector at this string index. * `vector.to_string(v)`: * Returns a string of the form `"(x, y, z)"`. + * `tostring(v)` does the same. * `vector.direction(p1, p2)`: * Returns a vector of length 1 with direction `p1` to `p2`. * If `p1` and `p2` are identical, returns `(0, 0, 0)`. @@ -3308,6 +3411,9 @@ vectors are written like this: `(x, y, z)`: * `vector.apply(v, func)`: * Returns a vector where the function `func` has been applied to each component. +* `vector.combine(v, w, func)`: + * Returns a vector where the function `func` has combined both components of `v` and `w` + for each component * `vector.equals(v1, v2)`: * Returns a boolean, `true` if the vectors are identical. * `vector.sort(v1, v2)`: @@ -3320,7 +3426,7 @@ vectors are written like this: `(x, y, z)`: * Returns the cross product of `v1` and `v2`. * `vector.offset(v, x, y, z)`: * Returns the sum of the vectors `v` and `(x, y, z)`. -* `vector.check()`: +* `vector.check(v)`: * Returns a boolean value indicating whether `v` is a real vector, eg. created by a `vector.*` function. * Returns `false` for anything else, including tables like `{x=3,y=1,z=4}`. @@ -3342,6 +3448,9 @@ For the following functions `x` can be either a vector or a number: * Returns a scaled vector. * Deprecated: If `s` is a vector: Returns the Schur quotient. +Operators +--------- + Operators can be used if all of the involved vectors have metatables: * `v1 == v2`: * Returns whether `v1` and `v2` are identical. @@ -3358,8 +3467,11 @@ Operators can be used if all of the involved vectors have metatables: * `v / s`: * Returns `v` scaled by `1 / s`. +Rotation-related functions +-------------------------- + For the following functions `a` is an angle in radians and `r` is a rotation -vector ({x = , y = , z = }) where pitch, yaw and roll are +vector (`{x = , y = , z = }`) where pitch, yaw and roll are angles in radians. * `vector.rotate(v, r)`: @@ -3376,6 +3488,18 @@ angles in radians. * If `up` is omitted, the roll of the returned vector defaults to zero. * Otherwise `direction` and `up` need to be vectors in a 90 degree angle to each other. +Further helpers +--------------- + +There are more helper functions involving vectors, but they are listed elsewhere +because they only work on specific sorts of vectors or involve things that are not +vectors. + +For example: + +* `minetest.hash_node_position` (Only works on node positions.) +* `minetest.dir_to_wallmounted` (Involves wallmounted param2 values.) + @@ -3460,8 +3584,8 @@ Helper functions * `minetest.pointed_thing_to_face_pos(placer, pointed_thing)`: returns a position. * returns the exact position on the surface of a pointed node -* `minetest.get_dig_params(groups, tool_capabilities)`: Simulates an item - that digs a node. +* `minetest.get_dig_params(groups, tool_capabilities [, wear])`: + Simulates an item that digs a node. Returns a table with the following fields: * `diggable`: `true` if node can be dug, `false` otherwise. * `time`: Time it would take to dig the node. @@ -3470,15 +3594,17 @@ Helper functions Parameters: * `groups`: Table of the node groups of the node that would be dug * `tool_capabilities`: Tool capabilities table of the item -* `minetest.get_hit_params(groups, tool_capabilities [, time_from_last_punch])`: + * `wear`: Amount of wear the tool starts with (default: 0) +* `minetest.get_hit_params(groups, tool_capabilities [, time_from_last_punch [, wear]])`: Simulates an item that punches an object. Returns a table with the following fields: - * `hp`: How much damage the punch would cause. + * `hp`: How much damage the punch would cause (between -65535 and 65535). * `wear`: How much wear would be added to the tool (ignored for non-tools). Parameters: * `groups`: Damage groups of the object * `tool_capabilities`: Tool capabilities table of the item * `time_from_last_punch`: time in seconds since last punch action + * `wear`: Amount of wear the item starts with (default: 0) @@ -4149,7 +4275,7 @@ differences: ### Other API functions operating on a VoxelManip -If any VoxelManip contents were set to a liquid node, +If any VoxelManip contents were set to a liquid node (`liquidtype ~= "none"`), `VoxelManip:update_liquids()` must be called for these liquid nodes to begin flowing. It is recommended to call this function only after having written all buffered data back to the VoxelManip object, save for special situations where @@ -4571,6 +4697,8 @@ Utilities abm_min_max_y = true, -- dynamic_add_media supports passing a table with options (5.5.0) dynamic_add_media_table = true, + -- allows get_sky to return a table instead of separate values (5.6.0) + get_sky_as_table = true, } * `minetest.has_feature(arg)`: returns `boolean, missing_features` @@ -4606,6 +4734,19 @@ Utilities * `minetest.mkdir(path)`: returns success. * Creates a directory specified by `path`, creating parent directories if they don't exist. +* `minetest.rmdir(path, recursive)`: returns success. + * Removes a directory specified by `path`. + * If `recursive` is set to `true`, the directory is recursively removed. + Otherwise, the directory will only be removed if it is empty. + * Returns true on success, false on failure. +* `minetest.cpdir(source, destination)`: returns success. + * Copies a directory specified by `path` to `destination` + * Any files in `destination` will be overwritten if they already exist. + * Returns true on success, false on failure. +* `minetest.mvdir(source, destination)`: returns success. + * Moves a directory specified by `path` to `destination`. + * If the `destination` is a non-empty directory, then the move will fail. + * Returns true on success, false on failure. * `minetest.get_dir_list(path, [is_dir])`: returns list of entry names * is_dir is one of: * nil: return all entries, @@ -4960,8 +5101,8 @@ Call these functions only at load time! * You should have joined some channels to receive events. * If message comes from a server mod, `sender` field is an empty string. * `minetest.register_on_liquid_transformed(function(pos_list, node_list))` - * Called after liquid nodes are modified by the engine's liquid transformation - process. + * Called after liquid nodes (`liquidtype ~= "none"`) are modified by the + engine's liquid transformation process. * `pos_list` is an array of all modified positions. * `node_list` is an array of the old node that was previously at the position with the corresponding index in pos_list. @@ -5303,7 +5444,8 @@ Environment access * `pos1`: start of the ray * `pos2`: end of the ray * `objects`: if false, only nodes will be returned. Default is `true`. - * `liquids`: if false, liquid nodes won't be returned. Default is `false`. + * `liquids`: if false, liquid nodes (`liquidtype ~= "none"`) won't be + returned. Default is `false`. * `minetest.find_path(pos1,pos2,searchdistance,max_jump,max_drop,algorithm)` * returns table containing path that can be walked on * returns a table of 3D points representing a path from `pos1` to `pos2` or @@ -5327,7 +5469,7 @@ Environment access * `minetest.spawn_tree (pos, {treedef})` * spawns L-system tree at given `pos` with definition in `treedef` table * `minetest.transforming_liquid_add(pos)` - * add node to liquid update queue + * add node to liquid flow update queue * `minetest.get_node_max_level(pos)` * get max available level for leveled node * `minetest.get_node_level(pos)` @@ -5405,7 +5547,7 @@ Inventory * `minetest.remove_detached_inventory(name)` * Returns a `boolean` indicating whether the removal succeeded. * `minetest.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed_thing)`: - returns left over ItemStack. + returns leftover ItemStack or nil to indicate no inventory change * See `minetest.item_eat` and `minetest.register_on_item_eat` Formspec @@ -5630,6 +5772,68 @@ Timing * `job:cancel()` * Cancels the job function from being called +Async environment +----------------- + +The engine allows you to submit jobs to be ran in an isolated environment +concurrently with normal server operation. +A job consists of a function to be ran in the async environment, any amount of +arguments (will be serialized) and a callback that will be called with the return +value of the job function once it is finished. + +The async environment does *not* have access to the map, entities, players or any +globals defined in the 'usual' environment. Consequently, functions like +`minetest.get_node()` or `minetest.get_player_by_name()` simply do not exist in it. + +Arguments and return values passed through this can contain certain userdata +objects that will be seamlessly copied (not shared) to the async environment. +This allows you easy interoperability for delegating work to jobs. + +* `minetest.handle_async(func, callback, ...)`: + * Queue the function `func` to be ran in an async environment. + Note that there are multiple persistent workers and any of them may + end up running a given job. The engine will scale the amount of + worker threads automatically. + * When `func` returns the callback is called (in the normal environment) + with all of the return values as arguments. + * Optional: Variable number of arguments that are passed to `func` +* `minetest.register_async_dofile(path)`: + * Register a path to a Lua file to be imported when an async environment + is initialized. You can use this to preload code which you can then call + later using `minetest.handle_async()`. + +### List of APIs available in an async environment + +Classes: +* `ItemStack` +* `PerlinNoise` +* `PerlinNoiseMap` +* `PseudoRandom` +* `PcgRandom` +* `SecureRandom` +* `VoxelArea` +* `VoxelManip` + * only if transferred into environment; can't read/write to map +* `Settings` + +Class instances that can be transferred between environments: +* `ItemStack` +* `PerlinNoise` +* `PerlinNoiseMap` +* `VoxelManip` + +Functions: +* Standalone helpers such as logging, filesystem, encoding, + hashing or compression APIs +* `minetest.request_insecure_environment` (same restrictions apply) + +Variables: +* `minetest.settings` +* `minetest.registered_items`, `registered_nodes`, `registered_tools`, + `registered_craftitems` and `registered_aliases` + * with all functions and userdata values replaced by `true`, calling any + callbacks here is obviously not possible + Server ------ @@ -5648,6 +5852,8 @@ Server a player joined. * This function may be overwritten by mods to customize the status message. * `minetest.get_server_uptime()`: returns the server uptime in seconds +* `minetest.get_server_max_lag()`: returns the current maximum lag + of the server in seconds or nil if server is not fully loaded yet * `minetest.remove_player(name)`: remove player from database (if they are not connected). * As auth data is not removed, minetest.player_exists will continue to @@ -5697,6 +5903,10 @@ Bans * `minetest.kick_player(name, [reason])`: disconnect a player with an optional reason. * Returns boolean indicating success (false if player nonexistant) +* `minetest.disconnect_player(name, [reason])`: disconnect a player with an + optional reason, this will not prefix with 'Kicked: ' like kick_player. + If no reason is given, it will default to 'Disconnected.' + * Returns boolean indicating success (false if player nonexistant) Particles --------- @@ -5899,11 +6109,11 @@ Misc. This is due to the fact that JSON has two distinct array and object values. * Example: `write_json({10, {a = false}})`, - returns `"[10, {\"a\": false}]"` + returns `'[10, {"a": false}]'` * `minetest.serialize(table)`: returns a string * Convert a table containing tables, strings, numbers, booleans and `nil`s into string form readable by `minetest.deserialize` - * Example: `serialize({foo='bar'})`, returns `'return { ["foo"] = "bar" }'` + * Example: `serialize({foo="bar"})`, returns `'return { ["foo"] = "bar" }'` * `minetest.deserialize(string[, safe])`: returns a table * Convert a string returned by `minetest.serialize` into a table * `string` is loaded in an empty sandbox environment. @@ -5915,7 +6125,7 @@ Misc. value of `safe`. It is fine to serialize then deserialize user-provided data, but directly providing user input to deserialize is always unsafe. * Example: `deserialize('return { ["foo"] = "bar" }')`, - returns `{foo='bar'}` + returns `{foo="bar"}` * Example: `deserialize('print("foo")')`, returns `nil` (function call fails), returns `error:[string "print("foo")"]:1: attempt to call global 'print' (a nil value)` @@ -6128,45 +6338,53 @@ Sorted alphabetically. `AreaStore` ----------- -A fast access data structure to store areas, and find areas near a given -position or area. -Every area has a `data` string attribute to store additional information. -You can create an empty `AreaStore` by calling `AreaStore()`, or -`AreaStore(type_name)`. The mod decides where to save and load AreaStore. -If you chose the parameter-less constructor, a fast implementation will be -automatically chosen for you. +AreaStore is a data structure to calculate intersections of 3D cuboid volumes +and points. The `data` field (string) may be used to store and retrieve any +mod-relevant information to the specified area. + +Despite its name, mods must take care of persisting AreaStore data. They may +use the provided load and write functions for this. + ### Methods -* `get_area(id, include_borders, include_data)` +* `AreaStore(type_name)` + * Returns a new AreaStore instance + * `type_name`: optional, forces the internally used API. + * Possible values: `"LibSpatial"` (default). + * When other values are specified, or SpatialIndex is not available, + the custom Minetest functions are used. +* `get_area(id, include_corners, include_data)` * Returns the area information about the specified ID. * Returned values are either of these: nil -- Area not found - true -- Without `include_borders` and `include_data` + true -- Without `include_corners` and `include_data` { - min = pos, max = pos -- `include_borders == true` + min = pos, max = pos -- `include_corners == true` data = string -- `include_data == true` } -* `get_areas_for_pos(pos, include_borders, include_data)` +* `get_areas_for_pos(pos, include_corners, include_data)` * Returns all areas as table, indexed by the area ID. * Table values: see `get_area`. -* `get_areas_in_area(edge1, edge2, accept_overlap, include_borders, include_data)` - * Returns all areas that contain all nodes inside the area specified by `edge1` - and `edge2` (inclusive). +* `get_areas_in_area(corner1, corner2, accept_overlap, include_corners, include_data)` + * Returns all areas that contain all nodes inside the area specified by` + `corner1 and `corner2` (inclusive). * `accept_overlap`: if `true`, areas are returned that have nodes in common (intersect) with the specified area. * Returns the same values as `get_areas_for_pos`. -* `insert_area(edge1, edge2, data, [id])`: inserts an area into the store. +* `insert_area(corner1, corner2, data, [id])`: inserts an area into the store. * Returns the new area's ID, or nil if the insertion failed. - * The (inclusive) positions `edge1` and `edge2` describe the area. + * The (inclusive) positions `corner1` and `corner2` describe the area. * `data` is a string stored with the area. * `id` (optional): will be used as the internal area ID if it is an unique number between 0 and 2^32-2. -* `reserve(count)`: reserves resources for at most `count` many contained - areas. - Only needed for efficiency, and only some implementations profit. +* `reserve(count)` + * Requires SpatialIndex, no-op function otherwise. + * Reserves resources for `count` many contained areas to improve + efficiency when working with many area entries. Additional areas can still + be inserted afterwards at the usual complexity. * `remove_area(id)`: removes the area with the given id from the store, returns success. * `set_cache_params(params)`: sets params for the included prefiltering cache. @@ -6204,9 +6422,9 @@ An `InvRef` is a reference to an inventory. * `set_width(listname, width)`: set width of list; currently used for crafting * `get_stack(listname, i)`: get a copy of stack index `i` in list * `set_stack(listname, i, stack)`: copy `stack` to index `i` in list -* `get_list(listname)`: return full list +* `get_list(listname)`: return full list (list of `ItemStack`s) * `set_list(listname, list)`: set full list (size will not change) -* `get_lists()`: returns list of inventory lists +* `get_lists()`: returns table that maps listnames to inventory lists * `set_lists(lists)`: sets inventory lists (size will not change) * `add_item(listname, stack)`: add item somewhere in list, returns leftover `ItemStack`. @@ -6566,7 +6784,7 @@ object you are working with still exists. * Fourth column: subject looking to the right * Fifth column: subject viewed from above * Sixth column: subject viewed from below -* `get_entity_name()` (**Deprecated**: Will be removed in a future version) +* `get_entity_name()` (**Deprecated**: Will be removed in a future version, use the field `self.name` instead) * `get_luaentity()` #### Player only (no-op for other objects) @@ -6627,6 +6845,7 @@ object you are working with still exists. * `set_inventory_formspec(formspec)` * Redefine player's inventory form * Should usually be called in `on_joinplayer` + * If `formspec` is `""`, the player's inventory is disabled. * `get_inventory_formspec()`: returns a formspec string * `set_formspec_prepend(formspec)`: * the formspec string will be added to every formspec shown to the user, @@ -6641,18 +6860,21 @@ object you are working with still exists. `aux1`, `sneak`, `dig`, `place`, `LMB`, `RMB`, and `zoom`. * The fields `LMB` and `RMB` are equal to `dig` and `place` respectively, and exist only to preserve backwards compatibility. + * Returns an empty table `{}` if the object is not a player. * `get_player_control_bits()`: returns integer with bit packed player pressed - keys. Bits: - * 0 - up - * 1 - down - * 2 - left - * 3 - right - * 4 - jump - * 5 - aux1 - * 6 - sneak - * 7 - dig - * 8 - place - * 9 - zoom + keys. + * Bits: + * 0 - up + * 1 - down + * 2 - left + * 3 - right + * 4 - jump + * 5 - aux1 + * 6 - sneak + * 7 - dig + * 8 - place + * 9 - zoom + * Returns `0` (no bits set) if the object is not a player. * `set_physics_override(override_table)` * `override_table` is a table with the following fields: * `speed`: multiplier to default walking speed value (default: `1`) @@ -6675,17 +6897,18 @@ object you are working with still exists. * `hud_get(id)`: gets the HUD element definition structure of the specified ID * `hud_set_flags(flags)`: sets specified HUD flags of player. * `flags`: A table with the following fields set to boolean values - * hotbar - * healthbar - * crosshair - * wielditem - * breathbar - * minimap - * minimap_radar + * `hotbar` + * `healthbar` + * `crosshair` + * `wielditem` + * `breathbar` + * `minimap`: Modifies the client's permission to view the minimap. + The client may locally elect to not view the minimap. + * `minimap_radar`: is only usable when `minimap` is true + * `basic_debug`: Allow showing basic debug info that might give a gameplay advantage. + This includes map seed, player position, look direction, the pointed node and block bounds. + Does not affect players with the `debug` privilege. * If a flag equals `nil`, the flag is not modified - * `minimap`: Modifies the client's permission to view the minimap. - The client may locally elect to not view the minimap. - * `minimap_radar` is only usable when `minimap` is true * `hud_get_flags()`: returns a table of player HUD flags with boolean values. * See `hud_set_flags` for a list of flags that can be toggled. * `hud_set_hotbar_itemcount(count)`: sets number of items in builtin hotbar @@ -6721,43 +6944,46 @@ object you are working with still exists. * `set_sky(sky_parameters)` * The presence of the function `set_sun`, `set_moon` or `set_stars` indicates whether `set_sky` accepts this format. Check the legacy format otherwise. + * Passing no arguments resets the sky to its default values. * `sky_parameters` is a table with the following optional fields: * `base_color`: ColorSpec, changes fog in "skybox" and "plain". + (default: `#ffffff`) * `type`: Available types: * `"regular"`: Uses 0 textures, `base_color` ignored * `"skybox"`: Uses 6 textures, `base_color` used as fog. * `"plain"`: Uses 0 textures, `base_color` used as both fog and sky. + (default: `"regular"`) * `textures`: A table containing up to six textures in the following order: Y+ (top), Y- (bottom), X- (west), X+ (east), Z+ (north), Z- (south). * `clouds`: Boolean for whether clouds appear. (default: `true`) - * `sky_color`: A table containing the following values, alpha is ignored: - * `day_sky`: ColorSpec, for the top half of the `"regular"` - sky during the day. (default: `#61b5f5`) - * `day_horizon`: ColorSpec, for the bottom half of the - `"regular"` sky during the day. (default: `#90d3f6`) - * `dawn_sky`: ColorSpec, for the top half of the `"regular"` - sky during dawn/sunset. (default: `#b4bafa`) + * `sky_color`: A table used in `"regular"` type only, containing the + following values (alpha is ignored): + * `day_sky`: ColorSpec, for the top half of the sky during the day. + (default: `#61b5f5`) + * `day_horizon`: ColorSpec, for the bottom half of the sky during the day. + (default: `#90d3f6`) + * `dawn_sky`: ColorSpec, for the top half of the sky during dawn/sunset. + (default: `#b4bafa`) The resulting sky color will be a darkened version of the ColorSpec. Warning: The darkening of the ColorSpec is subject to change. - * `dawn_horizon`: ColorSpec, for the bottom half of the `"regular"` - sky during dawn/sunset. (default: `#bac1f0`) + * `dawn_horizon`: ColorSpec, for the bottom half of the sky during dawn/sunset. + (default: `#bac1f0`) The resulting sky color will be a darkened version of the ColorSpec. Warning: The darkening of the ColorSpec is subject to change. - * `night_sky`: ColorSpec, for the top half of the `"regular"` - sky during the night. (default: `#006bff`) + * `night_sky`: ColorSpec, for the top half of the sky during the night. + (default: `#006bff`) The resulting sky color will be a dark version of the ColorSpec. Warning: The darkening of the ColorSpec is subject to change. - * `night_horizon`: ColorSpec, for the bottom half of the `"regular"` - sky during the night. (default: `#4090ff`) + * `night_horizon`: ColorSpec, for the bottom half of the sky during the night. + (default: `#4090ff`) The resulting sky color will be a dark version of the ColorSpec. Warning: The darkening of the ColorSpec is subject to change. - * `indoors`: ColorSpec, for when you're either indoors or - underground. Only applies to the `"regular"` sky. + * `indoors`: ColorSpec, for when you're either indoors or underground. (default: `#646464`) * `fog_sun_tint`: ColorSpec, changes the fog tinting for the sun - at sunrise and sunset. + at sunrise and sunset. (default: `#f47d1d`) * `fog_moon_tint`: ColorSpec, changes the fog tinting for the moon - at sunrise and sunset. + at sunrise and sunset. (default: `#7f99cc`) * `fog_tint_type`: string, changes which mode the directional fog abides by, `"custom"` uses `sun_tint` and `moon_tint`, while `"default"` uses the classic Minetest sun and moon tinting. @@ -6771,15 +6997,22 @@ object you are working with still exists. * `"plain"`: Uses 0 textures, `bgcolor` used * `clouds`: Boolean for whether clouds appear in front of `"skybox"` or `"plain"` custom skyboxes (default: `true`) -* `get_sky()`: returns base_color, type, table of textures, clouds. -* `get_sky_color()`: returns a table with the `sky_color` parameters as in - `set_sky`. +* `get_sky(as_table)`: + * `as_table`: boolean that determines whether the deprecated version of this + function is being used. + * `true` returns a table containing sky parameters as defined in `set_sky(sky_parameters)`. + * Deprecated: `false` or `nil` returns base_color, type, table of textures, + clouds. +* `get_sky_color()`: + * Deprecated: Use `get_sky(as_table)` instead. + * returns a table with the `sky_color` parameters as in `set_sky`. * `set_sun(sun_parameters)`: + * Passing no arguments resets the sun to its default values. * `sun_parameters` is a table with the following optional fields: * `visible`: Boolean for whether the sun is visible. (default: `true`) * `texture`: A regular texture for the sun. Setting to `""` - will re-enable the mesh sun. (default: `"sun.png"`) + will re-enable the mesh sun. (default: "sun.png", if it exists) * `tonemap`: A 512x1 texture containing the tonemap for the sun (default: `"sun_tonemap.png"`) * `sunrise`: A regular texture for the sunrise texture. @@ -6790,17 +7023,21 @@ object you are working with still exists. * `get_sun()`: returns a table with the current sun parameters as in `set_sun`. * `set_moon(moon_parameters)`: + * Passing no arguments resets the moon to its default values. * `moon_parameters` is a table with the following optional fields: * `visible`: Boolean for whether the moon is visible. (default: `true`) * `texture`: A regular texture for the moon. Setting to `""` - will re-enable the mesh moon. (default: `"moon.png"`) + will re-enable the mesh moon. (default: `"moon.png"`, if it exists) + Note: Relative to the sun, the moon texture is rotated by 180°. + You can use the `^[transformR180` texture modifier to achieve the same orientation. * `tonemap`: A 512x1 texture containing the tonemap for the moon (default: `"moon_tonemap.png"`) * `scale`: Float controlling the overall size of the moon (default: `1`) * `get_moon()`: returns a table with the current moon parameters as in `set_moon`. * `set_stars(star_parameters)`: + * Passing no arguments resets stars to their default values. * `star_parameters` is a table with the following optional fields: * `visible`: Boolean for whether the stars are visible. (default: `true`) @@ -6814,6 +7051,7 @@ object you are working with still exists. * `get_stars()`: returns a table with the current stars parameters as in `set_stars`. * `set_clouds(cloud_parameters)`: set cloud parameters + * Passing no arguments resets clouds to their default values. * `cloud_parameters` is a table with the following optional fields: * `density`: from `0` (no clouds) to `1` (full clouds) (default `0.4`) * `color`: basic cloud color with alpha channel, ColorSpec @@ -6848,6 +7086,12 @@ object you are working with still exists. * Returns `false` if failed. * Resource intensive - use sparsely * To get blockpos, integer divide pos by 16 +* `set_lighting(light_definition)`: sets lighting for the player + * `light_definition` is a table with the following optional fields: + * `shadows` is a table that controls ambient shadows + * `intensity` sets the intensity of the shadows from 0 (no shadows, default) to 1 (blackness) +* `get_lighting()`: returns the current state of lighting for the player. + * Result is a table with the same fields as `light_definition` in `set_lighting`. `PcgRandom` ----------- @@ -6980,7 +7224,8 @@ It can be created via `Raycast(pos1, pos2, objects, liquids)` or * `pos1`: start of the ray * `pos2`: end of the ray * `objects`: if false, only nodes will be returned. Default is true. -* `liquids`: if false, liquid nodes won't be returned. Default is false. +* `liquids`: if false, liquid nodes (`liquidtype ~= "none"`) won't be + returned. Default is false. ### Methods @@ -7139,6 +7384,7 @@ Player properties need to be saved manually. -- "sprite" uses 1 texture. -- "upright_sprite" uses 2 textures: {front, back}. -- "wielditem" expects 'textures = {itemname}' (see 'visual' above). + -- "mesh" requires one texture for each mesh buffer/material (in order) colors = {}, -- Number of required colors depends on visual @@ -7264,7 +7510,7 @@ Used by `minetest.register_entity`. -- for more info) by using a '_' prefix } -Collision info passed to `on_step`: +Collision info passed to `on_step` (`moveresult` argument): { touching_ground = boolean, @@ -7281,6 +7527,8 @@ Collision info passed to `on_step`: }, ... } + -- `collisions` does not contain data of unloaded mapblock collisions + -- or when the velocity changes are negligibly small } ABM (ActiveBlockModifier) definition @@ -7464,6 +7712,8 @@ Used by `minetest.register_node`, `minetest.register_craftitem`, and range = 4.0, liquids_pointable = false, + -- If true, item points to all liquid nodes (`liquidtype ~= "none"`), + -- even those for which `pointable = false` light_source = 0, -- When used for nodes: Defines amount of light emitted by node. @@ -7521,12 +7771,15 @@ Used by `minetest.register_node`, `minetest.register_craftitem`, and on_place = function(itemstack, placer, pointed_thing), -- When the 'place' key was pressed with the item in hand -- and a node was pointed at. - -- Shall place item and return the leftover itemstack. + -- Shall place item and return the leftover itemstack + -- or nil to not modify the inventory. -- The placer may be any ObjectRef or nil. -- default: minetest.item_place on_secondary_use = function(itemstack, user, pointed_thing), -- Same as on_place but called when not pointing at a node. + -- Function must return either nil if inventory shall not be modified, + -- or an itemstack to replace the original itemstack. -- The user may be any ObjectRef or nil. -- default: nil @@ -7538,8 +7791,8 @@ Used by `minetest.register_node`, `minetest.register_craftitem`, and on_use = function(itemstack, user, pointed_thing), -- default: nil -- When user pressed the 'punch/mine' key with the item in hand. - -- Function must return either nil if no item shall be removed from - -- inventory, or an itemstack to replace the original itemstack. + -- Function must return either nil if inventory shall not be modified, + -- or an itemstack to replace the original itemstack. -- e.g. itemstack:take_item(); return itemstack -- Otherwise, the function is free to do what it wants. -- The user may be any ObjectRef or nil. @@ -7649,14 +7902,21 @@ Used by `minetest.register_node`. climbable = false, -- If true, can be climbed on (ladder) + move_resistance = 0, + -- Slows down movement of players through this node (max. 7). + -- If this is nil, it will be equal to liquid_viscosity. + -- Note: If liquid movement physics apply to the node + -- (see `liquid_move_physics`), the movement speed will also be + -- affected by the `movement_liquid_*` settings. + buildable_to = false, -- If true, placed nodes can replace this node floodable = false, -- If true, liquids flow into and replace this node. -- Warning: making a liquid node 'floodable' will cause problems. - liquidtype = "none", -- specifies liquid physics - -- * "none": no liquid physics + liquidtype = "none", -- specifies liquid flowing physics + -- * "none": no liquid flowing physics -- * "source": spawns flowing liquid nodes at all 4 sides and below; -- recommended drawtype: "liquid". -- * "flowing": spawned from source, spawns more flowing liquid nodes @@ -7670,12 +7930,26 @@ Used by `minetest.register_node`. liquid_alternative_source = "", -- Source version of flowing liquid - liquid_viscosity = 0, -- Higher viscosity = slower flow (max. 7) + liquid_viscosity = 0, + -- Controls speed at which the liquid spreads/flows (max. 7). + -- 0 is fastest, 7 is slowest. + -- By default, this also slows down movement of players inside the node + -- (can be overridden using `move_resistance`) liquid_renewable = true, -- If true, a new liquid source can be created by placing two or more -- sources nearby + liquid_move_physics = nil, -- specifies movement physics if inside node + -- * false: No liquid movement physics apply. + -- * true: Enables liquid movement physics. Enables things like + -- ability to "swim" up/down, sinking slowly if not moving, + -- smoother speed change when falling into, etc. The `movement_liquid_*` + -- settings apply. + -- * nil: Will be treated as true if `liquidype ~= "none"` + -- and as false otherwise. + -- Default: nil + leveled = 0, -- Only valid for "nodebox" drawtype with 'type = "leveled"'. -- Allows defining the nodebox height without using param2. @@ -7825,7 +8099,7 @@ Used by `minetest.register_node`. items = {"default:sand", "default:desert_sand"}, }, { - -- Only drop if using an item in the "magicwand" group, or + -- Only drop if using an item in the "magicwand" group, or -- an item that is in both the "pickaxe" and the "lucky" -- groups. tool_groups = { @@ -7967,11 +8241,11 @@ Used by `minetest.register_craft`. ### Shaped { - output = 'default:pick_stone', + output = "default:pick_stone", recipe = { - {'default:cobble', 'default:cobble', 'default:cobble'}, - {'', 'default:stick', ''}, - {'', 'default:stick', ''}, -- Also groups; e.g. 'group:crumbly' + {"default:cobble", "default:cobble", "default:cobble"}, + {"", "default:stick", ""}, + {"", "default:stick", ""}, -- Also groups; e.g. "group:crumbly" }, replacements = , -- replacements: replace one input item with another item on crafting @@ -7982,7 +8256,7 @@ Used by `minetest.register_craft`. { type = "shapeless", - output = 'mushrooms:mushroom_stew', + output = "mushrooms:mushroom_stew", recipe = { "mushrooms:bowl", "mushrooms:mushroom_brown", @@ -8753,3 +9027,10 @@ Used by `minetest.register_authentication_handler`. -- Returns an iterator (use with `for` loops) for all player names -- currently in the auth database } + +Bit Library +----------- + +Functions: bit.tobit, bit.tohex, bit.bnot, bit.band, bit.bor, bit.bxor, bit.lshift, bit.rshift, bit.arshift, bit.rol, bit.ror, bit.bswap + +See http://bitop.luajit.org/ for advanced information. diff --git a/doc/menu_lua_api.txt b/doc/menu_lua_api.txt index f4dfff261..c2931af31 100644 --- a/doc/menu_lua_api.txt +++ b/doc/menu_lua_api.txt @@ -1,4 +1,4 @@ -Minetest Lua Mainmenu API Reference 5.5.0 +Minetest Lua Mainmenu API Reference 5.6.0 ========================================= Introduction @@ -85,7 +85,9 @@ core.get_video_drivers() core.get_mapgen_names([include_hidden=false]) -> table of map generator algorithms registered in the core (possible in async calls) core.get_cache_path() -> path of cache -core.get_temp_path() -> path of temp folder +core.get_temp_path([param]) (possible in async calls) +^ param=true: returns path to a temporary file +^ otherwise: returns path to the temporary folder HTTP Requests @@ -219,7 +221,24 @@ Package - content which is downloadable from the content db, may or may not be i * returns path to global user data, the directory that contains user-provided mods, worlds, games, and texture packs. * core.get_modpath() (possible in async calls) - * returns path to global modpath + * returns path to global modpath in the user path, where mods can be installed +* core.get_modpaths() (possible in async calls) + * returns table of virtual path to global modpaths, where mods have been installed + The difference with "core.get_modpath" is that no mods should be installed in these + directories by Minetest -- they might be read-only. + + Ex: + + ``` + { + mods = "/home/user/.minetest/mods", + share = "/usr/share/minetest/mods", + + -- Custom dirs can be specified by the MINETEST_MOD_DIR env variable + ["/path/to/custom/dir"] = "/path/to/custom/dir", + } + ``` + * core.get_clientmodpath() (possible in async calls) * returns path to global client-side modpath * core.get_gamepath() (possible in async calls) diff --git a/doc/minetest.6 b/doc/minetest.6 index bac70fe1a..6a3601f80 100644 --- a/doc/minetest.6 +++ b/doc/minetest.6 @@ -112,6 +112,10 @@ leveldb, and files. Migrate from current players backend to another. Possible values are sqlite3, leveldb, postgresql, dummy, and files. .TP +.B \-\-migrate-mod-storage +Migrate from current mod storage backend to another. Possible values are +sqlite3, dummy, and files. +.TP .B \-\-terminal Display an interactive terminal over ncurses during execution. @@ -119,6 +123,9 @@ Display an interactive terminal over ncurses during execution. .TP .B MINETEST_SUBGAME_PATH Colon delimited list of directories to search for games. +.TP +.B MINETEST_MOD_PATH +Colon delimited list of directories to search for mods. .SH BUGS Please report all bugs at https://github.com/minetest/minetest/issues. diff --git a/doc/texture_packs.txt b/doc/texture_packs.txt index 8af2cbad6..f738032b6 100644 --- a/doc/texture_packs.txt +++ b/doc/texture_packs.txt @@ -90,9 +90,10 @@ by texture packs. All existing fallback textures can be found in the directory * `minimap_mask_square.png`: mask used for the square minimap * `minimap_overlay_round.png`: overlay texture for the round minimap * `minimap_overlay_square.png`: overlay texture for the square minimap -* `no_texture_airlike.png`: fallback inventory image for airlike nodes * `object_marker_red.png`: texture for players on the minimap * `player_marker.png`: texture for the own player on the square minimap +* `no_texture_airlike.png`: fallback inventory image for airlike nodes +* `no_texture.png`: fallback image for unspecified textures * `player.png`: front texture of the 2D upright sprite player * `player_back.png`: back texture of the 2D upright sprite player diff --git a/doc/world_format.txt b/doc/world_format.txt index eb1d7f728..17923df8e 100644 --- a/doc/world_format.txt +++ b/doc/world_format.txt @@ -129,9 +129,34 @@ Example content (added indentation and - explanations): backend = sqlite3 - which DB backend to use for blocks (sqlite3, dummy, leveldb, redis, postgresql) player_backend = sqlite3 - which DB backend to use for player data readonly_backend = sqlite3 - optionally readonly seed DB (DB file _must_ be located in "readonly" subfolder) + auth_backend = files - which DB backend to use for authentication data server_announce = false - whether the server is publicly announced or not load_mod_ = false - whether is to be loaded in this world - auth_backend = files - which DB backend to use for authentication data + +For load_mod_, the possible values are: + +* `false` - Do not load the mod. +* `true` - Load the mod from wherever it is found (may cause conflicts if the same mod appears also in some other place). +* `mods/modpack/moddir` - Relative path to the mod + * Must be one of the following: + * `mods/`: mods in the user path's mods folder (ex `/home/user/.minetest/mods`) + * `share/`: mods in the share's mods folder (ex: `/usr/share/minetest/mods`) + * `/path/to/env`: you can use absolute paths to mods inside folders specified with the `MINETEST_MOD_PATH` env variable. + * Other locations and absolute paths are not supported + * Note that `moddir` is the directory name, not the mod name specified in mod.conf. + +PostgreSQL backend specific settings: + pgsql_connection = host=127.0.0.1 port=5432 user=mt_user password=mt_password dbname=minetest + pgsql_player_connection = (same parameters as above) + pgsql_readonly_connection = (same parameters as above) + pgsql_auth_connection = (same parameters as above) + +Redis backend specific settings: + redis_address = 127.0.0.1 - Redis server address + redis_hash = foo - Database hash + redis_port = 6379 - (optional) connection port + redis_password = hunter2 - (optional) server password + Player File Format =================== @@ -333,6 +358,9 @@ if map format version >= 29: - 0xffffffff = invalid/unknown timestamp, nothing should be done with the time difference when loaded + u8 name_id_mapping_version + - Should be zero for map format version 29. + u16 num_name_id_mappings foreach num_name_id_mappings u16 id @@ -434,7 +462,7 @@ if map format version < 29: u8[name_len] name - Node timers -if map format version == 25: +if map format version >= 25: u8 length of the data of a single timer (always 2+4+4=10) u16 num_of_timers foreach num_of_timers: diff --git a/fonts/mono_dejavu_sans_10.xml b/fonts/mono_dejavu_sans_10.xml deleted file mode 100644 index 0276cedb6..000000000 Binary files a/fonts/mono_dejavu_sans_10.xml and /dev/null differ diff --git a/fonts/mono_dejavu_sans_100.png b/fonts/mono_dejavu_sans_100.png deleted file mode 100644 index 45a312542..000000000 Binary files a/fonts/mono_dejavu_sans_100.png and /dev/null differ diff --git a/fonts/mono_dejavu_sans_11.xml b/fonts/mono_dejavu_sans_11.xml deleted file mode 100644 index f727ed2bb..000000000 Binary files a/fonts/mono_dejavu_sans_11.xml and /dev/null differ diff --git a/fonts/mono_dejavu_sans_110.png b/fonts/mono_dejavu_sans_110.png deleted file mode 100644 index c90c0e242..000000000 Binary files a/fonts/mono_dejavu_sans_110.png and /dev/null differ diff --git a/fonts/mono_dejavu_sans_12.xml b/fonts/mono_dejavu_sans_12.xml deleted file mode 100644 index 38f6427be..000000000 Binary files a/fonts/mono_dejavu_sans_12.xml and /dev/null differ diff --git a/fonts/mono_dejavu_sans_120.png b/fonts/mono_dejavu_sans_120.png deleted file mode 100644 index 0cebd70e8..000000000 Binary files a/fonts/mono_dejavu_sans_120.png and /dev/null differ diff --git a/fonts/mono_dejavu_sans_14.xml b/fonts/mono_dejavu_sans_14.xml deleted file mode 100644 index b90a34960..000000000 Binary files a/fonts/mono_dejavu_sans_14.xml and /dev/null differ diff --git a/fonts/mono_dejavu_sans_140.png b/fonts/mono_dejavu_sans_140.png deleted file mode 100644 index a413759ea..000000000 Binary files a/fonts/mono_dejavu_sans_140.png and /dev/null differ diff --git a/fonts/mono_dejavu_sans_16.xml b/fonts/mono_dejavu_sans_16.xml deleted file mode 100644 index 3f7d2c2a2..000000000 Binary files a/fonts/mono_dejavu_sans_16.xml and /dev/null differ diff --git a/fonts/mono_dejavu_sans_160.png b/fonts/mono_dejavu_sans_160.png deleted file mode 100644 index bd8a2f40a..000000000 Binary files a/fonts/mono_dejavu_sans_160.png and /dev/null differ diff --git a/fonts/mono_dejavu_sans_18.xml b/fonts/mono_dejavu_sans_18.xml deleted file mode 100644 index 92865cbfc..000000000 Binary files a/fonts/mono_dejavu_sans_18.xml and /dev/null differ diff --git a/fonts/mono_dejavu_sans_180.png b/fonts/mono_dejavu_sans_180.png deleted file mode 100644 index a299afcbe..000000000 Binary files a/fonts/mono_dejavu_sans_180.png and /dev/null differ diff --git a/fonts/mono_dejavu_sans_20.xml b/fonts/mono_dejavu_sans_20.xml deleted file mode 100644 index acd8c77d0..000000000 Binary files a/fonts/mono_dejavu_sans_20.xml and /dev/null differ diff --git a/fonts/mono_dejavu_sans_200.png b/fonts/mono_dejavu_sans_200.png deleted file mode 100644 index 68ee62681..000000000 Binary files a/fonts/mono_dejavu_sans_200.png and /dev/null differ diff --git a/fonts/mono_dejavu_sans_22.xml b/fonts/mono_dejavu_sans_22.xml deleted file mode 100644 index eafb4def6..000000000 Binary files a/fonts/mono_dejavu_sans_22.xml and /dev/null differ diff --git a/fonts/mono_dejavu_sans_220.png b/fonts/mono_dejavu_sans_220.png deleted file mode 100644 index 042d7e094..000000000 Binary files a/fonts/mono_dejavu_sans_220.png and /dev/null differ diff --git a/fonts/mono_dejavu_sans_24.xml b/fonts/mono_dejavu_sans_24.xml deleted file mode 100644 index fc8b6232e..000000000 Binary files a/fonts/mono_dejavu_sans_24.xml and /dev/null differ diff --git a/fonts/mono_dejavu_sans_240.png b/fonts/mono_dejavu_sans_240.png deleted file mode 100644 index d2d68c5bb..000000000 Binary files a/fonts/mono_dejavu_sans_240.png and /dev/null differ diff --git a/fonts/mono_dejavu_sans_26.xml b/fonts/mono_dejavu_sans_26.xml deleted file mode 100644 index 829f09948..000000000 Binary files a/fonts/mono_dejavu_sans_26.xml and /dev/null differ diff --git a/fonts/mono_dejavu_sans_260.png b/fonts/mono_dejavu_sans_260.png deleted file mode 100644 index 3a8cb6c57..000000000 Binary files a/fonts/mono_dejavu_sans_260.png and /dev/null differ diff --git a/fonts/mono_dejavu_sans_28.xml b/fonts/mono_dejavu_sans_28.xml deleted file mode 100644 index b5b25bd07..000000000 Binary files a/fonts/mono_dejavu_sans_28.xml and /dev/null differ diff --git a/fonts/mono_dejavu_sans_280.png b/fonts/mono_dejavu_sans_280.png deleted file mode 100644 index ccf62ba48..000000000 Binary files a/fonts/mono_dejavu_sans_280.png and /dev/null differ diff --git a/fonts/mono_dejavu_sans_4.xml b/fonts/mono_dejavu_sans_4.xml deleted file mode 100644 index cfebb39b3..000000000 Binary files a/fonts/mono_dejavu_sans_4.xml and /dev/null differ diff --git a/fonts/mono_dejavu_sans_40.png b/fonts/mono_dejavu_sans_40.png deleted file mode 100644 index 24ed693f7..000000000 Binary files a/fonts/mono_dejavu_sans_40.png and /dev/null differ diff --git a/fonts/mono_dejavu_sans_6.xml b/fonts/mono_dejavu_sans_6.xml deleted file mode 100644 index d0e1de21d..000000000 Binary files a/fonts/mono_dejavu_sans_6.xml and /dev/null differ diff --git a/fonts/mono_dejavu_sans_60.png b/fonts/mono_dejavu_sans_60.png deleted file mode 100644 index 326af996f..000000000 Binary files a/fonts/mono_dejavu_sans_60.png and /dev/null differ diff --git a/fonts/mono_dejavu_sans_8.xml b/fonts/mono_dejavu_sans_8.xml deleted file mode 100644 index c48bf7ccc..000000000 Binary files a/fonts/mono_dejavu_sans_8.xml and /dev/null differ diff --git a/fonts/mono_dejavu_sans_80.png b/fonts/mono_dejavu_sans_80.png deleted file mode 100644 index 04326dbb2..000000000 Binary files a/fonts/mono_dejavu_sans_80.png and /dev/null differ diff --git a/fonts/mono_dejavu_sans_9.xml b/fonts/mono_dejavu_sans_9.xml deleted file mode 100644 index 74e841034..000000000 Binary files a/fonts/mono_dejavu_sans_9.xml and /dev/null differ diff --git a/fonts/mono_dejavu_sans_90.png b/fonts/mono_dejavu_sans_90.png deleted file mode 100644 index 65ac51858..000000000 Binary files a/fonts/mono_dejavu_sans_90.png and /dev/null differ diff --git a/games/devtest/.luacheckrc b/games/devtest/.luacheckrc new file mode 100644 index 000000000..1c7d3994f --- /dev/null +++ b/games/devtest/.luacheckrc @@ -0,0 +1,43 @@ +unused_args = false +allow_defined_top = true +max_string_line_length = false +max_line_length = false + +ignore = { + "131", -- Unused global variable + "211", -- Unused local variable + "231", -- Local variable never accessed + "311", -- Value assigned to a local variable is unused + "412", -- Redefining an argument + "421", -- Shadowing a local variable + "431", -- Shadowing an upvalue + "432", -- Shadowing an upvalue argument + "611", -- Line contains only whitespace +} + +read_globals = { + "ItemStack", + "INIT", + "DIR_DELIM", + "dump", "dump2", + "fgettext", "fgettext_ne", + "vector", + "VoxelArea", + "profiler", + "Settings", + "check", + "PseudoRandom", + + string = {fields = {"split", "trim"}}, + table = {fields = {"copy", "getn", "indexof", "insert_all"}}, + math = {fields = {"hypot", "round"}}, +} + +globals = { + "aborted", + "minetest", + "core", + os = { fields = { "tempfolder" } }, + "_", +} + diff --git a/games/devtest/mods/basetools/init.lua b/games/devtest/mods/basetools/init.lua index bd7480030..3ec69d39f 100644 --- a/games/devtest/mods/basetools/init.lua +++ b/games/devtest/mods/basetools/init.lua @@ -16,11 +16,11 @@ Tool types: Tool materials: -* Dirt: dig nodes of rating 3, one use only * Wood: dig nodes of rating 3 * Stone: dig nodes of rating 3 or 2 * Steel: dig nodes of rating 3, 2 or 1 * Mese: dig "everything" instantly +* n-Uses: can be used n times before breaking ]] -- The hand @@ -92,20 +92,6 @@ minetest.register_tool("basetools:pick_mese", { -- Pickaxes: Dig cracky -- --- This should break after only 1 use -minetest.register_tool("basetools:pick_dirt", { - description = "Dirt Pickaxe".."\n".. - "Digs cracky=3".."\n".. - "1 use only", - inventory_image = "basetools_dirtpick.png", - tool_capabilities = { - max_drop_level=0, - groupcaps={ - cracky={times={[3]=2.00}, uses=1, maxlevel=0} - }, - }, -}) - minetest.register_tool("basetools:pick_wood", { description = "Wooden Pickaxe".."\n".. "Digs cracky=3", @@ -293,50 +279,135 @@ minetest.register_tool("basetools:sword_wood", { }) minetest.register_tool("basetools:sword_stone", { description = "Stone Sword".."\n".. - "Damage: fleshy=4", + "Damage: fleshy=5", inventory_image = "basetools_stonesword.png", tool_capabilities = { full_punch_interval = 1.0, max_drop_level=0, - damage_groups = {fleshy=4}, + damage_groups = {fleshy=5}, } }) minetest.register_tool("basetools:sword_steel", { description = "Steel Sword".."\n".. - "Damage: fleshy=6", + "Damage: fleshy=10", inventory_image = "basetools_steelsword.png", tool_capabilities = { full_punch_interval = 1.0, max_drop_level=1, - damage_groups = {fleshy=6}, + damage_groups = {fleshy=10}, + } +}) +minetest.register_tool("basetools:sword_titanium", { + description = "Titanium Sword".."\n".. + "Damage: fleshy=100", + inventory_image = "basetools_titaniumsword.png", + tool_capabilities = { + full_punch_interval = 1.0, + max_drop_level=1, + damage_groups = {fleshy=100}, + } +}) +minetest.register_tool("basetools:sword_blood", { + description = "Blood Sword".."\n".. + "Damage: fleshy=1000", + inventory_image = "basetools_bloodsword.png", + tool_capabilities = { + full_punch_interval = 1.0, + max_drop_level=1, + damage_groups = {fleshy=1000}, + } +}) + +-- Max. damage sword +minetest.register_tool("basetools:sword_mese", { + description = "Mese Sword".."\n".. + "Damage: fleshy=32767, fiery=32767, icy=32767".."\n".. + "Full Punch Interval: 0.0s", + inventory_image = "basetools_mesesword.png", + tool_capabilities = { + full_punch_interval = 0.0, + max_drop_level=1, + damage_groups = {fleshy=32767, fiery=32767, icy=32767}, } }) -- Fire/Ice sword: Deal damage to non-fleshy damage groups minetest.register_tool("basetools:sword_fire", { description = "Fire Sword".."\n".. - "Damage: icy=6", + "Damage: icy=10", inventory_image = "basetools_firesword.png", tool_capabilities = { full_punch_interval = 1.0, max_drop_level=0, - damage_groups = {icy=6}, + damage_groups = {icy=10}, } }) minetest.register_tool("basetools:sword_ice", { description = "Ice Sword".."\n".. - "Damage: fiery=6", + "Damage: fiery=10", inventory_image = "basetools_icesword.png", tool_capabilities = { full_punch_interval = 1.0, max_drop_level=0, - damage_groups = {fiery=6}, + damage_groups = {fiery=10}, } }) +minetest.register_tool("basetools:sword_elemental", { + description = "Elemental Sword".."\n".. + "Damage: fiery=10, icy=10", + inventory_image = "basetools_elementalsword.png", + tool_capabilities = { + full_punch_interval = 1.0, + max_drop_level=0, + damage_groups = {fiery=10, icy=10}, + } +}) + +-- Healing weapons: heal HP +minetest.register_tool("basetools:dagger_heal", { + description = "Healing Dagger".."\n".. + "Heal: fleshy=1".."\n".. + "Full Punch Interval: 0.5s", + inventory_image = "basetools_healdagger.png", + tool_capabilities = { + full_punch_interval = 0.5, + damage_groups = {fleshy=-1}, + } +}) +minetest.register_tool("basetools:sword_heal", { + description = "Healing Sword".."\n".. + "Heal: fleshy=10", + inventory_image = "basetools_healsword.png", + tool_capabilities = { + full_punch_interval = 1.0, + damage_groups = {fleshy=-10}, + } +}) +minetest.register_tool("basetools:sword_heal_super", { + description = "Super Healing Sword".."\n".. + "Heal: fleshy=32768, fiery=32768, icy=32768", + inventory_image = "basetools_superhealsword.png", + tool_capabilities = { + full_punch_interval = 1.0, + damage_groups = {fleshy=-32768, fiery=-32768, icy=-32768}, + } +}) + -- -- Dagger: Low damage, fast punch interval -- +minetest.register_tool("basetools:dagger_wood", { + description = "Wooden Dagger".."\n".. + "Damage: fleshy=1".."\n".. + "Full Punch Interval: 0.5s", + inventory_image = "basetools_wooddagger.png", + tool_capabilities = { + full_punch_interval = 0.5, + max_drop_level=0, + damage_groups = {fleshy=1}, + } +}) minetest.register_tool("basetools:dagger_steel", { description = "Steel Dagger".."\n".. "Damage: fleshy=2".."\n".. @@ -348,3 +419,31 @@ minetest.register_tool("basetools:dagger_steel", { damage_groups = {fleshy=2}, } }) + +-- Test tool uses and punch_attack_uses +local uses = { 1, 2, 3, 5, 10, 50, 100, 1000, 10000, 65535 } +for i=1, #uses do + local u = uses[i] + local color = string.format("#FF00%02X", math.floor(((i-1)/#uses) * 255)) + minetest.register_tool("basetools:pick_uses_"..string.format("%05d", u), { + description = u.."-Uses Pickaxe".."\n".. + "Digs cracky=3", + inventory_image = "basetools_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} + }, + }, + }) + + minetest.register_tool("basetools:sword_uses_"..string.format("%05d", u), { + description = u.."-Uses Sword".."\n".. + "Damage: fleshy=1", + inventory_image = "basetools_usessword.png^[colorize:"..color..":127", + tool_capabilities = { + damage_groups = {fleshy=1}, + punch_attack_uses = u, + }, + }) +end diff --git a/games/devtest/mods/basetools/textures/basetools_bloodsword.png b/games/devtest/mods/basetools/textures/basetools_bloodsword.png new file mode 100644 index 000000000..a521ba4a2 Binary files /dev/null and b/games/devtest/mods/basetools/textures/basetools_bloodsword.png differ diff --git a/games/devtest/mods/basetools/textures/basetools_dirtpick.png b/games/devtest/mods/basetools/textures/basetools_dirtpick.png deleted file mode 100644 index 20a021d72..000000000 Binary files a/games/devtest/mods/basetools/textures/basetools_dirtpick.png and /dev/null differ diff --git a/games/devtest/mods/basetools/textures/basetools_elementalsword.png b/games/devtest/mods/basetools/textures/basetools_elementalsword.png new file mode 100644 index 000000000..d007217ee Binary files /dev/null and b/games/devtest/mods/basetools/textures/basetools_elementalsword.png differ diff --git a/games/devtest/mods/basetools/textures/basetools_firesword.png b/games/devtest/mods/basetools/textures/basetools_firesword.png index ee2809ab7..eca999ba1 100644 Binary files a/games/devtest/mods/basetools/textures/basetools_firesword.png and b/games/devtest/mods/basetools/textures/basetools_firesword.png differ diff --git a/games/devtest/mods/basetools/textures/basetools_healdagger.png b/games/devtest/mods/basetools/textures/basetools_healdagger.png new file mode 100644 index 000000000..3e6eb9cd0 Binary files /dev/null and b/games/devtest/mods/basetools/textures/basetools_healdagger.png differ diff --git a/games/devtest/mods/basetools/textures/basetools_healsword.png b/games/devtest/mods/basetools/textures/basetools_healsword.png new file mode 100644 index 000000000..f93fddfb2 Binary files /dev/null and b/games/devtest/mods/basetools/textures/basetools_healsword.png differ diff --git a/games/devtest/mods/basetools/textures/basetools_icesword.png b/games/devtest/mods/basetools/textures/basetools_icesword.png index 35ba8214b..55a8d609d 100644 Binary files a/games/devtest/mods/basetools/textures/basetools_icesword.png and b/games/devtest/mods/basetools/textures/basetools_icesword.png differ diff --git a/games/devtest/mods/basetools/textures/basetools_mesepick.png b/games/devtest/mods/basetools/textures/basetools_mesepick.png index 2b5e12cdb..2993b475b 100644 Binary files a/games/devtest/mods/basetools/textures/basetools_mesepick.png and b/games/devtest/mods/basetools/textures/basetools_mesepick.png differ diff --git a/games/devtest/mods/basetools/textures/basetools_mesesword.png b/games/devtest/mods/basetools/textures/basetools_mesesword.png new file mode 100644 index 000000000..bc82769bc Binary files /dev/null and b/games/devtest/mods/basetools/textures/basetools_mesesword.png differ diff --git a/games/devtest/mods/basetools/textures/basetools_superhealsword.png b/games/devtest/mods/basetools/textures/basetools_superhealsword.png new file mode 100644 index 000000000..4175a0917 Binary files /dev/null and b/games/devtest/mods/basetools/textures/basetools_superhealsword.png differ diff --git a/games/devtest/mods/basetools/textures/basetools_titaniumsword.png b/games/devtest/mods/basetools/textures/basetools_titaniumsword.png new file mode 100644 index 000000000..55e22c7d5 Binary files /dev/null and b/games/devtest/mods/basetools/textures/basetools_titaniumsword.png differ diff --git a/games/devtest/mods/basetools/textures/basetools_usespick.png b/games/devtest/mods/basetools/textures/basetools_usespick.png new file mode 100644 index 000000000..27850f961 Binary files /dev/null and b/games/devtest/mods/basetools/textures/basetools_usespick.png differ diff --git a/games/devtest/mods/basetools/textures/basetools_usessword.png b/games/devtest/mods/basetools/textures/basetools_usessword.png new file mode 100644 index 000000000..0eaf4cf38 Binary files /dev/null and b/games/devtest/mods/basetools/textures/basetools_usessword.png differ diff --git a/games/devtest/mods/basetools/textures/basetools_wooddagger.png b/games/devtest/mods/basetools/textures/basetools_wooddagger.png new file mode 100644 index 000000000..6e5ab0fd6 Binary files /dev/null and b/games/devtest/mods/basetools/textures/basetools_wooddagger.png differ diff --git a/games/devtest/mods/broken/init.lua b/games/devtest/mods/broken/init.lua new file mode 100644 index 000000000..04993ca16 --- /dev/null +++ b/games/devtest/mods/broken/init.lua @@ -0,0 +1,11 @@ +-- Register stuff with empty definitions to test if Minetest fallback options +-- for these things work properly. + +-- The itemstrings are deliberately kept descriptive to keep them easy to +-- recognize. + +minetest.register_node("broken:node_with_empty_definition", {}) +minetest.register_tool("broken:tool_with_empty_definition", {}) +minetest.register_craftitem("broken:craftitem_with_empty_definition", {}) + +minetest.register_entity("broken:entity_with_empty_definition", {}) diff --git a/games/devtest/mods/broken/mod.conf b/games/devtest/mods/broken/mod.conf new file mode 100644 index 000000000..a24378a34 --- /dev/null +++ b/games/devtest/mods/broken/mod.conf @@ -0,0 +1,2 @@ +name = broken +description = Register items and an entity with empty definitions to test fallback diff --git a/games/devtest/mods/testentities/armor.lua b/games/devtest/mods/testentities/armor.lua index 306953d50..415e5bd19 100644 --- a/games/devtest/mods/testentities/armor.lua +++ b/games/devtest/mods/testentities/armor.lua @@ -4,10 +4,19 @@ local phasearmor = { [0]={icy=100}, [1]={fiery=100}, - [2]={fleshy=100}, - [3]={immortal=1}, - [4]={punch_operable=1}, + [2]={icy=100, fiery=100}, + [3]={fleshy=-100}, + [4]={fleshy=1}, + [5]={fleshy=10}, + [6]={fleshy=50}, + [7]={fleshy=100}, + [8]={fleshy=200}, + [9]={fleshy=1000}, + [10]={fleshy=32767}, + [11]={immortal=1}, + [12]={punch_operable=1}, } +local max_phase = 12 minetest.register_entity("testentities:armorball", { initial_properties = { @@ -17,11 +26,11 @@ minetest.register_entity("testentities:armorball", { visual = "sprite", visual_size = {x=1, y=1}, textures = {"testentities_armorball.png"}, - spritediv = {x=1, y=5}, + spritediv = {x=1, y=max_phase+1}, initial_sprite_basepos = {x=0, y=0}, }, - _phase = 2, + _phase = 7, on_activate = function(self, staticdata) minetest.log("action", "[testentities] armorball.on_activate") @@ -32,10 +41,21 @@ minetest.register_entity("testentities:armorball", { on_rightclick = function(self, clicker) -- Change armor group and sprite self._phase = self._phase + 1 - if self._phase >= 5 then + if self._phase >= max_phase + 1 then self._phase = 0 end self.object:set_sprite({x=0, y=self._phase}) self.object:set_armor_groups(phasearmor[self._phase]) end, + + on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir, damage) + if not puncher then + return + end + local name = puncher:get_player_name() + if not name then + return + end + minetest.chat_send_player(name, "time_from_last_punch="..string.format("%.3f", time_from_last_punch).."; damage="..tostring(damage)) + end, }) diff --git a/games/devtest/mods/testentities/textures/testentities_armorball.png b/games/devtest/mods/testentities/textures/testentities_armorball.png index 88147bd1f..708c7b36d 100644 Binary files a/games/devtest/mods/testentities/textures/testentities_armorball.png and b/games/devtest/mods/testentities/textures/testentities_armorball.png differ diff --git a/games/devtest/mods/testformspec/formspec.lua b/games/devtest/mods/testformspec/formspec.lua index 501b5e354..9f867631f 100644 --- a/games/devtest/mods/testformspec/formspec.lua +++ b/games/devtest/mods/testformspec/formspec.lua @@ -270,6 +270,16 @@ local scroll_fs = --style_type[label;border=;bgcolor=] --label[0.75,2;Reset] +local window = { + sizex = 12, + sizey = 13, + positionx = 0.5, + positiony = 0.5, + anchorx = 0.5, + anchory = 0.5, + paddingx = 0.05, + paddingy = 0.05 +} local pages = { -- Real Coordinates @@ -341,9 +351,28 @@ local pages = { "size[12,13]real_coordinates[true]" .. "container[0.5,1.5]" .. tabheaders_fs .. "container_end[]", - -- Inv + -- Inv "size[12,13]real_coordinates[true]" .. inv_style_fs, + -- Window + function() + return "formspec_version[3]" .. + string.format("size[%s,%s]position[%s,%s]anchor[%s,%s]padding[%s,%s]", + window.sizex, window.sizey, window.positionx, window.positiony, + window.anchorx, window.anchory, window.paddingx, window.paddingy) .. + string.format("field[0.5,0.5;2.5,0.5;sizex;X Size;%s]field[3.5,0.5;2.5,0.5;sizey;Y Size;%s]" .. + "field[0.5,1.5;2.5,0.5;positionx;X Position;%s]field[3.5,1.5;2.5,0.5;positiony;Y Position;%s]" .. + "field[0.5,2.5;2.5,0.5;anchorx;X Anchor;%s]field[3.5,2.5;2.5,0.5;anchory;Y Anchor;%s]" .. + "field[0.5,3.5;2.5,0.5;paddingx;X Padding;%s]field[3.5,3.5;2.5,0.5;paddingy;Y Padding;%s]" .. + "button[2,4.5;2.5,0.5;submit_window;Submit]", + window.sizex, window.sizey, window.positionx, window.positiony, + window.anchorx, window.anchory, window.paddingx, window.paddingy) .. + "field_close_on_enter[sizex;false]field_close_on_enter[sizey;false]" .. + "field_close_on_enter[positionx;false]field_close_on_enter[positiony;false]" .. + "field_close_on_enter[anchorx;false]field_close_on_enter[anchory;false]" .. + "field_close_on_enter[paddingx;false]field_close_on_enter[paddingy;false]" + end, + -- Animation [[ formspec_version[3] @@ -401,12 +430,43 @@ mouse control = true] checkbox[0.5,5.5.5;snd_chk;Sound;] tabheader[0.5,7;8,0.65;snd_tab;Soundtab1,Soundtab2,Soundtab3;1;false;false] ]], + + -- Background + [[ + formspec_version[3] + size[12,13] + box[0,0;12,13;#f0f1] + background[0,0;0,0;testformspec_bg.png;true] + box[3.9,2.9;6.2,4.2;#d00f] + scroll_container[4,3;6,4;scrbar;vertical] + background9[1,0.5;0,0;testformspec_bg_9slice.png;true;4,6] + label[0,0.2;Backgrounds are not be applied to scroll containers,] + label[0,0.5;but to the whole form.] + scroll_container_end[] + scrollbar[3.5,3;0.3,4;vertical;scrbar;0] + container[2,11] + box[-0.1,0.5;3.2,1;#fff5] + background[0,0;2,3;testformspec_bg.png;false] + background9[1,0;2,3;testformspec_bg_9slice.png;false;4,6] + container_end[] + ]], + + -- Unsized + [[ + formspec_version[3] + background9[0,0;0,0;testformspec_bg_9slice.png;true;4,6] + background[1,1;0,0;testformspec_bg.png;true] + ]], } -local function show_test_formspec(pname, page_id) - page_id = page_id or 2 +local page_id = 2 +local function show_test_formspec(pname) + local page = pages[page_id] + if type(page) == "function" then + page = page() + end - local fs = pages[page_id] .. "tabheader[0,0;8,0.65;maintabs;Real Coord,Styles,Noclip,Hypertext,Tabs,Invs,Anim,Model,ScrollC,Sound;" .. page_id .. ";false;false]" + local fs = page .. "tabheader[0,0;11,0.65;maintabs;Real Coord,Styles,Noclip,Hypertext,Tabs,Invs,Window,Anim,Model,ScrollC,Sound,Background,Unsized;" .. page_id .. ";false;false]" minetest.show_formspec(pname, "testformspec:formspec", fs) end @@ -416,9 +476,9 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) return false end - if fields.maintabs then - show_test_formspec(player:get_player_name(), tonumber(fields.maintabs)) + page_id = tonumber(fields.maintabs) + show_test_formspec(player:get_player_name()) return true end @@ -434,6 +494,26 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) minetest.chat_send_player(player:get_player_name(), "Hypertext action received: " .. tostring(fields.hypertext)) return true end + + for name, value in pairs(fields) do + if window[name] then + print(name, window[name]) + local num_val = tonumber(value) or 0 + + if name == "sizex" and num_val < 4 then + num_val = 6.5 + elseif name == "sizey" and num_val < 5 then + num_val = 5.5 + end + + window[name] = num_val + print(name, window[name]) + end + end + + if fields.submit_window then + show_test_formspec(player:get_player_name()) + end end) minetest.register_chatcommand("test_formspec", { diff --git a/games/devtest/mods/testnodes/liquids.lua b/games/devtest/mods/testnodes/liquids.lua index 3d2ea17f5..be33814af 100644 --- a/games/devtest/mods/testnodes/liquids.lua +++ b/games/devtest/mods/testnodes/liquids.lua @@ -40,9 +40,11 @@ for d=0, 8 do liquid_range = d, }) + if d <= 7 then + local mod = "^[colorize:#000000:127" minetest.register_node("testnodes:vliquid_"..d, { - description = "Test Liquid Source, Viscosity "..d, + description = "Test Liquid Source, Viscosity/Resistance "..d, drawtype = "liquid", tiles = {"testnodes_liquidsource_r"..d..".png"..mod}, special_tiles = { @@ -61,7 +63,7 @@ for d=0, 8 do }) minetest.register_node("testnodes:vliquid_flowing_"..d, { - description = "Flowing Test Liquid, Viscosity "..d, + description = "Flowing Test Liquid, Viscosity/Resistance "..d, drawtype = "flowingliquid", tiles = {"testnodes_liquidflowing_r"..d..".png"..mod}, special_tiles = { @@ -80,4 +82,53 @@ for d=0, 8 do liquid_viscosity = d, }) + mod = "^[colorize:#000000:192" + local v = 4 + minetest.register_node("testnodes:vrliquid_"..d, { + description = "Test Liquid Source, Viscosity "..v..", Resistance "..d, + drawtype = "liquid", + tiles = {"testnodes_liquidsource_r"..d..".png"..mod}, + special_tiles = { + {name = "testnodes_liquidsource_r"..d..".png"..mod, backface_culling = false}, + {name = "testnodes_liquidsource_r"..d..".png"..mod, backface_culling = true}, + }, + use_texture_alpha = "blend", + paramtype = "light", + walkable = false, + pointable = false, + diggable = false, + buildable_to = true, + is_ground_content = false, + liquidtype = "source", + liquid_alternative_flowing = "testnodes:vrliquid_flowing_"..d, + liquid_alternative_source = "testnodes:vrliquid_"..d, + liquid_viscosity = v, + move_resistance = d, + }) + + minetest.register_node("testnodes:vrliquid_flowing_"..d, { + description = "Flowing Test Liquid, Viscosity "..v..", Resistance "..d, + drawtype = "flowingliquid", + tiles = {"testnodes_liquidflowing_r"..d..".png"..mod}, + special_tiles = { + {name = "testnodes_liquidflowing_r"..d..".png"..mod, backface_culling = false}, + {name = "testnodes_liquidflowing_r"..d..".png"..mod, backface_culling = false}, + }, + use_texture_alpha = "blend", + paramtype = "light", + paramtype2 = "flowingliquid", + walkable = false, + pointable = false, + diggable = false, + buildable_to = true, + is_ground_content = false, + liquidtype = "flowing", + liquid_alternative_flowing = "testnodes:vrliquid_flowing_"..d, + liquid_alternative_source = "testnodes:vrliquid_"..d, + liquid_viscosity = v, + move_resistance = d, + }) + + end + end diff --git a/games/devtest/mods/testnodes/properties.lua b/games/devtest/mods/testnodes/properties.lua index a52cd1d6f..89facf71c 100644 --- a/games/devtest/mods/testnodes/properties.lua +++ b/games/devtest/mods/testnodes/properties.lua @@ -152,6 +152,66 @@ minetest.register_node("testnodes:liquidflowing_nojump", { post_effect_color = {a = 70, r = 255, g = 0, b = 200}, }) +-- A liquid which doesn't have liquid movement physics (source variant) +minetest.register_node("testnodes:liquid_noswim", { + description = S("No-swim Liquid Source Node"), + liquidtype = "source", + liquid_range = 1, + liquid_viscosity = 0, + liquid_alternative_flowing = "testnodes:liquidflowing_noswim", + liquid_alternative_source = "testnodes:liquid_noswim", + liquid_renewable = false, + liquid_move_physics = false, + groups = {dig_immediate=3}, + walkable = false, + + drawtype = "liquid", + tiles = {"testnodes_liquidsource.png^[colorize:#FF00FF:127"}, + special_tiles = { + {name = "testnodes_liquidsource.png^[colorize:#FF00FF:127", backface_culling = false}, + {name = "testnodes_liquidsource.png^[colorize:#FF00FF:127", backface_culling = true}, + }, + use_texture_alpha = "blend", + paramtype = "light", + pointable = false, + liquids_pointable = true, + buildable_to = true, + is_ground_content = false, + post_effect_color = {a = 70, r = 255, g = 200, b = 200}, +}) + +-- A liquid which doen't have liquid movement physics (flowing variant) +minetest.register_node("testnodes:liquidflowing_noswim", { + description = S("No-swim Flowing Liquid Node"), + liquidtype = "flowing", + liquid_range = 1, + liquid_viscosity = 0, + liquid_alternative_flowing = "testnodes:liquidflowing_noswim", + liquid_alternative_source = "testnodes:liquid_noswim", + liquid_renewable = false, + liquid_move_physics = false, + groups = {dig_immediate=3}, + walkable = false, + + + drawtype = "flowingliquid", + tiles = {"testnodes_liquidflowing.png^[colorize:#FF00FF:127"}, + special_tiles = { + {name = "testnodes_liquidflowing.png^[colorize:#FF00FF:127", backface_culling = false}, + {name = "testnodes_liquidflowing.png^[colorize:#FF00FF:127", backface_culling = false}, + }, + use_texture_alpha = "blend", + paramtype = "light", + paramtype2 = "flowingliquid", + pointable = false, + liquids_pointable = true, + buildable_to = true, + is_ground_content = false, + post_effect_color = {a = 70, r = 255, g = 200, b = 200}, +}) + + + -- Nodes that modify fall damage (various damage modifiers) for i=-100, 100, 25 do if i ~= 0 then @@ -192,9 +252,9 @@ for i=-100, 100, 25 do end -- Bouncy nodes (various bounce levels) -for i=20, 180, 20 do +for i=-140, 180, 20 do local val = math.floor(((i-20)/200)*255) - minetest.register_node("testnodes:bouncy"..i, { + minetest.register_node(("testnodes:bouncy"..i):gsub("-","NEG"), { description = S("Bouncy Node (@1%)", i), groups = {bouncy=i, dig_immediate=3}, @@ -216,6 +276,54 @@ for i=1, 5 do }) end +-- Move resistance nodes (various resistance levels) +for r=0, 7 do + if r > 0 then + minetest.register_node("testnodes:move_resistance"..r, { + description = S("Move-resistant Node (@1)", r), + walkable = false, + move_resistance = r, + + drawtype = "glasslike", + paramtype = "light", + sunlight_propagates = true, + tiles = { "testnodes_move_resistance.png" }, + is_ground_content = false, + groups = { dig_immediate = 3 }, + color = { b = 0, g = 255, r = math.floor((r/7)*255), a = 255 }, + }) + end + + minetest.register_node("testnodes:move_resistance_liquidlike"..r, { + description = S("Move-resistant Node, liquidlike (@1)", r), + walkable = false, + move_resistance = r, + liquid_move_physics = true, + + drawtype = "glasslike", + paramtype = "light", + sunlight_propagates = true, + tiles = { "testnodes_move_resistance.png" }, + is_ground_content = false, + groups = { dig_immediate = 3 }, + color = { b = 255, g = 0, r = math.floor((r/7)*255), a = 255 }, + }) +end + +minetest.register_node("testnodes:climbable_move_resistance_4", { + description = S("Climbable Move-resistant Node (4)"), + walkable = false, + climbable = true, + move_resistance = 4, + + drawtype = "glasslike", + paramtype = "light", + sunlight_propagates = true, + tiles = {"testnodes_climbable_resistance_side.png"}, + is_ground_content = false, + groups = { dig_immediate = 3 }, +}) + -- By placing something on the node, the node itself will be replaced minetest.register_node("testnodes:buildable_to", { description = S("Replacable Node"), diff --git a/games/devtest/mods/testnodes/textures.lua b/games/devtest/mods/testnodes/textures.lua index 4652007d9..2faacdd78 100644 --- a/games/devtest/mods/testnodes/textures.lua +++ b/games/devtest/mods/testnodes/textures.lua @@ -102,12 +102,22 @@ local function gen_checkers(w, h, tile) end local fractal = mandelbrot(512, 512, 128) +local frac_emb = mandelbrot(64, 64, 64) local checker = gen_checkers(512, 512, 32) local floor = math.floor local abs = math.abs +local data_emb = {} local data_mb = {} local data_ck = {} +for i=1, #frac_emb do + data_emb[i] = { + r = floor(abs(frac_emb[i] * 2 - 1) * 255), + g = floor(abs(1 - frac_emb[i]) * 255), + b = floor(frac_emb[i] * 255), + a = frac_emb[i] < 0.95 and 255 or 0, + } +end for i=1, #fractal do data_mb[i] = { r = floor(fractal[i] * 255), @@ -140,3 +150,141 @@ minetest.register_node("testnodes:generated_png_ck", { groups = { dig_immediate = 2 }, }) + +local png_emb = "[png:" .. minetest.encode_base64(minetest.encode_png(64,64,data_emb)) + +minetest.register_node("testnodes:generated_png_emb", { + description = S("Generated In-Band Mandelbrot PNG Test Node"), + tiles = { png_emb }, + + groups = { dig_immediate = 2 }, +}) +minetest.register_node("testnodes:generated_png_src_emb", { + description = S("Generated In-Band Source Blit Mandelbrot PNG Test Node"), + tiles = { png_emb .. "^testnodes_damage_neg.png" }, + + groups = { dig_immediate = 2 }, +}) +minetest.register_node("testnodes:generated_png_dst_emb", { + description = S("Generated In-Band Dest Blit Mandelbrot PNG Test Node"), + tiles = { "testnodes_generated_ck.png^" .. png_emb }, + + groups = { dig_immediate = 2 }, +}) + +--[[ + +The following nodes can be used to demonstrate the TGA format support. + +Minetest supports TGA types 1, 2, 3 & 10. While adding the support for +TGA type 9 (RLE-compressed, color-mapped) is easy, it is not advisable +to do so, as it is not backwards compatible with any Minetest pre-5.5; +content creators should therefore either use TGA type 1 or 10, or PNG. + +TODO: Types 1, 2 & 10 should have two test nodes each (i.e. bottom-top +and top-bottom) for 16bpp (A1R5G5B5), 24bpp (B8G8R8), 32bpp (B8G8R8A8) +colors. + +Note: Minetest requires the optional TGA footer for a texture to load. +If a TGA image does not load in Minetest, append eight (8) null bytes, +then the string “TRUEVISION-XFILE.”, then another null byte. + +]]-- + +minetest.register_node("testnodes:tga_type1_24bpp_bt", { + description = S("TGA Type 1 (color-mapped RGB) 24bpp bottom-top Test Node"), + drawtype = "glasslike", + paramtype = "light", + sunlight_propagates = true, + tiles = { "testnodes_tga_type1_24bpp_bt.tga" }, + groups = { dig_immediate = 2 }, +}) + +minetest.register_node("testnodes:tga_type1_24bpp_tb", { + description = S("TGA Type 1 (color-mapped RGB) 24bpp top-bottom Test Node"), + drawtype = "glasslike", + paramtype = "light", + sunlight_propagates = true, + tiles = { "testnodes_tga_type1_24bpp_tb.tga" }, + groups = { dig_immediate = 2 }, +}) + +minetest.register_node("testnodes:tga_type2_16bpp_bt", { + description = S("TGA Type 2 (uncompressed RGB) 16bpp bottom-top Test Node"), + drawtype = "glasslike", + paramtype = "light", + sunlight_propagates = true, + tiles = { "testnodes_tga_type2_16bpp_bt.tga" }, + use_texture_alpha = "clip", + groups = { dig_immediate = 2 }, +}) + +minetest.register_node("testnodes:tga_type2_16bpp_tb", { + description = S("TGA Type 2 (uncompressed RGB) 16bpp top-bottom Test Node"), + drawtype = "glasslike", + paramtype = "light", + sunlight_propagates = true, + tiles = { "testnodes_tga_type2_16bpp_tb.tga" }, + use_texture_alpha = "clip", + groups = { dig_immediate = 2 }, +}) + +minetest.register_node("testnodes:tga_type2_32bpp_bt", { + description = S("TGA Type 2 (uncompressed RGB) 32bpp bottom-top Test Node"), + drawtype = "glasslike", + paramtype = "light", + sunlight_propagates = true, + tiles = { "testnodes_tga_type2_32bpp_bt.tga" }, + use_texture_alpha = "blend", + groups = { dig_immediate = 2 }, +}) + +minetest.register_node("testnodes:tga_type2_32bpp_tb", { + description = S("TGA Type 2 (uncompressed RGB) 32bpp top-bottom Test Node"), + drawtype = "glasslike", + paramtype = "light", + sunlight_propagates = true, + tiles = { "testnodes_tga_type2_32bpp_tb.tga" }, + use_texture_alpha = "blend", + groups = { dig_immediate = 2 }, +}) + +minetest.register_node("testnodes:tga_type3_16bpp_bt", { + description = S("TGA Type 3 (uncompressed grayscale) 16bpp bottom-top Test Node"), + drawtype = "glasslike", + paramtype = "light", + sunlight_propagates = true, + tiles = { "testnodes_tga_type3_16bpp_bt.tga" }, + use_texture_alpha = "blend", + groups = { dig_immediate = 2 }, +}) + +minetest.register_node("testnodes:tga_type3_16bpp_tb", { + description = S("TGA Type 3 (uncompressed grayscale) 16bpp top-bottom Test Node"), + drawtype = "glasslike", + paramtype = "light", + sunlight_propagates = true, + tiles = { "testnodes_tga_type3_16bpp_tb.tga" }, + use_texture_alpha = "blend", + groups = { dig_immediate = 2 }, +}) + +minetest.register_node("testnodes:tga_type10_32bpp_bt", { + description = S("TGA Type 10 (RLE-compressed RGB) 32bpp bottom-top Test Node"), + tiles = { "testnodes_tga_type10_32bpp_bt.tga" }, + drawtype = "glasslike", + paramtype = "light", + sunlight_propagates = true, + use_texture_alpha = "blend", + groups = { dig_immediate = 2 }, +}) + +minetest.register_node("testnodes:tga_type10_32bpp_tb", { + description = S("TGA Type 10 (RLE-compressed RGB) 32bpp top-bottom Test Node"), + drawtype = "glasslike", + paramtype = "light", + sunlight_propagates = true, + tiles = { "testnodes_tga_type10_32bpp_tb.tga" }, + use_texture_alpha = "blend", + groups = { dig_immediate = 2 }, +}) diff --git a/games/devtest/mods/testnodes/textures/testnodes_climbable_resistance_side.png b/games/devtest/mods/testnodes/textures/testnodes_climbable_resistance_side.png new file mode 100644 index 000000000..be01583e6 Binary files /dev/null and b/games/devtest/mods/testnodes/textures/testnodes_climbable_resistance_side.png differ diff --git a/games/devtest/mods/testnodes/textures/testnodes_move_resistance.png b/games/devtest/mods/testnodes/textures/testnodes_move_resistance.png new file mode 100644 index 000000000..cac3944bf Binary files /dev/null and b/games/devtest/mods/testnodes/textures/testnodes_move_resistance.png differ diff --git a/games/devtest/mods/testnodes/textures/testnodes_tga_type10_32bpp_bt.tga b/games/devtest/mods/testnodes/textures/testnodes_tga_type10_32bpp_bt.tga new file mode 100644 index 000000000..2dc587bc3 Binary files /dev/null and b/games/devtest/mods/testnodes/textures/testnodes_tga_type10_32bpp_bt.tga differ diff --git a/games/devtest/mods/testnodes/textures/testnodes_tga_type10_32bpp_tb.tga b/games/devtest/mods/testnodes/textures/testnodes_tga_type10_32bpp_tb.tga new file mode 100644 index 000000000..b44a81c79 Binary files /dev/null and b/games/devtest/mods/testnodes/textures/testnodes_tga_type10_32bpp_tb.tga differ diff --git a/games/devtest/mods/testnodes/textures/testnodes_tga_type1_24bpp_bt.tga b/games/devtest/mods/testnodes/textures/testnodes_tga_type1_24bpp_bt.tga new file mode 100644 index 000000000..d2c2ca6d2 Binary files /dev/null and b/games/devtest/mods/testnodes/textures/testnodes_tga_type1_24bpp_bt.tga differ diff --git a/games/devtest/mods/testnodes/textures/testnodes_tga_type1_24bpp_tb.tga b/games/devtest/mods/testnodes/textures/testnodes_tga_type1_24bpp_tb.tga new file mode 100644 index 000000000..dfcb98864 Binary files /dev/null and b/games/devtest/mods/testnodes/textures/testnodes_tga_type1_24bpp_tb.tga differ diff --git a/games/devtest/mods/testnodes/textures/testnodes_tga_type2_16bpp_bt.tga b/games/devtest/mods/testnodes/textures/testnodes_tga_type2_16bpp_bt.tga new file mode 100644 index 000000000..0206216bb Binary files /dev/null and b/games/devtest/mods/testnodes/textures/testnodes_tga_type2_16bpp_bt.tga differ diff --git a/games/devtest/mods/testnodes/textures/testnodes_tga_type2_16bpp_tb.tga b/games/devtest/mods/testnodes/textures/testnodes_tga_type2_16bpp_tb.tga new file mode 100644 index 000000000..2563f084b Binary files /dev/null and b/games/devtest/mods/testnodes/textures/testnodes_tga_type2_16bpp_tb.tga differ diff --git a/games/devtest/mods/testnodes/textures/testnodes_tga_type2_32bpp_bt.tga b/games/devtest/mods/testnodes/textures/testnodes_tga_type2_32bpp_bt.tga new file mode 100644 index 000000000..3350500f8 Binary files /dev/null and b/games/devtest/mods/testnodes/textures/testnodes_tga_type2_32bpp_bt.tga differ diff --git a/games/devtest/mods/testnodes/textures/testnodes_tga_type2_32bpp_tb.tga b/games/devtest/mods/testnodes/textures/testnodes_tga_type2_32bpp_tb.tga new file mode 100644 index 000000000..216de0634 Binary files /dev/null and b/games/devtest/mods/testnodes/textures/testnodes_tga_type2_32bpp_tb.tga differ diff --git a/games/devtest/mods/testnodes/textures/testnodes_tga_type3_16bpp_bt.tga b/games/devtest/mods/testnodes/textures/testnodes_tga_type3_16bpp_bt.tga new file mode 100644 index 000000000..695bb4bb1 Binary files /dev/null and b/games/devtest/mods/testnodes/textures/testnodes_tga_type3_16bpp_bt.tga differ diff --git a/games/devtest/mods/testnodes/textures/testnodes_tga_type3_16bpp_tb.tga b/games/devtest/mods/testnodes/textures/testnodes_tga_type3_16bpp_tb.tga new file mode 100644 index 000000000..c08a093b2 Binary files /dev/null and b/games/devtest/mods/testnodes/textures/testnodes_tga_type3_16bpp_tb.tga differ diff --git a/games/devtest/mods/unittests/async_env.lua b/games/devtest/mods/unittests/async_env.lua new file mode 100644 index 000000000..3a21bd9e2 --- /dev/null +++ b/games/devtest/mods/unittests/async_env.lua @@ -0,0 +1,168 @@ +-- helper + +core.register_async_dofile(core.get_modpath(core.get_current_modname()) .. + DIR_DELIM .. "inside_async_env.lua") + +local function deepequal(a, b) + if type(a) == "function" then + return type(b) == "function" + elseif type(a) ~= "table" then + return a == b + elseif type(b) ~= "table" then + return false + end + for k, v in pairs(a) do + if not deepequal(v, b[k]) then + return false + end + end + for k, v in pairs(b) do + if not deepequal(a[k], v) then + return false + end + end + return true +end + +-- Object Passing / Serialization + +local test_object = { + name = "stairs:stair_glass", + type = "node", + groups = {oddly_breakable_by_hand = 3, cracky = 3, stair = 1}, + description = "Glass Stair", + sounds = { + dig = {name = "default_glass_footstep", gain = 0.5}, + footstep = {name = "default_glass_footstep", gain = 0.3}, + dug = {name = "default_break_glass", gain = 1} + }, + node_box = { + fixed = { + {-0.5, -0.5, -0.5, 0.5, 0, 0.5}, + {-0.5, 0, 0, 0.5, 0.5, 0.5} + }, + type = "fixed" + }, + tiles = { + {name = "stairs_glass_split.png", backface_culling = true}, + {name = "default_glass.png", backface_culling = true}, + {name = "stairs_glass_stairside.png^[transformFX", backface_culling = true} + }, + on_place = function(itemstack, placer) + return core.is_player(placer) + end, + sunlight_propagates = true, + is_ground_content = false, + light_source = 0, +} + +local function test_object_passing() + local tmp = core.serialize_roundtrip(test_object) + assert(deepequal(test_object, tmp)) + + local circular_key = {"foo", "bar"} + circular_key[circular_key] = true + tmp = core.serialize_roundtrip(circular_key) + assert(tmp[1] == "foo") + assert(tmp[2] == "bar") + assert(tmp[tmp] == true) + + local circular_value = {"foo"} + circular_value[2] = circular_value + tmp = core.serialize_roundtrip(circular_value) + assert(tmp[1] == "foo") + assert(tmp[2] == tmp) + + -- Two-segment cycle + local cycle_seg_1, cycle_seg_2 = {}, {} + cycle_seg_1[1] = cycle_seg_2 + cycle_seg_2[1] = cycle_seg_1 + tmp = core.serialize_roundtrip(cycle_seg_1) + assert(tmp[1][1] == tmp) + + -- Duplicated value without a cycle + local acyclic_dup_holder = {} + tmp = ItemStack("") + acyclic_dup_holder[tmp] = tmp + tmp = core.serialize_roundtrip(acyclic_dup_holder) + for k, v in pairs(tmp) do + assert(rawequal(k, v)) + end +end +unittests.register("test_object_passing", test_object_passing) + +local function test_userdata_passing(_, pos) + -- basic userdata passing + local obj = table.copy(test_object.tiles[1]) + obj.test = ItemStack("default:cobble 99") + local tmp = core.serialize_roundtrip(obj) + assert(type(tmp.test) == "userdata") + assert(obj.test:to_string() == tmp.test:to_string()) + + -- object can't be passed, should error + obj = core.raycast(pos, pos) + assert(not pcall(core.serialize_roundtrip, obj)) + + -- VManip + local vm = core.get_voxel_manip(pos, pos) + local expect = vm:get_node_at(pos) + local vm2 = core.serialize_roundtrip(vm) + assert(deepequal(vm2:get_node_at(pos), expect)) +end +unittests.register("test_userdata_passing", test_userdata_passing, {map=true}) + +-- Asynchronous jobs + +local function test_handle_async(cb) + -- Basic test including mod name tracking and unittests.async_test() + -- which is defined inside_async_env.lua + local func = function(x) + return core.get_last_run_mod(), _VERSION, unittests[x]() + end + local expect = {core.get_last_run_mod(), _VERSION, true} + + core.handle_async(func, function(...) + if not deepequal(expect, {...}) then + cb("Values did not equal") + end + if core.get_last_run_mod() ~= expect[1] then + cb("Mod name not tracked correctly") + end + + -- Test passing of nil arguments and return values + core.handle_async(function(a, b) + return a, b + end, function(a, b) + if b ~= 123 then + cb("Argument went missing") + end + cb() + end, nil, 123) + end, "async_test") +end +unittests.register("test_handle_async", test_handle_async, {async=true}) + +local function test_userdata_passing2(cb, _, pos) + -- VManip: check transfer into other env + local vm = core.get_voxel_manip(pos, pos) + local expect = vm:get_node_at(pos) + + core.handle_async(function(vm_, pos_) + return vm_:get_node_at(pos_) + end, function(ret) + if not deepequal(expect, ret) then + cb("Node data mismatch (one-way)") + end + + -- VManip: test a roundtrip + core.handle_async(function(vm_) + return vm_ + end, function(vm2) + if not deepequal(expect, vm2:get_node_at(pos)) then + cb("Node data mismatch (roundtrip)") + end + cb() + end, vm) + end, vm, pos) +end +unittests.register("test_userdata_passing2", test_userdata_passing2, {map=true, async=true}) diff --git a/games/devtest/mods/unittests/crafting.lua b/games/devtest/mods/unittests/crafting.lua index eff13ce09..8c16d3efb 100644 --- a/games/devtest/mods/unittests/crafting.lua +++ b/games/devtest/mods/unittests/crafting.lua @@ -1,6 +1,7 @@ +dofile(core.get_modpath(core.get_current_modname()) .. "/crafting_prepare.lua") + -- Test minetest.clear_craft function local function test_clear_craft() - minetest.log("info", "[unittests] Testing minetest.clear_craft") -- Clearing by output minetest.register_craft({ output = "foo", @@ -22,11 +23,10 @@ local function test_clear_craft() minetest.clear_craft({recipe={{"foo", "bar"}}}) assert(minetest.get_all_craft_recipes("foo") == nil) end +unittests.register("test_clear_craft", test_clear_craft) -- Test minetest.get_craft_result function local function test_get_craft_result() - minetest.log("info", "[unittests] Testing minetest.get_craft_result") - -- normal local input = { method = "normal", @@ -107,14 +107,6 @@ local function test_get_craft_result() assert(output.item) minetest.log("info", "[unittests] unrepairable tool crafting output.item:to_table(): "..dump(output.item:to_table())) -- unrepairable tool must not yield any output - assert(output.item:get_name() == "") - + assert(output.item:is_empty()) end - -function unittests.test_crafting() - test_clear_craft() - test_get_craft_result() - minetest.log("action", "[unittests] Crafting tests passed!") - return true -end - +unittests.register("test_get_craft_result", test_get_craft_result) diff --git a/games/devtest/mods/unittests/crafting_prepare.lua b/games/devtest/mods/unittests/crafting_prepare.lua index a09734827..5cf5775e0 100644 --- a/games/devtest/mods/unittests/crafting_prepare.lua +++ b/games/devtest/mods/unittests/crafting_prepare.lua @@ -31,25 +31,31 @@ minetest.register_craftitem("unittests:steel_ingot", { groups = { dummy = 1 }, }) +-- Use aliases in recipes for more complete testing + +minetest.register_alias("unittests:steel_ingot_alias", "unittests:steel_ingot") +minetest.register_alias("unittests:coal_lump_alias", "unittests:coal_lump") +minetest.register_alias("unittests:iron_lump_alias", "unittests:iron_lump") + -- Recipes for tests: Normal crafting, cooking and fuel minetest.register_craft({ output = 'unittests:torch 4', recipe = { - {'unittests:coal_lump'}, + {'unittests:coal_lump_alias'}, {'unittests:stick'}, } }) minetest.register_craft({ type = "cooking", - output = "unittests:steel_ingot", - recipe = "unittests:iron_lump", + output = "unittests:steel_ingot_alias", + recipe = "unittests:iron_lump_alias", }) minetest.register_craft({ type = "fuel", - recipe = "unittests:coal_lump", + recipe = "unittests:coal_lump_alias", burntime = 40, }) diff --git a/games/devtest/mods/unittests/init.lua b/games/devtest/mods/unittests/init.lua index 12c67f78b..0608f2dd2 100644 --- a/games/devtest/mods/unittests/init.lua +++ b/games/devtest/mods/unittests/init.lua @@ -1,18 +1,200 @@ unittests = {} -local modpath = minetest.get_modpath("unittests") -dofile(modpath .. "/random.lua") -dofile(modpath .. "/player.lua") -dofile(modpath .. "/crafting_prepare.lua") -dofile(modpath .. "/crafting.lua") -dofile(modpath .. "/itemdescription.lua") +unittests.list = {} -if minetest.settings:get_bool("devtest_unittests_autostart", false) then - unittests.test_random() - unittests.test_crafting() - unittests.test_short_desc() - minetest.register_on_joinplayer(function(player) - unittests.test_player(player) +-- name: Name of the test +-- func: +-- for sync: function(player, pos), should error on failure +-- for async: function(callback, player, pos) +-- MUST call callback() or callback("error msg") in case of error once test is finished +-- this means you cannot use assert() in the test implementation +-- opts: { +-- player = false, -- Does test require a player? +-- map = false, -- Does test require map access? +-- async = false, -- Does the test run asynchronously? (read notes above!) +-- } +function unittests.register(name, func, opts) + local def = table.copy(opts or {}) + def.name = name + def.func = func + table.insert(unittests.list, def) +end + +function unittests.on_finished(all_passed) + -- free to override +end + +-- Calls invoke with a callback as argument +-- Suspends coroutine until that callback is called +-- Return values are passed through +local function await(invoke) + local co = coroutine.running() + assert(co) + local called_early = true + invoke(function(...) + if called_early == true then + called_early = {...} + else + coroutine.resume(co, ...) + end + end) + if called_early ~= true then + -- callback was already called before yielding + return unpack(called_early) + end + called_early = nil + return coroutine.yield() +end + +function unittests.run_one(idx, counters, out_callback, player, pos) + local def = unittests.list[idx] + if not def.player then + player = nil + elseif player == nil then + out_callback(false) + return false + end + if not def.map then + pos = nil + elseif pos == nil then + out_callback(false) + return false + end + + local tbegin = core.get_us_time() + local function done(status, err) + local tend = core.get_us_time() + local ms_taken = (tend - tbegin) / 1000 + + if not status then + core.log("error", err) + end + print(string.format("[%s] %s - %dms", + status and "PASS" or "FAIL", def.name, ms_taken)) + counters.time = counters.time + ms_taken + counters.total = counters.total + 1 + if status then + counters.passed = counters.passed + 1 + end + end + + if def.async then + core.log("info", "[unittest] running " .. def.name .. " (async)") + def.func(function(err) + done(err == nil, err) + out_callback(true) + end, player, pos) + else + core.log("info", "[unittest] running " .. def.name) + local status, err = pcall(def.func, player, pos) + done(status, err) + out_callback(true) + end + + return true +end + +local function wait_for_player(callback) + if #core.get_connected_players() > 0 then + return callback(core.get_connected_players()[1]) + end + local first = true + core.register_on_joinplayer(function(player) + if first then + callback(player) + first = false + end end) end +local function wait_for_map(player, callback) + local check = function() + if core.get_node_or_nil(player:get_pos()) ~= nil then + callback() + else + minetest.after(0, check) + end + end + check() +end + +function unittests.run_all() + -- This runs in a coroutine so it uses await(). + local counters = { time = 0, total = 0, passed = 0 } + + -- Run standalone tests first + for idx = 1, #unittests.list do + local def = unittests.list[idx] + def.done = await(function(cb) + unittests.run_one(idx, counters, cb, nil, nil) + end) + end + + -- Wait for a player to join, run tests that require a player + local player = await(wait_for_player) + for idx = 1, #unittests.list do + local def = unittests.list[idx] + if not def.done then + def.done = await(function(cb) + unittests.run_one(idx, counters, cb, player, nil) + end) + end + end + + -- Wait for the world to generate/load, run tests that require map access + await(function(cb) + wait_for_map(player, cb) + end) + local pos = vector.round(player:get_pos()) + for idx = 1, #unittests.list do + local def = unittests.list[idx] + if not def.done then + def.done = await(function(cb) + unittests.run_one(idx, counters, cb, player, pos) + end) + end + end + + -- Print stats + assert(#unittests.list == counters.total) + print(string.rep("+", 80)) + print(string.format("Unit Test Results: %s", + counters.total == counters.passed and "PASSED" or "FAILED")) + print(string.format(" %d / %d failed tests.", + counters.total - counters.passed, counters.total)) + print(string.format(" Testing took %dms total.", counters.time)) + print(string.rep("+", 80)) + unittests.on_finished(counters.total == counters.passed) + return counters.total == counters.passed +end + +-------------- + +local modpath = minetest.get_modpath("unittests") +dofile(modpath .. "/misc.lua") +dofile(modpath .. "/player.lua") +dofile(modpath .. "/crafting.lua") +dofile(modpath .. "/itemdescription.lua") +dofile(modpath .. "/async_env.lua") + +-------------- + +if core.settings:get_bool("devtest_unittests_autostart", false) then + core.after(0, function() + coroutine.wrap(unittests.run_all)() + end) +else + minetest.register_chatcommand("unittests", { + privs = {basic_privs=true}, + description = "Runs devtest unittests (may modify player or map state)", + func = function(name, param) + unittests.on_finished = function(ok) + core.chat_send_player(name, + (ok and "All tests passed." or "There were test failures.") .. + " Check the console for detailed output.") + end + coroutine.wrap(unittests.run_all)() + return true, "" + end, + }) +end diff --git a/games/devtest/mods/unittests/inside_async_env.lua b/games/devtest/mods/unittests/inside_async_env.lua new file mode 100644 index 000000000..9774771f9 --- /dev/null +++ b/games/devtest/mods/unittests/inside_async_env.lua @@ -0,0 +1,15 @@ +unittests = {} + +core.log("info", "Hello World") + +function unittests.async_test() + assert(core == minetest) + -- stuff that should not be here + assert(not core.get_player_by_name) + assert(not core.set_node) + assert(not core.object_refs) + -- stuff that should be here + assert(ItemStack) + assert(core.registered_items[""]) + return true +end diff --git a/games/devtest/mods/unittests/itemdescription.lua b/games/devtest/mods/unittests/itemdescription.lua index d6ee6551a..b4c218c98 100644 --- a/games/devtest/mods/unittests/itemdescription.lua +++ b/games/devtest/mods/unittests/itemdescription.lua @@ -1,17 +1,7 @@ -local full_description = "Colorful Pickaxe\nThe best pick." -minetest.register_tool("unittests:colorful_pick", { +local full_description = "Description Test Item\nFor testing item decription" +minetest.register_tool("unittests:description_test", { description = full_description, - inventory_image = "basetools_mesepick.png", - tool_capabilities = { - full_punch_interval = 1.0, - max_drop_level=3, - groupcaps={ - cracky={times={[1]=2.0, [2]=1.0, [3]=0.5}, uses=20, maxlevel=3}, - crumbly={times={[1]=2.0, [2]=1.0, [3]=0.5}, uses=20, maxlevel=3}, - snappy={times={[1]=2.0, [2]=1.0, [3]=0.5}, uses=20, maxlevel=3} - }, - damage_groups = {fleshy=4}, - }, + inventory_image = "unittests_description_test.png", }) minetest.register_chatcommand("item_description", { @@ -25,23 +15,23 @@ minetest.register_chatcommand("item_description", { end }) -function unittests.test_short_desc() +local function test_short_desc() local function get_short_description(item) return ItemStack(item):get_short_description() end - local stack = ItemStack("unittests:colorful_pick") - assert(stack:get_short_description() == "Colorful Pickaxe") - assert(get_short_description("unittests:colorful_pick") == "Colorful Pickaxe") - assert(minetest.registered_items["unittests:colorful_pick"].short_description == nil) + local stack = ItemStack("unittests:description_test") + assert(stack:get_short_description() == "Description Test Item") + assert(get_short_description("unittests:description_test") == "Description Test Item") + assert(minetest.registered_items["unittests:description_test"].short_description == nil) assert(stack:get_description() == full_description) - assert(stack:get_description() == minetest.registered_items["unittests:colorful_pick"].description) + assert(stack:get_description() == minetest.registered_items["unittests:description_test"].description) stack:get_meta():set_string("description", "Hello World") assert(stack:get_short_description() == "Hello World") assert(stack:get_description() == "Hello World") assert(get_short_description(stack) == "Hello World") - assert(get_short_description("unittests:colorful_pick") == "Colorful Pickaxe") + assert(get_short_description("unittests:description_test") == "Description Test Item") stack:get_meta():set_string("short_description", "Foo Bar") assert(stack:get_short_description() == "Foo Bar") @@ -49,3 +39,4 @@ function unittests.test_short_desc() return true end +unittests.register("test_short_desc", test_short_desc) diff --git a/games/devtest/mods/unittests/misc.lua b/games/devtest/mods/unittests/misc.lua new file mode 100644 index 000000000..ba980866a --- /dev/null +++ b/games/devtest/mods/unittests/misc.lua @@ -0,0 +1,50 @@ +local function test_random() + -- Try out PseudoRandom + local pseudo = PseudoRandom(13) + assert(pseudo:next() == 22290) + assert(pseudo:next() == 13854) +end +unittests.register("test_random", test_random) + +local function test_dynamic_media(cb, player) + if core.get_player_information(player:get_player_name()).protocol_version < 40 then + core.log("warning", "test_dynamic_media: Client too old, skipping test.") + return cb() + end + + -- Check that the client acknowledges media transfers + local path = core.get_worldpath() .. "/test_media.obj" + local f = io.open(path, "w") + f:write("# contents don't matter\n") + f:close() + + local call_ok = false + local ok = core.dynamic_add_media({ + filepath = path, + to_player = player:get_player_name(), + }, function(name) + if not call_ok then + cb("impossible condition") + end + cb() + end) + if not ok then + return cb("dynamic_add_media() returned error") + end + call_ok = true + + -- if the callback isn't called this test will just hang :shrug: +end +unittests.register("test_dynamic_media", test_dynamic_media, {async=true, player=true}) + +local function test_v3f_metatable(player) + assert(vector.check(player:get_pos())) +end +unittests.register("test_v3f_metatable", test_v3f_metatable, {player=true}) + +local function test_v3s16_metatable(player, pos) + local node = minetest.get_node(pos) + local found_pos = minetest.find_node_near(pos, 0, node.name, true) + assert(vector.check(found_pos)) +end +unittests.register("test_v3s16_metatable", test_v3s16_metatable, {map=true}) diff --git a/games/devtest/mods/unittests/player.lua b/games/devtest/mods/unittests/player.lua index 4a681310d..fa0557960 100644 --- a/games/devtest/mods/unittests/player.lua +++ b/games/devtest/mods/unittests/player.lua @@ -2,6 +2,21 @@ -- HP Change Reasons -- local expect = nil +minetest.register_on_player_hpchange(function(player, hp, reason) + if expect == nil then + return + end + + for key, value in pairs(reason) do + assert(expect[key] == value) + end + for key, value in pairs(expect) do + assert(reason[key] == value) + end + + expect = nil +end) + local function run_hpchangereason_tests(player) local old_hp = player:get_hp() @@ -20,7 +35,11 @@ local function run_hpchangereason_tests(player) player:set_hp(old_hp) end +unittests.register("test_hpchangereason", run_hpchangereason_tests, {player=true}) +-- +-- Player meta +-- local function run_player_meta_tests(player) local meta = player:get_meta() meta:set_string("foo", "bar") @@ -48,29 +67,4 @@ local function run_player_meta_tests(player) assert(meta:get_string("foo") == "") assert(meta:equals(meta2)) end - -function unittests.test_player(player) - minetest.register_on_player_hpchange(function(player, hp, reason) - if not expect then - return - end - - for key, value in pairs(reason) do - assert(expect[key] == value) - end - - for key, value in pairs(expect) do - assert(reason[key] == value) - end - - expect = nil - end) - - run_hpchangereason_tests(player) - run_player_meta_tests(player) - local msg = "Player tests passed for player '"..player:get_player_name().."'!" - minetest.chat_send_all(msg) - minetest.log("action", "[unittests] "..msg) - return true -end - +unittests.register("test_player_meta", run_player_meta_tests, {player=true}) diff --git a/games/devtest/mods/unittests/random.lua b/games/devtest/mods/unittests/random.lua deleted file mode 100644 index f94f0a88e..000000000 --- a/games/devtest/mods/unittests/random.lua +++ /dev/null @@ -1,10 +0,0 @@ -function unittests.test_random() - -- Try out PseudoRandom - minetest.log("action", "[unittests] Testing PseudoRandom ...") - local pseudo = PseudoRandom(13) - assert(pseudo:next() == 22290) - assert(pseudo:next() == 13854) - minetest.log("action", "[unittests] PseudoRandom test passed!") - return true -end - diff --git a/games/devtest/mods/unittests/textures/unittests_description_test.png b/games/devtest/mods/unittests/textures/unittests_description_test.png new file mode 100644 index 000000000..a6be43314 Binary files /dev/null and b/games/devtest/mods/unittests/textures/unittests_description_test.png differ diff --git a/games/devtest/mods/util_commands/init.lua b/games/devtest/mods/util_commands/init.lua index ca5dca2d9..c37364042 100644 --- a/games/devtest/mods/util_commands/init.lua +++ b/games/devtest/mods/util_commands/init.lua @@ -114,6 +114,59 @@ minetest.register_chatcommand("detach", { end, }) +minetest.register_chatcommand("use_tool", { + params = "(dig ) | (hit ) []", + description = "Apply tool wear a number of times, as if it were used for digging", + func = function(name, param) + local player = minetest.get_player_by_name(name) + if not player then + return false, "No player." + end + local mode, group, level, uses = string.match(param, "([a-z]+) ([a-z0-9]+) (-?%d+) (%d+)") + if not mode then + mode, group, level = string.match(param, "([a-z]+) ([a-z0-9]+) (-?%d+)") + uses = 1 + end + if not mode or not group or not level then + return false + end + if mode ~= "dig" and mode ~= "hit" then + return false + end + local tool = player:get_wielded_item() + local caps = tool:get_tool_capabilities() + if not caps or tool:get_count() == 0 then + return false, "No tool in hand." + end + local actual_uses = 0 + for u=1, uses do + local wear = tool:get_wear() + local dp + if mode == "dig" then + dp = minetest.get_dig_params({[group]=3, level=level}, caps, wear) + else + dp = minetest.get_hit_params({[group]=100}, caps, level, wear) + end + tool:add_wear(dp.wear) + actual_uses = actual_uses + 1 + if tool:get_count() == 0 then + break + end + end + player:set_wielded_item(tool) + if tool:get_count() == 0 then + return true, string.format("Tool used %d time(s). ".. + "The tool broke after %d use(s).", uses, actual_uses) + else + local wear = tool:get_wear() + return true, string.format("Tool used %d time(s). ".. + "Final wear=%d", uses, wear) + end + end, +}) + + + -- Use this to test waypoint capabilities minetest.register_chatcommand("test_waypoints", { params = "[change_immediate]", @@ -193,3 +246,64 @@ function minetest.handle_node_drops(pos, drops, digger) end end end + +minetest.register_chatcommand("set_displayed_itemcount", { + params = "(-s \"\" [-c ]) | -a ", + description = "Set the displayed itemcount of the wielded item", + func = function(name, param) + local player = minetest.get_player_by_name(name) + local item = player:get_wielded_item() + local meta = item:get_meta() + local flag1 = param:sub(1, 2) + if flag1 == "-s" then + if param:sub(3, 4) ~= " \"" then + return false, "Error: Space and string with \"s expected after -s." + end + local se = param:find("\"", 5, true) + if not se then + return false, "Error: String with two \"s expected after -s." + end + local s = param:sub(5, se - 1) + if param:sub(se + 1, se + 4) == " -c " then + s = minetest.colorize(param:sub(se + 5), s) + end + meta:set_string("count_meta", s) + elseif flag1 == "-a" then + local num = tonumber(param:sub(4)) + if not num then + return false, "Error: Invalid number: "..param:sub(4) + end + meta:set_int("count_alignment", num) + else + return false + end + player:set_wielded_item(item) + return true, "Displayed itemcount set." + end, +}) + +minetest.register_chatcommand("dump_item", { + params = "", + description = "Prints a dump of the wielded item 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:to_table()) + print(str) + return true, str + end, +}) + +-- shadow control +minetest.register_on_joinplayer(function (player) + player:set_lighting({shadows={intensity = 0.33}}) +end) + +core.register_chatcommand("set_shadow", { + params = "", + description = "Set shadow parameters of current player.", + func = function(player_name, param) + local shadow_intensity = tonumber(param) + minetest.get_player_by_name(player_name):set_lighting({shadows = { intensity = shadow_intensity} }) + end +}) diff --git a/lib/bitop/CMakeLists.txt b/lib/bitop/CMakeLists.txt new file mode 100644 index 000000000..02e8a421d --- /dev/null +++ b/lib/bitop/CMakeLists.txt @@ -0,0 +1,4 @@ +add_library(bitop STATIC bit.c) +target_link_libraries(bitop) + +include_directories(${LUA_INCLUDE_DIR}) diff --git a/lib/bitop/bit.c b/lib/bitop/bit.c new file mode 100644 index 000000000..f23c31a4c --- /dev/null +++ b/lib/bitop/bit.c @@ -0,0 +1,189 @@ +/* +** Lua BitOp -- a bit operations library for Lua 5.1/5.2. +** http://bitop.luajit.org/ +** +** Copyright (C) 2008-2012 Mike Pall. All rights reserved. +** +** Permission is hereby granted, free of charge, to any person obtaining +** a copy of this software and associated documentation files (the +** "Software"), to deal in the Software without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Software, and to +** permit persons to whom the Software is furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be +** included in all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +** SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +** +** [ MIT license: http://www.opensource.org/licenses/mit-license.php ] +*/ + +#include "bit.h" + +#define LUA_BITOP_VERSION "1.0.2" + +#define LUA_LIB +#include "lauxlib.h" + +#ifdef _MSC_VER +/* MSVC is stuck in the last century and doesn't have C99's stdint.h. */ +typedef __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef unsigned __int64 uint64_t; +#else +#include +#endif + +typedef int32_t SBits; +typedef uint32_t UBits; + +typedef union { + lua_Number n; +#ifdef LUA_NUMBER_DOUBLE + uint64_t b; +#else + UBits b; +#endif +} BitNum; + +/* Convert argument to bit type. */ +static UBits barg(lua_State *L, int idx) +{ + BitNum bn; + UBits b; +#if LUA_VERSION_NUM < 502 + bn.n = lua_tonumber(L, idx); +#else + bn.n = luaL_checknumber(L, idx); +#endif +#if defined(LUA_NUMBER_DOUBLE) + bn.n += 6755399441055744.0; /* 2^52+2^51 */ +#ifdef SWAPPED_DOUBLE + b = (UBits)(bn.b >> 32); +#else + b = (UBits)bn.b; +#endif +#elif defined(LUA_NUMBER_INT) || defined(LUA_NUMBER_LONG) || \ + defined(LUA_NUMBER_LONGLONG) || defined(LUA_NUMBER_LONG_LONG) || \ + defined(LUA_NUMBER_LLONG) + if (sizeof(UBits) == sizeof(lua_Number)) + b = bn.b; + else + b = (UBits)(SBits)bn.n; +#elif defined(LUA_NUMBER_FLOAT) +#error "A 'float' lua_Number type is incompatible with this library" +#else +#error "Unknown number type, check LUA_NUMBER_* in luaconf.h" +#endif +#if LUA_VERSION_NUM < 502 + if (b == 0 && !lua_isnumber(L, idx)) { + luaL_typerror(L, idx, "number"); + } +#endif + return b; +} + +/* Return bit type. */ +#define BRET(b) lua_pushnumber(L, (lua_Number)(SBits)(b)); return 1; + +static int bit_tobit(lua_State *L) { BRET(barg(L, 1)) } +static int bit_bnot(lua_State *L) { BRET(~barg(L, 1)) } + +#define BIT_OP(func, opr) \ + static int func(lua_State *L) { int i; UBits b = barg(L, 1); \ + for (i = lua_gettop(L); i > 1; i--) b opr barg(L, i); BRET(b) } +BIT_OP(bit_band, &=) +BIT_OP(bit_bor, |=) +BIT_OP(bit_bxor, ^=) + +#define bshl(b, n) (b << n) +#define bshr(b, n) (b >> n) +#define bsar(b, n) ((SBits)b >> n) +#define brol(b, n) ((b << n) | (b >> (32-n))) +#define bror(b, n) ((b << (32-n)) | (b >> n)) +#define BIT_SH(func, fn) \ + static int func(lua_State *L) { \ + UBits b = barg(L, 1); UBits n = barg(L, 2) & 31; BRET(fn(b, n)) } +BIT_SH(bit_lshift, bshl) +BIT_SH(bit_rshift, bshr) +BIT_SH(bit_arshift, bsar) +BIT_SH(bit_rol, brol) +BIT_SH(bit_ror, bror) + +static int bit_bswap(lua_State *L) +{ + UBits b = barg(L, 1); + b = (b >> 24) | ((b >> 8) & 0xff00) | ((b & 0xff00) << 8) | (b << 24); + BRET(b) +} + +static int bit_tohex(lua_State *L) +{ + UBits b = barg(L, 1); + SBits n = lua_isnone(L, 2) ? 8 : (SBits)barg(L, 2); + const char *hexdigits = "0123456789abcdef"; + char buf[8]; + int i; + if (n < 0) { n = -n; hexdigits = "0123456789ABCDEF"; } + if (n > 8) n = 8; + for (i = (int)n; --i >= 0; ) { buf[i] = hexdigits[b & 15]; b >>= 4; } + lua_pushlstring(L, buf, (size_t)n); + return 1; +} + +static const struct luaL_Reg bit_funcs[] = { + { "tobit", bit_tobit }, + { "bnot", bit_bnot }, + { "band", bit_band }, + { "bor", bit_bor }, + { "bxor", bit_bxor }, + { "lshift", bit_lshift }, + { "rshift", bit_rshift }, + { "arshift", bit_arshift }, + { "rol", bit_rol }, + { "ror", bit_ror }, + { "bswap", bit_bswap }, + { "tohex", bit_tohex }, + { NULL, NULL } +}; + +/* Signed right-shifts are implementation-defined per C89/C99. +** But the de facto standard are arithmetic right-shifts on two's +** complement CPUs. This behaviour is required here, so test for it. +*/ +#define BAD_SAR (bsar(-8, 2) != (SBits)-2) + +LUALIB_API int luaopen_bit(lua_State *L) +{ + UBits b; + lua_pushnumber(L, (lua_Number)1437217655L); + b = barg(L, -1); + if (b != (UBits)1437217655L || BAD_SAR) { /* Perform a simple self-test. */ + const char *msg = "compiled with incompatible luaconf.h"; +#ifdef LUA_NUMBER_DOUBLE +#ifdef _WIN32 + if (b == (UBits)1610612736L) + msg = "use D3DCREATE_FPU_PRESERVE with DirectX"; +#endif + if (b == (UBits)1127743488L) + msg = "not compiled with SWAPPED_DOUBLE"; +#endif + if (BAD_SAR) + msg = "arithmetic right-shift broken"; + luaL_error(L, "bit library self-test failed (%s)", msg); + } +#if LUA_VERSION_NUM < 502 + luaL_register(L, "bit", bit_funcs); +#else + luaL_newlib(L, bit_funcs); +#endif + return 1; +} diff --git a/lib/bitop/bit.h b/lib/bitop/bit.h new file mode 100644 index 000000000..3e5845ee5 --- /dev/null +++ b/lib/bitop/bit.h @@ -0,0 +1,34 @@ +/* +** Lua BitOp -- a bit operations library for Lua 5.1/5.2. +** http://bitop.luajit.org/ +** +** Copyright (C) 2008-2012 Mike Pall. All rights reserved. +** +** Permission is hereby granted, free of charge, to any person obtaining +** a copy of this software and associated documentation files (the +** "Software"), to deal in the Software without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Software, and to +** permit persons to whom the Software is furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be +** included in all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +** SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +** +** [ MIT license: http://www.opensource.org/licenses/mit-license.php ] +*/ + +#pragma once + +#include "lua.h" + +#define LUA_BITLIBNAME "bit" +LUALIB_API int luaopen_bit(lua_State *L); diff --git a/lib/catch2/CMakeLists.txt b/lib/catch2/CMakeLists.txt new file mode 100644 index 000000000..3c471d49d --- /dev/null +++ b/lib/catch2/CMakeLists.txt @@ -0,0 +1,16 @@ +# catch2 is distributed as a standalone header. +# +# Downloaded from: +# +# https://github.com/catchorg/Catch2/releases/download/v2.13.9/catch.hpp +# +# The following changes were made to always print in microseconds, fixed format: +# +# - explicit Duration(double inNanoseconds, Unit units = Unit::Auto) +# + explicit Duration(double inNanoseconds, Unit units = Unit::Microseconds) +# +# - return os << duration.value() << ' ' << duration.unitsAsString(); +# + return os << std::fixed << duration.value() << ' ' << duration.unitsAsString(); + +add_library(catch2 INTERFACE) +target_include_directories(catch2 INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/lib/catch2/catch.hpp b/lib/catch2/catch.hpp new file mode 100644 index 000000000..88f110677 --- /dev/null +++ b/lib/catch2/catch.hpp @@ -0,0 +1,17970 @@ +/* + * Catch v2.13.9 + * Generated: 2022-04-12 22:37:23.260201 + * ---------------------------------------------------------- + * This file has been merged from multiple headers. Please don't edit it directly + * Copyright (c) 2022 Two Blue Cubes Ltd. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +// start catch.hpp + + +#define CATCH_VERSION_MAJOR 2 +#define CATCH_VERSION_MINOR 13 +#define CATCH_VERSION_PATCH 9 + +#ifdef __clang__ +# pragma clang system_header +#elif defined __GNUC__ +# pragma GCC system_header +#endif + +// start catch_suppress_warnings.h + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(push) +# pragma warning(disable: 161 1682) +# else // __ICC +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpadded" +# pragma clang diagnostic ignored "-Wswitch-enum" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +# endif +#elif defined __GNUC__ + // Because REQUIREs trigger GCC's -Wparentheses, and because still + // supported version of g++ have only buggy support for _Pragmas, + // Wparentheses have to be suppressed globally. +# pragma GCC diagnostic ignored "-Wparentheses" // See #674 for details + +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-variable" +# pragma GCC diagnostic ignored "-Wpadded" +#endif +// end catch_suppress_warnings.h +#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) +# define CATCH_IMPL +# define CATCH_CONFIG_ALL_PARTS +#endif + +// In the impl file, we want to have access to all parts of the headers +// Can also be used to sanely support PCHs +#if defined(CATCH_CONFIG_ALL_PARTS) +# define CATCH_CONFIG_EXTERNAL_INTERFACES +# if defined(CATCH_CONFIG_DISABLE_MATCHERS) +# undef CATCH_CONFIG_DISABLE_MATCHERS +# endif +# if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +# endif +#endif + +#if !defined(CATCH_CONFIG_IMPL_ONLY) +// start catch_platform.h + +// See e.g.: +// https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/TargetConditionals.h.auto.html +#ifdef __APPLE__ +# include +# if (defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1) || \ + (defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1) +# define CATCH_PLATFORM_MAC +# elif (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1) +# define CATCH_PLATFORM_IPHONE +# endif + +#elif defined(linux) || defined(__linux) || defined(__linux__) +# define CATCH_PLATFORM_LINUX + +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) +# define CATCH_PLATFORM_WINDOWS +#endif + +// end catch_platform.h + +#ifdef CATCH_IMPL +# ifndef CLARA_CONFIG_MAIN +# define CLARA_CONFIG_MAIN_NOT_DEFINED +# define CLARA_CONFIG_MAIN +# endif +#endif + +// start catch_user_interfaces.h + +namespace Catch { + unsigned int rngSeed(); +} + +// end catch_user_interfaces.h +// start catch_tag_alias_autoregistrar.h + +// start catch_common.h + +// start catch_compiler_capabilities.h + +// Detect a number of compiler features - by compiler +// The following features are defined: +// +// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? +// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? +// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? +// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? +// **************** +// Note to maintainers: if new toggles are added please document them +// in configuration.md, too +// **************** + +// In general each macro has a _NO_ form +// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + +#ifdef __cplusplus + +# if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) +# define CATCH_CPP14_OR_GREATER +# endif + +# if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +# define CATCH_CPP17_OR_GREATER +# endif + +#endif + +// Only GCC compiler should be used in this block, so other compilers trying to +// mask themselves as GCC should be ignored. +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && !defined(__LCC__) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" ) + +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) + +#endif + +#if defined(__clang__) + +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic pop" ) + +// As of this writing, IBM XL's implementation of __builtin_constant_p has a bug +// which results in calls to destructors being emitted for each temporary, +// without a matching initialization. In practice, this can result in something +// like `std::string::~string` being called on an uninitialized value. +// +// For example, this code will likely segfault under IBM XL: +// ``` +// REQUIRE(std::string("12") + "34" == "1234") +// ``` +// +// Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented. +# if !defined(__ibmxl__) && !defined(__CUDACC__) +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg, hicpp-vararg) */ +# endif + +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ + _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") + +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) + +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-template\"" ) + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// Assume that non-Windows platforms support posix signals by default +#if !defined(CATCH_PLATFORM_WINDOWS) + #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS +#endif + +//////////////////////////////////////////////////////////////////////////////// +// We know some environments not to support full POSIX signals +#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__) + #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +#endif + +#ifdef __OS400__ +# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +# define CATCH_CONFIG_COLOUR_NONE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Android somehow still does not support std::to_string +#if defined(__ANDROID__) +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING +# define CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Not all Windows environments support SEH properly +#if defined(__MINGW32__) +# define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH +#endif + +//////////////////////////////////////////////////////////////////////////////// +// PS4 +#if defined(__ORBIS__) +# define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Cygwin +#ifdef __CYGWIN__ + +// Required for some versions of Cygwin to declare gettimeofday +// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin +# define _BSD_SOURCE +// some versions of cygwin (most) do not support std::to_string. Use the libstd check. +// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 +# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ + && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) + +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING + +# endif +#endif // __CYGWIN__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#if defined(_MSC_VER) + +// Universal Windows platform does not support SEH +// Or console colours (or console at all...) +# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) +# define CATCH_CONFIG_COLOUR_NONE +# else +# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH +# endif + +# if !defined(__clang__) // Handle Clang masquerading for msvc + +// MSVC traditional preprocessor needs some workaround for __VA_ARGS__ +// _MSVC_TRADITIONAL == 0 means new conformant preprocessor +// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor +# if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) +# define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +# endif // MSVC_TRADITIONAL + +// Only do this if we're not using clang on Windows, which uses `diagnostic push` & `diagnostic pop` +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma( warning(push) ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION __pragma( warning(pop) ) +# endif // __clang__ + +#endif // _MSC_VER + +#if defined(_REENTRANT) || defined(_MSC_VER) +// Enable async processing, as -pthread is specified or no additional linking is required +# define CATCH_INTERNAL_CONFIG_USE_ASYNC +#endif // _MSC_VER + +//////////////////////////////////////////////////////////////////////////////// +// Check if we are compiled with -fno-exceptions or equivalent +#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) +# define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED +#endif + +//////////////////////////////////////////////////////////////////////////////// +// DJGPP +#ifdef __DJGPP__ +# define CATCH_INTERNAL_CONFIG_NO_WCHAR +#endif // __DJGPP__ + +//////////////////////////////////////////////////////////////////////////////// +// Embarcadero C++Build +#if defined(__BORLANDC__) + #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN +#endif + +//////////////////////////////////////////////////////////////////////////////// + +// Use of __COUNTER__ is suppressed during code analysis in +// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly +// handled by it. +// Otherwise all supported compilers support COUNTER macro, +// but user still might want to turn it off +#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) + #define CATCH_INTERNAL_CONFIG_COUNTER +#endif + +//////////////////////////////////////////////////////////////////////////////// + +// RTX is a special version of Windows that is real time. +// This means that it is detected as Windows, but does not provide +// the same set of capabilities as real Windows does. +#if defined(UNDER_RTSS) || defined(RTX64_BUILD) + #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH + #define CATCH_INTERNAL_CONFIG_NO_ASYNC + #define CATCH_CONFIG_COLOUR_NONE +#endif + +#if !defined(_GLIBCXX_USE_C99_MATH_TR1) +#define CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER +#endif + +// Various stdlib support checks that require __has_include +#if defined(__has_include) + // Check if string_view is available and usable + #if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW + #endif + + // Check if optional is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + + // Check if byte is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # include + # if defined(__cpp_lib_byte) && (__cpp_lib_byte > 0) + # define CATCH_INTERNAL_CONFIG_CPP17_BYTE + # endif + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + + // Check if variant is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # if defined(__clang__) && (__clang_major__ < 8) + // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 + // fix should be in clang 8, workaround in libstdc++ 8.2 + # include + # if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # define CATCH_CONFIG_NO_CPP17_VARIANT + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__clang__) && (__clang_major__ < 8) + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) +#endif // defined(__has_include) + +#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) +# define CATCH_CONFIG_COUNTER +#endif +#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) +# define CATCH_CONFIG_WINDOWS_SEH +#endif +// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. +#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) +# define CATCH_CONFIG_POSIX_SIGNALS +#endif +// This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions. +#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR) +# define CATCH_CONFIG_WCHAR +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) +# define CATCH_CONFIG_CPP11_TO_STRING +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL) +# define CATCH_CONFIG_CPP17_OPTIONAL +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) +# define CATCH_CONFIG_CPP17_STRING_VIEW +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) +# define CATCH_CONFIG_CPP17_VARIANT +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE) +# define CATCH_CONFIG_CPP17_BYTE +#endif + +#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) +# define CATCH_INTERNAL_CONFIG_NEW_CAPTURE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) +# define CATCH_CONFIG_NEW_CAPTURE +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +# define CATCH_CONFIG_DISABLE_EXCEPTIONS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN) +# define CATCH_CONFIG_POLYFILL_ISNAN +#endif + +#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) +# define CATCH_CONFIG_USE_ASYNC +#endif + +#if defined(CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_NO_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_ANDROID_LOGWRITE) +# define CATCH_CONFIG_ANDROID_LOGWRITE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) +# define CATCH_CONFIG_GLOBAL_NEXTAFTER +#endif + +// Even if we do not think the compiler has that warning, we still have +// to provide a macro that can be used by the code. +#if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION +#endif +#if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS +#endif + +// The goal of this macro is to avoid evaluation of the arguments, but +// still have the compiler warn on problems inside... +#if !defined(CATCH_INTERNAL_IGNORE_BUT_WARN) +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) +#endif + +#if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#elif defined(__clang__) && (__clang_major__ < 5) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif + +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif + +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +#define CATCH_TRY if ((true)) +#define CATCH_CATCH_ALL if ((false)) +#define CATCH_CATCH_ANON(type) if ((false)) +#else +#define CATCH_TRY try +#define CATCH_CATCH_ALL catch (...) +#define CATCH_CATCH_ANON(type) catch (type) +#endif + +#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) +#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#endif + +// end catch_compiler_capabilities.h +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#ifdef CATCH_CONFIG_COUNTER +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) +#else +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) +#endif + +#include +#include +#include + +// We need a dummy global operator<< so we can bring it into Catch namespace later +struct Catch_global_namespace_dummy {}; +std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); + +namespace Catch { + + struct CaseSensitive { enum Choice { + Yes, + No + }; }; + + class NonCopyable { + NonCopyable( NonCopyable const& ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable& operator = ( NonCopyable const& ) = delete; + NonCopyable& operator = ( NonCopyable && ) = delete; + + protected: + NonCopyable(); + virtual ~NonCopyable(); + }; + + struct SourceLineInfo { + + SourceLineInfo() = delete; + SourceLineInfo( char const* _file, std::size_t _line ) noexcept + : file( _file ), + line( _line ) + {} + + SourceLineInfo( SourceLineInfo const& other ) = default; + SourceLineInfo& operator = ( SourceLineInfo const& ) = default; + SourceLineInfo( SourceLineInfo&& ) noexcept = default; + SourceLineInfo& operator = ( SourceLineInfo&& ) noexcept = default; + + bool empty() const noexcept { return file[0] == '\0'; } + bool operator == ( SourceLineInfo const& other ) const noexcept; + bool operator < ( SourceLineInfo const& other ) const noexcept; + + char const* file; + std::size_t line; + }; + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); + + // Bring in operator<< from global namespace into Catch namespace + // This is necessary because the overload of operator<< above makes + // lookup stop at namespace Catch + using ::operator<<; + + // Use this in variadic streaming macros to allow + // >> +StreamEndStop + // as well as + // >> stuff +StreamEndStop + struct StreamEndStop { + std::string operator+() const; + }; + template + T const& operator + ( T const& value, StreamEndStop ) { + return value; + } +} + +#define CATCH_INTERNAL_LINEINFO \ + ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) + +// end catch_common.h +namespace Catch { + + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + }; + +} // end namespace Catch + +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +// end catch_tag_alias_autoregistrar.h +// start catch_test_registry.h + +// start catch_interfaces_testcase.h + +#include + +namespace Catch { + + class TestSpec; + + struct ITestInvoker { + virtual void invoke () const = 0; + virtual ~ITestInvoker(); + }; + + class TestCase; + struct IConfig; + + struct ITestCaseRegistry { + virtual ~ITestCaseRegistry(); + virtual std::vector const& getAllTests() const = 0; + virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; + }; + + bool isThrowSafe( TestCase const& testCase, IConfig const& config ); + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector const& getAllTestCasesSorted( IConfig const& config ); + +} + +// end catch_interfaces_testcase.h +// start catch_stringref.h + +#include +#include +#include +#include + +namespace Catch { + + /// A non-owning string class (similar to the forthcoming std::string_view) + /// Note that, because a StringRef may be a substring of another string, + /// it may not be null terminated. + class StringRef { + public: + using size_type = std::size_t; + using const_iterator = const char*; + + private: + static constexpr char const* const s_empty = ""; + + char const* m_start = s_empty; + size_type m_size = 0; + + public: // construction + constexpr StringRef() noexcept = default; + + StringRef( char const* rawChars ) noexcept; + + constexpr StringRef( char const* rawChars, size_type size ) noexcept + : m_start( rawChars ), + m_size( size ) + {} + + StringRef( std::string const& stdString ) noexcept + : m_start( stdString.c_str() ), + m_size( stdString.size() ) + {} + + explicit operator std::string() const { + return std::string(m_start, m_size); + } + + public: // operators + auto operator == ( StringRef const& other ) const noexcept -> bool; + auto operator != (StringRef const& other) const noexcept -> bool { + return !(*this == other); + } + + auto operator[] ( size_type index ) const noexcept -> char { + assert(index < m_size); + return m_start[index]; + } + + public: // named queries + constexpr auto empty() const noexcept -> bool { + return m_size == 0; + } + constexpr auto size() const noexcept -> size_type { + return m_size; + } + + // Returns the current start pointer. If the StringRef is not + // null-terminated, throws std::domain_exception + auto c_str() const -> char const*; + + public: // substrings and searches + // Returns a substring of [start, start + length). + // If start + length > size(), then the substring is [start, size()). + // If start > size(), then the substring is empty. + auto substr( size_type start, size_type length ) const noexcept -> StringRef; + + // Returns the current start pointer. May not be null-terminated. + auto data() const noexcept -> char const*; + + constexpr auto isNullTerminated() const noexcept -> bool { + return m_start[m_size] == '\0'; + } + + public: // iterators + constexpr const_iterator begin() const { return m_start; } + constexpr const_iterator end() const { return m_start + m_size; } + }; + + auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&; + auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; + + constexpr auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { + return StringRef( rawChars, size ); + } +} // namespace Catch + +constexpr auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { + return Catch::StringRef( rawChars, size ); +} + +// end catch_stringref.h +// start catch_preprocessor.hpp + + +#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ +#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) + +#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ +// MSVC needs more evaluations +#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) +#else +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) +#endif + +#define CATCH_REC_END(...) +#define CATCH_REC_OUT + +#define CATCH_EMPTY() +#define CATCH_DEFER(id) id CATCH_EMPTY() + +#define CATCH_REC_GET_END2() 0, CATCH_REC_END +#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 +#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 +#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT +#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0) +#define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) + +#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) + +#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) + +// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, +// and passes userdata as the first parameter to each invocation, +// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) +#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) +#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__ +#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ +#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) +#else +// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) +#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) +#endif + +#define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ +#define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) + +#define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper()) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) +#else +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper())) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) +#endif + +#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\ + CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__) + +#define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) +#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) +#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) +#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) +#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) +#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) +#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _3, _4, _5, _6) +#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) +#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) +#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) +#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) + +#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N + +#define INTERNAL_CATCH_TYPE_GEN\ + template struct TypeList {};\ + template\ + constexpr auto get_wrapper() noexcept -> TypeList { return {}; }\ + template class...> struct TemplateTypeList{};\ + template class...Cs>\ + constexpr auto get_wrapper() noexcept -> TemplateTypeList { return {}; }\ + template\ + struct append;\ + template\ + struct rewrap;\ + template class, typename...>\ + struct create;\ + template class, typename>\ + struct convert;\ + \ + template \ + struct append { using type = T; };\ + template< template class L1, typename...E1, template class L2, typename...E2, typename...Rest>\ + struct append, L2, Rest...> { using type = typename append, Rest...>::type; };\ + template< template class L1, typename...E1, typename...Rest>\ + struct append, TypeList, Rest...> { using type = L1; };\ + \ + template< template class Container, template class List, typename...elems>\ + struct rewrap, List> { using type = TypeList>; };\ + template< template class Container, template class List, class...Elems, typename...Elements>\ + struct rewrap, List, Elements...> { using type = typename append>, typename rewrap, Elements...>::type>::type; };\ + \ + template