1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-07-02 16:38:41 +00:00

Merge branch 'master' into master

This commit is contained in:
DustyBagel 2024-12-29 12:39:11 -06:00 committed by GitHub
commit b5aab1bcc7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
1196 changed files with 57421 additions and 35855 deletions

3
.gitattributes vendored
View file

@ -3,3 +3,6 @@
*.cpp diff=cpp *.cpp diff=cpp
*.h diff=cpp *.h diff=cpp
*.gltf binary
*.x binary

View file

@ -37,7 +37,7 @@ Contributions are welcome! Here's how you can help:
[Lua](http://dev.minetest.net/Lua_code_style_guidelines) code style guidelines. [Lua](http://dev.minetest.net/Lua_code_style_guidelines) code style guidelines.
- Check your code works as expected and document any changes to the Lua API. - 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. - To avoid conflicting changes between contributions, do not do the following manually. They will be done before each release.
- Run `updatepo.sh` or update `minetest.po{,t}` even if your code adds new translatable strings. - Run `updatepo.sh` or update `luanti.po{,t}` even if your code adds new translatable strings.
- Update `minetest.conf.example` and `settings_translation_file.cpp` even if your code adds new core settings. - Update `minetest.conf.example` and `settings_translation_file.cpp` even if your code adds new core settings.
4. Commit & [push](https://help.github.com/articles/pushing-to-a-remote/) your changes to a new branch (not `master`, one change per branch) 4. Commit & [push](https://help.github.com/articles/pushing-to-a-remote/) your changes to a new branch (not `master`, one change per branch)
@ -83,7 +83,7 @@ a stable release is on the way.
- Error logs (check the bottom of the `debug.txt` file). - Error logs (check the bottom of the `debug.txt` file).
- Screenshots. - Screenshots.
- Ways you have tried to solve the issue, and whether they worked or not. - Ways you have tried to solve the issue, and whether they worked or not.
- Your Minetest version and the content (games, mods or texture packs) you have installed. - Your Luanti version and the content (games, mods or texture packs) you have installed.
- Your platform (e.g. Windows 10 or Ubuntu 15.04 x64). - Your platform (e.g. Windows 10 or Ubuntu 15.04 x64).
After reporting you should aim to answer questions or clarifications as this After reporting you should aim to answer questions or clarifications as this
@ -99,7 +99,7 @@ possible.
## Translations ## Translations
The core translations of Minetest are performed using Weblate. You can access The core translations of Luanti are performed using Weblate. You can access
the project page with a list of current languages the project page with a list of current languages
[here](https://hosted.weblate.org/projects/minetest/minetest/). [here](https://hosted.weblate.org/projects/minetest/minetest/).
@ -110,7 +110,7 @@ translated by editing a `.tr` text file. See
## Donations ## Donations
If you'd like to monetarily support Minetest development, you can find donation 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.minetest.net/development/#donate).
# Maintaining # Maintaining
@ -118,7 +118,7 @@ methods on [our website](http://www.minetest.net/development/#donate).
* This is a concise version of the * This is a concise version of the
[Rules & Guidelines](http://dev.minetest.net/Category:Rules_and_Guidelines) on the developer wiki.* [Rules & Guidelines](http://dev.minetest.net/Category:Rules_and_Guidelines) on the developer wiki.*
These notes are for those who have push access Minetest (core developers / maintainers). 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](http://dev.minetest.net/Organisation) for the people involved.
@ -169,4 +169,4 @@ Submit a :+1: (+1) or "Looks good" comment to show you believe the pull-request
## Releasing a new version ## Releasing a new version
*Refer to [dev.minetest.net/Releasing_Minetest](http://dev.minetest.net/Releasing_Minetest)* *Refer to [dev.minetest.net/Releasing_Luanti](https://dev.minetest.net/Releasing_Luanti)*

View file

@ -6,22 +6,24 @@ body:
attributes: attributes:
value: | value: |
Please note the following: Please note the following:
1. **Please update your Minetest Engine 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.minetest.net/packages/?type=game) and submit a bug report in their issue trackers. 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.
* For example, you can submit issues about the Minetest Game (the official game of Minetest) [in its own repository](https://github.com/minetest/minetest_game/issues). 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).
3. Please provide as many details as possible for us to spot the problem quicker. 3. Please provide as many details as possible for us to spot the problem quicker.
- type: textarea - type: textarea
attributes: attributes:
label: Minetest version label: Luanti version
description: | description: |
Paste the Minetest version below. Paste the Luanti version below.
If you are on a dev version, please also indicate the git commit hash. If you are on a dev version, please also indicate the git commit hash.
Refer to the "About" tab of the menu or run `minetest --version` on the command line. Refer to the "About" tab of the menu or run `luanti --version` on the command line.
placeholder: | placeholder: |
Example: Example:
Minetest 5.7.0-dev-ca13c51 (Linux) Luanti 5.10.0-3ad6aee9b (Linux)
Using Irrlicht 1.9.0mt9 Using LuaJIT 2.1.1727870382
Using LuaJIT 2.1.0-beta3 Built by GCC 14.2
Running on Linux/6.11.5 x86_64
BUILD_TYPE=Release BUILD_TYPE=Release
RUN_IN_PLACE=1 RUN_IN_PLACE=1
USE_CURL=1 USE_CURL=1
@ -32,13 +34,6 @@ body:
render: "true" render: "true"
validations: validations:
required: true required: true
- type: input
attributes:
label: Irrlicht device
description:
placeholder: "Example: X11"
validations:
required: false
- type: input - type: input
attributes: attributes:
label: Operating system and version label: Operating system and version
@ -67,7 +62,7 @@ body:
attributes: attributes:
label: Active renderer label: Active renderer
description: You can find this in the "About" tab in the main menu. description: You can find this in the "About" tab in the main menu.
placeholder: "Example: OpenGL 4.6.0" placeholder: "Example: ES 3.2 / ogles2 / X11"
validations: validations:
required: false required: false
- type: textarea - type: textarea

View file

@ -4,5 +4,5 @@ contact_links:
url: https://github.com/minetest/minetest_game/issues/new/choose url: https://github.com/minetest/minetest_game/issues/new/choose
about: Only submit issues of the engine in this repository's issue tracker. Submit those of Minetest Game in its own issue tracker. 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 - name: Search for issue trackers of third-party games
url: https://content.minetest.net/packages/?type=game url: https://content.luanti.org/packages/?type=game
about: For issues of third-party games, search for the game in the ContentDB and then submit an issue in their issue tracker. about: For issues of third-party games, search for the game in the ContentDB and then submit an issue in their issue tracker.

View file

@ -7,7 +7,7 @@ body:
value: | value: |
Please note the following: Please note the following:
1. Only submit a feature request if the feature does not exist on the latest dev version. 1. Only submit a feature request if the feature does not exist on the latest dev version.
2. This page is for suggesting changes to **the engine itself**. To suggest changes to games, please [search for the game in the ContentDB](https://content.minetest.net/packages/?type=game) and submit a feature request in their issue trackers. 2. This page is for suggesting changes to **the engine itself**. To suggest changes to games, please [search for the game in the ContentDB](https://content.luanti.org/packages/?type=game) and submit a feature request in their issue trackers.
- type: textarea - type: textarea
attributes: attributes:
label: Problem label: Problem

2
.github/SECURITY.md vendored
View file

@ -14,7 +14,7 @@ to give us time to fix them. You can do that by emailing one of the following ad
* rubenwardy@minetest.net * rubenwardy@minetest.net
Depending on severity, we will either create a private issue for the vulnerability Depending on severity, we will either create a private issue for the vulnerability
and release a patch version of Minetest, or give you permission to file the issue publicly. and release a patch version of Luanti, or give you permission to file the issue publicly.
For more information on the justification of this policy, see For more information on the justification of this policy, see
[Responsible Disclosure](https://en.wikipedia.org/wiki/Responsible_disclosure). [Responsible Disclosure](https://en.wikipedia.org/wiki/Responsible_disclosure).

View file

@ -12,6 +12,7 @@ on:
- 'irr/**.cpp' - 'irr/**.cpp'
- '**/CMakeLists.txt' - '**/CMakeLists.txt'
- 'cmake/Modules/**' - 'cmake/Modules/**'
- 'po/**.po'
- 'android/**' - 'android/**'
- '.github/workflows/android.yml' - '.github/workflows/android.yml'
pull_request: pull_request:
@ -24,6 +25,7 @@ on:
- 'irr/**.cpp' - 'irr/**.cpp'
- '**/CMakeLists.txt' - '**/CMakeLists.txt'
- 'cmake/Modules/**' - 'cmake/Modules/**'
- 'po/**.po'
- 'android/**' - 'android/**'
- '.github/workflows/android.yml' - '.github/workflows/android.yml'
@ -50,25 +52,25 @@ jobs:
- name: Save AAB artifact - name: Save AAB artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: Minetest-release.aab name: Luanti-release.aab
path: android/app/build/outputs/bundle/release/app-release.aab path: android/app/build/outputs/bundle/release/app-release.aab
- name: Save armeabi artifact - name: Save armeabi artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: Minetest-armeabi-v7a.apk name: Luanti-armeabi-v7a.apk
path: android/app/build/outputs/apk/release/app-armeabi-v7a-release-unsigned.apk path: android/app/build/outputs/apk/release/app-armeabi-v7a-release-unsigned.apk
- name: Save arm64 artifact - name: Save arm64 artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: Minetest-arm64-v8a.apk name: Luanti-arm64-v8a.apk
path: android/app/build/outputs/apk/release/app-arm64-v8a-release-unsigned.apk path: android/app/build/outputs/apk/release/app-arm64-v8a-release-unsigned.apk
- name: Save x86 artifact - name: Save x86 artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: Minetest-x86.apk name: Luanti-x86.apk
path: android/app/build/outputs/apk/release/app-x86-release-unsigned.apk path: android/app/build/outputs/apk/release/app-x86-release-unsigned.apk
- name: Save x86_64 artifact - name: Save x86_64 artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: Minetest-x86_64.apk name: Luanti-x86_64.apk
path: android/app/build/outputs/apk/release/app-x86_64-release-unsigned.apk path: android/app/build/outputs/apk/release/app-x86_64-release-unsigned.apk

View file

@ -72,8 +72,8 @@ jobs:
with: with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
labels: | labels: |
org.opencontainers.image.title=Minetest org.opencontainers.image.title=Luanti
org.opencontainers.image.vendor=Minetest org.opencontainers.image.vendor=Luanti
org.opencontainers.image.licenses=LGPL-2.1-only org.opencontainers.image.licenses=LGPL-2.1-only
# Build and push Docker image # Build and push Docker image
@ -94,5 +94,5 @@ jobs:
- name: Test Docker Image - name: Test Docker Image
run: | run: |
docker run --rm $(cut -d, -f1 <<<"$DOCKER_METADATA_OUTPUT_TAGS") minetestserver --version docker run --rm $(cut -d, -f1 <<<"$DOCKER_METADATA_OUTPUT_TAGS") luantiserver --version
shell: bash shell: bash

View file

@ -12,9 +12,8 @@ on:
- 'irr/**.cpp' - 'irr/**.cpp'
- '**/CMakeLists.txt' - '**/CMakeLists.txt'
- 'cmake/Modules/**' - 'cmake/Modules/**'
- 'po/**.po'
- 'util/ci/**' - 'util/ci/**'
- 'Dockerfile'
- '.dockerignore'
- '.github/workflows/linux.yml' - '.github/workflows/linux.yml'
pull_request: pull_request:
paths: paths:
@ -26,9 +25,8 @@ on:
- 'irr/**.cpp' - 'irr/**.cpp'
- '**/CMakeLists.txt' - '**/CMakeLists.txt'
- 'cmake/Modules/**' - 'cmake/Modules/**'
- 'po/**.po'
- 'util/ci/**' - 'util/ci/**'
- 'Dockerfile'
- '.dockerignore'
- '.github/workflows/linux.yml' - '.github/workflows/linux.yml'
env: env:
@ -51,10 +49,12 @@ jobs:
env: env:
CC: gcc-7 CC: gcc-7
CXX: g++-7 CXX: g++-7
# Test fallback SHA implementations
CMAKE_FLAGS: '-DENABLE_OPENSSL=0'
- name: Test - name: Test
run: | run: |
./bin/minetest --run-unittests ./bin/luanti --run-unittests
# Current gcc version # Current gcc version
gcc_14: gcc_14:
@ -78,7 +78,7 @@ jobs:
mkdir nowrite mkdir nowrite
chmod a-w nowrite chmod a-w nowrite
cd nowrite cd nowrite
../bin/minetest --run-unittests ../bin/luanti --run-unittests
# Older clang version (should be close to our minimum supported version) # Older clang version (should be close to our minimum supported version)
clang_7: clang_7:
@ -88,7 +88,7 @@ jobs:
- name: Install deps - name: Install deps
run: | run: |
source ./util/ci/common.sh source ./util/ci/common.sh
install_linux_deps clang-7 llvm install_linux_deps clang-7 llvm-7
- name: Build - name: Build
run: | run: |
@ -100,7 +100,12 @@ jobs:
- name: Unittest - name: Unittest
run: | run: |
./bin/minetest --run-unittests ./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 # Current clang version
clang_18: clang_18:
@ -121,7 +126,7 @@ jobs:
- name: Test - name: Test
run: | run: |
./bin/minetest --run-unittests ./bin/luanti --run-unittests
- name: Integration test + devtest - name: Integration test + devtest
run: | run: |
@ -151,4 +156,4 @@ jobs:
- name: Test - name: Test
run: | run: |
./bin/minetestserver --run-unittests ./bin/luantiserver --run-unittests

View file

@ -13,6 +13,7 @@ on:
- 'irr/**.mm' # Objective-C(++) - 'irr/**.mm' # Objective-C(++)
- '**/CMakeLists.txt' - '**/CMakeLists.txt'
- 'cmake/Modules/**' - 'cmake/Modules/**'
- 'po/**.po'
- '.github/workflows/macos.yml' - '.github/workflows/macos.yml'
pull_request: pull_request:
paths: paths:
@ -25,12 +26,13 @@ on:
- 'irr/**.mm' # Objective-C(++) - 'irr/**.mm' # Objective-C(++)
- '**/CMakeLists.txt' - '**/CMakeLists.txt'
- 'cmake/Modules/**' - 'cmake/Modules/**'
- 'po/**.po'
- '.github/workflows/macos.yml' - '.github/workflows/macos.yml'
jobs: jobs:
build: build-intel-macos:
# use lowest possible macOS running on x86_64 to support more users # use lowest possible macOS running on x86_64 supported by brew to support more users
runs-on: macos-12 runs-on: macos-13
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install deps - name: Install deps
@ -53,7 +55,7 @@ jobs:
- name: Test - name: Test
run: | run: |
./build/macos/minetest.app/Contents/MacOS/minetest --run-unittests ./build/macos/luanti.app/Contents/MacOS/luanti --run-unittests
# Zipping the built .app preserves permissions on the contained files, # Zipping the built .app preserves permissions on the contained files,
# which the GitHub artifact pipeline would otherwise strip away. # which the GitHub artifact pipeline would otherwise strip away.
@ -66,5 +68,46 @@ jobs:
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
with: with:
name: minetest-macos name: luanti-macos
path: ./build/macos/*.zip path: ./build/macos/*.zip
build-arm-macos-xcode:
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- name: Install deps
run: |
source ./util/ci/common.sh
install_macos_deps
# brew jsoncpp do not include libjsoncpp.a, and if installed header conflict caused build failure
brew uninstall jsoncpp
- name: Build with Cmake
run: |
mkdir build
cd build
cmake .. \
-DCMAKE_OSX_DEPLOYMENT_TARGET=14 \
-DCMAKE_FIND_FRAMEWORK=LAST \
-DCMAKE_INSTALL_PREFIX=../build/macos/ \
-DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE \
-DENABLE_SYSTEM_JSONCPP=OFF
cmake --build . -j$(sysctl -n hw.logicalcpu)
make install
- name: Build and Archive with Xcode
run: |
mkdir build_xcode
cd build_xcode
../util/ci/build_xcode.sh
- name: Tests
run: |
mkdir -p "${HOME}/Library/Application Support/minetest/games/"
ln -s "${PWD}/games/devtest" "${HOME}/Library/Application Support/minetest/games/"
./build/macos/luanti.app/Contents/MacOS/luanti --run-unittests
./build_xcode/luanti.xcarchive/Products/Applications/luanti.app/Contents/MacOS/luanti --run-unittests
- name: Diff Resources
run: |
diff -rd ./build/macos/luanti.app/Contents/Resources ./build_xcode/build/Release/luanti.app/Contents/Resources || exit 1
diff -rd ./build/macos/luanti.app/Contents/Resources ./build_xcode/luanti.xcarchive/Products/Applications/luanti.app/Contents/Resources || exit 1

View file

@ -12,6 +12,7 @@ on:
- 'irr/**.cpp' - 'irr/**.cpp'
- '**/CMakeLists.txt' - '**/CMakeLists.txt'
- 'cmake/Modules/**' - 'cmake/Modules/**'
- 'po/**.po'
- 'util/buildbot/**' - 'util/buildbot/**'
- 'misc/*.manifest' - 'misc/*.manifest'
- '.github/workflows/windows.yml' - '.github/workflows/windows.yml'
@ -25,6 +26,7 @@ on:
- 'irr/**.cpp' - 'irr/**.cpp'
- '**/CMakeLists.txt' - '**/CMakeLists.txt'
- 'cmake/Modules/**' - 'cmake/Modules/**'
- 'po/**.po'
- 'util/buildbot/**' - 'util/buildbot/**'
- 'misc/*.manifest' - 'misc/*.manifest'
- '.github/workflows/windows.yml' - '.github/workflows/windows.yml'
@ -56,8 +58,8 @@ jobs:
run: | run: |
dest=$(mktemp -d) dest=$(mktemp -d)
unzip -q -d "$dest" B/build/*.zip unzip -q -d "$dest" B/build/*.zip
cd "$dest"/minetest-*-win* cd "$dest"/luanti-*-win*
wine bin/minetest.exe --version wine bin/luanti.exe --version
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
with: with:
@ -103,7 +105,7 @@ jobs:
vcpkgGitCommitId: ${{ env.VCPKG_VERSION }} vcpkgGitCommitId: ${{ env.VCPKG_VERSION }}
vcpkgTriplet: ${{ matrix.config.vcpkg_triplet }} vcpkgTriplet: ${{ matrix.config.vcpkg_triplet }}
- name: Minetest CMake - name: CMake
run: | run: |
cmake ${{matrix.config.generator}} ` cmake ${{matrix.config.generator}} `
-DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}\vcpkg\scripts\buildsystems\vcpkg.cmake" ` -DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}\vcpkg\scripts\buildsystems\vcpkg.cmake" `
@ -113,13 +115,13 @@ jobs:
-DREQUIRE_LUAJIT=TRUE ` -DREQUIRE_LUAJIT=TRUE `
-DRUN_IN_PLACE=${{ contains(matrix.type, 'portable') }} . -DRUN_IN_PLACE=${{ contains(matrix.type, 'portable') }} .
- name: Build Minetest - name: Build
run: cmake --build . --config Release run: cmake --build . --config Release
- name: Unittests - name: Unittests
# need this workaround for stdout to work # need this workaround for stdout to work
run: | run: |
$proc = start .\bin\Release\minetest.exe --run-unittests -NoNewWindow -Wait -PassThru $proc = start .\bin\Release\luanti.exe --run-unittests -NoNewWindow -Wait -PassThru
exit $proc.ExitCode exit $proc.ExitCode
continue-on-error: true # FIXME!! continue-on-error: true # FIXME!!

3
.gitignore vendored
View file

@ -26,6 +26,7 @@ tags
!tags/ !tags/
gtags.files gtags.files
.idea .idea
.qtcreator/
# Codelite # Codelite
*.project *.project
# Visual Studio Code & plugins # Visual Studio Code & plugins
@ -109,6 +110,8 @@ src/cmake_config_githash.h
*.layout *.layout
*.o *.o
*.a *.a
*.dump
*.dmp
*.ninja *.ninja
.ninja* .ninja*
*.gch *.gch

View file

@ -15,6 +15,7 @@ read_globals = {
"fgettext", "fgettext_ne", "fgettext", "fgettext_ne",
"vector", "vector",
"VoxelArea", "VoxelArea",
"VoxelManip",
"profiler", "profiler",
"Settings", "Settings",
"PerlinNoise", "PerlinNoiseMap", "PerlinNoise", "PerlinNoiseMap",

View file

@ -72,3 +72,4 @@ Lars Müller <appgurulars@gmx.de>
Lars Müller <appgurulars@gmx.de> <34514239+appgurueu@users.noreply.github.com> Lars Müller <appgurulars@gmx.de> <34514239+appgurueu@users.noreply.github.com>
ROllerozxa <rollerozxa@voxelmanip.se> ROllerozxa <rollerozxa@voxelmanip.se>
ROllerozxa <rollerozxa@voxelmanip.se> <temporaryemail4meh+github@gmail.com> ROllerozxa <rollerozxa@voxelmanip.se> <temporaryemail4meh+github@gmail.com>
Ælla Chiana Moskopp <erle@dieweltistgarnichtso.net> <nils@dieweltistgarnichtso.net>

View file

@ -1,8 +1,8 @@
cmake_minimum_required(VERSION 3.12) cmake_minimum_required(VERSION 3.12)
# This can be read from ${PROJECT_NAME} after project() is called # This can be read from ${PROJECT_NAME} after project() is called
project(minetest) project(luanti)
set(PROJECT_NAME_CAPITALIZED "Minetest") set(PROJECT_NAME_CAPITALIZED "Luanti")
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED TRUE) set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
@ -11,7 +11,7 @@ set(CLANG_MINIMUM_VERSION "7.0.1")
# You should not need to edit these manually, use util/bump_version.sh # You should not need to edit these manually, use util/bump_version.sh
set(VERSION_MAJOR 5) set(VERSION_MAJOR 5)
set(VERSION_MINOR 10) set(VERSION_MINOR 11)
set(VERSION_PATCH 0) set(VERSION_PATCH 0)
set(VERSION_EXTRA "" CACHE STRING "Stuff to append to version string") set(VERSION_EXTRA "" CACHE STRING "Stuff to append to version string")
@ -92,7 +92,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/")
# Load default options for Android # Load default options for Android
if(ANDROID) if(ANDROID)
cmake_minimum_required(VERSION 3.20) cmake_minimum_required(VERSION 3.20)
include(MinetestAndroidLibs) include(AndroidLibs)
endif() endif()
@ -261,23 +261,27 @@ install(FILES "doc/world_format.md" DESTINATION "${DOCDIR}" COMPONENT "Docs")
install(FILES "minetest.conf.example" DESTINATION "${EXAMPLE_CONF_DIR}") install(FILES "minetest.conf.example" DESTINATION "${EXAMPLE_CONF_DIR}")
if(UNIX AND NOT APPLE) if(UNIX AND NOT APPLE)
install(FILES "doc/minetest.6" "doc/minetestserver.6" DESTINATION "${MANDIR}/man6") 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.desktop" DESTINATION "${XDG_APPS_DIR}")
install(FILES "misc/net.minetest.minetest.metainfo.xml" DESTINATION "${METAINFODIR}") install(FILES "misc/net.minetest.minetest.metainfo.xml" DESTINATION "${METAINFODIR}")
install(FILES "misc/minetest.svg" DESTINATION "${ICONDIR}/hicolor/scalable/apps") install(FILES "misc/luanti.svg" DESTINATION "${ICONDIR}/hicolor/scalable/apps")
install(FILES "misc/minetest-xorg-icon-128.png" install(FILES "misc/luanti-xorg-icon-128.png"
DESTINATION "${ICONDIR}/hicolor/128x128/apps" DESTINATION "${ICONDIR}/hicolor/128x128/apps"
RENAME "minetest.png") RENAME "luanti.png")
endif() endif()
if(APPLE) if(APPLE)
install(FILES "misc/minetest-icon.icns" DESTINATION "${SHAREDIR}") install(FILES "misc/luanti-icon.icns" DESTINATION "${SHAREDIR}")
install(FILES "misc/Info.plist" DESTINATION "${BUNDLE_PATH}/Contents") install(FILES "${CMAKE_BINARY_DIR}/Info.plist" DESTINATION "${BUNDLE_PATH}/Contents")
endif()
if(CMAKE_GENERATOR STREQUAL "Xcode")
set(client_RESOURCES "${CMAKE_SOURCE_DIR}/misc/luanti-icon.icns")
endif() endif()
# Library pack # Library pack
find_package(GMP REQUIRED) find_package(GMP REQUIRED)
find_package(Json REQUIRED) find_package(Json 1.0.0 REQUIRED)
find_package(Lua REQUIRED) find_package(Lua REQUIRED)
if(NOT USE_LUAJIT) if(NOT USE_LUAJIT)
add_subdirectory(lib/bitop) add_subdirectory(lib/bitop)
@ -307,7 +311,7 @@ include(CPackComponent)
cpack_add_component(Docs cpack_add_component(Docs
DISPLAY_NAME "Documentation" DISPLAY_NAME "Documentation"
DESCRIPTION "Documentation about Minetest and Minetest modding" DESCRIPTION "Documentation about ${PROJECT_NAME_CAPITALIZED} and ${PROJECT_NAME_CAPITALIZED} modding"
) )
if(WIN32) if(WIN32)
@ -331,7 +335,7 @@ if(WIN32)
set(CPACK_CREATE_DESKTOP_LINKS ${PROJECT_NAME}) set(CPACK_CREATE_DESKTOP_LINKS ${PROJECT_NAME})
set(CPACK_PACKAGING_INSTALL_PREFIX "/${PROJECT_NAME_CAPITALIZED}") set(CPACK_PACKAGING_INSTALL_PREFIX "/${PROJECT_NAME_CAPITALIZED}")
set(CPACK_WIX_PRODUCT_ICON "${CMAKE_CURRENT_SOURCE_DIR}/misc/minetest-icon.ico") set(CPACK_WIX_PRODUCT_ICON "${CMAKE_CURRENT_SOURCE_DIR}/misc/luanti-icon.ico")
# Supported languages can be found at # Supported languages can be found at
# http://wixtoolset.org/documentation/manual/v3/wixui/wixui_localization.html # http://wixtoolset.org/documentation/manual/v3/wixui/wixui_localization.html
#set(CPACK_WIX_CULTURES "ar-SA,bg-BG,ca-ES,hr-HR,cs-CZ,da-DK,nl-NL,en-US,et-EE,fi-FI,fr-FR,de-DE") #set(CPACK_WIX_CULTURES "ar-SA,bg-BG,ca-ES,hr-HR,cs-CZ,da-DK,nl-NL,en-US,et-EE,fi-FI,fr-FR,de-DE")

2
CNAME
View file

@ -1 +1 @@
api.minetest.net api.luanti.org

View file

@ -32,22 +32,22 @@ RUN git clone --recursive https://github.com/jupp0r/prometheus-cpp && \
FROM dev as builder FROM dev as builder
COPY .git /usr/src/minetest/.git COPY .git /usr/src/luanti/.git
COPY CMakeLists.txt /usr/src/minetest/CMakeLists.txt COPY CMakeLists.txt /usr/src/luanti/CMakeLists.txt
COPY README.md /usr/src/minetest/README.md COPY README.md /usr/src/luanti/README.md
COPY minetest.conf.example /usr/src/minetest/minetest.conf.example COPY minetest.conf.example /usr/src/luanti/minetest.conf.example
COPY builtin /usr/src/minetest/builtin COPY builtin /usr/src/luanti/builtin
COPY cmake /usr/src/minetest/cmake COPY cmake /usr/src/luanti/cmake
COPY doc /usr/src/minetest/doc COPY doc /usr/src/luanti/doc
COPY fonts /usr/src/minetest/fonts COPY fonts /usr/src/luanti/fonts
COPY lib /usr/src/minetest/lib COPY lib /usr/src/luanti/lib
COPY misc /usr/src/minetest/misc COPY misc /usr/src/luanti/misc
COPY po /usr/src/minetest/po COPY po /usr/src/luanti/po
COPY src /usr/src/minetest/src COPY src /usr/src/luanti/src
COPY irr /usr/src/minetest/irr COPY irr /usr/src/luanti/irr
COPY textures /usr/src/minetest/textures COPY textures /usr/src/luanti/textures
WORKDIR /usr/src/minetest WORKDIR /usr/src/luanti
RUN cmake -B build \ RUN cmake -B build \
-DCMAKE_INSTALL_PREFIX=/usr/local \ -DCMAKE_INSTALL_PREFIX=/usr/local \
-DCMAKE_BUILD_TYPE=Release \ -DCMAKE_BUILD_TYPE=Release \
@ -68,9 +68,9 @@ RUN apk add --no-cache curl gmp libstdc++ libgcc libpq jsoncpp zstd-libs \
WORKDIR /var/lib/minetest WORKDIR /var/lib/minetest
COPY --from=builder /usr/local/share/minetest /usr/local/share/minetest COPY --from=builder /usr/local/share/luanti /usr/local/share/luanti
COPY --from=builder /usr/local/bin/minetestserver /usr/local/bin/minetestserver COPY --from=builder /usr/local/bin/luantiserver /usr/local/bin/luantiserver
COPY --from=builder /usr/local/share/doc/minetest/minetest.conf.example /etc/minetest/minetest.conf COPY --from=builder /usr/local/share/doc/luanti/minetest.conf.example /etc/minetest/minetest.conf
COPY --from=builder /usr/local/lib/libspatialindex* /usr/local/lib/ COPY --from=builder /usr/local/lib/libspatialindex* /usr/local/lib/
COPY --from=builder /usr/local/lib/libluajit* /usr/local/lib/ COPY --from=builder /usr/local/lib/libluajit* /usr/local/lib/
USER minetest:minetest USER minetest:minetest
@ -78,5 +78,5 @@ USER minetest:minetest
EXPOSE 30000/udp 30000/tcp EXPOSE 30000/udp 30000/tcp
VOLUME /var/lib/minetest/ /etc/minetest/ VOLUME /var/lib/minetest/ /etc/minetest/
ENTRYPOINT ["/usr/local/bin/minetestserver"] ENTRYPOINT ["/usr/local/bin/luantiserver"]
CMD ["--config", "/etc/minetest/minetest.conf"] CMD ["--config", "/etc/minetest/minetest.conf"]

View file

@ -1,8 +1,8 @@
License of Minetest textures and sounds License of Luanti textures and sounds
--------------------------------------- ---------------------------------------
This applies to textures and sounds contained in the main Minetest This applies to textures and sounds contained in the main Luanti
distribution. distribution.
Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0) Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)
@ -29,7 +29,6 @@ ShadowNinja:
textures/base/pack/smoke_puff.png textures/base/pack/smoke_puff.png
paramat: paramat:
textures/base/pack/menu_header.png
textures/base/pack/next_icon.png textures/base/pack/next_icon.png
textures/base/pack/prev_icon.png textures/base/pack/prev_icon.png
textures/base/pack/clear.png textures/base/pack/clear.png
@ -39,10 +38,10 @@ rubenwardy, paramat:
textures/base/pack/start_icon.png textures/base/pack/start_icon.png
textures/base/pack/end_icon.png textures/base/pack/end_icon.png
erlehmann: erle:
misc/minetest-icon-24x24.png misc/luanti-icon-24x24.png
misc/minetest-icon.ico misc/luanti-icon.ico
misc/minetest.svg misc/luanti.svg
textures/base/pack/logo.png textures/base/pack/logo.png
JRottm: JRottm:
@ -57,20 +56,20 @@ srifqi:
textures/base/pack/minimap_btn.png textures/base/pack/minimap_btn.png
Zughy: Zughy:
textures/base/pack/cdb_add.png
textures/base/pack/cdb_downloading.png textures/base/pack/cdb_downloading.png
textures/base/pack/cdb_queued.png textures/base/pack/cdb_queued.png
textures/base/pack/cdb_update.png textures/base/pack/cdb_update.png
textures/base/pack/cdb_update_cropped.png textures/base/pack/cdb_update_cropped.png
textures/base/pack/cdb_viewonline.png
textures/base/pack/settings_btn.png textures/base/pack/settings_btn.png
textures/base/pack/settings_info.png textures/base/pack/settings_info.png
textures/base/pack/settings_reset.png textures/base/pack/settings_reset.png
textures/base/pack/server_url.png
textures/base/pack/server_view_clients.png
appgurueu: appgurueu:
textures/base/pack/server_incompatible.png textures/base/pack/server_incompatible.png
erlehmann, Warr1024, rollerozxa: erle, Warr1024, rollerozxa:
textures/base/pack/no_screenshot.png textures/base/pack/no_screenshot.png
kilbith: kilbith:
@ -79,7 +78,6 @@ kilbith:
textures/base/pack/progress_bar_bg.png textures/base/pack/progress_bar_bg.png
SmallJoker: SmallJoker:
textures/base/pack/cdb_clear.png
textures/base/pack/server_favorite_delete.png (based on server_favorite.png) textures/base/pack/server_favorite_delete.png (based on server_favorite.png)
DS: DS:
@ -91,12 +89,17 @@ DS:
grorp: grorp:
textures/base/pack/exit_btn.png textures/base/pack/exit_btn.png
textures/base/pack/menu_header.png
using the font "undefined medium" (https://undefined-medium.com/),
which is licensed under the SIL Open Font License, Version 1.1
modified by DS
License of Minetest source code License of Luanti source code
------------------------------- -------------------------------
Minetest Luanti
Copyright (C) 2010-2018 celeron55, Perttu Ahola <celeron55@gmail.com> Copyright (C) 2010-2024 celeron55, Perttu Ahola <celeron55@gmail.com>
and contributors (see source file comments and the version control log)
This program is free software; you can redistribute it and/or modify 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 it under the terms of the GNU Lesser General Public License as published by
@ -115,7 +118,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
Irrlicht Irrlicht
--------------- ---------------
This program uses IrrlichtMt, Minetest's fork of This program uses IrrlichtMt, Luanti's fork of
the Irrlicht Engine. http://irrlicht.sourceforge.net/ the Irrlicht Engine. http://irrlicht.sourceforge.net/
The Irrlicht Engine License The Irrlicht Engine License

View file

@ -1,13 +1,13 @@
Minetest Luanti (formerly Minetest)
======== ==========================
![Build Status](https://github.com/minetest/minetest/workflows/build/badge.svg) ![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) [![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) [![License](https://img.shields.io/badge/license-LGPLv2.1%2B-blue.svg)](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html)
Minetest is a free open-source voxel game engine with easy modding and game creation. Luanti is a free open-source voxel game engine with easy modding and game creation.
Copyright (C) 2010-2022 Perttu Ahola <celeron55@gmail.com> Copyright (C) 2010-2024 Perttu Ahola <celeron55@gmail.com>
and contributors (see source file comments and the version control log) and contributors (see source file comments and the version control log)
Table of Contents Table of Contents
@ -25,9 +25,9 @@ Table of Contents
Further documentation Further documentation
---------------------- ----------------------
- Website: https://www.minetest.net/ - Website: https://www.luanti.org/
- Wiki: https://wiki.minetest.net/ - Wiki: https://wiki.luanti.org/
- Forum: https://forum.minetest.net/ - Forum: https://forum.luanti.org/
- GitHub: https://github.com/minetest/minetest/ - GitHub: https://github.com/minetest/minetest/
- [Developer documentation](doc/developing/) - [Developer documentation](doc/developing/)
- [doc/](doc/) directory of source distribution - [doc/](doc/) directory of source distribution
@ -106,7 +106,7 @@ Configuration file
------------------ ------------------
- Default location: - Default location:
`user/minetest.conf` `user/minetest.conf`
- This file is created by closing Minetest for the first time. - This file is created by closing Luanti for the first time.
- A specific file can be specified on the command line: - A specific file can be specified on the command line:
`--config <path-to-file>` `--config <path-to-file>`
- A run-in-place build will look for the configuration file in - A run-in-place build will look for the configuration file in

View file

@ -95,7 +95,7 @@ task prepareAssets() {
def moPath = "${assetsFolder}/locale/${poFile.parentFile.name}/LC_MESSAGES/" def moPath = "${assetsFolder}/locale/${poFile.parentFile.name}/LC_MESSAGES/"
file(moPath).mkdirs() file(moPath).mkdirs()
exec { exec {
commandLine 'msgfmt', '-o', "${moPath}/minetest.mo", poFile commandLine 'msgfmt', '-o', "${moPath}/luanti.mo", poFile
} }
} }
@ -103,7 +103,7 @@ task prepareAssets() {
} }
task zipAssets(dependsOn: prepareAssets, type: Zip) { task zipAssets(dependsOn: prepareAssets, type: Zip) {
archiveFileName = "Minetest.zip" archiveFileName = "assets.zip"
from assetsFolder from assetsFolder
destinationDirectory = file("src/main/assets") destinationDirectory = file("src/main/assets")
} }
@ -113,7 +113,7 @@ preBuild.dependsOn zipAssets
prepareAssets.dependsOn ':native:getDeps' prepareAssets.dependsOn ':native:getDeps'
clean { clean {
delete new File("src/main/assets", "Minetest.zip") delete new File("src/main/assets", "assets.zip")
} }
dependencies { dependencies {

View file

@ -43,9 +43,6 @@
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
</intent-filter> </intent-filter>
<meta-data
android:name="android.app.lib_name"
android:value="minetest" />
</activity> </activity>
<service <service

View file

@ -52,7 +52,7 @@ import java.util.Objects;
public class GameActivity extends SDLActivity { public class GameActivity extends SDLActivity {
@Override @Override
protected String getMainSharedObject() { protected String getMainSharedObject() {
return getContext().getApplicationInfo().nativeLibraryDir + "/libminetest.so"; return getContext().getApplicationInfo().nativeLibraryDir + "/libluanti.so";
} }
@Override @Override
@ -63,7 +63,7 @@ public class GameActivity extends SDLActivity {
@Override @Override
protected String[] getLibraries() { protected String[] getLibraries() {
return new String[] { return new String[] {
"minetest" "luanti"
}; };
} }

View file

@ -73,7 +73,7 @@ public class UnzipService extends IntentService {
@Override @Override
protected void onHandleIntent(Intent intent) { protected void onHandleIntent(Intent intent) {
Notification.Builder notificationBuilder = createNotification(); Notification.Builder notificationBuilder = createNotification();
final File zipFile = new File(getCacheDir(), "Minetest.zip"); final File zipFile = new File(getCacheDir(), "assets.zip");
try { try {
setIsRunning(true); setIsRunning(true);
File userDataDirectory = Utils.getUserDataDirectory(this); File userDataDirectory = Utils.getUserDataDirectory(this);

View file

@ -277,6 +277,7 @@ public class HIDDeviceManager {
0x044f, // Thrustmaster 0x044f, // Thrustmaster
0x045e, // Microsoft 0x045e, // Microsoft
0x0738, // Mad Catz 0x0738, // Mad Catz
0x0b05, // ASUS
0x0e6f, // PDP 0x0e6f, // PDP
0x0f0d, // Hori 0x0f0d, // Hori
0x10f5, // Turtle Beach 0x10f5, // Turtle Beach
@ -590,7 +591,13 @@ public class HIDDeviceManager {
} else { } else {
flags = 0; flags = 0;
} }
mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), flags)); if (Build.VERSION.SDK_INT >= 33 /* Android 14.0 (U) */) {
Intent intent = new Intent(HIDDeviceManager.ACTION_USB_PERMISSION);
intent.setPackage(mContext.getPackageName());
mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, intent, flags));
} else {
mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), flags));
}
} catch (Exception e) { } catch (Exception e) {
Log.v(TAG, "Couldn't request permission for USB device " + usbDevice); Log.v(TAG, "Couldn't request permission for USB device " + usbDevice);
HIDDeviceOpenResult(deviceID, false); HIDDeviceOpenResult(deviceID, false);

View file

@ -38,6 +38,10 @@ public class SDL {
} }
public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException { public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException {
loadLibrary(libraryName, mContext);
}
public static void loadLibrary(String libraryName, Context context) throws UnsatisfiedLinkError, SecurityException, NullPointerException {
if (libraryName == null) { if (libraryName == null) {
throw new NullPointerException("No library name provided."); throw new NullPointerException("No library name provided.");
@ -53,10 +57,10 @@ public class SDL {
// To use ReLinker, just add it as a dependency. For more information, see // To use ReLinker, just add it as a dependency. For more information, see
// https://github.com/KeepSafe/ReLinker for ReLinker's repository. // https://github.com/KeepSafe/ReLinker for ReLinker's repository.
// //
Class<?> relinkClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker"); Class<?> relinkClass = context.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker");
Class<?> relinkListenerClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener"); Class<?> relinkListenerClass = context.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener");
Class<?> contextClass = mContext.getClassLoader().loadClass("android.content.Context"); Class<?> contextClass = context.getClassLoader().loadClass("android.content.Context");
Class<?> stringClass = mContext.getClassLoader().loadClass("java.lang.String"); Class<?> stringClass = context.getClassLoader().loadClass("java.lang.String");
// Get a 'force' instance of the ReLinker, so we can ensure libraries are reinstalled if // Get a 'force' instance of the ReLinker, so we can ensure libraries are reinstalled if
// they've changed during updates. // they've changed during updates.
@ -66,7 +70,7 @@ public class SDL {
// Actually load the library! // Actually load the library!
Method loadMethod = relinkInstanceClass.getDeclaredMethod("loadLibrary", contextClass, stringClass, stringClass, relinkListenerClass); Method loadMethod = relinkInstanceClass.getDeclaredMethod("loadLibrary", contextClass, stringClass, stringClass, relinkListenerClass);
loadMethod.invoke(relinkInstance, mContext, libraryName, null, null); loadMethod.invoke(relinkInstance, context, libraryName, null, null);
} }
catch (final Throwable e) { catch (final Throwable e) {
// Fall back // Fall back

View file

@ -61,7 +61,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
private static final String TAG = "SDL"; private static final String TAG = "SDL";
private static final int SDL_MAJOR_VERSION = 2; private static final int SDL_MAJOR_VERSION = 2;
private static final int SDL_MINOR_VERSION = 30; private static final int SDL_MINOR_VERSION = 30;
private static final int SDL_MICRO_VERSION = 1; private static final int SDL_MICRO_VERSION = 8;
/* /*
// Display InputType.SOURCE/CLASS of events and devices // Display InputType.SOURCE/CLASS of events and devices
// //
@ -89,7 +89,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
| InputDevice.SOURCE_CLASS_POSITION | InputDevice.SOURCE_CLASS_POSITION
| InputDevice.SOURCE_CLASS_TRACKBALL); | InputDevice.SOURCE_CLASS_TRACKBALL);
if (s2 != 0) cls += "Some_Unkown"; if (s2 != 0) cls += "Some_Unknown";
s2 = s_copy & InputDevice.SOURCE_ANY; // keep source only, no class; s2 = s_copy & InputDevice.SOURCE_ANY; // keep source only, no class;
@ -163,7 +163,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
if (s == FLAG_TAINTED) src += " FLAG_TAINTED"; if (s == FLAG_TAINTED) src += " FLAG_TAINTED";
s2 &= ~FLAG_TAINTED; s2 &= ~FLAG_TAINTED;
if (s2 != 0) src += " Some_Unkown"; if (s2 != 0) src += " Some_Unknown";
Log.v(TAG, prefix + "int=" + s_copy + " CLASS={" + cls + " } source(s):" + src); Log.v(TAG, prefix + "int=" + s_copy + " CLASS={" + cls + " } source(s):" + src);
} }
@ -281,7 +281,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
// Load the .so // Load the .so
public void loadLibraries() { public void loadLibraries() {
for (String lib : getLibraries()) { for (String lib : getLibraries()) {
SDL.loadLibrary(lib); SDL.loadLibrary(lib, this);
} }
} }
@ -995,8 +995,8 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
/* No valid hint, nothing is explicitly allowed */ /* No valid hint, nothing is explicitly allowed */
if (!is_portrait_allowed && !is_landscape_allowed) { if (!is_portrait_allowed && !is_landscape_allowed) {
if (resizable) { if (resizable) {
/* All orientations are allowed */ /* All orientations are allowed, respecting user orientation lock setting */
req = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR; req = ActivityInfo.SCREEN_ORIENTATION_FULL_USER;
} else { } else {
/* Fixed window and nothing specified. Get orientation from w/h of created window */ /* Fixed window and nothing specified. Get orientation from w/h of created window */
req = (w > h ? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT); req = (w > h ? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);
@ -1005,8 +1005,8 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
/* At least one orientation is allowed */ /* At least one orientation is allowed */
if (resizable) { if (resizable) {
if (is_portrait_allowed && is_landscape_allowed) { if (is_portrait_allowed && is_landscape_allowed) {
/* hint allows both landscape and portrait, promote to full sensor */ /* hint allows both landscape and portrait, promote to full user */
req = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR; req = ActivityInfo.SCREEN_ORIENTATION_FULL_USER;
} else { } else {
/* Use the only one allowed "orientation" */ /* Use the only one allowed "orientation" */
req = (is_landscape_allowed ? orientation_landscape : orientation_portrait); req = (is_landscape_allowed ? orientation_landscape : orientation_portrait);

View file

@ -546,13 +546,15 @@ class SDLHapticHandler {
if (haptic == null) { if (haptic == null) {
InputDevice device = InputDevice.getDevice(deviceIds[i]); InputDevice device = InputDevice.getDevice(deviceIds[i]);
Vibrator vib = device.getVibrator(); Vibrator vib = device.getVibrator();
if (vib.hasVibrator()) { if (vib != null) {
haptic = new SDLHaptic(); if (vib.hasVibrator()) {
haptic.device_id = deviceIds[i]; haptic = new SDLHaptic();
haptic.name = device.getName(); haptic.device_id = deviceIds[i];
haptic.vib = vib; haptic.name = device.getName();
mHaptics.add(haptic); haptic.vib = vib;
SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name); mHaptics.add(haptic);
SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
}
} }
} }
} }

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="label">Luanti</string>
<string name="loading">Lädt…</string>
<string name="unzip_notification_title">Luanti lädt</string>
<string name="unzip_notification_description">Weniger als 1 Minute…</string>
<string name="ime_dialog_done">Fertig</string>
<string name="no_web_browser">Kein Web-Browser gefunden</string>
<string name="notification_channel_name">Allgemeine Benachrichtigung</string>
<string name="notification_channel_description">Benachrichtigungen von Luanti</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="unzip_notification_description">Menos de 1 minuto…</string>
<string name="ime_dialog_done">Hecho</string>
<string name="no_web_browser">No se encontró ningún navegador web</string>
<string name="loading">Cargando…</string>
<string name="notification_channel_name">Notificación General</string>
<string name="label">Luanti</string>
<string name="notification_channel_description">Notificaciones de Luanti</string>
<string name="unzip_notification_title">Cargando Luanti</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="no_web_browser">No se encontró ningún navegador web</string>
<string name="loading">Cargando…</string>
<string name="notification_channel_description">Notificaciones de Luanti</string>
<string name="unzip_notification_description">Menos de 1 minuto…</string>
<string name="ime_dialog_done">Hecho</string>
<string name="label">Luanti</string>
<string name="unzip_notification_title">Cargando Luanti</string>
<string name="notification_channel_name">Notificación General</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="loading">Betöltés…</string>
<string name="notification_channel_name">Általános értesítés</string>
<string name="notification_channel_description">Értesítések a Luanti-től</string>
<string name="unzip_notification_title">Luanti betöltése…</string>
<string name="ime_dialog_done">Kész</string>
<string name="no_web_browser">Nem található webböngésző</string>
<string name="label">Luanti</string>
<string name="unzip_notification_description">Kevesebb, mint 1 perc…</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="no_web_browser">Tidak ditemukan peramban web</string>
<string name="ime_dialog_done">Selesai</string>
<string name="label">Luanti</string>
<string name="loading">Memuat…</string>
<string name="notification_channel_name">Pemberitahuan umum</string>
<string name="unzip_notification_description">Kurang dari 1 menit…</string>
<string name="notification_channel_description">Pemberitahuan dari Luanti</string>
<string name="unzip_notification_title">Memuat Luanti…</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="label">Luanti</string>
<string name="notification_channel_description">Pemberitahuan dari Luanti</string>
<string name="unzip_notification_title">Memuatkan Luanti…</string>
<string name="unzip_notification_description">Kurang dari 1 minit…</string>
<string name="no_web_browser">Tiada pelayar sesawang dijumpai</string>
<string name="loading">Memuatkan…</string>
<string name="notification_channel_name">Pemberitahuan umum</string>
<string name="ime_dialog_done">Selesai</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="label">Luanti</string>
<string name="loading">Laster inn …</string>
<string name="notification_channel_name">Generell merknad</string>
<string name="notification_channel_description">Merknader fra Luanti</string>
<string name="unzip_notification_description">Mindre enn ett minutt …</string>
<string name="ime_dialog_done">Ferdig</string>
<string name="no_web_browser">Fant ingen nettleser</string>
<string name="unzip_notification_title">Laster inn Luanti …</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="unzip_notification_title">Загрузка Luanti</string>
<string name="unzip_notification_description">Меньше чам за 1 минуту…</string>
<string name="ime_dialog_done">Готово</string>
<string name="label">Luаnti</string>
<string name="notification_channel_description">Уведомления от Luanti</string>
<string name="notification_channel_name">Основные уведомления</string>
<string name="loading">Загрузка…</string>
<string name="no_web_browser">Не найдено веб-браузера</string>
</resources>

View file

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="label">Minetest</string> <string name="label">Luanti</string>
<string name="loading">Loading&#8230;</string> <string name="loading">Loading&#8230;</string>
<string name="notification_channel_name">General notification</string> <string name="notification_channel_name">General notification</string>
<string name="notification_channel_description">Notifications from Minetest</string> <string name="notification_channel_description">Notifications from Luanti</string>
<string name="unzip_notification_title">Loading Minetest</string> <string name="unzip_notification_title">Loading Luanti</string>
<string name="unzip_notification_description">Less than 1 minute&#8230;</string> <string name="unzip_notification_description">Less than 1 minute&#8230;</string>
<string name="ime_dialog_done">Done</string> <string name="ime_dialog_done">Done</string>
<string name="no_web_browser">No web browser found</string> <string name="no_web_browser">No web browser found</string>

View file

@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // 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("versionMajor", 5) // Version Major
project.ext.set("versionMinor", 10) // Version Minor project.ext.set("versionMinor", 11) // Version Minor
project.ext.set("versionPatch", 0) // Version Patch project.ext.set("versionPatch", 0) // Version Patch
// ^ keep in sync with cmake // ^ keep in sync with cmake
@ -9,7 +9,7 @@ project.ext.set("versionBuild", 0) // Version Build
// ^ fourth version number to allow releasing Android-only fixes and beta versions // ^ fourth version number to allow releasing Android-only fixes and beta versions
buildscript { buildscript {
ext.ndk_version = '26.2.11394342' ext.ndk_version = '27.2.12479018'
repositories { repositories {
google() google()
mavenCentral() mavenCentral()

View file

@ -1,2 +1,2 @@
rootProject.name = "Minetest" rootProject.name = "Luanti"
include ':app', ':native' include ':app', ':native'

View file

@ -1,5 +1,3 @@
-- Minetest: builtin/client/chatcommands.lua
core.register_on_sending_chat_message(function(message) core.register_on_sending_chat_message(function(message)
if message:sub(1,2) == ".." then if message:sub(1,2) == ".." then
return false return false

View file

@ -1,4 +1,3 @@
-- Minetest: builtin/client/init.lua
local scriptpath = core.get_builtin_path() local scriptpath = core.get_builtin_path()
local clientpath = scriptpath.."client"..DIR_DELIM local clientpath = scriptpath.."client"..DIR_DELIM
local commonpath = scriptpath.."common"..DIR_DELIM local commonpath = scriptpath.."common"..DIR_DELIM

View file

@ -155,7 +155,7 @@ end
function core.after(after, func, ...) function core.after(after, func, ...)
assert(tonumber(after) and not core.is_nan(after) and type(func) == "function", assert(tonumber(after) and not core.is_nan(after) and type(func) == "function",
"Invalid minetest.after invocation") "Invalid core.after invocation")
local new_job = { local new_job = {
mod_origin = core.get_last_run_mod(), mod_origin = core.get_last_run_mod(),

View file

@ -1,5 +1,3 @@
-- Minetest: builtin/common/chatcommands.lua
-- For server-side translations (if INIT == "game") -- For server-side translations (if INIT == "game")
-- Otherwise, use core.gettext -- Otherwise, use core.gettext
local S = core.get_translator("__builtin") local S = core.get_translator("__builtin")

View file

@ -1,4 +1,4 @@
--Minetest --Luanti
--Copyright (C) 2013 sapier --Copyright (C) 2013 sapier
-- --
--This program is free software; you can redistribute it and/or modify --This program is free software; you can redistribute it and/or modify

View file

@ -1,4 +1,3 @@
-- Minetest: builtin/item_s.lua
-- The distinction of what goes here is a bit tricky, basically it's everything -- The distinction of what goes here is a bit tricky, basically it's everything
-- that does not (directly or indirectly) need access to ServerEnvironment, -- that does not (directly or indirectly) need access to ServerEnvironment,
-- Server or writable access to IGameDef on the engine side. -- Server or writable access to IGameDef on the engine side.
@ -166,20 +165,19 @@ function core.is_colored_paramtype(ptype)
end end
function core.strip_param2_color(param2, paramtype2) function core.strip_param2_color(param2, paramtype2)
if not core.is_colored_paramtype(paramtype2) then if paramtype2 == "color" then
return param2
elseif paramtype2 == "colorfacedir" then
return math.floor(param2 / 32) * 32
elseif paramtype2 == "color4dir" then
return math.floor(param2 / 4) * 4
elseif paramtype2 == "colorwallmounted" then
return math.floor(param2 / 8) * 8
elseif paramtype2 == "colordegrotate" then
return math.floor(param2 / 32) * 32
else
return nil return nil
end end
if paramtype2 == "colorfacedir" then
param2 = math.floor(param2 / 32) * 32
elseif paramtype2 == "color4dir" then
param2 = math.floor(param2 / 4) * 4
elseif paramtype2 == "colorwallmounted" then
param2 = math.floor(param2 / 8) * 8
elseif paramtype2 == "colordegrotate" then
param2 = math.floor(param2 / 32) * 32
end
-- paramtype2 == "color" requires no modification.
return param2
end end
-- Content ID caching -- Content ID caching

View file

@ -11,8 +11,8 @@ end
core.known_metatables = known_metatables core.known_metatables = known_metatables
function core.register_async_metatable(...) function core.register_async_metatable(...)
core.log("deprecated", "minetest.register_async_metatable is deprecated. " .. core.log("deprecated", "core.register_async_metatable is deprecated. " ..
"Use minetest.register_portable_metatable instead.") "Use core.register_portable_metatable instead.")
return core.register_portable_metatable(...) return core.register_portable_metatable(...)
end end

View file

@ -1,5 +1,3 @@
-- Minetest: builtin/misc_helpers.lua
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Localize functions to avoid table lookups (better performance). -- Localize functions to avoid table lookups (better performance).
local string_sub, string_find = string.sub, string.find local string_sub, string_find = string.sub, string.find
@ -235,6 +233,16 @@ function core.formspec_escape(text)
end end
local hypertext_escapes = {
["\\"] = "\\\\",
["<"] = "\\<",
[">"] = "\\>",
}
function core.hypertext_escape(text)
return text and text:gsub("[\\<>]", hypertext_escapes)
end
function core.wrap_text(text, max_length, as_table) function core.wrap_text(text, max_length, as_table)
local result = {} local result = {}
local line = {} local line = {}
@ -564,12 +572,14 @@ function core.strip_colors(str)
return (str:gsub(ESCAPE_CHAR .. "%([bc]@[^)]+%)", "")) return (str:gsub(ESCAPE_CHAR .. "%([bc]@[^)]+%)", ""))
end end
function core.translate(textdomain, str, ...) local function translate(textdomain, str, num, ...)
local start_seq local start_seq
if textdomain == "" then if textdomain == "" and num == "" then
start_seq = ESCAPE_CHAR .. "T" start_seq = ESCAPE_CHAR .. "T"
else elseif num == "" then
start_seq = ESCAPE_CHAR .. "(T@" .. textdomain .. ")" start_seq = ESCAPE_CHAR .. "(T@" .. textdomain .. ")"
else
start_seq = ESCAPE_CHAR .. "(T@" .. textdomain .. "@" .. num .. ")"
end end
local arg = {n=select('#', ...), ...} local arg = {n=select('#', ...), ...}
local end_seq = ESCAPE_CHAR .. "E" local end_seq = ESCAPE_CHAR .. "E"
@ -600,8 +610,31 @@ function core.translate(textdomain, str, ...)
return start_seq .. translated .. end_seq return start_seq .. translated .. end_seq
end end
function core.translate(textdomain, str, ...)
return translate(textdomain, str, "", ...)
end
function core.translate_n(textdomain, str, str_plural, n, ...)
assert (type(n) == "number")
assert (n >= 0)
assert (math.floor(n) == n)
-- Truncate n if too large
local max = 1000000
if n >= 2 * max then
n = n % max + max
end
if n == 1 then
return translate(textdomain, str, "1", ...)
else
return translate(textdomain, str_plural, tostring(n), ...)
end
end
function core.get_translator(textdomain) function core.get_translator(textdomain)
return function(str, ...) return core.translate(textdomain or "", str, ...) end return
(function(str, ...) return core.translate(textdomain or "", str, ...) end),
(function(str, str_plural, n, ...) return core.translate_n(textdomain or "", str, str_plural, n, ...) end)
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------

View file

@ -204,18 +204,18 @@ local function dummy_func() end
function core.deserialize(str, safe) function core.deserialize(str, safe)
-- Backwards compatibility -- Backwards compatibility
if str == nil then if str == nil then
core.log("deprecated", "minetest.deserialize called with nil (expected string).") core.log("deprecated", "core.deserialize called with nil (expected string).")
return nil, "Invalid type: Expected a string, got nil" return nil, "Invalid type: Expected a string, got nil"
end end
local t = type(str) local t = type(str)
if t ~= "string" then if t ~= "string" then
error(("minetest.deserialize called with %s (expected string)."):format(t)) error(("core.deserialize called with %s (expected string)."):format(t))
end end
local func, err = loadstring(str) local func, err = loadstring(str)
if not func then return nil, err end if not func then return nil, err end
-- math.huge was serialized to inf and NaNs to nan by Lua in Minetest 5.6, so we have to support this here -- math.huge was serialized to inf and NaNs to nan by Lua in engine version 5.6, so we have to support this here
local env = {inf = math_huge, nan = 0/0} local env = {inf = math_huge, nan = 0/0}
if safe then if safe then
env.loadstring = dummy_func env.loadstring = dummy_func

View file

@ -1,4 +1,4 @@
--Minetest --Luanti
--Copyright (C) 2014 sapier --Copyright (C) 2014 sapier
--Copyright (C) 2023 Gregor Parzefall --Copyright (C) 2023 Gregor Parzefall
-- --

View file

@ -1,4 +1,4 @@
--Minetest --Luanti
--Copyright (C) 2014 sapier --Copyright (C) 2014 sapier
-- --
--This program is free software; you can redistribute it and/or modify --This program is free software; you can redistribute it and/or modify

View file

@ -1,4 +1,4 @@
--Minetest --Luanti
--Copyright (C) 2014 sapier --Copyright (C) 2014 sapier
-- --
--This program is free software; you can redistribute it and/or modify --This program is free software; you can redistribute it and/or modify
@ -66,13 +66,13 @@ local function get_formspec(self)
local content, prepend = tab.get_formspec(self, tab.name, tab.tabdata, tab.tabsize) local content, prepend = tab.get_formspec(self, tab.name, tab.tabdata, tab.tabsize)
local ENABLE_TOUCH = core.settings:get_bool("enable_touch") local TOUCH_GUI = core.settings:get_bool("touch_gui")
local orig_tsize = tab.tabsize or { width = self.width, height = self.height } local orig_tsize = tab.tabsize or { width = self.width, height = self.height }
local tsize = { width = orig_tsize.width, height = orig_tsize.height } local tsize = { width = orig_tsize.width, height = orig_tsize.height }
tsize.height = tsize.height tsize.height = tsize.height
+ TABHEADER_H -- tabheader included in formspec size + TABHEADER_H -- tabheader included in formspec size
+ (ENABLE_TOUCH and GAMEBAR_OFFSET_TOUCH or GAMEBAR_OFFSET_DESKTOP) + (TOUCH_GUI and GAMEBAR_OFFSET_TOUCH or GAMEBAR_OFFSET_DESKTOP)
+ GAMEBAR_H -- gamebar included in formspec size + GAMEBAR_H -- gamebar included in formspec size
if self.parent == nil and not prepend then if self.parent == nil and not prepend then

View file

@ -1,4 +1,4 @@
--Minetest --Luanti
--Copyright (C) 2014 sapier --Copyright (C) 2014 sapier
-- --
--This program is free software; you can redistribute it and/or modify --This program is free software; you can redistribute it and/or modify

View file

@ -10,7 +10,7 @@ end
function core.handle_async(func, callback, ...) function core.handle_async(func, callback, ...)
assert(type(func) == "function" and type(callback) == "function", assert(type(func) == "function" and type(callback) == "function",
"Invalid minetest.handle_async invocation") "Invalid core.handle_async invocation")
local args = {n = select("#", ...), ...} local args = {n = select("#", ...), ...}
local mod_origin = core.get_last_run_mod() local mod_origin = core.get_last_run_mod()

View file

@ -1,5 +1,3 @@
-- Minetest: builtin/auth.lua
-- --
-- Builtin authentication handler -- Builtin authentication handler
-- --
@ -95,11 +93,11 @@ core.builtin_auth_handler = {
for priv, value in pairs(privileges) do for priv, value in pairs(privileges) do
-- Warnings for improper API usage -- Warnings for improper API usage
if value == false then if value == false then
core.log('deprecated', "`false` value given to `minetest.set_player_privs`, ".. core.log('deprecated', "`false` value given to `core.set_player_privs`, "..
"this is almost certainly a bug, ".. "this is almost certainly a bug, "..
"granting a privilege rather than revoking it") "granting a privilege rather than revoking it")
elseif value ~= true then elseif value ~= true then
core.log('deprecated', "non-`true` value given to `minetest.set_player_privs`") core.log('deprecated', "non-`true` value given to `core.set_player_privs`")
end end
-- Run grant callbacks -- Run grant callbacks
if prev_privs[priv] == nil then if prev_privs[priv] == nil then
@ -196,7 +194,7 @@ function core.change_player_privs(name, changes)
elseif change == false then elseif change == false then
privs[priv] = nil privs[priv] = nil
else else
error("non-bool value given to `minetest.change_player_privs`") error("non-bool value given to `core.change_player_privs`")
end end
end end
core.set_player_privs(name, privs) core.set_player_privs(name, privs)

View file

@ -1,5 +1,3 @@
-- Minetest: builtin/game/chat.lua
local S = core.get_translator("__builtin") local S = core.get_translator("__builtin")
-- Helper function that implements search and replace without pattern matching -- Helper function that implements search and replace without pattern matching

View file

@ -1,5 +1,3 @@
-- Minetest: builtin/constants.lua
-- --
-- Constants values for use with the Lua API -- Constants values for use with the Lua API
-- --

View file

@ -1,5 +1,3 @@
-- Minetest: builtin/deprecated.lua
-- --
-- EnvRef -- EnvRef
-- --
@ -35,9 +33,9 @@ local settings = core.settings
local function setting_proxy(name) local function setting_proxy(name)
return function(...) return function(...)
core.log("deprecated", "WARNING: minetest.setting_* ".. core.log("deprecated", "WARNING: core.setting_* "..
"functions are deprecated. ".. "functions are deprecated. "..
"Use methods on the minetest.settings object.") "Use methods on the core.settings object.")
return settings[name](settings, ...) return settings[name](settings, ...)
end end
end end

View file

@ -1,5 +1,3 @@
-- Minetest: builtin/detached_inventory.lua
core.detached_inventories = {} core.detached_inventories = {}
local create_detached_inventory_raw = core.create_detached_inventory_raw local create_detached_inventory_raw = core.create_detached_inventory_raw

View file

@ -1,5 +1,3 @@
-- Minetest: builtin/item.lua
local builtin_shared = ... local builtin_shared = ...
local SCALE = 0.667 local SCALE = 0.667

View file

@ -1,5 +1,3 @@
-- Minetest: builtin/features.lua
core.features = { core.features = {
glasslike_framed = true, glasslike_framed = true,
nodebox_as_selectionbox = true, nodebox_as_selectionbox = true,
@ -45,6 +43,8 @@ core.features = {
hotbar_hud_element = true, hotbar_hud_element = true,
bulk_lbms = true, bulk_lbms = true,
abm_without_neighbors = true, abm_without_neighbors = true,
biome_weights = true,
particle_blend_clip = true,
} }
function core.has_feature(arg) function core.has_feature(arg)

View file

@ -1,5 +1,3 @@
-- Minetest: builtin/item.lua
local builtin_shared = ... local builtin_shared = ...
local function copy_pointed_thing(pointed_thing) local function copy_pointed_thing(pointed_thing)

View file

@ -1,5 +1,3 @@
-- Minetest: builtin/item_entity.lua
function core.spawn_item(pos, item) function core.spawn_item(pos, item)
-- Take item in any format -- Take item in any format
local stack = ItemStack(item) local stack = ItemStack(item)

View file

@ -1,5 +1,3 @@
-- Minetest: builtin/misc.lua
local S = core.get_translator("__builtin") local S = core.get_translator("__builtin")
-- --

View file

@ -1,4 +1,3 @@
-- Minetest: builtin/misc_s.lua
-- The distinction of what goes here is a bit tricky, basically it's everything -- The distinction of what goes here is a bit tricky, basically it's everything
-- that does not (directly or indirectly) need access to ServerEnvironment, -- that does not (directly or indirectly) need access to ServerEnvironment,
-- Server or writable access to IGameDef on the engine side. -- Server or writable access to IGameDef on the engine side.
@ -25,11 +24,8 @@ end
function core.get_item_group(name, group) function core.get_item_group(name, group)
if not core.registered_items[name] or not local def = core.registered_items[name]
core.registered_items[name].groups[group] then return def and def.groups[group] or 0
return 0
end
return core.registered_items[name].groups[group]
end end
@ -40,11 +36,7 @@ end
function core.setting_get_pos(name) function core.setting_get_pos(name)
local value = core.settings:get(name) return core.settings:get_pos(name)
if not value then
return nil
end
return core.string_to_pos(value)
end end

View file

@ -1,5 +1,3 @@
-- Minetest: builtin/privileges.lua
local S = core.get_translator("__builtin") local S = core.get_translator("__builtin")
-- --

View file

@ -1,5 +1,3 @@
-- Minetest: builtin/register.lua
local builtin_shared = ... local builtin_shared = ...
local S = core.get_translator("__builtin") local S = core.get_translator("__builtin")

View file

@ -1,9 +1,7 @@
-- Minetest: builtin/static_spawn.lua
local static_spawnpoint_string = core.settings:get("static_spawnpoint") local static_spawnpoint_string = core.settings:get("static_spawnpoint")
if static_spawnpoint_string and if static_spawnpoint_string and
static_spawnpoint_string ~= "" and static_spawnpoint_string ~= "" and
not core.setting_get_pos("static_spawnpoint") then not core.settings:get_pos("static_spawnpoint") then
error('The static_spawnpoint setting is invalid: "' .. error('The static_spawnpoint setting is invalid: "' ..
static_spawnpoint_string .. '"') static_spawnpoint_string .. '"')
end end

View file

@ -35,7 +35,7 @@ end
local log = function(...) end local log = function(...) end
--local log = print --local log = print
minetest.register_allow_player_inventory_action(function(_, action, inv, info) core.register_allow_player_inventory_action(function(_, action, inv, info)
log("\tallow " .. action, info.count or info.stack:to_string()) log("\tallow " .. action, info.count or info.stack:to_string())
if action == "move" then if action == "move" then
@ -69,7 +69,7 @@ minetest.register_allow_player_inventory_action(function(_, action, inv, info)
return -- Unlimited return -- Unlimited
end) end)
minetest.register_on_player_inventory_action(function(_, action, inv, info) core.register_on_player_inventory_action(function(_, action, inv, info)
log("\ton " .. action, info.count or info.stack:to_string()) log("\ton " .. action, info.count or info.stack:to_string())
if action == "take" or action == "put" then if action == "take" or action == "put" then

View file

@ -1,5 +1,5 @@
-- --
-- This file contains built-in stuff in Minetest implemented in Lua. -- This file contains built-in stuff in Luanti implemented in Lua.
-- --
-- It is always loaded and executed after registration of the C API, -- It is always loaded and executed after registration of the C API,
-- before loading and running any mods. -- before loading and running any mods.

View file

@ -11,11 +11,6 @@ end
core.async_event_handler = handle_job core.async_event_handler = handle_job
function core.handle_async(func, parameter, callback) function core.handle_async(func, parameter, callback)
-- Serialize function
local serialized_func = string.dump(func)
assert(serialized_func ~= nil)
-- Serialize parameters -- Serialize parameters
local serialized_param = core.serialize(parameter) local serialized_param = core.serialize(parameter)
@ -23,7 +18,7 @@ function core.handle_async(func, parameter, callback)
return false return false
end end
local jobid = core.do_async_callback(serialized_func, serialized_param) local jobid = core.do_async_callback(func, serialized_param)
core.async_jobs[jobid] = callback core.async_jobs[jobid] = callback

View file

@ -1,4 +1,4 @@
--Minetest --Luanti
--Copyright (C) 2014 sapier --Copyright (C) 2014 sapier
-- --
--This program is free software; you can redistribute it and/or modify --This program is free software; you can redistribute it and/or modify

View file

@ -1,4 +1,4 @@
--Minetest --Luanti
--Copyright (C) 2018-24 rubenwardy --Copyright (C) 2018-24 rubenwardy
-- --
--This program is free software; you can redistribute it and/or modify --This program is free software; you can redistribute it and/or modify
@ -182,6 +182,23 @@ function contentdb.get_package_by_id(id)
end end
function contentdb.calculate_package_id(type, author, name)
local id = author:lower() .. "/"
if (type == nil or type == "game") and #name > 5 and name:sub(#name - 4) == "_game" then
id = id .. name:sub(1, #name - 5)
else
id = id .. name
end
return id
end
function contentdb.get_package_by_info(author, name)
local id = contentdb.calculate_package_id(nil, author, name)
return contentdb.package_by_id[id]
end
-- Create a coroutine from `fn` and provide results to `callback` when complete (dead). -- Create a coroutine from `fn` and provide results to `callback` when complete (dead).
-- Returns a resumer function. -- Returns a resumer function.
local function make_callback_coroutine(fn, callback) local function make_callback_coroutine(fn, callback)
@ -375,7 +392,7 @@ function contentdb.resolve_dependencies(package, game, callback)
end end
local function fetch_pkgs(params) local function fetch_pkgs()
local version = core.get_version() local version = core.get_version()
local base_url = core.settings:get("contentdb_url") local base_url = core.settings:get("contentdb_url")
local url = base_url .. local url = base_url ..
@ -412,49 +429,43 @@ local function fetch_pkgs(params)
if not packages or #packages == 0 then if not packages or #packages == 0 then
return return
end end
local aliases = {} return packages
end
function contentdb.set_packages_from_api(packages)
contentdb.package_by_id = {}
contentdb.aliases = {}
for _, package in pairs(packages) do for _, package in pairs(packages) do
local name_len = #package.name package.id = contentdb.calculate_package_id(package.type, package.author, package.name)
-- This must match what contentdb.update_paths() does!
package.id = package.author:lower() .. "/"
if package.type == "game" and name_len > 5 and package.name:sub(name_len - 4) == "_game" then
package.id = package.id .. package.name:sub(1, name_len - 5)
else
package.id = package.id .. package.name
end
package.url_part = core.urlencode(package.author) .. "/" .. core.urlencode(package.name) package.url_part = core.urlencode(package.author) .. "/" .. core.urlencode(package.name)
contentdb.package_by_id[package.id] = package
if package.aliases then if package.aliases then
for _, alias in ipairs(package.aliases) do for _, alias in ipairs(package.aliases) do
-- We currently don't support name changing -- We currently don't support name changing
local suffix = "/" .. package.name local suffix = "/" .. package.name
if alias:sub(-#suffix) == suffix then if alias:sub(-#suffix) == suffix then
aliases[alias:lower()] = package.id contentdb.aliases[alias:lower()] = package.id
end end
end end
end end
end end
return { packages = packages, aliases = aliases } contentdb.load_ok = true
contentdb.load_error = false
contentdb.packages = packages
contentdb.packages_full = packages
contentdb.packages_full_unordered = packages
end end
function contentdb.fetch_pkgs(callback) function contentdb.fetch_pkgs(callback)
contentdb.loading = true contentdb.loading = true
core.handle_async(fetch_pkgs, nil, function(result) core.handle_async(fetch_pkgs, nil, function(result)
if result then if result then
contentdb.load_ok = true contentdb.set_packages_from_api(result)
contentdb.load_error = false
contentdb.packages = result.packages
contentdb.packages_full = result.packages
contentdb.packages_full_unordered = result.packages
contentdb.aliases = result.aliases
for _, package in ipairs(result.packages) do
contentdb.package_by_id[package.id] = package
end
else else
contentdb.load_error = true contentdb.load_error = true
end end
@ -554,30 +565,107 @@ function contentdb.filter_packages(query, by_type)
end end
local keywords = {} local keywords = {}
for word in query:lower():gmatch("%S+") do for word in query:gmatch("%S+") do
table.insert(keywords, word) table.insert(keywords, word:lower())
end
local function contains_all_keywords(str)
str = str:lower()
for _, keyword in ipairs(keywords) do
if not str:find(keyword, 1, true) then
return false
end
end
return true
end end
local function matches_keywords(package) local function matches_keywords(package)
for k = 1, #keywords do return contains_all_keywords(package.name) or
local keyword = keywords[k] contains_all_keywords(package.title) or
contains_all_keywords(package.author) or
if string.find(package.name:lower(), keyword, 1, true) or contains_all_keywords(package.short_description)
string.find(package.title:lower(), keyword, 1, true) or
string.find(package.author:lower(), keyword, 1, true) or
string.find(package.short_description:lower(), keyword, 1, true) then
return true
end
end
return false
end end
contentdb.packages = {} contentdb.packages = {}
for _, package in pairs(contentdb.packages_full) do for _, package in pairs(contentdb.packages_full) do
if (query == "" or matches_keywords(package)) and if (query == "" or matches_keywords(package)) and
(by_type == nil or package.type == by_type) then (by_type == nil or package.type == by_type) then
contentdb.packages[#contentdb.packages + 1] = package table.insert(contentdb.packages, package)
end end
end end
end 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" }
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
end
return core.parse_json(response.data)
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)
end
end
function contentdb.get_formspec_padding()
-- Padding is increased on Android to account for notches
-- TODO: use Android API to determine size of cut outs
return { x = PLATFORM == "Android" and 1 or 0.5, y = PLATFORM == "Android" and 0.25 or 0.5 }
end
function contentdb.get_formspec_size()
local window = core.get_window_info()
local size = { x = window.max_formspec_size.x, y = window.max_formspec_size.y }
-- Minimum formspec size
local min_x = 15.5
local min_y = 10
if size.x < min_x or size.y < min_y then
local scale = math.max(min_x / size.x, min_y / size.y)
size.x = size.x * scale
size.y = size.y * scale
end
return size
end

View file

@ -1,4 +1,4 @@
--Minetest --Luanti
--Copyright (C) 2018-20 rubenwardy --Copyright (C) 2018-20 rubenwardy
-- --
--This program is free software; you can redistribute it and/or modify --This program is free software; you can redistribute it and/or modify
@ -18,7 +18,7 @@
if not core.get_http_api then if not core.get_http_api then
function create_contentdb_dlg() function create_contentdb_dlg()
return messagebox("contentdb", return messagebox("contentdb",
fgettext("ContentDB is not available when Minetest was compiled without cURL")) fgettext("ContentDB is not available when Luanti was compiled without cURL"))
end end
return return
end end
@ -26,68 +26,20 @@ end
-- Filter -- Filter
local search_string = "" local search_string = ""
local cur_page = 1 local cur_page = 1
local num_per_page = 5 local filter_type
local filter_type = 1
local filter_types_titles = {
fgettext("All packages"),
fgettext("Games"),
fgettext("Mods"),
fgettext("Texture packs"),
}
-- Automatic package installation -- Automatic package installation
local auto_install_spec = nil local auto_install_spec = nil
local filter_types_type = {
nil, local filter_type_names = {
"game", { "type_all", nil },
"mod", { "type_game", "game" },
"txp", { "type_mod", "mod" },
{ "type_txp", "txp" },
} }
local function install_or_update_package(this, package)
local install_parent
if package.type == "mod" then
install_parent = core.get_modpath()
elseif package.type == "game" then
install_parent = core.get_gamepath()
elseif package.type == "txp" then
install_parent = core.get_texturepath()
else
error("Unknown package type: " .. package.type)
end
if package.queued or package.downloading then
return
end
local function on_confirm()
local dlg = create_install_dialog(package)
dlg:set_parent(this)
this:hide()
dlg:show()
dlg:load_deps()
end
if package.type == "mod" and #pkgmgr.games == 0 then
local dlg = messagebox("install_game",
fgettext("You need to install a game before you can install a mod"))
dlg:set_parent(this)
this:hide()
dlg:show()
elseif not package.path and core.is_dir(install_parent .. DIR_DELIM .. package.name) then
local dlg = create_confirm_overwrite(package, on_confirm)
dlg:set_parent(this)
this:hide()
dlg:show()
else
on_confirm()
end
end
-- Resolves the package specification stored in auto_install_spec into an actual package. -- Resolves the package specification stored in auto_install_spec into an actual package.
-- May only be called after the package list has been loaded successfully. -- May only be called after the package list has been loaded successfully.
local function resolve_auto_install_spec() local function resolve_auto_install_spec()
@ -145,7 +97,7 @@ end
local function sort_and_filter_pkgs() local function sort_and_filter_pkgs()
contentdb.update_paths() contentdb.update_paths()
contentdb.sort_packages() contentdb.sort_packages()
contentdb.filter_packages(search_string, filter_types_type[filter_type]) contentdb.filter_packages(search_string, filter_type)
local auto_install_pkg = resolve_auto_install_spec() local auto_install_pkg = resolve_auto_install_spec()
if auto_install_pkg then if auto_install_pkg then
@ -176,72 +128,151 @@ local function load()
end end
local function get_info_formspec(text) local function get_info_formspec(size, padding, text)
local H = 9.5
return table.concat({ return table.concat({
"formspec_version[6]", "formspec_version[6]",
"size[15.75,9.5]", "size[", size.x, ",", size.y, "]",
core.settings:get_bool("touch_gui") and "padding[0.01,0.01]" or "position[0.5,0.55]", "padding[0,0]",
"bgcolor[;true]",
"label[4,4.35;", text, "]", "label[", padding.x + 3.625, ",4.35;", text, "]",
"container[0,", H - 0.8 - 0.375, "]", "container[", padding.x, ",", size.y - 0.8 - padding.y, "]",
"button[0.375,0;5,0.8;back;", fgettext("Back to Main Menu"), "]", "button[0,0;2,0.8;back;", fgettext("Back"), "]",
"container_end[]", "container_end[]",
}) })
end end
-- Determines how to fit `num_per_page` into `size` space
local function fit_cells(num_per_page, size)
local cell_spacing = 0.5
local columns = 1
local cell_w, cell_h
-- Fit cells into the available height
while true do
cell_w = (size.x - (columns-1)*cell_spacing) / columns
cell_h = cell_w / 4
local required_height = math.ceil(num_per_page / columns) * (cell_h + cell_spacing) - cell_spacing
-- Add 0.1 to be more lenient
if required_height <= size.y + 0.1 then
break
end
columns = columns + 1
end
return cell_spacing, columns, cell_w, cell_h
end
local function calculate_num_per_page()
local size = contentdb.get_formspec_size()
local padding = contentdb.get_formspec_padding()
local window = core.get_window_info()
size.x = size.x - padding.x * 2
size.y = size.y - padding.y * 2 - 1.425 - 0.25 - 0.8
local coordToPx = window.size.x / window.max_formspec_size.x / window.real_gui_scaling
local num_per_page = 12
while num_per_page > 2 do
local _, _, cell_w, _ = fit_cells(num_per_page, size)
if cell_w * coordToPx > 350 then
break
end
num_per_page = num_per_page - 1
end
return num_per_page
end
local function get_formspec(dlgdata) local function get_formspec(dlgdata)
local window_padding = contentdb.get_formspec_padding()
local size = contentdb.get_formspec_size()
if contentdb.loading then if contentdb.loading then
return get_info_formspec(fgettext("Loading...")) return get_info_formspec(size, window_padding, fgettext("Loading..."))
end end
if contentdb.load_error then if contentdb.load_error then
return get_info_formspec(fgettext("No packages could be retrieved")) return get_info_formspec(size, window_padding, fgettext("No packages could be retrieved"))
end end
assert(contentdb.load_ok) assert(contentdb.load_ok)
contentdb.update_paths() contentdb.update_paths()
local num_per_page = dlgdata.num_per_page
dlgdata.pagemax = math.max(math.ceil(#contentdb.packages / num_per_page), 1) dlgdata.pagemax = math.max(math.ceil(#contentdb.packages / num_per_page), 1)
if cur_page > dlgdata.pagemax then if cur_page > dlgdata.pagemax then
cur_page = 1 cur_page = 1
end end
local W = 15.75 local W = size.x - window_padding.x * 2
local H = 9.5 local H = size.y - window_padding.y * 2
local category_x = 0
local number_category_buttons = 4
local max_button_w = (W - 0.375 - 0.25 - 7) / number_category_buttons
local category_button_w = math.min(max_button_w, 3)
local function make_category_button(name, label, selected)
category_x = category_x + 1
local color = selected and mt_color_green or ""
return ("style[%s;bgcolor=%s]button[%f,0;%f,0.8;%s;%s]"):format(name, color,
(category_x - 1) * category_button_w, category_button_w, name, label)
end
local selected_type = filter_type
local search_box_width = W - 0.375 - 0.25 - 2*0.8
- number_category_buttons * category_button_w
local formspec = { local formspec = {
"formspec_version[6]", "formspec_version[7]",
"size[15.75,9.5]", "size[", size.x, ",", size.y, "]",
core.settings:get_bool("touch_gui") and "padding[0.01,0.01]" or "position[0.5,0.55]", "padding[0,0]",
"bgcolor[;true]",
"style[status,downloading,queued;border=false]", "container[", window_padding.x, ",", window_padding.y, "]",
"container[0.375,0.375]", -- Top-left: categories
"field[0,0;7.225,0.8;search_string;;", core.formspec_escape(search_string), "]", make_category_button("type_all", fgettext("All"), selected_type == nil),
make_category_button("type_game", fgettext("Games"), selected_type == "game"),
make_category_button("type_mod", fgettext("Mods"), selected_type == "mod"),
make_category_button("type_txp", fgettext("Texture Packs"), selected_type == "txp"),
-- Top-right: Search
"container[", W - search_box_width - 0.8*2, ",0]",
"field[0,0;", search_box_width, ",0.8;search_string;;", core.formspec_escape(search_string), "]",
"field_enter_after_edit[search_string;true]", "field_enter_after_edit[search_string;true]",
"image_button[7.3,0;0.8,0.8;", core.formspec_escape(defaulttexturedir .. "search.png"), ";search;]", "image_button[", search_box_width, ",0;0.8,0.8;",
"image_button[8.125,0;0.8,0.8;", core.formspec_escape(defaulttexturedir .. "clear.png"), ";clear;]", core.formspec_escape(defaulttexturedir .. "search.png"), ";search;]",
"dropdown[9.175,0;2.7875,0.8;type;", table.concat(filter_types_titles, ","), ";", filter_type, "]", "image_button[", search_box_width + 0.8, ",0;0.8,0.8;",
core.formspec_escape(defaulttexturedir .. "clear.png"), ";clear;]",
"container_end[]", "container_end[]",
-- Page nav buttons -- Bottom strip start
"container[0,", H - 0.8 - 0.375, "]", "container[0,", H - 0.8, "]",
"button[0.375,0;5,0.8;back;", fgettext("Back to Main Menu"), "]", "button[0,0;2,0.8;back;", fgettext("Back"), "]",
"container[", W - 0.375 - 0.8*4 - 2, ",0]", -- Bottom-center: Page nav buttons
"image_button[0,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "start_icon.png;pstart;]", "container[", (W - 1*4 - 2) / 2, ",0]",
"image_button[0.8,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "prev_icon.png;pback;]", "image_button[0,0;1,0.8;", core.formspec_escape(defaulttexturedir), "start_icon.png;pstart;]",
"image_button[1,0;1,0.8;", core.formspec_escape(defaulttexturedir), "prev_icon.png;pback;]",
"style[pagenum;border=false]", "style[pagenum;border=false]",
"button[1.6,0;2,0.8;pagenum;", tonumber(cur_page), " / ", tonumber(dlgdata.pagemax), "]", "button[2,0;2,0.8;pagenum;", tonumber(cur_page), " / ", tonumber(dlgdata.pagemax), "]",
"image_button[3.6,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "next_icon.png;pnext;]", "image_button[4,0;1,0.8;", core.formspec_escape(defaulttexturedir), "next_icon.png;pnext;]",
"image_button[4.4,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "end_icon.png;pend;]", "image_button[5,0;1,0.8;", core.formspec_escape(defaulttexturedir), "end_icon.png;pend;]",
"container_end[]", "container_end[]", -- page nav end
"container_end[]", -- Bottom-right: updating
"container[", W - 3, ",0]",
"style[status,downloading,queued;border=false]",
} }
if contentdb.number_downloading > 0 then if contentdb.number_downloading > 0 then
formspec[#formspec + 1] = "button[12.5875,0.375;2.7875,0.8;downloading;" formspec[#formspec + 1] = "button[0,0;3,0.8;downloading;"
if #contentdb.download_queue > 0 then if #contentdb.download_queue > 0 then
formspec[#formspec + 1] = fgettext("$1 downloading,\n$2 queued", formspec[#formspec + 1] = fgettext("$1 downloading,\n$2 queued",
contentdb.number_downloading, #contentdb.download_queue) contentdb.number_downloading, #contentdb.download_queue)
@ -260,16 +291,19 @@ local function get_formspec(dlgdata)
end end
if num_avail_updates == 0 then if num_avail_updates == 0 then
formspec[#formspec + 1] = "button[12.5875,0.375;2.7875,0.8;status;" formspec[#formspec + 1] = "button[0,0;3,0.8;status;"
formspec[#formspec + 1] = fgettext("No updates") formspec[#formspec + 1] = fgettext("No updates")
formspec[#formspec + 1] = "]" formspec[#formspec + 1] = "]"
else else
formspec[#formspec + 1] = "button[12.5875,0.375;2.7875,0.8;update_all;" formspec[#formspec + 1] = "button[0,0;3,0.8;update_all;"
formspec[#formspec + 1] = fgettext("Update All [$1]", num_avail_updates) formspec[#formspec + 1] = fgettext("Update All [$1]", num_avail_updates)
formspec[#formspec + 1] = "]" formspec[#formspec + 1] = "]"
end end
end end
formspec[#formspec + 1] = "container_end[]" -- updating end
formspec[#formspec + 1] = "container_end[]" -- bottom strip end
if #contentdb.packages == 0 then if #contentdb.packages == 0 then
formspec[#formspec + 1] = "label[4,4.75;" formspec[#formspec + 1] = "label[4,4.75;"
formspec[#formspec + 1] = fgettext("No results") formspec[#formspec + 1] = fgettext("No results")
@ -281,81 +315,86 @@ local function get_formspec(dlgdata)
formspec[#formspec + 1] = "tooltip[downloading;" .. fgettext("Downloading...") .. tooltip_colors formspec[#formspec + 1] = "tooltip[downloading;" .. fgettext("Downloading...") .. tooltip_colors
formspec[#formspec + 1] = "tooltip[queued;" .. fgettext("Queued") .. tooltip_colors formspec[#formspec + 1] = "tooltip[queued;" .. fgettext("Queued") .. tooltip_colors
formspec[#formspec + 1] = "container[0,1.425]"
local cell_spacing, columns, cell_w, cell_h = fit_cells(num_per_page, {
x = W,
y = H - 1.425 - 0.25 - 0.8
})
local img_w = cell_h * 3 / 2
local start_idx = (cur_page - 1) * num_per_page + 1 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 for i=start_idx, math.min(#contentdb.packages, start_idx+num_per_page-1) do
local package = contentdb.packages[i] local package = contentdb.packages[i]
local container_y = (i - start_idx) * 1.375 + (2*0.375 + 0.8)
formspec[#formspec + 1] = "container[0.375,"
formspec[#formspec + 1] = container_y
formspec[#formspec + 1] = "]"
-- image table.insert_all(formspec, {
formspec[#formspec + 1] = "image[0,0;1.5,1;" "container[",
formspec[#formspec + 1] = core.formspec_escape(get_screenshot(package)) (cell_w + cell_spacing) * ((i - start_idx) % columns),
formspec[#formspec + 1] = "]" ",",
(cell_h + cell_spacing) * math.floor((i - start_idx) / columns),
"]",
-- title "box[0,0;", cell_w, ",", cell_h, ";#ffffff11]",
formspec[#formspec + 1] = "label[1.875,0.1;"
formspec[#formspec + 1] = core.formspec_escape(
core.colorize(mt_color_green, package.title) ..
core.colorize("#BFBFBF", " by " .. package.author))
formspec[#formspec + 1] = "]"
-- buttons -- image,
local description_width = W - 2.625 - 2 * 0.7 - 2 * 0.15 "image[0,0;", img_w, ",", cell_h, ";",
core.formspec_escape(get_screenshot(package, package.thumbnail, 2)), "]",
local second_base = "image_button[-1.55,0;0.7,0.7;" .. core.formspec_escape(defaulttexturedir) "label[", img_w + 0.25 + 0.05, ",0.5;",
local third_base = "image_button[-2.4,0;0.7,0.7;" .. core.formspec_escape(defaulttexturedir) core.formspec_escape(
formspec[#formspec + 1] = "container[" core.colorize(mt_color_green, package.title) ..
formspec[#formspec + 1] = W - 0.375*2 core.colorize("#BFBFBF", " by " .. package.author)), "]",
formspec[#formspec + 1] = ",0.1]"
if package.downloading then "textarea[", img_w + 0.25, ",0.75;", cell_w - img_w - 0.25, ",", cell_h - 0.75, ";;;",
formspec[#formspec + 1] = "animated_image[-1.7,-0.15;1,1;downloading;" core.formspec_escape(package.short_description), "]",
formspec[#formspec + 1] = core.formspec_escape(defaulttexturedir)
formspec[#formspec + 1] = "cdb_downloading.png;3;400;]"
elseif package.queued then
formspec[#formspec + 1] = second_base
formspec[#formspec + 1] = "cdb_queued.png;queued;]"
elseif not package.path then
local elem_name = "install_" .. i .. ";"
formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#71aa34]"
formspec[#formspec + 1] = second_base .. "cdb_add.png;" .. elem_name .. "]"
formspec[#formspec + 1] = "tooltip[" .. elem_name .. fgettext("Install") .. tooltip_colors
else
if package.installed_release < package.release then
-- The install_ action also handles updating
local elem_name = "install_" .. i .. ";"
formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#28ccdf]"
formspec[#formspec + 1] = third_base .. "cdb_update.png;" .. elem_name .. "]"
formspec[#formspec + 1] = "tooltip[" .. elem_name .. fgettext("Update") .. tooltip_colors
description_width = description_width - 0.7 - 0.15 "style[view_", i, ";border=false]",
end "style[view_", i, ":hovered;bgimg=", core.formspec_escape(defaulttexturedir .. "button_hover_semitrans.png"), "]",
"style[view_", i, ":pressed;bgimg=", core.formspec_escape(defaulttexturedir .. "button_press_semitrans.png"), "]",
"button[0,0;", cell_w, ",", cell_h, ";view_", i, ";]",
})
local elem_name = "uninstall_" .. i .. ";" if package.featured then
formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#a93b3b]" table.insert_all(formspec, {
formspec[#formspec + 1] = second_base .. "cdb_clear.png;" .. elem_name .. "]" "tooltip[0,0;0.8,0.8;", fgettext("Featured"), "]",
formspec[#formspec + 1] = "tooltip[" .. elem_name .. fgettext("Uninstall") .. tooltip_colors "image[0.2,0.2;0.4,0.4;", core.formspec_escape(defaulttexturedir .. "server_favorite.png"), "]",
})
end end
local web_elem_name = "view_" .. i .. ";" table.insert_all(formspec, {
formspec[#formspec + 1] = "image_button[-0.7,0;0.7,0.7;" .. "container[", cell_w - 0.625,",", 0.25, "]",
core.formspec_escape(defaulttexturedir) .. "cdb_viewonline.png;" .. web_elem_name .. "]" })
formspec[#formspec + 1] = "tooltip[" .. web_elem_name ..
fgettext("View more information in a web browser") .. tooltip_colors
formspec[#formspec + 1] = "container_end[]"
-- description if package.downloading then
formspec[#formspec + 1] = "textarea[1.855,0.3;" table.insert_all(formspec, {
formspec[#formspec + 1] = tostring(description_width) "animated_image[0,0;0.5,0.5;downloading;", core.formspec_escape(defaulttexturedir .. "cdb_downloading.png"),
formspec[#formspec + 1] = ",0.8;;;" ";3;400;;]",
formspec[#formspec + 1] = core.formspec_escape(package.short_description) })
formspec[#formspec + 1] = "]" elseif package.queued then
table.insert_all(formspec, {
"image[0,0;0.5,0.5;", core.formspec_escape(defaulttexturedir .. "cdb_queued.png"), "]",
})
elseif package.path then
if package.installed_release < package.release then
table.insert_all(formspec, {
"image[0,0;0.5,0.5;", core.formspec_escape(defaulttexturedir .. "cdb_update.png"), "]",
})
else
table.insert_all(formspec, {
"image[0.1,0.1;0.3,0.3;", core.formspec_escape(defaulttexturedir .. "checkbox_64.png"), "]",
})
end
end
formspec[#formspec + 1] = "container_end[]" table.insert_all(formspec, {
"container_end[]",
"container_end[]",
})
end end
formspec[#formspec + 1] = "container_end[]"
formspec[#formspec + 1] = "container_end[]"
return table.concat(formspec) return table.concat(formspec)
end end
@ -364,14 +403,14 @@ local function handle_submit(this, fields)
if fields.search or fields.key_enter_field == "search_string" then if fields.search or fields.key_enter_field == "search_string" then
search_string = fields.search_string:trim() search_string = fields.search_string:trim()
cur_page = 1 cur_page = 1
contentdb.filter_packages(search_string, filter_types_type[filter_type]) contentdb.filter_packages(search_string, filter_type)
return true return true
end end
if fields.clear then if fields.clear then
search_string = "" search_string = ""
cur_page = 1 cur_page = 1
contentdb.filter_packages("", filter_types_type[filter_type]) contentdb.filter_packages("", filter_type)
return true return true
end end
@ -407,12 +446,11 @@ local function handle_submit(this, fields)
return true return true
end end
if fields.type then for _, pair in ipairs(filter_type_names) do
local new_type = table.indexof(filter_types_titles, fields.type) if fields[pair[1]] then
if new_type ~= filter_type then filter_type = pair[2]
filter_type = new_type
cur_page = 1 cur_page = 1
contentdb.filter_packages(search_string, filter_types_type[filter_type]) contentdb.filter_packages(search_string, filter_type)
return true return true
end end
end end
@ -428,32 +466,20 @@ local function handle_submit(this, fields)
return true return true
end end
local num_per_page = this.data.num_per_page
local start_idx = (cur_page - 1) * num_per_page + 1 local start_idx = (cur_page - 1) * num_per_page + 1
assert(start_idx ~= nil) assert(start_idx ~= nil)
for i=start_idx, math.min(#contentdb.packages, start_idx+num_per_page-1) do for i=start_idx, math.min(#contentdb.packages, start_idx+num_per_page-1) do
local package = contentdb.packages[i] local package = contentdb.packages[i]
assert(package) assert(package)
if fields["install_" .. i] then if fields["view_" .. i] or fields["title_" .. i] or fields["author_" .. i] then
install_or_update_package(this, package) local dlg = create_package_dialog(package)
return true
end
if fields["uninstall_" .. i] then
local dlg = create_delete_content_dlg(package)
dlg:set_parent(this) dlg:set_parent(this)
this:hide() this:hide()
dlg:show() dlg:show()
return true return true
end end
if fields["view_" .. i] then
local url = ("%s/packages/%s?protocol_version=%d"):format(
core.settings:get("contentdb_url"), package.url_part,
core.get_max_supp_proto())
core.open_url(url)
return true
end
end end
return false return false
@ -462,8 +488,8 @@ end
local function handle_events(event) local function handle_events(event)
if event == "DialogShow" then if event == "DialogShow" then
-- On touchscreen, don't show the "MINETEST" header behind the dialog. -- Don't show the header image behind the dialog.
mm_game_theme.set_engine(core.settings:get_bool("touch_gui")) mm_game_theme.set_engine(true)
-- If ContentDB is already loaded, auto-install packages here. -- If ContentDB is already loaded, auto-install packages here.
do_auto_install() do_auto_install()
@ -471,6 +497,11 @@ local function handle_events(event)
return true return true
end end
if event == "WindowInfoChange" then
ui.update()
return true
end
return false return false
end end
@ -485,17 +516,7 @@ end
function create_contentdb_dlg(type, install_spec) function create_contentdb_dlg(type, install_spec)
search_string = "" search_string = ""
cur_page = 1 cur_page = 1
if type then filter_type = type
-- table.indexof does not work on tables that contain `nil`
for i, v in pairs(filter_types_type) do
if v == type then
filter_type = i
break
end
end
else
filter_type = 1
end
-- Keep the old auto_install_spec if the caller doesn't specify one. -- Keep the old auto_install_spec if the caller doesn't specify one.
if install_spec then if install_spec then
@ -504,8 +525,10 @@ function create_contentdb_dlg(type, install_spec)
load() load()
return dialog_create("contentdb", local dlg = dialog_create("contentdb",
get_formspec, get_formspec,
handle_submit, handle_submit,
handle_events) handle_events)
dlg.data.num_per_page = calculate_num_per_page()
return dlg
end end

View file

@ -1,4 +1,4 @@
--Minetest --Luanti
--Copyright (C) 2018-24 rubenwardy --Copyright (C) 2018-24 rubenwardy
-- --
--This program is free software; you can redistribute it and/or modify --This program is free software; you can redistribute it and/or modify
@ -244,3 +244,45 @@ function create_install_dialog(package)
return dlg return dlg
end end
function install_or_update_package(parent, package)
local install_parent
if package.type == "mod" then
install_parent = core.get_modpath()
elseif package.type == "game" then
install_parent = core.get_gamepath()
elseif package.type == "txp" then
install_parent = core.get_texturepath()
else
error("Unknown package type: " .. package.type)
end
if package.queued or package.downloading then
return
end
local function on_confirm()
local dlg = create_install_dialog(package)
dlg:set_parent(parent)
parent:hide()
dlg:show()
dlg:load_deps()
end
if package.type == "mod" and #pkgmgr.games == 0 then
local dlg = messagebox("install_game",
fgettext("You need to install a game before you can install a mod"))
dlg:set_parent(parent)
parent:hide()
dlg:show()
elseif not package.path and core.is_dir(install_parent .. DIR_DELIM .. package.name) then
local dlg = create_confirm_overwrite(package, on_confirm)
dlg:set_parent(parent)
parent:hide()
dlg:show()
else
on_confirm()
end
end

View file

@ -1,4 +1,4 @@
--Minetest --Luanti
--Copyright (C) 2018-24 rubenwardy --Copyright (C) 2018-24 rubenwardy
-- --
--This program is free software; you can redistribute it and/or modify --This program is free software; you can redistribute it and/or modify

View file

@ -0,0 +1,329 @@
--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.
local function get_info_formspec(size, padding, text)
return table.concat({
"formspec_version[6]",
"size[", size.x, ",", size.y, "]",
"padding[0,0]",
"bgcolor[;true]",
"label[4,4.35;", text, "]",
"container[", padding.x, ",", size.y - 0.8 - padding.y, "]",
"button[0,0;2,0.8;back;", fgettext("Back"), "]",
"container_end[]",
})
end
local function get_formspec(data)
local window_padding = contentdb.get_formspec_padding()
local size = contentdb.get_formspec_size()
size.x = math.min(size.x, 20)
local W = size.x - window_padding.x * 2
local H = size.y - window_padding.y * 2
if not data.info then
if not data.loading and not data.loading_error then
data.loading = true
contentdb.get_full_package_info(data.package, function(info)
data.loading = false
if info == nil then
data.loading_error = true
ui.update()
return
end
assert(data.package.name == info.name)
data.info = info
ui.update()
end)
end
-- get_full_package_info can return cached info immediately, so
-- 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"))
end
return get_info_formspec(size, window_padding, fgettext("Loading..."))
end
end
-- Check installation status
contentdb.update_paths()
local info = data.info
local info_line =
fgettext("by $1 — $2 downloads — +$3 / $4 / -$5",
info.author, info.downloads,
info.reviews.positive, info.reviews.neutral, info.reviews.negative)
local bottom_buttons_y = H - 0.8
local formspec = {
"formspec_version[7]",
"size[", size.x, ",", size.y, "]",
"padding[0,0]",
"bgcolor[;true]",
"container[", window_padding.x, ",", window_padding.y, "]",
"button[0,", bottom_buttons_y, ";2,0.8;back;", fgettext("Back"), "]",
"button[", W - 3, ",", bottom_buttons_y, ";3,0.8;open_contentdb;", fgettext("ContentDB page"), "]",
"style_type[label;font_size=+24;font=bold]",
"label[0,0.4;", core.formspec_escape(info.title), "]",
"style_type[label;font_size=;font=]",
"label[0,1.2;", core.formspec_escape(info_line), "]",
}
table.insert_all(formspec, {
"container[", W - 6, ",0]"
})
local left_button_rect = "0,0;2.875,1"
local right_button_rect = "3.125,0;2.875,1"
if data.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
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
formspec[#formspec + 1] = "style[install;bgcolor=green]"
formspec[#formspec + 1] = "button["
formspec[#formspec + 1] = right_button_rect
formspec[#formspec + 1] =";install;"
formspec[#formspec + 1] = fgettext("Install [$1]", info.download_size)
formspec[#formspec + 1] = "]"
else
if data.package.installed_release < data.package.release then
-- The install_ action also handles updating
formspec[#formspec + 1] = "style[install;bgcolor=#28ccdf]"
formspec[#formspec + 1] = "button["
formspec[#formspec + 1] = left_button_rect
formspec[#formspec + 1] = ";install;"
formspec[#formspec + 1] = fgettext("Update")
formspec[#formspec + 1] = "]"
end
formspec[#formspec + 1] = "style[uninstall;bgcolor=#a93b3b]"
formspec[#formspec + 1] = "button["
formspec[#formspec + 1] = right_button_rect
formspec[#formspec + 1] = ";uninstall;"
formspec[#formspec + 1] = fgettext("Uninstall")
formspec[#formspec + 1] = "]"
end
local current_tab = data.current_tab or 1
local tab_titles = {
fgettext("Description"),
fgettext("Information"),
}
local tab_body_height = bottom_buttons_y - 2.8
table.insert_all(formspec, {
"container_end[]",
"box[0,2.55;", W, ",", tab_body_height, ";#ffffff11]",
"tabheader[0,2.55;", W, ",0.8;tabs;",
table.concat(tab_titles, ","), ";", current_tab, ";true;true]",
"container[0,2.8]",
})
if current_tab == 1 then
-- Screenshots and description
local hypertext = "<big><b>" .. core.hypertext_escape(info.short_description) .. "</b></big>\n"
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 .. "<action name=\"ss_" .. i .. "\"><img name=\"" ..
core.hypertext_escape(path) .. "\" width=" .. (3 * fs_to_px) ..
" height=" .. (2 * fs_to_px) .. "></action>"
if i ~= #info.screenshots then
hypertext = hypertext .. "<img name=\"blank.png\" width=" .. (0.25 * fs_to_px) ..
" height=" .. (2.25 * fs_to_px).. ">"
end
end
hypertext = hypertext .. "\n" .. info.long_description.head
local first = true
local function add_link_button(label, name)
if info[name] then
if not first then
hypertext = hypertext .. " | "
end
hypertext = hypertext .. "<action name=link_" .. name .. ">" .. core.hypertext_escape(label) .. "</action>"
info.long_description.links["link_" .. name] = info[name]
first = false
end
end
add_link_button(fgettext("Donate"), "donate_url")
add_link_button(fgettext("Website"), "website")
add_link_button(fgettext("Source"), "repo")
add_link_button(fgettext("Issue Tracker"), "issue_tracker")
add_link_button(fgettext("Translate"), "translation_url")
add_link_button(fgettext("Forum Topic"), "forum_url")
hypertext = hypertext .. "\n\n" .. info.long_description.body
hypertext = hypertext:gsub("<img name=\"?blank.png\"? ",
"<img name=\"" .. core.hypertext_escape(defaulttexturedir) .. "blank.png\" ")
table.insert_all(formspec, {
"hypertext[0,0;", W, ",", tab_body_height - 0.375,
";desc;", core.formspec_escape(hypertext), "]",
})
elseif current_tab == 2 then
local hypertext = info.info_hypertext.head .. info.info_hypertext.body
table.insert_all(formspec, {
"hypertext[0,0;", W, ",", tab_body_height - 0.375,
";info;", core.formspec_escape(hypertext), "]",
})
else
error("Unknown tab " .. current_tab)
end
formspec[#formspec + 1] = "container_end[]"
formspec[#formspec + 1] = "container_end[]"
return table.concat(formspec)
end
local function handle_hypertext_event(this, event, hypertext_object)
if not (event and event:sub(1, 7) == "action:") then
return
end
for i, ss in ipairs(this.data.info.screenshots) do
if event == "action:ss_" .. i then
core.open_url(ss.url)
return true
end
end
local base_url = core.settings:get("contentdb_url"):gsub("(%W)", "%%%1")
for key, url in pairs(hypertext_object.links) do
if event == "action:" .. key then
local author, name = url:match("^" .. base_url .. "/?packages/([A-Za-z0-9 _-]+)/([a-z0-9_]+)/?$")
if author and name then
local package2 = contentdb.get_package_by_info(author, name)
if package2 then
local dlg = create_package_dialog(package2)
dlg:set_parent(this)
this:hide()
dlg:show()
return true
end
end
core.open_url_dialog(url)
return true
end
end
end
local function handle_submit(this, fields)
local info = this.data.info
local package = this.data.package
if fields.back then
this:delete()
return true
end
if not info then
return false
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())
core.open_url(url)
return true
end
if fields.install then
install_or_update_package(this, package)
return true
end
if fields.uninstall then
local dlg = create_delete_content_dlg(package)
dlg:set_parent(this)
this:hide()
dlg:show()
return true
end
if fields.tabs then
this.data.current_tab = tonumber(fields.tabs)
return true
end
if handle_hypertext_event(this, fields.desc, info.long_description) or
handle_hypertext_event(this, fields.info, info.info_hypertext) then
return true
end
end
local function handle_events(event)
if event == "WindowInfoChange" then
ui.update()
return true
end
return false
end
function create_package_dialog(package)
assert(package)
local dlg = dialog_create("package_dialog_" .. package.id,
get_formspec,
handle_submit,
handle_events)
local data = dlg.data
data.package = package
data.info = nil
data.loading = false
data.loading_error = nil
data.current_tab = 1
return dlg
end

View file

@ -1,4 +1,4 @@
--Minetest --Luanti
--Copyright (C) 2023 rubenwardy --Copyright (C) 2023 rubenwardy
-- --
--This program is free software; you can redistribute it and/or modify --This program is free software; you can redistribute it and/or modify
@ -23,4 +23,5 @@ dofile(path .. DIR_DELIM .. "update_detector.lua")
dofile(path .. DIR_DELIM .. "screenshots.lua") dofile(path .. DIR_DELIM .. "screenshots.lua")
dofile(path .. DIR_DELIM .. "dlg_install.lua") dofile(path .. DIR_DELIM .. "dlg_install.lua")
dofile(path .. DIR_DELIM .. "dlg_overwrite.lua") dofile(path .. DIR_DELIM .. "dlg_overwrite.lua")
dofile(path .. DIR_DELIM .. "dlg_package.lua")
dofile(path .. DIR_DELIM .. "dlg_contentdb.lua") dofile(path .. DIR_DELIM .. "dlg_contentdb.lua")

View file

@ -1,4 +1,4 @@
--Minetest --Luanti
--Copyright (C) 2013 sapier --Copyright (C) 2013 sapier
-- --
--This program is free software; you can redistribute it and/or modify --This program is free software; you can redistribute it and/or modify
@ -840,7 +840,7 @@ function pkgmgr.get_contentdb_id(content)
return content.author:lower() .. "/" .. content.name return content.author:lower() .. "/" .. content.name
end end
-- Until Minetest 5.8.0, Minetest Game was bundled with Minetest. -- Until version 5.8.0, Minetest Game was bundled with the engine.
-- Unfortunately, the bundled MTG was not versioned (missing "release" -- Unfortunately, the bundled MTG was not versioned (missing "release"
-- field in game.conf). -- field in game.conf).
-- Therefore, we consider any installation of MTG that is not versioned, -- Therefore, we consider any installation of MTG that is not versioned,

View file

@ -1,4 +1,4 @@
--Minetest --Luanti
--Copyright (C) 2023-24 rubenwardy --Copyright (C) 2023-24 rubenwardy
-- --
--This program is free software; you can redistribute it and/or modify --This program is free software; you can redistribute it and/or modify
@ -23,23 +23,40 @@ local screenshot_downloading = {}
local screenshot_downloaded = {} local screenshot_downloaded = {}
local function get_filename(path)
local parts = path:split("/")
return parts[#parts]
end
local function get_file_extension(path) local function get_file_extension(path)
local parts = path:split(".") local parts = path:split(".")
return parts[#parts] return parts[#parts]
end end
function get_screenshot(package) function get_screenshot(package, screenshot_url, level)
if not package.thumbnail then if not screenshot_url then
return defaulttexturedir .. "no_screenshot.png" return defaulttexturedir .. "no_screenshot.png"
elseif screenshot_downloading[package.thumbnail] then end
-- Luanti only supports png and jpg
local ext = get_file_extension(screenshot_url)
if ext ~= "png" and ext ~= "jpg" then
screenshot_url = screenshot_url:sub(0, -#ext - 1) .. "png"
end
-- Set thumbnail level
screenshot_url = screenshot_url:gsub("/thumbnails/[0-9]+/", "/thumbnails/" .. level .. "/")
screenshot_url = screenshot_url:gsub("/uploads/", "/thumbnails/" .. level .. "/")
if screenshot_downloading[screenshot_url] then
return defaulttexturedir .. "loading_screenshot.png" return defaulttexturedir .. "loading_screenshot.png"
end end
-- Get tmp screenshot path
local ext = get_file_extension(package.thumbnail)
local filepath = screenshot_dir .. DIR_DELIM .. local filepath = screenshot_dir .. DIR_DELIM ..
("%s-%s-%s.%s"):format(package.type, package.author, package.name, ext) ("%s-%s-%s-l%d-%s"):format(package.type, package.author, package.name,
level, get_filename(screenshot_url))
-- Return if already downloaded -- Return if already downloaded
local file = io.open(filepath, "r") local file = io.open(filepath, "r")
@ -49,7 +66,7 @@ function get_screenshot(package)
end end
-- Show error if we've failed to download before -- Show error if we've failed to download before
if screenshot_downloaded[package.thumbnail] then if screenshot_downloaded[screenshot_url] then
return defaulttexturedir .. "error_screenshot.png" return defaulttexturedir .. "error_screenshot.png"
end end
@ -59,16 +76,16 @@ function get_screenshot(package)
return core.download_file(params.url, params.dest) return core.download_file(params.url, params.dest)
end end
local function callback(success) local function callback(success)
screenshot_downloading[package.thumbnail] = nil screenshot_downloading[screenshot_url] = nil
screenshot_downloaded[package.thumbnail] = true screenshot_downloaded[screenshot_url] = true
if not success then if not success then
core.log("warning", "Screenshot download failed for some reason") core.log("warning", "Screenshot download failed for some reason")
end end
ui.update() ui.update()
end end
if core.handle_async(download_screenshot, if core.handle_async(download_screenshot,
{ dest = filepath, url = package.thumbnail }, callback) then { dest = filepath, url = screenshot_url }, callback) then
screenshot_downloading[package.thumbnail] = true screenshot_downloading[screenshot_url] = true
else else
core.log("error", "ERROR: async event failed") core.log("error", "ERROR: async event failed")
return defaulttexturedir .. "error_screenshot.png" return defaulttexturedir .. "error_screenshot.png"

View file

@ -1,4 +1,4 @@
--Minetest --Luanti
--Copyright (C) 2022 rubenwardy --Copyright (C) 2022 rubenwardy
-- --
--This program is free software; you can redistribute it and/or modify --This program is free software; you can redistribute it and/or modify

View file

@ -1,4 +1,4 @@
--Minetest --Luanti
--Copyright (C) 2023 rubenwardy --Copyright (C) 2023 rubenwardy
-- --
--This program is free software; you can redistribute it and/or modify --This program is free software; you can redistribute it and/or modify

View file

@ -13,7 +13,9 @@
"Desour/DS", "Desour/DS",
"srifqi", "srifqi",
"Gregor Parzefall (grorp)", "Gregor Parzefall (grorp)",
"Lars Müller (luatic)" "Lars Müller (luatic)",
"cx384",
"sfence"
], ],
"previous_core_developers": [ "previous_core_developers": [
"BlockMen", "BlockMen",
@ -44,30 +46,25 @@
], ],
"#": "For updating active/previous contributors, see the script in ./util/gather_git_credits.py", "#": "For updating active/previous contributors, see the script in ./util/gather_git_credits.py",
"contributors": [ "contributors": [
"cx384",
"numzero",
"AFCMS",
"sfence",
"Wuzzy",
"ROllerozxa",
"JosiahWI", "JosiahWI",
"OgelGames",
"David Heidelberg",
"1F616EMO", "1F616EMO",
"HybridDog",
"Bradley Pierce (Thresher)",
"savilli",
"Stvk imension",
"y5nw", "y5nw",
"Erich Schubert",
"numzero",
"red-001 <red-001@outlook.ie>",
"David Heidelberg",
"Wuzzy",
"paradust7",
"HybridDog",
"Zemtzov7",
"kromka-chleba",
"AFCMS",
"chmodsayshello", "chmodsayshello",
"jordan4ibanez", "OgelGames"
"superfloh247"
], ],
"previous_contributors": [ "previous_contributors": [
"Nils Dagsson Moskopp (erlehmann) <nils@dieweltistgarnichtso.net> [Minetest logo]", "Ælla Chiana Moskopp (erle) <erle@dieweltistgarnichtso.net> [Logo]",
"red-001 <red-001@outlook.ie>",
"Giuseppe Bilotta", "Giuseppe Bilotta",
"HybridDog",
"ClobberXD", "ClobberXD",
"Dániel Juhász (juhdanad) <juhdanad@gmail.com>", "Dániel Juhász (juhdanad) <juhdanad@gmail.com>",
"MirceaKitsune <mirceakitsune@gmail.com>", "MirceaKitsune <mirceakitsune@gmail.com>",
@ -75,6 +72,7 @@
"MoNTE48", "MoNTE48",
"Constantin Wenger (SpeedProg)", "Constantin Wenger (SpeedProg)",
"Ciaran Gultnieks (CiaranG)", "Ciaran Gultnieks (CiaranG)",
"ROllerozxa",
"Paul Ouellette (pauloue)", "Paul Ouellette (pauloue)",
"stujones11", "stujones11",
"Rogier <rogier777@gmail.com>", "Rogier <rogier777@gmail.com>",

View file

@ -0,0 +1,49 @@
-- Luanti
-- Copyright (C) 2024 siliconsniffer
-- SPDX-License-Identifier: LGPL-2.1-or-later
local function clients_list_formspec(dialogdata)
local TOUCH_GUI = core.settings:get_bool("touch_gui")
local clients_list = dialogdata.server.clients_list
local servername = dialogdata.server.name
local function fmt_formspec_list(clients_list)
local escaped = {}
for i, str in ipairs(clients_list) do
escaped[i] = core.formspec_escape(str)
end
return table.concat(escaped, ",")
end
local formspec = {
"formspec_version[8]",
"size[6,9.5]",
TOUCH_GUI and "padding[0.01,0.01]" or "",
"hypertext[0,0;6,1.5;;<global margin=5 halign=center valign=middle>",
fgettext("This is the list of clients connected to\n$1",
"<b>" .. core.hypertext_escape(servername) .. "</b>") .. "]",
"textlist[0.5,1.5;5,6.8;;" .. fmt_formspec_list(clients_list) .. "]",
"button[1.5,8.5;3,0.8;quit;OK]"
}
return table.concat(formspec, "")
end
local function clients_list_buttonhandler(this, fields)
if fields.quit then
this:delete()
return true
end
return false
end
function create_clientslist_dialog(server)
local retval = dialog_create("dlg_clients_list",
clients_list_formspec,
clients_list_buttonhandler,
nil)
retval.data.server = server
return retval
end

View file

@ -1,4 +1,4 @@
--Minetest --Luanti
--Copyright (C) 2013 sapier --Copyright (C) 2013 sapier
-- --
--This program is free software; you can redistribute it and/or modify --This program is free software; you can redistribute it and/or modify

View file

@ -1,4 +1,4 @@
--Minetest --Luanti
--Copyright (C) 2014 sapier --Copyright (C) 2014 sapier
-- --
--This program is free software; you can redistribute it and/or modify --This program is free software; you can redistribute it and/or modify

View file

@ -1,4 +1,4 @@
--Minetest --Luanti
--Copyright (C) 2014 sapier --Copyright (C) 2014 sapier
-- --
--This program is free software; you can redistribute it and/or modify --This program is free software; you can redistribute it and/or modify

View file

@ -1,4 +1,4 @@
--Minetest --Luanti
--Copyright (C) 2014 sapier --Copyright (C) 2014 sapier
-- --
--This program is free software; you can redistribute it and/or modify --This program is free software; you can redistribute it and/or modify

View file

@ -1,4 +1,4 @@
--Minetest --Luanti
--Copyright (C) 2022 rubenwardy --Copyright (C) 2022 rubenwardy
-- --
--This program is free software; you can redistribute it and/or modify --This program is free software; you can redistribute it and/or modify

View file

@ -1,4 +1,4 @@
--Minetest --Luanti
--Copyright (C) 2023 Gregor Parzefall --Copyright (C) 2023 Gregor Parzefall
-- --
--This program is free software; you can redistribute it and/or modify --This program is free software; you can redistribute it and/or modify
@ -68,15 +68,15 @@ end
local function get_formspec(dialogdata) local function get_formspec(dialogdata)
local markup = table.concat({ local markup = table.concat({
"<big>", fgettext("Minetest Game is no longer installed by default"), "</big>\n", "<big>", fgettext("Minetest Game is no longer installed by default"), "</big>\n",
fgettext("For a long time, the Minetest engine shipped with a default game called \"Minetest Game\". " .. fgettext("For a long time, Luanti shipped with a default game called \"Minetest Game\". " ..
"Since Minetest 5.8.0, Minetest ships without a default game."), "\n", "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."), fgettext("If you want to continue playing in your Minetest Game worlds, you need to reinstall Minetest Game."),
}) })
return table.concat({ return table.concat({
"formspec_version[6]", "formspec_version[6]",
"size[12.8,7]", "size[12.8,7]",
"hypertext[0.375,0.375;12.05,5.2;text;", minetest.formspec_escape(markup), "]", "hypertext[0.375,0.375;12.05,5.2;text;", core.formspec_escape(markup), "]",
"container[0.375,5.825]", "container[0.375,5.825]",
"style[dismiss;bgcolor=red]", "style[dismiss;bgcolor=red]",
"button[0,0;4,0.8;dismiss;", fgettext("Dismiss"), "]", "button[0,0;4,0.8;dismiss;", fgettext("Dismiss"), "]",
@ -114,7 +114,7 @@ local function eventhandler(event)
return true return true
elseif event == "MenuQuit" then elseif event == "MenuQuit" then
-- Don't allow closing the dialog with ESC, but still allow exiting -- Don't allow closing the dialog with ESC, but still allow exiting
-- Minetest. -- Luanti
core.close() core.close()
return true return true
end end

View file

@ -1,4 +1,4 @@
--Minetest --Luanti
--Copyright (C) 2014 sapier --Copyright (C) 2014 sapier
-- --
--This program is free software; you can redistribute it and/or modify --This program is free software; you can redistribute it and/or modify

View file

@ -1,5 +1,5 @@
--[[ --[[
Minetest Luanti
Copyright (C) 2018-2020 SmallJoker, 2022 rubenwardy Copyright (C) 2018-2020 SmallJoker, 2022 rubenwardy
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify

View file

@ -1,4 +1,4 @@
--Minetest --Luanti
--Copyright (C) 2013 sapier --Copyright (C) 2013 sapier
-- --
--This program is free software; you can redistribute it and/or modify --This program is free software; you can redistribute it and/or modify

View file

@ -1,4 +1,4 @@
--Minetest --Luanti
--Copyright (C) 2014 sapier --Copyright (C) 2014 sapier
-- --
--This program is free software; you can redistribute it and/or modify --This program is free software; you can redistribute it and/or modify
@ -55,6 +55,7 @@ dofile(menupath .. DIR_DELIM .. "dlg_register.lua")
dofile(menupath .. DIR_DELIM .. "dlg_rename_modpack.lua") dofile(menupath .. DIR_DELIM .. "dlg_rename_modpack.lua")
dofile(menupath .. DIR_DELIM .. "dlg_version_info.lua") dofile(menupath .. DIR_DELIM .. "dlg_version_info.lua")
dofile(menupath .. DIR_DELIM .. "dlg_reinstall_mtg.lua") dofile(menupath .. DIR_DELIM .. "dlg_reinstall_mtg.lua")
dofile(menupath .. DIR_DELIM .. "dlg_clients_list.lua")
local tabs = { local tabs = {
content = dofile(menupath .. DIR_DELIM .. "tab_content.lua"), content = dofile(menupath .. DIR_DELIM .. "tab_content.lua"),
@ -133,4 +134,5 @@ local function init_globals()
check_new_version() check_new_version()
end end
assert(os.execute == nil)
init_globals() init_globals()

View file

@ -1,4 +1,4 @@
--Minetest --Luanti
--Copyright (C) 2020 rubenwardy --Copyright (C) 2020 rubenwardy
-- --
--This program is free software; you can redistribute it and/or modify --This program is free software; you can redistribute it and/or modify

View file

@ -1,4 +1,4 @@
--Minetest --Luanti
--Copyright (C) 2022 rubenwardy --Copyright (C) 2022 rubenwardy
-- --
--This program is free software; you can redistribute it and/or modify --This program is free software; you can redistribute it and/or modify
@ -67,6 +67,19 @@ function make.heading(text)
end end
function make.note(text)
return {
full_width = true,
get_formspec = function(self, avail_w)
-- Assuming label height 0.4:
-- Position at y=0 to eat 0.2 of the padding above, leave 0.05.
-- The returned used_height doesn't include padding.
return ("label[0,0;%s]"):format(core.colorize("#bbb", core.formspec_escape(text))), 0.2
end,
}
end
--- Used for string and numeric style fields --- Used for string and numeric style fields
--- ---
--- @param converter Function to coerce values from strings. --- @param converter Function to coerce values from strings.

View file

@ -1,4 +1,4 @@
--Minetest --Luanti
--Copyright (C) 2015 PilzAdam --Copyright (C) 2015 PilzAdam
-- --
--This program is free software; you can redistribute it and/or modify --This program is free software; you can redistribute it and/or modify

View file

@ -1,4 +1,4 @@
--Minetest --Luanti
--Copyright (C) 2022 rubenwardy --Copyright (C) 2022 rubenwardy
-- --
--This program is free software; you can redistribute it and/or modify --This program is free software; you can redistribute it and/or modify
@ -123,6 +123,22 @@ local function load()
end, end,
} }
local touchscreen_layout = {
query_text = "Touchscreen layout",
requires = {
touchscreen = true,
},
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
end,
on_submit = function(self, fields)
if fields.btn_touch_layout then
core.show_touchscreen_layout()
end
end,
}
add_page({ add_page({
id = "accessibility", id = "accessibility",
title = fgettext_ne("Accessibility"), title = fgettext_ne("Accessibility"),
@ -151,10 +167,27 @@ local function load()
load_settingtypes() load_settingtypes()
table.insert(page_by_id.controls_keyboard_and_mouse.content, 1, change_keys) 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 do
local content = page_by_id.graphics_and_audio_shaders.content local content = page_by_id.graphics_and_audio_effects.content
local idx = table.indexof(content, "enable_dynamic_shadows") local idx = table.indexof(content, "enable_dynamic_shadows")
table.insert(content, idx, shadows_component) table.insert(content, idx, shadows_component)
idx = table.indexof(content, "enable_auto_exposure") + 1
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
table.insert(content, idx, note)
idx = table.indexof(content, "enable_bloom") + 1
note = component_funcs.note(fgettext_ne("(The game will need to enable bloom as well)"))
note.requires = get_setting_info("enable_bloom").requires
table.insert(content, idx, note)
idx = table.indexof(content, "enable_volumetric_lighting") + 1
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
table.insert(content, idx, note)
end end
-- These must not be translated, as they need to show in the local -- These must not be translated, as they need to show in the local
@ -222,6 +255,12 @@ local function load()
zh_CN = "中文 (简体) [zh_CN]", zh_CN = "中文 (简体) [zh_CN]",
zh_TW = "正體中文 (繁體) [zh_TW]", zh_TW = "正體中文 (繁體) [zh_TW]",
} }
get_setting_info("touch_controls").option_labels = {
["auto"] = fgettext_ne("Auto"),
["true"] = fgettext_ne("Enabled"),
["false"] = fgettext_ne("Disabled"),
}
end end
@ -320,13 +359,17 @@ local function check_requirements(name, requires)
end end
local video_driver = core.get_active_driver() local video_driver = core.get_active_driver()
local shaders_support = video_driver == "opengl" or video_driver == "opengl3" or video_driver == "ogles2" local touch_support = core.irrlicht_device_supports_touch()
local touch_controls = core.settings:get("touch_controls")
local special = { local special = {
android = PLATFORM == "Android", android = PLATFORM == "Android",
desktop = PLATFORM ~= "Android", desktop = PLATFORM ~= "Android",
shaders_support = shaders_support, touch_support = touch_support,
shaders = core.settings:get_bool("enable_shaders") and shaders_support, -- When touch_controls is "auto", we don't know which input method will
opengl = video_driver == "opengl", -- be used, so we show settings for both.
touchscreen = touch_support and (touch_controls == "auto" or core.is_yes(touch_controls)),
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", gles = video_driver:sub(1, 5) == "ogles",
} }
@ -334,7 +377,7 @@ local function check_requirements(name, requires)
if special[req_key] == nil then if special[req_key] == nil then
local required_setting = get_setting_info(req_key) local required_setting = get_setting_info(req_key)
if required_setting == nil then if required_setting == nil then
core.log("warning", "Unknown setting " .. req_key .. " required by " .. name) core.log("warning", "Unknown setting " .. req_key .. " required by " .. (name or "???"))
end end
local actual_value = core.settings:get_bool(req_key, local actual_value = core.settings:get_bool(req_key,
required_setting and core.is_yes(required_setting.default)) required_setting and core.is_yes(required_setting.default))
@ -433,19 +476,6 @@ local function build_page_components(page)
end end
--- Creates a scrollbaroptions for a scroll_container
--
-- @param visible_l the length of the scroll_container and scrollbar
-- @param total_l length of the scrollable area
-- @param scroll_factor as passed to scroll_container
local function make_scrollbaroptions_for_scroll_container(visible_l, total_l, scroll_factor)
assert(total_l >= visible_l)
local max = total_l - visible_l
local thumb_size = (visible_l / total_l) * max
return ("scrollbaroptions[min=0;max=%f;thumbsize=%f]"):format(max / scroll_factor, thumb_size / scroll_factor)
end
local formspec_show_hack = false local formspec_show_hack = false
@ -507,8 +537,8 @@ local function get_formspec(dialogdata)
"tooltip[search;", fgettext("Search"), "]", "tooltip[search;", fgettext("Search"), "]",
"tooltip[search_clear;", fgettext("Clear"), "]", "tooltip[search_clear;", fgettext("Clear"), "]",
"container_end[]", "container_end[]",
"scroll_container[0.25,1.25;", tostring(left_pane_width), ",", ("scroll_container[0.25,1.25;%f,%f;leftscroll;vertical;0.1;0]"):format(
tostring(tabsize.height - 1.5), ";leftscroll;vertical;0.1]", left_pane_width, tabsize.height - 1.5),
"style_type[button;border=false;bgcolor=#3333]", "style_type[button;border=false;bgcolor=#3333]",
"style_type[button:hover;border=false;bgcolor=#6663]", "style_type[button:hover;border=false;bgcolor=#6663]",
} }
@ -538,7 +568,6 @@ local function get_formspec(dialogdata)
fs[#fs + 1] = "scroll_container_end[]" fs[#fs + 1] = "scroll_container_end[]"
if y >= tabsize.height - 1.25 then if y >= tabsize.height - 1.25 then
fs[#fs + 1] = make_scrollbaroptions_for_scroll_container(tabsize.height - 1.5, y, 0.1)
fs[#fs + 1] = ("scrollbar[%f,1.25;%f,%f;vertical;leftscroll;%f]"):format( fs[#fs + 1] = ("scrollbar[%f,1.25;%f,%f;vertical;leftscroll;%f]"):format(
left_pane_width + 0.25, scrollbar_w, tabsize.height - 1.5, dialogdata.leftscroll or 0) left_pane_width + 0.25, scrollbar_w, tabsize.height - 1.5, dialogdata.leftscroll or 0)
end end
@ -550,7 +579,7 @@ local function get_formspec(dialogdata)
end end
local right_pane_width = tabsize.width - left_pane_width - 0.375 - 2*scrollbar_w - 0.25 local right_pane_width = tabsize.width - left_pane_width - 0.375 - 2*scrollbar_w - 0.25
fs[#fs + 1] = ("scroll_container[%f,0;%f,%f;rightscroll;vertical;0.1]"):format( fs[#fs + 1] = ("scroll_container[%f,0;%f,%f;rightscroll;vertical;0.1;0.25]"):format(
tabsize.width - right_pane_width - scrollbar_w, right_pane_width, tabsize.height) tabsize.width - right_pane_width - scrollbar_w, right_pane_width, tabsize.height)
y = 0.25 y = 0.25
@ -606,7 +635,6 @@ local function get_formspec(dialogdata)
fs[#fs + 1] = "scroll_container_end[]" fs[#fs + 1] = "scroll_container_end[]"
if y >= tabsize.height then if y >= tabsize.height then
fs[#fs + 1] = make_scrollbaroptions_for_scroll_container(tabsize.height, y + 0.375, 0.1)
fs[#fs + 1] = ("scrollbar[%f,0;%f,%f;vertical;rightscroll;%f]"):format( fs[#fs + 1] = ("scrollbar[%f,0;%f,%f;vertical;rightscroll;%f]"):format(
tabsize.width - scrollbar_w, scrollbar_w, tabsize.height, dialogdata.rightscroll or 0) tabsize.width - scrollbar_w, scrollbar_w, tabsize.height, dialogdata.rightscroll or 0)
end end
@ -624,6 +652,18 @@ function write_settings_early()
end end
end end
local function regenerate_page_list(dialogdata)
local suggested_page_id = update_filtered_pages(dialogdata.query)
dialogdata.components = nil
if not filtered_page_by_id[dialogdata.page_id] then
dialogdata.leftscroll = 0
dialogdata.rightscroll = 0
dialogdata.page_id = suggested_page_id
end
end
local function buttonhandler(this, fields) local function buttonhandler(this, fields)
local dialogdata = this.data local dialogdata = this.data
@ -648,27 +688,7 @@ local function buttonhandler(this, fields)
local value = core.is_yes(fields.show_advanced) local value = core.is_yes(fields.show_advanced)
core.settings:set_bool("show_advanced", value) core.settings:set_bool("show_advanced", value)
write_settings_early() write_settings_early()
end regenerate_page_list(dialogdata)
-- touch_controls is a checkbox in a setting component. We handle this
-- setting differently so we can hide/show pages using the next if-statement
if fields.touch_controls ~= nil then
local value = core.is_yes(fields.touch_controls)
core.settings:set_bool("touch_controls", value)
write_settings_early()
end
if fields.show_advanced ~= nil or fields.touch_controls ~= nil then
local suggested_page_id = update_filtered_pages(dialogdata.query)
dialogdata.components = nil
if not filtered_page_by_id[dialogdata.page_id] then
dialogdata.leftscroll = 0
dialogdata.rightscroll = 0
dialogdata.page_id = suggested_page_id
end
return true return true
end end
@ -701,20 +721,26 @@ local function buttonhandler(this, fields)
end end
end end
for i, comp in ipairs(dialogdata.components) do local function after_setting_change(comp)
if comp.on_submit and comp:on_submit(fields, this) then write_settings_early()
write_settings_early() if comp.setting and comp.setting.name == "touch_controls" then
-- Changing the "touch_controls" setting may result in a different
-- page list.
regenerate_page_list(dialogdata)
else
-- Clear components so they regenerate -- Clear components so they regenerate
dialogdata.components = nil dialogdata.components = nil
end
end
for i, comp in ipairs(dialogdata.components) do
if comp.on_submit and comp:on_submit(fields, this) then
after_setting_change(comp)
return true return true
end end
if comp.setting and fields["reset_" .. i] then if comp.setting and fields["reset_" .. i] then
core.settings:remove(comp.setting.name) core.settings:remove(comp.setting.name)
write_settings_early() after_setting_change(comp)
-- Clear components so they regenerate
dialogdata.components = nil
return true return true
end end
end end
@ -725,7 +751,7 @@ end
local function eventhandler(event) local function eventhandler(event)
if event == "DialogShow" then if event == "DialogShow" then
-- Don't show the "MINETEST" header behind the dialog. -- Don't show the header image behind the dialog.
mm_game_theme.set_engine(true) mm_game_theme.set_engine(true)
return true return true
end end

View file

@ -13,10 +13,10 @@ local minetest_example_header = [[
# ../minetest.conf # ../minetest.conf
# ../../minetest.conf # ../../minetest.conf
# Any other path can be chosen by passing the path as a parameter # Any other path can be chosen by passing the path as a parameter
# to the program, eg. "minetest.exe --config ../minetest.conf.example". # to the program, eg. "luanti.exe --config ../minetest.conf.example".
# Further documentation: # Further documentation:
# https://wiki.minetest.net/ # https://wiki.luanti.org/
]] ]]

View file

@ -1,4 +1,4 @@
--Minetest --Luanti
--Copyright (C) 2022 rubenwardy --Copyright (C) 2022 rubenwardy
-- --
--This program is free software; you can redistribute it and/or modify --This program is free software; you can redistribute it and/or modify

View file

@ -1,4 +1,4 @@
--Minetest --Luanti
--Copyright (C) 2015 PilzAdam --Copyright (C) 2015 PilzAdam
-- --
--This program is free software; you can redistribute it and/or modify --This program is free software; you can redistribute it and/or modify

View file

@ -1,4 +1,4 @@
--Minetest --Luanti
--Copyright (C) 2021-2 x2048 --Copyright (C) 2021-2 x2048
--Copyright (C) 2022-3 rubenwardy --Copyright (C) 2022-3 rubenwardy
-- --
@ -82,7 +82,6 @@ end
return { return {
query_text = "Shadows", query_text = "Shadows",
requires = { requires = {
shaders = true,
opengl = true, opengl = true,
}, },
get_formspec = function(self, avail_w) get_formspec = function(self, avail_w)

Some files were not shown because too many files have changed in this diff Show more