diff --git a/.clang-tidy b/.clang-tidy index 1b9f8bd07..db4529b8a 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,4 +1,4 @@ -Checks: '-*,modernize-use-emplace,modernize-avoid-bind,misc-throw-by-value-catch-by-reference,misc-unconventional-assign-operator,performance-*' +Checks: '-*,modernize-use-emplace,modernize-avoid-bind,misc-throw-by-value-catch-by-reference,misc-unconventional-assign-operator,performance-*,-performance-avoid-endl,performance-inefficient-string-concatenation' WarningsAsErrors: '-*,modernize-use-emplace,performance-type-promotion-in-math-fn,performance-faster-string-find,performance-implicit-cast-in-loop' CheckOptions: - key: performance-unnecessary-value-param.AllowedTypes diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 4edd63207..5c2718366 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -14,7 +14,7 @@ Contributions are welcome! Here's how you can help: [clone](https://help.github.com/articles/cloning-a-repository/) your fork. 2. Before you start coding, consider opening an - [issue at Github](https://github.com/minetest/minetest/issues) to discuss the + [issue on Github](https://github.com/luanti-org/luanti/issues) to discuss the suitability and implementation of your intended contribution with the core developers. @@ -25,16 +25,16 @@ Contributions are welcome! Here's how you can help: the work, to avoid disappointment. You may also benefit from discussing on our IRC development channel - [#minetest-dev](http://www.minetest.net/irc/). Note that a proper IRC client + [#luanti-dev](http://www.luanti.org/irc/). Note that a proper IRC client is required to speak on this channel. 3. Start coding! - Refer to the - [Lua API](https://github.com/minetest/minetest/blob/master/doc/lua_api.md), - [Developer Wiki](http://dev.minetest.net/Main_Page) and other - [documentation](https://github.com/minetest/minetest/tree/master/doc). - - Follow the [C/C++](http://dev.minetest.net/Code_style_guidelines) and - [Lua](http://dev.minetest.net/Lua_code_style_guidelines) code style guidelines. + [Lua API](https://github.com/luanti-org/luanti/blob/master/doc/lua_api.md), + [Developer Wiki](https://dev.luanti.org/) and other + [documentation](https://github.com/luanti-org/luanti/tree/master/doc). + - Follow the [C/C++](https://dev.luanti.org/Code_style_guidelines) and + [Lua](https://dev.luanti.org/Lua_code_style_guidelines) code style guidelines. - Check your code works as expected and document any changes to the Lua API. - To avoid conflicting changes between contributions, do not do the following manually. They will be done before each release. - Run `updatepo.sh` or update `luanti.po{,t}` even if your code adds new translatable strings. @@ -53,7 +53,7 @@ Contributions are welcome! Here's how you can help: - The following lines should describe the commit, starting a new line for each point. 5. Once you are happy with your changes, submit a pull request. - - Open the [pull-request form](https://github.com/minetest/minetest/pull/new/master). + - Open the [pull-request form](https://github.com/luanti-org/luanti/pull/new/master). - Add a description explaining what you've done (or if it's a work-in-progress - what you need to do). - Make sure to fill out the pull request template. @@ -64,8 +64,8 @@ Contributions are welcome! Here's how you can help: picture of the project. 2. It works. 3. It follows the code style for - [C/C++](http://dev.minetest.net/Code_style_guidelines) or - [Lua](http://dev.minetest.net/Lua_code_style_guidelines). + [C/C++](https://dev.luanti.org/Code_style_guidelines) or + [Lua](https://dev.luanti.org/Lua_code_style_guidelines). 4. The code's interfaces are well designed, regardless of other aspects that might need more work in the future. 5. It uses protocols and formats which include the required compatibility. @@ -76,9 +76,9 @@ If you experience an issue, we would like to know the details - especially when a stable release is on the way. 1. Do a quick search on GitHub to check if the issue has already been reported. -2. Is it an issue with the Minetest *engine*? If not, report it - [elsewhere](http://www.minetest.net/development/#reporting-issues). -3. [Open an issue](https://github.com/minetest/minetest/issues/new) and describe +2. Is it an issue with the Luanti *engine*? If not, report it + [elsewhere](http://www.luanti.org/development/#reporting-issues). +3. [Open an issue](https://github.com/luanti-org/luanti/issues/new) and describe the issue you are having - you could include: - Error logs (check the bottom of the `debug.txt` file). - Screenshots. @@ -106,21 +106,21 @@ the project page with a list of current languages Builtin (the component which contains things like server messages, chat command descriptions, privilege descriptions) is translated separately; it needs to be translated by editing a `.tr` text file. See -[Translation](https://dev.minetest.net/Translation) for more information. +[Translation](https://dev.luanti.org/Translation) for more information. ## Donations If you'd like to monetarily support Luanti development, you can find donation -methods on [our website](http://www.minetest.net/development/#donate). +methods on [our website](http://www.luanti.org/development/#donate). # Maintaining * This is a concise version of the - [Rules & Guidelines](http://dev.minetest.net/Category:Rules_and_Guidelines) on the developer wiki.* + [Rules & Guidelines](https://dev.luanti.org/engine-dev-process/) on the developer wiki.* These notes are for those who have push access Luanti (core developers / maintainers). -- See the [project organisation](http://dev.minetest.net/Organisation) for the people involved. +- See the [project organisation](https://dev.luanti.org/Organisation) for the people involved. ## Concept approvals and roadmaps @@ -159,14 +159,14 @@ Submit a :+1: (+1) or "Looks good" comment to show you believe the pull-request - The title should follow the commit guidelines (title starts with a capital letter, present tense, descriptive). - Don't modify history older than 10 minutes. - Use rebase, not merge to get linear history: - - `curl https://github.com/minetest/minetest/pull/1.patch | git am` + - `curl -Ls https://github.com/luanti-org/luanti/pull/1.patch | git am` ## Reviewing issues and feature requests - If an issue does not get a response from its author within 1 month (when requiring more details), it can be closed. - When an issue is a duplicate, refer to the first ones and close the later ones. -- Tag issues with the appropriate [labels](https://github.com/minetest/minetest/labels) for devices, platforms etc. +- Tag issues with the appropriate [labels](https://github.com/luanti-org/luanti/labels) for devices, platforms etc. ## Releasing a new version -*Refer to [dev.minetest.net/Releasing_Luanti](https://dev.minetest.net/Releasing_Luanti)* +*Refer to [dev.luanti.org/Releasing_Luanti](https://dev.luanti.org/Releasing_Luanti)* diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index ae736d52f..29df88d18 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -9,7 +9,7 @@ body: 1. **Please update Luanti to the latest stable or dev version** before submitting bug reports. Make sure the bug is still reproducible on the latest version. 2. This page is for reporting the bugs of **the engine itself**. For bugs in a particular game, please [search for the game in the ContentDB](https://content.luanti.org/packages/?type=game) and submit a bug report in their issue trackers. - * For example, you can submit issues about the Minetest Game [in its own repository](https://github.com/minetest/minetest_game/issues). + * For example, you can submit issues about the Minetest Game [in its own repository](https://github.com/luanti-org/minetest_game/issues). 3. Please provide as many details as possible for us to spot the problem quicker. - type: textarea attributes: diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 502e8e1d7..c6834265d 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,7 +1,7 @@ blank_issues_enabled: true contact_links: - name: Submit issues about Minetest Game - url: https://github.com/minetest/minetest_game/issues/new/choose + url: https://github.com/luanti-org/minetest_game/issues/new about: Only submit issues of the engine in this repository's issue tracker. Submit those of Minetest Game in its own issue tracker. - name: Search for issue trackers of third-party games url: https://content.luanti.org/packages/?type=game diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 0d7c0fb86..60a926039 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,7 +3,7 @@ Add compact, short information about your PR for easier understanding: - Goal of the PR - How does the PR work? - Does it resolve any reported issue? -- Does this relate to a goal in [the roadmap](https://github.com/minetest/minetest/blob/master/doc/direction.md)? +- Does this relate to a goal in [the roadmap](https://github.com/luanti-org/luanti/blob/master/doc/direction.md)? - If not a bug fix, why is this PR needed? What usecases does it solve? ## To do diff --git a/.github/SECURITY.md b/.github/SECURITY.md index d5db2751a..22b154458 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -3,7 +3,7 @@ ## Supported Versions We only support the latest stable version for security issues. -See the [releases page](https://github.com/minetest/minetest/releases). +See the [releases page](https://github.com/luanti-org/luanti/releases). ## Reporting a Vulnerability @@ -11,7 +11,7 @@ We ask that you report vulnerabilities privately, by contacting a core developer to give us time to fix them. You can do that by emailing one of the following addresses: * celeron55@gmail.com -* rubenwardy@minetest.net +* rw@rubenwardy.com Depending on severity, we will either create a private issue for the vulnerability and release a patch version of Luanti, or give you permission to file the issue publicly. diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 0fb780464..e5c98a901 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -34,28 +34,32 @@ env: jobs: # Older gcc version (should be close to our minimum supported version) - gcc_7: - runs-on: ubuntu-20.04 + gcc_9: + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - name: Install deps run: | source ./util/ci/common.sh - install_linux_deps g++-7 + install_linux_deps g++-9 - name: Build run: | ./util/ci/build.sh env: - CC: gcc-7 - CXX: g++-7 - # Test fallback SHA implementations - CMAKE_FLAGS: '-DENABLE_OPENSSL=0' + CC: gcc-9 + CXX: g++-9 + CMAKE_FLAGS: '-DCMAKE_C_FLAGS="-fsanitize=address" -DCMAKE_CXX_FLAGS="-fsanitize=address"' - - name: Test + - name: Unittest run: | ./bin/luanti --run-unittests + # Do this here because we have ASan and error paths are sensitive to dangling pointers + - name: Test error cases + run: | + ./util/test_error_cases.sh + # Current gcc version gcc_14: runs-on: ubuntu-24.04 @@ -81,32 +85,28 @@ jobs: ../bin/luanti --run-unittests # Older clang version (should be close to our minimum supported version) - clang_7: - runs-on: ubuntu-20.04 + clang_11: + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - name: Install deps run: | source ./util/ci/common.sh - install_linux_deps clang-7 llvm-7 + install_linux_deps clang-11 - name: Build run: | ./util/ci/build.sh env: - CC: clang-7 - CXX: clang++-7 - CMAKE_FLAGS: '-DCMAKE_C_FLAGS="-fsanitize=address" -DCMAKE_CXX_FLAGS="-fsanitize=address"' + CC: clang-11 + CXX: clang++-11 + # Test fallback SHA implementations + CMAKE_FLAGS: '-DENABLE_OPENSSL=0' - - name: Unittest + - name: Test run: | ./bin/luanti --run-unittests - # Do this here because we have ASan and error paths are sensitive to dangling pointers - - name: Test error cases - run: | - ./util/test_error_cases.sh - # Current clang version clang_18: runs-on: ubuntu-24.04 diff --git a/.github/workflows/lua_api_deploy.yml b/.github/workflows/lua_api_deploy.yml index e42335a78..68db8316f 100644 --- a/.github/workflows/lua_api_deploy.yml +++ b/.github/workflows/lua_api_deploy.yml @@ -16,7 +16,7 @@ on: jobs: build: - if: github.repository == 'minetest/minetest' + if: github.repository == 'luanti-org/luanti' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index c073fe106..859cd3060 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -45,7 +45,7 @@ jobs: mkdir build cd build cmake .. \ - -DCMAKE_OSX_DEPLOYMENT_TARGET=10.14 \ + -DCMAKE_OSX_DEPLOYMENT_TARGET=13 \ -DCMAKE_FIND_FRAMEWORK=LAST \ -DCMAKE_INSTALL_PREFIX=../build/macos/ \ -DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE \ diff --git a/.github/workflows/png_file_checks.yml b/.github/workflows/png_file_checks.yml new file mode 100644 index 000000000..86cd93527 --- /dev/null +++ b/.github/workflows/png_file_checks.yml @@ -0,0 +1,26 @@ +name: png_file_checks + +# Check whether all png files are in a valid format +on: + push: + paths: + - '**.png' + - '.github/workflows/**.yml' + pull_request: + paths: + - '**.png' + - '.github/workflows/**.yml' + +jobs: + png_optimized: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install deps + run: | + sudo apt-get update + sudo apt install -y optipng + + - name: Check whether all png files are optimized + run: | + ./util/ci/check_png_optimized.sh diff --git a/.github/workflows/whitespace_checks.yml b/.github/workflows/whitespace_checks.yml index cc16e7b22..ea8d41e3b 100644 --- a/.github/workflows/whitespace_checks.yml +++ b/.github/workflows/whitespace_checks.yml @@ -14,6 +14,7 @@ on: - '**.sh' - '**.cmake' - '**.glsl' + - '**.lua' pull_request: paths: - '**.txt' @@ -24,6 +25,7 @@ on: - '**.sh' - '**.cmake' - '**.glsl' + - '**.lua' jobs: trailing_whitespaces: @@ -32,7 +34,72 @@ jobs: - uses: actions/checkout@v4 # Line endings are already ensured by .gitattributes - name: Check trailing whitespaces - run: if git ls-files | grep -E '\.txt$|\.md$|\.[ch]$|\.cpp$|\.hpp$|\.sh$|\.cmake$|\.glsl$' | xargs grep -n '\s$'; then echo -e "\033[0;31mFound trailing whitespace"; (exit 1); else (exit 0); fi + run: | + if git ls-files |\ + grep -E '\.txt$|\.md$|\.[ch]$|\.cpp$|\.hpp$|\.sh$|\.cmake$|\.glsl$' |\ + xargs grep -n '\s$';\ + then\ + echo -e "\033[0;31mFound trailing whitespace";\ + (exit 1);\ + else\ + (exit 0);\ + fi + + indent_spaces: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + # Line endings are already ensured by .gitattributes + # Multiple multline comments in one line is not supported by this check + # and there is no reason to use them + # So lines like: "/* */ /*" or "*/ a = 5; /*" will result in error + - name: Check for unsupported multiline comments + run: | + if git ls-files |\ + grep -E '^src/.*\.cpp$|^src/.*\.[ch]$' |\ + xargs grep -n '\*/.*/\*';\ + then + echo -e "\033[0;31mUnsupported combination of multiline comments. New multiline comment should begin on new line.";\ + (exit 1);\ + else\ + (exit 0);\ + fi + if git ls-files |\ + grep -E '\.lua$' |\ + xargs grep -n -e '\]\].*--\[\[';\ + then + echo -e "\033[0;31mUnsupported combination of multiline comments. New multiline comment should begin on new line.";\ + (exit 1);\ + else\ + (exit 0);\ + fi + # This prepare files for final check + # See python script ./util/ci/indent_tab_preprocess.py for details. + - name: Preprocess files + run: | + git ls-files |\ + grep -E '^src/.*\.cpp$|^src/.*\.[ch]$' |\ + xargs -L 1 -P $(($(nproc) + 1)) \ + python3 ./util/ci/indent_tab_preprocess.py "/*" "*/" + git ls-files |\ + grep -E '\.lua$' |\ + xargs -L 1 -P $(($(nproc) + 1)) \ + python3 ./util/ci/indent_tab_preprocess.py "--[[" "]]" + # Check for bad indent. + # This runs over preprocessed files. + # If there is any remaining space on line beginning or after tab, + # error is generated + - name: Check indent spaces + run: | + if git ls-files |\ + grep -E '^src/.*\.cpp$|^src/.*\.[ch]$|\.lua$' |\ + xargs grep -n -P '^\t*[ ]';\ + then\ + echo -e "\033[0;31mFound incorrect indent whitespaces";\ + (exit 1);\ + else\ + (exit 0);\ + fi tabs_lua_api_files: runs-on: ubuntu-latest @@ -40,6 +107,12 @@ jobs: - uses: actions/checkout@v4 # Some files should not contain tabs - name: Check tabs in Lua API files - run: if grep -n $'\t' doc/lua_api.md doc/client_lua_api.md; then echo -e "\033[0;31mFound tab in markdown file"; (exit 1); else (exit 0); fi - + run: | + if grep -n $'\t' doc/lua_api.md doc/client_lua_api.md;\ + then\ + echo -e "\033[0;31mFound tab in markdown file";\ + (exit 1);\ + else\ + (exit 0);\ + fi diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index d82b9785d..979ab0144 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -71,9 +71,7 @@ jobs: name: VS 2019 ${{ matrix.config.arch }}-${{ matrix.type }} runs-on: windows-2019 env: - VCPKG_VERSION: 01f602195983451bc83e72f4214af2cbc495aa94 - # 2024.05.24 - vcpkg_packages: zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp sdl2 + VCPKG_DEFAULT_TRIPLET: ${{matrix.config.vcpkg_triplet}} strategy: fail-fast: false matrix: @@ -97,19 +95,17 @@ jobs: - uses: actions/checkout@v4 - name: Restore from cache and run vcpkg - uses: lukka/run-vcpkg@v7 + uses: lukka/run-vcpkg@v11 with: - vcpkgArguments: ${{env.vcpkg_packages}} vcpkgDirectory: '${{ github.workspace }}\vcpkg' - appendedCacheKey: ${{ matrix.config.vcpkg_triplet }} - vcpkgGitCommitId: ${{ env.VCPKG_VERSION }} - vcpkgTriplet: ${{ matrix.config.vcpkg_triplet }} - name: CMake + # Note: See #15976 for why CMAKE_POLICY_VERSION_MINIMUM=3.5 is set. run: | cmake ${{matrix.config.generator}} ` -DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}\vcpkg\scripts\buildsystems\vcpkg.cmake" ` -DCMAKE_BUILD_TYPE=Release ` + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 ` -DENABLE_POSTGRESQL=OFF ` -DENABLE_LUAJIT=TRUE ` -DREQUIRE_LUAJIT=TRUE ` diff --git a/.gitignore b/.gitignore index c7879380b..7fa100ed5 100644 --- a/.gitignore +++ b/.gitignore @@ -49,13 +49,13 @@ AppDir # Nix /result -## Files related to Minetest development cycle +## Files related to Luanti development cycle /*.patch *.diff # GNU Patch reject file *.rej -## Non-static Minetest directories or symlinks to these +## Non-static Luanti directories or symlinks to these /bin/ /games/* !/games/devtest/ @@ -80,7 +80,7 @@ minetest.conf debug.txt debug.txt.1 -## Other files generated by Minetest +## Other files generated by Luanti screenshot_*.png testbm.txt diff --git a/.luacheckrc b/.luacheckrc index 3a4667b4a..670c84325 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -10,6 +10,7 @@ ignore = { read_globals = { "ItemStack", "INIT", + "PLATFORM", "DIR_DELIM", "dump", "dump2", "fgettext", "fgettext_ne", @@ -18,10 +19,10 @@ read_globals = { "VoxelManip", "profiler", "Settings", - "PerlinNoise", "PerlinNoiseMap", + "ValueNoise", "ValueNoiseMap", string = {fields = {"split", "trim"}}, - table = {fields = {"copy", "getn", "indexof", "keyof", "insert_all"}}, + table = {fields = {"copy", "copy_with_metatables", "getn", "indexof", "keyof", "insert_all"}}, math = {fields = {"hypot", "round"}}, } @@ -32,6 +33,13 @@ globals = { "_", } +stds.menu_common = { + globals = { + "mt_color_grey", "mt_color_blue", "mt_color_lightblue", "mt_color_green", + "mt_color_dark_green", "mt_color_orange", "mt_color_red", + }, +} + files["builtin/client/register.lua"] = { globals = { debug = {fields={"getinfo"}}, @@ -72,13 +80,14 @@ files["builtin/common/filterlist.lua"] = { } files["builtin/mainmenu"] = { + std = "+menu_common", globals = { "gamedata", }, +} - read_globals = { - "PLATFORM", - }, +files["builtin/common/settings"] = { + std = "+menu_common", } files["builtin/common/tests"] = { diff --git a/CMakeLists.txt b/CMakeLists.txt index de97d49bf..70a027f57 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,7 @@ cmake_minimum_required(VERSION 3.12) +if(POLICY CMP0177) + cmake_policy(SET CMP0177 NEW) +endif() # This can be read from ${PROJECT_NAME} after project() is called project(luanti) @@ -11,7 +14,7 @@ set(CLANG_MINIMUM_VERSION "7.0.1") # You should not need to edit these manually, use util/bump_version.sh set(VERSION_MAJOR 5) -set(VERSION_MINOR 11) +set(VERSION_MINOR 13) set(VERSION_PATCH 0) set(VERSION_EXTRA "" CACHE STRING "Stuff to append to version string") @@ -48,7 +51,7 @@ endif() # - win32/gcc: fails to link # - win32/clang: works # - macOS on x86: seems to be fine -# - macOS on ARM: crashes, see +# - macOS on ARM: crashes, see # Note: since CMake has no easy architecture detection disabling for Mac entirely #### #### if((WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR APPLE) @@ -262,8 +265,8 @@ install(FILES "minetest.conf.example" DESTINATION "${EXAMPLE_CONF_DIR}") if(UNIX AND NOT APPLE) install(FILES "doc/luanti.6" "doc/luantiserver.6" DESTINATION "${MANDIR}/man6") - install(FILES "misc/net.minetest.minetest.desktop" DESTINATION "${XDG_APPS_DIR}") - install(FILES "misc/net.minetest.minetest.metainfo.xml" DESTINATION "${METAINFODIR}") + install(FILES "misc/org.luanti.luanti.desktop" DESTINATION "${XDG_APPS_DIR}") + install(FILES "misc/org.luanti.luanti.metainfo.xml" DESTINATION "${METAINFODIR}") install(FILES "misc/luanti.svg" DESTINATION "${ICONDIR}/hicolor/scalable/apps") install(FILES "misc/luanti-xorg-icon-128.png" DESTINATION "${ICONDIR}/hicolor/128x128/apps" diff --git a/LICENSE.txt b/LICENSE.txt index f7930f528..5c01e2c3b 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -14,6 +14,9 @@ https://www.apache.org/licenses/LICENSE-2.0.html Textures by Zughy are under CC BY-SA 4.0 https://creativecommons.org/licenses/by-sa/4.0/ +Textures by cx384 are under CC BY-SA 4.0 +https://creativecommons.org/licenses/by-sa/4.0/ + Media files by DS are under CC BY-SA 4.0 https://creativecommons.org/licenses/by-sa/4.0/ @@ -64,7 +67,13 @@ Zughy: textures/base/pack/settings_info.png textures/base/pack/settings_reset.png textures/base/pack/server_url.png + textures/base/pack/server_url_unavailable.png textures/base/pack/server_view_clients.png + textures/base/pack/server_view_clients_unavailable.png + +cx384: + textures/base/pack/server_view_mods.png + textures/base/pack/server_view_mods_unavailable.png appgurueu: textures/base/pack/server_incompatible.png @@ -93,6 +102,14 @@ grorp: using the font "undefined medium" (https://undefined-medium.com/), which is licensed under the SIL Open Font License, Version 1.1 modified by DS + textures/base/pack/dig_btn.png + textures/base/pack/place_btn.png + derived by editing the text in aux1_btn.svg + +Material Design, Google (Apache license v2.0): + textures/base/pack/contentdb_thumb_up.png + textures/base/pack/contentdb_thumb_down.png + textures/base/pack/contentdb_neutral.png License of Luanti source code ------------------------------- diff --git a/README.md b/README.md index aa8faccd5..6abd6d22c 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,15 @@ -Luanti (formerly Minetest) -========================== - -![Build Status](https://github.com/minetest/minetest/workflows/build/badge.svg) -[![Translation status](https://hosted.weblate.org/widgets/minetest/-/svg-badge.svg)](https://hosted.weblate.org/engage/minetest/?utm_source=widget) -[![License](https://img.shields.io/badge/license-LGPLv2.1%2B-blue.svg)](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html) +
+ +

Luanti (formerly Minetest)

+ Build Status + Translation status + License +
+
Luanti is a free open-source voxel game engine with easy modding and game creation. -Copyright (C) 2010-2024 Perttu Ahola +Copyright (C) 2010-2025 Perttu Ahola and contributors (see source file comments and the version control log) Table of Contents @@ -28,7 +30,7 @@ Further documentation - Website: https://www.luanti.org/ - Wiki: https://wiki.luanti.org/ - Forum: https://forum.luanti.org/ -- GitHub: https://github.com/minetest/minetest/ +- GitHub: https://github.com/luanti-org/luanti/ - [Developer documentation](doc/developing/) - [doc/](doc/) directory of source distribution @@ -55,6 +57,7 @@ Some can be changed in the key config dialog in the settings tab. | T | Chat | | / | Command | | Esc | Pause menu/abort/exit (pauses only singleplayer game) | +| Shift + Esc | Exit directly to main menu from anywhere, bypassing pause menu | | + | Increase view range | | - | Decrease view range | | K | Enable/disable fly mode (needs fly privilege) | diff --git a/android/SDL_Java_RMB_fix.patch b/android/SDL_Java_RMB_fix.patch index cc0eb4772..ef8840dca 100644 --- a/android/SDL_Java_RMB_fix.patch +++ b/android/SDL_Java_RMB_fix.patch @@ -1,5 +1,4 @@ diff --git a/android/app/src/main/java/org/libsdl/app/SDLActivity.java b/android/app/src/main/java/org/libsdl/app/SDLActivity.java -index fd5a056e3..83e3cf657 100644 --- a/android/app/src/main/java/org/libsdl/app/SDLActivity.java +++ b/android/app/src/main/java/org/libsdl/app/SDLActivity.java @@ -1345,7 +1345,12 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh @@ -9,7 +8,7 @@ index fd5a056e3..83e3cf657 100644 - if ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) { + if ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE || + /* -+ * CUSTOM ADDITION FOR MINETEST ++ * CUSTOM ADDITION FOR LUANTI + * should be upstreamed + */ + (source & InputDevice.SOURCE_MOUSE_RELATIVE) == InputDevice.SOURCE_MOUSE_RELATIVE) { 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 22e861fb0..770c1c4f2 100644 --- a/android/app/src/main/java/net/minetest/minetest/GameActivity.java +++ b/android/app/src/main/java/net/minetest/minetest/GameActivity.java @@ -22,14 +22,19 @@ package net.minetest.minetest; import org.libsdl.app.SDLActivity; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; import android.content.Intent; import android.content.ActivityNotFoundException; import android.net.Uri; -import android.os.Bundle; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; import android.text.InputType; import android.util.Log; import android.view.KeyEvent; -import android.view.View; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import android.widget.Button; @@ -91,6 +96,9 @@ public class GameActivity extends SDLActivity { saveSettings(); } + private NotificationManager mNotifyManager; + private boolean gameNotificationShown = false; + public void showTextInputDialog(String hint, String current, int editType) { runOnUiThread(() -> showTextInputDialogUI(hint, current, editType)); } @@ -263,4 +271,67 @@ public class GameActivity extends SDLActivity { public boolean hasPhysicalKeyboard() { return getContext().getResources().getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS; } + + // TODO: share code with UnzipService.createNotification + private void updateGameNotification() { + if (mNotifyManager == null) { + mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + } + + if (!gameNotificationShown) { + mNotifyManager.cancel(MainActivity.NOTIFICATION_ID_GAME); + return; + } + + Notification.Builder builder; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + builder = new Notification.Builder(this, MainActivity.NOTIFICATION_CHANNEL_ID); + } else { + builder = new Notification.Builder(this); + } + + Intent notificationIntent = new Intent(this, GameActivity.class); + notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP + | Intent.FLAG_ACTIVITY_SINGLE_TOP); + int pendingIntentFlag = 0; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + pendingIntentFlag = PendingIntent.FLAG_MUTABLE; + } + PendingIntent intent = PendingIntent.getActivity(this, 0, + notificationIntent, pendingIntentFlag); + + builder.setContentTitle(getString(R.string.game_notification_title)) + .setSmallIcon(R.mipmap.ic_launcher) + .setContentIntent(intent) + .setOngoing(true); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + // This avoids a stuck notification if the app is killed while + // in-game: (1) if the user closes the app from the "Recents" screen + // or (2) if the system kills the app while it is in background. + // onStop is called too early to remove the notification and + // onDestroy is often not called at all, so there's this hack instead. + builder.setTimeoutAfter(16000); + + // Replace the notification just before it expires as long as the app is + // running (and we're still in-game). + final Handler handler = new Handler(Looper.getMainLooper()); + handler.postDelayed(new Runnable() { + @Override + public void run() { + if (gameNotificationShown) { + updateGameNotification(); + } + } + }, 15000); + } + + mNotifyManager.notify(MainActivity.NOTIFICATION_ID_GAME, builder.build()); + } + + + public void setPlayingNowNotification(boolean show) { + gameNotificationShown = show; + updateGameNotification(); + } } 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 7735e8b3d..a2edb2d7a 100644 --- a/android/app/src/main/java/net/minetest/minetest/MainActivity.java +++ b/android/app/src/main/java/net/minetest/minetest/MainActivity.java @@ -43,6 +43,8 @@ import static net.minetest.minetest.UnzipService.*; public class MainActivity extends AppCompatActivity { public static final String NOTIFICATION_CHANNEL_ID = "Minetest channel"; + public static final int NOTIFICATION_ID_UNZIP = 1; + public static final int NOTIFICATION_ID_GAME = 2; private final static int versionCode = BuildConfig.VERSION_CODE; private static final String SETTINGS = "MinetestSettings"; 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 542b2a159..895ad66fc 100644 --- a/android/app/src/main/java/net/minetest/minetest/UnzipService.java +++ b/android/app/src/main/java/net/minetest/minetest/UnzipService.java @@ -51,7 +51,6 @@ public class UnzipService extends IntentService { 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; @@ -100,11 +99,14 @@ public class UnzipService extends IntentService { } } + // TODO: share code with GameActivity.updateGameNotification @NonNull private Notification.Builder createNotification() { - Notification.Builder builder; - if (mNotifyManager == null) + if (mNotifyManager == null) { mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + } + + Notification.Builder builder; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { builder = new Notification.Builder(this, MainActivity.NOTIFICATION_CHANNEL_ID); } else { @@ -128,7 +130,7 @@ public class UnzipService extends IntentService { .setOngoing(true) .setProgress(0, 0, true); - mNotifyManager.notify(id, builder.build()); + mNotifyManager.notify(MainActivity.NOTIFICATION_ID_UNZIP, builder.build()); return builder; } @@ -200,14 +202,14 @@ public class UnzipService extends IntentService { } else { notificationBuilder.setProgress(100, progress, false); } - mNotifyManager.notify(id, notificationBuilder.build()); + mNotifyManager.notify(MainActivity.NOTIFICATION_ID_UNZIP, notificationBuilder.build()); } } @Override public void onDestroy() { super.onDestroy(); - mNotifyManager.cancel(id); + mNotifyManager.cancel(MainActivity.NOTIFICATION_ID_UNZIP); publishProgress(null, R.string.loading, isSuccess ? SUCCESS : FAILURE); } } diff --git a/android/app/src/main/java/org/libsdl/app/SDLActivity.java b/android/app/src/main/java/org/libsdl/app/SDLActivity.java index 393347d43..919640040 100644 --- a/android/app/src/main/java/org/libsdl/app/SDLActivity.java +++ b/android/app/src/main/java/org/libsdl/app/SDLActivity.java @@ -60,8 +60,8 @@ import java.util.Locale; public class SDLActivity extends Activity implements View.OnSystemUiVisibilityChangeListener { private static final String TAG = "SDL"; private static final int SDL_MAJOR_VERSION = 2; - private static final int SDL_MINOR_VERSION = 30; - private static final int SDL_MICRO_VERSION = 8; + private static final int SDL_MINOR_VERSION = 32; + private static final int SDL_MICRO_VERSION = 0; /* // Display InputType.SOURCE/CLASS of events and devices // @@ -790,6 +790,9 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); SDLActivity.mFullscreenModeActive = false; } + if (Build.VERSION.SDK_INT >= 28 /* Android 9 (Pie) */) { + window.getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + } } } else { Log.e(TAG, "error handling message, getContext() returned no Activity"); @@ -1347,7 +1350,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh if ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE || /* - * CUSTOM ADDITION FOR MINETEST + * CUSTOM ADDITION FOR LUANTI * should be upstreamed */ (source & InputDevice.SOURCE_MOUSE_RELATIVE) == InputDevice.SOURCE_MOUSE_RELATIVE) { diff --git a/android/app/src/main/res/values-br/strings.xml b/android/app/src/main/res/values-br/strings.xml new file mode 100644 index 000000000..16ba0da21 --- /dev/null +++ b/android/app/src/main/res/values-br/strings.xml @@ -0,0 +1,11 @@ + + + O kargañ Luanti + Luanti + O kargañ… + Evezhiadennoù gant Luanti + Nebeutoc\'h eget ur vunutenn… + Graet + Merdeer web ebet bet kavet + Evezhiadennoù hollek + diff --git a/android/app/src/main/res/values-de/strings.xml b/android/app/src/main/res/values-de/strings.xml index 8585c95cc..80c025573 100644 --- a/android/app/src/main/res/values-de/strings.xml +++ b/android/app/src/main/res/values-de/strings.xml @@ -1,11 +1,12 @@ Luanti - Lädt… + Laden … Luanti lädt - Weniger als 1 Minute… + Weniger als 1 Minute … Fertig - Kein Web-Browser gefunden + Keinen Web-Browser gefunden Allgemeine Benachrichtigung Benachrichtigungen von Luanti - \ No newline at end of file + Luanti läuft + diff --git a/android/app/src/main/res/values-fa/strings.xml b/android/app/src/main/res/values-fa/strings.xml new file mode 100644 index 000000000..634b75067 --- /dev/null +++ b/android/app/src/main/res/values-fa/strings.xml @@ -0,0 +1,11 @@ + + + کمتر از 1 دقیقه… + در حال بارگذاری… + انجام شد + لوآنتی + در حال بارگذاری لوآنتی + نوتیفیکیشن عمومی + نوتیفیکیشن از لوآنتی + هیچ مرورگری یافت نشد + diff --git a/android/app/src/main/res/values-fr/strings.xml b/android/app/src/main/res/values-fr/strings.xml new file mode 100644 index 000000000..1b3ff29cc --- /dev/null +++ b/android/app/src/main/res/values-fr/strings.xml @@ -0,0 +1,12 @@ + + + Luanti + Chargement… + Notification générale + Notifications de Luanti + Chargement de Luanti + Moins d\'une minute… + Terminé + Aucun navigateur web trouvé + Luanti est en cours d\'exécution + diff --git a/android/app/src/main/res/values-gl/strings.xml b/android/app/src/main/res/values-gl/strings.xml new file mode 100644 index 000000000..89ee1f2bb --- /dev/null +++ b/android/app/src/main/res/values-gl/strings.xml @@ -0,0 +1,11 @@ + + + Luanti + Cargando… + Notificación xeral + Notificacións de Luanti + Cargando Luanti + Menos de 1 minuto… + Feito + Non se atopou ningún navegador web + diff --git a/android/app/src/main/res/values-in/strings.xml b/android/app/src/main/res/values-in/strings.xml index cbd8acd9a..34ce22bbc 100644 --- a/android/app/src/main/res/values-in/strings.xml +++ b/android/app/src/main/res/values-in/strings.xml @@ -8,4 +8,5 @@ Kurang dari 1 menit… Pemberitahuan dari Luanti Memuat Luanti… - \ No newline at end of file + Luanti sedang berjalan + diff --git a/android/app/src/main/res/values-ro/strings.xml b/android/app/src/main/res/values-ro/strings.xml new file mode 100644 index 000000000..debbd900a --- /dev/null +++ b/android/app/src/main/res/values-ro/strings.xml @@ -0,0 +1,11 @@ + + + Luanti + Se încarcă… + Notificare generală + Notificări de la Luanti + Luanti pornește + Sub 1 minut… + Gata + Niciun navigator web găsit + diff --git a/android/app/src/main/res/values-ru/strings.xml b/android/app/src/main/res/values-ru/strings.xml index 77748a9f6..5f0a89c6b 100644 --- a/android/app/src/main/res/values-ru/strings.xml +++ b/android/app/src/main/res/values-ru/strings.xml @@ -1,11 +1,12 @@ Загрузка Luanti - Меньше чам за 1 минуту… + Менее 1 минуты… Готово Luаnti Уведомления от Luanti Основные уведомления Загрузка… Не найдено веб-браузера - \ No newline at end of file + Luanti запущено + diff --git a/android/app/src/main/res/values-sl/strings.xml b/android/app/src/main/res/values-sl/strings.xml new file mode 100644 index 000000000..97661d348 --- /dev/null +++ b/android/app/src/main/res/values-sl/strings.xml @@ -0,0 +1,11 @@ + + + Ni bil najden spletni brskalnik + Glavno obvestilo + Nalaganje … + Manj kot 1 minuta … + Luanti + Obvestilo od Luantia + Končano!l + Nalaganje Luantia + diff --git a/android/app/src/main/res/values-sv/strings.xml b/android/app/src/main/res/values-sv/strings.xml new file mode 100644 index 000000000..38badff14 --- /dev/null +++ b/android/app/src/main/res/values-sv/strings.xml @@ -0,0 +1,12 @@ + + + Luanti + Laddar… + Mindre än 1 minut… + Färdig + Ingen webbläsare hittades + Allmän notifikation + Notifikationer från Luanti + Laddar Luanti + Luanti är igång + diff --git a/android/app/src/main/res/values-ta/strings.xml b/android/app/src/main/res/values-ta/strings.xml new file mode 100644 index 000000000..a6b3daec9 --- /dev/null +++ b/android/app/src/main/res/values-ta/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/android/app/src/main/res/values-uk/strings.xml b/android/app/src/main/res/values-uk/strings.xml new file mode 100644 index 000000000..2969e0e53 --- /dev/null +++ b/android/app/src/main/res/values-uk/strings.xml @@ -0,0 +1,11 @@ + + + Luanti + Браузерів не знайдено + Завантаження… + Загальні сповіщення + Luanti завантажується + Менше за 1 хвилину… + Готово + Сповіщення від Luanti + \ No newline at end of file diff --git a/android/app/src/main/res/values-zh-rCN/strings.xml b/android/app/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 000000000..5441ec322 --- /dev/null +++ b/android/app/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,11 @@ + + + Luanti + 加载中… + 一般通知 + Luanti 的通知 + 加载 Luanti 中 + 不到1分钟… + 完成 + 未找到网页浏览器 + diff --git a/android/app/src/main/res/values-zh-rTW/strings.xml b/android/app/src/main/res/values-zh-rTW/strings.xml new file mode 100644 index 000000000..2953aefb8 --- /dev/null +++ b/android/app/src/main/res/values-zh-rTW/strings.xml @@ -0,0 +1,11 @@ + + + 載入中… + 一般通知 + Luanti 的通知 + 不到1分鐘… + 完畢 + 未找到任何網頁瀏覽器 + Luanti + 載入Luanti中 + \ No newline at end of file diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 98511e72c..8b8bc625b 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -6,6 +6,7 @@ Notifications from Luanti Loading Luanti Less than 1 minute… + Luanti is running Done No web browser found diff --git a/android/build.gradle b/android/build.gradle index a6b9c512d..d2a3b3b2f 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,7 +1,7 @@ // 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", 11) // Version Minor +project.ext.set("versionMinor", 13) // Version Minor project.ext.set("versionPatch", 0) // Version Patch // ^ keep in sync with cmake diff --git a/android/icons/dig_btn.svg b/android/icons/dig_btn.svg new file mode 100644 index 000000000..cbfa69e6f --- /dev/null +++ b/android/icons/dig_btn.svg @@ -0,0 +1,148 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + LMB + + + diff --git a/android/icons/place_btn.svg b/android/icons/place_btn.svg new file mode 100644 index 000000000..b32dfc018 --- /dev/null +++ b/android/icons/place_btn.svg @@ -0,0 +1,148 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + RMB + + + diff --git a/android/native/build.gradle b/android/native/build.gradle index 806dda2f0..b5224c6a0 100644 --- a/android/native/build.gradle +++ b/android/native/build.gradle @@ -52,7 +52,7 @@ if (new File(depsDir, 'armeabi-v7a').exists()) { task downloadDeps(type: Download) { def depsZip = new File(buildDir, 'deps.zip') - src 'https://github.com/minetest/minetest_android_deps/releases/download/latest/deps-lite.zip' + src 'https://github.com/luanti-org/luanti_android_deps/releases/download/latest/deps-lite.zip' dest depsZip overwrite false diff --git a/builtin/async/game.lua b/builtin/async/game.lua index f7c9892c4..6bd369fb1 100644 --- a/builtin/async/game.lua +++ b/builtin/async/game.lua @@ -32,8 +32,8 @@ do all.registered_craftitems = {} all.registered_tools = {} for k, v in pairs(all.registered_items) do - -- Disable further modification - setmetatable(v, {__newindex = {}}) + -- Ignore new keys + setmetatable(v, {__newindex = function() end}) -- Reassemble the other tables if v.type == "node" then getmetatable(v).__index = all.nodedef_default @@ -59,6 +59,9 @@ end local alias_metatable = { __index = function(t, name) return rawget(t, core.registered_aliases[name]) + end, + __newindex = function() + error("table is read-only") end } setmetatable(core.registered_items, alias_metatable) diff --git a/builtin/async/mainmenu.lua b/builtin/async/mainmenu.lua index 0e9c222d1..c1d8618b4 100644 --- a/builtin/async/mainmenu.lua +++ b/builtin/async/mainmenu.lua @@ -1,5 +1,6 @@ core.log("info", "Initializing asynchronous environment") + function core.job_processor(func, serialized_param) local param = core.deserialize(serialized_param) @@ -7,3 +8,15 @@ function core.job_processor(func, serialized_param) return retval or core.serialize(nil) end + + +function core.get_http_accept_languages() + local languages + local current_language = core.get_language() + if current_language ~= "" then + languages = { current_language, "en;q=0.8" } + else + languages = { "en" } + end + return "Accept-Language: " .. table.concat(languages, ", ") +end diff --git a/builtin/client/init.lua b/builtin/client/init.lua index 1f83771ea..ee0f267db 100644 --- a/builtin/client/init.lua +++ b/builtin/client/init.lua @@ -2,7 +2,10 @@ local scriptpath = core.get_builtin_path() local clientpath = scriptpath.."client"..DIR_DELIM local commonpath = scriptpath.."common"..DIR_DELIM -dofile(clientpath .. "register.lua") +local builtin_shared = {} + +assert(loadfile(commonpath .. "register.lua"))(builtin_shared) +assert(loadfile(clientpath .. "register.lua"))(builtin_shared) dofile(commonpath .. "after.lua") dofile(commonpath .. "mod_storage.lua") dofile(commonpath .. "chatcommands.lua") diff --git a/builtin/client/register.lua b/builtin/client/register.lua index 61db4a30b..0e3e68fee 100644 --- a/builtin/client/register.lua +++ b/builtin/client/register.lua @@ -1,68 +1,6 @@ -core.callback_origins = {} +local builtin_shared = ... -local getinfo = debug.getinfo -debug.getinfo = nil - ---- Runs given callbacks. --- --- Note: this function is also called from C++ --- @tparam table callbacks a table with registered callbacks, like `core.registered_on_*` --- @tparam number mode a RunCallbacksMode, as defined in src/script/common/c_internal.h --- @param ... arguments for the callback --- @return depends on mode -function core.run_callbacks(callbacks, mode, ...) - assert(type(callbacks) == "table") - local cb_len = #callbacks - if cb_len == 0 then - if mode == 2 or mode == 3 then - return true - elseif mode == 4 or mode == 5 then - return false - end - end - local ret - for i = 1, cb_len do - local cb_ret = callbacks[i](...) - - if mode == 0 and i == 1 or mode == 1 and i == cb_len then - ret = cb_ret - elseif mode == 2 then - if not cb_ret or i == 1 then - ret = cb_ret - end - elseif mode == 3 then - if cb_ret then - return cb_ret - end - ret = cb_ret - elseif mode == 4 then - if (cb_ret and not ret) or i == 1 then - ret = cb_ret - end - elseif mode == 5 and cb_ret then - return cb_ret - end - end - return ret -end - --- --- Callback registration --- - -local function make_registration() - local t = {} - local registerfunc = function(func) - t[#t + 1] = func - core.callback_origins[func] = { - mod = core.get_current_modname() or "??", - name = getinfo(1, "n").name or "??" - } - --local origin = core.callback_origins[func] - --print(origin.name .. ": " .. origin.mod .. " registering cbk " .. tostring(func)) - end - return t, registerfunc -end +local make_registration = builtin_shared.make_registration core.registered_globalsteps, core.register_globalstep = make_registration() core.registered_on_mods_loaded, core.register_on_mods_loaded = make_registration() diff --git a/builtin/common/filterlist.lua b/builtin/common/filterlist.lua index c323a0d55..058a573c6 100644 --- a/builtin/common/filterlist.lua +++ b/builtin/common/filterlist.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2013 sapier --- ---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. +-- Luanti +-- Copyright (C) 2013 sapier +-- SPDX-License-Identifier: LGPL-2.1-or-later -------------------------------------------------------------------------------- -- TODO improve doc -- diff --git a/builtin/common/item_s.lua b/builtin/common/item_s.lua index 5098ef4e6..2761e41dd 100644 --- a/builtin/common/item_s.lua +++ b/builtin/common/item_s.lua @@ -90,7 +90,7 @@ local facedir_to_dir_map = { 1, 4, 3, 2, } function core.facedir_to_dir(facedir) - return facedir_to_dir[facedir_to_dir_map[facedir % 32]] + return vector.copy(facedir_to_dir[facedir_to_dir_map[facedir % 32]]) end function core.dir_to_fourdir(dir) @@ -110,7 +110,7 @@ function core.dir_to_fourdir(dir) end function core.fourdir_to_dir(fourdir) - return facedir_to_dir[facedir_to_dir_map[fourdir % 4]] + return vector.copy(facedir_to_dir[facedir_to_dir_map[fourdir % 4]]) end function core.dir_to_wallmounted(dir) @@ -147,7 +147,7 @@ local wallmounted_to_dir = { vector.new( 0, -1, 0), } function core.wallmounted_to_dir(wallmounted) - return wallmounted_to_dir[wallmounted % 8] + return vector.copy(wallmounted_to_dir[wallmounted % 8]) end function core.dir_to_yaw(dir) diff --git a/builtin/common/menu.lua b/builtin/common/menu.lua new file mode 100644 index 000000000..4eb3dfe44 --- /dev/null +++ b/builtin/common/menu.lua @@ -0,0 +1,15 @@ +-- Luanti +-- SPDX-License-Identifier: LGPL-2.1-or-later + +-- These colors are used by the main menu and the settings menu +mt_color_grey = "#AAAAAA" +mt_color_blue = "#6389FF" +mt_color_lightblue = "#99CCFF" +mt_color_green = "#72FF63" +mt_color_dark_green = "#25C191" +mt_color_orange = "#FF8800" +mt_color_red = "#FF3300" + +function core.are_keycodes_equal(k1, k2) + return core.normalize_keycode(k1) == core.normalize_keycode(k2) +end diff --git a/builtin/common/misc_helpers.lua b/builtin/common/misc_helpers.lua index ce4179f54..29aa3e5c2 100644 --- a/builtin/common/misc_helpers.lua +++ b/builtin/common/misc_helpers.lua @@ -7,18 +7,21 @@ local math = math local function basic_dump(o) local tp = type(o) if tp == "number" then - return tostring(o) + local s = tostring(o) + if tonumber(s) == o then + return s + end + -- Prefer an exact representation over a compact representation. + -- e.g. basic_dump(0.3) == "0.3", + -- but basic_dump(0.1 + 0.2) == "0.30000000000000004" + -- so the user can see that 0.1 + 0.2 ~= 0.3 + return string.format("%.17g", o) elseif tp == "string" then return string.format("%q", o) elseif tp == "boolean" then return tostring(o) elseif tp == "nil" then return "nil" - -- Uncomment for full function dumping support. - -- Not currently enabled because bytecode isn't very human-readable and - -- dump's output is intended for humans. - --elseif tp == "function" then - -- return string.format("loadstring(%q)", string.dump(o)) elseif tp == "userdata" then return tostring(o) else @@ -105,65 +108,141 @@ function dump2(o, name, dumped) return string.format("%s = {}\n%s", name, table.concat(t)) end --------------------------------------------------------------------------------- --- This dumps values in a one-statement format. + +-- This dumps values in a human-readable expression format. +-- If possible, the resulting string should evaluate to an equivalent value if loaded and executed. -- For example, {test = {"Testing..."}} becomes: -- [[{ -- test = { -- "Testing..." -- } -- }]] --- This supports tables as keys, but not circular references. --- It performs poorly with multiple references as it writes out the full --- table each time. --- The indent field specifies a indentation string, it defaults to a tab. --- Use the empty string to disable indentation. --- The dumped and level arguments are internal-only. - -function dump(o, indent, nested, level) - local t = type(o) - if not level and t == "userdata" then - -- when userdata (e.g. player) is passed directly, print its metatable: - return "userdata metatable: " .. dump(getmetatable(o)) - end - if t ~= "table" then - return basic_dump(o) - end - - -- Contains table -> true/nil of currently nested tables - nested = nested or {} - if nested[o] then - return "" - end - nested[o] = true +function dump(value, indent) indent = indent or "\t" - level = level or 1 + local newline = indent == "" and "" or "\n" - local ret = {} - local dumped_indexes = {} - for i, v in ipairs(o) do - ret[#ret + 1] = dump(v, indent, nested, level + 1) - dumped_indexes[i] = true - end - for k, v in pairs(o) do - if not dumped_indexes[k] then - if type(k) ~= "string" or not is_valid_identifier(k) then - k = "["..dump(k, indent, nested, level + 1).."]" - end - v = dump(v, indent, nested, level + 1) - ret[#ret + 1] = k.." = "..v + local rope = {} + local write + do + -- Keeping the length of the table as a local variable is *much* + -- faster than invoking the length operator. + -- See https://gitspartv.github.io/LuaJIT-Benchmarks/#test12. + local i = 0 + function write(str) + i = i + 1 + rope[i] = str end end - nested[o] = nil - if indent ~= "" then - local indent_str = "\n"..string.rep(indent, level) - local end_indent_str = "\n"..string.rep(indent, level - 1) - return string.format("{%s%s%s}", - indent_str, - table.concat(ret, ","..indent_str), - end_indent_str) + + local n_refs = {} + local function count_refs(val) + if type(val) ~= "table" then + return + end + local tbl = val + if n_refs[tbl] then + n_refs[tbl] = n_refs[tbl] + 1 + return + end + n_refs[tbl] = 1 + for k, v in pairs(tbl) do + count_refs(k) + count_refs(v) + end end - return "{"..table.concat(ret, ", ").."}" + count_refs(value) + + local refs = {} + local cur_ref = 1 + local function write_value(val, level) + if type(val) ~= "table" then + write(basic_dump(val)) + return + end + + local tbl = val + if refs[tbl] then + write(refs[tbl]) + return + end + + if n_refs[val] > 1 then + refs[val] = ("getref(%d)"):format(cur_ref) + write(("setref(%d)"):format(cur_ref)) + cur_ref = cur_ref + 1 + end + write("{") + if next(tbl) == nil then + write("}") + return + end + write(newline) + + local function write_entry(k, v) + write(indent:rep(level)) + write("[") + write_value(k, level + 1) + write("] = ") + write_value(v, level + 1) + write(",") + write(newline) + end + + local keys = {string = {}, number = {}} + for k in pairs(tbl) do + local t = type(k) + if keys[t] then + table.insert(keys[t], k) + end + end + + -- Write string-keyed entries + table.sort(keys.string) + for _, k in ipairs(keys.string) do + local v = val[k] + if is_valid_identifier(k) then + write(indent:rep(level)) + write(k) + write(" = ") + write_value(v, level + 1) + write(",") + write(newline) + else + write_entry(k, v) + end + end + + -- Write number-keyed entries + local len = 0 + for i in ipairs(tbl) do + len = i + end + if #keys.number == len then -- table is a list + for _, v in ipairs(tbl) do + write(indent:rep(level)) + write_value(v, level + 1) + write(",") + write(newline) + end + else -- table harbors arbitrary number keys + table.sort(keys.number) + for _, k in ipairs(keys.number) do + write_entry(k, tbl[k]) + end + end + + -- Write all remaining entries + for k, v in pairs(val) do + if not keys[type(k)] then + write_entry(k, v) + end + end + + write(indent:rep(level - 1)) + write("}") + end + write_value(value, 1) + return table.concat(rope) end -------------------------------------------------------------------------------- @@ -457,18 +536,37 @@ do end end --------------------------------------------------------------------------------- -function table.copy(t, seen) - local n = {} - seen = seen or {} - seen[t] = n - for k, v in pairs(t) do - n[(type(k) == "table" and (seen[k] or table.copy(k, seen))) or k] = - (type(v) == "table" and (seen[v] or table.copy(v, seen))) or v + +local function table_copy(value, preserve_metatables) + local seen = {} + local function copy(val) + if type(val) ~= "table" then + return val + end + local t = val + if seen[t] then + return seen[t] + end + local res = {} + seen[t] = res + for k, v in pairs(t) do + res[copy(k)] = copy(v) + end + if preserve_metatables then + setmetatable(res, getmetatable(t)) + end + return res end - return n + return copy(value) end +function table.copy(value) + return table_copy(value, false) +end + +function table.copy_with_metatables(value) + return table_copy(value, true) +end function table.insert_all(t, other) if table.move then -- LuaJIT @@ -536,6 +634,10 @@ if core.gettext then -- for client and mainmenu function fgettext(text, ...) return core.formspec_escape(fgettext_ne(text, ...)) end + + function hgettext(text, ...) + return core.hypertext_escape(fgettext_ne(text, ...)) + end end local ESCAPE_CHAR = string.char(0x1b) diff --git a/builtin/common/register.lua b/builtin/common/register.lua index c300ef90f..cbeac7c64 100644 --- a/builtin/common/register.lua +++ b/builtin/common/register.lua @@ -54,7 +54,8 @@ function builtin_shared.make_registration() local registerfunc = function(func) t[#t + 1] = func core.callback_origins[func] = { - mod = core.get_current_modname() or "??", + -- may be nil or return nil + mod = core.get_current_modname and core.get_current_modname() or "??", name = debug.getinfo(1, "n").name or "??" } end @@ -66,7 +67,8 @@ function builtin_shared.make_registration_reverse() local registerfunc = function(func) table.insert(t, 1, func) core.callback_origins[func] = { - mod = core.get_current_modname() or "??", + -- may be nil or return nil + mod = core.get_current_modname and core.get_current_modname() or "??", name = debug.getinfo(1, "n").name or "??" } end diff --git a/builtin/common/serialize.lua b/builtin/common/serialize.lua index 146128e0c..9ebece6d0 100644 --- a/builtin/common/serialize.lua +++ b/builtin/common/serialize.lua @@ -190,11 +190,41 @@ local function serialize(value, write) dump(value) end +-- Whether `value` recursively contains a function +local function contains_function(value) + local seen = {} + local function check(val) + if type(val) == "function" then + return true + end + if type(val) == "table" then + if seen[val] then + return false + end + seen[val] = true + for k, v in pairs(val) do + if check(k) or check(v) then + return true + end + end + end + return false + end + return check(value) +end + function core.serialize(value) + if contains_function(value) then + core.log("deprecated", "Support for dumping functions in `core.serialize` is deprecated.") + end local rope = {} + -- Keeping the length of the table as a local variable is *much* + -- faster than invoking the length operator. + -- See https://gitspartv.github.io/LuaJIT-Benchmarks/#test12. + local i = 0 serialize(value, function(text) - -- Faster than table.insert(rope, text) on PUC Lua 5.1 - rope[#rope + 1] = text + i = i + 1 + rope[i] = text end) return table_concat(rope) end diff --git a/builtin/mainmenu/settings/components.lua b/builtin/common/settings/components.lua similarity index 77% rename from builtin/mainmenu/settings/components.lua rename to builtin/common/settings/components.lua index 64cd27c3c..99fb0cd76 100644 --- a/builtin/mainmenu/settings/components.lua +++ b/builtin/common/settings/components.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2022 rubenwardy --- ---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. +-- Luanti +-- Copyright (C) 2022 rubenwardy +-- SPDX-License-Identifier: LGPL-2.1-or-later local make = {} @@ -37,6 +24,7 @@ local make = {} -- * `fs` is a string for the formspec. -- Components should be relative to `0,0`, and not exceed `avail_w` or the returned `used_height`. -- * `used_height` is the space used by components in `fs`. +-- * `spacing`: (Optional) the vertical margin to be added before the component (default 0.25) -- * `on_submit = function(self, fields, parent)`: -- * `fields`: submitted formspec fields -- * `parent`: the fstk element for the settings UI, use to show dialogs @@ -98,6 +86,7 @@ local function make_field(converter, validator, stringifier) local fs = ("field[0,0.3;%f,0.8;%s;%s;%s]"):format( avail_w - 1.5, setting.name, get_label(setting), core.formspec_escape(value)) fs = fs .. ("field_enter_after_edit[%s;true]"):format(setting.name) + fs = fs .. ("field_close_on_enter[%s;false]"):format(setting.name) -- for pause menu env fs = fs .. ("button[%f,0.3;1.5,0.8;%s;%s]"):format(avail_w - 1.5, "set_" .. setting.name, fgettext("Set")) return fs, 1.1 @@ -217,6 +206,8 @@ local function make_path(setting) local fs = ("field[0,0.3;%f,0.8;%s;%s;%s]"):format( avail_w - 3, setting.name, get_label(setting), core.formspec_escape(value)) + fs = fs .. ("field_enter_after_edit[%s;true]"):format(setting.name) + fs = fs .. ("field_close_on_enter[%s;false]"):format(setting.name) -- for pause menu env fs = fs .. ("button[%f,0.3;1.5,0.8;%s;%s]"):format(avail_w - 3, "pick_" .. setting.name, fgettext("Browse")) fs = fs .. ("button[%f,0.3;1.5,0.8;%s;%s]"):format(avail_w - 1.5, "set_" .. setting.name, fgettext("Set")) @@ -249,8 +240,11 @@ local function make_path(setting) } end -if PLATFORM == "Android" then +if PLATFORM == "Android" or INIT == "pause_menu" then -- The Irrlicht file picker doesn't work on Android. + -- Access to the Irrlicht file picker isn't implemented in the pause menu. + -- We want to delete the Irrlicht file picker anyway, so any time spent on + -- that would be wasted. make.path = make.string make.filepath = make.string else @@ -282,6 +276,14 @@ function make.v3f(setting) fs = fs .. ("field[%f,0.6;%f,0.8;%s;%s;%s]"):format( 2 * (field_width + 0.25), field_width, setting.name .. "_z", "Z", value.z) + fs = fs .. ("field_enter_after_edit[%s;true]"):format(setting.name .. "_x") + fs = fs .. ("field_enter_after_edit[%s;true]"):format(setting.name .. "_y") + fs = fs .. ("field_enter_after_edit[%s;true]"):format(setting.name .. "_z") + -- for pause menu env + fs = fs .. ("field_close_on_enter[%s;false]"):format(setting.name .. "_x") + fs = fs .. ("field_close_on_enter[%s;false]"):format(setting.name .. "_y") + fs = fs .. ("field_close_on_enter[%s;false]"):format(setting.name .. "_z") + fs = fs .. ("button[%f,0.6;1,0.8;%s;%s]"):format(avail_w, "set_" .. setting.name, fgettext("Set")) return fs, 1.4 @@ -428,8 +430,75 @@ local function make_noise_params(setting) } end -make.noise_params_2d = make_noise_params -make.noise_params_3d = make_noise_params +function make.key(setting) + local btn_bind = "bind_" .. setting.name + local btn_clear = "unbind_" .. setting.name + local function add_conflict_warnings(fs, height) + local value = core.settings:get(setting.name) + if value == "" then + return height + end + for _, o in ipairs(core.full_settingtypes) do + if o.type == "key" and o.name ~= setting.name and core.are_keycodes_equal(core.settings:get(o.name), value) then + table.insert(fs, ("label[0,%f;%s]"):format(height + 0.3, + core.colorize(mt_color_orange, fgettext([[Conflicts with "$1"]], fgettext(o.readable_name))))) + height = height + 0.6 + end + end + return height + end + return { + info_text = setting.comment, + setting = setting, + spacing = 0.1, + + get_formspec = function(self, avail_w) + self.resettable = core.settings:has(setting.name) + local btn_bind_width = math.max(2.5, avail_w/2) + local value = core.settings:get(setting.name) + local fs = { + ("label[0,0.4;%s]"):format(get_label(setting)), + ("button_key[%f,0;%f,0.8;%s;%s]"):format( + btn_bind_width, btn_bind_width-0.8, + btn_bind, core.formspec_escape(value)), + ("image_button[%f,0;0.8,0.8;%s;%s;]"):format(avail_w - 0.8, + core.formspec_escape(defaulttexturedir .. "clear.png"), + btn_clear), + ("tooltip[%s;%s]"):format(btn_clear, fgettext("Remove keybinding")), + } + local height = 0.8 + height = add_conflict_warnings(fs, height) + return table.concat(fs), height + end, + + on_submit = function(self, fields) + if fields[btn_bind] then + core.settings:set(setting.name, fields[btn_bind]) + return true + elseif fields[btn_clear] then + core.settings:set(setting.name, "") + return true + end + end, + } +end + +if INIT == "pause_menu" then + -- Making the noise parameter dialog work in the pause menu settings would + -- require porting "FSTK" (at least the dialog API) from the mainmenu formspec + -- API to the in-game formspec API. + -- There's no reason you'd want to adjust mapgen noise parameter settings + -- in-game (they only apply to new worlds, hidden as [world_creation]), + -- so there's no reason to implement this. + local empty = function() + return { get_formspec = function() return "", 0 end } + end + make.noise_params_2d = empty + make.noise_params_3d = empty +else + make.noise_params_2d = make_noise_params + make.noise_params_3d = make_noise_params +end return make diff --git a/builtin/mainmenu/settings/dlg_change_mapgen_flags.lua b/builtin/common/settings/dlg_change_mapgen_flags.lua similarity index 89% rename from builtin/mainmenu/settings/dlg_change_mapgen_flags.lua rename to builtin/common/settings/dlg_change_mapgen_flags.lua index 31dd40bd8..de96c75e3 100644 --- a/builtin/mainmenu/settings/dlg_change_mapgen_flags.lua +++ b/builtin/common/settings/dlg_change_mapgen_flags.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2015 PilzAdam --- ---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. +-- Luanti +-- Copyright (C) 2015 PilzAdam +-- SPDX-License-Identifier: LGPL-2.1-or-later local checkboxes = {} diff --git a/builtin/mainmenu/settings/dlg_settings.lua b/builtin/common/settings/dlg_settings.lua similarity index 84% rename from builtin/mainmenu/settings/dlg_settings.lua rename to builtin/common/settings/dlg_settings.lua index f1861879a..77fc8be3f 100644 --- a/builtin/mainmenu/settings/dlg_settings.lua +++ b/builtin/common/settings/dlg_settings.lua @@ -1,29 +1,14 @@ ---Luanti ---Copyright (C) 2022 rubenwardy --- ---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. +-- Luanti +-- Copyright (C) 2022 rubenwardy +-- SPDX-License-Identifier: LGPL-2.1-or-later -local component_funcs = dofile(core.get_mainmenu_path() .. DIR_DELIM .. - "settings" .. DIR_DELIM .. "components.lua") +local path = core.get_builtin_path() .. "common" .. DIR_DELIM .. "settings" .. DIR_DELIM -local shadows_component = dofile(core.get_mainmenu_path() .. DIR_DELIM .. - "settings" .. DIR_DELIM .. "shadows_component.lua") +local component_funcs = dofile(path .. "components.lua") +local shadows_component = dofile(path .. "shadows_component.lua") local loaded = false -local full_settings local info_icon_path = core.formspec_escape(defaulttexturedir .. "settings_info.png") local reset_icon_path = core.formspec_escape(defaulttexturedir .. "settings_reset.png") local all_pages = {} @@ -33,7 +18,7 @@ local filtered_page_by_id = page_by_id local function get_setting_info(name) - for _, entry in ipairs(full_settings) do + for _, entry in ipairs(core.full_settingtypes) do if entry.type ~= "category" and entry.name == name then return entry end @@ -71,7 +56,7 @@ local function load_settingtypes() end end - for _, entry in ipairs(full_settings) do + for _, entry in ipairs(core.full_settingtypes) do if entry.type == "category" then if entry.level == 0 then section = entry.name @@ -105,29 +90,14 @@ local function load() end loaded = true - full_settings = settingtypes.parse_config_file(false, true) - - local change_keys = { - query_text = "Controls", - requires = { - touch_controls = false, - }, - get_formspec = function(self, avail_w) - local btn_w = math.min(avail_w, 3) - return ("button[0,0;%f,0.8;btn_change_keys;%s]"):format(btn_w, fgettext("Controls")), 0.8 - end, - on_submit = function(self, fields) - if fields.btn_change_keys then - core.show_keys_menu() - end - end, - } + core.full_settingtypes = settingtypes.parse_config_file(false, true) local touchscreen_layout = { query_text = "Touchscreen layout", requires = { touchscreen = true, }, + context = "client", get_formspec = function(self, avail_w) local btn_w = math.min(avail_w, 6) return ("button[0,0;%f,0.8;btn_touch_layout;%s]"):format(btn_w, fgettext("Touchscreen layout")), 0.8 @@ -160,13 +130,11 @@ local function load() { heading = fgettext_ne("Movement") }, "arm_inertia", "view_bobbing_amount", - "fall_bobbing_amount", }, }) load_settingtypes() - table.insert(page_by_id.controls_keyboard_and_mouse.content, 1, change_keys) -- insert after "touch_controls" table.insert(page_by_id.controls_touchscreen.content, 2, touchscreen_layout) do @@ -175,18 +143,24 @@ local function load() table.insert(content, idx, shadows_component) idx = table.indexof(content, "enable_auto_exposure") + 1 + local setting_info = get_setting_info("enable_auto_exposure") local note = component_funcs.note(fgettext_ne("(The game will need to enable automatic exposure as well)")) - note.requires = get_setting_info("enable_auto_exposure").requires + note.requires = setting_info.requires + note.context = setting_info.context table.insert(content, idx, note) idx = table.indexof(content, "enable_bloom") + 1 + setting_info = get_setting_info("enable_bloom") note = component_funcs.note(fgettext_ne("(The game will need to enable bloom as well)")) - note.requires = get_setting_info("enable_bloom").requires + note.requires = setting_info.requires + note.context = setting_info.context table.insert(content, idx, note) idx = table.indexof(content, "enable_volumetric_lighting") + 1 + setting_info = get_setting_info("enable_volumetric_lighting") note = component_funcs.note(fgettext_ne("(The game will need to enable volumetric lighting as well)")) - note.requires = get_setting_info("enable_volumetric_lighting").requires + note.requires = setting_info.requires + note.context = setting_info.context table.insert(content, idx, note) end @@ -261,6 +235,17 @@ local function load() ["true"] = fgettext_ne("Enabled"), ["false"] = fgettext_ne("Disabled"), } + + get_setting_info("touch_interaction_style").option_labels = { + ["tap"] = fgettext_ne("Tap"), + ["tap_crosshair"] = fgettext_ne("Tap with crosshair"), + ["buttons_crosshair"] = fgettext("Buttons with crosshair"), + } + + get_setting_info("touch_punch_gesture").option_labels = { + ["short_tap"] = fgettext_ne("Short tap"), + ["long_tap"] = fgettext_ne("Long tap"), + } end @@ -353,7 +338,18 @@ local function update_filtered_pages(query) end -local function check_requirements(name, requires) +local shown_contexts = { + common = true, + client = true, + server = INIT ~= "pause_menu" or core.is_internal_server(), + world_creation = INIT ~= "pause_menu", +} + +local function check_requirements(name, requires, context) + if context and not shown_contexts[context] then + return false + end + if requires == nil then return true end @@ -361,6 +357,7 @@ local function check_requirements(name, requires) local video_driver = core.get_active_driver() local touch_support = core.irrlicht_device_supports_touch() local touch_controls = core.settings:get("touch_controls") + local touch_interaction_style = core.settings:get("touch_interaction_style") local special = { android = PLATFORM == "Android", desktop = PLATFORM ~= "Android", @@ -371,6 +368,7 @@ local function check_requirements(name, requires) keyboard_mouse = not touch_support or (touch_controls == "auto" or not core.is_yes(touch_controls)), opengl = (video_driver == "opengl" or video_driver == "opengl3"), gles = video_driver:sub(1, 5) == "ogles", + touch_interaction_style_tap = touch_interaction_style ~= "buttons_crosshair", } for req_key, req_value in pairs(requires) do @@ -378,6 +376,9 @@ local function check_requirements(name, requires) local required_setting = get_setting_info(req_key) if required_setting == nil then core.log("warning", "Unknown setting " .. req_key .. " required by " .. (name or "???")) + elseif required_setting.type ~= "bool" then + core.log("warning", "Setting " .. req_key .. " of type " .. required_setting.type .. + " used as requirement by " .. (name or "???") .. ", only bool is allowed") end local actual_value = core.settings:get_bool(req_key, required_setting and core.is_yes(required_setting.default)) @@ -409,11 +410,11 @@ function page_has_contents(page, actual_content) elseif type(item) == "string" then local setting = get_setting_info(item) assert(setting, "Unknown setting: " .. item) - if check_requirements(setting.name, setting.requires) then + if check_requirements(setting.name, setting.requires, setting.context) then return true end elseif item.get_formspec then - if check_requirements(item.id, item.requires) then + if check_requirements(item.id, item.requires, item.context) then return true end else @@ -435,20 +436,22 @@ local function build_page_components(page) elseif item.heading then last_heading = item else - local name, requires + local name, requires, context if type(item) == "string" then local setting = get_setting_info(item) assert(setting, "Unknown setting: " .. item) name = setting.name requires = setting.requires + context = setting.context elseif item.get_formspec then name = item.id requires = item.requires + context = item.context else error("Unknown content in page: " .. dump(item)) end - if check_requirements(name, requires) then + if check_requirements(name, requires, context) then if last_heading then content[#content + 1] = last_heading last_heading = nil @@ -514,7 +517,8 @@ local function get_formspec(dialogdata) "box[0,0;", tostring(tabsize.width), ",", tostring(tabsize.height), ";#0000008C]", ("button[0,%f;%f,0.8;back;%s]"):format( - tabsize.height + 0.2, back_w, fgettext("Back")), + tabsize.height + 0.2, back_w, + fgettext("Back")), ("box[%f,%f;%f,0.8;#0000008C]"):format( back_w + 0.2, tabsize.height + 0.2, checkbox_w), @@ -531,6 +535,7 @@ local function get_formspec(dialogdata) "field[0.25,0.25;", tostring(search_width), ",0.75;search_query;;", core.formspec_escape(dialogdata.query or ""), "]", "field_enter_after_edit[search_query;true]", + "field_close_on_enter[search_query;false]", -- for pause menu env "container[", tostring(search_width + 0.25), ", 0.25]", "image_button[0,0;0.75,0.75;", core.formspec_escape(defaulttexturedir .. "search.png"), ";search;]", "image_button[0.75,0;0.75,0.75;", core.formspec_escape(defaulttexturedir .. "clear.png"), ";search_clear;]", @@ -628,7 +633,13 @@ local function get_formspec(dialogdata) fs[#fs + 1] = "container_end[]" if used_h > 0 then - y = y + used_h + 0.25 + local spacing = 0.25 + local next_comp = dialogdata.components[i + 1] + if next_comp and next_comp.spacing then + spacing = next_comp.spacing + end + + y = y + used_h + spacing end end @@ -671,7 +682,8 @@ local function buttonhandler(this, fields) dialogdata.rightscroll = core.explode_scrollbar_event(fields.rightscroll).value or dialogdata.rightscroll dialogdata.query = fields.search_query - if fields.back then + -- "fields.quit" is for the pause menu env + if fields.back or fields.quit then this:delete() return true end @@ -765,11 +777,44 @@ local function eventhandler(event) end -function create_settings_dlg() - load() - local dlg = dialog_create("dlg_settings", get_formspec, buttonhandler, eventhandler) +if INIT == "mainmenu" then + function create_settings_dlg(page_id) + load() + local dlg = dialog_create("dlg_settings", get_formspec, buttonhandler, eventhandler) - dlg.data.page_id = update_filtered_pages("") + dlg.data.page_id = page_id or update_filtered_pages("") - return dlg + return dlg + end + +else + assert(INIT == "pause_menu") + + local dialog + + core.register_on_formspec_input(function(formname, fields) + if dialog and formname == "__builtin:settings" then + -- buttonhandler returning true means we should update the formspec. + -- dialog is re-checked since the buttonhandler may have closed it. + if buttonhandler(dialog, fields) and dialog then + core.show_formspec("__builtin:settings", get_formspec(dialog.data)) + end + return true + end + end) + + core.open_settings = function() + load() + dialog = {} + dialog.data = {} + dialog.data.page_id = update_filtered_pages("") + dialog.delete = function() + dialog = nil + -- only needed for the "fields.back" case, in the "fields.quit" + -- case it's a no-op + core.show_formspec("__builtin:settings", "") + end + + core.show_formspec("__builtin:settings", get_formspec(dialog.data)) + end end diff --git a/builtin/mainmenu/settings/generate_from_settingtypes.lua b/builtin/common/settings/generate_from_settingtypes.lua similarity index 97% rename from builtin/mainmenu/settings/generate_from_settingtypes.lua rename to builtin/common/settings/generate_from_settingtypes.lua index 52dfe71b1..4c33a8fc1 100644 --- a/builtin/mainmenu/settings/generate_from_settingtypes.lua +++ b/builtin/common/settings/generate_from_settingtypes.lua @@ -61,7 +61,7 @@ local function create_minetest_conf_example(settings) end end if entry.type == "key" then - local line = "See https://github.com/minetest/irrlicht/blob/master/include/Keycodes.h" + local line = "See https://docs.luanti.org/for-players/controls/" insert(result, "# " .. line .. "\n") end insert(result, "# type: " .. entry.type) diff --git a/builtin/common/settings/init.lua b/builtin/common/settings/init.lua new file mode 100644 index 000000000..71a95424b --- /dev/null +++ b/builtin/common/settings/init.lua @@ -0,0 +1,15 @@ +-- Luanti +-- Copyright (C) 2022 rubenwardy +-- SPDX-License-Identifier: LGPL-2.1-or-later + +local path = core.get_builtin_path() .. "common" .. DIR_DELIM .. "settings" .. DIR_DELIM + +dofile(path .. "settingtypes.lua") +dofile(path .. "dlg_change_mapgen_flags.lua") +dofile(path .. "dlg_settings.lua") + +-- Uncomment to generate 'minetest.conf.example' and 'settings_translation_file.cpp'. +-- For RUN_IN_PLACE the generated files may appear in the 'bin' folder. +-- See comment and alternative line at the end of 'generate_from_settingtypes.lua'. + +-- dofile(path .. DIR_DELIM .. "generate_from_settingtypes.lua") diff --git a/builtin/mainmenu/settings/settingtypes.lua b/builtin/common/settings/settingtypes.lua similarity index 79% rename from builtin/mainmenu/settings/settingtypes.lua rename to builtin/common/settings/settingtypes.lua index e763535a9..fa54ec33b 100644 --- a/builtin/mainmenu/settings/settingtypes.lua +++ b/builtin/common/settings/settingtypes.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2015 PilzAdam --- ---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. +-- Luanti +-- Copyright (C) 2015 PilzAdam +-- SPDX-License-Identifier: LGPL-2.1-or-later settingtypes = {} @@ -40,12 +27,24 @@ local CHAR_CLASSES = { FLAGS = "[%w_%-%.,]", } +local valid_contexts = {common = true, client = true, server = true, world_creation = true} + +local function check_context_annotation(context, force_context) + if force_context then + return "Context annotations are not allowed, context is always " .. force_context + end + if not valid_contexts[context] then + return "Unknown context" + end + return nil +end + local function flags_to_table(flags) return flags:gsub("%s+", ""):split(",", true) -- Remove all spaces and split end -- returns error message, or nil -local function parse_setting_line(settings, line, read_all, base_level, allow_secure) +local function parse_setting_line(settings, line, read_all, base_level, allow_secure, force_context) -- strip carriage returns (CR, /r) line = line:gsub("\r", "") @@ -69,9 +68,32 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se -- category local stars, category = line:match("^%[([%*]*)([^%]]+)%]$") + local category_context + if not category then + stars, category, category_context = line:match("^%[([%*]*)([^%]]+)%] %[([^%]]+)%]$") + end if category then local category_level = stars:len() + base_level + if settings.current_context_level and + category_level <= settings.current_context_level then + -- The start of this category marks the end of the context annotation's scope. + settings.current_context_level = nil + settings.current_context = nil + end + + if category_context then + local err = check_context_annotation(category_context, force_context) + if err then + return err + end + if settings.current_context_level then + return "Category context annotations cannot be nested" + end + settings.current_context_level = category_level + settings.current_context = category_context + end + if settings.current_hide_level then if settings.current_hide_level < category_level then -- Skip this category, it's inside a hidden category. @@ -102,7 +124,8 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se end -- settings - local first_part, name, readable_name, setting_type = line:match("^" + local function make_pattern(include_context) + return "^" -- this first capture group matches the whole first part, -- so we can later strip it from the rest of the line .. "(" @@ -110,9 +133,19 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se .. CHAR_CLASSES.SPACE .. "*" .. "%(([^%)]*)%)" -- readable name .. CHAR_CLASSES.SPACE .. "*" + .. (include_context and ( + "%[([^%]]+)%]" -- context annotation + .. CHAR_CLASSES.SPACE .. "*" + ) or "") .. "(" .. CHAR_CLASSES.VARIABLE .. "+)" -- type .. CHAR_CLASSES.SPACE .. "*" - .. ")") + .. ")" + end + local first_part, name, readable_name, setting_type = line:match(make_pattern(false)) + local setting_context + if not first_part then + first_part, name, readable_name, setting_context, setting_type = line:match(make_pattern(true)) + end if not first_part then return "Invalid line" @@ -122,6 +155,26 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se return "Tried to add \"secure.\" setting" end + if setting_context then + local err = check_context_annotation(setting_context, force_context) + if err then + return err + end + end + + local context + if force_context then + context = force_context + else + if setting_context then + context = setting_context + elseif settings.current_context_level then + context = settings.current_context + else + return "Missing context annotation" + end + end + local requires = {} local last_line = #current_comment > 0 and current_comment[#current_comment]:trim() if last_line and last_line:lower():sub(1, 9) == "requires:" then @@ -170,6 +223,7 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se min = min, max = max, requires = requires, + context = context, comment = comment, }) return @@ -182,9 +236,9 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se if not default then return "Invalid string setting" end - if setting_type == "key" and not read_all then - -- ignore key type if read_all is false - return + + if setting_type == "key" then + requires.keyboard_mouse = true end table.insert(settings, { @@ -193,6 +247,7 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se type = setting_type, default = default, requires = requires, + context = context, comment = comment, }) return @@ -245,6 +300,7 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se }, values = values, requires = requires, + context = context, comment = comment, noise_params = true, flags = flags_to_table("defaults,eased,absvalue") @@ -263,6 +319,7 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se type = "bool", default = remaining_line, requires = requires, + context = context, comment = comment, }) return @@ -290,6 +347,7 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se min = min, max = max, requires = requires, + context = context, comment = comment, }) return @@ -313,6 +371,7 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se default = default, values = values:split(",", true), requires = requires, + context = context, comment = comment, }) return @@ -331,6 +390,7 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se type = setting_type, default = default, requires = requires, + context = context, comment = comment, }) return @@ -361,6 +421,7 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se default = default, possible = flags_to_table(possible), requires = requires, + context = context, comment = comment, }) return @@ -369,14 +430,14 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se return "Invalid setting type \"" .. setting_type .. "\"" end -local function parse_single_file(file, filepath, read_all, result, base_level, allow_secure) +local function parse_single_file(file, filepath, read_all, result, base_level, allow_secure, force_context) -- store this helper variable in the table so it's easier to pass to parse_setting_line() result.current_comment = {} result.current_hide_level = nil local line = file:read("*line") while line do - local error_msg = parse_setting_line(result, line, read_all, base_level, allow_secure) + local error_msg = parse_setting_line(result, line, read_all, base_level, allow_secure, force_context) if error_msg then core.log("error", error_msg .. " in " .. filepath .. " \"" .. line .. "\"") end @@ -408,7 +469,15 @@ function settingtypes.parse_config_file(read_all, parse_mods) file:close() end - if parse_mods then + -- TODO: Support game/mod settings in the pause menu too + -- Note that this will need to work different from how it's done in the + -- mainmenu: + -- * Only show settings for the active game and mods + -- (add API function to get them, can return nil if on a remote server) + -- (names are probably not enough, will need paths for uniqueness) + -- This means just making "pkgmgr.lua" work won't get you very far. + + if INIT == "mainmenu" and parse_mods then -- Parse games local games_category_initialized = false for _, game in ipairs(pkgmgr.games) do @@ -432,7 +501,7 @@ function settingtypes.parse_config_file(read_all, parse_mods) type = "category", }) - parse_single_file(file, path, read_all, settings, 2, false) + parse_single_file(file, path, read_all, settings, 2, false, "server") file:close() end @@ -465,7 +534,7 @@ function settingtypes.parse_config_file(read_all, parse_mods) type = "category", }) - parse_single_file(file, path, read_all, settings, 2, false) + parse_single_file(file, path, read_all, settings, 2, false, "server") file:close() end @@ -496,7 +565,7 @@ function settingtypes.parse_config_file(read_all, parse_mods) type = "category", }) - parse_single_file(file, path, read_all, settings, 2, false) + parse_single_file(file, path, read_all, settings, 2, false, "client") file:close() end diff --git a/builtin/mainmenu/settings/shadows_component.lua b/builtin/common/settings/shadows_component.lua similarity index 78% rename from builtin/mainmenu/settings/shadows_component.lua rename to builtin/common/settings/shadows_component.lua index 2d68f9d3d..59b152ab8 100644 --- a/builtin/mainmenu/settings/shadows_component.lua +++ b/builtin/common/settings/shadows_component.lua @@ -1,20 +1,7 @@ ---Luanti ---Copyright (C) 2021-2 x2048 ---Copyright (C) 2022-3 rubenwardy --- ---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. +-- Luanti +-- Copyright (C) 2021-2 x2048 +-- Copyright (C) 2022-3 rubenwardy +-- SPDX-License-Identifier: LGPL-2.1-or-later local shadow_levels_labels = { @@ -84,6 +71,7 @@ return { requires = { opengl = true, }, + context = "client", get_formspec = function(self, avail_w) local labels = table.copy(shadow_levels_labels) local idx = detect_mapping_idx() diff --git a/builtin/common/tests/misc_helpers_spec.lua b/builtin/common/tests/misc_helpers_spec.lua index 10e2bf277..59eb4ec5f 100644 --- a/builtin/common/tests/misc_helpers_spec.lua +++ b/builtin/common/tests/misc_helpers_spec.lua @@ -178,6 +178,35 @@ describe("table", function() assert.equal(2, table.keyof({[2] = "foo", [3] = "bar"}, "foo")) assert.equal(3, table.keyof({[1] = "foo", [3] = "bar"}, "bar")) end) + + describe("copy()", function() + it("strips metatables", function() + local v = vector.new(1, 2, 3) + local w = table.copy(v) + assert.are_not.equal(v, w) + assert.same(v, w) + assert.equal(nil, getmetatable(w)) + end) + it("preserves referential structure", function() + local t = {{}, {}} + t[1][1] = t[2] + t[2][1] = t[1] + local copy = table.copy(t) + assert.same(t, copy) + assert.equal(copy[1][1], copy[2]) + assert.equal(copy[2][1], copy[1]) + end) + end) + + describe("copy_with_metatables()", function() + it("preserves metatables", function() + local v = vector.new(1, 2, 3) + local w = table.copy_with_metatables(v) + assert.equal(getmetatable(v), getmetatable(w)) + assert(vector.check(w)) + assert.equal(v, w) -- vector overrides == + end) + end) end) describe("formspec_escape", function() @@ -201,3 +230,124 @@ describe("math", function() assert.equal(0, math.round(-0.49999999999999994)) end) end) + +describe("dump", function() + local function test_expression(expr) + local chunk = assert(loadstring("return " .. expr)) + local refs = {} + setfenv(chunk, { + setref = function(id) + refs[id] = {} + return function(fields) + for k, v in pairs(fields) do + refs[id][k] = v + end + return refs[id] + end + end, + getref = function(id) + return assert(refs[id]) + end, + }) + assert.equal(expr, dump(chunk())) + end + + it("nil", function() + test_expression("nil") + end) + + it("booleans", function() + test_expression("false") + test_expression("true") + end) + + describe("numbers", function() + it("formats integers nicely", function() + test_expression("42") + end) + it("avoids misleading rounding", function() + test_expression("0.3") + assert.equal("0.30000000000000004", dump(0.1 + 0.2)) + end) + end) + + it("strings", function() + test_expression('"hello world"') + test_expression([["hello \"world\""]]) + end) + + describe("tables", function() + it("empty", function() + test_expression("{}") + end) + + it("lists", function() + test_expression([[ +{ + false, + true, + "foo", + 1, + 2, +}]]) + end) + + it("number keys", function() +test_expression([[ +{ + [0.5] = false, + [1.5] = true, + [2.5] = "foo", +}]]) + end) + + it("dicts", function() + test_expression([[{ + a = 1, + b = 2, + c = 3, +}]]) + end) + + it("mixed", function() + test_expression([[{ + a = 1, + b = 2, + c = 3, + ["d e"] = true, + "foo", + "bar", +}]]) + end) + + it("nested", function() +test_expression([[{ + a = { + 1, + {}, + }, + b = "foo", + c = { + [0.5] = 0.1, + [1.5] = 0.2, + }, +}]]) + end) + + it("circular references", function() +test_expression([[setref(1){ + child = { + parent = getref(1), + }, + other_child = { + parent = getref(1), + }, +}]]) + end) + + it("supports variable indent", function() + assert.equal('{1,2,3,{foo = "bar",},}', dump({1, 2, 3, {foo = "bar"}}, "")) + assert.equal('{\n "x",\n "y",\n}', dump({"x", "y"}, " ")) + end) + end) +end) diff --git a/builtin/common/tests/serialize_spec.lua b/builtin/common/tests/serialize_spec.lua index d4e501468..2a7a0f3ce 100644 --- a/builtin/common/tests/serialize_spec.lua +++ b/builtin/common/tests/serialize_spec.lua @@ -93,21 +93,49 @@ describe("serialize", function() assert_preserves(test_in) end) - it("strips functions in safe mode", function() - local test_in = { - func = function(a, b) - error("test") - end, - foo = "bar" - } - setfenv(test_in.func, _G) + describe("safe mode", function() + setup(function() + assert(not core.log) + -- logging a deprecation warning will be attempted + function core.log() end + end) + teardown(function() + core.log = nil + end) + it("functions are stripped", function() + local test_in = { + func = function(a, b) + error("test") + end, + foo = "bar" + } + setfenv(test_in.func, _G) - local str = core.serialize(test_in) - assert.not_nil(str:find("loadstring")) + local str = core.serialize(test_in) + assert.not_nil(str:find("loadstring")) - local test_out = core.deserialize(str, true) - assert.is_nil(test_out.func) - assert.equals(test_out.foo, "bar") + local test_out = core.deserialize(str, true) + assert.is_nil(test_out.func) + assert.equals(test_out.foo, "bar") + end) + end) + + describe("deprecation warnings", function() + before_each(function() + assert(not core.log) + core.log = spy.new(function(level) + assert(level == "deprecated") + end) + end) + after_each(function() + core.log = nil + end) + it("dumping functions", function() + local t = {f = function() end, g = function() end} + t.t = t + core.serialize(t) + assert.spy(core.log).was.called(1) -- should have been called exactly *once* + end) end) it("vectors work", function() diff --git a/builtin/common/vector.lua b/builtin/common/vector.lua index fb1d2a7d9..7a8558cbd 100644 --- a/builtin/common/vector.lua +++ b/builtin/common/vector.lua @@ -389,7 +389,7 @@ function vector.random_direction() local x, y, z, l2 repeat -- expected less than two attempts on average (volume sphere vs. cube) x, y, z = math.random() * 2 - 1, math.random() * 2 - 1, math.random() * 2 - 1 - l2 = x*x + y*y + z*z + l2 = x*x + y*y + z*z until l2 <= 1 and l2 >= 1e-6 -- normalize local l = math.sqrt(l2) diff --git a/builtin/emerge/env.lua b/builtin/emerge/env.lua index 5beb6285c..6d3fb0a3b 100644 --- a/builtin/emerge/env.lua +++ b/builtin/emerge/env.lua @@ -33,7 +33,7 @@ function core.get_node(pos) return core.vmanip:get_node_at(pos) end -function core.get_perlin(seed, octaves, persist, spread) +function core.get_value_noise(seed, octaves, persist, spread) local params if type(seed) == "table" then params = table.copy(seed) @@ -47,12 +47,18 @@ function core.get_perlin(seed, octaves, persist, spread) } end params.seed = core.get_seed(params.seed) -- add mapgen seed - return PerlinNoise(params) + return ValueNoise(params) end - -function core.get_perlin_map(params, size) +function core.get_value_noise_map(params, size) local params2 = table.copy(params) params2.seed = core.get_seed(params.seed) -- add mapgen seed - return PerlinNoiseMap(params2, size) + return ValueNoiseMap(params2, size) end + +-- deprecated as of 5.12, as it was not Perlin noise +-- but with no warnings (yet) for compatibility +core.get_perlin = core.get_value_noise +core.get_perlin_map = core.get_value_noise_map +PerlinNoise = ValueNoise +PerlinNoiseMap = ValueNoiseMap diff --git a/builtin/emerge/register.lua b/builtin/emerge/register.lua index 308fe4d7e..a07849c7b 100644 --- a/builtin/emerge/register.lua +++ b/builtin/emerge/register.lua @@ -9,8 +9,8 @@ do all.registered_craftitems = {} all.registered_tools = {} for k, v in pairs(all.registered_items) do - -- Disable further modification - setmetatable(v, {__newindex = {}}) + -- Ignore new keys + setmetatable(v, {__newindex = function() end}) -- Reassemble the other tables if v.type == "node" then getmetatable(v).__index = all.nodedef_default @@ -36,6 +36,9 @@ end local alias_metatable = { __index = function(t, name) return rawget(t, core.registered_aliases[name]) + end, + __newindex = function() + error("table is read-only") end } setmetatable(core.registered_items, alias_metatable) diff --git a/builtin/fstk/buttonbar.lua b/builtin/fstk/buttonbar.lua index ed478af82..461babc1f 100644 --- a/builtin/fstk/buttonbar.lua +++ b/builtin/fstk/buttonbar.lua @@ -1,20 +1,7 @@ ---Luanti ---Copyright (C) 2014 sapier ---Copyright (C) 2023 Gregor Parzefall --- ---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. +-- Luanti +-- Copyright (C) 2014 sapier +-- Copyright (C) 2023 Gregor Parzefall +-- SPDX-License-Identifier: LGPL-2.1-or-later local BASE_SPACING = 0.1 @@ -38,7 +25,7 @@ local function buttonbar_formspec(self) -- `BASE_SPACING` is used as the minimum spacing, like `gap` in CSS Flexbox. -- The number of buttons per page is always calculated as if the scroll - -- buttons were visible. + -- buttons were visible. local avail_space = self.size.x - 2*BASE_SPACING - 2*get_scroll_btn_width() local btns_per_page = math.floor((avail_space - BASE_SPACING) / (btn_size + BASE_SPACING)) diff --git a/builtin/fstk/dialog.lua b/builtin/fstk/dialog.lua index 7043646a4..f0b7cf14b 100644 --- a/builtin/fstk/dialog.lua +++ b/builtin/fstk/dialog.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2014 sapier --- ---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. +-- Luanti +-- Copyright (C) 2014 sapier +-- SPDX-License-Identifier: LGPL-2.1-or-later local function dialog_event_handler(self,event) if self.user_eventhandler == nil or diff --git a/builtin/fstk/tabview.lua b/builtin/fstk/tabview.lua index c88c4917a..13a96abff 100644 --- a/builtin/fstk/tabview.lua +++ b/builtin/fstk/tabview.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2014 sapier --- ---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. +-- Luanti +-- Copyright (C) 2014 sapier +-- SPDX-License-Identifier: LGPL-2.1-or-later -------------------------------------------------------------------------------- diff --git a/builtin/fstk/ui.lua b/builtin/fstk/ui.lua index b5b95d0e4..44d2ab3d8 100644 --- a/builtin/fstk/ui.lua +++ b/builtin/fstk/ui.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2014 sapier --- ---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. +-- Luanti +-- Copyright (C) 2014 sapier +-- SPDX-License-Identifier: LGPL-2.1-or-later ui = {} ui.childlist = {} @@ -179,6 +166,10 @@ end -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- core.button_handler = function(fields) + if fields["try_quit"] and not fields["key_enter"] then + core.event_handler("MenuQuit") + return + end if fields["btn_reconnect_yes"] then gamedata.reconnect_requested = false gamedata.errormessage = nil diff --git a/builtin/game/chat.lua b/builtin/game/chat.lua index 69c657194..80c9a6e81 100644 --- a/builtin/game/chat.lua +++ b/builtin/game/chat.lua @@ -60,6 +60,8 @@ core.register_on_chat_message(function(name, message) param = param or "" + core.log("verbose", string.format("Handling chat command %q with params %q", cmd, param)) + -- Run core.registered_on_chatcommands callbacks. if core.run_callbacks(core.registered_on_chatcommands, 5, name, cmd, param) then return true @@ -1275,7 +1277,7 @@ core.register_chatcommand("msg", { core.log("action", "DM from " .. name .. " to " .. sendto .. ": " .. message) core.chat_send_player(sendto, S("DM from @1: @2", name, message)) - return true, S("Message sent.") + return true, S("DM sent to @1: @2", sendto, message) end, }) diff --git a/builtin/game/deprecated.lua b/builtin/game/deprecated.lua index 87b785995..48e825ea2 100644 --- a/builtin/game/deprecated.lua +++ b/builtin/game/deprecated.lua @@ -61,3 +61,10 @@ function core.register_on_auth_fail(func) end end) end + +-- deprecated as of 5.12, as it was not Perlin noise +-- but with no warnings (yet) for compatibility +core.get_perlin = core.get_value_noise +core.get_perlin_map = core.get_value_noise_map +PerlinNoise = ValueNoise +PerlinNoiseMap = ValueNoiseMap diff --git a/builtin/game/falling.lua b/builtin/game/falling.lua index bb6ca1a64..6a4ed96d5 100644 --- a/builtin/game/falling.lua +++ b/builtin/game/falling.lua @@ -1,5 +1,4 @@ local builtin_shared = ... -local SCALE = 0.667 local facedir_to_euler = { {y = 0, x = 0, z = 0}, @@ -36,9 +35,7 @@ local gravity = tonumber(core.settings:get("movement_gravity")) or 9.81 core.register_entity(":__builtin:falling_node", { initial_properties = { - visual = "item", - visual_size = vector.new(SCALE, SCALE, SCALE), - textures = {}, + visual = "node", physical = true, is_visible = false, collide_with_objects = true, @@ -78,43 +75,17 @@ core.register_entity(":__builtin:falling_node", { self.floats = core.get_item_group(node.name, "float") ~= 0 -- Save liquidtype for falling water - self.liquidtype = def.liquidtype + self.liquidtype = def.liquidtype - -- Set entity visuals - if def.drawtype == "torchlike" or def.drawtype == "signlike" then - local textures - if def.tiles and def.tiles[1] then - local tile = def.tiles[1] - if type(tile) == "table" then - tile = tile.name - end - if def.drawtype == "torchlike" then - textures = { "("..tile..")^[transformFX", tile } - else - textures = { tile, "("..tile..")^[transformFX" } - end - end - local vsize - if def.visual_scale then - local s = def.visual_scale - vsize = vector.new(s, s, s) - end - self.object:set_properties({ - is_visible = true, - visual = "upright_sprite", - visual_size = vsize, - textures = textures, - glow = def.light_source, - }) - elseif def.drawtype ~= "airlike" then - local itemstring = node.name - if core.is_colored_paramtype(def.paramtype2) then - itemstring = core.itemstring_with_palette(itemstring, node.param2) - end - -- FIXME: solution needed for paramtype2 == "leveled" + -- Set up entity visuals + -- For compatibility with older clients we continue to use "item" visual + -- for simple situations. + local drawtypes = {normal=true, glasslike=true, allfaces=true, nodebox=true} + local p2types = {none=true, facedir=true, ["4dir"]=true} + if drawtypes[def.drawtype] and p2types[def.paramtype2] and def.use_texture_alpha ~= "blend" then -- Calculate size of falling node - local s = {} - s.x = (def.visual_scale or 1) * SCALE + local s = vector.zero() + s.x = (def.visual_scale or 1) * 0.667 s.y = s.x s.z = s.x -- Compensate for wield_scale @@ -125,10 +96,31 @@ core.register_entity(":__builtin:falling_node", { end self.object:set_properties({ is_visible = true, - wield_item = itemstring, + visual = "item", + wield_item = node.name, visual_size = s, glow = def.light_source, }) + -- Rotate as needed + if def.paramtype2 == "facedir" then + local fdir = node.param2 % 32 % 24 + local euler = facedir_to_euler[fdir + 1] + if euler then + self.object:set_rotation(euler) + end + elseif def.paramtype2 == "4dir" then + local fdir = node.param2 % 4 + local euler = facedir_to_euler[fdir + 1] + if euler then + self.object:set_rotation(euler) + end + end + elseif def.drawtype ~= "airlike" then + self.object:set_properties({ + is_visible = true, + node = node, + glow = def.light_source, + }) end -- Set collision box (certain nodeboxes only for now) @@ -148,111 +140,6 @@ core.register_entity(":__builtin:falling_node", { }) end end - - -- Rotate entity - if def.drawtype == "torchlike" then - if (def.paramtype2 == "wallmounted" or def.paramtype2 == "colorwallmounted") - and node.param2 % 8 == 7 then - self.object:set_yaw(-math.pi*0.25) - else - self.object:set_yaw(math.pi*0.25) - end - elseif ((node.param2 ~= 0 or def.drawtype == "nodebox" or def.drawtype == "mesh") - and (def.wield_image == "" or def.wield_image == nil)) - or def.drawtype == "signlike" - or def.drawtype == "mesh" - or def.drawtype == "normal" - or def.drawtype == "nodebox" then - if (def.paramtype2 == "facedir" or def.paramtype2 == "colorfacedir") then - local fdir = node.param2 % 32 % 24 - -- Get rotation from a precalculated lookup table - local euler = facedir_to_euler[fdir + 1] - if euler then - self.object:set_rotation(euler) - end - elseif (def.paramtype2 == "4dir" or def.paramtype2 == "color4dir") then - local fdir = node.param2 % 4 - -- Get rotation from a precalculated lookup table - local euler = facedir_to_euler[fdir + 1] - if euler then - self.object:set_rotation(euler) - end - elseif (def.drawtype ~= "plantlike" and def.drawtype ~= "plantlike_rooted" and - (def.paramtype2 == "wallmounted" or def.paramtype2 == "colorwallmounted" or def.drawtype == "signlike")) then - local rot = node.param2 % 8 - if (def.drawtype == "signlike" and def.paramtype2 ~= "wallmounted" and def.paramtype2 ~= "colorwallmounted") then - -- Change rotation to "floor" by default for non-wallmounted paramtype2 - rot = 1 - end - local pitch, yaw, roll = 0, 0, 0 - if def.drawtype == "nodebox" or def.drawtype == "mesh" then - if rot == 0 then - pitch, yaw = math.pi/2, 0 - elseif rot == 1 then - pitch, yaw = -math.pi/2, math.pi - elseif rot == 2 then - pitch, yaw = 0, math.pi/2 - elseif rot == 3 then - pitch, yaw = 0, -math.pi/2 - elseif rot == 4 then - pitch, yaw = 0, math.pi - elseif rot == 6 then - pitch, yaw = math.pi/2, 0 - elseif rot == 7 then - pitch, yaw = -math.pi/2, math.pi - end - else - if rot == 1 then - pitch, yaw = math.pi, math.pi - elseif rot == 2 then - pitch, yaw = math.pi/2, math.pi/2 - elseif rot == 3 then - pitch, yaw = math.pi/2, -math.pi/2 - elseif rot == 4 then - pitch, yaw = math.pi/2, math.pi - elseif rot == 5 then - pitch, yaw = math.pi/2, 0 - elseif rot == 6 then - pitch, yaw = math.pi, -math.pi/2 - elseif rot == 7 then - pitch, yaw = 0, -math.pi/2 - end - end - if def.drawtype == "signlike" then - pitch = pitch - math.pi/2 - if rot == 0 then - yaw = yaw + math.pi/2 - elseif rot == 1 then - yaw = yaw - math.pi/2 - elseif rot == 6 then - yaw = yaw - math.pi/2 - pitch = pitch + math.pi - elseif rot == 7 then - yaw = yaw + math.pi/2 - pitch = pitch + math.pi - end - elseif def.drawtype == "mesh" or def.drawtype == "normal" or def.drawtype == "nodebox" then - if rot == 0 or rot == 1 then - roll = roll + math.pi - elseif rot == 6 or rot == 7 then - if def.drawtype ~= "normal" then - roll = roll - math.pi/2 - end - else - yaw = yaw + math.pi - end - end - self.object:set_rotation({x=pitch, y=yaw, z=roll}) - elseif (def.drawtype == "mesh" and def.paramtype2 == "degrotate") then - local p2 = (node.param2 - (def.place_param2 or 0)) % 240 - local yaw = (p2 / 240) * (math.pi * 2) - self.object:set_yaw(yaw) - elseif (def.drawtype == "mesh" and def.paramtype2 == "colordegrotate") then - local p2 = (node.param2 % 32 - (def.place_param2 or 0) % 32) % 24 - local yaw = (p2 / 24) * (math.pi * 2) - self.object:set_yaw(yaw) - end - end end, get_staticdata = function(self) @@ -308,23 +195,26 @@ core.register_entity(":__builtin:falling_node", { core.remove_node(bcp) else + -- We are placing on top so check what's there np.y = np.y + 1 - end - -- Check what's here - local n2 = core.get_node(np) - local nd = core.registered_nodes[n2.name] - -- If it's not air or liquid, remove node and replace it with - -- it's drops - if n2.name ~= "air" and (not nd or nd.liquidtype ~= "source") then - if nd and nd.buildable_to == false then + local n2 = core.get_node(np) + local nd = core.registered_nodes[n2.name] + if not nd or nd.buildable_to then + core.remove_node(np) + else + -- 'walkable' is used to mean "falling nodes can't replace this" + -- here. Normally we would collide with the walkable node itself + -- and place our node on top (so `n2.name == "air"`), but we + -- re-check this in case we ended up inside a node. + if not nd.diggable or nd.walkable then + return false + end nd.on_dig(np, n2, nil) -- If it's still there, it might be protected if core.get_node(np).name == n2.name then return false end - else - core.remove_node(np) end end diff --git a/builtin/game/features.lua b/builtin/game/features.lua index ea80a09fb..1b329c7a3 100644 --- a/builtin/game/features.lua +++ b/builtin/game/features.lua @@ -45,6 +45,8 @@ core.features = { abm_without_neighbors = true, biome_weights = true, particle_blend_clip = true, + remove_item_match_meta = true, + httpfetch_additional_methods = true, } function core.has_feature(arg) diff --git a/builtin/game/item.lua b/builtin/game/item.lua index cc9be44af..4d68f1136 100644 --- a/builtin/game/item.lua +++ b/builtin/game/item.lua @@ -360,13 +360,12 @@ end function core.item_drop(itemstack, dropper, pos) local dropper_is_player = dropper and dropper:is_player() local p = table.copy(pos) - local cnt = itemstack:get_count() if dropper_is_player then p.y = p.y + 1.2 end - local item = itemstack:take_item(cnt) - local obj = core.add_item(p, item) + local obj = core.add_item(p, ItemStack(itemstack)) if obj then + itemstack:clear() if dropper_is_player then local dir = dropper:get_look_dir() dir.x = dir.x * 2.9 @@ -375,7 +374,7 @@ function core.item_drop(itemstack, dropper, pos) obj:set_velocity(dir) obj:get_luaentity().dropped_by = dropper:get_player_name() end - return itemstack + return itemstack, obj end -- If we reach this, adding the object to the -- environment failed @@ -514,7 +513,8 @@ function core.node_dig(pos, node, digger) .. node.name .. " at " .. core.pos_to_string(pos)) local wielded = digger and digger:get_wielded_item() - local drops = core.get_node_drops(node, wielded and wielded:get_name()) + local drops = core.get_node_drops(node, wielded and wielded:get_name(), + wielded and ItemStack(wielded), digger, vector.copy(pos)) if wielded then local wdef = wielded:get_definition() diff --git a/builtin/game/misc_s.lua b/builtin/game/misc_s.lua index 07ab09b37..9433f74bb 100644 --- a/builtin/game/misc_s.lua +++ b/builtin/game/misc_s.lua @@ -36,7 +36,7 @@ end function core.setting_get_pos(name) - return core.settings:get_pos(name) + return core.settings:get_pos(name) end @@ -112,3 +112,26 @@ if core.set_push_moveresult1 then end) core.set_push_moveresult1 = nil end + +-- Protocol version table +-- see also src/network/networkprotocol.cpp +core.protocol_versions = { + ["5.0.0"] = 37, + ["5.1.0"] = 38, + ["5.2.0"] = 39, + ["5.3.0"] = 39, + ["5.4.0"] = 39, + ["5.5.0"] = 40, + ["5.6.0"] = 41, + ["5.7.0"] = 42, + ["5.8.0"] = 43, + ["5.9.0"] = 44, + ["5.9.1"] = 45, + ["5.10.0"] = 46, + ["5.11.0"] = 47, + ["5.12.0"] = 48, +} + +setmetatable(core.protocol_versions, {__newindex = function() + error("core.protocol_versions is read-only") +end}) diff --git a/builtin/game/register.lua b/builtin/game/register.lua index 3e4b9be96..d6ada6920 100644 --- a/builtin/game/register.lua +++ b/builtin/game/register.lua @@ -61,7 +61,7 @@ local function check_modname_prefix(name) return name:sub(2) else -- Enforce that the name starts with the correct mod name. - local expected_prefix = core.get_current_modname() .. ":" + local expected_prefix = (core.get_current_modname() or "") .. ":" if name:sub(1, #expected_prefix) ~= expected_prefix then error("Name " .. name .. " does not follow naming conventions: " .. "\"" .. expected_prefix .. "\" or \":\" prefix required") @@ -95,6 +95,7 @@ function core.register_abm(spec) check_node_list(spec.nodenames, "nodenames") check_node_list(spec.neighbors, "neighbors") assert(type(spec.action) == "function", "Required field 'action' of type function") + core.registered_abms[#core.registered_abms + 1] = spec spec.mod_origin = core.get_current_modname() or "??" end @@ -128,127 +129,51 @@ function core.register_entity(name, prototype) prototype.mod_origin = core.get_current_modname() or "??" end -function core.register_item(name, itemdef) - -- Check name - if name == nil then - error("Unable to register item: Name is nil") +local function preprocess_node(nodedef) + -- Use the nodebox as selection box if it's not set manually + if nodedef.drawtype == "nodebox" and not nodedef.selection_box then + nodedef.selection_box = nodedef.node_box + elseif nodedef.drawtype == "fencelike" and not nodedef.selection_box then + nodedef.selection_box = { + type = "fixed", + fixed = {-1/8, -1/2, -1/8, 1/8, 1/2, 1/8}, + } end - name = check_modname_prefix(tostring(name)) - if forbidden_item_names[name] then - error("Unable to register item: Name is forbidden: " .. name) - end - itemdef.name = name - -- Apply defaults and add to registered_* table - if itemdef.type == "node" then - -- Use the nodebox as selection box if it's not set manually - if itemdef.drawtype == "nodebox" and not itemdef.selection_box then - itemdef.selection_box = itemdef.node_box - elseif itemdef.drawtype == "fencelike" and not itemdef.selection_box then - itemdef.selection_box = { - type = "fixed", - fixed = {-1/8, -1/2, -1/8, 1/8, 1/2, 1/8}, - } - end - if itemdef.light_source and itemdef.light_source > core.LIGHT_MAX then - itemdef.light_source = core.LIGHT_MAX - core.log("warning", "Node 'light_source' value exceeds maximum," .. - " limiting to maximum: " ..name) - end - setmetatable(itemdef, {__index = core.nodedef_default}) - core.registered_nodes[itemdef.name] = itemdef - elseif itemdef.type == "craft" then - setmetatable(itemdef, {__index = core.craftitemdef_default}) - core.registered_craftitems[itemdef.name] = itemdef - elseif itemdef.type == "tool" then - setmetatable(itemdef, {__index = core.tooldef_default}) - core.registered_tools[itemdef.name] = itemdef - elseif itemdef.type == "none" then - setmetatable(itemdef, {__index = core.noneitemdef_default}) - else - error("Unable to register item: Type is invalid: " .. dump(itemdef)) + if nodedef.light_source and nodedef.light_source > core.LIGHT_MAX then + nodedef.light_source = core.LIGHT_MAX + core.log("warning", "Node 'light_source' value exceeds maximum," .. + " limiting it: " .. nodedef.name) end -- Flowing liquid uses param2 - if itemdef.type == "node" and itemdef.liquidtype == "flowing" then - itemdef.paramtype2 = "flowingliquid" + if nodedef.liquidtype == "flowing" then + nodedef.paramtype2 = "flowingliquid" end +end +local function preprocess_craft(itemdef) -- BEGIN Legacy stuff - if itemdef.cookresult_itemstring ~= nil and itemdef.cookresult_itemstring ~= "" then - core.register_craft({ - type="cooking", - output=itemdef.cookresult_itemstring, - recipe=itemdef.name, - cooktime=itemdef.furnace_cooktime - }) - end - if itemdef.furnace_burntime ~= nil and itemdef.furnace_burntime >= 0 then - core.register_craft({ - type="fuel", - recipe=itemdef.name, - burntime=itemdef.furnace_burntime - }) + if itemdef.inventory_image == nil and itemdef.image ~= nil then + core.log("deprecated", "The `image` field in craftitem definitions " .. + "is deprecated. Use `inventory_image` instead. " .. + "Craftitem name: " .. itemdef.name, 3) + itemdef.inventory_image = itemdef.image end -- END Legacy stuff - - itemdef.mod_origin = core.get_current_modname() or "??" - - -- Disable all further modifications - getmetatable(itemdef).__newindex = {} - - --core.log("Registering item: " .. itemdef.name) - core.registered_items[itemdef.name] = itemdef - core.registered_aliases[itemdef.name] = nil - register_item_raw(itemdef) end -function core.unregister_item(name) - if not core.registered_items[name] then - core.log("warning", "Not unregistering item " ..name.. - " because it doesn't exist.") - return - end - -- Erase from registered_* table - local type = core.registered_items[name].type - if type == "node" then - core.registered_nodes[name] = nil - elseif type == "craft" then - core.registered_craftitems[name] = nil - elseif type == "tool" then - core.registered_tools[name] = nil - end - core.registered_items[name] = nil - - - unregister_item_raw(name) -end - -function core.register_node(name, nodedef) - nodedef.type = "node" - core.register_item(name, nodedef) -end - -function core.register_craftitem(name, craftitemdef) - craftitemdef.type = "craft" - - -- BEGIN Legacy stuff - if craftitemdef.inventory_image == nil and craftitemdef.image ~= nil then - craftitemdef.inventory_image = craftitemdef.image - end - -- END Legacy stuff - - core.register_item(name, craftitemdef) -end - -function core.register_tool(name, tooldef) - tooldef.type = "tool" +local function preprocess_tool(tooldef) tooldef.stack_max = 1 -- BEGIN Legacy stuff if tooldef.inventory_image == nil and tooldef.image ~= nil then + core.log("deprecated", "The `image` field in tool definitions " .. + "is deprecated. Use `inventory_image` instead. " .. + "Tool name: " .. tooldef.name, 3) tooldef.inventory_image = tooldef.image end + if tooldef.tool_capabilities == nil and (tooldef.full_punch_interval ~= nil or tooldef.basetime ~= nil or @@ -261,6 +186,9 @@ function core.register_tool(name, tooldef) tooldef.dd_crackiness ~= nil or tooldef.dd_crumbliness ~= nil or tooldef.dd_cuttability ~= nil) then + core.log("deprecated", "Specifying tool capabilities directly in the tool " .. + "definition is deprecated. Use the `tool_capabilities` field instead. " .. + "Tool name: " .. tooldef.name, 3) tooldef.tool_capabilities = { full_punch_interval = tooldef.full_punch_interval, basetime = tooldef.basetime, @@ -277,7 +205,7 @@ function core.register_tool(name, tooldef) end -- END Legacy stuff - -- This isn't just legacy, but more of a convenience feature + -- Automatically set punch_attack_uses as a convenience feature local toolcaps = tooldef.tool_capabilities if toolcaps and toolcaps.punch_attack_uses == nil then for _, cap in pairs(toolcaps.groupcaps or {}) do @@ -288,8 +216,126 @@ function core.register_tool(name, tooldef) end end end +end - core.register_item(name, tooldef) +local default_tables = { + node = core.nodedef_default, + craft = core.craftitemdef_default, + tool = core.tooldef_default, + none = core.noneitemdef_default, +} + +local preprocess_fns = { + node = preprocess_node, + craft = preprocess_craft, + tool = preprocess_tool, +} + +function core.register_item(name, itemdef) + -- Check name + if name == nil then + error("Unable to register item: Name is nil") + end + name = check_modname_prefix(tostring(name)) + if forbidden_item_names[name] then + error("Unable to register item: Name is forbidden: " .. name) + end + + itemdef.name = name + + -- Compatibility stuff depending on type + local fn = preprocess_fns[itemdef.type] + if fn then + fn(itemdef) + end + + -- Apply defaults + local defaults = default_tables[itemdef.type] + if defaults == nil then + error("Unable to register item: Type is invalid: " .. dump(itemdef)) + end + local old_mt = getmetatable(itemdef) + -- TODO most of these checks should become an error after a while (maybe in 2026?) + if old_mt ~= nil and next(old_mt) ~= nil then + -- Note that even registering multiple identical items with the same table + -- is not allowed, due to the 'name' property. + if old_mt.__index == defaults then + core.log("warning", "Item definition table was reused between registrations. ".. + "This is unsupported and broken: " .. name) + else + core.log("warning", "Item definition has a metatable, this is ".. + "unsupported and it will be overwritten: " .. name) + end + end + setmetatable(itemdef, {__index = defaults}) + + -- BEGIN Legacy stuff + if itemdef.cookresult_itemstring ~= nil and itemdef.cookresult_itemstring ~= "" then + core.log("deprecated", "The `cookresult_itemstring` item definition " .. + "field is deprecated. Use `core.register_craft` instead. " .. + "Item name: " .. itemdef.name, 2) + core.register_craft({ + type="cooking", + output=itemdef.cookresult_itemstring, + recipe=itemdef.name, + cooktime=itemdef.furnace_cooktime + }) + end + if itemdef.furnace_burntime ~= nil and itemdef.furnace_burntime >= 0 then + core.log("deprecated", "The `furnace_burntime` item definition " .. + "field is deprecated. Use `core.register_craft` instead. " .. + "Item name: " .. itemdef.name, 2) + core.register_craft({ + type="fuel", + recipe=itemdef.name, + burntime=itemdef.furnace_burntime + }) + end + -- END Legacy stuff + + itemdef.mod_origin = core.get_current_modname() or "??" + + -- Ignore new keys as a failsafe to prevent mistakes + getmetatable(itemdef).__newindex = function() end + + -- Add to registered_* tables + if itemdef.type == "node" then + core.registered_nodes[itemdef.name] = itemdef + elseif itemdef.type == "craft" then + core.registered_craftitems[itemdef.name] = itemdef + elseif itemdef.type == "tool" then + core.registered_tools[itemdef.name] = itemdef + end + core.registered_items[itemdef.name] = itemdef + core.registered_aliases[itemdef.name] = nil + + register_item_raw(itemdef) +end + +local function make_register_item_wrapper(the_type) + return function(name, itemdef) + itemdef.type = the_type + return core.register_item(name, itemdef) + end +end + +core.register_node = make_register_item_wrapper("node") +core.register_craftitem = make_register_item_wrapper("craft") +core.register_tool = make_register_item_wrapper("tool") + +function core.unregister_item(name) + if not core.registered_items[name] then + core.log("warning", "Not unregistering item " ..name.. + " because it doesn't exist.") + return + end + -- Erase from registered_* table + core.registered_nodes[name] = nil + core.registered_craftitems[name] = nil + core.registered_tools[name] = nil + core.registered_items[name] = nil + + unregister_item_raw(name) end function core.register_alias(name, convert_to) @@ -300,7 +346,6 @@ function core.register_alias(name, convert_to) core.log("warning", "Not registering alias, item with same name" .. " is already defined: " .. name .. " -> " .. convert_to) else - --core.log("Registering alias: " .. name .. " -> " .. convert_to) core.registered_aliases[name] = convert_to register_alias_raw(name, convert_to) end @@ -315,7 +360,6 @@ function core.register_alias_force(name, convert_to) core.log("info", "Removed item " ..name.. " while attempting to force add an alias") end - --core.log("Registering alias: " .. name .. " -> " .. convert_to) core.registered_aliases[name] = convert_to register_alias_raw(name, convert_to) end @@ -406,6 +450,7 @@ core.register_item(":", { groups = {not_in_creative_inventory=1}, }) +local itemdefs_finalized = false function core.override_item(name, redefinition, del_fields) if redefinition.name ~= nil then @@ -418,10 +463,16 @@ function core.override_item(name, redefinition, del_fields) if not item then error("Attempt to override non-existent item "..name, 2) end + if itemdefs_finalized then + -- TODO: it's not clear if this needs to be allowed at all? + core.log("warning", "Overriding item " .. name .. " after server startup. " .. + "This is unsupported and can cause problems related to data inconsistency.") + end for k, v in pairs(redefinition) do rawset(item, k, v) end for _, field in ipairs(del_fields or {}) do + assert(field ~= "name" and field ~= "type") rawset(item, field, nil) end register_item_raw(item) @@ -568,13 +619,57 @@ core.registered_on_rightclickplayers, core.register_on_rightclickplayer = make_r core.registered_on_liquid_transformed, core.register_on_liquid_transformed = make_registration() core.registered_on_mapblocks_changed, core.register_on_mapblocks_changed = make_registration() +-- A bunch of registrations are read by the C++ side once on env init, so we cannot +-- allow them to change afterwards (see s_env.cpp). +-- Nodes and items do not have this problem but there are obvious consistency +-- problems if this would be allowed. + +local function freeze_table(t) + -- Freezing a Lua table is not actually possible without some very intrusive + -- metatable hackery, but we can trivially prevent new additions. + local mt = table.copy(getmetatable(t) or {}) + mt.__newindex = function() + error("modification forbidden") + end + setmetatable(t, mt) +end + +local function generic_reg_error(what) + return function(something) + local described = what + if type(something) == "table" and type(something.name) == "string" then + described = what .. " " .. something.name + elseif type(something) == "string" then + described = what .. " " .. something + end + error("Tried to register " .. described .. " after load time!") + end +end + core.register_on_mods_loaded(function() core.after(0, function() - setmetatable(core.registered_on_mapblocks_changed, { - __newindex = function() - error("on_mapblocks_changed callbacks must be registered at load time") - end, - }) + itemdefs_finalized = true + + -- prevent direct modification + freeze_table(core.registered_abms) + freeze_table(core.registered_lbms) + freeze_table(core.registered_items) + freeze_table(core.registered_nodes) + freeze_table(core.registered_craftitems) + freeze_table(core.registered_tools) + freeze_table(core.registered_aliases) + freeze_table(core.registered_on_mapblocks_changed) + + -- neutralize registration functions + core.register_abm = generic_reg_error("ABM") + core.register_lbm = generic_reg_error("LBM") + core.register_item = generic_reg_error("item") + core.unregister_item = function(name) + error("Refusing to unregister item " .. name .. " after load time") + end + core.register_alias = generic_reg_error("alias") + core.register_alias_force = generic_reg_error("alias") + core.register_on_mapblocks_changed = generic_reg_error("on_mapblocks_changed callback") end) end) diff --git a/builtin/init.lua b/builtin/init.lua index 83e8a1df0..59d1558fc 100644 --- a/builtin/init.lua +++ b/builtin/init.lua @@ -78,6 +78,8 @@ elseif INIT == "client" then dofile(scriptdir .. "client" .. DIR_DELIM .. "init.lua") elseif INIT == "emerge" then dofile(scriptdir .. "emerge" .. DIR_DELIM .. "init.lua") +elseif INIT == "pause_menu" then + dofile(scriptdir .. "pause_menu" .. DIR_DELIM .. "init.lua") else error(("Unrecognized builtin initialization type %s!"):format(tostring(INIT))) end diff --git a/builtin/mainmenu/common.lua b/builtin/mainmenu/common.lua index d9edd745b..240e0f083 100644 --- a/builtin/mainmenu/common.lua +++ b/builtin/mainmenu/common.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2014 sapier --- ---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. +-- Luanti +-- Copyright (C) 2014 sapier +-- SPDX-License-Identifier: LGPL-2.1-or-later -- Global menu data menudata = {} @@ -34,7 +21,6 @@ function check_cache_age(key, max_age) end function core.on_before_close() - -- called before the menu is closed, either exit or to join a game cache_settings:write() end diff --git a/builtin/mainmenu/content/contentdb.lua b/builtin/mainmenu/content/contentdb.lua index 963400a12..fbd94376d 100644 --- a/builtin/mainmenu/content/contentdb.lua +++ b/builtin/mainmenu/content/contentdb.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2018-24 rubenwardy --- ---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. +-- Luanti +-- Copyright (C) 2018-24 rubenwardy +-- SPDX-License-Identifier: LGPL-2.1-or-later if not core.get_http_api then return @@ -41,6 +28,7 @@ contentdb = { REASON_DEPENDENCY = "dependency", } +-- API documentation: https://content.luanti.org/help/api/ local function get_download_url(package, reason) local base_url = core.settings:get("contentdb_url") @@ -182,14 +170,16 @@ function contentdb.get_package_by_id(id) end -function contentdb.calculate_package_id(type, author, name) - local id = author:lower() .. "/" +local function strip_game_suffix(type, name) if (type == nil or type == "game") and #name > 5 and name:sub(#name - 4) == "_game" then - id = id .. name:sub(1, #name - 5) + return name:sub(1, #name - 5) else - id = id .. name + return name end - return id +end + +function contentdb.calculate_package_id(type, author, name) + return author:lower() .. "/" .. strip_game_suffix(type, name) end @@ -398,7 +388,6 @@ local function fetch_pkgs() local url = base_url .. "/api/packages/?type=mod&type=game&type=txp&protocol_version=" .. core.get_max_supp_proto() .. "&engine_version=" .. core.urlencode(version.string) - for _, item in pairs(core.settings:get("contentdb_flag_blacklist"):split(",")) do item = item:trim() if item ~= "" then @@ -406,19 +395,11 @@ local function fetch_pkgs() end end - local languages - local current_language = core.get_language() - if current_language ~= "" then - languages = { current_language, "en;q=0.8" } - else - languages = { "en" } - end - local http = core.get_http_api() local response = http.fetch_sync({ url = url, extra_headers = { - "Accept-Language: " .. table.concat(languages, ", ") + core.get_http_accept_languages() }, }) if not response.succeeded then @@ -448,7 +429,7 @@ function contentdb.set_packages_from_api(packages) -- We currently don't support name changing local suffix = "/" .. package.name if alias:sub(-#suffix) == suffix then - contentdb.aliases[alias:lower()] = package.id + contentdb.aliases[strip_game_suffix(packages.type, alias:lower())] = package.id end end end @@ -596,57 +577,54 @@ function contentdb.filter_packages(query, by_type) end -function contentdb.get_full_package_info(package, callback) - assert(package) - if package.full_info then - callback(package.full_info) - return - end - - local function fetch(params) - local version = core.get_version() - local base_url = core.settings:get("contentdb_url") - - local languages - local current_language = core.get_language() - if current_language ~= "" then - languages = { current_language, "en;q=0.8" } - else - languages = { "en" } +local function get_package_info(key, path) + return function(package, callback) + assert(package) + if package[key] then + callback(package[key]) + return end - local url = base_url .. - "/api/packages/" .. params.package.url_part .. "/for-client/?" .. - "protocol_version=" .. core.urlencode(core.get_max_supp_proto()) .. - "&engine_version=" .. core.urlencode(version.string) .. - "&formspec_version=" .. core.urlencode(core.get_formspec_version()) .. - "&include_images=false" - local http = core.get_http_api() - local response = http.fetch_sync({ - url = url, - extra_headers = { - "Accept-Language: " .. table.concat(languages, ", ") - }, - }) - if not response.succeeded then - return nil + local function fetch(params) + local version = core.get_version() + local base_url = core.settings:get("contentdb_url") + local url = base_url .. + "/api/packages/" .. params.package.url_part .. params.path .. "?" .. + "protocol_version=" .. core.urlencode(core.get_max_supp_proto()) .. + "&engine_version=" .. core.urlencode(version.string) .. + "&formspec_version=" .. core.urlencode(core.get_formspec_version()) .. + "&include_images=false" + local http = core.get_http_api() + local response = http.fetch_sync({ + url = url, + extra_headers = { + core.get_http_accept_languages() + }, + }) + if not response.succeeded then + return nil + end + + return core.parse_json(response.data) end - return core.parse_json(response.data) - end + local function my_callback(value) + package[key] = value + callback(value) + end - local function my_callback(value) - package.full_info = value - callback(value) - end - - if not core.handle_async(fetch, { package = package }, my_callback) then - core.log("error", "ERROR: async event failed") - callback(nil) + if not core.handle_async(fetch, { package = package, path = path }, my_callback) then + core.log("error", "ERROR: async event failed") + callback(nil) + end end end +contentdb.get_full_package_info = get_package_info("full_info", "/for-client/") +contentdb.get_package_reviews = get_package_info("reviews", "/for-client/reviews/") + + function contentdb.get_formspec_padding() -- Padding is increased on Android to account for notches -- TODO: use Android API to determine size of cut outs diff --git a/builtin/mainmenu/content/dlg_contentdb.lua b/builtin/mainmenu/content/dlg_contentdb.lua index 3c3f7987d..7f389135b 100644 --- a/builtin/mainmenu/content/dlg_contentdb.lua +++ b/builtin/mainmenu/content/dlg_contentdb.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2018-20 rubenwardy --- ---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. +-- Luanti +-- Copyright (C) 2018-20 rubenwardy +-- SPDX-License-Identifier: LGPL-2.1-or-later if not core.get_http_api then function create_contentdb_dlg() @@ -323,9 +310,17 @@ local function get_formspec(dlgdata) }) local img_w = cell_h * 3 / 2 + -- Use as much of the available space as possible (so no padding on the + -- right/bottom), but don't quite allow the text to touch the border. + local text_w = cell_w - img_w - 0.25 - 0.025 + local text_h = cell_h - 0.25 - 0.025 + local start_idx = (cur_page - 1) * num_per_page + 1 for i=start_idx, math.min(#contentdb.packages, start_idx+num_per_page-1) do local package = contentdb.packages[i] + local text = core.colorize(mt_color_green, package.title) .. + core.colorize("#BFBFBF", " by " .. package.author) .. "\n" .. + package.short_description table.insert_all(formspec, { "container[", @@ -340,13 +335,14 @@ local function get_formspec(dlgdata) "image[0,0;", img_w, ",", cell_h, ";", core.formspec_escape(get_screenshot(package, package.thumbnail, 2)), "]", - "label[", img_w + 0.25 + 0.05, ",0.5;", - core.formspec_escape( - core.colorize(mt_color_green, package.title) .. - core.colorize("#BFBFBF", " by " .. package.author)), "]", + "label[", img_w + 0.25, ",0.25;", text_w, ",", text_h, ";", + core.formspec_escape(text), "]", - "textarea[", img_w + 0.25, ",0.75;", cell_w - img_w - 0.25, ",", cell_h - 0.75, ";;;", - core.formspec_escape(package.short_description), "]", + -- Add a tooltip in case the label overflows and the short description is cut off. + "tooltip[", img_w + 0.25, ",0.25;", text_w, ",", text_h, ";", + -- Text in tooltips doesn't wrap automatically, so we do it manually to + -- avoid everything being one long line. + core.formspec_escape(core.wrap_text(package.short_description, 80)), "]", "style[view_", i, ";border=false]", "style[view_", i, ":hovered;bgimg=", core.formspec_escape(defaulttexturedir .. "button_hover_semitrans.png"), "]", @@ -362,7 +358,7 @@ local function get_formspec(dlgdata) end table.insert_all(formspec, { - "container[", cell_w - 0.625,",", 0.25, "]", + "container[", cell_w - 0.625,",", 0.125, "]", }) if package.downloading then diff --git a/builtin/mainmenu/content/dlg_install.lua b/builtin/mainmenu/content/dlg_install.lua index ef201f56a..3d9e45760 100644 --- a/builtin/mainmenu/content/dlg_install.lua +++ b/builtin/mainmenu/content/dlg_install.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2018-24 rubenwardy --- ---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. +-- Luanti +-- Copyright (C) 2018-24 rubenwardy +-- SPDX-License-Identifier: LGPL-2.1-or-later local function is_still_visible(dlg) local this = ui.find_by_name("install_dialog") diff --git a/builtin/mainmenu/content/dlg_overwrite.lua b/builtin/mainmenu/content/dlg_overwrite.lua index 5baaa5cd2..08d49bce3 100644 --- a/builtin/mainmenu/content/dlg_overwrite.lua +++ b/builtin/mainmenu/content/dlg_overwrite.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2018-24 rubenwardy --- ---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. +-- Luanti +-- Copyright (C) 2018-24 rubenwardy +-- SPDX-License-Identifier: LGPL-2.1-or-later function get_formspec(data) local package = data.package diff --git a/builtin/mainmenu/content/dlg_package.lua b/builtin/mainmenu/content/dlg_package.lua index 7edbf678f..500fb3f6c 100644 --- a/builtin/mainmenu/content/dlg_package.lua +++ b/builtin/mainmenu/content/dlg_package.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2018-24 rubenwardy --- ---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. +-- Luanti +-- Copyright (C) 2018-24 rubenwardy +-- SPDX-License-Identifier: LGPL-2.1-or-later local function get_info_formspec(size, padding, text) @@ -32,6 +19,7 @@ end local function get_formspec(data) + local package = data.package local window_padding = contentdb.get_formspec_padding() local size = contentdb.get_formspec_size() size.x = math.min(size.x, 20) @@ -42,7 +30,7 @@ local function get_formspec(data) if not data.loading and not data.loading_error then data.loading = true - contentdb.get_full_package_info(data.package, function(info) + contentdb.get_full_package_info(package, function(info) data.loading = false if info == nil then @@ -61,7 +49,7 @@ local function get_formspec(data) -- check to see if that happened if not data.info then if data.loading_error then - return get_info_formspec(size, window_padding, fgettext("No packages could be retrieved")) + return get_info_formspec(size, window_padding, fgettext("Error loading package information")) end return get_info_formspec(size, window_padding, fgettext("Loading...")) end @@ -103,15 +91,15 @@ local function get_formspec(data) local left_button_rect = "0,0;2.875,1" local right_button_rect = "3.125,0;2.875,1" - if data.package.downloading then + if package.downloading then formspec[#formspec + 1] = "animated_image[5,0;1,1;downloading;" formspec[#formspec + 1] = core.formspec_escape(defaulttexturedir) formspec[#formspec + 1] = "cdb_downloading.png;3;400;]" - elseif data.package.queued then + elseif package.queued then formspec[#formspec + 1] = "style[queued;border=false]" formspec[#formspec + 1] = "image_button[5,0;1,1;" .. core.formspec_escape(defaulttexturedir) formspec[#formspec + 1] = "cdb_queued.png;queued;]" - elseif not data.package.path then + elseif not package.path then formspec[#formspec + 1] = "style[install;bgcolor=green]" formspec[#formspec + 1] = "button[" formspec[#formspec + 1] = right_button_rect @@ -119,7 +107,7 @@ local function get_formspec(data) formspec[#formspec + 1] = fgettext("Install [$1]", info.download_size) formspec[#formspec + 1] = "]" else - if data.package.installed_release < data.package.release then + if package.installed_release < package.release then -- The install_ action also handles updating formspec[#formspec + 1] = "style[install;bgcolor=#28ccdf]" formspec[#formspec + 1] = "button[" @@ -137,10 +125,12 @@ local function get_formspec(data) formspec[#formspec + 1] = "]" end + local review_count = info.reviews.positive + info.reviews.neutral + info.reviews.negative local current_tab = data.current_tab or 1 local tab_titles = { fgettext("Description"), fgettext("Information"), + fgettext("Reviews") .. core.formspec_escape(" [" .. review_count .. "]"), } local tab_body_height = bottom_buttons_y - 2.8 @@ -162,8 +152,8 @@ local function get_formspec(data) local winfo = core.get_window_info() local fs_to_px = winfo.size.x / winfo.max_formspec_size.x for i, ss in ipairs(info.screenshots) do - local path = get_screenshot(data.package, ss.url, 2) - hypertext = hypertext .. "" if i ~= #info.screenshots then @@ -194,22 +184,54 @@ local function get_formspec(data) hypertext = hypertext .. "\n\n" .. info.long_description.body + -- Fix the path to blank.png. This is needed for bullet indentation. hypertext = hypertext:gsub("", + "") + hypertext = hypertext:gsub("", + "") + hypertext = hypertext:gsub("", + "") + table.insert_all(formspec, { + "hypertext[0,0;", W, ",", tab_body_height - 0.375, + ";reviews;", core.formspec_escape(hypertext), "]", + }) + elseif data.reviews_error then + table.insert_all(formspec, {"label[2,2;", fgettext("Error loading reviews"), "]"} ) + else + table.insert_all(formspec, {"label[2,2;", fgettext("Loading..."), "]"} ) + end else error("Unknown tab " .. current_tab) end @@ -269,9 +291,10 @@ local function handle_submit(this, fields) end if fields.open_contentdb then - local url = ("%s/packages/%s/?protocol_version=%d"):format( - core.settings:get("contentdb_url"), package.url_part, - core.get_max_supp_proto()) + local version = core.get_version() + local url = core.settings:get("contentdb_url") .. "/packages/" .. package.url_part .. + "/?protocol_version=" .. core.urlencode(core.get_max_supp_proto()) .. + "&engine_version=" .. core.urlencode(version.string) core.open_url(url) return true end @@ -295,7 +318,8 @@ local function handle_submit(this, fields) end if handle_hypertext_event(this, fields.desc, info.long_description) or - handle_hypertext_event(this, fields.info, info.info_hypertext) then + handle_hypertext_event(this, fields.info, info.info_hypertext) or + (package.reviews and handle_hypertext_event(this, fields.reviews, package.reviews)) then return true end end diff --git a/builtin/mainmenu/content/init.lua b/builtin/mainmenu/content/init.lua index dbf4cc888..e35ca1734 100644 --- a/builtin/mainmenu/content/init.lua +++ b/builtin/mainmenu/content/init.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2023 rubenwardy --- ---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. +-- Luanti +-- Copyright (C) 2023 rubenwardy +-- SPDX-License-Identifier: LGPL-2.1-or-later local path = core.get_mainmenu_path() .. DIR_DELIM .. "content" diff --git a/builtin/mainmenu/content/pkgmgr.lua b/builtin/mainmenu/content/pkgmgr.lua index 986d80398..2863e2a5f 100644 --- a/builtin/mainmenu/content/pkgmgr.lua +++ b/builtin/mainmenu/content/pkgmgr.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2013 sapier --- ---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. +-- Luanti +-- Copyright (C) 2013 sapier +-- SPDX-License-Identifier: LGPL-2.1-or-later -------------------------------------------------------------------------------- local function get_last_folder(text,count) diff --git a/builtin/mainmenu/content/screenshots.lua b/builtin/mainmenu/content/screenshots.lua index 718666085..e53f03ed0 100644 --- a/builtin/mainmenu/content/screenshots.lua +++ b/builtin/mainmenu/content/screenshots.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2023-24 rubenwardy --- ---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. +-- Luanti +-- Copyright (C) 2023-24 rubenwardy +-- SPDX-License-Identifier: LGPL-2.1-or-later -- Screenshot diff --git a/builtin/mainmenu/content/tests/pkgmgr_spec.lua b/builtin/mainmenu/content/tests/pkgmgr_spec.lua index 8870bb68f..5532764d9 100644 --- a/builtin/mainmenu/content/tests/pkgmgr_spec.lua +++ b/builtin/mainmenu/content/tests/pkgmgr_spec.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2022 rubenwardy --- ---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. +-- Luanti +-- Copyright (C) 2022 rubenwardy +-- SPDX-License-Identifier: LGPL-2.1-or-later local mods_dir = "/tmp/.minetest/mods" local games_dir = "/tmp/.minetest/games" diff --git a/builtin/mainmenu/content/update_detector.lua b/builtin/mainmenu/content/update_detector.lua index 4d0c1196b..1479328f7 100644 --- a/builtin/mainmenu/content/update_detector.lua +++ b/builtin/mainmenu/content/update_detector.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2023 rubenwardy --- ---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. +-- Luanti +-- Copyright (C) 2023 rubenwardy +-- SPDX-License-Identifier: LGPL-2.1-or-later update_detector = {} diff --git a/builtin/mainmenu/credits.json b/builtin/mainmenu/credits.json index 8a946893c..61e53d3ba 100644 --- a/builtin/mainmenu/credits.json +++ b/builtin/mainmenu/credits.json @@ -1,5 +1,5 @@ { - "#": "https://github.com/orgs/minetest/teams/engine/members", + "#": "https://github.com/orgs/luanti-org/teams/engine/members", "core_developers": [ "Perttu Ahola (celeron55) [Project founder]", "sfan5 ", @@ -15,7 +15,8 @@ "Gregor Parzefall (grorp)", "Lars Müller (luatic)", "cx384", - "sfence" + "sfence", + "y5nw" ], "previous_core_developers": [ "BlockMen", @@ -38,7 +39,7 @@ "Hugues Ross ", "Dmitry Kostenko (x2048) " ], - "#": "Currently only https://github.com/orgs/minetest/teams/triagers/members", + "#": "Currently only https://github.com/orgs/luanti-org/teams/triagers/members", "core_team": [ "Zughy [Issue triager]", "wsor [Issue triager]", @@ -46,25 +47,20 @@ ], "#": "For updating active/previous contributors, see the script in ./util/gather_git_credits.py", "contributors": [ - "JosiahWI", - "1F616EMO", - "y5nw", "Erich Schubert", - "numzero", - "red-001 ", - "David Heidelberg", - "Wuzzy", - "paradust7", - "HybridDog", - "Zemtzov7", - "kromka-chleba", - "AFCMS", - "chmodsayshello", - "OgelGames" + "wrrrzr", + "siliconsniffer", + "JosiahWI", + "veprogames", + "Miguel P.L", + "AFCMS" ], "previous_contributors": [ "Ælla Chiana Moskopp (erle) [Logo]", + "numzero", + "red-001 ", "Giuseppe Bilotta", + "HybridDog", "ClobberXD", "Dániel Juhász (juhdanad) ", "MirceaKitsune ", @@ -77,6 +73,7 @@ "stujones11", "Rogier ", "Gregory Currie (gregorycu)", + "paradust7", "JacobF", "Jeija " ] diff --git a/builtin/mainmenu/dlg_clients_list.lua b/builtin/mainmenu/dlg_clients_list.lua index 2ea021c5e..0298ac152 100644 --- a/builtin/mainmenu/dlg_clients_list.lua +++ b/builtin/mainmenu/dlg_clients_list.lua @@ -21,7 +21,7 @@ local function clients_list_formspec(dialogdata) "size[6,9.5]", TOUCH_GUI and "padding[0.01,0.01]" or "", "hypertext[0,0;6,1.5;;", - fgettext("This is the list of clients connected to\n$1", + fgettext("Players connected to\n$1", "" .. core.hypertext_escape(servername) .. "") .. "]", "textlist[0.5,1.5;5,6.8;;" .. fmt_formspec_list(clients_list) .. "]", "button[1.5,8.5;3,0.8;quit;OK]" @@ -32,7 +32,7 @@ end local function clients_list_buttonhandler(this, fields) if fields.quit then - this:delete() + this:delete() return true end return false diff --git a/builtin/mainmenu/dlg_config_world.lua b/builtin/mainmenu/dlg_config_world.lua index cb5cdd635..36cc5580c 100644 --- a/builtin/mainmenu/dlg_config_world.lua +++ b/builtin/mainmenu/dlg_config_world.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2013 sapier --- ---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. +-- Luanti +-- Copyright (C) 2013 sapier +-- SPDX-License-Identifier: LGPL-2.1-or-later -------------------------------------------------------------------------------- @@ -299,7 +286,7 @@ local function handle_buttons(this, fields) 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") + worldfile:remove("load_mod_" .. mod.name) end elseif mod.enabled then gamedata.errormessage = fgettext_ne("Failed to enable mo" .. diff --git a/builtin/mainmenu/dlg_create_world.lua b/builtin/mainmenu/dlg_create_world.lua index 4844c85fe..27fe68050 100644 --- a/builtin/mainmenu/dlg_create_world.lua +++ b/builtin/mainmenu/dlg_create_world.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2014 sapier --- ---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. +-- Luanti +-- Copyright (C) 2014 sapier +-- SPDX-License-Identifier: LGPL-2.1-or-later local function table_to_flags(ftable) -- Convert e.g. { jungles = true, caves = false } to "jungles,nocaves" diff --git a/builtin/mainmenu/dlg_delete_content.lua b/builtin/mainmenu/dlg_delete_content.lua index a36bcb2d7..799050d0b 100644 --- a/builtin/mainmenu/dlg_delete_content.lua +++ b/builtin/mainmenu/dlg_delete_content.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2014 sapier --- ---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. +-- Luanti +-- Copyright (C) 2014 sapier +-- SPDX-License-Identifier: LGPL-2.1-or-later -------------------------------------------------------------------------------- diff --git a/builtin/mainmenu/dlg_delete_world.lua b/builtin/mainmenu/dlg_delete_world.lua index 531b4927d..25e9dd0fc 100644 --- a/builtin/mainmenu/dlg_delete_world.lua +++ b/builtin/mainmenu/dlg_delete_world.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2014 sapier --- ---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. +-- Luanti +-- Copyright (C) 2014 sapier +-- SPDX-License-Identifier: LGPL-2.1-or-later local function delete_world_formspec(dialogdata) diff --git a/builtin/mainmenu/dlg_rebind_keys.lua b/builtin/mainmenu/dlg_rebind_keys.lua new file mode 100644 index 000000000..d1b442004 --- /dev/null +++ b/builtin/mainmenu/dlg_rebind_keys.lua @@ -0,0 +1,108 @@ +-- Luanti +-- SPDX-License-Identifier: LGPL-2.1-or-later +-- Modified based on dlg_reinstall_mtg.lua +-- Note that this is only needed for migrating from <5.11 to 5.12. + +local doc_url = "https://docs.luanti.org/for-players/controls/" +local SETTING_NAME = "no_keycode_migration_warning" + +local function get_formspec(dialogdata) + local markup = table.concat({ + "" .. hgettext("Keybindings changed") .. "", + hgettext("The input handling system was reworked in Luanti 5.12.0."), + hgettext("As a result, your keybindings may have been changed."), + hgettext("Check out the key settings or refer to the documentation:"), + (""):format(doc_url), + }, "\n") + + return table.concat({ + "formspec_version[6]", + "size[12,7]", + "hypertext[0.5,0.5;11,4.7;text;", core.formspec_escape(markup), "]", + "container[0.5,5.7]", + "button[0,0;4,0.8;dismiss;", fgettext("Close"), "]", + "button[4.5,0;6.5,0.8;reconfigure;", fgettext("Open settings"), "]", + "container_end[]", + }) +end + +local function close_dialog(this) + cache_settings:set_bool(SETTING_NAME, true) + this:delete() +end + +local function buttonhandler(this, fields) + if fields.reconfigure then + close_dialog(this) + + local maintab = ui.find_by_name("maintab") + + local dlg = create_settings_dlg("controls_keyboard_and_mouse") + dlg:set_parent(maintab) + maintab:hide() + dlg:show() + + return true + end + + if fields.dismiss then + close_dialog(this) + return true + end + + if fields.text == "action:doc_url" then + core.open_url(doc_url) + end +end + +local function eventhandler(event) + if event == "DialogShow" then + mm_game_theme.set_engine() + return true + elseif event == "MenuQuit" then + -- Don't allow closing the dialog with ESC, but still allow exiting + -- Luanti + core.close() + return true + end + return false +end + +local function create_rebind_keys_dlg() + local dlg = dialog_create("dlg_rebind_keys", get_formspec, + buttonhandler, eventhandler) + return dlg +end + +function migrate_keybindings() + -- Show migration dialog if the user upgraded from an earlier version + -- and this has not yet been shown before, *or* if keys settings had to be changed + if core.is_first_run then + cache_settings:set_bool(SETTING_NAME, true) + end + local has_migration = not cache_settings:get_bool(SETTING_NAME) + + -- normalize all existing key settings, this converts them from KEY_KEY_C to SYSTEM_SCANCODE_6 + local settings = core.settings:to_table() + for name, value in pairs(settings) do + if name:match("^keymap_") then + local normalized = core.normalize_keycode(value) + if value ~= normalized then + has_migration = true + core.settings:set(name, normalized) + end + end + end + + if not has_migration then + return + end + + local maintab = ui.find_by_name("maintab") + + local dlg = create_rebind_keys_dlg() + dlg:set_parent(maintab) + maintab:hide() + dlg:show() + ui.update() +end diff --git a/builtin/mainmenu/dlg_register.lua b/builtin/mainmenu/dlg_register.lua index 88a449ae3..5047eda3c 100644 --- a/builtin/mainmenu/dlg_register.lua +++ b/builtin/mainmenu/dlg_register.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2022 rubenwardy --- ---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. +-- Luanti +-- Copyright (C) 2022 rubenwardy +-- SPDX-License-Identifier: LGPL-2.1-or-later -------------------------------------------------------------------------------- diff --git a/builtin/mainmenu/dlg_reinstall_mtg.lua b/builtin/mainmenu/dlg_reinstall_mtg.lua index 85bdedf7b..c167b2656 100644 --- a/builtin/mainmenu/dlg_reinstall_mtg.lua +++ b/builtin/mainmenu/dlg_reinstall_mtg.lua @@ -1,25 +1,12 @@ ---Luanti ---Copyright (C) 2023 Gregor Parzefall --- ---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. +-- Luanti +-- Copyright (C) 2023 Gregor Parzefall +-- SPDX-License-Identifier: LGPL-2.1-or-later ---- IMPORTANT ---- -- This whole file can be removed after a while. -- It was only directly useful for upgrades from 5.7.0 to 5.8.0, but -- maybe some odd fellow directly upgrades from 5.6.1 to 5.9.0 in the future... --- see in case it's not obvious +-- see in case it's not obvious ---- ---- local SETTING_NAME = "no_mtg_notification" @@ -67,10 +54,10 @@ end local function get_formspec(dialogdata) local markup = table.concat({ - "", fgettext("Minetest Game is no longer installed by default"), "\n", - fgettext("For a long time, Luanti shipped with a default game called \"Minetest Game\". " .. + "", hgettext("Minetest Game is no longer installed by default"), "\n", + hgettext("For a long time, Luanti shipped with a default game called \"Minetest Game\". " .. "Since version 5.8.0, Luanti ships without a default game."), "\n", - fgettext("If you want to continue playing in your Minetest Game worlds, you need to reinstall Minetest Game."), + hgettext("If you want to continue playing in your Minetest Game worlds, you need to reinstall Minetest Game."), }) return table.concat({ diff --git a/builtin/mainmenu/dlg_rename_modpack.lua b/builtin/mainmenu/dlg_rename_modpack.lua index 09df92d25..830c3cef3 100644 --- a/builtin/mainmenu/dlg_rename_modpack.lua +++ b/builtin/mainmenu/dlg_rename_modpack.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2014 sapier --- ---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. +-- Luanti +-- Copyright (C) 2014 sapier +-- SPDX-License-Identifier: LGPL-2.1-or-later -------------------------------------------------------------------------------- diff --git a/builtin/mainmenu/dlg_server_list_mods.lua b/builtin/mainmenu/dlg_server_list_mods.lua new file mode 100644 index 000000000..fc392ae06 --- /dev/null +++ b/builtin/mainmenu/dlg_server_list_mods.lua @@ -0,0 +1,105 @@ +-- Luanti +-- Copyright (C) 2024 cx384 +-- SPDX-License-Identifier: LGPL-2.1-or-later + +local function get_formspec(dialogdata) + local TOUCH_GUI = core.settings:get_bool("touch_gui") + local server = dialogdata.server + local group_by_prefix = dialogdata.group_by_prefix + local expand_all = dialogdata.expand_all + + -- A wrongly behaving server may send ill formed mod names + table.sort(server.mods) + + local cells = {} + if group_by_prefix then + local function get_prefix(mod) + return mod:match("[^_]*") + end + local count = {} + for _, mod in ipairs(server.mods) do + local prefix = get_prefix(mod) + count[prefix] = (count[prefix] or 0) + 1 + end + local last_prefix + local function add_row(depth, mod) + table.insert(cells, ("%d"):format(depth)) + table.insert(cells, mod) + end + for i, mod in ipairs(server.mods) do + local prefix = get_prefix(mod) + if last_prefix == prefix then + add_row(1, mod) + elseif count[prefix] > 1 then + add_row(0, prefix) + add_row(1, mod) + else + add_row(0, mod) + end + last_prefix = prefix + end + else + cells = table.copy(server.mods) + end + + for i, cell in ipairs(cells) do + cells[i] = core.formspec_escape(cell) + end + cells = table.concat(cells, ",") + + local heading + if server.gameid then + heading = fgettext("The $1 server uses a game called $2 and the following mods:", + "" .. core.hypertext_escape(server.name) .. "", + "") + else + heading = fgettext("The $1 server uses the following mods:", + "" .. core.hypertext_escape(server.name) .. "") + end + + local formspec = { + "formspec_version[8]", + "size[8,9.5]", + TOUCH_GUI and "padding[0.01,0.01]" or "", + "hypertext[0,0;8,1.5;;", heading, "]", + "tablecolumns[", group_by_prefix and + (expand_all and "indent;text" or "tree;text") or "text", "]", + "table[0.5,1.5;7,6.8;mods;", cells, "]", + "checkbox[0.5,8.7;group_by_prefix;", fgettext("Group by prefix"), ";", + group_by_prefix and "true" or "false", "]", + group_by_prefix and ("checkbox[0.5,9.15;expand_all;" .. fgettext("Expand all") .. ";" .. + (expand_all and "true" or "false") .. "]") or "", + "button[5.5,8.5;2,0.8;quit;OK]" + } + return table.concat(formspec, "") +end + +local function buttonhandler(this, fields) + if fields.quit then + this:delete() + return true + end + + if fields.group_by_prefix then + this.data.group_by_prefix = core.is_yes(fields.group_by_prefix) + return true + end + + if fields.expand_all then + this.data.expand_all = core.is_yes(fields.expand_all) + return true + end + + return false +end + +function create_server_list_mods_dialog(server) + local retval = dialog_create("dlg_server_list_mods", + get_formspec, + buttonhandler, + nil) + retval.data.group_by_prefix = false + retval.data.expand_all = false + retval.data.server = server + return retval +end diff --git a/builtin/mainmenu/game_theme.lua b/builtin/mainmenu/game_theme.lua index 7c6408157..729110025 100644 --- a/builtin/mainmenu/game_theme.lua +++ b/builtin/mainmenu/game_theme.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2013 sapier --- ---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. +-- Luanti +-- Copyright (C) 2013 sapier +-- SPDX-License-Identifier: LGPL-2.1-or-later mm_game_theme = {} diff --git a/builtin/mainmenu/init.lua b/builtin/mainmenu/init.lua index 4e1c201cd..14185a484 100644 --- a/builtin/mainmenu/init.lua +++ b/builtin/mainmenu/init.lua @@ -1,27 +1,6 @@ ---Luanti ---Copyright (C) 2014 sapier --- ---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. - -mt_color_grey = "#AAAAAA" -mt_color_blue = "#6389FF" -mt_color_lightblue = "#99CCFF" -mt_color_green = "#72FF63" -mt_color_dark_green = "#25C191" -mt_color_orange = "#FF8800" -mt_color_red = "#FF3300" +-- Luanti +-- Copyright (C) 2014 sapier +-- SPDX-License-Identifier: LGPL-2.1-or-later MAIN_TAB_W = 15.5 MAIN_TAB_H = 7.1 @@ -35,6 +14,7 @@ local basepath = core.get_builtin_path() defaulttexturedir = core.get_texturepath_share() .. DIR_DELIM .. "base" .. DIR_DELIM .. "pack" .. DIR_DELIM +dofile(basepath .. "common" .. DIR_DELIM .. "menu.lua") dofile(basepath .. "common" .. DIR_DELIM .. "filterlist.lua") dofile(basepath .. "fstk" .. DIR_DELIM .. "buttonbar.lua") dofile(basepath .. "fstk" .. DIR_DELIM .. "dialog.lua") @@ -47,7 +27,7 @@ dofile(menupath .. DIR_DELIM .. "game_theme.lua") dofile(menupath .. DIR_DELIM .. "content" .. DIR_DELIM .. "init.lua") dofile(menupath .. DIR_DELIM .. "dlg_config_world.lua") -dofile(menupath .. DIR_DELIM .. "settings" .. DIR_DELIM .. "init.lua") +dofile(basepath .. "common" .. DIR_DELIM .. "settings" .. DIR_DELIM .. "init.lua") dofile(menupath .. DIR_DELIM .. "dlg_create_world.lua") dofile(menupath .. DIR_DELIM .. "dlg_delete_content.lua") dofile(menupath .. DIR_DELIM .. "dlg_delete_world.lua") @@ -55,7 +35,9 @@ dofile(menupath .. DIR_DELIM .. "dlg_register.lua") dofile(menupath .. DIR_DELIM .. "dlg_rename_modpack.lua") dofile(menupath .. DIR_DELIM .. "dlg_version_info.lua") dofile(menupath .. DIR_DELIM .. "dlg_reinstall_mtg.lua") +dofile(menupath .. DIR_DELIM .. "dlg_rebind_keys.lua") dofile(menupath .. DIR_DELIM .. "dlg_clients_list.lua") +dofile(menupath .. DIR_DELIM .. "dlg_server_list_mods.lua") local tabs = { content = dofile(menupath .. DIR_DELIM .. "tab_content.lua"), @@ -131,6 +113,7 @@ local function init_globals() ui.update() check_reinstall_mtg() + migrate_keybindings() check_new_version() end diff --git a/builtin/mainmenu/serverlistmgr.lua b/builtin/mainmenu/serverlistmgr.lua index 35ae65758..3ac52b657 100644 --- a/builtin/mainmenu/serverlistmgr.lua +++ b/builtin/mainmenu/serverlistmgr.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2020 rubenwardy --- ---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. +-- Luanti +-- Copyright (C) 2020 rubenwardy +-- SPDX-License-Identifier: LGPL-2.1-or-later serverlistmgr = { -- continent code we detected for ourselves diff --git a/builtin/mainmenu/settings/init.lua b/builtin/mainmenu/settings/init.lua deleted file mode 100644 index c8615ba34..000000000 --- a/builtin/mainmenu/settings/init.lua +++ /dev/null @@ -1,28 +0,0 @@ ---Luanti ---Copyright (C) 2022 rubenwardy --- ---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. - -local path = core.get_mainmenu_path() .. DIR_DELIM .. "settings" - -dofile(path .. DIR_DELIM .. "settingtypes.lua") -dofile(path .. DIR_DELIM .. "dlg_change_mapgen_flags.lua") -dofile(path .. DIR_DELIM .. "dlg_settings.lua") - --- Uncomment to generate 'minetest.conf.example' and 'settings_translation_file.cpp'. --- For RUN_IN_PLACE the generated files may appear in the 'bin' folder. --- See comment and alternative line at the end of 'generate_from_settingtypes.lua'. - --- dofile(path .. DIR_DELIM .. "generate_from_settingtypes.lua") diff --git a/builtin/mainmenu/tab_about.lua b/builtin/mainmenu/tab_about.lua index 86c811457..5d2e606df 100644 --- a/builtin/mainmenu/tab_about.lua +++ b/builtin/mainmenu/tab_about.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2013 sapier --- ---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. +-- Luanti +-- Copyright (C) 2013 sapier +-- SPDX-License-Identifier: LGPL-2.1-or-later local function prepare_credits(dest, source) diff --git a/builtin/mainmenu/tab_content.lua b/builtin/mainmenu/tab_content.lua index cd384c905..8a6fc5b01 100644 --- a/builtin/mainmenu/tab_content.lua +++ b/builtin/mainmenu/tab_content.lua @@ -1,20 +1,7 @@ ---Luanti ---Copyright (C) 2014 sapier ---Copyright (C) 2018 rubenwardy --- ---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. +-- Luanti +-- Copyright (C) 2014 sapier +-- Copyright (C) 2018 rubenwardy +-- SPDX-License-Identifier: LGPL-2.1-or-later local function get_content_icons(packages_with_updates) diff --git a/builtin/mainmenu/tab_local.lua b/builtin/mainmenu/tab_local.lua index 083f9a50a..0d0b20ff1 100644 --- a/builtin/mainmenu/tab_local.lua +++ b/builtin/mainmenu/tab_local.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2014 sapier --- ---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. +-- Luanti +-- Copyright (C) 2014 sapier +-- SPDX-License-Identifier: LGPL-2.1-or-later local current_game, singleplayer_refresh_gamebar diff --git a/builtin/mainmenu/tab_online.lua b/builtin/mainmenu/tab_online.lua index 4d55b46c8..a17595401 100644 --- a/builtin/mainmenu/tab_online.lua +++ b/builtin/mainmenu/tab_online.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2014 sapier --- ---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. +-- Luanti +-- Copyright (C) 2014 sapier +-- SPDX-License-Identifier: LGPL-2.1-or-later local function get_sorted_servers() local servers = { @@ -129,6 +116,7 @@ local function get_formspec(tabview, name, tabdata) local retval = -- Search "field[0.25,0.25;7,0.75;te_search;;" .. core.formspec_escape(tabdata.search_for) .. "]" .. + "tooltip[te_search;" .. fgettext("Possible filters\ngame:\nmod:\nplayer:") .. "]" .. "field_enter_after_edit[te_search;true]" .. "container[7.25,0.25]" .. "image_button[0,0;0.75,0.75;" .. core.formspec_escape(defaulttexturedir .. "search.png") .. ";btn_mp_search;]" .. @@ -177,6 +165,26 @@ local function get_formspec(tabview, name, tabdata) core.formspec_escape(gamedata.serverdescription) .. "]" end + -- Mods button + local mods = selected_server.mods + if mods and #mods > 0 then + local tooltip = "" + if selected_server.gameid then + tooltip = fgettext("Game: $1", selected_server.gameid) .. "\n" + end + tooltip = tooltip .. fgettext("Number of mods: $1", #mods) + + retval = retval .. + "tooltip[btn_view_mods;" .. tooltip .. "]" .. + "style[btn_view_mods;padding=6]" .. + "image_button[4,1.3;0.5,0.5;" .. core.formspec_escape(defaulttexturedir .. + "server_view_mods.png") .. ";btn_view_mods;]" + else + retval = retval .. "image[4.1,1.4;0.3,0.3;" .. core.formspec_escape(defaulttexturedir .. + "server_view_mods_unavailable.png") .. "]" + end + + -- Clients list button local clients_list = selected_server.clients_list local can_view_clients_list = clients_list and #clients_list > 0 if can_view_clients_list then @@ -186,23 +194,31 @@ local function get_formspec(tabview, name, tabdata) local max_clients = 5 if #clients_list > max_clients then retval = retval .. "tooltip[btn_view_clients;" .. - fgettext("Clients:\n$1", table.concat(clients_list, "\n", 1, max_clients)) .. "\n..." .. "]" + fgettext("Players:\n$1", table.concat(clients_list, "\n", 1, max_clients)) .. "\n..." .. "]" else retval = retval .. "tooltip[btn_view_clients;" .. - fgettext("Clients:\n$1", table.concat(clients_list, "\n")) .. "]" + fgettext("Players:\n$1", table.concat(clients_list, "\n")) .. "]" end retval = retval .. "style[btn_view_clients;padding=6]" retval = retval .. "image_button[4.5,1.3;0.5,0.5;" .. core.formspec_escape(defaulttexturedir .. "server_view_clients.png") .. ";btn_view_clients;]" + else + retval = retval .. "image[4.6,1.4;0.3,0.3;" .. core.formspec_escape(defaulttexturedir .. + "server_view_clients_unavailable.png") .. "]" end + -- URL button if selected_server.url then retval = retval .. "tooltip[btn_server_url;" .. fgettext("Open server website") .. "]" retval = retval .. "style[btn_server_url;padding=6]" - retval = retval .. "image_button[" .. (can_view_clients_list and "4" or "4.5") .. ",1.3;0.5,0.5;" .. + retval = retval .. "image_button[3.5,1.3;0.5,0.5;" .. core.formspec_escape(defaulttexturedir .. "server_url.png") .. ";btn_server_url;]" + else + retval = retval .. "image[3.6,1.4;0.3,0.3;" .. core.formspec_escape(defaulttexturedir .. + "server_url_unavailable.png") .. "]" end + -- Favorites toggle button if is_selected_fav() then retval = retval .. "tooltip[btn_delete_favorite;" .. fgettext("Remove favorite") .. "]" retval = retval .. "style[btn_delete_favorite;padding=6]" @@ -289,19 +305,109 @@ end -------------------------------------------------------------------------------- -local function search_server_list(input) +local function parse_search_input(input) + if not input:find("%S") then + return -- Return nil if nothing to search for + end + + -- Search is not case sensitive + input = input:lower() + + local query = {keywords = {}, mods = {}, players = {}} + + -- Process quotation enclosed parts + input = input:gsub('(%S?)"([^"]*)"(%S?)', function(before, match, after) + if before == "" and after == "" then -- Also have be separated by spaces + table.insert(query.keywords, match) + return " " + end + return before..'"'..match..'"'..after + end) + + -- Separate by space characters and handle special prefixes + -- (words with special prefixes need an exact match and none of them can contain spaces) + for word in input:gmatch("%S+") do + local mod = word:match("^mod:(.*)") + table.insert(query.mods, mod) + local player = word:match("^player:(.*)") + table.insert(query.players, player) + local game = word:match("^game:(.*)") + query.game = query.game or game + if not (mod or player or game) then + table.insert(query.keywords, word) + end + end + + return query +end + +-- Prepares the server to be used for searching +local function uncapitalize_server(server) + local function table_lower(t) + local r = {} + for i, s in ipairs(t or {}) do + r[i] = s:lower() + end + return r + end + + return { + name = (server.name or ""):lower(), + description = (server.description or ""):lower(), + gameid = (server.gameid or ""):lower(), + mods = table_lower(server.mods), + clients_list = table_lower(server.clients_list), + } +end + +-- Returns false if the query does not match +-- otherwise returns a number to adjust the sorting priority +local function matches_query(server, query) + -- Search is not case sensitive + server = uncapitalize_server(server) + + -- Check if mods found + for _, mod in ipairs(query.mods) do + if table.indexof(server.mods, mod) < 0 then + return false + end + end + + -- Check if players found + for _, player in ipairs(query.players) do + if table.indexof(server.clients_list, player) < 0 then + return false + end + end + + -- Check if game matches + if query.game and query.game ~= server.gameid then + return false + end + + -- Check if keyword found + local name_matches = true + local description_matches = true + for _, keyword in ipairs(query.keywords) do + name_matches = name_matches and server.name:find(keyword, 1, true) + description_matches = description_matches and server.description:find(keyword, 1, true) + end + + return name_matches and 50 or description_matches and 0 +end + +local function search_server_list(input, tabdata) menudata.search_result = nil if #serverlistmgr.servers < 2 then return end - -- setup the keyword list - local keywords = {} - for word in input:gmatch("%S+") do - table.insert(keywords, word:lower()) - end - if #keywords == 0 then + tabdata.pre_search_selection = tabdata.pre_search_selection or find_selected_server() + + -- setup the search query + local query = parse_search_input(input) + if not query then return end @@ -310,16 +416,9 @@ local function search_server_list(input) -- Search the serverlist local search_result = {} for i, server in ipairs(serverlistmgr.servers) do - local name_matches, description_matches = true, true - for _, keyword in ipairs(keywords) do - name_matches = name_matches and not not - (server.name or ""):lower():find(keyword, 1, true) - description_matches = description_matches and not not - (server.description or ""):lower():find(keyword, 1, true) - end - if name_matches or description_matches then - server.points = #serverlistmgr.servers - i - + (name_matches and 50 or 0) + local match = matches_query(server, query) + if match then + server.points = #serverlistmgr.servers - i + match table.insert(search_result, server) end end @@ -328,10 +427,32 @@ local function search_server_list(input) return end + local current_server = find_selected_server() + table.sort(search_result, function(a, b) return a.points > b.points end) menudata.search_result = search_result + + -- Keep current selection if it's in search results + if current_server then + for _, server in ipairs(search_result) do + if server.address == current_server.address and + server.port == current_server.port then + return + end + end + end + + -- Find first compatible server (favorite or public) + for _, server in ipairs(search_result) do + if is_server_protocol_compat(server.proto_min, server.proto_max) then + set_selected_server(server) + return + end + end + -- If no compatible server found, clear selection + set_selected_server(nil) end local function main_button_handler(tabview, fields, name, tabdata) @@ -371,6 +492,7 @@ local function main_button_handler(tabview, fields, name, tabdata) end if event.type == "CHG" then set_selected_server(server) + tabdata.pre_search_selection = nil return true end end @@ -384,11 +506,9 @@ local function main_button_handler(tabview, fields, name, tabdata) if fields.btn_delete_favorite then local idx = core.get_table_index("servers") if not idx then return end - local server = tabdata.lookup[idx] - if not server then return end - serverlistmgr.delete_favorite(server) - set_selected_server(server) + serverlistmgr.delete_favorite(tabdata.lookup[idx]) + set_selected_server(tabdata.lookup[idx+1]) return true end @@ -405,20 +525,27 @@ local function main_button_handler(tabview, fields, name, tabdata) return true end + if fields.btn_view_mods then + local dlg = create_server_list_mods_dialog(find_selected_server()) + dlg:set_parent(tabview) + tabview:hide() + dlg:show() + return true + end + if fields.btn_mp_clear then tabdata.search_for = "" menudata.search_result = nil + if tabdata.pre_search_selection then + set_selected_server(tabdata.pre_search_selection) + tabdata.pre_search_selection = nil + end return true end if fields.btn_mp_search or fields.key_enter_field == "te_search" then tabdata.search_for = fields.te_search - search_server_list(fields.te_search:lower()) - if menudata.search_result then - -- Note: This clears the selection if there are no results - set_selected_server(menudata.search_result[1]) - end - + search_server_list(fields.te_search, tabdata) return true end diff --git a/builtin/pause_menu/init.lua b/builtin/pause_menu/init.lua new file mode 100644 index 000000000..01c5dc856 --- /dev/null +++ b/builtin/pause_menu/init.lua @@ -0,0 +1,13 @@ +local scriptpath = core.get_builtin_path() +local pausepath = scriptpath.."pause_menu"..DIR_DELIM +local commonpath = scriptpath.."common"..DIR_DELIM + +-- we're in-game, so no absolute paths are needed +defaulttexturedir = "" + +local builtin_shared = {} + +assert(loadfile(commonpath .. "register.lua"))(builtin_shared) +assert(loadfile(commonpath .. "menu.lua"))(builtin_shared) +assert(loadfile(pausepath .. "register.lua"))(builtin_shared) +dofile(commonpath .. "settings" .. DIR_DELIM .. "init.lua") diff --git a/builtin/pause_menu/register.lua b/builtin/pause_menu/register.lua new file mode 100644 index 000000000..ea97ca281 --- /dev/null +++ b/builtin/pause_menu/register.lua @@ -0,0 +1,5 @@ +local builtin_shared = ... + +local make_registration = builtin_shared.make_registration + +core.registered_on_formspec_input, core.register_on_formspec_input = make_registration() diff --git a/builtin/profiler/init.lua b/builtin/profiler/init.lua index f5b4b7c7e..ede9e0e7e 100644 --- a/builtin/profiler/init.lua +++ b/builtin/profiler/init.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2016 T4im --- ---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. +-- Luanti +-- Copyright (C) 2016 T4im +-- SPDX-License-Identifier: LGPL-2.1-or-later local S = core.get_translator("__builtin") diff --git a/builtin/profiler/instrumentation.lua b/builtin/profiler/instrumentation.lua index e012f07a0..613d3d692 100644 --- a/builtin/profiler/instrumentation.lua +++ b/builtin/profiler/instrumentation.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2016 T4im --- ---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. +-- Luanti +-- Copyright (C) 2016 T4im +-- SPDX-License-Identifier: LGPL-2.1-or-later local format, pairs, type = string.format, pairs, type local core, get_current_modname = core, core.get_current_modname diff --git a/builtin/profiler/reporter.lua b/builtin/profiler/reporter.lua index 7bc1b235d..b2bad7560 100644 --- a/builtin/profiler/reporter.lua +++ b/builtin/profiler/reporter.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2016 T4im --- ---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. +-- Luanti +-- Copyright (C) 2016 T4im +-- SPDX-License-Identifier: LGPL-2.1-or-later local S = core.get_translator("__builtin") -- Note: In this file, only messages are translated diff --git a/builtin/profiler/sampling.lua b/builtin/profiler/sampling.lua index 16d6c2012..5f1f5414c 100644 --- a/builtin/profiler/sampling.lua +++ b/builtin/profiler/sampling.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2016 T4im --- ---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. +-- Luanti +-- Copyright (C) 2016 T4im +-- SPDX-License-Identifier: LGPL-2.1-or-later local setmetatable = setmetatable local pairs, format = pairs, string.format local min, max, huge = math.min, math.max, math.huge diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 0a34d3ffb..b419f1247 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -2,9 +2,27 @@ # # General format: # name (Readable name) type type_args +# name (Readable name) [context] type type_args # # Note that the parts are separated by exactly one space # +# `context` (optional) is used to document where the setting is read. It can be: +# - common: Read by both client and server. +# - client: Read by the client. +# (Includes settings read by the mainmenu.) +# - server: Read by the server. +# - world_creation: Read at world creation, thus only applied to new worlds. +# (Worlds are commonly created in the mainmenu (part of the client), but +# world creation is conceptually a server-side thing...) +# If not specified, the value is inherited from the context value of the containing +# category instead. +# For the builtin/settingtypes.txt file, every setting needs to have a context defined, +# either via a category containing it or via the setting itself. In game/mod-provided +# settingtypes.txt files, context annotations are invalid. +# Note: For context annotations, it's irrelevant whether changes to a setting +# after startup/game-join will be read. A separate mechanism for declaring that +# is needed. +# # `type` can be: # - int # - string @@ -13,7 +31,7 @@ # - enum # - path # - filepath -# - key (will be ignored in GUI, since a special key change dialog exists) +# - key # - flags # - noise_params_2d # - noise_params_3d @@ -73,10 +91,14 @@ # * touchscreen / keyboard_mouse # * opengl / gles # * You can negate any requirement by prepending with ! +# * The "keyboard_mouse" requirement is automatically added to settings with the +# "key" type. # # Sections are marked by a single line in the format: [Section Name] # Sub-section are marked by adding * in front of the section name: [*Sub-section] # Sub-sub-sections have two * etc. +# A context (see above) can be specified optionally: [Section Name] [context] +# Context annotations on categories cannot be nested. # There shouldn't be too many settings per category. # # The top-level categories "Advanced", "Client and Server" and "Mapgen" are @@ -84,7 +106,7 @@ # They contain settings not intended for the "average user". -[Controls] +[Controls] [client] [*General] @@ -111,6 +133,13 @@ doubletap_jump (Double tap jump for fly) bool false # enabled. always_fly_fast (Always fly fast) bool true +# If enabled, the "Sneak" key will toggle when pressed. +# This functionality is ignored when fly is enabled. +toggle_sneak_key (Toggle Sneak key) bool false + +# If enabled, the "Aux1" key will toggle when pressed. +toggle_aux1_key (Toggle Aux1 key) bool false + # The time in seconds it takes between repeated node placements when holding # the place button. # @@ -151,6 +180,228 @@ enable_hotbar_mouse_wheel (Hotbar: Enable mouse wheel for selection) bool true # Requires: keyboard_mouse invert_hotbar_mouse_wheel (Hotbar: Invert mouse wheel direction) bool false +[**Keybindings] + +# Key for moving the player forward. +keymap_forward (Move forward) key SYSTEM_SCANCODE_26 + +# Key for moving the player backward. +# Will also disable autoforward, when active. +keymap_backward (Move backward) key SYSTEM_SCANCODE_22 + +# Key for moving the player left. +keymap_left (Move left) key SYSTEM_SCANCODE_4 + +# Key for moving the player right. +keymap_right (Move right) key SYSTEM_SCANCODE_7 + +# Key for jumping. +keymap_jump (Jump) key SYSTEM_SCANCODE_44 + +# Key for sneaking. +# Also used for climbing down and descending in water if aux1_descends is disabled. +keymap_sneak (Sneak) key SYSTEM_SCANCODE_225 + +# Key for digging, punching or using something. +# (Note: The actual meaning might vary on a per-game basis.) +keymap_dig (Dig/punch/use) key KEY_LBUTTON + +# Key for placing an item/block or for using something. +# (Note: The actual meaning might vary on a per-game basis.) +keymap_place (Place/use) key KEY_RBUTTON + +# Key for opening the inventory. +keymap_inventory (Open inventory) key SYSTEM_SCANCODE_12 + +# Key for moving fast in fast mode. +keymap_aux1 (Aux1) key SYSTEM_SCANCODE_8 + +# Key for opening the chat window. +keymap_chat (Open chat) key SYSTEM_SCANCODE_23 + +# Key for opening the chat window to type commands. +keymap_cmd (Command) key SYSTEM_SCANCODE_56 + +# Key for opening the chat window to type local commands. +keymap_cmd_local (Local command) key SYSTEM_SCANCODE_55 + +# Key for toggling unlimited view range. +keymap_rangeselect (Range select) key + +# Key for toggling flying. +keymap_freemove (Toggle fly) key SYSTEM_SCANCODE_14 + +# Key for toggling pitch move mode. +keymap_pitchmove (Toggle pitchmove) key + +# Key for toggling fast mode. +keymap_fastmove (Toggle fast) key SYSTEM_SCANCODE_13 + +# Key for toggling noclip mode. +keymap_noclip (Toggle noclip) key SYSTEM_SCANCODE_11 + +# Key for selecting the next item in the hotbar. +keymap_hotbar_next (Hotbar: select next item) key SYSTEM_SCANCODE_17 + +# Key for selecting the previous item in the hotbar. +keymap_hotbar_previous (Hotbar: select previous item) key SYSTEM_SCANCODE_5 + +# Key for muting the game. +keymap_mute (Mute) key SYSTEM_SCANCODE_16 + +# Key for increasing the volume. +keymap_increase_volume (Increase volume) key + +# Key for decreasing the volume. +keymap_decrease_volume (Decrease volume) key + +# Key for toggling autoforward. +keymap_autoforward (Toggle automatic forward) key + +# Key for toggling cinematic mode. +keymap_cinematic (Toggle cinematic mode) key + +# Key for toggling display of minimap. +keymap_minimap (Toggle minimap) key SYSTEM_SCANCODE_25 + +# Key for taking screenshots. +keymap_screenshot (Screenshot) key SYSTEM_SCANCODE_69 + +# Key for toggling fullscreen mode. +keymap_fullscreen (Toggle fullscreen) key SYSTEM_SCANCODE_68 + +# Key for dropping the currently selected item. +keymap_drop (Drop item) key SYSTEM_SCANCODE_20 + +# Key to use view zoom when possible. +keymap_zoom (Zoom) key SYSTEM_SCANCODE_29 + +# Key for toggling the display of the HUD. +keymap_toggle_hud (Toggle HUD) key SYSTEM_SCANCODE_58 + +# Key for toggling the display of chat. +keymap_toggle_chat (Toggle chat log) key SYSTEM_SCANCODE_59 + +# Key for toggling the display of the large chat console. +keymap_console (Large chat console) key SYSTEM_SCANCODE_67 + +# Key for toggling the display of fog. +keymap_toggle_fog (Toggle fog) key SYSTEM_SCANCODE_60 + +# Key for toggling the display of debug info. +keymap_toggle_debug (Toggle debug info) key SYSTEM_SCANCODE_62 + +# Key for toggling the display of the profiler. Used for development. +keymap_toggle_profiler (Toggle profiler) key SYSTEM_SCANCODE_63 + +# Key for toggling the display of mapblock boundaries. +keymap_toggle_block_bounds (Toggle block bounds) key + +# Key for switching between first- and third-person camera. +keymap_camera_mode (Toggle camera mode) key SYSTEM_SCANCODE_6 + +# Key for increasing the viewing range. +keymap_increase_viewing_range_min (Increase view range) key SYSTEM_SCANCODE_46 + +# Key for decreasing the viewing range. +keymap_decrease_viewing_range_min (Decrease view range) key SYSTEM_SCANCODE_45 + +# Key for selecting the first hotbar slot. +keymap_slot1 (Hotbar slot 1) key SYSTEM_SCANCODE_30 + +# Key for selecting the second hotbar slot. +keymap_slot2 (Hotbar slot 2) key SYSTEM_SCANCODE_31 + +# Key for selecting the third hotbar slot. +keymap_slot3 (Hotbar slot 3) key SYSTEM_SCANCODE_32 + +# Key for selecting the fourth hotbar slot. +keymap_slot4 (Hotbar slot 4) key SYSTEM_SCANCODE_33 + +# Key for selecting the fifth hotbar slot. +keymap_slot5 (Hotbar slot 5) key SYSTEM_SCANCODE_34 + +# Key for selecting the sixth hotbar slot. +keymap_slot6 (Hotbar slot 6) key SYSTEM_SCANCODE_35 + +# Key for selecting the seventh hotbar slot. +keymap_slot7 (Hotbar slot 7) key SYSTEM_SCANCODE_36 + +# Key for selecting the eighth hotbar slot. +keymap_slot8 (Hotbar slot 8) key SYSTEM_SCANCODE_37 + +# Key for selecting the ninth hotbar slot. +keymap_slot9 (Hotbar slot 9) key SYSTEM_SCANCODE_38 + +# Key for selecting the tenth hotbar slot. +keymap_slot10 (Hotbar slot 10) key SYSTEM_SCANCODE_39 + +# Key for selecting the 11th hotbar slot. +keymap_slot11 (Hotbar slot 11) key + +# Key for selecting the 12th hotbar slot. +keymap_slot12 (Hotbar slot 12) key + +# Key for selecting the 13th hotbar slot. +keymap_slot13 (Hotbar slot 13) key + +# Key for selecting the 14th hotbar slot. +keymap_slot14 (Hotbar slot 14) key + +# Key for selecting the 15th hotbar slot. +keymap_slot15 (Hotbar slot 15) key + +# Key for selecting the 16th hotbar slot. +keymap_slot16 (Hotbar slot 16) key + +# Key for selecting the 17th hotbar slot. +keymap_slot17 (Hotbar slot 17) key + +# Key for selecting the 18th hotbar slot. +keymap_slot18 (Hotbar slot 18) key + +# Key for selecting the 19th hotbar slot. +keymap_slot19 (Hotbar slot 19) key + +# Key for selecting the 20th hotbar slot. +keymap_slot20 (Hotbar slot 20) key + +# Key for selecting the 21st hotbar slot. +keymap_slot21 (Hotbar slot 21) key + +# Key for selecting the 22nd hotbar slot. +keymap_slot22 (Hotbar slot 22) key + +# Key for selecting the 23rd hotbar slot. +keymap_slot23 (Hotbar slot 23) key + +# Key for selecting the 24th hotbar slot. +keymap_slot24 (Hotbar slot 24) key + +# Key for selecting the 25th hotbar slot. +keymap_slot25 (Hotbar slot 25) key + +# Key for selecting the 26th hotbar slot. +keymap_slot26 (Hotbar slot 26) key + +# Key for selecting the 27th hotbar slot. +keymap_slot27 (Hotbar slot 27) key + +# Key for selecting the 28th hotbar slot. +keymap_slot28 (Hotbar slot 28) key + +# Key for selecting the 29th hotbar slot. +keymap_slot29 (Hotbar slot 29) key + +# Key for selecting the 30th hotbar slot. +keymap_slot30 (Hotbar slot 30) key + +# Key for selecting the 31st hotbar slot. +keymap_slot31 (Hotbar slot 31) key + +# Key for selecting the 32nd hotbar slot. +keymap_slot32 (Hotbar slot 32) key + [*Touchscreen] # Enables the touchscreen controls, allowing you to play the game with a touchscreen. @@ -160,6 +411,36 @@ invert_hotbar_mouse_wheel (Hotbar: Invert mouse wheel direction) bool false # Requires: touch_support touch_controls (Touchscreen controls) enum auto auto,true,false +# The kind of digging/placing controls used. +# +# * Tap +# Long/short tap anywhere on the screen to interact. +# Interaction happens at finger position. +# +# * Tap with crosshair +# Long/short tap anywhere on the screen to interact. +# Interaction happens at crosshair position. +# +# * Buttons with crosshair +# Use dedicated dig/place buttons to interact. +# Interaction happens at crosshair position. +# +# Requires: touchscreen +touch_interaction_style (Interaction style) enum tap tap,tap_crosshair,buttons_crosshair + +# The gesture for punching players/entities. +# This can be overridden by games and mods. +# +# * Short tap +# Easy to use and well-known from other games that shall not be named. +# +# * Long tap +# Known from the classic Luanti mobile controls. +# Combat is more or less impossible. +# +# Requires: touchscreen, touch_interaction_style_tap +touch_punch_gesture (Punch gesture) enum short_tap short_tap,long_tap + # Touchscreen sensitivity multiplier. # # Requires: touchscreen @@ -175,12 +456,6 @@ touchscreen_threshold (Movement threshold) int 20 0 100 # Requires: touchscreen touch_long_tap_delay (Threshold for long taps) int 400 100 1000 -# Use crosshair to select object instead of whole screen. -# If enabled, a crosshair will be shown and will be used for selecting object. -# -# Requires: touchscreen -touch_use_crosshair (Use crosshair for touch screen) bool false - # Fixes the position of virtual joystick. # If disabled, virtual joystick will center to first-touch's position. # @@ -193,21 +468,7 @@ fixed_virtual_joystick (Fixed virtual joystick) bool false # Requires: touchscreen virtual_joystick_triggers_aux1 (Virtual joystick triggers Aux1 button) bool false -# The gesture for punching players/entities. -# This can be overridden by games and mods. -# -# * short_tap -# Easy to use and well-known from other games that shall not be named. -# -# * long_tap -# Known from the classic Luanti mobile controls. -# Combat is more or less impossible. -# -# Requires: touchscreen -touch_punch_gesture (Punch gesture) enum short_tap short_tap,long_tap - - -[Graphics and Audio] +[Graphics and Audio] [client] [*Graphics] @@ -256,8 +517,8 @@ fps_max (Maximum FPS) int 60 1 4294967295 # Vertical screen synchronization. Your system may still force VSync on even if this is disabled. vsync (VSync) bool false -# Maximum FPS when the window is not focused, or when the game is paused. -fps_max_unfocused (FPS when unfocused or paused) int 20 1 4294967295 +# Maximum FPS when the window is not focused. +fps_max_unfocused (FPS when unfocused) int 10 1 4294967295 # View distance in nodes. viewing_range (Viewing range) int 190 20 4000 @@ -295,10 +556,6 @@ arm_inertia (Arm inertia) bool true # For example: 0 for no view bobbing; 1.0 for normal; 2.0 for double. view_bobbing_amount (View bobbing factor) float 1.0 0.0 7.9 -# Multiplier for fall bobbing. -# For example: 0 for no view bobbing; 1.0 for normal; 2.0 for double. -fall_bobbing_amount (Fall bobbing factor) float 0.03 0.0 100.0 - [**Camera] # Field of view in degrees. @@ -377,17 +634,12 @@ fog_start (Fog start) float 0.4 0.0 0.99 [**Clouds] -# Clouds are a client-side effect. -enable_clouds (Clouds) bool true - -# Use 3D cloud look instead of flat. -# -# Requires: enable_clouds +# Allow clouds to look 3D instead of flat. enable_3d_clouds (3D clouds) bool true # Use smooth cloud shading. # -# Requires: enable_3d_clouds, enable_clouds +# Requires: enable_3d_clouds soft_clouds (Soft clouds) bool false [**Filtering and Antialiasing] @@ -472,9 +724,6 @@ smooth_lighting (Smooth lighting) bool true # at the expense of minor visual glitches that do not impact game playability. performance_tradeoffs (Tradeoffs for performance) bool false -# Adds particles when digging a node. -enable_particles (Digging particles) bool true - [**Waving Nodes] @@ -546,8 +795,8 @@ shadow_map_texture_32bit (Shadow map texture in 32 bits) bool true # Requires: enable_dynamic_shadows, opengl shadow_filters (Shadow filter quality) enum 1 0,1,2 -# Enable colored shadows. -# On true translucent nodes cast colored shadows. This is expensive. +# Enable colored shadows for transculent nodes. +# This is expensive. # # Requires: enable_dynamic_shadows, opengl shadow_map_color (Colored shadows) bool false @@ -623,11 +872,6 @@ enable_volumetric_lighting (Volumetric lighting) bool false # Requires: enable_dynamic_shadows enable_translucent_foliage (Translucent foliage) bool false -# Apply specular shading to nodes. -# -# Requires: enable_dynamic_shadows -enable_node_specular (Node specular) bool false - # When enabled, liquid reflections are simulated. # # Requires: enable_waving_water, enable_dynamic_shadows @@ -642,8 +886,7 @@ sound_volume (Volume) float 0.8 0.0 1.0 # Volume multiplier when the window is unfocused. sound_volume_unfocused (Volume when unfocused) float 0.3 0.0 1.0 -# Whether to mute sounds. You can unmute sounds at any time, unless the -# sound system is disabled (enable_sound=false). +# Whether to mute sounds. You can unmute sounds at any time. # In-game, you can toggle the mute state with the mute key or by using the # pause menu. mute_sound (Mute sound) bool false @@ -684,12 +927,6 @@ formspec_fullscreen_bg_color (Formspec Full-Screen Background Color) string (0,0 # to hardware (e.g. render-to-texture for nodes in inventory). gui_scaling_filter (GUI scaling filter) bool false -# When gui_scaling_filter_txr2img is true, copy those images -# from hardware to software for scaling. When false, fall back -# to the old scaling method, for video drivers that don't -# properly support downloading textures back from hardware. -gui_scaling_filter_txr2img (GUI scaling filter txr2img) bool true - # Delay showing tooltips, stated in milliseconds. tooltip_show_delay (Tooltip delay) int 400 0 18446744073709551615 @@ -732,9 +969,6 @@ console_color (Console color) string (0,0,0) # In-game chat console background alpha (opaqueness, between 0 and 255). console_alpha (Console alpha) int 200 0 255 -# 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 #8888FF @@ -767,26 +1001,26 @@ contentdb_max_concurrent_downloads (ContentDB Max Concurrent Downloads) int 3 1 [Client and Server] -[*Client] +[*Client] [client] # Save the map received by the client on disk. enable_local_map_saving (Saving map received from server) bool false # URL to the server list displayed in the Multiplayer Tab. -serverlist_url (Serverlist URL) string https://servers.luanti.org +serverlist_url (Serverlist URL) [common] string https://servers.luanti.org -# If enabled, account registration is separate from login in the UI. -# If disabled, new accounts will be registered automatically when logging in. +# If enabled, server account registration is separate from login in the UI. +# If disabled, connecting to a server will automatically register a new account. enable_split_login_register (Enable split login/register) bool true # URL to JSON file which provides information about the newest Luanti release. # If this is empty the engine will never check for updates. -update_information_url (Update information URL) string https://www.minetest.net/release_info.json +update_information_url (Update information URL) string https://www.luanti.org/release_info.json -[*Server] +[*Server] [server] # Name of the player. -# When running a server, clients connecting with this name are admins. +# When running a server, a client connecting with this name is admin. # When starting from the main menu, this is overridden. name (Admin name) string @@ -814,7 +1048,7 @@ server_announce_send_players (Send player names to the server list) bool true serverlist_lan (Show local servers) bool true # Announce to this serverlist. -serverlist_url (Serverlist URL) string https://servers.luanti.org +serverlist_url (Serverlist URL) [common] string https://servers.luanti.org # Message of the day displayed to players connecting. motd (Message of the day) string @@ -853,12 +1087,14 @@ protocol_version_min (Protocol version minimum) int 1 1 65535 # Files that are not present will be fetched the usual way. remote_media (Remote media) string -# Enable/disable running an IPv6 server. +# Enable IPv6 support for server. +# Note that clients will be able to connect with both IPv4 and IPv6. # Ignored if bind_address is set. -# Needs enable_ipv6 to be enabled. -ipv6_server (IPv6 server) bool false +# +# Requires: enable_ipv6 +ipv6_server (IPv6 server) bool true -[*Server Security] +[*Server Security] [server] # New users need to input this password. default_password (Default password) string @@ -918,7 +1154,7 @@ chat_message_limit_per_10sec (Chat message count limit) float 8.0 1.0 # Kick players who sent more than X messages per 10 seconds. chat_message_limit_trigger_kick (Chat message kick threshold) int 50 1 65535 -[*Server Gameplay] +[*Server Gameplay] [server] # Controls length of day/night cycle. # Examples: @@ -926,7 +1162,7 @@ chat_message_limit_trigger_kick (Chat message kick threshold) int 50 1 65535 time_speed (Time speed) int 72 0 # Time of day when a new world is started, in millihours (0-23999). -world_start_time (World start time) int 6125 0 23999 +world_start_time (World start time) [world_creation] int 6125 0 23999 # Time in seconds for item entity (dropped items) to live. # Setting it to -1 disables the feature. @@ -981,7 +1217,7 @@ movement_liquid_sink (Liquid sinking) float 10.0 movement_gravity (Gravity) float 9.81 -[Mapgen] +[Mapgen] [world_creation] # A chosen map seed for a new map, leave empty for random. # Will be overridden when creating a new world in the main menu. @@ -997,7 +1233,7 @@ mg_name (Mapgen name) enum v7 v7,valleys,carpathian,v5,flat,fractal,singlenode,v water_level (Water level) int 1 -31000 31000 # From how far blocks are generated for clients, stated in mapblocks (16 nodes). -max_block_generate_distance (Max block generate distance) int 10 1 32767 +max_block_generate_distance (Max block generate distance) [server] int 10 1 32767 # Limit of map generation, in nodes, in all 6 directions from (0, 0, 0). # Only mapchunks completely within the mapgen limit are generated. @@ -1710,12 +1946,12 @@ mgvalleys_np_dungeons (Dungeon noise) noise_params_3d 0.9, 0.5, (500, 500, 500), # Enable Lua modding support on client. # This support is experimental and API can change. -enable_client_modding (Client modding) bool false +enable_client_modding (Client modding) [client] bool false # Replaces the default main menu with a custom one. -main_menu_script (Main menu script) string +main_menu_script (Main menu script) [client] string -[**Mod Security] +[**Mod Security] [server] # Prevent mods from doing insecure things like running shell commands. secure.enable_security (Enable mod security) bool true @@ -1739,33 +1975,33 @@ secure.http_mods (HTTP mods) string # - info # - verbose # - trace -debug_log_level (Debug log level) enum action ,none,error,warning,action,info,verbose,trace +debug_log_level (Debug log level) [common] 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, # deleting an older debug.txt.1 if it exists. # debug.txt is only moved if this setting is positive. -debug_log_size_max (Debug log file size threshold) int 50 1 +debug_log_size_max (Debug log file size threshold) [common] int 50 1 # Minimal level of logging to be written to chat. -chat_log_level (Chat log level) enum error ,none,error,warning,action,info,verbose,trace +chat_log_level (Chat log level) [client] enum error ,none,error,warning,action,info,verbose,trace # Handling for deprecated Lua API calls: # - none: Do not log deprecated calls # - log: mimic and log backtrace of deprecated call (default). # - error: abort on usage of deprecated call (suggested for mod developers). -deprecated_lua_api_handling (Deprecated Lua API handling) enum log none,log,error +deprecated_lua_api_handling (Deprecated Lua API handling) [common] enum log none,log,error # Enable random user input (only used for testing). -random_input (Random input) bool false +random_input (Random input) [client] bool false # Enable random mod loading (mainly used for testing). -random_mod_load_order (Random mod load order) bool false +random_mod_load_order (Random mod load order) [server] bool false # Enable mod channels support. -enable_mod_channels (Mod channels) bool false +enable_mod_channels (Mod channels) [server] bool false -[**Mod Profiler] +[**Mod Profiler] [server] # Load the game profiler to collect game profiling data. # Provides a /profiler command to access the compiled profile. @@ -1805,24 +2041,15 @@ instrument.builtin (Builtin) bool false # * Instrument the sampler being used to update the statistics. instrument.profiler (Profiler) bool false -[**Engine Profiler] +[**Engine Profiler] [common] # Print the engine's profiling data in regular intervals (in seconds). # 0 = disable. Useful for developers. profiler_print_interval (Engine profiling data print interval) int 0 0 - [*Advanced] -# Enable IPv6 support (for both client and server). -# Required for IPv6 connections to work at all. -enable_ipv6 (IPv6) bool true - -# If enabled, invalid world data won't cause the server to shut down. -# Only enable this if you know what you are doing. -ignore_world_load_errors (Ignore world errors) bool false - -[**Graphics] +[**Graphics] [client] # Enables debug and error-checking in the OpenGL driver. opengl_debug (OpenGL debug) bool false @@ -1847,10 +2074,7 @@ transparency_sorting_group_by_buffers (Transparency Sorting Group by Buffers) bo # Radius of cloud area stated in number of 64 node cloud squares. # Values larger than 26 will start to produce sharp cutoffs at cloud area corners. -cloud_radius (Cloud radius) int 12 1 62 - -# Whether node texture animations should be desynchronized per mapblock. -desynchronize_mapblock_texture_animation (Desynchronize block animation) bool false +cloud_radius (Cloud radius) int 12 8 62 # Delay between mesh updates on the client in ms. Increasing this will slow # down the rate of mesh updates, thus reducing jitter on slower clients. @@ -1862,7 +2086,7 @@ mesh_generation_threads (Mapblock mesh generation threads) int 0 0 8 # All mesh buffers with less than this number of vertices will be merged # during map rendering. This improves rendering performance. -mesh_buffer_min_vertices (Minimum vertex count for mesh buffers) int 100 0 1000 +mesh_buffer_min_vertices (Minimum vertex count for mesh buffers) int 300 0 1000 # True = 256 # False = 128 @@ -1880,7 +2104,7 @@ world_aligned_mode (World-aligned textures mode) enum enable disable,enable,forc # World-aligned textures may be scaled to span several nodes. However, # the server may not send the scale you want, especially if you use # a specially-designed texture pack; with this option, the client tries -# to determine the scale automatically basing on the texture size. +# to determine the scale automatically based on the texture size. # See also texture_min_size. # Warning: This option is EXPERIMENTAL! autoscale_mode (Autoscaling mode) enum disable disable,enable,force @@ -1892,7 +2116,7 @@ autoscale_mode (Autoscaling mode) enum disable disable,enable,force # This setting is ONLY applied if any of the mentioned filters are enabled. # This is also used as the base node texture size for world-aligned # texture autoscaling. -texture_min_size (Base texture size) int 64 1 32768 +texture_min_size (Base texture size) int 192 192 16384 # Side length of a cube of map blocks that the client will consider together # when generating meshes. @@ -1902,8 +2126,8 @@ texture_min_size (Base texture size) int 64 1 32768 client_mesh_chunk (Client Mesh Chunksize) int 1 1 16 # Decide the color depth of the texture used for the post-processing pipeline. -# Reducing this can improve performance, but might cause some effects (e.g. bloom) -# to not work. +# Reducing this can improve performance, but some effects (e.g. debanding) +# require more than 8 bits to work. # # Requires: enable_post_processing post_processing_texture_bits (Color depth for post-processing texture) enum 16 8,10,16 @@ -1914,13 +2138,12 @@ post_processing_texture_bits (Color depth for post-processing texture) enum 16 8 # Requires: enable_dynamic_shadows, opengl shadow_poisson_filter (Poisson filtering) bool true -# Spread a complete update of shadow map over given number of frames. +# Spread a complete update of the shadow map over a given number of frames. # Higher values might make shadows laggy, lower values # will consume more resources. -# Minimum value: 1; maximum value: 16 # # Requires: enable_dynamic_shadows, opengl -shadow_update_frames (Map shadows update frames) int 8 1 16 +shadow_update_frames (Map shadows update frames) int 16 1 32 # Set to true to render debugging breakdown of the bloom effect. # In debug mode, the screen is split into 4 quadrants: @@ -1930,12 +2153,12 @@ shadow_update_frames (Map shadows update frames) int 8 1 16 # Requires: enable_post_processing, enable_bloom enable_bloom_debug (Enable Bloom Debug) bool false -[**Sound] +[**Sound] [client] # Comma-separated list of AL and ALC extensions that should not be used. # Useful for testing. See al_extensions.[h,cpp] for details. sound_extensions_blacklist (Sound Extensions Blacklist) string -[**Font] +[**Font] [client] font_bold (Font bold by default) bool false @@ -1985,7 +2208,7 @@ mono_font_path_bold_italic (Bold and italic monospace font path) filepath fonts/ # 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 -[**Lighting] +[**Lighting] [client] # Gradient of light curve at minimum light level. # Controls the contrast of the lowest light levels. @@ -2011,43 +2234,50 @@ lighting_boost_spread (Light curve boost spread) float 0.2 0.0 0.4 [**Networking] -# Prometheus listener address. -# If Luanti is compiled with ENABLE_PROMETHEUS option enabled, -# enable metrics listener for Prometheus on that address. -# Metrics can be fetched on http://127.0.0.1:30000/metrics -prometheus_listener_address (Prometheus listener address) string 127.0.0.1:30000 +# Enable IPv6 support (for both client and server). +# Required for IPv6 connections to work at all. +enable_ipv6 (IPv6) [common] bool true -# Maximum size of the outgoing chat queue. +# Prometheus listener address. +# If Luanti is compiled with Prometheus support, this setting +# enables the metrics listener for Prometheus on that address. +# By default you can fetch metrics from http://127.0.0.1:30000/metrics. +# An empty value disables the metrics listener. +prometheus_listener_address (Prometheus listener address) [server] string 127.0.0.1:30000 + +# Maximum size of the client's outgoing chat queue. # 0 to disable queueing and -1 to make the queue size unlimited. -max_out_chat_queue_size (Maximum size of the outgoing chat queue) int 20 -1 32767 +max_out_chat_queue_size (Maximum size of the client's outgoing chat queue) [client] int 20 -1 32767 # Timeout for client to remove unused map data from memory, in seconds. -client_unload_unused_data_timeout (Mapblock unload timeout) float 600.0 0.0 +client_unload_unused_data_timeout (Mapblock unload timeout) [client] float 600.0 0.0 # Maximum number of mapblocks for client to be kept in memory. -# Set to -1 for unlimited amount. -client_mapblock_limit (Mapblock limit) int 7500 -1 2147483647 +# Note that there is an internal dynamic minimum number of blocks that +# won't be deleted, depending on the current view range. +# Set to -1 for no limit. +client_mapblock_limit (Mapblock limit) [client] int 7500 -1 2147483647 # Maximum number of blocks that are simultaneously sent per client. # The maximum total count is calculated dynamically: # max_total = ceil((#clients + max_users) * per_client / 4) -max_simultaneous_block_sends_per_client (Maximum simultaneous block sends per client) int 40 1 4294967295 +max_simultaneous_block_sends_per_client (Maximum simultaneous block sends per client) [server] int 40 1 4294967295 # To reduce lag, block transfers are slowed down when a player is building something. # This determines how long they are slowed down after placing or removing a node. -full_block_send_enable_min_time_from_building (Delay in sending blocks after building) float 2.0 0.0 +full_block_send_enable_min_time_from_building (Delay in sending blocks after building) [server] float 2.0 0.0 # Maximum number of packets sent per send step in the low-level networking code. # You generally don't need to change this, however busy servers may benefit from a higher number. -max_packets_per_iteration (Max. packets per iteration) int 1024 1 65535 +max_packets_per_iteration (Max. packets per iteration) [common] int 1024 1 65535 # Compression level to use when sending mapblocks to the client. # -1 - use default compression level # 0 - least compression, fastest # 9 - best compression, slowest -map_compression_level_net (Map Compression Level for Network Transfer) int -1 -1 9 +map_compression_level_net (Map Compression Level for Network Transfer) [server] int -1 -1 9 -[**Server] +[**Server] [server] # Format of player chat messages. The following strings are valid placeholders: # @name, @message, @timestamp (optional) @@ -2067,7 +2297,7 @@ kick_msg_crash (Crash message) string This server has experienced an internal er # Set this to true if your server is set up to restart automatically. ask_reconnect_on_crash (Ask to reconnect after crash) bool false -[**Server/Env Performance] +[**Server/Env Performance] [server] # Length of a server tick (the interval at which everything is generally updated), # stated in seconds. @@ -2104,9 +2334,6 @@ max_block_send_distance (Max block send distance) int 12 1 65535 # Set this to -1 to disable the limit. max_forceloaded_blocks (Maximum forceloaded blocks) int 16 -1 -# Interval of sending time of day to clients, stated in seconds. -time_send_interval (Time send interval) float 5.0 0.001 - # Interval of saving important changes in the world, stated in seconds. server_map_save_interval (Map save interval) float 5.3 0.001 @@ -2115,20 +2342,20 @@ server_map_save_interval (Map save interval) float 5.3 0.001 server_unload_unused_data_timeout (Unload unused server data) int 29 0 4294967295 # Maximum number of statically stored objects in a block. -max_objects_per_block (Maximum objects per block) int 256 1 65535 +max_objects_per_block (Maximum objects per block) int 256 256 65535 # Length of time between active block management cycles, stated in seconds. active_block_mgmt_interval (Active block management interval) float 2.0 0.0 # Length of time between Active Block Modifier (ABM) execution cycles, stated in seconds. -abm_interval (ABM interval) float 1.0 0.0 +abm_interval (ABM interval) float 1.0 0.1 30.0 # The time budget allowed for ABMs to execute on each step # (as a fraction of the ABM Interval) abm_time_budget (ABM time budget) float 0.2 0.1 0.9 # Length of time between NodeTimer execution cycles, stated in seconds. -nodetimer_interval (NodeTimer interval) float 0.2 0.0 +nodetimer_interval (NodeTimer interval) float 0.2 0.1 1.0 # Max liquids processed per step. liquid_loop_max (Liquid loop max) int 100000 1 4294967295 @@ -2163,7 +2390,7 @@ server_side_occlusion_culling (Server-side occlusion culling) bool true # Stated in MapBlocks (16 nodes). block_cull_optimize_distance (Block cull optimize distance) int 25 2 2047 -[**Mapgen] +[**Mapgen] [server] # Size of mapchunks generated by mapgen, stated in mapblocks (16 nodes). # WARNING: There is no benefit, and there are several dangers, in @@ -2171,7 +2398,7 @@ block_cull_optimize_distance (Block cull optimize distance) int 25 2 2047 # Reducing this value increases cave and dungeon density. # Altering this value is for special usage, leaving it unchanged is # recommended. -chunksize (Chunk size) int 5 1 10 +chunksize (Chunk size) [world_creation] int 5 1 10 # Dump the mapgen debug information. enable_mapgen_debug_info (Mapgen debug) bool false @@ -2199,7 +2426,7 @@ emergequeue_limit_generate (Per-player limit of queued blocks to generate) int 1 # 'on_generated'. For many users the optimum setting may be '1'. num_emerge_threads (Number of emerge threads) int 1 0 32767 -[**cURL] +[**cURL] [common] # Maximum time an interactive request (e.g. server list fetch) may take, stated in milliseconds. curl_timeout (cURL interactive timeout) int 20000 1000 2147483647 @@ -2214,44 +2441,68 @@ curl_parallel_limit (cURL parallel limit) int 8 1 2147483647 # Maximum time a file download (e.g. a mod download) may take, stated in milliseconds. curl_file_download_timeout (cURL file download timeout) int 300000 5000 2147483647 +[**Client Debugging] [client] + +# Key for toggling the camera update. Only usable with 'debug' privilege. +keymap_toggle_update_camera (Toggle camera update) key + +# Key for switching to the previous entry in Quicktune. +keymap_quicktune_prev (Quicktune: select previous entry) key + +# Key for switching to the next entry in Quicktune. +keymap_quicktune_next (Quicktune: select next entry) key + +# Key for decrementing the selected value in Quicktune. +keymap_quicktune_dec (Quicktune: decrement value) key + +# Key for incrementing the selected value in Quicktune. +keymap_quicktune_inc (Quicktune: increment value) key + [**Miscellaneous] +# Clickable weblinks (middle-click or Ctrl+left-click) enabled in chat console output. +clickable_chat_weblinks (Chat weblinks) [client] bool true + +# If enabled, invalid world data won't cause the server to shut down. +# Only enable this if you know what you are doing. +ignore_world_load_errors (Ignore world errors) [server] bool false + # Adjust the detected display density, used for scaling UI elements. -display_density_factor (Display Density Scaling Factor) float 1 0.5 5.0 +display_density_factor (Display Density Scaling Factor) [client] float 1 0.5 5.0 # Windows systems only: Start Luanti 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 +enable_console (Enable console window) [common] bool false # Number of extra blocks that can be loaded by /clearobjects at once. # 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 0 4294967295 +max_clearobjects_extra_loaded_blocks (Max. clearobjects extra blocks) [server] int 4096 0 4294967295 # World directory (everything in the world is stored here). # Not needed if starting from the main menu. -map-dir (Map directory) path +map-dir (Map directory) [server] path # See https://www.sqlite.org/pragma.html#pragma_synchronous -sqlite_synchronous (Synchronous SQLite) enum 2 0,1,2 +sqlite_synchronous (Synchronous SQLite) [server] enum 2 0,1,2 # Compression level to use when saving mapblocks to disk. # -1 - use default compression level # 0 - least compression, fastest # 9 - best compression, slowest -map_compression_level_disk (Map Compression Level for Disk Storage) int -1 -1 9 +map_compression_level_disk (Map Compression Level for Disk Storage) [server] int -1 -1 9 # Enable usage of remote media server (if provided by server). # Remote servers offer a significantly faster way to download media (e.g. textures) # when connecting to the server. -enable_remote_media_server (Connect to external media server) bool true +enable_remote_media_server (Connect to external media server) [client] bool true # File in client/serverlist/ that contains your favorite servers displayed in the # Multiplayer Tab. -serverlist_file (Serverlist file) string favoriteservers.json +serverlist_file (Serverlist file) [client] string favoriteservers.json -[*Gamepads] +[*Gamepads] [client] # Enable joysticks. Requires a restart to take effect enable_joysticks (Enable joysticks) bool false @@ -2274,7 +2525,7 @@ joystick_deadzone (Joystick dead zone) int 2048 0 65535 joystick_frustum_sensitivity (Joystick frustum sensitivity) float 170.0 0.001 -[*Hide: Temporary Settings] +[*Hide: Temporary Settings] [common] # Path to texture directory. All textures are first searched from here. texture_path (Texture path) path @@ -2333,232 +2584,3 @@ show_technical_names (Show technical names) bool false # Controlled by a checkbox in the settings menu. show_advanced (Show advanced settings) bool false - -# Enables the sound system. -# If disabled, this completely disables all sounds everywhere and the in-game -# sound controls will be non-functional. -# Changing this setting requires a restart. -enable_sound (Sound) bool true - -# Key for moving the player forward. -keymap_forward (Forward key) key KEY_KEY_W - -# Key for moving the player backward. -# Will also disable autoforward, when active. -keymap_backward (Backward key) key KEY_KEY_S - -# Key for moving the player left. -keymap_left (Left key) key KEY_KEY_A - -# Key for moving the player right. -keymap_right (Right key) key KEY_KEY_D - -# Key for jumping. -keymap_jump (Jump key) key KEY_SPACE - -# Key for sneaking. -# Also used for climbing down and descending in water if aux1_descends is disabled. -keymap_sneak (Sneak key) key KEY_LSHIFT - -# Key for digging, punching or using something. -# (Note: The actual meaning might vary on a per-game basis.) -keymap_dig (Dig/punch/use key) key KEY_LBUTTON - -# Key for placing an item/block or for using something. -# (Note: The actual meaning might vary on a per-game basis.) -keymap_place (Place/use key) key KEY_RBUTTON - -# Key for opening the inventory. -keymap_inventory (Inventory key) key KEY_KEY_I - -# Key for moving fast in fast mode. -keymap_aux1 (Aux1 key) key KEY_KEY_E - -# Key for opening the chat window. -keymap_chat (Chat key) key KEY_KEY_T - -# Key for opening the chat window to type commands. -keymap_cmd (Command key) key / - -# Key for opening the chat window to type local commands. -keymap_cmd_local (Command key) key . - -# Key for toggling unlimited view range. -keymap_rangeselect (Range select key) key - -# Key for toggling flying. -keymap_freemove (Fly key) key KEY_KEY_K - -# Key for toggling pitch move mode. -keymap_pitchmove (Pitch move key) key - -# Key for toggling fast mode. -keymap_fastmove (Fast key) key KEY_KEY_J - -# Key for toggling noclip mode. -keymap_noclip (Noclip key) key KEY_KEY_H - -# Key for selecting the next item in the hotbar. -keymap_hotbar_next (Hotbar next key) key KEY_KEY_N - -# Key for selecting the previous item in the hotbar. -keymap_hotbar_previous (Hotbar previous key) key KEY_KEY_B - -# Key for muting the game. -keymap_mute (Mute key) key KEY_KEY_M - -# Key for increasing the volume. -keymap_increase_volume (Inc. volume key) key - -# Key for decreasing the volume. -keymap_decrease_volume (Dec. volume key) key - -# Key for toggling autoforward. -keymap_autoforward (Automatic forward key) key - -# Key for toggling cinematic mode. -keymap_cinematic (Cinematic mode key) key - -# Key for toggling display of minimap. -keymap_minimap (Minimap key) key KEY_KEY_V - -# Key for taking screenshots. -keymap_screenshot (Screenshot) key KEY_F12 - -# Key for toggling fullscreen mode. -keymap_fullscreen (Fullscreen key) key KEY_F11 - -# Key for dropping the currently selected item. -keymap_drop (Drop item key) key KEY_KEY_Q - -# Key to use view zoom when possible. -keymap_zoom (View zoom key) key KEY_KEY_Z - -# Key for selecting the first hotbar slot. -keymap_slot1 (Hotbar slot 1 key) key KEY_KEY_1 - -# Key for selecting the second hotbar slot. -keymap_slot2 (Hotbar slot 2 key) key KEY_KEY_2 - -# Key for selecting the third hotbar slot. -keymap_slot3 (Hotbar slot 3 key) key KEY_KEY_3 - -# Key for selecting the fourth hotbar slot. -keymap_slot4 (Hotbar slot 4 key) key KEY_KEY_4 - -# Key for selecting the fifth hotbar slot. -keymap_slot5 (Hotbar slot 5 key) key KEY_KEY_5 - -# Key for selecting the sixth hotbar slot. -keymap_slot6 (Hotbar slot 6 key) key KEY_KEY_6 - -# Key for selecting the seventh hotbar slot. -keymap_slot7 (Hotbar slot 7 key) key KEY_KEY_7 - -# Key for selecting the eighth hotbar slot. -keymap_slot8 (Hotbar slot 8 key) key KEY_KEY_8 - -# Key for selecting the ninth hotbar slot. -keymap_slot9 (Hotbar slot 9 key) key KEY_KEY_9 - -# Key for selecting the tenth hotbar slot. -keymap_slot10 (Hotbar slot 10 key) key KEY_KEY_0 - -# Key for selecting the 11th hotbar slot. -keymap_slot11 (Hotbar slot 11 key) key - -# Key for selecting the 12th hotbar slot. -keymap_slot12 (Hotbar slot 12 key) key - -# Key for selecting the 13th hotbar slot. -keymap_slot13 (Hotbar slot 13 key) key - -# Key for selecting the 14th hotbar slot. -keymap_slot14 (Hotbar slot 14 key) key - -# Key for selecting the 15th hotbar slot. -keymap_slot15 (Hotbar slot 15 key) key - -# Key for selecting the 16th hotbar slot. -keymap_slot16 (Hotbar slot 16 key) key - -# Key for selecting the 17th hotbar slot. -keymap_slot17 (Hotbar slot 17 key) key - -# Key for selecting the 18th hotbar slot. -keymap_slot18 (Hotbar slot 18 key) key - -# Key for selecting the 19th hotbar slot. -keymap_slot19 (Hotbar slot 19 key) key - -# Key for selecting the 20th hotbar slot. -keymap_slot20 (Hotbar slot 20 key) key - -# Key for selecting the 21st hotbar slot. -keymap_slot21 (Hotbar slot 21 key) key - -# Key for selecting the 22nd hotbar slot. -keymap_slot22 (Hotbar slot 22 key) key - -# Key for selecting the 23rd hotbar slot. -keymap_slot23 (Hotbar slot 23 key) key - -# Key for selecting the 24th hotbar slot. -keymap_slot24 (Hotbar slot 24 key) key - -# Key for selecting the 25th hotbar slot. -keymap_slot25 (Hotbar slot 25 key) key - -# Key for selecting the 26th hotbar slot. -keymap_slot26 (Hotbar slot 26 key) key - -# Key for selecting the 27th hotbar slot. -keymap_slot27 (Hotbar slot 27 key) key - -# Key for selecting the 28th hotbar slot. -keymap_slot28 (Hotbar slot 28 key) key - -# Key for selecting the 29th hotbar slot. -keymap_slot29 (Hotbar slot 29 key) key - -# Key for selecting the 30th hotbar slot. -keymap_slot30 (Hotbar slot 30 key) key - -# Key for selecting the 31st hotbar slot. -keymap_slot31 (Hotbar slot 31 key) key - -# Key for selecting the 32nd hotbar slot. -keymap_slot32 (Hotbar slot 32 key) key - -# Key for toggling the display of the HUD. -keymap_toggle_hud (HUD toggle key) key KEY_F1 - -# Key for toggling the display of chat. -keymap_toggle_chat (Chat toggle key) key KEY_F2 - -# Key for toggling the display of the large chat console. -keymap_console (Large chat console key) key KEY_F10 - -# Key for toggling the display of fog. -keymap_toggle_fog (Fog toggle key) key KEY_F3 - -# Key for toggling the camera update. Only usable with 'debug' privilege. -keymap_toggle_update_camera (Camera update toggle key) key - -# Key for toggling the display of debug info. -keymap_toggle_debug (Debug info toggle key) key KEY_F5 - -# Key for toggling the display of the profiler. Used for development. -keymap_toggle_profiler (Profiler toggle key) key KEY_F6 - -# Key for toggling the display of mapblock boundaries. -keymap_toggle_block_bounds (Block bounds toggle key) key - -# Key for switching between first- and third-person camera. -keymap_camera_mode (Toggle camera mode key) key KEY_KEY_C - -# Key for increasing the viewing range. -keymap_increase_viewing_range_min (View range increase key) key + - -# Key for decreasing the viewing range. -keymap_decrease_viewing_range_min (View range decrease key) key - diff --git a/client/shaders/nodes_shader/opengl_fragment.glsl b/client/shaders/nodes_shader/opengl_fragment.glsl index 537f8b4a7..ed0404cea 100644 --- a/client/shaders/nodes_shader/opengl_fragment.glsl +++ b/client/shaders/nodes_shader/opengl_fragment.glsl @@ -51,7 +51,7 @@ centroid varying float nightRatio; varying highp vec3 eyeVec; #ifdef ENABLE_DYNAMIC_SHADOWS -#if (defined(ENABLE_WATER_REFLECTIONS) && MATERIAL_WAVING_LIQUID && ENABLE_WAVING_WATER) +#if (defined(ENABLE_WATER_REFLECTIONS) && MATERIAL_WATER_REFLECTIONS && ENABLE_WAVING_WATER) vec4 perm(vec4 x) { return mod(((x * 34.0) + 1.0) * x, 289.0); @@ -502,7 +502,7 @@ void main(void) vec3 viewVec = normalize(worldPosition + cameraOffset - cameraPosition); // Water reflections -#if (defined(ENABLE_WATER_REFLECTIONS) && MATERIAL_WAVING_LIQUID && ENABLE_WAVING_WATER) +#if (defined(ENABLE_WATER_REFLECTIONS) && MATERIAL_WATER_REFLECTIONS && ENABLE_WAVING_WATER) vec3 wavePos = worldPosition * vec3(2.0, 0.0, 2.0); float off = animationTimer * WATER_WAVE_SPEED * 10.0; wavePos.x /= WATER_WAVE_LENGTH * 3.0; @@ -530,7 +530,7 @@ void main(void) col.rgb += water_reflect_color * f_adj_shadow_strength * brightness_factor; #endif -#if (defined(ENABLE_NODE_SPECULAR) && !MATERIAL_WAVING_LIQUID) +#if (defined(ENABLE_NODE_SPECULAR) && !MATERIAL_WATER_REFLECTIONS) // Apply specular to blocks. if (dot(v_LightDirection, vNormal) < 0.0) { float intensity = 2.0 * (1.0 - (base.r * varColor.r)); diff --git a/client/shaders/nodes_shader/opengl_vertex.glsl b/client/shaders/nodes_shader/opengl_vertex.glsl index 0f508dc6a..6fe7acd85 100644 --- a/client/shaders/nodes_shader/opengl_vertex.glsl +++ b/client/shaders/nodes_shader/opengl_vertex.glsl @@ -44,8 +44,6 @@ centroid varying float nightRatio; varying float perspective_factor; #endif -varying float area_enable_parallax; - varying highp vec3 eyeVec; // Color of the light emitted by the light sources. const vec3 artificialLight = vec3(1.04, 1.04, 1.04); diff --git a/client/shaders/second_stage/opengl_fragment.glsl b/client/shaders/second_stage/opengl_fragment.glsl index 0a73aa393..6053884bc 100644 --- a/client/shaders/second_stage/opengl_fragment.glsl +++ b/client/shaders/second_stage/opengl_fragment.glsl @@ -61,7 +61,7 @@ vec4 applyBloom(vec4 color, vec2 uv) equation used: ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F */ -// see https://github.com/minetest/minetest/pull/14688 +// highp for GLES, see highp vec3 uncharted2Tonemap(highp vec3 x) { return ((x * (0.22 * x + 0.03) + 0.002) / (x * (0.22 * x + 0.3) + 0.06)) - 0.03333; diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in index ae36fd6bf..98ef36fd6 100644 --- a/doc/Doxyfile.in +++ b/doc/Doxyfile.in @@ -1,7 +1,7 @@ # Project properties PROJECT_NAME = @PROJECT_NAME_CAPITALIZED@ PROJECT_NUMBER = @VERSION_STRING@ -PROJECT_LOGO = @CMAKE_CURRENT_SOURCE_DIR@/misc/minetest.svg +PROJECT_LOGO = @CMAKE_CURRENT_SOURCE_DIR@/misc/luanti.svg # Parsing JAVADOC_AUTOBRIEF = YES diff --git a/doc/breakages.md b/doc/breakages.md index 2d2994385..412cf2e41 100644 --- a/doc/breakages.md +++ b/doc/breakages.md @@ -10,15 +10,18 @@ This list is largely advisory and items may be reevaluated once the time comes. * `game.conf` name/id mess * remove `depends.txt` / `description.txt` (would simplify ContentDB and Luanti code a little) * rotate moon texture by 180°, making it coherent with the sun - * https://github.com/minetest/minetest/pull/11902 + * https://github.com/luanti-org/luanti/pull/11902 * remove undocumented `set_physics_override(num, num, num)` * remove special handling of `${key}` syntax in metadata values * remove old_move * change physics_override `sneak` to disable the speed change and not just the node clipping - * https://github.com/minetest/minetest/issues/13699 + * https://github.com/luanti-org/luanti/issues/13699 * migrate from player names to UUIDs, this would enable renaming of accounts and unicode player names (if desired) * harmonize use_texture_alpha between entities & nodes, change default to 'opaque' and remove bool compat code * merge `sound` and `sounds` table in itemdef * remove `DIR_DELIM` from Lua * stop reading initial properties from bare entity def * change particle default blend mode to `clip` +* remove built-in knockback and related functions entirely +* remove `safe` parameter from `core.serialize`, always enforce `safe = true`. + possibly error when `loadstring` calls are encountered in `core.deserialize`. diff --git a/doc/client_lua_api.md b/doc/client_lua_api.md index fe65dd92e..017f8b89b 100644 --- a/doc/client_lua_api.md +++ b/doc/client_lua_api.md @@ -1,12 +1,12 @@ -Luanti Lua Client Modding API Reference 5.11.0 +Luanti Lua Client Modding API Reference 5.13.0 ============================================== **WARNING**: if you're looking for the `minetest` namespace (e.g. `minetest.something`), it's now called `core` due to the renaming of Luanti (formerly Minetest). `minetest` will keep existing as an alias, so that old code won't break. -* More information at -* Developer Wiki: +* More information at +* Developer Wiki: Introduction ------------ @@ -23,7 +23,7 @@ Transferring client-sided mods from the server to the client is planned, but not If you see a deficiency in the API, feel free to attempt to add the functionality in the engine and API. You can send such improvements as -source code patches on GitHub (https://github.com/minetest/minetest). +source code patches on GitHub. Programming in Lua ------------------ diff --git a/doc/compiling/README.md b/doc/compiling/README.md index 16167977b..9ce8a800e 100644 --- a/doc/compiling/README.md +++ b/doc/compiling/README.md @@ -23,6 +23,7 @@ General options and their default values: PRECOMPILE_HEADERS=FALSE - Precompile some headers (experimental; requires CMake 3.16 or later) PRECOMPILED_HEADERS_PATH= - Path to a file listing all headers to precompile (default points to src/precompiled_headers.txt) USE_SDL2=TRUE - Build with SDL2; Enables IrrlichtMt device SDL2 + USE_SDL2_STATIC=TRUE - Links with SDL2::SDL2-static instead of SDL2::SDL2 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_GETTEXT=ON - Build with Gettext; Allows using translations diff --git a/doc/compiling/linux.md b/doc/compiling/linux.md index 54a44d501..7984d34d7 100644 --- a/doc/compiling/linux.md +++ b/doc/compiling/linux.md @@ -70,14 +70,14 @@ For Void users: Download source (this is the URL to the latest of source repository, which might not work at all times) using Git: - git clone --depth 1 https://github.com/minetest/minetest.git - cd minetest + git clone --depth 1 https://github.com/luanti-org/luanti + cd luanti Download source, without using Git: - wget https://github.com/minetest/minetest/archive/master.tar.gz + wget https://github.com/luanti-org/luanti/archive/master.tar.gz tar xf master.tar.gz - cd minetest-master + cd luanti-master ## Build @@ -88,7 +88,11 @@ Build a version that runs directly from the source directory: Run it: - ./bin/minetest + ./bin/luanti + +Run unit tests: + + ./bin/luanti --run-unittests - Use `cmake . -LH` to see all CMake options and their current state. - If you want to install it system-wide (or are making a distribution package), diff --git a/doc/compiling/macos.md b/doc/compiling/macos.md index 09452004e..12818a334 100644 --- a/doc/compiling/macos.md +++ b/doc/compiling/macos.md @@ -16,7 +16,7 @@ brew install cmake freetype gettext gmp hiredis jpeg-turbo jsoncpp leveldb libog 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 luanti +git clone --depth 1 https://github.com/luanti-org/luanti luanti cd luanti ``` diff --git a/doc/compiling/windows.md b/doc/compiling/windows.md index a4d9699f6..f61a8d677 100644 --- a/doc/compiling/windows.md +++ b/doc/compiling/windows.md @@ -33,8 +33,8 @@ Use `--triplet` to specify the target triplet, e.g. `x64-windows` or `x86-window ### a) Using the vcpkg toolchain and CMake GUI 1. Start up the CMake GUI -2. Select **Browse Source...** and select DIR/minetest -3. Select **Browse Build...** and select DIR/minetest-build +2. Select **Browse Source...** and select DIR/luanti +3. Select **Browse Build...** and select DIR/luanti-build 4. Select **Configure** 5. Choose the right visual Studio version and target platform. It has to match the version of the installed dependencies 6. Choose **Specify toolchain file for cross-compiling** diff --git a/doc/developing/README.md b/doc/developing/README.md index 1aa424b00..419e41edd 100644 --- a/doc/developing/README.md +++ b/doc/developing/README.md @@ -2,15 +2,15 @@ ## Wiki -Some important development docs are found in the wiki: https://dev.minetest.net/ +Some important development docs are found in the wiki: https://dev.luanti.org/ Notable pages: -- [Releasing Luanti](https://dev.minetest.net/Releasing_Minetest) -- [Engine translations](https://dev.minetest.net/Translation#Maintaining_engine_translations) -- [Changelog](https://dev.minetest.net/Changelog) -- [Organisation](https://dev.minetest.net/Organisation) -- [Code style guidelines](https://dev.minetest.net/Code_style_guidelines) +- [Releasing Luanti](https://dev.luanti.org/Releasing_Luanti) +- [Engine translations](https://dev.luanti.org/Translation#Maintaining_engine_translations) +- [Changelog](https://dev.luanti.org/Changelog) +- [Organisation](https://dev.luanti.org/Organisation) +- [Code style guidelines](https://dev.luanti.org/Code_style_guidelines) ## In this folder diff --git a/doc/developing/android.md b/doc/developing/android.md index 5a134d561..285351cf0 100644 --- a/doc/developing/android.md +++ b/doc/developing/android.md @@ -2,7 +2,7 @@ ## Sign the Android APK from CI -The [Github Actions Workflow](https://github.com/minetest/minetest/actions?query=workflow%3Aandroid+event%3Apush) +The [Github Actions Workflow](https://github.com/luanti-org/luanti/actions?query=workflow%3Aandroid+event%3Apush) automatically produces an APK for each architecture. Before installing them onto a device they however need to be signed. diff --git a/doc/developing/misc.md b/doc/developing/misc.md index 9895cc624..5992946cb 100644 --- a/doc/developing/misc.md +++ b/doc/developing/misc.md @@ -9,7 +9,7 @@ To get usable results you need to build Luanti with debug symbols Run the client (or server) like this and do whatever you wanted to test: ```bash -perf record -z --call-graph dwarf -- ./bin/minetest +perf record -z --call-graph dwarf -- ./bin/luanti ``` This will leave a file called "perf.data". diff --git a/doc/direction.md b/doc/direction.md index 85b44f57c..bd70003d1 100644 --- a/doc/direction.md +++ b/doc/direction.md @@ -5,9 +5,9 @@ The long-term roadmaps, aims, and guiding philosophies are set out using the following documents: -* [What is Minetest?](http://c55.me/blog/?p=1491) +* [What is Minetest? (archived)](https://web.archive.org/web/20160328054721/http://c55.me/blog/?p=1491) * [celeron55's roadmap](https://forum.luanti.org/viewtopic.php?t=9177) -* [celeron55's comment in "A clear mission statement for Minetest is missing"](https://github.com/minetest/minetest/issues/3476#issuecomment-167399287) +* [celeron55's comment in "A clear mission statement for Minetest is missing"](https://github.com/luanti-org/luanti/issues/3476#issuecomment-167399287) * [Core developer to-do/wish lists](https://forum.luanti.org/viewforum.php?f=7) ## 2. Medium-term Roadmap @@ -16,7 +16,7 @@ These are the current medium-term goals for Luanti development, in no particular order. These goals were created from the top points in a -[roadmap brainstorm](https://github.com/minetest/minetest/issues/10461). +[roadmap brainstorm](https://github.com/luanti-org/luanti/issues/10461). This should be reviewed approximately yearly, or when goals are achieved. Pull requests that address one of these goals will be labeled as "Roadmap". @@ -32,9 +32,9 @@ Once that is done, fancier features can be worked on, such as water shaders, shadows, and improved lighting. Examples include -[transparency sorting](https://github.com/minetest/minetest/issues/95), -[particle performance](https://github.com/minetest/minetest/issues/1414), -[general view distance](https://github.com/minetest/minetest/issues/7222). +[transparency sorting](https://github.com/luanti-org/luanti/issues/95), +[particle performance](https://github.com/luanti-org/luanti/issues/1414), +[general view distance](https://github.com/luanti-org/luanti/issues/7222). This includes work on maintaining [our Irrlicht fork](https://github.com/minetest/irrlicht), and switching to @@ -43,16 +43,16 @@ alternative libraries to replace Irrlicht functionality as needed ### 2.2 Internal code refactoring To ensure sustainable development, Luanti's code needs to be -[refactored and improved](https://github.com/minetest/minetest/pulls?q=is%3Aopen+sort%3Aupdated-desc+label%3A%22Code+quality%22+). +[refactored and improved](https://github.com/luanti-org/luanti/pulls?q=is%3Aopen+sort%3Aupdated-desc+label%3A%22Code+quality%22). This will remove code rot and allow for more efficient development. ### 2.3 UI Improvements -A [formspec replacement](https://github.com/minetest/minetest/issues/6527) is +A [formspec replacement](https://github.com/luanti-org/luanti/issues/6527) is needed to make GUIs better and easier to create. This replacement could also be a replacement for HUDs, allowing for a unified API. -A [new mainmenu](https://github.com/minetest/minetest/issues/6733) is needed to +A [new mainmenu](https://github.com/luanti-org/luanti/issues/6733) is needed to improve user experience. First impressions matter, and the current main menu doesn't do a very good job at selling Luanti or explaining what it is. A new main menu should promote games to users, allowing Minetest Game to no @@ -65,5 +65,5 @@ an issue for any large changes before spending lots of time. There are still a significant number of issues with objects. Collisions, -[performance](https://github.com/minetest/minetest/issues/6453), +[performance](https://github.com/luanti-org/luanti/issues/6453), API convenience, and discrepancies between players and entities. diff --git a/doc/docker_server.md b/doc/docker_server.md index eebe776b9..c89c4e6f0 100644 --- a/doc/docker_server.md +++ b/doc/docker_server.md @@ -4,22 +4,25 @@ We provide Luanti server Docker images using the GitHub container registry. Images are built on each commit and available using the following tag scheme: -* `ghcr.io/minetest/minetest:master` (latest build) -* `ghcr.io/minetest/minetest:` (specific Git tag) -* `ghcr.io/minetest/minetest:latest` (latest Git tag, which is the stable release) +* `ghcr.io/luanti-org/luanti:master` (latest build) +* `ghcr.io/luanti-org/luanti:` (specific Git tag) +* `ghcr.io/luanti-org/luanti:latest` (latest Git tag, which is the stable release) -See [here](https://github.com/minetest/minetest/pkgs/container/minetest) for all available tags. +See [here](https://github.com/luanti-org/luanti/pkgs/container/luanti) for all available tags. + +Versions released before the project was renamed are available with the same tag scheme at `ghcr.io/minetest/minetest`. +See [here](https://github.com/orgs/minetest/packages/container/package/minetest) for all available tags. For a quick test you can easily run: ```shell -docker run ghcr.io/minetest/minetest:master +docker run ghcr.io/luanti-org/luanti:master ``` To use it in a production environment, you should use volumes bound to the Docker host to persist data and modify the configuration: ```shell -docker create -v /home/minetest/data/:/var/lib/minetest/ -v /home/minetest/conf/:/etc/minetest/ ghcr.io/minetest/minetest:master +docker create -v /home/minetest/data/:/var/lib/minetest/ -v /home/minetest/conf/:/etc/minetest/ ghcr.io/luanti-org/luanti:master ``` You may also want to use [Docker Compose](https://docs.docker.com/compose): @@ -29,7 +32,7 @@ You may also want to use [Docker Compose](https://docs.docker.com/compose): version: "2" services: minetest_server: - image: ghcr.io/minetest/minetest:master + image: ghcr.io/luanti-org/luanti:master restart: always networks: - default diff --git a/doc/lua_api.md b/doc/lua_api.md index af57c5442..b604b317c 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -5,10 +5,10 @@ Luanti Lua Modding API Reference it's now called `core` due to the renaming of Luanti (formerly Minetest). `minetest` will keep existing as an alias, so that old code won't break. -* More information at -* Developer Wiki: +* More information at +* Developer Wiki: * (Unofficial) Minetest Modding Book by rubenwardy: -* Modding tools: +* Modding tools: Introduction ------------ @@ -194,7 +194,8 @@ Mod directory structure │   │   │ └── another_subfolder │   │   └── bar_subfolder │   ├── sounds - │   ├── media + │   ├── fonts + │ ├── media │   ├── locale │   └── └── another @@ -265,7 +266,7 @@ The main Lua script. Running this script should register everything it wants to register. Subsequent execution depends on Luanti calling the registered callbacks. -### `textures`, `sounds`, `media`, `models`, `locale` +### `textures`, `sounds`, `media`, `models`, `locale`, `fonts` Media files (textures, sounds, whatever) that will be transferred to the client and will be available for use by the mod and translation files for @@ -278,6 +279,7 @@ Accepted formats are: images: .png, .jpg, .tga sounds: .ogg vorbis models: .x, .b3d, .obj, (since version 5.10:) .gltf, .glb + fonts: .ttf, .woff (both since version 5.11, see notes below) Other formats won't be sent to the client (e.g. you can store .blend files in a folder for convenience, without the risk that such files are transferred) @@ -325,7 +327,7 @@ Many glTF features are not supported *yet*, including: * Double-sided materials don't work * Alternative means of supplying data * Embedded images. You can use `gltfutil.py` from the - [modding tools](https://github.com/minetest/modtools) to strip or extract embedded images. + [modding tools](https://github.com/luanti-org/modtools) to strip or extract embedded images. * References to files via URIs Textures are supplied solely via the same means as for the other model file formats: @@ -342,6 +344,28 @@ For example, if your model used an emissive material, you should expect that a future version of Luanti may respect this, and thus cause your model to render differently there. +#### Custom fonts + +You can supply custom fonts in TrueType Font (`.ttf`) or Web Open Font Format (`.woff`) format. +The former is supported primarily for convenience. The latter is preferred due to its compression. + +In the future, having multiple custom fonts and the ability to switch between them is planned, +but for now this feature is limited to the ability to override Luanti's default fonts via mods. +It is recommended that this only be used by game mods to set a look and feel. + +The stems (file names without extension) are self-explanatory: + +* Regular variants: + * `regular` + * `bold` + * `italic` + * `bold_italic` +* Monospaced variants: + * `mono` + * `mono_bold` + * `mono_italic` + * `mono_bold_italic` + Naming conventions ------------------ @@ -494,6 +518,15 @@ stripping out the file extension: Supported texture formats are PNG (`.png`), JPEG (`.jpg`) and Targa (`.tga`). +Luanti generally uses nearest-neighbor upscaling for textures to preserve the crisp +look of pixel art (low-res textures). +Users can optionally enable bilinear and/or trilinear filtering. However, to avoid +everything becoming blurry, textures smaller than 192px will either not be filtered, +or will be upscaled to that minimum resolution first without filtering. + +This is subject to change to move more control to the Lua API, but you can rely on +low-res textures not suddenly becoming filtered. + Texture modifiers ----------------- @@ -1460,8 +1493,6 @@ Node drawtypes There are a bunch of different looking node types. -Look for examples in `games/devtest` or `games/minetest_game`. - * `normal` * A node-sized cube. * `airlike` @@ -1500,7 +1531,7 @@ Look for examples in `games/devtest` or `games/minetest_game`. * `allfaces` * Often used for partially-transparent nodes. * External sides of textures, and unlike other drawtypes, the external sides - of other blocks, are visible from the inside. + of other nodes, are visible from the inside. * `allfaces_optional` * Often used for leaves nodes. * This switches between `normal`, `glasslike` and `allfaces` according to @@ -1574,7 +1605,8 @@ Look for examples in `games/devtest` or `games/minetest_game`. Node boxes ---------- -Node selection boxes are defined using "node boxes". +Node selection boxes and collision boxes, and the appearance of the `nodebox` +drawtype, are defined using "node boxes". A nodebox is defined as any of: @@ -1659,7 +1691,9 @@ roughly 1x1x1 meters in size. A 'mapblock' (often abbreviated to 'block') is 16x16x16 nodes and is the fundamental region of a world that is stored in the world database, sent to -clients and handled by many parts of the engine. +clients and handled by many parts of the engine. This size is available as the +constant `core.MAP_BLOCKSIZE` (=16). + 'mapblock' is preferred terminology to 'block' to help avoid confusion with 'node', however 'block' often appears in the API. @@ -1668,6 +1702,38 @@ A 'mapchunk' (sometimes abbreviated to 'chunk') is usually 5x5x5 mapblocks the map generator. The size in mapblocks has been chosen to optimize map generation. +### Mapblock status + +A mapblock being "loaded" means that is in memory. These are the mapblocks that +API functions like `core.get_node` or `core.set_node` can operate on. To reach +this state, the mapblock must first go through the process of being "emerged". +This means that it is loaded from disk, and/or, if it isn't yet generated, +generated by the map generator. + +Mapblocks are loaded in a broad area around each player. They become "unloaded" +again if no player is close enough. The engine commonly represents the contents +of unloaded mapblocks as `"ignore"` nodes. + +A mapblock being "active" means that it is not only in memory, but also affected +by world simulation: + +* Entities are active + * They are in memory as `ServerActiveObject`, exposed to Lua as `ObjectRef` + * They exist in Lua as luaentity tables +* ABMs are executed +* Node timers are executed + +Also, when a mapblock is "activated", LBMs are executed. Mapblocks are active +in a smaller area around each player, and are "deactivated" again if no player +is close enough. + +Related API functions: + +* `core.compare_block_status` +* `core.forceload_block` +* `core.load_area` +* `core.emerge_area` + Coordinates ----------- @@ -1691,7 +1757,7 @@ node position (0,0,0) to node position (15,15,15). To calculate the blockpos of the mapblock that contains the node at 'nodepos', for each axis: -* blockpos = math.floor(nodepos / 16) +* blockpos = math.floor(nodepos / core.MAP_BLOCKSIZE) #### Converting blockpos to min/max node positions @@ -1699,9 +1765,9 @@ To calculate the min/max node positions contained in the mapblock at 'blockpos', for each axis: * Minimum: - nodepos = blockpos * 16 + nodepos = blockpos * core.MAP_BLOCKSIZE * Maximum: - nodepos = blockpos * 16 + 15 + nodepos = (blockpos + 1) * core.MAP_BLOCKSIZE - 1 @@ -2002,6 +2068,21 @@ that acts as tool in a gameplay sense as a craftitem, and vice-versa. Craftitems can be used for items that neither need to be a node nor a tool. +Special Items +------------- +The following items are predefined and have special properties. + +* `"unknown"`: An item that represents every item which has not been registered +* `"air"`: The node which appears everywhere where no other node is +* `"ignore"`: Mapblocks that are not loaded are represented using this node. + * Also used for nodes that have not yet been set by the map generator. + * This is also what appears outside of the map boundary. +* `""`: The player's hand, which is in use whenever the player wields no item. + * Its range and tool capabilities are also used as a fallback for the wielded item. + * It can be overridden to change those properties: + * globally using `core.override_item` + * per-player using the special `"hand"` inventory list + Amount and wear --------------- @@ -2761,6 +2842,9 @@ Version History * Add field_enter_after_edit[] (experimental) * Formspec version 8 (5.10.0) * scroll_container[]: content padding parameter +* Formspec version 9 (5.12.0) + * Add allow_close[] + * label[]: Add "area label" variant Elements -------- @@ -2831,6 +2915,13 @@ Elements * For information on converting forms to the new coordinate system, see `Migrating to Real Coordinates`. +### `allow_close[]` + +* When set to false, the formspec will not close when the user tries to close + it with the Escape key or similar. Default true. +* The formspec can still be closed with `*_exit[]` elements and + `core.close_formspec()`, regardless of this setting. + ### `container[,]` * Start of a container block, moves all physical elements in the container by @@ -3066,9 +3157,11 @@ Elements ### `textarea[,;,;;